This removes the magic from the `inject` test helper that would inspect the current zone and would only work with our `async` test helper. Now, `inject` is always synchronous, and if you are using a module that requires async precompilation, you're required to call `doAsyncPrecompilation` in your tests. This is part of the breaking changes introduced with the swap to each test having an AppModule. Closes #9975 Closes #9593 BREAKING CHANGE: `TestInjector` is now renamed to `TestBed` Before: ```js import {TestInjector, getTestInjector} from '@angular/core/testing'; ``` After: ```js import {TestBed, getTestBed} from '@angular/core/testing'; ```
		
			
				
	
	
		
			390 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			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 {}
 |