feat(core): Introduce TestBed.inject to replace TestBed.get (#32200)
TestBed.get is not type safe, fixing it would be a massive breaking change. The Angular team has proposed replacing it with TestBed.inject and deprecate TestBed.get. Deprecation from TestBed.get will come as a separate commit. Issue #26491 Fixes #29905 BREAKING CHANGE: Injector.get now accepts abstract classes to return type-safe values. Previous implementation returned `any` through the deprecated implementation. PR Close #32200
This commit is contained in:

committed by
Misko Hevery

parent
2b28b91fd9
commit
3aba7ebe6a
@ -11,6 +11,7 @@
|
||||
// this statement only.
|
||||
// clang-format off
|
||||
import {
|
||||
AbstractType,
|
||||
Component,
|
||||
Directive,
|
||||
InjectFlags,
|
||||
@ -49,7 +50,7 @@ const UNDEFINED: Symbol = Symbol('UNDEFINED');
|
||||
* Note: Use `TestBed` in tests. It will be set to either `TestBedViewEngine` or `TestBedRender3`
|
||||
* according to the compiler used.
|
||||
*/
|
||||
export class TestBedRender3 implements Injector, TestBed {
|
||||
export class TestBedRender3 implements TestBed {
|
||||
/**
|
||||
* Initialize the environment for testing with a compiler factory, a PlatformRef, and an
|
||||
* angular module. These are common to every test in the suite.
|
||||
@ -150,15 +151,26 @@ export class TestBedRender3 implements Injector, TestBed {
|
||||
return TestBedRender3 as any as TestBedStatic;
|
||||
}
|
||||
|
||||
static inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue?: T, flags?: InjectFlags): T;
|
||||
static inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue: null, flags?: InjectFlags): T
|
||||
|null;
|
||||
static inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue?: T|null,
|
||||
flags?: InjectFlags): T|null {
|
||||
return _getTestBedRender3().inject(token, notFoundValue, flags);
|
||||
}
|
||||
|
||||
/** TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject */
|
||||
static get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): any;
|
||||
/**
|
||||
* @deprecated from v8.0.0 use Type<T> or InjectionToken<T>
|
||||
*/
|
||||
/** TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject */
|
||||
static get(token: any, notFoundValue?: any): any;
|
||||
/** TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject */
|
||||
static get(
|
||||
token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND,
|
||||
flags: InjectFlags = InjectFlags.Default): any {
|
||||
return _getTestBedRender3().get(token, notFoundValue);
|
||||
return _getTestBedRender3().inject(token, notFoundValue, flags);
|
||||
}
|
||||
|
||||
static createComponent<T>(component: Type<T>): ComponentFixture<T> {
|
||||
@ -244,22 +256,33 @@ export class TestBedRender3 implements Injector, TestBed {
|
||||
|
||||
compileComponents(): Promise<any> { return this.compiler.compileComponents(); }
|
||||
|
||||
get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): any;
|
||||
/**
|
||||
* @deprecated from v8.0.0 use Type<T> or InjectionToken<T>
|
||||
*/
|
||||
get(token: any, notFoundValue?: any): any;
|
||||
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND,
|
||||
flags: InjectFlags = InjectFlags.Default): any {
|
||||
if (token === TestBedRender3) {
|
||||
return this;
|
||||
inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue?: T, flags?: InjectFlags): T;
|
||||
inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue: null, flags?: InjectFlags): T
|
||||
|null;
|
||||
inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue?: T|null,
|
||||
flags?: InjectFlags): T|null {
|
||||
if (token as unknown === TestBedRender3) {
|
||||
return this as any;
|
||||
}
|
||||
const result = this.testModuleRef.injector.get(token, UNDEFINED, flags);
|
||||
return result === UNDEFINED ? this.compiler.injector.get(token, notFoundValue, flags) : result;
|
||||
}
|
||||
|
||||
/** TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject */
|
||||
get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): any;
|
||||
/** TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject */
|
||||
get(token: any, notFoundValue?: any): any;
|
||||
/** TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject */
|
||||
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND,
|
||||
flags: InjectFlags = InjectFlags.Default): any {
|
||||
return this.inject(token, notFoundValue, flags);
|
||||
}
|
||||
|
||||
execute(tokens: any[], fn: Function, context?: any): any {
|
||||
const params = tokens.map(t => this.get(t));
|
||||
const params = tokens.map(t => this.inject(t));
|
||||
return fn.apply(context, params);
|
||||
}
|
||||
|
||||
@ -299,7 +322,7 @@ export class TestBedRender3 implements Injector, TestBed {
|
||||
}
|
||||
|
||||
createComponent<T>(type: Type<T>): ComponentFixture<T> {
|
||||
const testComponentRenderer: TestComponentRenderer = this.get(TestComponentRenderer);
|
||||
const testComponentRenderer = this.inject(TestComponentRenderer);
|
||||
const rootElId = `root-ng-internal-isolated-${_nextRootElementId++}`;
|
||||
testComponentRenderer.insertRootElement(rootElId);
|
||||
|
||||
@ -310,11 +333,12 @@ export class TestBedRender3 implements Injector, TestBed {
|
||||
`It looks like '${stringify(type)}' has not been IVY compiled - it has no 'ngComponentDef' field`);
|
||||
}
|
||||
|
||||
// TODO: Don't cast as `any`, proper type is boolean[]
|
||||
const noNgZone = this.get(ComponentFixtureNoNgZone as any, false);
|
||||
// TODO: Don't cast as `any`, proper type is boolean[]
|
||||
const autoDetect: boolean = this.get(ComponentFixtureAutoDetect as any, false);
|
||||
const ngZone: NgZone|null = noNgZone ? null : this.get(NgZone as Type<NgZone|null>, null);
|
||||
// TODO: Don't cast as `InjectionToken<boolean>`, proper type is boolean[]
|
||||
const noNgZone = this.inject(ComponentFixtureNoNgZone as InjectionToken<boolean>, false);
|
||||
// TODO: Don't cast as `InjectionToken<boolean>`, proper type is boolean[]
|
||||
const autoDetect: boolean =
|
||||
this.inject(ComponentFixtureAutoDetect as InjectionToken<boolean>, false);
|
||||
const ngZone: NgZone|null = noNgZone ? null : this.inject(NgZone, null);
|
||||
const componentFactory = new ComponentFactory(componentDef);
|
||||
const initComponent = () => {
|
||||
const componentRef =
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ApplicationInitStatus, CompilerOptions, Component, Directive, InjectFlags, InjectionToken, Injector, NgModule, NgModuleFactory, NgModuleRef, NgZone, Optional, Pipe, PlatformRef, Provider, SchemaMetadata, SkipSelf, StaticProvider, Type, ɵAPP_ROOT as APP_ROOT, ɵDepFlags as DepFlags, ɵNodeFlags as NodeFlags, ɵclearOverrides as clearOverrides, ɵgetInjectableDef as getInjectableDef, ɵivyEnabled as ivyEnabled, ɵoverrideComponentView as overrideComponentView, ɵoverrideProvider as overrideProvider, ɵstringify as stringify, ɵɵInjectableDef} from '@angular/core';
|
||||
import {AbstractType, ApplicationInitStatus, CompilerOptions, Component, Directive, InjectFlags, InjectionToken, Injector, NgModule, NgModuleFactory, NgModuleRef, NgZone, Optional, Pipe, PlatformRef, Provider, SchemaMetadata, SkipSelf, StaticProvider, Type, ɵAPP_ROOT as APP_ROOT, ɵDepFlags as DepFlags, ɵNodeFlags as NodeFlags, ɵclearOverrides as clearOverrides, ɵgetInjectableDef as getInjectableDef, ɵivyEnabled as ivyEnabled, ɵoverrideComponentView as overrideComponentView, ɵoverrideProvider as overrideProvider, ɵstringify as stringify, ɵɵInjectableDef} from '@angular/core';
|
||||
|
||||
import {AsyncTestCompleter} from './async_test_completer';
|
||||
import {ComponentFixture} from './component_fixture';
|
||||
@ -56,16 +56,15 @@ export interface TestBed {
|
||||
|
||||
compileComponents(): Promise<any>;
|
||||
|
||||
get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): any;
|
||||
inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue?: T, flags?: InjectFlags): T;
|
||||
inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue: null, flags?: InjectFlags): T
|
||||
|null;
|
||||
|
||||
// TODO: switch back to official deprecation marker once TSLint issue is resolved
|
||||
// https://github.com/palantir/tslint/issues/4522
|
||||
/**
|
||||
* deprecated from v8.0.0 use Type<T> or InjectionToken<T>
|
||||
* This does not use the deprecated jsdoc tag on purpose
|
||||
* because it renders all overloads as deprecated in TSLint
|
||||
* due to https://github.com/palantir/tslint/issues/4522.
|
||||
*/
|
||||
/** TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject */
|
||||
get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): any;
|
||||
/** TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject */
|
||||
get(token: any, notFoundValue?: any): any;
|
||||
|
||||
execute(tokens: any[], fn: Function, context?: any): any;
|
||||
@ -104,7 +103,7 @@ export interface TestBed {
|
||||
* Note: Use `TestBed` in tests. It will be set to either `TestBedViewEngine` or `TestBedRender3`
|
||||
* according to the compiler used.
|
||||
*/
|
||||
export class TestBedViewEngine implements Injector, TestBed {
|
||||
export class TestBedViewEngine implements TestBed {
|
||||
/**
|
||||
* Initialize the environment for testing with a compiler factory, a PlatformRef, and an
|
||||
* angular module. These are common to every test in the suite.
|
||||
@ -216,16 +215,29 @@ export class TestBedViewEngine implements Injector, TestBed {
|
||||
return TestBedViewEngine as any as TestBedStatic;
|
||||
}
|
||||
|
||||
static inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue?: T, flags?: InjectFlags): T;
|
||||
static inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue: null, flags?: InjectFlags): T
|
||||
|null;
|
||||
static inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue?: T|null,
|
||||
flags?: InjectFlags): T|null {
|
||||
return _getTestBedViewEngine().inject(token, notFoundValue, flags);
|
||||
}
|
||||
|
||||
/** TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject */
|
||||
static get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): any;
|
||||
/**
|
||||
* @deprecated from v8.0.0 use Type<T> or InjectionToken<T>
|
||||
* TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject
|
||||
* @suppress {duplicate}
|
||||
*/
|
||||
static get(token: any, notFoundValue?: any): any;
|
||||
/** TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject */
|
||||
static get(
|
||||
token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND,
|
||||
flags: InjectFlags = InjectFlags.Default): any {
|
||||
return _getTestBedViewEngine().get(token, notFoundValue, flags);
|
||||
return _getTestBedViewEngine().inject(token, notFoundValue, flags);
|
||||
}
|
||||
|
||||
static createComponent<T>(component: Type<T>): ComponentFixture<T> {
|
||||
@ -431,8 +443,7 @@ export class TestBedViewEngine implements Injector, TestBed {
|
||||
class DynamicTestModule {
|
||||
}
|
||||
|
||||
const compilerFactory: TestingCompilerFactory =
|
||||
this.platform.injector.get(TestingCompilerFactory);
|
||||
const compilerFactory = this.platform.injector.get(TestingCompilerFactory);
|
||||
this._compiler = compilerFactory.createTestingCompiler(this._compilerOptions);
|
||||
for (const summary of [this._testEnvAotSummaries, ...this._aotSummaries]) {
|
||||
this._compiler.loadAotSummaries(summary);
|
||||
@ -454,16 +465,17 @@ export class TestBedViewEngine implements Injector, TestBed {
|
||||
}
|
||||
}
|
||||
|
||||
get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): any;
|
||||
/**
|
||||
* @deprecated from v8.0.0 use Type<T> or InjectionToken<T>
|
||||
*/
|
||||
get(token: any, notFoundValue?: any): any;
|
||||
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND,
|
||||
flags: InjectFlags = InjectFlags.Default): any {
|
||||
inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue?: T, flags?: InjectFlags): T;
|
||||
inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue: null, flags?: InjectFlags): T
|
||||
|null;
|
||||
inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue?: T|null,
|
||||
flags?: InjectFlags): T|null {
|
||||
this._initIfNeeded();
|
||||
if (token === TestBed) {
|
||||
return this;
|
||||
if (token as unknown === TestBed) {
|
||||
return this as any;
|
||||
}
|
||||
// Tests can inject things from the ng module and from the compiler,
|
||||
// but the ng module can't inject things from the compiler and vice versa.
|
||||
@ -471,9 +483,19 @@ export class TestBedViewEngine implements Injector, TestBed {
|
||||
return result === UNDEFINED ? this._compiler.injector.get(token, notFoundValue, flags) : result;
|
||||
}
|
||||
|
||||
/** TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject */
|
||||
get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): any;
|
||||
/** TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject */
|
||||
get(token: any, notFoundValue?: any): any;
|
||||
/** TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject */
|
||||
get(token: any, notFoundValue: any = Injector.THROW_IF_NOT_FOUND,
|
||||
flags: InjectFlags = InjectFlags.Default): any {
|
||||
return this.inject(token, notFoundValue, flags);
|
||||
}
|
||||
|
||||
execute(tokens: any[], fn: Function, context?: any): any {
|
||||
this._initIfNeeded();
|
||||
const params = tokens.map(t => this.get(t));
|
||||
const params = tokens.map(t => this.inject(t));
|
||||
return fn.apply(context, params);
|
||||
}
|
||||
|
||||
@ -575,12 +597,13 @@ export class TestBedViewEngine implements Injector, TestBed {
|
||||
`Cannot create the component ${stringify(component)} as it was not imported into the testing module!`);
|
||||
}
|
||||
|
||||
// TODO: Don't cast as `any`, proper type is boolean[]
|
||||
const noNgZone = this.get(ComponentFixtureNoNgZone as any, false);
|
||||
// TODO: Don't cast as `any`, proper type is boolean[]
|
||||
const autoDetect: boolean = this.get(ComponentFixtureAutoDetect as any, false);
|
||||
const ngZone: NgZone|null = noNgZone ? null : this.get(NgZone as Type<NgZone|null>, null);
|
||||
const testComponentRenderer: TestComponentRenderer = this.get(TestComponentRenderer);
|
||||
// TODO: Don't cast as `InjectionToken<boolean>`, declared type is boolean[]
|
||||
const noNgZone = this.inject(ComponentFixtureNoNgZone as InjectionToken<boolean>, false);
|
||||
// TODO: Don't cast as `InjectionToken<boolean>`, declared type is boolean[]
|
||||
const autoDetect: boolean =
|
||||
this.inject(ComponentFixtureAutoDetect as InjectionToken<boolean>, false);
|
||||
const ngZone: NgZone|null = noNgZone ? null : this.inject(NgZone, null);
|
||||
const testComponentRenderer: TestComponentRenderer = this.inject(TestComponentRenderer);
|
||||
const rootElId = `root${_nextRootElementId++}`;
|
||||
testComponentRenderer.insertRootElement(rootElId);
|
||||
|
||||
@ -658,7 +681,7 @@ export function inject(tokens: any[], fn: Function): () => any {
|
||||
// Return an async test method that returns a Promise if AsyncTestCompleter is one of
|
||||
// the injected tokens.
|
||||
return testBed.compileComponents().then(() => {
|
||||
const completer: AsyncTestCompleter = testBed.get(AsyncTestCompleter);
|
||||
const completer = testBed.inject(AsyncTestCompleter);
|
||||
testBed.execute(tokens, fn, this);
|
||||
return completer.promise;
|
||||
});
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, Directive, InjectFlags, InjectionToken, NgModule, Pipe, PlatformRef, SchemaMetadata, Type} from '@angular/core';
|
||||
import {AbstractType, Component, Directive, InjectFlags, InjectionToken, NgModule, Pipe, PlatformRef, SchemaMetadata, Type} from '@angular/core';
|
||||
|
||||
import {ComponentFixture} from './component_fixture';
|
||||
import {MetadataOverride} from './metadata_override';
|
||||
@ -114,15 +114,15 @@ export interface TestBedStatic {
|
||||
deps?: any[],
|
||||
}): TestBedStatic;
|
||||
|
||||
inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue?: T, flags?: InjectFlags): T;
|
||||
inject<T>(
|
||||
token: Type<T>|InjectionToken<T>|AbstractType<T>, notFoundValue: null, flags?: InjectFlags): T
|
||||
|null;
|
||||
|
||||
/** TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject */
|
||||
get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T, flags?: InjectFlags): any;
|
||||
// TODO: switch back to official deprecation marker once TSLint issue is resolved
|
||||
// https://github.com/palantir/tslint/issues/4522
|
||||
/**
|
||||
* deprecated from v8.0.0 use Type<T> or InjectionToken<T>
|
||||
* This does not use the deprecated jsdoc tag on purpose
|
||||
* because it renders all overloads as deprecated in TSLint
|
||||
* due to https://github.com/palantir/tslint/issues/4522.
|
||||
*/
|
||||
/** TODO(goodwine): Mark as deprecated from v9.0.0 use TestBed.inject */
|
||||
get(token: any, notFoundValue?: any): any;
|
||||
|
||||
createComponent<T>(component: Type<T>): ComponentFixture<T>;
|
||||
|
Reference in New Issue
Block a user