9a9b07dea8
The quotes rule had to be disabled for e2e tests generated from ngdoc
because dgeni templates use double quotes as string delimiters.
Since we can't have guarantees that dgeni template wrappers will follow
the same JS code style the Angular 1 repo uses, we should find a way
to enforce our ESLint setup only for the parts in this repo, perhaps
via prepending a generated `/* eslint-enable OUR_RULES */` pragma.
(partially cherry-picked from 9360aa2d27)
Closes #15011
1165 lines
36 KiB
JavaScript
1165 lines
36 KiB
JavaScript
'use strict';
|
|
|
|
/* globals support: false */
|
|
|
|
describe('injector', function() {
|
|
var providers;
|
|
var injector;
|
|
var providerInjector;
|
|
var controllerProvider;
|
|
|
|
beforeEach(module(function($provide, $injector, $controllerProvider) {
|
|
providers = function(name, factory, annotations) {
|
|
$provide.factory(name, extend(factory, annotations || {}));
|
|
};
|
|
providerInjector = $injector;
|
|
controllerProvider = $controllerProvider;
|
|
}));
|
|
beforeEach(inject(function($injector) {
|
|
injector = $injector;
|
|
}));
|
|
|
|
|
|
it('should return same instance from calling provider', function() {
|
|
var instance = {},
|
|
original = instance;
|
|
providers('instance', function() { return instance; });
|
|
expect(injector.get('instance')).toEqual(instance);
|
|
instance = 'deleted';
|
|
expect(injector.get('instance')).toEqual(original);
|
|
});
|
|
|
|
|
|
it('should inject providers', function() {
|
|
providers('a', function() {return 'Mi';});
|
|
providers('b', function(mi) {return mi + 'sko';}, {$inject:['a']});
|
|
expect(injector.get('b')).toEqual('Misko');
|
|
});
|
|
|
|
|
|
it('should check its modulesToLoad argument', function() {
|
|
expect(function() { angular.injector('test'); })
|
|
.toThrowMinErr('ng', 'areq');
|
|
});
|
|
|
|
|
|
it('should resolve dependency graph and instantiate all services just once', function() {
|
|
var log = [];
|
|
|
|
// s1
|
|
// / | \
|
|
// / s2 \
|
|
// / / | \ \
|
|
// /s3 < s4 > s5
|
|
// //
|
|
// s6
|
|
|
|
|
|
providers('s1', function() { log.push('s1'); return {}; }, {$inject: ['s2', 's5', 's6']});
|
|
providers('s2', function() { log.push('s2'); return {}; }, {$inject: ['s3', 's4', 's5']});
|
|
providers('s3', function() { log.push('s3'); return {}; }, {$inject: ['s6']});
|
|
providers('s4', function() { log.push('s4'); return {}; }, {$inject: ['s3', 's5']});
|
|
providers('s5', function() { log.push('s5'); return {}; });
|
|
providers('s6', function() { log.push('s6'); return {}; });
|
|
|
|
injector.get('s1');
|
|
|
|
expect(log).toEqual(['s6', 's3', 's5', 's4', 's2', 's1']);
|
|
});
|
|
|
|
|
|
it('should allow query names', function() {
|
|
providers('abc', function() { return ''; });
|
|
|
|
expect(injector.has('abc')).toBe(true);
|
|
expect(injector.has('xyz')).toBe(false);
|
|
expect(injector.has('$injector')).toBe(true);
|
|
});
|
|
|
|
|
|
it('should provide useful message if no provider', function() {
|
|
expect(function() {
|
|
injector.get('idontexist');
|
|
}).toThrowMinErr('$injector', 'unpr', 'Unknown provider: idontexistProvider <- idontexist');
|
|
});
|
|
|
|
|
|
it('should provide the caller name if given', function() {
|
|
expect(function() {
|
|
injector.get('idontexist', 'callerName');
|
|
}).toThrowMinErr('$injector', 'unpr', 'Unknown provider: idontexistProvider <- idontexist <- callerName');
|
|
});
|
|
|
|
|
|
it('should provide the caller name for controllers', function() {
|
|
controllerProvider.register('myCtrl', function(idontexist) {});
|
|
var $controller = injector.get('$controller');
|
|
expect(function() {
|
|
$controller('myCtrl', {$scope: {}});
|
|
}).toThrowMinErr('$injector', 'unpr', 'Unknown provider: idontexistProvider <- idontexist <- myCtrl');
|
|
});
|
|
|
|
|
|
it('should not corrupt the cache when an object fails to get instantiated', function() {
|
|
expect(function() {
|
|
injector.get('idontexist');
|
|
}).toThrowMinErr('$injector', 'unpr', 'Unknown provider: idontexistProvider <- idontexist');
|
|
|
|
expect(function() {
|
|
injector.get('idontexist');
|
|
}).toThrowMinErr('$injector', 'unpr', 'Unknown provider: idontexistProvider <- idontexist');
|
|
});
|
|
|
|
|
|
it('should provide path to the missing provider', function() {
|
|
providers('a', function(idontexist) {return 1;});
|
|
providers('b', function(a) {return 2;});
|
|
expect(function() {
|
|
injector.get('b');
|
|
}).toThrowMinErr('$injector', 'unpr', 'Unknown provider: idontexistProvider <- idontexist <- a <- b');
|
|
});
|
|
|
|
|
|
it('should create a new $injector for the run phase', inject(function($injector) {
|
|
expect($injector).not.toBe(providerInjector);
|
|
}));
|
|
|
|
it('should have an false strictDi property', inject(function($injector) {
|
|
expect($injector.strictDi).toBe(false);
|
|
}));
|
|
|
|
|
|
describe('invoke', function() {
|
|
var args;
|
|
|
|
beforeEach(function() {
|
|
args = null;
|
|
providers('a', function() {return 1;});
|
|
providers('b', function() {return 2;});
|
|
});
|
|
|
|
|
|
function Fn(a, b, c, d) {
|
|
args = [this, a, b, c, d];
|
|
return a + b + c + d;
|
|
}
|
|
|
|
|
|
it('should call function', function() {
|
|
Fn.$inject = ['a', 'b', 'c', 'd'];
|
|
injector.invoke(Fn, {name:'this'}, {c:3, d:4});
|
|
expect(args).toEqual([{name:'this'}, 1, 2, 3, 4]);
|
|
});
|
|
|
|
|
|
it('should treat array as annotations', function() {
|
|
injector.invoke(['a', 'b', 'c', 'd', Fn], {name:'this'}, {c:3, d:4});
|
|
expect(args).toEqual([{name:'this'}, 1, 2, 3, 4]);
|
|
});
|
|
|
|
|
|
it('should invoke the passed-in fn with all of the dependencies as arguments', function() {
|
|
providers('c', function() {return 3;});
|
|
providers('d', function() {return 4;});
|
|
expect(injector.invoke(['a', 'b', 'c', 'd', Fn])).toEqual(10);
|
|
});
|
|
|
|
|
|
it('should fail with errors if not function or array', function() {
|
|
expect(function() {
|
|
injector.invoke({});
|
|
}).toThrowMinErr('ng', 'areq', 'Argument \'fn\' is not a function, got Object');
|
|
expect(function() {
|
|
injector.invoke(['a', 123], {});
|
|
}).toThrowMinErr('ng', 'areq', 'Argument \'fn\' is not a function, got number');
|
|
});
|
|
});
|
|
|
|
|
|
describe('annotation', function() {
|
|
/* global annotate: false */
|
|
it('should return $inject', function() {
|
|
function fn() {}
|
|
fn.$inject = ['a'];
|
|
expect(annotate(fn)).toBe(fn.$inject);
|
|
expect(annotate(function() {})).toEqual([]);
|
|
expect(annotate(function() {})).toEqual([]);
|
|
/* eslint-disable space-before-function-paren, no-multi-spaces */
|
|
expect(annotate(function () {})).toEqual([]);
|
|
expect(annotate(function /* */ () {})).toEqual([]);
|
|
/* eslint-enable */
|
|
});
|
|
|
|
|
|
it('should create $inject', function() {
|
|
var extraParans = angular.noop;
|
|
/* eslint-disable space-before-function-paren */
|
|
// keep the multi-line to make sure we can handle it
|
|
function $f_n0 /*
|
|
*/(
|
|
$a, // x, <-- looks like an arg but it is a comment
|
|
b_, /* z, <-- looks like an arg but it is a
|
|
multi-line comment
|
|
function(a, b) {}
|
|
*/
|
|
_c,
|
|
/* {some type} */ d) { extraParans(); }
|
|
/* eslint-enable */
|
|
expect(annotate($f_n0)).toEqual(['$a', 'b_', '_c', 'd']);
|
|
expect($f_n0.$inject).toEqual(['$a', 'b_', '_c', 'd']);
|
|
});
|
|
|
|
|
|
it('should strip leading and trailing underscores from arg name during inference', function() {
|
|
function beforeEachFn(_foo_) { /* foo = _foo_ */ }
|
|
expect(annotate(beforeEachFn)).toEqual(['foo']);
|
|
});
|
|
|
|
it('should not strip service names with a single underscore', function() {
|
|
function beforeEachFn(_) { /* _ = _ */ }
|
|
expect(annotate(beforeEachFn)).toEqual(['_']);
|
|
});
|
|
|
|
it('should handle no arg functions', function() {
|
|
function $f_n0() {}
|
|
expect(annotate($f_n0)).toEqual([]);
|
|
expect($f_n0.$inject).toEqual([]);
|
|
});
|
|
|
|
|
|
it('should handle no arg functions with spaces in the arguments list', function() {
|
|
function fn() {}
|
|
expect(annotate(fn)).toEqual([]);
|
|
expect(fn.$inject).toEqual([]);
|
|
});
|
|
|
|
|
|
it('should handle args with both $ and _', function() {
|
|
function $f_n0($a_) {}
|
|
expect(annotate($f_n0)).toEqual(['$a_']);
|
|
expect($f_n0.$inject).toEqual(['$a_']);
|
|
});
|
|
|
|
it('should handle functions with overridden toString', function() {
|
|
function fn(a) {}
|
|
fn.toString = function() { return 'fn'; };
|
|
expect(annotate(fn)).toEqual(['a']);
|
|
expect(fn.$inject).toEqual(['a']);
|
|
});
|
|
|
|
it('should throw on non function arg', function() {
|
|
expect(function() {
|
|
annotate({});
|
|
}).toThrow();
|
|
});
|
|
|
|
|
|
describe('es6', function() {
|
|
if (support.ES6Function) {
|
|
// The functions are generated using `eval` as just having the ES6 syntax can break some browsers.
|
|
it('should be possible to annotate functions that are declared using ES6 syntax', function() {
|
|
// eslint-disable-next-line no-eval
|
|
expect(annotate(eval('({ fn(x) { return; } })').fn)).toEqual(['x']);
|
|
});
|
|
}
|
|
|
|
|
|
if (support.fatArrow) {
|
|
it('should create $inject for arrow functions', function() {
|
|
// eslint-disable-next-line no-eval
|
|
expect(annotate(eval('(a, b) => a'))).toEqual(['a', 'b']);
|
|
});
|
|
}
|
|
|
|
|
|
if (support.fatArrow) {
|
|
it('should create $inject for arrow functions with no parenthesis', function() {
|
|
// eslint-disable-next-line no-eval
|
|
expect(annotate(eval('a => a'))).toEqual(['a']);
|
|
});
|
|
}
|
|
|
|
|
|
if (support.fatArrow) {
|
|
it('should take args before first arrow', function() {
|
|
// eslint-disable-next-line no-eval
|
|
expect(annotate(eval('a => b => b'))).toEqual(['a']);
|
|
});
|
|
|
|
// Support: Chrome 50-51 only
|
|
// TODO (gkalpak): Remove when Chrome v52 is released.
|
|
// it('should be able to inject fat-arrow function', function() {
|
|
// inject(($injector) => {
|
|
// expect($injector).toBeDefined();
|
|
// });
|
|
// });
|
|
}
|
|
|
|
if (support.classes) {
|
|
it('should be possible to instantiate ES6 classes', function() {
|
|
providers('a', function() { return 'a-value'; });
|
|
// eslint-disable-next-line no-eval
|
|
var Clazz = eval('(class { constructor(a) { this.a = a; } aVal() { return this.a; } })');
|
|
var instance = injector.instantiate(Clazz);
|
|
expect(instance).toEqual(new Clazz('a-value'));
|
|
expect(instance.aVal()).toEqual('a-value');
|
|
});
|
|
|
|
if (/chrome/.test(window.navigator.userAgent)) {
|
|
they('should detect ES6 classes regardless of whitespace/comments ($prop)', [
|
|
'class Test {}',
|
|
'class Test{}',
|
|
'class //<--ES6 stuff\nTest {}',
|
|
'class//<--ES6 stuff\nTest {}',
|
|
'class {}',
|
|
'class{}',
|
|
'class //<--ES6 stuff\n {}',
|
|
'class//<--ES6 stuff\n {}',
|
|
'class/* Test */{}',
|
|
'class /* Test */ {}'
|
|
], function(classDefinition) {
|
|
// eslint-disable-next-line no-eval
|
|
var Clazz = eval('(' + classDefinition + ')');
|
|
var instance = injector.invoke(Clazz);
|
|
|
|
expect(instance).toEqual(jasmine.any(Clazz));
|
|
});
|
|
}
|
|
|
|
// Support: Chrome 50-51 only
|
|
// TODO (gkalpak): Remove when Chrome v52 is released.
|
|
// it('should be able to invoke classes', function() {
|
|
// class Test {
|
|
// constructor($injector) {
|
|
// this.$injector = $injector;
|
|
// }
|
|
// }
|
|
// var instance = injector.invoke(Test, null, null, 'Test');
|
|
|
|
// expect(instance.$injector).toBe(injector);
|
|
// });
|
|
}
|
|
});
|
|
|
|
|
|
it('should publish annotate API', function() {
|
|
expect(angular.mock.$$annotate).toBe(annotate);
|
|
spyOn(angular.mock, '$$annotate').and.callThrough();
|
|
function fn() {}
|
|
injector.annotate(fn);
|
|
expect(angular.mock.$$annotate).toHaveBeenCalledWith(fn);
|
|
});
|
|
});
|
|
|
|
|
|
it('should have $injector', function() {
|
|
var $injector = createInjector();
|
|
expect($injector.get('$injector')).toBe($injector);
|
|
});
|
|
|
|
|
|
it('should define module', function() {
|
|
var log = '';
|
|
var injector = createInjector([function($provide) {
|
|
$provide.value('value', 'value;');
|
|
$provide.factory('fn', valueFn('function;'));
|
|
$provide.provider('service', function Provider() {
|
|
this.$get = valueFn('service;');
|
|
});
|
|
}, function(valueProvider, fnProvider, serviceProvider) {
|
|
log += valueProvider.$get() + fnProvider.$get() + serviceProvider.$get();
|
|
}]).invoke(function(value, fn, service) {
|
|
log += '->' + value + fn + service;
|
|
});
|
|
expect(log).toEqual('value;function;service;->value;function;service;');
|
|
});
|
|
|
|
|
|
describe('module', function() {
|
|
it('should provide $injector even when no module is requested', function() {
|
|
var $provide,
|
|
$injector = createInjector([
|
|
angular.extend(function(p) { $provide = p; }, {$inject: ['$provide']})
|
|
]);
|
|
expect($injector.get('$injector')).toBe($injector);
|
|
});
|
|
|
|
|
|
it('should load multiple function modules and infer inject them', function() {
|
|
var a = 'junk';
|
|
var $injector = createInjector([
|
|
function() {
|
|
a = 'A'; // reset to prove we ran
|
|
},
|
|
function($provide) {
|
|
$provide.value('a', a);
|
|
},
|
|
angular.extend(function(p, serviceA) {
|
|
p.value('b', serviceA.$get() + 'B');
|
|
}, {$inject:['$provide', 'aProvider']}),
|
|
['$provide', 'bProvider', function(p, serviceB) {
|
|
p.value('c', serviceB.$get() + 'C');
|
|
}]
|
|
]);
|
|
expect($injector.get('a')).toEqual('A');
|
|
expect($injector.get('b')).toEqual('AB');
|
|
expect($injector.get('c')).toEqual('ABC');
|
|
});
|
|
|
|
|
|
it('should run symbolic modules', function() {
|
|
angularModule('myModule', []).value('a', 'abc');
|
|
var $injector = createInjector(['myModule']);
|
|
expect($injector.get('a')).toEqual('abc');
|
|
});
|
|
|
|
|
|
it('should error on invalid module name', function() {
|
|
expect(function() {
|
|
createInjector(['IDontExist'], {});
|
|
}).toThrowMinErr('$injector', 'modulerr',
|
|
/\[\$injector:nomod\] Module 'IDontExist' is not available! You either misspelled the module name or forgot to load it/);
|
|
});
|
|
|
|
|
|
it('should load dependant modules only once', function() {
|
|
var log = '';
|
|
angular.module('a', [], function() { log += 'a'; });
|
|
angular.module('b', ['a'], function() { log += 'b'; });
|
|
angular.module('c', ['a', 'b'], function() { log += 'c'; });
|
|
createInjector(['c', 'c']);
|
|
expect(log).toEqual('abc');
|
|
});
|
|
|
|
it('should load different instances of dependent functions', function() {
|
|
function generateValueModule(name, value) {
|
|
return function($provide) {
|
|
$provide.value(name, value);
|
|
};
|
|
}
|
|
var injector = createInjector([generateValueModule('name1', 'value1'),
|
|
generateValueModule('name2', 'value2')]);
|
|
expect(injector.get('name2')).toBe('value2');
|
|
});
|
|
|
|
it('should load same instance of dependent function only once', function() {
|
|
var count = 0;
|
|
function valueModule($provide) {
|
|
count++;
|
|
$provide.value('name', 'value');
|
|
}
|
|
|
|
var injector = createInjector([valueModule, valueModule]);
|
|
expect(injector.get('name')).toBe('value');
|
|
expect(count).toBe(1);
|
|
});
|
|
|
|
it('should execute runBlocks after injector creation', function() {
|
|
var log = '';
|
|
angular.module('a', [], function() { log += 'a'; }).run(function() { log += 'A'; });
|
|
angular.module('b', ['a'], function() { log += 'b'; }).run(function() { log += 'B'; });
|
|
createInjector([
|
|
'b',
|
|
valueFn(function() { log += 'C'; }),
|
|
[valueFn(function() { log += 'D'; })]
|
|
]);
|
|
expect(log).toEqual('abABCD');
|
|
});
|
|
|
|
it('should execute own config blocks after all own providers are invoked', function() {
|
|
var log = '';
|
|
angular.module('a', ['b'])
|
|
.config(function($aProvider) {
|
|
log += 'aConfig;';
|
|
})
|
|
.provider('$a', function Provider$a() {
|
|
log += '$aProvider;';
|
|
this.$get = function() {};
|
|
});
|
|
angular.module('b', [])
|
|
.config(function($bProvider) {
|
|
log += 'bConfig;';
|
|
})
|
|
.provider('$b', function Provider$b() {
|
|
log += '$bProvider;';
|
|
this.$get = function() {};
|
|
});
|
|
|
|
createInjector(['a']);
|
|
expect(log).toBe('$bProvider;bConfig;$aProvider;aConfig;');
|
|
});
|
|
|
|
describe('$provide', function() {
|
|
|
|
it('should throw an exception if we try to register a service called "hasOwnProperty"', function() {
|
|
createInjector([function($provide) {
|
|
expect(function() {
|
|
$provide.provider('hasOwnProperty', function() { });
|
|
}).toThrowMinErr('ng', 'badname');
|
|
}]);
|
|
});
|
|
|
|
it('should throw an exception if we try to register a constant called "hasOwnProperty"', function() {
|
|
createInjector([function($provide) {
|
|
expect(function() {
|
|
$provide.constant('hasOwnProperty', {});
|
|
}).toThrowMinErr('ng', 'badname');
|
|
}]);
|
|
});
|
|
|
|
|
|
describe('constant', function() {
|
|
it('should create configuration injectable constants', function() {
|
|
var log = [];
|
|
createInjector([
|
|
function($provide) {
|
|
$provide.constant('abc', 123);
|
|
$provide.constant({a: 'A', b:'B'});
|
|
return function(a) {
|
|
log.push(a);
|
|
};
|
|
},
|
|
function(abc) {
|
|
log.push(abc);
|
|
return function(b) {
|
|
log.push(b);
|
|
};
|
|
}
|
|
]).get('abc');
|
|
expect(log).toEqual([123, 'A', 'B']);
|
|
});
|
|
});
|
|
|
|
|
|
describe('value', function() {
|
|
it('should configure $provide values', function() {
|
|
expect(createInjector([function($provide) {
|
|
$provide.value('value', 'abc');
|
|
}]).get('value')).toEqual('abc');
|
|
});
|
|
|
|
|
|
it('should configure a set of values', function() {
|
|
expect(createInjector([function($provide) {
|
|
$provide.value({value: Array});
|
|
}]).get('value')).toEqual(Array);
|
|
});
|
|
});
|
|
|
|
|
|
describe('factory', function() {
|
|
it('should configure $provide factory function', function() {
|
|
expect(createInjector([function($provide) {
|
|
$provide.factory('value', valueFn('abc'));
|
|
}]).get('value')).toEqual('abc');
|
|
});
|
|
|
|
|
|
it('should configure a set of factories', function() {
|
|
expect(createInjector([function($provide) {
|
|
$provide.factory({value: Array});
|
|
}]).get('value')).toEqual([]);
|
|
});
|
|
});
|
|
|
|
|
|
describe('service', function() {
|
|
it('should register a class', function() {
|
|
function Type(value) {
|
|
this.value = value;
|
|
}
|
|
|
|
var instance = createInjector([function($provide) {
|
|
$provide.value('value', 123);
|
|
$provide.service('foo', Type);
|
|
}]).get('foo');
|
|
|
|
expect(instance instanceof Type).toBe(true);
|
|
expect(instance.value).toBe(123);
|
|
});
|
|
|
|
|
|
it('should register a set of classes', function() {
|
|
var Type = function() {};
|
|
|
|
var injector = createInjector([function($provide) {
|
|
$provide.service({
|
|
foo: Type,
|
|
bar: Type
|
|
});
|
|
}]);
|
|
|
|
expect(injector.get('foo') instanceof Type).toBe(true);
|
|
expect(injector.get('bar') instanceof Type).toBe(true);
|
|
});
|
|
});
|
|
|
|
|
|
describe('provider', function() {
|
|
it('should configure $provide provider object', function() {
|
|
expect(createInjector([function($provide) {
|
|
$provide.provider('value', {
|
|
$get: valueFn('abc')
|
|
});
|
|
}]).get('value')).toEqual('abc');
|
|
});
|
|
|
|
|
|
it('should configure $provide provider type', function() {
|
|
function Type() {}
|
|
Type.prototype.$get = function() {
|
|
expect(this instanceof Type).toBe(true);
|
|
return 'abc';
|
|
};
|
|
expect(createInjector([function($provide) {
|
|
$provide.provider('value', Type);
|
|
}]).get('value')).toEqual('abc');
|
|
});
|
|
|
|
|
|
it('should configure $provide using an array', function() {
|
|
function Type(PREFIX) {
|
|
this.prefix = PREFIX;
|
|
}
|
|
Type.prototype.$get = function() {
|
|
return this.prefix + 'def';
|
|
};
|
|
expect(createInjector([function($provide) {
|
|
$provide.constant('PREFIX', 'abc');
|
|
$provide.provider('value', ['PREFIX', Type]);
|
|
}]).get('value')).toEqual('abcdef');
|
|
});
|
|
|
|
|
|
it('should configure a set of providers', function() {
|
|
expect(createInjector([function($provide) {
|
|
$provide.provider({value: valueFn({$get:Array})});
|
|
}]).get('value')).toEqual([]);
|
|
});
|
|
});
|
|
|
|
|
|
describe('decorator', function() {
|
|
var log, injector;
|
|
|
|
beforeEach(function() {
|
|
log = [];
|
|
});
|
|
|
|
|
|
it('should be called with the original instance', function() {
|
|
injector = createInjector([function($provide) {
|
|
$provide.value('myService', function(val) {
|
|
log.push('myService:' + val);
|
|
return 'origReturn';
|
|
});
|
|
|
|
$provide.decorator('myService', function($delegate) {
|
|
return function(val) {
|
|
log.push('myDecoratedService:' + val);
|
|
var origVal = $delegate('decInput');
|
|
return 'dec+' + origVal;
|
|
};
|
|
});
|
|
}]);
|
|
|
|
var out = injector.get('myService')('input');
|
|
log.push(out);
|
|
expect(log.join('; ')).
|
|
toBe('myDecoratedService:input; myService:decInput; dec+origReturn');
|
|
});
|
|
|
|
|
|
it('should allow multiple decorators to be applied to a service', function() {
|
|
injector = createInjector([function($provide) {
|
|
$provide.value('myService', function(val) {
|
|
log.push('myService:' + val);
|
|
return 'origReturn';
|
|
});
|
|
|
|
$provide.decorator('myService', function($delegate) {
|
|
return function(val) {
|
|
log.push('myDecoratedService1:' + val);
|
|
var origVal = $delegate('decInput1');
|
|
return 'dec1+' + origVal;
|
|
};
|
|
});
|
|
|
|
$provide.decorator('myService', function($delegate) {
|
|
return function(val) {
|
|
log.push('myDecoratedService2:' + val);
|
|
var origVal = $delegate('decInput2');
|
|
return 'dec2+' + origVal;
|
|
};
|
|
});
|
|
}]);
|
|
|
|
var out = injector.get('myService')('input');
|
|
log.push(out);
|
|
expect(log).toEqual(['myDecoratedService2:input',
|
|
'myDecoratedService1:decInput2',
|
|
'myService:decInput1',
|
|
'dec2+dec1+origReturn']);
|
|
});
|
|
|
|
|
|
it('should decorate services with dependencies', function() {
|
|
injector = createInjector([function($provide) {
|
|
$provide.value('dep1', 'dependency1');
|
|
|
|
$provide.factory('myService', ['dep1', function(dep1) {
|
|
return function(val) {
|
|
log.push('myService:' + val + ',' + dep1);
|
|
return 'origReturn';
|
|
};
|
|
}]);
|
|
|
|
$provide.decorator('myService', function($delegate) {
|
|
return function(val) {
|
|
log.push('myDecoratedService:' + val);
|
|
var origVal = $delegate('decInput');
|
|
return 'dec+' + origVal;
|
|
};
|
|
});
|
|
}]);
|
|
|
|
var out = injector.get('myService')('input');
|
|
log.push(out);
|
|
expect(log.join('; ')).
|
|
toBe('myDecoratedService:input; myService:decInput,dependency1; dec+origReturn');
|
|
});
|
|
|
|
|
|
it('should allow for decorators to be injectable', function() {
|
|
injector = createInjector([function($provide) {
|
|
$provide.value('dep1', 'dependency1');
|
|
|
|
$provide.factory('myService', function() {
|
|
return function(val) {
|
|
log.push('myService:' + val);
|
|
return 'origReturn';
|
|
};
|
|
});
|
|
|
|
$provide.decorator('myService', function($delegate, dep1) {
|
|
return function(val) {
|
|
log.push('myDecoratedService:' + val + ',' + dep1);
|
|
var origVal = $delegate('decInput');
|
|
return 'dec+' + origVal;
|
|
};
|
|
});
|
|
}]);
|
|
|
|
var out = injector.get('myService')('input');
|
|
log.push(out);
|
|
expect(log.join('; ')).
|
|
toBe('myDecoratedService:input,dependency1; myService:decInput; dec+origReturn');
|
|
});
|
|
|
|
|
|
it('should allow for decorators to $injector', function() {
|
|
injector = createInjector(['ng', function($provide) {
|
|
$provide.decorator('$injector', function($delegate) {
|
|
return extend({}, $delegate, {get: function(val) {
|
|
if (val === 'key') {
|
|
return 'value';
|
|
}
|
|
return $delegate.get(val);
|
|
}});
|
|
});
|
|
}]);
|
|
|
|
expect(injector.get('key')).toBe('value');
|
|
expect(injector.get('$http')).not.toBeUndefined();
|
|
});
|
|
});
|
|
});
|
|
|
|
|
|
describe('error handling', function() {
|
|
it('should handle wrong argument type', function() {
|
|
expect(function() {
|
|
createInjector([
|
|
{}
|
|
], {});
|
|
}).toThrowMinErr('$injector', 'modulerr', /Failed to instantiate module \{\} due to:\n.*\[ng:areq\] Argument 'module' is not a function, got Object/);
|
|
});
|
|
|
|
|
|
it('should handle exceptions', function() {
|
|
expect(function() {
|
|
createInjector([function() {
|
|
throw new Error('MyError');
|
|
}], {});
|
|
}).toThrowMinErr('$injector', 'modulerr', /Failed to instantiate module .+ due to:\n.*MyError/);
|
|
});
|
|
|
|
|
|
it('should decorate the missing service error with module name', function() {
|
|
angular.module('TestModule', [], function(xyzzy) {});
|
|
expect(function() {
|
|
createInjector(['TestModule']);
|
|
}).toThrowMinErr(
|
|
'$injector', 'modulerr', /Failed to instantiate module TestModule due to:\n.*\[\$injector:unpr] Unknown provider: xyzzy/
|
|
);
|
|
});
|
|
|
|
|
|
it('should decorate the missing service error with module function', function() {
|
|
function myModule(xyzzy) {}
|
|
expect(function() {
|
|
createInjector([myModule]);
|
|
}).toThrowMinErr(
|
|
'$injector', 'modulerr', /Failed to instantiate module function myModule\(xyzzy\) due to:\n.*\[\$injector:unpr] Unknown provider: xyzzy/
|
|
);
|
|
});
|
|
|
|
|
|
it('should decorate the missing service error with module array function', function() {
|
|
function myModule(xyzzy) {}
|
|
expect(function() {
|
|
createInjector([['xyzzy', myModule]]);
|
|
}).toThrowMinErr(
|
|
'$injector', 'modulerr', /Failed to instantiate module function myModule\(xyzzy\) due to:\n.*\[\$injector:unpr] Unknown provider: xyzzy/
|
|
);
|
|
});
|
|
|
|
|
|
it('should throw error when trying to inject oneself', function() {
|
|
expect(function() {
|
|
createInjector([function($provide) {
|
|
$provide.factory('service', function(service) {});
|
|
return function(service) {};
|
|
}]);
|
|
}).toThrowMinErr('$injector', 'cdep', 'Circular dependency found: service <- service');
|
|
});
|
|
|
|
|
|
it('should throw error when trying to inject circular dependency', function() {
|
|
expect(function() {
|
|
createInjector([function($provide) {
|
|
$provide.factory('a', function(b) {});
|
|
$provide.factory('b', function(a) {});
|
|
return function(a) {};
|
|
}]);
|
|
}).toThrowMinErr('$injector', 'cdep', 'Circular dependency found: a <- b <- a');
|
|
});
|
|
|
|
});
|
|
});
|
|
|
|
|
|
describe('retrieval', function() {
|
|
var instance = {name:'angular'};
|
|
function Instance() { this.name = 'angular'; }
|
|
|
|
function createInjectorWithValue(instanceName, instance) {
|
|
return createInjector([['$provide', function(provide) {
|
|
provide.value(instanceName, instance);
|
|
}]]);
|
|
}
|
|
function createInjectorWithFactory(serviceName, serviceDef) {
|
|
return createInjector([['$provide', function(provide) {
|
|
provide.factory(serviceName, serviceDef);
|
|
}]]);
|
|
}
|
|
|
|
|
|
it('should retrieve by name', function() {
|
|
var $injector = createInjectorWithValue('instance', instance);
|
|
var retrievedInstance = $injector.get('instance');
|
|
expect(retrievedInstance).toBe(instance);
|
|
});
|
|
|
|
|
|
it('should cache instance', function() {
|
|
var $injector = createInjectorWithFactory('instance', function() { return new Instance(); });
|
|
var instance = $injector.get('instance');
|
|
expect($injector.get('instance')).toBe(instance);
|
|
expect($injector.get('instance')).toBe(instance);
|
|
});
|
|
|
|
|
|
it('should call functions and infer arguments', function() {
|
|
var $injector = createInjectorWithValue('instance', instance);
|
|
expect($injector.invoke(function(instance) { return instance; })).toBe(instance);
|
|
});
|
|
|
|
});
|
|
|
|
|
|
describe('method invoking', function() {
|
|
var $injector;
|
|
|
|
beforeEach(function() {
|
|
$injector = createInjector([function($provide) {
|
|
$provide.value('book', 'moby');
|
|
$provide.value('author', 'melville');
|
|
}]);
|
|
});
|
|
|
|
|
|
it('should invoke method', function() {
|
|
expect($injector.invoke(function(book, author) {
|
|
return author + ':' + book;
|
|
})).toEqual('melville:moby');
|
|
expect($injector.invoke(function(book, author) {
|
|
expect(this).toEqual($injector);
|
|
return author + ':' + book;
|
|
}, $injector)).toEqual('melville:moby');
|
|
});
|
|
|
|
|
|
it('should invoke method with locals', function() {
|
|
expect($injector.invoke(function(book, author) {
|
|
return author + ':' + book;
|
|
})).toEqual('melville:moby');
|
|
expect($injector.invoke(
|
|
function(book, author, chapter) {
|
|
expect(this).toEqual($injector);
|
|
return author + ':' + book + '-' + chapter;
|
|
}, $injector, {author:'m', chapter:'ch1'})).toEqual('m:moby-ch1');
|
|
});
|
|
|
|
|
|
it('should invoke method which is annotated', function() {
|
|
expect($injector.invoke(extend(function(b, a) {
|
|
return a + ':' + b;
|
|
}, {$inject:['book', 'author']}))).toEqual('melville:moby');
|
|
expect($injector.invoke(extend(function(b, a) {
|
|
expect(this).toEqual($injector);
|
|
return a + ':' + b;
|
|
}, {$inject:['book', 'author']}), $injector)).toEqual('melville:moby');
|
|
});
|
|
|
|
|
|
it('should invoke method which is an array of annotation', function() {
|
|
expect($injector.invoke(function(book, author) {
|
|
return author + ':' + book;
|
|
})).toEqual('melville:moby');
|
|
expect($injector.invoke(function(book, author) {
|
|
expect(this).toEqual($injector);
|
|
return author + ':' + book;
|
|
}, $injector)).toEqual('melville:moby');
|
|
});
|
|
|
|
|
|
it('should throw usefull error on wrong argument type]', function() {
|
|
expect(function() {
|
|
$injector.invoke({});
|
|
}).toThrowMinErr('ng', 'areq', 'Argument \'fn\' is not a function, got Object');
|
|
});
|
|
});
|
|
|
|
|
|
describe('service instantiation', function() {
|
|
var $injector;
|
|
|
|
beforeEach(function() {
|
|
$injector = createInjector([function($provide) {
|
|
$provide.value('book', 'moby');
|
|
$provide.value('author', 'melville');
|
|
}]);
|
|
});
|
|
|
|
|
|
function Type(book, author) {
|
|
this.book = book;
|
|
this.author = author;
|
|
}
|
|
Type.prototype.title = function() {
|
|
return this.author + ': ' + this.book;
|
|
};
|
|
|
|
|
|
it('should instantiate object and preserve constructor property and be instanceof', function() {
|
|
var t = $injector.instantiate(Type);
|
|
expect(t.book).toEqual('moby');
|
|
expect(t.author).toEqual('melville');
|
|
expect(t.title()).toEqual('melville: moby');
|
|
expect(t instanceof Type).toBe(true);
|
|
});
|
|
|
|
|
|
it('should instantiate object and preserve constructor property and be instanceof ' +
|
|
'with the array annotated type', function() {
|
|
var t = $injector.instantiate(['book', 'author', Type]);
|
|
expect(t.book).toEqual('moby');
|
|
expect(t.author).toEqual('melville');
|
|
expect(t.title()).toEqual('melville: moby');
|
|
expect(t instanceof Type).toBe(true);
|
|
});
|
|
|
|
|
|
it('should allow constructor to return different object', function() {
|
|
var obj = {};
|
|
var Class = function() {
|
|
return obj;
|
|
};
|
|
|
|
expect($injector.instantiate(Class)).toBe(obj);
|
|
});
|
|
|
|
|
|
it('should allow constructor to return a function', function() {
|
|
var fn = function() {};
|
|
var Class = function() {
|
|
return fn;
|
|
};
|
|
|
|
expect($injector.instantiate(Class)).toBe(fn);
|
|
});
|
|
|
|
|
|
it('should handle constructor exception', function() {
|
|
expect(function() {
|
|
$injector.instantiate(function() { throw 'MyError'; });
|
|
}).toThrow('MyError');
|
|
});
|
|
|
|
|
|
it('should return instance if constructor returns non-object value', function() {
|
|
var A = function() {
|
|
return 10;
|
|
};
|
|
|
|
var B = function() {
|
|
return 'some-string';
|
|
};
|
|
|
|
var C = function() {
|
|
return undefined;
|
|
};
|
|
|
|
expect($injector.instantiate(A) instanceof A).toBe(true);
|
|
expect($injector.instantiate(B) instanceof B).toBe(true);
|
|
expect($injector.instantiate(C) instanceof C).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('protection modes', function() {
|
|
it('should prevent provider lookup in app', function() {
|
|
var $injector = createInjector([function($provide) {
|
|
$provide.value('name', 'angular');
|
|
}]);
|
|
expect(function() {
|
|
$injector.get('nameProvider');
|
|
}).toThrowMinErr('$injector', 'unpr', 'Unknown provider: nameProviderProvider <- nameProvider');
|
|
});
|
|
|
|
|
|
it('should prevent provider configuration in app', function() {
|
|
var $injector = createInjector([]);
|
|
expect(function() {
|
|
$injector.get('$provide').value('a', 'b');
|
|
}).toThrowMinErr('$injector', 'unpr', 'Unknown provider: $provideProvider <- $provide');
|
|
});
|
|
|
|
|
|
it('should prevent instance lookup in module', function() {
|
|
function instanceLookupInModule(name) { throw new Error('FAIL'); }
|
|
expect(function() {
|
|
createInjector([function($provide) {
|
|
$provide.value('name', 'angular');
|
|
}, instanceLookupInModule]);
|
|
}).toThrowError(/\[\$injector:unpr] Unknown provider: name/);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('strict-di injector', function() {
|
|
beforeEach(inject.strictDi(true));
|
|
|
|
describe('with ngMock', function() {
|
|
it('should not throw when calling mock.module() with "magic" annotations', function() {
|
|
expect(function() {
|
|
module(function($provide, $httpProvider, $compileProvider) {
|
|
// Don't throw!
|
|
});
|
|
}).not.toThrow();
|
|
});
|
|
|
|
|
|
it('should not throw when calling mock.inject() with "magic" annotations', function() {
|
|
expect(function() {
|
|
inject(function($rootScope, $compile, $http) {
|
|
// Don't throw!
|
|
});
|
|
}).not.toThrow();
|
|
});
|
|
});
|
|
|
|
|
|
it('should throw if magic annotation is used by service', function() {
|
|
module(function($provide) {
|
|
$provide.service({
|
|
'$test': function() { return this; },
|
|
'$test2': function($test) { return this; }
|
|
});
|
|
});
|
|
inject(function($injector) {
|
|
expect(function() {
|
|
$injector.invoke(function($test2) {});
|
|
}).toThrowMinErr('$injector', 'strictdi');
|
|
});
|
|
});
|
|
|
|
|
|
it('should throw if magic annotation is used by provider', function() {
|
|
module(function($provide) {
|
|
$provide.provider({
|
|
'$test': function() { this.$get = function($rootScope) { return $rootScope; }; }
|
|
});
|
|
});
|
|
inject(function($injector) {
|
|
expect(function() {
|
|
$injector.invoke(['$test', function($test) {}]);
|
|
}).toThrowMinErr('$injector', 'strictdi');
|
|
});
|
|
});
|
|
|
|
|
|
it('should throw if magic annotation is used by factory', function() {
|
|
module(function($provide) {
|
|
$provide.factory({
|
|
'$test': function($rootScope) { return function() {}; }
|
|
});
|
|
});
|
|
inject(function($injector) {
|
|
expect(function() {
|
|
$injector.invoke(['$test', function(test) {}]);
|
|
}).toThrowMinErr('$injector', 'strictdi');
|
|
});
|
|
});
|
|
|
|
|
|
it('should throw if factory does not return a value', function() {
|
|
module(function($provide) {
|
|
$provide.factory('$test', function() {});
|
|
});
|
|
expect(function() {
|
|
inject(function($test) {});
|
|
}).toThrowMinErr('$injector', 'undef');
|
|
});
|
|
|
|
|
|
it('should always use provider as `this` when invoking a factory', function() {
|
|
var called = false;
|
|
|
|
function factoryFn() {
|
|
called = true;
|
|
expect(typeof this.$get).toBe('function');
|
|
return this;
|
|
}
|
|
module(function($provide) {
|
|
$provide.factory('$test', factoryFn);
|
|
});
|
|
inject(function($test) {});
|
|
expect(called).toBe(true);
|
|
});
|
|
|
|
it('should set strictDi property to true on the injector instance', inject(function($injector) {
|
|
expect($injector.strictDi).toBe(true);
|
|
}));
|
|
});
|