Skip to content

Commit

Permalink
fix(MoneyInput): use locale from context if no locale provided
Browse files Browse the repository at this point in the history
  • Loading branch information
meriouma committed Nov 29, 2024
1 parent cf751a6 commit 67b664f
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 46 deletions.
1 change: 1 addition & 0 deletions packages/react/src/components/money-input/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { MoneyInput, type MoneyInputProps } from './money-input';
45 changes: 21 additions & 24 deletions packages/react/src/components/money-input/money-input.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { fireEvent, render } from '@testing-library/react';
import { renderPortalWithProviders, renderWithTheme } from '../../test-utils/renderer';
import { themeProvider } from '../../test-utils/theme-provider';
import { fireEvent } from '@testing-library/react';
import { renderWithProviders } from '../../test-utils/testing-library';
import { MoneyInput } from './money-input';

function simulateValueChange(input: HTMLInputElement, value: String): void {
Expand All @@ -15,9 +14,8 @@ function getInputElement(container: Element): HTMLInputElement {

describe('CurrencyInput Component', () => {
it('should remove formatting on focus', () => {
const { container } = render(
const { container } = renderWithProviders(
<MoneyInput value={12345.25} />,
{ wrapper: themeProvider() },
);
const input = getInputElement(container);

Expand All @@ -27,7 +25,7 @@ describe('CurrencyInput Component', () => {
});

it('should format on blur', () => {
const { container } = render(<MoneyInput value={12345.25} />, { wrapper: themeProvider() });
const { container } = renderWithProviders(<MoneyInput value={12345.25} />);
const input = getInputElement(container);

fireEvent.focus(input);
Expand All @@ -37,45 +35,45 @@ describe('CurrencyInput Component', () => {
});

it('should remove fractions when precision is 0 and changing value', () => {
const { container } = render(<MoneyInput precision={0} value={12345.25} />, { wrapper: themeProvider() });
const { container } = renderWithProviders(<MoneyInput precision={0} value={12345.25} />);
const input = getInputElement(container);

simulateValueChange(input, '12345.25');
expect(input.value).toMatchFormattedMoney('12 345 $');
});

it('should use precision when changing value', () => {
const { container } = render(<MoneyInput value={null} precision={2} />, { wrapper: themeProvider() });
const { container } = renderWithProviders(<MoneyInput value={null} precision={2} />);
const input = getInputElement(container);

simulateValueChange(input, '123.457');
expect(input.value).toMatchFormattedMoney('123,46 $');
});

it('should format according to locale when changing value', () => {
const { container } = render(<MoneyInput locale="en-CA" />, { wrapper: themeProvider() });
const { container } = renderWithProviders(<MoneyInput locale="en-CA" />);
const input = getInputElement(container);

simulateValueChange(input, '12345');
expect(input.value).toMatchFormattedMoney('$12,345.00');
});

it('should format provided value', () => {
const { container } = render(<MoneyInput value={12345.25} />, { wrapper: themeProvider() });
const { container } = renderWithProviders(<MoneyInput value={12345.25} />);
const input = getInputElement(container);

expect(input.value).toMatchFormattedMoney('12 345,25 $');
});

it('should format to provided currency', () => {
const { container } = render(<MoneyInput value={12345.25} currency="USD" />, { wrapper: themeProvider() });
const { container } = renderWithProviders(<MoneyInput value={12345.25} currency="USD" />);
const input = getInputElement(container);

expect(input.value).toMatchFormattedMoney('12 345,25 $');
});

it('should select all text on focus', () => {
const { container } = render(<MoneyInput value={12345} />, { wrapper: themeProvider() });
const { container } = renderWithProviders(<MoneyInput value={12345} />);
const input = getInputElement(container);

fireEvent.focus(input);
Expand All @@ -86,9 +84,8 @@ describe('CurrencyInput Component', () => {

it('should respect precision and locale when calling change handler', () => {
const handleChange = jest.fn();
const { container } = render(
const { container } = renderWithProviders(
<MoneyInput locale="en-CA" precision={0} value={0} onChange={handleChange} />,
{ wrapper: themeProvider() },
);
const input = getInputElement(container);

Expand All @@ -97,7 +94,7 @@ describe('CurrencyInput Component', () => {
});

it('should allow 0 as a value', () => {
const { container } = render(<MoneyInput precision={0} value={0} />, { wrapper: themeProvider() });
const { container } = renderWithProviders(<MoneyInput precision={0} value={0} />);
const input = getInputElement(container);

fireEvent.focus(input);
Expand All @@ -107,17 +104,17 @@ describe('CurrencyInput Component', () => {
});

it('should allow to be empty', () => {
const { container } = render(<MoneyInput precision={0} value={0} />, { wrapper: themeProvider() });
const { container } = renderWithProviders(<MoneyInput precision={0} value={0} />);
const input = getInputElement(container);

simulateValueChange(input, '');
expect(input.value).toMatchFormattedMoney('');
});

test('should not show validation message when input is empty and required onBlur', () => {
const { getByTestId: byTestId, queryByTestId } = renderPortalWithProviders(
const { getByTestId: byTestId, queryByTestId } = renderWithProviders(
<form>
<MoneyInput label='test' required validationErrorMessage='This field is required' />
<MoneyInput label="test" required validationErrorMessage="This field is required" />
<button data-testid="submit-button" type="submit">Submit</button>
</form>,
);
Expand All @@ -130,20 +127,20 @@ describe('CurrencyInput Component', () => {
});

it('matches snapshot (fr-CA)', () => {
const tree = renderWithTheme(<MoneyInput value={100} />);
const { container } = renderWithProviders(<MoneyInput value={100} />);

expect(tree).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
});

it('matches snapshot (en-CA)', () => {
const tree = renderWithTheme(<MoneyInput value={100} locale="en-CA" />);
const { container } = renderWithProviders(<MoneyInput value={100} locale="en-CA" />);

expect(tree).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
});

it('matches snapshot (en-US)', () => {
const tree = renderWithTheme(<MoneyInput value={100} locale="en-CA" />);
const { container } = renderWithProviders(<MoneyInput value={100} locale="en-CA" />);

expect(tree).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
});
});
13 changes: 8 additions & 5 deletions packages/react/src/components/money-input/money-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useDataAttributes } from '../../hooks/use-data-attributes';
import { useTranslation } from '../../i18n/use-translation';
import { formatCurrency } from '../../utils/currency';
import { TextInput, textInputClasses } from '../text-input';
import { toLocale } from './toLocale';

type TextAlignment = 'left' | 'right';

Expand All @@ -23,7 +24,7 @@ function safeFormatCurrency(
return value === null ? '' : formatCurrency(value, precision, locale, currency);
}

interface Props {
export interface MoneyInputProps {
id?: string;
className?: string;
disabled?: boolean;
Expand All @@ -37,7 +38,7 @@ interface Props {
value?: number | null;
/**
* Sets input locale and changes visual format accordingly
* @default fr-CA
* Defaults to the locale provided to the DesignSystem provider
*/
locale?: string;
/**
Expand Down Expand Up @@ -67,7 +68,7 @@ function parseAndRound(val: string, precision: number): number | null {
return val === '' ? null : roundValueToPrecision(Number(val.replace(',', '.')), precision);
}

export const MoneyInput: VoidFunctionComponent<Props> = ({
export const MoneyInput: VoidFunctionComponent<MoneyInputProps> = ({
id: providedId,
className,
required,
Expand All @@ -77,14 +78,16 @@ export const MoneyInput: VoidFunctionComponent<Props> = ({
precision = 2,
value = null,
validationErrorMessage,
locale = 'fr-CA',
locale: providedLocale,
currency = 'CAD',
hint,
noMargin,
textAlignment = 'left',
...otherProps
}) => {
const { t } = useTranslation('money-input');
const { i18n, t } = useTranslation('money-input');
const locale = providedLocale ?? toLocale(i18n.language, currency);
console.log(locale);
const inputElement = useRef<HTMLInputElement>(null);
const [displayValue, setDisplayValue] = useState(safeFormatCurrency(value, precision, locale, currency));
const [maskedValue, setMaskedValue] = useState(safeFormatCurrency(value, precision, locale, currency));
Expand Down
32 changes: 32 additions & 0 deletions packages/react/src/components/money-input/toLocale.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { toLocale } from './toLocale';

describe('toLocale', () => {
it.each`
language | expected
${'en'} | ${'en-CA'}
${'fr'} | ${'fr-CA'}
`('should return $expected for $language with CAD currency', ({ language, expected }) => {
expect(toLocale(language, 'CAD')).toBe(expected);
});

it.each`
language | expected
${'en'} | ${'en-US'}
${'fr'} | ${'fr-US'}
`('should return $expected for $language with USD currency', ({ language, expected }) => {
expect(toLocale(language, 'USD')).toBe(expected);
});

it('should return language as is if language is a locale', () => {
expect(toLocale('fr-FR', 'EUR')).toBe('fr-FR');
});

it.each`
language | currency | expected
${'en'} | ${'EUR'} | ${'en'}
${'fr'} | ${'GBP'} | ${'fr'}
${'fr'} | ${'ABC'} | ${'fr'}
`('should return $language as is for unmapped $currency currency', ({ language, currency, expected }) => {
expect(toLocale(language, currency)).toBe(expected);
});
});
15 changes: 15 additions & 0 deletions packages/react/src/components/money-input/toLocale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export function toLocale(
language: string,
currency: string,
): string {
if (!language.match(/^[a-z]{2}-[a-z]{2}$/i)) {
switch (currency.toUpperCase()) {
case 'CAD':
return `${language}-CA`;
case 'USD':
return `${language}-US`;
}
}

return language;
}
9 changes: 7 additions & 2 deletions packages/react/src/test-utils/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ import { DesignSystem, DesignSystemProps } from '../components/design-system';
import { DeviceType } from '../components/device-context-provider/device-context-provider';
import { ThemeWrapper } from '../components/theme-wrapper/theme-wrapper';

export const AllProviders: FunctionComponent<PropsWithChildren<DesignSystemProps>> = ({ children, staticDevice }) => (
export const AllProviders: FunctionComponent<PropsWithChildren<DesignSystemProps>> = ({
children,
language,
theme,
staticDevice,
}) => (
<MemoryRouter>
<DesignSystem staticDevice={staticDevice}>
<DesignSystem language={language} staticDevice={staticDevice} theme={theme}>
{children}
</DesignSystem>
</MemoryRouter>
Expand Down
40 changes: 40 additions & 0 deletions packages/react/src/test-utils/testing-library.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { DesignSystem, DesignSystemProps } from '../components/design-system';
import { equisoftTheme } from '../theme';

export function renderWithProviders(
ui: Parameters<typeof render>[0],
renderOptions: Omit<Parameters<typeof render>[1], 'wrapper'> | undefined = undefined,
wrapperProps: DesignSystemProps = {},
): ReturnType<typeof render> {
const {
isolateStyles,
language,
staticDevice,
theme,
} = {
isolateStyles: false,
language: 'fr',
theme: equisoftTheme,
...wrapperProps,
};
return render(
ui,
{
...renderOptions,
wrapper: ({ children }) => (
<MemoryRouter>
<DesignSystem
isolateStyles={isolateStyles}
language={language}
staticDevice={staticDevice}
theme={theme}
>
{children}
</DesignSystem>
</MemoryRouter>
),
},
);
}
14 changes: 0 additions & 14 deletions packages/react/src/test-utils/theme-provider.tsx

This file was deleted.

3 changes: 2 additions & 1 deletion packages/storybook/stories/money-input.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ const MoneyInputMeta: Meta<typeof MoneyInput> = {
args: {
label: 'Entrez un montant',
hint: 'Hint',
locale: 'fr-CA',
value: 12345,
locale: 'en',
},
argTypes: {
onChange: {
Expand Down

0 comments on commit 67b664f

Please sign in to comment.