@@ -3,6 +3,7 @@ library angular2.src.testing.test_component_builder;
33import "dart:async" ;
44import "package:angular2/core.dart"
55 show
6+ OpaqueToken,
67 ComponentRef,
78 DynamicComponentLoader,
89 Injector,
@@ -11,10 +12,15 @@ import "package:angular2/core.dart"
1112 ElementRef,
1213 EmbeddedViewRef,
1314 ChangeDetectorRef,
14- provide;
15+ provide,
16+ NgZone,
17+ NgZoneError;
1518import "package:angular2/compiler.dart" show DirectiveResolver, ViewResolver;
16- import "package:angular2/src/facade/lang.dart" show Type, isPresent, isBlank;
17- import "package:angular2/src/facade/async.dart" show PromiseWrapper;
19+ import "package:angular2/src/facade/exceptions.dart" show BaseException;
20+ import "package:angular2/src/facade/lang.dart"
21+ show Type, isPresent, isBlank, IS_DART;
22+ import "package:angular2/src/facade/async.dart"
23+ show PromiseWrapper, ObservableWrapper, PromiseCompleter;
1824import "package:angular2/src/facade/collection.dart"
1925 show ListWrapper, MapWrapper;
2026import "utils.dart" show el;
@@ -24,6 +30,9 @@ import "package:angular2/src/core/debug/debug_node.dart"
2430 show DebugNode, DebugElement, getDebugNode;
2531import "fake_async.dart" show tick;
2632
33+ var ComponentFixtureAutoDetect = new OpaqueToken ("ComponentFixtureAutoDetect" );
34+ var ComponentFixtureNoNgZone = new OpaqueToken ("ComponentFixtureNoNgZone" );
35+
2736/**
2837 * Fixture for debugging and testing a component.
2938 */
@@ -52,34 +61,144 @@ class ComponentFixture {
5261 * The ChangeDetectorRef for the component
5362 */
5463 ChangeDetectorRef changeDetectorRef;
55- ComponentFixture (ComponentRef componentRef) {
64+ /**
65+ * The NgZone in which this component was instantiated.
66+ */
67+ NgZone ngZone;
68+ bool _autoDetect;
69+ bool _isStable = true ;
70+ PromiseCompleter <dynamic > _completer = null ;
71+ var _onUnstableSubscription = null ;
72+ var _onStableSubscription = null ;
73+ var _onMicrotaskEmptySubscription = null ;
74+ var _onErrorSubscription = null ;
75+ ComponentFixture (ComponentRef componentRef, NgZone ngZone, bool autoDetect) {
5676 this .changeDetectorRef = componentRef.changeDetectorRef;
5777 this .elementRef = componentRef.location;
5878 this .debugElement =
5979 (getDebugNode (this .elementRef.nativeElement) as DebugElement );
6080 this .componentInstance = componentRef.instance;
6181 this .nativeElement = this .elementRef.nativeElement;
6282 this .componentRef = componentRef;
83+ this .ngZone = ngZone;
84+ this ._autoDetect = autoDetect;
85+ if (ngZone != null ) {
86+ this ._onUnstableSubscription =
87+ ObservableWrapper .subscribe (ngZone.onUnstable, (_) {
88+ this ._isStable = false ;
89+ });
90+ this ._onMicrotaskEmptySubscription =
91+ ObservableWrapper .subscribe (ngZone.onMicrotaskEmpty, (_) {
92+ if (this ._autoDetect) {
93+ // Do a change detection run with checkNoChanges set to true to check
94+
95+ // there are no changes on the second run.
96+ this .detectChanges (true );
97+ }
98+ });
99+ this ._onStableSubscription =
100+ ObservableWrapper .subscribe (ngZone.onStable, (_) {
101+ this ._isStable = true ;
102+ if (this ._completer != null ) {
103+ this ._completer.resolve (true );
104+ this ._completer = null ;
105+ }
106+ });
107+ this ._onErrorSubscription =
108+ ObservableWrapper .subscribe (ngZone.onError, (NgZoneError error) {
109+ throw error.error;
110+ });
111+ }
63112 }
113+ _tick (bool checkNoChanges) {
114+ this .changeDetectorRef.detectChanges ();
115+ if (checkNoChanges) {
116+ this .checkNoChanges ();
117+ }
118+ }
119+
64120 /**
65121 * Trigger a change detection cycle for the component.
66122 */
67123 void detectChanges ([bool checkNoChanges = true ]) {
68- this .changeDetectorRef.detectChanges ();
69- if (checkNoChanges) {
70- this .checkNoChanges ();
124+ if (this .ngZone != null ) {
125+ // Run the change detection inside the NgZone so that any async tasks as part of the change
126+
127+ // detection are captured by the zone and can be waited for in isStable.
128+ this .ngZone.run (() {
129+ this ._tick (checkNoChanges);
130+ });
131+ } else {
132+ // Running without zone. Just do the change detection.
133+ this ._tick (checkNoChanges);
71134 }
72135 }
73136
137+ /**
138+ * Do a change detection run to make sure there were no changes.
139+ */
74140 void checkNoChanges () {
75141 this .changeDetectorRef.checkNoChanges ();
76142 }
77143
144+ /**
145+ * Set whether the fixture should autodetect changes.
146+ *
147+ * Also runs detectChanges once so that any existing change is detected.
148+ */
149+ autoDetectChanges ([bool autoDetect = true ]) {
150+ if (this .ngZone == null ) {
151+ throw new BaseException (
152+ "Cannot call autoDetectChanges when ComponentFixtureNoNgZone is set" );
153+ }
154+ this ._autoDetect = autoDetect;
155+ this .detectChanges ();
156+ }
157+
158+ /**
159+ * Return whether the fixture is currently stable or has async tasks that have not been completed
160+ * yet.
161+ */
162+ bool isStable () {
163+ return this ._isStable;
164+ }
165+
166+ /**
167+ * Get a promise that resolves when the fixture is stable.
168+ *
169+ * This can be used to resume testing after events have triggered asynchronous activity or
170+ * asynchronous change detection.
171+ */
172+ Future <dynamic > whenStable () {
173+ if (this ._isStable) {
174+ return PromiseWrapper .resolve (false );
175+ } else {
176+ this ._completer = new PromiseCompleter <dynamic >();
177+ return this ._completer.promise;
178+ }
179+ }
180+
78181 /**
79182 * Trigger component destruction.
80183 */
81184 void destroy () {
82185 this .componentRef.destroy ();
186+ if (this ._onUnstableSubscription != null ) {
187+ ObservableWrapper .dispose (this ._onUnstableSubscription);
188+ this ._onUnstableSubscription = null ;
189+ }
190+ if (this ._onStableSubscription != null ) {
191+ ObservableWrapper .dispose (this ._onStableSubscription);
192+ this ._onStableSubscription = null ;
193+ }
194+ if (this ._onMicrotaskEmptySubscription != null ) {
195+ ObservableWrapper .dispose (this ._onMicrotaskEmptySubscription);
196+ this ._onMicrotaskEmptySubscription = null ;
197+ }
198+ if (this ._onErrorSubscription != null ) {
199+ ObservableWrapper .dispose (this ._onErrorSubscription);
200+ this ._onErrorSubscription = null ;
201+ }
83202 }
84203}
85204
@@ -228,38 +347,45 @@ class TestComponentBuilder {
228347 *
229348 */
230349 Future <ComponentFixture > createAsync (Type rootComponentType) {
231- var mockDirectiveResolver = this ._injector.get (DirectiveResolver );
232- var mockViewResolver = this ._injector.get (ViewResolver );
233- this
234- ._viewOverrides
235- .forEach ((type, view) => mockViewResolver.setView (type, view));
236- this ._templateOverrides.forEach (
237- (type, template) => mockViewResolver.setInlineTemplate (type, template));
238- this ._directiveOverrides.forEach ((component, overrides) {
239- overrides.forEach ((from, to) {
240- mockViewResolver.overrideViewDirective (component, from, to);
350+ var noNgZone =
351+ IS_DART || this ._injector.get (ComponentFixtureNoNgZone , false );
352+ NgZone ngZone = noNgZone ? null : this ._injector.get (NgZone , null );
353+ bool autoDetect = this ._injector.get (ComponentFixtureAutoDetect , false );
354+ var initComponent = () {
355+ var mockDirectiveResolver = this ._injector.get (DirectiveResolver );
356+ var mockViewResolver = this ._injector.get (ViewResolver );
357+ this
358+ ._viewOverrides
359+ .forEach ((type, view) => mockViewResolver.setView (type, view));
360+ this ._templateOverrides.forEach ((type, template) =>
361+ mockViewResolver.setInlineTemplate (type, template));
362+ this ._directiveOverrides.forEach ((component, overrides) {
363+ overrides.forEach ((from, to) {
364+ mockViewResolver.overrideViewDirective (component, from, to);
365+ });
241366 });
242- });
243- this ._bindingsOverrides.forEach ((type, bindings) =>
244- mockDirectiveResolver.setBindingsOverride (type, bindings));
245- this ._viewBindingsOverrides.forEach ((type, bindings) =>
246- mockDirectiveResolver.setViewBindingsOverride (type, bindings));
247- var rootElId = '''root${ _nextRootElementId ++}''' ;
248- var rootEl = el ('''<div id="${ rootElId }"></div>''' );
249- var doc = this ._injector.get (DOCUMENT );
250- // TODO(juliemr): can/should this be optional?
251- var oldRoots = DOM .querySelectorAll (doc, "[id^=root]" );
252- for (var i = 0 ; i < oldRoots.length; i++ ) {
253- DOM .remove (oldRoots[i]);
254- }
255- DOM .appendChild (doc.body, rootEl);
256- Future <ComponentRef > promise = this
257- ._injector
258- .get (DynamicComponentLoader )
259- .loadAsRoot (rootComponentType, '''#${ rootElId }''' , this ._injector);
260- return promise.then ((componentRef) {
261- return new ComponentFixture (componentRef);
262- });
367+ this ._bindingsOverrides.forEach ((type, bindings) =>
368+ mockDirectiveResolver.setBindingsOverride (type, bindings));
369+ this ._viewBindingsOverrides.forEach ((type, bindings) =>
370+ mockDirectiveResolver.setViewBindingsOverride (type, bindings));
371+ var rootElId = '''root${ _nextRootElementId ++}''' ;
372+ var rootEl = el ('''<div id="${ rootElId }"></div>''' );
373+ var doc = this ._injector.get (DOCUMENT );
374+ // TODO(juliemr): can/should this be optional?
375+ var oldRoots = DOM .querySelectorAll (doc, "[id^=root]" );
376+ for (var i = 0 ; i < oldRoots.length; i++ ) {
377+ DOM .remove (oldRoots[i]);
378+ }
379+ DOM .appendChild (doc.body, rootEl);
380+ Future <ComponentRef > promise = this
381+ ._injector
382+ .get (DynamicComponentLoader )
383+ .loadAsRoot (rootComponentType, '''#${ rootElId }''' , this ._injector);
384+ return promise.then ((componentRef) {
385+ return new ComponentFixture (componentRef, ngZone, autoDetect);
386+ });
387+ };
388+ return ngZone == null ? initComponent () : ngZone.run (initComponent);
263389 }
264390
265391 ComponentFixture createFakeAsync (Type rootComponentType) {
0 commit comments