Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

* Work In Progress : enhancements, bug fixes, new features (seal, uns…

…eal, and stuff)
  • Loading branch information...
commit a61b8f00e12a5da4ce619756964d6e6c652dc284 1 parent 88d13c7
@DrBenton authored
Showing with 660 additions and 185 deletions.
  1. +206 −73 medic-injector.js
  2. +34 −0 package.json
  3. +420 −0 test/test.js
  4. +0 −112 tests/test.js
View
279 medic-injector.js
@@ -5,7 +5,8 @@
// InjectionMapping
var FORBIDDEN_INJECTIONS_NAMES = [
- 'callback'
+ 'callback',
+ 'injectionValue'
];
/**
@@ -30,7 +31,7 @@
/**
*
* @param value
- * @return {InjectionMapping}
+ * @return {InjectionMapping} The <code>InjectionMapping</code> the method is invoked on
* @throws Error
*/
InjectionMapping.prototype.toValue = function (value)
@@ -42,20 +43,20 @@
/**
*
- * @param {Function} callback
- * @return {InjectionMapping}
+ * @param {Function} injectionValueProviderFunction
+ * @return {InjectionMapping} The <code>InjectionMapping</code> the method is invoked on
* @throws Error
*/
- InjectionMapping.prototype.toFunctionResult = function (callback)
+ InjectionMapping.prototype.toProvider = function (injectionValueProviderFunction)
{
this._sealed && this._throwSealedException();
- this._toFunctionResult = callback;
+ this._toProvider = injectionValueProviderFunction;
return this;
};
/**
*
- * @return {InjectionMapping}
+ * @return {InjectionMapping} The <code>InjectionMapping</code> the method is invoked on
* @throws Error
*/
InjectionMapping.prototype.asSingleton = function ()
@@ -66,45 +67,88 @@
};
/**
+ * Resolves the injection mapping.
+ * When it's resolved, the callback function is triggered in the supplied context.
+ * The first arg of the triggered callback will be the injection value. If you have anothers
+ * args in the callback function, they will be inspected and may be filled with other injections values too,
+ * for each of these function arg whose name s the same that an existing InjectionMapping.
+ *
+ * @param {Function} callback
+ * @param {Object/null} [context=null]
+ * @param {Boolean/null} [forceAsync=false]
+ */
+ InjectionMapping.prototype.resolveInjection = function (callback, context, forceAsync)
+ {
+ if (this._singletonValue) {
+
+ // Simple and immediate callback trigger
+ this._triggerFunction(callback, [this._singletonValue], context, forceAsync);
+
+ } else if (this._toValue) {
+
+ // Simple and immediate callback trigger
+ this._triggerFunction(callback, [this._toValue], context, forceAsync);
+
+ } else if (this._toProvider) {
+
+ // This InjectionMapping value is retrieved from a Provider function
+ // It's gonna be... well... a bit less simple :-)
+
+ if (this._asSingleton) {
+ // For "singletons" InjectionMappings, we trigger the Provider value only once,
+ // even if other resolutions are asked while it's being resolved
+ if (this._resolutionInProgress) {
+ this._queuedResolutions.push({'cb': callback, 'ctx': context, 'async': forceAsync});
+ return;//this Injection resolution is queued (it will be handled in "this._resolveProvider()"); we stop right now
+ } else {
+ this._resolutionInProgress = true;
+ this._queuedResolutions = [];//we will queue subsequent requests
+ }
+ }
+
+ this._resolveProvider(callback, context, forceAsync);
+
+ }
+ };
+
+ /**
* Seal this Injection mapping. Any subsequent call to any of the
* "toValue()", "toCallbackResult()" or "asSingleton()" methods will throw
* an Error.
- * @return {InjectionMapping}
+ * @return {Object} returns a "unseal" key ; the only way to unseal this InjectionMapping it to call its "unseal()" method with this key
* @throws Error
+ * @see #unseal()
*/
InjectionMapping.prototype.seal = function ()
{
this._sealed && this._throwSealedException();
this._sealed = true;
- return this;
+ this._sealKey = {};
+ return this._sealKey;
};
/**
- * Resolves the injection mapping.
+ * Reverts the effect of <code>seal</code>, makes the mapping changeable again.
*
- * @param {Function} callback
- * @param {Object/null} [context=null]
- * @param {Boolean/null} [forceAsync=false]
+ * @param {Object} key The key to unseal the mapping. Has to be the instance returned by
+ * <code>seal()</code>
+ * @return {InjectionMapping} The <code>InjectionMapping</code> the method is invoked on
+ * @throws Error Has to be invoked with the unique key object returned by an earlier call to <code>seal</code>
+ * @throws Error Can't unseal a mapping that's not sealed
+ * @see #seal()
*/
- InjectionMapping.prototype.resolveInjection = function (callback, context, forceAsync)
+ InjectionMapping.prototype.unseal = function (key)
{
- var triggerCallback = function(injectionValue) {
- if (!forceAsync)
- callback.call(context, injectionValue);
- else
- nextTick(function () {callback.call(context, injectionValue);});
- };
-
- if (this._singletonValue) {
- return triggerCallback(this._singletonValue);
- } else if (this._toValue) {
- return triggerCallback(this._toValue);
+ if (!this._sealed) {
+ throw new Error('Can\'t unseal a non-sealed mapping.');
}
-
- if (this._toFunctionResult) {
- this._resolveFunctionResultInjection(callback, context, forceAsync);
+ if (key !== this._sealKey)
+ {
+ throw new InjectorError('Can\'t unseal mapping without the correct key.');
}
-
+ this._sealed = false;
+ this._sealKey = null;
+ return this;
};
/**
@@ -117,7 +161,7 @@
};
/**
- * Recursive resolution of a "function result" injection. Any of this function's argument whose name is a registered
+ * Recursive resolution of a "provider" injection. Any of this provider function's argument whose name equals a registered
* injection name will itself trigger a recursive injection resolution.
*
* @param {Function} callback
@@ -125,59 +169,120 @@
* @param {Boolean/null} [forceAsync=false]
* @private
*/
- InjectionMapping.prototype._resolveFunctionResultInjection = function (callback, context, forceAsync)
+ InjectionMapping.prototype._resolveProvider = function (callback, context, forceAsync)
{
- var targetFunction = this._toFunctionResult;
- var functionArgsNames = getArgumentNames(targetFunction);
- var isAnAsyncFunction = (-1 < functionArgsNames.indexOf('callback')) ? true : false ;
+ var providerFunction = this._toProvider;
+ var providerArgsNames = getArgumentNames(providerFunction);
+ var callbackArgIndex = providerArgsNames.indexOf('callback');
+ var isAnAsyncFunction = (-1 < callbackArgIndex) ? true : false ;
- myDebug && console && console.log('_resolveFunctionResultInjection() ; isAnAsyncFunction=', isAnAsyncFunction, ', functionArgsNames=', functionArgsNames);
+ myDebug && console && console.log('******** _resolveProvider() ; isAnAsyncFunction=', isAnAsyncFunction, ', providerArgsNames=', providerArgsNames);
var that = this;//we don't bundle Underscore nor jQuery, so let's back to the good old "that = this" scope trick :-)
// 2) This function will be triggered when all the required injections of the function will have been resolved
- var onFunctionArgsInjectionsResolution = function (resolvedInjectionsValues)
+ var onProviderArgsInjectionsResolution = function (resolvedInjectionsValues)
{
- myDebug && console && console.log('_resolveFunctionResultInjection()#onFunctionArgsInjectionsResolution ; functionArgsNames=', functionArgsNames);
- var injectionValue;
+ myDebug && console && console.log('_resolveFunctionResultInjection()#onProviderArgsInjectionsResolution ; providerArgsNames=', providerArgsNames);
if (isAnAsyncFunction) {
- // This function is async (it has a "callback" argument)
+ // This Provider is async (it has a "callback" argument)
// --> we trigger it right now and wait for the callback resolution (it will have the injection value as only argument)
- var callbackArgIndex = functionArgsNames.indexOf('callback');
- resolvedInjectionsValues[callbackArgIndex] = onFunctionResult;//we insert our callback in the function args
- targetFunction.apply(null, resolvedInjectionsValues);
+ resolvedInjectionsValues[callbackArgIndex] = onProviderResult;//we insert our callback in the Provider "callback" arg
+ providerFunction.apply(null, resolvedInjectionsValues);
} else {
- // This function is not aysnc (it doesn't iteslf have a "callback" argument) ; the injection value is the return value of the callback
+ // This Provider is not aysnc (it doesn't iteslf have a "callback" argument) ; the injection value is the return value of the callback
// --> we trigger it right now and just retrieve its return value
- injectionValue = targetFunction.apply(null, resolvedInjectionsValues);
- onFunctionResult(injectionValue);
+ var injectionValue = providerFunction.apply(null, resolvedInjectionsValues);
+ onProviderResult(injectionValue);
}
-
-
- resolvedInjectionsValues.unshift(that._toFunctionResult);
};
- // 3) We have the function result : handle it and trigger the injection callback
- var onFunctionResult = function (functionResultInjectionValue) {
- myDebug && console && console.log('_resolveFunctionResultInjection()#onFunctionResult ; functionResultInjectionValue=', functionResultInjectionValue);
+ // 3) We have the Provider result : handle it and trigger the injection callback
+ var onProviderResult = function (providerReturnedValue) {
+ myDebug && console && console.log('_resolveFunctionResultInjection()#onFunctionResult ; providerReturnedValue=', providerReturnedValue);
if (that._asSingleton) {
- // now we will always use this return value for this Injection
- that._singletonValue = functionResultInjectionValue;
+ // from now on, we will always use this return value for this Injection (it won't have to be resolved again)
+ that._singletonValue = providerReturnedValue;
}
- // Now we can call our Injection callback (it will be itself "injected") : the first param is always the injection value, the following ones are handled by the reflection/injection mechanism)
- var callbackArgsNames = getArgumentNames(callback);
- var onInjectionsResolution = function (resolvedInjectionsValues) {
- resolvedInjectionsValues.unshift(functionResultInjectionValue);
- callback.apply(context, resolvedInjectionsValues);
- };
- that._injector.resolveInjections(callbackArgsNames, onInjectionsResolution, context, forceAsync);
+ // Now we can call our Injection callback (it will be itself "injected")
+ that._triggerInjectionResolutionCallback(providerReturnedValue, callback, context, forceAsync);
+
+ // In "as singleton" mode we may have pending Injection Resolutions
+ // --> let's resolve them if needed
+ if (that._queuedResolutions && that._queuedResolutions.length > 0) {
+ for (var i = 0; i < that._queuedResolutions.length; i++) {
+ var queuedResolution = that._queuedResolutions[i]
+ , queuedResolutionCallback = queuedResolution['cb']
+ , queuedResolutionContext = queuedResolution['ctx']
+ , queuedResolutionForceAsync = queuedResolution['async'];
+ that._triggerInjectionResolutionCallback(providerReturnedValue, queuedResolutionCallback, queuedResolutionContext, queuedResolutionForceAsync);
+ }
+ that._queuedResolutions = null;//garbage collection on queued callbacks
+ }
+ that._resolutionInProgress = false;
+
};
// 1) Okay, let's start with the resolution of the requested injections of this function...
- this._injector.resolveInjections(functionArgsNames, onFunctionArgsInjectionsResolution);
+ this._injector.resolveInjections(providerArgsNames, onProviderArgsInjectionsResolution);
+
+ };
+
+ /**
+ *
+ * @param injectionResolvedValue
+ * @param {Function} callback
+ * @param {Object/null} [context=null]
+ * @param {Boolean/null} [forceAsync=false]
+ * @private
+ * @throws Error an Error will be thrown if the callback function have more than 1 arg and none of them is called "injectionName"
+ */
+ InjectionMapping.prototype._triggerInjectionResolutionCallback = function (injectionResolvedValue, callback, context, forceAsync)
+ {
+ var callbackArgsNames = getArgumentNames(callback);
+
+ if (2 > callbackArgsNames) {
+
+ // The InjectionMapping value callback only have 1 arg.
+ // --> this arg is simply filled with the resolved injection value!
+ this._triggerFunction(callback, [injectionResolvedValue], context, forceAsync);
+
+ } else {
+
+ // The InjectionMapping value callback have multiple args.
+ // --> we launch an injections resolution process on it, then we will trigger it.
+ // The injection value will be injected to its "injectionValue" arg
+ var injectionValueArgIndex = callbackArgsNames.indexOf('injectionValue');
+ if (-1 === injectionValueArgIndex) {
+ throw new Error('An injection resolution callback function with more than 1 argument *must* have a "injectionValue" arg!');
+ }
+ var that = this;//sorry...
+ var onInjectionsResolution = function (resolvedInjectionsValues) {
+ resolvedInjectionsValues[injectionValueArgIndex] = injectionResolvedValue;//we insert our injection value in the callback "injectionValue" arg
+ that._triggerFunction(callback, resolvedInjectionsValues, context, forceAsync);
+ };
+ this._injector.resolveInjections(callbackArgsNames, onInjectionsResolution, this);
+
+ }
+ };
+
+ /**
+ *
+ * @param {Function} func
+ * @param {Array} argsArray
+ * @param {Object/null} [context=null]
+ * @param {Boolean/null} [forceAsync=false]
+ * @private
+ */
+ InjectionMapping.prototype._triggerFunction = function (func, argsArray, context, forceAsync)
+ {
+ if (!forceAsync)
+ func.apply(context, argsArray);
+ else
+ nextTick(function () {func.apply(context, argsArray);});
};
/**
@@ -203,8 +308,8 @@
*/
Injector.prototype.addMapping = function (injectionName)
{
- if (!!this._mappings[injectionName] && this._mappings[injectionName].isSealed()) {
- throw new Error('Injection name "'+injectionName+'" is already used by a sealed InjectionMapping!');
+ if (!!this._mappings[injectionName]) {
+ throw new Error('Injection name "'+injectionName+'" is already used!');
}
var newInjectionMapping = new InjectionMapping(this, injectionName);
this._mappings[injectionName] = newInjectionMapping;
@@ -213,6 +318,34 @@
/**
*
+ * @param {String} injectionName
+ * @return {Injector}
+ * @throws Error An Error is thrown if the target InjectionMapping has been sealed
+ */
+ Injector.prototype.removeMapping = function (injectionName)
+ {
+ if (!!this._mappings[injectionName] && this._mappings[injectionName].isSealed()) {
+ throw new Error('Injection name "'+injectionName+'" is sealed and cannot be removed!');
+ }
+ delete this._mappings[injectionName];
+ return this;
+ };
+
+ /**
+ *
+ * @param {String} injectionName
+ * @return {Boolean}
+ */
+ Injector.prototype.hasMapping = function (injectionName)
+ {
+ return !!this._mappings[injectionName];
+ };
+
+ /**
+ * Triggers the target function with the supplied context.
+ * The function args are parsed, and for each of these args whose name equals a registered InjectionMapping name
+ * the injection will be resolved and its value will fill the matching function arg value.
+ *
* @param {Function} func
* @param {Object/null} [context=null]
* @param {Boolean/null} [forceAsync=false]
@@ -220,11 +353,15 @@
Injector.prototype.triggerFunctionWithInjectedParams = function (func, context, forceAsync)
{
myDebug && console && console.log('triggerFunctionWithInjectedParams() ; func=', func);
- var callbackArgsNames = getArgumentNames(func);
+ var functionArgsNames = getArgumentNames(func);
var onInjectionsResolution = function (resolvedInjectionsValues) {
- func.apply(context, resolvedInjectionsValues);
+ if (!forceAsync) {
+ func.apply(context, resolvedInjectionsValues);
+ } else {
+ nextTick(function () { func.call(context, resolvedInjectionsValues); });
+ }
};
- this.resolveInjections(callbackArgsNames, onInjectionsResolution, context, forceAsync);
+ this.resolveInjections(functionArgsNames, onInjectionsResolution, this);
};
@@ -233,9 +370,8 @@
* @param {Array} injectionsNamesArray an Array of Strings
* @param {Function} callback Will be triggered when all injections are resolved ; it will receive an Array of resolved injections (with `null` for each arg whose name is not a registered injection name)
* @param {Object/null} [context=null]
- * @param {Boolean/null} [forceAsync=false]
*/
- Injector.prototype.resolveInjections = function (injectionsNamesArray, callback, context, forceAsync)
+ Injector.prototype.resolveInjections = function (injectionsNamesArray, callback, context)
{
myDebug && console && console.log('resolveInjections() ; injectionsNamesArray=', injectionsNamesArray);
var resolvedInjectionPoints = [];
@@ -258,10 +394,7 @@
var triggerCallback = function ()
{
myDebug && console && console.log('resolveInjections()#triggerCallback');
- if (!forceAsync)
- callback.call(context, resolvedInjectionPoints);
- else
- nextTick(function () { callback.call(context, resolvedInjectionPoints); });
+ callback.call(context, resolvedInjectionPoints);
};
var that = this;// :-/
@@ -275,7 +408,7 @@
if (0 === nbInjectionsPointsToResolve)
{
- // No arg for this callback ; immediate triggering!
+ // No arg for this callback ; immediate trigger!
triggerCallback();
return;
}
View
34 package.json
@@ -0,0 +1,34 @@
+{
+ "name": "medic-injector",
+ "description": "A lightweight Javascript Dependency Injection tool, strongly inspired by the great ActionScript3 RobotLegs & SwiftSuspenders frameworks",
+ "version": "0.0.1-WIP",
+ "homepage": "https://github.com/DrBenton/Medic-Injector-JS",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/DrBenton/Medic-Injector-JS.git"
+ },
+ "authors": [
+ "Dr. Benton (http://github.com/DrBenton)"
+ ],
+ "main": "medic-injector.js",
+ "directories": {
+ "test": "test"
+ },
+ "dependencies": {
+ },
+ "devDependencies": {
+ "mocha": "*"
+ },
+ "optionalDependencies": {
+ },
+ "scripts": {
+ "test": "mocha test/test.js"
+ },
+ "keywords": [
+ "dependency", "injection", "injector"
+ ],
+ "engines": {
+ "node": ">= 0.6"
+ },
+ "# vim: ts=2 sw=2 et ": null
+}
View
420 test/test.js
@@ -0,0 +1,420 @@
+
+var assert = require('assert');
+
+var InjectorLib = require('../medic-injector')
+ , Injector = InjectorLib.MedicInjector
+ , InjectionMapping = InjectorLib.MedicInjectionMapping;
+
+describe('InjectionMapping', function(){
+
+ describe('#toValue()', function(){
+ it('should immediately return the value', function(){
+ var injector = new Injector();
+ var counter = 0;
+ var injectionMapping = injector.addMapping('test').toValue(10);
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ assert.strictEqual(0, counter);//since this a value mapping without the use of the "forceAsync" mode, the callback should be triggered immediately
+ });
+ counter++;
+ });
+ it('should asynchronously return the value when forceAsync is used', function(done){
+ var injector = new Injector();
+ var counter = 0;
+ var injectionMapping = injector.addMapping('test').toValue(10);
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ assert.strictEqual(1, counter);
+ done();
+ }, null, true);//async mode is forced
+ counter++;
+ });
+ });// end "#toValue()" tests
+
+ describe('#toProvider()', function(){
+ it('should synchronously return the value when a sync callback result is used', function(done){
+ var injector = new Injector();
+ var counter = 0;
+ var injectionMapping = injector.addMapping('test').toProvider(function() {
+ counter++;
+ return 10;
+ });
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ assert.strictEqual(1, counter);
+ done();
+ });
+ counter++;
+ });
+ it('should asynchronously return the value when an async callback result is used', function(done){
+ var injector = new Injector();
+ var counter = 0;
+ var injectionMapping = injector.addMapping('test').toProvider(function(callback) {
+ counter++;
+ setTimeout(function () {
+ counter++;
+ callback(10);
+ }, 20);
+ });
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ assert.strictEqual(3, counter);
+ done();
+ });
+ counter++;
+ });
+ it('should received injected args too', function(done){
+ var injector = new Injector();
+ var counter = 0;
+ injector.addMapping('injection1').toValue(-10);
+ injector.addMapping('injection2').toValue(-20);
+ var injectionMapping = injector.addMapping('test').toProvider(function(injection1, dummy1, injection2, dummy2) {
+ counter++;
+ assert.strictEqual(-10, injection1);
+ assert.strictEqual(null, dummy1);
+ assert.strictEqual(-20, injection2);
+ assert.strictEqual(null, dummy2);
+ return 10;
+ });
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ assert.strictEqual(1, counter);
+ done();
+ });
+ counter++;
+ });
+ it('should received injected args, even in aysnc mode, with a randomly ordered "callback" arg', function(done){
+ var injector = new Injector();
+ var counter = 0;
+ injector.addMapping('injection1').toValue(-10);
+ injector.addMapping('injection2').toValue(-20);
+ var injectionMapping = injector.addMapping('test').toProvider(function(injection1, dummy1, callback, injection2, dummy2) {
+ counter++;
+ assert.strictEqual(-10, injection1);
+ assert.strictEqual(null, dummy1);
+ assert.strictEqual(-20, injection2);
+ assert.strictEqual(null, dummy2);
+ setTimeout(function () {
+ counter++;
+ callback(10);
+ }, 20);
+ });
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(injectionValue, 10);
+ assert.strictEqual(3, counter);
+ done();
+ });
+ counter++;
+ });
+ it('should trigger the Provider function multiple times for multiple Injections resolutions requests, even in sync mode', function(done){
+ var injector = new Injector();
+ var counter = 0;
+ var nbCallbacksTriggered = 0;
+ var injectionMapping = injector.addMapping('test').toProvider(function() {
+ counter++;
+ return 10;
+ });
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ assert.strictEqual(1, counter);
+ (++nbCallbacksTriggered === 2) && done();
+ });
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ assert.strictEqual(2, counter);
+ (++nbCallbacksTriggered === 2) && done();
+ });
+ counter++;
+ });
+ it('should trigger the Provider function multiple times for multiple Injections resolutions requests in async mode', function(done){
+ var injector = new Injector();
+ var counter = 0;
+ var nbCallbacksTriggered = 0;
+ var injectionMapping = injector.addMapping('test').toProvider(function(callback) {
+ counter++;
+ setTimeout(function () {
+ counter++;
+ callback(10);
+ }, 20);
+ });
+ var onAllInjectionsResolved = function ()
+ {
+ assert.strictEqual(5, counter);
+ done();
+ };
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ (++nbCallbacksTriggered === 2) && onAllInjectionsResolved();
+ });
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ (++nbCallbacksTriggered === 2) && onAllInjectionsResolved();
+ });
+ counter++;
+ });
+ });// end "#toProvider()" tests
+
+ describe('#asSingleton()', function(){
+ it('should normally immediately return the value when used with #toValue', function(){
+ var injector = new Injector();
+ var counter = 0;
+ var injectionMapping = injector.addMapping('test').toValue(10).asSingleton();
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ assert.strictEqual(0, counter);//since this a value mapping without the use of the "forceAsync" mode, the callback should be triggered immediately
+ });
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ assert.strictEqual(0, counter);
+ });
+ counter++;
+ });
+ it('should normally asynchronously return the value when forceAsync is used when used with #toValue', function(done){
+ var injector = new Injector();
+ var counter = 0;
+ var nbCallbacksTriggered = 0;
+ var injectionMapping = injector.addMapping('test').toValue(10).asSingleton();
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ assert.strictEqual(1, counter);
+ (++nbCallbacksTriggered === 2) && done();
+ }, null, true);//async mode is forced
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ assert.strictEqual(1, counter);
+ (++nbCallbacksTriggered === 2) && done();
+ }, null, true);//async mode is forced
+ counter++;
+ });
+ it('should synchronously return the *same* value with only one Provider trigger when a sync callback result is used multiple times', function(done){
+ var injector = new Injector();
+ var counter = 0;
+ var nbCallbacksTriggered = 0;
+ var injectionMapping = injector.addMapping('test')
+ .toProvider(function() {
+ counter++;
+ return 10;
+ })
+ .asSingleton();
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ assert.strictEqual(1, counter);
+ (++nbCallbacksTriggered === 2) && done();
+ });
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ assert.strictEqual(1, counter);//the counter stays at "1", because the Provider is triggered only once
+ (++nbCallbacksTriggered === 2) && done();
+ });
+ counter++;
+ });
+ it('should asynchronously return the *same* value with only one Provider trigger when an async callback result is used multiple times', function(done){
+ var injector = new Injector();
+ var counter = 0;
+ var nbCallbacksTriggered = 0;
+ var injectionMapping = injector.addMapping('test')
+ .toProvider(function(callback) {
+ counter++;
+ setTimeout(function () {
+ counter++;
+ callback(10);
+ }, 20);
+ })
+ .asSingleton();
+ var onAllInjectionsResolved = function ()
+ {
+ assert.strictEqual(3, counter);
+ done();
+ };
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ assert.strictEqual(3, counter);
+ (++nbCallbacksTriggered === 2) && onAllInjectionsResolved();
+ });
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(10, injectionValue);
+ assert.strictEqual(3, counter);//the counter stays at "3", because the Provider is triggered only once
+ (++nbCallbacksTriggered === 2) && onAllInjectionsResolved();
+ });
+ counter++;
+ });
+ it('should received injected args, even in aysnc mode, with a randomly ordered "callback" arg, with only one Provider trigger', function(done){
+ var injector = new Injector();
+ var counter = 0;
+ var nbCallbacksTriggered = 0;
+ injector.addMapping('injection1').toValue(-10);
+ injector.addMapping('injection2').toValue(-20);
+ var injectionMapping = injector.addMapping('test')
+ .toProvider(function(injection1, dummy1, callback, injection2, dummy2) {
+ counter++;
+ assert.strictEqual(-10, injection1);
+ assert.strictEqual(null, dummy1);
+ assert.strictEqual(-20, injection2);
+ assert.strictEqual(null, dummy2);
+ setTimeout(function () {
+ counter++;
+ callback(10);
+ }, 20);
+ })
+ .asSingleton();
+ var onAllInjectionsResolved = function ()
+ {
+ assert.strictEqual(3, counter);
+ done();
+ };
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(injectionValue, 10);
+ (++nbCallbacksTriggered === 2) && onAllInjectionsResolved();
+ });
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(injectionValue, 10);
+ (++nbCallbacksTriggered === 2) && onAllInjectionsResolved();
+ });
+ counter++;
+ });
+ })// end "#asSingleton()" tests
+
+ describe('#seal()/#unseal()', function(){
+ it('should seal', function(){
+ var injector = new Injector();
+ var injectionMapping = injector.addMapping('test').toValue(10);
+ injectionMapping.seal();
+ assert(injectionMapping.isSealed());
+ assert.throws(
+ function () {
+ injector.asSingleton();
+ },
+ Error
+ );
+ assert.throws(
+ function () {
+ injector.toValue(20);
+ },
+ Error
+ );
+ assert.throws(
+ function () {
+ injector.toProvider(function() {return 30;});
+ },
+ Error
+ );
+ injectionMapping.resolveInjection(function (injectionValue) {
+ assert.strictEqual(injectionValue, 10);
+ });
+ });
+ it('should not unseal with an invalid key', function(){
+ var injector = new Injector();
+ var injectionMapping = injector.addMapping('test').toValue(10);
+ injectionMapping.seal();
+ assert(injectionMapping.isSealed());
+ assert.throws(
+ function () {
+ injector.unseal(null);
+ },
+ Error
+ );
+ assert.throws(
+ function () {
+ injector.unseal({});
+ },
+ Error
+ );
+ });
+ it('should successfully unseal with the seal key', function(){
+ var injector = new Injector();
+ var injectionMapping = injector.addMapping('test').toValue(10);
+ var sealKey = injectionMapping.seal();
+ assert(injectionMapping.isSealed());
+ injectionMapping.unseal(sealKey);
+ assert(!injectionMapping.isSealed());
+ });
+
+ });// end "#seal()/#unseal()" tests
+
+});//end "InjectionMapping" tests
+
+describe('Injector', function(){
+
+ describe('#triggerFunctionWithInjectedParams()', function(){
+ it('should handle a given context successfully', function(){
+ var injector = new Injector();
+ injector.addMapping('injection1').toValue(10);
+ injector.addMapping('injection2').toProvider(function () {return 20;});
+ var targetFunctionContext = {
+ 'contextProp': -1
+ };
+ var targetFunction = function (injection1, injection2)
+ {
+ assert.strictEqual(10, injection1);
+ assert.strictEqual(20, injection2);
+ assert.strictEqual(-1, this.contextProp);
+ };
+ injector.triggerFunctionWithInjectedParams(targetFunction, targetFunctionContext);
+ });
+ it('should be able to resolve misc injected params', function(done){
+ // We already tested each of these InjectionsMappings, we can mix them from now on
+ var injector = new Injector();
+ injector.addMapping('injection1').toValue(10);
+ injector.addMapping('injection2').toProvider(function () {return 20;});
+ injector.addMapping('injection3').toProvider(function (callback) {
+ setTimeout(function () { callback(30); }, 20);
+ });
+ injector.addMapping('injection4').toProvider(function (callback) {
+ setTimeout(function () { callback(40); }, 20);
+ });
+ injector.addMapping('injection5').toProvider(function (callback, injection4) {
+ setTimeout(function () { callback(injection4+10); }, 20);
+ });
+ injector.addMapping('injection6').toProvider(function (callback, injection5) {//this provider is itself "injected"
+ setTimeout(function () { callback(injection5+10); }, 20);
+ }).asSingleton();
+ var targetFunctionContext = {
+ 'contextProp': -1
+ };
+ var targetFunction = function (injection2, injection1, injection7, injection6, injection3)
+ {
+ assert.strictEqual(10, injection1);
+ assert.strictEqual(20, injection2);
+ assert.strictEqual(30, injection3);
+ assert.strictEqual(60, injection6);
+ assert.strictEqual(null, injection7);
+ assert.strictEqual(-1, this.contextProp);
+ done();
+ };
+ injector.triggerFunctionWithInjectedParams(targetFunction, targetFunctionContext);
+ });
+ });// end "#triggerFunctionWithInjectedParams()" tests
+
+ describe('#removeMapping()', function(){
+ it('should remove a given mapping', function(){
+ var injector = new Injector();
+ assert(!injector.hasMapping('injection1'));
+ injector.addMapping('injection1').toValue(10);
+ assert(injector.hasMapping('injection1'));
+ injector.removeMapping('injection1');
+ assert(!injector.hasMapping('injection1'));
+ });
+ it('should not remove a sealed mapping', function(){
+ var injector = new Injector();
+ injector.addMapping('injection1').toValue(10).seal();
+ assert(injector.hasMapping('injection1'));
+ assert.throws(
+ function () {
+ injector.removeMapping('injection1');
+ },
+ Error
+ );
+ assert(injector.hasMapping('injection1'));
+ });
+ it('should remove an unsealed mapping', function(){
+ var injector = new Injector();
+ var injectionMapping = injector.addMapping('injection1').toValue(10);
+ var sealKey = injectionMapping.seal();
+ assert(injector.hasMapping('injection1'));
+ injectionMapping.unseal(sealKey);
+ injector.removeMapping('injection1');
+ assert(!injector.hasMapping('injection1'));
+ });
+ });// end "#removeMapping()" tests
+
+});//end "Injector" tests
View
112 tests/test.js
@@ -1,112 +0,0 @@
-
-//var assert = require(process.env._ + "/../../lib/node_modules/should");//TODO: shouldn't have to do this :-)
-var assert = require("assert");
-
-var InjectorLib = require('../medic-injector')
- , Injector = InjectorLib.MedicInjector
- , InjectionMapping = InjectorLib.MedicInjectionMapping;
-
-describe('InjectionMapping', function(){
-
- describe('#toValue()', function(){
- it('should immediately return the value', function(){
- var injector = new Injector();
- var counter = 0;
- var injectionMapping = injector.addMapping('test').toValue(10);
- injectionMapping.resolveInjection(function (injectionValue) {
- assert.strictEqual(10, injectionValue);
- assert.strictEqual(0, counter);//since this a value mapping without the use of the "forceAsync" mode, the callback should be triggered immediately
- });
- counter++;
- })
- it('should asynchronously return the value when forceAsync is used', function(done){
- var injector = new Injector();
- var counter = 0;
- var injectionMapping = injector.addMapping('test').toValue(10);
- injectionMapping.resolveInjection(function (injectionValue) {
- assert.strictEqual(10, injectionValue);
- assert.strictEqual(1, counter);
- done();
- }, null, true);//async mode is forced
- counter++;
- })
- })
-
- describe('#toFunctionResult()', function(){
- it('should synchronously return the value when a sync callback result is used', function(done){
- var injector = new Injector();
- var counter = 0;
- var injectionMapping = injector.addMapping('test').toFunctionResult(function() {
- counter++;
- return 10;
- })
- injectionMapping.resolveInjection(function (injectionValue) {
- assert.strictEqual(10, injectionValue);
- assert.strictEqual(1, counter);
- done();
- });
- counter++;
- })
- it('should asynchronously return the value when an async callback result is used', function(done){
- var injector = new Injector();
- var counter = 0;
- var injectionMapping = injector.addMapping('test').toFunctionResult(function(callback) {
- counter++;
- setTimeout(function () {
- counter++;
- callback(10);
- }, 200);
- })
- injectionMapping.resolveInjection(function (injectionValue) {
- assert.strictEqual(10, injectionValue);
- assert.strictEqual(3, counter);
- done();
- });
- counter++;
- })
- it('should received injected args too', function(done){
- var injector = new Injector();
- var counter = 0;
- injector.addMapping('injection1').toValue(-10);
- injector.addMapping('injection2').toValue(-20);
- var injectionMapping = injector.addMapping('test').toFunctionResult(function(injection1, dummy1, injection2, dummy2) {
- counter++;
- assert.strictEqual(-10, injection1);
- assert.strictEqual(null, dummy1);
- assert.strictEqual(-20, injection2);
- assert.strictEqual(null, dummy2);
- return 10;
- })
- injectionMapping.resolveInjection(function (injectionValue) {
- assert.strictEqual(10, injectionValue);
- assert.strictEqual(1, counter);
- done();
- });
- counter++;
- })
- it('should received injected args, even in aysnc mode, with a randomly ordered "callback" arg', function(done){
- var injector = new Injector();
- var counter = 0;
- injector.addMapping('injection1').toValue(-10);
- injector.addMapping('injection2').toValue(-20);
- var injectionMapping = injector.addMapping('test').toFunctionResult(function(injection1, dummy1, callback, injection2, dummy2) {
- counter++;
- assert.strictEqual(-10, injection1);
- assert.strictEqual(null, dummy1);
- assert.strictEqual(-20, injection2);
- assert.strictEqual(null, dummy2);
- setTimeout(function () {
- counter++;
- callback(10);
- }, 200);
- })
- injectionMapping.resolveInjection(function (injectionValue) {
- assert.strictEqual(injectionValue, 10);
- assert.strictEqual(3, counter);
- done();
- });
- counter++;
- })
- })
-
-})
Please sign in to comment.
Something went wrong with that request. Please try again.