I have a form that has an input and I have a button that needs to be enabled only when the form data was changed. I am using a pristine check and it all works functionally in the browser but I am having trouble to test it. No matter what I do the pristine check is always true no matter how many times I set the values. Any idea what I am doing wrong?
The HTML has 2 inputs with labels and a button
<form (ngSubmit)="login()"
[formGroup]="form">
<label>Email</label>
<input type="email" formControlName="email" name="email">
<label>Password</label>
<input type="password" formControlName="password">
<button type="submit">Login</button>
</form>
My typescript file
import {Component, EventEmitter, OnInit, Output} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from "@angular/forms";
export class User {
constructor(public email: string,
public password: string) {
}
}
@Component({
selector: 'app-login-component',
templateUrl: './login-component.component.html',
styleUrls: ['./login-component.component.scss']
})
export class LoginComponentComponent implements OnInit {
@Output() loggedIn = new EventEmitter<User>();
form: FormGroup;
constructor(private fb: FormBuilder) {
}
ngOnInit() {
this.form = this.fb.group({
email: ['', [Validators.required, Validators.pattern("[^ @]*@[^ @]*")]],
password: ['', [Validators.required, Validators.minLength(8)]],
});
}
login() {
console.log(`Login ${this.form.value}`);
if (this.form.valid) {
this.loggedIn.emit(
new User(
this.form.value.email,
this.form.value.password
)
);
}
}
}
And the 2 tests. I have tried with asych testing and without. I also tried setting the value of the native element and on the form. But in both cases the pristine check is always true. Any idea what I am doing wrong?
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {LoginComponentComponent} from './login-component.component';
import {FormsModule, ReactiveFormsModule} from "@angular/forms";
import {By} from "@angular/platform-browser";
describe('Component: Login', () => {
let component: LoginComponentComponent;
let fixture: ComponentFixture<LoginComponentComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule, FormsModule],
declarations: [LoginComponentComponent]
});
fixture = TestBed.createComponent(LoginComponentComponent);
component = fixture.componentInstance;
component.ngOnInit();
});
it('Tried WIHTOUT async function', () => {
expect(component.form.pristine).toBeTrue(); //Should not be in a modified state when it starts
fixture.detectChanges();
const inputElement = fixture.debugElement.query(By.css('input[name="email"]')).nativeElement;
//Try to set the control itself and the form
component.form.controls.email.setValue("2")
inputElement.value = '2';
//Detect changes and wait to be stable
fixture.detectChanges();
expect(inputElement.value).toEqual("2"); //Test that the value has infact change
expect(component.form.pristine).toBeFalse(); //This fails
});
it('Tried using async function', async(() => {
expect(component.form.pristine).toBeTrue(); //Should not be in a modified state when it starts
fixture.detectChanges();
const inputElement = fixture.debugElement.query(By.css('input[name="email"]')).nativeElement;
//Try to set the control itself and the form
component.form.controls.email.setValue("2")
inputElement.value = '2';
//Detect changes and wait to be stable
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(inputElement.value).toEqual("2"); //Test that the value has infact change
expect(component.form.pristine).toBeFalse(); //This fails
});
}));
});
This is not working because
A control is pristine if the user has not yet changed the value in the UI.
Only if you change value using UI, the pristine property will become false. Setting/changing form values programmatically doesn't change it.
One way to fix this is, in your tests you can use component.form.markAsDirty()
to make pristine false and make the tests work properly.
Other way is to simulate the behaviour as if value was changed from UI, you can use
component.form.controls.email.setValue("2");
inputElement.dispatchEvent(new Event('input'));
Or
inputElement.dispatchEvent(new Event('input'));
inputElement.value = '2';