├── jsTestDriver.conf ├── LICENSE ├── jsInject.min.js ├── README.md ├── jsInject.js └── jsInjectTests.js /jsTestDriver.conf: -------------------------------------------------------------------------------- 1 | load: 2 | - jsInject.js 3 | - jasmine-1.1.0.js 4 | - JasmineAdapter-1.1.2.js 5 | 6 | test: 7 | - jsInjectTests.js 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jeremy Likness 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /jsInject.min.js: -------------------------------------------------------------------------------- 1 | "use strict";(function(e){function r(){this.container={}}function i(){var e=new r,t=this;this.get=e.get.bind(e);this.register=e.register.bind(e);e.container["$$jsInject"]=function(){return t}}var t=20,n=function(e){return Object.prototype.toString.call(e)==="[object Array]"};r.ERROR_RECURSION="Maximum recursion at ";r.ERROR_REGISTRATION="Already registered.";r.ERROR_ARRAY="Must pass array.";r.ERROR_FUNCTION="Must pass function to invoke.";r.ERROR_SERVICE="Service does not exist.";r.prototype.get=function(e,t){var n=this.container[e],i=t||0;if(n){return n(i)}throw r.ERROR_SERVICE};r.prototype.invoke=function(e,n,i,s){var o=0,u=[],a=s||0;if(a>t){throw r.ERROR_RECURSION+a}for(;o maxRecursion) { 34 | throw JsInject.ERROR_RECURSION + lvl; 35 | } 36 | for (; i < deps.length; i += 1) { 37 | args.push(this.get(deps[i], lvl + 1)); 38 | } 39 | return fn.apply(instance, args); 40 | }; 41 | 42 | JsInject.prototype.register = function (name, annotatedArray) { 43 | if (!isArray(annotatedArray)) { 44 | throw JsInject.ERROR_ARRAY; 45 | } 46 | 47 | if (this.container[name]) { 48 | throw JsInject.ERROR_REGISTRATION; 49 | } 50 | 51 | if (typeof annotatedArray[annotatedArray.length - 1] !== 'function') { 52 | throw JsInject.ERROR_FUNCTION; 53 | } 54 | 55 | var _this = this; 56 | this.container[name] = function (level) { 57 | var lvl = level || 0, 58 | Template = function () {}, 59 | result = {}, 60 | instance, 61 | fn = annotatedArray[annotatedArray.length - 1], 62 | deps = annotatedArray.length === 1 ? (annotatedArray[0].$$deps || []) : 63 | annotatedArray.slice(0, annotatedArray.length - 1), 64 | injected; 65 | Template.prototype = fn.prototype; 66 | instance = new Template(); 67 | injected = _this.invoke(fn, deps, instance, lvl + 1); 68 | result = injected || instance; 69 | _this.container[name] = function () { 70 | return result; 71 | }; 72 | return result; 73 | }; 74 | }; 75 | 76 | function Wrapper() { 77 | var ioc = new JsInject(), _that = this; 78 | this.get = ioc.get.bind(ioc); 79 | this.register = ioc.register.bind(ioc); 80 | ioc.container['$$jsInject'] = function () { 81 | return _that; 82 | }; 83 | } 84 | 85 | w.$$jsInject = Wrapper; 86 | })(window); -------------------------------------------------------------------------------- /jsInjectTests.js: -------------------------------------------------------------------------------- 1 | describe("jsInject Inversion of Control", function () { 2 | 3 | var empty = function() {}; 4 | var $jsInject; 5 | 6 | beforeEach(function() { 7 | $jsInject = new $$jsInject(); 8 | $jsInject.register("echoFn", [function() { 9 | return { 10 | echo: function(msg) { 11 | return msg; 12 | } 13 | }; 14 | }]); 15 | }); 16 | 17 | it("Given ioc when $$jsInject requested then returns self.", function () { 18 | var actual = $jsInject.get('$$jsInject'); 19 | expect(actual).toBe($jsInject); 20 | }); 21 | 22 | describe("First-time registration", function() { 23 | 24 | it("Given self-invoking constructor function when registered then registers successfully", function() { 25 | 26 | var expected = "1"; 27 | 28 | var fn = (function() { 29 | function Fn(echo) { 30 | this.echo = echo; 31 | this.test = function() { 32 | return echo.echo(expected); 33 | }; 34 | } 35 | return Fn; 36 | })(); 37 | 38 | $jsInject.register("1", ["echoFn", fn]); 39 | var actual = $jsInject.get("1"); 40 | expect(actual.test()).toBe(expected); 41 | }); 42 | 43 | it("Given function constructor when registered then registers successfully", function() { 44 | 45 | var expected = "2"; 46 | 47 | function Fn(echo) { 48 | this.test = echo.echo(expected); 49 | } 50 | 51 | $jsInject.register("2", ["echoFn", Fn]); 52 | var actual = $jsInject.get("2"); 53 | expect(actual.test).toBe(expected); 54 | }); 55 | 56 | it("Given function factory method when registered then registers successfully", function() { 57 | 58 | var expected = "3"; 59 | 60 | var factory = function(echo) { 61 | return { 62 | test: echo.echo(expected) 63 | }; 64 | }; 65 | 66 | factory.$$deps = ["echoFn"]; 67 | 68 | $jsInject.register("3", [factory]); 69 | var actual = $jsInject.get("3"); 70 | expect(actual.test).toBe(expected); 71 | }); 72 | 73 | }); 74 | 75 | describe("Validity checks and contracts", function() { 76 | 77 | it("Given something other than an array passed then it throws an error", function() { 78 | expect(function() { 79 | $jsInject.register("4", "4"); 80 | }).toThrow($$jsInject.ERROR_ARRAY); 81 | }); 82 | 83 | it("Given lst item in array passed is not a function then it throws an error", function() { 84 | expect(function() { 85 | $jsInject.register("4", ["4"]); 86 | }).toThrow($$jsInject.ERROR_FUNCTION); 87 | }); 88 | 89 | it("Given a registration already exists when duplicate registration is attempted then it throws an error", function() 90 | { 91 | $jsInject.register("3", [empty]); 92 | 93 | expect(function() { 94 | $jsInject.register("3", [empty]); 95 | }).toThrow($$jsInject.ERROR_REGISTRATION); 96 | }); 97 | 98 | it("Given recursive dependencies when a dependency is requested then it throws an error", function () { 99 | 100 | $jsInject.register("depA", ["depB", empty]); 101 | $jsInject.register("depB", ["depA", empty]); 102 | 103 | expect(function () { 104 | var depA = $jsInject.get("depA"); 105 | }).toThrow($$jsInject.ERROR_RECURSION); 106 | }); 107 | 108 | it("Given get request on service that does not exist then it throws an error", function () { 109 | expect(function () { 110 | var nothing = $jsInject.get("nothing"); 111 | }).toThrow($$jsInject.ERROR_SERVICE); 112 | }); 113 | 114 | }); 115 | 116 | describe("Run-time annotations", function(){ 117 | 118 | function serviceA() { 119 | return { 120 | id: "a" 121 | }; 122 | } 123 | 124 | function serviceB(a) { 125 | return { 126 | id: a.id + "b" 127 | }; 128 | } 129 | 130 | function serviceC(a, b) { 131 | return { 132 | id: a.id + b.id + "c" 133 | }; 134 | } 135 | 136 | beforeEach(function() { 137 | $jsInject.register("serviceA", [serviceA]); 138 | $jsInject.register("serviceB", ["serviceA", serviceB]); 139 | $jsInject.register("serviceC", ["serviceA", serviceC]); 140 | }); 141 | 142 | it ("Given registration with proper annotation then returns properly configured instance", function() { 143 | var expected = "ab"; 144 | var actual = $jsInject.get("serviceB").id; 145 | expect(actual).toBe(expected); 146 | }); 147 | 148 | it ("Given registration with improper annotation then throws exception due to bad reference", function() { 149 | expect(function () { 150 | $jsInject.get("serviceC"); 151 | }).toThrow(); 152 | }); 153 | }); 154 | 155 | describe("Object annotations", function(){ 156 | 157 | function ServiceA() { 158 | this.id = "a"; 159 | } 160 | 161 | function ServiceB(serviceA) { 162 | this.id = serviceA.id + "b"; 163 | } 164 | 165 | ServiceB.$$deps = ["ServiceA"]; 166 | 167 | function ServiceC(serviceA, serviceB) { 168 | this.id = serviceA.id + serviceB.id + "b"; 169 | } 170 | 171 | ServiceC.$$deps = ["ServiceA"]; 172 | 173 | beforeEach(function() { 174 | $jsInject.register("ServiceA", [ServiceA]); 175 | $jsInject.register("ServiceB", [ServiceB]); 176 | $jsInject.register("ServiceC", [ServiceC]); 177 | }); 178 | 179 | it ("Given registration with properly annotated function then returns properly configured instance", function() { 180 | var expected = "ab"; 181 | var actual = $jsInject.get("ServiceB").id; 182 | expect(actual).toBe(expected); 183 | }); 184 | 185 | it ("Given registration with improperly annotated function then throws exception due to bad reference", function() { 186 | expect(function () { 187 | $jsInject.get("ServiceC"); 188 | }).toThrow(); 189 | }); 190 | }); 191 | }); --------------------------------------------------------------------------------