describe("service", function(){ var scope, $xhrError, $log, mockServices, $browser, $browserXhr, $xhrBulk, $xhr; beforeEach(function(){ $xhrError = jasmine.createSpy('$xhr.error'); $log = {}; scope = createScope({}, angularService, { '$xhr.error': $xhrError, '$log': $log }); $browser = scope.$service('$browser'); $browserXhr = $browser.xhr; $xhrBulk = scope.$service('$xhr.bulk'); $xhr = scope.$service('$xhr'); }); afterEach(function(){ dealoc(scope); }); it("should inject $window", function(){ expect(scope.$service('$window')).toEqual(window); }); 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:''}]}), $log = scope.$service('$log'); $log.log(); $log.warn(); $log.info(); $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:''}]}); var $log = scope.$service('$log'); $log.log(); $log.warn(); $log.info(); $log.error(); expect(logger).toEqual('log;log;log;log;'); }); it('should use noop if no console', function(){ var scope = createScope({}, angularService, {$window: {}, $document:[{cookie:''}]}), $log = scope.$service('$log'); $log.log(); $log.warn(); $log.info(); $log.error(); }); describe('Error', function(){ var e, $log, $console, errorArgs; beforeEach(function(){ e = new Error(''); e.message = undefined; e.sourceURL = undefined; e.line = undefined; e.stack = undefined; $console = angular.service('$log')({console:{error:function(){ errorArgs = arguments; }}}); }); it('should pass error if does not have trace', function(){ $console.error('abc', e); expect(errorArgs).toEqual(['abc', e]); }); it('should print stack', function(){ e.stack = 'stack'; $console.error('abc', e); expect(errorArgs).toEqual(['abc', 'stack']); }); it('should print line', function(){ e.message = 'message'; e.sourceURL = 'sourceURL'; e.line = '123'; $console.error('abc', e); expect(errorArgs).toEqual(['abc', 'message\nsourceURL:123']); }); }); }); describe("$exceptionHandler", function(){ it('should log errors', function(){ var error = ''; $log.error = function(m) { error += m; }; scope.$service('$exceptionHandler')('myError'); expect(error).toEqual('myError'); }); }); describe("$location", function(){ var $location; beforeEach(function() { $location = scope.$service('$location'); }); 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='; $location.update(href); expect($location.href).toEqual(href); expect($location.protocol).toEqual("http"); expect($location.host).toEqual("host"); expect($location.port).toEqual("123"); expect($location.path).toEqual("/p/a/t/h.html"); expect($location.search).toEqual({query:'value'}); expect($location.hash).toEqual('path?key=value&flag&key2='); expect($location.hashPath).toEqual('path'); expect($location.hashSearch).toEqual({key: 'value', flag: true, key2: ''}); }); it('should update location when browser url changed', function() { var origUrl = $location.href; expect(origUrl).toEqual($browser.getUrl()); var newUrl = 'http://somenew/url#foo'; $browser.setUrl(newUrl); $browser.poll(); expect($location.href).toEqual(newUrl); }); it('toString() should return actual representation', function() { var href = 'http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2='; $location.update(href); expect($location.toString()).toEqual(href); scope.$eval(); $location.host = 'new'; $location.path = ''; expect($location.toString()).toEqual('http://new:123?query=value#path?key=value&flag&key2='); }); it('toString() should not update browser', function() { var url = $browser.getUrl(); $location.update('http://www.angularjs.org'); expect($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(); $location.update('http://www.angularjs.org/'); $location.update({path: '/a/b'}); expect($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 update hashPath and hashSearch on hash update', function(){ $location.update('http://server/#path?a=b'); scope.$eval(); $location.update({hash: ''}); expect($location.hashPath).toEqual(''); expect($location.hashSearch).toEqual({}); }); it('should update hash on hashPath or hashSearch update', function() { $location.update('http://server/#path?a=b'); scope.$eval(); $location.update({hashPath: '', hashSearch: {}}); expect($location.hash).toEqual(''); }); it('should update hashPath and hashSearch on hash property change', function(){ $location.update('http://server/#path?a=b'); scope.$eval(); $location.hash = ''; expect($location.toString()).toEqual('http://server/'); expect($location.hashPath).toEqual(''); expect($location.hashSearch).toEqual({}); }); it('should update hash on hashPath or hashSearch property change', function() { $location.update('http://server/#path?a=b'); scope.$eval(); $location.hashPath = ''; $location.hashSearch = {}; expect($location.toString()).toEqual('http://server/'); expect($location.hash).toEqual(''); }); it('should update hash before any processing', function(){ scope = compile('
'); scope.$location = scope.$service('$location'); 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() { $location.update("http://host:123/p/a/t/h.html?query=value#path?key=value&flag&key2="); $location.update({host: 'new', port: 24}); expect($location.host).toEqual('new'); expect($location.port).toEqual(24); expect($location.protocol).toEqual('http'); expect($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() { $location.updateHash('path'); expect($location.hash).toEqual('path'); expect($location.hashPath).toEqual('path'); }); it('updateHash() should accept one hash argument to update search', function() { $location.updateHash({a: 'b'}); expect($location.hash).toEqual('?a=b'); expect($location.hashSearch).toEqual({a: 'b'}); }); it('updateHash() should accept path and search both', function() { $location.updateHash('path', {a: 'b'}); expect($location.hash).toEqual('path?a=b'); expect($location.hashSearch).toEqual({a: 'b'}); expect($location.hashPath).toEqual('path'); }); it('should remove # if hash is empty', function() { $location.update('http://www.angularjs.org/index.php#'); expect($location.href).toEqual('http://www.angularjs.org/index.php'); }); it('should not change browser\'s url with empty hash', function() { $browser.setUrl('http://www.angularjs.org/index.php#'); spyOn($browser, 'setUrl'); $browser.poll(); expect($browser.setUrl).not.toHaveBeenCalled(); }); }); describe("$invalidWidgets", function(){ it("should count number of invalid widgets", function(){ scope = compile(''); jqLite(document.body).append(scope.$element); scope.$init(); var $invalidWidgets = scope.$service('$invalidWidgets'); expect($invalidWidgets.length).toEqual(1); scope.price = 123; scope.$eval(); expect($invalidWidgets.length).toEqual(0); scope.$element.remove(); scope.price = 'abc'; scope.$eval(); expect($invalidWidgets.length).toEqual(0); jqLite(document.body).append(scope.$element); scope.price = 'abcd'; //force revalidation, maybe this should be done automatically? scope.$eval(); expect($invalidWidgets.length).toEqual(1); jqLite(document.body).html(''); scope.$eval(); expect($invalidWidgets.length).toEqual(0); }); }); describe("$route", function(){ it('should route and fire change event', function(){ var log = '', $location, $route; function BookChapter() { this.log = ''; } scope = compile('
').$init(); $location = scope.$service('$location'); $route = scope.$service('$route'); $route.when('/Book/:book/Chapter/:chapter', {controller: BookChapter, template:'Chapter.html'}); $route.when('/Blank'); $route.onChange(function(){ log += 'onChange();'; }); $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 = ''; $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 = ''; $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('$defer', function() { var $defer, $exceptionHandler; beforeEach(function(){ scope = createScope({}, angularService, { '$exceptionHandler': jasmine.createSpy('$exceptionHandler') }); $browser = scope.$service('$browser'); $defer = scope.$service('$defer'); $exceptionHandler = scope.$service('$exceptionHandler'); }); it('should delegate functions to $browser.defer', function() { var counter = 0; $defer(function() { counter++; }); expect(counter).toBe(0); $browser.defer.flush(); expect(counter).toBe(1); $browser.defer.flush(); //does nothing expect(counter).toBe(1); expect($exceptionHandler).not.toHaveBeenCalled(); }); it('should delegate exception to the $exceptionHandler service', function() { $defer(function() {throw "Test Error";}); expect($exceptionHandler).not.toHaveBeenCalled(); $browser.defer.flush(); expect($exceptionHandler).toHaveBeenCalledWith("Test Error"); }); it('should call eval after each callback is executed', function() { var eval = this.spyOn(scope, '$eval').andCallThrough(); $defer(function() {}); expect(eval).wasNotCalled(); $browser.defer.flush(); expect(eval).wasCalled(); eval.reset(); //reset the spy; $defer(function() {}); $defer(function() {}); $browser.defer.flush(); expect(eval.callCount).toBe(2); }); it('should call eval even if an exception is thrown in callback', function() { var eval = this.spyOn(scope, '$eval').andCallThrough(); $defer(function() {throw "Test Error";}); expect(eval).wasNotCalled(); $browser.defer.flush(); expect(eval).wasCalled(); }); }); 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.$service('$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); $browser.defer.flush(); $browserXhr.flush(); expect(log).toEqual('"first";"first";'); cache('GET', '/url', null, callback, false); $browser.defer.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); $browser.defer.flush(); 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); $browser.defer.flush(); expect(log).toEqual('"123";'); cache('GET', 'url', null, callback, false); $browser.defer.flush(); expect(log).toEqual('"123";"123";'); }); it('should keep track of in flight requests and request only once', function(){ scope.$service('$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(); }); it('should call callback asynchronously for both cache hit and cache miss', function() { $browserXhr.expectGET('/url').respond('+'); cache('GET', '/url', null, callback); expect(log).toEqual(''); //callback hasn't executed $browserXhr.flush(); expect(log).toEqual('"+";'); //callback has executed cache('GET', '/url', null, callback); expect(log).toEqual('"+";'); //callback hasn't executed $browser.defer.flush(); expect(log).toEqual('"+";"+";'); //callback has executed }); it('should call eval after callbacks for both cache hit and cache miss execute', function() { var eval = this.spyOn(scope, '$eval').andCallThrough(); $browserXhr.expectGET('/url').respond('+'); cache('GET', '/url', null, callback); expect(eval).wasNotCalled(); $browserXhr.flush(); expect(eval).wasCalled(); eval.reset(); //reset the spy cache('GET', '/url', null, callback); expect(eval).wasNotCalled(); $browser.defer.flush(); expect(eval).wasCalled(); }); }); }); describe('$cookies', function() { var scope, $browser; beforeEach(function() { $browser = new MockBrowser(); $browser.cookieHash['preexisting'] = 'oldCookie'; scope = createScope(null, angularService, {$browser: $browser}); scope.$cookies = scope.$service('$cookies'); }); 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.$service('$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.$service('$cookieStore').get('objectCookie')).toEqual({id: 123, name: 'blah'}); }); it('should delete objects from the store when remove is called', function() { scope.$service('$cookieStore').put('gonner', { "I'll":"Be Back"}); scope.$eval(); //force eval in test expect($browser.cookies()).toEqual({'gonner': '{"I\'ll":"Be Back"}'}); }); }); describe('URL_MATCH', function() { it('should parse basic url', function() { var match = URL_MATCH.exec('http://www.angularjs.org/path?search#hash?x=x'); expect(match[1]).toEqual('http'); expect(match[3]).toEqual('www.angularjs.org'); expect(match[6]).toEqual('/path'); expect(match[8]).toEqual('search'); expect(match[10]).toEqual('hash?x=x'); }); it('should parse file://', function(){ var match = URL_MATCH.exec('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html'); expect(match[1]).toEqual('file'); expect(match[3]).toEqual(''); expect(match[5]).toBeFalsy(); expect(match[6]).toEqual('/Users/Shared/misko/work/angular.js/scenario/widgets.html'); expect(match[8]).toBeFalsy(); }); it('should parse url with "-" in host', function(){ var match = URL_MATCH.exec('http://a-b1.c-d.09/path'); expect(match[1]).toEqual('http'); expect(match[3]).toEqual('a-b1.c-d.09'); expect(match[5]).toBeFalsy(); expect(match[6]).toEqual('/path'); expect(match[8]).toBeFalsy(); }); it('should parse host without "/" at the end', function() { var match = URL_MATCH.exec('http://host.org'); expect(match[3]).toEqual('host.org'); match = URL_MATCH.exec('http://host.org#'); expect(match[3]).toEqual('host.org'); match = URL_MATCH.exec('http://host.org?'); expect(match[3]).toEqual('host.org'); }); it('should match with just "/" path', function() { var match = URL_MATCH.exec('http://server/#?book=moby'); expect(match[10]).toEqual('?book=moby'); }); }); describe('$updateView', function(){ var scope, browser, evalCount, $updateView; beforeEach(function(){ browser = new MockBrowser(); // Pretend that you are real Browser so that we see the delays browser.isMock = false; browser.defer = jasmine.createSpy('defer'); scope = angular.scope(null, null, {$browser:browser}); $updateView = scope.$service('$updateView'); scope.$onEval(function(){ evalCount++; }); evalCount = 0; }); it('should eval root scope after a delay', function(){ $updateView(); expect(evalCount).toEqual(0); expect(browser.defer).toHaveBeenCalled(); expect(browser.defer.mostRecentCall.args[1]).toEqual(25); browser.defer.mostRecentCall.args[0](); expect(evalCount).toEqual(1); }); it('should allow changing of delay time', function(){ var oldValue = angular.service('$updateView').delay; angular.service('$updateView').delay = 50; $updateView(); expect(evalCount).toEqual(0); expect(browser.defer).toHaveBeenCalled(); expect(browser.defer.mostRecentCall.args[1]).toEqual(50); angular.service('$updateView').delay = oldValue; }); it('should ignore multiple requests for update', function(){ $updateView(); $updateView(); expect(evalCount).toEqual(0); expect(browser.defer).toHaveBeenCalled(); expect(browser.defer.callCount).toEqual(1); browser.defer.mostRecentCall.args[0](); expect(evalCount).toEqual(1); }); it('should update immediatelly in test/mock mode', function(){ scope = angular.scope(); scope.$onEval(function(){ evalCount++; }); expect(evalCount).toEqual(0); scope.$service('$updateView')(); expect(evalCount).toEqual(1); }); }); });