Skip to content

Commit

Permalink
fix(input): adiciona indicação visual de campo inválido
Browse files Browse the repository at this point in the history
adiciona indicação visual de campo inválido no `po-input`

Fixes 7479
  • Loading branch information
anliben committed Oct 6, 2023
1 parent 2bc70bd commit 84bb571
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -98,10 +110,12 @@ describe('PoInputGeneric:', () => {
it('should call keydown from mask with keyCode different 229', () => {
const fakeThis = {
mask: '(999)',
passedWriteValue: true,
objMask: {
keydown: (value: any) => {}
},
eventOnBlur: e => {}
eventOnBlur: e => {},
validateClassesForMask: (value: boolean) => {}
};
spyOn(fakeThis.objMask, 'keydown');
component.onKeydown.call(fakeThis, fakeEvent);
Expand All @@ -111,6 +125,7 @@ describe('PoInputGeneric:', () => {
it('should not call keydown from mask with keyCode different 229', () => {
const fakeThis = {
mask: '',
passedWriteValue: true,
objMask: {
keydown: (value: any) => {}
},
Expand All @@ -126,6 +141,7 @@ describe('PoInputGeneric:', () => {
it('should not call keydown when the mask is empty and keyCode is different of 229', () => {
const fakeThis = {
mask: '999',
passedWriteValue: true,
objMask: {
keydown: (value: any) => {}
},
Expand Down Expand Up @@ -644,6 +660,117 @@ 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'
};

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',
};
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,
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,
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';

Expand Down Expand Up @@ -690,7 +817,9 @@ describe('PoInputGeneric:', () => {
inputEl: '',
mask: '',
changeModel: component.changeModel,
passedWriteValue: false
passedWriteValue: false,
validateClassesForMask: () => {},
validateInitMask: () => {}
};
spyOn(component.changeModel, 'emit');
component.writeValueModel.call(fakeThis, value);
Expand All @@ -703,7 +832,8 @@ describe('PoInputGeneric:', () => {
inputEl: '',
mask: '',
changeModel: component.changeModel,
passedWriteValue: false
passedWriteValue: false,
validateClassesForMask: () => {}
};
spyOn(component.changeModel, 'emit');
component.writeValueModel.call(fakeThis, '');
Expand All @@ -716,7 +846,9 @@ describe('PoInputGeneric:', () => {
inputEl: component.inputEl,
mask: '',
changeModel: component.changeModel,
passedWriteValue: false
passedWriteValue: false,
validateClassesForMask: () => {},
validateInitMask: () => {}
};
component.writeValueModel.call(fakeThis, 'valor');
expect(component.inputEl.nativeElement.value).toBe('valor');
Expand All @@ -732,7 +864,9 @@ describe('PoInputGeneric:', () => {
_formatModel: false
},
changeModel: component.changeModel,
passedWriteValue: false
passedWriteValue: false,
validateClassesForMask: () => {},
validateInitMask: () => {}
};
component.writeValueModel.call(fakeThis, 'valor');
expect(component.inputEl.nativeElement.value).toBe('valor formatted');
Expand All @@ -749,7 +883,9 @@ describe('PoInputGeneric:', () => {
},
changeModel: component.changeModel,
callUpdateModelWithTimeout: component.callUpdateModelWithTimeout,
passedWriteValue: false
passedWriteValue: false,
validateClassesForMask: () => {},
validateInitMask: () => {}
};
const callUpdateModelWithTimeout = spyOn(fakeThis, <any>'callUpdateModelWithTimeout');
component.writeValueModel.call(fakeThis, 'valor');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { AfterViewInit, ElementRef, HostListener, ViewChild, Directive, ChangeDetectorRef } from '@angular/core';
import {
AfterViewInit,
ElementRef,
HostListener,
ViewChild,
Directive,
ChangeDetectorRef
} from '@angular/core';
import { AbstractControl } from '@angular/forms';

import { PoInputBaseComponent } from '../po-input/po-input-base.component';
Expand Down Expand Up @@ -28,6 +35,9 @@ export abstract class PoInputGeneric extends PoInputBaseComponent implements Aft
if (this.mask && !this.readonly && e.target.keyCode !== 229) {
this.eventOnBlur(e);
this.objMask.keydown(e);
if (this.passedWriteValue) {
this.validateClassesForMask();
}
}
}

Expand Down Expand Up @@ -166,6 +176,24 @@ export abstract class PoInputGeneric extends PoInputBaseComponent implements Aft
}
}

validateClassesForMask() {
const element = this.el.nativeElement;
const elementValue = this.inputEl.nativeElement.value;

if (!elementValue && 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);
}
Expand Down Expand Up @@ -197,6 +225,7 @@ export abstract class PoInputGeneric extends PoInputBaseComponent implements Aft

// Emite evento quando o model é atualizado, inclusive a primeira vez
if (value) {
this.validateInitMask();
this.changeModel.emit(value);
}
}
Expand All @@ -212,5 +241,11 @@ export abstract class PoInputGeneric extends PoInputBaseComponent implements Aft
}
}

validateInitMask() {
if (this.mask) {
this.validateClassesForMask();
}
}

abstract extraValidation(c: AbstractControl): { [key: string]: any };
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions projects/ui/src/lib/components/po-field/po-input/po-mask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,10 @@ export class PoMask {
this.valueToInput = valueProcessed;
this.valueToModel = this.removeFormattingValue(valueProcessed);
}

return valueProcessed;
}

// 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))) {
Expand Down

0 comments on commit 84bb571

Please sign in to comment.