From 05ccb3a05bd7ae04bec7c6097c6723831282ba67 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Wed, 19 Aug 2026 12:51:36 +0200 Subject: [PATCH 1/3] [input-number] fixed thousand's separator for spanish locale --- semcore/input-number/__tests__/index.test.tsx | 22 +++++++++++++++++++ semcore/input-number/src/InputNumber.tsx | 2 +- .../tests/input-number.stories.tsx | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/semcore/input-number/__tests__/index.test.tsx b/semcore/input-number/__tests__/index.test.tsx index be3896e7c7..bb649caea1 100644 --- a/semcore/input-number/__tests__/index.test.tsx +++ b/semcore/input-number/__tests__/index.test.tsx @@ -251,6 +251,28 @@ describe('InputNumber', () => { expect(input.value).toBe('12,345.99'); }); + test.sequential('Verify Spanish locale decimal separator', async () => { + const spy = vi.fn(); + const { getByTestId } = render( + + + , + ); + + const input = getByTestId('input3344') as HTMLInputElement; + await userEvent.keyboard('[Tab]'); + await userEvent.keyboard('1000'); + + expect(spy).lastCalledWith('1000', expect.anything()); + expect(input.value).toBe('1000'); + + // in Spanish thousand's separator starts from ten-thousandths instead of thousandths. + await userEvent.keyboard('00'); + + expect(spy).lastCalledWith('100000', expect.anything()); + expect(input.value).toBe('100.000'); + }); + test.sequential('Verify format in hundredths fractions numbers', async () => { const spy = vi.fn(); const { getByTestId } = render( diff --git a/semcore/input-number/src/InputNumber.tsx b/semcore/input-number/src/InputNumber.tsx index 327f00fb33..b5becb5f4f 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 }) { diff --git a/stories/components/input-number/tests/input-number.stories.tsx b/stories/components/input-number/tests/input-number.stories.tsx index aae12cfce6..d6c913b6f5 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: ['de', 'ko', 'es'], }, disabledValue: { control: { type: 'boolean' }, From b25920dbde6a436403279b6b58f306e967826ccc Mon Sep 17 00:00:00 2001 From: Valeryia Zimnitskaya Date: Wed, 19 Aug 2026 17:11:37 +0200 Subject: [PATCH 2/3] [input-number] update test story and test --- semcore/input-number/__tests__/index.test.tsx | 164 ++++++++++-------- .../tests/input-number.stories.tsx | 2 +- 2 files changed, 90 insertions(+), 76 deletions(-) diff --git a/semcore/input-number/__tests__/index.test.tsx b/semcore/input-number/__tests__/index.test.tsx index bb649caea1..04c6fae305 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,64 +179,7 @@ describe('InputNumber', () => { expect(input.value).toBe('1.00'); }); - test.sequential('Verify int numbers', 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()); - }); - - test.concurrent('Verify format in int numbers', async () => { - const spy = vi.fn(); - const { getByTestId } = render( - - - , - ); - - const input = getByTestId('input3') as HTMLInputElement; - await userEvent.keyboard('[Tab]'); - await userEvent.keyboard('12345'); - - expect(spy).lastCalledWith('12345', expect.anything()); - expect(input.value).toBe('12,345'); - }); - - test.sequential('Verify format in float numbers', async () => { - const spy = vi.fn(); - const { getByTestId } = render( - - - , - ); - - 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'); - }); - - test.sequential('Verify not locale decimal separator', async () => { + test.sequential('Verify English locale accepts comma as an alternative decimal separator', async () => { const spy = vi.fn(); const { getByTestId } = render( @@ -251,27 +195,97 @@ describe('InputNumber', () => { expect(input.value).toBe('12,345.99'); }); - test.sequential('Verify Spanish locale decimal separator', 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('input3344') as HTMLInputElement; - await userEvent.keyboard('[Tab]'); - await userEvent.keyboard('1000'); + await focusInput(input); + await userEvent.keyboard('1000'); - expect(spy).lastCalledWith('1000', expect.anything()); - expect(input.value).toBe('1000'); + const expectedFourDigitValue = groupFourDigitNumbers + ? `1${thousandsSeparator}000` + : '1000'; + expect(input.value, `Unexpected four-digit grouping for locale "${locale}"`) + .toBe(expectedFourDigitValue); - // in Spanish thousand's separator starts from ten-thousandths instead of thousandths. - await userEvent.keyboard('00'); + await userEvent.keyboard('0'); - expect(spy).lastCalledWith('100000', expect.anything()); - expect(input.value).toBe('100.000'); - }); + expect(input.value, `Unexpected five-digit grouping for locale "${locale}"`) + .toBe(`10${thousandsSeparator}000`); + + 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`); + }); + } + } test.sequential('Verify format in hundredths fractions numbers', async () => { const spy = vi.fn(); diff --git a/stories/components/input-number/tests/input-number.stories.tsx b/stories/components/input-number/tests/input-number.stories.tsx index d6c913b6f5..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', 'es'], + options: ['en', 'de', 'es', 'fr', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'sv', 'tr', 'vi', 'zh'], }, disabledValue: { control: { type: 'boolean' }, From 566655f74323bf35ef28feeb19a2105bcaa59009 Mon Sep 17 00:00:00 2001 From: "ilia.brauer" Date: Thu, 20 Aug 2026 22:13:02 +0200 Subject: [PATCH 3/3] [input-number] fixed separators for different locales --- semcore/input-number/src/InputNumber.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/semcore/input-number/src/InputNumber.tsx b/semcore/input-number/src/InputNumber.tsx index b5becb5f4f..2c1d51ee29 100644 --- a/semcore/input-number/src/InputNumber.tsx +++ b/semcore/input-number/src/InputNumber.tsx @@ -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);