From e9e71267ea1d193854a2c468c197d0312e577cf4 Mon Sep 17 00:00:00 2001 From: Shahar Talmi Date: Sun, 8 Jun 2014 21:31:17 +0300 Subject: [PATCH] fix(injector): allow multiple loading of function modules Change HashMap to give $$hashKey also for functions so it will be possible to load multiple module function instances. In order to prevent problem in angular's test suite, added an option to HashMap to maintain its own id counter and added cleanup of $$hashKey from all module functions after each test. --- src/apis.js | 20 +++++++++++++------- src/auto/injector.js | 2 +- src/ngMock/angular-mocks.js | 6 ++++++ test/ApiSpecs.js | 30 ++++++++++++++++++++++++++++++ test/auto/injectorSpec.js | 23 +++++++++++++++++++++++ test/ngMock/angular-mocksSpec.js | 17 +++++++++++++++++ 6 files changed, 90 insertions(+), 8 deletions(-) diff --git a/src/apis.js b/src/apis.js index a6df5811c87d..3b0a22ab3191 100644 --- a/src/apis.js +++ b/src/apis.js @@ -13,16 +13,16 @@ * @returns {string} hash string such that the same input will have the same hash string. * The resulting string key is in 'type:hashKey' format. */ -function hashKey(obj) { +function hashKey(obj, nextUidFn) { var objType = typeof obj, key; - if (objType == 'object' && obj !== null) { + if (objType == 'function' || (objType == 'object' && obj !== null)) { if (typeof (key = obj.$$hashKey) == 'function') { // must invoke on object to keep the right this key = obj.$$hashKey(); } else if (key === undefined) { - key = obj.$$hashKey = nextUid(); + key = obj.$$hashKey = (nextUidFn || nextUid)(); } } else { key = obj; @@ -34,7 +34,13 @@ function hashKey(obj) { /** * HashMap which can use objects as keys */ -function HashMap(array){ +function HashMap(array, isolatedUid) { + if (isolatedUid) { + var uid = 0; + this.nextUid = function() { + return ++uid; + }; + } forEach(array, this.put, this); } HashMap.prototype = { @@ -44,7 +50,7 @@ HashMap.prototype = { * @param value value to store can be any type */ put: function(key, value) { - this[hashKey(key)] = value; + this[hashKey(key, this.nextUid)] = value; }, /** @@ -52,7 +58,7 @@ HashMap.prototype = { * @returns {Object} the value for the key */ get: function(key) { - return this[hashKey(key)]; + return this[hashKey(key, this.nextUid)]; }, /** @@ -60,7 +66,7 @@ HashMap.prototype = { * @param key */ remove: function(key) { - var value = this[key = hashKey(key)]; + var value = this[key = hashKey(key, this.nextUid)]; delete this[key]; return value; } diff --git a/src/auto/injector.js b/src/auto/injector.js index 6778367c8920..08fa4f756b9b 100644 --- a/src/auto/injector.js +++ b/src/auto/injector.js @@ -611,7 +611,7 @@ function createInjector(modulesToLoad, strictDi) { var INSTANTIATING = {}, providerSuffix = 'Provider', path = [], - loadedModules = new HashMap(), + loadedModules = new HashMap([], true), providerCache = { $provide: { provider: supportObject(provider), diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index a44b90b5aa3e..e78de52bacea 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -1983,6 +1983,12 @@ if(window.jasmine || window.mocha) { (window.afterEach || window.teardown)(function() { var injector = currentSpec.$injector; + angular.forEach(currentSpec.$modules, function(module) { + if (module && module.$$hashKey) { + module.$$hashKey = undefined; + } + }); + currentSpec.$injector = null; currentSpec.$modules = null; currentSpec = null; diff --git a/test/ApiSpecs.js b/test/ApiSpecs.js index 12de39d03fcd..d11558c27fa5 100644 --- a/test/ApiSpecs.js +++ b/test/ApiSpecs.js @@ -22,6 +22,36 @@ describe('api', function() { expect(map.get('b')).toBe(1); expect(map.get('c')).toBe(undefined); }); + + it('should maintain hashKey for object keys', function() { + var map = new HashMap(); + var key = {}; + map.get(key); + expect(key.$$hashKey).toBeDefined(); + }); + + it('should maintain hashKey for function keys', function() { + var map = new HashMap(); + var key = function() {}; + map.get(key); + expect(key.$$hashKey).toBeDefined(); + }); + + it('should share hashKey between HashMap by default', function() { + var map1 = new HashMap(), map2 = new HashMap(); + var key1 = {}, key2 = {}; + map1.get(key1); + map2.get(key2); + expect(key1.$$hashKey).not.toEqual(key2.$$hashKey); + }); + + it('should maintain hashKey per HashMap if flag is passed', function() { + var map1 = new HashMap([], true), map2 = new HashMap([], true); + var key1 = {}, key2 = {}; + map1.get(key1); + map2.get(key2); + expect(key1.$$hashKey).toEqual(key2.$$hashKey); + }); }); }); diff --git a/test/auto/injectorSpec.js b/test/auto/injectorSpec.js index 6b731fe796e4..38d3ece69c65 100644 --- a/test/auto/injectorSpec.js +++ b/test/auto/injectorSpec.js @@ -296,6 +296,29 @@ describe('injector', function() { 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'; }); diff --git a/test/ngMock/angular-mocksSpec.js b/test/ngMock/angular-mocksSpec.js index 1d587b03da73..9934c37db6a2 100644 --- a/test/ngMock/angular-mocksSpec.js +++ b/test/ngMock/angular-mocksSpec.js @@ -808,6 +808,23 @@ describe('ngMock', function() { expect(example).toEqual('win'); }); }); + + describe('module cleanup', function() { + function testFn() { + + } + + it('should add hashKey to module function', function() { + module(testFn); + inject(function () { + expect(testFn.$$hashKey).toBeDefined(); + }); + }); + + it('should cleanup hashKey after previous test', function() { + expect(testFn.$$hashKey).toBeUndefined(); + }); + }); }); describe('in DSL', function() {