fix(testing): Check for pending macrotasks in ComponentFixture.whenStable() and ComponentFixture.isStable()

Closes #8389
This commit is contained in:
Vikram Subramanian 2016-05-03 11:26:07 -07:00 committed by Martin Probst
parent 27a7b51d99
commit 509f4ec611
2 changed files with 138 additions and 7 deletions

View File

@ -25,6 +25,7 @@ import {
ComponentResolver ComponentResolver
} from '@angular/core'; } from '@angular/core';
import {NgIf} from '@angular/common'; import {NgIf} from '@angular/common';
import {TimerWrapper} from '../src/facade/async';
import {IS_DART} from '../src/facade/lang'; import {IS_DART} from '../src/facade/lang';
import {PromiseWrapper} from '../src/facade/promise'; import {PromiseWrapper} from '../src/facade/promise';
import {dispatchEvent} from "@angular/platform-browser/testing"; import {dispatchEvent} from "@angular/platform-browser/testing";
@ -123,6 +124,26 @@ class AsyncChangeComp {
click() { this.text += '1'; } click() { this.text += '1'; }
} }
@Component({selector: 'async-timeout-comp', template: `<span (click)='click()'>{{text}}</span>`})
class AsyncTimeoutComp {
text: string = '1';
click() {
TimerWrapper.setTimeout(() => { this.text += '1'; }, 10);
}
}
@Component(
{selector: 'nested-async-timeout-comp', template: `<span (click)='click()'>{{text}}</span>`})
class NestedAsyncTimeoutComp {
text: string = '1';
click() {
TimerWrapper.setTimeout(() => { TimerWrapper.setTimeout(() => { this.text += '1'; }, 10); },
10);
}
}
class FancyService { class FancyService {
value: string = 'real value'; value: string = 'real value';
} }
@ -378,6 +399,108 @@ export function main() {
}); });
})); }));
it('should wait for macroTask(setTimeout) while checking for whenStable ' +
'(autoDetectChanges)',
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.createAsync(AsyncTimeoutComp)
.then((componentFixture) => {
componentFixture.autoDetectChanges();
expect(componentFixture.nativeElement).toHaveText('1');
let element = componentFixture.debugElement.children[0];
dispatchEvent(element.nativeElement, 'click');
expect(componentFixture.nativeElement).toHaveText('1');
// Component is updated asynchronously. Wait for the fixture to become
// stable before checking for new value.
expect(componentFixture.isStable()).toBe(false);
componentFixture.whenStable().then((waited) => {
expect(waited).toBe(true);
expect(componentFixture.nativeElement).toHaveText('11');
async.done();
});
});
}));
it('should wait for macroTask(setTimeout) while checking for whenStable ' +
'(no autoDetectChanges)',
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.createAsync(AsyncTimeoutComp)
.then((componentFixture) => {
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('1');
let element = componentFixture.debugElement.children[0];
dispatchEvent(element.nativeElement, 'click');
expect(componentFixture.nativeElement).toHaveText('1');
// Component is updated asynchronously. Wait for the fixture to become
// stable before checking for new value.
expect(componentFixture.isStable()).toBe(false);
componentFixture.whenStable().then((waited) => {
expect(waited).toBe(true);
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('11');
async.done();
});
});
}));
it('should wait for nested macroTasks(setTimeout) while checking for whenStable ' +
'(autoDetectChanges)',
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.createAsync(NestedAsyncTimeoutComp)
.then((componentFixture) => {
componentFixture.autoDetectChanges();
expect(componentFixture.nativeElement).toHaveText('1');
let element = componentFixture.debugElement.children[0];
dispatchEvent(element.nativeElement, 'click');
expect(componentFixture.nativeElement).toHaveText('1');
// Component is updated asynchronously. Wait for the fixture to become
// stable before checking for new value.
expect(componentFixture.isStable()).toBe(false);
componentFixture.whenStable().then((waited) => {
expect(waited).toBe(true);
expect(componentFixture.nativeElement).toHaveText('11');
async.done();
});
});
}));
it('should wait for nested macroTasks(setTimeout) while checking for whenStable ' +
'(no autoDetectChanges)',
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
tcb.createAsync(NestedAsyncTimeoutComp)
.then((componentFixture) => {
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('1');
let element = componentFixture.debugElement.children[0];
dispatchEvent(element.nativeElement, 'click');
expect(componentFixture.nativeElement).toHaveText('1');
// Component is updated asynchronously. Wait for the fixture to become
// stable before checking for new value.
expect(componentFixture.isStable()).toBe(false);
componentFixture.whenStable().then((waited) => {
expect(waited).toBe(true);
componentFixture.detectChanges();
expect(componentFixture.nativeElement).toHaveText('11');
async.done();
});
});
}));
it('should stabilize after async task in change detection (autoDetectChanges)', it('should stabilize after async task in change detection (autoDetectChanges)',
inject([TestComponentBuilder, AsyncTestCompleter], inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => { (tcb: TestComponentBuilder, async) => {

View File

@ -16,7 +16,7 @@ import {
import {DirectiveResolver, ViewResolver} from '../index'; import {DirectiveResolver, ViewResolver} from '../index';
import {BaseException} from '../src/facade/exceptions'; import {BaseException} from '../src/facade/exceptions';
import {Type, isPresent, isBlank, IS_DART} from '../src/facade/lang'; import {Type, isPresent, isBlank, IS_DART, scheduleMicroTask} from '../src/facade/lang';
import {PromiseWrapper, ObservableWrapper, PromiseCompleter} from '../src/facade/async'; import {PromiseWrapper, ObservableWrapper, PromiseCompleter} from '../src/facade/async';
import {ListWrapper, MapWrapper} from '../src/facade/collection'; import {ListWrapper, MapWrapper} from '../src/facade/collection';
@ -102,10 +102,16 @@ export class ComponentFixture<T> {
}); });
this._onStableSubscription = ObservableWrapper.subscribe(ngZone.onStable, (_) => { this._onStableSubscription = ObservableWrapper.subscribe(ngZone.onStable, (_) => {
this._isStable = true; this._isStable = true;
if (this._completer != null) { // Check whether there are no pending macrotasks in a microtask so that ngZone gets a chance
this._completer.resolve(true); // to update the state of pending macrotasks.
this._completer = null; scheduleMicroTask(() => {
} if (!this.ngZone.hasPendingMacrotasks) {
if (this._completer != null) {
this._completer.resolve(true);
this._completer = null;
}
}
});
}); });
this._onErrorSubscription = ObservableWrapper.subscribe( this._onErrorSubscription = ObservableWrapper.subscribe(
@ -156,7 +162,7 @@ export class ComponentFixture<T> {
* Return whether the fixture is currently stable or has async tasks that have not been completed * Return whether the fixture is currently stable or has async tasks that have not been completed
* yet. * yet.
*/ */
isStable(): boolean { return this._isStable; } isStable(): boolean { return this._isStable && !this.ngZone.hasPendingMacrotasks; }
/** /**
* Get a promise that resolves when the fixture is stable. * Get a promise that resolves when the fixture is stable.
@ -165,8 +171,10 @@ export class ComponentFixture<T> {
* asynchronous change detection. * asynchronous change detection.
*/ */
whenStable(): Promise<any> { whenStable(): Promise<any> {
if (this._isStable) { if (this.isStable()) {
return PromiseWrapper.resolve(false); return PromiseWrapper.resolve(false);
} else if (this._completer !== null) {
return this._completer.promise;
} else { } else {
this._completer = new PromiseCompleter<any>(); this._completer = new PromiseCompleter<any>();
return this._completer.promise; return this._completer.promise;