From 4f1f104ba0ed704f13975835caa05ac39a530997 Mon Sep 17 00:00:00 2001 From: aktanoff <36963472+aktanoff@users.noreply.github.com> Date: Thu, 18 Jan 2024 11:28:43 +0300 Subject: [PATCH] fix(kit): `Number` has problems when prefix/postfix includes `decimalSeparator` symbol (#816) --- .../number-postfix-with-point.cy.ts | 6 +- .../kit/src/lib/masks/number/number-mask.ts | 48 +++++- .../leading-zeroes-validation.plugin.ts | 21 ++- .../plugins/not-empty-integer.plugin.ts | 25 ++- .../processors/affixes-filter-preprocessor.ts | 28 +++ .../decimal-zero-padding-postprocessor.ts | 20 ++- .../initialization-only-preprocessor.ts | 19 +- ...leading-zeroes-validation-postprocessor.ts | 30 +++- .../pseudo-character-preprocessor.ts | 26 ++- ...repeated-decimal-separator-preprocessor.ts | 18 +- ...ng-zeroes-validation-postprocessor.spec.ts | 7 +- .../thousand-separator-postprocessor.ts | 29 ++-- .../processors/zero-precision-preprocessor.ts | 26 ++- .../masks/number/tests/number-mask.spec.ts | 162 +++++++++++++++--- projects/kit/src/lib/utils/extract-affixes.ts | 20 +++ projects/kit/src/lib/utils/index.ts | 1 + 16 files changed, 391 insertions(+), 95 deletions(-) create mode 100644 projects/kit/src/lib/masks/number/processors/affixes-filter-preprocessor.ts create mode 100644 projects/kit/src/lib/utils/extract-affixes.ts diff --git a/projects/demo-integrations/src/tests/component-testing/number-postfix/number-postfix-with-point.cy.ts b/projects/demo-integrations/src/tests/component-testing/number-postfix/number-postfix-with-point.cy.ts index e17ddbe6a..7c5ae6541 100644 --- a/projects/demo-integrations/src/tests/component-testing/number-postfix/number-postfix-with-point.cy.ts +++ b/projects/demo-integrations/src/tests/component-testing/number-postfix/number-postfix-with-point.cy.ts @@ -35,8 +35,7 @@ describe('Number | postfix with point', () => { .should('have.prop', 'selectionEnd', 2); }); - // TODO https://github.com/taiga-family/maskito/issues/703 - it.skip('Empty => Type 0.42 => 0.42 lbs.', () => { + it('Empty => Type 0.42 => 0.42 lbs.', () => { cy.mount(TestInput, {componentProperties: {maskitoOptions}}); cy.get('input') .type('0') @@ -52,8 +51,7 @@ describe('Number | postfix with point', () => { }); }); - // TODO https://github.com/taiga-family/maskito/issues/703 - describe.skip('Complex: maskitoCaretGuard + maskitoAddOnFocusPlugin + maskitoRemoveOnBlurPlugin', () => { + describe('Complex: maskitoCaretGuard + maskitoAddOnFocusPlugin + maskitoRemoveOnBlurPlugin', () => { const postfix = ' lbs.'; const numberOptions = maskitoNumberOptionsGenerator({ postfix, diff --git a/projects/kit/src/lib/masks/number/number-mask.ts b/projects/kit/src/lib/masks/number/number-mask.ts index e042174eb..3be3bab7b 100644 --- a/projects/kit/src/lib/masks/number/number-mask.ts +++ b/projects/kit/src/lib/masks/number/number-mask.ts @@ -29,6 +29,7 @@ import { createThousandSeparatorPostprocessor, createZeroPrecisionPreprocessor, } from './processors'; +import {createAffixesFilterPreprocessor} from './processors/affixes-filter-preprocessor'; import {generateMaskExpression, validateDecimalPseudoSeparators} from './utils'; export function maskitoNumberOptionsGenerator({ @@ -79,21 +80,40 @@ export function maskitoNumberOptionsGenerator({ decimalSeparator, decimalPseudoSeparators: validatedDecimalPseudoSeparators, pseudoMinuses, + prefix, + postfix, }), + createAffixesFilterPreprocessor({prefix, postfix}), createFullWidthToHalfWidthPreprocessor(), - createPseudoCharactersPreprocessor(CHAR_MINUS, pseudoMinuses), - createPseudoCharactersPreprocessor( - decimalSeparator, - validatedDecimalPseudoSeparators, - ), + createPseudoCharactersPreprocessor({ + validCharacter: CHAR_MINUS, + pseudoCharacters: pseudoMinuses, + prefix, + postfix, + }), + createPseudoCharactersPreprocessor({ + validCharacter: decimalSeparator, + pseudoCharacters: validatedDecimalPseudoSeparators, + prefix, + postfix, + }), createNotEmptyIntegerPartPreprocessor({decimalSeparator, precision}), createNonRemovableCharsDeletionPreprocessor({ decimalSeparator, decimalZeroPadding, thousandSeparator, }), - createZeroPrecisionPreprocessor(precision, decimalSeparator), - createRepeatedDecimalSeparatorPreprocessor(decimalSeparator), + createZeroPrecisionPreprocessor({ + precision, + decimalSeparator, + prefix, + postfix, + }), + createRepeatedDecimalSeparatorPreprocessor({ + decimalSeparator, + prefix, + postfix, + }), ], postprocessors: [ createMinMaxPostprocessor({decimalSeparator, min, max}), @@ -109,12 +129,22 @@ export function maskitoNumberOptionsGenerator({ decimalSeparator, decimalZeroPadding, precision, + prefix, postfix, }), ], plugins: [ - createLeadingZeroesValidationPlugin(decimalSeparator, thousandSeparator), - createNotEmptyIntegerPlugin(decimalSeparator), + createLeadingZeroesValidationPlugin({ + decimalSeparator, + thousandSeparator, + prefix, + postfix, + }), + createNotEmptyIntegerPlugin({ + decimalSeparator, + prefix, + postfix, + }), createMinMaxPlugin({min, max, decimalSeparator}), ], overwriteMode: decimalZeroPadding diff --git a/projects/kit/src/lib/masks/number/plugins/leading-zeroes-validation.plugin.ts b/projects/kit/src/lib/masks/number/plugins/leading-zeroes-validation.plugin.ts index 9f3c134b0..21dd2fcf9 100644 --- a/projects/kit/src/lib/masks/number/plugins/leading-zeroes-validation.plugin.ts +++ b/projects/kit/src/lib/masks/number/plugins/leading-zeroes-validation.plugin.ts @@ -10,14 +10,23 @@ const DUMMY_SELECTION = [0, 0] as const; * @example 000000 => blur => 0 * @example 00005 => blur => 5 */ -export function createLeadingZeroesValidationPlugin( - decimalSeparator: string, - thousandSeparator: string, -): MaskitoPlugin { - const dropRepeatedLeadingZeroes = createLeadingZeroesValidationPostprocessor( +export function createLeadingZeroesValidationPlugin({ + decimalSeparator, + thousandSeparator, + prefix, + postfix, +}: { + decimalSeparator: string; + thousandSeparator: string; + prefix: string; + postfix: string; +}): MaskitoPlugin { + const dropRepeatedLeadingZeroes = createLeadingZeroesValidationPostprocessor({ decimalSeparator, thousandSeparator, - ); + prefix, + postfix, + }); return maskitoEventHandler( 'blur', 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 9378ed4bb..a8a6870b0 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 @@ -1,21 +1,36 @@ import {MaskitoPlugin, maskitoUpdateElement} from '@maskito/core'; import {maskitoEventHandler} from '../../../plugins'; -import {escapeRegExp} from '../../../utils'; +import {escapeRegExp, extractAffixes} from '../../../utils'; /** * It pads EMPTY integer part with zero if decimal parts exists. * It works on blur event only! * @example 1|,23 => Backspace => Blur => 0,23 */ -export function createNotEmptyIntegerPlugin(decimalSeparator: string): MaskitoPlugin { +export function createNotEmptyIntegerPlugin({ + decimalSeparator, + prefix, + postfix, +}: { + decimalSeparator: string; + prefix: string; + postfix: string; +}): MaskitoPlugin { return maskitoEventHandler( 'blur', element => { - const newValue = element.value.replace( - new RegExp(`^(\\D+)?${escapeRegExp(decimalSeparator)}`), - `$10${decimalSeparator}`, + const {cleanValue, extractedPostfix, extractedPrefix} = extractAffixes( + element.value, + {prefix, postfix}, ); + const newValue = + extractedPrefix + + cleanValue.replace( + new RegExp(`^(\\D+)?${escapeRegExp(decimalSeparator)}`), + `$10${decimalSeparator}`, + ) + + extractedPostfix; if (newValue !== element.value) { maskitoUpdateElement(element, newValue); diff --git a/projects/kit/src/lib/masks/number/processors/affixes-filter-preprocessor.ts b/projects/kit/src/lib/masks/number/processors/affixes-filter-preprocessor.ts new file mode 100644 index 000000000..d1aec7aa7 --- /dev/null +++ b/projects/kit/src/lib/masks/number/processors/affixes-filter-preprocessor.ts @@ -0,0 +1,28 @@ +import {MaskitoPreprocessor} from '@maskito/core'; + +import {extractAffixes} from '../../../utils'; + +/** + * It drops prefix and postfix from data + * Needed for case, when prefix or postfix contain decimalSeparator, to ignore it in resulting number + * @example User pastes '{prefix}123.45{postfix}' => 123.45 + */ +export function createAffixesFilterPreprocessor({ + prefix, + postfix, +}: { + prefix: string; + postfix: string; +}): MaskitoPreprocessor { + return ({elementState, data}) => { + const {cleanValue: cleanData} = extractAffixes(data, { + prefix, + postfix, + }); + + return { + elementState, + data: cleanData, + }; + }; +} diff --git a/projects/kit/src/lib/masks/number/processors/decimal-zero-padding-postprocessor.ts b/projects/kit/src/lib/masks/number/processors/decimal-zero-padding-postprocessor.ts index 9b5c21260..92e4053c5 100644 --- a/projects/kit/src/lib/masks/number/processors/decimal-zero-padding-postprocessor.ts +++ b/projects/kit/src/lib/masks/number/processors/decimal-zero-padding-postprocessor.ts @@ -1,6 +1,6 @@ import {MaskitoPostprocessor} from '@maskito/core'; -import {escapeRegExp, identity} from '../../../utils'; +import {extractAffixes, identity} from '../../../utils'; import {maskitoParseNumber} from '../utils'; /** @@ -12,34 +12,38 @@ export function createDecimalZeroPaddingPostprocessor({ decimalSeparator, precision, decimalZeroPadding, + prefix, postfix, }: { decimalSeparator: string; decimalZeroPadding: boolean; precision: number; + prefix: string; postfix: string; }): MaskitoPostprocessor { if (precision <= 0 || !decimalZeroPadding) { return identity; } - const trailingPostfixRegExp = new RegExp(`${escapeRegExp(postfix)}$`); - return ({value, selection}) => { - if (Number.isNaN(maskitoParseNumber(value, decimalSeparator))) { + const {cleanValue, extractedPrefix, extractedPostfix} = extractAffixes(value, { + prefix, + postfix, + }); + + if (Number.isNaN(maskitoParseNumber(cleanValue, decimalSeparator))) { return {value, selection}; } - const [integerPart, decimalPart = ''] = value - .replace(trailingPostfixRegExp, '') - .split(decimalSeparator); + const [integerPart, decimalPart = ''] = cleanValue.split(decimalSeparator); return { value: + extractedPrefix + integerPart + decimalSeparator + decimalPart.padEnd(precision, '0') + - postfix, + extractedPostfix, selection, }; }; diff --git a/projects/kit/src/lib/masks/number/processors/initialization-only-preprocessor.ts b/projects/kit/src/lib/masks/number/processors/initialization-only-preprocessor.ts index 955a818e9..235bf3d58 100644 --- a/projects/kit/src/lib/masks/number/processors/initialization-only-preprocessor.ts +++ b/projects/kit/src/lib/masks/number/processors/initialization-only-preprocessor.ts @@ -1,5 +1,6 @@ import {MaskitoPreprocessor, maskitoTransform} from '@maskito/core'; +import {extractAffixes} from '../../../utils'; import {generateMaskExpression} from '../utils'; /** @@ -16,10 +17,14 @@ export function createInitializationOnlyPreprocessor({ decimalSeparator, decimalPseudoSeparators, pseudoMinuses, + prefix, + postfix, }: { decimalSeparator: string; decimalPseudoSeparators: readonly string[]; pseudoMinuses: readonly string[]; + prefix: string; + postfix: string; }): MaskitoPreprocessor { let isInitializationPhase = true; const cleanNumberMask = generateMaskExpression({ @@ -40,10 +45,18 @@ export function createInitializationOnlyPreprocessor({ isInitializationPhase = false; + const {cleanValue} = extractAffixes(elementState.value, {prefix, postfix}); + return { - elementState: maskitoTransform(elementState, { - mask: cleanNumberMask, - }), + elementState: maskitoTransform( + { + ...elementState, + value: cleanValue, + }, + { + mask: cleanNumberMask, + }, + ), data, }; }; 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 ad926ec3b..f0ded369c 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 @@ -1,6 +1,6 @@ import {MaskitoPostprocessor} from '@maskito/core'; -import {escapeRegExp} from '../../../utils'; +import {escapeRegExp, extractAffixes} from '../../../utils'; /** * It removes repeated leading zeroes for integer part. @@ -9,10 +9,17 @@ import {escapeRegExp} from '../../../utils'; * @example User types "000000" => 0| * @example 0| => User types "5" => 5| */ -export function createLeadingZeroesValidationPostprocessor( - decimalSeparator: string, - thousandSeparator: string, -): MaskitoPostprocessor { +export function createLeadingZeroesValidationPostprocessor({ + decimalSeparator, + thousandSeparator, + prefix, + postfix, +}: { + decimalSeparator: string; + thousandSeparator: string; + prefix: string; + postfix: string; +}): MaskitoPostprocessor { const trimLeadingZeroes = (value: string): string => { const escapedThousandSeparator = escapeRegExp(thousandSeparator); @@ -42,8 +49,13 @@ export function createLeadingZeroesValidationPostprocessor( return ({value, selection}) => { const [from, to] = selection; - const hasDecimalSeparator = value.includes(decimalSeparator); - const [integerPart, decimalPart = ''] = value.split(decimalSeparator); + const {cleanValue, extractedPrefix, extractedPostfix} = extractAffixes(value, { + prefix, + postfix, + }); + + const hasDecimalSeparator = cleanValue.includes(decimalSeparator); + const [integerPart, decimalPart = ''] = cleanValue.split(decimalSeparator); const zeroTrimmedIntegerPart = trimLeadingZeroes(integerPart); if (integerPart === zeroTrimmedIntegerPart) { @@ -55,9 +67,11 @@ export function createLeadingZeroesValidationPostprocessor( return { value: + extractedPrefix + zeroTrimmedIntegerPart + (hasDecimalSeparator ? decimalSeparator : '') + - decimalPart, + decimalPart + + extractedPostfix, selection: [Math.max(newFrom, 0), Math.max(newTo, 0)], }; }; diff --git a/projects/kit/src/lib/masks/number/processors/pseudo-character-preprocessor.ts b/projects/kit/src/lib/masks/number/processors/pseudo-character-preprocessor.ts index 76e5eacd0..52e25524c 100644 --- a/projects/kit/src/lib/masks/number/processors/pseudo-character-preprocessor.ts +++ b/projects/kit/src/lib/masks/number/processors/pseudo-character-preprocessor.ts @@ -1,23 +1,39 @@ import {MaskitoPreprocessor} from '@maskito/core'; +import {extractAffixes} from '../../../utils'; + /** * It replaces pseudo characters with valid one. * @example User types '.' (but separator is equal to comma) => dot is replaced with comma. * @example User types hyphen / en-dash / em-dash => it is replaced with minus. */ -export function createPseudoCharactersPreprocessor( - validCharacter: string, - pseudoCharacters: string[], -): MaskitoPreprocessor { +export function createPseudoCharactersPreprocessor({ + validCharacter, + pseudoCharacters, + prefix, + postfix, +}: { + validCharacter: string; + pseudoCharacters: string[]; + prefix: string; + postfix: string; +}): MaskitoPreprocessor { const pseudoCharactersRegExp = new RegExp(`[${pseudoCharacters.join('')}]`, 'gi'); return ({elementState, data}) => { const {value, selection} = elementState; + const {cleanValue, extractedPostfix, extractedPrefix} = extractAffixes(value, { + prefix, + postfix, + }); return { elementState: { selection, - value: value.replace(pseudoCharactersRegExp, validCharacter), + value: + extractedPrefix + + cleanValue.replace(pseudoCharactersRegExp, validCharacter) + + extractedPostfix, }, data: data.replace(pseudoCharactersRegExp, validCharacter), }; 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 1eafcc2dc..e03d9b8f1 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,23 +1,31 @@ import {MaskitoPreprocessor} from '@maskito/core'; -import {escapeRegExp} from '../../../utils'; +import {escapeRegExp, extractAffixes} from '../../../utils'; /** * It rejects new typed decimal separator if it already exists in text field. * Behaviour is similar to native (Chrome). * @example 1|23,45 => Press comma (decimal separator) => 1|23,45 (do nothing). */ -export function createRepeatedDecimalSeparatorPreprocessor( - decimalSeparator: string, -): MaskitoPreprocessor { +export function createRepeatedDecimalSeparatorPreprocessor({ + decimalSeparator, + prefix, + postfix, +}: { + decimalSeparator: string; + prefix: string; + postfix: string; +}): MaskitoPreprocessor { return ({elementState, data}) => { const {value, selection} = elementState; const [from, to] = selection; + const {cleanValue} = extractAffixes(value, {prefix, postfix}); + return { elementState, data: - !value.includes(decimalSeparator) || + !cleanValue.includes(decimalSeparator) || value.slice(from, to + 1).includes(decimalSeparator) ? data : data.replace(new RegExp(escapeRegExp(decimalSeparator), 'gi'), ''), diff --git a/projects/kit/src/lib/masks/number/processors/tests/leading-zeroes-validation-postprocessor.spec.ts b/projects/kit/src/lib/masks/number/processors/tests/leading-zeroes-validation-postprocessor.spec.ts index 5acfa233a..ce6882321 100644 --- a/projects/kit/src/lib/masks/number/processors/tests/leading-zeroes-validation-postprocessor.spec.ts +++ b/projects/kit/src/lib/masks/number/processors/tests/leading-zeroes-validation-postprocessor.spec.ts @@ -1,7 +1,12 @@ import {createLeadingZeroesValidationPostprocessor} from '../leading-zeroes-validation-postprocessor'; describe('createLeadingZeroesValidationPostprocessor', () => { - const processor = createLeadingZeroesValidationPostprocessor(',', ''); + const processor = createLeadingZeroesValidationPostprocessor({ + decimalSeparator: ',', + thousandSeparator: '', + prefix: '', + postfix: '', + }); const DUMMY_INITIAL_STATE = {value: '', selection: [0, 0]} as const; const process = ( 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 b10eaedbf..64ba8c508 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 @@ -1,7 +1,7 @@ import {MaskitoPostprocessor} from '@maskito/core'; import {CHAR_MINUS} from '../../../constants'; -import {escapeRegExp, identity} from '../../../utils'; +import {extractAffixes, identity} from '../../../utils'; /** * It adds symbol for separating thousands. @@ -22,21 +22,21 @@ export function createThousandSeparatorPostprocessor({ return identity; } - const prefixReg = new RegExp(`^${escapeRegExp(prefix)}${CHAR_MINUS}?`); - const postfixReg = new RegExp(`${escapeRegExp(postfix)}$`); const isAllSpaces = (...chars: string[]): boolean => chars.every(x => /\s/.test(x)); return ({value, selection}) => { - const [integerPart, decimalPart = ''] = value.split(decimalSeparator); + const {cleanValue, extractedPostfix, extractedPrefix} = extractAffixes(value, { + prefix, + postfix, + }); + + const [integerPart, decimalPart = ''] = cleanValue + .replace(CHAR_MINUS, '') + .split(decimalSeparator); const [initialFrom, initialTo] = selection; let [from, to] = selection; - const cleanIntegerPart = integerPart - .replace(prefixReg, '') - .replace(postfixReg, ''); - const [integerPartPrefix = ''] = integerPart.match(prefixReg) || []; - const [integerPartPostfix = ''] = integerPart.match(postfixReg) || []; - const processedIntegerPart = Array.from(cleanIntegerPart).reduceRight( + const processedIntegerPart = Array.from(integerPart).reduceRight( (formattedValuePart, char, i) => { const isLeadingThousandSeparator = !i && char === thousandSeparator; const isPositionForSeparator = @@ -82,11 +82,12 @@ export function createThousandSeparatorPostprocessor({ return { value: - integerPartPrefix + + extractedPrefix + + (cleanValue.includes(CHAR_MINUS) ? CHAR_MINUS : '') + processedIntegerPart + - integerPartPostfix + - (value.includes(decimalSeparator) ? decimalSeparator : '') + - decimalPart, + (cleanValue.includes(decimalSeparator) ? 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 11787ef5b..cd767b8e9 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 @@ -1,15 +1,22 @@ import {MaskitoPreprocessor} from '@maskito/core'; -import {escapeRegExp, identity} from '../../../utils'; +import {escapeRegExp, extractAffixes, identity} from '../../../utils'; /** * It drops decimal part if precision is zero. * @example User pastes '123.45' (but precision is zero) => 123 */ -export function createZeroPrecisionPreprocessor( - precision: number, - decimalSeparator: string, -): MaskitoPreprocessor { +export function createZeroPrecisionPreprocessor({ + precision, + decimalSeparator, + prefix, + postfix, +}: { + precision: number; + decimalSeparator: string; + prefix: string; + postfix: string; +}): MaskitoPreprocessor { if (precision > 0) { return identity; } @@ -18,8 +25,15 @@ export function createZeroPrecisionPreprocessor( return ({elementState, data}) => { const {value, selection} = elementState; + const {cleanValue, extractedPrefix, extractedPostfix} = extractAffixes(value, { + prefix, + postfix, + }); const [from, to] = selection; - const newValue = value.replace(decimalPartRegExp, ''); + const newValue = + extractedPrefix + + cleanValue.replace(decimalPartRegExp, '') + + extractedPostfix; return { elementState: { 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 691e41a12..babf8ef68 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 @@ -84,39 +84,159 @@ describe('Number (maskitoTransform)', () => { }); }); - // TODO https://github.com/taiga-family/maskito/issues/703 - xdescribe('`postfix` contains point and space (` lbs.`)', () => { + describe('`postfix` contains point and space (` lbs.`)', () => { let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS; - beforeEach(() => { - options = maskitoNumberOptionsGenerator({ - postfix: ' lbs.', - precision: 2, + describe('precision: 2', () => { + beforeEach(() => { + options = maskitoNumberOptionsGenerator({ + postfix: ' lbs.', + precision: 2, + }); }); - }); - it('Empty textfield => empty textfield', () => { - expect(maskitoTransform('', options)).toBe(''); - }); + it('Empty textfield => empty textfield', () => { + expect(maskitoTransform('', options)).toBe(''); + }); - it('Only postfix => Only postfix', () => { - expect(maskitoTransform(' lbs.', options)).toBe(' lbs.'); - }); + it('Only postfix => Only postfix', () => { + expect(maskitoTransform(' lbs.', options)).toBe(' lbs.'); + }); - it('5 => 5 lbs.', () => { - expect(maskitoTransform('5', options)).toBe('5 lbs.'); + it('5 => 5 lbs.', () => { + expect(maskitoTransform('5', options)).toBe('5 lbs.'); + }); + + it('0.42 => 0.42 lbs.', () => { + expect(maskitoTransform('0.42', options)).toBe('0.42 lbs.'); + }); + + it('1 000 => 1 000 lbs.', () => { + expect(maskitoTransform('1 000', options)).toBe('1 000 lbs.'); + }); + + it('1 000. => 1 000. lbs.', () => { + expect(maskitoTransform('1 000.', options)).toBe('1 000. lbs.'); + }); + + it('1 000 lbs. => 1 000 lbs.', () => { + expect(maskitoTransform('1 000 lbs.', options)).toBe('1 000 lbs.'); + }); }); - it('0.42 => 5 lbs.', () => { - expect(maskitoTransform('0.42', options)).toBe('0.42 lbs.'); + describe('precision: 0', () => { + beforeEach(() => { + options = maskitoNumberOptionsGenerator({ + postfix: ' lbs.', + precision: 0, + }); + }); + + it('Empty textfield => empty textfield', () => { + expect(maskitoTransform('', options)).toBe(''); + }); + + it('Only postfix => Only postfix', () => { + expect(maskitoTransform(' lbs.', options)).toBe(' lbs.'); + }); + + it('5 => 5 lbs.', () => { + expect(maskitoTransform('5', options)).toBe('5 lbs.'); + }); + + it('0.42 => 0 lbs.', () => { + expect(maskitoTransform('0.42', options)).toBe('0 lbs.'); + }); + + it('1 000 => 1 000 lbs.', () => { + expect(maskitoTransform('1 000', options)).toBe('1 000 lbs.'); + }); + + it('1 000. => 1 000 lbs.', () => { + expect(maskitoTransform('1 000.', options)).toBe('1 000 lbs.'); + }); + + it('1 000 lbs. => 1 000 lbs.', () => { + expect(maskitoTransform('1 000 lbs.', options)).toBe('1 000 lbs.'); + }); }); + }); - it('1 000 => 1 000 lbs.', () => { - expect(maskitoTransform('1 000', options)).toBe('1 000 lbs.'); + describe('`prefix` contains point and space (`lbs. `)', () => { + let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS; + + describe('precision: 2', () => { + beforeEach(() => { + options = maskitoNumberOptionsGenerator({ + prefix: 'lbs. ', + precision: 2, + }); + }); + + it('Empty textfield => empty textfield', () => { + expect(maskitoTransform('', options)).toBe(''); + }); + + it('Only prefix => Only prefix', () => { + expect(maskitoTransform('lbs. ', options)).toBe('lbs. '); + }); + + it('5 => lbs. 5', () => { + expect(maskitoTransform('5', options)).toBe('lbs. 5'); + }); + + it('0.42 => lbs. 0.42', () => { + expect(maskitoTransform('0.42', options)).toBe('lbs. 0.42'); + }); + + it('1 000 => lbs. 1 000', () => { + expect(maskitoTransform('1 000', options)).toBe('lbs. 1 000'); + }); + + it('1 000. => lbs. 1 000', () => { + expect(maskitoTransform('1 000.', options)).toBe('lbs. 1 000.'); + }); + + it('lbs. 1 000 => lbs. 1 000', () => { + expect(maskitoTransform('lbs. 1 000', options)).toBe('lbs. 1 000'); + }); }); - it('1 000 lbs. => 1 000 lbs.', () => { - expect(maskitoTransform('1 000 lbs.', options)).toBe('1 000 lbs.'); + describe('precision: 0', () => { + beforeEach(() => { + options = maskitoNumberOptionsGenerator({ + prefix: 'lbs. ', + precision: 0, + }); + }); + + it('Empty textfield => empty textfield', () => { + expect(maskitoTransform('', options)).toBe(''); + }); + + it('Only prefix => Only prefix', () => { + expect(maskitoTransform('lbs. ', options)).toBe('lbs. '); + }); + + it('5 => lbs. 5', () => { + expect(maskitoTransform('5', options)).toBe('lbs. 5'); + }); + + it('0.42 => lbs. 0', () => { + expect(maskitoTransform('0.42', options)).toBe('lbs. 0'); + }); + + it('1 000 => lbs. 1 000', () => { + expect(maskitoTransform('1 000', options)).toBe('lbs. 1 000'); + }); + + it('1 000. => lbs. 1 000', () => { + expect(maskitoTransform('1 000.', options)).toBe('lbs. 1 000'); + }); + + it('lbs. 1 000 => lbs. 1 000', () => { + expect(maskitoTransform('lbs. 1 000', options)).toBe('lbs. 1 000'); + }); }); }); }); diff --git a/projects/kit/src/lib/utils/extract-affixes.ts b/projects/kit/src/lib/utils/extract-affixes.ts new file mode 100644 index 000000000..f99b8295c --- /dev/null +++ b/projects/kit/src/lib/utils/extract-affixes.ts @@ -0,0 +1,20 @@ +import {escapeRegExp} from '.'; + +export function extractAffixes( + value: string, + {prefix, postfix}: {prefix: string; postfix: string}, +): { + extractedPrefix: string; + extractedPostfix: string; + cleanValue: string; +} { + const prefixRegExp = new RegExp(`^${escapeRegExp(prefix)}`); + const postfixRegExp = new RegExp(`${escapeRegExp(postfix)}$`); + + const [extractedPrefix = ''] = value.match(prefixRegExp) ?? []; + const [extractedPostfix = ''] = value.match(postfixRegExp) ?? []; + + const cleanValue = value.replace(prefixRegExp, '').replace(postfixRegExp, ''); + + return {extractedPrefix, extractedPostfix, cleanValue}; +} diff --git a/projects/kit/src/lib/utils/index.ts b/projects/kit/src/lib/utils/index.ts index 25994b95b..23a02ae52 100644 --- a/projects/kit/src/lib/utils/index.ts +++ b/projects/kit/src/lib/utils/index.ts @@ -9,6 +9,7 @@ export * from './date/segments-to-date'; export * from './date/to-date-string'; export * from './date/validate-date-string'; export * from './escape-reg-exp'; +export * from './extract-affixes'; export * from './find-common-beginning-substr'; export * from './get-focused'; export * from './identity';