From 5f1c6cbec532ba9a1b1c1f777e7afbdb88c46dce Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Tue, 26 Aug 2014 15:23:47 -0700 Subject: [PATCH] fix($browser): detect changes to the browser url that happened in sync Closes #6976. --- src/ng/browser.js | 7 +++++++ src/ng/rootScope.js | 2 ++ src/ngMock/angular-mocks.js | 2 ++ test/ng/browserSpecs.js | 37 +++++++++++++++++++++++++++++++++---- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/ng/browser.js b/src/ng/browser.js index 3ca4a7c0a86c..e769c4535f15 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 9938240a60e6..de37f55dd34a 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -687,6 +687,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 ba9790539ff8..cd75c7b34625 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 fc50795b4ec5..f8183887843d 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 }; @@ -419,7 +419,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() { @@ -431,7 +431,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() { @@ -453,7 +453,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() { @@ -587,6 +587,7 @@ describe('browser', function() { fakeWindow.fire('hashchange'); expect(callback).not.toHaveBeenCalled(); }); + }); @@ -620,4 +621,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'); + }); + }); + }); + });