diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 66492cf22..02bc84662 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -111,7 +111,8 @@ jobs: - name: Run Cypress tests # Replace with npm run cy:run -- --spec "**/!(kit|recipes)/*.cy.ts" --config baseUrl="${{ env.UNIVERSAL_SERVER }}" # After this issue fix: https://github.com/cypress-io/cypress/issues/22407 - run: npm run cy:run -- --spec "**/(angular|ssr)/**/*.cy.ts" --config baseUrl="${{ env.UNIVERSAL_SERVER }}" + run: + npm run cy:run -- --spec "**/(angular|ssr|others)/**/*.cy.ts" --config baseUrl="${{ env.UNIVERSAL_SERVER }}" concurrency: group: e2e-${{ github.workflow }}-${{ github.ref }} diff --git a/projects/core/src/lib/mask.ts b/projects/core/src/lib/mask.ts index aec2a11a2..4b3cf9363 100644 --- a/projects/core/src/lib/mask.ts +++ b/projects/core/src/lib/mask.ts @@ -134,6 +134,12 @@ export class Maskito extends MaskHistory { }; } + private get maxLength(): number { + const {maxLength} = this.element; + + return maxLength === -1 ? Infinity : maxLength; + } + destroy(): void { this.eventListener.destroy(); this.teardowns.forEach(teardown => teardown?.()); @@ -294,6 +300,10 @@ export class Maskito extends MaskHistory { elementState.value.slice(0, from) + data + elementState.value.slice(to); const newElementState = this.postprocessor(maskModel, initialElementState); + if (newElementState.value.length > this.maxLength) { + return event.preventDefault(); + } + if (newPossibleValue !== newElementState.value) { event.preventDefault(); diff --git a/projects/demo-integrations/cypress/tests/others/native-maxlength-attribute.cy.ts b/projects/demo-integrations/cypress/tests/others/native-maxlength-attribute.cy.ts new file mode 100644 index 000000000..68fddf21c --- /dev/null +++ b/projects/demo-integrations/cypress/tests/others/native-maxlength-attribute.cy.ts @@ -0,0 +1,105 @@ +import {DemoPath} from '@demo/constants'; + +import {BROWSER_SUPPORTS_REAL_EVENTS} from '../../support/constants'; + +describe('Native attribute maxlength works', () => { + describe(' & overwriteMode = shift', () => { + beforeEach(() => { + cy.visit(DemoPath.Cypress); + cy.get('#maxlength input[maxlength="3"]') + .should('have.prop', 'maxlength', 3) + .as('input'); + }); + + it('accepts 2 digits', () => { + cy.get('@input').type('12').should('have.value', '12'); + }); + + it('accepts 3 digits', () => { + cy.get('@input').type('123').should('have.value', '123'); + }); + + it( + 'can replace selected digit by new one (even if length of the value is already equal to maxlength-property)', + BROWSER_SUPPORTS_REAL_EVENTS, + () => { + cy.get('@input').type('123').realPress(['Shift', 'ArrowLeft']); + + cy.get('@input').type('0').should('have.value', '120'); + }, + ); + + describe('rejects to enter digits more than maxlength-property', () => { + it('123| => Type 4 => 123|', () => { + cy.get('@input') + .type('1234') + .should('have.value', '123') + .should('have.prop', 'selectionStart', '123'.length) + .should('have.prop', 'selectionEnd', '123'.length); + }); + + it('12|3 => Type 0 => 12|3', () => { + cy.get('@input') + .type('123') + .type('{leftArrow}') + .type('0') + .should('have.value', '123') + .should('have.prop', 'selectionStart', '12'.length) + .should('have.prop', 'selectionEnd', '12'.length); + }); + + it('1|23 => Type 0 => 1|23', () => { + cy.get('@input') + .type('123') + .type('{leftArrow}'.repeat(2)) + .type('0') + .should('have.value', '123') + .should('have.prop', 'selectionStart', 1) + .should('have.prop', 'selectionEnd', 1); + }); + + it('|123 => Type 9 => |123', () => { + cy.get('@input') + .type('123') + .type('{moveToStart}') + .type('9') + .should('have.value', '123') + .should('have.prop', 'selectionStart', 0) + .should('have.prop', 'selectionEnd', 0); + }); + }); + }); + + describe(' & overwriteMode = replace', () => { + beforeEach(() => { + cy.visit(DemoPath.Cypress); + cy.get('#maxlength input[maxlength="6"]') + .should('have.prop', 'maxlength', 6) + .as('input'); + }); + + it('accepts valid 526ed3', () => { + cy.get('@input').type('526ed3').should('have.value', '526ED3'); + }); + + describe('does not allow to type characters more than [maxlength]', () => { + it('many letters', () => { + cy.get('@input') + .type('aaabbbcccdddeeefff') + .should('have.value', 'AAABBB'); + }); + + it('many digits', () => { + cy.get('@input').type('1234567890').should('have.value', '123456'); + }); + }); + + it("overwriteMode 'replace' works even if value's length is equal to [maxlength]", () => { + cy.get('@input') + .type('123456') + .type('{leftArrow}'.repeat(3)) + .type('09') + .should('have.value', '123096'); + }); + }); +}); diff --git a/projects/demo/src/app/app.routes.ts b/projects/demo/src/app/app.routes.ts index f395d8ff7..e77436b97 100644 --- a/projects/demo/src/app/app.routes.ts +++ b/projects/demo/src/app/app.routes.ts @@ -263,6 +263,7 @@ export const appRoutes: Routes = [ title: `Stackblitz Starter`, }, }, + // TODO: replace this page with Cypress Component Testing after angular13+ update { path: DemoPath.Cypress, loadChildren: async () => diff --git a/projects/demo/src/pages/cypress/cypress.module.ts b/projects/demo/src/pages/cypress/cypress.module.ts index 3a7d775da..ba400f255 100644 --- a/projects/demo/src/pages/cypress/cypress.module.ts +++ b/projects/demo/src/pages/cypress/cypress.module.ts @@ -9,6 +9,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'; @NgModule({ imports: [ @@ -20,7 +21,7 @@ import {TestDocExample1} from './examples/1-predicate/component'; TuiAddonDocModule, RouterModule.forChild(tuiGenerateRoutes(CypressDocPageComponent)), ], - declarations: [CypressDocPageComponent, TestDocExample1], + declarations: [CypressDocPageComponent, TestDocExample1, TestDocExample2], exports: [CypressDocPageComponent], }) export class CypressDocPageModule {} diff --git a/projects/demo/src/pages/cypress/cypress.template.html b/projects/demo/src/pages/cypress/cypress.template.html index 887203849..69d7efc32 100644 --- a/projects/demo/src/pages/cypress/cypress.template.html +++ b/projects/demo/src/pages/cypress/cypress.template.html @@ -1,5 +1,7 @@ + + diff --git a/projects/demo/src/pages/cypress/examples/2-native-max-length/component.ts b/projects/demo/src/pages/cypress/examples/2-native-max-length/component.ts new file mode 100644 index 000000000..37f6a4704 --- /dev/null +++ b/projects/demo/src/pages/cypress/examples/2-native-max-length/component.ts @@ -0,0 +1,35 @@ +import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {MaskitoOptions} from '@maskito/core'; +import {maskitoNumberOptionsGenerator} from '@maskito/kit'; + +@Component({ + selector: 'test-doc-example-2', + template: ` + + + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TestDocExample2 { + readonly numberMask = maskitoNumberOptionsGenerator({ + thousandSeparator: ' ', + }); + + readonly hexColorMask: MaskitoOptions = { + mask: /^[A-F\d]*$/gi, + overwriteMode: 'replace', + postprocessors: [ + ({value, selection}) => ({ + selection, + value: value.toUpperCase(), + }), + ], + }; +}