@@ -3,6 +3,7 @@ library angular2.src.testing.test_component_builder;
3
3
import "dart:async" ;
4
4
import "package:angular2/core.dart"
5
5
show
6
+ OpaqueToken,
6
7
ComponentRef,
7
8
DynamicComponentLoader,
8
9
Injector,
@@ -11,10 +12,15 @@ import "package:angular2/core.dart"
11
12
ElementRef,
12
13
EmbeddedViewRef,
13
14
ChangeDetectorRef,
14
- provide;
15
+ provide,
16
+ NgZone,
17
+ NgZoneError;
15
18
import "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;
18
24
import "package:angular2/src/facade/collection.dart"
19
25
show ListWrapper, MapWrapper;
20
26
import "utils.dart" show el;
@@ -24,6 +30,9 @@ import "package:angular2/src/core/debug/debug_node.dart"
24
30
show DebugNode, DebugElement, getDebugNode;
25
31
import "fake_async.dart" show tick;
26
32
33
+ var ComponentFixtureAutoDetect = new OpaqueToken ("ComponentFixtureAutoDetect" );
34
+ var ComponentFixtureNoNgZone = new OpaqueToken ("ComponentFixtureNoNgZone" );
35
+
27
36
/**
28
37
* Fixture for debugging and testing a component.
29
38
*/
@@ -52,34 +61,144 @@ class ComponentFixture {
52
61
* The ChangeDetectorRef for the component
53
62
*/
54
63
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) {
56
76
this .changeDetectorRef = componentRef.changeDetectorRef;
57
77
this .elementRef = componentRef.location;
58
78
this .debugElement =
59
79
(getDebugNode (this .elementRef.nativeElement) as DebugElement );
60
80
this .componentInstance = componentRef.instance;
61
81
this .nativeElement = this .elementRef.nativeElement;
62
82
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
+ }
63
112
}
113
+ _tick (bool checkNoChanges) {
114
+ this .changeDetectorRef.detectChanges ();
115
+ if (checkNoChanges) {
116
+ this .checkNoChanges ();
117
+ }
118
+ }
119
+
64
120
/**
65
121
* Trigger a change detection cycle for the component.
66
122
*/
67
123
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);
71
134
}
72
135
}
73
136
137
+ /**
138
+ * Do a change detection run to make sure there were no changes.
139
+ */
74
140
void checkNoChanges () {
75
141
this .changeDetectorRef.checkNoChanges ();
76
142
}
77
143
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
+
78
181
/**
79
182
* Trigger component destruction.
80
183
*/
81
184
void destroy () {
82
185
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
+ }
83
202
}
84
203
}
85
204
@@ -228,38 +347,45 @@ class TestComponentBuilder {
228
347
*
229
348
*/
230
349
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
+ });
241
366
});
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);
263
389
}
264
390
265
391
ComponentFixture createFakeAsync (Type rootComponentType) {
0 commit comments