feat(testing): Use NgZone in TestComponentBuilder.
Instantiating the test component within an NgZone will let us track async tasks in event handlers and change detection. We can also do auto change detection when triggering events through dispatchEvent and not have to do fixture.detectChange() manually in the test. New API: ComponentFixture.autoDetectChanges() - This puts the fixture in auto detect mode that automatically calls detectChanges when the microtask queue is empty (Similar to how change detection is triggered in an actual application). ComponentFixture.isStable() - This returns a boolean whether the fixture is currently stable or has some async tasks that need to be completed. ComponentFixture.whenStable() - This returns a promise that is resolved when the fixture is stable after all async tasks are complete. Closes #8301
This commit is contained in:
parent
ac55e1e27b
commit
769835e53e
|
@ -20,13 +20,17 @@ import {MockNgZone} from 'angular2/src/mock/ng_zone_mock';
|
||||||
import {XHRImpl} from "angular2/src/platform/browser/xhr_impl";
|
import {XHRImpl} from "angular2/src/platform/browser/xhr_impl";
|
||||||
import {XHR} from 'angular2/compiler';
|
import {XHR} from 'angular2/compiler';
|
||||||
|
|
||||||
import {TestComponentBuilder} from 'angular2/src/testing/test_component_builder';
|
import {
|
||||||
|
TestComponentBuilder,
|
||||||
|
ComponentFixtureAutoDetect,
|
||||||
|
ComponentFixtureNoNgZone
|
||||||
|
} from 'angular2/src/testing/test_component_builder';
|
||||||
|
|
||||||
import {BrowserDetection} from 'angular2/src/testing/utils';
|
import {BrowserDetection} from 'angular2/src/testing/utils';
|
||||||
|
|
||||||
import {ELEMENT_PROBE_PROVIDERS} from 'angular2/platform/common_dom';
|
import {ELEMENT_PROBE_PROVIDERS} from 'angular2/platform/common_dom';
|
||||||
|
|
||||||
import {CONST_EXPR} from 'angular2/src/facade/lang';
|
import {CONST_EXPR, IS_DART} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
import {Log} from 'angular2/src/testing/utils';
|
import {Log} from 'angular2/src/testing/utils';
|
||||||
|
|
||||||
|
@ -35,6 +39,10 @@ function initBrowserTests() {
|
||||||
BrowserDetection.setup();
|
BrowserDetection.setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createNgZone(): NgZone {
|
||||||
|
return IS_DART ? new MockNgZone() : new NgZone({enableLongStackTrace: true});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default platform providers for testing without a compiler.
|
* Default platform providers for testing without a compiler.
|
||||||
*/
|
*/
|
||||||
|
@ -52,7 +60,7 @@ export const ADDITIONAL_TEST_BROWSER_PROVIDERS: Array<any /*Type | Provider | an
|
||||||
new Provider(ViewResolver, {useClass: MockViewResolver}),
|
new Provider(ViewResolver, {useClass: MockViewResolver}),
|
||||||
Log,
|
Log,
|
||||||
TestComponentBuilder,
|
TestComponentBuilder,
|
||||||
new Provider(NgZone, {useClass: MockNgZone}),
|
new Provider(NgZone, {useFactory: createNgZone}),
|
||||||
new Provider(LocationStrategy, {useClass: MockLocationStrategy}),
|
new Provider(LocationStrategy, {useClass: MockLocationStrategy}),
|
||||||
new Provider(AnimationBuilder, {useClass: MockAnimationBuilder}),
|
new Provider(AnimationBuilder, {useClass: MockAnimationBuilder}),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {MockViewResolver} from 'angular2/src/mock/view_resolver_mock';
|
||||||
import {MockLocationStrategy} from 'angular2/src/mock/mock_location_strategy';
|
import {MockLocationStrategy} from 'angular2/src/mock/mock_location_strategy';
|
||||||
import {MockNgZone} from 'angular2/src/mock/ng_zone_mock';
|
import {MockNgZone} from 'angular2/src/mock/ng_zone_mock';
|
||||||
|
|
||||||
|
import {createNgZone} from 'angular2/src/core/application_ref';
|
||||||
import {TestComponentBuilder} from 'angular2/src/testing/test_component_builder';
|
import {TestComponentBuilder} from 'angular2/src/testing/test_component_builder';
|
||||||
import {XHR} from 'angular2/src/compiler/xhr';
|
import {XHR} from 'angular2/src/compiler/xhr';
|
||||||
import {BrowserDetection} from 'angular2/src/testing/utils';
|
import {BrowserDetection} from 'angular2/src/testing/utils';
|
||||||
|
@ -85,7 +86,7 @@ export const TEST_SERVER_APPLICATION_PROVIDERS: Array<any /*Type | Provider | an
|
||||||
new Provider(ViewResolver, {useClass: MockViewResolver}),
|
new Provider(ViewResolver, {useClass: MockViewResolver}),
|
||||||
Log,
|
Log,
|
||||||
TestComponentBuilder,
|
TestComponentBuilder,
|
||||||
new Provider(NgZone, {useClass: MockNgZone}),
|
new Provider(NgZone, {useFactory: createNgZone}),
|
||||||
new Provider(LocationStrategy, {useClass: MockLocationStrategy}),
|
new Provider(LocationStrategy, {useClass: MockLocationStrategy}),
|
||||||
new Provider(AnimationBuilder, {useClass: MockAnimationBuilder}),
|
new Provider(AnimationBuilder, {useClass: MockAnimationBuilder}),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
OpaqueToken,
|
||||||
ComponentRef,
|
ComponentRef,
|
||||||
DynamicComponentLoader,
|
DynamicComponentLoader,
|
||||||
Injector,
|
Injector,
|
||||||
|
@ -7,12 +8,15 @@ import {
|
||||||
ElementRef,
|
ElementRef,
|
||||||
EmbeddedViewRef,
|
EmbeddedViewRef,
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
provide
|
provide,
|
||||||
|
NgZone,
|
||||||
|
NgZoneError
|
||||||
} from 'angular2/core';
|
} from 'angular2/core';
|
||||||
import {DirectiveResolver, ViewResolver} from 'angular2/compiler';
|
import {DirectiveResolver, ViewResolver} from 'angular2/compiler';
|
||||||
|
|
||||||
import {Type, isPresent, isBlank} from 'angular2/src/facade/lang';
|
import {BaseException} from 'angular2/src/facade/exceptions';
|
||||||
import {PromiseWrapper} from 'angular2/src/facade/async';
|
import {Type, isPresent, isBlank, IS_DART} from 'angular2/src/facade/lang';
|
||||||
|
import {PromiseWrapper, ObservableWrapper, PromiseCompleter} from 'angular2/src/facade/async';
|
||||||
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
import {el} from './utils';
|
import {el} from './utils';
|
||||||
|
@ -24,6 +28,9 @@ import {DebugNode, DebugElement, getDebugNode} from 'angular2/src/core/debug/deb
|
||||||
|
|
||||||
import {tick} from './fake_async';
|
import {tick} from './fake_async';
|
||||||
|
|
||||||
|
export var ComponentFixtureAutoDetect = new OpaqueToken("ComponentFixtureAutoDetect");
|
||||||
|
export var ComponentFixtureNoNgZone = new OpaqueToken("ComponentFixtureNoNgZone");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fixture for debugging and testing a component.
|
* Fixture for debugging and testing a component.
|
||||||
*/
|
*/
|
||||||
|
@ -58,31 +65,136 @@ export class ComponentFixture {
|
||||||
*/
|
*/
|
||||||
changeDetectorRef: ChangeDetectorRef;
|
changeDetectorRef: ChangeDetectorRef;
|
||||||
|
|
||||||
constructor(componentRef: ComponentRef) {
|
/**
|
||||||
|
* The NgZone in which this component was instantiated.
|
||||||
|
*/
|
||||||
|
ngZone: NgZone;
|
||||||
|
|
||||||
|
private _autoDetect: boolean;
|
||||||
|
|
||||||
|
private _isStable: boolean = true;
|
||||||
|
private _completer: PromiseCompleter<any> = null;
|
||||||
|
private _onUnstableSubscription = null;
|
||||||
|
private _onStableSubscription = null;
|
||||||
|
private _onMicrotaskEmptySubscription = null;
|
||||||
|
private _onErrorSubscription = null;
|
||||||
|
|
||||||
|
constructor(componentRef: ComponentRef, ngZone: NgZone, autoDetect: boolean) {
|
||||||
this.changeDetectorRef = componentRef.changeDetectorRef;
|
this.changeDetectorRef = componentRef.changeDetectorRef;
|
||||||
this.elementRef = componentRef.location;
|
this.elementRef = componentRef.location;
|
||||||
this.debugElement = <DebugElement>getDebugNode(this.elementRef.nativeElement);
|
this.debugElement = <DebugElement>getDebugNode(this.elementRef.nativeElement);
|
||||||
this.componentInstance = componentRef.instance;
|
this.componentInstance = componentRef.instance;
|
||||||
this.nativeElement = this.elementRef.nativeElement;
|
this.nativeElement = this.elementRef.nativeElement;
|
||||||
this.componentRef = componentRef;
|
this.componentRef = componentRef;
|
||||||
|
this.ngZone = ngZone;
|
||||||
|
this._autoDetect = autoDetect;
|
||||||
|
|
||||||
|
if (ngZone != null) {
|
||||||
|
this._onUnstableSubscription =
|
||||||
|
ObservableWrapper.subscribe(ngZone.onUnstable, (_) => { this._isStable = false; });
|
||||||
|
this._onMicrotaskEmptySubscription =
|
||||||
|
ObservableWrapper.subscribe(ngZone.onMicrotaskEmpty, (_) => {
|
||||||
|
if (this._autoDetect) {
|
||||||
|
// Do a change detection run with checkNoChanges set to true to check
|
||||||
|
// there are no changes on the second run.
|
||||||
|
this.detectChanges(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._onStableSubscription = ObservableWrapper.subscribe(ngZone.onStable, (_) => {
|
||||||
|
this._isStable = true;
|
||||||
|
if (this._completer != null) {
|
||||||
|
this._completer.resolve(true);
|
||||||
|
this._completer = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._onErrorSubscription = ObservableWrapper.subscribe(
|
||||||
|
ngZone.onError, (error: NgZoneError) => { throw error.error; });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private _tick(checkNoChanges: boolean) {
|
||||||
* Trigger a change detection cycle for the component.
|
|
||||||
*/
|
|
||||||
detectChanges(checkNoChanges: boolean = true): void {
|
|
||||||
this.changeDetectorRef.detectChanges();
|
this.changeDetectorRef.detectChanges();
|
||||||
if (checkNoChanges) {
|
if (checkNoChanges) {
|
||||||
this.checkNoChanges();
|
this.checkNoChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trigger a change detection cycle for the component.
|
||||||
|
*/
|
||||||
|
detectChanges(checkNoChanges: boolean = true): void {
|
||||||
|
if (this.ngZone != null) {
|
||||||
|
// Run the change detection inside the NgZone so that any async tasks as part of the change
|
||||||
|
// detection are captured by the zone and can be waited for in isStable.
|
||||||
|
this.ngZone.run(() => { this._tick(checkNoChanges); });
|
||||||
|
} else {
|
||||||
|
// Running without zone. Just do the change detection.
|
||||||
|
this._tick(checkNoChanges);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do a change detection run to make sure there were no changes.
|
||||||
|
*/
|
||||||
checkNoChanges(): void { this.changeDetectorRef.checkNoChanges(); }
|
checkNoChanges(): void { this.changeDetectorRef.checkNoChanges(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether the fixture should autodetect changes.
|
||||||
|
*
|
||||||
|
* Also runs detectChanges once so that any existing change is detected.
|
||||||
|
*/
|
||||||
|
autoDetectChanges(autoDetect: boolean = true) {
|
||||||
|
if (this.ngZone == null) {
|
||||||
|
throw new BaseException('Cannot call autoDetectChanges when ComponentFixtureNoNgZone is set');
|
||||||
|
}
|
||||||
|
this._autoDetect = autoDetect;
|
||||||
|
this.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether the fixture is currently stable or has async tasks that have not been completed
|
||||||
|
* yet.
|
||||||
|
*/
|
||||||
|
isStable(): boolean { return this._isStable; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a promise that resolves when the fixture is stable.
|
||||||
|
*
|
||||||
|
* This can be used to resume testing after events have triggered asynchronous activity or
|
||||||
|
* asynchronous change detection.
|
||||||
|
*/
|
||||||
|
whenStable(): Promise<any> {
|
||||||
|
if (this._isStable) {
|
||||||
|
return PromiseWrapper.resolve(false);
|
||||||
|
} else {
|
||||||
|
this._completer = new PromiseCompleter<any>();
|
||||||
|
return this._completer.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger component destruction.
|
* Trigger component destruction.
|
||||||
*/
|
*/
|
||||||
destroy(): void { this.componentRef.destroy(); }
|
destroy(): void {
|
||||||
|
this.componentRef.destroy();
|
||||||
|
if (this._onUnstableSubscription != null) {
|
||||||
|
ObservableWrapper.dispose(this._onUnstableSubscription);
|
||||||
|
this._onUnstableSubscription = null;
|
||||||
|
}
|
||||||
|
if (this._onStableSubscription != null) {
|
||||||
|
ObservableWrapper.dispose(this._onStableSubscription);
|
||||||
|
this._onStableSubscription = null;
|
||||||
|
}
|
||||||
|
if (this._onMicrotaskEmptySubscription != null) {
|
||||||
|
ObservableWrapper.dispose(this._onMicrotaskEmptySubscription);
|
||||||
|
this._onMicrotaskEmptySubscription = null;
|
||||||
|
}
|
||||||
|
if (this._onErrorSubscription != null) {
|
||||||
|
ObservableWrapper.dispose(this._onErrorSubscription);
|
||||||
|
this._onErrorSubscription = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _nextRootElementId = 0;
|
var _nextRootElementId = 0;
|
||||||
|
@ -108,7 +220,7 @@ export class TestComponentBuilder {
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_clone(): TestComponentBuilder {
|
_clone(): TestComponentBuilder {
|
||||||
var clone = new TestComponentBuilder(this._injector);
|
let clone = new TestComponentBuilder(this._injector);
|
||||||
clone._viewOverrides = MapWrapper.clone(this._viewOverrides);
|
clone._viewOverrides = MapWrapper.clone(this._viewOverrides);
|
||||||
clone._directiveOverrides = MapWrapper.clone(this._directiveOverrides);
|
clone._directiveOverrides = MapWrapper.clone(this._directiveOverrides);
|
||||||
clone._templateOverrides = MapWrapper.clone(this._templateOverrides);
|
clone._templateOverrides = MapWrapper.clone(this._templateOverrides);
|
||||||
|
@ -127,7 +239,7 @@ export class TestComponentBuilder {
|
||||||
* @return {TestComponentBuilder}
|
* @return {TestComponentBuilder}
|
||||||
*/
|
*/
|
||||||
overrideTemplate(componentType: Type, template: string): TestComponentBuilder {
|
overrideTemplate(componentType: Type, template: string): TestComponentBuilder {
|
||||||
var clone = this._clone();
|
let clone = this._clone();
|
||||||
clone._templateOverrides.set(componentType, template);
|
clone._templateOverrides.set(componentType, template);
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
@ -141,7 +253,7 @@ export class TestComponentBuilder {
|
||||||
* @return {TestComponentBuilder}
|
* @return {TestComponentBuilder}
|
||||||
*/
|
*/
|
||||||
overrideView(componentType: Type, view: ViewMetadata): TestComponentBuilder {
|
overrideView(componentType: Type, view: ViewMetadata): TestComponentBuilder {
|
||||||
var clone = this._clone();
|
let clone = this._clone();
|
||||||
clone._viewOverrides.set(componentType, view);
|
clone._viewOverrides.set(componentType, view);
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
@ -156,8 +268,8 @@ export class TestComponentBuilder {
|
||||||
* @return {TestComponentBuilder}
|
* @return {TestComponentBuilder}
|
||||||
*/
|
*/
|
||||||
overrideDirective(componentType: Type, from: Type, to: Type): TestComponentBuilder {
|
overrideDirective(componentType: Type, from: Type, to: Type): TestComponentBuilder {
|
||||||
var clone = this._clone();
|
let clone = this._clone();
|
||||||
var overridesForComponent = clone._directiveOverrides.get(componentType);
|
let overridesForComponent = clone._directiveOverrides.get(componentType);
|
||||||
if (!isPresent(overridesForComponent)) {
|
if (!isPresent(overridesForComponent)) {
|
||||||
clone._directiveOverrides.set(componentType, new Map<Type, Type>());
|
clone._directiveOverrides.set(componentType, new Map<Type, Type>());
|
||||||
overridesForComponent = clone._directiveOverrides.get(componentType);
|
overridesForComponent = clone._directiveOverrides.get(componentType);
|
||||||
|
@ -182,7 +294,7 @@ export class TestComponentBuilder {
|
||||||
* @return {TestComponentBuilder}
|
* @return {TestComponentBuilder}
|
||||||
*/
|
*/
|
||||||
overrideProviders(type: Type, providers: any[]): TestComponentBuilder {
|
overrideProviders(type: Type, providers: any[]): TestComponentBuilder {
|
||||||
var clone = this._clone();
|
let clone = this._clone();
|
||||||
clone._bindingsOverrides.set(type, providers);
|
clone._bindingsOverrides.set(type, providers);
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
@ -210,7 +322,7 @@ export class TestComponentBuilder {
|
||||||
* @return {TestComponentBuilder}
|
* @return {TestComponentBuilder}
|
||||||
*/
|
*/
|
||||||
overrideViewProviders(type: Type, providers: any[]): TestComponentBuilder {
|
overrideViewProviders(type: Type, providers: any[]): TestComponentBuilder {
|
||||||
var clone = this._clone();
|
let clone = this._clone();
|
||||||
clone._viewBindingsOverrides.set(type, providers);
|
clone._viewBindingsOverrides.set(type, providers);
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
@ -228,8 +340,13 @@ export class TestComponentBuilder {
|
||||||
* @return {Promise<ComponentFixture>}
|
* @return {Promise<ComponentFixture>}
|
||||||
*/
|
*/
|
||||||
createAsync(rootComponentType: Type): Promise<ComponentFixture> {
|
createAsync(rootComponentType: Type): Promise<ComponentFixture> {
|
||||||
var mockDirectiveResolver = this._injector.get(DirectiveResolver);
|
let noNgZone = IS_DART || this._injector.get(ComponentFixtureNoNgZone, false);
|
||||||
var mockViewResolver = this._injector.get(ViewResolver);
|
let ngZone: NgZone = noNgZone ? null : this._injector.get(NgZone, null);
|
||||||
|
let autoDetect: boolean = this._injector.get(ComponentFixtureAutoDetect, false);
|
||||||
|
|
||||||
|
let initComponent = () => {
|
||||||
|
let mockDirectiveResolver = this._injector.get(DirectiveResolver);
|
||||||
|
let mockViewResolver = this._injector.get(ViewResolver);
|
||||||
this._viewOverrides.forEach((view, type) => mockViewResolver.setView(type, view));
|
this._viewOverrides.forEach((view, type) => mockViewResolver.setView(type, view));
|
||||||
this._templateOverrides.forEach((template, type) =>
|
this._templateOverrides.forEach((template, type) =>
|
||||||
mockViewResolver.setInlineTemplate(type, template));
|
mockViewResolver.setInlineTemplate(type, template));
|
||||||
|
@ -237,31 +354,35 @@ export class TestComponentBuilder {
|
||||||
overrides.forEach(
|
overrides.forEach(
|
||||||
(to, from) => { mockViewResolver.overrideViewDirective(component, from, to); });
|
(to, from) => { mockViewResolver.overrideViewDirective(component, from, to); });
|
||||||
});
|
});
|
||||||
this._bindingsOverrides.forEach((bindings, type) =>
|
this._bindingsOverrides.forEach(
|
||||||
mockDirectiveResolver.setBindingsOverride(type, bindings));
|
(bindings, type) => mockDirectiveResolver.setBindingsOverride(type, bindings));
|
||||||
this._viewBindingsOverrides.forEach(
|
this._viewBindingsOverrides.forEach(
|
||||||
(bindings, type) => mockDirectiveResolver.setViewBindingsOverride(type, bindings));
|
(bindings, type) => mockDirectiveResolver.setViewBindingsOverride(type, bindings));
|
||||||
|
|
||||||
var rootElId = `root${_nextRootElementId++}`;
|
let rootElId = `root${_nextRootElementId++}`;
|
||||||
var rootEl = el(`<div id="${rootElId}"></div>`);
|
let rootEl = el(`<div id="${rootElId}"></div>`);
|
||||||
var doc = this._injector.get(DOCUMENT);
|
let doc = this._injector.get(DOCUMENT);
|
||||||
|
|
||||||
// TODO(juliemr): can/should this be optional?
|
// TODO(juliemr): can/should this be optional?
|
||||||
var oldRoots = DOM.querySelectorAll(doc, '[id^=root]');
|
let oldRoots = DOM.querySelectorAll(doc, '[id^=root]');
|
||||||
for (var i = 0; i < oldRoots.length; i++) {
|
for (let i = 0; i < oldRoots.length; i++) {
|
||||||
DOM.remove(oldRoots[i]);
|
DOM.remove(oldRoots[i]);
|
||||||
}
|
}
|
||||||
DOM.appendChild(doc.body, rootEl);
|
DOM.appendChild(doc.body, rootEl);
|
||||||
|
|
||||||
var promise: Promise<ComponentRef> =
|
let promise: Promise<ComponentRef> =
|
||||||
this._injector.get(DynamicComponentLoader)
|
this._injector.get(DynamicComponentLoader)
|
||||||
.loadAsRoot(rootComponentType, `#${rootElId}`, this._injector);
|
.loadAsRoot(rootComponentType, `#${rootElId}`, this._injector);
|
||||||
return promise.then((componentRef) => { return new ComponentFixture(componentRef); });
|
return promise.then(
|
||||||
|
(componentRef) => { return new ComponentFixture(componentRef, ngZone, autoDetect); });
|
||||||
|
};
|
||||||
|
|
||||||
|
return ngZone == null ? initComponent() : ngZone.run(initComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
createFakeAsync(rootComponentType: Type): ComponentFixture {
|
createFakeAsync(rootComponentType: Type): ComponentFixture {
|
||||||
var result;
|
let result;
|
||||||
var error;
|
let error;
|
||||||
PromiseWrapper.then(this.createAsync(rootComponentType), (_result) => { result = _result; },
|
PromiseWrapper.then(this.createAsync(rootComponentType), (_result) => { result = _result; },
|
||||||
(_error) => { error = _error; });
|
(_error) => { error = _error; });
|
||||||
tick();
|
tick();
|
||||||
|
|
|
@ -157,7 +157,7 @@ export class InjectSetupWrapper {
|
||||||
inject(tokens: any[], fn: Function): Function {
|
inject(tokens: any[], fn: Function): Function {
|
||||||
return () => {
|
return () => {
|
||||||
this._addProviders();
|
this._addProviders();
|
||||||
return inject(tokens, fn)();
|
return inject_impl(tokens, fn)();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +165,7 @@ export class InjectSetupWrapper {
|
||||||
injectAsync(tokens: any[], fn: Function): Function {
|
injectAsync(tokens: any[], fn: Function): Function {
|
||||||
return () => {
|
return () => {
|
||||||
this._addProviders();
|
this._addProviders();
|
||||||
return injectAsync(tokens, fn)();
|
return injectAsync_impl(tokens, fn)();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,3 +197,8 @@ export function withProviders(providers: () => any) {
|
||||||
export function injectAsync(tokens: any[], fn: Function): Function {
|
export function injectAsync(tokens: any[], fn: Function): Function {
|
||||||
return async(inject(tokens, fn));
|
return async(inject(tokens, fn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is to ensure inject(Async) within InjectSetupWrapper doesn't call itself
|
||||||
|
// when transpiled to Dart.
|
||||||
|
var inject_impl = inject;
|
||||||
|
var injectAsync_impl = injectAsync;
|
||||||
|
|
|
@ -146,7 +146,7 @@ export function main() {
|
||||||
DOM.appendChild(doc.body, rootEl);
|
DOM.appendChild(doc.body, rootEl);
|
||||||
loader.loadAsRoot(ChildComp, null, injector)
|
loader.loadAsRoot(ChildComp, null, injector)
|
||||||
.then((componentRef) => {
|
.then((componentRef) => {
|
||||||
var el = new ComponentFixture(componentRef);
|
var el = new ComponentFixture(componentRef, null, false);
|
||||||
|
|
||||||
expect(rootEl.parentNode).toBe(doc.body);
|
expect(rootEl.parentNode).toBe(doc.body);
|
||||||
|
|
||||||
|
|
|
@ -9,15 +9,20 @@ import {
|
||||||
iit,
|
iit,
|
||||||
inject,
|
inject,
|
||||||
beforeEachProviders,
|
beforeEachProviders,
|
||||||
|
withProviders,
|
||||||
it,
|
it,
|
||||||
xit,
|
xit,
|
||||||
TestComponentBuilder
|
TestComponentBuilder,
|
||||||
|
ComponentFixtureAutoDetect,
|
||||||
|
ComponentFixtureNoNgZone
|
||||||
} from 'angular2/testing_internal';
|
} from 'angular2/testing_internal';
|
||||||
|
|
||||||
import {CONST_EXPR} from 'angular2/src/facade/lang';
|
import {CONST_EXPR} from 'angular2/src/facade/lang';
|
||||||
import {Injectable, provide} from 'angular2/core';
|
import {Injectable, provide} from 'angular2/core';
|
||||||
import {NgIf} from 'angular2/common';
|
import {NgIf} from 'angular2/common';
|
||||||
import {Directive, Component, ViewMetadata} from 'angular2/src/core/metadata';
|
import {Directive, Component, ViewMetadata, Input} from 'angular2/src/core/metadata';
|
||||||
|
import {IS_DART} from 'angular2/src/facade/lang';
|
||||||
|
import {PromiseWrapper} from 'angular2/src/facade/promise';
|
||||||
|
|
||||||
@Component(
|
@Component(
|
||||||
{selector: 'child-comp', template: `<span>Original {{childBinding}}</span>`, directives: []})
|
{selector: 'child-comp', template: `<span>Original {{childBinding}}</span>`, directives: []})
|
||||||
|
@ -72,7 +77,42 @@ class ChildWithChildComp {
|
||||||
class MockChildChildComp {
|
class MockChildChildComp {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'autodetect-comp', template: `<span (click)='click()'>{{text}}</span>`})
|
||||||
|
class AutoDetectComp {
|
||||||
|
text: string = '1';
|
||||||
|
|
||||||
|
click() { this.text += '1'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'async-comp', template: `<span (click)='click()'>{{text}}</span>`})
|
||||||
|
class AsyncComp {
|
||||||
|
text: string = '1';
|
||||||
|
|
||||||
|
click() {
|
||||||
|
PromiseWrapper.resolve(null).then((_) => { this.text += '1'; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'async-child-comp', template: '<span>{{localText}}</span>'})
|
||||||
|
class AsyncChildComp {
|
||||||
|
localText: string = '';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set text(value: string) {
|
||||||
|
PromiseWrapper.resolve(null).then((_) => { this.localText = value; });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'async-change-comp',
|
||||||
|
template: `<async-child-comp (click)='click()' [text]="text"></async-child-comp>`,
|
||||||
|
directives: [AsyncChildComp]
|
||||||
|
})
|
||||||
|
class AsyncChangeComp {
|
||||||
|
text: string = '1';
|
||||||
|
|
||||||
|
click() { this.text += '1'; }
|
||||||
|
}
|
||||||
|
|
||||||
class FancyService {
|
class FancyService {
|
||||||
value: string = 'real value';
|
value: string = 'real value';
|
||||||
|
@ -244,5 +284,184 @@ export function main() {
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (!IS_DART) {
|
||||||
|
describe('ComponentFixture', () => {
|
||||||
|
it('should auto detect changes if autoDetectChanges is called',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter],
|
||||||
|
(tcb: TestComponentBuilder, async) => {
|
||||||
|
|
||||||
|
tcb.createAsync(AutoDetectComp)
|
||||||
|
.then((componentFixture) => {
|
||||||
|
expect(componentFixture.ngZone).not.toBeNull();
|
||||||
|
componentFixture.autoDetectChanges();
|
||||||
|
expect(componentFixture.nativeElement).toHaveText('1');
|
||||||
|
|
||||||
|
let element = componentFixture.debugElement.children[0];
|
||||||
|
dispatchEvent(element.nativeElement, 'click');
|
||||||
|
|
||||||
|
expect(componentFixture.isStable()).toBe(true);
|
||||||
|
expect(componentFixture.nativeElement).toHaveText('11');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should auto detect changes if ComponentFixtureAutoDetect is provided as true',
|
||||||
|
withProviders(() => [provide(ComponentFixtureAutoDetect, {useValue: true})])
|
||||||
|
.inject([TestComponentBuilder, AsyncTestCompleter],
|
||||||
|
(tcb: TestComponentBuilder, async) => {
|
||||||
|
|
||||||
|
tcb.createAsync(AutoDetectComp)
|
||||||
|
.then((componentFixture) => {
|
||||||
|
expect(componentFixture.nativeElement).toHaveText('1');
|
||||||
|
|
||||||
|
let element = componentFixture.debugElement.children[0];
|
||||||
|
dispatchEvent(element.nativeElement, 'click');
|
||||||
|
|
||||||
|
expect(componentFixture.nativeElement).toHaveText('11');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should signal through whenStable when the fixture is stable (autoDetectChanges)',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter],
|
||||||
|
(tcb: TestComponentBuilder, async) => {
|
||||||
|
|
||||||
|
tcb.createAsync(AsyncComp).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 signal through isStable when the fixture is stable (no autoDetectChanges)',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter],
|
||||||
|
(tcb: TestComponentBuilder, async) => {
|
||||||
|
|
||||||
|
tcb.createAsync(AsyncComp).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.
|
||||||
|
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) => {
|
||||||
|
|
||||||
|
tcb.createAsync(AsyncChangeComp)
|
||||||
|
.then((componentFixture) => {
|
||||||
|
componentFixture.autoDetectChanges();
|
||||||
|
componentFixture.whenStable().then((_) => {
|
||||||
|
expect(componentFixture.nativeElement).toHaveText('1');
|
||||||
|
|
||||||
|
let element = componentFixture.debugElement.children[0];
|
||||||
|
dispatchEvent(element.nativeElement, 'click');
|
||||||
|
|
||||||
|
componentFixture.whenStable().then((_) => {
|
||||||
|
expect(componentFixture.nativeElement).toHaveText('11');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should stabilize after async task in change detection(no autoDetectChanges)',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter],
|
||||||
|
(tcb: TestComponentBuilder, async) => {
|
||||||
|
|
||||||
|
tcb.createAsync(AsyncChangeComp)
|
||||||
|
.then((componentFixture) => {
|
||||||
|
componentFixture.detectChanges();
|
||||||
|
componentFixture.whenStable().then((_) => {
|
||||||
|
// Run detectChanges again so that stabilized value is reflected in the
|
||||||
|
// DOM.
|
||||||
|
componentFixture.detectChanges();
|
||||||
|
expect(componentFixture.nativeElement).toHaveText('1');
|
||||||
|
|
||||||
|
let element = componentFixture.debugElement.children[0];
|
||||||
|
dispatchEvent(element.nativeElement, 'click');
|
||||||
|
componentFixture.detectChanges();
|
||||||
|
|
||||||
|
componentFixture.whenStable().then((_) => {
|
||||||
|
// Run detectChanges again so that stabilized value is reflected in
|
||||||
|
// the DOM.
|
||||||
|
componentFixture.detectChanges();
|
||||||
|
expect(componentFixture.nativeElement).toHaveText('11');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('No NgZone', () => {
|
||||||
|
beforeEachProviders(() => [provide(ComponentFixtureNoNgZone, {useValue: true})]);
|
||||||
|
|
||||||
|
it('calling autoDetectChanges raises an error', () => {
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder,
|
||||||
|
async) => {
|
||||||
|
tcb.createAsync(ChildComp).then((componentFixture) => {
|
||||||
|
expect(() => {
|
||||||
|
componentFixture.autoDetectChanges();
|
||||||
|
}).toThrow('Cannot call autoDetectChanges when ComponentFixtureNoNgZone is set!!');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should instantiate a component with valid DOM',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter],
|
||||||
|
(tcb: TestComponentBuilder, async) => {
|
||||||
|
|
||||||
|
tcb.createAsync(ChildComp).then((componentFixture) => {
|
||||||
|
expect(componentFixture.ngZone).toBeNull();
|
||||||
|
componentFixture.detectChanges();
|
||||||
|
expect(componentFixture.nativeElement).toHaveText('Original Child');
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should allow changing members of the component',
|
||||||
|
inject([TestComponentBuilder, AsyncTestCompleter],
|
||||||
|
(tcb: TestComponentBuilder, async) => {
|
||||||
|
|
||||||
|
tcb.createAsync(MyIfComp).then((componentFixture) => {
|
||||||
|
componentFixture.detectChanges();
|
||||||
|
expect(componentFixture.nativeElement).toHaveText('MyIf()');
|
||||||
|
|
||||||
|
componentFixture.componentInstance.showMore = true;
|
||||||
|
componentFixture.detectChanges();
|
||||||
|
expect(componentFixture.nativeElement).toHaveText('MyIf(More)');
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
AsyncTestCompleter,
|
AsyncTestCompleter,
|
||||||
|
withProviders,
|
||||||
inject,
|
inject,
|
||||||
describe,
|
describe,
|
||||||
it,
|
it,
|
||||||
|
@ -8,6 +9,7 @@ import {
|
||||||
beforeEachProviders,
|
beforeEachProviders,
|
||||||
SpyObject
|
SpyObject
|
||||||
} from 'angular2/testing_internal';
|
} from 'angular2/testing_internal';
|
||||||
|
import {provide} from 'angular2/src/core/di';
|
||||||
import {ObservableWrapper, TimerWrapper} from 'angular2/src/facade/async';
|
import {ObservableWrapper, TimerWrapper} from 'angular2/src/facade/async';
|
||||||
import {MessageBus} from 'angular2/src/web_workers/shared/message_bus';
|
import {MessageBus} from 'angular2/src/web_workers/shared/message_bus';
|
||||||
import {createConnectedMessageBus} from './message_bus_util';
|
import {createConnectedMessageBus} from './message_bus_util';
|
||||||
|
@ -111,14 +113,16 @@ export function main() {
|
||||||
// TODO(mlaval): timeout is fragile, test to be rewritten
|
// TODO(mlaval): timeout is fragile, test to be rewritten
|
||||||
function flushMessages(fn: () => void) { TimerWrapper.setTimeout(fn, 50); }
|
function flushMessages(fn: () => void) { TimerWrapper.setTimeout(fn, 50); }
|
||||||
|
|
||||||
beforeEach(() => { bus = createConnectedMessageBus(); });
|
|
||||||
|
|
||||||
it("should buffer messages and wait for the zone to exit before sending",
|
it("should buffer messages and wait for the zone to exit before sending",
|
||||||
inject([AsyncTestCompleter, NgZone], (async, zone: MockNgZone) => {
|
withProviders(() => [provide(NgZone, {useClass: MockNgZone})])
|
||||||
|
.inject([AsyncTestCompleter, NgZone],
|
||||||
|
(async, zone: MockNgZone) => {
|
||||||
|
bus = createConnectedMessageBus();
|
||||||
setup(true, zone);
|
setup(true, zone);
|
||||||
|
|
||||||
var wasCalled = false;
|
var wasCalled = false;
|
||||||
ObservableWrapper.subscribe(bus.from(CHANNEL), (message) => { wasCalled = true; });
|
ObservableWrapper.subscribe(bus.from(CHANNEL),
|
||||||
|
(message) => { wasCalled = true; });
|
||||||
ObservableWrapper.callEmit(bus.to(CHANNEL), "hi");
|
ObservableWrapper.callEmit(bus.to(CHANNEL), "hi");
|
||||||
|
|
||||||
|
|
||||||
|
@ -131,10 +135,12 @@ export function main() {
|
||||||
async.done();
|
async.done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}), 500);
|
}),
|
||||||
|
500);
|
||||||
|
|
||||||
it("should send messages immediatly when run outside the zone",
|
it("should send messages immediatly when run outside the zone",
|
||||||
inject([AsyncTestCompleter, NgZone], (async, zone: MockNgZone) => {
|
inject([AsyncTestCompleter, NgZone], (async, zone: MockNgZone) => {
|
||||||
|
bus = createConnectedMessageBus();
|
||||||
setup(false, zone);
|
setup(false, zone);
|
||||||
|
|
||||||
var wasCalled = false;
|
var wasCalled = false;
|
||||||
|
|
|
@ -7,7 +7,12 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export * from './src/testing/testing';
|
export * from './src/testing/testing';
|
||||||
export {ComponentFixture, TestComponentBuilder} from './src/testing/test_component_builder';
|
export {
|
||||||
|
ComponentFixture,
|
||||||
|
TestComponentBuilder,
|
||||||
|
ComponentFixtureAutoDetect,
|
||||||
|
ComponentFixtureNoNgZone
|
||||||
|
} from './src/testing/test_component_builder';
|
||||||
export * from './src/testing/test_injector';
|
export * from './src/testing/test_injector';
|
||||||
export * from './src/testing/fake_async';
|
export * from './src/testing/fake_async';
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue