From 19effe2c7218646335b2f08c53a1ed3c3f0d89a1 Mon Sep 17 00:00:00 2001 From: Nikita Barsukov Date: Thu, 26 Dec 2024 11:45:37 +0300 Subject: [PATCH] fix(kit): `Number` should ignore `[decimalSeparator]` value if `[precision]=0` (#1908) --- .../tests/kit/number/number-precision.cy.ts | 29 ++++++++++++++++--- .../kit/src/lib/masks/number/number-mask.ts | 21 +++++++++----- .../plugins/not-empty-integer.plugin.ts | 6 +++- .../number/processors/empty-postprocessor.ts | 5 +++- ...leading-zeroes-validation-postprocessor.ts | 7 +++-- ...repeated-decimal-separator-preprocessor.ts | 6 +++- .../thousand-separator-postprocessor.ts | 8 ++--- .../processors/zero-precision-preprocessor.ts | 5 +++- .../masks/number/tests/number-mask.spec.ts | 10 +++++++ .../lib/masks/number/utils/parse-number.ts | 2 +- .../number/utils/tests/parse-number.spec.ts | 10 +++++++ .../lib/masks/number/utils/to-number-parts.ts | 4 ++- .../src/lib/plugins/time/meridiem-stepping.ts | 3 +- .../plugins/time/time-segments-stepping.ts | 3 +- projects/kit/src/lib/utils/dummy.ts | 6 ++++ projects/kit/src/lib/utils/identity.ts | 3 -- projects/kit/src/lib/utils/index.ts | 2 +- 17 files changed, 99 insertions(+), 31 deletions(-) create mode 100644 projects/kit/src/lib/utils/dummy.ts delete mode 100644 projects/kit/src/lib/utils/identity.ts diff --git a/projects/demo-integrations/src/tests/kit/number/number-precision.cy.ts b/projects/demo-integrations/src/tests/kit/number/number-precision.cy.ts index 9bb96ab47..46ef6f410 100644 --- a/projects/demo-integrations/src/tests/kit/number/number-precision.cy.ts +++ b/projects/demo-integrations/src/tests/kit/number/number-precision.cy.ts @@ -48,21 +48,42 @@ describe('Number | precision', () => { }); describe('rejects decimal separator if `precision` is equal to 0', () => { - beforeEach(() => { - openNumberPage('decimalSeparator=,&precision=0'); - }); - it('empty input => Type "," => Empty input', () => { + openNumberPage('decimalSeparator=,&precision=0'); cy.get('@input').type(',').should('have.value', ''); }); it('Type "5," => "5"', () => { + openNumberPage('decimalSeparator=,&precision=0'); + cy.get('@input') .type('5,') .should('have.value', '5') .should('have.prop', 'selectionStart', 1) .should('have.prop', 'selectionEnd', 1); }); + + describe('dont rejects thousand separator if it is equal to decimal separator (for precision=0 value of decimal separator does not matter)', () => { + it('simple typing', () => { + openNumberPage('precision=0&thousandSeparator=.&decimalSeparator=.'); + + cy.get('@input') + .type('1234') + .should('have.value', '1.234') + .should('have.prop', 'selectionStart', '1.234'.length) + .should('have.prop', 'selectionEnd', '1.234'.length); + }); + + it('paste from clipboard', () => { + openNumberPage('precision=0&thousandSeparator=.&decimalSeparator=.'); + + cy.get('@input') + .paste('1.234') + .should('have.value', '1.234') + .should('have.prop', 'selectionStart', '1.234'.length) + .should('have.prop', 'selectionEnd', '1.234'.length); + }); + }); }); describe('keeps untouched decimal part if `precision: Infinity`', () => { diff --git a/projects/kit/src/lib/masks/number/number-mask.ts b/projects/kit/src/lib/masks/number/number-mask.ts index 29c0418c6..56397fcb9 100644 --- a/projects/kit/src/lib/masks/number/number-mask.ts +++ b/projects/kit/src/lib/masks/number/number-mask.ts @@ -78,6 +78,18 @@ export function maskitoNumberOptionsGenerator({ ? `${unsafePrefix}${CHAR_ZERO_WIDTH_SPACE}` : unsafePrefix; + const initializationOnlyPreprocessor = createInitializationOnlyPreprocessor({ + decimalSeparator, + decimalPseudoSeparators: validatedDecimalPseudoSeparators, + pseudoMinuses, + prefix, + postfix, + minusSign, + }); + + decimalSeparator = + precision <= 0 && decimalSeparator === thousandSeparator ? '' : decimalSeparator; + return { ...MASKITO_DEFAULT_OPTIONS, mask: generateMaskExpression({ @@ -91,14 +103,7 @@ export function maskitoNumberOptionsGenerator({ }), preprocessors: [ createFullWidthToHalfWidthPreprocessor(), - createInitializationOnlyPreprocessor({ - decimalSeparator, - decimalPseudoSeparators: validatedDecimalPseudoSeparators, - pseudoMinuses, - prefix, - postfix, - minusSign, - }), + initializationOnlyPreprocessor, createAffixesFilterPreprocessor({prefix, postfix}), createPseudoCharactersPreprocessor({ validCharacter: minusSign, diff --git a/projects/kit/src/lib/masks/number/plugins/not-empty-integer.plugin.ts b/projects/kit/src/lib/masks/number/plugins/not-empty-integer.plugin.ts index 17a33cdab..31bef8ea7 100644 --- a/projects/kit/src/lib/masks/number/plugins/not-empty-integer.plugin.ts +++ b/projects/kit/src/lib/masks/number/plugins/not-empty-integer.plugin.ts @@ -2,7 +2,7 @@ import type {MaskitoPlugin} from '@maskito/core'; import {maskitoUpdateElement} from '@maskito/core'; import {maskitoEventHandler} from '../../../plugins'; -import {escapeRegExp, extractAffixes} from '../../../utils'; +import {escapeRegExp, extractAffixes, noop} from '../../../utils'; /** * It pads EMPTY integer part with zero if decimal parts exists. @@ -18,6 +18,10 @@ export function createNotEmptyIntegerPlugin({ prefix: string; postfix: string; }): MaskitoPlugin { + if (!decimalSeparator) { + return noop; + } + return maskitoEventHandler( 'blur', (element) => { diff --git a/projects/kit/src/lib/masks/number/processors/empty-postprocessor.ts b/projects/kit/src/lib/masks/number/processors/empty-postprocessor.ts index 1c4f7b1a4..fbe02a00b 100644 --- a/projects/kit/src/lib/masks/number/processors/empty-postprocessor.ts +++ b/projects/kit/src/lib/masks/number/processors/empty-postprocessor.ts @@ -31,7 +31,10 @@ export function emptyPostprocessor({ minusSign, }); const aloneDecimalSeparator = - !integerPart && !decimalPart && cleanValue.includes(decimalSeparator); + !integerPart && + !decimalPart && + Boolean(decimalSeparator) && + cleanValue.includes(decimalSeparator); if ( (!integerPart && diff --git a/projects/kit/src/lib/masks/number/processors/leading-zeroes-validation-postprocessor.ts b/projects/kit/src/lib/masks/number/processors/leading-zeroes-validation-postprocessor.ts index 9232382e0..ce91cf81e 100644 --- a/projects/kit/src/lib/masks/number/processors/leading-zeroes-validation-postprocessor.ts +++ b/projects/kit/src/lib/masks/number/processors/leading-zeroes-validation-postprocessor.ts @@ -54,8 +54,11 @@ export function createLeadingZeroesValidationPostprocessor({ postfix, }); - const hasDecimalSeparator = cleanValue.includes(decimalSeparator); - const [integerPart = '', decimalPart = ''] = cleanValue.split(decimalSeparator); + const hasDecimalSeparator = + Boolean(decimalSeparator) && cleanValue.includes(decimalSeparator); + const [integerPart = '', decimalPart = ''] = decimalSeparator + ? cleanValue.split(decimalSeparator) + : [cleanValue]; const zeroTrimmedIntegerPart = trimLeadingZeroes(integerPart); if (integerPart === zeroTrimmedIntegerPart) { diff --git a/projects/kit/src/lib/masks/number/processors/repeated-decimal-separator-preprocessor.ts b/projects/kit/src/lib/masks/number/processors/repeated-decimal-separator-preprocessor.ts index 0ca48a342..d04f0aebf 100644 --- a/projects/kit/src/lib/masks/number/processors/repeated-decimal-separator-preprocessor.ts +++ b/projects/kit/src/lib/masks/number/processors/repeated-decimal-separator-preprocessor.ts @@ -1,6 +1,6 @@ import type {MaskitoPreprocessor} from '@maskito/core'; -import {escapeRegExp, extractAffixes} from '../../../utils'; +import {escapeRegExp, extractAffixes, identity} from '../../../utils'; /** * It rejects new typed decimal separator if it already exists in text field. @@ -16,6 +16,10 @@ export function createRepeatedDecimalSeparatorPreprocessor({ prefix: string; postfix: string; }): MaskitoPreprocessor { + if (!decimalSeparator) { + return identity; + } + return ({elementState, data}) => { const {value, selection} = elementState; const [from, to] = selection; diff --git a/projects/kit/src/lib/masks/number/processors/thousand-separator-postprocessor.ts b/projects/kit/src/lib/masks/number/processors/thousand-separator-postprocessor.ts index b78eca348..02196c2c8 100644 --- a/projects/kit/src/lib/masks/number/processors/thousand-separator-postprocessor.ts +++ b/projects/kit/src/lib/masks/number/processors/thousand-separator-postprocessor.ts @@ -39,14 +39,14 @@ export function createThousandSeparatorPostprocessor({ decimalSeparator, minusSign, }); + const hasDecimalSeparator = + decimalSeparator && cleanValue.includes(decimalSeparator); const deletedChars = cleanValue.length - ( minus + integerPart + - (cleanValue.includes(decimalSeparator) - ? decimalSeparator + decimalPart - : '') + (hasDecimalSeparator ? decimalSeparator + decimalPart : '') ).length; if (deletedChars > 0 && initialFrom && initialFrom <= deletedChars) { @@ -105,7 +105,7 @@ export function createThousandSeparatorPostprocessor({ extractedPrefix + minus + processedIntegerPart + - (cleanValue.includes(decimalSeparator) ? decimalSeparator : '') + + (hasDecimalSeparator ? decimalSeparator : '') + decimalPart + extractedPostfix, selection: [from, to], diff --git a/projects/kit/src/lib/masks/number/processors/zero-precision-preprocessor.ts b/projects/kit/src/lib/masks/number/processors/zero-precision-preprocessor.ts index 8f17ea471..5e79a02c0 100644 --- a/projects/kit/src/lib/masks/number/processors/zero-precision-preprocessor.ts +++ b/projects/kit/src/lib/masks/number/processors/zero-precision-preprocessor.ts @@ -17,7 +17,10 @@ export function createZeroPrecisionPreprocessor({ prefix: string; postfix: string; }): MaskitoPreprocessor { - if (precision > 0) { + if ( + precision > 0 || + !decimalSeparator // all separators should be treated only as thousand separators + ) { return identity; } diff --git a/projects/kit/src/lib/masks/number/tests/number-mask.spec.ts b/projects/kit/src/lib/masks/number/tests/number-mask.spec.ts index 00023ae12..03108f97a 100644 --- a/projects/kit/src/lib/masks/number/tests/number-mask.spec.ts +++ b/projects/kit/src/lib/masks/number/tests/number-mask.spec.ts @@ -405,4 +405,14 @@ describe('Number (maskitoTransform)', () => { expect(maskitoTransform(' 123456 ', options)).toBe('123 456'); }); + + it('[thousandSeparator] is equal to [decimalSeparator] when [precision]=0', () => { + const options = maskitoNumberOptionsGenerator({ + thousandSeparator: '.', + decimalSeparator: '.', // default value + precision: 0, // default value + }); + + expect(maskitoTransform('123.456', options)).toBe('123.456'); + }); }); diff --git a/projects/kit/src/lib/masks/number/utils/parse-number.ts b/projects/kit/src/lib/masks/number/utils/parse-number.ts index 50b4409e2..5af938df8 100644 --- a/projects/kit/src/lib/masks/number/utils/parse-number.ts +++ b/projects/kit/src/lib/masks/number/utils/parse-number.ts @@ -18,7 +18,7 @@ export function maskitoParseNumber(maskedNumber: string, decimalSeparator = '.') .replaceAll(new RegExp(`${escapedDecimalSeparator}(?!\\d)`, 'g'), '') // drop all non-digit characters except decimal separator .replaceAll(new RegExp(`[^\\d${escapedDecimalSeparator}]`, 'g'), '') - .replace(decimalSeparator, '.'); + .replace(decimalSeparator, decimalSeparator && '.'); if (unmaskedNumber) { const sign = hasNegativeSign ? CHAR_HYPHEN : ''; diff --git a/projects/kit/src/lib/masks/number/utils/tests/parse-number.spec.ts b/projects/kit/src/lib/masks/number/utils/tests/parse-number.spec.ts index c17c3298b..8896917d7 100644 --- a/projects/kit/src/lib/masks/number/utils/tests/parse-number.spec.ts +++ b/projects/kit/src/lib/masks/number/utils/tests/parse-number.spec.ts @@ -54,6 +54,16 @@ describe('maskitoParseNumber', () => { }); }); + describe('decimal separator is empty string', () => { + it('thousand separator is point', () => { + expect(maskitoParseNumber('123.456.789', '')).toBe(123456789); + }); + + it('thousand separator is empty string', () => { + expect(maskitoParseNumber('123456', '')).toBe(123456); + }); + }); + describe('negative numbers', () => { describe('minus sign', () => { it('can be minus', () => { diff --git a/projects/kit/src/lib/masks/number/utils/to-number-parts.ts b/projects/kit/src/lib/masks/number/utils/to-number-parts.ts index fd38efeb8..f9a26e5fb 100644 --- a/projects/kit/src/lib/masks/number/utils/to-number-parts.ts +++ b/projects/kit/src/lib/masks/number/utils/to-number-parts.ts @@ -4,7 +4,9 @@ export function toNumberParts( value: string, {decimalSeparator, minusSign}: {decimalSeparator: string; minusSign: string}, ): {minus: string; integerPart: string; decimalPart: string} { - const [integerWithMinus = '', decimalPart = ''] = value.split(decimalSeparator); + const [integerWithMinus = '', decimalPart = ''] = decimalSeparator + ? value.split(decimalSeparator) + : [value]; const escapedMinus = escapeRegExp(minusSign); const [, minus = '', integerPart = ''] = new RegExp(`^(?:[^\\d${escapedMinus}])?(${escapedMinus})?(.*)`).exec( diff --git a/projects/kit/src/lib/plugins/time/meridiem-stepping.ts b/projects/kit/src/lib/plugins/time/meridiem-stepping.ts index 95af4c587..ae6e9f4b4 100644 --- a/projects/kit/src/lib/plugins/time/meridiem-stepping.ts +++ b/projects/kit/src/lib/plugins/time/meridiem-stepping.ts @@ -2,10 +2,11 @@ import type {MaskitoPlugin} from '@maskito/core'; import {maskitoUpdateElement} from '@maskito/core'; import {ANY_MERIDIEM_CHARACTER_RE, CHAR_NO_BREAK_SPACE} from '../../constants'; +import {noop} from '../../utils'; export function createMeridiemSteppingPlugin(meridiemStartIndex: number): MaskitoPlugin { if (meridiemStartIndex < 0) { - return () => {}; + return noop; } return (element) => { diff --git a/projects/kit/src/lib/plugins/time/time-segments-stepping.ts b/projects/kit/src/lib/plugins/time/time-segments-stepping.ts index 280686602..2b59cf73c 100644 --- a/projects/kit/src/lib/plugins/time/time-segments-stepping.ts +++ b/projects/kit/src/lib/plugins/time/time-segments-stepping.ts @@ -2,8 +2,7 @@ import type {MaskitoPlugin} from '@maskito/core'; import {maskitoUpdateElement} from '@maskito/core'; import type {MaskitoTimeSegments} from '../../types'; - -const noop = (): void => {}; +import {noop} from '../../utils'; export function createTimeSegmentsSteppingPlugin({ step, diff --git a/projects/kit/src/lib/utils/dummy.ts b/projects/kit/src/lib/utils/dummy.ts new file mode 100644 index 000000000..51f3ce631 --- /dev/null +++ b/projects/kit/src/lib/utils/dummy.ts @@ -0,0 +1,6 @@ +export function identity(x: T): T { + return x; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-function +export function noop(): void {} diff --git a/projects/kit/src/lib/utils/identity.ts b/projects/kit/src/lib/utils/identity.ts deleted file mode 100644 index 469762015..000000000 --- a/projects/kit/src/lib/utils/identity.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function identity(x: T): T { - return x; -} diff --git a/projects/kit/src/lib/utils/index.ts b/projects/kit/src/lib/utils/index.ts index a3ceb0e74..809a4cf11 100644 --- a/projects/kit/src/lib/utils/index.ts +++ b/projects/kit/src/lib/utils/index.ts @@ -10,10 +10,10 @@ export * from './date/parse-date-string'; export * from './date/segments-to-date'; export * from './date/to-date-string'; export * from './date/validate-date-string'; +export * from './dummy'; export * from './escape-reg-exp'; export * from './extract-affixes'; export * from './find-common-beginning-substr'; -export * from './identity'; export * from './is-empty'; export * from './pad-with-zeroes-until-valid'; export * from './to-half-width-colon';