Reputation: 1833
I have a change function I am passing to the change event for a reactive form control that evaluates the dirty state and to check if there are any errors on the control and if so then set a boolean flag to true/false. This boolean flag is then used to determine whether or not to show a <div>
element that has an error message. This works just fine in the browser, but when the unit test runs, the "dirty" is never being set to true. Here is my code:
HTML
<form [formGroup]="myForm" novalidate>
<input id="age" formControlName="age" (change)="onChange()" />
<div id="ageError" *ngIf="ageIsError()">
<label>Age has errored</label>
</div>
</form>
Component
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.myForm = this.fb.group({
age: [null, [Validators.min(18)]]
});
}
onChange() {
if (this.ageIsError())
// do something
}
ageIsError() {
return this.myForm.controls.age.hasError('min') &&
this.myForm.controls.age.dirty;
}
Unit Test
it('should show error message when age is less than 18', fakeAsync(() => {
let age = component.myForm.controls.age;
age.setValue('10', { emitEvent: true });
fixture.detectChanges();
fixture.whenStable().then(() => {
let ageError = debugElement.query(By.css('#ageError')).nativeElement;
expect(component.ageIsError()).toBe(true);
expect(ageError.innerText).toContain('Age has errored');
});
}));
Again, the actual implementation works in the browser, but the unit test fails. Does anyone know hoe to emit the event in jasmine to set the control to a dirty state, or is there a better way to achieve this? Thanks!
Upvotes: 8
Views: 6914
Reputation: 1261
The form control bindings are updated automatically.
The below code is within a test block. It simply gets an input field and dispatches an update value on it.
I then render out the controller state and can confirm the bindings worked automatically.
const usernameInput: HTMLInputElement = fixture.nativeElement.querySelector(
'#username'
);
usernameInput.value = 'testValueNotEmail';
usernameInput.dispatchEvent(new Event('input'));
// FormGroup and Controll Bindings and Validations were automatically updated after the event.
console.log(component.loginForm.getRawValue());
console.log(component.loginForm.get('username')?.errors);
console.log(component.loginForm.get('username')?.touched);
console.log(component.loginForm.get('username')?.dirty);
// In order for the fixture render element changes to take effect,
// I had to detect the changes. The form error fields were then rendered.
fixture.detectChanges();
const errorField = fixture.nativeElement.querySelector(
'#usernameSmall'
);
console.log(errorField);
Upvotes: 0
Reputation: 3179
In your example age.setValue(...)
actually sets correct value to the input, but it doesn't append ageError
to the DOM - BECAUSE there wasn't real/emulated event to mark the control as dirty. This is why the method ageIsError
always returns false
in this case.
As a workaround I just emulated input event using document.createEvent('Event')
and seems like it works fine:
it('should show error message when age is less than 18', async(() => {
const customEvent: Event = document.createEvent('Event');
customEvent.initEvent('input', false, false);
const ageInput = fixture.debugElement.query(By.css('input#age')).nativeElement;
ageInput.value = 10;
ageInput.dispatchEvent(customEvent);
fixture.detectChanges();
fixture.whenStable().then(() => {
let ageError = fixture.debugElement.query(By.css('#ageError')).nativeElement;
expect(component.ageIsError()).toBe(true);
expect(ageError.innerText).toContain('Age has errored');
});
}));
I also found the fix to your solution - just call age.markAsDirty()
before detectChanges
:
it('should show error message when age is less than 18', async(() => {
let age = component.myForm.controls.age;
age.setValue('10'); // { emitEvent: true } is by default
age.markAsDirty(); // add this line
fixture.detectChanges();
fixture.whenStable().then(() => {
let ageError = fixture.debugElement.query(By.css('#ageError')).nativeElement;
expect(component.ageIsError()).toBe(true);
expect(ageError.innerText).toContain('Age has errored');
});
}));
I've also created a stackblitz example, please check it out as well. Hope these solutions will be helpful for you :)
Upvotes: 2