refactor($route): move when/otherwise to provider

This commit is contained in:
Misko Hevery 2012-02-15 15:45:07 -08:00 committed by Vojta Jina
parent ef7346ff70
commit f16bd2f747
4 changed files with 636 additions and 542 deletions

View file

@ -63,6 +63,78 @@
</doc:example> </doc:example>
*/ */
function $RouteProvider(){ function $RouteProvider(){
var routes = {};
/**
* @ngdoc method
* @name angular.module.ng.$route#when
* @methodOf angular.module.ng.$route
*
* @param {string} path Route path (matched against `$location.hash`)
* @param {Object} route Mapping information to be assigned to `$route.current` on route
* match.
*
* Object properties:
*
* - `controller` `{function()=}` Controller fn that should be associated with newly
* created scope.
* - `template` `{string=}` path to an html template that should be used by
* {@link angular.module.ng.$compileProvider.directive.ng:view ng:view} or
* {@link angular.module.ng.$compileProvider.directive.ng:include ng:include} widgets.
* - `redirectTo` {(string|function())=} value to update
* {@link angular.module.ng.$location $location} path with and trigger route redirection.
*
* If `redirectTo` is a function, it will be called with the following parameters:
*
* - `{Object.<string>}` - route parameters extracted from the current
* `$location.path()` by applying the current route template.
* - `{string}` - current `$location.path()`
* - `{Object}` - current `$location.search()`
*
* The custom `redirectTo` function is expected to return a string which will be used
* to update `$location.path()` and `$location.search()`.
*
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search()
* changes.
*
* If the option is set to false and url in the browser changes, then
* $routeUpdate event is emited on the current route scope. You can use this event to
* react to {@link angular.module.ng.$routeParams} changes:
*
* function MyCtrl($route, $routeParams) {
* this.$on('$routeUpdate', function() {
* // do stuff with $routeParams
* });
* }
*
* @returns {Object} route object
*
* @description
* Adds a new route definition to the `$route` service.
*/
this.when = function(path, route) {
var routeDef = routes[path];
if (!routeDef) routeDef = routes[path] = {reloadOnSearch: true};
if (route) extend(routeDef, route); // TODO(im): what the heck? merge two route definitions?
return routeDef;
};
/**
* @ngdoc method
* @name angular.module.ng.$route#otherwise
* @methodOf angular.module.ng.$route
*
* @description
* Sets route definition that will be used on route change when no other route definition
* is matched.
*
* @param {Object} params Mapping information to be assigned to `$route.current`.
*/
this.otherwise = function(params) {
this.when(null, params);
};
this.$get = ['$rootScope', '$location', '$routeParams', '$controller', this.$get = ['$rootScope', '$location', '$routeParams', '$controller',
function( $rootScope, $location, $routeParams, $controller) { function( $rootScope, $location, $routeParams, $controller) {
/** /**
@ -112,8 +184,7 @@ function $RouteProvider(){
* instance of the Controller. * instance of the Controller.
*/ */
var routes = {}, var matcher = switchRouteMatcher,
matcher = switchRouteMatcher,
parentScope = $rootScope, parentScope = $rootScope,
dirty = 0, dirty = 0,
forceReload = false, forceReload = false,
@ -136,76 +207,6 @@ function $RouteProvider(){
if (scope) parentScope = scope; if (scope) parentScope = scope;
}, },
/**
* @ngdoc method
* @name angular.module.ng.$route#when
* @methodOf angular.module.ng.$route
*
* @param {string} path Route path (matched against `$location.hash`)
* @param {Object} route Mapping information to be assigned to `$route.current` on route
* match.
*
* Object properties:
*
* - `controller` `{function()=}` Controller fn that should be associated with newly
* created scope.
* - `template` `{string=}` path to an html template that should be used by
* {@link angular.module.ng.$compileProvider.directive.ng:view ng:view} or
* {@link angular.module.ng.$compileProvider.directive.ng:include ng:include} widgets.
* - `redirectTo` {(string|function())=} value to update
* {@link angular.module.ng.$location $location} path with and trigger route redirection.
*
* If `redirectTo` is a function, it will be called with the following parameters:
*
* - `{Object.<string>}` - route parameters extracted from the current
* `$location.path()` by applying the current route template.
* - `{string}` - current `$location.path()`
* - `{Object}` - current `$location.search()`
*
* The custom `redirectTo` function is expected to return a string which will be used
* to update `$location.path()` and `$location.search()`.
*
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search()
* changes.
*
* If the option is set to false and url in the browser changes, then
* $routeUpdate event is emited on the current route scope. You can use this event to
* react to {@link angular.module.ng.$routeParams} changes:
*
* function MyCtrl($route, $routeParams) {
* this.$on('$routeUpdate', function() {
* // do stuff with $routeParams
* });
* }
*
* @returns {Object} route object
*
* @description
* Adds a new route definition to the `$route` service.
*/
when: function(path, route) {
var routeDef = routes[path];
if (!routeDef) routeDef = routes[path] = {reloadOnSearch: true};
if (route) extend(routeDef, route); // TODO(im): what the heck? merge two route definitions?
dirty++;
return routeDef;
},
/**
* @ngdoc method
* @name angular.module.ng.$route#otherwise
* @methodOf angular.module.ng.$route
*
* @description
* Sets route definition that will be used on route change when no other route definition
* is matched.
*
* @param {Object} params Mapping information to be assigned to `$route.current`.
*/
otherwise: function(params) {
$route.when(null, params);
},
/** /**
* @ngdoc method * @ngdoc method
* @name angular.module.ng.$route#reload * @name angular.module.ng.$route#reload

View file

@ -1,16 +1,20 @@
'use strict'; 'use strict';
describe('$routeParams', function() { describe('$routeParams', function() {
it('should publish the params into a service', inject(function($rootScope, $route, $location, $routeParams) { it('should publish the params into a service', function() {
$route.when('/foo'); module(function($routeProvider) {
$route.when('/bar/:barId'); $routeProvider.when('/foo');
$routeProvider.when('/bar/:barId');
});
$location.path('/foo').search('a=b'); inject(function($rootScope, $route, $location, $routeParams) {
$rootScope.$digest(); $location.path('/foo').search('a=b');
expect($routeParams).toEqual({a:'b'}); $rootScope.$digest();
expect($routeParams).toEqual({a:'b'});
$location.path('/bar/123').search('x=abc'); $location.path('/bar/123').search('x=abc');
$rootScope.$digest(); $rootScope.$digest();
expect($routeParams).toEqual({barId:'123', x:'abc'}); expect($routeParams).toEqual({barId:'123', x:'abc'});
})); });
});
}); });

View file

@ -1,7 +1,7 @@
'use strict'; 'use strict';
describe('$route', function() { describe('$route', function() {
it('should route and fire change event', inject(function($route, $location, $rootScope) { it('should route and fire change event', function() {
var log = '', var log = '',
lastRoute, lastRoute,
nextRoute; nextRoute;
@ -9,367 +9,415 @@ describe('$route', function() {
function BookChapter() { function BookChapter() {
log += '<init>;'; log += '<init>;';
} }
module(function($routeProvider) {
$route.when('/Book/:book/Chapter/:chapter', $routeProvider.when('/Book/:book/Chapter/:chapter',
{controller: BookChapter, template: 'Chapter.html'}); {controller: BookChapter, template: 'Chapter.html'});
$route.when('/Blank'); $routeProvider.when('/Blank');
$rootScope.$on('$beforeRouteChange', function(event, next, current) {
log += 'before();';
expect(current).toBe($route.current);
lastRoute = current;
nextRoute = next;
}); });
$rootScope.$on('$afterRouteChange', function(event, current, last) { inject(function($route, $location, $rootScope) {
log += 'after();'; $rootScope.$on('$beforeRouteChange', function(event, next, current) {
expect(current).toBe($route.current); log += 'before();';
expect(lastRoute).toBe(last); expect(current).toBe($route.current);
expect(nextRoute).toBe(current); lastRoute = current;
nextRoute = next;
});
$rootScope.$on('$afterRouteChange', function(event, current, last) {
log += 'after();';
expect(current).toBe($route.current);
expect(lastRoute).toBe(last);
expect(nextRoute).toBe(current);
});
$location.path('/Book/Moby/Chapter/Intro').search('p=123');
$rootScope.$digest();
expect(log).toEqual('before();<init>;after();');
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', p:'123'});
var lastId = $route.current.scope.$id;
log = '';
$location.path('/Blank').search('ignore');
$rootScope.$digest();
expect(log).toEqual('before();after();');
expect($route.current.params).toEqual({ignore:true});
expect($route.current.scope.$id).not.toEqual(lastId);
log = '';
$location.path('/NONE');
$rootScope.$digest();
expect(log).toEqual('before();after();');
expect($route.current).toEqual(null);
});
});
it('should match a route that contains special chars in the path', function() {
module(function($routeProvider) {
$routeProvider.when('/$test.23/foo(bar)/:baz', {template: 'test.html'});
});
inject(function($route, $location, $rootScope) {
$location.path('/test');
$rootScope.$digest();
expect($route.current).toBeUndefined();
$location.path('/$testX23/foo(bar)/222');
$rootScope.$digest();
expect($route.current).toBeUndefined();
$location.path('/$test.23/foo(bar)/222');
$rootScope.$digest();
expect($route.current).toBeDefined();
$location.path('/$test.23/foo\\(bar)/222');
$rootScope.$digest();
expect($route.current).toBeUndefined();
});
});
it('should change route even when only search param changes', function() {
module(function($routeProvider) {
$routeProvider.when('/test', {template: 'test.html'});
}); });
$location.path('/Book/Moby/Chapter/Intro').search('p=123'); inject(function($route, $location, $rootScope) {
$rootScope.$digest(); var callback = jasmine.createSpy('onRouteChange');
expect(log).toEqual('before();<init>;after();');
expect($route.current.params).toEqual({book:'Moby', chapter:'Intro', p:'123'});
var lastId = $route.current.scope.$id;
log = ''; $rootScope.$on('$beforeRouteChange', callback);
$location.path('/Blank').search('ignore'); $location.path('/test');
$rootScope.$digest(); $rootScope.$digest();
expect(log).toEqual('before();after();'); callback.reset();
expect($route.current.params).toEqual({ignore:true});
expect($route.current.scope.$id).not.toEqual(lastId);
log = ''; $location.search({any: true});
$location.path('/NONE'); $rootScope.$digest();
$rootScope.$digest();
expect(log).toEqual('before();after();');
expect($route.current).toEqual(null);
$route.when('/NONE', {template:'instant update'}); expect(callback).toHaveBeenCalled();
$rootScope.$digest(); });
expect($route.current.template).toEqual('instant update'); });
}));
it('should match a route that contains special chars in the path', it('should allow routes to be defined with just templates without controllers', function() {
inject(function($route, $location, $rootScope) { module(function($routeProvider) {
$route.when('/$test.23/foo(bar)/:baz', {template: 'test.html'}); $routeProvider.when('/foo', {template: 'foo.html'});
});
$location.path('/test'); inject(function($route, $location, $rootScope) {
$rootScope.$digest();
expect($route.current).toBeUndefined();
$location.path('/$testX23/foo(bar)/222');
$rootScope.$digest();
expect($route.current).toBeUndefined();
$location.path('/$test.23/foo(bar)/222');
$rootScope.$digest();
expect($route.current).toBeDefined();
$location.path('/$test.23/foo\\(bar)/222');
$rootScope.$digest();
expect($route.current).toBeUndefined();
}));
it('should change route even when only search param changes',
inject(function($route, $location, $rootScope) {
var callback = jasmine.createSpy('onRouteChange');
$route.when('/test', {template: 'test.html'});
$rootScope.$on('$beforeRouteChange', callback);
$location.path('/test');
$rootScope.$digest();
callback.reset();
$location.search({any: true});
$rootScope.$digest();
expect(callback).toHaveBeenCalled();
}));
it('should allow routes to be defined with just templates without controllers',
inject(function($route, $location, $rootScope) {
var onChangeSpy = jasmine.createSpy('onChange');
$route.when('/foo', {template: 'foo.html'});
$rootScope.$on('$beforeRouteChange', onChangeSpy);
expect($route.current).toBeUndefined();
expect(onChangeSpy).not.toHaveBeenCalled();
$location.path('/foo');
$rootScope.$digest();
expect($route.current.template).toEqual('foo.html');
expect($route.current.controller).toBeUndefined();
expect(onChangeSpy).toHaveBeenCalled();
}));
it('should handle unknown routes with "otherwise" route definition',
inject(function($route, $location, $rootScope) {
var onChangeSpy = jasmine.createSpy('onChange');
function NotFoundCtrl($scope) {$scope.notFoundProp = 'not found!';}
$route.when('/foo', {template: 'foo.html'});
$route.otherwise({template: '404.html', controller: NotFoundCtrl});
$rootScope.$on('$beforeRouteChange', onChangeSpy);
expect($route.current).toBeUndefined();
expect(onChangeSpy).not.toHaveBeenCalled();
$location.path('/unknownRoute');
$rootScope.$digest();
expect($route.current.template).toBe('404.html');
expect($route.current.controller).toBe(NotFoundCtrl);
expect($route.current.scope.notFoundProp).toBe('not found!');
expect(onChangeSpy).toHaveBeenCalled();
onChangeSpy.reset();
$location.path('/foo');
$rootScope.$digest();
expect($route.current.template).toEqual('foo.html');
expect($route.current.controller).toBeUndefined();
expect($route.current.scope.notFoundProp).toBeUndefined();
expect(onChangeSpy).toHaveBeenCalled();
}));
it('should $destroy old routes', inject(function($route, $location, $rootScope) {
$route.when('/foo', {template: 'foo.html', controller: function() {this.name = 'FOO';}});
$route.when('/bar', {template: 'bar.html', controller: function() {this.name = 'BAR';}});
$route.when('/baz', {template: 'baz.html'});
expect($rootScope.$childHead).toEqual(null);
$location.path('/foo');
$rootScope.$digest();
expect($rootScope.$$childHead.$id).toBeTruthy();
expect($rootScope.$$childHead.$id).toEqual($rootScope.$$childTail.$id);
$location.path('/bar');
$rootScope.$digest();
expect($rootScope.$$childHead.$id).toBeTruthy();
expect($rootScope.$$childHead.$id).toEqual($rootScope.$$childTail.$id);
$location.path('/baz');
$rootScope.$digest();
expect($rootScope.$$childHead.$id).toBeTruthy();
expect($rootScope.$$childHead.$id).toEqual($rootScope.$$childTail.$id);
$location.path('/');
$rootScope.$digest();
expect($rootScope.$$childHead).toEqual(null);
expect($rootScope.$$childTail).toEqual(null);
}));
it('should infer arguments in injection', inject(function($route, $location, $rootScope) {
var injectedRoute;
$route.when('/test', {controller: function($route) {injectedRoute = $route;}});
$location.path('/test');
$rootScope.$digest();
expect(injectedRoute).toBe($route);
}));
describe('redirection', function() {
it('should support redirection via redirectTo property by updating $location',
inject(function($route, $location, $rootScope) {
var onChangeSpy = jasmine.createSpy('onChange'); var onChangeSpy = jasmine.createSpy('onChange');
$route.when('/', {redirectTo: '/foo'});
$route.when('/foo', {template: 'foo.html'});
$route.when('/bar', {template: 'bar.html'});
$route.when('/baz', {redirectTo: '/bar'});
$route.otherwise({template: '404.html'});
$rootScope.$on('$beforeRouteChange', onChangeSpy); $rootScope.$on('$beforeRouteChange', onChangeSpy);
expect($route.current).toBeUndefined(); expect($route.current).toBeUndefined();
expect(onChangeSpy).not.toHaveBeenCalled(); expect(onChangeSpy).not.toHaveBeenCalled();
$location.path('/'); $location.path('/foo');
$rootScope.$digest(); $rootScope.$digest();
expect($location.path()).toBe('/foo');
expect($route.current.template).toBe('foo.html'); expect($route.current.template).toEqual('foo.html');
expect(onChangeSpy.callCount).toBe(2); expect($route.current.controller).toBeUndefined();
expect(onChangeSpy).toHaveBeenCalled();
});
});
it('should handle unknown routes with "otherwise" route definition', function() {
function NotFoundCtrl($scope) {
$scope.notFoundProp = 'not found!';
}
module(function($routeProvider){
$routeProvider.when('/foo', {template: 'foo.html'});
$routeProvider.otherwise({template: '404.html', controller: NotFoundCtrl});
});
inject(function($route, $location, $rootScope) {
var onChangeSpy = jasmine.createSpy('onChange');
$rootScope.$on('$beforeRouteChange', onChangeSpy);
expect($route.current).toBeUndefined();
expect(onChangeSpy).not.toHaveBeenCalled();
$location.path('/unknownRoute');
$rootScope.$digest();
expect($route.current.template).toBe('404.html');
expect($route.current.controller).toBe(NotFoundCtrl);
expect($route.current.scope.notFoundProp).toBe('not found!');
expect(onChangeSpy).toHaveBeenCalled();
onChangeSpy.reset(); onChangeSpy.reset();
$location.path('/foo');
$rootScope.$digest();
expect($route.current.template).toEqual('foo.html');
expect($route.current.controller).toBeUndefined();
expect($route.current.scope.notFoundProp).toBeUndefined();
expect(onChangeSpy).toHaveBeenCalled();
});
});
it('should $destroy old routes', function() {
module(function($routeProvider) {
$routeProvider.when('/foo', {template: 'foo.html', controller: function() {this.name = 'FOO';}});
$routeProvider.when('/bar', {template: 'bar.html', controller: function() {this.name = 'BAR';}});
$routeProvider.when('/baz', {template: 'baz.html'});
});
inject(function($route, $location, $rootScope) {
expect($rootScope.$childHead).toEqual(null);
$location.path('/foo');
$rootScope.$digest();
expect($rootScope.$$childHead.$id).toBeTruthy();
expect($rootScope.$$childHead.$id).toEqual($rootScope.$$childTail.$id);
$location.path('/bar');
$rootScope.$digest();
expect($rootScope.$$childHead.$id).toBeTruthy();
expect($rootScope.$$childHead.$id).toEqual($rootScope.$$childTail.$id);
$location.path('/baz'); $location.path('/baz');
$rootScope.$digest(); $rootScope.$digest();
expect($location.path()).toBe('/bar'); expect($rootScope.$$childHead.$id).toBeTruthy();
expect($route.current.template).toBe('bar.html'); expect($rootScope.$$childHead.$id).toEqual($rootScope.$$childTail.$id);
expect(onChangeSpy.callCount).toBe(2);
}));
$location.path('/');
it('should interpolate route vars in the redirected path from original path',
inject(function($route, $location, $rootScope) {
$route.when('/foo/:id/foo/:subid/:extraId', {redirectTo: '/bar/:id/:subid/23'});
$route.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'});
$location.path('/foo/id1/foo/subid3/gah');
$rootScope.$digest(); $rootScope.$digest();
expect($rootScope.$$childHead).toEqual(null);
expect($location.path()).toEqual('/bar/id1/subid3/23'); expect($rootScope.$$childTail).toEqual(null);
expect($location.search()).toEqual({extraId: 'gah'}); });
expect($route.current.template).toEqual('bar.html'); });
}));
it('should interpolate route vars in the redirected path from original search', it('should infer arguments in injection', function() {
inject(function($route, $location, $rootScope) { var injectedRoute;
$route.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'}); module(function($routeProvider) {
$route.when('/foo/:id/:extra', {redirectTo: '/bar/:id/:subid/99'}); $routeProvider.when('/test', {controller: function($route) {injectedRoute = $route;}});
});
$location.path('/foo/id3/eId').search('subid=sid1&appended=true'); inject(function($route, $location, $rootScope) {
$location.path('/test');
$rootScope.$digest(); $rootScope.$digest();
expect(injectedRoute).toBe($route);
expect($location.path()).toEqual('/bar/id3/sid1/99'); });
expect($location.search()).toEqual({appended: 'true', extra: 'eId'}); });
expect($route.current.template).toEqual('bar.html');
}));
it('should allow custom redirectTo function to be used', describe('redirection', function() {
inject(function($route, $location, $rootScope) { it('should support redirection via redirectTo property by updating $location', function() {
$route.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'}); module(function($routeProvider) {
$route.when('/foo/:id', {redirectTo: customRedirectFn}); $routeProvider.when('/', {redirectTo: '/foo'});
$routeProvider.when('/foo', {template: 'foo.html'});
$location.path('/foo/id3').search('subid=sid1&appended=true'); $routeProvider.when('/bar', {template: 'bar.html'});
$rootScope.$digest(); $routeProvider.when('/baz', {redirectTo: '/bar'});
$routeProvider.otherwise({template: '404.html'});
expect($location.path()).toEqual('/custom');
function customRedirectFn(routePathParams, path, search) {
expect(routePathParams).toEqual({id: 'id3'});
expect(path).toEqual($location.path());
expect(search).toEqual($location.search());
return '/custom';
}
}));
it('should replace the url when redirecting', inject(function($route, $location, $rootScope) {
$route.when('/bar/:id', {template: 'bar.html'});
$route.when('/foo/:id/:extra', {redirectTo: '/bar/:id'});
var replace;
$rootScope.$watch(function() {
if (isUndefined(replace)) replace = $location.$$replace;
}); });
$location.path('/foo/id3/eId'); inject(function($route, $location, $rootScope) {
$rootScope.$digest(); var onChangeSpy = jasmine.createSpy('onChange');
expect($location.path()).toEqual('/bar/id3'); $rootScope.$on('$beforeRouteChange', onChangeSpy);
expect(replace).toBe(true); expect($route.current).toBeUndefined();
})); expect(onChangeSpy).not.toHaveBeenCalled();
$location.path('/');
$rootScope.$digest();
expect($location.path()).toBe('/foo');
expect($route.current.template).toBe('foo.html');
expect(onChangeSpy.callCount).toBe(2);
onChangeSpy.reset();
$location.path('/baz');
$rootScope.$digest();
expect($location.path()).toBe('/bar');
expect($route.current.template).toBe('bar.html');
expect(onChangeSpy.callCount).toBe(2);
});
});
it('should interpolate route vars in the redirected path from original path', function() {
module(function($routeProvider) {
$routeProvider.when('/foo/:id/foo/:subid/:extraId', {redirectTo: '/bar/:id/:subid/23'});
$routeProvider.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'});
});
inject(function($route, $location, $rootScope) {
$location.path('/foo/id1/foo/subid3/gah');
$rootScope.$digest();
expect($location.path()).toEqual('/bar/id1/subid3/23');
expect($location.search()).toEqual({extraId: 'gah'});
expect($route.current.template).toEqual('bar.html');
});
});
it('should interpolate route vars in the redirected path from original search', function() {
module(function($routeProvider) {
$routeProvider.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'});
$routeProvider.when('/foo/:id/:extra', {redirectTo: '/bar/:id/:subid/99'});
});
inject(function($route, $location, $rootScope) {
$location.path('/foo/id3/eId').search('subid=sid1&appended=true');
$rootScope.$digest();
expect($location.path()).toEqual('/bar/id3/sid1/99');
expect($location.search()).toEqual({appended: 'true', extra: 'eId'});
expect($route.current.template).toEqual('bar.html');
});
});
it('should allow custom redirectTo function to be used', function() {
function customRedirectFn(routePathParams, path, search) {
expect(routePathParams).toEqual({id: 'id3'});
expect(path).toEqual('/foo/id3');
expect(search).toEqual({ subid: 'sid1', appended: 'true' });
return '/custom';
}
module(function($routeProvider){
$routeProvider.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'});
$routeProvider.when('/foo/:id', {redirectTo: customRedirectFn});
});
inject(function($route, $location, $rootScope) {
$location.path('/foo/id3').search('subid=sid1&appended=true');
$rootScope.$digest();
expect($location.path()).toEqual('/custom');
});
});
it('should replace the url when redirecting', function() {
module(function($routeProvider) {
$routeProvider.when('/bar/:id', {template: 'bar.html'});
$routeProvider.when('/foo/:id/:extra', {redirectTo: '/bar/:id'});
});
inject(function($route, $location, $rootScope) {
var replace;
$rootScope.$watch(function() {
if (isUndefined(replace)) replace = $location.$$replace;
});
$location.path('/foo/id3/eId');
$rootScope.$digest();
expect($location.path()).toEqual('/bar/id3');
expect(replace).toBe(true);
});
});
}); });
describe('reloadOnSearch', function() { describe('reloadOnSearch', function() {
it('should reload a route when reloadOnSearch is enabled and .search() changes', it('should reload a route when reloadOnSearch is enabled and .search() changes', function() {
inject(function($route, $location, $rootScope, $routeParams) {
var reloaded = jasmine.createSpy('route reload'); var reloaded = jasmine.createSpy('route reload');
$route.when('/foo', {controller: FooCtrl});
$rootScope.$on('$beforeRouteChange', reloaded);
function FooCtrl() { function FooCtrl() {
reloaded(); reloaded();
} }
$location.path('/foo'); module(function($routeProvider) {
$rootScope.$digest(); $routeProvider.when('/foo', {controller: FooCtrl});
expect(reloaded).toHaveBeenCalled(); });
expect($routeParams).toEqual({});
reloaded.reset();
// trigger reload inject(function($route, $location, $rootScope, $routeParams) {
$location.search({foo: 'bar'}); $rootScope.$on('$beforeRouteChange', reloaded);
$rootScope.$digest(); $location.path('/foo');
expect(reloaded).toHaveBeenCalled(); $rootScope.$digest();
expect($routeParams).toEqual({foo:'bar'}); expect(reloaded).toHaveBeenCalled();
})); expect($routeParams).toEqual({});
reloaded.reset();
// trigger reload
$location.search({foo: 'bar'});
$rootScope.$digest();
expect(reloaded).toHaveBeenCalled();
expect($routeParams).toEqual({foo:'bar'});
});
});
it('should not reload a route when reloadOnSearch is disabled and only .search() changes', it('should not reload a route when reloadOnSearch is disabled and only .search() changes', function() {
inject(function($route, $location, $rootScope) {
var reloaded = jasmine.createSpy('route reload'), var reloaded = jasmine.createSpy('route reload'),
routeUpdateEvent = jasmine.createSpy('route reload'); routeUpdateEvent = jasmine.createSpy('route reload');
$route.when('/foo', {controller: FooCtrl, reloadOnSearch: false});
$rootScope.$on('$beforeRouteChange', reloaded);
function FooCtrl($scope) { function FooCtrl($scope) {
reloaded(); reloaded();
$scope.$on('$routeUpdate', routeUpdateEvent); $scope.$on('$routeUpdate', routeUpdateEvent);
} }
expect(reloaded).not.toHaveBeenCalled(); module(function($routeProvider) {
$routeProvider.when('/foo', {controller: FooCtrl, reloadOnSearch: false});
});
$location.path('/foo'); inject(function($route, $location, $rootScope) {
$rootScope.$digest(); $rootScope.$on('$beforeRouteChange', reloaded);
expect(reloaded).toHaveBeenCalled();
expect(routeUpdateEvent).not.toHaveBeenCalled();
reloaded.reset();
// don't trigger reload expect(reloaded).not.toHaveBeenCalled();
$location.search({foo: 'bar'});
$rootScope.$digest(); $location.path('/foo');
expect(reloaded).not.toHaveBeenCalled(); $rootScope.$digest();
expect(routeUpdateEvent).toHaveBeenCalled(); expect(reloaded).toHaveBeenCalled();
})); expect(routeUpdateEvent).not.toHaveBeenCalled();
reloaded.reset();
// don't trigger reload
$location.search({foo: 'bar'});
$rootScope.$digest();
expect(reloaded).not.toHaveBeenCalled();
expect(routeUpdateEvent).toHaveBeenCalled();
});
});
it('should reload reloadOnSearch route when url differs only in route path param', it('should reload reloadOnSearch route when url differs only in route path param', function() {
inject(function($route, $location, $rootScope) {
var reloaded = jasmine.createSpy('routeReload'), var reloaded = jasmine.createSpy('routeReload'),
onRouteChange = jasmine.createSpy('onRouteChange'); onRouteChange = jasmine.createSpy('onRouteChange');
$route.when('/foo/:fooId', {controller: FooCtrl, reloadOnSearch: false});
$rootScope.$on('$beforeRouteChange', onRouteChange);
function FooCtrl() { function FooCtrl() {
reloaded(); reloaded();
} }
expect(reloaded).not.toHaveBeenCalled(); module(function($routeProvider) {
expect(onRouteChange).not.toHaveBeenCalled(); $routeProvider.when('/foo/:fooId', {controller: FooCtrl, reloadOnSearch: false});
});
$location.path('/foo/aaa'); inject(function($route, $location, $rootScope) {
$rootScope.$digest(); $rootScope.$on('$beforeRouteChange', onRouteChange);
expect(reloaded).toHaveBeenCalled();
expect(onRouteChange).toHaveBeenCalled();
reloaded.reset();
onRouteChange.reset();
$location.path('/foo/bbb'); expect(reloaded).not.toHaveBeenCalled();
$rootScope.$digest(); expect(onRouteChange).not.toHaveBeenCalled();
expect(reloaded).toHaveBeenCalled();
expect(onRouteChange).toHaveBeenCalled();
reloaded.reset();
onRouteChange.reset();
$location.search({foo: 'bar'}); $location.path('/foo/aaa');
$rootScope.$digest(); $rootScope.$digest();
expect(reloaded).not.toHaveBeenCalled(); expect(reloaded).toHaveBeenCalled();
expect(onRouteChange).not.toHaveBeenCalled(); expect(onRouteChange).toHaveBeenCalled();
})); reloaded.reset();
onRouteChange.reset();
$location.path('/foo/bbb');
$rootScope.$digest();
expect(reloaded).toHaveBeenCalled();
expect(onRouteChange).toHaveBeenCalled();
reloaded.reset();
onRouteChange.reset();
$location.search({foo: 'bar'});
$rootScope.$digest();
expect(reloaded).not.toHaveBeenCalled();
expect(onRouteChange).not.toHaveBeenCalled();
});
});
it('should update params when reloadOnSearch is disabled and .search() changes', it('should update params when reloadOnSearch is disabled and .search() changes', function() {
inject(function($route, $location, $rootScope) {
var routeParams = jasmine.createSpy('routeParams'); var routeParams = jasmine.createSpy('routeParams');
$route.when('/foo', {controller: FooCtrl}); function FooCtrl($scope, $route) {
$route.when('/bar/:barId', {controller: FooCtrl, reloadOnSearch: false});
function FooCtrl($scope) {
$scope.$watch(function() { $scope.$watch(function() {
return $route.current.params; return $route.current.params;
}, function(value) { }, function(value) {
@ -377,33 +425,39 @@ describe('$route', function() {
}); });
} }
expect(routeParams).not.toHaveBeenCalled(); module(function($routeProvider) {
$routeProvider.when('/foo', {controller: FooCtrl});
$routeProvider.when('/bar/:barId', {controller: FooCtrl, reloadOnSearch: false});
});
$location.path('/foo'); inject(function($route, $location, $rootScope) {
$rootScope.$digest(); expect(routeParams).not.toHaveBeenCalled();
expect(routeParams).toHaveBeenCalledWith({});
routeParams.reset();
// trigger reload $location.path('/foo');
$location.search({foo: 'bar'}); $rootScope.$digest();
$rootScope.$digest(); expect(routeParams).toHaveBeenCalledWith({});
expect(routeParams).toHaveBeenCalledWith({foo: 'bar'}); routeParams.reset();
routeParams.reset();
$location.path('/bar/123').search({}); // trigger reload
$rootScope.$digest(); $location.search({foo: 'bar'});
expect(routeParams).toHaveBeenCalledWith({barId: '123'}); $rootScope.$digest();
routeParams.reset(); expect(routeParams).toHaveBeenCalledWith({foo: 'bar'});
routeParams.reset();
// don't trigger reload $location.path('/bar/123').search({});
$location.search({foo: 'bar'}); $rootScope.$digest();
$rootScope.$digest(); expect(routeParams).toHaveBeenCalledWith({barId: '123'});
expect(routeParams).toHaveBeenCalledWith({barId: '123', foo: 'bar'}); routeParams.reset();
}));
// don't trigger reload
$location.search({foo: 'bar'});
$rootScope.$digest();
expect(routeParams).toHaveBeenCalledWith({barId: '123', foo: 'bar'});
});
});
it('should $destroy scope after update and reload', it('should $destroy scope after update and reload', function() {
inject(function($route, $location, $rootScope) {
// this is a regression of bug, where $route doesn't copy scope when only updating // this is a regression of bug, where $route doesn't copy scope when only updating
var log = []; var log = [];
@ -422,48 +476,55 @@ describe('$route', function() {
}; };
} }
$route.when('/foo', {controller: createController('foo'), reloadOnSearch: false}); module(function($routeProvider) {
$route.when('/bar', {controller: createController('bar')}); $routeProvider.when('/foo', {controller: createController('foo'), reloadOnSearch: false});
$routeProvider.when('/bar', {controller: createController('bar')});
});
$location.url('/foo'); inject(function($route, $location, $rootScope) {
$rootScope.$digest(); $location.url('/foo');
expect(log).toEqual(['init-foo']); $rootScope.$digest();
expect(log).toEqual(['init-foo']);
$location.search({q: 'some'}); $location.search({q: 'some'});
$rootScope.$digest(); $rootScope.$digest();
expect(log).toEqual(['init-foo', 'route-update']); expect(log).toEqual(['init-foo', 'route-update']);
$location.url('/bar'); $location.url('/bar');
$rootScope.$digest(); $rootScope.$digest();
expect(log).toEqual(['init-foo', 'route-update', 'destroy-foo', 'init-bar']); expect(log).toEqual(['init-foo', 'route-update', 'destroy-foo', 'init-bar']);
})); });
});
describe('reload', function() { describe('reload', function() {
it('should reload even if reloadOnSearch is false', it('should reload even if reloadOnSearch is false', function() {
inject(function($route, $location, $rootScope, $routeParams) {
var count = 0; var count = 0;
$route.when('/bar/:barId', {controller: FooCtrl, reloadOnSearch: false});
function FooCtrl() { count ++; } function FooCtrl() { count ++; }
$location.path('/bar/123'); module(function($routeProvider) {
$rootScope.$digest(); $routeProvider.when('/bar/:barId', {controller: FooCtrl, reloadOnSearch: false});
expect($routeParams).toEqual({barId:'123'}); });
expect(count).toEqual(1);
$location.path('/bar/123').search('a=b'); inject(function($route, $location, $rootScope, $routeParams) {
$rootScope.$digest(); $location.path('/bar/123');
expect($routeParams).toEqual({barId:'123', a:'b'}); $rootScope.$digest();
expect(count).toEqual(1); expect($routeParams).toEqual({barId:'123'});
expect(count).toEqual(1);
$route.reload(); $location.path('/bar/123').search('a=b');
$rootScope.$digest(); $rootScope.$digest();
expect($routeParams).toEqual({barId:'123', a:'b'}); expect($routeParams).toEqual({barId:'123', a:'b'});
expect(count).toEqual(2); expect(count).toEqual(1);
}));
$route.reload();
$rootScope.$digest();
expect($routeParams).toEqual({barId:'123', a:'b'});
expect(count).toEqual(2);
});
});
}); });
}); });
}); });

View file

@ -618,8 +618,10 @@ describe('widget', function() {
describe('ng:view', function() { describe('ng:view', function() {
beforeEach(inject(function($rootScope, $compile) { beforeEach(module(function() {
element = $compile('<ng:view></ng:view>')($rootScope); return function($rootScope, $compile) {
element = $compile('<ng:view></ng:view>')($rootScope);
};
})); }));
@ -631,71 +633,82 @@ describe('widget', function() {
})); }));
it('should load content via xhr when route changes', it('should load content via xhr when route changes', function() {
inject(function($rootScope, $compile, $httpBackend, $location, $route) { module(function($routeProvider) {
$route.when('/foo', {template: 'myUrl1'}); $routeProvider.when('/foo', {template: 'myUrl1'});
$route.when('/bar', {template: 'myUrl2'}); $routeProvider.when('/bar', {template: 'myUrl2'});
});
expect(element.text()).toEqual(''); inject(function($rootScope, $compile, $httpBackend, $location, $route) {
expect(element.text()).toEqual('');
$location.path('/foo'); $location.path('/foo');
$httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>'); $httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>');
$rootScope.$digest(); $rootScope.$digest();
$httpBackend.flush(); $httpBackend.flush();
expect(element.text()).toEqual('4'); expect(element.text()).toEqual('4');
$location.path('/bar'); $location.path('/bar');
$httpBackend.expect('GET', 'myUrl2').respond('angular is da best'); $httpBackend.expect('GET', 'myUrl2').respond('angular is da best');
$rootScope.$digest(); $rootScope.$digest();
$httpBackend.flush(); $httpBackend.flush();
expect(element.text()).toEqual('angular is da best'); expect(element.text()).toEqual('angular is da best');
})); });
});
it('should remove all content when location changes to an unknown route', it('should remove all content when location changes to an unknown route', function() {
inject(function($rootScope, $compile, $location, $httpBackend, $route) { module(function($routeProvider) {
$route.when('/foo', {template: 'myUrl1'}); $routeProvider.when('/foo', {template: 'myUrl1'});
});
$location.path('/foo'); inject(function($rootScope, $compile, $location, $httpBackend, $route) {
$httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>'); $location.path('/foo');
$rootScope.$digest(); $httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>');
$httpBackend.flush(); $rootScope.$digest();
expect(element.text()).toEqual('4'); $httpBackend.flush();
expect(element.text()).toEqual('4');
$location.path('/unknown'); $location.path('/unknown');
$rootScope.$digest(); $rootScope.$digest();
expect(element.text()).toEqual(''); expect(element.text()).toEqual('');
})); });
});
it('should chain scopes and propagate evals to the child scope', it('should chain scopes and propagate evals to the child scope', function() {
inject(function($rootScope, $compile, $location, $httpBackend, $route) { module(function($routeProvider) {
$route.when('/foo', {template: 'myUrl1'}); $routeProvider.when('/foo', {template: 'myUrl1'});
$rootScope.parentVar = 'parent'; });
$location.path('/foo'); inject(function($rootScope, $compile, $location, $httpBackend, $route) {
$httpBackend.expect('GET', 'myUrl1').respond('<div>{{parentVar}}</div>'); $rootScope.parentVar = 'parent';
$rootScope.$digest();
$httpBackend.flush();
expect(element.text()).toEqual('parent');
$rootScope.parentVar = 'new parent'; $location.path('/foo');
$rootScope.$digest(); $httpBackend.expect('GET', 'myUrl1').respond('<div>{{parentVar}}</div>');
expect(element.text()).toEqual('new parent'); $rootScope.$digest();
})); $httpBackend.flush();
expect(element.text()).toEqual('parent');
$rootScope.parentVar = 'new parent';
$rootScope.$digest();
expect(element.text()).toEqual('new parent');
});
});
it('should be possible to nest ng:view in ng:include', inject(function() { it('should be possible to nest ng:view in ng:include', inject(function() {
// TODO(vojta): refactor this test // TODO(vojta): refactor this test
dealoc(element); dealoc(element);
var injector = angular.injector(['ng', 'ngMock']); var injector = angular.injector(['ng', 'ngMock', function($routeProvider) {
$routeProvider.when('/foo', {controller: angular.noop, template: 'viewPartial.html'});
}]);
var myApp = injector.get('$rootScope'); var myApp = injector.get('$rootScope');
var $httpBackend = injector.get('$httpBackend'); var $httpBackend = injector.get('$httpBackend');
$httpBackend.expect('GET', 'includePartial.html').respond('view: <ng:view></ng:view>'); $httpBackend.expect('GET', 'includePartial.html').respond('view: <ng:view></ng:view>');
injector.get('$location').path('/foo'); injector.get('$location').path('/foo');
var $route = injector.get('$route'); var $route = injector.get('$route');
$route.when('/foo', {controller: angular.noop, template: 'viewPartial.html'});
element = injector.get('$compile')( element = injector.get('$compile')(
'<div>' + '<div>' +
@ -714,94 +727,109 @@ describe('widget', function() {
it('should initialize view template after the view controller was initialized even when ' + it('should initialize view template after the view controller was initialized even when ' +
'templates were cached', 'templates were cached', function() {
inject(function($rootScope, $compile, $location, $httpBackend, $route) { //this is a test for a regression that was introduced by making the ng:view cache sync
//this is a test for a regression that was introduced by making the ng:view cache sync
$route.when('/foo', {controller: ParentCtrl, template: 'viewPartial.html'});
$rootScope.log = [];
function ParentCtrl($scope) { function ParentCtrl($scope) {
$scope.log.push('parent'); $scope.log.push('parent');
} }
$rootScope.ChildCtrl = function($scope) { module(function($routeProvider) {
$scope.log.push('child'); $routeProvider.when('/foo', {controller: ParentCtrl, template: 'viewPartial.html'});
}; });
$location.path('/foo');
$httpBackend.expect('GET', 'viewPartial.html').
respond('<div ng:init="log.push(\'init\')">' +
'<div ng:controller="ChildCtrl"></div>' +
'</div>');
$rootScope.$apply();
$httpBackend.flush();
expect($rootScope.log).toEqual(['parent', 'init', 'child']); inject(function($rootScope, $compile, $location, $httpBackend, $route) {
$rootScope.log = [];
$location.path('/'); $rootScope.ChildCtrl = function($scope) {
$rootScope.$apply(); $scope.log.push('child');
expect($rootScope.log).toEqual(['parent', 'init', 'child']); };
$rootScope.log = []; $location.path('/foo');
$location.path('/foo'); $httpBackend.expect('GET', 'viewPartial.html').
$rootScope.$apply(); respond('<div ng:init="log.push(\'init\')">' +
'<div ng:controller="ChildCtrl"></div>' +
'</div>');
$rootScope.$apply();
$httpBackend.flush();
expect($rootScope.log).toEqual(['parent', 'init', 'child']); expect($rootScope.log).toEqual(['parent', 'init', 'child']);
}));
$location.path('/');
$rootScope.$apply();
expect($rootScope.log).toEqual(['parent', 'init', 'child']);
$rootScope.log = [];
$location.path('/foo');
$rootScope.$apply();
expect($rootScope.log).toEqual(['parent', 'init', 'child']);
});
});
it('should discard pending xhr callbacks if a new route is requested before the current ' + it('should discard pending xhr callbacks if a new route is requested before the current ' +
'finished loading', inject(function($route, $rootScope, $location, $httpBackend) { 'finished loading', function() {
// this is a test for a bad race condition that affected feedback // this is a test for a bad race condition that affected feedback
$route.when('/foo', {template: 'myUrl1'}); module(function($routeProvider) {
$route.when('/bar', {template: 'myUrl2'}); $routeProvider.when('/foo', {template: 'myUrl1'});
$routeProvider.when('/bar', {template: 'myUrl2'});
expect(element.text()).toEqual('');
$location.path('/foo');
$httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>');
$rootScope.$digest();
$location.path('/bar');
$httpBackend.expect('GET', 'myUrl2').respond('<div>{{1+1}}</div>');
$rootScope.$digest();
$httpBackend.flush(); // now that we have two requests pending, flush!
expect(element.text()).toEqual('2');
}));
it('should clear the content when error during xhr request',
inject(function($route, $location, $rootScope, $httpBackend) {
$route.when('/foo', {controller: noop, template: 'myUrl1'});
$location.path('/foo');
$httpBackend.expect('GET', 'myUrl1').respond(404, '');
element.text('content');
$rootScope.$digest();
$httpBackend.flush();
expect(element.text()).toBe('');
}));
it('should be async even if served from cache',
inject(function($route, $rootScope, $location, $templateCache, $browser) {
$templateCache.put('myUrl1', [200, 'my partial', {}]);
$route.when('/foo', {controller: noop, template: 'myUrl1'});
$location.path('/foo');
var called = 0;
// we want to assert only during first watch
$rootScope.$watch(function() {
if (!called++) expect(element.text()).toBe('');
}); });
$rootScope.$digest(); inject(function($route, $rootScope, $location, $httpBackend) {
expect(element.text()).toBe('my partial'); expect(element.text()).toEqual('');
}));
$location.path('/foo');
$httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>');
$rootScope.$digest();
$location.path('/bar');
$httpBackend.expect('GET', 'myUrl2').respond('<div>{{1+1}}</div>');
$rootScope.$digest();
$httpBackend.flush(); // now that we have two requests pending, flush!
expect(element.text()).toEqual('2');
});
});
it('should clear the content when error during xhr request', function() {
module(function($routeProvider) {
$routeProvider.when('/foo', {controller: noop, template: 'myUrl1'});
});
inject(function($route, $location, $rootScope, $httpBackend) {
$location.path('/foo');
$httpBackend.expect('GET', 'myUrl1').respond(404, '');
element.text('content');
$rootScope.$digest();
$httpBackend.flush();
expect(element.text()).toBe('');
});
});
it('should be async even if served from cache', function() {
module(function($routeProvider) {
$routeProvider.when('/foo', {controller: noop, template: 'myUrl1'});
});
inject(function($route, $rootScope, $location, $templateCache, $browser) {
$templateCache.put('myUrl1', [200, 'my partial', {}]);
$location.path('/foo');
var called = 0;
// we want to assert only during first watch
$rootScope.$watch(function() {
if (!called++) expect(element.text()).toBe('');
});
$rootScope.$digest();
expect(element.text()).toBe('my partial');
});
});
}); });