angular-cn/modules/@angular/core/testing/test_bed.ts

390 lines
11 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AppModule, AppModuleFactory, AppModuleMetadata, AppModuleRef, Compiler, CompilerFactory, ComponentStillLoadingError, Injector, PlatformRef, Provider, ReflectiveInjector, Type, assertPlatform, createPlatform, getPlatform} from '../index';
import {ListWrapper} from '../src/facade/collection';
import {BaseException} from '../src/facade/exceptions';
import {FunctionWrapper, isPresent, stringify} from '../src/facade/lang';
import {AsyncTestCompleter} from './async_test_completer';
const UNDEFINED = new Object();
/**
* @experimental
*/
export class TestBed implements Injector {
private _instantiated: boolean = false;
private _compiler: Compiler = null;
private _moduleRef: AppModuleRef<any> = null;
private _appModuleFactory: AppModuleFactory<any> = null;
private _compilerProviders: Array<Type|Provider|any[]|any> = [];
private _compilerUseJit: boolean = true;
private _providers: Array<Type|Provider|any[]|any> = [];
private _directives: Array<Type|any[]|any> = [];
private _pipes: Array<Type|any[]|any> = [];
private _modules: Array<Type|any[]|any> = [];
private _precompile: Array<Type|any[]|any> = [];
reset() {
this._compiler = null;
this._moduleRef = null;
this._appModuleFactory = null;
this._compilerProviders = [];
this._compilerUseJit = true;
this._providers = [];
this._directives = [];
this._pipes = [];
this._modules = [];
this._precompile = [];
this._instantiated = false;
}
platform: PlatformRef = null;
appModule: Type = null;
configureCompiler(config: {providers?: any[], useJit?: boolean}) {
if (this._instantiated) {
throw new BaseException('Cannot add configuration after test injector is instantiated');
}
if (config.providers) {
this._compilerProviders = ListWrapper.concat(this._compilerProviders, config.providers);
}
if (config.useJit !== undefined) {
this._compilerUseJit = config.useJit;
}
}
configureModule(moduleDef: {
providers?: any[],
directives?: any[],
pipes?: any[],
precompile?: any[],
modules?: any[]
}) {
if (this._instantiated) {
throw new BaseException('Cannot add configuration after test injector is instantiated');
}
if (moduleDef.providers) {
this._providers = ListWrapper.concat(this._providers, moduleDef.providers);
}
if (moduleDef.directives) {
this._directives = ListWrapper.concat(this._directives, moduleDef.directives);
}
if (moduleDef.pipes) {
this._pipes = ListWrapper.concat(this._pipes, moduleDef.pipes);
}
if (moduleDef.precompile) {
this._precompile = ListWrapper.concat(this._precompile, moduleDef.precompile);
}
if (moduleDef.modules) {
this._modules = ListWrapper.concat(this._modules, moduleDef.modules);
}
}
createAppModuleFactory(): Promise<AppModuleFactory<any>> {
if (this._instantiated) {
throw new BaseException(
'Cannot run precompilation when the test AppModule has already been instantiated. ' +
'Make sure you are not using `inject` before `doAsyncPrecompilation`.');
}
if (this._appModuleFactory) {
return Promise.resolve(this._appModuleFactory);
}
let moduleMeta = this._createCompilerAndModuleMeta();
return this._compiler.compileAppModuleAsync(_NoopModule, moduleMeta)
.then((appModuleFactory) => {
this._appModuleFactory = appModuleFactory;
return appModuleFactory;
});
}
initTestAppModule() {
if (this._instantiated) {
return;
}
if (this._appModuleFactory) {
this._createFromModuleFactory(this._appModuleFactory);
} else {
let moduleMeta = this._createCompilerAndModuleMeta();
this._createFromModuleFactory(this._compiler.compileAppModuleSync(_NoopModule, moduleMeta));
}
}
/**
* @internal
*/
_createInjectorAsync(): Promise<Injector> {
if (this._instantiated) {
return Promise.resolve(this);
}
let moduleMeta = this._createCompilerAndModuleMeta();
return this._compiler.compileAppModuleAsync(_NoopModule, moduleMeta)
.then((appModuleFactory) => this._createFromModuleFactory(appModuleFactory));
}
private _createCompilerAndModuleMeta(): AppModuleMetadata {
const compilerFactory: CompilerFactory = this.platform.injector.get(CompilerFactory);
this._compiler = compilerFactory.createCompiler({
providers: this._compilerProviders,
useJit: this._compilerUseJit,
deprecatedAppProviders: this._providers
});
const moduleMeta = new AppModuleMetadata({
providers: this._providers.concat([{provide: TestBed, useValue: this}]),
modules: this._modules.concat([this.appModule]),
directives: this._directives,
pipes: this._pipes,
precompile: this._precompile
});
return moduleMeta;
}
private _createFromModuleFactory(appModuleFactory: AppModuleFactory<any>): Injector {
this._moduleRef = appModuleFactory.create(this.platform.injector);
this._instantiated = true;
return this;
}
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND) {
if (!this._instantiated) {
throw new BaseException(
'Illegal state: The test bed\'s injector has not yet been created. Call initTestAppModule first!');
}
if (token === TestBed) {
return this;
}
// Tests can inject things from the app module and from the compiler,
// but the app module can't inject things from the compiler and vice versa.
let result = this._moduleRef.injector.get(token, UNDEFINED);
return result === UNDEFINED ? this._compiler.injector.get(token, notFoundValue) : result;
}
execute(tokens: any[], fn: Function): any {
if (!this._instantiated) {
throw new BaseException(
'Illegal state: The test bed\'s injector has not yet been created. Call initTestAppModule first!');
}
var params = tokens.map(t => this.get(t));
return FunctionWrapper.apply(fn, params);
}
}
var _testBed: TestBed = null;
/**
* @experimental
*/
export function getTestBed() {
if (_testBed == null) {
_testBed = new TestBed();
}
return _testBed;
}
/**
* @deprecated use getTestBed instead.
*/
export function getTestInjector() {
return getTestBed();
}
/**
* Set the providers that the test injector should use. These should be providers
* common to every test in the suite.
*
* This may only be called once, to set up the common providers for the current test
* suite on the current platform. If you absolutely need to change the providers,
* first use `resetBaseTestProviders`.
*
* Test modules and platforms for individual platforms are available from
* 'angular2/platform/testing/<platform_name>'.
*
* @deprecated Use initTestEnvironment instead
*/
export function setBaseTestProviders(
platformProviders: Array<Type|Provider|any[]>,
applicationProviders: Array<Type|Provider|any[]>) {
// Create a platform based on the Platform Providers.
var platformRef = createPlatform(ReflectiveInjector.resolveAndCreate(platformProviders));
// Create an AppModule based on the application providers.
@AppModule({providers: applicationProviders})
class TestAppModule {
}
initTestEnvironment(TestAppModule, platformRef);
}
/**
* Initialize the environment for testing with a compiler factory, a PlatformRef, and an
* application module. These are common to every test in the suite.
*
* This may only be called once, to set up the common providers for the current test
* suite on the current platform. If you absolutely need to change the providers,
* first use `resetTestEnvironment`.
*
* Test modules and platforms for individual platforms are available from
* 'angular2/platform/testing/<platform_name>'.
*
* @experimental
*/
export function initTestEnvironment(appModule: Type, platform: PlatformRef) {
var testBed = getTestBed();
if (testBed.platform || testBed.appModule) {
throw new BaseException('Cannot set base providers because it has already been called');
}
testBed.platform = platform;
testBed.appModule = appModule;
}
/**
* Reset the providers for the test injector.
*
* @deprecated Use resetTestEnvironment instead.
*/
export function resetBaseTestProviders() {
resetTestEnvironment();
}
/**
* Reset the providers for the test injector.
*
* @experimental
*/
export function resetTestEnvironment() {
var testBed = getTestBed();
testBed.platform = null;
testBed.appModule = null;
testBed.reset();
}
/**
* Run asynchronous precompilation for the test's AppModule. It is necessary to call this function
* if your test is using an AppModule which has precompiled components that require an asynchronous
* call, such as an XHR. Should be called once before the test case.
*
* @experimental
*/
export function doAsyncPrecompilation(): Promise<any> {
let testBed = getTestBed();
return testBed.createAppModuleFactory();
}
/**
* Allows injecting dependencies in `beforeEach()` and `it()`.
*
* Example:
*
* ```
* beforeEach(inject([Dependency, AClass], (dep, object) => {
* // some code that uses `dep` and `object`
* // ...
* }));
*
* it('...', inject([AClass], (object) => {
* object.doSomething();
* expect(...);
* })
* ```
*
* Notes:
* - inject is currently a function because of some Traceur limitation the syntax should
* eventually
* becomes `it('...', @Inject (object: AClass, async: AsyncTestCompleter) => { ... });`
*
* @stable
*/
export function inject(tokens: any[], fn: Function): () => any {
let testBed = getTestBed();
if (tokens.indexOf(AsyncTestCompleter) >= 0) {
return () => {
// Return an async test method that returns a Promise if AsyncTestCompleter is one of the
// injected tokens.
return testBed._createInjectorAsync().then(() => {
let completer: AsyncTestCompleter = testBed.get(AsyncTestCompleter);
testBed.execute(tokens, fn);
return completer.promise;
});
};
} else {
return () => {
try {
testBed.initTestAppModule();
} catch (e) {
if (e instanceof ComponentStillLoadingError) {
throw new Error(
`This test module precompiles the component ${stringify(e.compType)} which is using a "templateUrl", but precompilation was never done. ` +
`Please call "doAsyncPrecompilation" before "inject".`);
} else {
throw e;
}
}
return testBed.execute(tokens, fn);
};
}
}
/**
* @experimental
*/
export class InjectSetupWrapper {
constructor(private _moduleDef: () => {
providers?: any[],
directives?: any[],
pipes?: any[],
precompile?: any[],
modules?: any[]
}) {}
private _addModule() {
var moduleDef = this._moduleDef();
if (moduleDef) {
getTestBed().configureModule(moduleDef);
}
}
inject(tokens: any[], fn: Function): () => any {
return () => {
this._addModule();
return inject(tokens, fn)();
};
}
}
/**
* @experimental
*/
export function withProviders(providers: () => any) {
return new InjectSetupWrapper(() => {{return {providers: providers()};}});
}
/**
* @experimental
*/
export function withModule(moduleDef: () => {
providers?: any[],
directives?: any[],
pipes?: any[],
precompile?: any[],
modules?: any[]
}) {
return new InjectSetupWrapper(moduleDef);
}
class _NoopModule {}