diff --git a/projects/demo-integrations/cypress/tests/kit/number/number-prefix-postfix.cy.ts b/projects/demo-integrations/cypress/tests/kit/number/number-prefix-postfix.cy.ts index 032a8ef4b..e24a15630 100644 --- a/projects/demo-integrations/cypress/tests/kit/number/number-prefix-postfix.cy.ts +++ b/projects/demo-integrations/cypress/tests/kit/number/number-prefix-postfix.cy.ts @@ -1,5 +1,6 @@ import {DemoPath} from '@demo/constants'; +import {BROWSER_SUPPORTS_REAL_EVENTS} from '../../../support/constants'; import {openNumberPage} from './utils'; describe('Number | Prefix & Postfix', () => { @@ -179,18 +180,88 @@ describe('Number | Prefix & Postfix', () => { }); }); - describe('works if the same character ends/starts', () => { - describe('[prefix]="$_" | [postfix]="_per_day"', () => { + describe('prefix/postfix ends/starts with the same character', () => { + describe('[prefix]="$_" | [postfix]="_per_day" (with caret guard)', () => { beforeEach(() => { openNumberPage('prefix=$_&postfix=_per_day'); + + cy.get('@input') + .should('have.value', '$__per_day') + .should('have.prop', 'selectionStart', '$_'.length) + .should('have.prop', 'selectionEnd', '$_'.length); }); - // TODO - it('$_|_per_day => Type Backspace => $_|_per_day', () => {}); + it('$_|_per_day => Type Backspace => $_|_per_day', () => { + cy.get('@input') + .type('{backspace}') + .should('have.value', '$__per_day') + .should('have.prop', 'selectionStart', '$_'.length) + .should('have.prop', 'selectionEnd', '$_'.length); + }); + + it('$_|_per_day => Type Delete => $_|_per_day', () => { + cy.get('@input') + .type('{del}') + .should('have.value', '$__per_day') + .should('have.prop', 'selectionStart', '$_'.length) + .should('have.prop', 'selectionEnd', '$_'.length); + }); - it('$_|_per_day => Type Delete => $_|_per_day', () => {}); + it('$_|_per_day => Select all + Delete => $_|_per_day', () => { + cy.get('@input') + .type('{selectAll}{del}') + .should('have.value', '$__per_day') + .should('have.prop', 'selectionStart', '$_'.length) + .should('have.prop', 'selectionEnd', '$_'.length); + }); + }); - it('$_|_per_day => Select all + Delete => $_|_per_day', () => {}); + describe('[prefix]="$ " | [postfix]=" per day" (without caret guard)', () => { + beforeEach(() => { + cy.visit(DemoPath.Cypress); + cy.get('#mirrored-prefix-postfix input') + .focus() + .type('{selectAll}{del}') + .should('have.value', '$ per day') + .should('have.prop', 'selectionStart', '$ '.length) + .should('have.prop', 'selectionEnd', '$ '.length) + .as('input'); + }); + + it('$ per day| => Type Backspace => $ per da|y', () => { + cy.get('@input') + .type('{moveToEnd}') + .type('{backspace}') + .should('have.value', '$ per day') + .should('have.prop', 'selectionStart', '$ per da'.length) + .should('have.prop', 'selectionEnd', '$ per da'.length); + }); + + it('$ per da|y => Type Backspace => $ per d|ay', () => { + cy.get('@input') + .type('{moveToEnd}{leftArrow}') + .type('{backspace}') + .should('have.value', '$ per day') + .should('have.prop', 'selectionStart', '$ per d'.length) + .should('have.prop', 'selectionEnd', '$ per d'.length); + }); + + it( + '$ p|er |day => Type Backspace => $ p|er da|y', + BROWSER_SUPPORTS_REAL_EVENTS, + () => { + cy.get('@input') + .type('{moveToEnd}') + .type('{leftArrow}'.repeat('day'.length)) + .realPress(['Shift', ...Array('1.1'.length).fill('ArrowLeft')]); + + cy.get('@input') + .type('{backspace}') + .should('have.value', '$ per day') + .should('have.prop', 'selectionStart', '$ p'.length) + .should('have.prop', 'selectionEnd', '$ p'.length); + }, + ); }); }); }); diff --git a/projects/demo/src/pages/cypress/examples/3-mirrored-prefix-postfix/component.ts b/projects/demo/src/pages/cypress/examples/3-mirrored-prefix-postfix/component.ts index 5da7d4c79..68087e81b 100644 --- a/projects/demo/src/pages/cypress/examples/3-mirrored-prefix-postfix/component.ts +++ b/projects/demo/src/pages/cypress/examples/3-mirrored-prefix-postfix/component.ts @@ -6,7 +6,7 @@ import {maskitoNumberOptionsGenerator} from '@maskito/kit'; selector: 'test-doc-example-3', template: ` `, @@ -15,6 +15,6 @@ import {maskitoNumberOptionsGenerator} from '@maskito/kit'; export class TestDocExample3 { readonly numberMask: MaskitoOptions = maskitoNumberOptionsGenerator({ prefix: '$ ', - postfix: ' per kg', + postfix: ' per day', }); } diff --git a/projects/kit/src/lib/processors/postfix-postprocessor.ts b/projects/kit/src/lib/processors/postfix-postprocessor.ts index eaba67934..4d3457802 100644 --- a/projects/kit/src/lib/processors/postfix-postprocessor.ts +++ b/projects/kit/src/lib/processors/postfix-postprocessor.ts @@ -1,14 +1,16 @@ import {MaskitoPostprocessor} from '@maskito/core'; -import {escapeRegExp, identity} from '../utils'; +import {escapeRegExp, findCommonBeginningSubstr, identity} from '../utils'; export function maskitoPostfixPostprocessorGenerator( postfix: string, ): MaskitoPostprocessor { + const postfixRE = new RegExp(`${escapeRegExp(postfix)}$`); + return postfix ? ({value, selection}, initialElementState) => { if (!value && !initialElementState.value.endsWith(postfix)) { - // cases when developer wants input to be empty + // cases when developer wants input to be empty (programmatically) return {value, selection}; } @@ -20,10 +22,15 @@ export function maskitoPostfixPostprocessorGenerator( } const initialValueBeforePostfix = initialElementState.value.replace( - new RegExp(`${escapeRegExp(postfix)}$`), + postfixRE, '', ); - const untouchedPart = getUntouched(initialValueBeforePostfix, value); + const postfixWasModified = + initialElementState.selection[1] >= initialValueBeforePostfix.length; + const alreadyExistedValueBeforePostfix = findCommonBeginningSubstr( + initialValueBeforePostfix, + value, + ); return { selection, @@ -31,8 +38,11 @@ export function maskitoPostfixPostprocessorGenerator( .reverse() .reduce((newValue, char, index) => { const i = newValue.length - 1 - index; + const isInitiallyMirroredChar = + alreadyExistedValueBeforePostfix[i] === char && + postfixWasModified; - return newValue[i] !== char || untouchedPart[i] === char + return newValue[i] !== char || isInitiallyMirroredChar ? newValue.slice(0, i + 1) + char + newValue.slice(i + 1) : newValue; }, value), @@ -40,17 +50,3 @@ export function maskitoPostfixPostprocessorGenerator( } : identity; } - -function getUntouched(initial: string, now: string): string { - let res = ''; - - for (let i = 0; i < now.length; i++) { - if (now[i] !== initial[i]) { - return res; - } - - res += now[i]; - } - - return res; -} diff --git a/projects/kit/src/lib/utils/find-common-beginning-substr.ts b/projects/kit/src/lib/utils/find-common-beginning-substr.ts new file mode 100644 index 000000000..64256a7a2 --- /dev/null +++ b/projects/kit/src/lib/utils/find-common-beginning-substr.ts @@ -0,0 +1,13 @@ +export function findCommonBeginningSubstr(a: string, b: string): string { + let res = ''; + + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return res; + } + + res += a[i]; + } + + return res; +} diff --git a/projects/kit/src/lib/utils/index.ts b/projects/kit/src/lib/utils/index.ts index a2abdbab1..93838d748 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 './find-common-beginning-substr'; export * from './get-focused'; export * from './get-object-from-entries'; export * from './identity'; diff --git a/projects/kit/src/lib/utils/tests/find-common-beginning-substr.spec.ts b/projects/kit/src/lib/utils/tests/find-common-beginning-substr.spec.ts new file mode 100644 index 000000000..92372cb84 --- /dev/null +++ b/projects/kit/src/lib/utils/tests/find-common-beginning-substr.spec.ts @@ -0,0 +1,20 @@ +import {findCommonBeginningSubstr} from '../find-common-beginning-substr'; + +describe('findCommonBeginningSubstr', () => { + it('returns common substring until all characters are equal', () => { + expect(findCommonBeginningSubstr('123_456', '123456')).toBe('123'); + }); + + it('returns empty string if any string is empty', () => { + expect(findCommonBeginningSubstr('123_456', '')).toBe(''); + expect(findCommonBeginningSubstr('', '123_456')).toBe(''); + }); + + it('returns empty string if the first characters are different', () => { + expect(findCommonBeginningSubstr('012345', '123')).toBe(''); + }); + + it('returns the whole string if all characters are equal', () => { + expect(findCommonBeginningSubstr('777999', '777999')).toBe('777999'); + }); +});