395 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			395 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import {isBlank, BaseException} from 'angular2/src/facade/lang';
 | |
| import {describe, ddescribe, it, iit, expect, beforeEach} from 'angular2/test_lib';
 | |
| import {Injector, bind, ResolvedBinding} from 'angular2/di';
 | |
| import {Optional, Inject, InjectLazy} from 'angular2/src/di/annotations_impl';
 | |
| 
 | |
| 
 | |
| 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 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', function() {
 | |
|         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);
 | |
|         });
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| }
 |