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>
*/
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',
function( $rootScope, $location, $routeParams, $controller) {
/**
@ -112,8 +184,7 @@ function $RouteProvider(){
* instance of the Controller.
*/
var routes = {},
matcher = switchRouteMatcher,
var matcher = switchRouteMatcher,
parentScope = $rootScope,
dirty = 0,
forceReload = false,
@ -136,76 +207,6 @@ function $RouteProvider(){
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
* @name angular.module.ng.$route#reload

View file

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

View file

@ -1,7 +1,7 @@
'use strict';
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 = '',
lastRoute,
nextRoute;
@ -9,367 +9,415 @@ describe('$route', function() {
function BookChapter() {
log += '<init>;';
}
$route.when('/Book/:book/Chapter/:chapter',
{controller: BookChapter, template: 'Chapter.html'});
$route.when('/Blank');
$rootScope.$on('$beforeRouteChange', function(event, next, current) {
log += 'before();';
expect(current).toBe($route.current);
lastRoute = current;
nextRoute = next;
module(function($routeProvider) {
$routeProvider.when('/Book/:book/Chapter/:chapter',
{controller: BookChapter, template: 'Chapter.html'});
$routeProvider.when('/Blank');
});
$rootScope.$on('$afterRouteChange', function(event, current, last) {
log += 'after();';
expect(current).toBe($route.current);
expect(lastRoute).toBe(last);
expect(nextRoute).toBe(current);
inject(function($route, $location, $rootScope) {
$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) {
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');
$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;
inject(function($route, $location, $rootScope) {
var callback = jasmine.createSpy('onRouteChange');
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);
$rootScope.$on('$beforeRouteChange', callback);
$location.path('/test');
$rootScope.$digest();
callback.reset();
log = '';
$location.path('/NONE');
$rootScope.$digest();
expect(log).toEqual('before();after();');
expect($route.current).toEqual(null);
$location.search({any: true});
$rootScope.$digest();
$route.when('/NONE', {template:'instant update'});
$rootScope.$digest();
expect($route.current.template).toEqual('instant update');
}));
expect(callback).toHaveBeenCalled();
});
});
it('should match a route that contains special chars in the path',
inject(function($route, $location, $rootScope) {
$route.when('/$test.23/foo(bar)/:baz', {template: 'test.html'});
it('should allow routes to be defined with just templates without controllers', function() {
module(function($routeProvider) {
$routeProvider.when('/foo', {template: 'foo.html'});
});
$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',
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) {
inject(function($route, $location, $rootScope) {
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);
expect($route.current).toBeUndefined();
expect(onChangeSpy).not.toHaveBeenCalled();
$location.path('/');
$location.path('/foo');
$rootScope.$digest();
expect($location.path()).toBe('/foo');
expect($route.current.template).toBe('foo.html');
expect(onChangeSpy.callCount).toBe(2);
expect($route.current.template).toEqual('foo.html');
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();
$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');
$rootScope.$digest();
expect($location.path()).toBe('/bar');
expect($route.current.template).toBe('bar.html');
expect(onChangeSpy.callCount).toBe(2);
}));
expect($rootScope.$$childHead.$id).toBeTruthy();
expect($rootScope.$$childHead.$id).toEqual($rootScope.$$childTail.$id);
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');
$location.path('/');
$rootScope.$digest();
expect($location.path()).toEqual('/bar/id1/subid3/23');
expect($location.search()).toEqual({extraId: 'gah'});
expect($route.current.template).toEqual('bar.html');
}));
expect($rootScope.$$childHead).toEqual(null);
expect($rootScope.$$childTail).toEqual(null);
});
});
it('should interpolate route vars in the redirected path from original search',
inject(function($route, $location, $rootScope) {
$route.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'});
$route.when('/foo/:id/:extra', {redirectTo: '/bar/:id/:subid/99'});
it('should infer arguments in injection', function() {
var injectedRoute;
module(function($routeProvider) {
$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();
expect($location.path()).toEqual('/bar/id3/sid1/99');
expect($location.search()).toEqual({appended: 'true', extra: 'eId'});
expect($route.current.template).toEqual('bar.html');
}));
expect(injectedRoute).toBe($route);
});
});
it('should allow custom redirectTo function to be used',
inject(function($route, $location, $rootScope) {
$route.when('/bar/:id/:subid/:subsubid', {template: 'bar.html'});
$route.when('/foo/:id', {redirectTo: customRedirectFn});
$location.path('/foo/id3').search('subid=sid1&appended=true');
$rootScope.$digest();
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;
describe('redirection', function() {
it('should support redirection via redirectTo property by updating $location', function() {
module(function($routeProvider) {
$routeProvider.when('/', {redirectTo: '/foo'});
$routeProvider.when('/foo', {template: 'foo.html'});
$routeProvider.when('/bar', {template: 'bar.html'});
$routeProvider.when('/baz', {redirectTo: '/bar'});
$routeProvider.otherwise({template: '404.html'});
});
$location.path('/foo/id3/eId');
$rootScope.$digest();
inject(function($route, $location, $rootScope) {
var onChangeSpy = jasmine.createSpy('onChange');
expect($location.path()).toEqual('/bar/id3');
expect(replace).toBe(true);
}));
$rootScope.$on('$beforeRouteChange', onChangeSpy);
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() {
it('should reload a route when reloadOnSearch is enabled and .search() changes',
inject(function($route, $location, $rootScope, $routeParams) {
it('should reload a route when reloadOnSearch is enabled and .search() changes', function() {
var reloaded = jasmine.createSpy('route reload');
$route.when('/foo', {controller: FooCtrl});
$rootScope.$on('$beforeRouteChange', reloaded);
function FooCtrl() {
reloaded();
}
$location.path('/foo');
$rootScope.$digest();
expect(reloaded).toHaveBeenCalled();
expect($routeParams).toEqual({});
reloaded.reset();
module(function($routeProvider) {
$routeProvider.when('/foo', {controller: FooCtrl});
});
// trigger reload
$location.search({foo: 'bar'});
$rootScope.$digest();
expect(reloaded).toHaveBeenCalled();
expect($routeParams).toEqual({foo:'bar'});
}));
inject(function($route, $location, $rootScope, $routeParams) {
$rootScope.$on('$beforeRouteChange', reloaded);
$location.path('/foo');
$rootScope.$digest();
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',
inject(function($route, $location, $rootScope) {
it('should not reload a route when reloadOnSearch is disabled and only .search() changes', function() {
var reloaded = jasmine.createSpy('route reload'),
routeUpdateEvent = jasmine.createSpy('route reload');
$route.when('/foo', {controller: FooCtrl, reloadOnSearch: false});
$rootScope.$on('$beforeRouteChange', reloaded);
function FooCtrl($scope) {
reloaded();
$scope.$on('$routeUpdate', routeUpdateEvent);
}
expect(reloaded).not.toHaveBeenCalled();
module(function($routeProvider) {
$routeProvider.when('/foo', {controller: FooCtrl, reloadOnSearch: false});
});
$location.path('/foo');
$rootScope.$digest();
expect(reloaded).toHaveBeenCalled();
expect(routeUpdateEvent).not.toHaveBeenCalled();
reloaded.reset();
inject(function($route, $location, $rootScope) {
$rootScope.$on('$beforeRouteChange', reloaded);
// don't trigger reload
$location.search({foo: 'bar'});
$rootScope.$digest();
expect(reloaded).not.toHaveBeenCalled();
expect(routeUpdateEvent).toHaveBeenCalled();
}));
expect(reloaded).not.toHaveBeenCalled();
$location.path('/foo');
$rootScope.$digest();
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',
inject(function($route, $location, $rootScope) {
it('should reload reloadOnSearch route when url differs only in route path param', function() {
var reloaded = jasmine.createSpy('routeReload'),
onRouteChange = jasmine.createSpy('onRouteChange');
$route.when('/foo/:fooId', {controller: FooCtrl, reloadOnSearch: false});
$rootScope.$on('$beforeRouteChange', onRouteChange);
function FooCtrl() {
reloaded();
}
expect(reloaded).not.toHaveBeenCalled();
expect(onRouteChange).not.toHaveBeenCalled();
module(function($routeProvider) {
$routeProvider.when('/foo/:fooId', {controller: FooCtrl, reloadOnSearch: false});
});
$location.path('/foo/aaa');
$rootScope.$digest();
expect(reloaded).toHaveBeenCalled();
expect(onRouteChange).toHaveBeenCalled();
reloaded.reset();
onRouteChange.reset();
inject(function($route, $location, $rootScope) {
$rootScope.$on('$beforeRouteChange', onRouteChange);
$location.path('/foo/bbb');
$rootScope.$digest();
expect(reloaded).toHaveBeenCalled();
expect(onRouteChange).toHaveBeenCalled();
reloaded.reset();
onRouteChange.reset();
expect(reloaded).not.toHaveBeenCalled();
expect(onRouteChange).not.toHaveBeenCalled();
$location.search({foo: 'bar'});
$rootScope.$digest();
expect(reloaded).not.toHaveBeenCalled();
expect(onRouteChange).not.toHaveBeenCalled();
}));
$location.path('/foo/aaa');
$rootScope.$digest();
expect(reloaded).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',
inject(function($route, $location, $rootScope) {
it('should update params when reloadOnSearch is disabled and .search() changes', function() {
var routeParams = jasmine.createSpy('routeParams');
$route.when('/foo', {controller: FooCtrl});
$route.when('/bar/:barId', {controller: FooCtrl, reloadOnSearch: false});
function FooCtrl($scope) {
function FooCtrl($scope, $route) {
$scope.$watch(function() {
return $route.current.params;
}, 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');
$rootScope.$digest();
expect(routeParams).toHaveBeenCalledWith({});
routeParams.reset();
inject(function($route, $location, $rootScope) {
expect(routeParams).not.toHaveBeenCalled();
// trigger reload
$location.search({foo: 'bar'});
$rootScope.$digest();
expect(routeParams).toHaveBeenCalledWith({foo: 'bar'});
routeParams.reset();
$location.path('/foo');
$rootScope.$digest();
expect(routeParams).toHaveBeenCalledWith({});
routeParams.reset();
$location.path('/bar/123').search({});
$rootScope.$digest();
expect(routeParams).toHaveBeenCalledWith({barId: '123'});
routeParams.reset();
// trigger reload
$location.search({foo: 'bar'});
$rootScope.$digest();
expect(routeParams).toHaveBeenCalledWith({foo: 'bar'});
routeParams.reset();
// don't trigger reload
$location.search({foo: 'bar'});
$rootScope.$digest();
expect(routeParams).toHaveBeenCalledWith({barId: '123', foo: 'bar'});
}));
$location.path('/bar/123').search({});
$rootScope.$digest();
expect(routeParams).toHaveBeenCalledWith({barId: '123'});
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',
inject(function($route, $location, $rootScope) {
it('should $destroy scope after update and reload', function() {
// this is a regression of bug, where $route doesn't copy scope when only updating
var log = [];
@ -422,48 +476,55 @@ describe('$route', function() {
};
}
$route.when('/foo', {controller: createController('foo'), reloadOnSearch: false});
$route.when('/bar', {controller: createController('bar')});
module(function($routeProvider) {
$routeProvider.when('/foo', {controller: createController('foo'), reloadOnSearch: false});
$routeProvider.when('/bar', {controller: createController('bar')});
});
$location.url('/foo');
$rootScope.$digest();
expect(log).toEqual(['init-foo']);
inject(function($route, $location, $rootScope) {
$location.url('/foo');
$rootScope.$digest();
expect(log).toEqual(['init-foo']);
$location.search({q: 'some'});
$rootScope.$digest();
expect(log).toEqual(['init-foo', 'route-update']);
$location.search({q: 'some'});
$rootScope.$digest();
expect(log).toEqual(['init-foo', 'route-update']);
$location.url('/bar');
$rootScope.$digest();
expect(log).toEqual(['init-foo', 'route-update', 'destroy-foo', 'init-bar']);
}));
$location.url('/bar');
$rootScope.$digest();
expect(log).toEqual(['init-foo', 'route-update', 'destroy-foo', 'init-bar']);
});
});
describe('reload', function() {
it('should reload even if reloadOnSearch is false',
inject(function($route, $location, $rootScope, $routeParams) {
it('should reload even if reloadOnSearch is false', function() {
var count = 0;
$route.when('/bar/:barId', {controller: FooCtrl, reloadOnSearch: false});
function FooCtrl() { count ++; }
$location.path('/bar/123');
$rootScope.$digest();
expect($routeParams).toEqual({barId:'123'});
expect(count).toEqual(1);
module(function($routeProvider) {
$routeProvider.when('/bar/:barId', {controller: FooCtrl, reloadOnSearch: false});
});
$location.path('/bar/123').search('a=b');
$rootScope.$digest();
expect($routeParams).toEqual({barId:'123', a:'b'});
expect(count).toEqual(1);
inject(function($route, $location, $rootScope, $routeParams) {
$location.path('/bar/123');
$rootScope.$digest();
expect($routeParams).toEqual({barId:'123'});
expect(count).toEqual(1);
$route.reload();
$rootScope.$digest();
expect($routeParams).toEqual({barId:'123', a:'b'});
expect(count).toEqual(2);
}));
$location.path('/bar/123').search('a=b');
$rootScope.$digest();
expect($routeParams).toEqual({barId:'123', a:'b'});
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() {
beforeEach(inject(function($rootScope, $compile) {
element = $compile('<ng:view></ng:view>')($rootScope);
beforeEach(module(function() {
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',
inject(function($rootScope, $compile, $httpBackend, $location, $route) {
$route.when('/foo', {template: 'myUrl1'});
$route.when('/bar', {template: 'myUrl2'});
it('should load content via xhr when route changes', function() {
module(function($routeProvider) {
$routeProvider.when('/foo', {template: 'myUrl1'});
$routeProvider.when('/bar', {template: 'myUrl2'});
});
expect(element.text()).toEqual('');
inject(function($rootScope, $compile, $httpBackend, $location, $route) {
expect(element.text()).toEqual('');
$location.path('/foo');
$httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>');
$rootScope.$digest();
$httpBackend.flush();
expect(element.text()).toEqual('4');
$location.path('/foo');
$httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>');
$rootScope.$digest();
$httpBackend.flush();
expect(element.text()).toEqual('4');
$location.path('/bar');
$httpBackend.expect('GET', 'myUrl2').respond('angular is da best');
$rootScope.$digest();
$httpBackend.flush();
expect(element.text()).toEqual('angular is da best');
}));
$location.path('/bar');
$httpBackend.expect('GET', 'myUrl2').respond('angular is da best');
$rootScope.$digest();
$httpBackend.flush();
expect(element.text()).toEqual('angular is da best');
});
});
it('should remove all content when location changes to an unknown route',
inject(function($rootScope, $compile, $location, $httpBackend, $route) {
$route.when('/foo', {template: 'myUrl1'});
it('should remove all content when location changes to an unknown route', function() {
module(function($routeProvider) {
$routeProvider.when('/foo', {template: 'myUrl1'});
});
$location.path('/foo');
$httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>');
$rootScope.$digest();
$httpBackend.flush();
expect(element.text()).toEqual('4');
inject(function($rootScope, $compile, $location, $httpBackend, $route) {
$location.path('/foo');
$httpBackend.expect('GET', 'myUrl1').respond('<div>{{1+3}}</div>');
$rootScope.$digest();
$httpBackend.flush();
expect(element.text()).toEqual('4');
$location.path('/unknown');
$rootScope.$digest();
expect(element.text()).toEqual('');
}));
$location.path('/unknown');
$rootScope.$digest();
expect(element.text()).toEqual('');
});
});
it('should chain scopes and propagate evals to the child scope',
inject(function($rootScope, $compile, $location, $httpBackend, $route) {
$route.when('/foo', {template: 'myUrl1'});
$rootScope.parentVar = 'parent';
it('should chain scopes and propagate evals to the child scope', function() {
module(function($routeProvider) {
$routeProvider.when('/foo', {template: 'myUrl1'});
});
$location.path('/foo');
$httpBackend.expect('GET', 'myUrl1').respond('<div>{{parentVar}}</div>');
$rootScope.$digest();
$httpBackend.flush();
expect(element.text()).toEqual('parent');
inject(function($rootScope, $compile, $location, $httpBackend, $route) {
$rootScope.parentVar = 'parent';
$rootScope.parentVar = 'new parent';
$rootScope.$digest();
expect(element.text()).toEqual('new parent');
}));
$location.path('/foo');
$httpBackend.expect('GET', 'myUrl1').respond('<div>{{parentVar}}</div>');
$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() {
// TODO(vojta): refactor this test
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 $httpBackend = injector.get('$httpBackend');
$httpBackend.expect('GET', 'includePartial.html').respond('view: <ng:view></ng:view>');
injector.get('$location').path('/foo');
var $route = injector.get('$route');
$route.when('/foo', {controller: angular.noop, template: 'viewPartial.html'});
element = injector.get('$compile')(
'<div>' +
@ -714,94 +727,109 @@ describe('widget', function() {
it('should initialize view template after the view controller was initialized even when ' +
'templates were cached',
inject(function($rootScope, $compile, $location, $httpBackend, $route) {
//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 = [];
'templates were cached', function() {
//this is a test for a regression that was introduced by making the ng:view cache sync
function ParentCtrl($scope) {
$scope.log.push('parent');
$scope.log.push('parent');
}
$rootScope.ChildCtrl = function($scope) {
$scope.log.push('child');
};
module(function($routeProvider) {
$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.$apply();
expect($rootScope.log).toEqual(['parent', 'init', 'child']);
$rootScope.ChildCtrl = function($scope) {
$scope.log.push('child');
};
$rootScope.log = [];
$location.path('/foo');
$rootScope.$apply();
$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']);
}));
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 ' +
'finished loading', inject(function($route, $rootScope, $location, $httpBackend) {
'finished loading', function() {
// this is a test for a bad race condition that affected feedback
$route.when('/foo', {template: 'myUrl1'});
$route.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('');
module(function($routeProvider) {
$routeProvider.when('/foo', {template: 'myUrl1'});
$routeProvider.when('/bar', {template: 'myUrl2'});
});
$rootScope.$digest();
expect(element.text()).toBe('my partial');
}));
inject(function($route, $rootScope, $location, $httpBackend) {
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');
});
});
});