Skip to content

Commit

Permalink
fix(kit): Prefix/Postfix is incompatible if they end/start with t…
Browse files Browse the repository at this point in the history
…he same character (#366)
  • Loading branch information
nsbarsukov authored Jul 11, 2023
1 parent 501cf9c commit 06afbcb
Show file tree
Hide file tree
Showing 11 changed files with 360 additions and 157 deletions.

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions projects/demo/src/pages/cypress/cypress.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {ChangeDetectionStrategy, Component} from '@angular/core';
@Component({
selector: 'cypress-doc-page',
templateUrl: './cypress.template.html',
styleUrls: ['./cypress.style.less'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CypressDocPageComponent {}
8 changes: 7 additions & 1 deletion projects/demo/src/pages/cypress/cypress.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {TuiInputModule} from '@taiga-ui/kit';
import {CypressDocPageComponent} from './cypress.component';
import {TestDocExample1} from './examples/1-predicate/component';
import {TestDocExample2} from './examples/2-native-max-length/component';
import {TestDocExample3} from './examples/3-mirrored-prefix-postfix/component';

@NgModule({
imports: [
Expand All @@ -21,7 +22,12 @@ import {TestDocExample2} from './examples/2-native-max-length/component';
TuiAddonDocModule,
RouterModule.forChild(tuiGenerateRoutes(CypressDocPageComponent)),
],
declarations: [CypressDocPageComponent, TestDocExample1, TestDocExample2],
declarations: [
CypressDocPageComponent,
TestDocExample1,
TestDocExample2,
TestDocExample3,
],
exports: [CypressDocPageComponent],
})
export class CypressDocPageModule {}
5 changes: 5 additions & 0 deletions projects/demo/src/pages/cypress/cypress.style.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.tests-wrapper {
display: flex;
flex-direction: column;
gap: 3rem;
}
10 changes: 8 additions & 2 deletions projects/demo/src/pages/cypress/cypress.template.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
<tui-doc-page header="Cypress">
<ng-template pageTab="Tests">
<test-doc-example-1 id="predicate"></test-doc-example-1>
<div class="tests-wrapper">
<test-doc-example-1 id="predicate"></test-doc-example-1>

<test-doc-example-2 id="maxlength"></test-doc-example-2>
<test-doc-example-2 id="maxlength"></test-doc-example-2>

<test-doc-example-3
id="mirrored-prefix-postfix"
></test-doc-example-3>
</div>
</ng-template>
</tui-doc-page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {MaskitoOptions} from '@maskito/core';
import {maskitoNumberOptionsGenerator} from '@maskito/kit';

@Component({
selector: 'test-doc-example-3',
template: `
<input
value="$ 100 per day"
[maskito]="numberMask"
/>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TestDocExample3 {
readonly numberMask: MaskitoOptions = maskitoNumberOptionsGenerator({
prefix: '$ ',
postfix: ' per day',
});
}
26 changes: 20 additions & 6 deletions projects/kit/src/lib/processors/postfix-postprocessor.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import {MaskitoPostprocessor} from '@maskito/core';

import {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.endsWith(postfix) || // already valid
(!value && !initialElementState.value.endsWith(postfix)) // cases when developer wants input to be empty
) {
if (!value && !initialElementState.value.endsWith(postfix)) {
// cases when developer wants input to be empty (programmatically)
return {value, selection};
}

Expand All @@ -21,14 +21,28 @@ export function maskitoPostfixPostprocessorGenerator(
return {selection, value: value + postfix};
}

const initialValueBeforePostfix = initialElementState.value.replace(
postfixRE,
'',
);
const postfixWasModified =
initialElementState.selection[1] >= initialValueBeforePostfix.length;
const alreadyExistedValueBeforePostfix = findCommonBeginningSubstr(
initialValueBeforePostfix,
value,
);

return {
selection,
value: Array.from(postfix)
.reverse()
.reduce((newValue, char, index) => {
const i = newValue.length - 1 - index;
const isInitiallyMirroredChar =
alreadyExistedValueBeforePostfix[i] === char &&
postfixWasModified;

return newValue[i] !== char
return newValue[i] !== char || isInitiallyMirroredChar
? newValue.slice(0, i + 1) + char + newValue.slice(i + 1)
: newValue;
}, value),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {maskitoPostfixPostprocessorGenerator} from '../postfix-postprocessor';
describe('maskitoPostfixPostprocessorGenerator', () => {
const EMPTY_INPUT = {value: '', selection: [0, 0] as const};

describe('prefix is a single character', () => {
describe('postfix is a single character', () => {
const postprocessor = maskitoPostfixPostprocessorGenerator('%');

it('does not add prefix if input was initially empty', () => {
it('does not add postfix if input was initially empty', () => {
expect(postprocessor(EMPTY_INPUT, EMPTY_INPUT)).toEqual(EMPTY_INPUT);
});

Expand All @@ -31,10 +31,10 @@ describe('maskitoPostfixPostprocessorGenerator', () => {
});
});

describe('prefix consists of many characters', () => {
describe('postfix consists of many characters', () => {
const postprocessor = maskitoPostfixPostprocessorGenerator('.00');

it('does not add prefix if input was initially empty', () => {
it('does not add postfix if input was initially empty', () => {
expect(postprocessor(EMPTY_INPUT, EMPTY_INPUT)).toEqual(EMPTY_INPUT);
});

Expand Down Expand Up @@ -67,4 +67,31 @@ describe('maskitoPostfixPostprocessorGenerator', () => {
).toEqual({value: '100.00', selection: [4, 4]});
});
});

describe('postfix starts with the same character as other part of the value ends', () => {
it('$_100_per_kg => $_|100_|per_kg (select all digits and underscore) => Delete => $_|_per_kg', () => {
const postprocessor = maskitoPostfixPostprocessorGenerator('_per_kg');

expect(
postprocessor(
{value: '$_per_kg', selection: [2, 2]}, // after
{value: '$_100_per_kg', selection: ['$_'.length, '$_100_'.length]}, // initial
),
).toEqual({value: '$__per_kg', selection: [2, 2]});
});

it('$__100__per_kg => $__|100__|per_kg (select all digits and 2 underscore) => Delete => $__|__per_kg', () => {
const postprocessor = maskitoPostfixPostprocessorGenerator('__per_kg');

expect(
postprocessor(
{value: '$__per_kg', selection: [3, 3]}, // after
{
value: '$__100__per_kg',
selection: ['$__'.length, '$__100__'.length],
}, // initial
),
).toEqual({value: '$____per_kg', selection: [3, 3]});
});
});
});
13 changes: 13 additions & 0 deletions projects/kit/src/lib/utils/find-common-beginning-substr.ts
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions projects/kit/src/lib/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
@@ -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');
});
});

0 comments on commit 06afbcb

Please sign in to comment.