From fefdcff14f5579727ae3aeb24400f728b25029af Mon Sep 17 00:00:00 2001 From: Nikita Barsukov Date: Tue, 3 Dec 2024 19:01:44 +0300 Subject: [PATCH] fix(legacy): `InputNumber` has bad support of dynamic postfix (#9899) --- .../tests/input-number/dynamic-postfix.cy.ts | 126 ++++++++++++++++++ .../input-number/input-number.component.ts | 9 ++ 2 files changed, 135 insertions(+) create mode 100644 projects/demo-cypress/src/tests/input-number/dynamic-postfix.cy.ts diff --git a/projects/demo-cypress/src/tests/input-number/dynamic-postfix.cy.ts b/projects/demo-cypress/src/tests/input-number/dynamic-postfix.cy.ts new file mode 100644 index 000000000000..09e1237d9b1d --- /dev/null +++ b/projects/demo-cypress/src/tests/input-number/dynamic-postfix.cy.ts @@ -0,0 +1,126 @@ +import '@angular/common/locales/global/ru'; + +import {I18nPluralPipe} from '@angular/common'; +import {ChangeDetectionStrategy, Component} from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {TuiRoot} from '@taiga-ui/core'; +import {TuiInputNumberModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy'; + +@Component({ + standalone: true, + imports: [ + FormsModule, + I18nPluralPipe, + TuiInputNumberModule, + TuiRoot, + TuiTextfieldControllerModule, + ], + template: ` + + + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TestInputNumberWithPostfix { + protected value: number | null = null; + protected pluralMap = { + one: 'секунда', + few: 'секунды', + many: 'секунд', + other: 'секунды', + }; +} + +describe('InputNumber with dynamic postfix', () => { + describe('Plural forms of seconds (locale ru-RU)', () => { + beforeEach(() => { + cy.mount(TestInputNumberWithPostfix); + cy.get('tui-input-number input').as('textfield'); + }); + + const withPostfix = ( + range: number[], + postfix: string, + ): ReadonlyArray<{typed: string; value: string}> => + range.map(String).map((x) => ({typed: x, value: `${x} ${postfix}`})); + + ( + [ + {typed: '1', value: '1 секунда'}, + ...withPostfix(range(2, 4), 'секунды'), + ...withPostfix(range(5, 20), 'секунд'), + {typed: '21', value: '21 секунда'}, + ...withPostfix(range(22, 24), 'секунды'), + ...withPostfix(range(25, 30), 'секунд'), + {typed: '31', value: '31 секунда'}, + ] as const + ).forEach(({typed, value}) => { + it(`Type ${typed} => ${value}`, () => { + cy.get('@textfield').type(typed); + + cy.get('@textfield') + .should('have.value', value) + .should('have.prop', 'selectionStart', typed.length) + .should('have.prop', 'selectionEnd', typed.length); + }); + }); + + it('Press sequentially 123', () => { + cy.get('@textfield') + .type('1') + .should('have.value', '1 секунда') + .should('have.prop', 'selectionStart', 1) + .should('have.prop', 'selectionEnd', 1) + .type('2') + .should('have.value', '12 секунд') + .should('have.prop', 'selectionStart', 2) + .should('have.prop', 'selectionEnd', 2) + .type('3') + .should('have.value', '123 секунды') + .should('have.prop', 'selectionStart', 3) + .should('have.prop', 'selectionEnd', 3); + }); + + it('12|3 секунды => Backspace => 1|3 секунд', () => { + cy.get('@textfield') + .type('123') + .should('have.value', '123 секунды') + .type('{leftArrow}') + .should('have.prop', 'selectionStart', 2) + .should('have.prop', 'selectionEnd', 2) + .type('{backspace}') + .should('have.value', '13 секунд') + .should('have.prop', 'selectionStart', 1) + .should('have.prop', 'selectionEnd', 1); + }); + + it('1|3 секунды => Delete => 1| секунда', () => { + cy.get('@textfield') + .type('13') + .type('{leftArrow}') + .should('have.value', '13 секунд') + .should('have.prop', 'selectionStart', 1) + .should('have.prop', 'selectionEnd', 1) + .type('{del}') + .should('have.value', '1 секунда') + .should('have.prop', 'selectionStart', 1) + .should('have.prop', 'selectionEnd', 1); + }); + + it('1| секунда => Backspace => Empty textfield', () => { + cy.get('@textfield') + .type('1') + .should('have.value', '1 секунда') + .type('{backspace}') + .should('have.value', ''); + }); + }); +}); + +export function range(from: number, to: number): number[] { + return new Array(to - from + 1).fill(null).map((_, i) => from + i); +} diff --git a/projects/legacy/components/input-number/input-number.component.ts b/projects/legacy/components/input-number/input-number.component.ts index df6888c60872..4eb1eefd2625 100644 --- a/projects/legacy/components/input-number/input-number.component.ts +++ b/projects/legacy/components/input-number/input-number.component.ts @@ -9,6 +9,7 @@ import { } from '@angular/core'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; import type {MaskitoOptions} from '@maskito/core'; +import {maskitoInitialCalibrationPlugin} from '@maskito/core'; import { maskitoCaretGuard, maskitoNumberOptionsGenerator, @@ -336,11 +337,19 @@ export class TuiInputNumberComponent decimalZeroPadding: decimalMode === 'always', }; const {plugins, ...options} = maskitoNumberOptionsGenerator(generatorParams); + const initialCalibrationPlugin = maskitoInitialCalibrationPlugin( + maskitoNumberOptionsGenerator({ + ...generatorParams, + min: Number.MIN_SAFE_INTEGER, + max: Number.MAX_SAFE_INTEGER, + }), + ); return { ...options, plugins: [ ...plugins, + initialCalibrationPlugin, maskitoCaretGuard((value) => [ prefix.length, value.length - postfix.length,