feat(core): Create StaticInjector which does not depend on Reflect polyfill.

This commit is contained in:
Miško Hevery
2017-07-27 13:49:33 -07:00
committed by Victor Berchet
parent f69561b2de
commit d9d00bd9b5
12 changed files with 1013 additions and 52 deletions

View File

@ -13,12 +13,13 @@ import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
export function main() {
describe('Injector.NULL', () => {
it('should throw if no arg is given', () => {
expect(() => Injector.NULL.get('someToken')).toThrowError('No provider for someToken!');
expect(() => Injector.NULL.get('someToken'))
.toThrowError('NullInjectorError: No provider for someToken!');
});
it('should throw if THROW_IF_NOT_FOUND is given', () => {
expect(() => Injector.NULL.get('someToken', Injector.THROW_IF_NOT_FOUND))
.toThrowError('No provider for someToken!');
.toThrowError('NullInjectorError: No provider for someToken!');
});
it('should return the default value',

View File

@ -0,0 +1,479 @@
/**
* @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 {Inject, InjectionToken, Injector, Optional, ReflectiveKey, Self, SkipSelf, forwardRef} from '@angular/core';
import {getOriginalError} from '@angular/core/src/errors';
import {expect} from '@angular/platform-browser/testing/src/matchers';
import {stringify} from '../../src/util';
class Engine {
static PROVIDER = {provide: Engine, useClass: Engine, deps: []};
}
class BrokenEngine {
static PROVIDER = {provide: Engine, useClass: BrokenEngine, deps: []};
constructor() { throw new Error('Broken Engine'); }
}
class DashboardSoftware {
static PROVIDER = {provide: DashboardSoftware, useClass: DashboardSoftware, deps: []};
}
class Dashboard {
static PROVIDER = {provide: Dashboard, useClass: Dashboard, deps: [DashboardSoftware]};
constructor(software: DashboardSoftware) {}
}
class TurboEngine extends Engine {
static PROVIDER = {provide: Engine, useClass: TurboEngine, deps: []};
}
class Car {
static PROVIDER = {provide: Car, useClass: Car, deps: [Engine]};
constructor(public engine: Engine) {}
}
class CarWithOptionalEngine {
static PROVIDER = {
provide: CarWithOptionalEngine,
useClass: CarWithOptionalEngine,
deps: [[new Optional(), Engine]]
};
constructor(public engine: Engine) {}
}
class CarWithDashboard {
static PROVIDER = {
provide: CarWithDashboard,
useClass: CarWithDashboard,
deps: [Engine, Dashboard]
};
engine: Engine;
dashboard: Dashboard;
constructor(engine: Engine, dashboard: Dashboard) {
this.engine = engine;
this.dashboard = dashboard;
}
}
class SportsCar extends Car {
static PROVIDER = {provide: Car, useClass: SportsCar, deps: [Engine]};
}
class CyclicEngine {
static PROVIDER = {provide: Engine, useClass: CyclicEngine, deps: [Car]};
constructor(car: Car) {}
}
class NoAnnotations {
constructor(secretDependency: any) {}
}
function factoryFn(a: any) {}
export function main() {
const dynamicProviders = [
{provide: 'provider0', useValue: 1}, {provide: 'provider1', useValue: 1},
{provide: 'provider2', useValue: 1}, {provide: 'provider3', useValue: 1},
{provide: 'provider4', useValue: 1}, {provide: 'provider5', useValue: 1},
{provide: 'provider6', useValue: 1}, {provide: 'provider7', useValue: 1},
{provide: 'provider8', useValue: 1}, {provide: 'provider9', useValue: 1},
{provide: 'provider10', useValue: 1}
];
describe(`StaticInjector`, () => {
it('should instantiate a class without dependencies', () => {
const injector = Injector.create([Engine.PROVIDER]);
const engine = injector.get(Engine);
expect(engine).toBeAnInstanceOf(Engine);
});
it('should resolve dependencies based on type information', () => {
const injector = Injector.create([Engine.PROVIDER, Car.PROVIDER]);
const car = injector.get(Car);
expect(car).toBeAnInstanceOf(Car);
expect(car.engine).toBeAnInstanceOf(Engine);
});
it('should cache instances', () => {
const injector = Injector.create([Engine.PROVIDER]);
const e1 = injector.get(Engine);
const e2 = injector.get(Engine);
expect(e1).toBe(e2);
});
it('should provide to a value', () => {
const injector = Injector.create([{provide: Engine, useValue: 'fake engine'}]);
const engine = injector.get(Engine);
expect(engine).toEqual('fake engine');
});
it('should inject dependencies instance of InjectionToken', () => {
const TOKEN = new InjectionToken<string>('token');
const injector = Injector.create([
{provide: TOKEN, useValue: 'by token'},
{provide: Engine, useFactory: (v: string) => v, deps: [[TOKEN]]},
]);
const engine = injector.get(Engine);
expect(engine).toEqual('by token');
});
it('should provide to a factory', () => {
function sportsCarFactory(e: any) { return new SportsCar(e); }
const injector = Injector.create(
[Engine.PROVIDER, {provide: Car, useFactory: sportsCarFactory, deps: [Engine]}]);
const car = injector.get(Car);
expect(car).toBeAnInstanceOf(SportsCar);
expect(car.engine).toBeAnInstanceOf(Engine);
});
it('should supporting provider to null', () => {
const injector = Injector.create([{provide: Engine, useValue: null}]);
const engine = injector.get(Engine);
expect(engine).toBeNull();
});
it('should provide to an alias', () => {
const injector = Injector.create([
Engine.PROVIDER, {provide: SportsCar, useClass: SportsCar, deps: [Engine]},
{provide: Car, useExisting: SportsCar}
]);
const car = injector.get(Car);
const sportsCar = injector.get(SportsCar);
expect(car).toBeAnInstanceOf(SportsCar);
expect(car).toBe(sportsCar);
});
it('should support multiProviders', () => {
const injector = Injector.create([
Engine.PROVIDER, {provide: Car, useClass: SportsCar, deps: [Engine], multi: true},
{provide: Car, useClass: CarWithOptionalEngine, deps: [Engine], multi: true}
]);
const cars = injector.get(Car) as any as Car[];
expect(cars.length).toEqual(2);
expect(cars[0]).toBeAnInstanceOf(SportsCar);
expect(cars[1]).toBeAnInstanceOf(CarWithOptionalEngine);
});
it('should support multiProviders that are created using useExisting', () => {
const injector = Injector.create([
Engine.PROVIDER, {provide: SportsCar, useClass: SportsCar, deps: [Engine]},
{provide: Car, useExisting: SportsCar, multi: true}
]);
const cars = injector.get(Car) as any as Car[];
expect(cars.length).toEqual(1);
expect(cars[0]).toBe(injector.get(SportsCar));
});
it('should throw when the aliased provider does not exist', () => {
const injector = Injector.create([{provide: 'car', useExisting: SportsCar}]);
const e =
`StaticInjectorError[car -> ${stringify(SportsCar)}]: \n NullInjectorError: No provider for ${stringify(SportsCar)}!`;
expect(() => injector.get('car')).toThrowError(e);
});
it('should handle forwardRef in useExisting', () => {
const injector = Injector.create([
{provide: 'originalEngine', useClass: forwardRef(() => Engine), deps: []}, {
provide: 'aliasedEngine',
useExisting: <any>forwardRef(() => 'originalEngine'),
deps: []
}
]);
expect(injector.get('aliasedEngine')).toBeAnInstanceOf(Engine);
});
it('should support overriding factory dependencies', () => {
const injector = Injector.create([
Engine.PROVIDER,
{provide: Car, useFactory: (e: Engine) => new SportsCar(e), deps: [Engine]}
]);
const car = injector.get(Car);
expect(car).toBeAnInstanceOf(SportsCar);
expect(car.engine).toBeAnInstanceOf(Engine);
});
it('should support optional dependencies', () => {
const injector = Injector.create([CarWithOptionalEngine.PROVIDER]);
const car = injector.get(CarWithOptionalEngine);
expect(car.engine).toEqual(null);
});
it('should flatten passed-in providers', () => {
const injector = Injector.create([[[Engine.PROVIDER, Car.PROVIDER]]]);
const car = injector.get(Car);
expect(car).toBeAnInstanceOf(Car);
});
it('should use the last provider when there are multiple providers for same token', () => {
const injector = Injector.create([
{provide: Engine, useClass: Engine, deps: []},
{provide: Engine, useClass: TurboEngine, deps: []}
]);
expect(injector.get(Engine)).toBeAnInstanceOf(TurboEngine);
});
it('should use non-type tokens', () => {
const injector = Injector.create([{provide: 'token', useValue: 'value'}]);
expect(injector.get('token')).toEqual('value');
});
it('should throw when given invalid providers', () => {
expect(() => Injector.create(<any>['blah']))
.toThrowError('StaticInjectorError[blah]: Unexpected provider');
});
it('should throw when missing deps', () => {
expect(() => Injector.create(<any>[{provide: Engine, useClass: Engine}]))
.toThrowError(
'StaticInjectorError[{provide:Engine, useClass:Engine}]: \'deps\' required');
});
it('should throw when using reflective API', () => {
expect(() => Injector.create(<any>[Engine]))
.toThrowError('StaticInjectorError[Engine]: Function/Class not supported');
});
it('should throw when unknown provider shape API', () => {
expect(() => Injector.create(<any>[{provide: 'abc', deps: [Engine]}]))
.toThrowError(
'StaticInjectorError[{provide:"abc", deps:[Engine]}]: StaticProvider does not have [useValue|useFactory|useExisting|useClass] or [provide] is not newable');
});
it('should throw when given invalid providers and serialize the provider', () => {
expect(() => Injector.create(<any>[{foo: 'bar', bar: Car}]))
.toThrowError('StaticInjectorError[{foo:"bar", bar:Car}]: Unexpected provider');
});
it('should provide itself', () => {
const parent = Injector.create([]);
const child = Injector.create([], parent);
expect(child.get(Injector)).toBe(child);
});
it('should throw when no provider defined', () => {
const injector = Injector.create([]);
expect(() => injector.get('NonExisting'))
.toThrowError(
'StaticInjectorError[NonExisting]: \n NullInjectorError: No provider for NonExisting!');
});
it('should show the full path when no provider', () => {
const injector =
Injector.create([CarWithDashboard.PROVIDER, Engine.PROVIDER, Dashboard.PROVIDER]);
expect(() => injector.get(CarWithDashboard))
.toThrowError(
`StaticInjectorError[${stringify(CarWithDashboard)} -> ${stringify(Dashboard)} -> DashboardSoftware]:
NullInjectorError: No provider for DashboardSoftware!`);
});
it('should throw when trying to instantiate a cyclic dependency', () => {
const injector = Injector.create([Car.PROVIDER, CyclicEngine.PROVIDER]);
expect(() => injector.get(Car))
.toThrowError(
`StaticInjectorError[${stringify(Car)} -> ${stringify(Engine)} -> ${stringify(Car)}]: Circular dependency`);
});
it('should show the full path when error happens in a constructor', () => {
const error = new Error('MyError');
const injector = Injector.create(
[Car.PROVIDER, {provide: Engine, useFactory: () => { throw error; }, deps: []}]);
try {
injector.get(Car);
throw 'Must throw';
} catch (e) {
expect(e).toBe(error);
expect(e.message).toContain(
`StaticInjectorError[${stringify(Car)} -> Engine]: \n MyError`);
expect(e.ngTokenPath[0]).toEqual(Car);
expect(e.ngTokenPath[1]).toEqual(Engine);
}
});
it('should instantiate an object after a failed attempt', () => {
let isBroken = true;
const injector = Injector.create([
Car.PROVIDER, {
provide: Engine,
useFactory: (() => isBroken ? new BrokenEngine() : new Engine()),
deps: []
}
]);
expect(() => injector.get(Car))
.toThrowError('StaticInjectorError[Car -> Engine]: \n Broken Engine');
isBroken = false;
expect(injector.get(Car)).toBeAnInstanceOf(Car);
});
it('should support null/undefined values', () => {
const injector = Injector.create([
{provide: 'null', useValue: null},
{provide: 'undefined', useValue: undefined},
]);
expect(injector.get('null')).toBe(null);
expect(injector.get('undefined')).toBe(undefined);
});
});
describe('child', () => {
it('should load instances from parent injector', () => {
const parent = Injector.create([Engine.PROVIDER]);
const child = Injector.create([], parent);
const engineFromParent = parent.get(Engine);
const engineFromChild = child.get(Engine);
expect(engineFromChild).toBe(engineFromParent);
});
it('should not use the child providers when resolving the dependencies of a parent provider',
() => {
const parent = Injector.create([Car.PROVIDER, Engine.PROVIDER]);
const child = Injector.create([TurboEngine.PROVIDER], parent);
const carFromChild = child.get(Car);
expect(carFromChild.engine).toBeAnInstanceOf(Engine);
});
it('should create new instance in a child injector', () => {
const parent = Injector.create([Engine.PROVIDER]);
const child = Injector.create([TurboEngine.PROVIDER], parent);
const engineFromParent = parent.get(Engine);
const engineFromChild = child.get(Engine);
expect(engineFromParent).not.toBe(engineFromChild);
expect(engineFromChild).toBeAnInstanceOf(TurboEngine);
});
it('should give access to parent', () => {
const parent = Injector.create([]);
const child = Injector.create([], parent);
expect((child as any).parent).toBe(parent);
});
});
describe('instantiate', () => {
it('should instantiate an object in the context of the injector', () => {
const inj = Injector.create([Engine.PROVIDER]);
const childInj = Injector.create([Car.PROVIDER], inj);
const car = childInj.get(Car);
expect(car).toBeAnInstanceOf(Car);
expect(car.engine).toBe(inj.get(Engine));
});
});
describe('depedency resolution', () => {
describe('@Self()', () => {
it('should return a dependency from self', () => {
const inj = Injector.create([
Engine.PROVIDER,
{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[Engine, new Self()]]}
]);
expect(inj.get(Car)).toBeAnInstanceOf(Car);
});
it('should throw when not requested provider on self', () => {
const parent = Injector.create([Engine.PROVIDER]);
const child = Injector.create(
[{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[Engine, new Self()]]}],
parent);
expect(() => child.get(Car))
.toThrowError(`StaticInjectorError[${stringify(Car)} -> ${stringify(Engine)}]:
NullInjectorError: No provider for Engine!`);
});
});
describe('default', () => {
it('should skip self', () => {
const parent = Injector.create([Engine.PROVIDER]);
const child = Injector.create(
[
TurboEngine.PROVIDER,
{provide: Car, useFactory: (e: Engine) => new Car(e), deps: [[SkipSelf, Engine]]}
],
parent);
expect(child.get(Car).engine).toBeAnInstanceOf(Engine);
});
});
});
describe('resolve', () => {
it('should throw when mixing multi providers with regular providers', () => {
expect(() => {
Injector.create(
[{provide: Engine, useClass: BrokenEngine, deps: [], multi: true}, Engine.PROVIDER]);
}).toThrowError(/Cannot mix multi providers and regular providers/);
expect(() => {
Injector.create(
[Engine.PROVIDER, {provide: Engine, useClass: BrokenEngine, deps: [], multi: true}]);
}).toThrowError(/Cannot mix multi providers and regular providers/);
});
it('should resolve forward references', () => {
const injector = Injector.create([
[{provide: forwardRef(() => BrokenEngine), useClass: forwardRef(() => Engine), deps: []}], {
provide: forwardRef(() => String),
useFactory: (e: any) => e,
deps: [forwardRef(() => BrokenEngine)]
}
]);
expect(injector.get(String)).toBeAnInstanceOf(Engine);
expect(injector.get(BrokenEngine)).toBeAnInstanceOf(Engine);
});
it('should support overriding factory dependencies with dependency annotations', () => {
const injector = Injector.create([
Engine.PROVIDER,
{provide: 'token', useFactory: (e: any) => e, deps: [[new Inject(Engine)]]}
]);
expect(injector.get('token')).toBeAnInstanceOf(Engine);
});
});
describe('displayName', () => {
it('should work', () => {
expect(Injector.create([Engine.PROVIDER, {provide: BrokenEngine, useValue: null}]).toString())
.toEqual('StaticInjector[Injector, Engine, BrokenEngine]');
});
});
}

View File

@ -720,7 +720,7 @@ function declareTests({useJit}: {useJit: boolean}) {
it('should throw when the aliased provider does not exist', () => {
const injector = createInjector([{provide: 'car', useExisting: SportsCar}]);
const e = `No provider for ${stringify(SportsCar)}!`;
const e = `NullInjectorError: No provider for ${stringify(SportsCar)}!`;
expect(() => injector.get('car')).toThrowError(e);
});
@ -830,7 +830,8 @@ function declareTests({useJit}: {useJit: boolean}) {
it('should throw when no provider defined', () => {
const injector = createInjector([]);
expect(() => injector.get('NonExisting')).toThrowError('No provider for NonExisting!');
expect(() => injector.get('NonExisting'))
.toThrowError('NullInjectorError: No provider for NonExisting!');
});
it('should throw when trying to instantiate a cyclic dependency', () => {