import {isBlank, BaseException} from 'angular2/src/facade/lang'; import {describe, ddescribe, it, iit, expect, beforeEach} from 'angular2/test_lib'; import {Injector, bind, ResolvedBinding, Key, forwardRef, DependencyAnnotation} from 'angular2/di'; import {Optional, Inject, InjectLazy} from 'angular2/src/di/annotations_impl'; class CustomDependencyAnnotation extends DependencyAnnotation { } class Engine { } class BrokenEngine { constructor() { throw new BaseException("Broken Engine"); } } class DashboardSoftware { } class Dashboard { constructor(software: DashboardSoftware) {} } class TurboEngine extends Engine { } class Car { engine:Engine; constructor(engine:Engine) { this.engine = engine; } } class CarWithLazyEngine { engineFactory; constructor(@InjectLazy(Engine) engineFactory) { this.engineFactory = engineFactory; } } class CarWithOptionalEngine { engine; constructor(@Optional() engine:Engine) { this.engine = engine; } } class CarWithDashboard { engine:Engine; dashboard:Dashboard; constructor(engine:Engine, dashboard:Dashboard) { this.engine = engine; this.dashboard = dashboard; } } class SportsCar extends Car { engine:Engine; constructor(engine:Engine) { super(engine); } } class CarWithInject { engine:Engine; constructor(@Inject(TurboEngine) engine:Engine) { this.engine = engine; } } class CyclicEngine { constructor(car:Car) {} } class NoAnnotations { constructor(secretDependency) {} } export function main() { describe('injector', function () { it('should instantiate a class without dependencies', function () { var injector = Injector.resolveAndCreate([Engine]); var engine = injector.get(Engine); expect(engine).toBeAnInstanceOf(Engine); }); it('should resolve dependencies based on type information', function () { var injector = Injector.resolveAndCreate([Engine, Car]); var car = injector.get(Car); expect(car).toBeAnInstanceOf(Car); expect(car.engine).toBeAnInstanceOf(Engine); }); it('should resolve dependencies based on @Inject annotation', function () { var injector = Injector.resolveAndCreate([TurboEngine, Engine, CarWithInject]); var car = injector.get(CarWithInject); expect(car).toBeAnInstanceOf(CarWithInject); expect(car.engine).toBeAnInstanceOf(TurboEngine); }); it('should throw when no type and not @Inject', function () { expect(() => Injector.resolveAndCreate([NoAnnotations])).toThrowError( 'Cannot resolve all parameters for NoAnnotations. '+ 'Make sure they all have valid type or annotations.'); }); it('should cache instances', function () { var injector = Injector.resolveAndCreate([Engine]); var e1 = injector.get(Engine); var e2 = injector.get(Engine); expect(e1).toBe(e2); }); it('should bind to a value', function () { var injector = Injector.resolveAndCreate([ bind(Engine).toValue("fake engine") ]); var engine = injector.get(Engine); expect(engine).toEqual("fake engine"); }); it('should bind to a factory', function () { function sportsCarFactory(e:Engine) { return new SportsCar(e); } var injector = Injector.resolveAndCreate([ Engine, bind(Car).toFactory(sportsCarFactory) ]); var car = injector.get(Car); expect(car).toBeAnInstanceOf(SportsCar); expect(car.engine).toBeAnInstanceOf(Engine); }); it('should bind to an alias', function() { var injector = Injector.resolveAndCreate([ Engine, bind(SportsCar).toClass(SportsCar), bind(Car).toAlias(SportsCar) ]); var car = injector.get(Car); var sportsCar = injector.get(SportsCar); expect(car).toBeAnInstanceOf(SportsCar); expect(car).toBe(sportsCar); }); it('should throw when the aliased binding does not exist', function () { var injector = Injector.resolveAndCreate([ bind('car').toAlias(SportsCar) ]); expect(() => injector.get('car')).toThrowError('No provider for SportsCar! (car -> SportsCar)'); }); it('should handle forwardRef in toAlias', function () { var injector = Injector.resolveAndCreate([ bind('originalEngine').toClass(forwardRef(() => Engine)), bind('aliasedEngine').toAlias(forwardRef(() => 'originalEngine')) ]); expect(injector.get('aliasedEngine')).toBeAnInstanceOf(Engine); }); it('should support overriding factory dependencies', function () { var injector = Injector.resolveAndCreate([ Engine, bind(Car).toFactory((e) => new SportsCar(e), [Engine]) ]); var car = injector.get(Car); expect(car).toBeAnInstanceOf(SportsCar); expect(car.engine).toBeAnInstanceOf(Engine); }); it('should support optional dependencies', function () { var injector = Injector.resolveAndCreate([ CarWithOptionalEngine ]); var car = injector.get(CarWithOptionalEngine); expect(car.engine).toEqual(null); }); it("should flatten passed-in bindings", function () { var injector = Injector.resolveAndCreate([ [[Engine, Car]] ]); var car = injector.get(Car); expect(car).toBeAnInstanceOf(Car); }); it("should use the last binding "+ "when there are mutliple bindings for same token", function () { var injector = Injector.resolveAndCreate([ bind(Engine).toClass(Engine), bind(Engine).toClass(TurboEngine) ]); expect(injector.get(Engine)).toBeAnInstanceOf(TurboEngine); }); it('should use non-type tokens', function () { var injector = Injector.resolveAndCreate([ bind('token').toValue('value') ]); expect(injector.get('token')).toEqual('value'); }); it('should throw when given invalid bindings', function () { expect(() => Injector.resolveAndCreate(["blah"])) .toThrowError('Invalid binding - only instances of Binding and Type are allowed, got: blah'); expect(() => Injector.resolveAndCreate([bind("blah")])) .toThrowError('Invalid binding - only instances of Binding and Type are allowed, ' + 'got: blah'); }); it('should provide itself', function () { var parent = Injector.resolveAndCreate([]); var child = parent.resolveAndCreateChild([]); expect(child.get(Injector)).toBe(child); }); it('should throw when no provider defined', function () { var injector = Injector.resolveAndCreate([]); expect(() => injector.get('NonExisting')).toThrowError('No provider for NonExisting!'); }); it('should show the full path when no provider', function () { var injector = Injector.resolveAndCreate([CarWithDashboard, Engine, Dashboard]); expect(() => injector.get(CarWithDashboard)). toThrowError('No provider for DashboardSoftware! (CarWithDashboard -> Dashboard -> DashboardSoftware)'); }); it('should throw when trying to instantiate a cyclic dependency', function () { var injector = Injector.resolveAndCreate([ Car, bind(Engine).toClass(CyclicEngine) ]); expect(() => injector.get(Car)) .toThrowError('Cannot instantiate cyclic dependency! (Car -> Engine -> Car)'); expect(() => injector.asyncGet(Car)) .toThrowError('Cannot instantiate cyclic dependency! (Car -> Engine -> Car)'); }); it('should show the full path when error happens in a constructor', function () { var injector = Injector.resolveAndCreate([ Car, bind(Engine).toClass(BrokenEngine) ]); try { injector.get(Car); throw "Must throw"; } catch (e) { expect(e.message).toContain("Error during instantiation of Engine! (Car -> Engine)"); expect(e.cause instanceof BaseException).toBeTruthy(); expect(e.causeKey.token).toEqual(Engine); } }); it('should instantiate an object after a failed attempt', function () { var isBroken = true; var injector = Injector.resolveAndCreate([ Car, bind(Engine).toFactory(() => isBroken ? new BrokenEngine() : new Engine()) ]); expect(() => injector.get(Car)).toThrowError(new RegExp("Error")); isBroken = false; expect(injector.get(Car)).toBeAnInstanceOf(Car); }); it('should support null values', () => { var injector = Injector.resolveAndCreate([bind('null').toValue(null)]); expect(injector.get('null')).toBe(null); }); describe("default bindings", function () { it("should be used when no matching binding found", function () { var injector = Injector.resolveAndCreate([], {defaultBindings: true}); var car = injector.get(Car); expect(car).toBeAnInstanceOf(Car); }); it("should use the matching binding when it is available", function () { var injector = Injector.resolveAndCreate([ bind(Car).toClass(SportsCar) ], {defaultBindings: true}); var car = injector.get(Car); expect(car).toBeAnInstanceOf(SportsCar); }); }); describe("child", function () { it('should load instances from parent injector', function () { var parent = Injector.resolveAndCreate([Engine]); var child = parent.resolveAndCreateChild([]); var engineFromParent = parent.get(Engine); var engineFromChild = child.get(Engine); expect(engineFromChild).toBe(engineFromParent); }); it("should not use the child bindings when resolving the dependencies of a parent binding", function () { var parent = Injector.resolveAndCreate([ Car, Engine ]); var child = parent.resolveAndCreateChild([ bind(Engine).toClass(TurboEngine) ]); var carFromChild = child.get(Car); expect(carFromChild.engine).toBeAnInstanceOf(Engine); }); it('should create new instance in a child injector', function () { var parent = Injector.resolveAndCreate([Engine]); var child = parent.resolveAndCreateChild([ bind(Engine).toClass(TurboEngine) ]); var engineFromParent = parent.get(Engine); var engineFromChild = child.get(Engine); expect(engineFromParent).not.toBe(engineFromChild); expect(engineFromChild).toBeAnInstanceOf(TurboEngine); }); it("should create child injectors without default bindings", function () { var parent = Injector.resolveAndCreate([], {defaultBindings: true}); var child = parent.resolveAndCreateChild([]); //child delegates to parent the creation of Car var childCar = child.get(Car); var parentCar = parent.get(Car); expect(childCar).toBe(parentCar); }); it("should give access to direct parent", () => { var parent = Injector.resolveAndCreate([]); var child = parent.resolveAndCreateChild([]); expect(child.parent).toBe(parent); }); }); describe("lazy", function () { it("should create dependencies lazily", function () { var injector = Injector.resolveAndCreate([ Engine, CarWithLazyEngine ]); var car = injector.get(CarWithLazyEngine); expect(car.engineFactory()).toBeAnInstanceOf(Engine); }); it("should cache instance created lazily", function () { var injector = Injector.resolveAndCreate([ Engine, CarWithLazyEngine ]); var car = injector.get(CarWithLazyEngine); var e1 = car.engineFactory(); var e2 = car.engineFactory(); expect(e1).toBe(e2); }); }); describe('resolve', function() { it('should resolve and flatten', () => { var bindings = Injector.resolve([Engine, [BrokenEngine]]); bindings.forEach(function(b) { if (isBlank(b)) return; // the result is a sparse array expect(b instanceof ResolvedBinding).toBe(true); }); }); it('should resolve forward references', () => { var bindings = Injector.resolve([ forwardRef(() => Engine), [ bind(forwardRef(() => BrokenEngine)).toClass(forwardRef(() => Engine)) ], bind(forwardRef(() => String)).toFactory(() => 'OK', [forwardRef(() => Engine)]), bind(forwardRef(() => DashboardSoftware)).toAsyncFactory(() => 123, [forwardRef(() => BrokenEngine)]) ]); var engineBinding = bindings[Key.get(Engine).id]; var brokenEngineBinding = bindings[Key.get(BrokenEngine).id]; var stringBinding = bindings[Key.get(String).id]; var dashboardSoftwareBinding = bindings[Key.get(DashboardSoftware).id]; expect(engineBinding.factory() instanceof Engine).toBe(true); expect(brokenEngineBinding.factory() instanceof Engine).toBe(true); expect(stringBinding.dependencies[0].key).toEqual(Key.get(Engine)); expect(dashboardSoftwareBinding.dependencies[0].key).toEqual(Key.get(BrokenEngine)); }); it('should support overriding factory dependencies with dependency annotations', function () { var bindings = Injector.resolve([ bind("token").toFactory((e) => "result", [[new Inject("dep"), new CustomDependencyAnnotation()]]) ]); var binding = bindings[Key.get("token").id]; expect(binding.dependencies[0].key).toEqual(Key.get("dep")); expect(binding.dependencies[0].properties).toEqual([new CustomDependencyAnnotation()]); }); }); }); }