I have an Angular component with this method:
deleteRule(index: number): void {
this.rules = this.formGroup.get('rules') as FormArray;
if (this.rules.length > 1) {
this.rules.controls.splice(index, 1);
this.rules.controls.forEach(control => control.updateValueAndValidity());
}
}
And a corresponding test:
beforeEach(() => {
fixture = TestBed.createComponent(MyFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should update validity of remaining controls after deleting a rule', () => {
let rules: FormArray = component.formGroup.get('rules') as FormArray;
rules.push(component.createRule());
rules.push(component.createRule());
component.rules = rules;
const control1 = component.rules.at(0);
const control2 = component.rules.at(1);
spyOn(control1, 'updateValueAndValidity').and.callThrough();
spyOn(control2, 'updateValueAndValidity').and.callThrough();
component.deleteRule(0);
expect(control1.updateValueAndValidity).toHaveBeenCalled();
expect(control2.updateValueAndValidity).toHaveBeenCalled();
});
Even though I can see that a breakpoint in
this.rules.controls.forEach(control => control.updateValueAndValidity());
is hit, I still get an error:
Expected spy updateValueAndValidity to have been called.
Can anyone help me understand why? And hopefully come up with a fix. :-)
You are removing one of the controls in the function and then running the for loop, hence, for the control that was removed, the function updateValueAndValidity
is never called, you can change your code to check only for the controls that are present.
it('should update validity of remaining controls after deleting a rule', () => {
let rules: FormArray = component.formGroup.get('rules') as FormArray;
rules.push(component.createRule());
rules.push(component.createRule());
component.rules = rules;
const control1 = component.rules.at(0);
const control2 = component.rules.at(1);
spyOn(control1, 'updateValueAndValidity').and.callThrough();
spyOn(control2, 'updateValueAndValidity').and.callThrough();
component.deleteRule(0);
// expect(control1.updateValueAndValidity).toHaveBeenCalled();
expect(control2.updateValueAndValidity).toHaveBeenCalled();
});
When you call the method updateValueAndValidity
at the form array level, it will call the same method at the child level, so you can also change the code to below.
deleteRule(index: number): void {
this.rules = this.formGroup.get('rules') as FormArray;
if (this.rules.length > 1) {
this.rules.controls.splice(index, 1);
this.rules.updateValueAndValidity();
}
}
it('should update validity of remaining controls after deleting a rule', () => {
let rules: FormArray = component.formGroup.get('rules') as FormArray;
rules.push(component.createRule());
rules.push(component.createRule());
component.rules = rules;
spyOn(rules, 'updateValueAndValidity').and.callThrough();
component.deleteRule(0);
expect(rules.updateValueAndValidity).toHaveBeenCalled();
});