diff options
| -rw-r--r-- | Rakefile | 1 | ||||
| -rw-r--r-- | src/Angular.js | 6 | ||||
| -rw-r--r-- | src/AngularPublic.js | 1 | ||||
| -rw-r--r-- | src/Compiler.js | 7 | ||||
| -rw-r--r-- | src/Injector.js | 59 | ||||
| -rw-r--r-- | src/Scope.js | 61 | ||||
| -rw-r--r-- | src/angular-bootstrap.js | 1 | ||||
| -rw-r--r-- | src/services.js | 67 | ||||
| -rw-r--r-- | src/widgets.js | 6 | ||||
| -rw-r--r-- | test/InjectorSpec.js | 71 | ||||
| -rw-r--r-- | test/ResourceSpec.js | 20 | ||||
| -rw-r--r-- | test/ScenarioSpec.js | 5 | ||||
| -rw-r--r-- | test/ScopeSpec.js | 75 | ||||
| -rw-r--r-- | test/ValidatorsTest.js | 3 | ||||
| -rw-r--r-- | test/directivesSpec.js | 2 | ||||
| -rw-r--r-- | test/servicesSpec.js | 171 | ||||
| -rw-r--r-- | test/widgetsSpec.js | 4 |
17 files changed, 347 insertions, 213 deletions
@@ -29,6 +29,7 @@ task :compile_scenario do src/jqLite.js \ src/JSON.js \ src/Scope.js \ + src/Injector.js \ src/Parser.js \ src/Resource.js \ src/Browser.js \ diff --git a/src/Angular.js b/src/Angular.js index f7351bdc..5bffac53 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -330,6 +330,10 @@ function escapeAttr(html) { '"'); } +function concat(array1, array2, index) { + return array1.concat(slice.call(array2, index, array2.length)); +} + function bind(self, fn) { var curryArgs = arguments.length > 2 ? slice.call(arguments, 2, arguments.length) : []; if (typeof fn == $function) { @@ -403,7 +407,7 @@ function angularInit(config){ // TODO default to the source of angular.js var scope = compile(window.document, _null, {'$config':config}); if (config.css) - scope.$browser.addCss(config.base_url + config.css); + scope.$inject('$browser').addCss(config.base_url + config.css); scope.$init(); } } diff --git a/src/AngularPublic.js b/src/AngularPublic.js index 617a7e2e..2b5d4fbc 100644 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -21,6 +21,7 @@ extend(angular, { 'extend': extend, 'equals': equals, 'foreach': foreach, + 'injector': createInjector, 'noop':noop, 'bind':bind, 'toJson': toJson, diff --git a/src/Compiler.js b/src/Compiler.js index 0c80d3fc..a78a04c9 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -34,7 +34,7 @@ Template.prototype = { foreach(this.inits, function(fn) { queue.push(function() { childScope.$tryEval(function(){ - return fn.call(childScope, element); + return childScope.$inject(fn, childScope, element); }, element); }); }); @@ -96,8 +96,7 @@ Compiler.prototype = { return function(element, parentScope){ element = jqLite(element); var scope = parentScope && parentScope.$eval ? - parentScope : - createScope(parentScope || {}, angularService); + parentScope : createScope(parentScope); return extend(scope, { $element:element, $init: function() { @@ -124,7 +123,7 @@ Compiler.prototype = { text:function(text) {return jqLite(document.createTextNode(text));}, descend: function(value){ if(isDefined(value)) descend = value; return descend;}, directives: function(value){ if(isDefined(value)) directives = value; return directives;}, - scope: function(value){ if(isDefined(value)) template.newScope = template.newScope || value ; return template.newScope;} + scope: function(value){ if(isDefined(value)) template.newScope = template.newScope || value; return template.newScope;} }; try { priority = element.attr('ng:eval-order') || priority || 0; diff --git a/src/Injector.js b/src/Injector.js new file mode 100644 index 00000000..554e9089 --- /dev/null +++ b/src/Injector.js @@ -0,0 +1,59 @@ +/** + * Create an inject method + * @param providerScope provider's "this" + * @param providers a function(name) which returns provider function + * @param cache place where instances are saved for reuse + * @returns {Function} + */ +function createInjector(providerScope, providers, cache) { + providers = providers || angularService; + cache = cache || {}; + providerScope = providerScope || {}; + /** + * injection function + * @param value: string, array, object or function. + * @param scope: optional function "this" + * @param args: optional arguments to pass to function after injection + * parameters + * @returns depends on value: + * string: return an instance for the injection key. + * array of keys: returns an array of instances. + * function: look at $inject property of function to determine instances + * and then call the function with instances and scope. Any + * additional arguments are passed on to function. + * object: initialize eager providers and publish them the ones with publish here. + * none: same as object but use providerScope as place to publish. + */ + return function inject(value, scope, args){ + var returnValue, provider, creation; + if (isString(value)) { + if (!cache.hasOwnProperty(value)) { + provider = providers[value]; + if (!provider) throw "Unknown provider for '"+value+"'."; + cache[value] = inject(provider, providerScope); + } + returnValue = cache[value]; + } else if (isArray(value)) { + returnValue = []; + foreach(value, function(name) { + returnValue.push(inject(name)); + }); + } else if (isFunction(value)) { + returnValue = inject(value.$inject || []); + returnValue = value.apply(scope, concat(returnValue, arguments, 2)); + } else if (isObject(value)) { + foreach(providers, function(provider, name){ + creation = provider.$creation; + if (creation == 'eager') { + inject(name); + } + if (creation == 'eager-published') { + setter(value, name, inject(name)); + } + }); + } else { + returnValue = inject(providerScope); + } + return returnValue; + }; +}
\ No newline at end of file diff --git a/src/Scope.js b/src/Scope.js index 3aaff361..ed608e95 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -108,20 +108,14 @@ function errorHandlerFor(element, error) { elementError(element, NG_EXCEPTION, isDefined(error) ? toJson(error) : error); } -function createScope(parent, services, existing) { +function createScope(parent, providers, instanceCache) { function Parent(){} - function API(){} - function Behavior(){} - parent = Parent.prototype = (parent || {}); + var instance = new Parent(); var evalLists = {sorted:[]}; var postList = [], postHash = {}, postId = 0; - var servicesCache = extend({}, existing); - var api = API.prototype = new Parent(); - var behavior = Behavior.prototype = new API(); - var instance = new Behavior(); - extend(api, { + extend(instance, { 'this': instance, $id: (scopeId++), $parent: parent, @@ -227,46 +221,29 @@ function createScope(parent, services, existing) { }, $become: function(Class) { - // remove existing - foreach(behavior, function(value, key){ delete behavior[key]; }); - foreach((Class || noop).prototype, function(fn, name){ - behavior[name] = bind(instance, fn); - }); - (Class || noop).call(instance); - - //TODO: backwards compatibility hack, remove when Feedback's init methods are removed - if (behavior.hasOwnProperty('init')) { - behavior.init(); + if (isFunction(Class)) { + instance.constructor = Class; + foreach(Class.prototype, function(fn, name){ + instance[name] = bind(instance, fn); + }); + instance.$inject.apply(instance, concat([Class, instance], arguments, 1)); } + }, + + $new: function(Class) { + var child = createScope(instance); + child.$become.apply(instance, concat([Class], arguments, 1)); + instance.$onEval(child.$eval); + return child; } }); if (!parent.$root) { - api.$root = instance; - api.$parent = instance; - } - - function inject(name){ - var service = servicesCache[name], factory, args = []; - if (isUndefined(service)) { - factory = services[name]; - if (!isFunction(factory)) - throw "Don't know how to inject '" + name + "'."; - foreach(factory.inject, function(dependency){ - args.push(inject(dependency)); - }); - servicesCache[name] = service = factory.apply(instance, args); - } - return service; + instance.$root = instance; + instance.$parent = instance; + (instance.$inject = createInjector(instance, providers, instanceCache))(); } - foreach(services, function(_, name){ - var service = inject(name); - if (service) { - setter(instance, name, service); - } - }); - return instance; } diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js index 1f03b8a3..581f4ff9 100644 --- a/src/angular-bootstrap.js +++ b/src/angular-bootstrap.js @@ -41,6 +41,7 @@ addScript("/JSON.js"); addScript("/Compiler.js"); addScript("/Scope.js"); + addScript("/Injector.js"); addScript("/jqLite.js"); addScript("/Parser.js"); addScript("/Resource.js"); diff --git a/src/services.js b/src/services.js index 9dc3b456..5b65f8fd 100644 --- a/src/services.js +++ b/src/services.js @@ -1,13 +1,19 @@ var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?([^\?#]+)(\?([^#]*))?(#(.*))?$/, HASH_MATCH = /^([^\?]*)?(\?([^\?]*))?$/, - DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21}; + DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21}, + EAGER = 'eager', + EAGER_PUBLISHED = EAGER + '-published'; -angularService("$window", bind(window, identity, window)); -angularService("$document", function(window){ +function angularServiceInject(name, fn, inject, eager) { + angularService(name, fn, {$inject:inject, $creation:eager}); +} + +angularServiceInject("$window", bind(window, identity, window), [], EAGER_PUBLISHED); +angularServiceInject("$document", function(window){ return jqLite(window.document); -}, {inject:['$window']}); +}, ['$window'], EAGER_PUBLISHED); -angularService("$location", function(browser){ +angularServiceInject("$location", function(browser){ var scope = this, location = {parse:parseUrl, toString:toString, update:update}, lastLocation = {}; @@ -92,9 +98,9 @@ angularService("$location", function(browser){ update(); return location.href; } -}, {inject: ['$browser']}); +}, ['$browser'], EAGER_PUBLISHED); -angularService("$log", function($window){ +angularServiceInject("$log", function($window){ var console = $window.console || {log: noop, warn: noop, info: noop, error: noop}, log = console.log || noop; return { @@ -103,15 +109,15 @@ angularService("$log", function($window){ info: bind(console, console.info || log), error: bind(console, console.error || log) }; -}, {inject:['$window']}); +}, ['$window'], EAGER_PUBLISHED); -angularService('$exceptionHandler', function($log){ +angularServiceInject('$exceptionHandler', function($log){ return function(e) { $log.error(e); }; -}, {inject:['$log']}); +}, ['$log'], EAGER_PUBLISHED); -angularService("$hover", function(browser, document) { +angularServiceInject("$hover", function(browser, document) { var tooltip, self = this, error, width = 300, arrowWidth = 10, body = jqLite(document[0].body); browser.hover(function(element, show){ if (show && (error = element.attr(NG_EXCEPTION) || element.attr(NG_VALIDATION_ERROR))) { @@ -155,14 +161,13 @@ angularService("$hover", function(browser, document) { tooltip = _null; } }); -}, {inject:['$browser', '$document']}); - +}, ['$browser', '$document'], EAGER); /* Keeps references to all invalid widgets found during validation. Can be queried to find if there * are invalid widgets currently displayed */ -angularService("$invalidWidgets", function(){ +angularServiceInject("$invalidWidgets", function(){ var invalidWidgets = []; @@ -217,7 +222,7 @@ angularService("$invalidWidgets", function(){ } return invalidWidgets; -}); +}, [], EAGER_PUBLISHED); @@ -244,7 +249,7 @@ function switchRouteMatcher(on, when, dstName) { return match ? dst : _null; } -angularService('$route', function(location){ +angularServiceInject('$route', function(location){ var routes = {}, onChange = [], matcher = switchRouteMatcher, @@ -284,9 +289,9 @@ angularService('$route', function(location){ } this.$watch(function(){return dirty + location.hash;}, updateRoute); return $route; -}, {inject: ['$location']}); +}, ['$location']); -angularService('$xhr', function($browser, $error, $log){ +angularServiceInject('$xhr', function($browser, $error, $log){ var self = this; return function(method, url, post, callback){ if (isFunction(post)) { @@ -315,15 +320,15 @@ angularService('$xhr', function($browser, $error, $log){ } }); }; -}, {inject:['$browser', '$xhr.error', '$log']}); +}, ['$browser', '$xhr.error', '$log']); -angularService('$xhr.error', function($log){ +angularServiceInject('$xhr.error', function($log){ return function(request, response){ $log.error('ERROR: XHR: ' + request.url, request, response); }; -}, {inject:['$log']}); +}, ['$log']); -angularService('$xhr.bulk', function($xhr, $error, $log){ +angularServiceInject('$xhr.bulk', function($xhr, $error, $log){ var requests = [], scope = this; function bulkXHR(method, url, post, callback) { @@ -371,9 +376,9 @@ angularService('$xhr.bulk', function($xhr, $error, $log){ }; this.$onEval(PRIORITY_LAST, bulkXHR.flush); return bulkXHR; -}, {inject:['$xhr', '$xhr.error', '$log']}); +}, ['$xhr', '$xhr.error', '$log']); -angularService('$xhr.cache', function($xhr){ +angularServiceInject('$xhr.cache', function($xhr){ var inflight = {}, self = this; function cache(method, url, post, callback, verifyCache){ if (isFunction(post)) { @@ -415,12 +420,12 @@ angularService('$xhr.cache', function($xhr){ cache.data = {}; cache.delegate = $xhr; return cache; -}, {inject:['$xhr.bulk']}); +}, ['$xhr.bulk']); -angularService('$resource', function($xhr){ +angularServiceInject('$resource', function($xhr){ var resource = new ResourceFactory($xhr); return bind(resource, resource.route); -}, {inject: ['$xhr.cache']}); +}, ['$xhr.cache']); /** @@ -430,7 +435,7 @@ angularService('$resource', function($xhr){ * Only a simple Object is exposed and by adding or removing properties to/from this object, new * cookies are created or deleted from the browser at the end of the current eval. */ -angularService('$cookies', function($browser) { +angularServiceInject('$cookies', function($browser) { var rootScope = this, cookies = {}, lastCookies = {}, @@ -499,14 +504,14 @@ angularService('$cookies', function($browser) { } } } -}, {inject: ['$browser']}); +}, ['$browser'], EAGER_PUBLISHED); /** * $cookieStore provides a key-value (string-object) storage that is backed by session cookies. * Objects put or retrieved from this storage are automatically serialized or deserialized. */ -angularService('$cookieStore', function($store) { +angularServiceInject('$cookieStore', function($store) { return { get: function(/**string*/key) { @@ -522,4 +527,4 @@ angularService('$cookieStore', function($store) { } }; -}, {inject: ['$cookies']}); +}, ['$cookies'], EAGER_PUBLISHED); diff --git a/src/widgets.js b/src/widgets.js index 90f6837d..c9dfab99 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -253,7 +253,7 @@ angularWidget('ng:include', function(element){ this.directives(true); } else { element[0]['ng:compiled'] = true; - return function(element){ + return extend(function(xhr, element){ var scope = this, childScope; var changeCounter = 0; function incrementChange(){ changeCounter++;} @@ -266,7 +266,7 @@ angularWidget('ng:include', function(element){ var src = this.$eval(srcExp), useScope = this.$eval(scopeExp); if (src) { - scope.$xhr.cache('GET', src, function(code, response){ + xhr('GET', src, function(code, response){ element.html(response); childScope = useScope || createScope(scope); compiler.compile(element)(element, childScope); @@ -276,7 +276,7 @@ angularWidget('ng:include', function(element){ element.html(''); } }); - }; + }, {$inject:['$xhr.cache']}); } }); diff --git a/test/InjectorSpec.js b/test/InjectorSpec.js new file mode 100644 index 00000000..ba0f27f7 --- /dev/null +++ b/test/InjectorSpec.js @@ -0,0 +1,71 @@ +describe('injector', function(){ + var providers; + var cache; + var inject; + var scope; + + beforeEach(function(){ + providers = extensionMap({}, 'providers'); + cache = {}; + scope = {}; + inject = createInjector(scope, providers, cache); + }); + + it("should return same instance from calling provider", function(){ + providers('text', function(){ return scope.name; }); + scope.name = 'abc'; + expect(inject('text')).toEqual('abc'); + expect(cache.text).toEqual('abc'); + scope.name = 'deleted'; + expect(inject('text')).toEqual('abc'); + }); + + it("should return an array of instances", function(){ + cache.a = 0; + providers('b', function(){return 2;}); + expect(inject(['a', 'b'])).toEqual([0,2]); + }); + + it("should call function", function(){ + providers('a', function(){return 1;}); + providers('b', function(){return 2;}); + var args; + function fn(a, b, c, d) { + args = [this, a, b, c, d]; + } + fn.$inject = ['a', 'b']; + inject(fn, {name:"this"}, 3, 4); + expect(args).toEqual([{name:'this'}, 1, 2, 3, 4]); + }); + + it('should inject providers', function(){ + providers('a', function(){return this.mi = 'Mi';}); + providers('b', function(mi){return this.name = mi+'sko';}, {$inject:['a']}); + expect(inject('b')).toEqual('Misko'); + expect(scope).toEqual({mi:'Mi', name:'Misko'}); + }); + + it('should provide usefull message if no provider', function(){ + assertThrows("Unknown provider for 'idontexist'.", function(){ + inject('idontexist'); + }); + }); + + it('should autostart eager services', function(){ + var log = ''; + providers('eager', function(){log += 'eager;';}, {$creation: 'eager'}); + inject(); + expect(log).toEqual('eager;'); + expect(scope.eager).not.toBeDefined(); + }); + + + it('should return a list of published objects', function(){ + var log = ''; + providers('eager', function(){log += 'eager;'; return 'pub'; }, {$creation: 'eager-published'}); + inject(); + expect(log).toEqual('eager;'); + expect(scope.eager).toEqual('pub'); + + }); +});
\ No newline at end of file diff --git a/test/ResourceSpec.js b/test/ResourceSpec.js index 435176b0..e258d5a3 100644 --- a/test/ResourceSpec.js +++ b/test/ResourceSpec.js @@ -162,26 +162,30 @@ describe("resource", function() { it('should excersize full stack', function(){ var scope = angular.compile('<div></div>'); - var Person = scope.$resource('/Person/:id'); - scope.$browser.xhr.expectGET('/Person/123').respond('\n{\nname:\n"misko"\n}\n'); + var $browser = scope.$inject('$browser'); + var $resource = scope.$inject('$resource'); + var Person = $resource('/Person/:id'); + $browser.xhr.expectGET('/Person/123').respond('\n{\nname:\n"misko"\n}\n'); var person = Person.get({id:123}); - scope.$browser.xhr.flush(); + $browser.xhr.flush(); expect(person.name).toEqual('misko'); }); it('should return the same object when verifying the cache', function(){ var scope = angular.compile('<div></div>'); - var Person = scope.$resource('/Person/:id', null, {query: {method:'GET', isArray: true, verifyCache: true}}); - scope.$browser.xhr.expectGET('/Person/123').respond('[\n{\nname:\n"misko"\n}\n]'); + var $browser = scope.$inject('$browser'); + var $resource = scope.$inject('$resource'); + var Person = $resource('/Person/:id', null, {query: {method:'GET', isArray: true, verifyCache: true}}); + $browser.xhr.expectGET('/Person/123').respond('[\n{\nname:\n"misko"\n}\n]'); var person = Person.query({id:123}); - scope.$browser.xhr.flush(); + $browser.xhr.flush(); expect(person[0].name).toEqual('misko'); - scope.$browser.xhr.expectGET('/Person/123').respond('[\n{\nname:\n"rob"\n}\n]'); + $browser.xhr.expectGET('/Person/123').respond('[\n{\nname:\n"rob"\n}\n]'); var person2 = Person.query({id:123}); expect(person2[0].name).toEqual('misko'); var person2Cache = person2; - scope.$browser.xhr.flush(); + $browser.xhr.flush(); expect(person2Cache).toEqual(person2); expect(person2[0].name).toEqual('rob'); }); diff --git a/test/ScenarioSpec.js b/test/ScenarioSpec.js index ede49a49..730019a2 100644 --- a/test/ScenarioSpec.js +++ b/test/ScenarioSpec.js @@ -43,9 +43,10 @@ describe("ScenarioSpec: configuration", function(){ var url = "http://server/#?book=moby"; var scope = compile("<div>{{$location}}</div>"); var $location = scope.$location; + var $browser = scope.$inject('$browser'); expect($location.hashSearch.book).toBeUndefined(); - scope.$browser.setUrl(url); - scope.$browser.poll(); + $browser.setUrl(url); + $browser.poll(); expect($location.hashSearch.book).toEqual('moby'); }); }); diff --git a/test/ScopeSpec.js b/test/ScopeSpec.js index 66e9d489..4a207bf0 100644 --- a/test/ScopeSpec.js +++ b/test/ScopeSpec.js @@ -1,11 +1,27 @@ describe('scope/model', function(){ + var temp; + + beforeEach(function() { + temp = window.temp = {}; + temp.InjectController = function(exampleService, extra) { + this.localService = exampleService; + this.extra = extra; + this.$root.injectController = this; + }; + temp.InjectController.$inject = ["exampleService"]; + }); + + afterEach(function() { + window.temp = undefined; + }); + it('should create a scope with parent', function(){ var model = createScope({name:'Misko'}); expect(model.name).toEqual('Misko'); }); - it('should have $get/set$/parent$', function(){ + it('should have $get/$set/$parent', function(){ var parent = {}; var model = createScope(parent); model.$set('name', 'adam'); @@ -138,40 +154,6 @@ describe('scope/model', function(){ }); }); - describe('service injection', function(){ - it('should inject services', function(){ - var scope = createScope(null, { - service:function(){ - return "ABC"; - } - }); - expect(scope.service).toEqual("ABC"); - }); - - it('should inject arugments', function(){ - var scope = createScope(null, { - name:function(){ - return "misko"; - }, - greet: extend(function(name) { - return 'hello ' + name; - }, {inject:['name']}) - }); - expect(scope.greet).toEqual("hello misko"); - }); - - it('should throw error on missing dependency', function(){ - try { - createScope(null, { - greet: extend(function(name) { - }, {inject:['name']}) - }); - } catch(e) { - expect(e).toEqual("Don't know how to inject 'name'."); - } - }); - }); - describe('getterFn', function(){ it('should get chain', function(){ expect(getterFn('a.b')(undefined)).toEqual(undefined); @@ -215,4 +197,27 @@ describe('scope/model', function(){ }); }); + describe('$new', function(){ + it('should $new should create new child scope and $become controller', function(){ + var parent = createScope(null, {exampleService: function(){return 'Example Service';}}); + var child = parent.$new(temp.InjectController, 10); + expect(child.localService).toEqual('Example Service'); + expect(child.extra).toEqual(10); + + child.$onEval(function(){ this.run = true; }); + parent.$eval(); + expect(child.run).toEqual(true); + }); + }); + + describe('$become', function(){ + it('should inject properties on controller defined in $inject', function(){ + var parent = createScope(null, {exampleService: function(){return 'Example Service';}}); + var child = createScope(parent); + child.$become(temp.InjectController, 10); + expect(child.localService).toEqual('Example Service'); + expect(child.extra).toEqual(10); + }); + }); + }); diff --git a/test/ValidatorsTest.js b/test/ValidatorsTest.js index 07f3e488..463e05de 100644 --- a/test/ValidatorsTest.js +++ b/test/ValidatorsTest.js @@ -112,6 +112,9 @@ describe('Validator:asynchronous', function(){ if (self.$element) self.$element.remove(); var oldCache = jqCache; jqCache = {}; + if (size(oldCache)) { + dump(oldCache); + } expect(size(oldCache)).toEqual(0); }); diff --git a/test/directivesSpec.js b/test/directivesSpec.js index 42a4879a..d8f04b29 100644 --- a/test/directivesSpec.js +++ b/test/directivesSpec.js @@ -282,7 +282,7 @@ describe("directives", function(){ }); it('should support nested controllers', function(){ - temp.ChildGreeter = function() { + temp.ChildGreeter = function(){ this.greeting = 'hey'; this.$root.childGreeter = this; }; diff --git a/test/servicesSpec.js b/test/servicesSpec.js index c0101098..6c586d0a 100644 --- a/test/servicesSpec.js +++ b/test/servicesSpec.js @@ -1,13 +1,19 @@ describe("service", function(){ - var scope, $xhrError, $log, mockServices; + var scope, $xhrError, $log, mockServices, inject, $browser, $browserXhr, $xhrBulk, $xhr, $route; beforeEach(function(){ $xhrError = jasmine.createSpy('$xhr.error'); $log = {}; - scope = createScope(null, angularService, { + scope = createScope({}, angularService, { '$xhr.error': $xhrError, '$log': $log }); + inject = scope.$inject; + $browser = inject('$browser'); + $browserXhr = $browser.xhr; + $xhrBulk = scope.$inject('$xhr.bulk'); + $xhr = scope.$inject('$xhr'); + $route = scope.$inject('$route'); }); afterEach(function(){ @@ -38,7 +44,7 @@ describe("service", function(){ function warn(){ logger+= 'warn;'; } function info(){ logger+= 'info;'; } function error(){ logger+= 'error;'; } - var scope = createScope(null, angularService, {$window: {console:{log:log, warn:warn, info:info, error:error}}, $document:[{cookie:''}]}); + var scope = createScope({}, angularService, {$window: {console:{log:log, warn:warn, info:info, error:error}}, $document:[{cookie:''}]}); scope.$log.log(); scope.$log.warn(); scope.$log.info(); @@ -49,7 +55,7 @@ describe("service", function(){ it('should use console.log if other not present', function(){ var logger = ""; function log(){ logger+= 'log;'; } - var scope = createScope(null, angularService, {$window: {console:{log:log}}, $document:[{cookie:''}]}); + var scope = createScope({}, angularService, {$window: {console:{log:log}}, $document:[{cookie:''}]}); scope.$log.log(); scope.$log.warn(); scope.$log.info(); @@ -58,7 +64,7 @@ describe("service", function(){ }); it('should use noop if no console', function(){ - var scope = createScope(null, angularService, {$window: {}, $document:[{cookie:''}]}); + var scope = createScope({}, angularService, {$window: {}, $document:[{cookie:''}]}); scope.$log.log(); scope.$log.warn(); scope.$log.info(); @@ -182,49 +188,47 @@ describe("service", function(){ function BookChapter() { this.log = '<init>'; } - BookChapter.prototype.init = function(){ - log += 'init();'; - }; var scope = compile('<div></div>').$init(); - scope.$route.when('/Book/:book/Chapter/:chapter', {controller: BookChapter, template:'Chapter.html'}); - scope.$route.when('/Blank'); - scope.$route.onChange(function(){ + var $route = scope.$inject('$route'); + $route.when('/Book/:book/Chapter/:chapter', {controller: BookChapter, template:'Chapter.html'}); + $route.when('/Blank'); + $route.onChange(function(){ log += 'onChange();'; }); scope.$location.parse('http://server#/Book/Moby/Chapter/Intro?p=123'); scope.$eval(); - expect(log).toEqual('onChange();init();'); - expect(scope.$route.current.params).toEqual({book:'Moby', chapter:'Intro', p:'123'}); - expect(scope.$route.current.scope.log).toEqual('<init>'); - var lastId = scope.$route.current.scope.$id; + expect(log).toEqual('onChange();'); + expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', p:'123'}); + expect($route.current.scope.log).toEqual('<init>'); + var lastId = $route.current.scope.$id; log = ''; scope.$location.parse('http://server#/Blank?ignore'); scope.$eval(); expect(log).toEqual('onChange();'); - expect(scope.$route.current.params).toEqual({ignore:true}); - expect(scope.$route.current.scope.$id).not.toEqual(lastId); + expect($route.current.params).toEqual({ignore:true}); + expect($route.current.scope.$id).not.toEqual(lastId); log = ''; scope.$location.parse('http://server#/NONE'); scope.$eval(); expect(log).toEqual('onChange();'); - expect(scope.$route.current).toEqual(null); + expect($route.current).toEqual(null); - scope.$route.when('/NONE', {template:'instant update'}); + $route.when('/NONE', {template:'instant update'}); scope.$eval(); - expect(scope.$route.current.template).toEqual('instant update'); + expect($route.current.template).toEqual('instant update'); }); }); describe('$resource', function(){ it('should publish to root scope', function(){ - expect(scope.$resource).toBeTruthy(); + expect(scope.$inject('$resource')).toBeTruthy(); }); }); describe('$xhr', function(){ - var log, xhr; + var log; function callback(code, response) { expect(code).toEqual(200); log = log + toJson(response) + ';'; @@ -232,27 +236,26 @@ describe("service", function(){ beforeEach(function(){ log = ''; - xhr = scope.$browser.xhr; }); it('should forward the request to $browser and decode JSON', function(){ - xhr.expectGET('/reqGET').respond('first'); - xhr.expectGET('/reqGETjson').respond('["second"]'); - xhr.expectPOST('/reqPOST', {post:'data'}).respond('third'); + $browserXhr.expectGET('/reqGET').respond('first'); + $browserXhr.expectGET('/reqGETjson').respond('["second"]'); + $browserXhr.expectPOST('/reqPOST', {post:'data'}).respond('third'); - scope.$xhr('GET', '/reqGET', null, callback); - scope.$xhr('GET', '/reqGETjson', null, callback); - scope.$xhr('POST', '/reqPOST', {post:'data'}, callback); + $xhr('GET', '/reqGET', null, callback); + $xhr('GET', '/reqGETjson', null, callback); + $xhr('POST', '/reqPOST', {post:'data'}, callback); - xhr.flush(); + $browserXhr.flush(); expect(log).toEqual('"third";["second"];"first";'); }); it('should handle non 200 status codes by forwarding to error handler', function(){ - xhr.expectPOST('/req', 'MyData').respond(500, 'MyError'); - scope.$xhr('POST', '/req', 'MyData', callback); - xhr.flush(); + $browserXhr.expectPOST('/req', 'MyData').respond(500, 'MyError'); + $xhr('POST', '/req', 'MyData', callback); + $browserXhr.flush(); var cb = $xhrError.mostRecentCall.args[0].callback; expect(typeof cb).toEqual($function); expect($xhrError).wasCalledWith( @@ -262,45 +265,45 @@ describe("service", function(){ it('should handle exceptions in callback', function(){ $log.error = jasmine.createSpy('$log.error'); - xhr.expectGET('/reqGET').respond('first'); - scope.$xhr('GET', '/reqGET', null, function(){ throw "MyException"; }); - xhr.flush(); + $browserXhr.expectGET('/reqGET').respond('first'); + $xhr('GET', '/reqGET', null, function(){ throw "MyException"; }); + $browserXhr.flush(); expect($log.error).wasCalledWith("MyException"); }); describe('bulk', function(){ it('should collect requests', function(){ - scope.$xhr.bulk.urls["/"] = {match:/.*/}; - scope.$xhr.bulk('GET', '/req1', null, callback); - scope.$xhr.bulk('POST', '/req2', {post:'data'}, callback); + $xhrBulk.urls["/"] = {match:/.*/}; + $xhrBulk('GET', '/req1', null, callback); + $xhrBulk('POST', '/req2', {post:'data'}, callback); - xhr.expectPOST('/', { + $browserXhr.expectPOST('/', { requests:[{method:'GET', url:'/req1', data: null}, {method:'POST', url:'/req2', data:{post:'data'} }] }).respond([ {status:200, response:'first'}, {status:200, response:'second'} ]); - scope.$xhr.bulk.flush(function(){ log += 'DONE';}); - xhr.flush(); + $xhrBulk.flush(function(){ log += 'DONE';}); + $browserXhr.flush(); expect(log).toEqual('"first";"second";DONE'); }); it('should handle non 200 status code by forwarding to error handler', function(){ - scope.$xhr.bulk.urls['/'] = {match:/.*/}; - scope.$xhr.bulk('GET', '/req1', null, callback); - scope.$xhr.bulk('POST', '/req2', {post:'data'}, callback); + $xhrBulk.urls['/'] = {match:/.*/}; + $xhrBulk('GET', '/req1', null, callback); + $xhrBulk('POST', '/req2', {post:'data'}, callback); - xhr.expectPOST('/', { + $browserXhr.expectPOST('/', { requests:[{method:'GET', url:'/req1', data: null}, {method:'POST', url:'/req2', data:{post:'data'} }] }).respond([ {status:404, response:'NotFound'}, {status:200, response:'second'} ]); - scope.$xhr.bulk.flush(function(){ log += 'DONE';}); - xhr.flush(); + $xhrBulk.flush(function(){ log += 'DONE';}); + $browserXhr.flush(); expect($xhrError).wasCalled(); var cb = $xhrError.mostRecentCall.args[0].callback; @@ -315,29 +318,29 @@ describe("service", function(){ describe('cache', function(){ var cache; - beforeEach(function(){ cache = scope.$xhr.cache; }); + beforeEach(function(){ cache = scope.$inject('$xhr.cache'); }); it('should cache requests', function(){ - xhr.expectGET('/url').respond('first'); + $browserXhr.expectGET('/url').respond('first'); cache('GET', '/url', null, callback); - xhr.flush(); - xhr.expectGET('/url').respond('ERROR'); + $browserXhr.flush(); + $browserXhr.expectGET('/url').respond('ERROR'); cache('GET', '/url', null, callback); - xhr.flush(); + $browserXhr.flush(); expect(log).toEqual('"first";"first";'); cache('GET', '/url', null, callback, false); - xhr.flush(); + $browserXhr.flush(); expect(log).toEqual('"first";"first";"first";'); }); it('should first return cache request, then return server request', function(){ - xhr.expectGET('/url').respond('first'); + $browserXhr.expectGET('/url').respond('first'); cache('GET', '/url', null, callback, true); - xhr.flush(); - xhr.expectGET('/url').respond('ERROR'); + $browserXhr.flush(); + $browserXhr.expectGET('/url').respond('ERROR'); cache('GET', '/url', null, callback, true); expect(log).toEqual('"first";"first";'); - xhr.flush(); + $browserXhr.flush(); expect(log).toEqual('"first";"first";"ERROR";'); }); @@ -350,12 +353,12 @@ describe("service", function(){ }); it('should keep track of in flight requests and request only once', function(){ - scope.$xhr.bulk.urls['/bulk'] = { + scope.$inject('$xhr.bulk').urls['/bulk'] = { match:function(url){ return url == '/url'; } }; - xhr.expectPOST('/bulk', { + $browserXhr.expectPOST('/bulk', { requests:[{method:'GET', url:'/url', data: null}] }).respond([ {status:200, response:'123'} @@ -363,12 +366,12 @@ describe("service", function(){ cache('GET', '/url', null, callback); cache('GET', '/url', null, callback); cache.delegate.flush(); - xhr.flush(); + $browserXhr.flush(); expect(log).toEqual('"123";"123";'); }); it('should clear cache on non GET', function(){ - xhr.expectPOST('abc', {}).respond({}); + $browserXhr.expectPOST('abc', {}).respond({}); cache.data.url = {value:123}; cache('POST', 'abc', {}); expect(cache.data.url).toBeUndefined(); @@ -380,12 +383,12 @@ describe("service", function(){ describe('$cookies', function() { - var scope; + var scope, $browser; beforeEach(function() { - var browser = new MockBrowser(); - browser.cookieHash['preexisting'] = 'oldCookie'; - scope = createScope(null, angularService, {$browser: browser}); + $browser = new MockBrowser(); + $browser.cookieHash['preexisting'] = 'oldCookie'; + scope = createScope(null, angularService, {$browser: $browser}); }); @@ -393,19 +396,19 @@ describe("service", function(){ function(){ expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'}); - // access internal cookie storage of the browser mock directly to simulate behavior of + // access internal cookie storage of the browser mock directly to simulate behavior of // document.cookie - scope.$browser.cookieHash['brandNew'] = 'cookie'; - scope.$browser.poll(); + $browser.cookieHash['brandNew'] = 'cookie'; + $browser.poll(); expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'brandNew':'cookie'}); - scope.$browser.cookieHash['brandNew'] = 'cookie2'; - scope.$browser.poll(); + $browser.cookieHash['brandNew'] = 'cookie2'; + $browser.poll(); expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'brandNew':'cookie2'}); - delete scope.$browser.cookieHash['brandNew']; - scope.$browser.poll(); + delete $browser.cookieHash['brandNew']; + $browser.poll(); expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'}); }); @@ -414,13 +417,13 @@ describe("service", function(){ scope.$cookies.oatmealCookie = 'nom nom'; scope.$eval(); - expect(scope.$browser.cookies()). + expect($browser.cookies()). toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'}); scope.$cookies.oatmealCookie = 'gone'; scope.$eval(); - expect(scope.$browser.cookies()). + expect($browser.cookies()). toEqual({'preexisting': 'oldCookie', 'oatmealCookie': 'gone'}); }); @@ -428,7 +431,7 @@ describe("service", function(){ it('should ignore non-string values when asked to create a cookie', function() { scope.$cookies.nonString = [1, 2, 3]; scope.$eval(); - expect(scope.$browser.cookies()).toEqual({'preexisting': 'oldCookie'}); + expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'}); expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'}); }); @@ -438,21 +441,21 @@ describe("service", function(){ scope.$cookies.undefVal = undefined; scope.$eval(); - expect(scope.$browser.cookies()).toEqual({'preexisting': 'oldCookie'}); + expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'}); }); it('should remove a cookie when a $cookies property is deleted', function() { scope.$cookies.oatmealCookie = 'nom nom'; scope.$eval(); - scope.$browser.poll(); - expect(scope.$browser.cookies()). + $browser.poll(); + expect($browser.cookies()). toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'}); delete scope.$cookies.oatmealCookie; scope.$eval(); - expect(scope.$browser.cookies()).toEqual({'preexisting': 'oldCookie'}); + expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'}); }); @@ -485,13 +488,13 @@ describe("service", function(){ it('should serialize objects to json', function() { scope.$cookieStore.put('objectCookie', {id: 123, name: 'blah'}); scope.$eval(); //force eval in test - expect(scope.$browser.cookies()).toEqual({'objectCookie': '{"id":123,"name":"blah"}'}); + expect($browser.cookies()).toEqual({'objectCookie': '{"id":123,"name":"blah"}'}); }); it('should deserialize json to object', function() { - scope.$browser.cookies('objectCookie', '{"id":123,"name":"blah"}'); - scope.$browser.poll(); + $browser.cookies('objectCookie', '{"id":123,"name":"blah"}'); + $browser.poll(); expect(scope.$cookieStore.get('objectCookie')).toEqual({id: 123, name: 'blah'}); }); @@ -499,7 +502,7 @@ describe("service", function(){ it('should delete objects from the store when remove is called', function() { scope.$cookieStore.put('gonner', { "I'll":"Be Back"}); scope.$eval(); //force eval in test - expect(scope.$browser.cookies()).toEqual({'gonner': '{"I\'ll":"Be Back"}'}); + expect($browser.cookies()).toEqual({'gonner': '{"I\'ll":"Be Back"}'}); }); }); diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index 80acc928..8f6ccaea 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -476,7 +476,7 @@ describe("widget", function(){ scope.childScope = createScope(); scope.childScope.name = 'misko'; scope.url = 'myUrl'; - scope.$xhr.cache.data.myUrl = {value:'{{name}}'}; + scope.$inject('$xhr.cache').data.myUrl = {value:'{{name}}'}; scope.$init(); expect(element.text()).toEqual('misko'); }); @@ -487,7 +487,7 @@ describe("widget", function(){ scope.childScope = createScope(); scope.childScope.name = 'igor'; scope.url = 'myUrl'; - scope.$xhr.cache.data.myUrl = {value:'{{name}}'}; + scope.$inject('$xhr.cache').data.myUrl = {value:'{{name}}'}; scope.$init(); expect(element.text()).toEqual('igor'); |
