UNPKG

miter

Version:

A typescript web framework based on ExpressJs based loosely on SailsJs

520 lines 24.3 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); const chai_1 = require("chai"); const sinon = require("sinon"); const sinonChai = require("sinon-chai"); chai_1.use(sinonChai); const injector_1 = require("../injector"); const logger_core_1 = require("../../services/logger-core"); const injectable_decorator_1 = require("../../decorators/services/injectable.decorator"); const meta_decorator_1 = require("../../decorators/services/meta.decorator"); const name_decorator_1 = require("../../decorators/services/name.decorator"); describe('Injector', () => { let loggerCore; let instance; beforeEach(() => { loggerCore = new logger_core_1.LoggerCore('abc-xyz', 'default', false); instance = new injector_1.Injector(loggerCore); }); describe('.resolveInjectable', () => { it('should provide the logger core when requested', () => { chai_1.expect(instance.resolveInjectable(logger_core_1.LoggerCore)).to.equal(loggerCore); }); it('should provide itself when requested', () => { chai_1.expect(instance.resolveInjectable(injector_1.Injector)).to.equal(instance); }); it('should instantiate a class that has not been created before', () => { let ctorCalled = false; let TestClass = class TestClass { constructor() { ctorCalled = true; } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass); chai_1.expect(instance.resolveInjectable(TestClass)).to.be.an.instanceOf(TestClass); chai_1.expect(ctorCalled).to.be.true; }); it('should not instantiate a class more than once', () => { let ctorCallCount = 0; let TestClass = class TestClass { constructor() { ctorCallCount++; } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass); let testInst = instance.resolveInjectable(TestClass); chai_1.expect(testInst).to.be.an.instanceOf(TestClass); chai_1.expect(ctorCallCount).to.eq(1); chai_1.expect(instance.resolveInjectable(TestClass)).to.eq(testInst); chai_1.expect(ctorCallCount).to.eq(1); }); it('should throw an error if you try to inject a falsey type', () => { chai_1.expect(() => instance.resolveInjectable(null)).to.throw('falsey'); }); it('should inject constructor parameters when it instantiates a type', () => { let TestClassInner = class TestClassInner { constructor() { } }; TestClassInner = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClassInner); let TestClassOuter = class TestClassOuter { constructor(inner) { this.inner = inner; } }; TestClassOuter = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", [TestClassInner]) ], TestClassOuter); let outer = instance.resolveInjectable(TestClassOuter); chai_1.expect(outer).not.to.be.undefined; chai_1.expect(outer).to.be.an.instanceOf(TestClassOuter); chai_1.expect(outer.inner).to.be.an.instanceOf(TestClassInner); }); it('should invoke resolveDependencies when constructing a new instance', () => { let TestClass2 = class TestClass2 { constructor() { } }; TestClass2 = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass2); let TestClass = class TestClass { constructor(tc2) { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", [TestClass2]) ], TestClass); sinon.spy(instance, 'resolveDependencies'); instance.resolveInjectable(TestClass); chai_1.expect(instance.resolveDependencies).to.have.been.calledTwice .calledWith([TestClass2], TestClass) .calledWith([], TestClass2); }); it('should throw an error when the constructor parameters cannot be resolved', () => { let TestClass = class TestClass { constructor(dependency) { this.dependency = dependency; } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", [Object]) ], TestClass); chai_1.expect(() => instance.resolveInjectable(TestClass)).to.throw(/Failed to resolve dependencies/); }); it('should throw an error when a constructor introduces a circular dependency', () => { let TestClass = class TestClass { constructor() { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass); let TestClass2 = class TestClass2 { constructor() { } }; TestClass2 = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass2); Reflect.defineMetadata('design:paramtypes', [TestClass2], TestClass); Reflect.defineMetadata('design:paramtypes', [TestClass], TestClass2); chai_1.expect(() => instance.resolveInjectable(TestClass)).to.throw(/circular dependency/i); }); }); describe('.provide', () => { it('should throw when the provide value is falsey', () => { chai_1.expect(() => instance.provide(null)).to.throw(/Invalid ProvideMetadata:/); }); it('should throw when the provide constructor function is falsey', () => { chai_1.expect(() => instance.provide({ provide: null, useValue: null })).to.throw(/provide a value for a falsey type/); }); it('should throw when multiple values for a single type are provided', () => { let TestClass = class TestClass { constructor() { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass); instance.provide({ provide: TestClass, useValue: null }); chai_1.expect(() => instance.provide({ provide: TestClass, useValue: null })).to.throw(/Duplicate value provided/); }); describe('when the provide metadata is a replacement class', () => { it('should dependency inject a replacement class', () => { let TestClass = class TestClass { constructor() { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass); let FauxTestClass = class FauxTestClass { constructor() { } }; FauxTestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], FauxTestClass); ; instance.provide({ provide: TestClass, useClass: FauxTestClass }); chai_1.expect(instance.resolveInjectable(TestClass)).to.be.an.instanceOf(FauxTestClass); }); }); describe('when the provide metadata is a replacement value', () => { it('should dependency inject a replacement value', () => { let TestClass = class TestClass { constructor() { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass); let replacementValue = { myObj: 'someVal' }; instance.provide({ provide: TestClass, useValue: replacementValue }); chai_1.expect(instance.resolveInjectable(TestClass)).to.eq(replacementValue); }); it('should dependency inject a falsey value', () => { let TestClass = class TestClass { constructor() { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass); instance.provide({ provide: TestClass, useValue: null }); chai_1.expect(instance.resolveInjectable(TestClass)).to.be.null; }); }); describe('when the provide metadata is a factory function', () => { it('should dependency inject a factory function', () => { let TestClass = class TestClass { constructor() { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass); let testInst = new TestClass(); instance.provide({ provide: TestClass, useCallback: () => testInst }); chai_1.expect(instance.resolveInjectable(TestClass)).to.eq(testInst); }); it('should call the factory function every time the class is resolved', () => { let TestClass = class TestClass { constructor() { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass); let callbackCallCount = 0; instance.provide({ provide: TestClass, useCallback: () => callbackCallCount++ }); instance.resolveInjectable(TestClass); chai_1.expect(callbackCallCount).to.eq(1); instance.resolveInjectable(TestClass); chai_1.expect(callbackCallCount).to.eq(2); }); it('should only call the factory function once if cache = true', () => { let TestClass = class TestClass { constructor() { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass); let callbackCallCount = 0; instance.provide({ provide: TestClass, useCallback: () => callbackCallCount++, cache: true }); instance.resolveInjectable(TestClass); chai_1.expect(callbackCallCount).to.eq(1); instance.resolveInjectable(TestClass); chai_1.expect(callbackCallCount).to.eq(1); }); it('should dependency inject callback dependencies into the callback', () => { let TestClass = class TestClass { constructor() { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass); let TestClass2 = class TestClass2 { constructor() { } }; TestClass2 = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass2); let testInst = new TestClass(); let callbackCallCount = 0; instance.provide({ provide: TestClass, useCallback: (num2) => { callbackCallCount++; chai_1.expect(num2).to.be.an.instanceOf(TestClass2); return testInst; }, deps: [TestClass2] }); chai_1.expect(instance.resolveInjectable(TestClass)).to.eq(testInst); }); it('should invoke resolveDependencies', () => { let TestClass = class TestClass { constructor() { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass); let meta = { provide: TestClass, useCallback: () => void (0), deps: [] }; sinon.stub(instance, 'resolveDependencies').returns([]); instance.provide(meta); instance.resolveInjectable(TestClass); chai_1.expect(instance.resolveDependencies).to.have.been.calledOnce.calledWith(meta.deps, TestClass); }); it('should resolve string dependencies to defined metadata', () => { let testAbc = 'my?test:abc!'; let TestClass2 = class TestClass2 { constructor() { } }; TestClass2 = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass2); let TestClass = class TestClass { constructor(tc2) { this.tc2 = tc2; } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), meta_decorator_1.Meta('abc', testAbc), __metadata("design:paramtypes", [TestClass2]) ], TestClass); let resolvedAbc = ''; let meta = { provide: TestClass2, useCallback: (abc) => resolvedAbc = abc, deps: ['abc'] }; instance.provide(meta); instance.resolveInjectable(TestClass); chai_1.expect(resolvedAbc).to.eq(testAbc); }); it('should not resolve string dependencies for metadata on the factory function class', () => { let testAbc = 'my?test:abc!'; let TestClass = class TestClass { constructor() { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), meta_decorator_1.Meta('abc', testAbc), __metadata("design:paramtypes", []) ], TestClass); let resolvedAbc = ''; let meta = { provide: TestClass, useCallback: (abc) => resolvedAbc = abc, deps: ['abc'] }; instance.provide(meta); instance.resolveInjectable(TestClass); chai_1.expect(resolvedAbc).to.be.undefined; }); it('should resolve string dependencies for metadata on the factory function class', () => { let testAbc = 'my?test:abc!'; let TestClass = class TestClass { constructor() { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), meta_decorator_1.Meta('name', testAbc), __metadata("design:paramtypes", []) ], TestClass); let resolvedAbc = ''; let meta = { provide: TestClass, useCallback: (abc) => resolvedAbc = abc, deps: ['abc'] }; instance.provide(meta); instance.resolveInjectable(TestClass); chai_1.expect(resolvedAbc).to.be.undefined; }); it(`should resolve the 'name' dependency even if the name has been explicitly defined`, () => { let TestClass2 = class TestClass2 { constructor() { } }; TestClass2 = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass2); let TestClass = class TestClass { constructor(tc2) { this.tc2 = tc2; } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", [TestClass2]) ], TestClass); let resolvedName = ''; let meta = { provide: TestClass2, useCallback: (name) => resolvedName = name, deps: ['name'] }; instance.provide(meta); instance.resolveInjectable(TestClass); chai_1.expect(resolvedName).to.eq(TestClass.name); }); it(`should allow a custom 'name' metadata to be explicitly defined`, () => { let testName = 'my!!!!test:name##'; let TestClass2 = class TestClass2 { constructor() { } }; TestClass2 = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass2); let TestClass = class TestClass { constructor(tc2) { this.tc2 = tc2; } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), name_decorator_1.Name(testName), __metadata("design:paramtypes", [TestClass2]) ], TestClass); let resolvedName = ''; let meta = { provide: TestClass2, useCallback: (name) => resolvedName = name, deps: ['name'] }; instance.provide(meta); instance.resolveInjectable(TestClass); chai_1.expect(resolvedName).to.eq(testName); }); it('should resolve string dependencies for metadata on the factory function class when resolving other dependencies', () => { let testName = 'my?test:name!'; let resolvedName = ''; let TestClass3 = class TestClass3 { constructor() { } }; TestClass3 = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass3); let TestClass2 = class TestClass2 { constructor(tc3) { this.tc3 = tc3; } }; TestClass2 = __decorate([ injectable_decorator_1.Injectable(), meta_decorator_1.Meta('name', testName + '2'), __metadata("design:paramtypes", [TestClass3]) ], TestClass2); let TestClass = class TestClass { constructor(tc2) { this.tc2 = tc2; } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), meta_decorator_1.Meta('name', testName), __metadata("design:paramtypes", [TestClass2]) ], TestClass); let meta3 = { provide: TestClass3, useCallback: (name) => resolvedName = name, deps: ['name'] }; instance.provide(meta3); let meta2 = { provide: TestClass2, useCallback: (tc3) => void (0), deps: [TestClass3] }; instance.provide(meta2); instance.resolveInjectable(TestClass); chai_1.expect(resolvedName).to.eq(testName + '2'); }); it('should throw an error when the factory function depends on itself', () => { let TestClass = class TestClass { constructor() { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass); let meta = { provide: TestClass, useCallback: () => void (0), deps: [TestClass] }; instance.provide(meta); chai_1.expect(() => instance.resolveInjectable(TestClass)).to.throw(/circular dependency/i); }); it('should throw an error when a factory function introduces a circular dependency', () => { let TestClass = class TestClass { constructor() { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass); let TestClass2 = class TestClass2 { constructor(tc) { this.tc = tc; } }; TestClass2 = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", [TestClass]) ], TestClass2); let meta = { provide: TestClass, useCallback: (tc2) => void (0), deps: [TestClass2] }; instance.provide(meta); chai_1.expect(() => instance.resolveInjectable(TestClass)).to.throw(/circular dependency/i); }); }); }); describe('.resolveDependencies', () => { let resolveDependencies; beforeEach(() => { resolveDependencies = instance.resolveDependencies.bind(instance); }); it('should throw when the dependencies cannot be resolved', () => { let TestClass = class TestClass { constructor() { } }; TestClass = __decorate([ injectable_decorator_1.Injectable(), __metadata("design:paramtypes", []) ], TestClass); chai_1.expect(() => resolveDependencies([null], TestClass)).to.throw(/Failed to resolve dependencies/); chai_1.expect(() => resolveDependencies([Object], TestClass)).to.throw(/Failed to resolve dependencies/); }); }); }); //# sourceMappingURL=injector.spec.js.map