'use strict'; describe('ngInclude', function() { describe('basic', 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: https:\/\/fanyv88.com:443\/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: https:\/\/fanyv88.com:443\/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(''); })); 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); }); $httpBackend.whenGET('url').respond('my partial'); $rootScope.$on('$includeContentRequested', contentRequestedSpy); element = $compile('
')($rootScope); $rootScope.$digest(); expect(contentRequestedSpy).toHaveBeenCalledOnceWith(jasmine.any(Object), 'url'); $httpBackend.flush(); })); 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); element = $compile('
')($rootScope); $rootScope.$digest(); expect(contentLoadedSpy).toHaveBeenCalledOnceWith(jasmine.any(Object), 'url'); })); 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.$on('$includeContentLoaded', contentLoadedSpy); $rootScope.$on('$includeContentError', contentErrorSpy); $httpBackend.expect('GET', 'tpl.html').respond(400, 'nope'); element = $compile('
')($rootScope); $rootScope.$apply(function() { $rootScope.template = 'tpl.html'; }); $httpBackend.flush(); expect(contentLoadedSpy).not.toHaveBeenCalled(); expect(contentErrorSpy).toHaveBeenCalledOnceWith(jasmine.any(Object), 'tpl.html'); expect(element.children('div').contents().length).toBe(0); })); 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(); $rootScope.url = 'myUrl'; $rootScope.$digest(); expect(element.text()).toEqual('my partial'); expect($rootScope.loaded).toBe(true); })); 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); element = $compile('
')($rootScope); expect(element.children().scope()).toBeFalsy(); $rootScope.url = 'url1'; $rootScope.$digest(); $httpBackend.flush(); expect(element.children().scope().$parent).toBe($rootScope); expect(element.text()).toBe('partial url1'); $rootScope.url = 'url2'; $rootScope.$digest(); $httpBackend.flush(); expect($rootScope.$$childHead).toBeFalsy(); expect(element.text()).toBe(''); $rootScope.url = 'url1'; $rootScope.$digest(); expect(element.children().scope().$parent).toBe($rootScope); $rootScope.url = null; $rootScope.$digest(); expect($rootScope.$$childHead).toBeFalsy(); })); it('should do xhr request and cache it', inject(function($rootScope, $httpBackend, $compile) { element = $compile('
')($rootScope); $httpBackend.expect('GET', 'myUrl').respond('my partial'); $rootScope.url = 'myUrl'; $rootScope.$digest(); $httpBackend.flush(); expect(element.text()).toEqual('my partial'); $rootScope.url = null; $rootScope.$digest(); expect(element.text()).toEqual(''); $rootScope.url = 'myUrl'; $rootScope.$digest(); expect(element.text()).toEqual('my partial'); dealoc($rootScope); })); it('should clear content when error during xhr request', inject(function($httpBackend, $compile, $rootScope) { element = $compile('
content
')($rootScope); $httpBackend.expect('GET', 'myUrl').respond(404, ''); $rootScope.url = 'myUrl'; $rootScope.$digest(); $httpBackend.flush(); expect(element.text()).toBe(''); })); it('should be async even if served from cache', inject( putIntoCache('myUrl', 'my partial'), function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.url = 'myUrl'; var called = 0; // we want to assert only during first watch $rootScope.$watch(function() { if (!called) expect(element.text()).toBe(''); called++; }); $rootScope.$digest(); expect(element.text()).toBe('my partial'); })); 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 = {}; $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 }); })); it('should compile only the content', inject(function($compile, $rootScope, $templateCache) { // regression 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 = '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; } element = $compile('
')($rootScope); // 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); }); }); 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 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(); }); }); it('should not trigger a digest when the include is changed', function() { 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'); $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('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(); }); }); describe('autoscroll', function() { var autoScrollSpy; function spyOnAnchorScroll() { return function($provide) { autoScrollSpy = jasmine.createSpy('$anchorScroll'); $provide.value('$anchorScroll', autoScrollSpy); }; } 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 call $anchorScroll if autoscroll attribute is present', inject( compileAndLink('
'), function($rootScope, $animate, $timeout) { $rootScope.$apply(function() { $rootScope.tpl = 'template.html'; }); expect(autoScrollSpy).not.toHaveBeenCalled(); $animate.flush(); $rootScope.$digest(); expect($animate.queue.shift().event).toBe('enter'); expect(autoScrollSpy).toHaveBeenCalledOnce(); })); it('should call $anchorScroll if autoscroll evaluates to true', inject(function($rootScope, $compile, $animate, $timeout) { element = $compile('
')($rootScope); $rootScope.$apply(function() { $rootScope.tpl = 'template.html'; $rootScope.value = true; }); expect($animate.queue.shift().event).toBe('enter'); $rootScope.$apply(function() { $rootScope.tpl = 'another.html'; $rootScope.value = 'some-string'; }); 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) { $rootScope.$apply(function() { $rootScope.tpl = 'template.html'; }); expect($animate.queue.shift().event).toBe('enter'); expect(autoScrollSpy).not.toHaveBeenCalled(); })); 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(); })); 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(); } )); }); }); 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); }); }); 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'); }); }); 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'); }); }); 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]); }); }); }); describe('and 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); }); beforeEach(module('ngAnimateMock')); afterEach(function() { dealoc(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(); 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); $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; }); $scope.$apply('inc = "two"'); $scope.$apply('inc = "one"'); $scope.$apply('inc = "two"'); expect(destroyed).toBe(true); }); }); }); });