angular.js/test/ng/directive/ngIncludeSpec.js
Michał Gołębiowski 3410f65e79 perf(jqLite): implement and use the empty method in place of html(‘’)
jQuery's elem.html('') is way slower than elem.empty(). As clearing
element contents happens quite often in certain scenarios, switching
to using .empty() provides a significant performance boost when using
Angular with jQuery.

Closes #4457
2013-12-13 02:07:11 -08:00

663 lines
20 KiB
JavaScript

'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('<div><div ng-include="\'url\'"></div></div>')($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('<div><div ng-include="fooUrl"></div></div>')($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('<div><ng:include src="url"></ng:include></div>');
var body = jqLite(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('<div><div ng-include="url"></div></div>');
jqLite(document.body).append(element);
element = $compile(element)($rootScope);
$rootScope.name = 'Alibaba';
$rootScope.url = 'myUrl';
$rootScope.$digest();
expect(element.text()).toEqual('Alibaba');
jqLite(document.body).empty();
}));
it('should NOT use untrusted URL expressions ', inject(putIntoCache('myUrl', '{{name}} text'),
function($rootScope, $compile, $sce) {
element = jqLite('<ng:include src="url"></ng:include>');
jqLite(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. URL: http:\/\/example.com\/myUrl.*/);
jqLite(document.body).empty();
}));
it('should NOT use mistyped expressions ', inject(putIntoCache('myUrl', '{{name}} text'),
function($rootScope, $compile, $sce) {
element = jqLite('<ng:include src="url"></ng:include>');
jqLite(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. URL: http:\/\/example.com\/myUrl.*/);
jqLite(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('<div><ng:include src="url"></ng:include></div>');
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').andCallFake(function (event) {
expect(event.targetScope).toBe($rootScope);
});
$httpBackend.whenGET('url').respond('my partial');
$rootScope.$on('$includeContentRequested', contentRequestedSpy);
element = $compile('<div><div><ng:include src="\'url\'"></ng:include></div></div>')($rootScope);
$rootScope.$digest();
expect(contentRequestedSpy).toHaveBeenCalledOnce();
$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').andCallFake(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('<div><div><ng:include src="\'url\'"></ng:include></div></div>')($rootScope);
$rootScope.$digest();
expect(contentLoadedSpy).toHaveBeenCalledOnce();
}));
it('should evaluate onload expression when a partial is loaded', inject(
putIntoCache('myUrl', 'my partial'),
function($rootScope, $compile) {
element = jqLite('<div><div><ng:include src="url" onload="loaded = true"></ng:include></div></div>');
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('<div><ng:include src="url"></ng:include></div>')($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('<div><ng:include src="url"></ng:include></div>')($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('<div><ng:include src="url">content</ng:include></div>')($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('<div><ng:include src="url"></ng:include></div>')($rootScope);
$rootScope.url = 'myUrl';
var called = 0;
// we want to assert only during first watch
$rootScope.$watch(function() {
if (!called++) expect(element.text()).toBe('');
});
$rootScope.$digest();
expect(element.text()).toBe('my partial');
}));
it('should discard pending xhr callbacks if a new template is requested before the current ' +
'finished loading', inject(function($rootScope, $compile, $httpBackend) {
element = jqLite("<div><ng:include src='templateUrl'></ng:include></div>");
var log = {};
$rootScope.templateUrl = 'myUrl1';
$rootScope.logger = function(msg) {
log[msg] = true;
}
$compile(element)($rootScope);
expect(log).toEqual({});
$httpBackend.expect('GET', 'myUrl1').respond('<div>{{logger("url1")}}</div>');
$rootScope.$digest();
expect(log).toEqual({});
$rootScope.templateUrl = 'myUrl2';
$httpBackend.expect('GET', 'myUrl2').respond('<div>{{logger("url2")}}</div>');
$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('<div><div ng-repeat="i in [1]">' +
'<ng:include src="tpl"></ng:include></div></div>')($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('<div><span foo="#/{{hrefUrl}}" ng:include="includeUrl"></span></div>')($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('<div><span ng-include="includeUrl"></span></div>')($rootScope);
// the element needs to be appended for the script to run
element.appendTo(document.body);
window._ngIncludeCausesScriptToRun = false;
$httpBackend.expect('GET', 'url1').respond('<script>window._ngIncludeCausesScriptToRun = true;</script>');
$rootScope.includeUrl = 'url1';
$rootScope.$digest();
$httpBackend.flush();
expect(window._ngIncludeCausesScriptToRun).toBe(true);
// IE8 doesn't like deleting properties of window
window._ngIncludeCausesScriptToRun = undefined;
try {
delete window._ngIncludeCausesScriptToRun;
} catch (e) {}
}));
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(), 'mock.animate'));
beforeEach(inject(
putIntoCache('template.html', 'CONTENT'),
putIntoCache('another.html', 'CONTENT')));
it('should call $anchorScroll if autoscroll attribute is present', inject(
compileAndLink('<div><ng:include src="tpl" autoscroll></ng:include></div>'),
function($rootScope, $animate, $timeout) {
$rootScope.$apply(function () {
$rootScope.tpl = 'template.html';
});
expect(autoScrollSpy).not.toHaveBeenCalled();
$animate.flushNext('enter');
$timeout.flush();
expect(autoScrollSpy).toHaveBeenCalledOnce();
}));
it('should call $anchorScroll if autoscroll evaluates to true',
inject(function($rootScope, $compile, $animate, $timeout) {
element = $compile('<div><ng:include src="tpl" autoscroll="value"></ng:include></div>')($rootScope);
$rootScope.$apply(function () {
$rootScope.tpl = 'template.html';
$rootScope.value = true;
});
$animate.flushNext('enter');
$timeout.flush();
$rootScope.$apply(function () {
$rootScope.tpl = 'another.html';
$rootScope.value = 'some-string';
});
$animate.flushNext('leave');
$animate.flushNext('enter');
$timeout.flush();
$rootScope.$apply(function() {
$rootScope.tpl = 'template.html';
$rootScope.value = 100;
});
$animate.flushNext('leave');
$animate.flushNext('enter');
$timeout.flush();
expect(autoScrollSpy).toHaveBeenCalled();
expect(autoScrollSpy.callCount).toBe(3);
}));
it('should not call $anchorScroll if autoscroll attribute is not present', inject(
compileAndLink('<div><ng:include src="tpl"></ng:include></div>'),
function($rootScope, $animate, $timeout) {
$rootScope.$apply(function () {
$rootScope.tpl = 'template.html';
});
$animate.flushNext('enter');
$timeout.flush();
expect(autoScrollSpy).not.toHaveBeenCalled();
}));
it('should not call $anchorScroll if autoscroll evaluates to false',
inject(function($rootScope, $compile, $animate, $timeout) {
element = $compile('<div><ng:include src="tpl" autoscroll="value"></ng:include></div>')($rootScope);
$rootScope.$apply(function () {
$rootScope.tpl = 'template.html';
$rootScope.value = false;
});
$animate.flushNext('enter');
$timeout.flush();
$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('<div><ng:include src="tpl" autoscroll></ng:include></div>'),
function($rootScope, $animate, $timeout) {
expect(autoScrollSpy).not.toHaveBeenCalled();
$rootScope.$apply("tpl = 'template.html'");
$animate.flushNext('enter');
$timeout.flush();
expect(autoScrollSpy).toHaveBeenCalledOnce();
}));
});
});
describe('ngInclude 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: '<div ng-include="\'include.html\'"></div>',
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('<div><div test></div></div>');
element = $compile('<div><div template></div></div>')($rootScope);
$rootScope.$apply();
$httpBackend.flush();
expect(controller.flag).toBe(true);
});
});
it("should compile it's 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('<div><div ng-include="\'include.html\'"><div test></div></div></div>')($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('<div><div ng-include="\'include.html\'" test></div>')($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('<span test></span>');
element = $compile('<div><div ng-include="\'include.html\'"></div>')($rootScope);
$rootScope.$apply();
$httpBackend.flush();
expect(root[0]).toBe(element[0]);
});
});
});
describe('ngInclude animations', function() {
var body, element, $rootElement;
function html(html) {
$rootElement.html(html);
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(document.body);
body.append($rootElement);
};
}));
afterEach(function(){
dealoc(body);
dealoc(element);
});
beforeEach(module('mock.animate'));
afterEach(function(){
dealoc(element);
});
it('should fire off the enter animation',
inject(function($compile, $rootScope, $templateCache, $animate) {
var item;
$templateCache.put('enter', [200, '<div>data</div>', {}]);
$rootScope.tpl = 'enter';
element = $compile(html(
'<div><div ' +
'ng-include="tpl">' +
'</div></div>'
))($rootScope);
$rootScope.$digest();
item = $animate.flushNext('enter').element;
expect(item.text()).toBe('data');
}));
it('should fire off the leave animation',
inject(function($compile, $rootScope, $templateCache, $animate) {
var item;
$templateCache.put('enter', [200, '<div>data</div>', {}]);
$rootScope.tpl = 'enter';
element = $compile(html(
'<div><div ' +
'ng-include="tpl">' +
'</div></div>'
))($rootScope);
$rootScope.$digest();
item = $animate.flushNext('enter').element;
expect(item.text()).toBe('data');
$rootScope.tpl = '';
$rootScope.$digest();
item = $animate.flushNext('leave').element;
expect(item.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(
'<div><div ' +
'ng-include="tpl">' +
'</div></div>'
))($rootScope);
$rootScope.$digest();
item = $animate.flushNext('enter').element;
expect(item.text()).toBe('one');
$rootScope.tpl = 'two';
$rootScope.$digest();
var itemA = $animate.flushNext('leave').element;
var itemB = $animate.flushNext('enter').element;
expect(itemA.attr('ng-include')).toBe('tpl');
expect(itemB.attr('ng-include')).toBe('tpl');
expect(itemA).not.toEqual(itemB);
}));
});