diff --git a/src/ng/browser.js b/src/ng/browser.js index f9502cd4..5876b068 100644 --- a/src/ng/browser.js +++ b/src/ng/browser.js @@ -234,6 +234,13 @@ function Browser(window, document, $log, $sniffer) { return callback; }; + /** + * Checks whether the url has changed outside of Angular. + * Needs to be exported to be able to check for changes that have been done in sync, + * as hashchange/popstate events fire in async. + */ + self.$$checkUrlChange = fireUrlChange; + ////////////////////////////////////////////////////////////// // Misc API ////////////////////////////////////////////////////////////// diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index 13012ad7..b769fdb9 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -608,6 +608,8 @@ function $RootScopeProvider(){ logIdx, logMsg, asyncTask; beginPhase('$digest'); + // Check for changes to browser url that happened in sync before the call to $digest + $browser.$$checkUrlChange(); lastDirtyWatch = null; diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index a68370d5..3ba85ac6 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -56,6 +56,8 @@ angular.mock.$Browser = function() { return listener; }; + self.$$checkUrlChange = angular.noop; + self.cookieHash = {}; self.lastCookieHash = {}; self.deferredFns = []; diff --git a/test/ng/browserSpecs.js b/test/ng/browserSpecs.js index 96a7a149..1bb106eb 100755 --- a/test/ng/browserSpecs.js +++ b/test/ng/browserSpecs.js @@ -36,7 +36,7 @@ function MockWindow() { }; this.location = { - href: 'http://server', + href: 'http://server/', replace: noop }; @@ -414,7 +414,7 @@ describe('browser', function() { expect(replaceState).not.toHaveBeenCalled(); expect(locationReplace).not.toHaveBeenCalled(); - expect(fakeWindow.location.href).toEqual('http://server'); + expect(fakeWindow.location.href).toEqual('http://server/'); }); it('should use history.replaceState when available', function() { @@ -426,7 +426,7 @@ describe('browser', function() { expect(pushState).not.toHaveBeenCalled(); expect(locationReplace).not.toHaveBeenCalled(); - expect(fakeWindow.location.href).toEqual('http://server'); + expect(fakeWindow.location.href).toEqual('http://server/'); }); it('should set location.href when pushState not available', function() { @@ -448,7 +448,7 @@ describe('browser', function() { expect(pushState).not.toHaveBeenCalled(); expect(replaceState).not.toHaveBeenCalled(); - expect(fakeWindow.location.href).toEqual('http://server'); + expect(fakeWindow.location.href).toEqual('http://server/'); }); it('should return $browser to allow chaining', function() { @@ -615,4 +615,32 @@ describe('browser', function() { expect(browser.baseHref()).toEqual('/base/path/'); }); }); + + describe('integration tests with $location', function() { + + beforeEach(module(function($provide, $locationProvider) { + spyOn(fakeWindow.history, 'pushState').andCallFake(function(stateObj, title, newUrl) { + fakeWindow.location.href = newUrl; + }); + $provide.value('$browser', browser); + browser.pollFns = []; + + $locationProvider.html5Mode(true); + })); + + it('should update $location when it was changed outside of Angular in sync '+ + 'before $digest was called', function() { + inject(function($rootScope, $location) { + fakeWindow.history.pushState(null, '', 'http://server/someTestHash'); + + // Verify that infinite digest reported in #6976 no longer occurs + expect(function() { + $rootScope.$digest(); + }).not.toThrow(); + + expect($location.path()).toBe('/someTestHash'); + }); + }); + }); + });