From 1f850329b4a6c9bfd14b618a9d76ef802eea28ca Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Mon, 29 Sep 2014 01:21:08 +1000 Subject: Lots of changes: now async, behaviour almost entirely managed by plugins --- injector-tests.js | 445 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 302 insertions(+), 143 deletions(-) (limited to 'injector-tests.js') diff --git a/injector-tests.js b/injector-tests.js index 339034f..e94126f 100644 --- a/injector-tests.js +++ b/injector-tests.js @@ -1,200 +1,359 @@ -/*global describe,it,expect,beforeEach*/ -describe("injector", function() { - var injector; +/*global describe,it,expect,beforeEach,Injector, setTimeout*/ + +describe("deferred", function() { + var deferred, promise; beforeEach(function() { - injector = new Injector(); + deferred = Injector.defer(); + promise = deferred.promise; }); + this.timeout(100); - describe("#register()", function() { - it("should return the injector", function() { - var result = injector.register("A", function(){ return "A"; }); - expect(result).toBe(injector); - }); - - it("should use a function's name if no name provided", function() { - injector.register(function A(){ return "A"; }); - expect(injector.specs.A).not.toBe(undefined); // IMPLEMENTATION DETAIL + describe("promise", function() { + it("should have a 'then' method", function() { + expect(promise.then).not.to.equal(undefined); }); - it("should register an object as a series of dependencies", function() { - injector.register({ - A: function(){ return "A"; }, - B: function(){ return "B"; } + it("should be able to chain 'then' methods", function() { + var promise1 = promise; + var promise2 = promise1.then(function(x) { + return x + 1; }); - expect(injector.specs.A).not.toBe(undefined); // IMPLEMENTATION DETAIL - expect(injector.specs.B).not.toBe(undefined); // IMPLEMENTATION DETAIL + var promise3 = promise1.then(function(x) { + return x + 1; + }); + expect(promise1.then).not.to.equal(undefined); + expect(promise2.then).not.to.equal(undefined); + expect(promise3.then).not.to.equal(undefined); }); }); - describe("#get()", function() { - it("should throw if the dependency can't be found", function() { - expect(function() { - injector.get("A"); - }).toThrow(); + describe("resolution", function() { + + it("should resolve asynchronously", function(done) { + var hasRun = false; + promise.then(function(val) { + hasRun = true; + return val; + }); + deferred.resolve("value"); + expect(hasRun).to.equal(false); + setTimeout(function() { + expect(hasRun).to.equal(true); + done(); + }); }); - }); - describe("#invoke()", function() { - it("should be able to invoke a function with no dependencies", function() { - var A = injector.invoke(function() {return "functionResult";}); - expect(A).toBe("functionResult"); + it("should propagate through multiple promises", function(done) { + var promise2 = promise.then(function(value) { + return value + 1; + }); + var promise3 = promise2.then(function(value) { + return value + 1; + }); + promise3.then(function(value) { + expect(value).to.equal(3); + done(); + }); + deferred.resolve(1); }); - it("should be able to invoke a function with a dependency in an array", function() { - injector.register("B", function() {return "constructorB";}); - var A = injector.invoke(["B", function(B) {return B + "A";}]); - expect(A).toBe("constructorBA"); + it("should call all registered handlers", function(done) { + var called = 0; + promise.then(function() {called++;}); + promise.then(function() {called++;}); + promise.then(function() {called++;}); + expect(called).to.equal(0); + deferred.resolve(null); + setTimeout(function() { + expect(called).to.equal(3); + done(); + }); }); - it("should be able to invoke a function with an implicit dependency", function() { - injector.register("B", function() {return "constructorB";}); - var A = injector.invoke(function(B) {return B + "A";}); - expect(A).toBe("constructorBA"); + it("should call handlers, even when added after resolution", function(done) { + deferred.resolve(null); + promise.then(function() { + done(); + }); }); - it("should not touch any exceptions thrown by the function", function() { + it("should not allow multiple resolution", function() { + deferred.resolve(1); expect(function() { - throw new Error("An error"); - }).toThrow(new Error("An error")); + deferred.resolve(2); + }).to.throw(); }); - it("should invoke the function with 'this' set to the injector", function() { - var hasRun = false; - injector.invoke(function() { - hasRun = true; - expect(this).toBe(injector); - }); - expect(hasRun).toBe(true); + it("should not allow rejecting after resolving", function() { + deferred.resolve(1); + expect(function() { + deferred.reject(2); + }).to.throw(); }); - it("should not provide 'this.requestor' to the invoked function", function() { - var caught = {}; - try { - injector.invoke(function() { - this.requestor(); - }); - } catch (e) { - caught = e; - } - expect(caught.name).toBe("InjectorError"); + it("should handle a returned promise by 'unwrapping' it", function(done) { + promise.then(function(value) { + var deferred = Injector.defer(); + deferred.resolve(value + 1); + return deferred.promise; + }).then(function(value) { + try { + expect(value).to.equal(2); + done(); + } catch (e) { + done(e); + } + }); + deferred.resolve(1); }); }); - describe("constructor dependency injection", function() { - it("should be able to register a constructor with no dependencies, and get the value for it", function() { - injector.register("A", function() {return "constructorA";}); - - var A = injector.get("A"); - expect(A).toBe("constructorA"); + describe("rejection", function() { + it("should reject asynchronously", function(done) { + var hasRun = false; + promise.then(null, function(val) { + hasRun = true; + return val; + }); + deferred.reject("value"); + expect(hasRun).to.equal(false); + setTimeout(function() { + expect(hasRun).to.equal(true); + done(); + }); }); - it("should be able to register a constructor with a dependency in an array, and get the value for it", function() { - injector.register("B", function() {return "constructorB";}); - injector.register("A", ["B", function(B) {return B;}]); - var A = injector.get("A"); - expect(A).toBe("constructorB"); + it("should be turned into resolution by a handler", function(done) { + var promise2 = promise.then(function(value) { + return value + 1; + }, function(value) { + return value + 100; + }); + promise2.then(function(value) { + try { + expect(value).to.equal(101); + done(); + } catch (e) { + done(e); + } + }); + deferred.reject(1); }); - it("should be able to register a constructor with an implicit dependency, and get the value for it", function() { - injector.register("B", function() {return "constructorB";}); - injector.register("A", function(B) {return B;}); + it("should call all registered handlers", function(done) { + var called = 0; + promise.then(null, function() {called++;}); + promise.then(null, function() {called++;}); + promise.then(null, function() {called++;}); + expect(called).to.equal(0); + deferred.reject(null); + setTimeout(function() { + expect(called).to.equal(3); + done(); + }); + }); - var A = injector.get("A"); - expect(A).toBe("constructorB"); + it("should not allow multiple rejection", function() { + deferred.reject(1); + expect(function() { + deferred.reject(2); + }).to.throw(); }); - it("should be able to register a constructor with an implicit name", function() { - injector.register(function A() {return "constructorA";}); - var A = injector.get("A"); - expect(A).toBe("constructorA"); + it("should not allow resolving after rejecting", function() { + deferred.reject(1); + expect(function() { + deferred.resolve(2); + }).to.throw(); }); - it("should be able to register constructors with an object", function() { - injector.register({ - A: ["B", function (B) {return "constructorA" + B;}], - B: function(C) { return "B" + C; }, - C: function() { return "C"; } + it("should call handlers, even when added after resolution", function(done) { + deferred.reject(null); + promise.then(null, function() { + done(); }); - var A = injector.get("A"); - expect(A).toBe("constructorABC"); }); - it("should wrap any exceptions thrown by constructors", function() { - var error = new Error("constructorA"); - injector.register("A", function() {throw error;}); - var caught = {}; - try { - injector.get("A"); - } catch (e) { - caught = e; - } - expect(caught.name).toBe("InjectorError"); - expect(caught.cause).toBe(error); + it("should handle a returned promise by 'unwrapping' in the error case", function(done) { + promise.then(function(value) { + var deferred = Injector.defer(); + deferred.reject(value + 1); + return deferred.promise; + }).then(null, function(value) { + try { + expect(value).to.equal(2); + done(); + } catch (e) { + done(e); + } + }); + deferred.resolve(1); }); + }); - it("should detect circular dependencies", function() { - injector.register("A", ["B", function(B) {return null;}]); - injector.register("B", ["A", function(A) {return null;}]); + describe("waitForAll", function() { + it("should resolve with an array if all successful", function(done) { + var d1 = Injector.defer(), + d2 = Injector.defer(), + d3 = Injector.defer(); + Injector + .waitForAll([d1.promise, d2.promise, d3.promise]) + .then(function(values) { + if (values.length == 3 && + values[0] === 0 && + values[1] === 1 && + values[2] === 2) { + done(); + } else { + done(new Error("Error in resolved result: " + values)); + } + }, done); + d1.resolve(0); + d2.resolve(1); + d3.resolve(2); + }); - var caught = {message: ''}; - try { - injector.get("A"); - } catch (e) { - caught = e; - } - expect(caught.message.indexOf("Cyclic dependency detected")).toBe(0); + it("should reject with an object of successes/errors if any fail", function(done) { + var d1 = Injector.defer(), + d2 = Injector.defer(), + d3 = Injector.defer(); + Injector + .waitForAll([d1.promise, d2.promise, d3.promise]) + .then(function(values) { + done(new Error("incorrectly resolved promise")); + }, function(e) { + if (e.errors && e.values && + e.values[0] === 0 && + e.values[1] === 1 && + e.errors[2] === 2) { + done(); + } else { + done(new Error("incorrect reject value")); + } + }); + d1.resolve(0); + d2.resolve(1); + d3.reject(2); }); + }); +}); - it("should provide access to the requestor through 'this.requestor()'", function() { - injector.register("B", function() { - expect(this.requestor()).toBe("A"); - return "returnValueB"; - }); - injector.register("A", ["B", function(B) { - return B + "A"; - }]); +describe("injector", function() { + var injector, valuePlugin; + beforeEach(function() { + valuePlugin = new Injector.ValuePlugin(); + injector = new Injector([valuePlugin]); + }); + this.timeout(100); - // make sure the function actually ran - expect(injector.get("A")).toBe("returnValueBA"); + describe("value registration and retrieval", function() { + it("works", function(done) { + injector.register("a", function(){return "the value of a";}); + injector.get("a").then(function(value){ + if (value == "the value of a") + done(); + else + done(new Error("incorrect value for a (" + value + ")")); + }, done); }); - }); - describe("caching", function() { - it("should return the same exact value for multiple requests of the same dependency", function() { - injector.register("A", function() {return {an: 'object'};}); + it("with dependencies works", function(done) { + injector + .register("a", function(){return "the value of a";}) + .register("b", ["a", function(a){return a.replace(/a/g, "b");}]); + injector.get("b").then(function(value){ + if (value == "the vblue of b") + done(); + else + done(new Error("incorrect value for b (" + value + ")")); + }, done); + }); + }); - var first = injector.get("A"); - var second = injector.get("A"); - expect(first).toBe(second); + describe("destroy handlers", function() { + it("work with single gets", function(done) { + injector.register("a", function(){return "a";}); + expect("a" in valuePlugin.values).to.equal(false); + var result = injector.get("a"); + result.then(function() { + if ("a" in valuePlugin.values) { + result.destroy().then(function() { + if ("a" in valuePlugin.values) { + done(new Error("Value found for a after destruction")); + } else { + done(); + } + }); + } else { + done(new Error("No value found for a")); + } + }, done); }); - it("shouldn't re-run constructors for cached values", function() { - var numTimes = 0; - injector.register("A", function() { - numTimes++; - if (numTimes > 1) - throw new Error("Shouldn't be run more than once"); - return {an: 'object'}; - }); + it("works with multiple gets", function(done) { + injector.register("a", function(){return "a";}); + expect("a" in valuePlugin.values).to.equal(false); + var result = [injector.get("a"), injector.get("a"), injector.get("a")]; + result[1].destroy(); + result[2].destroy(); + result[0].then(function() { + if ("a" in valuePlugin.values) { + result[0].destroy().then(function() { + if ("a" in valuePlugin.values) { + done(new Error("Value found for a after destruction")); + } else { + done(); + } + }); + } else { + done(new Error("No value found for a")); + } + }, done); + }); - var first = injector.get("A"); - var second = injector.get("A"); - expect(first).toBe(second); + it("works through dependencies", function(done) { + injector + .register("a", function(){return "a";}) + .register("b", ["a", function(a){return a + "b";}]); + expect("a" in valuePlugin.values).to.equal(false); + var result = injector.get("b"); + result.then(function() { + if ("a" in valuePlugin.values) { + result.destroy().then(function() { + if ("a" in valuePlugin.values) { + done(new Error("Value found for a after destruction")); + } else { + done(); + } + }); + } else { + done(new Error("No value found for a")); + } + }, done); }); + }); - it("shouldn't cache values which depend on their requestor", function() { - var numTimes = 0; - injector.register("A", function() { - numTimes++; - return "A" + this.requestor(); + describe("cyclic dependency detection", function() { + it("detects simple cycles", function(done) { + injector.register("a", ["b", function(b) {return b;}]); + injector.register("b", ["a", function(a) {return a;}]); + injector.get("a").then(function() { + done(new Error("Promise should have been rejected")); + }, function(e) { + done(); }); - injector.register("B", ["A", function(A) {return "B" + A;}]); - injector.register("C", ["A", function(A) {return "C" + A;}]); + }); - expect(injector.get("B")).toBe("BAB"); - expect(injector.get("C")).toBe("CAC"); - expect(numTimes).toBe(2); + it("detects cycles with intermediate nodes", function(done) { + injector.register("a", ["c", function(c) {return c;}]); + injector.register("b", ["a", function(a) {return a;}]); + injector.register("c", ["b", function(b) {return b;}]); + injector.get("a").then(function() { + done(new Error("Promise should have been rejected")); + }, function(e) { + done(); + }); }); }); }); -- cgit v1.2.3