diff --git a/packages/forms/test/reactive_integration_spec.ts b/packages/forms/test/reactive_integration_spec.ts
index 0253dfc0c5e..c465a37c596 100644
--- a/packages/forms/test/reactive_integration_spec.ts
+++ b/packages/forms/test/reactive_integration_spec.ts
@@ -1758,21 +1758,27 @@ describe('reactive forms integration tests', () => {
it('should work with single fields and async validators', async () => {
const fixture = initTest(FormControlComp);
- const control = new FormControl('', null!, uniqLoginAsyncValidator('good'));
+ // Delay validation so the scheduled renders can observe the pending state.
+ const control = new FormControl('', null!, uniqLoginAsyncValidator('good', 100));
fixture.debugElement.componentInstance.control = control;
- fixture.detectChanges();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-untouched']);
dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-touched']);
input.value = 'good';
dispatchEvent(input, 'input');
- await timeout();
- fixture.detectChanges();
+ await fixture.whenStable();
+ expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-pending', 'ng-touched']);
+
+ // Wait for the validator's 100 ms delay, which keeps it pending during rendering.
+ // whenStable() doesn't wait for this timer; useAutoTick advances the mock clock.
+ await timeout(100);
+ await fixture.whenStable();
expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
});
diff --git a/packages/forms/test/template_integration_spec.ts b/packages/forms/test/template_integration_spec.ts
index 79827048530..eca7653c495 100644
--- a/packages/forms/test/template_integration_spec.ts
+++ b/packages/forms/test/template_integration_spec.ts
@@ -59,8 +59,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(StandaloneNgModel);
fixture.componentInstance.name = 'oldValue';
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
// model -> view
const input = fixture.debugElement.query(By.css('input')).nativeElement;
@@ -68,7 +67,7 @@ describe('template-driven forms integration tests', () => {
input.value = 'updatedValue';
dispatchEvent(input, 'input');
- await timeout();
+ await fixture.whenStable();
// view -> model
expect(fixture.componentInstance.name).toEqual('updatedValue');
@@ -78,8 +77,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'Nancy';
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(form.value).toEqual({name: 'Nancy'});
@@ -101,8 +99,8 @@ describe('template-driven forms integration tests', () => {
}
TestBed.configureTestingModule({imports: [FormsModule], declarations: [AppComponent]});
const fixture = TestBed.createComponent(AppComponent);
- // We need the Await as `ngModel` writes data asynchronously into the DOM
- await fixture.detectChanges();
+ // NgModel writes data asynchronously into the DOM.
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input'));
expect(input.properties['checked']).toBe(true);
expect(input.nativeElement.checked).toBe(true);
@@ -111,8 +109,7 @@ describe('template-driven forms integration tests', () => {
it('should add novalidate by default to form element', async () => {
const fixture = initTest(NgModelForm);
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.query(By.css('form'));
expect(form.nativeElement.getAttribute('novalidate')).toEqual('');
@@ -121,8 +118,7 @@ describe('template-driven forms integration tests', () => {
it('should be possible to use native validation and angular forms', async () => {
const fixture = initTest(NgModelNativeValidateForm);
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.query(By.css('form'));
expect(form.nativeElement.hasAttribute('novalidate')).toEqual(false);
@@ -134,8 +130,7 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.last = 'Drew';
fixture.componentInstance.email = 'some email';
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
// model -> view
const inputs = fixture.debugElement.queryAll(By.css('input'));
@@ -144,7 +139,7 @@ describe('template-driven forms integration tests', () => {
inputs[0].nativeElement.value = 'Carson';
dispatchEvent(inputs[0].nativeElement, 'input');
- await timeout();
+ await fixture.whenStable();
// view -> model
const form = fixture.debugElement.children[0].injector.get(NgForm);
@@ -157,8 +152,7 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.last = 'Drew';
fixture.componentInstance.email = 'some email';
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(form.control.get('name')!.value).toEqual({first: 'Nancy', last: 'Drew'});
@@ -172,8 +166,7 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.first = 'Nancy';
fixture.componentInstance.email = 'some email';
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(form.control.get('email')!.value).toEqual('some email');
@@ -182,8 +175,7 @@ describe('template-driven forms integration tests', () => {
// should remove individual control successfully
fixture.componentInstance.emailShowing = false;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(form.control.get('email')).toBe(null);
expect(form.value).toEqual({name: {first: 'Nancy'}});
@@ -194,8 +186,7 @@ describe('template-driven forms integration tests', () => {
// should remove form group successfully
fixture.componentInstance.groupShowing = false;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(form.control.get('name')).toBe(null);
expect(form.control.get('name.first')).toBe(null);
@@ -205,59 +196,61 @@ describe('template-driven forms integration tests', () => {
it('should set status classes with ngModel', async () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'aa';
- fixture.detectChanges();
- fixture.whenStable().then(() => {
- fixture.detectChanges();
+ await fixture.whenStable();
- const input = fixture.debugElement.query(By.css('input')).nativeElement;
- expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']);
+ const input = fixture.debugElement.query(By.css('input')).nativeElement;
+ expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']);
- dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ dispatchEvent(input, 'blur');
+ await fixture.whenStable();
- expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']);
+ expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']);
- input.value = 'updatedValue';
- dispatchEvent(input, 'input');
- fixture.detectChanges();
- expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
+ input.value = 'updatedValue';
+ dispatchEvent(input, 'input');
+ await fixture.whenStable();
+ expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
- const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
- dispatchEvent(formEl, 'submit');
- fixture.detectChanges();
+ const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
+ dispatchEvent(formEl, 'submit');
+ await fixture.whenStable();
- expect(sortedClassList(formEl)).toEqual([
- 'ng-dirty',
- 'ng-submitted',
- 'ng-touched',
- 'ng-valid',
- ]);
- expect(sortedClassList(input)).not.toContain('ng-submitted');
+ expect(sortedClassList(formEl)).toEqual([
+ 'ng-dirty',
+ 'ng-submitted',
+ 'ng-touched',
+ 'ng-valid',
+ ]);
+ expect(sortedClassList(input)).not.toContain('ng-submitted');
- dispatchEvent(formEl, 'reset');
- fixture.detectChanges();
+ dispatchEvent(formEl, 'reset');
+ await fixture.whenStable();
- expect(sortedClassList(formEl)).toEqual(['ng-pristine', 'ng-untouched', 'ng-valid']);
- expect(sortedClassList(input)).not.toContain('ng-submitted');
- });
+ expect(sortedClassList(formEl)).toEqual(['ng-pristine', 'ng-untouched', 'ng-valid']);
+ expect(sortedClassList(input)).not.toContain('ng-submitted');
});
it('should set status classes with ngModel and async validators', async () => {
const fixture = initTest(NgModelAsyncValidation, NgAsyncValidator);
- fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-untouched']);
dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-touched']);
input.value = 'updatedValue';
dispatchEvent(input, 'input');
await fixture.whenStable();
+ expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-pending', 'ng-touched']);
+
+ // Wait for the validator's 100 ms delay, which keeps it pending during rendering.
+ // whenStable() doesn't wait for this timer; useAutoTick advances the mock clock.
+ await timeout(100);
+ await fixture.whenStable();
expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
});
@@ -265,64 +258,58 @@ describe('template-driven forms integration tests', () => {
it('should set status classes with ngModelGroup and ngForm', async () => {
const fixture = initTest(NgModelGroupForm);
fixture.componentInstance.first = '';
- fixture.detectChanges();
+ await fixture.whenStable();
const form = fixture.debugElement.query(By.css('form')).nativeElement;
const modelGroup = fixture.debugElement.query(By.css('[ngModelGroup]')).nativeElement;
const input = fixture.debugElement.query(By.css('input')).nativeElement;
- // ngModelGroup creates its control asynchronously
- fixture.whenStable().then(() => {
- fixture.detectChanges();
- expect(sortedClassList(modelGroup)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']);
+ expect(sortedClassList(modelGroup)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']);
- expect(sortedClassList(form)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']);
+ expect(sortedClassList(form)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']);
- dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ dispatchEvent(input, 'blur');
+ await fixture.whenStable();
- expect(sortedClassList(modelGroup)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']);
- expect(sortedClassList(form)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']);
+ expect(sortedClassList(modelGroup)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']);
+ expect(sortedClassList(form)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']);
- input.value = 'updatedValue';
- dispatchEvent(input, 'input');
- fixture.detectChanges();
+ input.value = 'updatedValue';
+ dispatchEvent(input, 'input');
+ await fixture.whenStable();
- expect(sortedClassList(modelGroup)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
- expect(sortedClassList(form)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
+ expect(sortedClassList(modelGroup)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
+ expect(sortedClassList(form)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
- const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
- dispatchEvent(formEl, 'submit');
- fixture.detectChanges();
+ const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
+ dispatchEvent(formEl, 'submit');
+ await fixture.whenStable();
- expect(sortedClassList(formEl)).toEqual([
- 'ng-dirty',
- 'ng-submitted',
- 'ng-touched',
- 'ng-valid',
- ]);
- });
+ expect(sortedClassList(formEl)).toEqual([
+ 'ng-dirty',
+ 'ng-submitted',
+ 'ng-touched',
+ 'ng-valid',
+ ]);
});
it('should set status classes involving nested FormGroups', async () => {
const fixture = initTest(NgModelNestedForm);
fixture.componentInstance.first = '';
fixture.componentInstance.other = '';
- fixture.detectChanges();
+ await fixture.whenStable();
const form = fixture.debugElement.query(By.css('form')).nativeElement;
const modelGroup = fixture.debugElement.query(By.css('[ngModelGroup]')).nativeElement;
const input = fixture.debugElement.query(By.css('input')).nativeElement;
- await fixture.whenStable();
- fixture.detectChanges();
expect(sortedClassList(modelGroup)).toEqual(['ng-pristine', 'ng-untouched', 'ng-valid']);
expect(sortedClassList(form)).toEqual(['ng-pristine', 'ng-untouched', 'ng-valid']);
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
dispatchEvent(formEl, 'submit');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(sortedClassList(modelGroup)).toEqual(['ng-pristine', 'ng-untouched', 'ng-valid']);
expect(sortedClassList(form)).toEqual([
@@ -334,22 +321,22 @@ describe('template-driven forms integration tests', () => {
expect(sortedClassList(input)).not.toContain('ng-submitted');
dispatchEvent(formEl, 'reset');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(sortedClassList(modelGroup)).toEqual(['ng-pristine', 'ng-untouched', 'ng-valid']);
expect(sortedClassList(form)).toEqual(['ng-pristine', 'ng-untouched', 'ng-valid']);
expect(sortedClassList(input)).not.toContain('ng-submitted');
});
- it('should not create a template-driven form when ngNoForm is used', () => {
+ it('should not create a template-driven form when ngNoForm is used', async () => {
const fixture = initTest(NgNoFormComp);
- fixture.detectChanges();
+ await fixture.whenStable();
expect(fixture.debugElement.children[0].providerTokens!.length).toEqual(0);
});
- it('should not add novalidate when ngNoForm is used', () => {
+ it('should not add novalidate when ngNoForm is used', async () => {
const fixture = initTest(NgNoFormComp);
- fixture.detectChanges();
+ await fixture.whenStable();
const form = fixture.debugElement.query(By.css('form'));
expect(form.nativeElement.hasAttribute('novalidate')).toEqual(false);
});
@@ -386,32 +373,27 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(App);
fixture.componentInstance.add(3);
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(getValues()).toEqual(['0', '1', '2']);
fixture.componentInstance.remove(1);
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(getValues()).toEqual(['0', '2']);
fixture.componentInstance.add(1);
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(getValues()).toEqual(['0', '2', '3']);
fixture.componentInstance.items[1].value = '1';
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(getValues()).toEqual(['0', '1', '3']);
fixture.componentInstance.items[2].value = '2';
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(getValues()).toEqual(['0', '1', '2']);
});
@@ -450,57 +432,52 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(App);
fixture.componentInstance.add(3);
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(getValues()).toEqual(['0', '1', '2']);
fixture.componentInstance.remove(1);
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(getValues()).toEqual(['0', '2']);
fixture.componentInstance.add(1);
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(getValues()).toEqual(['0', '2', '3']);
fixture.componentInstance.items[1].value = '1';
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(getValues()).toEqual(['0', '1', '3']);
fixture.componentInstance.items[2].value = '2';
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(getValues()).toEqual(['0', '1', '2']);
});
});
describe('name and ngModelOptions', () => {
- it('should throw if ngModel has a parent form but no name attr or standalone label', () => {
+ it('should throw if ngModel has a parent form but no name attr or standalone label', async () => {
const fixture = initTest(InvalidNgModelNoName);
- expect(() => fixture.detectChanges()).toThrowError(new RegExp(`name attribute must be set`));
+ await expectAsync(fixture.whenStable()).toBeRejectedWithError(
+ new RegExp(`name attribute must be set`),
+ );
});
- it('should not throw if ngModel has a parent form, no name attr, and a standalone label', () => {
+ it('should not throw if ngModel has a parent form, no name attr, and a standalone label', async () => {
const fixture = initTest(NgModelOptionsStandalone);
- expect(() => fixture.detectChanges()).not.toThrow();
+ await expectAsync(fixture.whenStable()).toBeResolved();
});
it('should not register standalone ngModels with parent form', async () => {
const fixture = initTest(NgModelOptionsStandalone);
fixture.componentInstance.one = 'some data';
fixture.componentInstance.two = 'should not show';
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
const inputs = fixture.debugElement.queryAll(By.css('input'));
- await timeout();
expect(form.value).toEqual({one: 'some data'});
expect(inputs[1].nativeElement.value).toEqual('should not show');
@@ -510,8 +487,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.options = {name: 'override'};
fixture.componentInstance.name = 'some data';
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(form.value).toEqual({override: 'some data'});
@@ -524,8 +500,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = '';
fixture.componentInstance.options = {};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
const name = form.control.get('name') as FormControl;
@@ -537,8 +512,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = '';
fixture.componentInstance.options = {updateOn: 'blur'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
const name = form.control.get('name') as FormControl;
@@ -550,8 +524,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'Nancy Drew';
fixture.componentInstance.options = {updateOn: 'blur'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
@@ -569,13 +542,11 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.name = 'Nancy Drew';
fixture.componentInstance.options = {updateOn: 'blur'};
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
fixture.componentInstance.name = 'Carson';
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
@@ -595,14 +566,12 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'Carson';
fixture.componentInstance.options = {updateOn: 'blur'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'Nancy Drew';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(fixture.componentInstance.name)
@@ -611,7 +580,7 @@ describe('template-driven forms integration tests', () => {
expect(form.valid).withContext('Expected validation not to run on input.').toBe(false);
dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(fixture.componentInstance.name)
.withContext('Expected value to update on blur.')
@@ -623,21 +592,19 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'Carson';
fixture.componentInstance.options = {updateOn: 'blur'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'Nancy Drew';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ await fixture.whenStable();
input.value = 'Carson';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(fixture.componentInstance.name)
@@ -648,7 +615,7 @@ describe('template-driven forms integration tests', () => {
.toBe(true);
dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(fixture.componentInstance.name)
.withContext('Expected value to update on second blur.')
@@ -660,20 +627,18 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = '';
fixture.componentInstance.options = {updateOn: 'blur'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'Nancy Drew';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(form.dirty).withContext('Expected dirtiness not to update on input.').toBe(false);
dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.dirty).withContext('Expected dirtiness to update on blur.').toBe(true);
});
@@ -682,20 +647,18 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = '';
fixture.componentInstance.options = {updateOn: 'blur'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'Nancy Drew';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(form.touched).withContext('Expected touched not to update on input.').toBe(false);
dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.touched).withContext('Expected touched to update on blur.').toBe(true);
});
@@ -704,8 +667,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = '';
fixture.componentInstance.options = {updateOn: 'blur'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const values: any[] = [];
const form = fixture.debugElement.children[0].injector.get(NgForm);
@@ -717,15 +679,14 @@ describe('template-driven forms integration tests', () => {
const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'Nancy Drew';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(values)
.withContext('Expected no valueChanges or statusChanges on input.')
.toEqual([]);
dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(values).toEqual(
[{name: 'Nancy Drew'}, 'VALID'],
@@ -739,8 +700,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelChangesForm);
fixture.componentInstance.name = 'Carson';
fixture.componentInstance.options = {updateOn: 'blur'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(fixture.componentInstance.events)
.withContext('Expected ngModelChanges not to fire.')
@@ -748,7 +708,7 @@ describe('template-driven forms integration tests', () => {
const input = fixture.debugElement.query(By.css('input')).nativeElement;
dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(fixture.componentInstance.events)
.withContext('Expected ngModelChanges not to fire if value unchanged.')
@@ -756,22 +716,21 @@ describe('template-driven forms integration tests', () => {
input.value = 'Carson';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(fixture.componentInstance.events)
.withContext('Expected ngModelChanges not to fire on input.')
.toEqual([]);
dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(fixture.componentInstance.events)
.withContext('Expected ngModelChanges to fire once blurred if value changed.')
.toEqual(['fired']);
dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(fixture.componentInstance.events).toEqual(
['fired'],
@@ -780,15 +739,14 @@ describe('template-driven forms integration tests', () => {
input.value = 'Bess';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(fixture.componentInstance.events)
.withContext('Expected ngModelChanges not to fire on input after blur.')
.toEqual(['fired']);
dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(fixture.componentInstance.events).toEqual(
['fired', 'fired'],
@@ -802,8 +760,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = '';
fixture.componentInstance.options = {updateOn: 'submit'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
const name = form.control.get('name') as FormControl;
@@ -815,8 +772,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'Nancy Drew';
fixture.componentInstance.options = {updateOn: 'submit'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
@@ -834,13 +790,11 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.name = 'Nancy Drew';
fixture.componentInstance.options = {updateOn: 'submit'};
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
fixture.componentInstance.name = 'Carson';
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
@@ -859,14 +813,12 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'Carson';
fixture.componentInstance.options = {updateOn: 'submit'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'Nancy Drew';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(fixture.componentInstance.name)
@@ -875,8 +827,7 @@ describe('template-driven forms integration tests', () => {
expect(form.valid).withContext('Expected validation not to run on input.').toBe(false);
dispatchEvent(input, 'blur');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(fixture.componentInstance.name)
.withContext('Expected value not to update on blur.')
@@ -885,7 +836,7 @@ describe('template-driven forms integration tests', () => {
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
dispatchEvent(formEl, 'submit');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(fixture.componentInstance.name)
.withContext('Expected value to update on submit.')
@@ -897,24 +848,20 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'Carson';
fixture.componentInstance.options = {updateOn: 'submit'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'Nancy Drew';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
dispatchEvent(formEl, 'submit');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
input.value = 'Carson';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(fixture.componentInstance.name)
@@ -925,8 +872,7 @@ describe('template-driven forms integration tests', () => {
.toBe(true);
dispatchEvent(formEl, 'submit');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(fixture.componentInstance.name)
.withContext('Expected value to update on second submit.')
@@ -940,8 +886,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelGroupForm);
fixture.componentInstance.options = {updateOn: 'submit'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
form.control.get('name')!.setValidators(groupValidatorSpy);
@@ -949,7 +894,7 @@ describe('template-driven forms integration tests', () => {
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
dispatchEvent(formEl, 'submit');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(validatorSpy).not.toHaveBeenCalled();
expect(groupValidatorSpy).not.toHaveBeenCalled();
@@ -959,27 +904,24 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = '';
fixture.componentInstance.options = {updateOn: 'submit'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'Nancy Drew';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(form.dirty).withContext('Expected dirtiness not to update on input.').toBe(false);
dispatchEvent(input, 'blur');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(form.dirty).withContext('Expected dirtiness not to update on blur.').toBe(false);
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
dispatchEvent(formEl, 'submit');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.dirty).withContext('Expected dirtiness to update on submit.').toBe(true);
});
@@ -988,25 +930,22 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = '';
fixture.componentInstance.options = {updateOn: 'submit'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'Nancy Drew';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
dispatchEvent(input, 'blur');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(form.touched).withContext('Expected touched not to update on blur.').toBe(false);
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
dispatchEvent(formEl, 'submit');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.touched).withContext('Expected touched to update on submit.').toBe(true);
});
@@ -1015,21 +954,19 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'Nancy' as string | null;
fixture.componentInstance.options = {updateOn: 'submit'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'Nancy Drew';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
form.resetForm();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(input.value).withContext('Expected view value to reset.').toEqual('');
expect(form.value).withContext('Expected form value to reset.').toEqual({name: null});
@@ -1041,7 +978,7 @@ describe('template-driven forms integration tests', () => {
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
dispatchEvent(formEl, 'submit');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.value).withContext('Expected form value to stay empty on submit').toEqual({
name: null,
@@ -1057,8 +994,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = '';
fixture.componentInstance.options = {updateOn: 'submit'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const values: any[] = [];
const form = fixture.debugElement.children[0].injector.get(NgForm);
@@ -1070,16 +1006,14 @@ describe('template-driven forms integration tests', () => {
const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'Nancy Drew';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(values)
.withContext('Expected no valueChanges or statusChanges on input.')
.toEqual([]);
dispatchEvent(input, 'blur');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(values)
.withContext('Expected no valueChanges or statusChanges on blur.')
@@ -1087,7 +1021,7 @@ describe('template-driven forms integration tests', () => {
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
dispatchEvent(formEl, 'submit');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(values).toEqual(
[{name: 'Nancy Drew'}, 'VALID'],
@@ -1100,12 +1034,11 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelChangesForm);
fixture.componentInstance.name = 'Carson';
fixture.componentInstance.options = {updateOn: 'submit'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
dispatchEvent(formEl, 'submit');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(fixture.componentInstance.events)
.withContext('Expected ngModelChanges not to fire if value unchanged.')
@@ -1114,22 +1047,21 @@ describe('template-driven forms integration tests', () => {
const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'Carson';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(fixture.componentInstance.events)
.withContext('Expected ngModelChanges not to fire on input.')
.toEqual([]);
dispatchEvent(formEl, 'submit');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(fixture.componentInstance.events)
.withContext('Expected ngModelChanges to fire once submitted if value changed.')
.toEqual(['fired']);
dispatchEvent(formEl, 'submit');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(fixture.componentInstance.events).toEqual(
['fired'],
@@ -1138,15 +1070,14 @@ describe('template-driven forms integration tests', () => {
input.value = 'Bess';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(fixture.componentInstance.events)
.withContext('Expected ngModelChanges not to fire on input after submit.')
.toEqual(['fired']);
dispatchEvent(formEl, 'submit');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(fixture.componentInstance.events).toEqual(
['fired', 'fired'],
@@ -1160,10 +1091,9 @@ describe('template-driven forms integration tests', () => {
}
const fixture = initTest(NativeDialogForm);
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const event = dispatchEvent(fixture.componentInstance.form.nativeElement, 'submit');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(event.defaultPrevented).toBe(false);
});
@@ -1174,8 +1104,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelOptionsStandalone);
fixture.componentInstance.options = {name: 'two'};
fixture.componentInstance.formOptions = {updateOn: 'blur'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
const controlOne = form.control.get('one')! as FormControl;
@@ -1195,14 +1124,12 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelOptionsStandalone);
fixture.componentInstance.one = '';
fixture.componentInstance.formOptions = {updateOn: 'blur'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'Nancy Drew';
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(form.value).withContext('Expected value not to update on input.').toEqual({
@@ -1210,7 +1137,7 @@ describe('template-driven forms integration tests', () => {
});
dispatchEvent(input, 'blur');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.value).withContext('Expected value to update on blur.').toEqual({
one: 'Nancy Drew',
@@ -1221,8 +1148,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelOptionsStandalone);
fixture.componentInstance.options = {updateOn: 'blur', name: 'two'};
fixture.componentInstance.formOptions = {updateOn: 'change'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
const controlOne = form.control.get('one')! as FormControl;
@@ -1246,13 +1172,12 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.two = '';
fixture.componentInstance.options = {updateOn: 'blur', name: 'two'};
fixture.componentInstance.formOptions = {updateOn: 'change'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const [inputOne, inputTwo] = fixture.debugElement.queryAll(By.css('input'));
inputOne.nativeElement.value = 'Nancy Drew';
dispatchEvent(inputOne.nativeElement, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(form.value).withContext('Expected first value to update on input.').toEqual({
@@ -1262,8 +1187,7 @@ describe('template-driven forms integration tests', () => {
inputTwo.nativeElement.value = 'Carson Drew';
dispatchEvent(inputTwo.nativeElement, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(form.value).withContext('Expected second value not to update on input.').toEqual({
one: 'Nancy Drew',
@@ -1271,7 +1195,7 @@ describe('template-driven forms integration tests', () => {
});
dispatchEvent(inputTwo.nativeElement, 'blur');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.value).toEqual(
{one: 'Nancy Drew', two: 'Carson Drew'},
@@ -1284,13 +1208,12 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.two = '';
fixture.componentInstance.options = {standalone: true};
fixture.componentInstance.formOptions = {updateOn: 'blur'};
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const inputTwo = fixture.debugElement.queryAll(By.css('input'))[1].nativeElement;
inputTwo.value = 'Nancy Drew';
dispatchEvent(inputTwo, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(fixture.componentInstance.two)
.withContext('Expected standalone ngModel not to inherit blur update.')
@@ -1303,10 +1226,11 @@ describe('template-driven forms integration tests', () => {
it('should emit ngSubmit event with the original submit event on submit', async () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.event = null!;
+ await fixture.whenStable();
const form = fixture.debugElement.query(By.css('form'));
dispatchEvent(form.nativeElement, 'submit');
- await timeout();
+ await fixture.whenStable();
expect(fixture.componentInstance.event.type).toEqual('submit');
});
@@ -1314,13 +1238,13 @@ describe('template-driven forms integration tests', () => {
it('should mark NgForm as submitted on submit event', async () => {
const fixture = initTest(NgModelForm);
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(form.submitted).toBe(false);
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
dispatchEvent(formEl, 'submit');
- await timeout();
+ await fixture.whenStable();
expect(form.submitted).toBe(true);
});
@@ -1328,8 +1252,7 @@ describe('template-driven forms integration tests', () => {
it('should reset the form to empty when reset event is fired', async () => {
const fixture = initTest(NgModelForm);
fixture.componentInstance.name = 'should be cleared' as string | null;
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
const formEl = fixture.debugElement.query(By.css('form'));
@@ -1340,8 +1263,7 @@ describe('template-driven forms integration tests', () => {
expect(form.value.name).toEqual('should be cleared'); // control value
dispatchEvent(formEl.nativeElement, 'reset');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(input.nativeElement.value).toBe(''); // view value
expect(fixture.componentInstance.name).toBe(null); // ngModel value
@@ -1350,17 +1272,16 @@ describe('template-driven forms integration tests', () => {
it('should reset the form submit state when reset button is clicked', async () => {
const fixture = initTest(NgModelForm);
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
const formEl = fixture.debugElement.query(By.css('form'));
dispatchEvent(formEl.nativeElement, 'submit');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(form.submitted).toBe(true);
dispatchEvent(formEl.nativeElement, 'reset');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(form.submitted).toBe(false);
});
});
@@ -1370,7 +1291,6 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
const form = fixture.debugElement.children[0].injector.get(NgForm);
fixture.componentInstance.name = 'aa';
- fixture.detectChanges();
expect(form.valid).toEqual(true);
expect(form.value).toEqual({});
@@ -1381,7 +1301,7 @@ describe('template-driven forms integration tests', () => {
form.statusChanges!.subscribe((status: string) => (formValidity = status));
form.valueChanges!.subscribe((value: string) => (formValue = value));
- await timeout();
+ await fixture.whenStable();
expect(formValidity).toEqual('INVALID');
expect(formValue).toEqual({name: 'aa'});
@@ -1391,8 +1311,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelForm);
const form = fixture.debugElement.children[0].injector.get(NgForm).form;
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
form.get('name')!.valueChanges.subscribe(() => {
expect(form.get('name')!.dirty).toBe(true);
@@ -1406,8 +1325,7 @@ describe('template-driven forms integration tests', () => {
it('should mark controls pristine before emitting the value change event when resetting ', async () => {
const fixture = initTest(NgModelForm);
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm).form;
const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
@@ -1434,8 +1352,7 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.last = 'Drew';
fixture.componentInstance.email = 'some email';
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(form.value).toEqual({name: {first: '', last: 'Drew'}, email: 'some email'});
@@ -1444,8 +1361,7 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.isDisabled = true;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(form.value).toEqual({name: {last: 'Drew'}, email: 'some email'});
expect(form.valid).toBe(true);
@@ -1456,13 +1372,11 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelGroupForm);
fixture.componentInstance.isDisabled = false;
fixture.componentInstance.first = 'Nancy';
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
form.control.get('name.first')!.disable();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css(`[name="first"]`));
expect(input.nativeElement.disabled).toBe(true);
@@ -1472,9 +1386,6 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelCustomWrapper, NgModelCustomComp);
fixture.componentInstance.name = 'Nancy';
fixture.componentInstance.isDisabled = true;
- fixture.detectChanges();
- await fixture.whenStable();
- fixture.detectChanges();
await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
@@ -1495,8 +1406,7 @@ describe('template-driven forms integration tests', () => {
},
});
const fixture = initTest(NgModelForm);
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(form.control.get('name')!.disabled).toBe(true);
@@ -1504,8 +1414,7 @@ describe('template-driven forms integration tests', () => {
expect(input.nativeElement.disabled).toEqual(true);
form.control.enable();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(input.nativeElement.disabled).toEqual(false);
});
});
@@ -1513,8 +1422,7 @@ describe('template-driven forms integration tests', () => {
describe('validation directives', () => {
it('required validator should validate checkbox', async () => {
const fixture = initTest(NgModelCheckboxRequiredValidator);
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const control = fixture.debugElement.children[0].injector
.get(NgForm)
@@ -1526,32 +1434,28 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.required = true;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(input.nativeElement.checked).toBe(false);
expect(control.hasError('required')).toBe(true);
input.nativeElement.checked = true;
dispatchEvent(input.nativeElement, 'change');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(input.nativeElement.checked).toBe(true);
expect(control.hasError('required')).toBe(false);
input.nativeElement.checked = false;
dispatchEvent(input.nativeElement, 'change');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(input.nativeElement.checked).toBe(false);
expect(control.hasError('required')).toBe(true);
fixture.componentInstance.required = false;
dispatchEvent(input.nativeElement, 'change');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(input.nativeElement.checked).toBe(false);
expect(control.hasError('required')).toBe(false);
@@ -1559,8 +1463,7 @@ describe('template-driven forms integration tests', () => {
it('should validate email', async () => {
const fixture = initTest(NgModelEmailValidator);
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const control = fixture.debugElement.children[0].injector.get(NgForm).control.get('email')!;
@@ -1569,31 +1472,28 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.validatorEnabled = true;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(input.nativeElement.value).toEqual('');
expect(control.hasError('email')).toBe(false);
input.nativeElement.value = '@';
dispatchEvent(input.nativeElement, 'input');
- await timeout();
+ await fixture.whenStable();
expect(input.nativeElement.value).toEqual('@');
expect(control.hasError('email')).toBe(true);
input.nativeElement.value = 'test@gmail.com';
dispatchEvent(input.nativeElement, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(input.nativeElement.value).toEqual('test@gmail.com');
expect(control.hasError('email')).toBe(false);
input.nativeElement.value = 'text';
dispatchEvent(input.nativeElement, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(input.nativeElement.value).toEqual('text');
expect(control.hasError('email')).toBe(true);
@@ -1605,8 +1505,7 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.minLen = 3;
fixture.componentInstance.maxLen = 3;
fixture.componentInstance.pattern = '.{3,}';
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const required = fixture.debugElement.query(By.css('[name=required]'));
const minLength = fixture.debugElement.query(By.css('[name=minlength]'));
@@ -1622,7 +1521,7 @@ describe('template-driven forms integration tests', () => {
dispatchEvent(minLength.nativeElement, 'input');
dispatchEvent(maxLength.nativeElement, 'input');
dispatchEvent(pattern.nativeElement, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
expect(form.control.hasError('required', ['required'])).toEqual(true);
@@ -1647,20 +1546,19 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelMultipleValidators);
fixture.componentInstance.required = false;
fixture.componentInstance.pattern = '[a-z]+';
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
const input = fixture.debugElement.query(By.css('input'));
input.nativeElement.value = '';
dispatchEvent(input.nativeElement, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toBeTruthy();
input.nativeElement.value = '1';
dispatchEvent(input.nativeElement, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toBeFalsy();
expect(form.control.hasError('pattern', ['tovalidate'])).toBeTruthy();
});
@@ -1669,20 +1567,19 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelMultipleValidators);
fixture.componentInstance.required = false;
fixture.componentInstance.pattern = /^[a-z]+$/;
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
const input = fixture.debugElement.query(By.css('input'));
input.nativeElement.value = '';
dispatchEvent(input.nativeElement, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toBeTruthy();
input.nativeElement.value = '1';
dispatchEvent(input.nativeElement, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toBeFalsy();
expect(form.control.hasError('pattern', ['tovalidate'])).toBeTruthy();
});
@@ -1691,28 +1588,26 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelMultipleValidators);
fixture.componentInstance.required = false;
fixture.componentInstance.minLen = 2;
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const form = fixture.debugElement.children[0].injector.get(NgForm);
const input = fixture.debugElement.query(By.css('input'));
input.nativeElement.value = '';
dispatchEvent(input.nativeElement, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toBeTruthy();
input.nativeElement.value = '1';
dispatchEvent(input.nativeElement, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toBeFalsy();
expect(form.control.hasError('minlength', ['tovalidate'])).toBeTruthy();
});
it('changes on bound properties should change the validation state of the form', async () => {
const fixture = initTest(NgModelValidationBindings);
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const required = fixture.debugElement.query(By.css('[name=required]'));
const minLength = fixture.debugElement.query(By.css('[name=minlength]'));
@@ -1740,7 +1635,8 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.minLen = 3;
fixture.componentInstance.maxLen = 3;
fixture.componentInstance.pattern = '.{3,}';
- fixture.detectChanges();
+ fixture.changeDetectorRef.markForCheck();
+ await fixture.whenStable();
dispatchEvent(required.nativeElement, 'input');
dispatchEvent(minLength.nativeElement, 'input');
@@ -1768,7 +1664,8 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.minLen = null!;
fixture.componentInstance.maxLen = null!;
fixture.componentInstance.pattern = null!;
- fixture.detectChanges();
+ fixture.changeDetectorRef.markForCheck();
+ await fixture.whenStable();
expect(form.control.hasError('required', ['required'])).toEqual(false);
expect(form.control.hasError('minlength', ['minlength'])).toEqual(false);
@@ -1788,8 +1685,7 @@ describe('template-driven forms integration tests', () => {
const inputNativeEl = inputEl.nativeElement;
const onNgModelChange = jasmine.createSpy('onNgModelChange');
fixture.componentInstance.onNgModelChange = onNgModelChange;
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(onNgModelChange).not.toHaveBeenCalled();
@@ -1800,7 +1696,7 @@ describe('template-driven forms integration tests', () => {
});
dispatchEvent(inputNativeEl, 'input');
expect(onNgModelChange).toHaveBeenCalled();
- await timeout();
+ await fixture.whenStable();
inputNativeEl.value = '333';
onNgModelChange.and.callFake((ngModel: NgModel) => {
@@ -1809,51 +1705,49 @@ describe('template-driven forms integration tests', () => {
});
dispatchEvent(inputNativeEl, 'input');
expect(onNgModelChange).toHaveBeenCalledTimes(2);
- await timeout();
+ await fixture.whenStable();
});
it('should validate max', async () => {
const fixture = initTest(NgModelMaxValidator);
fixture.componentInstance.max = 10;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
input.value = '';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(input.getAttribute('max')).toEqual('10');
expect(form.valid).toEqual(true);
expect(form.controls['max'].errors).toBeNull();
input.value = 11;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['max'].errors).toEqual({max: {max: 10, actual: 11}});
input.value = 9;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['max'].errors).toBeNull();
fixture.componentInstance.max = 0;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(input.getAttribute('max')).toEqual('0');
expect(form.valid).toEqual(false);
expect(form.controls['max'].errors).toEqual({max: {max: 0, actual: 9}});
input.value = 0;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['max'].errors).toBeNull();
});
@@ -1861,34 +1755,33 @@ describe('template-driven forms integration tests', () => {
it('should validate max for float number', async () => {
const fixture = initTest(NgModelMaxValidator);
fixture.componentInstance.max = 10.25;
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
input.value = '';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(input.getAttribute('max')).toEqual('10.25');
expect(form.valid).toEqual(true);
expect(form.controls['max'].errors).toBeNull();
input.value = 10.25;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['max'].errors).toBeNull();
input.value = 10.15;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['max'].errors).toBeNull();
input.value = 10.35;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['max'].errors).toEqual({max: {max: 10.25, actual: 10.35}});
});
@@ -1896,22 +1789,21 @@ describe('template-driven forms integration tests', () => {
it('should apply max validation when control value is defined as a string', async () => {
const fixture = initTest(NgModelMaxValidator);
fixture.componentInstance.max = 10;
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
input.value = '11';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(input.getAttribute('max')).toEqual('10');
expect(form.valid).toEqual(false);
expect(form.controls['max'].errors).toEqual({max: {max: 10, actual: 11}});
input.value = '9';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['max'].errors).toBeNull();
});
@@ -1920,27 +1812,26 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelMaxValidator);
fixture.componentInstance.max = 10;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
input.value = 11;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['max'].errors).toEqual({max: {max: 10, actual: 11}});
input.value = 9;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['max'].errors).toBeNull();
fixture.componentInstance.max = 5;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['max'].errors).toEqual({max: {max: 5, actual: 9}});
});
@@ -1949,45 +1840,43 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelMinValidator);
fixture.componentInstance.min = 10;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
input.value = '';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(input.getAttribute('min')).toEqual('10');
expect(form.valid).toEqual(true);
expect(form.controls['min'].errors).toBeNull();
input.value = 11;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min'].errors).toBeNull();
input.value = 9;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['min'].errors).toEqual({min: {min: 10, actual: 9}});
fixture.componentInstance.min = 0;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
input.value = -5;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(input.getAttribute('min')).toEqual('0');
expect(form.valid).toEqual(false);
expect(form.controls['min'].errors).toEqual({min: {min: 0, actual: -5}});
input.value = 0;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min'].errors).toBeNull();
});
@@ -1995,56 +1884,54 @@ describe('template-driven forms integration tests', () => {
it('should validate min for float number', async () => {
const fixture = initTest(NgModelMinValidator);
fixture.componentInstance.min = 10.25;
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
input.value = '';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(input.getAttribute('min')).toEqual('10.25');
expect(form.valid).toEqual(true);
expect(form.controls['min'].errors).toBeNull();
input.value = 10.35;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min'].errors).toBeNull();
input.value = 10.25;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min'].errors).toBeNull();
input.value = 10.15;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['min'].errors).toEqual({min: {min: 10.25, actual: 10.15}});
});
it('should apply min validation when control value is defined as a string', async () => {
const fixture = initTest(NgModelMinValidator);
fixture.componentInstance.min = 10;
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
input.value = '11';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(input.getAttribute('min')).toEqual('10');
expect(form.valid).toEqual(true);
expect(form.controls['min'].errors).toBeNull();
input.value = '9';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['min'].errors).toEqual({min: {min: 10, actual: 9}});
});
@@ -2053,27 +1940,26 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelMinValidator);
fixture.componentInstance.min = 10;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
input.value = 11;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min'].errors).toBeNull();
input.value = 9;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['min'].errors).toEqual({min: {min: 10, actual: 9}});
fixture.componentInstance.min = 9;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min'].errors).toBeNull();
});
@@ -2084,8 +1970,7 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.min = 10;
fixture.componentInstance.max = 20;
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const min = fixture.debugElement.query(By.directive(MinValidator));
expect(min).toBeNull();
@@ -2134,8 +2019,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(AppComponent, MyCustomComponentDirective);
const validateFnSpy = spyOn(MaxValidator.prototype, 'validate');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const mv = fixture.debugElement.query(By.directive(MaxValidator));
expect(mv).toBeNull();
@@ -2158,8 +2042,7 @@ describe('template-driven forms integration tests', () => {
const maxValidateFnSpy = spyOn(MaxValidator.prototype, 'validate');
const minValidateFnSpy = spyOn(MinValidator.prototype, 'validate');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const maxValidator = fixture.debugElement.query(By.directive(MaxValidator));
expect(maxValidator).toBeNull();
@@ -2186,8 +2069,7 @@ describe('template-driven forms integration tests', () => {
}
const fixture = initTest(MinLengthMaxLengthComponent);
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
@@ -2205,20 +2087,20 @@ describe('template-driven forms integration tests', () => {
failedValidator?: string;
}
- const setInputValue = (value: number) => {
+ const setInputValue = async (value: number) => {
input.value = value;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
};
const verifyValidatorAttrValues = (values: {minlength: any; maxlength: any}) => {
expect(input.getAttribute('minlength')).toBe(values.minlength);
expect(input.getAttribute('maxlength')).toBe(values.maxlength);
};
- const setValidatorValues = (values: minmax) => {
+ const setValidatorValues = async (values: minmax) => {
fixture.componentInstance.minlen = values.minlength;
fixture.componentInstance.maxlen = values.maxlength;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
+ await fixture.whenStable();
};
const verifyFormState = (state: state) => {
expect(form.valid).toBe(state.isValid);
@@ -2235,29 +2117,29 @@ describe('template-driven forms integration tests', () => {
// 2. Verify that setting validator inputs (to a value different from `null`) activate
// validators.
- setInputValue(12345);
- setValidatorValues({minlength: 2, maxlength: 4});
+ await setInputValue(12345);
+ await setValidatorValues({minlength: 2, maxlength: 4});
verifyValidatorAttrValues({minlength: '2', maxlength: '4'});
verifyFormState({isValid: false, failedValidator: 'maxlength'});
// 3. Changing value to the valid range should make the form valid.
- setInputValue(123);
+ await setInputValue(123);
verifyFormState({isValid: true});
// 4. Changing value to trigger `minlength` validator.
- setInputValue(1);
+ await setInputValue(1);
verifyFormState({isValid: false, failedValidator: 'minlength'});
// 5. Changing validator inputs to verify that attribute values are updated (and the
// form is now valid).
- setInputValue(1);
- setValidatorValues({minlength: 1, maxlength: 5});
+ await setInputValue(1);
+ await setValidatorValues({minlength: 1, maxlength: 5});
verifyValidatorAttrValues({minlength: '1', maxlength: '5'});
verifyFormState({isValid: true});
// 6. Reset validator inputs back to `null` should deactivate validators.
- setInputValue(123);
- setValidatorValues({minlength: null, maxlength: null});
+ await setInputValue(123);
+ await setValidatorValues({minlength: null, maxlength: null});
verifyValidatorAttrValues({minlength: null, maxlength: null});
verifyFormState({isValid: true});
});
@@ -2276,8 +2158,7 @@ describe('template-driven forms integration tests', () => {
}
const fixture = initTest(MinLengthMaxLengthComponent);
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
@@ -2295,20 +2176,20 @@ describe('template-driven forms integration tests', () => {
failedValidator?: string;
}
- const setInputValue = (value: number) => {
+ const setInputValue = async (value: number) => {
input.value = value;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
};
const verifyValidatorAttrValues = (values: {min: any; max: any}) => {
expect(input.getAttribute('min')).toBe(values.min);
expect(input.getAttribute('max')).toBe(values.max);
};
- const setValidatorValues = (values: minmax) => {
+ const setValidatorValues = async (values: minmax) => {
fixture.componentInstance.minlen = values.min;
fixture.componentInstance.maxlen = values.max;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
+ await fixture.whenStable();
};
const verifyFormState = (state: state) => {
expect(form.valid).toBe(state.isValid);
@@ -2325,29 +2206,29 @@ describe('template-driven forms integration tests', () => {
// 2. Verify that setting validator inputs (to a value different from `null`) activate
// validators.
- setInputValue(12345);
- setValidatorValues({min: 2, max: 4});
+ await setInputValue(12345);
+ await setValidatorValues({min: 2, max: 4});
verifyValidatorAttrValues({min: '2', max: '4'});
verifyFormState({isValid: false, failedValidator: 'max'});
// 3. Changing value to the valid range should make the form valid.
- setInputValue(3);
+ await setInputValue(3);
verifyFormState({isValid: true});
// 4. Changing value to trigger `minlength` validator.
- setInputValue(1);
+ await setInputValue(1);
verifyFormState({isValid: false, failedValidator: 'min'});
// 5. Changing validator inputs to verify that attribute values are updated (and the
// form is now valid).
- setInputValue(1);
- setValidatorValues({min: 1, max: 5});
+ await setInputValue(1);
+ await setValidatorValues({min: 1, max: 5});
verifyValidatorAttrValues({min: '1', max: '5'});
verifyFormState({isValid: true});
// 6. Reset validator inputs back to `null` should deactivate validators.
- setInputValue(123);
- setValidatorValues({min: null, max: null});
+ await setInputValue(123);
+ await setValidatorValues({min: null, max: null});
verifyValidatorAttrValues({min: null, max: null});
verifyFormState({isValid: true});
});
@@ -2360,33 +2241,32 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.min = inputType === 'string' ? '5' : 5;
fixture.componentInstance.max = inputType === 'string' ? '10' : 10;
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
input.value = '';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min_max'].errors).toBeNull();
input.value = 11;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['min_max'].errors).toEqual({max: {max: 10, actual: 11}});
input.value = 4;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['min_max'].errors).toEqual({min: {min: 5, actual: 4}});
input.value = 9;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min_max'].errors).toBeNull();
});
@@ -2395,33 +2275,32 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelMinMaxValidator);
fixture.componentInstance.min = 5;
fixture.componentInstance.max = 10;
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
input.value = '';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min_max'].errors).toBeNull();
input.value = 11;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['min_max'].errors).toEqual({max: {max: 10, actual: 11}});
input.value = 4;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['min_max'].errors).toEqual({min: {min: 5, actual: 4}});
input.value = 9;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min_max'].errors).toBeNull();
});
@@ -2430,33 +2309,32 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelMinMaxValidator);
fixture.componentInstance.min = 5;
fixture.componentInstance.max = 10;
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
input.value = '';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min_max'].errors).toBeNull();
input.value = '11';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['min_max'].errors).toEqual({max: {max: 10, actual: 11}});
input.value = '4';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['min_max'].errors).toEqual({min: {min: 5, actual: 4}});
input.value = '9';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min_max'].errors).toBeNull();
});
@@ -2466,45 +2344,44 @@ describe('template-driven forms integration tests', () => {
fixture.componentInstance.min = 5;
fixture.componentInstance.max = 10;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
input.value = 10;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min_max'].errors).toBeNull();
input.value = 12;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['min_max'].errors).toEqual({max: {max: 10, actual: 12}});
fixture.componentInstance.max = 12;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min_max'].errors).toBeNull();
input.value = 5;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min_max'].errors).toBeNull();
input.value = 0;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(false);
expect(form.controls['min_max'].errors).toEqual({min: {min: 5, actual: 0}});
fixture.componentInstance.min = 0;
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min_max'].errors).toBeNull();
});
@@ -2513,8 +2390,7 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelMinMaxValidator);
fixture.componentInstance.min = 5;
fixture.componentInstance.max = 10;
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
@@ -2524,7 +2400,7 @@ describe('template-driven forms integration tests', () => {
input.value = '';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toEqual(true);
expect(form.controls['min_max'].errors).toBeNull();
@@ -2536,33 +2412,32 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(NgModelMinMaxValidator);
fixture.componentInstance.min = -20;
fixture.componentInstance.max = -10;
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
const form = fixture.debugElement.children[0].injector.get(NgForm);
input.value = '-30';
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toBeFalse();
expect(form.controls['min_max'].errors).toEqual({min: {min: -20, actual: -30}});
input.value = -15;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toBeTruthy();
expect(form.controls['min_max'].errors).toBeNull();
input.value = -5;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toBeFalse();
expect(form.controls['min_max'].errors).toEqual({max: {max: -10, actual: -5}});
input.value = 0;
dispatchEvent(input, 'input');
- fixture.detectChanges();
+ await fixture.whenStable();
expect(form.valid).toBeFalse();
expect(form.controls['min_max'].errors).toEqual({max: {max: -10, actual: 0}});
});
@@ -2636,15 +2511,14 @@ describe('template-driven forms integration tests', () => {
}
const fixture = initTest(NgModelNoOpValidation, NoOpValidator, NoOpAsyncValidator);
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(registerOnValidatorChangeFired).toBe(1);
expect(registerOnAsyncValidatorChangeFired).toBe(1);
fixture.componentInstance.validatorInput = 'bar';
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
+ await fixture.whenStable();
// Changing validator inputs should not cause `registerOnValidatorChange` to be invoked,
// since it's invoked just once during the setup phase.
@@ -2659,15 +2533,14 @@ describe('template-driven forms integration tests', () => {
const inputEl = fixture.debugElement.query(By.css('input'));
const inputNativeEl = inputEl.nativeElement;
fixture.componentInstance.name = 'oldValue';
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(inputNativeEl.value).toEqual('oldValue');
inputEl.triggerEventHandler('compositionstart');
inputNativeEl.value = 'updatedValue';
dispatchEvent(inputNativeEl, 'input');
- await timeout();
+ await fixture.whenStable();
const isAndroid = /android (\d+)/.test(getDOM().getUserAgent().toLowerCase());
if (isAndroid) {
@@ -2679,8 +2552,7 @@ describe('template-driven forms integration tests', () => {
inputEl.triggerEventHandler('compositionend', {target: {value: 'updatedValue'}});
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(fixture.componentInstance.name).toEqual('updatedValue');
}
@@ -2694,23 +2566,21 @@ describe('template-driven forms integration tests', () => {
const inputEl = fixture.debugElement.query(By.css('input'));
const inputNativeEl = inputEl.nativeElement;
fixture.componentInstance.name = 'oldValue';
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(inputNativeEl.value).toEqual('oldValue');
inputEl.triggerEventHandler('compositionstart');
inputNativeEl.value = 'updatedValue';
dispatchEvent(inputNativeEl, 'input');
- await timeout();
+ await fixture.whenStable();
// ngModel should not update when compositionstart
expect(fixture.componentInstance.name).toEqual('oldValue');
inputEl.triggerEventHandler('compositionend', {target: {value: 'updatedValue'}});
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
// ngModel should update when compositionend
expect(fixture.componentInstance.name).toEqual('updatedValue');
@@ -2725,15 +2595,14 @@ describe('template-driven forms integration tests', () => {
const inputEl = fixture.debugElement.query(By.css('input'));
const inputNativeEl = inputEl.nativeElement;
fixture.componentInstance.name = 'oldValue';
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(inputNativeEl.value).toEqual('oldValue');
inputEl.triggerEventHandler('compositionstart');
inputNativeEl.value = 'updatedValue';
dispatchEvent(inputNativeEl, 'input');
- await timeout();
+ await fixture.whenStable();
// ngModel should update normally
expect(fixture.componentInstance.name).toEqual('updatedValue');
@@ -2745,64 +2614,59 @@ describe('template-driven forms integration tests', () => {
const fixture = initTest(StandaloneNgModel);
fixture.componentInstance.name = '';
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'aa';
input.selectionStart = 1;
dispatchEvent(input, 'input');
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(fixture.componentInstance.name).toEqual('aa');
// Programmatically update the input value to be "bb".
fixture.componentInstance.name = 'bb';
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(input.value).toEqual('bb');
// Programatically set it back to "aa".
fixture.componentInstance.name = 'aa';
fixture.changeDetectorRef.markForCheck();
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(input.value).toEqual('aa');
});
it('should not crash when validity is checked from a binding', async () => {
const fixture = initTest(NgModelValidBinding);
- await timeout();
- expect(() => fixture.detectChanges()).not.toThrowError();
+ await expectAsync(fixture.whenStable()).toBeResolved();
});
describe('cross-component boundary warning', () => {
- it('should warn when ngModel in a child component cannot reach parent NgForm via @Host()', () => {
+ it('should warn when ngModel in a child component cannot reach parent NgForm via @Host()', async () => {
const warnSpy = spyOn(console, 'warn');
const fixture = initTest(NgModelCrossComponentParent, NgModelCrossComponentChild);
- fixture.detectChanges();
+ await fixture.whenStable();
expect(warnSpy).toHaveBeenCalledWith(jasmine.stringContaining('NgForm'));
expect(warnSpy).toHaveBeenCalledWith(jasmine.stringContaining('viewProviders'));
});
- it('should warn with FormGroupDirective name when ngModel cannot reach parent FormGroupDirective via @Host()', () => {
+ it('should warn with FormGroupDirective name when ngModel cannot reach parent FormGroupDirective via @Host()', async () => {
const warnSpy = spyOn(console, 'warn');
TestBed.configureTestingModule({
declarations: [NgModelCrossComponentFormGroupParent, NgModelCrossComponentFormGroupChild],
imports: [FormsModule, ReactiveFormsModule, CommonModule],
});
const fixture = TestBed.createComponent(NgModelCrossComponentFormGroupParent);
- fixture.detectChanges();
+ await fixture.whenStable();
expect(warnSpy).toHaveBeenCalledWith(jasmine.stringContaining('FormGroupDirective'));
expect(warnSpy).toHaveBeenCalledWith(jasmine.stringContaining('viewProviders'));
});
- it('should warn with NgModelGroup name when ngModel cannot reach parent NgModelGroup via @Host()', () => {
+ it('should warn with NgModelGroup name when ngModel cannot reach parent NgModelGroup via @Host()', async () => {
const warnSpy = spyOn(console, 'warn');
const fixture = initTest(NgModelCrossComponentGroupParent, NgModelCrossComponentGroupChild);
- fixture.detectChanges();
+ await fixture.whenStable();
expect(warnSpy).toHaveBeenCalledWith(jasmine.stringContaining('NgModelGroup'));
expect(warnSpy).not.toHaveBeenCalledWith(
jasmine.stringContaining('useExisting: ControlContainer'),
@@ -2812,30 +2676,28 @@ describe('template-driven forms integration tests', () => {
it('should not warn when ngModel is in the same component as NgForm', async () => {
const warnSpy = spyOn(console, 'warn');
const fixture = initTest(NgModelForm);
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(warnSpy).not.toHaveBeenCalledWith(jasmine.stringContaining('viewProviders'));
});
- it('should not warn when child component uses viewProviders to bridge ControlContainer', () => {
+ it('should not warn when child component uses viewProviders to bridge ControlContainer', async () => {
const warnSpy = spyOn(console, 'warn');
const fixture = initTest(
NgModelCrossComponentParentWithViewProviders,
NgModelCrossComponentChildWithViewProviders,
);
- fixture.detectChanges();
+ await fixture.whenStable();
expect(warnSpy).not.toHaveBeenCalledWith(jasmine.stringContaining('viewProviders'));
});
it('should not warn when ngModel is standalone with no parent form', async () => {
const warnSpy = spyOn(console, 'warn');
const fixture = initTest(StandaloneNgModel);
- fixture.detectChanges();
- await timeout();
+ await fixture.whenStable();
expect(warnSpy).not.toHaveBeenCalledWith(jasmine.stringContaining('viewProviders'));
});
- it('should not warn when ngModel in a child component uses [ngModelOptions]="{standalone: true}"', () => {
+ it('should not warn when ngModel in a child component uses [ngModelOptions]="{standalone: true}"', async () => {
const warnSpy = spyOn(console, 'warn');
TestBed.configureTestingModule({
declarations: [
@@ -2845,18 +2707,18 @@ describe('template-driven forms integration tests', () => {
imports: [FormsModule],
});
const fixture = TestBed.createComponent(NgModelCrossComponentParentStandaloneOpt);
- fixture.detectChanges();
+ await fixture.whenStable();
expect(warnSpy).not.toHaveBeenCalledWith(jasmine.stringContaining('viewProviders'));
});
- it('should not warn when ngModel inside a ControlValueAccessor uses [ngModelOptions]="{standalone: true}"', () => {
+ it('should not warn when ngModel inside a ControlValueAccessor uses [ngModelOptions]="{standalone: true}"', async () => {
const warnSpy = spyOn(console, 'warn');
TestBed.configureTestingModule({
declarations: [NgModelCvaHostParent, NgModelCvaWithInternalNgModel],
imports: [FormsModule],
});
const fixture = TestBed.createComponent(NgModelCvaHostParent);
- fixture.detectChanges();
+ await fixture.whenStable();
expect(warnSpy).not.toHaveBeenCalledWith(jasmine.stringContaining('viewProviders'));
});
});
@@ -3094,7 +2956,8 @@ class NgModelEmailValidator {
})
class NgAsyncValidator implements AsyncValidator {
validate(c: AbstractControl) {
- return Promise.resolve(null);
+ // Delay validation so the scheduled renders can observe the pending state.
+ return timeout(100).then(() => null);
}
}
diff --git a/packages/forms/test/value_accessor_integration_spec.ts b/packages/forms/test/value_accessor_integration_spec.ts
index 4f474db62d9..fce40422527 100644
--- a/packages/forms/test/value_accessor_integration_spec.ts
+++ b/packages/forms/test/value_accessor_integration_spec.ts
@@ -54,14 +54,14 @@ describe('value accessors', () => {
return TestBed.createComponent(component);
}
- it('should support without type', () => {
+ it('should support without type', async () => {
TestBed.overrideComponent(FormControlComp, {
set: {template: ``},
});
const fixture = initTest(FormControlComp);
const control = new FormControl('old');
fixture.componentInstance.control = control;
- fixture.detectChanges();
+ await fixture.whenStable();
// model -> view
const input = fixture.debugElement.query(By.css('input'));
@@ -74,11 +74,11 @@ describe('value accessors', () => {
expect(control.value).toEqual('new');
});
- it('should support ', () => {
+ it('should support ', async () => {
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('old')});
fixture.componentInstance.form = form;
- fixture.detectChanges();
+ await fixture.whenStable();
// model -> view
const input = fixture.debugElement.query(By.css('input'));
@@ -91,11 +91,11 @@ describe('value accessors', () => {
expect(form.value).toEqual({'login': 'new'});
});
- it('should ignore the change event for ', () => {
+ it('should ignore the change event for ', async () => {
const fixture = initTest(FormGroupComp);
const form = new FormGroup({'login': new FormControl('oldValue')});
fixture.componentInstance.form = form;
- fixture.detectChanges();
+ await fixture.whenStable();
const input = fixture.debugElement.query(By.css('input'));
form.valueChanges.subscribe({
@@ -108,14 +108,14 @@ describe('value accessors', () => {
dispatchEvent(input.nativeElement, 'change');
});
- it('should support