diff --git a/semcore/input-number/__tests__/index.test.tsx b/semcore/input-number/__tests__/index.test.tsx
index be3896e7c7..88604c9aee 100644
--- a/semcore/input-number/__tests__/index.test.tsx
+++ b/semcore/input-number/__tests__/index.test.tsx
@@ -6,6 +6,7 @@ import React from 'react';
import InputNumber from '../src';
import type { NSInputNumber } from '../src';
+import { localizedMessages } from '../src/translations/__intergalactic-dynamic-locales';
describe('input-number Dependency imports', () => {
runDependencyCheckTests('input-number');
@@ -178,78 +179,161 @@ describe('InputNumber', () => {
expect(input.value).toBe('1.00');
});
- test.sequential('Verify int numbers', async () => {
+ test.sequential('Verify English locale accepts comma as an alternative decimal separator', async () => {
const spy = vi.fn();
const { getByTestId } = render(
-
+
,
);
- const input = getByTestId('input1');
- await focusInput(input);
- await userEvent.keyboard('123');
- expect(spy).lastCalledWith('123', expect.anything());
- });
- test.sequential('Verify float numbers', async () => {
- const spy = vi.fn();
- const { getByTestId } = render(
-
-
- ,
- );
- const input = getByTestId('input2');
- await focusInput(input);
- await userEvent.keyboard('123.4');
- expect(spy).lastCalledWith('123.4', expect.anything());
+ const input = getByTestId('input3333') as HTMLInputElement;
+ await userEvent.keyboard('[Tab]');
+ await userEvent.keyboard('12345,99');
+
+ expect(spy).lastCalledWith('12345.99', expect.anything());
+ expect(input.value).toBe('12,345.99');
});
- test.concurrent('Verify format in int numbers', async () => {
- const spy = vi.fn();
- const { getByTestId } = render(
-
-
- ,
- );
+ const localeGroups = [
+ {
+ locales: ['en', 'ja', 'ko', 'zh'],
+ thousandsSeparator: ',',
+ decimalSeparator: '.',
+ groupFourDigitNumbers: true,
+ },
+ {
+ locales: ['de', 'nl', 'pt', 'tr', 'vi'],
+ thousandsSeparator: '.',
+ decimalSeparator: ',',
+ groupFourDigitNumbers: true,
+ },
+ {
+ locales: ['es', 'it'],
+ thousandsSeparator: '.',
+ decimalSeparator: ',',
+ groupFourDigitNumbers: false,
+ },
+ {
+ locales: ['fr'],
+ thousandsSeparator: '\u202F',
+ decimalSeparator: ',',
+ groupFourDigitNumbers: true,
+ },
+ {
+ locales: ['sv'],
+ thousandsSeparator: '\u00A0',
+ decimalSeparator: ',',
+ groupFourDigitNumbers: true,
+ },
+ {
+ locales: ['pl'],
+ thousandsSeparator: '\u00A0',
+ decimalSeparator: ',',
+ groupFourDigitNumbers: false,
+ },
+ ];
+
+ test('Verify locale separator cases cover every supported locale', () => {
+ const testedLocales = localeGroups.flatMap(({ locales }) => locales).sort();
+ const supportedLocales = Object.keys(localizedMessages).sort();
+
+ expect(testedLocales).toEqual(supportedLocales);
+ });
+
+ for (const {
+ locales,
+ thousandsSeparator,
+ decimalSeparator,
+ groupFourDigitNumbers,
+ } of localeGroups) {
+ for (const locale of locales) {
+ test.sequential(`Verify locale=${locale} thousands and decimal separators`, async () => {
+ const spy = vi.fn();
+ const { getByTestId } = render(
+
+
+ ,
+ );
+ const input = getByTestId('localized-input') as HTMLInputElement;
- const input = getByTestId('input3') as HTMLInputElement;
- await userEvent.keyboard('[Tab]');
- await userEvent.keyboard('12345');
+ await focusInput(input);
+ await userEvent.keyboard('1000');
- expect(spy).lastCalledWith('12345', expect.anything());
- expect(input.value).toBe('12,345');
- });
+ const expectedFourDigitValue = groupFourDigitNumbers
+ ? `1${thousandsSeparator}000`
+ : '1000';
+ expect(input.value, `Unexpected four-digit grouping for locale "${locale}"`)
+ .toBe(expectedFourDigitValue);
- test.sequential('Verify format in float numbers', async () => {
- const spy = vi.fn();
- const { getByTestId } = render(
-
-
- ,
- );
+ await userEvent.keyboard('0');
- const input = getByTestId('input4') as HTMLInputElement;
- await focusInput(input);
- await userEvent.keyboard('12345.4');
- expect(spy).lastCalledWith('12345.4', expect.anything());
- expect(input.value).toBe('12,345.4');
- });
+ expect(input.value, `Unexpected five-digit grouping for locale "${locale}"`)
+ .toBe(`10${thousandsSeparator}000`);
- test.sequential('Verify not locale decimal separator', async () => {
- const spy = vi.fn();
- const { getByTestId } = render(
-
-
- ,
+ await userEvent.keyboard('0');
+
+ expect(spy).lastCalledWith('100000', expect.anything());
+ expect(input.value, `Unexpected thousands separator for locale "${locale}"`)
+ .toBe(`100${thousandsSeparator}000`);
+
+ await userEvent.keyboard(`${decimalSeparator}99`);
+
+ expect(spy.mock.lastCall?.[0], `Unexpected parsed value for locale "${locale}"`)
+ .toBe('100000.99');
+ expect(input.value, `Unexpected decimal separator for locale "${locale}"`)
+ .toBe(`100${thousandsSeparator}000${decimalSeparator}99`);
+ });
+ }
+ }
+
+ const ControlledLocalized = ({
+ spy,
+ locale,
+ }: {
+ spy: (value: string, event?: React.SyntheticEvent) => void;
+ locale: string;
+ }) => {
+ const [value, setValue] = React.useState('');
+
+ return (
+
+ {
+ spy(nextValue, event);
+ setValue(nextValue);
+ }}
+ />
+
);
+ };
- const input = getByTestId('input3333') as HTMLInputElement;
- await userEvent.keyboard('[Tab]');
- await userEvent.keyboard('12345,99');
+ for (const { locales, thousandsSeparator } of localeGroups) {
+ for (const locale of locales) {
+ test.sequential(`Verify locale=${locale} keeps the typed value after blur`, async () => {
+ const spy = vi.fn();
+ const { getByTestId } = render();
+ const input = getByTestId('localized-blur-input') as HTMLInputElement;
- expect(spy).lastCalledWith('12345.99', expect.anything());
- expect(input.value).toBe('12,345.99');
- });
+ await focusInput(input);
+ await userEvent.keyboard('100000');
+
+ expect(spy.mock.lastCall?.[0], `Unexpected parsed value before blur for locale "${locale}"`)
+ .toBe('100000');
+ expect(input.value, `Unexpected display value before blur for locale "${locale}"`)
+ .toBe(`100${thousandsSeparator}000`);
+
+ await userEvent.tab();
+
+ expect(spy.mock.lastCall?.[0], `Parsed value corrupted on blur for locale "${locale}"`)
+ .toBe('100000');
+ expect(input.value, `Display value corrupted on blur for locale "${locale}"`)
+ .toBe(`100${thousandsSeparator}000`);
+ });
+ }
+ }
test.sequential('Verify format in hundredths fractions numbers', async () => {
const spy = vi.fn();
diff --git a/semcore/input-number/src/InputNumber.tsx b/semcore/input-number/src/InputNumber.tsx
index 327f00fb33..2c1d51ee29 100644
--- a/semcore/input-number/src/InputNumber.tsx
+++ b/semcore/input-number/src/InputNumber.tsx
@@ -149,7 +149,7 @@ class Value extends Component<
get separatorThousands() {
const { numberFormatter } = this.props;
- return numberFormatter.format(1111).replace(/\d/g, '');
+ return numberFormatter.format(11111).replace(/\d/g, '');
}
onPropsChange(changedProps: { value?: string | number | null }) {
@@ -167,7 +167,8 @@ class Value extends Component<
getFormattedValue = (value: string) => {
return value
.replace(new RegExp(`[${this.separatorThousands}]`, 'g'), '')
- .replace(this.separatorDecimal, '.');
+ .replace(this.separatorDecimal, '.')
+ .replace(new RegExp('\\.\\.', 'g'), '.');
};
valueParser = (
@@ -176,8 +177,7 @@ class Value extends Component<
prevDisplayValue: NSInputNumber.Value.State['displayValue'],
) => {
const { numberFormatter } = this.props;
-
- const stringNumber = this.getFormattedValue(String(value));
+ const stringNumber = String(value);
if (Number.isNaN(Number(stringNumber))) {
return {
@@ -189,7 +189,7 @@ class Value extends Component<
let displayValue = '';
if (/\.[0-9]*0$/.test(stringNumber)) {
- const [int, decimal] = stringNumber.split(this.separatorDecimal);
+ const [int, decimal] = stringNumber.split('.');
displayValue = numberFormatter.format(+int) + this.separatorDecimal + decimal;
} else if (stringNumber !== '') {
displayValue = numberFormatter.format(+stringNumber);
diff --git a/stories/components/input-number/tests/input-number.stories.tsx b/stories/components/input-number/tests/input-number.stories.tsx
index aae12cfce6..7a89389e46 100644
--- a/stories/components/input-number/tests/input-number.stories.tsx
+++ b/stories/components/input-number/tests/input-number.stories.tsx
@@ -21,7 +21,7 @@ export const Basic: StoryObj = {
},
locale: {
control: { type: 'select' },
- options: ['de', 'ko'],
+ options: ['en', 'de', 'es', 'fr', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'sv', 'tr', 'vi', 'zh'],
},
disabledValue: {
control: { type: 'boolean' },