diff options
| author | Vojta Jina | 2012-02-19 12:31:11 -0800 |
|---|---|---|
| committer | Vojta Jina | 2012-02-28 17:46:58 -0800 |
| commit | 9486590e1be7282bb0e87586a35ca0bee6c64ee0 (patch) | |
| tree | bb7a808e077f134ddc26b84e35a406e62dbf117d /test/widgetsSpec.js | |
| parent | e31d1c287d972d633bdaf9c385d3012192f64918 (diff) | |
| download | angular.js-9486590e1be7282bb0e87586a35ca0bee6c64ee0.tar.bz2 | |
refactor(ng:view) Make $route scope agnostic, add $contentLoaded event
Problems:
- controller was instantiated immediately on $afterRouteChange (even if no content), that's
different compare to ng:controller, which instantiates controllers after compiling
- route listened on current scope ($afterRouteChange), so if you were listening on $rootScope
($afterRouteChange), you get called first and current.scope === undefined, which is flaky
- route handles scope destroying, but scope is created by ng:view
- route fires after/before route change even if there is no route (when no otherwise specified)
Solution:
- route has no idea about scope, whole scope business moved to ng:view (creating/destroying)
- scope is created (and controller instantiated) AFTER compiling the content
- that means on $afterRouteChange - there is no scope yet (current.scope === undefined)
- added $contentLoaded event fired by ng:view, after linking the content
Diffstat (limited to 'test/widgetsSpec.js')
| -rw-r--r-- | test/widgetsSpec.js | 182 |
1 files changed, 172 insertions, 10 deletions
diff --git a/test/widgetsSpec.js b/test/widgetsSpec.js index 3b245d11..d9517866 100644 --- a/test/widgetsSpec.js +++ b/test/widgetsSpec.js @@ -633,16 +633,35 @@ describe('widget', function() { })); - it('should create controller instance on $afterRouteChange event', inject( - function($route, $rootScope) { - var controllerScope; - $route.current = { controller: function($scope) { controllerScope = $scope; } }; - $rootScope.$broadcast('$afterRouteChange', $route.current); - - expect(controllerScope.$parent.$id).toBe($rootScope.$id); - expect(controllerScope.$id).toBe($route.current.scope.$id); - } - )); + it('should instantiate controller after compiling the content', function() { + var log = [], controllerScope, + Ctrl = function($scope) { + controllerScope = $scope; + log.push('ctrl-init'); + }; + + module(function($compileProvider, $routeProvider) { + $compileProvider.directive('compileLog', function() { + return { + compile: function() { + log.push('compile'); + } + }; + }); + + $routeProvider.when('/some', {template: '/tpl.html', controller: Ctrl}); + }); + + inject(function($route, $rootScope, $templateCache, $location) { + $templateCache.put('/tpl.html', [200, '<div compile-log>partial</div>', {}]); + $location.path('/some'); + $rootScope.$digest(); + + expect(controllerScope.$parent).toBe($rootScope); + expect(controllerScope).toBe($route.current.scope); + expect(log).toEqual(['compile', 'ctrl-init']); + }); + }); it('should load content via xhr when route changes', function() { @@ -842,6 +861,149 @@ describe('widget', function() { expect(element.text()).toBe('my partial'); }); }); + + it('should fire $contentLoaded event when content compiled and linked', function() { + var log = []; + var logger = function(name) { + return function() { + log.push(name); + }; + }; + var Ctrl = function($scope) { + $scope.value = 'bound-value'; + log.push('init-ctrl'); + }; + + module(function($routeProvider) { + $routeProvider.when('/foo', {template: 'tpl.html', controller: Ctrl}); + }); + + inject(function($templateCache, $rootScope, $location) { + $rootScope.$on('$beforeRouteChange', logger('$beforeRouteChange')); + $rootScope.$on('$afterRouteChange', logger('$afterRouteChange')); + $rootScope.$on('$contentLoaded', logger('$contentLoaded')); + + $templateCache.put('tpl.html', [200, '{{value}}', {}]); + $location.path('/foo'); + $rootScope.$digest(); + + expect(element.text()).toBe('bound-value'); + expect(log).toEqual(['$beforeRouteChange', '$afterRouteChange', 'init-ctrl', + '$contentLoaded']); + }); + }); + + it('should destroy previous scope', function() { + module(function($routeProvider) { + $routeProvider.when('/foo', {template: 'tpl.html'}); + }); + + inject(function($templateCache, $rootScope, $location) { + $templateCache.put('tpl.html', [200, 'partial', {}]); + + expect($rootScope.$$childHead).toBeNull(); + expect($rootScope.$$childTail).toBeNull(); + + $location.path('/foo'); + $rootScope.$digest(); + + expect(element.text()).toBe('partial'); + expect($rootScope.$$childHead).not.toBeNull(); + expect($rootScope.$$childTail).not.toBeNull(); + + $location.path('/non/existing/route'); + $rootScope.$digest(); + + expect(element.text()).toBe(''); + expect($rootScope.$$childHead).toBeNull(); + expect($rootScope.$$childTail).toBeNull(); + }); + }); + + + it('should destroy previous scope if multiple route changes occur before server responds', + function() { + var log = []; + var createCtrl = function(name) { + return function($scope) { + log.push('init-' + name); + $scope.$on('$destroy', function() { + log.push('destroy-' + name); + }); + }; + }; + + module(function($routeProvider) { + $routeProvider.when('/one', {template: 'one.html', controller: createCtrl('ctrl1')}); + $routeProvider.when('/two', {template: 'two.html', controller: createCtrl('ctrl2')}); + }); + + inject(function($httpBackend, $rootScope, $location) { + $httpBackend.whenGET('one.html').respond('content 1'); + $httpBackend.whenGET('two.html').respond('content 2'); + + $location.path('/one'); + $rootScope.$digest(); + $location.path('/two'); + $rootScope.$digest(); + + $httpBackend.flush(); + expect(element.text()).toBe('content 2'); + expect(log).toEqual(['init-ctrl2']); + + $location.path('/non-existing'); + $rootScope.$digest(); + + expect(element.text()).toBe(''); + expect(log).toEqual(['init-ctrl2', 'destroy-ctrl2']); + + expect($rootScope.$$childHead).toBeNull(); + expect($rootScope.$$childTail).toBeNull(); + }); + }); + + + 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 = []; + + function logger(msg) { + return function() { + log.push(msg); + }; + } + + function createController(name) { + return function($scope) { + log.push('init-' + name); + $scope.$on('$destroy', logger('destroy-' + name)); + $scope.$on('$routeUpdate', logger('route-update')); + }; + } + + module(function($routeProvider) { + $routeProvider.when('/bar', {template: 'tpl.html', controller: createController('bar')}); + $routeProvider.when('/foo', { + template: 'tpl.html', controller: createController('foo'), reloadOnSearch: false}); + }); + + inject(function($templateCache, $location, $rootScope) { + $templateCache.put('tpl.html', [200, 'partial', {}]); + + $location.url('/foo'); + $rootScope.$digest(); + expect(log).toEqual(['init-foo']); + + $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']); + }); + }); }); |
