/* global LocationHashbangUrl: false, LocationHtml5Url: false */ 'use strict'; describe('$location', function() { // Mock out the $log function - see testabilityPatch.js beforeEach(module(provideLog)); afterEach(function() { // link rewriting used in html5 mode on legacy browsers binds to document.onClick, so we need // to clean this up after each test. jqLite(window.document).off('click'); }); describe('defaults', function() { it('should have hashPrefix of "!"', function() { initService({}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), function($location) { $location.path('/a/b/c'); expect($location.absUrl()).toEqual('http://host.com/base/index.html#!/a/b/c'); }); }); it('should not be html5 mode', function() { initService({}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), function($location) { $location.path('/a/b/c'); expect($location.absUrl()).toContain('#!'); }); }); }); describe('File Protocol', function() { /* global urlParsingNode: true */ var urlParsingNodePlaceholder; beforeEach(function() { // Support: non-Windows browsers // These tests expect a Windows environment which we can only guarantee // on IE & Edge. if (msie || /\bEdge\/[\d.]+\b/.test(window.navigator.userAgent)) return; urlParsingNodePlaceholder = urlParsingNode; //temporarily overriding the DOM element //with output from IE, if not in IE urlParsingNode = { hash: '#/C:/', host: '', hostname: '', href: 'file:///C:/base#!/C:/foo', pathname: '/C:/foo', port: '', protocol: 'file:', search: '', setAttribute: angular.noop }; }); afterEach(function() { // Support: non-Windows browsers if (msie || /\bEdge\/[\d.]+\b/.test(window.navigator.userAgent)) return; //reset urlParsingNode urlParsingNode = urlParsingNodePlaceholder; }); it('should not include the drive name in path() on WIN', function() { //See issue #4680 for details var locationUrl = new LocationHashbangUrl('file:///base', 'file:///', '#!'); locationUrl.$$parse('file:///base#!/foo?a=b&c#hash'); expect(locationUrl.path()).toBe('/foo'); }); it('should include the drive name if it was provided in the input url', function() { var locationUrl = new LocationHashbangUrl('file:///base', 'file:///', '#!'); locationUrl.$$parse('file:///base#!/C:/foo?a=b&c#hash'); expect(locationUrl.path()).toBe('/C:/foo'); }); }); describe('NewUrl', function() { function createLocationHtml5Url() { var locationUrl = new LocationHtml5Url('http://www.domain.com:9877/', 'http://www.domain.com:9877/'); locationUrl.$$parse('http://www.domain.com:9877/path/b?search=a&b=c&d#hash'); return locationUrl; } it('should provide common getters', function() { var locationUrl = createLocationHtml5Url(); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#hash'); expect(locationUrl.protocol()).toBe('http'); expect(locationUrl.host()).toBe('www.domain.com'); expect(locationUrl.port()).toBe(9877); expect(locationUrl.path()).toBe('/path/b'); expect(locationUrl.search()).toEqual({search: 'a', b: 'c', d: true}); expect(locationUrl.hash()).toBe('hash'); expect(locationUrl.url()).toBe('/path/b?search=a&b=c&d#hash'); }); it('path() should change path', function() { var locationUrl = createLocationHtml5Url(); locationUrl.path('/new/path'); expect(locationUrl.path()).toBe('/new/path'); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/new/path?search=a&b=c&d#hash'); }); it('path() should not break on numeric values', function() { var locationUrl = createLocationHtml5Url(); locationUrl.path(1); expect(locationUrl.path()).toBe('/1'); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/1?search=a&b=c&d#hash'); }); it('path() should allow using 0 as path', function() { var locationUrl = createLocationHtml5Url(); locationUrl.path(0); expect(locationUrl.path()).toBe('/0'); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/0?search=a&b=c&d#hash'); }); it('path() should set to empty path on null value', function() { var locationUrl = createLocationHtml5Url(); locationUrl.path('/foo'); expect(locationUrl.path()).toBe('/foo'); locationUrl.path(null); expect(locationUrl.path()).toBe('/'); }); it('search() should accept string', function() { var locationUrl = createLocationHtml5Url(); locationUrl.search('x=y&c'); expect(locationUrl.search()).toEqual({x: 'y', c: true}); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/path/b?x=y&c#hash'); }); it('search() should accept object', function() { var locationUrl = createLocationHtml5Url(); locationUrl.search({one: 1, two: true}); expect(locationUrl.search()).toEqual({one: 1, two: true}); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/path/b?one=1&two#hash'); }); it('search() should copy object', function() { var locationUrl = createLocationHtml5Url(); var obj = {one: 1, two: true, three: null}; locationUrl.search(obj); expect(obj).toEqual({one: 1, two: true, three: null}); obj.one = 'changed'; expect(locationUrl.search()).toEqual({one: 1, two: true}); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/path/b?one=1&two#hash'); }); it('search() should change single parameter', function() { var locationUrl = createLocationHtml5Url(); locationUrl.search({id: 'old', preserved: true}); locationUrl.search('id', 'new'); expect(locationUrl.search()).toEqual({id: 'new', preserved: true}); }); it('search() should remove single parameter', function() { var locationUrl = createLocationHtml5Url(); locationUrl.search({id: 'old', preserved: true}); locationUrl.search('id', null); expect(locationUrl.search()).toEqual({preserved: true}); }); it('search() should remove multiple parameters', function() { var locationUrl = createLocationHtml5Url(); locationUrl.search({one: 1, two: true}); expect(locationUrl.search()).toEqual({one: 1, two: true}); locationUrl.search({one: null, two: null}); expect(locationUrl.search()).toEqual({}); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/path/b#hash'); }); it('search() should accept numeric keys', function() { var locationUrl = createLocationHtml5Url(); locationUrl.search({1: 'one', 2: 'two'}); expect(locationUrl.search()).toEqual({'1': 'one', '2': 'two'}); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/path/b?1=one&2=two#hash'); }); it('search() should handle multiple value', function() { var locationUrl = createLocationHtml5Url(); locationUrl.search('a&b'); expect(locationUrl.search()).toEqual({a: true, b: true}); locationUrl.search('a', null); expect(locationUrl.search()).toEqual({b: true}); locationUrl.search('b', undefined); expect(locationUrl.search()).toEqual({}); }); it('search() should handle single value', function() { var locationUrl = createLocationHtml5Url(); locationUrl.search('ignore'); expect(locationUrl.search()).toEqual({ignore: true}); locationUrl.search(1); expect(locationUrl.search()).toEqual({1: true}); }); it('search() should throw error an incorrect argument', function() { var locationUrl = createLocationHtml5Url(); expect(function() { locationUrl.search(null); }).toThrowMinErr('$location', 'isrcharg', 'The first argument of the `$location#search()` call must be a string or an object.'); expect(function() { locationUrl.search(undefined); }).toThrowMinErr('$location', 'isrcharg', 'The first argument of the `$location#search()` call must be a string or an object.'); }); it('hash() should change hash fragment', function() { var locationUrl = createLocationHtml5Url(); locationUrl.hash('new-hash'); expect(locationUrl.hash()).toBe('new-hash'); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#new-hash'); }); it('hash() should accept numeric parameter', function() { var locationUrl = createLocationHtml5Url(); locationUrl.hash(5); expect(locationUrl.hash()).toBe('5'); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#5'); }); it('hash() should allow using 0', function() { var locationUrl = createLocationHtml5Url(); locationUrl.hash(0); expect(locationUrl.hash()).toBe('0'); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#0'); }); it('hash() should accept null parameter', function() { var locationUrl = createLocationHtml5Url(); locationUrl.hash(null); expect(locationUrl.hash()).toBe(''); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d'); }); it('url() should change the path, search and hash', function() { var locationUrl = createLocationHtml5Url(); locationUrl.url('/some/path?a=b&c=d#hhh'); expect(locationUrl.url()).toBe('/some/path?a=b&c=d#hhh'); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/some/path?a=b&c=d#hhh'); expect(locationUrl.path()).toBe('/some/path'); expect(locationUrl.search()).toEqual({a: 'b', c: 'd'}); expect(locationUrl.hash()).toBe('hhh'); }); it('url() should change only hash when no search and path specified', function() { var locationUrl = createLocationHtml5Url(); locationUrl.url('#some-hash'); expect(locationUrl.hash()).toBe('some-hash'); expect(locationUrl.url()).toBe('/path/b?search=a&b=c&d#some-hash'); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/path/b?search=a&b=c&d#some-hash'); }); it('url() should change only search and hash when no path specified', function() { var locationUrl = createLocationHtml5Url(); locationUrl.url('?a=b'); expect(locationUrl.search()).toEqual({a: 'b'}); expect(locationUrl.hash()).toBe(''); expect(locationUrl.path()).toBe('/path/b'); }); it('url() should reset search and hash when only path specified', function() { var locationUrl = createLocationHtml5Url(); locationUrl.url('/new/path'); expect(locationUrl.path()).toBe('/new/path'); expect(locationUrl.search()).toEqual({}); expect(locationUrl.hash()).toBe(''); }); it('url() should change path when empty string specified', function() { var locationUrl = createLocationHtml5Url(); locationUrl.url(''); expect(locationUrl.path()).toBe('/'); expect(locationUrl.search()).toEqual({}); expect(locationUrl.hash()).toBe(''); }); it('replace should set $$replace flag and return itself', function() { var locationUrl = createLocationHtml5Url(); expect(locationUrl.$$replace).toBe(false); locationUrl.replace(); expect(locationUrl.$$replace).toBe(true); expect(locationUrl.replace()).toBe(locationUrl); }); it('should parse new url', function() { var locationUrl = new LocationHtml5Url('http://host.com/', 'http://host.com/'); locationUrl.$$parse('http://host.com/base'); expect(locationUrl.path()).toBe('/base'); locationUrl = new LocationHtml5Url('http://host.com/', 'http://host.com/'); locationUrl.$$parse('http://host.com/base#'); expect(locationUrl.path()).toBe('/base'); }); it('should prefix path with forward-slash', function() { var locationUrl = new LocationHtml5Url('http://server/', 'http://server/'); locationUrl.path('b'); expect(locationUrl.path()).toBe('/b'); expect(locationUrl.absUrl()).toBe('http://server/b'); }); it('should set path to forward-slash when empty', function() { var locationUrl = new LocationHtml5Url('http://server/', 'http://server/'); locationUrl.$$parse('http://server/'); expect(locationUrl.path()).toBe('/'); expect(locationUrl.absUrl()).toBe('http://server/'); }); it('setters should return Url object to allow chaining', function() { var locationUrl = createLocationHtml5Url(); expect(locationUrl.path('/any')).toBe(locationUrl); expect(locationUrl.search('')).toBe(locationUrl); expect(locationUrl.hash('aaa')).toBe(locationUrl); expect(locationUrl.url('/some')).toBe(locationUrl); }); it('should not preserve old properties when parsing new url', function() { var locationUrl = createLocationHtml5Url(); locationUrl.$$parse('http://www.domain.com:9877/a'); expect(locationUrl.path()).toBe('/a'); expect(locationUrl.search()).toEqual({}); expect(locationUrl.hash()).toBe(''); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/a'); }); it('should not rewrite when hashbang url is not given', function() { initService({html5Mode:true,hashPrefix: '!',supportHistory: true}); inject( initBrowser({url:'http://domain.com/base/a/b',basePath: '/base'}), function($rootScope, $location, $browser) { expect($browser.url()).toBe('http://domain.com/base/a/b'); } ); }); it('should prepend path with basePath', function() { var locationUrl = new LocationHtml5Url('http://server/base/', 'http://server/base/'); locationUrl.$$parse('http://server/base/abc?a'); expect(locationUrl.path()).toBe('/abc'); expect(locationUrl.search()).toEqual({a: true}); locationUrl.path('/new/path'); expect(locationUrl.absUrl()).toBe('http://server/base/new/path?a'); }); it('should throw error when invalid server url given', function() { var locationUrl = new LocationHtml5Url('http://server.org/base/abc', 'http://server.org/base/'); expect(function() { locationUrl.$$parse('http://other.server.org/path#/path'); }).toThrowMinErr('$location', 'ipthprfx', 'Invalid url "http://other.server.org/path#/path", missing path prefix "http://server.org/base/".'); }); it('should throw error when invalid base url given', function() { var locationUrl = new LocationHtml5Url('http://server.org/base/abc', 'http://server.org/base/'); expect(function() { locationUrl.$$parse('http://server.org/path#/path'); }).toThrowMinErr('$location', 'ipthprfx', 'Invalid url "http://server.org/path#/path", missing path prefix "http://server.org/base/".'); }); describe('state', function() { it('should set $$state and return itself', function() { var locationUrl = createLocationHtml5Url(); expect(locationUrl.$$state).toEqual(undefined); var returned = locationUrl.state({a: 2}); expect(locationUrl.$$state).toEqual({a: 2}); expect(returned).toBe(locationUrl); }); it('should set state', function() { var locationUrl = createLocationHtml5Url(); locationUrl.state({a: 2}); expect(locationUrl.state()).toEqual({a: 2}); }); it('should allow to set both URL and state', function() { var locationUrl = createLocationHtml5Url(); locationUrl.url('/foo').state({a: 2}); expect(locationUrl.url()).toEqual('/foo'); expect(locationUrl.state()).toEqual({a: 2}); }); it('should allow to mix state and various URL functions', function() { var locationUrl = createLocationHtml5Url(); locationUrl.path('/foo').hash('abcd').state({a: 2}).search('bar', 'baz'); expect(locationUrl.path()).toEqual('/foo'); expect(locationUrl.state()).toEqual({a: 2}); expect(locationUrl.search() && locationUrl.search().bar).toBe('baz'); expect(locationUrl.hash()).toEqual('abcd'); }); }); describe('encoding', function() { it('should encode special characters', function() { var locationUrl = createLocationHtml5Url(); locationUrl.path('/a <>#'); locationUrl.search({'i j': '<>#'}); locationUrl.hash('<>#'); expect(locationUrl.path()).toBe('/a <>#'); expect(locationUrl.search()).toEqual({'i j': '<>#'}); expect(locationUrl.hash()).toBe('<>#'); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/a%20%3C%3E%23?i%20j=%3C%3E%23#%3C%3E%23'); }); it('should not encode !$:@', function() { var locationUrl = createLocationHtml5Url(); locationUrl.path('/!$:@'); locationUrl.search(''); locationUrl.hash('!$:@'); expect(locationUrl.absUrl()).toBe('http://www.domain.com:9877/!$:@#!$:@'); }); it('should decode special characters', function() { var locationUrl = new LocationHtml5Url('http://host.com/', 'http://host.com/'); locationUrl.$$parse('http://host.com/a%20%3C%3E%23?i%20j=%3C%3E%23#x%20%3C%3E%23'); expect(locationUrl.path()).toBe('/a <>#'); expect(locationUrl.search()).toEqual({'i j': '<>#'}); expect(locationUrl.hash()).toBe('x <>#'); }); it('should not decode encoded forward slashes in the path', function() { var locationUrl = new LocationHtml5Url('http://host.com/base/', 'http://host.com/base/'); locationUrl.$$parse('http://host.com/base/a/ng2;path=%2Fsome%2Fpath'); expect(locationUrl.path()).toBe('/a/ng2;path=%2Fsome%2Fpath'); expect(locationUrl.search()).toEqual({}); expect(locationUrl.hash()).toBe(''); expect(locationUrl.url()).toBe('/a/ng2;path=%2Fsome%2Fpath'); expect(locationUrl.absUrl()).toBe('http://host.com/base/a/ng2;path=%2Fsome%2Fpath'); }); it('should decode pluses as spaces in urls', function() { var locationUrl = new LocationHtml5Url('http://host.com/', 'http://host.com/'); locationUrl.$$parse('http://host.com/?a+b=c+d'); expect(locationUrl.search()).toEqual({'a b':'c d'}); }); it('should retain pluses when setting search queries', function() { var locationUrl = createLocationHtml5Url(); locationUrl.search({'a+b':'c+d'}); expect(locationUrl.search()).toEqual({'a+b':'c+d'}); }); }); }); describe('HashbangUrl', function() { function createHashbangUrl() { var locationUrl = new LocationHashbangUrl('http://www.server.org:1234/base', 'http://www.server.org:1234/', '#!'); locationUrl.$$parse('http://www.server.org:1234/base#!/path?a=b&c#hash'); return locationUrl; } it('should parse hashbang url into path and search', function() { var locationUrl = createHashbangUrl(); expect(locationUrl.protocol()).toBe('http'); expect(locationUrl.host()).toBe('www.server.org'); expect(locationUrl.port()).toBe(1234); expect(locationUrl.path()).toBe('/path'); expect(locationUrl.search()).toEqual({a: 'b', c: true}); expect(locationUrl.hash()).toBe('hash'); }); it('absUrl() should return hashbang url', function() { var locationUrl = createHashbangUrl(); expect(locationUrl.absUrl()).toBe('http://www.server.org:1234/base#!/path?a=b&c#hash'); locationUrl.path('/new/path'); locationUrl.search({one: 1}); locationUrl.hash('hhh'); expect(locationUrl.absUrl()).toBe('http://www.server.org:1234/base#!/new/path?one=1#hhh'); }); it('should preserve query params in base', function() { var locationUrl = new LocationHashbangUrl('http://www.server.org:1234/base?base=param', 'http://www.server.org:1234/', '#'); locationUrl.$$parse('http://www.server.org:1234/base?base=param#/path?a=b&c#hash'); expect(locationUrl.absUrl()).toBe('http://www.server.org:1234/base?base=param#/path?a=b&c#hash'); locationUrl.path('/new/path'); locationUrl.search({one: 1}); locationUrl.hash('hhh'); expect(locationUrl.absUrl()).toBe('http://www.server.org:1234/base?base=param#/new/path?one=1#hhh'); }); it('should prefix path with forward-slash', function() { var locationUrl = new LocationHashbangUrl('http://host.com/base', 'http://host.com/', '#'); locationUrl.$$parse('http://host.com/base#path'); expect(locationUrl.path()).toBe('/path'); expect(locationUrl.absUrl()).toBe('http://host.com/base#/path'); locationUrl.path('wrong'); expect(locationUrl.path()).toBe('/wrong'); expect(locationUrl.absUrl()).toBe('http://host.com/base#/wrong'); }); it('should set path to forward-slash when empty', function() { var locationUrl = new LocationHashbangUrl('http://server/base', 'http://server/', '#!'); locationUrl.$$parse('http://server/base'); locationUrl.path('aaa'); expect(locationUrl.path()).toBe('/aaa'); expect(locationUrl.absUrl()).toBe('http://server/base#!/aaa'); }); it('should not preserve old properties when parsing new url', function() { var locationUrl = createHashbangUrl(); locationUrl.$$parse('http://www.server.org:1234/base#!/'); expect(locationUrl.path()).toBe('/'); expect(locationUrl.search()).toEqual({}); expect(locationUrl.hash()).toBe(''); expect(locationUrl.absUrl()).toBe('http://www.server.org:1234/base#!/'); }); it('should insert default hashbang if a hash is given with no hashbang prefix', function() { var locationUrl = createHashbangUrl(); locationUrl.$$parse('http://www.server.org:1234/base#/path'); expect(locationUrl.absUrl()).toBe('http://www.server.org:1234/base#!#%2Fpath'); expect(locationUrl.hash()).toBe('/path'); expect(locationUrl.path()).toBe(''); locationUrl.$$parse('http://www.server.org:1234/base#'); expect(locationUrl.absUrl()).toBe('http://www.server.org:1234/base'); expect(locationUrl.hash()).toBe(''); expect(locationUrl.path()).toBe(''); }); it('should ignore extra path segments if no hashbang is given', function() { var locationUrl = createHashbangUrl(); locationUrl.$$parse('http://www.server.org:1234/base/extra/path'); expect(locationUrl.absUrl()).toBe('http://www.server.org:1234/base'); expect(locationUrl.path()).toBe(''); expect(locationUrl.hash()).toBe(''); }); describe('encoding', function() { it('should encode special characters', function() { var locationUrl = createHashbangUrl(); locationUrl.path('/a <>#'); locationUrl.search({'i j': '<>#'}); locationUrl.hash('<>#'); expect(locationUrl.path()).toBe('/a <>#'); expect(locationUrl.search()).toEqual({'i j': '<>#'}); expect(locationUrl.hash()).toBe('<>#'); expect(locationUrl.absUrl()).toBe('http://www.server.org:1234/base#!/a%20%3C%3E%23?i%20j=%3C%3E%23#%3C%3E%23'); }); it('should not encode !$:@', function() { var locationUrl = createHashbangUrl(); locationUrl.path('/!$:@'); locationUrl.search(''); locationUrl.hash('!$:@'); expect(locationUrl.absUrl()).toBe('http://www.server.org:1234/base#!/!$:@#!$:@'); }); it('should decode special characters', function() { var locationUrl = new LocationHashbangUrl('http://host.com/a', 'http://host.com/', '#'); locationUrl.$$parse('http://host.com/a#/%20%3C%3E%23?i%20j=%3C%3E%23#x%20%3C%3E%23'); expect(locationUrl.path()).toBe('/ <>#'); expect(locationUrl.search()).toEqual({'i j': '<>#'}); expect(locationUrl.hash()).toBe('x <>#'); }); it('should return decoded characters for search specified in URL', function() { var locationUrl = new LocationHtml5Url('http://host.com/', 'http://host.com/'); locationUrl.$$parse('http://host.com/?q=1%2F2%203'); expect(locationUrl.search()).toEqual({'q': '1/2 3'}); }); it('should return decoded characters for search specified with setter', function() { var locationUrl = new LocationHtml5Url('http://host.com/', 'http://host.com/'); locationUrl.$$parse('http://host.com/'); locationUrl.search('q', '1/2 3'); expect(locationUrl.search()).toEqual({'q': '1/2 3'}); }); it('should return an array for duplicate params', function() { var locationUrl = new LocationHtml5Url('http://host.com', 'http://host.com'); locationUrl.$$parse('http://host.com'); locationUrl.search('q', ['1/2 3','4/5 6']); expect(locationUrl.search()).toEqual({'q': ['1/2 3','4/5 6']}); }); it('should encode an array correctly from search and add to url', function() { var locationUrl = new LocationHtml5Url('http://host.com', 'http://host.com'); locationUrl.$$parse('http://host.com'); locationUrl.search({'q': ['1/2 3','4/5 6']}); expect(locationUrl.absUrl()).toEqual('http://host.com?q=1%2F2%203&q=4%2F5%206'); }); it('should rewrite params when specifying a single param in search', function() { var locationUrl = new LocationHtml5Url('http://host.com', 'http://host.com'); locationUrl.$$parse('http://host.com'); locationUrl.search({'q': '1/2 3'}); expect(locationUrl.absUrl()).toEqual('http://host.com?q=1%2F2%203'); locationUrl.search({'q': '4/5 6'}); expect(locationUrl.absUrl()).toEqual('http://host.com?q=4%2F5%206'); }); it('url() should decode non-component special characters in hashbang mode', function() { var locationUrl = new LocationHashbangUrl('http://host.com', 'http://host.com'); locationUrl.$$parse('http://host.com'); locationUrl.url('/foo%3Abar'); expect(locationUrl.path()).toEqual('/foo:bar'); }); it('url() should decode non-component special characters in html5 mode', function() { var locationUrl = new LocationHtml5Url('http://host.com', 'http://host.com'); locationUrl.$$parse('http://host.com'); locationUrl.url('/foo%3Abar'); expect(locationUrl.path()).toEqual('/foo:bar'); }); }); }); describe('location watch', function() { it('should not update browser if only the empty hash fragment is cleared', function() { initService({supportHistory: true}); mockUpBrowser({initialUrl: 'http://new.com/a/b#', baseHref: '/base/'}); inject(function($browser, $rootScope) { $browser.url('http://new.com/a/b'); var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); $rootScope.$digest(); expect($browserUrl).not.toHaveBeenCalled(); }); }); it('should not replace browser url if only the empty hash fragment is cleared', function() { initService({html5Mode: true, supportHistory: true}); mockUpBrowser({initialUrl: 'http://new.com/#', baseHref: '/'}); inject(function($browser, $location, $window) { expect($browser.url()).toBe('http://new.com/'); expect($location.absUrl()).toBe('http://new.com/'); expect($window.location.href).toBe('http://new.com/#'); }); }); it('should not get caught in infinite digest when replacing path in locationChangeSuccess handler', function() { initService({html5Mode:true,supportHistory:false}); mockUpBrowser({initialUrl:'http://server/base/home', baseHref:'/base/'}); inject( function($browser, $location, $rootScope, $window) { var handlerCalled = false; $rootScope.$on('$locationChangeSuccess', function() { handlerCalled = true; if ($location.path() !== '/') { $location.path('/').replace(); } }); expect($browser.url()).toEqual('http://server/base/#!/home'); $rootScope.$digest(); expect(handlerCalled).toEqual(true); expect($browser.url()).toEqual('http://server/base/#!/'); } ); }); it('should not infinitely digest when using a semicolon in initial path', function() { initService({html5Mode:true,supportHistory:true}); mockUpBrowser({initialUrl:'http://localhost:9876/;jsessionid=foo', baseHref:'/'}); inject(function($location, $browser, $rootScope) { expect(function() { $rootScope.$digest(); }).not.toThrow(); }); }); //https://github.com/angular/angular.js/issues/16592 it('should not infinitely digest when initial params contain a quote', function() { initService({html5Mode:true,supportHistory:true}); mockUpBrowser({initialUrl:'http://localhost:9876/?q=\'', baseHref:'/'}); inject(function($location, $browser, $rootScope) { expect(function() { $rootScope.$digest(); }).not.toThrow(); }); }); //https://github.com/angular/angular.js/issues/16592 it('should not infinitely digest when initial params contain an escaped quote', function() { initService({html5Mode:true,supportHistory:true}); mockUpBrowser({initialUrl:'http://localhost:9876/?q=%27', baseHref:'/'}); inject(function($location, $browser, $rootScope) { expect(function() { $rootScope.$digest(); }).not.toThrow(); }); }); //https://github.com/angular/angular.js/issues/16592 it('should not infinitely digest when updating params containing a quote (via $browser.url)', function() { initService({html5Mode:true,supportHistory:true}); mockUpBrowser({initialUrl:'http://localhost:9876/', baseHref:'/'}); inject(function($location, $browser, $rootScope) { $rootScope.$digest(); $browser.url('http://localhost:9876/?q=\''); expect(function() { $rootScope.$digest(); }).not.toThrow(); }); }); //https://github.com/angular/angular.js/issues/16592 it('should not infinitely digest when updating params containing a quote (via window.location + popstate)', function() { initService({html5Mode:true,supportHistory:true}); mockUpBrowser({initialUrl:'http://localhost:9876/', baseHref:'/'}); inject(function($window, $location, $browser, $rootScope) { $rootScope.$digest(); $window.location.href = 'http://localhost:9876/?q=\''; expect(function() { jqLite($window).triggerHandler('popstate'); }).not.toThrow(); }); }); describe('when changing the browser URL/history directly during a `$digest`', function() { beforeEach(function() { initService({supportHistory: true}); mockUpBrowser({initialUrl: 'http://foo.bar/', baseHref: '/'}); }); it('should correctly update `$location` from history and not digest infinitely', inject( function($browser, $location, $rootScope, $window) { $location.url('baz'); $rootScope.$digest(); var originalUrl = $window.location.href; $rootScope.$apply(function() { $rootScope.$evalAsync(function() { $window.history.pushState({}, null, originalUrl + '/qux'); }); }); expect($browser.url()).toBe('http://foo.bar/#!/baz/qux'); expect($location.absUrl()).toBe('http://foo.bar/#!/baz/qux'); $rootScope.$apply(function() { $rootScope.$evalAsync(function() { $window.history.replaceState({}, null, originalUrl + '/quux'); }); }); expect($browser.url()).toBe('http://foo.bar/#!/baz/quux'); expect($location.absUrl()).toBe('http://foo.bar/#!/baz/quux'); }) ); it('should correctly update `$location` from URL and not digest infinitely', inject( function($browser, $location, $rootScope, $window) { $location.url('baz'); $rootScope.$digest(); $rootScope.$apply(function() { $rootScope.$evalAsync(function() { $window.location.href += '/qux'; }); }); jqLite($window).triggerHandler('hashchange'); expect($browser.url()).toBe('http://foo.bar/#!/baz/qux'); expect($location.absUrl()).toBe('http://foo.bar/#!/baz/qux'); }) ); }); function updatePathOnLocationChangeSuccessTo(newPath, newParams) { inject(function($rootScope, $location) { $rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) { $location.path(newPath); if (newParams) { $location.search(newParams); } }); }); } describe('location watch for hashbang browsers', function() { it('should not infinite $digest when going to base URL without trailing slash when $locationChangeSuccess watcher changes path to /Home', function() { initService({html5Mode: true, supportHistory: false}); mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'}); inject(function($rootScope, $location, $browser) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); updatePathOnLocationChangeSuccessTo('/Home'); $rootScope.$digest(); expect($browser.url()).toEqual('http://server/app/#!/Home'); expect($location.path()).toEqual('/Home'); expect($browserUrl).toHaveBeenCalledTimes(1); }); }); it('should not infinite $digest when going to base URL without trailing slash when $locationChangeSuccess watcher changes path to /', function() { initService({html5Mode: true, supportHistory: false}); mockUpBrowser({initialUrl:'http://server/app/Home', baseHref:'/app/'}); inject(function($rootScope, $location, $browser, $window) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); updatePathOnLocationChangeSuccessTo('/'); $rootScope.$digest(); expect($browser.url()).toEqual('http://server/app/#!/'); expect($location.path()).toEqual('/'); expect($browserUrl).toHaveBeenCalledTimes(1); expect($browserUrl.calls.argsFor(0)).toEqual(['http://server/app/#!/', false, null]); }); }); it('should not infinite $digest when going to base URL with trailing slash when $locationChangeSuccess watcher changes path to /Home', function() { initService({html5Mode: true, supportHistory: false}); mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'}); inject(function($rootScope, $location, $browser) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); updatePathOnLocationChangeSuccessTo('/Home'); $rootScope.$digest(); expect($browser.url()).toEqual('http://server/app/#!/Home'); expect($location.path()).toEqual('/Home'); expect($browserUrl).toHaveBeenCalledTimes(1); expect($browserUrl.calls.argsFor(0)).toEqual(['http://server/app/#!/Home', false, null]); }); }); it('should not infinite $digest when going to base URL with trailing slash when $locationChangeSuccess watcher changes path to /', function() { initService({html5Mode: true, supportHistory: false}); mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'}); inject(function($rootScope, $location, $browser) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); updatePathOnLocationChangeSuccessTo('/'); $rootScope.$digest(); expect($browser.url()).toEqual('http://server/app/#!/'); expect($location.path()).toEqual('/'); expect($browserUrl).toHaveBeenCalledTimes(1); }); }); }); describe('location watch for HTML5 browsers', function() { it('should not infinite $digest when going to base URL without trailing slash when $locationChangeSuccess watcher changes path to /Home', function() { initService({html5Mode: true, supportHistory: true}); mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'}); inject(function($rootScope, $injector, $browser) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); var $location = $injector.get('$location'); updatePathOnLocationChangeSuccessTo('/Home'); $rootScope.$digest(); expect($browser.url()).toEqual('http://server/app/Home'); expect($location.path()).toEqual('/Home'); expect($browserUrl).toHaveBeenCalledTimes(1); }); }); it('should not infinite $digest when going to base URL without trailing slash when $locationChangeSuccess watcher changes path to /', function() { initService({html5Mode: true, supportHistory: true}); mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'}); inject(function($rootScope, $injector, $browser) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); var $location = $injector.get('$location'); updatePathOnLocationChangeSuccessTo('/'); $rootScope.$digest(); expect($browser.url()).toEqual('http://server/app/'); expect($location.path()).toEqual('/'); expect($browserUrl).not.toHaveBeenCalled(); }); }); it('should not infinite $digest when going to base URL with trailing slash when $locationChangeSuccess watcher changes path to /Home', function() { initService({html5Mode: true, supportHistory: true}); mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'}); inject(function($rootScope, $injector, $browser) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); var $location = $injector.get('$location'); updatePathOnLocationChangeSuccessTo('/Home'); $rootScope.$digest(); expect($browser.url()).toEqual('http://server/app/Home'); expect($location.path()).toEqual('/Home'); expect($browserUrl).toHaveBeenCalledTimes(1); }); }); it('should not infinite $digest when going to base URL with trailing slash when $locationChangeSuccess watcher changes path to /', function() { initService({html5Mode: true, supportHistory: true}); mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'}); inject(function($rootScope, $injector, $browser) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); var $location = $injector.get('$location'); updatePathOnLocationChangeSuccessTo('/'); $rootScope.$digest(); expect($browser.url()).toEqual('http://server/app/'); expect($location.path()).toEqual('/'); expect($browserUrl).not.toHaveBeenCalled(); }); }); //https://github.com/angular/angular.js/issues/16592 it('should not infinite $digest when going to base URL with trailing slash when $locationChangeSuccess watcher changes query params to contain quote', function() { initService({html5Mode: true, supportHistory: true}); mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'}); inject(function($rootScope, $injector, $browser) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); var $location = $injector.get('$location'); updatePathOnLocationChangeSuccessTo('/', {q: '\''}); $rootScope.$digest(); expect($location.path()).toEqual('/'); expect($location.search()).toEqual({q: '\''}); expect($browserUrl).toHaveBeenCalledTimes(1); }); }); }); }); describe('wiring', function() { it('should update $location when browser url changes', function() { initService({html5Mode:false,hashPrefix: '!',supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b#!', baseHref:'/a/b'}); inject(function($window, $browser, $location, $rootScope) { spyOn($location, '$$parse').and.callThrough(); $window.location.href = 'http://new.com/a/b#!/aaa'; $browser.$$checkUrlChange(); expect($location.absUrl()).toBe('http://new.com/a/b#!/aaa'); expect($location.path()).toBe('/aaa'); expect($location.$$parse).toHaveBeenCalledOnce(); }); }); // location.href = '...' fires hashchange event synchronously, so it might happen inside $apply it('should not $apply when browser url changed inside $apply', function() { initService({html5Mode:false,hashPrefix: '!',supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b#!', baseHref:'/a/b'}); inject(function($rootScope, $browser, $location, $window) { var OLD_URL = $browser.url(), NEW_URL = 'http://new.com/a/b#!/new'; $rootScope.$apply(function() { $window.location.href = NEW_URL; $browser.$$checkUrlChange(); // simulate firing event from browser expect($location.absUrl()).toBe(OLD_URL); // should be async }); expect($location.absUrl()).toBe(NEW_URL); }); }); // location.href = '...' fires hashchange event synchronously, so it might happen inside $digest it('should not $apply when browser url changed inside $digest', function() { initService({html5Mode:false,hashPrefix: '!',supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b#!', baseHref:'/a/b'}); inject(function($rootScope, $browser, $location, $window) { var OLD_URL = $browser.url(), NEW_URL = 'http://new.com/a/b#!/new', notRunYet = true; $rootScope.$watch(function() { if (notRunYet) { notRunYet = false; $window.location.href = NEW_URL; $browser.$$checkUrlChange(); // simulate firing event from browser expect($location.absUrl()).toBe(OLD_URL); // should be async } }); $rootScope.$digest(); expect($location.absUrl()).toBe(NEW_URL); }); }); it('should update browser when $location changes', function() { initService({html5Mode:false,hashPrefix: '!',supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b#!', baseHref:'/a/b'}); inject(function($rootScope, $browser, $location) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); $location.path('/new/path'); expect($browserUrl).not.toHaveBeenCalled(); $rootScope.$apply(); expect($browserUrl).toHaveBeenCalledOnce(); expect($browser.url()).toBe('http://new.com/a/b#!/new/path'); }); }); it('should update browser only once per $apply cycle', function() { initService({html5Mode:false,hashPrefix: '!',supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b#!', baseHref:'/a/b'}); inject(function($rootScope, $browser, $location) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); $location.path('/new/path'); $rootScope.$watch(function() { $location.search('a=b'); }); $rootScope.$apply(); expect($browserUrl).toHaveBeenCalledOnce(); expect($browser.url()).toBe('http://new.com/a/b#!/new/path?a=b'); }); }); it('should replace browser url when url was replaced at least once', function() { initService({html5Mode:false,hashPrefix: '!',supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b#!', baseHref:'/a/b'}); inject(function($rootScope, $browser, $location) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); $location.path('/n/url').replace(); $rootScope.$apply(); expect($browserUrl).toHaveBeenCalledOnce(); expect($browserUrl.calls.mostRecent().args).toEqual(['http://new.com/a/b#!/n/url', true, null]); expect($location.$$replace).toBe(false); }); }); it('should always reset replace flag after running watch', function() { initService({html5Mode:false,hashPrefix: '!',supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b#!', baseHref:'/a/b'}); inject(function($rootScope, $browser, $location) { // init watches $location.url('/initUrl'); $rootScope.$apply(); // changes url but resets it before digest $location.url('/newUrl').replace().url('/initUrl'); $rootScope.$apply(); expect($location.$$replace).toBe(false); // set the url to the old value $location.url('/newUrl').replace(); $rootScope.$apply(); expect($location.$$replace).toBe(false); // doesn't even change url only calls replace() $location.replace(); $rootScope.$apply(); expect($location.$$replace).toBe(false); }); }); it('should update the browser if changed from within a watcher', function() { initService({html5Mode:false,hashPrefix: '!',supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b#!', baseHref:'/a/b'}); inject(function($rootScope, $browser, $location) { $rootScope.$watch(function() { return true; }, function() { $location.path('/changed'); }); $rootScope.$digest(); expect($browser.url()).toBe('http://new.com/a/b#!/changed'); }); }); it('should not infinitely digest if hash is set when there is no hashPrefix', function() { initService({html5Mode:false, hashPrefix:'', supportHistory:true}); mockUpBrowser({initialUrl:'http://new.com/a/b', baseHref:'/a/b'}); inject(function($rootScope, $browser, $location) { $location.hash('test'); $rootScope.$digest(); expect($browser.url()).toBe('http://new.com/a/b##test'); }); }); }); describe('wiring in html5 mode', function() { it('should initialize state to initial state from the browser', function() { initService({html5Mode:true, supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/', state: {a: 2}}); inject(function($location) { expect($location.state()).toEqual({a: 2}); }); }); it('should update $location when browser state changes', function() { initService({html5Mode: true, supportHistory: true}); mockUpBrowser({initialUrl: 'http://new.com/a/b/', baseHref: '/a/b/'}); inject(function($location, $rootScope, $window) { $window.history.pushState({b: 3}); $rootScope.$digest(); expect($location.state()).toEqual({b: 3}); $window.history.pushState({b: 4}, null, $window.location.href + 'c?d=e#f'); $rootScope.$digest(); expect($location.path()).toBe('/c'); expect($location.search()).toEqual({d: 'e'}); expect($location.hash()).toBe('f'); expect($location.state()).toEqual({b: 4}); }); }); //https://github.com/angular/angular.js/issues/16592 it('should not infinite $digest on pushState() with quote in param', function() { initService({html5Mode: true, supportHistory: true}); mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'}); inject(function($rootScope, $injector, $window) { var $location = $injector.get('$location'); $rootScope.$digest(); //allow $location initialization to finish $window.history.pushState({}, null, 'http://server/app/Home?q=\''); $rootScope.$digest(); expect($location.absUrl()).toEqual('http://server/app/Home?q=\''); expect($location.path()).toEqual('/Home'); expect($location.search()).toEqual({q: '\''}); }); }); //https://github.com/angular/angular.js/issues/16592 it('should not infinite $digest on popstate event with quote in param', function() { initService({html5Mode: true, supportHistory: true}); mockUpBrowser({initialUrl:'http://server/app/', baseHref:'/app/'}); inject(function($rootScope, $injector, $window) { var $location = $injector.get('$location'); $rootScope.$digest(); //allow $location initialization to finish $window.location.href = 'http://server/app/Home?q=\''; jqLite($window).triggerHandler('popstate'); expect($location.absUrl()).toEqual('http://server/app/Home?q=\''); expect($location.path()).toEqual('/Home'); expect($location.search()).toEqual({q: '\''}); }); }); it('should replace browser url & state when replace() was called at least once', function() { initService({html5Mode:true, supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/'}); inject(function($rootScope, $location, $browser) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); $location.path('/n/url').state({a: 2}).replace(); $rootScope.$apply(); expect($browserUrl).toHaveBeenCalledOnce(); expect($browserUrl.calls.mostRecent().args).toEqual(['http://new.com/a/b/n/url', true, {a: 2}]); expect($location.$$replace).toBe(false); expect($location.$$state).toEqual({a: 2}); }); }); it('should use only the most recent url & state definition', function() { initService({html5Mode:true, supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/'}); inject(function($rootScope, $location, $browser) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); $location.path('/n/url').state({a: 2}).replace().state({b: 3}).path('/o/url'); $rootScope.$apply(); expect($browserUrl).toHaveBeenCalledOnce(); expect($browserUrl.calls.mostRecent().args).toEqual(['http://new.com/a/b/o/url', true, {b: 3}]); expect($location.$$replace).toBe(false); expect($location.$$state).toEqual({b: 3}); }); }); it('should allow to set state without touching the URL', function() { initService({html5Mode:true, supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/'}); inject(function($rootScope, $location, $browser) { var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); $location.state({a: 2}).replace().state({b: 3}); $rootScope.$apply(); expect($browserUrl).toHaveBeenCalledOnce(); expect($browserUrl.calls.mostRecent().args).toEqual(['http://new.com/a/b/', true, {b: 3}]); expect($location.$$replace).toBe(false); expect($location.$$state).toEqual({b: 3}); }); }); it('should always reset replace flag after running watch', function() { initService({html5Mode:true, supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/'}); inject(function($rootScope, $location) { // init watches $location.url('/initUrl').state({a: 2}); $rootScope.$apply(); // changes url & state but resets them before digest $location.url('/newUrl').state({a: 2}).replace().state({b: 3}).url('/initUrl'); $rootScope.$apply(); expect($location.$$replace).toBe(false); // set the url to the old value $location.url('/newUrl').state({a: 2}).replace(); $rootScope.$apply(); expect($location.$$replace).toBe(false); // doesn't even change url only calls replace() $location.replace(); $rootScope.$apply(); expect($location.$$replace).toBe(false); }); }); it('should allow to modify state only before digest', function() { initService({html5Mode:true, supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/'}); inject(function($rootScope, $location, $browser) { var o = {a: 2}; $location.state(o); o.a = 3; $rootScope.$apply(); expect($browser.state()).toEqual({a: 3}); o.a = 4; $rootScope.$apply(); expect($browser.state()).toEqual({a: 3}); }); }); it('should make $location.state() referencially identical with $browser.state() after digest', function() { initService({html5Mode:true, supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/'}); inject(function($rootScope, $location, $browser) { $location.state({a: 2}); $rootScope.$apply(); expect($location.state()).toBe($browser.state()); }); }); it('should allow to query the state after digest', function() { initService({html5Mode:true, supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/'}); inject(function($rootScope, $location) { $location.url('/foo').state({a: 2}); $rootScope.$apply(); expect($location.state()).toEqual({a: 2}); }); }); it('should reset the state on .url() after digest', function() { initService({html5Mode:true, supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/'}); inject(function($rootScope, $location, $browser) { $location.url('/foo').state({a: 2}); $rootScope.$apply(); var $browserUrl = spyOnlyCallsWithArgs($browser, 'url').and.callThrough(); $location.url('/bar'); $rootScope.$apply(); expect($browserUrl).toHaveBeenCalledOnce(); expect($browserUrl.calls.mostRecent().args).toEqual(['http://new.com/a/b/bar', false, null]); }); }); it('should force a page reload if navigating outside of the application base href', function() { initService({html5Mode:true, supportHistory: true}); mockUpBrowser({initialUrl:'http://new.com/a/b/', baseHref:'/a/b/'}); inject(function($window, $browser, $location) { $window.location.href = 'http://new.com/a/outside.html'; spyOn($window.location, '$$setHref'); expect($window.location.$$setHref).not.toHaveBeenCalled(); $browser.$$checkUrlChange(); expect($window.location.$$setHref).toHaveBeenCalledWith('http://new.com/a/outside.html'); }); }); }); // html5 history is disabled describe('disabled history', function() { it('should use hashbang url with hash prefix', function() { initService({html5Mode:false,hashPrefix: '!'}); mockUpBrowser({initialUrl:'http://domain.com/base/index.html#!/a/b', baseHref:'/base/index.html'}); inject( function($rootScope, $location, $browser) { expect($browser.url()).toBe('http://domain.com/base/index.html#!/a/b'); $location.path('/new'); $location.search({a: true}); $rootScope.$apply(); expect($browser.url()).toBe('http://domain.com/base/index.html#!/new?a'); } ); }); it('should use hashbang url without hash prefix', function() { initService({html5Mode:false,hashPrefix: ''}); mockUpBrowser({initialUrl:'http://domain.com/base/index.html#/a/b', baseHref:'/base/index.html'}); inject( function($rootScope, $location, $browser) { expect($browser.url()).toBe('http://domain.com/base/index.html#/a/b'); $location.path('/new'); $location.search({a: true}); $rootScope.$apply(); expect($browser.url()).toBe('http://domain.com/base/index.html#/new?a'); } ); }); }); // html5 history enabled, but not supported by browser describe('history on old browser', function() { it('should use hashbang url with hash prefix', function() { initService({html5Mode:true,hashPrefix: '!!',supportHistory: false}); inject( initBrowser({url:'http://domain.com/base/index.html#!!/a/b',basePath: '/base/index.html'}), function($rootScope, $location, $browser) { expect($browser.url()).toBe('http://domain.com/base/index.html#!!/a/b'); $location.path('/new'); $location.search({a: true}); $rootScope.$apply(); expect($browser.url()).toBe('http://domain.com/base/index.html#!!/new?a'); } ); }); it('should redirect to hashbang url when new url given', function() { initService({html5Mode:true,hashPrefix: '!'}); inject( initBrowser({url:'http://domain.com/base/new-path/index.html',basePath: '/base/index.html'}), function($browser, $location) { expect($browser.url()).toBe('http://domain.com/base/index.html#!/new-path/index.html'); } ); }); it('should correctly convert html5 url with path matching basepath to hashbang url', function() { initService({html5Mode:true,hashPrefix: '!',supportHistory: false}); inject( initBrowser({url:'http://domain.com/base/index.html',basePath: '/base/index.html'}), function($browser, $location) { expect($browser.url()).toBe('http://domain.com/base/index.html#!/index.html'); } ); }); }); // html5 history enabled and supported by browser describe('history on new browser', function() { it('should use new url', function() { initService({html5Mode:true,hashPrefix:'',supportHistory:true}); mockUpBrowser({initialUrl:'http://domain.com/base/old/index.html#a', baseHref:'/base/index.html'}); inject( function($rootScope, $location, $browser) { expect($browser.url()).toBe('http://domain.com/base/old/index.html#a'); $location.path('/new'); $location.search({a: true}); $rootScope.$apply(); expect($browser.url()).toBe('http://domain.com/base/new?a#a'); } ); }); it('should rewrite when hashbang url given', function() { initService({html5Mode:true,hashPrefix: '!',supportHistory: true}); mockUpBrowser({initialUrl:'http://domain.com/base/index.html#!/a/b', baseHref:'/base/index.html'}); inject( function($rootScope, $location, $browser) { expect($browser.url()).toBe('http://domain.com/base/a/b'); $location.path('/new'); $location.hash('abc'); $rootScope.$apply(); expect($browser.url()).toBe('http://domain.com/base/new#abc'); expect($location.path()).toBe('/new'); } ); }); it('should rewrite when hashbang url given (without hash prefix)', function() { initService({html5Mode:true,hashPrefix: '',supportHistory: true}); mockUpBrowser({initialUrl:'http://domain.com/base/index.html#/a/b', baseHref:'/base/index.html'}); inject( function($rootScope, $location, $browser) { expect($browser.url()).toBe('http://domain.com/base/a/b'); expect($location.path()).toBe('/a/b'); } ); }); }); describe('PATH_MATCH', function() { /* global PATH_MATCH: false */ it('should parse just path', function() { var match = PATH_MATCH.exec('/path'); expect(match[1]).toBe('/path'); }); it('should parse path with search', function() { var match = PATH_MATCH.exec('/ppp/a?a=b&c'); expect(match[1]).toBe('/ppp/a'); expect(match[3]).toBe('a=b&c'); }); it('should parse path with hash', function() { var match = PATH_MATCH.exec('/ppp/a#abc?'); expect(match[1]).toBe('/ppp/a'); expect(match[5]).toBe('abc?'); }); it('should parse path with both search and hash', function() { var match = PATH_MATCH.exec('/ppp/a?a=b&c#abc/d?'); expect(match[3]).toBe('a=b&c'); }); }); describe('link rewriting', function() { var root, link, originalBrowser, lastEventPreventDefault; function configureTestLink(options) { var linkHref = options.linkHref, relLink = options.relLink, attrs = options.attrs, content = options.content; attrs = attrs ? ' ' + attrs + ' ' : ''; if (typeof linkHref === 'string' && !relLink) { if (linkHref[0] === '/') { linkHref = 'http://host.com' + linkHref; } else if (!linkHref.match(/:\/\//)) { // fake the behavior of tag linkHref = 'http://host.com/base/' + linkHref; } } if (linkHref) { link = jqLite('' + content + '')[0]; } else { link = jqLite('' + content + '')[0]; } module(function($provide) { return function($rootElement, $document) { $rootElement.append(link); root = $rootElement[0]; // we need to do this otherwise we can't simulate events $document.find('body').append($rootElement); }; }); } function setupRewriteChecks() { return function($browser, $location, $rootElement) { originalBrowser = $browser.url(); // we have to prevent the default operation, as we need to test absolute links (http://...) // and navigating to these links would kill jstd $rootElement.on('click', function(e) { lastEventPreventDefault = e.isDefaultPrevented(); e.preventDefault(); }); }; } function expectRewriteTo($browser, url) { expect(lastEventPreventDefault).toBe(true); expect($browser.url()).toBe(url); } function expectNoRewrite($browser) { expect(lastEventPreventDefault).toBe(false); expect($browser.url()).toBe(originalBrowser); } afterEach(function() { dealoc(root); dealoc(window.document.body); }); it('should rewrite rel link to new url when history enabled on new browser', function() { configureTestLink({linkHref: 'link?a#b'}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/link?a#b'); } ); }); it('should do nothing if already on the same URL', function() { configureTestLink({linkHref: '/base/'}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/'); jqLite(link).attr('href', 'http://host.com/base/foo'); browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/foo'); jqLite(link).attr('href', 'http://host.com/base/'); browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/'); jqLite(link). attr('href', 'http://host.com/base/foo'). on('click', function(e) { e.preventDefault(); }); browserTrigger(link, 'click'); expect($browser.url()).toBe('http://host.com/base/'); } ); }); it('should rewrite abs link to new url when history enabled on new browser', function() { configureTestLink({linkHref: '/base/link?a#b'}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/link?a#b'); } ); }); it('should rewrite rel link to hashbang url when history enabled on old browser', function() { configureTestLink({linkHref: 'link?a#b'}); initService({html5Mode:true,supportHistory:false,hashPrefix:'!'}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/index.html#!/link?a#b'); } ); }); // Regression (gh-7721) it('should not throw when clicking anchor with no href attribute when history enabled on old browser', function() { configureTestLink({linkHref: null}); initService({html5Mode:true,supportHistory:false}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should produce relative paths correctly when $location.path() is "/" when history enabled on old browser', function() { configureTestLink({linkHref: 'partial1'}); initService({html5Mode:true,supportHistory:false,hashPrefix:'!'}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser, $location, $rootScope) { $rootScope.$apply(function() { $location.path('/'); }); browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/index.html#!/partial1'); } ); }); it('should rewrite abs link to hashbang url when history enabled on old browser', function() { configureTestLink({linkHref: '/base/link?a#b'}); initService({html5Mode:true,supportHistory:false,hashPrefix:'!'}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/index.html#!/link?a#b'); } ); }); it('should not rewrite full url links to different domain', function() { configureTestLink({linkHref: 'http://www.dot.abc/a?b=c'}); initService({html5Mode:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite links with target="_blank"', function() { configureTestLink({linkHref: 'base/a?b=c', attrs: 'target="_blank"'}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite links with target specified', function() { configureTestLink({linkHref: 'base/a?b=c', attrs: 'target="some-frame"'}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite links with `javascript:` URI', function() { configureTestLink({linkHref: ' jAvAsCrIpT:throw new Error("Boom!")', relLink: true}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite links with `mailto:` URI', function() { configureTestLink({linkHref: ' mAiLtO:foo@bar.com', relLink: true}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite links when rewriting links is disabled', function() { configureTestLink({linkHref: 'link?a#b'}); initService({html5Mode:{enabled: true, rewriteLinks:false},supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should rewrite links when the specified rewriteLinks attr is present', function() { configureTestLink({linkHref: 'link?a#b', attrs: 'do-rewrite'}); initService({html5Mode: {enabled: true, rewriteLinks: 'do-rewrite'}, supportHistory: true}); inject( initBrowser({url: 'http://host.com/base/index.html', basePath: '/base/index.html'}), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/link?a#b'); } ); }); it('should not rewrite links when the specified rewriteLinks attr is not present', function() { configureTestLink({linkHref: 'link?a#b'}); initService({html5Mode: {enabled: true, rewriteLinks: 'do-rewrite'}, supportHistory: true}); inject( initBrowser({url: 'http://host.com/base/index.html', basePath: '/base/index.html'}), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should rewrite full url links to same domain and base path', function() { configureTestLink({linkHref: 'http://host.com/base/new'}); initService({html5Mode:true,supportHistory:false,hashPrefix:'!'}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectRewriteTo($browser, 'http://host.com/base/index.html#!/new'); } ); }); it('should rewrite when clicked span inside link', function() { configureTestLink({linkHref: 'some/link', attrs: '', content: 'link'}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { var span = jqLite(link).find('span'); browserTrigger(span, 'click'); expectRewriteTo($browser, 'http://host.com/base/some/link'); } ); }); it('should not rewrite when link to different base path when history enabled on new browser', function() { configureTestLink({linkHref: '/other_base/link'}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite when link to different base path when history enabled on old browser', function() { configureTestLink({linkHref: '/other_base/link'}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite when link to different base path when history disabled', function() { configureTestLink({linkHref: '/other_base/link'}); initService({html5Mode:false}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite when full link to different base path when history enabled on new browser', function() { configureTestLink({linkHref: 'http://host.com/other_base/link'}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite when full link to different base path when history enabled on old browser', function() { configureTestLink({linkHref: 'http://host.com/other_base/link'}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should not rewrite when full link to different base path when history disabled', function() { configureTestLink({linkHref: 'http://host.com/other_base/link'}); initService({html5Mode:false}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click'); expectNoRewrite($browser); } ); }); it('should replace current hash fragment when link begins with "#" history disabled', function() { configureTestLink({linkHref: '#link', relLink: true}); initService({html5Mode:true,supportHistory:false,hashPrefix:'!'}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser, $location, $rootScope) { $rootScope.$apply(function() { $location.path('/some'); $location.hash('foo'); }); browserTrigger(link, 'click'); expect($location.hash()).toBe('link'); expectRewriteTo($browser, 'http://host.com/base/index.html#!/some#link'); } ); }); it('should replace current hash fragment when link begins with "#" history enabled', function() { configureTestLink({linkHref: '#link', relLink: true}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser, $location, $rootScope) { $rootScope.$apply(function() { $location.path('/some'); $location.hash('foo'); }); browserTrigger(link, 'click'); expect($location.hash()).toBe('link'); expectRewriteTo($browser, 'http://host.com/base/some#link'); } ); }); it('should not rewrite when clicked with ctrl pressed', function() { configureTestLink({linkHref: 'base/a?b=c'}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click', { keys: ['ctrl'] }); expectNoRewrite($browser); } ); }); it('should not rewrite when clicked with meta pressed', function() { configureTestLink({linkHref: 'base/a?b=c'}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click', { keys: ['meta'] }); expectNoRewrite($browser); } ); }); it('should not rewrite when right click pressed', function() { configureTestLink({linkHref: 'base/a?b=c'}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { var rightClick = window.document.createEvent('MouseEvents'); rightClick.initMouseEvent('click', true, true, window, 1, 10, 10, 10, 10, false, false, false, false, 2, null); link.dispatchEvent(rightClick); expectNoRewrite($browser); } ); }); it('should not rewrite when clicked with shift pressed', function() { configureTestLink({linkHref: 'base/a?b=c'}); initService({html5Mode:true,supportHistory:true}); inject( initBrowser({ url: 'http://host.com/base/index.html', basePath: '/base/index.html' }), setupRewriteChecks(), function($browser) { browserTrigger(link, 'click', { keys: ['shift'] }); expectNoRewrite($browser); } ); }); it('should not mess up hash urls when clicking on links in hashbang mode', function() { var base; module(function() { return function($browser) { window.location.hash = 'someHash'; base = window.location.href; $browser.url(base); base = base.split('#')[0]; }; }); inject(function($rootScope, $compile, $browser, $rootElement, $document, $location) { // we need to do this otherwise we can't simulate events $document.find('body').append($rootElement); var element = $compile('v1v2')($rootScope); $rootElement.append(element); var av1 = $rootElement.find('a').eq(0); var av2 = $rootElement.find('a').eq(1); browserTrigger(av1, 'click'); expect($browser.url()).toEqual(base + '#!/view1'); browserTrigger(av2, 'click'); expect($browser.url()).toEqual(base + '#!/view2'); $rootElement.remove(); }); }); it('should not mess up hash urls when clicking on links in hashbang mode with a prefix', function() { var base; module(function($locationProvider) { return function($browser) { window.location.hash = '!!someHash'; $browser.url(base = window.location.href); base = base.split('#')[0]; $locationProvider.hashPrefix('!!'); }; }); inject(function($rootScope, $compile, $browser, $rootElement, $document, $location) { // we need to do this otherwise we can't simulate events $document.find('body').append($rootElement); var element = $compile('v1v2')($rootScope); $rootElement.append(element); var av1 = $rootElement.find('a').eq(0); var av2 = $rootElement.find('a').eq(1); browserTrigger(av1, 'click'); expect($browser.url()).toEqual(base + '#!!/view1'); browserTrigger(av2, 'click'); expect($browser.url()).toEqual(base + '#!!/view2'); }); }); it('should not intercept clicks outside the current hash prefix', function() { var base, clickHandler; module(function($provide) { $provide.value('$rootElement', { on: function(event, handler) { expect(event).toEqual('click'); clickHandler = handler; }, off: noop }); return function($browser) { $browser.url(base = 'http://server/'); }; }); inject(function($location) { // make IE happy jqLite(window.document.body).html('link'); var event = { target: jqLite(window.document.body).find('a')[0], preventDefault: jasmine.createSpy('preventDefault'), isDefaultPrevented: jasmine.createSpy().and.returnValue(false) }; clickHandler(event); expect(event.preventDefault).not.toHaveBeenCalled(); }); }); it('should not intercept hash link clicks outside the app base url space', function() { var base, clickHandler; module(function($provide) { $provide.value('$rootElement', { on: function(event, handler) { expect(event).toEqual('click'); clickHandler = handler; }, off: angular.noop }); return function($browser) { $browser.url(base = 'http://server/'); }; }); inject(function($rootScope, $compile, $browser, $rootElement, $document, $location) { // make IE happy jqLite(window.document.body).html('link'); var event = { target: jqLite(window.document.body).find('a')[0], preventDefault: jasmine.createSpy('preventDefault'), isDefaultPrevented: jasmine.createSpy().and.returnValue(false) }; clickHandler(event); expect(event.preventDefault).not.toHaveBeenCalled(); }); }); // regression https://github.com/angular/angular.js/issues/1058 it('should not throw if element was removed', inject(function($document, $rootElement, $location) { // we need to do this otherwise we can't simulate events $document.find('body').append($rootElement); $rootElement.html(''); var button = $rootElement.find('button'); button.on('click', function() { button.remove(); }); browserTrigger(button, 'click'); })); it('should not throw when clicking an SVGAElement link', function() { var base; module(function($locationProvider) { return function($browser) { window.location.hash = '!someHash'; $browser.url(base = window.location.href); base = base.split('#')[0]; $locationProvider.hashPrefix('!'); }; }); inject(function($rootScope, $compile, $browser, $rootElement, $document, $location) { // we need to do this otherwise we can't simulate events $document.find('body').append($rootElement); var template = ''; var element = $compile(template)($rootScope); $rootElement.append(element); var av1 = $rootElement.find('a').eq(0); expect(function() { browserTrigger(av1, 'click'); }).not.toThrow(); }); }); }); describe('location cancellation', function() { it('should fire $before/afterLocationChange event', inject(function($location, $browser, $rootScope, $log) { expect($browser.url()).toEqual('http://server/'); $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) { $log.info('before', newUrl, oldUrl, $browser.url()); }); $rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) { $log.info('after', newUrl, oldUrl, $browser.url()); }); expect($location.url()).toEqual(''); $location.url('/somePath'); expect($location.url()).toEqual('/somePath'); expect($browser.url()).toEqual('http://server/'); expect($log.info.logs).toEqual([]); $rootScope.$apply(); expect($log.info.logs.shift()). toEqual(['before', 'http://server/#!/somePath', 'http://server/', 'http://server/']); expect($log.info.logs.shift()). toEqual(['after', 'http://server/#!/somePath', 'http://server/', 'http://server/#!/somePath']); expect($location.url()).toEqual('/somePath'); expect($browser.url()).toEqual('http://server/#!/somePath'); })); it('should allow $locationChangeStart event cancellation', inject(function($location, $browser, $rootScope, $log) { expect($browser.url()).toEqual('http://server/'); expect($location.url()).toEqual(''); $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) { $log.info('before', newUrl, oldUrl, $browser.url()); event.preventDefault(); }); $rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) { throw new Error('location should have been canceled'); }); expect($location.url()).toEqual(''); $location.url('/somePath'); expect($location.url()).toEqual('/somePath'); expect($browser.url()).toEqual('http://server/'); expect($log.info.logs).toEqual([]); $rootScope.$apply(); expect($log.info.logs.shift()). toEqual(['before', 'http://server/#!/somePath', 'http://server/', 'http://server/']); expect($log.info.logs[1]).toBeUndefined(); expect($location.url()).toEqual(''); expect($browser.url()).toEqual('http://server/'); })); it('should allow redirect during $locationChangeStart', inject(function($location, $browser, $rootScope, $log) { $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) { $log.info('before', newUrl, oldUrl, $browser.url()); if (newUrl === 'http://server/#!/somePath') { $location.url('/redirectPath'); } }); $rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) { $log.info('after', newUrl, oldUrl, $browser.url()); }); $location.url('/somePath'); $rootScope.$apply(); expect($log.info.logs.shift()). toEqual(['before', 'http://server/#!/somePath', 'http://server/', 'http://server/']); expect($log.info.logs.shift()). toEqual(['before', 'http://server/#!/redirectPath', 'http://server/', 'http://server/']); expect($log.info.logs.shift()). toEqual(['after', 'http://server/#!/redirectPath', 'http://server/', 'http://server/#!/redirectPath']); expect($location.url()).toEqual('/redirectPath'); expect($browser.url()).toEqual('http://server/#!/redirectPath'); }) ); it('should allow redirect during $locationChangeStart even if default prevented', inject(function($location, $browser, $rootScope, $log) { $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) { $log.info('before', newUrl, oldUrl, $browser.url()); if (newUrl === 'http://server/#!/somePath') { event.preventDefault(); $location.url('/redirectPath'); } }); $rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) { $log.info('after', newUrl, oldUrl, $browser.url()); }); $location.url('/somePath'); $rootScope.$apply(); expect($log.info.logs.shift()). toEqual(['before', 'http://server/#!/somePath', 'http://server/', 'http://server/']); expect($log.info.logs.shift()). toEqual(['before', 'http://server/#!/redirectPath', 'http://server/', 'http://server/']); expect($log.info.logs.shift()). toEqual(['after', 'http://server/#!/redirectPath', 'http://server/', 'http://server/#!/redirectPath']); expect($location.url()).toEqual('/redirectPath'); expect($browser.url()).toEqual('http://server/#!/redirectPath'); }) ); it('should allow multiple redirect during $locationChangeStart', inject(function($location, $browser, $rootScope, $log) { $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) { $log.info('before', newUrl, oldUrl, $browser.url()); if (newUrl === 'http://server/#!/somePath') { $location.url('/redirectPath'); } else if (newUrl === 'http://server/#!/redirectPath') { $location.url('/redirectPath2'); } }); $rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) { $log.info('after', newUrl, oldUrl, $browser.url()); }); $location.url('/somePath'); $rootScope.$apply(); expect($log.info.logs.shift()). toEqual(['before', 'http://server/#!/somePath', 'http://server/', 'http://server/']); expect($log.info.logs.shift()). toEqual(['before', 'http://server/#!/redirectPath', 'http://server/', 'http://server/']); expect($log.info.logs.shift()). toEqual(['before', 'http://server/#!/redirectPath2', 'http://server/', 'http://server/']); expect($log.info.logs.shift()). toEqual(['after', 'http://server/#!/redirectPath2', 'http://server/', 'http://server/#!/redirectPath2']); expect($location.url()).toEqual('/redirectPath2'); expect($browser.url()).toEqual('http://server/#!/redirectPath2'); }) ); it('should fire $locationChangeSuccess event when change from browser location bar', inject(function($log, $location, $browser, $rootScope) { $rootScope.$apply(); // clear initial $locationChangeStart expect($browser.url()).toEqual('http://server/'); expect($location.url()).toEqual(''); $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) { $log.info('start', newUrl, oldUrl); }); $rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) { $log.info('after', newUrl, oldUrl); }); $browser.url('http://server/#!/somePath'); $browser.poll(); expect($log.info.logs.shift()). toEqual(['start', 'http://server/#!/somePath', 'http://server/']); expect($log.info.logs.shift()). toEqual(['after', 'http://server/#!/somePath', 'http://server/']); }) ); it('should fire $locationChangeSuccess when browser location changes to URL which ends with #', inject(function($location, $browser, $rootScope, $log) { $location.url('/somepath'); $rootScope.$apply(); expect($browser.url()).toEqual('http://server/#!/somepath'); expect($location.url()).toEqual('/somepath'); $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) { $log.info('start', newUrl, oldUrl); }); $rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) { $log.info('after', newUrl, oldUrl); }); $browser.url('http://server/#'); $browser.poll(); expect($log.info.logs.shift()). toEqual(['start', 'http://server/', 'http://server/#!/somepath']); expect($log.info.logs.shift()). toEqual(['after', 'http://server/', 'http://server/#!/somepath']); }) ); it('should allow redirect during browser url change', inject(function($location, $browser, $rootScope, $log) { $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) { $log.info('before', newUrl, oldUrl, $browser.url()); if (newUrl === 'http://server/#!/somePath') { $location.url('/redirectPath'); } }); $rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) { $log.info('after', newUrl, oldUrl, $browser.url()); }); $browser.url('http://server/#!/somePath'); $browser.poll(); expect($log.info.logs.shift()). toEqual(['before', 'http://server/#!/somePath', 'http://server/', 'http://server/#!/somePath']); expect($log.info.logs.shift()). toEqual(['before', 'http://server/#!/redirectPath', 'http://server/#!/somePath', 'http://server/#!/somePath']); expect($log.info.logs.shift()). toEqual(['after', 'http://server/#!/redirectPath', 'http://server/#!/somePath', 'http://server/#!/redirectPath']); expect($location.url()).toEqual('/redirectPath'); expect($browser.url()).toEqual('http://server/#!/redirectPath'); }) ); it('should allow redirect during browser url change even if default prevented', inject(function($location, $browser, $rootScope, $log) { $rootScope.$on('$locationChangeStart', function(event, newUrl, oldUrl) { $log.info('before', newUrl, oldUrl, $browser.url()); if (newUrl === 'http://server/#!/somePath') { event.preventDefault(); $location.url('/redirectPath'); } }); $rootScope.$on('$locationChangeSuccess', function(event, newUrl, oldUrl) { $log.info('after', newUrl, oldUrl, $browser.url()); }); $browser.url('http://server/#!/somePath'); $browser.poll(); expect($log.info.logs.shift()). toEqual(['before', 'http://server/#!/somePath', 'http://server/', 'http://server/#!/somePath']); expect($log.info.logs.shift()). toEqual(['before', 'http://server/#!/redirectPath', 'http://server/#!/somePath', 'http://server/#!/somePath']); expect($log.info.logs.shift()). toEqual(['after', 'http://server/#!/redirectPath', 'http://server/#!/somePath', 'http://server/#!/redirectPath']); expect($location.url()).toEqual('/redirectPath'); expect($browser.url()).toEqual('http://server/#!/redirectPath'); }) ); it('should listen on click events on href and prevent browser default in hashbang mode', function() { module(function() { return function($rootElement, $compile, $rootScope) { $rootElement.html('link'); $compile($rootElement)($rootScope); jqLite(window.document.body).append($rootElement); }; }); inject(function($location, $rootScope, $browser, $rootElement) { var log = '', link = $rootElement.find('a'); $rootScope.$on('$locationChangeStart', function(event) { event.preventDefault(); log += '$locationChangeStart'; }); $rootScope.$on('$locationChangeSuccess', function() { throw new Error('after cancellation in hashbang mode'); }); browserTrigger(link, 'click'); expect(log).toEqual('$locationChangeStart'); expect($browser.url()).toEqual('http://server/'); dealoc($rootElement); }); }); it('should listen on click events on href and prevent browser default in html5 mode', function() { module(function($locationProvider, $provide) { $locationProvider.html5Mode(true); return function($rootElement, $compile, $rootScope) { $rootElement.html('link'); $compile($rootElement)($rootScope); jqLite(window.document.body).append($rootElement); }; }); inject(function($location, $rootScope, $browser, $rootElement) { var log = '', link = $rootElement.find('a'), browserUrlBefore = $browser.url(); $rootScope.$on('$locationChangeStart', function(event) { event.preventDefault(); log += '$locationChangeStart'; }); $rootScope.$on('$locationChangeSuccess', function() { throw new Error('after cancellation in html5 mode'); }); browserTrigger(link, 'click'); expect(log).toEqual('$locationChangeStart'); expect($browser.url()).toBe(browserUrlBefore); dealoc($rootElement); }); }); it('should always return the new url value via path() when $locationChangeStart event occurs regardless of cause', inject(function($location, $rootScope, $browser, log) { var base = $browser.url(); $rootScope.$on('$locationChangeStart', function() { log($location.path()); }); // change through $location service $rootScope.$apply(function() { $location.path('/myNewPath'); }); // reset location $rootScope.$apply(function() { $location.path(''); }); // change through $browser $browser.url(base + '#!/myNewPath'); $browser.poll(); expect(log).toEqual(['/myNewPath', '/', '/myNewPath']); }) ); }); describe('$locationProvider', function() { describe('html5Mode', function() { it('should set enabled, requireBase and rewriteLinks when called with object', function() { module(function($locationProvider) { $locationProvider.html5Mode({enabled: true, requireBase: false, rewriteLinks: false}); expect($locationProvider.html5Mode()).toEqual({ enabled: true, requireBase: false, rewriteLinks: false }); }); inject(function() {}); }); it('should only overwrite existing properties if values are of the correct type', function() { module(function($locationProvider) { $locationProvider.html5Mode({ enabled: 'duh', requireBase: 'probably', rewriteLinks: 0 }); expect($locationProvider.html5Mode()).toEqual({ enabled: false, requireBase: true, rewriteLinks: true }); }); inject(function() {}); }); it('should support setting rewriteLinks to a string', function() { module(function($locationProvider) { $locationProvider.html5Mode({ rewriteLinks: 'yes-rewrite' }); expect($locationProvider.html5Mode().rewriteLinks).toEqual('yes-rewrite'); }); inject(function() {}); }); it('should not set unknown input properties to html5Mode object', function() { module(function($locationProvider) { $locationProvider.html5Mode({ someProp: 'foo' }); expect($locationProvider.html5Mode()).toEqual({ enabled: false, requireBase: true, rewriteLinks: true }); }); inject(function() {}); }); it('should default to enabled:false, requireBase:true and rewriteLinks:true', function() { module(function($locationProvider) { expect($locationProvider.html5Mode()).toEqual({ enabled: false, requireBase: true, rewriteLinks: true }); }); inject(function() {}); }); }); }); describe('LocationHtml5Url', function() { var locationUrl, locationUmlautUrl, locationIndexUrl; beforeEach(function() { locationUrl = new LocationHtml5Url('http://server/pre/', 'http://server/pre/'); locationUmlautUrl = new LocationHtml5Url('http://särver/pre/', 'http://särver/pre/'); locationIndexUrl = new LocationHtml5Url('http://server/pre/index.html', 'http://server/pre/'); }); it('should rewrite URL', function() { expect(parseLinkAndReturn(locationUrl, 'http://other')).toEqual(undefined); expect(parseLinkAndReturn(locationUrl, 'http://server/pre')).toEqual('http://server/pre/'); expect(parseLinkAndReturn(locationUrl, 'http://server/pre/')).toEqual('http://server/pre/'); expect(parseLinkAndReturn(locationUrl, 'http://server/pre/otherPath')).toEqual('http://server/pre/otherPath'); // Note: relies on the previous state! expect(parseLinkAndReturn(locationUrl, 'someIgnoredAbsoluteHref', '#test')).toEqual('http://server/pre/otherPath#test'); expect(parseLinkAndReturn(locationUmlautUrl, 'http://other')).toEqual(undefined); expect(parseLinkAndReturn(locationUmlautUrl, 'http://särver/pre')).toEqual('http://särver/pre/'); expect(parseLinkAndReturn(locationUmlautUrl, 'http://särver/pre/')).toEqual('http://särver/pre/'); expect(parseLinkAndReturn(locationUmlautUrl, 'http://särver/pre/otherPath')).toEqual('http://särver/pre/otherPath'); // Note: relies on the previous state! expect(parseLinkAndReturn(locationUmlautUrl, 'someIgnoredAbsoluteHref', '#test')).toEqual('http://särver/pre/otherPath#test'); expect(parseLinkAndReturn(locationIndexUrl, 'http://server/pre')).toEqual('http://server/pre/'); expect(parseLinkAndReturn(locationIndexUrl, 'http://server/pre/')).toEqual('http://server/pre/'); expect(parseLinkAndReturn(locationIndexUrl, 'http://server/pre/otherPath')).toEqual('http://server/pre/otherPath'); // Note: relies on the previous state! expect(parseLinkAndReturn(locationUrl, 'someIgnoredAbsoluteHref', '#test')).toEqual('http://server/pre/otherPath#test'); }); it('should complain if the path starts with double slashes', function() { expect(function() { parseLinkAndReturn(locationUrl, 'http://server/pre///other/path'); }).toThrowMinErr('$location', 'badpath'); expect(function() { parseLinkAndReturn(locationUrl, 'http://server/pre/\\\\other/path'); }).toThrowMinErr('$location', 'badpath'); expect(function() { parseLinkAndReturn(locationUrl, 'http://server/pre//\\//other/path'); }).toThrowMinErr('$location', 'badpath'); }); it('should complain if no base tag present', function() { module(function($locationProvider) { $locationProvider.html5Mode(true); }); inject(function($browser, $injector) { $browser.$$baseHref = undefined; expect(function() { $injector.get('$location'); }).toThrowMinErr('$location', 'nobase', '$location in HTML5 mode requires a tag to be present!'); }); }); it('should not complain if baseOptOut set to true in html5Mode', function() { module(function($locationProvider) { $locationProvider.html5Mode({ enabled: true, requireBase: false }); }); inject(function($browser, $injector) { $browser.$$baseHref = undefined; expect(function() { $injector.get('$location'); }).not.toThrowMinErr('$location', 'nobase', '$location in HTML5 mode requires a tag to be present!'); }); }); it('should support state', function() { expect(locationUrl.state({a: 2}).state()).toEqual({a: 2}); }); }); describe('LocationHashbangUrl', function() { var locationUrl; it('should rewrite URL', function() { locationUrl = new LocationHashbangUrl('http://server/pre/', 'http://server/pre/', '#'); expect(parseLinkAndReturn(locationUrl, 'http://other')).toEqual(undefined); expect(parseLinkAndReturn(locationUrl, 'http://server/pre/')).toEqual('http://server/pre/'); expect(parseLinkAndReturn(locationUrl, 'http://server/pre/#otherPath')).toEqual('http://server/pre/#/otherPath'); // eslint-disable-next-line no-script-url expect(parseLinkAndReturn(locationUrl, 'javascript:void(0)')).toEqual(undefined); }); it('should not set hash if one was not originally specified', function() { locationUrl = new LocationHashbangUrl('http://server/pre/index.html', 'http://server/pre/', '#'); locationUrl.$$parse('http://server/pre/index.html'); expect(locationUrl.url()).toBe(''); expect(locationUrl.absUrl()).toBe('http://server/pre/index.html'); }); it('should parse hash if one was specified', function() { locationUrl = new LocationHashbangUrl('http://server/pre/index.html', 'http://server/pre/', '#'); locationUrl.$$parse('http://server/pre/index.html#/foo/bar'); expect(locationUrl.url()).toBe('/foo/bar'); expect(locationUrl.absUrl()).toBe('http://server/pre/index.html#/foo/bar'); }); it('should prefix hash url with / if one was originally missing', function() { locationUrl = new LocationHashbangUrl('http://server/pre/index.html', 'http://server/pre/', '#'); locationUrl.$$parse('http://server/pre/index.html#not-starting-with-slash'); expect(locationUrl.url()).toBe('/not-starting-with-slash'); expect(locationUrl.absUrl()).toBe('http://server/pre/index.html#/not-starting-with-slash'); }); it('should not strip stuff from path just because it looks like Windows drive when it\'s not', function() { locationUrl = new LocationHashbangUrl('http://server/pre/index.html', 'http://server/pre/', '#'); locationUrl.$$parse('http://server/pre/index.html#http%3A%2F%2Fexample.com%2F'); expect(locationUrl.url()).toBe('/http://example.com/'); expect(locationUrl.absUrl()).toBe('http://server/pre/index.html#/http://example.com/'); }); it('should throw on url(urlString, stateObject)', function() { expectThrowOnStateChange(locationUrl); }); it('should allow navigating outside the original base URL', function() { locationUrl = new LocationHashbangUrl('http://server/pre/index.html', 'http://server/pre/', '#'); locationUrl.$$parse('http://server/next/index.html'); expect(locationUrl.url()).toBe(''); expect(locationUrl.absUrl()).toBe('http://server/next/index.html'); }); }); describe('LocationHashbangInHtml5Url', function() { /* global LocationHashbangInHtml5Url: false */ var locationUrl, locationIndexUrl; beforeEach(function() { locationUrl = new LocationHashbangInHtml5Url('http://server/pre/', 'http://server/pre/', '#!'); locationIndexUrl = new LocationHashbangInHtml5Url('http://server/pre/index.html', 'http://server/pre/', '#!'); }); it('should rewrite URL', function() { expect(parseLinkAndReturn(locationUrl, 'http://other')).toEqual(undefined); expect(parseLinkAndReturn(locationUrl, 'http://server/pre')).toEqual('http://server/pre/#!'); expect(parseLinkAndReturn(locationUrl, 'http://server/pre/')).toEqual('http://server/pre/#!'); expect(parseLinkAndReturn(locationUrl, 'http://server/pre/otherPath')).toEqual('http://server/pre/#!/otherPath'); // Note: relies on the previous state! expect(parseLinkAndReturn(locationUrl, 'someIgnoredAbsoluteHref', '#test')).toEqual('http://server/pre/#!/otherPath#test'); expect(parseLinkAndReturn(locationIndexUrl, 'http://server/pre')).toEqual('http://server/pre/index.html#!'); expect(parseLinkAndReturn(locationIndexUrl, 'http://server/pre/')).toEqual(undefined); expect(parseLinkAndReturn(locationIndexUrl, 'http://server/pre/otherPath')).toEqual('http://server/pre/index.html#!/otherPath'); // Note: relies on the previous state! expect(parseLinkAndReturn(locationIndexUrl, 'someIgnoredAbsoluteHref', '#test')).toEqual('http://server/pre/index.html#!/otherPath#test'); }); it('should throw on url(urlString, stateObject)', function() { expectThrowOnStateChange(locationUrl); }); it('should not throw when base path is another domain', function() { initService({html5Mode: true, hashPrefix: '!', supportHistory: true}); inject( initBrowser({url: 'http://domain.com/base/', basePath: 'http://otherdomain.com/base/'}), function($location) { expect(function() { $location.absUrl(); }).not.toThrow(); } ); }); }); function initService(options) { return module(function($provide, $locationProvider) { $locationProvider.html5Mode(options.html5Mode); $locationProvider.hashPrefix(options.hashPrefix); $provide.value('$sniffer', {history: options.supportHistory}); }); } function mockUpBrowser(options) { module(function($windowProvider, $browserProvider) { var browser; var parser = window.document.createElement('a'); parser.href = options.initialUrl; $windowProvider.$get = function() { var win = {}; angular.extend(win, window); // Ensure `window` is a reference to the mock global object, so that // jqLite does the right thing. win.window = win; win.history = { state: options.state || null, replaceState: function(state, title, url) { win.history.state = copy(state); if (url) win.location.href = url; }, pushState: function(state, title, url) { win.history.state = copy(state); if (url) win.location.href = url; } }; win.addEventListener = angular.noop; win.removeEventListener = angular.noop; win.location = { get href() { return this.$$getHref(); }, $$getHref: function() { return parser.href; }, set href(val) { this.$$setHref(val); }, $$setHref: function(val) { parser.href = val; }, get hash() { return parser.hash; }, // The parser correctly strips on a single preceding hash character if necessary // before joining the fragment onto the href by a new hash character // See hash setter spec: https://url.spec.whatwg.org/#urlutils-and-urlutilsreadonly-members set hash(val) { parser.hash = val; }, replace: function(val) { win.location.href = val; } }; return win; }; $browserProvider.$get = function($document, $window, $log, $sniffer, $$taskTrackerFactory) { /* global Browser: false */ browser = new Browser($window, $document, $log, $sniffer, $$taskTrackerFactory); browser.baseHref = function() { return options.baseHref; }; return browser; }; }); } function initBrowser(options) { return function($browser) { $browser.url(options.url); $browser.$$baseHref = options.basePath; }; } function expectThrowOnStateChange(location) { expect(function() { location.state({a: 2}); }).toThrowMinErr('$location', 'nostate', 'History API state support is available only ' + 'in HTML5 mode and only in browsers supporting HTML5 History API' ); } function parseLinkAndReturn(location, url, relHref) { if (location.$$parseLinkUrl(url, relHref)) { return location.absUrl(); } return undefined; } });