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 {}
 |