Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 927f355

Browse files
committed
fixup! fix(ngRoute): make route.resolve count as a pending request
1 parent 785c987 commit 927f355

File tree

4 files changed

+116
-56
lines changed

4 files changed

+116
-56
lines changed

src/ngMock/angular-mocks.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ angular.mock.$Browser = function() {
4242
var outstandingRequestCount = 0;
4343
var outstandingRequestCallbacks = [];
4444
self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
45-
self.$$completeOutstandingRequest = function() {
45+
self.$$completeOutstandingRequest = function(fn) {
46+
fn();
4647
outstandingRequestCount--;
4748
if (!outstandingRequestCount) {
4849
while (outstandingRequestCallbacks.length) {

test/e2e/fixtures/ng-route-promise/script.js

+32-14
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,40 @@ angular.
44
module('lettersApp', ['ngRoute']).
55
config(function($routeProvider) {
66
$routeProvider.
7-
when('/foo', {
8-
resolveRedirectTo: function($q) {
7+
otherwise(resolveRedirectTo('/foo1')).
8+
when('/foo1', resolveRedirectTo('/bar1')).
9+
when('/bar1', resolveRedirectTo('/baz1')).
10+
when('/baz1', resolveRedirectTo('/qux1')).
11+
when('/qux1', {
12+
template: '<ul><li ng-repeat="letter in $resolve.letters">{{ letter }}</li></ul>',
13+
resolve: resolveLetters()
14+
}).
15+
when('/foo2', resolveRedirectTo('/bar2')).
16+
when('/bar2', resolveRedirectTo('/baz2')).
17+
when('/baz2', resolveRedirectTo('/qux2')).
18+
when('/qux2', {
19+
template: '{{ $resolve.letters.length }}',
20+
resolve: resolveLetters()
21+
});
22+
23+
// Helpers
24+
function resolveLetters() {
25+
return {
26+
letters: function($q) {
927
return $q(function(resolve) {
10-
window.setTimeout(resolve, 1000, '/bar');
28+
window.setTimeout(resolve, 1000, ['a', 'b', 'c', 'd', 'e']);
1129
});
1230
}
13-
}).
14-
when('/bar', {
15-
template: '<ul><li ng-repeat="letter in $resolve.letters">{{ letter }}</li></ul>',
16-
resolve: {
17-
letters: function($q) {
18-
return $q(function(resolve) {
19-
window.setTimeout(resolve, 1000, ['a', 'b', 'c', 'd', 'e']);
20-
});
21-
}
31+
};
32+
}
33+
34+
function resolveRedirectTo(path) {
35+
return {
36+
resolveRedirectTo: function($q) {
37+
return $q(function(resolve) {
38+
window.setTimeout(resolve, 250, path);
39+
});
2240
}
23-
}).
24-
otherwise('/foo');
41+
};
42+
}
2543
});

test/e2e/tests/ng-route-promise.spec.js

+7-5
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,19 @@ describe('ngRoute promises', function() {
1515
browser.waitForAngular().then(function() {
1616
fail('waitForAngular() should have timed out, but didn\'t');
1717
}, function(error) {
18-
var errorRe = new RegExp(
19-
'Timed out waiting for asynchronous Angular tasks to finish after ' +
20-
'(?:1\\.5 seconds|15\\d\\d ?ms)\\.', '');
21-
expect(error.message).toMatch(errorRe);
18+
expect(error.message).toContain('Timed out waiting for asynchronous Angular tasks to finish');
2219
});
2320
});
2421

22+
it('should wait for route promises when navigating to another route', function() {
23+
browser.setLocation('/foo2');
24+
expect(element(by.tagName('body')).getText()).toBe('5');
25+
});
26+
2527
afterEach(function(done) {
2628
// Restore old timeout limit
2729
browser.getProcessedConfig().then(function(config) {
2830
return browser.manage().timeouts().setScriptTimeout(config.allScriptsTimeout);
29-
}).then(function() { done(); });
31+
}).then(done);
3032
});
3133
});

test/ngRoute/routeSpec.js

+75-36
Original file line numberDiff line numberDiff line change
@@ -2088,12 +2088,15 @@ describe('$route', function() {
20882088
var deferred;
20892089

20902090
module(function($provide, $routeProvider) {
2091-
$routeProvider.when('/path', { template: '', resolve: {
2092-
a: function($q) {
2093-
deferred = $q.defer();
2094-
return deferred.promise;
2091+
$routeProvider.when('/path', {
2092+
template: '',
2093+
resolve: {
2094+
a: function($q) {
2095+
deferred = $q.defer();
2096+
return deferred.promise;
2097+
}
20952098
}
2096-
} });
2099+
});
20972100
});
20982101

20992102
inject(function($location, $route, $rootScope, $httpBackend, $$testability) {
@@ -2114,12 +2117,15 @@ describe('$route', function() {
21142117
var deferred;
21152118

21162119
module(function($provide, $routeProvider) {
2117-
$routeProvider.when('/path', { template: '', resolve: {
2118-
a: function($q) {
2119-
deferred = $q.defer();
2120-
return deferred.promise;
2120+
$routeProvider.when('/path', {
2121+
template: '',
2122+
resolve: {
2123+
a: function($q) {
2124+
deferred = $q.defer();
2125+
return deferred.promise;
2126+
}
21212127
}
2122-
} });
2128+
});
21232129
});
21242130

21252131
inject(function($location, $route, $rootScope, $httpBackend, $$testability) {
@@ -2140,7 +2146,8 @@ describe('$route', function() {
21402146
var deferred;
21412147

21422148
module(function($provide, $routeProvider) {
2143-
$routeProvider.when('/path', { template: '', resolveRedirectTo: function($q) {
2149+
$routeProvider.when('/path', {
2150+
resolveRedirectTo: function($q) {
21442151
deferred = $q.defer();
21452152
return deferred.promise;
21462153
}
@@ -2165,7 +2172,8 @@ describe('$route', function() {
21652172
var deferred;
21662173

21672174
module(function($provide, $routeProvider) {
2168-
$routeProvider.when('/path', { template: '', resolveRedirectTo: function($q) {
2175+
$routeProvider.when('/path', {
2176+
resolveRedirectTo: function($q) {
21692177
deferred = $q.defer();
21702178
return deferred.promise;
21712179
}
@@ -2187,45 +2195,76 @@ describe('$route', function() {
21872195
});
21882196

21892197
it('should wait for all route promises before calling callbacks', function() {
2190-
var redirectDeferred;
2191-
var localsDeferred;
2198+
var deferreds = {};
21922199

21932200
module(function($provide, $routeProvider) {
2194-
$routeProvider.
2195-
when('/foo', { template: '', resolveRedirectTo: function($q) {
2196-
redirectDeferred = $q.defer();
2197-
return redirectDeferred.promise;
2198-
} }).
2199-
when('/bar', { template: '', resolve: {
2201+
// While normally `$browser.defer()` modifies the `outstandingRequestCount`, the mocked
2202+
// version (provided by `ngMock`) does not. This doesn't matter in most tests, but it does
2203+
// here:
2204+
// `$browser.defer()` will be indirectly called as a result of `$locationWatch`'s call to
2205+
// `$rootScope.$evalAsync()`. In the async function, the `$locationChangeSuccess` event will
2206+
// be broadcasted, which will in turn trigger `commitRoute()` in `ngRoute`.
2207+
// `outstandingRequestCount` must not reach 0 during the time between calling
2208+
// `$rootScope.$evalAsync()` and executing the async function.
2209+
$provide.decorator('$browser', function($delegate) {
2210+
var oldDefer = $delegate.defer;
2211+
var newDefer = function(fn, delay) {
2212+
var requestCountAwareFn = function() { $delegate.$$completeOutstandingRequest(fn); };
2213+
$delegate.$$incOutstandingRequestCount();
2214+
return oldDefer.call($delegate, requestCountAwareFn, delay);
2215+
};
2216+
2217+
$delegate.defer = angular.extend(newDefer, oldDefer);
2218+
2219+
return $delegate;
2220+
});
2221+
2222+
addRouteWithAsyncRedirect('/foo', '/bar');
2223+
addRouteWithAsyncRedirect('/bar', '/baz');
2224+
addRouteWithAsyncRedirect('/baz', '/qux');
2225+
$routeProvider.when('/qux', {
2226+
template: '',
2227+
resolve: {
22002228
a: function($q) {
2201-
localsDeferred = $q.defer();
2202-
return localsDeferred.promise;
2229+
var deferred = deferreds['/qux'] = $q.defer();
2230+
return deferred.promise;
2231+
}
2232+
}
2233+
});
2234+
2235+
// Helpers
2236+
function addRouteWithAsyncRedirect(fromPath, toPath) {
2237+
$routeProvider.when(fromPath, {
2238+
resolveRedirectTo: function($q) {
2239+
var deferred = deferreds[fromPath] = $q.defer();
2240+
return deferred.promise.then(function() { return toPath; });
22032241
}
2204-
} });
2242+
});
2243+
}
22052244
});
22062245

2207-
inject(function($browser, $location, $route, $rootScope, $httpBackend, $$testability) {
2246+
inject(function($browser, $location, $rootScope, $route, $$testability) {
22082247
$location.path('/foo');
22092248
$rootScope.$digest();
22102249

22112250
var callback = jasmine.createSpy('callback');
22122251
$$testability.whenStable(callback);
22132252
expect(callback).not.toHaveBeenCalled();
22142253

2215-
// ngRoute code is wrapped in a $browser.defer call (via $rootScope.evalAsync), which in
2216-
// production would automatically call $$incOutstandingRequestCount and
2217-
// $$completeOutstandingRequest before/after execution. However, ngMock does not call these
2218-
// functions in its $browser.defer implementation, as this logic would make many tests
2219-
// difficult to write. In this one case we need that logic, however, so we call the
2220-
// functions manually.
2221-
$browser.$$incOutstandingRequestCount();
2222-
redirectDeferred.resolve('/bar');
2223-
$rootScope.$digest();
2224-
$browser.$$completeOutstandingRequest();
2254+
deferreds['/foo'].resolve();
2255+
$browser.defer.flush();
22252256
expect(callback).not.toHaveBeenCalled();
22262257

2227-
localsDeferred.resolve();
2228-
$rootScope.$digest();
2258+
deferreds['/bar'].resolve();
2259+
$browser.defer.flush();
2260+
expect(callback).not.toHaveBeenCalled();
2261+
2262+
deferreds['/baz'].resolve();
2263+
$browser.defer.flush();
2264+
expect(callback).not.toHaveBeenCalled();
2265+
2266+
deferreds['/qux'].resolve();
2267+
$browser.defer.flush();
22292268
expect(callback).toHaveBeenCalled();
22302269
});
22312270
});

0 commit comments

Comments
 (0)