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(),
+ }),
+ ],
+ };
+}