diff --git a/src/ng/directive/ngIf.js b/src/ng/directive/ngIf.js index 117f13e4788e..d2471f09b0fe 100644 --- a/src/ng/directive/ngIf.js +++ b/src/ng/directive/ngIf.js @@ -115,8 +115,8 @@ var ngIfDirective = ['$animate', '$compile', function($animate, $compile) { } if (block) { previousElements = getBlockNodes(block.clone); - $animate.leave(previousElements).then(function() { - previousElements = null; + $animate.leave(previousElements).done(function(response) { + if (response !== false) previousElements = null; }); block = null; } diff --git a/src/ng/directive/ngInclude.js b/src/ng/directive/ngInclude.js index 4409c7dee9da..496c79050cf8 100644 --- a/src/ng/directive/ngInclude.js +++ b/src/ng/directive/ngInclude.js @@ -214,8 +214,8 @@ var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', currentScope = null; } if (currentElement) { - $animate.leave(currentElement).then(function() { - previousElement = null; + $animate.leave(currentElement).done(function(response) { + if (response !== false) previousElement = null; }); previousElement = currentElement; currentElement = null; @@ -223,9 +223,10 @@ var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', }; scope.$watch(srcExp, function ngIncludeWatchAction(src) { - var afterAnimation = function() { - if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { - $anchorScroll(); + var afterAnimation = function(response) { + if (response !== false && isDefined(autoScrollExp) && + (!autoScrollExp || scope.$eval(autoScrollExp))) { + $anchorScroll(); } }; var thisChangeId = ++changeCounter; @@ -248,7 +249,7 @@ var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', // directives to non existing elements. var clone = $transclude(newScope, function(clone) { cleanupLastIncludeContent(); - $animate.enter(clone, null, $element).then(afterAnimation); + $animate.enter(clone, null, $element).done(afterAnimation); }); currentScope = newScope; diff --git a/src/ng/directive/ngSwitch.js b/src/ng/directive/ngSwitch.js index 15273b92d22d..545932cfc5a6 100644 --- a/src/ng/directive/ngSwitch.js +++ b/src/ng/directive/ngSwitch.js @@ -153,21 +153,24 @@ var ngSwitchDirective = ['$animate', '$compile', function($animate, $compile) { selectedScopes = []; var spliceFactory = function(array, index) { - return function() { array.splice(index, 1); }; + return function(response) { + if (response !== false) array.splice(index, 1); + }; }; scope.$watch(watchExpr, function ngSwitchWatchAction(value) { var i, ii; - for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) { - $animate.cancel(previousLeaveAnimations[i]); + + // Start with the last, in case the array is modified during the loop + while (previousLeaveAnimations.length) { + $animate.cancel(previousLeaveAnimations.pop()); } - previousLeaveAnimations.length = 0; for (i = 0, ii = selectedScopes.length; i < ii; ++i) { var selected = getBlockNodes(selectedElements[i].clone); selectedScopes[i].$destroy(); - var promise = previousLeaveAnimations[i] = $animate.leave(selected); - promise.then(spliceFactory(previousLeaveAnimations, i)); + var runner = previousLeaveAnimations[i] = $animate.leave(selected); + runner.done(spliceFactory(previousLeaveAnimations, i)); } selectedElements.length = 0; diff --git a/src/ngRoute/directive/ngView.js b/src/ngRoute/directive/ngView.js index ead89554f10f..97c6d41cb6d3 100644 --- a/src/ngRoute/directive/ngView.js +++ b/src/ngRoute/directive/ngView.js @@ -207,8 +207,8 @@ function ngViewFactory($route, $anchorScroll, $animate) { } if (currentElement) { previousLeaveAnimation = $animate.leave(currentElement); - previousLeaveAnimation.then(function() { - previousLeaveAnimation = null; + previousLeaveAnimation.done(function(response) { + if (response !== false) previousLeaveAnimation = null; }); currentElement = null; } @@ -229,8 +229,8 @@ function ngViewFactory($route, $anchorScroll, $animate) { // function is called before linking the content, which would apply child // directives to non existing elements. var clone = $transclude(newScope, function(clone) { - $animate.enter(clone, null, currentElement || $element).then(function onNgViewEnter() { - if (angular.isDefined(autoScrollExp) + $animate.enter(clone, null, currentElement || $element).done(function onNgViewEnter(response) { + if (response !== false && angular.isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { $anchorScroll(); } diff --git a/test/ng/directive/ngIfSpec.js b/test/ng/directive/ngIfSpec.js index 6941befa254f..6f7e7cb355f3 100644 --- a/test/ng/directive/ngIfSpec.js +++ b/test/ng/directive/ngIfSpec.js @@ -1,371 +1,393 @@ 'use strict'; describe('ngIf', function() { - var $scope, $compile, element, $compileProvider; - - beforeEach(module(function(_$compileProvider_) { - $compileProvider = _$compileProvider_; - })); - beforeEach(inject(function($rootScope, _$compile_) { - $scope = $rootScope.$new(); - $compile = _$compile_; - element = $compile('
')($scope); - })); - - afterEach(function() { - dealoc(element); - }); - function makeIf() { - forEach(arguments, function(expr) { - element.append($compile('
Hi
')($scope)); - }); - $scope.$apply(); - } + describe('basic', function() { + var $scope, $compile, element, $compileProvider; - it('should immediately remove the element if condition is falsy', function() { - makeIf('false', 'undefined', 'null', 'NaN', '\'\'', '0'); - expect(element.children().length).toBe(0); - }); + beforeEach(module(function(_$compileProvider_) { + $compileProvider = _$compileProvider_; + })); + beforeEach(inject(function($rootScope, _$compile_) { + $scope = $rootScope.$new(); + $compile = _$compile_; + element = $compile('
')($scope); + })); - it('should leave the element if condition is true', function() { - makeIf('true'); - expect(element.children().length).toBe(1); - }); - - it('should leave the element if the condition is a non-empty string', function() { - makeIf('\'f\'', '\'0\'', '\'false\'', '\'no\'', '\'n\'', '\'[]\''); - expect(element.children().length).toBe(6); - }); + afterEach(function() { + dealoc(element); + }); - it('should leave the element if the condition is an object', function() { - makeIf('[]', '{}'); - expect(element.children().length).toBe(2); - }); + function makeIf() { + forEach(arguments, function(expr) { + element.append($compile('
Hi
')($scope)); + }); + $scope.$apply(); + } - it('should not add the element twice if the condition goes from true to true', function() { - $scope.hello = 'true1'; - makeIf('hello'); - expect(element.children().length).toBe(1); - $scope.$apply('hello = "true2"'); - expect(element.children().length).toBe(1); - }); + it('should immediately remove the element if condition is falsy', function() { + makeIf('false', 'undefined', 'null', 'NaN', '\'\'', '0'); + expect(element.children().length).toBe(0); + }); - it('should not recreate the element if the condition goes from true to true', function() { - $scope.hello = 'true1'; - makeIf('hello'); - element.children().data('flag', true); - $scope.$apply('hello = "true2"'); - expect(element.children().data('flag')).toBe(true); - }); + it('should leave the element if condition is true', function() { + makeIf('true'); + expect(element.children().length).toBe(1); + }); - it('should create then remove the element if condition changes', function() { - $scope.hello = true; - makeIf('hello'); - expect(element.children().length).toBe(1); - $scope.$apply('hello = false'); - expect(element.children().length).toBe(0); - }); + it('should leave the element if the condition is a non-empty string', function() { + makeIf('\'f\'', '\'0\'', '\'false\'', '\'no\'', '\'n\'', '\'[]\''); + expect(element.children().length).toBe(6); + }); - it('should create a new scope every time the expression evaluates to true', function() { - $scope.$apply('value = true'); - element.append($compile( - '
' - )($scope)); - $scope.$apply(); - expect(element.children('div').length).toBe(1); - }); + it('should leave the element if the condition is an object', function() { + makeIf('[]', '{}'); + expect(element.children().length).toBe(2); + }); - it('should destroy the child scope every time the expression evaluates to false', function() { - $scope.value = true; - element.append($compile( - '
' - )($scope)); - $scope.$apply(); + it('should not add the element twice if the condition goes from true to true', function() { + $scope.hello = 'true1'; + makeIf('hello'); + expect(element.children().length).toBe(1); + $scope.$apply('hello = "true2"'); + expect(element.children().length).toBe(1); + }); - var childScope = element.children().scope(); - var destroyed = false; + it('should not recreate the element if the condition goes from true to true', function() { + $scope.hello = 'true1'; + makeIf('hello'); + element.children().data('flag', true); + $scope.$apply('hello = "true2"'); + expect(element.children().data('flag')).toBe(true); + }); - childScope.$on('$destroy', function() { - destroyed = true; + it('should create then remove the element if condition changes', function() { + $scope.hello = true; + makeIf('hello'); + expect(element.children().length).toBe(1); + $scope.$apply('hello = false'); + expect(element.children().length).toBe(0); }); - $scope.value = false; - $scope.$apply(); + it('should create a new scope every time the expression evaluates to true', function() { + $scope.$apply('value = true'); + element.append($compile( + '
' + )($scope)); + $scope.$apply(); + expect(element.children('div').length).toBe(1); + }); - expect(destroyed).toBe(true); - }); + it('should destroy the child scope every time the expression evaluates to false', function() { + $scope.value = true; + element.append($compile( + '
' + )($scope)); + $scope.$apply(); - it('should play nice with other elements beside it', function() { - $scope.values = [1, 2, 3, 4]; - element.append($compile( - '
' + - '
' + - '
' - )($scope)); - $scope.$apply(); - expect(element.children().length).toBe(9); - $scope.$apply('values.splice(0,1)'); - expect(element.children().length).toBe(6); - $scope.$apply('values.push(1)'); - expect(element.children().length).toBe(9); - }); + var childScope = element.children().scope(); + var destroyed = false; - it('should play nice with ngInclude on the same element', inject(function($templateCache) { - $templateCache.put('test.html', [200, '{{value}}', {}]); - - $scope.value = 'first'; - element.append($compile( - '
' - )($scope)); - $scope.$apply(); - expect(element.text()).toBe('first'); - - $scope.value = 'later'; - $scope.$apply(); - expect(element.text()).toBe(''); - })); - - it('should work with multiple elements', function() { - $scope.show = true; - $scope.things = [1, 2, 3]; - element.append($compile( - '
before;
' + - '
start;
' + - '
{{thing}};
' + - '
end;
' + - '
after;
' - )($scope)); - $scope.$apply(); - expect(element.text()).toBe('before;start;1;2;3;end;after;'); - - $scope.things.push(4); - $scope.$apply(); - expect(element.text()).toBe('before;start;1;2;3;4;end;after;'); - - $scope.show = false; - $scope.$apply(); - expect(element.text()).toBe('before;after;'); - }); + childScope.$on('$destroy', function() { + destroyed = true; + }); - it('should restore the element to its compiled state', function() { - $scope.value = true; - makeIf('value'); - expect(element.children().length).toBe(1); - jqLite(element.children()[0]).removeClass('my-class'); - expect(element.children()[0].className).not.toContain('my-class'); - $scope.$apply('value = false'); - expect(element.children().length).toBe(0); - $scope.$apply('value = true'); - expect(element.children().length).toBe(1); - expect(element.children()[0].className).toContain('my-class'); - }); + $scope.value = false; + $scope.$apply(); - it('should work when combined with an ASYNC template that loads after the first digest', inject(function($httpBackend, $compile, $rootScope) { - $compileProvider.directive('test', function() { - return { - templateUrl: 'test.html' - }; + expect(destroyed).toBe(true); }); - $httpBackend.whenGET('test.html').respond('hello'); - element.append('
'); - $compile(element)($rootScope); - $rootScope.show = true; - $rootScope.$apply(); - expect(element.text()).toBe(''); - - $httpBackend.flush(); - expect(element.text()).toBe('hello'); - - $rootScope.show = false; - $rootScope.$apply(); - // Note: there are still comments in element! - expect(element.children().length).toBe(0); - expect(element.text()).toBe(''); - })); -}); -describe('ngIf and transcludes', function() { - it('should allow access to directive controller from children when used in a replace template', function() { - var controller; - module(function($compileProvider) { - var directive = $compileProvider.directive; - directive('template', valueFn({ - template: '
', - replace: true, - controller: function() { - this.flag = true; - } - })); - directive('test', valueFn({ - require: '^template', - link: function(scope, el, attr, ctrl) { - controller = ctrl; - } - })); + it('should play nice with other elements beside it', function() { + $scope.values = [1, 2, 3, 4]; + element.append($compile( + '
' + + '
' + + '
' + )($scope)); + $scope.$apply(); + expect(element.children().length).toBe(9); + $scope.$apply('values.splice(0,1)'); + expect(element.children().length).toBe(6); + $scope.$apply('values.push(1)'); + expect(element.children().length).toBe(9); }); - inject(function($compile, $rootScope) { - var element = $compile('
')($rootScope); - $rootScope.$apply(); - expect(controller.flag).toBe(true); - dealoc(element); - }); - }); - - it('should use the correct transcluded scope', function() { - module(function($compileProvider) { - $compileProvider.directive('iso', valueFn({ - link: function(scope) { - scope.val = 'value in iso scope'; - }, - restrict: 'E', - transclude: true, - template: '
val={{val}}-
', - scope: {} - })); - }); - inject(function($compile, $rootScope) { - $rootScope.val = 'transcluded content'; - var element = $compile('')($rootScope); - $rootScope.$digest(); - expect(trim(element.text())).toEqual('val=value in iso scope-transcluded content'); - dealoc(element); + it('should play nice with ngInclude on the same element', inject(function($templateCache) { + $templateCache.put('test.html', [200, '{{value}}', {}]); + + $scope.value = 'first'; + element.append($compile( + '
' + )($scope)); + $scope.$apply(); + expect(element.text()).toBe('first'); + + $scope.value = 'later'; + $scope.$apply(); + expect(element.text()).toBe(''); + })); + + it('should work with multiple elements', function() { + $scope.show = true; + $scope.things = [1, 2, 3]; + element.append($compile( + '
before;
' + + '
start;
' + + '
{{thing}};
' + + '
end;
' + + '
after;
' + )($scope)); + $scope.$apply(); + expect(element.text()).toBe('before;start;1;2;3;end;after;'); + + $scope.things.push(4); + $scope.$apply(); + expect(element.text()).toBe('before;start;1;2;3;4;end;after;'); + + $scope.show = false; + $scope.$apply(); + expect(element.text()).toBe('before;after;'); }); - }); -}); -describe('ngIf animations', function() { - var body, element, $rootElement; - - function html(content) { - $rootElement.html(content); - element = $rootElement.children().eq(0); - return element; - } - - beforeEach(module('ngAnimateMock')); - - beforeEach(module(function() { - // we need to run animation on attached elements; - return function(_$rootElement_) { - $rootElement = _$rootElement_; - body = jqLite(window.document.body); - body.append($rootElement); - }; - })); - - afterEach(function() { - dealoc(body); - dealoc(element); - }); - - beforeEach(module(function($animateProvider, $provide) { - return function($animate) { - $animate.enabled(true); - }; - })); - - it('should fire off the enter animation', - inject(function($compile, $rootScope, $animate) { - var item; - var $scope = $rootScope.$new(); - element = $compile(html( - '
' + - '
Hi
' + - '
' - ))($scope); - - $rootScope.$digest(); + it('should restore the element to its compiled state', function() { + $scope.value = true; + makeIf('value'); + expect(element.children().length).toBe(1); + jqLite(element.children()[0]).removeClass('my-class'); + expect(element.children()[0].className).not.toContain('my-class'); + $scope.$apply('value = false'); + expect(element.children().length).toBe(0); $scope.$apply('value = true'); + expect(element.children().length).toBe(1); + expect(element.children()[0].className).toContain('my-class'); + }); + + it('should work when combined with an ASYNC template that loads after the first digest', inject(function($httpBackend, $compile, $rootScope) { + $compileProvider.directive('test', function() { + return { + templateUrl: 'test.html' + }; + }); + $httpBackend.whenGET('test.html').respond('hello'); + element.append('
'); + $compile(element)($rootScope); + $rootScope.show = true; + $rootScope.$apply(); + expect(element.text()).toBe(''); - item = $animate.queue.shift(); - expect(item.event).toBe('enter'); - expect(item.element.text()).toBe('Hi'); + $httpBackend.flush(); + expect(element.text()).toBe('hello'); - expect(element.children().length).toBe(1); - }) - ); - - it('should fire off the leave animation', - inject(function($compile, $rootScope, $animate) { - var item; - var $scope = $rootScope.$new(); - element = $compile(html( - '
' + - '
Hi
' + - '
' - ))($scope); - $scope.$apply('value = true'); + $rootScope.show = false; + $rootScope.$apply(); + // Note: there are still comments in element! + expect(element.children().length).toBe(0); + expect(element.text()).toBe(''); + })); - item = $animate.queue.shift(); - expect(item.event).toBe('enter'); - expect(item.element.text()).toBe('Hi'); + it('should not trigger a digest when the element is removed', inject(function($$rAF, $rootScope, $timeout) { + var spy = spyOn($rootScope, '$digest').and.callThrough(); + $scope.hello = true; + makeIf('hello'); expect(element.children().length).toBe(1); - $scope.$apply('value = false'); + $scope.$apply('hello = false'); + spy.calls.reset(); + expect(element.children().length).toBe(0); + // The animation completion is async even without actual animations + $$rAF.flush(); - item = $animate.queue.shift(); - expect(item.event).toBe('leave'); - expect(item.element.text()).toBe('Hi'); + expect(spy).not.toHaveBeenCalled(); + // A digest may have been triggered asynchronously, so check the queue + $timeout.verifyNoPendingTasks(); + })); + }); - expect(element.children().length).toBe(0); - }) - ); - - it('should destroy the previous leave animation if a new one takes place', function() { - module(function($provide) { - $provide.decorator('$animate', function($delegate, $$q) { - var emptyPromise = $$q.defer().promise; - $delegate.leave = function() { - return emptyPromise; - }; - return $delegate; + describe('and transcludes', function() { + it('should allow access to directive controller from children when used in a replace template', function() { + var controller; + module(function($compileProvider) { + var directive = $compileProvider.directive; + directive('template', valueFn({ + template: '
', + replace: true, + controller: function() { + this.flag = true; + } + })); + directive('test', valueFn({ + require: '^template', + link: function(scope, el, attr, ctrl) { + controller = ctrl; + } + })); + }); + inject(function($compile, $rootScope) { + var element = $compile('
')($rootScope); + $rootScope.$apply(); + expect(controller.flag).toBe(true); + dealoc(element); }); }); - inject(function($compile, $rootScope, $animate) { - var item; - var $scope = $rootScope.$new(); - element = $compile(html( - '
' + - '
Yo
' + - '
' - ))($scope); - $scope.$apply('value = true'); - var destroyed, inner = element.children(0); - inner.on('$destroy', function() { - destroyed = true; + it('should use the correct transcluded scope', function() { + module(function($compileProvider) { + $compileProvider.directive('iso', valueFn({ + link: function(scope) { + scope.val = 'value in iso scope'; + }, + restrict: 'E', + transclude: true, + template: '
val={{val}}-
', + scope: {} + })); + }); + inject(function($compile, $rootScope) { + $rootScope.val = 'transcluded content'; + var element = $compile('')($rootScope); + $rootScope.$digest(); + expect(trim(element.text())).toEqual('val=value in iso scope-transcluded content'); + dealoc(element); }); + }); + }); - $scope.$apply('value = false'); + describe('and animations', function() { + var body, element, $rootElement; - $scope.$apply('value = true'); + function html(content) { + $rootElement.html(content); + element = $rootElement.children().eq(0); + return element; + } - $scope.$apply('value = false'); + beforeEach(module('ngAnimateMock')); - expect(destroyed).toBe(true); + beforeEach(module(function() { + // we need to run animation on attached elements; + return function(_$rootElement_) { + $rootElement = _$rootElement_; + body = jqLite(window.document.body); + body.append($rootElement); + }; + })); + + afterEach(function() { + dealoc(body); + dealoc(element); }); - }); - it('should work with svg elements when the svg container is transcluded', function() { - module(function($compileProvider) { - $compileProvider.directive('svgContainer', function() { - return { - template: '', - replace: true, - transclude: true - }; + beforeEach(module(function($animateProvider, $provide) { + return function($animate) { + $animate.enabled(true); + }; + })); + + it('should fire off the enter animation', + inject(function($compile, $rootScope, $animate) { + var item; + var $scope = $rootScope.$new(); + element = $compile(html( + '
' + + '
Hi
' + + '
' + ))($scope); + + $rootScope.$digest(); + $scope.$apply('value = true'); + + item = $animate.queue.shift(); + expect(item.event).toBe('enter'); + expect(item.element.text()).toBe('Hi'); + + expect(element.children().length).toBe(1); + }) + ); + + it('should fire off the leave animation', + inject(function($compile, $rootScope, $animate) { + var item; + var $scope = $rootScope.$new(); + element = $compile(html( + '
' + + '
Hi
' + + '
' + ))($scope); + $scope.$apply('value = true'); + + item = $animate.queue.shift(); + expect(item.event).toBe('enter'); + expect(item.element.text()).toBe('Hi'); + + expect(element.children().length).toBe(1); + $scope.$apply('value = false'); + + item = $animate.queue.shift(); + expect(item.event).toBe('leave'); + expect(item.element.text()).toBe('Hi'); + + expect(element.children().length).toBe(0); + }) + ); + + it('should destroy the previous leave animation if a new one takes place', function() { + module(function($provide) { + $provide.decorator('$animate', function($delegate, $$q) { + var emptyPromise = $$q.defer().promise; + emptyPromise.done = noop; + + $delegate.leave = function() { + return emptyPromise; + }; + return $delegate; + }); + }); + inject(function($compile, $rootScope, $animate) { + var item; + var $scope = $rootScope.$new(); + element = $compile(html( + '
' + + '
Yo
' + + '
' + ))($scope); + + $scope.$apply('value = true'); + + var destroyed, inner = element.children(0); + inner.on('$destroy', function() { + destroyed = true; + }); + + $scope.$apply('value = false'); + + $scope.$apply('value = true'); + + $scope.$apply('value = false'); + + expect(destroyed).toBe(true); }); }); - inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - $rootScope.flag = true; - $rootScope.$apply(); - var circle = element.find('circle'); - expect(circle[0].toString()).toMatch(/SVG/); + it('should work with svg elements when the svg container is transcluded', function() { + module(function($compileProvider) { + $compileProvider.directive('svgContainer', function() { + return { + template: '', + replace: true, + transclude: true + }; + }); + }); + inject(function($compile, $rootScope) { + element = $compile('')($rootScope); + $rootScope.flag = true; + $rootScope.$apply(); + + var circle = element.find('circle'); + expect(circle[0].toString()).toMatch(/SVG/); + }); }); }); }); diff --git a/test/ng/directive/ngIncludeSpec.js b/test/ng/directive/ngIncludeSpec.js index c87cb5e4e32d..e1261c2040e7 100644 --- a/test/ng/directive/ngIncludeSpec.js +++ b/test/ng/directive/ngIncludeSpec.js @@ -1,794 +1,827 @@ 'use strict'; describe('ngInclude', function() { - var element; - afterEach(function() { - dealoc(element); - }); - - - function putIntoCache(url, content) { - return function($templateCache) { - $templateCache.put(url, [200, content, {}]); - }; - } - - - it('should trust and use literal urls', inject(function( - $rootScope, $httpBackend, $compile) { - element = $compile('
')($rootScope); - $httpBackend.expect('GET', 'url').respond('template text'); - $rootScope.$digest(); - $httpBackend.flush(); - expect(element.text()).toEqual('template text'); - dealoc($rootScope); - })); - - - it('should trust and use trusted urls', inject(function($rootScope, $httpBackend, $compile, $sce) { - element = $compile('
')($rootScope); - $httpBackend.expect('GET', 'http://foo.bar/url').respond('template text'); - $rootScope.fooUrl = $sce.trustAsResourceUrl('http://foo.bar/url'); - $rootScope.$digest(); - $httpBackend.flush(); - expect(element.text()).toEqual('template text'); - dealoc($rootScope); - })); - - - it('should include an external file', inject(putIntoCache('myUrl', '{{name}}'), - function($rootScope, $compile) { - element = jqLite('
'); - var body = jqLite(window.document.body); - body.append(element); - element = $compile(element)($rootScope); - $rootScope.name = 'misko'; - $rootScope.url = 'myUrl'; - $rootScope.$digest(); - expect(body.text()).toEqual('misko'); - body.empty(); - })); - - - it('should support ng-include="src" syntax', inject(putIntoCache('myUrl', '{{name}}'), - function($rootScope, $compile) { - element = jqLite('
'); - jqLite(window.document.body).append(element); - element = $compile(element)($rootScope); - $rootScope.name = 'Alibaba'; - $rootScope.url = 'myUrl'; - $rootScope.$digest(); - expect(element.text()).toEqual('Alibaba'); - jqLite(window.document.body).empty(); - })); - - - it('should NOT use untrusted URL expressions ', inject(putIntoCache('myUrl', '{{name}} text'), - function($rootScope, $compile, $sce) { - element = jqLite(''); - jqLite(window.document.body).append(element); - element = $compile(element)($rootScope); - $rootScope.name = 'chirayu'; - $rootScope.url = 'http://example.com/myUrl'; - expect(function() { $rootScope.$digest(); }).toThrowMinErr( - '$sce', 'insecurl', - /Blocked loading resource from url not allowed by \$sceDelegate policy. {2}URL: http:\/\/example.com\/myUrl.*/); - jqLite(window.document.body).empty(); - })); - - - it('should NOT use mistyped expressions ', inject(putIntoCache('myUrl', '{{name}} text'), - function($rootScope, $compile, $sce) { - element = jqLite(''); - jqLite(window.document.body).append(element); - element = $compile(element)($rootScope); - $rootScope.name = 'chirayu'; - $rootScope.url = $sce.trustAsUrl('http://example.com/myUrl'); - expect(function() { $rootScope.$digest(); }).toThrowMinErr( - '$sce', 'insecurl', - /Blocked loading resource from url not allowed by \$sceDelegate policy. {2}URL: http:\/\/example.com\/myUrl.*/); - jqLite(window.document.body).empty(); - })); - - - it('should remove previously included text if a falsy value is bound to src', inject( - putIntoCache('myUrl', '{{name}}'), - function($rootScope, $compile) { - element = jqLite('
'); - element = $compile(element)($rootScope); - $rootScope.name = 'igor'; - $rootScope.url = 'myUrl'; - $rootScope.$digest(); - - expect(element.text()).toEqual('igor'); - - $rootScope.url = undefined; - $rootScope.$digest(); - - expect(element.text()).toEqual(''); - })); + describe('basic', function() { + var element; - it('should fire $includeContentRequested event on scope after making the xhr call', inject( - function($rootScope, $compile, $httpBackend) { - var contentRequestedSpy = jasmine.createSpy('content requested').and.callFake(function(event) { - expect(event.targetScope).toBe($rootScope); + afterEach(function() { + dealoc(element); }); - $httpBackend.whenGET('url').respond('my partial'); - $rootScope.$on('$includeContentRequested', contentRequestedSpy); - - element = $compile('
')($rootScope); - $rootScope.$digest(); - expect(contentRequestedSpy).toHaveBeenCalledOnceWith(jasmine.any(Object), 'url'); - - $httpBackend.flush(); - })); + function putIntoCache(url, content) { + return function($templateCache) { + $templateCache.put(url, [200, content, {}]); + }; + } - it('should fire $includeContentLoaded event on child scope after linking the content', inject( - function($rootScope, $compile, $templateCache) { - var contentLoadedSpy = jasmine.createSpy('content loaded').and.callFake(function(event) { - expect(event.targetScope.$parent).toBe($rootScope); - expect(element.text()).toBe('partial content'); - }); - $templateCache.put('url', [200, 'partial content', {}]); - $rootScope.$on('$includeContentLoaded', contentLoadedSpy); + it('should trust and use literal urls', inject(function( + $rootScope, $httpBackend, $compile) { + element = $compile('
')($rootScope); + $httpBackend.expect('GET', 'url').respond('template text'); + $rootScope.$digest(); + $httpBackend.flush(); + expect(element.text()).toEqual('template text'); + dealoc($rootScope); + })); - element = $compile('
')($rootScope); - $rootScope.$digest(); - expect(contentLoadedSpy).toHaveBeenCalledOnceWith(jasmine.any(Object), 'url'); - })); + it('should trust and use trusted urls', inject(function($rootScope, $httpBackend, $compile, $sce) { + element = $compile('
')($rootScope); + $httpBackend.expect('GET', 'http://foo.bar/url').respond('template text'); + $rootScope.fooUrl = $sce.trustAsResourceUrl('http://foo.bar/url'); + $rootScope.$digest(); + $httpBackend.flush(); + expect(element.text()).toEqual('template text'); + dealoc($rootScope); + })); - it('should fire $includeContentError event when content request fails', inject( - function($rootScope, $compile, $httpBackend, $templateCache) { - var contentLoadedSpy = jasmine.createSpy('content loaded'), - contentErrorSpy = jasmine.createSpy('content error'); + it('should include an external file', inject(putIntoCache('myUrl', '{{name}}'), + function($rootScope, $compile) { + element = jqLite('
'); + var body = jqLite(window.document.body); + body.append(element); + element = $compile(element)($rootScope); + $rootScope.name = 'misko'; + $rootScope.url = 'myUrl'; + $rootScope.$digest(); + expect(body.text()).toEqual('misko'); + body.empty(); + })); - $rootScope.$on('$includeContentLoaded', contentLoadedSpy); - $rootScope.$on('$includeContentError', contentErrorSpy); - $httpBackend.expect('GET', 'tpl.html').respond(400, 'nope'); + it('should support ng-include="src" syntax', inject(putIntoCache('myUrl', '{{name}}'), + function($rootScope, $compile) { + element = jqLite('
'); + jqLite(window.document.body).append(element); + element = $compile(element)($rootScope); + $rootScope.name = 'Alibaba'; + $rootScope.url = 'myUrl'; + $rootScope.$digest(); + expect(element.text()).toEqual('Alibaba'); + jqLite(window.document.body).empty(); + })); - element = $compile('
')($rootScope); - $rootScope.$apply(function() { - $rootScope.template = 'tpl.html'; - }); - $httpBackend.flush(); + it('should NOT use untrusted URL expressions ', inject(putIntoCache('myUrl', '{{name}} text'), + function($rootScope, $compile, $sce) { + element = jqLite(''); + jqLite(window.document.body).append(element); + element = $compile(element)($rootScope); + $rootScope.name = 'chirayu'; + $rootScope.url = 'http://example.com/myUrl'; + expect(function() { $rootScope.$digest(); }).toThrowMinErr( + '$sce', 'insecurl', + /Blocked loading resource from url not allowed by \$sceDelegate policy. {2}URL: http:\/\/example.com\/myUrl.*/); + jqLite(window.document.body).empty(); + })); - expect(contentLoadedSpy).not.toHaveBeenCalled(); - expect(contentErrorSpy).toHaveBeenCalledOnceWith(jasmine.any(Object), 'tpl.html'); - expect(element.children('div').contents().length).toBe(0); - })); + it('should NOT use mistyped expressions ', inject(putIntoCache('myUrl', '{{name}} text'), + function($rootScope, $compile, $sce) { + element = jqLite(''); + jqLite(window.document.body).append(element); + element = $compile(element)($rootScope); + $rootScope.name = 'chirayu'; + $rootScope.url = $sce.trustAsUrl('http://example.com/myUrl'); + expect(function() { $rootScope.$digest(); }).toThrowMinErr( + '$sce', 'insecurl', + /Blocked loading resource from url not allowed by \$sceDelegate policy. {2}URL: http:\/\/example.com\/myUrl.*/); + jqLite(window.document.body).empty(); + })); - it('should evaluate onload expression when a partial is loaded', inject( - putIntoCache('myUrl', 'my partial'), - function($rootScope, $compile) { - element = jqLite('
'); - element = $compile(element)($rootScope); - expect($rootScope.loaded).not.toBeDefined(); + it('should remove previously included text if a falsy value is bound to src', inject( + putIntoCache('myUrl', '{{name}}'), + function($rootScope, $compile) { + element = jqLite('
'); + element = $compile(element)($rootScope); + $rootScope.name = 'igor'; + $rootScope.url = 'myUrl'; + $rootScope.$digest(); - $rootScope.url = 'myUrl'; - $rootScope.$digest(); + expect(element.text()).toEqual('igor'); - expect(element.text()).toEqual('my partial'); - expect($rootScope.loaded).toBe(true); - })); + $rootScope.url = undefined; + $rootScope.$digest(); + expect(element.text()).toEqual(''); + })); - it('should create child scope and destroy old one', inject( + it('should fire $includeContentRequested event on scope after making the xhr call', inject( function($rootScope, $compile, $httpBackend) { - $httpBackend.whenGET('url1').respond('partial {{$parent.url}}'); - $httpBackend.whenGET('url2').respond(404); + var contentRequestedSpy = jasmine.createSpy('content requested').and.callFake(function(event) { + expect(event.targetScope).toBe($rootScope); + }); - element = $compile('
')($rootScope); - expect(element.children().scope()).toBeFalsy(); + $httpBackend.whenGET('url').respond('my partial'); + $rootScope.$on('$includeContentRequested', contentRequestedSpy); - $rootScope.url = 'url1'; - $rootScope.$digest(); - $httpBackend.flush(); - expect(element.children().scope().$parent).toBe($rootScope); - expect(element.text()).toBe('partial url1'); + element = $compile('
')($rootScope); + $rootScope.$digest(); - $rootScope.url = 'url2'; - $rootScope.$digest(); - $httpBackend.flush(); + expect(contentRequestedSpy).toHaveBeenCalledOnceWith(jasmine.any(Object), 'url'); - expect($rootScope.$$childHead).toBeFalsy(); - expect(element.text()).toBe(''); + $httpBackend.flush(); + })); - $rootScope.url = 'url1'; - $rootScope.$digest(); - expect(element.children().scope().$parent).toBe($rootScope); + it('should fire $includeContentLoaded event on child scope after linking the content', inject( + function($rootScope, $compile, $templateCache) { + var contentLoadedSpy = jasmine.createSpy('content loaded').and.callFake(function(event) { + expect(event.targetScope.$parent).toBe($rootScope); + expect(element.text()).toBe('partial content'); + }); - $rootScope.url = null; - $rootScope.$digest(); - expect($rootScope.$$childHead).toBeFalsy(); - })); + $templateCache.put('url', [200, 'partial content', {}]); + $rootScope.$on('$includeContentLoaded', contentLoadedSpy); + element = $compile('
')($rootScope); + $rootScope.$digest(); - it('should do xhr request and cache it', - inject(function($rootScope, $httpBackend, $compile) { - element = $compile('
')($rootScope); - $httpBackend.expect('GET', 'myUrl').respond('my partial'); + expect(contentLoadedSpy).toHaveBeenCalledOnceWith(jasmine.any(Object), 'url'); + })); - $rootScope.url = 'myUrl'; - $rootScope.$digest(); - $httpBackend.flush(); - expect(element.text()).toEqual('my partial'); - $rootScope.url = null; - $rootScope.$digest(); - expect(element.text()).toEqual(''); + it('should fire $includeContentError event when content request fails', inject( + function($rootScope, $compile, $httpBackend, $templateCache) { + var contentLoadedSpy = jasmine.createSpy('content loaded'), + contentErrorSpy = jasmine.createSpy('content error'); - $rootScope.url = 'myUrl'; - $rootScope.$digest(); - expect(element.text()).toEqual('my partial'); - dealoc($rootScope); - })); + $rootScope.$on('$includeContentLoaded', contentLoadedSpy); + $rootScope.$on('$includeContentError', contentErrorSpy); + $httpBackend.expect('GET', 'tpl.html').respond(400, 'nope'); - it('should clear content when error during xhr request', - inject(function($httpBackend, $compile, $rootScope) { - element = $compile('
content
')($rootScope); - $httpBackend.expect('GET', 'myUrl').respond(404, ''); + element = $compile('
')($rootScope); - $rootScope.url = 'myUrl'; - $rootScope.$digest(); - $httpBackend.flush(); + $rootScope.$apply(function() { + $rootScope.template = 'tpl.html'; + }); + $httpBackend.flush(); - expect(element.text()).toBe(''); - })); + expect(contentLoadedSpy).not.toHaveBeenCalled(); + expect(contentErrorSpy).toHaveBeenCalledOnceWith(jasmine.any(Object), 'tpl.html'); + expect(element.children('div').contents().length).toBe(0); + })); - it('should be async even if served from cache', inject( + it('should evaluate onload expression when a partial is loaded', inject( putIntoCache('myUrl', 'my partial'), function($rootScope, $compile) { - element = $compile('
')($rootScope); + element = jqLite('
'); + element = $compile(element)($rootScope); - $rootScope.url = 'myUrl'; + expect($rootScope.loaded).not.toBeDefined(); - var called = 0; - // we want to assert only during first watch - $rootScope.$watch(function() { - if (!called) expect(element.text()).toBe(''); - called++; - }); + $rootScope.url = 'myUrl'; + $rootScope.$digest(); - $rootScope.$digest(); - expect(element.text()).toBe('my partial'); - })); + expect(element.text()).toEqual('my partial'); + expect($rootScope.loaded).toBe(true); + })); - it('should discard pending xhr callbacks if a new template is requested before the current ' + - 'finished loading', inject(function($rootScope, $compile, $httpBackend) { - element = jqLite('
'); - var log = {}; + it('should create child scope and destroy old one', inject( + function($rootScope, $compile, $httpBackend) { + $httpBackend.whenGET('url1').respond('partial {{$parent.url}}'); + $httpBackend.whenGET('url2').respond(404); - $rootScope.templateUrl = 'myUrl1'; - $rootScope.logger = function(msg) { - log[msg] = true; - }; - $compile(element)($rootScope); - expect(log).toEqual({}); + element = $compile('
')($rootScope); + expect(element.children().scope()).toBeFalsy(); - $httpBackend.expect('GET', 'myUrl1').respond('
{{logger("url1")}}
'); - $rootScope.$digest(); - expect(log).toEqual({}); - $rootScope.templateUrl = 'myUrl2'; - $httpBackend.expect('GET', 'myUrl2').respond('
{{logger("url2")}}
'); - $httpBackend.flush(); // now that we have two requests pending, flush! + $rootScope.url = 'url1'; + $rootScope.$digest(); + $httpBackend.flush(); + expect(element.children().scope().$parent).toBe($rootScope); + expect(element.text()).toBe('partial url1'); - expect(log).toEqual({ url2: true }); - })); + $rootScope.url = 'url2'; + $rootScope.$digest(); + $httpBackend.flush(); + expect($rootScope.$$childHead).toBeFalsy(); + expect(element.text()).toBe(''); - it('should compile only the content', inject(function($compile, $rootScope, $templateCache) { - // regression + $rootScope.url = 'url1'; + $rootScope.$digest(); + expect(element.children().scope().$parent).toBe($rootScope); - var onload = jasmine.createSpy('$includeContentLoaded'); - $rootScope.$on('$includeContentLoaded', onload); - $templateCache.put('tpl.html', [200, 'partial {{tpl}}', {}]); + $rootScope.url = null; + $rootScope.$digest(); + expect($rootScope.$$childHead).toBeFalsy(); + })); - element = $compile('
' + - '
')($rootScope); - expect(onload).not.toHaveBeenCalled(); - $rootScope.$apply(function() { - $rootScope.tpl = 'tpl.html'; - }); - expect(onload).toHaveBeenCalledOnce(); - - $rootScope.tpl = ''; - $rootScope.$digest(); - dealoc(element); - })); - - - it('should not break attribute bindings on the same element', inject(function($compile, $rootScope, $httpBackend) { - // regression #3793 - - element = $compile('
')($rootScope); - $httpBackend.expect('GET', 'url1').respond('template text 1'); - $rootScope.hrefUrl = 'fooUrl1'; - $rootScope.includeUrl = 'url1'; - $rootScope.$digest(); - $httpBackend.flush(); - expect(element.text()).toBe('template text 1'); - expect(element.find('span').attr('foo')).toBe('#/fooUrl1'); - - $httpBackend.expect('GET', 'url2').respond('template text 2'); - $rootScope.includeUrl = 'url2'; - $rootScope.$digest(); - $httpBackend.flush(); - expect(element.text()).toBe('template text 2'); - expect(element.find('span').attr('foo')).toBe('#/fooUrl1'); - - $rootScope.hrefUrl = 'fooUrl2'; - $rootScope.$digest(); - expect(element.text()).toBe('template text 2'); - expect(element.find('span').attr('foo')).toBe('#/fooUrl2'); - })); - - - it('should exec scripts when jQuery is included', inject(function($compile, $rootScope, $httpBackend) { - if (!jQuery) { - return; - } + it('should do xhr request and cache it', + inject(function($rootScope, $httpBackend, $compile) { + element = $compile('
')($rootScope); + $httpBackend.expect('GET', 'myUrl').respond('my partial'); - element = $compile('
')($rootScope); + $rootScope.url = 'myUrl'; + $rootScope.$digest(); + $httpBackend.flush(); + expect(element.text()).toEqual('my partial'); - // the element needs to be appended for the script to run - element.appendTo(window.document.body); - window._ngIncludeCausesScriptToRun = false; - $httpBackend.expect('GET', 'url1').respond(''); - $rootScope.includeUrl = 'url1'; - $rootScope.$digest(); - $httpBackend.flush(); + $rootScope.url = null; + $rootScope.$digest(); + expect(element.text()).toEqual(''); - expect(window._ngIncludeCausesScriptToRun).toBe(true); + $rootScope.url = 'myUrl'; + $rootScope.$digest(); + expect(element.text()).toEqual('my partial'); + dealoc($rootScope); + })); - delete window._ngIncludeCausesScriptToRun; - })); + it('should clear content when error during xhr request', + inject(function($httpBackend, $compile, $rootScope) { + element = $compile('
content
')($rootScope); + $httpBackend.expect('GET', 'myUrl').respond(404, ''); - it('should construct SVG template elements with correct namespace', function() { - if (!window.SVGRectElement) return; - module(function($compileProvider) { - $compileProvider.directive('test', valueFn({ - templateNamespace: 'svg', - templateUrl: 'my-rect.html', - replace: true - })); - }); - inject(function($compile, $rootScope, $httpBackend) { - $httpBackend.expectGET('my-rect.html').respond(''); - $httpBackend.expectGET('include.svg').respond(''); - element = $compile('')($rootScope); + $rootScope.url = 'myUrl'; + $rootScope.$digest(); $httpBackend.flush(); - var child = element.find('rect'); - expect(child.length).toBe(2); - // eslint-disable-next-line no-undef - expect(child[0] instanceof SVGRectElement).toBe(true); - }); - }); + + expect(element.text()).toBe(''); + })); - it('should compile only the template content of an SVG template', function() { - if (!window.SVGRectElement) return; - module(function($compileProvider) { - $compileProvider.directive('test', valueFn({ - templateNamespace: 'svg', - templateUrl: 'my-rect.html', - replace: true - })); - }); - inject(function($compile, $rootScope, $httpBackend) { - $httpBackend.expectGET('my-rect.html').respond(''); - $httpBackend.expectGET('include.svg').respond(''); - element = $compile('')($rootScope); - $httpBackend.flush(); - expect(element.find('a').length).toBe(0); - }); - }); + it('should be async even if served from cache', inject( + putIntoCache('myUrl', 'my partial'), + function($rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.url = 'myUrl'; - it('should not compile template if original scope is destroyed', function() { - module(function($provide) { - $provide.decorator('$compile', function($delegate) { - var result = jasmine.createSpy('$compile').and.callFake($delegate); - result.$$createComment = $delegate.$$createComment; - return result; + var called = 0; + // we want to assert only during first watch + $rootScope.$watch(function() { + if (!called) expect(element.text()).toBe(''); + called++; }); - }); - inject(function($rootScope, $httpBackend, $compile) { - $httpBackend.when('GET', 'url').respond('template text'); - $rootScope.show = true; - element = $compile('
')($rootScope); - $rootScope.$digest(); - $rootScope.show = false; + $rootScope.$digest(); - $compile.calls.reset(); - $httpBackend.flush(); - expect($compile).not.toHaveBeenCalled(); - }); - }); + expect(element.text()).toBe('my partial'); + })); - describe('autoscroll', function() { - var autoScrollSpy; + it('should discard pending xhr callbacks if a new template is requested before the current ' + + 'finished loading', inject(function($rootScope, $compile, $httpBackend) { + element = jqLite('
'); + var log = {}; - function spyOnAnchorScroll() { - return function($provide) { - autoScrollSpy = jasmine.createSpy('$anchorScroll'); - $provide.value('$anchorScroll', autoScrollSpy); + $rootScope.templateUrl = 'myUrl1'; + $rootScope.logger = function(msg) { + log[msg] = true; }; - } + $compile(element)($rootScope); + expect(log).toEqual({}); + + $httpBackend.expect('GET', 'myUrl1').respond('
{{logger("url1")}}
'); + $rootScope.$digest(); + expect(log).toEqual({}); + $rootScope.templateUrl = 'myUrl2'; + $httpBackend.expect('GET', 'myUrl2').respond('
{{logger("url2")}}
'); + $httpBackend.flush(); // now that we have two requests pending, flush! + + expect(log).toEqual({ url2: true }); + })); - function compileAndLink(tpl) { - return function($compile, $rootScope) { - element = $compile(tpl)($rootScope); - }; - } - beforeEach(module(spyOnAnchorScroll(), 'ngAnimateMock')); - beforeEach(inject( - putIntoCache('template.html', 'CONTENT'), - putIntoCache('another.html', 'CONTENT'))); + it('should compile only the content', inject(function($compile, $rootScope, $templateCache) { + // regression - it('should call $anchorScroll if autoscroll attribute is present', inject( - compileAndLink('
'), - function($rootScope, $animate, $timeout) { + var onload = jasmine.createSpy('$includeContentLoaded'); + $rootScope.$on('$includeContentLoaded', onload); + $templateCache.put('tpl.html', [200, 'partial {{tpl}}', {}]); + + element = $compile('
' + + '
')($rootScope); + expect(onload).not.toHaveBeenCalled(); $rootScope.$apply(function() { - $rootScope.tpl = 'template.html'; + $rootScope.tpl = 'tpl.html'; }); + expect(onload).toHaveBeenCalledOnce(); + + $rootScope.tpl = ''; + $rootScope.$digest(); + dealoc(element); + })); - expect(autoScrollSpy).not.toHaveBeenCalled(); - $animate.flush(); + it('should not break attribute bindings on the same element', inject(function($compile, $rootScope, $httpBackend) { + // regression #3793 + + element = $compile('
')($rootScope); + $httpBackend.expect('GET', 'url1').respond('template text 1'); + $rootScope.hrefUrl = 'fooUrl1'; + $rootScope.includeUrl = 'url1'; $rootScope.$digest(); + $httpBackend.flush(); + expect(element.text()).toBe('template text 1'); + expect(element.find('span').attr('foo')).toBe('#/fooUrl1'); - expect($animate.queue.shift().event).toBe('enter'); - expect(autoScrollSpy).toHaveBeenCalledOnce(); + $httpBackend.expect('GET', 'url2').respond('template text 2'); + $rootScope.includeUrl = 'url2'; + $rootScope.$digest(); + $httpBackend.flush(); + expect(element.text()).toBe('template text 2'); + expect(element.find('span').attr('foo')).toBe('#/fooUrl1'); + + $rootScope.hrefUrl = 'fooUrl2'; + $rootScope.$digest(); + expect(element.text()).toBe('template text 2'); + expect(element.find('span').attr('foo')).toBe('#/fooUrl2'); })); - it('should call $anchorScroll if autoscroll evaluates to true', - inject(function($rootScope, $compile, $animate, $timeout) { + it('should exec scripts when jQuery is included', inject(function($compile, $rootScope, $httpBackend) { + if (!jQuery) { + return; + } - element = $compile('
')($rootScope); + element = $compile('
')($rootScope); - $rootScope.$apply(function() { - $rootScope.tpl = 'template.html'; - $rootScope.value = true; + // the element needs to be appended for the script to run + element.appendTo(window.document.body); + window._ngIncludeCausesScriptToRun = false; + $httpBackend.expect('GET', 'url1').respond(''); + $rootScope.includeUrl = 'url1'; + $rootScope.$digest(); + $httpBackend.flush(); + + expect(window._ngIncludeCausesScriptToRun).toBe(true); + + delete window._ngIncludeCausesScriptToRun; + })); + + + it('should construct SVG template elements with correct namespace', function() { + if (!window.SVGRectElement) return; + module(function($compileProvider) { + $compileProvider.directive('test', valueFn({ + templateNamespace: 'svg', + templateUrl: 'my-rect.html', + replace: true + })); + }); + inject(function($compile, $rootScope, $httpBackend) { + $httpBackend.expectGET('my-rect.html').respond(''); + $httpBackend.expectGET('include.svg').respond(''); + element = $compile('')($rootScope); + $httpBackend.flush(); + var child = element.find('rect'); + expect(child.length).toBe(2); + // eslint-disable-next-line no-undef + expect(child[0] instanceof SVGRectElement).toBe(true); }); + }); - expect($animate.queue.shift().event).toBe('enter'); - $rootScope.$apply(function() { - $rootScope.tpl = 'another.html'; - $rootScope.value = 'some-string'; + it('should compile only the template content of an SVG template', function() { + if (!window.SVGRectElement) return; + module(function($compileProvider) { + $compileProvider.directive('test', valueFn({ + templateNamespace: 'svg', + templateUrl: 'my-rect.html', + replace: true + })); }); + inject(function($compile, $rootScope, $httpBackend) { + $httpBackend.expectGET('my-rect.html').respond(''); + $httpBackend.expectGET('include.svg').respond(''); + element = $compile('')($rootScope); + $httpBackend.flush(); + expect(element.find('a').length).toBe(0); + }); + }); - expect($animate.queue.shift().event).toBe('leave'); - expect($animate.queue.shift().event).toBe('enter'); - $rootScope.$apply(function() { - $rootScope.tpl = 'template.html'; - $rootScope.value = 100; + it('should not compile template if original scope is destroyed', function() { + module(function($provide) { + $provide.decorator('$compile', function($delegate) { + var result = jasmine.createSpy('$compile').and.callFake($delegate); + result.$$createComment = $delegate.$$createComment; + return result; + }); }); + inject(function($rootScope, $httpBackend, $compile) { + $httpBackend.when('GET', 'url').respond('template text'); + $rootScope.show = true; + element = $compile('
')($rootScope); + $rootScope.$digest(); + $rootScope.show = false; + $rootScope.$digest(); + $compile.calls.reset(); + $httpBackend.flush(); + expect($compile).not.toHaveBeenCalled(); + }); + }); - expect($animate.queue.shift().event).toBe('leave'); - expect($animate.queue.shift().event).toBe('enter'); - $animate.flush(); - $rootScope.$digest(); + it('should not trigger a digest when the include is changed', function() { - expect(autoScrollSpy).toHaveBeenCalled(); - expect(autoScrollSpy).toHaveBeenCalledTimes(3); - })); + inject(function($$rAF, $templateCache, $rootScope, $compile, $timeout) { + var spy = spyOn($rootScope, '$digest').and.callThrough(); + $templateCache.put('myUrl', 'my template content'); + $templateCache.put('myOtherUrl', 'my other template content'); - it('should not call $anchorScroll if autoscroll attribute is not present', inject( - compileAndLink('
'), - function($rootScope, $animate, $timeout) { + $rootScope.url = 'myUrl'; + element = jqLite('
'); + element = $compile(element)($rootScope); + $rootScope.$digest(); + // The animation completion is async even without actual animations + $$rAF.flush(); + expect(element.text()).toEqual('my template content'); - $rootScope.$apply(function() { - $rootScope.tpl = 'template.html'; + $rootScope.$apply('url = "myOtherUrl"'); + spy.calls.reset(); + expect(element.text()).toEqual('my other template content'); + $$rAF.flush(); + + expect(spy).not.toHaveBeenCalled(); + // A digest may have been triggered asynchronously, so check the queue + $timeout.verifyNoPendingTasks(); }); + }); - expect($animate.queue.shift().event).toBe('enter'); - expect(autoScrollSpy).not.toHaveBeenCalled(); - })); + describe('autoscroll', function() { + var autoScrollSpy; - it('should not call $anchorScroll if autoscroll evaluates to false', - inject(function($rootScope, $compile, $animate, $timeout) { + function spyOnAnchorScroll() { + return function($provide) { + autoScrollSpy = jasmine.createSpy('$anchorScroll'); + $provide.value('$anchorScroll', autoScrollSpy); + }; + } - element = $compile('
')($rootScope); + function compileAndLink(tpl) { + return function($compile, $rootScope) { + element = $compile(tpl)($rootScope); + }; + } - $rootScope.$apply(function() { - $rootScope.tpl = 'template.html'; - $rootScope.value = false; - }); + beforeEach(module(spyOnAnchorScroll(), 'ngAnimateMock')); + beforeEach(inject( + putIntoCache('template.html', 'CONTENT'), + putIntoCache('another.html', 'CONTENT'))); - expect($animate.queue.shift().event).toBe('enter'); + it('should call $anchorScroll if autoscroll attribute is present', inject( + compileAndLink('
'), + function($rootScope, $animate, $timeout) { - $rootScope.$apply(function() { - $rootScope.tpl = 'template.html'; - $rootScope.value = undefined; - }); + $rootScope.$apply(function() { + $rootScope.tpl = 'template.html'; + }); - $rootScope.$apply(function() { - $rootScope.tpl = 'template.html'; - $rootScope.value = null; - }); + expect(autoScrollSpy).not.toHaveBeenCalled(); - expect(autoScrollSpy).not.toHaveBeenCalled(); - })); + $animate.flush(); + $rootScope.$digest(); + + expect($animate.queue.shift().event).toBe('enter'); + expect(autoScrollSpy).toHaveBeenCalledOnce(); + })); - it('should only call $anchorScroll after the "enter" animation completes', inject( - compileAndLink('
'), - function($rootScope, $animate, $timeout) { - expect(autoScrollSpy).not.toHaveBeenCalled(); - $rootScope.$apply('tpl = \'template.html\''); - expect($animate.queue.shift().event).toBe('enter'); + it('should call $anchorScroll if autoscroll evaluates to true', + inject(function($rootScope, $compile, $animate, $timeout) { - $animate.flush(); - $rootScope.$digest(); + element = $compile('
')($rootScope); - expect(autoScrollSpy).toHaveBeenCalledOnce(); - } - )); - }); -}); + $rootScope.$apply(function() { + $rootScope.tpl = 'template.html'; + $rootScope.value = true; + }); -describe('ngInclude and transcludes', function() { - var element, directive; + expect($animate.queue.shift().event).toBe('enter'); - beforeEach(module(function($compileProvider) { - element = null; - directive = $compileProvider.directive; - })); + $rootScope.$apply(function() { + $rootScope.tpl = 'another.html'; + $rootScope.value = 'some-string'; + }); - afterEach(function() { - if (element) { - dealoc(element); - } - }); + expect($animate.queue.shift().event).toBe('leave'); + expect($animate.queue.shift().event).toBe('enter'); + + $rootScope.$apply(function() { + $rootScope.tpl = 'template.html'; + $rootScope.value = 100; + }); + + expect($animate.queue.shift().event).toBe('leave'); + expect($animate.queue.shift().event).toBe('enter'); + + $animate.flush(); + $rootScope.$digest(); + + expect(autoScrollSpy).toHaveBeenCalled(); + expect(autoScrollSpy).toHaveBeenCalledTimes(3); + })); + + + it('should not call $anchorScroll if autoscroll attribute is not present', inject( + compileAndLink('
'), + function($rootScope, $animate, $timeout) { - it('should allow access to directive controller from children when used in a replace template', function() { - var controller; - module(function() { - directive('template', valueFn({ - template: '
', - replace: true, - controller: function() { - this.flag = true; - } + $rootScope.$apply(function() { + $rootScope.tpl = 'template.html'; + }); + + expect($animate.queue.shift().event).toBe('enter'); + expect(autoScrollSpy).not.toHaveBeenCalled(); })); - directive('test', valueFn({ - require: '^template', - link: function(scope, el, attr, ctrl) { - controller = ctrl; - } + + + it('should not call $anchorScroll if autoscroll evaluates to false', + inject(function($rootScope, $compile, $animate, $timeout) { + + element = $compile('
')($rootScope); + + $rootScope.$apply(function() { + $rootScope.tpl = 'template.html'; + $rootScope.value = false; + }); + + expect($animate.queue.shift().event).toBe('enter'); + + $rootScope.$apply(function() { + $rootScope.tpl = 'template.html'; + $rootScope.value = undefined; + }); + + $rootScope.$apply(function() { + $rootScope.tpl = 'template.html'; + $rootScope.value = null; + }); + + expect(autoScrollSpy).not.toHaveBeenCalled(); })); - }); - inject(function($compile, $rootScope, $httpBackend) { - $httpBackend.expectGET('include.html').respond('
'); - element = $compile('
')($rootScope); - $rootScope.$apply(); - $httpBackend.flush(); - expect(controller.flag).toBe(true); - }); - }); - it('should compile its content correctly (although we remove it later)', function() { - var testElement; - module(function() { - directive('test', function() { - return { - link: function(scope, element) { - testElement = element; + it('should only call $anchorScroll after the "enter" animation completes', inject( + compileAndLink('
'), + function($rootScope, $animate, $timeout) { + expect(autoScrollSpy).not.toHaveBeenCalled(); + + $rootScope.$apply('tpl = \'template.html\''); + expect($animate.queue.shift().event).toBe('enter'); + + $animate.flush(); + $rootScope.$digest(); + + expect(autoScrollSpy).toHaveBeenCalledOnce(); } - }; - }); - }); - inject(function($compile, $rootScope, $httpBackend) { - $httpBackend.expectGET('include.html').respond(' '); - element = $compile('
')($rootScope); - $rootScope.$apply(); - $httpBackend.flush(); - expect(testElement[0].nodeName).toBe('DIV'); + )); }); - }); - it('should link directives on the same element after the content has been loaded', function() { - var contentOnLink; - module(function() { - directive('test', function() { - return { - link: function(scope, element) { - contentOnLink = element.text(); + describe('and transcludes', function() { + var element, directive; + + beforeEach(module(function($compileProvider) { + element = null; + directive = $compileProvider.directive; + })); + + afterEach(function() { + if (element) { + dealoc(element); + } + }); + + it('should allow access to directive controller from children when used in a replace template', function() { + var controller; + module(function() { + directive('template', valueFn({ + template: '
', + replace: true, + controller: function() { + this.flag = true; } - }; + })); + directive('test', valueFn({ + require: '^template', + link: function(scope, el, attr, ctrl) { + controller = ctrl; + } + })); + }); + inject(function($compile, $rootScope, $httpBackend) { + $httpBackend.expectGET('include.html').respond('
'); + element = $compile('
')($rootScope); + $rootScope.$apply(); + $httpBackend.flush(); + expect(controller.flag).toBe(true); }); }); - inject(function($compile, $rootScope, $httpBackend) { - $httpBackend.expectGET('include.html').respond('someContent'); - element = $compile('
')($rootScope); - $rootScope.$apply(); - $httpBackend.flush(); - expect(contentOnLink).toBe('someContent'); - }); - }); - it('should add the content to the element before compiling it', function() { - var root; - module(function() { - directive('test', function() { - return { - link: function(scope, element) { - root = element.parent().parent(); - } - }; + it('should compile its content correctly (although we remove it later)', function() { + var testElement; + module(function() { + directive('test', function() { + return { + link: function(scope, element) { + testElement = element; + } + }; + }); + }); + inject(function($compile, $rootScope, $httpBackend) { + $httpBackend.expectGET('include.html').respond(' '); + element = $compile('
')($rootScope); + $rootScope.$apply(); + $httpBackend.flush(); + expect(testElement[0].nodeName).toBe('DIV'); }); + }); - inject(function($compile, $rootScope, $httpBackend) { - $httpBackend.expectGET('include.html').respond(''); - element = $compile('
')($rootScope); - $rootScope.$apply(); - $httpBackend.flush(); - expect(root[0]).toBe(element[0]); + + it('should link directives on the same element after the content has been loaded', function() { + var contentOnLink; + module(function() { + directive('test', function() { + return { + link: function(scope, element) { + contentOnLink = element.text(); + } + }; + }); + }); + inject(function($compile, $rootScope, $httpBackend) { + $httpBackend.expectGET('include.html').respond('someContent'); + element = $compile('
')($rootScope); + $rootScope.$apply(); + $httpBackend.flush(); + expect(contentOnLink).toBe('someContent'); + }); }); - }); -}); -describe('ngInclude animations', function() { - var body, element, $rootElement; - - function html(content) { - $rootElement.html(content); - element = $rootElement.children().eq(0); - return element; - } - - beforeEach(module(function() { - // we need to run animation on attached elements; - return function(_$rootElement_) { - $rootElement = _$rootElement_; - body = jqLite(window.document.body); - body.append($rootElement); - }; - })); - - afterEach(function() { - dealoc(body); - dealoc(element); + it('should add the content to the element before compiling it', function() { + var root; + module(function() { + directive('test', function() { + return { + link: function(scope, element) { + root = element.parent().parent(); + } + }; + }); + }); + inject(function($compile, $rootScope, $httpBackend) { + $httpBackend.expectGET('include.html').respond(''); + element = $compile('
')($rootScope); + $rootScope.$apply(); + $httpBackend.flush(); + expect(root[0]).toBe(element[0]); + }); + }); }); - beforeEach(module('ngAnimateMock')); + describe('and animations', function() { + var body, element, $rootElement; - afterEach(function() { - dealoc(element); - }); + function html(content) { + $rootElement.html(content); + element = $rootElement.children().eq(0); + return element; + } - it('should fire off the enter animation', - inject(function($compile, $rootScope, $templateCache, $animate) { - var item; - - $templateCache.put('enter', [200, '
data
', {}]); - $rootScope.tpl = 'enter'; - element = $compile(html( - '
' + - '
' - ))($rootScope); - $rootScope.$digest(); + beforeEach(module(function() { + // we need to run animation on attached elements; + return function(_$rootElement_) { + $rootElement = _$rootElement_; + body = jqLite(window.document.body); + body.append($rootElement); + }; + })); - var animation = $animate.queue.pop(); - expect(animation.event).toBe('enter'); - expect(animation.element.text()).toBe('data'); - }) - ); - - it('should fire off the leave animation', - inject(function($compile, $rootScope, $templateCache, $animate) { - var item; - $templateCache.put('enter', [200, '
data
', {}]); - $rootScope.tpl = 'enter'; - element = $compile(html( - '
' + - '
' - ))($rootScope); - $rootScope.$digest(); + afterEach(function() { + dealoc(body); + dealoc(element); + }); - var animation = $animate.queue.shift(); - expect(animation.event).toBe('enter'); - expect(animation.element.text()).toBe('data'); + beforeEach(module('ngAnimateMock')); - $rootScope.tpl = ''; - $rootScope.$digest(); + afterEach(function() { + dealoc(element); + }); - animation = $animate.queue.shift(); - expect(animation.event).toBe('leave'); - expect(animation.element.text()).toBe('data'); - }) - ); - - it('should animate two separate ngInclude elements', - inject(function($compile, $rootScope, $templateCache, $animate) { - var item; - $templateCache.put('one', [200, 'one', {}]); - $templateCache.put('two', [200, 'two', {}]); - $rootScope.tpl = 'one'; - element = $compile(html( - '
' + - '
' - ))($rootScope); - $rootScope.$digest(); + it('should fire off the enter animation', + inject(function($compile, $rootScope, $templateCache, $animate) { + var item; + + $templateCache.put('enter', [200, '
data
', {}]); + $rootScope.tpl = 'enter'; + element = $compile(html( + '
' + + '
' + ))($rootScope); + $rootScope.$digest(); + + var animation = $animate.queue.pop(); + expect(animation.event).toBe('enter'); + expect(animation.element.text()).toBe('data'); + }) + ); + + it('should fire off the leave animation', + inject(function($compile, $rootScope, $templateCache, $animate) { + var item; + $templateCache.put('enter', [200, '
data
', {}]); + $rootScope.tpl = 'enter'; + element = $compile(html( + '
' + + '
' + ))($rootScope); + $rootScope.$digest(); + + var animation = $animate.queue.shift(); + expect(animation.event).toBe('enter'); + expect(animation.element.text()).toBe('data'); + + $rootScope.tpl = ''; + $rootScope.$digest(); + + animation = $animate.queue.shift(); + expect(animation.event).toBe('leave'); + expect(animation.element.text()).toBe('data'); + }) + ); + + it('should animate two separate ngInclude elements', + inject(function($compile, $rootScope, $templateCache, $animate) { + var item; + $templateCache.put('one', [200, 'one', {}]); + $templateCache.put('two', [200, 'two', {}]); + $rootScope.tpl = 'one'; + element = $compile(html( + '
' + + '
' + ))($rootScope); + $rootScope.$digest(); + + var item1 = $animate.queue.shift().element; + expect(item1.text()).toBe('one'); + + $rootScope.tpl = 'two'; + $rootScope.$digest(); + + var itemA = $animate.queue.shift().element; + var itemB = $animate.queue.shift().element; + expect(itemA.attr('ng-include')).toBe('tpl'); + expect(itemB.attr('ng-include')).toBe('tpl'); + expect(itemA).not.toEqual(itemB); + }) + ); + + it('should destroy the previous leave animation if a new one takes place', function() { + module(function($provide) { + $provide.decorator('$animate', function($delegate, $$q) { + var emptyPromise = $$q.defer().promise; + emptyPromise.done = noop; + + $delegate.leave = function() { + return emptyPromise; + }; + return $delegate; + }); + }); + inject(function($compile, $rootScope, $animate, $templateCache) { + var item; + var $scope = $rootScope.$new(); + element = $compile(html( + '
' + + '
Yo
' + + '
' + ))($scope); - var item1 = $animate.queue.shift().element; - expect(item1.text()).toBe('one'); + $templateCache.put('one', [200, '
one
', {}]); + $templateCache.put('two', [200, '
two
', {}]); - $rootScope.tpl = 'two'; - $rootScope.$digest(); + $scope.$apply('inc = "one"'); - var itemA = $animate.queue.shift().element; - var itemB = $animate.queue.shift().element; - expect(itemA.attr('ng-include')).toBe('tpl'); - expect(itemB.attr('ng-include')).toBe('tpl'); - expect(itemA).not.toEqual(itemB); - }) - ); - - it('should destroy the previous leave animation if a new one takes place', function() { - module(function($provide) { - $provide.decorator('$animate', function($delegate, $$q) { - var emptyPromise = $$q.defer().promise; - $delegate.leave = function() { - return emptyPromise; - }; - return $delegate; - }); - }); - inject(function($compile, $rootScope, $animate, $templateCache) { - var item; - var $scope = $rootScope.$new(); - element = $compile(html( - '
' + - '
Yo
' + - '
' - ))($scope); - - $templateCache.put('one', [200, '
one
', {}]); - $templateCache.put('two', [200, '
two
', {}]); - - $scope.$apply('inc = "one"'); - - var destroyed, inner = element.children(0); - inner.on('$destroy', function() { - destroyed = true; - }); + var destroyed, inner = element.children(0); + inner.on('$destroy', function() { + destroyed = true; + }); - $scope.$apply('inc = "two"'); + $scope.$apply('inc = "two"'); - $scope.$apply('inc = "one"'); + $scope.$apply('inc = "one"'); - $scope.$apply('inc = "two"'); + $scope.$apply('inc = "two"'); - expect(destroyed).toBe(true); + expect(destroyed).toBe(true); + }); }); }); }); diff --git a/test/ng/directive/ngSwitchSpec.js b/test/ng/directive/ngSwitchSpec.js index 9e22ec8ba83f..fb2bf02db991 100644 --- a/test/ng/directive/ngSwitchSpec.js +++ b/test/ng/directive/ngSwitchSpec.js @@ -54,12 +54,14 @@ describe('ngSwitch', function() { $rootScope.name = 'shyam'; $rootScope.$apply(); expect(element.text()).toEqual('first:shyam, first too:shyam'); + $rootScope.select = 2; $rootScope.$apply(); expect(element.text()).toEqual('second:shyam'); $rootScope.name = 'misko'; $rootScope.$apply(); expect(element.text()).toEqual('second:misko'); + $rootScope.select = true; $rootScope.$apply(); expect(element.text()).toEqual('true:misko'); @@ -301,7 +303,66 @@ describe('ngSwitch', function() { })); + it('should not trigger a digest after an element is removed', inject(function($$rAF, $compile, $rootScope, $timeout) { + var spy = spyOn($rootScope, '$digest').and.callThrough(); + + $rootScope.select = 1; + element = $compile( + '
' + + '
first
' + + '
second
' + + '
')($rootScope); + $rootScope.$apply(); + + expect(element.text()).toEqual('first'); + + $rootScope.select = 2; + $rootScope.$apply(); + spy.calls.reset(); + expect(element.text()).toEqual('second'); + // If ngSwitch re-introduces code that triggers a digest after an element is removed (in an + // animation .then callback), flushing the queue ensures the callback will be called, and the test + // fails + $$rAF.flush(); + + expect(spy).not.toHaveBeenCalled(); + // A digest may have been triggered asynchronously, so check the queue + $timeout.verifyNoPendingTasks(); + })); + + + it('should handle changes to the switch value in a digest loop with multiple value matches', + inject(function($compile, $rootScope) { + var scope = $rootScope.$new(); + scope.value = 'foo'; + + scope.$watch('value', function() { + if (scope.value === 'bar') { + scope.$evalAsync(function() { + scope.value = 'baz'; + }); + } + }); + + element = $compile( + '
' + + '
FOO 1
' + + '
FOO 2
' + + '
BAR
' + + '
BAZ
' + + '
')(scope); + + scope.$apply(); + expect(element.text()).toBe('FOO 1FOO 2'); + + scope.$apply('value = "bar"'); + expect(element.text()).toBe('BAZ'); + }) + ); + + describe('ngSwitchWhen separator', function() { + it('should be possible to define a separator', inject(function($rootScope, $compile) { element = $compile( '
' + @@ -315,9 +376,11 @@ describe('ngSwitch', function() { expect(element.children().length).toBe(2); expect(element.text()).toBe('Block1|Block2|'); $rootScope.$apply('mode = "b"'); + expect(element.children().length).toBe(1); expect(element.text()).toBe('Block1|'); $rootScope.$apply('mode = "c"'); + expect(element.children().length).toBe(1); expect(element.text()).toBe('Block3|'); })); @@ -336,9 +399,11 @@ describe('ngSwitch', function() { expect(element.children().length).toBe(2); expect(element.text()).toBe('Block1|Block2|'); $rootScope.$apply('mode = ""'); + expect(element.children().length).toBe(1); expect(element.text()).toBe('Block1|'); $rootScope.$apply('mode = "c"'); + expect(element.children().length).toBe(1); expect(element.text()).toBe('Block3|'); })); @@ -357,9 +422,11 @@ describe('ngSwitch', function() { expect(element.children().length).toBe(2); expect(element.text()).toBe('Block1|Block2|'); $rootScope.$apply('mode = "b"'); + expect(element.children().length).toBe(1); expect(element.text()).toBe('Block1|'); $rootScope.$apply('mode = "c"'); + expect(element.children().length).toBe(1); expect(element.text()).toBe('Block3|'); })); @@ -378,9 +445,11 @@ describe('ngSwitch', function() { expect(element.children().length).toBe(2); expect(element.text()).toBe('Block1|Block2|'); $rootScope.$apply('mode = "b|a"'); + expect(element.children().length).toBe(1); expect(element.text()).toBe('Block1|'); $rootScope.$apply('mode = "c"'); + expect(element.children().length).toBe(1); expect(element.text()).toBe('Block3|'); })); @@ -399,9 +468,11 @@ describe('ngSwitch', function() { expect(element.children().length).toBe(2); expect(element.text()).toBe('Block1|Block2|'); $rootScope.$apply('mode = "b"'); + expect(element.children().length).toBe(1); expect(element.text()).toBe('Block1|'); $rootScope.$apply('mode = "c"'); + expect(element.children().length).toBe(1); expect(element.text()).toBe('Block3|'); })); diff --git a/test/ngRoute/directive/ngViewSpec.js b/test/ngRoute/directive/ngViewSpec.js index d201ff331dac..0ea9bfea15c2 100644 --- a/test/ngRoute/directive/ngViewSpec.js +++ b/test/ngRoute/directive/ngViewSpec.js @@ -584,6 +584,40 @@ describe('ngView', function() { }); }); }); + + + it('should not trigger a digest when the view is changed', function() { + module(function($routeProvider) { + $routeProvider.when('/foo', {templateUrl: 'myUrl1'}); + $routeProvider.when('/bar', {templateUrl: 'myUrl2'}); + }); + + inject(function($$rAF, $templateCache, $rootScope, $compile, $timeout, $location, $httpBackend) { + var spy = spyOn($rootScope, '$digest').and.callThrough(); + + $templateCache.put('myUrl1', 'my template content'); + $templateCache.put('myUrl2', 'my other template content'); + + $location.path('/foo'); + $rootScope.$digest(); + + // The animation completion is async even without actual animations + $$rAF.flush(); + expect(element.text()).toEqual('my template content'); + + $location.path('/bar'); + $rootScope.$digest(); + spy.calls.reset(); + + $$rAF.flush(); + expect(element.text()).toEqual('my other template content'); + + expect(spy).not.toHaveBeenCalled(); + // A digest may have been triggered asynchronously, so check the queue + $timeout.verifyNoPendingTasks(); + }); + }); + }); describe('and transcludes', function() {