From 5dabf81bdfc01e8d0ae7edc326b693f86c53dfc7 Mon Sep 17 00:00:00 2001
From: Jaime Burgos <73321943+SkyZeroZx@users.noreply.github.com>
Date: Wed, 9 Sep 2026 09:24:37 -0500
Subject: [PATCH] test(forms): migrate integration tests to zoneless scheduling
Use whenStable instead of manual change detection so integration tests exercise scheduled rendering. Await asynchronous callbacks and remove redundant timer waits to keep assertions within the test lifecycle.
(cherry picked from commit 8afd85c6d752207f975718dbc6306c5ae079f2ca)
---
.../forms/test/reactive_integration_spec.ts | 16 +-
.../forms/test/template_integration_spec.ts | 829 ++++++++----------
.../test/value_accessor_integration_spec.ts | 420 ++++-----
3 files changed, 543 insertions(+), 722 deletions(-)
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