I wanted to use template forms and [min] and [max] directives, so I have created them and they work. But the test confuses me: validation is not executed asynchronously, yet after changing my values and stuff, I have to go through this:
component.makeSomeChangeThatInvalidatesMyInput();
// control.invalid = false, expected
fixture.detectChanges();
// control.invalid is still false, not expected
// but if I go do this
fixture.whenStable().then(() => {
// control.invalid is STILL false, not expected
fixture.detectChanges();
// control.invalid now true
// expect(... .errors ... ) now passes
})
I don't understand why would I need even that whenStable(), let alone another detectChanges() cycle. What am I missing here? Why do I need 2 cycles of change detection for this validation to be executed?
Doesn't matter if I run the test as async or not.
Here's my test:
@Component({
selector: 'test-cmp',
template: `<form>
<input [max]="maxValue" [(ngModel)]="numValue" name="numValue" #val="ngModel">
<span class="error" *ngIf="val.invalid">Errors there.</span>
</form>`
})
class TestMaxDirectiveComponent {
maxValue: number;
numValue: number;
}
fdescribe('ValidateMaxDirective', () => {
let fixture: ComponentFixture<TestMaxDirectiveComponent>;
let component: TestMaxDirectiveComponent;
beforeEach(async(() => TestBed.configureTestingModule({
imports: [FormsModule],
declarations: [TestMaxDirectiveComponent, ValidateMaxDirective],
}).compileComponents()
.then(() => {
fixture = TestBed.createComponent(TestMaxDirectiveComponent);
component = fixture.componentInstance;
return fixture.detectChanges();
})
));
fit('should have errors even when value is greater than maxValue', async(() => {
component.numValue = 42;
component.maxValue = 2;
fixture.detectChanges();
fixture.whenStable().then(() => {
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('.error')).toBeTruthy();
});
}));
});
And here's the directive itself (simplified a bit):
const VALIDATE_MAX_PROVIDER = {
provide: NG_VALIDATORS, useExisting: forwardRef(() => ValidateMaxDirective), multi: true,
};
@Directive({
selector: '[max][ngModel]',
providers: [VALIDATE_MAX_PROVIDER],
})
export class ValidateMaxDirective implements Validator {
private _max: number | string;
@Input() get max(): number | string {
return this._max;
}
set max(value: number | string) {
this._max = value;
}
validate(control: AbstractControl): ValidationErrors | null {
if (isEmptyInputValue(control.value) || isEmptyInputValue(this._max)) {
return null; // don't validate empty values to allow optional controls
}
const value = parseFloat(control.value);
return !isNaN(value) && value > this._max ? {'max': {'max': this._max, 'actual': control.value}} : null;
}
}
I have tested this on a brand new ng new app with @angular/cli version 1.6.8 and latest angular 5.2.

whenStablebefore as you do, but tests are very complex to get them right for non simple test case like testing many condition one after another. I prefer to create non async tests using fakeAsync and/or tick. this way you have more control on your tests. you can try to create a demo like I did here in one of my answers that you can let people to understand and help you more: stackblitz.com/edit/angular-testing-c25ezq?file=app/….detectChanges();without importingChangeDetectorRef?