diff --git a/modules/@angular/compiler/test/test_component_builder_spec.ts b/modules/@angular/compiler/test/test_component_builder_spec.ts
index 0c9db5ae26..a59b049a75 100644
--- a/modules/@angular/compiler/test/test_component_builder_spec.ts
+++ b/modules/@angular/compiler/test/test_component_builder_spec.ts
@@ -25,6 +25,7 @@ import {
ComponentResolver
} from '@angular/core';
import {NgIf} from '@angular/common';
+import {TimerWrapper} from '../src/facade/async';
import {IS_DART} from '../src/facade/lang';
import {PromiseWrapper} from '../src/facade/promise';
import {dispatchEvent} from "@angular/platform-browser/testing";
@@ -123,6 +124,26 @@ class AsyncChangeComp {
click() { this.text += '1'; }
}
+@Component({selector: 'async-timeout-comp', template: `{{text}}`})
+class AsyncTimeoutComp {
+ text: string = '1';
+
+ click() {
+ TimerWrapper.setTimeout(() => { this.text += '1'; }, 10);
+ }
+}
+
+@Component(
+ {selector: 'nested-async-timeout-comp', template: `{{text}}`})
+class NestedAsyncTimeoutComp {
+ text: string = '1';
+
+ click() {
+ TimerWrapper.setTimeout(() => { TimerWrapper.setTimeout(() => { this.text += '1'; }, 10); },
+ 10);
+ }
+}
+
class FancyService {
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)',
inject([TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async) => {
diff --git a/modules/@angular/compiler/testing/test_component_builder.ts b/modules/@angular/compiler/testing/test_component_builder.ts
index dc26cee561..328fbe7db7 100644
--- a/modules/@angular/compiler/testing/test_component_builder.ts
+++ b/modules/@angular/compiler/testing/test_component_builder.ts
@@ -16,7 +16,7 @@ import {
import {DirectiveResolver, ViewResolver} from '../index';
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 {ListWrapper, MapWrapper} from '../src/facade/collection';
@@ -102,10 +102,16 @@ export class ComponentFixture {
});
this._onStableSubscription = ObservableWrapper.subscribe(ngZone.onStable, (_) => {
this._isStable = true;
- if (this._completer != null) {
- this._completer.resolve(true);
- this._completer = null;
- }
+ // Check whether there are no pending macrotasks in a microtask so that ngZone gets a chance
+ // to update the state of pending macrotasks.
+ scheduleMicroTask(() => {
+ if (!this.ngZone.hasPendingMacrotasks) {
+ if (this._completer != null) {
+ this._completer.resolve(true);
+ this._completer = null;
+ }
+ }
+ });
});
this._onErrorSubscription = ObservableWrapper.subscribe(
@@ -156,7 +162,7 @@ export class ComponentFixture {
* Return whether the fixture is currently stable or has async tasks that have not been completed
* yet.
*/
- isStable(): boolean { return this._isStable; }
+ isStable(): boolean { return this._isStable && !this.ngZone.hasPendingMacrotasks; }
/**
* Get a promise that resolves when the fixture is stable.
@@ -165,8 +171,10 @@ export class ComponentFixture {
* asynchronous change detection.
*/
whenStable(): Promise {
- if (this._isStable) {
+ if (this.isStable()) {
return PromiseWrapper.resolve(false);
+ } else if (this._completer !== null) {
+ return this._completer.promise;
} else {
this._completer = new PromiseCompleter();
return this._completer.promise;