diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bd82057..5c11837e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# 19.0.3(2024-12-05) + +### Contributing Fix + +- Fix ([#1455](https://github.com/JsDaddy/ngx-mask/pull/1455)) + +### Fix + +- Fix ([#1472](https://github.com/JsDaddy/ngx-mask/pull/1472)) +- Fix ([#1415](https://github.com/JsDaddy/ngx-mask/pull/1415)) + # 19.0.2(2024-12-03) ### Fix diff --git a/README.md b/README.md index 2727163a..a3e63565 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,14 @@ const maskConfigFunction: () => Partial = () => { Then, just define masks in inputs. +## Actively supported versions + +ngx-mask follows the official Angular support policy, supporting the Active and LTS (Long-Term Support) versions of Angular. As of the latest release, Angular v17 and newer are supported. + +Projects using Angular versions outside the supported range (e.g., older than v17) should use the last compatible version of ngx-mask. However, these versions will no longer receive updates, bug fixes, or new features. + +For detailed information about Angular's versioning and support schedule, visit the official [Angular releases page](https://angular.dev/reference/releases#actively-supported-versions). + ## Usage Text [documentation](https://github.com/JsDaddy/ngx-mask/blob/develop/USAGE.md) diff --git a/USAGE.md b/USAGE.md index 8e60b629..a29fdfdc 100644 --- a/USAGE.md +++ b/USAGE.md @@ -104,10 +104,20 @@ Input value: 789HelloWorld Masked value: (Hel-loW) ``` -### Custom pattern for this +### Custom Pattern Definition for Input Masks -You can define custom pattern and specify symbol to be rendered in input field. -Patterns may conflict with such letters as h, d, m, s, because we use these characters for dates. +You can define a custom pattern and specify a unique symbol to be rendered in the input field. + +Important Notes: + +Reserved Characters: Certain characters (h, d, m, s) are reserved for date patterns and should not be used in custom patterns to avoid conflicts. + +```html +Special Symbol *: The * character is reserved for patterns like 0*, which means any length of digits +can appear before the asterisk. Avoid using this symbol in custom patterns. + + +``` ```typescript pattern = { diff --git a/package.json b/package.json index 40ec6ef6..6265dfd8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ngx-mask", - "version": "19.0.2", + "version": "19.0.3", "description": "Awesome ngx mask", "license": "MIT", "engines": { diff --git a/projects/ngx-mask-lib/package.json b/projects/ngx-mask-lib/package.json index 418437ff..7abb7f1f 100644 --- a/projects/ngx-mask-lib/package.json +++ b/projects/ngx-mask-lib/package.json @@ -1,6 +1,6 @@ { "name": "ngx-mask", - "version": "19.0.2", + "version": "19.0.3", "description": "awesome ngx mask", "keywords": [ "ng2-mask", diff --git a/projects/ngx-mask-lib/src/lib/ngx-mask-applier.service.ts b/projects/ngx-mask-lib/src/lib/ngx-mask-applier.service.ts index aad310f5..b2bd5f2b 100644 --- a/projects/ngx-mask-lib/src/lib/ngx-mask-applier.service.ts +++ b/projects/ngx-mask-lib/src/lib/ngx-mask-applier.service.ts @@ -487,7 +487,7 @@ export class NgxMaskApplierService { ? processedPosition + 1 : processedPosition; cursor += 1; - this._shiftStep(maskExpression, cursor, inputArray.length); + this._shiftStep(cursor); i--; if (this.leadZeroDateTime) { result += '0'; @@ -527,7 +527,7 @@ export class NgxMaskApplierService { ? processedPosition + 1 : processedPosition; cursor += 1; - this._shiftStep(maskExpression, cursor, inputArray.length); + this._shiftStep(cursor); i--; if (this.leadZeroDateTime) { result += '0'; @@ -576,7 +576,7 @@ export class NgxMaskApplierService { ? processedPosition + 1 : processedPosition; cursor += 1; - this._shiftStep(maskExpression, cursor, inputArray.length); + this._shiftStep(cursor); i--; if (this.leadZeroDateTime) { @@ -649,7 +649,7 @@ export class NgxMaskApplierService { ? processedPosition + 1 : processedPosition; cursor += 1; - this._shiftStep(maskExpression, cursor, inputArray.length); + this._shiftStep(cursor); i--; if (this.leadZeroDateTime) { result += '0'; @@ -672,13 +672,13 @@ export class NgxMaskApplierService { ) { result += maskExpression[cursor]; cursor++; - this._shiftStep(maskExpression, cursor, inputArray.length); + this._shiftStep(cursor); i--; } else if ( maskExpression[cursor] === MaskExpression.NUMBER_NINE && this.showMaskTyped ) { - this._shiftStep(maskExpression, cursor, inputArray.length); + this._shiftStep(cursor); } else if ( this.patterns[maskExpression[cursor] ?? MaskExpression.EMPTY_STRING] && this.patterns[maskExpression[cursor] ?? MaskExpression.EMPTY_STRING]?.optional @@ -738,6 +738,7 @@ export class NgxMaskApplierService { } } if ( + result[processedPosition - 1] && result.length + 1 === maskExpression.length && this.specialCharacters.indexOf( maskExpression[maskExpression.length - 1] ?? MaskExpression.EMPTY_STRING @@ -982,11 +983,8 @@ export class NgxMaskApplierService { return char; } - private _shiftStep(maskExpression: string, cursor: number, inputLength: number) { - const shiftStep: number = /[*?]/g.test(maskExpression.slice(0, cursor)) - ? inputLength - : cursor; - this._shift.add(shiftStep + this.prefix.length || 0); + private _shiftStep(cursor: number) { + this._shift.add(cursor + this.prefix.length || 0); } protected _compareOrIncludes(value: T, comparedValue: T | T[], excludedValue: T): boolean { diff --git a/projects/ngx-mask-lib/src/lib/ngx-mask.config.ts b/projects/ngx-mask-lib/src/lib/ngx-mask.config.ts index 5af2a662..cb9c5a05 100644 --- a/projects/ngx-mask-lib/src/lib/ngx-mask.config.ts +++ b/projects/ngx-mask-lib/src/lib/ngx-mask.config.ts @@ -16,7 +16,7 @@ export type NgxMaskConfig = { shownMaskExpression: string; specialCharacters: string[] | readonly string[]; dropSpecialCharacters: boolean | string[] | readonly string[]; - hiddenInput: boolean | null; + hiddenInput: boolean; validation: boolean; separatorLimit: string; apm: boolean; @@ -52,7 +52,7 @@ export const initialConfig: NgxMaskConfig = { showMaskTyped: false, placeHolderCharacter: '_', dropSpecialCharacters: true, - hiddenInput: null, + hiddenInput: false, shownMaskExpression: '', separatorLimit: '', allowNegativeNumbers: false, diff --git a/projects/ngx-mask-lib/src/lib/ngx-mask.directive.ts b/projects/ngx-mask-lib/src/lib/ngx-mask.directive.ts index c49f86fd..b58573fa 100644 --- a/projects/ngx-mask-lib/src/lib/ngx-mask.directive.ts +++ b/projects/ngx-mask-lib/src/lib/ngx-mask.directive.ts @@ -164,6 +164,9 @@ export class NgxMaskDirective implements ControlValueAccessor, OnChanges, Valida } if (hiddenInput) { this._maskService.hiddenInput = hiddenInput.currentValue; + if (hiddenInput.previousValue === true && hiddenInput.currentValue === false) { + this._inputValue.set(this._maskService.actualValue); + } } if (showMaskTyped) { this._maskService.showMaskTyped = showMaskTyped.currentValue; @@ -1115,11 +1118,12 @@ export class NgxMaskDirective implements ControlValueAccessor, OnChanges, Valida this._maskService.maskExpression = maskValue; } } else { + const cleanMask = this._maskService.removeMask(mask); const check: boolean = this._maskService .removeMask(this._inputValue()) ?.split(MaskExpression.EMPTY_STRING) .every((character, index) => { - const indexMask = mask.charAt(index); + const indexMask = cleanMask.charAt(index); return this._maskService._checkSymbolMask(character, indexMask); }); diff --git a/projects/ngx-mask-lib/src/lib/ngx-mask.service.ts b/projects/ngx-mask-lib/src/lib/ngx-mask.service.ts index 1c3f62ae..064e88a1 100644 --- a/projects/ngx-mask-lib/src/lib/ngx-mask.service.ts +++ b/projects/ngx-mask-lib/src/lib/ngx-mask.service.ts @@ -38,6 +38,16 @@ export class NgxMaskService extends NgxMaskApplierService { private readonly _renderer = inject(Renderer2, { optional: true }); + /** + * Applies the mask to the input value. + * @param inputValue The input value to be masked. + * @param maskExpression The mask expression to apply. + * @param position The position in the input value. + * @param justPasted Whether the value was just pasted. + * @param backspaced Whether the value was backspaced. + * @param cb Callback function. + * @returns The masked value. + */ public override applyMask( inputValue: string, maskExpression: string, @@ -47,18 +57,25 @@ export class NgxMaskService extends NgxMaskApplierService { // eslint-disable-next-line @typescript-eslint/no-empty-function cb: (...args: any[]) => any = () => {} ): string { + // If no mask expression, return the input value or the actual value if (!maskExpression) { return inputValue !== this.actualValue ? this.actualValue : inputValue; } + + // Show mask in input if required this.maskIsShown = this.showMaskTyped ? this.showMaskInInput() : MaskExpression.EMPTY_STRING; + + // Handle specific mask expressions if (this.maskExpression === MaskExpression.IP && this.showMaskTyped) { this.maskIsShown = this.showMaskInInput(inputValue || MaskExpression.HASH); } if (this.maskExpression === MaskExpression.CPF_CNPJ && this.showMaskTyped) { this.maskIsShown = this.showMaskInInput(inputValue || MaskExpression.HASH); } + + // Handle empty input value with mask typed if (!inputValue && this.showMaskTyped) { this.formControlResult(this.prefix); return `${this.prefix}${this.maskIsShown}${this.suffix}`; @@ -70,41 +87,87 @@ export class NgxMaskService extends NgxMaskApplierService { : MaskExpression.EMPTY_STRING; let newInputValue = ''; let newPosition = position; - if (this.hiddenInput !== null && !this.writingValue) { + + // Handle hidden input or input with asterisk symbol + if ( + (this.hiddenInput || + (inputValue && inputValue.indexOf(MaskExpression.SYMBOL_STAR) >= 0)) && + !this.writingValue + ) { let actualResult: string[] = inputValue && inputValue.length === 1 ? inputValue.split(MaskExpression.EMPTY_STRING) : this.actualValue.split(MaskExpression.EMPTY_STRING); + + // Handle backspace + if (backspaced) { + actualResult = actualResult + .slice(0, position) + .concat(actualResult.slice(position + 1)); + } + + // Remove mask if showMaskTyped is true + if (this.showMaskTyped) { + // eslint-disable-next-line no-param-reassign + inputValue = this.removeMask(inputValue); + actualResult = this.removeMask(actualResult.join('')).split( + MaskExpression.EMPTY_STRING + ); + } + + // Handle selection start and end if (typeof this.selStart === 'object' && typeof this.selEnd === 'object') { this.selStart = Number(this.selStart); this.selEnd = Number(this.selEnd); } else { - // eslint-disable-next-line no-unused-expressions,@typescript-eslint/no-unused-expressions - inputValue !== MaskExpression.EMPTY_STRING && actualResult.length - ? typeof this.selStart === 'number' && typeof this.selEnd === 'number' - ? inputValue.length > actualResult.length - ? actualResult.splice(this.selStart, 0, getSymbol) - : inputValue.length < actualResult.length - ? actualResult.length - inputValue.length === 1 - ? backspaced - ? actualResult.splice(this.selStart - 1, 1) - : actualResult.splice(inputValue.length - 1, 1) - : actualResult.splice(this.selStart, this.selEnd - this.selStart) - : null - : null - : (actualResult = []); + if (inputValue !== MaskExpression.EMPTY_STRING && actualResult.length) { + if (typeof this.selStart === 'number' && typeof this.selEnd === 'number') { + if (inputValue.length > actualResult.length) { + actualResult.splice(this.selStart, 0, getSymbol); + } else if (inputValue.length < actualResult.length) { + if (actualResult.length - inputValue.length === 1) { + if (backspaced) { + actualResult.splice(this.selStart - 1, 1); + } else { + actualResult.splice(inputValue.length - 1, 1); + } + } else { + actualResult.splice(this.selStart, this.selEnd - this.selStart); + } + } + } + } else { + actualResult = []; + } } + + // Remove mask if showMaskTyped is true and hiddenInput is false if (this.showMaskTyped && !this.hiddenInput) { newInputValue = this.removeMask(inputValue); } - newInputValue = - this.actualValue.length && actualResult.length <= inputValue.length - ? this.shiftTypedSymbols(actualResult.join(MaskExpression.EMPTY_STRING)) - : inputValue; + + // Handle actual value length + if (this.actualValue.length) { + if (actualResult.length < inputValue.length) { + newInputValue = this.shiftTypedSymbols( + actualResult.join(MaskExpression.EMPTY_STRING) + ); + } else if (actualResult.length === inputValue.length) { + newInputValue = actualResult.join(MaskExpression.EMPTY_STRING); + } else { + newInputValue = inputValue; + } + } else { + newInputValue = inputValue; + } } + + // Handle just pasted input if (justPasted && (this.hiddenInput || !this.hiddenInput)) { newInputValue = inputValue; } + + // Handle backspace with special characters if ( backspaced && this.specialCharacters.indexOf( @@ -115,6 +178,8 @@ export class NgxMaskService extends NgxMaskApplierService { ) { newInputValue = this.currentValue; } + + // Handle deleted special character if (this.deletedSpecialCharacter && newPosition) { if ( this.specialCharacters.includes( @@ -130,14 +195,17 @@ export class NgxMaskService extends NgxMaskApplierService { this.deletedSpecialCharacter = false; } + + // Remove mask if showMaskTyped is true and placeHolderCharacter length is 1 if ( this.showMaskTyped && this.placeHolderCharacter.length === 1 && !this.leadZeroDateTime ) { - newInputValue = this.removeMask(inputValue); + newInputValue = this.removeMask(newInputValue); } + // Handle mask changed if (this.maskChanged) { newInputValue = inputValue; } else { @@ -145,6 +213,7 @@ export class NgxMaskService extends NgxMaskApplierService { Boolean(newInputValue) && newInputValue.length ? newInputValue : inputValue; } + // Handle showMaskTyped and keepCharacterPositions if ( this.showMaskTyped && this.keepCharacterPositions && @@ -161,6 +230,7 @@ export class NgxMaskService extends NgxMaskApplierService { : `${this.prefix}${this.maskIsShown}${this.suffix}`; } + // Apply the mask using the parent class method const result: string = super.applyMask( newInputValue, maskExpression, @@ -171,6 +241,7 @@ export class NgxMaskService extends NgxMaskApplierService { ); this.actualValue = this.getActualValue(result); + // handle some separator implications: // a.) adjust decimalMarker default (. -> ,) if thousandSeparator is a dot if ( @@ -190,6 +261,7 @@ export class NgxMaskService extends NgxMaskApplierService { ); } + // Update previous and current values if (result || result === '') { this.previousValue = this.currentValue; this.currentValue = result; @@ -200,6 +272,7 @@ export class NgxMaskService extends NgxMaskApplierService { (this.previousValue === this.currentValue && justPasted); } + // Propagate the input value back to the Angular model // eslint-disable-next-line no-unused-expressions,@typescript-eslint/no-unused-expressions this._emitValue ? this.writingValue && this.triggerOnMaskChange @@ -207,18 +280,18 @@ export class NgxMaskService extends NgxMaskApplierService { : this.formControlResult(result) : ''; + // Handle hidden input and showMaskTyped if (!this.showMaskTyped || (this.showMaskTyped && this.hiddenInput)) { if (this.hiddenInput) { - if (backspaced) { - return this.hideInput(result, this.maskExpression); - } return `${this.hideInput(result, this.maskExpression)}${this.maskIsShown.slice(result.length)}`; } return result; } + const resLen: number = result.length; const prefNmask = `${this.prefix}${this.maskIsShown}${this.suffix}`; + // Handle specific mask expressions if (this.maskExpression.includes(MaskExpression.HOURS)) { const countSkipedSymbol = this._numberSkipedSymbols(result); return `${result}${prefNmask.slice(resLen + countSkipedSymbol)}`; @@ -228,6 +301,7 @@ export class NgxMaskService extends NgxMaskApplierService { ) { return `${result}${prefNmask}`; } + return `${result}${prefNmask.slice(resLen)}`; } @@ -523,7 +597,7 @@ export class NgxMaskService extends NgxMaskApplierService { */ private formControlResult(inputValue: string): void { if (this.writingValue && !inputValue) { - this.onChange(this.outputTransformFn('')); + this.onChange(this.outputTransformFn(null)); return; } if (this.writingValue || (!this.triggerOnMaskChange && this.maskChanged)) { diff --git a/projects/ngx-mask-lib/src/test/add-prefix.spec.ts b/projects/ngx-mask-lib/src/test/add-prefix.spec.ts index 6a277d6f..e36beb7f 100644 --- a/projects/ngx-mask-lib/src/test/add-prefix.spec.ts +++ b/projects/ngx-mask-lib/src/test/add-prefix.spec.ts @@ -2,7 +2,7 @@ import type { ComponentFixture } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { TestMaskComponent } from './utils/test-component.component'; -import { equal } from './utils/test-functions.component'; +import { equal, Paste } from './utils/test-functions.component'; import { NgxMaskDirective, provideNgxMask } from 'ngx-mask'; describe('Directive: Mask (Add prefix)', () => { @@ -38,18 +38,18 @@ describe('Directive: Mask (Add prefix)', () => { equal('123456789', '+55 (12) 3456-789_', fixture); equal('1234567890', '+55 (12) 3456-7890', fixture); equal('12345678901', '+55 (12) 3 4567-8901', fixture); - equal('+55 (1', '+55 (1_) ____-____', fixture); - equal('+55 (12', '+55 (12) ____-____', fixture); - equal('+55 (12)', '+55 (12) ____-____', fixture); - equal('+55 (12) 3', '+55 (12) 3___-____', fixture); - equal('+55 (12) 34', '+55 (12) 34__-____', fixture); - equal('+55 (12) 345', '+55 (12) 345_-____', fixture); - equal('+55 (12) 3456', '+55 (12) 3456-____', fixture); - equal('+55 (12) 3456-7', '+55 (12) 3456-7___', fixture); - equal('+55 (12) 3456-78', '+55 (12) 3456-78__', fixture); - equal('+55 (12) 3456-789', '+55 (12) 3456-789_', fixture); - equal('+55 (12) 3456-7890', '+55 (12) 3456-7890', fixture); - equal('+55 (12) 3 4567-8901', '+55 (12) 3 4567-8901', fixture); + equal('+55 (1', '+55 (1_) ____-____', fixture, false, Paste); + equal('+55 (12', '+55 (12) ____-____', fixture, false, Paste); + equal('+55 (12)', '+55 (12) ____-____', fixture, false, Paste); + equal('+55 (12) 3', '+55 (12) 3___-____', fixture, false, Paste); + equal('+55 (12) 34', '+55 (12) 34__-____', fixture, false, Paste); + equal('+55 (12) 345', '+55 (12) 345_-____', fixture, false, Paste); + equal('+55 (12) 3456', '+55 (12) 3456-____', fixture, false, Paste); + equal('+55 (12) 3456-7', '+55 (12) 3456-7___', fixture, false, Paste); + equal('+55 (12) 3456-78', '+55 (12) 3456-78__', fixture, false, Paste); + equal('+55 (12) 3456-789', '+55 (12) 3456-789_', fixture, false, Paste); + equal('+55 (12) 3456-7890', '+55 (12) 3456-7890', fixture, false, Paste); + equal('+55 (12) 3 4567-8901', '+55 (12) 3 4567-8901', fixture, false, Paste); }); it('dropSpecialCharacters false should return value with prefix', () => { component.mask.set('00-000-000-00'); @@ -69,15 +69,15 @@ describe('Directive: Mask (Add prefix)', () => { it('should delete prefix in pasted content', () => { component.mask.set('AAA-AAA-AAA'); component.prefix.set('FOO-'); - equal('FOO-D', 'FOO-D', fixture); - equal('FOO-DD', 'FOO-DD', fixture); - equal('FOO-DDD', 'FOO-DDD', fixture); - equal('FOO-DDD-D', 'FOO-DDD-D', fixture); - equal('FOO-DDD-DD', 'FOO-DDD-DD', fixture); - equal('FOO-DDD-DDD', 'FOO-DDD-DDD', fixture); - equal('FOO-DDD-DDD-D', 'FOO-DDD-DDD-D', fixture); - equal('FOO-DDD-DDD-DD', 'FOO-DDD-DDD-DD', fixture); - equal('FOO-DDD-DDD-DDD', 'FOO-DDD-DDD-DDD', fixture); + equal('FOO-D', 'FOO-D', fixture, false, Paste); + equal('FOO-DD', 'FOO-DD', fixture, false, Paste); + equal('FOO-DDD', 'FOO-DDD', fixture, false, Paste); + equal('FOO-DDD-D', 'FOO-DDD-D', fixture, false, Paste); + equal('FOO-DDD-DD', 'FOO-DDD-DD', fixture, false, Paste); + equal('FOO-DDD-DDD', 'FOO-DDD-DDD', fixture, false, Paste); + equal('FOO-DDD-DDD-D', 'FOO-DDD-DDD-D', fixture, false, Paste); + equal('FOO-DDD-DDD-DD', 'FOO-DDD-DDD-DD', fixture, false, Paste); + equal('FOO-DDD-DDD-DDD', 'FOO-DDD-DDD-DDD', fixture, false, Paste); expect(component.form.value).toEqual('DDDDDDDDD'); }); @@ -85,15 +85,15 @@ describe('Directive: Mask (Add prefix)', () => { component.mask.set('AAA-AAA-AAA'); component.prefix.set('FOO-'); component.dropSpecialCharacters.set(false); - equal('FOO-S', 'FOO-S', fixture); - equal('FOO-SS', 'FOO-SS', fixture); - equal('FOO-SSS', 'FOO-SSS', fixture); - equal('FOO-SSS-S', 'FOO-SSS-S', fixture); - equal('FOO-SSS-SS', 'FOO-SSS-SS', fixture); - equal('FOO-SSS-SSS', 'FOO-SSS-SSS', fixture); - equal('FOO-SSS-SSS-S', 'FOO-SSS-SSS-S', fixture); - equal('FOO-SSS-SSS-SS', 'FOO-SSS-SSS-SS', fixture); - equal('FOO-SSS-SSS-SSS', 'FOO-SSS-SSS-SSS', fixture); + equal('FOO-S', 'FOO-S', fixture, false, Paste); + equal('FOO-SS', 'FOO-SS', fixture, false, Paste); + equal('FOO-SSS', 'FOO-SSS', fixture, false, Paste); + equal('FOO-SSS-S', 'FOO-SSS-S', fixture, false, Paste); + equal('FOO-SSS-SS', 'FOO-SSS-SS', fixture, false, Paste); + equal('FOO-SSS-SSS', 'FOO-SSS-SSS', fixture, false, Paste); + equal('FOO-SSS-SSS-S', 'FOO-SSS-SSS-S', fixture, false, Paste); + equal('FOO-SSS-SSS-SS', 'FOO-SSS-SSS-SS', fixture, false, Paste); + equal('FOO-SSS-SSS-SSS', 'FOO-SSS-SSS-SSS', fixture, false, Paste); expect(component.form.value).toEqual('FOO-SSS-SSS-SSS'); }); @@ -142,21 +142,21 @@ describe('Directive: Mask (Add prefix)', () => { expect(component.form.value).toBe('KZ123 123'); }); - it('should remove prefix when setValue triggerOnMaskChange = false', () => { + it('should remove prefix when setValue triggerOnMaskChange = false & dropSpecialCharacters = true', () => { component.mask.set('000 000'); component.prefix.set('KZ'); component.dropSpecialCharacters.set(true); component.form.setValue('KZ123123'); equal('KZ123123', 'KZ123 123', fixture); - expect(component.form.value).toBe('KZ123123'); + expect(component.form.value).toBe('123123'); }); - it('should remove prefix when setValue triggerOnMaskChange = false', () => { + it('should remove prefix when setValue triggerOnMaskChange = false & dropSpecialCharacters = false', () => { component.mask.set('000 000'); component.prefix.set('KZ'); component.dropSpecialCharacters.set(false); component.form.setValue('KZ123123'); equal('KZ123123', 'KZ123 123', fixture); - expect(component.form.value).toBe('KZ123123'); + expect(component.form.value).toBe('KZ123 123'); }); }); diff --git a/projects/ngx-mask-lib/src/test/basic-logic.spec.ts b/projects/ngx-mask-lib/src/test/basic-logic.spec.ts index b916c348..8d76cadf 100644 --- a/projects/ngx-mask-lib/src/test/basic-logic.spec.ts +++ b/projects/ngx-mask-lib/src/test/basic-logic.spec.ts @@ -5,7 +5,7 @@ import { By } from '@angular/platform-browser'; import { ReactiveFormsModule } from '@angular/forms'; import { TestMaskComponent } from './utils/test-component.component'; -import { equal, typeTest } from './utils/test-functions.component'; +import { equal, Paste, pasteTest } from './utils/test-functions.component'; import { provideNgxMask, NgxMaskDirective } from 'ngx-mask'; describe('Directive: Mask', () => { @@ -108,7 +108,7 @@ describe('Directive: Mask', () => { it('Masks with numbers, strings e special characters', () => { component.mask.set('(099) A99-SSSS'); - equal('as', '(', fixture); + equal('as', '(', fixture, false, Paste); equal('(1', '(1', fixture); equal('(12', '(12', fixture); equal('(123', '(123', fixture); @@ -337,7 +337,7 @@ describe('Directive: Mask', () => { it('should strip special characters from form control value', () => { component.mask.set('00/00/0000'); - typeTest('30/08/19921', fixture); + pasteTest('30/08/19921', fixture); expect(component.form.value).toBe('30081992'); }); @@ -366,9 +366,9 @@ describe('Directive: Mask', () => { }, }); equal('', '', fixture); - equal('2578989', '[', fixture); + equal('2578989', '[', fixture, false, Paste); equal('hello world', '[hel]-[low]*[or]', fixture); - equal('111.111-11', '[', fixture); + equal('111.111-11', '[', fixture, false, Paste); component.mask.set('(000-000)'); component.specialCharacters.set(['(', '-', ')']); @@ -494,7 +494,7 @@ describe('Directive: Mask', () => { component.mask.set('(000) 000-00-00'); fixture.detectChanges(); equal('0', '(0', fixture); - equal('(', '(', fixture); + equal('(', '(', fixture, false, Paste); const debugElement: DebugElement = fixture.debugElement.query(By.css('input')); const inputTarget: HTMLInputElement = debugElement.nativeElement as HTMLInputElement; debugElement.triggerEventHandler('keydown', { @@ -502,7 +502,9 @@ describe('Directive: Mask', () => { keyCode: 8, target: inputTarget, }); - equal('(', '', fixture); + debugElement.triggerEventHandler('input', { target: inputTarget }); + debugElement.triggerEventHandler('ngModelChange', { target: inputTarget }); + expect(inputTarget.value).toBe(''); }); it('should remove ghost character on toggling mask', () => { @@ -939,7 +941,7 @@ describe('Directive: Mask', () => { component.showMaskTyped.set(true); component.keepCharacterPositions.set(true); - equal('11/11/1111', '11/11/1111', fixture); + equal('11111111', '11/11/1111', fixture, false, Paste); component.form.setValue('22/22/2222'); fixture.detectChanges(); @@ -1018,4 +1020,12 @@ describe('Directive: Mask', () => { expect(inputTarget.value).toBe(''); }); + + it('should show correct value d0.M0.', () => { + component.mask.set('d0.M0.'); + equal('1', '1', fixture); + equal('12', '12', fixture); + equal('122', '12.2', fixture); + equal('12.22', '12.2.', fixture); + }); }); diff --git a/projects/ngx-mask-lib/src/test/delete.cy-spec.ts b/projects/ngx-mask-lib/src/test/delete.cy-spec.ts index 864656fa..092a01ec 100644 --- a/projects/ngx-mask-lib/src/test/delete.cy-spec.ts +++ b/projects/ngx-mask-lib/src/test/delete.cy-spec.ts @@ -216,6 +216,7 @@ describe('Directive: Mask (Delete)', () => { .should('have.prop', 'selectionStart', 14) .should('have.value', '+7 (123) 456-7___') .type('{backspace}') + .type('{backspace}') .should('have.prop', 'selectionStart', 12) .should('have.value', '+7 (123) 456-____') .type('{backspace}') @@ -225,6 +226,7 @@ describe('Directive: Mask (Delete)', () => { .should('have.prop', 'selectionStart', 10) .should('have.value', '+7 (123) 4__-____') .type('{backspace}') + .type('{backspace}') .should('have.prop', 'selectionStart', 7) .should('have.value', '+7 (123) ___-____') .type('{backspace}') @@ -254,12 +256,14 @@ describe('Directive: Mask (Delete)', () => { .should('have.prop', 'selectionStart', 15) .should('have.value', '+32 12 345 67 8_') .type('{backspace}') + .type('{backspace}') .should('have.prop', 'selectionStart', 13) .should('have.value', '+32 12 345 67 __') .type('{backspace}') .should('have.prop', 'selectionStart', 12) .should('have.value', '+32 12 345 6_ __') .type('{backspace}') + .type('{backspace}') .should('have.prop', 'selectionStart', 10) .should('have.value', '+32 12 345 __ __') .type('{backspace}') @@ -269,6 +273,7 @@ describe('Directive: Mask (Delete)', () => { .should('have.prop', 'selectionStart', 8) .should('have.value', '+32 12 3__ __ __') .type('{backspace}') + .type('{backspace}') .should('have.prop', 'selectionStart', 6) .should('have.value', '+32 12 ___ __ __') .type('{backspace}') @@ -338,7 +343,7 @@ describe('Directive: Mask (Delete)', () => { cy.get('#masked') .type('1234') .should('have.value', '12:34') - .type('{backspace}'.repeat(4)) + .type('{backspace}'.repeat(5)) .should('have.value', '__:__'); }); diff --git a/projects/ngx-mask-lib/src/test/delete.spec.ts b/projects/ngx-mask-lib/src/test/delete.spec.ts index eaf32869..4e86ac01 100644 --- a/projects/ngx-mask-lib/src/test/delete.spec.ts +++ b/projects/ngx-mask-lib/src/test/delete.spec.ts @@ -5,7 +5,6 @@ import type { DebugElement } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { TestMaskComponent } from './utils/test-component.component'; -import { equal } from './utils/test-functions.component'; import { provideNgxMask, NgxMaskDirective } from 'ngx-mask'; describe('Directive: Mask (Delete)', () => { @@ -84,8 +83,9 @@ describe('Directive: Mask (Delete)', () => { target: inputTarget, }); debugElement.triggerEventHandler('input', { target: inputTarget }); + debugElement.triggerEventHandler('ngModelChange', { target: inputTarget }); - equal(inputTarget.value, '***/*5/6789', fixture); + expect(inputTarget.value).toEqual('***/*5/6789'); expect(inputTarget.selectionStart).toEqual(6); }); diff --git a/projects/ngx-mask-lib/src/test/dynamic.spec.ts b/projects/ngx-mask-lib/src/test/dynamic.spec.ts index 908c6692..f4f62c13 100644 --- a/projects/ngx-mask-lib/src/test/dynamic.spec.ts +++ b/projects/ngx-mask-lib/src/test/dynamic.spec.ts @@ -219,7 +219,7 @@ describe('Directive: Mask (Dynamic)', () => { expect(component.form.valid).toBeFalse(); equal('A0', 'A0', fixture); expect(component.form.valid).toBeFalse(); - equal('A00', 'A00', fixture); + equal('A00', 'A0 0', fixture); expect(component.form.valid).toBeFalse(); equal('AAA0DD', 'AAA 0DD', fixture); expect(component.form.valid).toBeTrue(); diff --git a/projects/ngx-mask-lib/src/test/mask.pipe.spec.ts b/projects/ngx-mask-lib/src/test/mask.pipe.spec.ts index 87b3d646..7f4a47c5 100644 --- a/projects/ngx-mask-lib/src/test/mask.pipe.spec.ts +++ b/projects/ngx-mask-lib/src/test/mask.pipe.spec.ts @@ -352,12 +352,13 @@ describe('Pipe: Mask', () => { }); it('should show second pipe without suffix', () => { - const valueWithSuffix: string | number = maskPipe.transform('55555', '00 (000)', { + const valueWithSuffix: string | number = maskPipe.transform('55555 ', '00 (000)', { suffix: ' DDD', }); - const valueWithPrefix: string | number = maskPipe.transform('55555', '00 (000)', { + const valueWithPrefix: string | number = maskPipe.transform('55555 ', '00 (000)', { prefix: 'DDD ', }); + expect(valueWithSuffix).toEqual('55 (555) DDD'); expect(valueWithPrefix).toEqual('DDD 55 (555)'); }); diff --git a/projects/ngx-mask-lib/src/test/secure-mask.spec.ts b/projects/ngx-mask-lib/src/test/secure-mask.spec.ts index 7916b436..42ac0de9 100644 --- a/projects/ngx-mask-lib/src/test/secure-mask.spec.ts +++ b/projects/ngx-mask-lib/src/test/secure-mask.spec.ts @@ -4,7 +4,7 @@ import { TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { TestMaskComponent } from './utils/test-component.component'; -import { equal, typeTest } from './utils/test-functions.component'; +import { equal, typeTest, pasteTest } from './utils/test-functions.component'; import { provideNgxMask, NgxMaskDirective } from 'ngx-mask'; import type { DebugElement } from '@angular/core'; import { By } from '@angular/platform-browser'; @@ -109,7 +109,7 @@ describe('Directive: Mask (Secure)', () => { fixture.detectChanges(); expect(component.form.dirty).toBeTruthy(); expect(component.form.pristine).toBeFalsy(); - fixture.whenStable().then(() => { + return fixture.whenStable().then(() => { expect(fixture.nativeElement.querySelector('input').value).toBe('123/45/6789'); }); }); @@ -136,7 +136,7 @@ describe('Directive: Mask (Secure)', () => { component.hiddenInput.set(true); component.mask.set('XXX/X0/0000'); equal('54321', '***/*1', fixture); - typeTest('1', fixture); + pasteTest('1', fixture); expect(component.form.value).toBe('1'); component.form.reset(); expect(component.form.value).toBe(null); @@ -235,9 +235,11 @@ describe('Directive: Mask (Secure)', () => { component.hiddenInput.set(true); equal('123456789', '***/**/****', fixture); expect(component.form.value).toBe('123456789'); - fixture.detectChanges(); component.hiddenInput.set(false); - equal(inputTarget.value, '123/45/6789', fixture, true); - expect(component.form.value).toBe('123456789'); + fixture.detectChanges(); + return fixture.whenStable().then(() => { + expect(inputTarget.value).toBe('123/45/6789'); + expect(component.form.value).toBe('123456789'); + }); }); }); diff --git a/projects/ngx-mask-lib/src/test/separator.spec.ts b/projects/ngx-mask-lib/src/test/separator.spec.ts index 521abee4..6266affe 100644 --- a/projects/ngx-mask-lib/src/test/separator.spec.ts +++ b/projects/ngx-mask-lib/src/test/separator.spec.ts @@ -4,7 +4,7 @@ import { By } from '@angular/platform-browser'; import type { DebugElement } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { TestMaskComponent } from './utils/test-component.component'; -import { equal, typeTest } from './utils/test-functions.component'; +import { equal, Paste, typeTest } from './utils/test-functions.component'; import { initialConfig, NgxMaskDirective, provideNgxMask } from 'ngx-mask'; describe('Separator: Mask', () => { @@ -111,7 +111,7 @@ describe('Separator: Mask', () => { it('separator precision 0 for 1000000.00', () => { component.mask.set('separator.0'); - equal('1000000.00', '1 000 000', fixture); + equal('1000000.00', '1 000 000', fixture, false, Paste); }); it('separator precision 2 with 0 after point for 1000000.00', () => { @@ -198,7 +198,7 @@ describe('Separator: Mask', () => { it('separator thousandSeparator . precision 0 for 1000000.00', () => { component.mask.set('separator.0'); component.thousandSeparator.set('.'); - equal('1000000,00', '1.000.000', fixture); + equal('1000000,00', '1.000.000', fixture, false, Paste); }); it('separator thousandSeparator , for 1000000', () => { @@ -222,7 +222,7 @@ describe('Separator: Mask', () => { it('separator thousandSeparator , precision 0 for 1000000.00', () => { component.mask.set('separator.0'); component.thousandSeparator.set(','); - equal('1000000.00', '1,000,000', fixture); + equal('1000000.00', '1,000,000', fixture, false, Paste); }); it(`separator thousandSeparator ' for 1000000`, () => { @@ -246,7 +246,7 @@ describe('Separator: Mask', () => { it(`separator thousandSeparator ' precision 0 for 1000000.00`, () => { component.mask.set('separator.0'); component.thousandSeparator.set(`'`); - equal('1000000.00', `1'000'000`, fixture); + equal('1000000.00', `1'000'000`, fixture, false, Paste); }); it('should not shift cursor for input in-between digits', () => { diff --git a/projects/ngx-mask-lib/src/test/show-mask-typed.spec.ts b/projects/ngx-mask-lib/src/test/show-mask-typed.spec.ts index ac8cce81..c1bc1c4a 100644 --- a/projects/ngx-mask-lib/src/test/show-mask-typed.spec.ts +++ b/projects/ngx-mask-lib/src/test/show-mask-typed.spec.ts @@ -3,7 +3,8 @@ import { TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { TestMaskComponent } from './utils/test-component.component'; -import { equal } from './utils/test-functions.component'; +import { equal, Paste } from './utils/test-functions.component'; + import { NgxMaskDirective, provideNgxMask } from 'ngx-mask'; import type { DebugElement } from '@angular/core'; import { By } from '@angular/platform-browser'; @@ -217,9 +218,9 @@ describe('Directive: Mask', () => { spyOnProperty(document, 'activeElement').and.returnValue(inputTarget); fixture.detectChanges(); - equal('+38 1', '+38 1', fixture); - equal('+38 12', '+38 12', fixture); - equal('+38 123', '+38 123', fixture); + equal('+38 1', '+38 1', fixture, false, Paste); + equal('+38 12', '+38 12', fixture, false, Paste); + equal('+38 123', '+38 123', fixture, false, Paste); expect(inputTarget.selectionStart).toBe(7); component.showMaskTyped.set(true); inputTarget.focus(); diff --git a/projects/ngx-mask-lib/src/test/time-mask.spec.ts b/projects/ngx-mask-lib/src/test/time-mask.spec.ts index 0c315452..0377bad7 100644 --- a/projects/ngx-mask-lib/src/test/time-mask.spec.ts +++ b/projects/ngx-mask-lib/src/test/time-mask.spec.ts @@ -3,7 +3,7 @@ import { TestBed } from '@angular/core/testing'; import { ReactiveFormsModule } from '@angular/forms'; import { TestMaskComponent } from './utils/test-component.component'; -import { equal } from './utils/test-functions.component'; +import { equal, Paste } from './utils/test-functions.component'; import { provideNgxMask, NgxMaskDirective } from 'ngx-mask'; describe('Directive: Mask (Time)', () => { @@ -43,7 +43,8 @@ describe('Directive: Mask (Time)', () => { it('Hours', () => { component.showMaskTyped.set(true); component.mask.set('Hh:m0'); - equal('3__:__', '3_:__', fixture); + equal('3__:__', '3_:__', fixture, false, Paste); + equal('3__:__', '3:__', fixture); equal('33:__', '3:3_', fixture); equal('33__:__', '3:3_', fixture); }); @@ -205,30 +206,30 @@ describe('Directive: Mask (Time)', () => { it('Date (d0/M0:0000', () => { component.mask.set('d0/M0:0000'); - equal('999999', '9/9:9999', fixture); - equal('888888', '8/8:8888', fixture); - equal('777777', '7/7:7777', fixture); - equal('666666', '6/6:6666', fixture); - equal('555555', '5/5:5555', fixture); - equal('444444', '4/4:4444', fixture); - equal('333333', '3/3:3333', fixture); - equal('2222222', '22/2:2222', fixture); - equal('11111111', '11/11:1111', fixture); - equal('20232023', '20/2:3202', fixture); + equal('999999', '9/9:9999', fixture, false, Paste); + equal('888888', '8/8:8888', fixture, false, Paste); + equal('777777', '7/7:7777', fixture, false, Paste); + equal('666666', '6/6:6666', fixture, false, Paste); + equal('555555', '5/5:5555', fixture, false, Paste); + equal('444444', '4/4:4444', fixture, false, Paste); + equal('333333', '3/3:3333', fixture, false, Paste); + equal('2222222', '22/2:2222', fixture, false, Paste); + equal('11111111', '11/11:1111', fixture, false, Paste); + equal('20232023', '20/2:3202', fixture, false, Paste); }); it('Date (m0/d0/0000', () => { component.mask.set('m0/d0/0000'); - equal('999999', '9/9/9999', fixture); - equal('888888', '8/8/8888', fixture); - equal('777777', '7/7/7777', fixture); - equal('666666', '6/6/6666', fixture); - equal('5555555', '55/5/5555', fixture); - equal('4444444', '44/4/4444', fixture); - equal('3333333', '33/3/3333', fixture); - equal('22222222', '22/22/2222', fixture); - equal('11111111', '11/11/1111', fixture); - equal('20232023', '20/2/3202', fixture); + equal('999999', '9/9/9999', fixture, false, Paste); + equal('888888', '8/8/8888', fixture, false, Paste); + equal('777777', '7/7/7777', fixture, false, Paste); + equal('666666', '6/6/6666', fixture, false, Paste); + equal('5555555', '55/5/5555', fixture, false, Paste); + equal('4444444', '44/4/4444', fixture, false, Paste); + equal('3333333', '33/3/3333', fixture, false, Paste); + equal('22222222', '22/22/2222', fixture, false, Paste); + equal('11111111', '11/11/1111', fixture, false, Paste); + equal('20232023', '20/2/3202', fixture, false, Paste); }); it('Date (0000-M0-d0', () => { diff --git a/projects/ngx-mask-lib/src/test/utils/test-functions.component.ts b/projects/ngx-mask-lib/src/test/utils/test-functions.component.ts index c55bb246..859e600a 100644 --- a/projects/ngx-mask-lib/src/test/utils/test-functions.component.ts +++ b/projects/ngx-mask-lib/src/test/utils/test-functions.component.ts @@ -1,16 +1,61 @@ -export function typeTest(inputValue: string, fixture: any): string { +export const Paste = 'Paste'; +export const Type = 'Type'; + +export function pasteTest(inputValue: string, fixture: any): string { fixture.detectChanges(); fixture.nativeElement.querySelector('input').value = inputValue; + fixture.nativeElement.querySelector('input').dispatchEvent(new Event('paste')); fixture.nativeElement.querySelector('input').dispatchEvent(new Event('input')); + fixture.nativeElement.querySelector('input').dispatchEvent(new Event('ngModelChange')); - fixture.detectChanges(); return fixture.nativeElement.querySelector('input').value; } -export function equal(value: string, expectedValue: string, fixture: any, async = false): void { - typeTest(value, fixture); +export function typeTest(inputValue: string, fixture: any): string { + fixture.detectChanges(); + const inputArray = inputValue.split(''); + const inputElement = fixture.nativeElement.querySelector('input'); + + inputElement.value = ''; + inputElement.dispatchEvent(new Event('input')); + inputElement.dispatchEvent(new Event('ngModelChange')); + + { + for (const element of inputArray) { + inputElement.dispatchEvent(new KeyboardEvent('keydown'), { key: element }); + if (inputElement.type === 'text') { + const selectionStart = inputElement.selectionStart || 0; + const selectionEnd = inputElement.selectionEnd || 0; + inputElement.value = + inputElement.value.slice(0, selectionStart) + + element + + inputElement.value.slice(selectionEnd); + + inputElement.selectionStart = selectionStart + 1; + } else { + inputElement.value += element; + } + inputElement.dispatchEvent(new Event('input')); + inputElement.dispatchEvent(new Event('ngModelChange')); + } + } + return inputElement.value; +} + +export function equal( + value: string, + expectedValue: string, + fixture: any, + async = false, + testType: typeof Paste | typeof Type = Type +): void { + if (testType === Paste) { + pasteTest(value, fixture); + } else { + typeTest(value, fixture); + } if (async) { Promise.resolve().then(() => {