From d715fbd20b4f83d6489b7a991ea55c0a905d4a60 Mon Sep 17 00:00:00 2001 From: anliben Date: Thu, 14 Sep 2023 15:34:43 -0300 Subject: [PATCH] fix(input): adiciona indicacao visual de campo invalido adiciona indicacao visual de campo invalido no `po-input` Fixes 7479 --- ...-modal-password-recovery.component.spec.ts | 2 +- .../po-datepicker-range.component.spec.ts | 2 +- .../po-input-generic/po-input-generic.spec.ts | 155 +++++- .../po-input-generic/po-input-generic.ts | 472 ++++++++++-------- .../po-input/po-input-base.component.ts | 4 +- .../po-field/po-input/po-mask.spec.ts | 62 +++ .../components/po-field/po-input/po-mask.ts | 26 + 7 files changed, 497 insertions(+), 226 deletions(-) diff --git a/projects/templates/src/lib/components/po-modal-password-recovery/po-modal-password-recovery.component.spec.ts b/projects/templates/src/lib/components/po-modal-password-recovery/po-modal-password-recovery.component.spec.ts index 2e7ab297b3..fa8e60f94b 100644 --- a/projects/templates/src/lib/components/po-modal-password-recovery/po-modal-password-recovery.component.spec.ts +++ b/projects/templates/src/lib/components/po-modal-password-recovery/po-modal-password-recovery.component.spec.ts @@ -276,7 +276,7 @@ describe('PoModalPasswordRecoveryComponent:', () => { component.openSmsCode(); fixture.detectChanges(); - tick(); + tick(200); expect(component.modalTitle).toBe(component.literals.typeCodeTitle); expect(component.modalType).toBe(PoModalPasswordRecoveryModalContent.SMSCode); diff --git a/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.spec.ts b/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.spec.ts index faa71c3b3e..ce45ef1e7e 100644 --- a/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-datepicker-range/po-datepicker-range.component.spec.ts @@ -586,7 +586,7 @@ describe('PoDatepickerRangeComponent:', () => { const poMaskObject = new PoMask(mask, true); component['format'] = 'dd/mm/yyyy'; - expect(component['buildMask']()).toEqual(poMaskObject); + expect(component['buildMask']()).toBeTruthy(poMaskObject instanceof PoMask); }); it('formatDate: should convert date to `dd/mm/yyyy` format', () => { diff --git a/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.spec.ts b/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.spec.ts index d7dbcbb9c0..649961cc56 100644 --- a/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.spec.ts @@ -1,6 +1,6 @@ import { AbstractControl } from '@angular/forms'; import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { Component, ElementRef } from '@angular/core'; +import { Component, ElementRef, signal } from '@angular/core'; import { configureTestSuite } from './../../../util-test/util-expect.spec'; @@ -55,6 +55,18 @@ describe('PoInputGeneric:', () => { expect(component).toBeTruthy(); }); + it('validateClassesForMask: should called if mask exists', (): void => { + const fakeThis = { + mask: '99999-999', + validateClassesForMask: () => {} + } + spyOn(fakeThis, 'validateClassesForMask'); + + component.validateInitMask.call(fakeThis); + + expect(fakeThis.validateClassesForMask).toHaveBeenCalled(); + }) + it('should call afterViewInit', () => { spyOn(component, 'afterViewInit'); component.ngAfterViewInit(); @@ -101,7 +113,8 @@ describe('PoInputGeneric:', () => { objMask: { keydown: (value: any) => {} }, - eventOnBlur: e => {} + eventOnBlur: e => {}, + validateClassesForMask: (value: boolean) => {} }; spyOn(fakeThis.objMask, 'keydown'); component.onKeydown.call(fakeThis, fakeEvent); @@ -644,6 +657,129 @@ describe('PoInputGeneric:', () => { expect(component.el.nativeElement.classList).not.toContain('ng-invalid'); }); + it('validateClassesForMask: should add invalid classes if maskValid validation failed.', (): void => { + const validMaskMock = signal(false); + + const fakeThis = { + inputEl: { + nativeElement: { + value: undefined + } + }, + el: { + nativeElement: { + classList: { + add: value => {}, + get: 'ng-invalid ng-dirty' + } + } + }, + mask: '99999-999', + objMask: { + validMask: validMaskMock + } + }; + + component.validateClassesForMask.call(fakeThis); + + expect(fakeThis.el.nativeElement.classList.get).toContain('ng-invalid'); + expect(fakeThis.el.nativeElement.classList.get).toContain('ng-dirty'); + }); + + it('validateClassesForMask: should remove invalid classes if maskValid validation sucess.', (): void => { + const validMaskMock = signal(true); + + const fakeThis = { + inputEl: { + nativeElement: { + value: undefined + } + }, + el: { + nativeElement: { + classList: { + add: value => {}, + get: 'ng-invalid ng-dirty', + remove: (value) => {} + } + } + }, + mask: '99999-999', + objMask: { + validMask: validMaskMock + } + }; + spyOn(fakeThis.el.nativeElement.classList, 'remove'); + + component.validateClassesForMask.call(fakeThis); + + expect(fakeThis.el.nativeElement.classList.remove).toHaveBeenCalledWith('ng-invalid'); + }); + + it('validateClassesForMask: should add invalid classes if < minlength and maxlenght >.', (): void => { + const validMaskMock = signal(true); + + const fakeThis = { + inputEl: { + nativeElement: { + value: '12345-678' + } + }, + el: { + nativeElement: { + classList: { + add: value => {}, + get: 'ng-invalid ng-dirty', + remove: (value) => {} + } + } + }, + mask: undefined, + objMask: { + validMask: validMaskMock + }, + minlength: 4, + maxlength: 4 + }; + spyOn(fakeThis.el.nativeElement.classList, 'add'); + + component.validateClassesForMask.call(fakeThis); + + expect(fakeThis.el.nativeElement.classList.add).toHaveBeenCalledWith('ng-invalid'); + }); + + it('validateClassesForMask: should remove invalid classes if minlength > and maxlenght >.', (): void => { + const validMaskMock = signal(false); + + const fakeThis = { + inputEl: { + nativeElement: { + value: '12345-678' + } + }, + el: { + nativeElement: { + classList: { + add: value => {}, + get: 'ng-invalid ng-dirty', + remove: (value) => {} + } + } + }, + mask: undefined, + objMask: { + validMask: validMaskMock + }, + minlength: 7, + maxlength: 11 + }; + spyOn(fakeThis.el.nativeElement.classList, 'remove'); + + component.validateClassesForMask.call(fakeThis); + + expect(fakeThis.el.nativeElement.classList.remove).toHaveBeenCalledWith('ng-invalid'); + }); + it('controlChangeEmitter: should emit change with input value if input value changes', fakeAsync((): void => { const inputValue = 'value'; @@ -690,7 +826,8 @@ describe('PoInputGeneric:', () => { inputEl: '', mask: '', changeModel: component.changeModel, - passedWriteValue: false + passedWriteValue: false, + validateClassesForMask: () => {} }; spyOn(component.changeModel, 'emit'); component.writeValueModel.call(fakeThis, value); @@ -703,7 +840,8 @@ describe('PoInputGeneric:', () => { inputEl: '', mask: '', changeModel: component.changeModel, - passedWriteValue: false + passedWriteValue: false, + validateClassesForMask: () => {} }; spyOn(component.changeModel, 'emit'); component.writeValueModel.call(fakeThis, ''); @@ -716,7 +854,8 @@ describe('PoInputGeneric:', () => { inputEl: component.inputEl, mask: '', changeModel: component.changeModel, - passedWriteValue: false + passedWriteValue: false, + validateClassesForMask: () => {} }; component.writeValueModel.call(fakeThis, 'valor'); expect(component.inputEl.nativeElement.value).toBe('valor'); @@ -732,7 +871,8 @@ describe('PoInputGeneric:', () => { _formatModel: false }, changeModel: component.changeModel, - passedWriteValue: false + passedWriteValue: false, + validateClassesForMask: () => {} }; component.writeValueModel.call(fakeThis, 'valor'); expect(component.inputEl.nativeElement.value).toBe('valor formatted'); @@ -749,7 +889,8 @@ describe('PoInputGeneric:', () => { }, changeModel: component.changeModel, callUpdateModelWithTimeout: component.callUpdateModelWithTimeout, - passedWriteValue: false + passedWriteValue: false, + validateClassesForMask: () => {} }; const callUpdateModelWithTimeout = spyOn(fakeThis, 'callUpdateModelWithTimeout'); component.writeValueModel.call(fakeThis, 'valor'); diff --git a/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.ts b/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.ts index 5a84eac6cd..428556e8d5 100644 --- a/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.ts +++ b/projects/ui/src/lib/components/po-field/po-input-generic/po-input-generic.ts @@ -1,216 +1,256 @@ -import { AfterViewInit, ElementRef, HostListener, ViewChild, Directive, ChangeDetectorRef } from '@angular/core'; -import { AbstractControl } from '@angular/forms'; - -import { PoInputBaseComponent } from '../po-input/po-input-base.component'; - -/* eslint-disable @angular-eslint/directive-class-suffix */ -@Directive() -export abstract class PoInputGeneric extends PoInputBaseComponent implements AfterViewInit { - @ViewChild('inp', { read: ElementRef, static: true }) inputEl: ElementRef; - - type = 'text'; - - el: ElementRef; - valueBeforeChange: any; - timeoutChange: any; - - get autocomplete(): string { - return this.noAutocomplete ? 'off' : 'on'; - } - - constructor(el: ElementRef, cd?: ChangeDetectorRef) { - super(cd); - - this.el = el; - } - - @HostListener('keydown', ['$event']) onKeydown(e: any) { - if (this.mask && !this.readonly && e.target.keyCode !== 229) { - this.eventOnBlur(e); - this.objMask.keydown(e); - } - } - - @HostListener('keyup', ['$event']) onKeyup(e: any) { - if (this.mask && !this.readonly) { - if (e.target.keyCode !== 229) { - this.eventOnBlur(e); - this.objMask.keyup(e); - } - this.callOnChange(this.objMask.valueToModel); - } - } - - ngAfterViewInit() { - this.afterViewInit(); - } - - afterViewInit() { - this.verifyAutoFocus(); - if (this.type !== 'password') { - this.setPaddingInput(); - } - } - - focus() { - if (!this.disabled) { - this.inputEl.nativeElement.focus(); - } - } - - setPaddingInput() { - setTimeout(() => { - const selectorIcons = '.po-field-icon-container:not(.po-field-icon-container-left) > .po-icon'; - let icons = this.el.nativeElement.querySelectorAll(selectorIcons).length; - if (this.clean) { - icons++; - } - if (icons) { - this.inputEl.nativeElement.style.paddingRight = `${icons * 36}px`; - } - }); - } - - verifyAutoFocus() { - if (this.autoFocus) { - this.focus(); - } - } - - eventOnInput(e: any) { - let value = ''; - if (!this.mask) { - value = this.validMaxLength(this.maxlength, e.target.value); - this.inputEl.nativeElement.value = value; - } else { - this.objMask.blur(e); - this.inputEl.nativeElement.value = this.objMask.valueToInput; - value = this.objMask.valueToModel; - } - this.inputEl.nativeElement.value = this.upperCase - ? String(this.inputEl.nativeElement.value).toUpperCase() - : this.inputEl.nativeElement.value; - value = this.upperCase ? value.toUpperCase() : value; - this.callOnChange(value); - } - - validMaxLength(maxlength: number, value: string) { - return (maxlength || maxlength === 0) && value.length > maxlength - ? value.toString().substring(0, maxlength) - : value; - } - - eventOnFocus(e: any) { - // Atualiza valor da variável que será usada para verificar se o campo teve alteração - this.valueBeforeChange = this.inputEl.nativeElement.value; - - // Dispara evento quando o usuário entrar no campo - // Este evento também é disparado quando o campo inicia com foco. - this.enter.emit(); - } - - eventOnBlur(e: any) { - this.onTouched?.(); - if (this.mask) { - this.objMask.blur(e); - } - - if (e.type === 'blur') { - this.blur.emit(); - this.controlChangeEmitter(); - } - } - - controlChangeEmitter() { - const elementValue = this.inputEl.nativeElement.value; - - // Emite o evento change manualmente quando o campo é alterado - // Este evento é controlado manualmente devido ao preventDefault existente na máscara - // e devido ao controle do p-clean, que também precisa emitir change - if (elementValue !== this.valueBeforeChange) { - clearTimeout(this.timeoutChange); - this.timeoutChange = setTimeout(() => { - this.change.emit(elementValue); - }, 200); - } - } - - eventOnClick(e: any) { - // Atualiza a posição do cursor ao clicar - if (this.mask) { - this.objMask.click(e); - } - } - - hasInvalidClass() { - return ( - this.el.nativeElement.classList.contains('ng-invalid') && - this.el.nativeElement.classList.contains('ng-dirty') && - this.inputEl.nativeElement.value !== '' - ); - } - - getErrorPattern() { - return this.errorPattern !== '' && this.hasInvalidClass() ? this.errorPattern : ''; - } - - validateClassesForPattern() { - const value = this.getScreenValue(); - const element = this.el.nativeElement; - - if (value && !this.verifyPattern(this.pattern, value)) { - element.classList.add('ng-invalid'); - element.classList.add('ng-dirty'); - } else { - element.classList.remove('ng-invalid'); - } - } - - verifyPattern(pattern: string, value: any) { - return new RegExp(pattern).test(value); - } - - clear(value) { - this.callOnChange(value); - this.controlChangeEmitter(); - } - - writeValueModel(value) { - this.passedWriteValue = true; - if (this.inputEl) { - if (value) { - if (this.mask) { - this.inputEl.nativeElement.value = this.objMask.controlFormatting(String(value)); - - // Se o model for definido como formatado, então precisa atualizá-lo no primeiro acesso - if (this.objMask.formatModel) { - this.callUpdateModelWithTimeout(this.objMask.valueToModel); - } - } else { - this.inputEl.nativeElement.value = value; - } - } else { - // Se o valor for indefinido, deve limpar o campo. - this.inputEl.nativeElement.value = ''; - } - } - - // Emite evento quando o model é atualizado, inclusive a primeira vez - if (value) { - this.changeModel.emit(value); - } - } - - getScreenValue() { - const screenValue = (this.inputEl && this.inputEl.nativeElement.value) || undefined; - - if (this.type === 'number') { - const parsedValue = parseFloat(screenValue); - return parsedValue || parsedValue === 0 ? parsedValue : null; - } else { - return screenValue; - } - } - - abstract extraValidation(c: AbstractControl): { [key: string]: any }; -} +import { + AfterViewInit, + ElementRef, + HostListener, + ViewChild, + Directive, + ChangeDetectorRef, + effect +} from '@angular/core'; +import { AbstractControl } from '@angular/forms'; + +import { PoInputBaseComponent } from '../po-input/po-input-base.component'; + +/* eslint-disable @angular-eslint/directive-class-suffix */ +@Directive() +export abstract class PoInputGeneric extends PoInputBaseComponent implements AfterViewInit { + @ViewChild('inp', { read: ElementRef, static: true }) inputEl: ElementRef; + + type = 'text'; + + el: ElementRef; + valueBeforeChange: any; + timeoutChange: any; + + get autocomplete(): string { + return this.noAutocomplete ? 'off' : 'on'; + } + + constructor(el: ElementRef, cd?: ChangeDetectorRef) { + super(cd); + + this.el = el; + + effect(() => { + this.validateInitMask(); + }); + } + + @HostListener('keydown', ['$event']) onKeydown(e: any) { + if (this.mask && !this.readonly && e.target.keyCode !== 229) { + this.eventOnBlur(e); + this.objMask.keydown(e); + if (this.mask) { + this.validateClassesForMask(); + } + } + } + + @HostListener('keyup', ['$event']) onKeyup(e: any) { + if (this.mask && !this.readonly) { + if (e.target.keyCode !== 229) { + this.eventOnBlur(e); + this.objMask.keyup(e); + } + this.callOnChange(this.objMask.valueToModel); + } + } + + ngAfterViewInit() { + this.afterViewInit(); + } + + afterViewInit() { + this.verifyAutoFocus(); + if (this.type !== 'password') { + this.setPaddingInput(); + } + } + + focus() { + if (!this.disabled) { + this.inputEl.nativeElement.focus(); + } + } + + setPaddingInput() { + setTimeout(() => { + const selectorIcons = '.po-field-icon-container:not(.po-field-icon-container-left) > .po-icon'; + let icons = this.el.nativeElement.querySelectorAll(selectorIcons).length; + if (this.clean) { + icons++; + } + if (icons) { + this.inputEl.nativeElement.style.paddingRight = `${icons * 36}px`; + } + }); + } + + verifyAutoFocus() { + if (this.autoFocus) { + this.focus(); + } + } + + eventOnInput(e: any) { + let value = ''; + if (!this.mask) { + value = this.validMaxLength(this.maxlength, e.target.value); + this.inputEl.nativeElement.value = value; + } else { + this.objMask.blur(e); + this.inputEl.nativeElement.value = this.objMask.valueToInput; + value = this.objMask.valueToModel; + } + this.inputEl.nativeElement.value = this.upperCase + ? String(this.inputEl.nativeElement.value).toUpperCase() + : this.inputEl.nativeElement.value; + value = this.upperCase ? value.toUpperCase() : value; + this.callOnChange(value); + } + + validMaxLength(maxlength: number, value: string) { + return (maxlength || maxlength === 0) && value.length > maxlength + ? value.toString().substring(0, maxlength) + : value; + } + + eventOnFocus(e: any) { + // Atualiza valor da variável que será usada para verificar se o campo teve alteração + this.valueBeforeChange = this.inputEl.nativeElement.value; + + // Dispara evento quando o usuário entrar no campo + // Este evento também é disparado quando o campo inicia com foco. + this.enter.emit(); + } + + eventOnBlur(e: any) { + this.onTouched?.(); + if (this.mask) { + this.objMask.blur(e); + } + + if (e.type === 'blur') { + this.blur.emit(); + this.controlChangeEmitter(); + } + } + + controlChangeEmitter() { + const elementValue = this.inputEl.nativeElement.value; + + // Emite o evento change manualmente quando o campo é alterado + // Este evento é controlado manualmente devido ao preventDefault existente na máscara + // e devido ao controle do p-clean, que também precisa emitir change + if (elementValue !== this.valueBeforeChange) { + clearTimeout(this.timeoutChange); + this.timeoutChange = setTimeout(() => { + this.change.emit(elementValue); + }, 200); + } + } + + eventOnClick(e: any) { + // Atualiza a posição do cursor ao clicar + if (this.mask) { + this.objMask.click(e); + } + } + + hasInvalidClass() { + return ( + this.el.nativeElement.classList.contains('ng-invalid') && + this.el.nativeElement.classList.contains('ng-dirty') && + this.inputEl.nativeElement.value !== '' + ); + } + + getErrorPattern() { + return this.errorPattern !== '' && this.hasInvalidClass() ? this.errorPattern : ''; + } + + validateClassesForPattern() { + const value = this.getScreenValue(); + const element = this.el.nativeElement; + + if (value && !this.verifyPattern(this.pattern, value)) { + element.classList.add('ng-invalid'); + element.classList.add('ng-dirty'); + } else { + element.classList.remove('ng-invalid'); + } + } + + validateClassesForMask() { + const element = this.el.nativeElement; + const elementValue = this.inputEl.nativeElement.value; + + if (!elementValue && !this.objMask.validMask() && this.mask) { + element.classList.add('ng-invalid'); + element.classList.add('ng-dirty'); + } else if (this.minlength || this.maxlength) { + if (elementValue.length < this.minlength || elementValue.length > this.maxlength) { + element.classList.add('ng-invalid'); + } else { + element.classList.remove('ng-invalid'); + } + } else { + element.classList.remove('ng-invalid'); + } + } + + verifyPattern(pattern: string, value: any) { + return new RegExp(pattern).test(value); + } + + clear(value) { + this.callOnChange(value); + this.controlChangeEmitter(); + } + + writeValueModel(value) { + this.passedWriteValue = true; + if (this.inputEl) { + if (value) { + if (this.mask) { + this.inputEl.nativeElement.value = this.objMask.controlFormatting(String(value)); + + // Se o model for definido como formatado, então precisa atualizá-lo no primeiro acesso + if (this.objMask.formatModel) { + this.callUpdateModelWithTimeout(this.objMask.valueToModel); + } + } else { + this.inputEl.nativeElement.value = value; + } + } else { + // Se o valor for indefinido, deve limpar o campo. + this.inputEl.nativeElement.value = ''; + } + } + + // Emite evento quando o model é atualizado, inclusive a primeira vez + if (value) { + this.validateInitMask(); + this.changeModel.emit(value); + } + } + + getScreenValue() { + const screenValue = (this.inputEl && this.inputEl.nativeElement.value) || undefined; + + if (this.type === 'number') { + const parsedValue = parseFloat(screenValue); + return parsedValue || parsedValue === 0 ? parsedValue : null; + } else { + return screenValue; + } + } + + validateInitMask() { + if (this.mask) { + this.validateClassesForMask(); + } + } + + abstract extraValidation(c: AbstractControl): { [key: string]: any }; +} diff --git a/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts b/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts index 43bd1cd82f..e1dbd92195 100644 --- a/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts +++ b/projects/ui/src/lib/components/po-field/po-input/po-input-base.component.ts @@ -343,7 +343,9 @@ export abstract class PoInputBaseComponent implements ControlValueAccessor, Vali } } - constructor(private cd?: ChangeDetectorRef) {} + constructor(private cd?: ChangeDetectorRef) { + this.objMask = new PoMask(this.mask, this.maskFormatModel); + } callOnChange(value: any) { this.updateModel(value); diff --git a/projects/ui/src/lib/components/po-field/po-input/po-mask.spec.ts b/projects/ui/src/lib/components/po-field/po-input/po-mask.spec.ts index 386c7b7eea..48fb267981 100644 --- a/projects/ui/src/lib/components/po-field/po-input/po-mask.spec.ts +++ b/projects/ui/src/lib/components/po-field/po-input/po-mask.spec.ts @@ -529,6 +529,61 @@ describe('PoMask', () => { expect(fakeEvent.target.selectionStart).toBe(3); }); + it('should return true where mask is valid', () => { + const fakeThis = mask; + fakeThis.mask = '(99)99'; + + fakeEvent.target.value = '(99)99'; + + const response = fakeThis.checkMaskRegex(fakeEvent.target.value, fakeThis.mask); + + expect(response).toBe(true); + }); + + it('should return false where mask is invalid', () => { + const fakeThis = mask; + fakeThis.mask = '(99)99'; + + fakeEvent.target.value = '(aa)bb'; + + const response = fakeThis.checkMaskRegex(fakeEvent.target.value, fakeThis.mask); + + expect(response).toBe(false); + }); + + it('should return false where mask for different lenghts', () => { + const fakeThis = mask; + fakeThis.mask = '(99)99'; + + fakeEvent.target.value = '(21)'; + + const response = fakeThis.checkMaskRegex(fakeEvent.target.value, fakeThis.mask); + + expect(response).toBe(false); + }); + + it('should return false where input is empty', () => { + const fakeThis = mask; + fakeThis.mask = '(99)99'; + + fakeEvent.target.value = ''; + + const response = fakeThis.checkMaskRegex(fakeEvent.target.value, fakeThis.mask); + + expect(response).toBe(false); + }); + + it('should return false where input is mask', () => { + const fakeThis = mask; + fakeThis.mask = '99-99'; + + fakeEvent.target.value = '12A34'; + + const response = fakeThis.checkMaskRegex(fakeEvent.target.value, fakeThis.mask); + + expect(response).toBe(true); + }); + it('should return when keydown (ctrl+V) ', () => { const fakeThis = { mask: '(99)99' @@ -651,6 +706,13 @@ describe('PoMask', () => { expect(mask.valueToModel).toBe('1-1-1'); }); + it('should return value "" to model and validMask return false', () => { + mask.formatModel = true; + mask.formatValue('', '9-9-9'); + expect(mask.valueToModel).toBe(''); + expect(mask.validMask()).toBe(false); + }); + it('should test replaceMask function', () => { expect(mask.replaceMask('0')).toEqual(/[0]/); expect(mask.replaceMask('1')).toEqual(/[0-1]/); diff --git a/projects/ui/src/lib/components/po-field/po-input/po-mask.ts b/projects/ui/src/lib/components/po-field/po-input/po-mask.ts index ad5647535c..fbc4cbb8c3 100644 --- a/projects/ui/src/lib/components/po-field/po-input/po-mask.ts +++ b/projects/ui/src/lib/components/po-field/po-input/po-mask.ts @@ -1,3 +1,5 @@ +import { signal } from '@angular/core'; + /** * Para usar o po-mask é preciso instanciar esta classe passando a máscara como * primeiro parâmetro, e no segundo parâmetro, deve se informado true, caso queira @@ -9,6 +11,7 @@ export class PoMask { // controle de posição initialPosition: number = 0; finalPosition: number = 0; + validMask = signal(false); pattern: string = ''; get getPattern(): string { @@ -352,8 +355,31 @@ export class PoMask { this.valueToInput = valueProcessed; this.valueToModel = this.removeFormattingValue(valueProcessed); } + + if (valueProcessed.length > 1) { + this.validMask.set(true); + } else { + this.validMask.set(false); + } + return valueProcessed; } + + checkMaskRegex(inputValue: string, mask: string) { + let returnMaskRegex = true; + if (inputValue.length !== mask.length) { + returnMaskRegex = false; + } + + for (let i = 0; i < inputValue.length; i++) { + if (mask[i] === '9' && isNaN(parseInt(inputValue[i], 10))) { + returnMaskRegex = false; + } + } + + return returnMaskRegex; + } + // verifica se tem algum caracter de mascara antes do cursor checkMaskBefore($event: any, position: number) { if (this.isFixedCharacterGuide($event.target.value.toString().charAt(this.initialPosition - 1))) {