describe("service", function(){ var scope, $xhrError, $log, mockServices, inject, $browser, $browserXhr, $xhrBulk, $xhr, $route; beforeEach(function(){ $xhrError = jasmine.createSpy('$xhr.error'); $log = {}; 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(){ if (scope && scope.$element) scope.$element.remove(); }); it("should inject $window", function(){ expect(scope.$window).toEqual(window); }); xit('should add stylesheets', function(){ scope.$document = { getElementsByTagName: function(name){ expect(name).toEqual('LINK'); return []; } }; scope.$document.addStyleSheet('css/angular.css'); }); describe("$log", function(){ it('should use console if present', function(){ var logger = ""; function log(){ logger+= 'log;'; } function warn(){ logger+= 'warn;'; } function info(){ logger+= 'info;'; } function error(){ logger+= 'error;'; } 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(); scope.$log.error(); expect(logger).toEqual('log;warn;info;error;'); }); it('should use console.log if other not present', function(){ var logger = ""; function log(){ logger+= 'log;'; } var scope = createScope({}, angularService, {$window: {console:{log:log}}, $document:[{cookie:''}]}); scope.$log.log(); scope.$log.warn(); scope.$log.info(); scope.$log.error(); expect(logger).toEqual('log;log;log;log;'); }); it('should use noop if no console', function(){ var scope = createScope({}, angularService, {$window: {}, $document:[{cookie:''}]}); scope.$log.log(); scope.$log.warn(); scope.$log.info(); scope.$log.error(); }); }); describe("$exceptionHandler", function(){ it('should log errors', function(){ var error = ''; $log.error = function(m) { error += m; }; scope.$exceptionHandler('myError'); expect(error).toEqual('myError'); }); }); describe("$location", function(){ it("should inject $location", function() { expect(scope.$location).toBeDefined(); }); it("update should update location object immediately", function() { var href = 'http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2='; scope.$location.update(href); expect(scope.$location.href).toEqual(href); expect(scope.$location.protocol).toEqual("http"); expect(scope.$location.host).toEqual("host"); expect(scope.$location.port).toEqual("123"); expect(scope.$location.path).toEqual("/p/a/t/h.html"); expect(scope.$location.search).toEqual({query:'value'}); expect(scope.$location.hash).toEqual('path?key=value&flag&key2='); expect(scope.$location.hashPath).toEqual('path'); expect(scope.$location.hashSearch).toEqual({key: 'value', flag: true, key2: ''}); }); it('toString() should return actual representation', function() { var href = 'http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2='; scope.$location.update(href); expect(scope.$location.toString()).toEqual(href); scope.$eval(); scope.$location.host = 'new'; scope.$location.path = ''; expect(scope.$location.toString()).toEqual('http://new:123?query=value#path?key=value&flag&key2='); }); it('toString() should not update browser', function() { var url = $browser.getUrl(); scope.$location.update('http://www.angularjs.org'); expect(scope.$location.toString()).toEqual('http://www.angularjs.org'); expect($browser.getUrl()).toEqual(url); }); it('should update browser at the end of $eval', function() { var url = $browser.getUrl(); scope.$location.update('http://www.angularjs.org/'); scope.$location.update({path: '/a/b'}); expect(scope.$location.toString()).toEqual('http://www.angularjs.org/a/b'); expect($browser.getUrl()).toEqual(url); scope.$eval(); expect($browser.getUrl()).toEqual('http://www.angularjs.org/a/b'); }); it('should parse file://', function(){ scope.$location.update('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html'); expect(scope.$location.href).toEqual("file:///Users/Shared/misko/work/angular.js/scenario/widgets.html"); expect(scope.$location.protocol).toEqual("file"); expect(scope.$location.host).toEqual(""); expect(scope.$location.port).toEqual(null); expect(scope.$location.path).toEqual("/Users/Shared/misko/work/angular.js/scenario/widgets.html"); expect(scope.$location.search).toEqual({}); expect(scope.$location.hash).toEqual(''); expect(scope.$location.hashPath).toEqual(''); expect(scope.$location.hashSearch).toEqual({}); }); it('should update hashPath and hashSearch on hash update', function(){ scope.$location.update('http://server/#path?a=b'); scope.$eval(); scope.$location.update({hash: ''}); expect(scope.$location.hashPath).toEqual(''); expect(scope.$location.hashSearch).toEqual({}); }); it('should update hash on hashPath or hashSearch update', function() { scope.$location.update('http://server/#path?a=b'); scope.$eval(); scope.$location.update({hashPath: '', hashSearch: {}}); expect(scope.$location.hash).toEqual(''); }); it('should update hashPath and hashSearch on hash property change', function(){ scope.$location.update('http://server/#path?a=b'); scope.$eval(); scope.$location.hash = ''; expect(scope.$location.toString()).toEqual('http://server/'); expect(scope.$location.hashPath).toEqual(''); expect(scope.$location.hashSearch).toEqual({}); }); it('should update hash on hashPath or hashSearch property change', function() { scope.$location.update('http://server/#path?a=b'); scope.$eval(); scope.$location.hashPath = ''; scope.$location.hashSearch = {}; expect(scope.$location.toString()).toEqual('http://server/'); expect(scope.$location.hash).toEqual(''); }); it("should parse url which contains - in host", function(){ scope.$location.update('http://a-b1.c-d.09/path'); expect(scope.$location.href).toEqual('http://a-b1.c-d.09/path'); expect(scope.$location.protocol).toEqual('http'); expect(scope.$location.host).toEqual('a-b1.c-d.09'); expect(scope.$location.path).toEqual('/path'); }); it('should update hash before any processing', function(){ var scope = compile('
'); var log = ''; scope.$watch('$location.hash', function(){ log += this.$location.hashPath + ';'; }); expect(log).toEqual(';'); log = ''; scope.$location.hash = '/abc'; scope.$eval(); expect(log).toEqual('/abc;'); }); it('udpate() should accept hash object and update only given properties', function() { scope.$location.update("http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2="); scope.$location.update({host: 'new', port: 24}); expect(scope.$location.host).toEqual('new'); expect(scope.$location.port).toEqual(24); expect(scope.$location.protocol).toEqual('http'); expect(scope.$location.href).toEqual("http://new:24/p/a/t/h.html?query=value#path?key=value&flag&key2="); }); it('updateHash() should accept one string argument to update path', function() { scope.$location.updateHash('path'); expect(scope.$location.hash).toEqual('path'); expect(scope.$location.hashPath).toEqual('path'); }); it('updateHash() should accept one hash argument to update search', function() { scope.$location.updateHash({a: 'b'}); expect(scope.$location.hash).toEqual('?a=b'); expect(scope.$location.hashSearch).toEqual({a: 'b'}); }); it('updateHash() should accept path and search both', function() { scope.$location.updateHash('path', {a: 'b'}); expect(scope.$location.hash).toEqual('path?a=b'); expect(scope.$location.hashSearch).toEqual({a: 'b'}); expect(scope.$location.hashPath).toEqual('path'); }); }); describe("$invalidWidgets", function(){ it("should count number of invalid widgets", function(){ var scope = compile(''); jqLite(document.body).append(scope.$element); scope.$init(); expect(scope.$invalidWidgets.length).toEqual(1); scope.price = 123; scope.$eval(); expect(scope.$invalidWidgets.length).toEqual(0); scope.$element.remove(); scope.price = 'abc'; scope.$eval(); expect(scope.$invalidWidgets.length).toEqual(0); jqLite(document.body).append(scope.$element); scope.price = 'abcd'; //force revalidation, maybe this should be done automatically? scope.$eval(); expect(scope.$invalidWidgets.length).toEqual(1); jqLite(document.body).html(''); scope.$eval(); expect(scope.$invalidWidgets.length).toEqual(0); }); }); describe("$route", function(){ it('should route and fire change event', function(){ var log = ''; function BookChapter() { this.log = ''; } var scope = compile('
').$init(); 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.update('http://server#/Book/Moby/Chapter/Intro?p=123'); scope.$eval(); expect(log).toEqual('onChange();'); expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', p:'123'}); expect($route.current.scope.log).toEqual(''); var lastId = $route.current.scope.$id; log = ''; scope.$location.update('http://server#/Blank?ignore'); scope.$eval(); expect(log).toEqual('onChange();'); expect($route.current.params).toEqual({ignore:true}); expect($route.current.scope.$id).not.toEqual(lastId); log = ''; scope.$location.update('http://server#/NONE'); scope.$eval(); expect(log).toEqual('onChange();'); expect($route.current).toEqual(null); $route.when('/NONE', {template:'instant update'}); scope.$eval(); expect($route.current.template).toEqual('instant update'); }); }); describe('$resource', function(){ it('should publish to root scope', function(){ expect(scope.$inject('$resource')).toBeTruthy(); }); }); describe('$xhr', function(){ var log; function callback(code, response) { expect(code).toEqual(200); log = log + toJson(response) + ';'; } beforeEach(function(){ log = ''; }); it('should forward the request to $browser and decode JSON', function(){ $browserXhr.expectGET('/reqGET').respond('first'); $browserXhr.expectGET('/reqGETjson').respond('["second"]'); $browserXhr.expectPOST('/reqPOST', {post:'data'}).respond('third'); $xhr('GET', '/reqGET', null, callback); $xhr('GET', '/reqGETjson', null, callback); $xhr('POST', '/reqPOST', {post:'data'}, callback); $browserXhr.flush(); expect(log).toEqual('"third";["second"];"first";'); }); it('should handle non 200 status codes by forwarding to error handler', function(){ $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( {url:'/req', method:'POST', data:'MyData', callback:cb}, {status:500, body:'MyError'}); }); it('should handle exceptions in callback', function(){ $log.error = jasmine.createSpy('$log.error'); $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(){ $xhrBulk.urls["/"] = {match:/.*/}; $xhrBulk('GET', '/req1', null, callback); $xhrBulk('POST', '/req2', {post:'data'}, callback); $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'} ]); $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(){ $xhrBulk.urls['/'] = {match:/.*/}; $xhrBulk('GET', '/req1', null, callback); $xhrBulk('POST', '/req2', {post:'data'}, callback); $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'} ]); $xhrBulk.flush(function(){ log += 'DONE';}); $browserXhr.flush(); expect($xhrError).wasCalled(); var cb = $xhrError.mostRecentCall.args[0].callback; expect(typeof cb).toEqual($function); expect($xhrError).wasCalledWith( {url:'/req1', method:'GET', data:null, callback:cb}, {status:404, response:'NotFound'}); expect(log).toEqual('"second";DONE'); }); }); describe('cache', function(){ var cache; beforeEach(function(){ cache = scope.$inject('$xhr.cache'); }); it('should cache requests', function(){ $browserXhr.expectGET('/url').respond('first'); cache('GET', '/url', null, callback); $browserXhr.flush(); $browserXhr.expectGET('/url').respond('ERROR'); cache('GET', '/url', null, callback); $browserXhr.flush(); expect(log).toEqual('"first";"first";'); cache('GET', '/url', null, callback, false); $browserXhr.flush(); expect(log).toEqual('"first";"first";"first";'); }); it('should first return cache request, then return server request', function(){ $browserXhr.expectGET('/url').respond('first'); cache('GET', '/url', null, callback, true); $browserXhr.flush(); $browserXhr.expectGET('/url').respond('ERROR'); cache('GET', '/url', null, callback, true); expect(log).toEqual('"first";"first";'); $browserXhr.flush(); expect(log).toEqual('"first";"first";"ERROR";'); }); it('should serve requests from cache', function(){ cache.data.url = {value:'123'}; cache('GET', 'url', null, callback); expect(log).toEqual('"123";'); cache('GET', 'url', null, callback, false); expect(log).toEqual('"123";"123";'); }); it('should keep track of in flight requests and request only once', function(){ scope.$inject('$xhr.bulk').urls['/bulk'] = { match:function(url){ return url == '/url'; } }; $browserXhr.expectPOST('/bulk', { requests:[{method:'GET', url:'/url', data: null}] }).respond([ {status:200, response:'123'} ]); cache('GET', '/url', null, callback); cache('GET', '/url', null, callback); cache.delegate.flush(); $browserXhr.flush(); expect(log).toEqual('"123";"123";'); }); it('should clear cache on non GET', function(){ $browserXhr.expectPOST('abc', {}).respond({}); cache.data.url = {value:123}; cache('POST', 'abc', {}); expect(cache.data.url).toBeUndefined(); }); }); }); describe('$cookies', function() { var scope, $browser; beforeEach(function() { $browser = new MockBrowser(); $browser.cookieHash['preexisting'] = 'oldCookie'; scope = createScope(null, angularService, {$browser: $browser}); }); it('should provide access to existing cookies via object properties and keep them in sync', function(){ expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'}); // access internal cookie storage of the browser mock directly to simulate behavior of // document.cookie $browser.cookieHash['brandNew'] = 'cookie'; $browser.poll(); expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'brandNew':'cookie'}); $browser.cookieHash['brandNew'] = 'cookie2'; $browser.poll(); expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'brandNew':'cookie2'}); delete $browser.cookieHash['brandNew']; $browser.poll(); expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'}); }); it('should create or update a cookie when a value is assigned to a property', function() { scope.$cookies.oatmealCookie = 'nom nom'; scope.$eval(); expect($browser.cookies()). toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'}); scope.$cookies.oatmealCookie = 'gone'; scope.$eval(); expect($browser.cookies()). toEqual({'preexisting': 'oldCookie', 'oatmealCookie': 'gone'}); }); it('should ignore non-string values when asked to create a cookie', function() { scope.$cookies.nonString = [1, 2, 3]; scope.$eval(); expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'}); expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'}); }); it('should drop any null or undefined properties', function() { scope.$cookies.nullVal = null; scope.$cookies.undefVal = undefined; scope.$eval(); 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(); $browser.poll(); expect($browser.cookies()). toEqual({'preexisting': 'oldCookie', 'oatmealCookie':'nom nom'}); delete scope.$cookies.oatmealCookie; scope.$eval(); expect($browser.cookies()).toEqual({'preexisting': 'oldCookie'}); }); it('should drop or reset cookies that browser refused to store', function() { var i, longVal; for (i=0; i<5000; i++) { longVal += '*'; } //drop if no previous value scope.$cookies.longCookie = longVal; scope.$eval(); expect(scope.$cookies).toEqual({'preexisting': 'oldCookie'}); //reset if previous value existed scope.$cookies.longCookie = 'shortVal'; scope.$eval(); expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'longCookie': 'shortVal'}); scope.$cookies.longCookie = longVal; scope.$eval(); expect(scope.$cookies).toEqual({'preexisting': 'oldCookie', 'longCookie': 'shortVal'}); }); }); describe('$cookieStore', function() { it('should serialize objects to json', function() { scope.$cookieStore.put('objectCookie', {id: 123, name: 'blah'}); scope.$eval(); //force eval in test expect($browser.cookies()).toEqual({'objectCookie': '{"id":123,"name":"blah"}'}); }); it('should deserialize json to object', function() { $browser.cookies('objectCookie', '{"id":123,"name":"blah"}'); $browser.poll(); expect(scope.$cookieStore.get('objectCookie')).toEqual({id: 123, name: 'blah'}); }); 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($browser.cookies()).toEqual({'gonner': '{"I\'ll":"Be Back"}'}); }); }); });