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;