From b6b266dc08b15542046784df3fca9c0ffbc6a886 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Fri, 15 Aug 2014 00:27:26 -0700 Subject: [PATCH 1/5] WIP --- src/ng/compile.js | 111 +- src/ng/directive/ngTransclude.js | 4 +- test/ng/compileSpec.js | 6013 +----------------------------- 3 files changed, 153 insertions(+), 5975 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index 175efc13211e..e8312d3c0f64 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1,5 +1,7 @@ 'use strict'; +window.dump = function() {}; + /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! * * DOM-related variables: @@ -545,6 +547,35 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // 'on' and be composed of only English letters. var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; + var svgWrap = function(template) { + var wrapper = document.createElement('div'); + wrapper.innerHTML = ''+template+''; + return wrapper.childNodes[0].childNodes; + }; + + var SVG_NAMESPACE = { + type: 'svg', + wrap: svgWrap, + clone: svgWrap, + cloneJqLite: function(elements) { + var nodes = []; + forEach(elements, function(element) { + nodes.push(svgWrap(element.outerHTML)[0]); + }); + return jqLite(nodes); + } + }; + + var HTML_NAMESPACE = { + type: 'html', + wrap: identity, + clone: jqLiteClone, + cloneJqLite: function(elements) { + return JQLitePrototype.clone.call(elements); + } + }; + + /** * @ngdoc method * @name $compileProvider#directive @@ -852,12 +883,15 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { //================================ function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, - previousCompileContext) { + previousCompileContext, namespace) { if (!($compileNodes instanceof jqLite)) { // jquery always rewraps, whereas we need to preserve the original selector so that we can // modify it. $compileNodes = jqLite($compileNodes); } + + $compileNodes.namespace = namespace || HTML_NAMESPACE; + // We can not compile top level text elements since text nodes can be merged and we will // not be able to attach scope data to them, so we will wrap them in forEach($compileNodes, function(node, index){ @@ -867,16 +901,21 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { }); var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, - maxPriority, ignoreDirective, previousCompileContext); + maxPriority, ignoreDirective, previousCompileContext, namespace); safeAddClass($compileNodes, 'ng-scope'); return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn){ assertArg(scope, 'scope'); // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart // and sometimes changes the structure of the DOM. + //BAD? + + dump($compileNodes.namespace, $compileNodes) var $linkNode = cloneConnectFn - ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! + ? $compileNodes.namespace.cloneJqLite($compileNodes) // IMPORTANT!!! : $compileNodes; + $linkNode.namespace = $compileNodes.namespace; + forEach(transcludeControllers, function(instance, name) { $linkNode.data('$' + name + 'Controller', instance); }); @@ -914,28 +953,41 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * @returns {Function} A composite linking function of all of the matched directives or null. */ function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, - previousCompileContext) { + previousCompileContext, namespace) { var linkFns = [], attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound; for (var i = 0; i < nodeList.length; i++) { attrs = new Attributes(); + var node = nodeList[i]; + namespace = namespace || HTML_NAMESPACE; + + if(node.tagName === 'svg') { + namespace = SVG_NAMESPACE; + } + + nodeList.namespace = namespace; + // we must always refer to nodeList[i] since the nodes can be replaced underneath us. - directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, + directives = collectDirectives(node, [], attrs, i === 0 ? maxPriority : undefined, ignoreDirective); nodeLinkFn = (directives.length) - ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, - null, [], [], previousCompileContext) + ? applyDirectivesToNode(directives, node, attrs, transcludeFn, $rootElement, + null, [], [], previousCompileContext, namespace) : null; + if (nodeLinkFn) { + nodeLinkFn.namespace = nodeLinkFn.namespace || namespace; + } + if (nodeLinkFn && nodeLinkFn.scope) { safeAddClass(attrs.$$element, 'ng-scope'); } childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || - !(childNodes = nodeList[i].childNodes) || + !(childNodes = node.childNodes) || !childNodes.length) ? null : compileNodes(childNodes, @@ -999,7 +1051,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { - var boundTranscludeFn = function(transcludedScope, cloneFn, controllers) { + var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, namespace) { var scopeCreated = false; if (!transcludedScope) { @@ -1008,7 +1060,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { scopeCreated = true; } - var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn); + var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn, namespace); if (scopeCreated) { clone.on('$destroy', function() { transcludedScope.$destroy(); }); } @@ -1189,7 +1241,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { */ function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, - previousCompileContext) { + previousCompileContext, namespace) { previousCompileContext = previousCompileContext || {}; var terminalPriority = -Number.MAX_VALUE, @@ -1276,6 +1328,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { $compileNode = templateAttrs.$$element = jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' ')); + $compileNode.namespace = namespace; compileNode = $compileNode[0]; replaceWith(jqCollection, sliceArgs($template), compileNode); @@ -1291,9 +1344,23 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { nonTlbTranscludeDirective: nonTlbTranscludeDirective }); } else { - $template = jqLite(jqLiteClone(compileNode)).contents(); - $compileNode.empty(); // clear contents - childTranscludeFn = compile($template, transcludeFn); + //BAD? + //$template = jqLite(namespace.clone(compileNode)).contents(); + //$compileNode.empty(); // clear contents + $template = $compileNode.contents(); + $template.remove(); + childTranscludeFn = (function($template) { + return function childTranscludeFnFactory() { + dump('x', arguments[4]) + var childTranscludeFn = childTranscludeFnFactory.fn; + + if (!childTranscludeFn) { + childTranscludeFn = childTranscludeFnFactory.fn = compile($template, transcludeFn, null, null, null, arguments[4]); + } + + return childTranscludeFn.apply(undefined, arguments); + } + }($template)); } } @@ -1313,7 +1380,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (jqLiteIsTextNode(directiveValue)) { $template = []; } else { - $template = jqLite(wrapTemplate(directive.type, trim(directiveValue))); + //BAD? + $template = jqLite(namespace.wrap(trim(directiveValue))); } compileNode = $template[0]; @@ -1325,6 +1393,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { replaceWith(jqCollection, $compileNode, compileNode); + + if ($compileNode[0].tagName === 'svg') { + nodeLinkFn.namespace = $compileNode.namespace = SVG_NAMESPACE; + } + + var newTemplateAttrs = {$attr: {}}; // combine directives from the original node and from the template: @@ -1604,6 +1678,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { for(i = postLinkFns.length - 1; i >= 0; i--) { try { linkFn = postLinkFns[i]; + dump(transcludeFn, nodeLinkFn.namespace); + $element.namespace = nodeLinkFn.namespace; linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); } catch (e) { @@ -1612,7 +1688,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } // This is the function that is injected as `$transclude`. - function controllersBoundTransclude(scope, cloneAttachFn) { + function controllersBoundTransclude(scope, cloneAttachFn, namespace) { var transcludeControllers; // no scope passed @@ -1625,7 +1701,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { transcludeControllers = elementControllers; } - return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers); + return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, namespace); } } } @@ -1815,6 +1891,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { if (!(previousCompileContext.hasElementTranscludeDirective && origAsyncDirective.replace)) { // it was cloned therefore we have to clone as well. + //BAD linkNode = jqLiteClone(compileNode); } diff --git a/src/ng/directive/ngTransclude.js b/src/ng/directive/ngTransclude.js index 9f0aa58c5e0b..e8746f72fd5c 100644 --- a/src/ng/directive/ngTransclude.js +++ b/src/ng/directive/ngTransclude.js @@ -65,9 +65,9 @@ var ngTranscludeDirective = ngDirective({ startingTag($element)); } - $transclude(function(clone) { + $transclude(null, function(clone) { $element.empty(); $element.append(clone); - }); + }, $element.namespace); } }); diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 83e899345607..c9ceece3bdae 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -8,7 +8,7 @@ function calcCacheSize() { } -describe('$compile', function() { +ddescribe('$compile', function() { var element, directive, $compile, $rootScope; beforeEach(module(provideLog, function($provide, $compileProvider){ @@ -63,12 +63,29 @@ describe('$compile', function() { terminal: true })); - return function(_$compile_, _$rootScope_) { - $rootScope = _$rootScope_; - $compile = _$compile_; - }; + directive('svgContainer', function() { + return { + template: '', + replace: true, + transclude: true, + }; + }); + + directive('svgCircle', function(){ + return { + template: '', + replace: true, + }; + }); + })); + + beforeEach(inject(function(_$compile_, _$rootScope_) { + + $rootScope = _$rootScope_; + $compile = _$compile_; })); + function compile(html) { element = angular.element(html); $compile(element)($rootScope); @@ -78,5966 +95,50 @@ describe('$compile', function() { dealoc(element); }); + // this method assumes some sort of sized SVG element is being inspected. + function isInvalidSvgElement(elem) { + var unknownElement = Object.prototype.toString.call(elem) === '[object HTMLUnknownElement]'; + expect(unknownElement).toBe(false); + var box = elem.getBoundingClientRect(); + return unknownElement || (box.width === 0 && box.height === 0); + } - describe('configuration', function() { - it('should register a directive', function() { - module(function() { - directive('div', function(log) { - return { - restrict: 'ECA', - link: function(scope, element) { - log('OK'); - element.text('SUCCESS'); - } - }; - }); - }); - inject(function($compile, $rootScope, log) { - element = $compile('
')($rootScope); - expect(element.text()).toEqual('SUCCESS'); - expect(log).toEqual('OK'); - }); - }); - - it('should allow registration of multiple directives with same name', function() { - module(function() { - directive('div', function(log) { - return { - restrict: 'ECA', - link: { - pre: log.fn('pre1'), - post: log.fn('post1') - } - }; - }); - directive('div', function(log) { - return { - restrict: 'ECA', - link: { - pre: log.fn('pre2'), - post: log.fn('post2') - } - }; - }); - }); - inject(function($compile, $rootScope, log) { - element = $compile('
')($rootScope); - expect(log).toEqual('pre1; pre2; post2; post1'); - }); - }); - - it('should throw an exception if a directive is called "hasOwnProperty"', function() { - module(function() { - expect(function() { - directive('hasOwnProperty', function() { }); - }).toThrowMinErr('ng','badname', "hasOwnProperty is not a valid directive name"); - }); - inject(function($compile) {}); - }); - }); - - - describe('compile phase', function() { - - it('should attach scope to the document node when it is compiled explicitly', inject(function($document){ - $compile($document)($rootScope); - expect($document.scope()).toBe($rootScope); - })); - - it('should wrap root text nodes in spans', inject(function($compile, $rootScope) { - element = jqLite('
A<a>B</a>C
'); - var text = element.contents(); - expect(text[0].nodeName).toEqual('#text'); - text = $compile(text)($rootScope); - expect(text[0].nodeName).toEqual('SPAN'); - expect(element.find('span').text()).toEqual('ABC'); - })); - - - it('should not wrap root whitespace text nodes in spans', function() { - element = jqLite( - '
A
\n '+ // The spaces and newlines here should not get wrapped - '
B
C\t\n '+ // The "C", tabs and spaces here will be wrapped - '
'); - $compile(element.contents())($rootScope); - var spans = element.find('span'); - expect(spans.length).toEqual(1); - expect(spans.text().indexOf('C')).toEqual(0); - }); - - it('should not leak memory when there are top level empty text nodes', function() { - // We compile the contents of element (i.e. not element itself) - // Then delete these contents and check the cache has been reset to zero - - // First with only elements at the top level - element = jqLite('
'); - $compile(element.contents())($rootScope); - element.empty(); - expect(calcCacheSize()).toEqual(0); - - // Next with non-empty text nodes at the top level - // (in this case the compiler will wrap them in a ) - element = jqLite('
xxx
'); - $compile(element.contents())($rootScope); - element.empty(); - expect(calcCacheSize()).toEqual(0); - - // Next with comment nodes at the top level - element = jqLite('
'); - $compile(element.contents())($rootScope); - element.empty(); - expect(calcCacheSize()).toEqual(0); - - // Finally with empty text nodes at the top level - element = jqLite('
\n
'); + describe('svg namespace', function() { + it('should handle transcluded svg elements', inject(function($compile){ + element = jqLite('
' + + '' + + '
'); $compile(element.contents())($rootScope); - element.empty(); - expect(calcCacheSize()).toEqual(0); - }); - - - it('should not blow up when elements with no childNodes property are compiled', inject( - function($compile, $rootScope) { - // it turns out that when a browser plugin is bound to an DOM element (typically ), - // the plugin's context rather than the usual DOM apis are exposed on this element, so - // childNodes might not exist. - if (msie < 9) return; - - element = jqLite('
{{1+2}}
'); - - try { - element[0].childNodes[1] = {nodeType: 3, nodeName: 'OBJECT', textContent: 'fake node'}; - } catch(e) { - } finally { - if (!element[0].childNodes[1]) return; //browser doesn't support this kind of mocking - } - expect(element[0].childNodes[1].textContent).toBe('fake node'); - - $compile(element)($rootScope); - $rootScope.$apply(); - - // object's children can't be compiled in this case, so we expect them to be raw - expect(element.html()).toBe("3"); + document.body.appendChild(element[0]); + + var circle = element.find('circle'); + //dump(element) + dump(circle[0]) + expect(isInvalidSvgElement(circle[0])).toBe(false); })); + iit('should handle custom svg elements inside svg tag', function(){ + element = jqLite('
' + + '' + + '
'); + $compile(element.contents())($rootScope); + document.body.appendChild(element[0]); - describe('multiple directives per element', function() { - it('should allow multiple directives per element', inject(function($compile, $rootScope, log){ - element = $compile( - '') - ($rootScope); - expect(element.text()).toEqual('Hello angular'); - expect(log).toEqual('L; M; H'); - })); - - - it('should recurse to children', inject(function($compile, $rootScope){ - element = $compile('
01234
')($rootScope); - expect(element.text()).toEqual('0hello2angular4'); - })); - - - it('should allow directives in classes', inject(function($compile, $rootScope, log) { - element = $compile('
')($rootScope); - expect(element.html()).toEqual('Hello angular'); - expect(log).toEqual('123'); - })); - - - it('should ignore not set CSS classes on SVG elements', inject(function($compile, $rootScope, log) { - if (!window.SVGElement) return; - // According to spec SVG element className property is readonly, but only FF - // implements it this way which causes compile exceptions. - element = $compile('{{1}}')($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('1'); - })); - - - it('should receive scope, element, and attributes', function() { - var injector; - module(function() { - directive('log', function($injector, $rootScope) { - injector = $injector; - return { - restrict: 'CA', - compile: function(element, templateAttr) { - expect(typeof templateAttr.$normalize).toBe('function'); - expect(typeof templateAttr.$set).toBe('function'); - expect(isElement(templateAttr.$$element)).toBeTruthy(); - expect(element.text()).toEqual('unlinked'); - expect(templateAttr.exp).toEqual('abc'); - expect(templateAttr.aa).toEqual('A'); - expect(templateAttr.bb).toEqual('B'); - expect(templateAttr.cc).toEqual('C'); - return function(scope, element, attr) { - expect(element.text()).toEqual('unlinked'); - expect(attr).toBe(templateAttr); - expect(scope).toEqual($rootScope); - element.text('worked'); - }; - } - }; - }); - }); - inject(function($rootScope, $compile, $injector) { - element = $compile( - '
unlinked
')($rootScope); - expect(element.text()).toEqual('worked'); - expect(injector).toBe($injector); // verify that directive is injectable - }); - }); + var circle = element.find('circle'); + expect(isInvalidSvgElement(circle[0])).toBe(false); }); - describe('error handling', function() { - - it('should handle exceptions', function() { - module(function($exceptionHandlerProvider) { - $exceptionHandlerProvider.mode('log'); - directive('factoryError', function() { throw 'FactoryError'; }); - directive('templateError', - valueFn({ compile: function() { throw 'TemplateError'; } })); - directive('linkingError', - valueFn(function() { throw 'LinkingError'; })); - }); - inject(function($rootScope, $compile, $exceptionHandler) { - element = $compile('
')($rootScope); - expect($exceptionHandler.errors[0]).toEqual('FactoryError'); - expect($exceptionHandler.errors[1][0]).toEqual('TemplateError'); - expect(ie($exceptionHandler.errors[1][1])). - toEqual('
'); - expect($exceptionHandler.errors[2][0]).toEqual('LinkingError'); - expect(ie($exceptionHandler.errors[2][1])). - toEqual('
'); - - - // crazy stuff to make IE happy - function ie(text) { - var list = [], - parts, elementName; - - parts = lowercase(text). - replace('<', ''). - replace('>', ''). - split(' '); - elementName = parts.shift(); - parts.sort(); - parts.unshift(elementName); - forEach(parts, function(value){ - if (value.substring(0,2) !== 'ng') { - value = value.replace('=""', ''); - var match = value.match(/=(.*)/); - if (match && match[1].charAt(0) != '"') { - value = value.replace(/=(.*)/, '="$1"'); - } - list.push(value); - } - }); - return '<' + list.join(' ') + '>'; - } - }); - }); - - - it('should allow changing the template structure after the current node', function() { - module(function(){ - directive('after', valueFn({ - compile: function(element) { - element.after('B'); - } - })); - }); - inject(function($compile, $rootScope, log){ - element = jqLite("
A
"); - $compile(element)($rootScope); - expect(element.text()).toBe('AB'); - expect(log).toEqual('LOG'); - }); - }); - - - it('should allow changing the template structure after the current node inside ngRepeat', function() { - module(function(){ - directive('after', valueFn({ - compile: function(element) { - element.after('B'); - } - })); - }); - inject(function($compile, $rootScope, log){ - element = jqLite('
A
'); - $compile(element)($rootScope); - $rootScope.$digest(); - expect(element.text()).toBe('ABAB'); - expect(log).toEqual('LOG; LOG'); - }); - }); - + it('should handle transcluded custom svg elements', function(){ + element = jqLite('' + + '' + + ''); + $compile(element.contents())($rootScope); + document.body.appendChild(element[0]); - it('should allow modifying the DOM structure in post link fn', function() { - module(function() { - directive('removeNode', valueFn({ - link: function($scope, $element) { - $element.remove(); - } - })); - }); - inject(function($compile, $rootScope) { - element = jqLite('
{{test}}
'); - $rootScope.test = 'Hello'; - $compile(element)($rootScope); - $rootScope.$digest(); - expect(element.children().length).toBe(1); - expect(element.text()).toBe('Hello'); - }); - }); + var circle = element.find('circle'); + expect(isInvalidSvgElement(circle[0])).toBe(false); }); + }); - describe('compiler control', function() { - describe('priority', function() { - it('should honor priority', inject(function($compile, $rootScope, log){ - element = $compile( - '') - ($rootScope); - expect(log).toEqual('L; M; H'); - })); - }); - - - describe('terminal', function() { - - it('should prevent further directives from running', inject(function($rootScope, $compile) { - element = $compile('')($rootScope); - expect(element.text()).toEqual('OK'); - } - )); - - - it('should prevent further directives from running, but finish current priority level', - inject(function($rootScope, $compile, log) { - // class is processed after attrs, so putting log in class will put it after - // the stop in the current level. This proves that the log runs after stop - element = $compile( - '')($rootScope); - expect(element.text()).toEqual('OK'); - expect(log.toArray().sort()).toEqual(['HIGH', 'MEDIUM']); - }) - ); - }); - - - describe('restrict', function() { - - it('should allow restriction of availability', function () { - module(function () { - forEach({div: 'E', attr: 'A', clazz: 'C', comment: 'M', all: 'EACM'}, - function (restrict, name) { - directive(name, function (log) { - return { - restrict: restrict, - compile: valueFn(function (scope, element, attr) { - log(name); - }) - }; - }); - }); - }); - inject(function ($rootScope, $compile, log) { - dealoc($compile('')($rootScope)); - expect(log).toEqual(''); - log.reset(); - - dealoc($compile('
')($rootScope)); - expect(log).toEqual('div'); - log.reset(); - - dealoc($compile('')($rootScope)); - expect(log).toEqual(''); - log.reset(); - - dealoc($compile('')($rootScope)); - expect(log).toEqual('attr'); - log.reset(); - - dealoc($compile('')($rootScope)); - expect(log).toEqual(''); - log.reset(); - - dealoc($compile('')($rootScope)); - expect(log).toEqual('clazz'); - log.reset(); - - dealoc($compile('')($rootScope)); - expect(log).toEqual('comment'); - log.reset(); - - dealoc($compile('')($rootScope)); - expect(log).toEqual('all; all; all; all'); - }); - }); - - - it('should use EA rule as the default', function () { - module(function () { - directive('defaultDir', function (log) { - return { - compile: function () { - log('defaultDir'); - } - }; - }); - }); - inject(function ($rootScope, $compile, log) { - dealoc($compile('')($rootScope)); - expect(log).toEqual('defaultDir'); - log.reset(); - - dealoc($compile('')($rootScope)); - expect(log).toEqual('defaultDir'); - log.reset(); - - dealoc($compile('')($rootScope)); - expect(log).toEqual(''); - log.reset(); - }); - }); - }); - - - describe('template', function() { - - beforeEach(module(function() { - directive('replace', valueFn({ - restrict: 'CAM', - replace: true, - template: '
Replace!
', - compile: function(element, attr) { - attr.$set('compiled', 'COMPILED'); - expect(element).toBe(attr.$$element); - } - })); - directive('nomerge', valueFn({ - restrict: 'CAM', - replace: true, - template: '
No Merge!
', - compile: function(element, attr) { - attr.$set('compiled', 'COMPILED'); - expect(element).toBe(attr.$$element); - } - })); - directive('append', valueFn({ - restrict: 'CAM', - template: '
Append!
', - compile: function(element, attr) { - attr.$set('compiled', 'COMPILED'); - expect(element).toBe(attr.$$element); - } - })); - directive('replaceWithInterpolatedClass', valueFn({ - replace: true, - template: '
Replace with interpolated class!
', - compile: function(element, attr) { - attr.$set('compiled', 'COMPILED'); - expect(element).toBe(attr.$$element); - } - })); - directive('replaceWithInterpolatedStyle', valueFn({ - replace: true, - template: '
Replace with interpolated style!
', - compile: function(element, attr) { - attr.$set('compiled', 'COMPILED'); - expect(element).toBe(attr.$$element); - } - })); - directive('replaceWithTr', valueFn({ - replace: true, - template: 'TR' - })); - directive('replaceWithTd', valueFn({ - replace: true, - template: 'TD' - })); - directive('replaceWithTh', valueFn({ - replace: true, - template: 'TH' - })); - directive('replaceWithThead', valueFn({ - replace: true, - template: 'TD' - })); - directive('replaceWithTbody', valueFn({ - replace: true, - template: 'TD' - })); - directive('replaceWithTfoot', valueFn({ - replace: true, - template: 'TD' - })); - directive('replaceWithOption', valueFn({ - replace: true, - template: '' - })); - directive('replaceWithOptgroup', valueFn({ - replace: true, - template: 'OPTGROUP' - })); - })); - - - it('should replace element with template', inject(function($compile, $rootScope) { - element = $compile('
ignore
')($rootScope); - expect(element.text()).toEqual('Replace!'); - expect(element.find('div').attr('compiled')).toEqual('COMPILED'); - })); - - - it('should append element with template', inject(function($compile, $rootScope) { - element = $compile('
ignore
')($rootScope); - expect(element.text()).toEqual('Append!'); - expect(element.find('div').attr('compiled')).toEqual('COMPILED'); - })); - - - it('should compile template when replacing', inject(function($compile, $rootScope, log) { - element = $compile('
ignore
') - ($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('Replace!'); - expect(log).toEqual('LOG; HIGH; MEDIUM'); - })); - - - it('should compile template when appending', inject(function($compile, $rootScope, log) { - element = $compile('
ignore
') - ($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('Append!'); - expect(log).toEqual('LOG; HIGH; MEDIUM'); - })); - - - it('should merge attributes including style attr', inject(function($compile, $rootScope) { - element = $compile( - '
') - ($rootScope); - var div = element.find('div'); - expect(div.hasClass('medium-log')).toBe(true); - expect(div.hasClass('log')).toBe(true); - expect(div.css('width')).toBe('10px'); - expect(div.css('height')).toBe('20px'); - expect(div.attr('replace')).toEqual(''); - expect(div.attr('high-log')).toEqual(''); - })); - - it('should not merge attributes if they are the same', inject(function($compile, $rootScope) { - element = $compile( - '
') - ($rootScope); - var div = element.find('div'); - expect(div.hasClass('medium-log')).toBe(true); - expect(div.hasClass('log')).toBe(true); - expect(div.attr('id')).toEqual('myid'); - })); - - it('should prevent multiple templates per element', inject(function($compile) { - try { - $compile('
'); - this.fail(new Error('should have thrown Multiple directives error')); - } catch(e) { - expect(e.message).toMatch(/Multiple directives .* asking for template/); - } - })); - - it('should play nice with repeater when replacing', inject(function($compile, $rootScope) { - element = $compile( - '
' + - '
' + - '
')($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('Replace!Replace!'); - })); - - - it('should play nice with repeater when appending', inject(function($compile, $rootScope) { - element = $compile( - '
' + - '
' + - '
')($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('Append!Append!'); - })); - - - it('should handle interpolated css class from replacing directive', inject( - function($compile, $rootScope) { - element = $compile('
')($rootScope); - $rootScope.$digest(); - expect(element).toHaveClass('class_2'); - })); - - if (!msie || msie > 11) { - // style interpolation not working on IE (including IE11). - it('should handle interpolated css style from replacing directive', inject( - function($compile, $rootScope) { - element = $compile('
')($rootScope); - $rootScope.$digest(); - expect(element.css('width')).toBe('2px'); - } - )); - } - - it('should merge interpolated css class', inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); - - $rootScope.$apply(function() { - $rootScope.cls = 'two'; - }); - - expect(element).toHaveClass('one'); - expect(element).toHaveClass('two'); // interpolated - expect(element).toHaveClass('three'); - expect(element).toHaveClass('log'); // merged from replace directive template - })); - - - it('should merge interpolated css class with ngRepeat', - inject(function($compile, $rootScope) { - element = $compile( - '
' + - '
' + - '
')($rootScope); - - $rootScope.$apply(function() { - $rootScope.cls = 'two'; - }); - - var child = element.find('div').eq(0); - expect(child).toHaveClass('one'); - expect(child).toHaveClass('two'); // interpolated - expect(child).toHaveClass('three'); - expect(child).toHaveClass('log'); // merged from replace directive template - })); - - it("should fail if replacing and template doesn't have a single root element", function() { - module(function() { - directive('noRootElem', function() { - return { - replace: true, - template: 'dada' - }; - }); - directive('multiRootElem', function() { - return { - replace: true, - template: '
' - }; - }); - directive('singleRootWithWhiteSpace', function() { - return { - replace: true, - template: '
\n' - }; - }); - }); - - inject(function($compile) { - expect(function() { - $compile('

'); - }).toThrowMinErr("$compile", "tplrt", "Template for directive 'noRootElem' must have exactly one root element. "); - - expect(function() { - $compile('

'); - }).toThrowMinErr("$compile", "tplrt", "Template for directive 'multiRootElem' must have exactly one root element. "); - - // ws is ok - expect(function() { - $compile('

'); - }).not.toThrow(); - }); - }); - - it('should support templates with root tags', inject(function($compile, $rootScope) { - expect(function() { - element = $compile('
')($rootScope); - }).not.toThrow(); - expect(nodeName_(element)).toMatch(/tr/i); - })); - - it('should support templates with root tags', inject(function($compile, $rootScope) { - expect(function() { - element = $compile('
')($rootScope); - }).not.toThrow(); - expect(nodeName_(element)).toMatch(/td/i); - })); - - it('should support templates with root tags', inject(function($compile, $rootScope) { - expect(function() { - element = $compile('
')($rootScope); - }).not.toThrow(); - expect(nodeName_(element)).toMatch(/th/i); - })); - - it('should support templates with root tags', inject(function($compile, $rootScope) { - expect(function() { - element = $compile('
')($rootScope); - }).not.toThrow(); - expect(nodeName_(element)).toMatch(/thead/i); - })); - - it('should support templates with root tags', inject(function($compile, $rootScope) { - expect(function() { - element = $compile('
')($rootScope); - }).not.toThrow(); - expect(nodeName_(element)).toMatch(/tbody/i); - })); - - it('should support templates with root tags', inject(function($compile, $rootScope) { - expect(function() { - element = $compile('
')($rootScope); - }).not.toThrow(); - expect(nodeName_(element)).toMatch(/tfoot/i); - })); - - it('should support templates with root tags', inject(function($compile, $rootScope) { - expect(function() { - element = $compile('
')($rootScope); - }).not.toThrow(); - expect(nodeName_(element)).toMatch(/optgroup/i); - })); - - if (window.SVGAElement) { - it('should support SVG templates using directive.type=svg', function() { - module(function() { - directive('svgAnchor', valueFn({ - replace: true, - template: '{{text}}', - type: 'SVG', - scope: { - linkurl: '@svgAnchor', - text: '@?' - } - })); - }); - inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - var child = element.children().eq(0); - $rootScope.$digest(); - expect(nodeName_(child)).toMatch(/a/i); - expect(child[0].constructor).toBe(window.SVGAElement); - expect(child[0].href.baseVal).toBe("/foo/bar"); - }); - }); - } - - // MathML is only natively supported in Firefox at the time of this test's writing, - // and even there, the browser does not export MathML element constructors globally. - // So the test is slightly limited in what it does. But as browsers begin to - // implement MathML natively, this can be tightened up to be more meaningful. - it('should support MathML templates using directive.type=math', function() { - module(function() { - directive('pow', valueFn({ - replace: true, - transclude: true, - template: '{{pow}}', - type: 'MATH', - scope: { - pow: '@pow', - }, - link: function(scope, elm, attr, ctrl, transclude) { - transclude(function(node) { - elm.prepend(node[0]); - }); - } - })); - }); - inject(function($compile, $rootScope) { - element = $compile('8')($rootScope); - $rootScope.$digest(); - var child = element.children().eq(0); - expect(nodeName_(child)).toMatch(/msup/i); - }); - }); - }); - - - describe('template as function', function() { - - beforeEach(module(function() { - directive('myDirective', valueFn({ - replace: true, - template: function($element, $attrs) { - expect($element.text()).toBe('original content'); - expect($attrs.myDirective).toBe('some value'); - return '
template content
'; - }, - compile: function($element, $attrs) { - expect($element.text()).toBe('template content'); - expect($attrs.id).toBe('templateContent'); - } - })); - })); - - - it('should evaluate `template` when defined as fn and use returned string as template', inject( - function($compile, $rootScope) { - element = $compile('
original content
')($rootScope); - expect(element.text()).toEqual('template content'); - })); - }); - - - describe('templateUrl', function() { - - beforeEach(module( - function() { - directive('hello', valueFn({ - restrict: 'CAM', - templateUrl: 'hello.html', - transclude: true - })); - directive('cau', valueFn({ - restrict: 'CAM', - templateUrl: 'cau.html' - })); - directive('crossDomainTemplate', valueFn({ - restrict: 'CAM', - templateUrl: 'http://example.com/should-not-load.html' - })); - directive('trustedTemplate', function($sce) { - return { - restrict: 'CAM', - templateUrl: function() { - return $sce.trustAsResourceUrl('http://example.com/trusted-template.html'); - } - }; - }); - directive('cError', valueFn({ - restrict: 'CAM', - templateUrl:'error.html', - compile: function() { - throw new Error('cError'); - } - })); - directive('lError', valueFn({ - restrict: 'CAM', - templateUrl: 'error.html', - compile: function() { - throw new Error('lError'); - } - })); - - - directive('iHello', valueFn({ - restrict: 'CAM', - replace: true, - templateUrl: 'hello.html' - })); - directive('iCau', valueFn({ - restrict: 'CAM', - replace: true, - templateUrl:'cau.html' - })); - - directive('iCError', valueFn({ - restrict: 'CAM', - replace: true, - templateUrl:'error.html', - compile: function() { - throw new Error('cError'); - } - })); - directive('iLError', valueFn({ - restrict: 'CAM', - replace: true, - templateUrl: 'error.html', - compile: function() { - throw new Error('lError'); - } - })); - - directive('replace', valueFn({ - replace: true, - template: 'Hello, {{name}}!' - })); - - directive('replaceWithTr', valueFn({ - replace: true, - templateUrl: 'tr.html' - })); - directive('replaceWithTd', valueFn({ - replace: true, - templateUrl: 'td.html' - })); - directive('replaceWithTh', valueFn({ - replace: true, - templateUrl: 'th.html' - })); - directive('replaceWithThead', valueFn({ - replace: true, - templateUrl: 'thead.html' - })); - directive('replaceWithTbody', valueFn({ - replace: true, - templateUrl: 'tbody.html' - })); - directive('replaceWithTfoot', valueFn({ - replace: true, - templateUrl: 'tfoot.html' - })); - directive('replaceWithOption', valueFn({ - replace: true, - templateUrl: 'option.html' - })); - directive('replaceWithOptgroup', valueFn({ - replace: true, - templateUrl: 'optgroup.html' - })); - } - )); - - it('should not load cross domain templates by default', inject( - function($compile, $rootScope, $templateCache, $sce) { - expect(function() { - $templateCache.put('http://example.com/should-not-load.html', 'Should not load even if in cache.'); - $compile('
')($rootScope); - }).toThrowMinErr('$sce', 'insecurl', 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: http://example.com/should-not-load.html'); - } - )); - - it('should load cross domain templates when trusted', inject( - function($compile, $httpBackend, $rootScope, $sce) { - $httpBackend.expect('GET', 'http://example.com/trusted-template.html').respond('example.com/trusted_template_contents'); - element = $compile('
')($rootScope); - expect(sortedHtml(element)). - toEqual('
'); - $httpBackend.flush(); - expect(sortedHtml(element)). - toEqual('
example.com/trusted_template_contents
'); - } - )); - - it('should append template via $http and cache it in $templateCache', inject( - function($compile, $httpBackend, $templateCache, $rootScope, $browser) { - $httpBackend.expect('GET', 'hello.html').respond('Hello! World!'); - $templateCache.put('cau.html', 'Cau!'); - element = $compile('
ignoreignore
')($rootScope); - expect(sortedHtml(element)). - toEqual('
'); - - $rootScope.$digest(); - - - expect(sortedHtml(element)). - toEqual('
Cau!
'); - - $httpBackend.flush(); - expect(sortedHtml(element)).toEqual( - '
' + - 'Hello! World!' + - 'Cau!' + - '
'); - } - )); - - - it('should inline template via $http and cache it in $templateCache', inject( - function($compile, $httpBackend, $templateCache, $rootScope) { - $httpBackend.expect('GET', 'hello.html').respond('Hello!'); - $templateCache.put('cau.html', 'Cau!'); - element = $compile('
ignoreignore
')($rootScope); - expect(sortedHtml(element)). - toEqual('
'); - - $rootScope.$digest(); - - - expect(sortedHtml(element)).toBeOneOf( - '
Cau!
', - '
Cau!
' //ie8 - ); - - $httpBackend.flush(); - expect(sortedHtml(element)).toBeOneOf( - '
Hello!Cau!
', - '
Hello!Cau!
' //ie8 - ); - } - )); - - - it('should compile, link and flush the template append', inject( - function($compile, $templateCache, $rootScope, $browser) { - $templateCache.put('hello.html', 'Hello, {{name}}!'); - $rootScope.name = 'Elvis'; - element = $compile('
')($rootScope); - - $rootScope.$digest(); - - expect(sortedHtml(element)). - toEqual('
Hello, Elvis!
'); - } - )); - - - it('should compile, link and flush the template inline', inject( - function($compile, $templateCache, $rootScope) { - $templateCache.put('hello.html', 'Hello, {{name}}!'); - $rootScope.name = 'Elvis'; - element = $compile('
')($rootScope); - - $rootScope.$digest(); - - expect(sortedHtml(element)).toBeOneOf( - '
Hello, Elvis!
', - '
Hello, Elvis!
' //ie8 - ); - } - )); - - - it('should compile, flush and link the template append', inject( - function($compile, $templateCache, $rootScope) { - $templateCache.put('hello.html', 'Hello, {{name}}!'); - $rootScope.name = 'Elvis'; - var template = $compile('
'); - - element = template($rootScope); - $rootScope.$digest(); - - expect(sortedHtml(element)). - toEqual('
Hello, Elvis!
'); - } - )); - - - it('should compile, flush and link the template inline', inject( - function($compile, $templateCache, $rootScope) { - $templateCache.put('hello.html', 'Hello, {{name}}!'); - $rootScope.name = 'Elvis'; - var template = $compile('
'); - - element = template($rootScope); - $rootScope.$digest(); - - expect(sortedHtml(element)).toBeOneOf( - '
Hello, Elvis!
', - '
Hello, Elvis!
' //ie8 - ); - } - )); - - - it('should compile template when replacing element in another template', - inject(function($compile, $templateCache, $rootScope) { - $templateCache.put('hello.html', '
'); - $rootScope.name = 'Elvis'; - element = $compile('
')($rootScope); - - $rootScope.$digest(); - - expect(sortedHtml(element)). - toEqual('
Hello, Elvis!
'); - })); - - - it('should compile template when replacing root element', - inject(function($compile, $templateCache, $rootScope) { - $rootScope.name = 'Elvis'; - element = $compile('
')($rootScope); - - $rootScope.$digest(); - - expect(sortedHtml(element)). - toEqual('Hello, Elvis!'); - })); - - - it('should resolve widgets after cloning in append mode', function() { - module(function($exceptionHandlerProvider) { - $exceptionHandlerProvider.mode('log'); - }); - inject(function($compile, $templateCache, $rootScope, $httpBackend, $browser, - $exceptionHandler) { - $httpBackend.expect('GET', 'hello.html').respond('{{greeting}} '); - $httpBackend.expect('GET', 'error.html').respond('
'); - $templateCache.put('cau.html', '{{name}}'); - $rootScope.greeting = 'Hello'; - $rootScope.name = 'Elvis'; - var template = $compile( - '
' + - '' + - '' + - '' + - '' + - '
'); - var e1; - var e2; - - e1 = template($rootScope.$new(), noop); // clone - expect(e1.text()).toEqual(''); - - $httpBackend.flush(); - - e2 = template($rootScope.$new(), noop); // clone - $rootScope.$digest(); - expect(e1.text()).toEqual('Hello Elvis'); - expect(e2.text()).toEqual('Hello Elvis'); - - expect($exceptionHandler.errors.length).toEqual(2); - expect($exceptionHandler.errors[0][0].message).toEqual('cError'); - expect($exceptionHandler.errors[1][0].message).toEqual('lError'); - - dealoc(e1); - dealoc(e2); - }); - }); - - it('should resolve widgets after cloning in append mode without $templateCache', function() { - module(function($exceptionHandlerProvider) { - $exceptionHandlerProvider.mode('log'); - }); - inject(function($compile, $templateCache, $rootScope, $httpBackend, $browser, - $exceptionHandler) { - $httpBackend.expect('GET', 'cau.html').respond('{{name}}'); - $rootScope.name = 'Elvis'; - var template = $compile('
'); - var e1; - var e2; - - e1 = template($rootScope.$new(), noop); // clone - expect(e1.text()).toEqual(''); - - $httpBackend.flush(); - - e2 = template($rootScope.$new(), noop); // clone - $rootScope.$digest(); - expect(e1.text()).toEqual('Elvis'); - expect(e2.text()).toEqual('Elvis'); - - dealoc(e1); - dealoc(e2); - }); - }); - - it('should resolve widgets after cloning in inline mode', function() { - module(function($exceptionHandlerProvider) { - $exceptionHandlerProvider.mode('log'); - }); - inject(function($compile, $templateCache, $rootScope, $httpBackend, $browser, - $exceptionHandler) { - $httpBackend.expect('GET', 'hello.html').respond('{{greeting}} '); - $httpBackend.expect('GET', 'error.html').respond('
'); - $templateCache.put('cau.html', '{{name}}'); - $rootScope.greeting = 'Hello'; - $rootScope.name = 'Elvis'; - var template = $compile( - '
' + - '' + - '' + - '' + - '' + - '
'); - var e1; - var e2; - - e1 = template($rootScope.$new(), noop); // clone - expect(e1.text()).toEqual(''); - - $httpBackend.flush(); - - e2 = template($rootScope.$new(), noop); // clone - $rootScope.$digest(); - expect(e1.text()).toEqual('Hello Elvis'); - expect(e2.text()).toEqual('Hello Elvis'); - - expect($exceptionHandler.errors.length).toEqual(2); - expect($exceptionHandler.errors[0][0].message).toEqual('cError'); - expect($exceptionHandler.errors[1][0].message).toEqual('lError'); - - dealoc(e1); - dealoc(e2); - }); - }); - - it('should resolve widgets after cloning in inline mode without $templateCache', function() { - module(function($exceptionHandlerProvider) { - $exceptionHandlerProvider.mode('log'); - }); - inject(function($compile, $templateCache, $rootScope, $httpBackend, $browser, - $exceptionHandler) { - $httpBackend.expect('GET', 'cau.html').respond('{{name}}'); - $rootScope.name = 'Elvis'; - var template = $compile('
'); - var e1; - var e2; - - e1 = template($rootScope.$new(), noop); // clone - expect(e1.text()).toEqual(''); - - $httpBackend.flush(); - - e2 = template($rootScope.$new(), noop); // clone - $rootScope.$digest(); - expect(e1.text()).toEqual('Elvis'); - expect(e2.text()).toEqual('Elvis'); - - dealoc(e1); - dealoc(e2); - }); - }); - - - it('should be implicitly terminal and not compile placeholder content in append', inject( - function($compile, $templateCache, $rootScope, log) { - // we can't compile the contents because that would result in a memory leak - - $templateCache.put('hello.html', 'Hello!'); - element = $compile('
')($rootScope); - - expect(log).toEqual(''); - } - )); - - - it('should be implicitly terminal and not compile placeholder content in inline', inject( - function($compile, $templateCache, $rootScope, log) { - // we can't compile the contents because that would result in a memory leak - - $templateCache.put('hello.html', 'Hello!'); - element = $compile('
')($rootScope); - - expect(log).toEqual(''); - } - )); - - - it('should throw an error and clear element content if the template fails to load', inject( - function($compile, $httpBackend, $rootScope) { - $httpBackend.expect('GET', 'hello.html').respond(404, 'Not Found!'); - element = $compile('
content
')($rootScope); - - expect(function() { - $httpBackend.flush(); - }).toThrowMinErr('$compile', 'tpload', 'Failed to load template: hello.html'); - expect(sortedHtml(element)).toBe('
'); - } - )); - - - it('should prevent multiple templates per element', function() { - module(function() { - directive('sync', valueFn({ - restrict: 'C', - template: '' - })); - directive('async', valueFn({ - restrict: 'C', - templateUrl: 'template.html' - })); - }); - inject(function($compile, $httpBackend){ - $httpBackend.whenGET('template.html').respond('

template.html

'); - expect(function() { - $compile('
'); - $httpBackend.flush(); - }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [async, sync] asking for template on: '+ - '
'); - }); - }); - - - it('should copy classes from pre-template node into linked element', function() { - module(function() { - directive('test', valueFn({ - templateUrl: 'test.html', - replace: true - })); - }); - inject(function($compile, $templateCache, $rootScope) { - var child; - $templateCache.put('test.html', '

Hello

'); - element = $compile('
')($rootScope, function(node) { - node.addClass('clonefn-class'); - }); - $rootScope.$digest(); - expect(element).toHaveClass('template-class'); - expect(element).toHaveClass('clonefn-class'); - }); - }); - - - describe('delay compile / linking functions until after template is resolved', function(){ - var template; - beforeEach(module(function() { - function logDirective (name, priority, options) { - directive(name, function(log) { - return (extend({ - priority: priority, - compile: function() { - log(name + '-C'); - return { - pre: function() { log(name + '-PreL'); }, - post: function() { log(name + '-PostL'); } - }; - } - }, options || {})); - }); - } - - logDirective('first', 10); - logDirective('second', 5, { templateUrl: 'second.html' }); - logDirective('third', 3); - logDirective('last', 0); - - logDirective('iFirst', 10, {replace: true}); - logDirective('iSecond', 5, {replace: true, templateUrl: 'second.html' }); - logDirective('iThird', 3, {replace: true}); - logDirective('iLast', 0, {replace: true}); - })); - - it('should flush after link append', inject( - function($compile, $rootScope, $httpBackend, log) { - $httpBackend.expect('GET', 'second.html').respond('
{{1+2}}
'); - template = $compile('
'); - element = template($rootScope); - expect(log).toEqual('first-C'); - - log('FLUSH'); - $httpBackend.flush(); - $rootScope.$digest(); - expect(log).toEqual( - 'first-C; FLUSH; second-C; last-C; third-C; ' + - 'first-PreL; second-PreL; last-PreL; third-PreL; ' + - 'third-PostL; last-PostL; second-PostL; first-PostL'); - - var span = element.find('span'); - expect(span.attr('first')).toEqual(''); - expect(span.attr('second')).toEqual(''); - expect(span.find('div').attr('third')).toEqual(''); - expect(span.attr('last')).toEqual(''); - - expect(span.text()).toEqual('3'); - })); - - - it('should flush after link inline', inject( - function($compile, $rootScope, $httpBackend, log) { - $httpBackend.expect('GET', 'second.html').respond('
{{1+2}}
'); - template = $compile('
'); - element = template($rootScope); - expect(log).toEqual('iFirst-C'); - - log('FLUSH'); - $httpBackend.flush(); - $rootScope.$digest(); - expect(log).toEqual( - 'iFirst-C; FLUSH; iSecond-C; iThird-C; iLast-C; ' + - 'iFirst-PreL; iSecond-PreL; iThird-PreL; iLast-PreL; ' + - 'iLast-PostL; iThird-PostL; iSecond-PostL; iFirst-PostL'); - - var div = element.find('div'); - expect(div.attr('i-first')).toEqual(''); - expect(div.attr('i-second')).toEqual(''); - expect(div.attr('i-third')).toEqual(''); - expect(div.attr('i-last')).toEqual(''); - - expect(div.text()).toEqual('3'); - })); - - - it('should flush before link append', inject( - function($compile, $rootScope, $httpBackend, log) { - $httpBackend.expect('GET', 'second.html').respond('
{{1+2}}
'); - template = $compile('
'); - expect(log).toEqual('first-C'); - log('FLUSH'); - $httpBackend.flush(); - expect(log).toEqual('first-C; FLUSH; second-C; last-C; third-C'); - - element = template($rootScope); - $rootScope.$digest(); - expect(log).toEqual( - 'first-C; FLUSH; second-C; last-C; third-C; ' + - 'first-PreL; second-PreL; last-PreL; third-PreL; ' + - 'third-PostL; last-PostL; second-PostL; first-PostL'); - - var span = element.find('span'); - expect(span.attr('first')).toEqual(''); - expect(span.attr('second')).toEqual(''); - expect(span.find('div').attr('third')).toEqual(''); - expect(span.attr('last')).toEqual(''); - - expect(span.text()).toEqual('3'); - })); - - - it('should flush before link inline', inject( - function($compile, $rootScope, $httpBackend, log) { - $httpBackend.expect('GET', 'second.html').respond('
{{1+2}}
'); - template = $compile('
'); - expect(log).toEqual('iFirst-C'); - log('FLUSH'); - $httpBackend.flush(); - expect(log).toEqual('iFirst-C; FLUSH; iSecond-C; iThird-C; iLast-C'); - - element = template($rootScope); - $rootScope.$digest(); - expect(log).toEqual( - 'iFirst-C; FLUSH; iSecond-C; iThird-C; iLast-C; ' + - 'iFirst-PreL; iSecond-PreL; iThird-PreL; iLast-PreL; ' + - 'iLast-PostL; iThird-PostL; iSecond-PostL; iFirst-PostL'); - - var div = element.find('div'); - expect(div.attr('i-first')).toEqual(''); - expect(div.attr('i-second')).toEqual(''); - expect(div.attr('i-third')).toEqual(''); - expect(div.attr('i-last')).toEqual(''); - - expect(div.text()).toEqual('3'); - })); - }); - - - it('should allow multiple elements in template', inject(function($compile, $httpBackend) { - $httpBackend.expect('GET', 'hello.html').respond('before mid after'); - element = jqLite('
'); - $compile(element); - $httpBackend.flush(); - expect(element.text()).toEqual('before mid after'); - })); - - - it('should work when directive is on the root element', inject( - function($compile, $httpBackend, $rootScope) { - $httpBackend.expect('GET', 'hello.html'). - respond('3=='); - element = jqLite('{{1+2}}'); - $compile(element)($rootScope); - - $httpBackend.flush(); - expect(element.text()).toEqual('3==3'); - } - )); - - - it('should work when directive is in a repeater', inject( - function($compile, $httpBackend, $rootScope) { - $httpBackend.expect('GET', 'hello.html'). - respond('i=;'); - element = jqLite('
{{i}}
'); - $compile(element)($rootScope); - - $httpBackend.flush(); - expect(element.text()).toEqual('i=1;i=2;'); - } - )); - - - it("should fail if replacing and template doesn't have a single root element", function() { - module(function($exceptionHandlerProvider) { - $exceptionHandlerProvider.mode('log'); - - directive('template', function() { - return { - replace: true, - templateUrl: 'template.html' - }; - }); - }); - - inject(function($compile, $templateCache, $rootScope, $exceptionHandler) { - // no root element - $templateCache.put('template.html', 'dada'); - $compile('

'); - $rootScope.$digest(); - expect($exceptionHandler.errors.pop().message). - toMatch(/\[\$compile:tplrt\] Template for directive 'template' must have exactly one root element\. template\.html/); - - // multi root - $templateCache.put('template.html', '
'); - $compile('

'); - $rootScope.$digest(); - expect($exceptionHandler.errors.pop().message). - toMatch(/\[\$compile:tplrt\] Template for directive 'template' must have exactly one root element\. template\.html/); - - // ws is ok - $templateCache.put('template.html', '
\n'); - $compile('

'); - $rootScope.$apply(); - expect($exceptionHandler.errors).toEqual([]); - }); - }); - - - it('should resume delayed compilation without duplicates when in a repeater', function() { - // this is a test for a regression - // scope creation, isolate watcher setup, controller instantiation, etc should happen - // only once even if we are dealing with delayed compilation of a node due to templateUrl - // and the template node is in a repeater - - var controllerSpy = jasmine.createSpy('controller'); - - module(function($compileProvider) { - $compileProvider.directive('delayed', valueFn({ - controller: controllerSpy, - templateUrl: 'delayed.html', - scope: { - title: '@' - } - })); - }); - - inject(function($templateCache, $compile, $rootScope) { - $rootScope.coolTitle = 'boom!'; - $templateCache.put('delayed.html', '
{{title}}
'); - element = $compile( - '
|
' - )($rootScope); - - $rootScope.$apply(); - - expect(controllerSpy.callCount).toBe(2); - expect(element.text()).toBe('boom!1|boom!2|'); - }); - }); - - - it('should support templateUrl with replace', function() { - // a regression https://github.com/angular/angular.js/issues/3792 - module(function($compileProvider) { - $compileProvider.directive('simple', function() { - return { - templateUrl: '/some.html', - replace: true - }; - }); - }); - - inject(function($templateCache, $rootScope, $compile) { - $templateCache.put('/some.html', - '
' + - '
i = 1
' + - '
I dont know what `i` is.
' + - '
'); - - element = $compile('
')($rootScope); - - $rootScope.$apply(function() { - $rootScope.i = 1; - }); - - expect(element.html()).toContain('i = 1'); - }); - }); - - it('should support templates with root tags', inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('tr.html', 'TR'); - expect(function() { - element = $compile('
')($rootScope); - }).not.toThrow(); - $rootScope.$digest(); - expect(nodeName_(element)).toMatch(/tr/i); - })); - - it('should support templates with root tags', inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('td.html', 'TD'); - expect(function() { - element = $compile('
')($rootScope); - }).not.toThrow(); - $rootScope.$digest(); - expect(nodeName_(element)).toMatch(/td/i); - })); - - it('should support templates with root tags', inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('th.html', 'TH'); - expect(function() { - element = $compile('
')($rootScope); - }).not.toThrow(); - $rootScope.$digest(); - expect(nodeName_(element)).toMatch(/th/i); - })); - - it('should support templates with root tags', inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('thead.html', 'TD'); - expect(function() { - element = $compile('
')($rootScope); - }).not.toThrow(); - $rootScope.$digest(); - expect(nodeName_(element)).toMatch(/thead/i); - })); - - it('should support templates with root tags', inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('tbody.html', 'TD'); - expect(function() { - element = $compile('
')($rootScope); - }).not.toThrow(); - $rootScope.$digest(); - expect(nodeName_(element)).toMatch(/tbody/i); - })); - - it('should support templates with root tags', inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('tfoot.html', 'TD'); - expect(function() { - element = $compile('
')($rootScope); - }).not.toThrow(); - $rootScope.$digest(); - expect(nodeName_(element)).toMatch(/tfoot/i); - })); - - it('should support templates with root '); - expect(function() { - element = $compile('
')($rootScope); - }).not.toThrow(); - $rootScope.$digest(); - expect(nodeName_(element)).toMatch(/option/i); - })); - - it('should support templates with root tags', inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('optgroup.html', 'OPTGROUP'); - expect(function() { - element = $compile('
')($rootScope); - }).not.toThrow(); - $rootScope.$digest(); - expect(nodeName_(element)).toMatch(/optgroup/i); - })); - - if (window.SVGAElement) { - it('should support SVG templates using directive.type=svg', function() { - module(function() { - directive('svgAnchor', valueFn({ - replace: true, - templateUrl: 'template.html', - type: 'SVG', - scope: { - linkurl: '@svgAnchor', - text: '@?' - } - })); - }); - inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('template.html', '{{text}}'); - element = $compile('')($rootScope); - $rootScope.$digest(); - var child = element.children().eq(0); - expect(nodeName_(child)).toMatch(/a/i); - expect(child[0].constructor).toBe(window.SVGAElement); - expect(child[0].href.baseVal).toBe("/foo/bar"); - }); - }); - } - - // MathML is only natively supported in Firefox at the time of this test's writing, - // and even there, the browser does not export MathML element constructors globally. - // So the test is slightly limited in what it does. But as browsers begin to - // implement MathML natively, this can be tightened up to be more meaningful. - it('should support MathML templates using directive.type=math', function() { - module(function() { - directive('pow', valueFn({ - replace: true, - transclude: true, - templateUrl: 'template.html', - type: 'MATH', - scope: { - pow: '@pow', - }, - link: function(scope, elm, attr, ctrl, transclude) { - transclude(function(node) { - elm.prepend(node[0]); - }); - } - })); - }); - inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('template.html', '{{pow}}'); - element = $compile('8')($rootScope); - $rootScope.$digest(); - var child = element.children().eq(0); - expect(nodeName_(child)).toMatch(/msup/i); - }); - }); - }); - - - describe('templateUrl as function', function() { - - beforeEach(module(function() { - directive('myDirective', valueFn({ - replace: true, - templateUrl: function($element, $attrs) { - expect($element.text()).toBe('original content'); - expect($attrs.myDirective).toBe('some value'); - return 'my-directive.html'; - }, - compile: function($element, $attrs) { - expect($element.text()).toBe('template content'); - expect($attrs.id).toBe('templateContent'); - } - })); - })); - - - it('should evaluate `templateUrl` when defined as fn and use returned value as url', inject( - function($compile, $rootScope, $templateCache) { - $templateCache.put('my-directive.html', '
template content'); - element = $compile('
original content
')($rootScope); - expect(element.text()).toEqual(''); - - $rootScope.$digest(); - - expect(element.text()).toEqual('template content'); - })); - }); - - - describe('scope', function() { - var iscope; - - beforeEach(module(function() { - forEach(['', 'a', 'b'], function(name) { - directive('scope' + uppercase(name), function(log) { - return { - scope: true, - restrict: 'CA', - compile: function() { - return {pre: function (scope, element) { - log(scope.$id); - expect(element.data('$scope')).toBe(scope); - }}; - } - }; - }); - directive('iscope' + uppercase(name), function(log) { - return { - scope: {}, - restrict: 'CA', - compile: function() { - return function (scope, element) { - iscope = scope; - log(scope.$id); - expect(element.data('$isolateScopeNoTemplate')).toBe(scope); - }; - } - }; - }); - directive('tscope' + uppercase(name), function(log) { - return { - scope: true, - restrict: 'CA', - templateUrl: 'tscope.html', - compile: function() { - return function (scope, element) { - log(scope.$id); - expect(element.data('$scope')).toBe(scope); - }; - } - }; - }); - directive('stscope' + uppercase(name), function(log) { - return { - scope: true, - restrict: 'CA', - template: '', - compile: function() { - return function (scope, element) { - log(scope.$id); - expect(element.data('$scope')).toBe(scope); - }; - } - }; - }); - directive('trscope' + uppercase(name), function(log) { - return { - scope: true, - replace: true, - restrict: 'CA', - templateUrl: 'trscope.html', - compile: function() { - return function (scope, element) { - log(scope.$id); - expect(element.data('$scope')).toBe(scope); - }; - } - }; - }); - directive('tiscope' + uppercase(name), function(log) { - return { - scope: {}, - restrict: 'CA', - templateUrl: 'tiscope.html', - compile: function() { - return function (scope, element) { - iscope = scope; - log(scope.$id); - expect(element.data('$isolateScope')).toBe(scope); - }; - } - }; - }); - directive('stiscope' + uppercase(name), function(log) { - return { - scope: {}, - restrict: 'CA', - template: '', - compile: function() { - return function (scope, element) { - iscope = scope; - log(scope.$id); - expect(element.data('$isolateScope')).toBe(scope); - }; - } - }; - }); - }); - directive('log', function(log) { - return { - restrict: 'CA', - link: {pre: function(scope) { - log('log-' + scope.$id + '-' + (scope.$parent && scope.$parent.$id || 'no-parent')); - }} - }; - }); - })); - - - it('should allow creation of new scopes', inject(function($rootScope, $compile, log) { - element = $compile('
')($rootScope); - expect(log).toEqual('2; log-2-1; LOG'); - expect(element.find('span').hasClass('ng-scope')).toBe(true); - })); - - - it('should allow creation of new isolated scopes for directives', inject( - function($rootScope, $compile, log) { - element = $compile('
')($rootScope); - expect(log).toEqual('log-1-no-parent; LOG; 2'); - $rootScope.name = 'abc'; - expect(iscope.$parent).toBe($rootScope); - expect(iscope.name).toBeUndefined(); - })); - - - it('should allow creation of new scopes for directives with templates', inject( - function($rootScope, $compile, log, $httpBackend) { - $httpBackend.expect('GET', 'tscope.html').respond('{{name}}; scopeId: {{$id}}'); - element = $compile('
')($rootScope); - $httpBackend.flush(); - expect(log).toEqual('log-2-1; LOG; 2'); - $rootScope.name = 'Jozo'; - $rootScope.$apply(); - expect(element.text()).toBe('Jozo; scopeId: 2'); - expect(element.find('span').scope().$id).toBe(2); - })); - - - it('should allow creation of new scopes for replace directives with templates', inject( - function($rootScope, $compile, log, $httpBackend) { - $httpBackend.expect('GET', 'trscope.html'). - respond('

{{name}}; scopeId: {{$id}}

'); - element = $compile('
')($rootScope); - $httpBackend.flush(); - expect(log).toEqual('log-2-1; LOG; 2'); - $rootScope.name = 'Jozo'; - $rootScope.$apply(); - expect(element.text()).toBe('Jozo; scopeId: 2'); - expect(element.find('a').scope().$id).toBe(2); - })); - - - it('should allow creation of new scopes for replace directives with templates in a repeater', - inject(function($rootScope, $compile, log, $httpBackend) { - $httpBackend.expect('GET', 'trscope.html'). - respond('

{{name}}; scopeId: {{$id}} |

'); - element = $compile('
')($rootScope); - $httpBackend.flush(); - expect(log).toEqual('log-3-2; LOG; 3; log-5-4; LOG; 5; log-7-6; LOG; 7'); - $rootScope.name = 'Jozo'; - $rootScope.$apply(); - expect(element.text()).toBe('Jozo; scopeId: 3 |Jozo; scopeId: 5 |Jozo; scopeId: 7 |'); - expect(element.find('p').scope().$id).toBe(3); - expect(element.find('a').scope().$id).toBe(3); - })); - - - it('should allow creation of new isolated scopes for directives with templates', inject( - function($rootScope, $compile, log, $httpBackend) { - $httpBackend.expect('GET', 'tiscope.html').respond(''); - element = $compile('
')($rootScope); - $httpBackend.flush(); - expect(log).toEqual('log-2-1; LOG; 2'); - $rootScope.name = 'abc'; - expect(iscope.$parent).toBe($rootScope); - expect(iscope.name).toBeUndefined(); - })); - - - it('should correctly create the scope hierachy', inject( - function($rootScope, $compile, log) { - element = $compile( - '
' + //1 - '' + //2 - '' + //3 - '' + - '' + - '' + //4 - '' + - '' + - '
' - )($rootScope); - expect(log).toEqual('2; 3; log-3-2; LOG; log-2-1; LOG; 4; log-4-1; LOG'); - }) - ); - - - it('should allow more than one new scope directives per element, but directives should share' + - 'the scope', inject( - function($rootScope, $compile, log) { - element = $compile('
')($rootScope); - expect(log).toEqual('2; 2'); - }) - ); - - it('should not allow more then one isolate scope creation per element', inject( - function($rootScope, $compile) { - expect(function(){ - $compile('
'); - }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [iscopeA, scopeB] asking for new/isolated scope on: ' + - '
'); - }) - ); - - it('should not allow more than one isolate scope creation per element regardless of directive priority', function() { - module(function($compileProvider) { - $compileProvider.directive('highPriorityScope', function() { - return { - restrict: 'C', - priority: 1, - scope: true, - link: function() {} - }; - }); - }); - inject(function($compile) { - expect(function(){ - $compile('
'); - }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [highPriorityScope, iscopeA] asking for new/isolated scope on: ' + - '
'); - }); - }); - - - it('should create new scope even at the root of the template', inject( - function($rootScope, $compile, log) { - element = $compile('
')($rootScope); - expect(log).toEqual('2'); - }) - ); - - - it('should create isolate scope even at the root of the template', inject( - function($rootScope, $compile, log) { - element = $compile('
')($rootScope); - expect(log).toEqual('2'); - }) - ); - - - describe('scope()/isolate() scope getters', function() { - - describe('with no directives', function() { - - it('should return the scope of the parent node', inject( - function($rootScope, $compile) { - element = $compile('
')($rootScope); - expect(element.scope()).toBe($rootScope); - }) - ); - }); - - - describe('with new scope directives', function() { - - it('should return the new scope at the directive element', inject( - function($rootScope, $compile) { - element = $compile('
')($rootScope); - expect(element.scope().$parent).toBe($rootScope); - }) - ); - - - it('should return the new scope for children in the original template', inject( - function($rootScope, $compile) { - element = $compile('
')($rootScope); - expect(element.find('a').scope().$parent).toBe($rootScope); - }) - ); - - - it('should return the new scope for children in the directive template', inject( - function($rootScope, $compile, $httpBackend) { - $httpBackend.expect('GET', 'tscope.html').respond(''); - element = $compile('
')($rootScope); - $httpBackend.flush(); - expect(element.find('a').scope().$parent).toBe($rootScope); - }) - ); - - it('should return the new scope for children in the directive sync template', inject( - function($rootScope, $compile) { - element = $compile('
')($rootScope); - expect(element.find('span').scope().$parent).toBe($rootScope); - }) - ); - }); - - - describe('with isolate scope directives', function() { - - it('should return the root scope for directives at the root element', inject( - function($rootScope, $compile) { - element = $compile('
')($rootScope); - expect(element.scope()).toBe($rootScope); - }) - ); - - - it('should return the non-isolate scope at the directive element', inject( - function($rootScope, $compile) { - var directiveElement; - element = $compile('
')($rootScope); - directiveElement = element.children(); - expect(directiveElement.scope()).toBe($rootScope); - expect(directiveElement.isolateScope().$parent).toBe($rootScope); - }) - ); - - - it('should return the isolate scope for children in the original template', inject( - function($rootScope, $compile) { - element = $compile('
')($rootScope); - expect(element.find('a').scope()).toBe($rootScope); //xx - }) - ); - - - it('should return the isolate scope for children in directive template', inject( - function($rootScope, $compile, $httpBackend) { - $httpBackend.expect('GET', 'tiscope.html').respond(''); - element = $compile('
')($rootScope); - expect(element.isolateScope()).toBeUndefined(); // this is the current behavior, not desired feature - $httpBackend.flush(); - expect(element.find('a').scope()).toBe(element.isolateScope()); - expect(element.isolateScope()).not.toBe($rootScope); - }) - ); - - it('should return the isolate scope for children in directive sync template', inject( - function($rootScope, $compile) { - element = $compile('
')($rootScope); - expect(element.find('span').scope()).toBe(element.isolateScope()); - expect(element.isolateScope()).not.toBe($rootScope); - }) - ); - }); - - - describe('with isolate scope directives and directives that manually create a new scope', function() { - - it('should return the new scope at the directive element', inject( - function($rootScope, $compile) { - var directiveElement; - element = $compile('
')($rootScope); - $rootScope.$apply(); - directiveElement = element.find('a'); - expect(directiveElement.scope().$parent).toBe($rootScope); - expect(directiveElement.scope()).not.toBe(directiveElement.isolateScope()); - }) - ); - - - it('should return the isolate scope for child elements', inject( - function($rootScope, $compile, $httpBackend) { - var directiveElement, child; - $httpBackend.expect('GET', 'tiscope.html').respond(''); - element = $compile('
')($rootScope); - $rootScope.$apply(); - $httpBackend.flush(); - directiveElement = element.find('a'); - child = directiveElement.find('span'); - expect(child.scope()).toBe(directiveElement.isolateScope()); - }) - ); - - it('should return the isolate scope for child elements in directive sync template', inject( - function($rootScope, $compile) { - var directiveElement, child; - element = $compile('
')($rootScope); - $rootScope.$apply(); - directiveElement = element.find('a'); - child = directiveElement.find('span'); - expect(child.scope()).toBe(directiveElement.isolateScope()); - }) - ); - }); - }); - }); - }); - }); - - - describe('interpolation', function() { - var observeSpy, directiveAttrs, deregisterObserver; - - beforeEach(module(function() { - directive('observer', function() { - return function(scope, elm, attr) { - directiveAttrs = attr; - observeSpy = jasmine.createSpy('$observe attr'); - deregisterObserver = attr.$observe('someAttr', observeSpy); - }; - }); - directive('replaceSomeAttr', valueFn({ - compile: function(element, attr) { - attr.$set('someAttr', 'bar-{{1+1}}'); - expect(element).toBe(attr.$$element); - } - })); - })); - - - it('should compile and link both attribute and text bindings', inject( - function($rootScope, $compile) { - $rootScope.name = 'angular'; - element = $compile('
text: {{name}}
')($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('text: angular'); - expect(element.attr('name')).toEqual('attr: angular'); - }) - ); - - - it('should one-time bind if the expression starts with two colons', inject( - function($rootScope, $compile) { - $rootScope.name = 'angular'; - element = $compile('
text: {{::name}}
')($rootScope); - expect($rootScope.$$watchers.length).toBe(2); - $rootScope.$digest(); - expect(element.text()).toEqual('text: angular'); - expect(element.attr('name')).toEqual('attr: angular'); - expect($rootScope.$$watchers.length).toBe(0); - $rootScope.name = 'not-angular'; - $rootScope.$digest(); - expect(element.text()).toEqual('text: angular'); - expect(element.attr('name')).toEqual('attr: angular'); - }) - ); - - it('should one-time bind if the expression starts with a space and two colons', inject( - function($rootScope, $compile) { - $rootScope.name = 'angular'; - element = $compile('
text: {{ ::name }}
')($rootScope); - expect($rootScope.$$watchers.length).toBe(2); - $rootScope.$digest(); - expect(element.text()).toEqual('text: angular'); - expect(element.attr('name')).toEqual('attr: angular'); - expect($rootScope.$$watchers.length).toBe(0); - $rootScope.name = 'not-angular'; - $rootScope.$digest(); - expect(element.text()).toEqual('text: angular'); - expect(element.attr('name')).toEqual('attr: angular'); - }) - ); - - - it('should process attribute interpolation in pre-linking phase at priority 100', function() { - module(function() { - directive('attrLog', function(log) { - return { - compile: function($element, $attrs) { - log('compile=' + $attrs.myName); - - return { - pre: function($scope, $element, $attrs) { - log('preLinkP0=' + $attrs.myName); - }, - post: function($scope, $element, $attrs) { - log('postLink=' + $attrs.myName); - } - }; - } - }; - }); - }); - module(function() { - directive('attrLogHighPriority', function(log) { - return { - priority: 101, - compile: function() { - return { - pre: function($scope, $element, $attrs) { - log('preLinkP101=' + $attrs.myName); - } - }; - } - }; - }); - }); - inject(function($rootScope, $compile, log) { - element = $compile('
')($rootScope); - $rootScope.name = 'angular'; - $rootScope.$apply(); - log('digest=' + element.attr('my-name')); - expect(log).toEqual('compile={{name}}; preLinkP101={{name}}; preLinkP0=; postLink=; digest=angular'); - }); - }); - - - describe('SCE values', function() { - it('should resolve compile and link both attribute and text bindings', inject( - function($rootScope, $compile, $sce) { - $rootScope.name = $sce.trustAsHtml('angular'); - element = $compile('
text: {{name}}
')($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('text: angular'); - expect(element.attr('name')).toEqual('attr: angular'); - })); - }); - - it('should decorate the binding with ng-binding and interpolation function', inject( - function($compile, $rootScope) { - element = $compile('
{{1+2}}
')($rootScope); - expect(element.hasClass('ng-binding')).toBe(true); - expect(element.data('$binding')[0].exp).toEqual('{{1+2}}'); - })); - - - it('should observe interpolated attrs', inject(function($rootScope, $compile) { - $compile('
')($rootScope); - - // should be async - expect(observeSpy).not.toHaveBeenCalled(); - - $rootScope.$apply(function() { - $rootScope.value = 'bound-value'; - }); - expect(observeSpy).toHaveBeenCalledOnceWith('bound-value'); - })); - - - it('should return a deregistration function while observing an attribute', inject(function($rootScope, $compile) { - $compile('
')($rootScope); - - $rootScope.$apply('value = "first-value"'); - expect(observeSpy).toHaveBeenCalledWith('first-value'); - - deregisterObserver(); - $rootScope.$apply('value = "new-value"'); - expect(observeSpy).not.toHaveBeenCalledWith('new-value'); - })); - - - it('should set interpolated attrs to initial interpolation value', inject(function($rootScope, $compile) { - // we need the interpolated attributes to be initialized so that linking fn in a component - // can access the value during link - $rootScope.whatever = 'test value'; - $compile('
')($rootScope); - expect(directiveAttrs.someAttr).toBe($rootScope.whatever); - })); - - - it('should allow directive to replace interpolated attributes before attr interpolation compilation', inject( - function($compile, $rootScope) { - element = $compile('
')($rootScope); - $rootScope.$digest(); - expect(element.attr('some-attr')).toEqual('bar-2'); - })); - - - it('should call observer of non-interpolated attr through $evalAsync', - inject(function($rootScope, $compile) { - $compile('
')($rootScope); - expect(directiveAttrs.someAttr).toBe('nonBound'); - - expect(observeSpy).not.toHaveBeenCalled(); - $rootScope.$digest(); - expect(observeSpy).toHaveBeenCalled(); - }) - ); - - - it('should delegate exceptions to $exceptionHandler', function() { - observeSpy = jasmine.createSpy('$observe attr').andThrow('ERROR'); - - module(function($exceptionHandlerProvider) { - $exceptionHandlerProvider.mode('log'); - directive('error', function() { - return function(scope, elm, attr) { - attr.$observe('someAttr', observeSpy); - attr.$observe('someAttr', observeSpy); - }; - }); - }); - - inject(function($compile, $rootScope, $exceptionHandler) { - $compile('
')($rootScope); - $rootScope.$digest(); - - expect(observeSpy).toHaveBeenCalled(); - expect(observeSpy.callCount).toBe(2); - expect($exceptionHandler.errors).toEqual(['ERROR', 'ERROR']); - }); - }); - - - it('should translate {{}} in terminal nodes', inject(function($rootScope, $compile) { - element = $compile('')($rootScope); - $rootScope.$digest(); - expect(sortedHtml(element).replace(' selected="true"', '')). - toEqual(''); - $rootScope.name = 'Misko'; - $rootScope.$digest(); - expect(sortedHtml(element).replace(' selected="true"', '')). - toEqual(''); - })); - - - it('should support custom start/end interpolation symbols in template and directive template', - function() { - module(function($interpolateProvider, $compileProvider) { - $interpolateProvider.startSymbol('##').endSymbol(']]'); - $compileProvider.directive('myDirective', function() { - return { - template: '{{hello}}|{{hello|uppercase}}' - }; - }); - }); - - inject(function($compile, $rootScope) { - element = $compile('
##hello|uppercase]]|
')($rootScope); - $rootScope.hello = 'ahoj'; - $rootScope.$digest(); - expect(element.text()).toBe('AHOJ|ahoj|AHOJ'); - }); - }); - - - it('should support custom start/end interpolation symbols in async directive template', - function() { - module(function($interpolateProvider, $compileProvider) { - $interpolateProvider.startSymbol('##').endSymbol(']]'); - $compileProvider.directive('myDirective', function() { - return { - templateUrl: 'myDirective.html' - }; - }); - }); - - inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('myDirective.html', '{{hello}}|{{hello|uppercase}}'); - element = $compile('
##hello|uppercase]]|
')($rootScope); - $rootScope.hello = 'ahoj'; - $rootScope.$digest(); - expect(element.text()).toBe('AHOJ|ahoj|AHOJ'); - }); - }); - - - it('should make attributes observable for terminal directives', function() { - module(function() { - directive('myAttr', function(log) { - return { - terminal: true, - link: function(scope, element, attrs) { - attrs.$observe('myAttr', function(val) { - log(val); - }); - } - }; - }); - }); - - inject(function($compile, $rootScope, log) { - element = $compile('
')($rootScope); - expect(log).toEqual([]); - - $rootScope.myVal = 'carrot'; - $rootScope.$digest(); - - expect(log).toEqual(['carrot']); - }); - }); - }); - - - describe('link phase', function() { - - beforeEach(module(function() { - - forEach(['a', 'b', 'c'], function(name) { - directive(name, function(log) { - return { - restrict: 'ECA', - compile: function() { - log('t' + uppercase(name)); - return { - pre: function() { - log('pre' + uppercase(name)); - }, - post: function linkFn() { - log('post' + uppercase(name)); - } - }; - } - }; - }); - }); - })); - - - it('should not store linkingFns for noop branches', inject(function ($rootScope, $compile) { - element = jqLite('
ignore
'); - var linkingFn = $compile(element); - // Now prune the branches with no directives - element.find('span').remove(); - expect(element.find('span').length).toBe(0); - // and we should still be able to compile without errors - linkingFn($rootScope); - })); - - - it('should compile from top to bottom but link from bottom up', inject( - function($compile, $rootScope, log) { - element = $compile('')($rootScope); - expect(log).toEqual('tA; tB; tC; preA; preB; preC; postC; postB; postA'); - } - )); - - - it('should support link function on directive object', function() { - module(function() { - directive('abc', valueFn({ - link: function(scope, element, attrs) { - element.text(attrs.abc); - } - })); - }); - inject(function($compile, $rootScope) { - element = $compile('
FAIL
')($rootScope); - expect(element.text()).toEqual('WORKS'); - }); - }); - - it('should support $observe inside link function on directive object', function() { - module(function() { - directive('testLink', valueFn({ - templateUrl: 'test-link.html', - link: function(scope, element, attrs) { - attrs.$observe( 'testLink', function ( val ) { - scope.testAttr = val; - }); - } - })); - }); - inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('test-link.html', '{{testAttr}}' ); - element = $compile('
')($rootScope); - $rootScope.$apply(); - expect(element.text()).toBe('3'); - }); - }); - }); - - - describe('attrs', function() { - - it('should allow setting of attributes', function() { - module(function() { - directive({ - setter: valueFn(function(scope, element, attr) { - attr.$set('name', 'abc'); - attr.$set('disabled', true); - expect(attr.name).toBe('abc'); - expect(attr.disabled).toBe(true); - }) - }); - }); - inject(function($rootScope, $compile) { - element = $compile('
')($rootScope); - expect(element.attr('name')).toEqual('abc'); - expect(element.attr('disabled')).toEqual('disabled'); - }); - }); - - - it('should read boolean attributes as boolean only on control elements', function() { - var value; - module(function() { - directive({ - input: valueFn({ - restrict: 'ECA', - link:function(scope, element, attr) { - value = attr.required; - } - }) - }); - }); - inject(function($rootScope, $compile) { - element = $compile('')($rootScope); - expect(value).toEqual(true); - }); - }); - - it('should read boolean attributes as text on non-controll elements', function() { - var value; - module(function() { - directive({ - div: valueFn({ - restrict: 'ECA', - link:function(scope, element, attr) { - value = attr.required; - } - }) - }); - }); - inject(function($rootScope, $compile) { - element = $compile('
')($rootScope); - expect(value).toEqual('some text'); - }); - }); - - - it('should create new instance of attr for each template stamping', function() { - module(function($provide) { - var state = { first: [], second: [] }; - $provide.value('state', state); - directive({ - first: valueFn({ - priority: 1, - compile: function(templateElement, templateAttr) { - return function(scope, element, attr) { - state.first.push({ - template: {element: templateElement, attr:templateAttr}, - link: {element: element, attr: attr} - }); - }; - } - }), - second: valueFn({ - priority: 2, - compile: function(templateElement, templateAttr) { - return function(scope, element, attr) { - state.second.push({ - template: {element: templateElement, attr:templateAttr}, - link: {element: element, attr: attr} - }); - }; - } - }) - }); - }); - inject(function($rootScope, $compile, state) { - var template = $compile('
'); - dealoc(template($rootScope.$new(), noop)); - dealoc(template($rootScope.$new(), noop)); - - // instance between directives should be shared - expect(state.first[0].template.element).toBe(state.second[0].template.element); - expect(state.first[0].template.attr).toBe(state.second[0].template.attr); - - // the template and the link can not be the same instance - expect(state.first[0].template.element).not.toBe(state.first[0].link.element); - expect(state.first[0].template.attr).not.toBe(state.first[0].link.attr); - - // each new template needs to be new instance - expect(state.first[0].link.element).not.toBe(state.first[1].link.element); - expect(state.first[0].link.attr).not.toBe(state.first[1].link.attr); - expect(state.second[0].link.element).not.toBe(state.second[1].link.element); - expect(state.second[0].link.attr).not.toBe(state.second[1].link.attr); - }); - }); - - - it('should properly $observe inside ng-repeat', function() { - var spies = []; - - module(function() { - directive('observer', function() { - return function(scope, elm, attr) { - spies.push(jasmine.createSpy('observer ' + spies.length)); - attr.$observe('some', spies[spies.length - 1]); - }; - }); - }); - - inject(function($compile, $rootScope) { - element = $compile('
'+ - ''+ - '
')($rootScope); - - $rootScope.$apply(function() { - $rootScope.items = [{id: 1}, {id: 2}]; - }); - - expect(spies[0]).toHaveBeenCalledOnceWith('id_1'); - expect(spies[1]).toHaveBeenCalledOnceWith('id_2'); - spies[0].reset(); - spies[1].reset(); - - $rootScope.$apply(function() { - $rootScope.items[0].id = 5; - }); - - expect(spies[0]).toHaveBeenCalledOnceWith('id_5'); - }); - }); - - - describe('$set', function() { - var attr; - beforeEach(function(){ - module(function() { - directive('input', valueFn({ - restrict: 'ECA', - link: function(scope, element, attr) { - scope.attr = attr; - } - })); - }); - inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - attr = $rootScope.attr; - expect(attr).toBeDefined(); - }); - }); - - - it('should set attributes', function() { - attr.$set('ngMyAttr', 'value'); - expect(element.attr('ng-my-attr')).toEqual('value'); - expect(attr.ngMyAttr).toEqual('value'); - }); - - - it('should allow overriding of attribute name and remember the name', function() { - attr.$set('ngOther', '123', true, 'other'); - expect(element.attr('other')).toEqual('123'); - expect(attr.ngOther).toEqual('123'); - - attr.$set('ngOther', '246'); - expect(element.attr('other')).toEqual('246'); - expect(attr.ngOther).toEqual('246'); - }); - - - it('should remove attribute', function() { - attr.$set('ngMyAttr', 'value'); - expect(element.attr('ng-my-attr')).toEqual('value'); - - attr.$set('ngMyAttr', undefined); - expect(element.attr('ng-my-attr')).toBe(undefined); - - attr.$set('ngMyAttr', 'value'); - attr.$set('ngMyAttr', null); - expect(element.attr('ng-my-attr')).toBe(undefined); - }); - - - it('should not set DOM element attr if writeAttr false', function() { - attr.$set('test', 'value', false); - - expect(element.attr('test')).toBeUndefined(); - expect(attr.test).toBe('value'); - }); - }); - }); - - - describe('isolated locals', function() { - var componentScope, regularScope; - - beforeEach(module(function() { - directive('myComponent', function() { - return { - scope: { - attr: '@', - attrAlias: '@attr', - ref: '=', - refAlias: '= ref', - reference: '=', - optref: '=?', - optrefAlias: '=? optref', - optreference: '=?', - expr: '&', - exprAlias: '&expr' - }, - link: function(scope) { - componentScope = scope; - } - }; - }); - directive('badDeclaration', function() { - return { - scope: { attr: 'xxx' } - }; - }); - directive('storeScope', function() { - return { - link: function(scope) { - regularScope = scope; - } - }; - }); - })); - - - it('should give other directives the parent scope', inject(function($rootScope) { - compile('
'); - $rootScope.$apply(function() { - $rootScope.value = 'from-parent'; - }); - expect(element.find('input').val()).toBe('from-parent'); - expect(componentScope).not.toBe(regularScope); - expect(componentScope.$parent).toBe(regularScope); - })); - - - it('should not give the isolate scope to other directive template', function() { - module(function() { - directive('otherTplDir', function() { - return { - template: 'value: {{value}}' - }; - }); - }); - - inject(function($rootScope) { - compile('
'); - - $rootScope.$apply(function() { - $rootScope.value = 'from-parent'; - }); - - expect(element.html()).toBe('value: from-parent'); - }); - }); - - - it('should not give the isolate scope to other directive template (with templateUrl)', function() { - module(function() { - directive('otherTplDir', function() { - return { - templateUrl: 'other.html' - }; - }); - }); - - inject(function($rootScope, $templateCache) { - $templateCache.put('other.html', 'value: {{value}}'); - compile('
'); - - $rootScope.$apply(function() { - $rootScope.value = 'from-parent'; - }); - - expect(element.html()).toBe('value: from-parent'); - }); - }); - - - it('should not give the isolate scope to regular child elements', function() { - inject(function($rootScope) { - compile('
value: {{value}}
'); - - $rootScope.$apply(function() { - $rootScope.value = 'from-parent'; - }); - - expect(element.html()).toBe('value: from-parent'); - }); - }); - - - it('should update parent scope when "="-bound NaN changes', inject(function($compile, $rootScope) { - $rootScope.num = NaN; - compile('
'); - var isolateScope = element.isolateScope(); - expect(isolateScope.reference).toBeNaN(); - - isolateScope.$apply(function(scope) { scope.reference = 64; }); - expect($rootScope.num).toBe(64); - })); - - - it('should update isolate scope when "="-bound NaN changes', inject(function($compile, $rootScope) { - $rootScope.num = NaN; - compile('
'); - var isolateScope = element.isolateScope(); - expect(isolateScope.reference).toBeNaN(); - - $rootScope.$apply(function(scope) { scope.num = 64; }); - expect(isolateScope.reference).toBe(64); - })); - - - describe('bind-once', function () { - - function countWatches(scope) { - var result = 0; - while (scope !== null) { - result += (scope.$$watchers && scope.$$watchers.length) || 0; - result += countWatches(scope.$$childHead); - scope = scope.$$nextSibling; - } - return result; - } - - it('should be possible to one-time bind a parameter on a component with a template', function() { - module(function() { - directive('otherTplDir', function() { - return { - scope: {param1: '=', param2: '='}, - template: '1:{{param1}};2:{{param2}};3:{{::param1}};4:{{::param2}}' - }; - }); - }); - - inject(function($rootScope) { - compile('
'); - expect(countWatches($rootScope)).toEqual(7); // 5 -> template watch group, 2 -> '=' - $rootScope.$digest(); - expect(element.html()).toBe('1:;2:;3:;4:'); - expect(countWatches($rootScope)).toEqual(7); - - $rootScope.foo = 'foo'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:;3:foo;4:'); - expect(countWatches($rootScope)).toEqual(5); - - $rootScope.foo = 'baz'; - $rootScope.bar = 'bar'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:bar;3:foo;4:bar'); - expect(countWatches($rootScope)).toEqual(4); - - $rootScope.bar = 'baz'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:baz;3:foo;4:bar'); - }); - }); - - it('should be possible to one-time bind a parameter on a component with a template', function() { - module(function() { - directive('otherTplDir', function() { - return { - scope: {param1: '@', param2: '@'}, - template: '1:{{param1}};2:{{param2}};3:{{::param1}};4:{{::param2}}' - }; - }); - }); - - inject(function($rootScope) { - compile('
'); - expect(countWatches($rootScope)).toEqual(7); // 5 -> template watch group, 2 -> {{ }} - $rootScope.$digest(); - expect(element.html()).toBe('1:;2:;3:;4:'); - expect(countWatches($rootScope)).toEqual(5); // (- 2) -> bind-once in template - - $rootScope.foo = 'foo'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:;3:;4:'); - expect(countWatches($rootScope)).toEqual(4); - - $rootScope.foo = 'baz'; - $rootScope.bar = 'bar'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:bar;3:;4:'); - expect(countWatches($rootScope)).toEqual(4); - - $rootScope.bar = 'baz'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:baz;3:;4:'); - }); - }); - - it('should be possible to one-time bind a parameter on a component with a template', function() { - module(function() { - directive('otherTplDir', function() { - return { - scope: {param1: '=', param2: '='}, - templateUrl: 'other.html' - }; - }); - }); - - inject(function($rootScope, $templateCache) { - $templateCache.put('other.html', '1:{{param1}};2:{{param2}};3:{{::param1}};4:{{::param2}}'); - compile('
'); - $rootScope.$digest(); - expect(element.html()).toBe('1:;2:;3:;4:'); - expect(countWatches($rootScope)).toEqual(7); // 5 -> template watch group, 2 -> '=' - - $rootScope.foo = 'foo'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:;3:foo;4:'); - expect(countWatches($rootScope)).toEqual(5); - - $rootScope.foo = 'baz'; - $rootScope.bar = 'bar'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:bar;3:foo;4:bar'); - expect(countWatches($rootScope)).toEqual(4); - - $rootScope.bar = 'baz'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:baz;3:foo;4:bar'); - }); - }); - - it('should be possible to one-time bind a parameter on a component with a template', function() { - module(function() { - directive('otherTplDir', function() { - return { - scope: {param1: '@', param2: '@'}, - templateUrl: 'other.html' - }; - }); - }); - - inject(function($rootScope, $templateCache) { - $templateCache.put('other.html', '1:{{param1}};2:{{param2}};3:{{::param1}};4:{{::param2}}'); - compile('
'); - $rootScope.$digest(); - expect(element.html()).toBe('1:;2:;3:;4:'); - expect(countWatches($rootScope)).toEqual(5); // (5 - 2) -> template watch group, 2 -> {{ }} - - $rootScope.foo = 'foo'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:;3:;4:'); - expect(countWatches($rootScope)).toEqual(4); - - $rootScope.foo = 'baz'; - $rootScope.bar = 'bar'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:bar;3:;4:'); - expect(countWatches($rootScope)).toEqual(4); - - $rootScope.bar = 'baz'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:baz;3:;4:'); - }); - }); - - }); - - - describe('attribute', function() { - it('should copy simple attribute', inject(function() { - compile('
'); - - expect(componentScope.attr).toEqual('some text'); - expect(componentScope.attrAlias).toEqual('some text'); - expect(componentScope.attrAlias).toEqual(componentScope.attr); - })); - - it('should set up the interpolation before it reaches the link function', inject(function() { - $rootScope.name = 'misko'; - compile('
'); - expect(componentScope.attr).toEqual('hello misko'); - expect(componentScope.attrAlias).toEqual('hello misko'); - })); - - it('should update when interpolated attribute updates', inject(function() { - compile('
'); - - $rootScope.name = 'igor'; - $rootScope.$apply(); - - expect(componentScope.attr).toEqual('hello igor'); - expect(componentScope.attrAlias).toEqual('hello igor'); - })); - }); - - - describe('object reference', function() { - it('should update local when origin changes', inject(function() { - compile('
'); - expect(componentScope.ref).toBe(undefined); - expect(componentScope.refAlias).toBe(componentScope.ref); - - $rootScope.name = 'misko'; - $rootScope.$apply(); - - expect($rootScope.name).toBe('misko'); - expect(componentScope.ref).toBe('misko'); - expect(componentScope.refAlias).toBe('misko'); - - $rootScope.name = {}; - $rootScope.$apply(); - expect(componentScope.ref).toBe($rootScope.name); - expect(componentScope.refAlias).toBe($rootScope.name); - })); - - - it('should update local when both change', inject(function() { - compile('
'); - $rootScope.name = {mark:123}; - componentScope.ref = 'misko'; - - $rootScope.$apply(); - expect($rootScope.name).toEqual({mark:123}); - expect(componentScope.ref).toBe($rootScope.name); - expect(componentScope.refAlias).toBe($rootScope.name); - - $rootScope.name = 'igor'; - componentScope.ref = {}; - $rootScope.$apply(); - expect($rootScope.name).toEqual('igor'); - expect(componentScope.ref).toBe($rootScope.name); - expect(componentScope.refAlias).toBe($rootScope.name); - })); - - it('should not break if local and origin both change to the same value', inject(function() { - $rootScope.name = 'aaa'; - - compile('
'); - - //change both sides to the same item withing the same digest cycle - componentScope.ref = 'same'; - $rootScope.name = 'same'; - $rootScope.$apply(); - - //change origin back to its previous value - $rootScope.name = 'aaa'; - $rootScope.$apply(); - - expect($rootScope.name).toBe('aaa'); - expect(componentScope.ref).toBe('aaa'); - })); - - it('should complain on non assignable changes', inject(function() { - compile('
'); - $rootScope.name = 'world'; - $rootScope.$apply(); - expect(componentScope.ref).toBe('hello world'); - - componentScope.ref = 'ignore me'; - expect($rootScope.$apply). - toThrowMinErr("$compile", "nonassign", "Expression ''hello ' + name' used with directive 'myComponent' is non-assignable!"); - expect(componentScope.ref).toBe('hello world'); - // reset since the exception was rethrown which prevented phase clearing - $rootScope.$$phase = null; - - $rootScope.name = 'misko'; - $rootScope.$apply(); - expect(componentScope.ref).toBe('hello misko'); - })); - - // regression - it('should stabilize model', inject(function() { - compile('
'); - - var lastRefValueInParent; - $rootScope.$watch('name', function(ref) { - lastRefValueInParent = ref; - }); - - $rootScope.name = 'aaa'; - $rootScope.$apply(); - - componentScope.reference = 'new'; - $rootScope.$apply(); - - expect(lastRefValueInParent).toBe('new'); - })); - - describe('literal objects', function() { - it('should copy parent changes', inject(function() { - compile('
'); - - $rootScope.name = 'a'; - $rootScope.$apply(); - expect(componentScope.reference).toEqual({name: 'a'}); - - $rootScope.name = 'b'; - $rootScope.$apply(); - expect(componentScope.reference).toEqual({name: 'b'}); - })); - - it('should not change the component when parent does not change', inject(function() { - compile('
'); - - $rootScope.name = 'a'; - $rootScope.$apply(); - var lastComponentValue = componentScope.reference; - $rootScope.$apply(); - expect(componentScope.reference).toBe(lastComponentValue); - })); - - it('should complain when the component changes', inject(function() { - compile('
'); - - $rootScope.name = 'a'; - $rootScope.$apply(); - componentScope.reference = {name: 'b'}; - expect(function() { - $rootScope.$apply(); - }).toThrowMinErr("$compile", "nonassign", "Expression '{name: name}' used with directive 'myComponent' is non-assignable!"); - - })); - - it('should work for primitive literals', inject(function() { - test('1', 1); - test('null', null); - test('undefined', undefined); - test("'someString'", 'someString'); - - - function test(literalString, literalValue) { - compile('
'); - - $rootScope.$apply(); - expect(componentScope.reference).toBe(literalValue); - dealoc(element); - - } - - })); - - }); - - }); - - - describe('optional object reference', function() { - it('should update local when origin changes', inject(function() { - compile('
'); - expect(componentScope.optRef).toBe(undefined); - expect(componentScope.optRefAlias).toBe(componentScope.optRef); - - $rootScope.name = 'misko'; - $rootScope.$apply(); - expect(componentScope.optref).toBe($rootScope.name); - expect(componentScope.optrefAlias).toBe($rootScope.name); - - $rootScope.name = {}; - $rootScope.$apply(); - expect(componentScope.optref).toBe($rootScope.name); - expect(componentScope.optrefAlias).toBe($rootScope.name); - })); - - it('should not throw exception when reference does not exist', inject(function() { - compile('
'); - - expect(componentScope.optref).toBe(undefined); - expect(componentScope.optrefAlias).toBe(undefined); - expect(componentScope.optreference).toBe(undefined); - })); - }); - - - describe('executable expression', function() { - it('should allow expression execution with locals', inject(function() { - compile('
'); - $rootScope.count = 2; - - expect(typeof componentScope.expr).toBe('function'); - expect(typeof componentScope.exprAlias).toBe('function'); - - expect(componentScope.expr({offset: 1})).toEqual(3); - expect($rootScope.count).toEqual(3); - - expect(componentScope.exprAlias({offset: 10})).toEqual(13); - expect($rootScope.count).toEqual(13); - })); - }); - - it('should throw on unknown definition', inject(function() { - expect(function() { - compile('
'); - }).toThrowMinErr("$compile", "iscp", "Invalid isolate scope definition for directive 'badDeclaration'. Definition: {... attr: 'xxx' ...}"); - })); - - it('should expose a $$isolateBindings property onto the scope', inject(function() { - compile('
'); - - expect(typeof componentScope.$$isolateBindings).toBe('object'); - - expect(componentScope.$$isolateBindings.attr).toBe('@attr'); - expect(componentScope.$$isolateBindings.attrAlias).toBe('@attr'); - expect(componentScope.$$isolateBindings.ref).toBe('=ref'); - expect(componentScope.$$isolateBindings.refAlias).toBe('=ref'); - expect(componentScope.$$isolateBindings.reference).toBe('=reference'); - expect(componentScope.$$isolateBindings.expr).toBe('&expr'); - expect(componentScope.$$isolateBindings.exprAlias).toBe('&expr'); - - })); - }); - - - describe('controller', function() { - it('should get required controller', function() { - module(function() { - directive('main', function(log) { - return { - priority: 2, - controller: function() { - this.name = 'main'; - }, - link: function(scope, element, attrs, controller) { - log(controller.name); - } - }; - }); - directive('dep', function(log) { - return { - priority: 1, - require: 'main', - link: function(scope, element, attrs, controller) { - log('dep:' + controller.name); - } - }; - }); - directive('other', function(log) { - return { - link: function(scope, element, attrs, controller) { - log(!!controller); // should be false - } - }; - }); - }); - inject(function(log, $compile, $rootScope) { - element = $compile('
')($rootScope); - expect(log).toEqual('false; dep:main; main'); - }); - }); - - - it('should get required controller via linkingFn (template)', function() { - module(function() { - directive('dirA', function() { - return { - controller: function() { - this.name = 'dirA'; - } - }; - }); - directive('dirB', function(log) { - return { - require: 'dirA', - template: '

dirB

', - link: function(scope, element, attrs, dirAController) { - log('dirAController.name: ' + dirAController.name); - } - }; - }); - }); - inject(function(log, $compile, $rootScope) { - element = $compile('
')($rootScope); - expect(log).toEqual('dirAController.name: dirA'); - }); - }); - - - it('should get required controller via linkingFn (templateUrl)', function() { - module(function() { - directive('dirA', function() { - return { - controller: function() { - this.name = 'dirA'; - } - }; - }); - directive('dirB', function(log) { - return { - require: 'dirA', - templateUrl: 'dirB.html', - link: function(scope, element, attrs, dirAController) { - log('dirAController.name: ' + dirAController.name); - } - }; - }); - }); - inject(function(log, $compile, $rootScope, $templateCache) { - $templateCache.put('dirB.html', '

dirB

'); - element = $compile('
')($rootScope); - $rootScope.$digest(); - expect(log).toEqual('dirAController.name: dirA'); - }); - }); - - - it('should require controller of an isolate directive from a non-isolate directive on the ' + - 'same element', function() { - var IsolateController = function() {}; - var isolateDirControllerInNonIsolateDirective; - - module(function() { - directive('isolate', function() { - return { - scope: {}, - controller: IsolateController - }; - }); - directive('nonIsolate', function() { - return { - require: 'isolate', - link: function(_, __, ___, isolateDirController) { - isolateDirControllerInNonIsolateDirective = isolateDirController; - } - }; - }); - }); - - inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); - - expect(isolateDirControllerInNonIsolateDirective).toBeDefined(); - expect(isolateDirControllerInNonIsolateDirective instanceof IsolateController).toBe(true); - }); - }); - - - it('should give the isolate scope to the controller of another replaced directives in the template', function() { - module(function() { - directive('testDirective', function() { - return { - replace: true, - restrict: 'E', - scope: {}, - template: '' - }; - }); - }); - - inject(function($rootScope) { - compile('
'); - - element = element.children().eq(0); - expect(element[0].checked).toBe(false); - element.isolateScope().model = true; - $rootScope.$digest(); - expect(element[0].checked).toBe(true); - }); - }); - - - it('should share isolate scope with replaced directives (template)', function() { - var normalScope; - var isolateScope; - - module(function() { - directive('isolate', function() { - return { - replace: true, - scope: {}, - template: '{{name}}', - link: function(s) { - isolateScope = s; - } - }; - }); - directive('nonIsolate', function() { - return { - link: function(s) { - normalScope = s; - } - }; - }); - }); - - inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); - - expect(normalScope).toBe($rootScope); - expect(normalScope.name).toEqual(undefined); - expect(isolateScope.name).toEqual('WORKS'); - $rootScope.$digest(); - expect(element.text()).toEqual('WORKS'); - }); - }); - - - it('should share isolate scope with replaced directives (templateUrl)', function() { - var normalScope; - var isolateScope; - - module(function() { - directive('isolate', function() { - return { - replace: true, - scope: {}, - templateUrl: 'main.html', - link: function(s) { - isolateScope = s; - } - }; - }); - directive('nonIsolate', function() { - return { - link: function(s) { - normalScope = s; - } - }; - }); - }); - - inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('main.html', '{{name}}'); - element = $compile('
')($rootScope); - $rootScope.$apply(); - - expect(normalScope).toBe($rootScope); - expect(normalScope.name).toEqual(undefined); - expect(isolateScope.name).toEqual('WORKS'); - expect(element.text()).toEqual('WORKS'); - }); - }); - - - it('should not get confused about where to use isolate scope when a replaced directive is used multiple times', - function() { - - module(function() { - directive('isolate', function() { - return { - replace: true, - scope: {}, - template: '' - }; - }); - directive('scopeTester', function(log) { - return { - link: function($scope, $element) { - log($element.attr('scope-tester') + '=' + ($scope.$root === $scope ? 'non-isolate' : 'isolate')); - } - }; - }); - }); - - inject(function($compile, $rootScope, log) { - element = $compile('
' + - '
' + - '' + - '
')($rootScope); - - $rootScope.$digest(); - expect(log).toEqual('inside=isolate; ' + - 'outside replaced=non-isolate; ' + // outside - 'outside replaced=isolate; ' + // replaced - 'sibling=non-isolate'); - }); - }); - - - it('should require controller of a non-isolate directive from an isolate directive on the ' + - 'same element', function() { - var NonIsolateController = function() {}; - var nonIsolateDirControllerInIsolateDirective; - - module(function() { - directive('isolate', function() { - return { - scope: {}, - require: 'nonIsolate', - link: function(_, __, ___, nonIsolateDirController) { - nonIsolateDirControllerInIsolateDirective = nonIsolateDirController; - } - }; - }); - directive('nonIsolate', function() { - return { - controller: NonIsolateController - }; - }); - }); - - inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); - - expect(nonIsolateDirControllerInIsolateDirective).toBeDefined(); - expect(nonIsolateDirControllerInIsolateDirective instanceof NonIsolateController).toBe(true); - }); - }); - - - it('should support controllerAs', function() { - module(function() { - directive('main', function() { - return { - templateUrl: 'main.html', - transclude: true, - scope: {}, - controller: function() { - this.name = 'lucas'; - }, - controllerAs: 'mainCtrl' - }; - }); - }); - inject(function($templateCache, $compile, $rootScope) { - $templateCache.put('main.html', 'template:{{mainCtrl.name}}
'); - element = $compile('
transclude:{{mainCtrl.name}}
')($rootScope); - $rootScope.$apply(); - expect(element.text()).toBe('template:lucas transclude:'); - }); - }); - - - it('should support controller alias', function() { - module(function($controllerProvider) { - $controllerProvider.register('MainCtrl', function() { - this.name = 'lucas'; - }); - directive('main', function() { - return { - templateUrl: 'main.html', - scope: {}, - controller: 'MainCtrl as mainCtrl' - }; - }); - }); - inject(function($templateCache, $compile, $rootScope) { - $templateCache.put('main.html', '{{mainCtrl.name}}'); - element = $compile('
')($rootScope); - $rootScope.$apply(); - expect(element.text()).toBe('lucas'); - }); - }); - - - - it('should require controller on parent element',function() { - module(function() { - directive('main', function(log) { - return { - controller: function() { - this.name = 'main'; - } - }; - }); - directive('dep', function(log) { - return { - require: '^main', - link: function(scope, element, attrs, controller) { - log('dep:' + controller.name); - } - }; - }); - }); - inject(function(log, $compile, $rootScope) { - element = $compile('
')($rootScope); - expect(log).toEqual('dep:main'); - }); - }); - - - it("should throw an error if required controller can't be found",function() { - module(function() { - directive('dep', function(log) { - return { - require: '^main', - link: function(scope, element, attrs, controller) { - log('dep:' + controller.name); - } - }; - }); - }); - inject(function(log, $compile, $rootScope) { - expect(function() { - $compile('
')($rootScope); - }).toThrowMinErr("$compile", "ctreq", "Controller 'main', required by directive 'dep', can't be found!"); - }); - }); - - - it('should have optional controller on current element', function() { - module(function() { - directive('dep', function(log) { - return { - require: '?main', - link: function(scope, element, attrs, controller) { - log('dep:' + !!controller); - } - }; - }); - }); - inject(function(log, $compile, $rootScope) { - element = $compile('
')($rootScope); - expect(log).toEqual('dep:false'); - }); - }); - - - it('should support multiple controllers', function() { - module(function() { - directive('c1', valueFn({ - controller: function() { this.name = 'c1'; } - })); - directive('c2', valueFn({ - controller: function() { this.name = 'c2'; } - })); - directive('dep', function(log) { - return { - require: ['^c1', '^c2'], - link: function(scope, element, attrs, controller) { - log('dep:' + controller[0].name + '-' + controller[1].name); - } - }; - }); - }); - inject(function(log, $compile, $rootScope) { - element = $compile('
')($rootScope); - expect(log).toEqual('dep:c1-c2'); - }); - }); - - - it('should instantiate the controller just once when template/templateUrl', function() { - var syncCtrlSpy = jasmine.createSpy('sync controller'), - asyncCtrlSpy = jasmine.createSpy('async controller'); - - module(function() { - directive('myDirectiveSync', valueFn({ - template: '
Hello!
', - controller: syncCtrlSpy - })); - directive('myDirectiveAsync', valueFn({ - templateUrl: 'myDirectiveAsync.html', - controller: asyncCtrlSpy, - compile: function() { - return function() { - }; - } - })); - }); - - inject(function($templateCache, $compile, $rootScope) { - expect(syncCtrlSpy).not.toHaveBeenCalled(); - expect(asyncCtrlSpy).not.toHaveBeenCalled(); - - $templateCache.put('myDirectiveAsync.html', '
Hello!
'); - element = $compile('
'+ - '' + - '' + - '
')($rootScope); - expect(syncCtrlSpy).not.toHaveBeenCalled(); - expect(asyncCtrlSpy).not.toHaveBeenCalled(); - - $rootScope.$apply(); - - //expect(syncCtrlSpy).toHaveBeenCalledOnce(); - expect(asyncCtrlSpy).toHaveBeenCalledOnce(); - }); - }); - - - - it('should instantiate controllers in the parent->child order when transluction, templateUrl and replacement ' + - 'are in the mix', function() { - // When a child controller is in the transclusion that replaces the parent element that has a directive with - // a controller, we should ensure that we first instantiate the parent and only then stuff that comes from the - // transclusion. - // - // The transclusion moves the child controller onto the same element as parent controller so both controllers are - // on the same level. - - module(function() { - directive('parentDirective', function() { - return { - transclude: true, - replace: true, - templateUrl: 'parentDirective.html', - controller: function (log) { log('parentController'); } - }; - }); - directive('childDirective', function() { - return { - require: '^parentDirective', - templateUrl: 'childDirective.html', - controller : function(log) { log('childController'); } - }; - }); - }); - - inject(function($templateCache, log, $compile, $rootScope) { - $templateCache.put('parentDirective.html', '
parentTemplateText;
'); - $templateCache.put('childDirective.html', 'childTemplateText;'); - - element = $compile('
childContentText;
')($rootScope); - $rootScope.$apply(); - expect(log).toEqual('parentController; childController'); - expect(element.text()).toBe('childTemplateText;childContentText;'); - }); - }); - - - it('should instantiate the controller after the isolate scope bindings are initialized (with template)', function () { - module(function () { - var Ctrl = function ($scope, log) { - log('myFoo=' + $scope.myFoo); - }; - - directive('myDirective', function () { - return { - scope: { - myFoo: "=" - }, - template: '

Hello

', - controller: Ctrl - }; - }); - }); - - inject(function ($templateCache, $compile, $rootScope, log) { - $rootScope.foo = "bar"; - - element = $compile('
')($rootScope); - $rootScope.$apply(); - expect(log).toEqual('myFoo=bar'); - }); - }); - - - it('should instantiate the controller after the isolate scope bindings are initialized (with templateUrl)', function () { - module(function () { - var Ctrl = function ($scope, log) { - log('myFoo=' + $scope.myFoo); - }; - - directive('myDirective', function () { - return { - scope: { - myFoo: "=" - }, - templateUrl: 'hello.html', - controller: Ctrl - }; - }); - }); - - inject(function ($templateCache, $compile, $rootScope, log) { - $templateCache.put('hello.html', '

Hello

'); - $rootScope.foo = "bar"; - - element = $compile('
')($rootScope); - $rootScope.$apply(); - expect(log).toEqual('myFoo=bar'); - }); - }); - - - it('should instantiate controllers in the parent->child->baby order when nested transluction, templateUrl and ' + - 'replacement are in the mix', function() { - // similar to the test above, except that we have one more layer of nesting and nested transclusion - - module(function() { - directive('parentDirective', function() { - return { - transclude: true, - replace: true, - templateUrl: 'parentDirective.html', - controller: function (log) { log('parentController'); } - }; - }); - directive('childDirective', function() { - return { - require: '^parentDirective', - transclude: true, - replace: true, - templateUrl: 'childDirective.html', - controller : function(log) { log('childController'); } - }; - }); - directive('babyDirective', function() { - return { - require: '^childDirective', - templateUrl: 'babyDirective.html', - controller : function(log) { log('babyController'); } - }; - }); - }); - - inject(function($templateCache, log, $compile, $rootScope) { - $templateCache.put('parentDirective.html', '
parentTemplateText;
'); - $templateCache.put('childDirective.html', 'childTemplateText;'); - $templateCache.put('babyDirective.html', 'babyTemplateText;'); - - element = $compile('
' + - '
' + - 'childContentText;' + - '
babyContent;
' + - '
' + - '
')($rootScope); - $rootScope.$apply(); - expect(log).toEqual('parentController; childController; babyController'); - expect(element.text()).toBe('childContentText;babyTemplateText;'); - }); - }); - - - it('should allow controller usage in pre-link directive functions with templateUrl', function () { - module(function () { - var Ctrl = function (log) { - log('instance'); - }; - - directive('myDirective', function () { - return { - scope: true, - templateUrl: 'hello.html', - controller: Ctrl, - compile: function () { - return { - pre: function (scope, template, attr, ctrl) {}, - post: function () {} - }; - } - }; - }); - }); - - inject(function ($templateCache, $compile, $rootScope, log) { - $templateCache.put('hello.html', '

Hello

'); - - element = $compile('
')($rootScope); - $rootScope.$apply(); - - expect(log).toEqual('instance'); - expect(element.text()).toBe('Hello'); - }); - }); - - - it('should allow controller usage in pre-link directive functions with a template', function () { - module(function () { - var Ctrl = function (log) { - log('instance'); - }; - - directive('myDirective', function () { - return { - scope: true, - template: '

Hello

', - controller: Ctrl, - compile: function () { - return { - pre: function (scope, template, attr, ctrl) {}, - post: function () {} - }; - } - }; - }); - }); - - inject(function ($templateCache, $compile, $rootScope, log) { - element = $compile('
')($rootScope); - $rootScope.$apply(); - - expect(log).toEqual('instance'); - expect(element.text()).toBe('Hello'); - }); - }); - - - it('should throw ctreq with correct directive name, regardless of order', function() { - module(function($compileProvider) { - $compileProvider.directive('aDir', valueFn({ - restrict: "E", - require: "ngModel", - link: noop - })); - }); - inject(function($compile, $rootScope) { - expect(function() { - // a-dir will cause a ctreq error to be thrown. Previously, the error would reference - // the last directive in the chain (which in this case would be ngClick), based on - // priority and alphabetical ordering. This test verifies that the ordering does not - // affect which directive is referenced in the minErr message. - element = $compile('')($rootScope); - }).toThrowMinErr('$compile', 'ctreq', - "Controller 'ngModel', required by directive 'aDir', can't be found!"); - }); - }); - }); - - - describe('transclude', function() { - - describe('content transclusion', function() { - - it('should support transclude directive', function() { - module(function() { - directive('trans', function() { - return { - transclude: 'content', - replace: true, - scope: true, - template: '
  • W:{{$parent.$id}}-{{$id}};
' - }; - }); - }); - inject(function(log, $rootScope, $compile) { - element = $compile('
T:{{$parent.$id}}-{{$id}};
') - ($rootScope); - $rootScope.$apply(); - expect(element.text()).toEqual('W:1-2;T:1-3;'); - expect(jqLite(element.find('span')[0]).text()).toEqual('T:1-3'); - expect(jqLite(element.find('span')[1]).text()).toEqual(';'); - }); - }); - - - it('should transclude transcluded content', function() { - module(function() { - directive('book', valueFn({ - transclude: 'content', - template: '
book-
(
)
' - })); - directive('chapter', valueFn({ - transclude: 'content', - templateUrl: 'chapter.html' - })); - directive('section', valueFn({ - transclude: 'content', - template: '
section-!
!
' - })); - return function($httpBackend) { - $httpBackend. - expect('GET', 'chapter.html'). - respond('
chapter-
[
]
'); - }; - }); - inject(function(log, $rootScope, $compile, $httpBackend) { - element = $compile('
paragraph
')($rootScope); - $rootScope.$apply(); - - expect(element.text()).toEqual('book-'); - - $httpBackend.flush(); - $rootScope.$apply(); - expect(element.text()).toEqual('book-chapter-section-![(paragraph)]!'); - }); - }); - - - it('should only allow one content transclusion per element', function() { - module(function() { - directive('first', valueFn({ - transclude: true - })); - directive('second', valueFn({ - transclude: true - })); - }); - inject(function($compile) { - expect(function() { - $compile('
'); - }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second\] asking for transclusion on:
{{x}}
')($rootScope); - expect(calcCacheSize()).toEqual(1); - - $rootScope.$apply('xs = [0,1]'); - expect(calcCacheSize()).toEqual(2); - - $rootScope.$apply('xs = [0]'); - expect(calcCacheSize()).toEqual(1); - - $rootScope.$apply('xs = []'); - expect(calcCacheSize()).toEqual(1); - - element.remove(); - expect(calcCacheSize()).toEqual(0); - }); - }); - - - it('should not leak if two "element" transclusions are on the same element', function () { - if (jQuery) { - // jQuery 2.x doesn't expose the cache storage. - return; - } - var calcCacheSize = function() { - var size = 0; - forEach(jqLite.cache, function(item, key) { size++; }); - return size; - }; - inject(function($compile, $rootScope) { - expect(calcCacheSize()).toEqual(0); - element = $compile('
{{x}}
')($rootScope); - - $rootScope.$apply('xs = [0,1]'); - // At this point we have a bunch of comment placeholders but no real transcluded elements - // So the cache only contains the root element's data - expect(calcCacheSize()).toEqual(1); - - $rootScope.$apply('val = true'); - // Now we have two concrete transcluded elements plus some comments so two more cache items - expect(calcCacheSize()).toEqual(3); - - $rootScope.$apply('val = false'); - // Once again we only have comments so no transcluded elements and the cache is back to just - // the root element - expect(calcCacheSize()).toEqual(1); - - element.remove(); - // Now we've even removed the root element along with its cache - expect(calcCacheSize()).toEqual(0); - }); - }); - - if (jQuery) { - describe('cleaning up after a replaced element', function () { - var $compile, xs; - beforeEach(inject(function (_$compile_) { - $compile = _$compile_; - xs = [0, 1]; - })); - - function testCleanup() { - var privateData, firstRepeatedElem; - - element = $compile('
{{x}}
')($rootScope); - - $rootScope.$apply('xs = [' + xs + ']'); - firstRepeatedElem = element.children('.ng-scope').eq(0); - - expect(firstRepeatedElem.data('$scope')).toBeDefined(); - privateData = jQuery._data(firstRepeatedElem[0]); - expect(privateData.events).toBeDefined(); - expect(privateData.events.$destroy).toBeDefined(); - expect(privateData.events.$destroy[0]).toBeDefined(); - - $rootScope.$apply('xs = null'); - - expect(firstRepeatedElem.data('$scope')).not.toBeDefined(); - privateData = jQuery._data(firstRepeatedElem[0]); - expect(privateData && privateData.events).not.toBeDefined(); - } - - it('should work without external libraries (except jQuery)', testCleanup); - - it('should work with another library patching jQuery.cleanData after Angular', function () { - var cleanedCount = 0; - var currentCleanData = jQuery.cleanData; - jQuery.cleanData = function (elems) { - cleanedCount += elems.length; - // Don't return the output and expicitly pass only the first parameter - // so that we're sure we're not relying on either of them. jQuery UI patch - // behaves in this way. - currentCleanData(elems); - }; - - testCleanup(); - - // The initial ng-repeat div is dumped after parsing hence we expect cleanData - // count to be one larger than size of the iterated array. - expect(cleanedCount).toBe(xs.length + 1); - - // Restore the previous jQuery.cleanData. - jQuery.cleanData = currentCleanData; - }); - }); - } - - - it('should remove transclusion scope, when the DOM is destroyed', function() { - module(function() { - directive('box', valueFn({ - transclude: true, - scope: { name: '=', show: '=' }, - template: '

Hello: {{name}}!

', - link: function(scope, element) { - scope.$watch( - 'show', - function(show) { - if (!show) { - element.find('div').find('div').remove(); - } - } - ); - } - })); - }); - inject(function($compile, $rootScope) { - $rootScope.username = 'Misko'; - $rootScope.select = true; - element = $compile( - '
user: {{username}}
') - ($rootScope); - $rootScope.$apply(); - expect(element.text()).toEqual('Hello: Misko!user: Misko'); - - var widgetScope = $rootScope.$$childHead; - var transcludeScope = widgetScope.$$nextSibling; - expect(widgetScope.name).toEqual('Misko'); - expect(widgetScope.$parent).toEqual($rootScope); - expect(transcludeScope.$parent).toEqual($rootScope); - - $rootScope.select = false; - $rootScope.$apply(); - expect(element.text()).toEqual('Hello: Misko!'); - expect(widgetScope.$$nextSibling).toEqual(null); - }); - }); - - - it('should add a $$transcluded property onto the transcluded scope', function() { - module(function() { - directive('trans', function() { - return { - transclude: true, - replace: true, - scope: true, - template: '
I:{{$$transcluded}}
' - }; - }); - }); - inject(function(log, $rootScope, $compile) { - element = $compile('
T:{{$$transcluded}}
') - ($rootScope); - $rootScope.$apply(); - expect(jqLite(element.find('span')[0]).text()).toEqual('I:'); - expect(jqLite(element.find('span')[1]).text()).toEqual('T:true'); - }); - }); - - - it('should clear contents of the ng-translude element before appending transcluded content', function() { - module(function() { - directive('trans', function() { - return { - transclude: true, - template: '
old stuff!
' - }; - }); - }); - inject(function(log, $rootScope, $compile) { - element = $compile('
unicorn!
')($rootScope); - $rootScope.$apply(); - expect(sortedHtml(element.html())).toEqual('
unicorn!
'); - }); - }); - - - it('should throw on an ng-transclude element inside no transclusion directive', function() { - inject(function ($rootScope, $compile) { - // we need to do this because different browsers print empty attributes differently - try { - $compile('
')($rootScope); - } catch(e) { - expect(e.message).toMatch(new RegExp( - '^\\[ngTransclude:orphan\\] ' + - 'Illegal use of ngTransclude directive in the template! ' + - 'No parent directive that requires a transclusion found\\. ' + - 'Element:
' + - '
' + - '
this one should get replaced with content
' + - '
' + - '
', - transclude: true - - })); - - $compileProvider.directive('noTransBar', valueFn({ - template: '
' + - // This ng-transclude is invalid. It should throw an error. - '
' + - '
', - transclude: false - - })); - }); - - inject(function($compile, $rootScope) { - expect(function() { - $compile('
content
')($rootScope); - }).toThrowMinErr('ngTransclude', 'orphan', - 'Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element:
'); - }); - }); - - - it('should not pass transclusion into a templateUrl directive', function() { - - module(function($compileProvider) { - - $compileProvider.directive('transFoo', valueFn({ - template: '
' + - '
' + - '
this one should get replaced with content
' + - '
' + - '
', - transclude: true - - })); - - $compileProvider.directive('noTransBar', valueFn({ - templateUrl: 'noTransBar.html', - transclude: false - - })); - }); - - inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('noTransBar.html', - '
' + - // This ng-transclude is invalid. It should throw an error. - '
' + - '
'); - - expect(function() { - element = $compile('
content
')($rootScope); - $rootScope.$apply(); - }).toThrowMinErr('ngTransclude', 'orphan', - 'Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element:
'); - }); - }); - - - it('should expose transcludeFn in compile fn even for templateUrl', function() { - module(function() { - directive('transInCompile', valueFn({ - transclude: true, - // template: '
whatever
', - templateUrl: 'foo.html', - compile: function(_, __, transclude) { - return function(scope, element) { - transclude(scope, function(clone, scope) { - element.html(''); - element.append(clone); - }); - }; - } - })); - }); - - inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('foo.html', '
whatever
'); - - compile('
transcluded content
'); - $rootScope.$apply(); - - expect(trim(element.text())).toBe('transcluded content'); - }); - }); - - - it('should make the result of a transclusion available to the parent directive in post-linking phase' + - '(template)', function() { - module(function() { - directive('trans', function(log) { - return { - transclude: true, - template: '
', - link: { - pre: function($scope, $element) { - log('pre(' + $element.text() + ')'); - }, - post: function($scope, $element) { - log('post(' + $element.text() + ')'); - } - } - }; - }); - }); - inject(function(log, $rootScope, $compile) { - element = $compile('
unicorn!
')($rootScope); - $rootScope.$apply(); - expect(log).toEqual('pre(); post(unicorn!)'); - }); - }); - - - it('should make the result of a transclusion available to the parent directive in post-linking phase' + - '(templateUrl)', function() { - // when compiling an async directive the transclusion is always processed before the directive - // this is different compared to sync directive. delaying the transclusion makes little sense. - - module(function() { - directive('trans', function(log) { - return { - transclude: true, - templateUrl: 'trans.html', - link: { - pre: function($scope, $element) { - log('pre(' + $element.text() + ')'); - }, - post: function($scope, $element) { - log('post(' + $element.text() + ')'); - } - } - }; - }); - }); - inject(function(log, $rootScope, $compile, $templateCache) { - $templateCache.put('trans.html', '
'); - - element = $compile('
unicorn!
')($rootScope); - $rootScope.$apply(); - expect(log).toEqual('pre(); post(unicorn!)'); - }); - }); - - - it('should make the result of a transclusion available to the parent *replace* directive in post-linking phase' + - '(template)', function() { - module(function() { - directive('replacedTrans', function(log) { - return { - transclude: true, - replace: true, - template: '
', - link: { - pre: function($scope, $element) { - log('pre(' + $element.text() + ')'); - }, - post: function($scope, $element) { - log('post(' + $element.text() + ')'); - } - } - }; - }); - }); - inject(function(log, $rootScope, $compile) { - element = $compile('
unicorn!
')($rootScope); - $rootScope.$apply(); - expect(log).toEqual('pre(); post(unicorn!)'); - }); - }); - - - it('should make the result of a transclusion available to the parent *replace* directive in post-linking phase' + - ' (templateUrl)', function() { - module(function() { - directive('replacedTrans', function(log) { - return { - transclude: true, - replace: true, - templateUrl: 'trans.html', - link: { - pre: function($scope, $element) { - log('pre(' + $element.text() + ')'); - }, - post: function($scope, $element) { - log('post(' + $element.text() + ')'); - } - } - }; - }); - }); - inject(function(log, $rootScope, $compile, $templateCache) { - $templateCache.put('trans.html', '
'); - - element = $compile('
unicorn!
')($rootScope); - $rootScope.$apply(); - expect(log).toEqual('pre(); post(unicorn!)'); - }); - }); - - it('should copy the directive controller to all clones', function() { - var transcludeCtrl, cloneCount = 2; - module(function() { - directive('transclude', valueFn({ - transclude: 'content', - controller: function($transclude) { - transcludeCtrl = this; - }, - link: function(scope, el, attr, ctrl, $transclude) { - var i; - for (i=0; i
')($rootScope); - var children = element.children(), i; - expect(transcludeCtrl).toBeDefined(); - - expect(element.data('$transcludeController')).toBe(transcludeCtrl); - for (i=0; i
')($rootScope); - expect(ctrlTransclude).toBeDefined(); - expect(ctrlTransclude).toBe(preLinkTransclude); - expect(ctrlTransclude).toBe(postLinkTransclude); - }); - }); - - it('should allow an optional scope argument in $transclude', function() { - var capturedChildCtrl; - module(function() { - directive('transclude', valueFn({ - transclude: 'content', - link: function(scope, element, attr, ctrl, $transclude) { - $transclude(scope, function(clone) { - element.append(clone); - }); - } - })); - }); - inject(function($compile) { - element = $compile('
{{$id}}
')($rootScope); - $rootScope.$apply(); - expect(element.text()).toBe('' + $rootScope.$id); - }); - - }); - - it('should expose the directive controller to transcluded children', function() { - var capturedChildCtrl; - module(function() { - directive('transclude', valueFn({ - transclude: 'content', - controller: function() { - }, - link: function(scope, element, attr, ctrl, $transclude) { - $transclude(function(clone) { - element.append(clone); - }); - } - })); - directive('child', valueFn({ - require: '^transclude', - link: function(scope, element, attr, ctrl) { - capturedChildCtrl = ctrl; - } - })); - }); - inject(function($compile) { - element = $compile('
')($rootScope); - expect(capturedChildCtrl).toBeTruthy(); - }); - - }); - - - describe('nested transcludes', function() { - - beforeEach(module(function($compileProvider) { - - $compileProvider.directive('noop', valueFn({})); - - $compileProvider.directive('sync', valueFn({ - template: '
', - transclude: true - })); - - $compileProvider.directive('async', valueFn({ - templateUrl: 'async', - transclude: true - })); - - $compileProvider.directive('syncSync', valueFn({ - template: '
', - transclude: true - })); - - $compileProvider.directive('syncAsync', valueFn({ - template: '
', - transclude: true - })); - - $compileProvider.directive('asyncSync', valueFn({ - templateUrl: 'asyncSync', - transclude: true - })); - - $compileProvider.directive('asyncAsync', valueFn({ - templateUrl: 'asyncAsync', - transclude: true - })); - - })); - - beforeEach(inject(function($templateCache) { - $templateCache.put('async', '
'); - $templateCache.put('asyncSync', '
'); - $templateCache.put('asyncAsync', '
'); - })); - - - it('should allow nested transclude directives with sync template containing sync template', inject(function($compile, $rootScope) { - element = $compile('
transcluded content
')($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('transcluded content'); - })); - - it('should allow nested transclude directives with sync template containing async template', inject(function($compile, $rootScope) { - element = $compile('
transcluded content
')($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('transcluded content'); - })); - - it('should allow nested transclude directives with async template containing sync template', inject(function($compile, $rootScope) { - element = $compile('
transcluded content
')($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('transcluded content'); - })); - - it('should allow nested transclude directives with async template containing asynch template', inject(function($compile, $rootScope) { - element = $compile('
transcluded content
')($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('transcluded content'); - })); - - - it('should not leak memory with nested transclusion', function() { - var calcCacheSize = function() { - var count = 0; - for (var k in jqLite.cache) { ++count; } - return count; - }; - - inject(function($compile, $rootScope) { - var size; - - expect(calcCacheSize()).toEqual(0); - - element = jqLite('
  • {{n}} => EvenOdd
'); - $compile(element)($rootScope.$new()); - - $rootScope.nums = [0,1,2]; - $rootScope.$apply(); - size = calcCacheSize(); - - $rootScope.nums = [3,4,5]; - $rootScope.$apply(); - expect(calcCacheSize()).toEqual(size); - - element.remove(); - expect(calcCacheSize()).toEqual(0); - }); - }); - }); - - - describe('nested isolated scope transcludes', function() { - beforeEach(module(function($compileProvider) { - - $compileProvider.directive('trans', valueFn({ - restrict: 'E', - template: '
', - transclude: true - })); - - $compileProvider.directive('transAsync', valueFn({ - restrict: 'E', - templateUrl: 'transAsync', - transclude: true - })); - - $compileProvider.directive('iso', valueFn({ - restrict: 'E', - transclude: true, - template: '', - scope: {} - })); - $compileProvider.directive('isoAsync1', valueFn({ - restrict: 'E', - transclude: true, - template: '', - scope: {} - })); - $compileProvider.directive('isoAsync2', valueFn({ - restrict: 'E', - transclude: true, - templateUrl: 'isoAsync', - scope: {} - })); - })); - - beforeEach(inject(function($templateCache) { - $templateCache.put('transAsync', '
'); - $templateCache.put('isoAsync', ''); - })); - - - it('should pass the outer scope to the transclude on the isolated template sync-sync', inject(function($compile, $rootScope) { - - $rootScope.val = 'transcluded content'; - element = $compile('')($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('transcluded content'); - })); - - it('should pass the outer scope to the transclude on the isolated template async-sync', inject(function($compile, $rootScope) { - - $rootScope.val = 'transcluded content'; - element = $compile('')($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('transcluded content'); - })); - - it('should pass the outer scope to the transclude on the isolated template async-async', inject(function($compile, $rootScope) { - - $rootScope.val = 'transcluded content'; - element = $compile('')($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('transcluded content'); - })); - - }); - - describe('multiple siblings receiving transclusion', function() { - - it("should only receive transclude from parent", function() { - - module(function($compileProvider) { - - $compileProvider.directive('myExample', valueFn({ - scope: {}, - link: function link(scope, element, attrs) { - var foo = element[0].querySelector('.foo'); - scope.children = angular.element(foo).children().length; - }, - template: '
' + - '
myExample {{children}}!
' + - '
has children
' + - '
' + - '
', - transclude: true - - })); - - }); - - inject(function($compile, $rootScope) { - var element = $compile('
')($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('myExample 0!'); - dealoc(element); - - element = $compile('

')($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('myExample 1!has children'); - dealoc(element); - }); - }); - }); - }); - - - describe('element transclusion', function() { - - it('should support basic element transclusion', function() { - module(function() { - directive('trans', function(log) { - return { - transclude: 'element', - priority: 2, - controller: function($transclude) { this.$transclude = $transclude; }, - compile: function(element, attrs, template) { - log('compile: ' + angular.mock.dump(element)); - return function(scope, element, attrs, ctrl) { - log('link'); - var cursor = element; - template(scope.$new(), function(clone) {cursor.after(cursor = clone);}); - ctrl.$transclude(function(clone) {cursor.after(clone);}); - }; - } - }; - }); - }); - inject(function(log, $rootScope, $compile) { - element = $compile('
{{$parent.$id}}-{{$id}};
') - ($rootScope); - $rootScope.$apply(); - expect(log).toEqual('compile: ; link; LOG; LOG; HIGH'); - expect(element.text()).toEqual('1-2;1-3;'); - }); - }); - - it('should only allow one element transclusion per element', function() { - module(function() { - directive('first', valueFn({ - transclude: 'element' - })); - directive('second', valueFn({ - transclude: 'element' - })); - }); - inject(function($compile) { - expect(function() { - $compile('
'); - }).toThrowMinErr('$compile', 'multidir', 'Multiple directives [first, second] asking for transclusion on: ' + - ''); - }); - }); - - - it('should only allow one element transclusion per element when directives have different priorities', function() { - // we restart compilation in this case and we need to remember the duplicates during the second compile - // regression #3893 - module(function() { - directive('first', valueFn({ - transclude: 'element', - priority: 100 - })); - directive('second', valueFn({ - transclude: 'element' - })); - }); - inject(function($compile) { - expect(function() { - $compile('
'); - }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second\] asking for transclusion on:
template.html

'); - $compile('
'); - expect(function() { - $httpBackend.flush(); - }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second\] asking for transclusion on:

before

after
').contents(); - expect(element.length).toEqual(3); - expect(nodeName_(element[1])).toBe('div'); - $compile(element)($rootScope); - expect(nodeName_(element[1])).toBe('#comment'); - expect(nodeName_(comment)).toBe('#comment'); - }); - }); - - - it('should terminate compilation only for element trasclusion', function() { - module(function() { - directive('elementTrans', function(log) { - return { - transclude: 'element', - priority: 50, - compile: log.fn('compile:elementTrans') - }; - }); - directive('regularTrans', function(log) { - return { - transclude: true, - priority: 50, - compile: log.fn('compile:regularTrans') - }; - }); - }); - inject(function(log, $compile, $rootScope) { - $compile('
')($rootScope); - expect(log).toEqual('compile:elementTrans; compile:regularTrans; regular'); - }); - }); - - - it('should instantiate high priority controllers only once, but low priority ones each time we transclude', - function() { - module(function() { - directive('elementTrans', function(log) { - return { - transclude: 'element', - priority: 50, - controller: function($transclude, $element) { - log('controller:elementTrans'); - $transclude(function(clone) { - $element.after(clone); - }); - $transclude(function(clone) { - $element.after(clone); - }); - $transclude(function(clone) { - $element.after(clone); - }); - } - }; - }); - directive('normalDir', function(log) { - return { - controller: function() { - log('controller:normalDir'); - } - }; - }); - }); - inject(function($compile, $rootScope, log) { - element = $compile('
')($rootScope); - expect(log).toEqual([ - 'controller:elementTrans', - 'controller:normalDir', - 'controller:normalDir', - 'controller:normalDir' - ]); - }); - }); - - it('should allow to access $transclude in the same directive', function() { - var _$transclude; - module(function() { - directive('transclude', valueFn({ - transclude: 'element', - controller: function($transclude) { - _$transclude = $transclude; - } - })); - }); - inject(function($compile) { - element = $compile('
')($rootScope); - expect(_$transclude).toBeDefined(); - }); - }); - - it('should copy the directive controller to all clones', function() { - var transcludeCtrl, cloneCount = 2; - module(function() { - directive('transclude', valueFn({ - transclude: 'element', - controller: function() { - transcludeCtrl = this; - }, - link: function(scope, el, attr, ctrl, $transclude) { - var i; - for (i=0; i
')($rootScope); - var children = element.children(), i; - for (i=0; i
')($rootScope); - expect(capturedTranscludeCtrl).toBeTruthy(); - }); - }); - - it('should allow access to $transclude in a templateUrl directive', function() { - var transclude; - module(function() { - directive('template', valueFn({ - templateUrl: 'template.html', - replace: true - })); - directive('transclude', valueFn({ - transclude: 'content', - controller: function($transclude) { - transclude = $transclude; - } - })); - }); - inject(function($compile, $httpBackend) { - $httpBackend.expectGET('template.html').respond('
'); - element = $compile('
')($rootScope); - $httpBackend.flush(); - expect(transclude).toBeDefined(); - }); - }); - - // issue #6006 - it('should link directive with $element as a comment node', function() { - module(function($provide) { - directive('innerAgain', function(log) { - return { - transclude: 'element', - link: function(scope, element, attr, controllers, transclude) { - log('innerAgain:'+lowercase(nodeName_(element))+':'+trim(element[0].data)); - transclude(scope, function(clone) { - element.parent().append(clone); - }); - } - }; - }); - directive('inner', function(log) { - return { - replace: true, - templateUrl: 'inner.html', - link: function(scope, element) { - log('inner:'+lowercase(nodeName_(element))+':'+trim(element[0].data)); - } - }; - }); - directive('outer', function(log) { - return { - transclude: 'element', - link: function(scope, element, attrs, controllers, transclude) { - log('outer:'+lowercase(nodeName_(element))+':'+trim(element[0].data)); - transclude(scope, function(clone) { - element.parent().append(clone); - }); - } - }; - }); - }); - inject(function(log, $compile, $rootScope, $templateCache) { - $templateCache.put('inner.html', '

Content

'); - element = $compile('
')($rootScope); - $rootScope.$digest(); - var child = element.children(); - - expect(log.toArray()).toEqual([ - "outer:#comment:outer:", - "innerAgain:#comment:innerAgain:", - "inner:#comment:innerAgain:" - ]); - expect(child.length).toBe(1); - expect(child.contents().length).toBe(2); - expect(lowercase(nodeName_(child.contents().eq(0)))).toBe('#comment'); - expect(lowercase(nodeName_(child.contents().eq(1)))).toBe('div'); - }); - }); - }); - - it('should safely create transclude comment node and not break with "-->"', - inject(function($rootScope) { - // see: https://github.com/angular/angular.js/issues/1740 - element = $compile('
  • {{item}}|
')($rootScope); - $rootScope.$digest(); - - expect(element.text()).toBe('-->|x|'); - })); - - - // See https://github.com/angular/angular.js/issues/7183 - it("should pass transclusion through to template of a 'replace' directive", function() { - module(function() { - directive('transSync', function() { - return { - transclude: true, - link: function(scope, element, attr, ctrl, transclude) { - - expect(transclude).toEqual(jasmine.any(Function)); - - transclude(function(child) { element.append(child); }); - } - }; - }); - - directive('trans', function($timeout) { - return { - transclude: true, - link: function(scope, element, attrs, ctrl, transclude) { - - // We use timeout here to simulate how ng-if works - $timeout(function() { - transclude(function(child) { element.append(child); }); - }); - } - }; - }); - - directive('replaceWithTemplate', function() { - return { - templateUrl: "template.html", - replace: true - }; - }); - }); - - inject(function($compile, $rootScope, $templateCache, $timeout) { - - $templateCache.put('template.html', '
Content To Be Transcluded
'); - - expect(function() { - element = $compile('
')($rootScope); - $timeout.flush(); - }).not.toThrow(); - - expect(element.text()).toEqual('Content To Be Transcluded'); - }); - - }); - }); - - - describe('img[src] sanitization', function() { - - it('should NOT require trusted values for img src', inject(function($rootScope, $compile, $sce) { - element = $compile('')($rootScope); - $rootScope.testUrl = 'http://example.com/image.png'; - $rootScope.$digest(); - expect(element.attr('src')).toEqual('http://example.com/image.png'); - // But it should accept trusted values anyway. - $rootScope.testUrl = $sce.trustAsUrl('http://example.com/image2.png'); - $rootScope.$digest(); - expect(element.attr('src')).toEqual('http://example.com/image2.png'); - })); - - it('should not sanitize attributes other than src', inject(function($compile, $rootScope) { - /* jshint scripturl:true */ - element = $compile('')($rootScope); - $rootScope.testUrl = "javascript:doEvilStuff()"; - $rootScope.$apply(); - - expect(element.attr('title')).toBe('javascript:doEvilStuff()'); - })); - - it('should use $$sanitizeUriProvider for reconfiguration of the src whitelist', function() { - module(function($compileProvider, $$sanitizeUriProvider) { - var newRe = /javascript:/, - returnVal; - expect($compileProvider.imgSrcSanitizationWhitelist()).toBe($$sanitizeUriProvider.imgSrcSanitizationWhitelist()); - - returnVal = $compileProvider.imgSrcSanitizationWhitelist(newRe); - expect(returnVal).toBe($compileProvider); - expect($$sanitizeUriProvider.imgSrcSanitizationWhitelist()).toBe(newRe); - expect($compileProvider.imgSrcSanitizationWhitelist()).toBe(newRe); - }); - inject(function() { - // needed to the module definition above is run... - }); - }); - - it('should use $$sanitizeUri', function() { - var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri'); - module(function($provide) { - $provide.value('$$sanitizeUri', $$sanitizeUri); - }); - inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - $rootScope.testUrl = "someUrl"; - - $$sanitizeUri.andReturn('someSanitizedUrl'); - $rootScope.$apply(); - expect(element.attr('src')).toBe('someSanitizedUrl'); - expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, true); - }); - }); - }); - - - describe('a[href] sanitization', function() { - - it('should not sanitize href on elements other than anchor', inject(function($compile, $rootScope) { - /* jshint scripturl:true */ - element = $compile('
')($rootScope); - $rootScope.testUrl = "javascript:doEvilStuff()"; - $rootScope.$apply(); - - expect(element.attr('href')).toBe('javascript:doEvilStuff()'); - })); - - it('should not sanitize attributes other than href', inject(function($compile, $rootScope) { - /* jshint scripturl:true */ - element = $compile('')($rootScope); - $rootScope.testUrl = "javascript:doEvilStuff()"; - $rootScope.$apply(); - - expect(element.attr('title')).toBe('javascript:doEvilStuff()'); - })); - - it('should use $$sanitizeUriProvider for reconfiguration of the href whitelist', function() { - module(function($compileProvider, $$sanitizeUriProvider) { - var newRe = /javascript:/, - returnVal; - expect($compileProvider.aHrefSanitizationWhitelist()).toBe($$sanitizeUriProvider.aHrefSanitizationWhitelist()); - - returnVal = $compileProvider.aHrefSanitizationWhitelist(newRe); - expect(returnVal).toBe($compileProvider); - expect($$sanitizeUriProvider.aHrefSanitizationWhitelist()).toBe(newRe); - expect($compileProvider.aHrefSanitizationWhitelist()).toBe(newRe); - }); - inject(function() { - // needed to the module definition above is run... - }); - }); - - it('should use $$sanitizeUri', function() { - var $$sanitizeUri = jasmine.createSpy('$$sanitizeUri'); - module(function($provide) { - $provide.value('$$sanitizeUri', $$sanitizeUri); - }); - inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - $rootScope.testUrl = "someUrl"; - - $$sanitizeUri.andReturn('someSanitizedUrl'); - $rootScope.$apply(); - expect(element.attr('href')).toBe('someSanitizedUrl'); - expect($$sanitizeUri).toHaveBeenCalledWith($rootScope.testUrl, false); - }); - }); - - }); - - describe('interpolation on HTML DOM event handler attributes onclick, onXYZ, formaction', function() { - it('should disallow interpolation on onclick', inject(function($compile, $rootScope) { - // All interpolations are disallowed. - $rootScope.onClickJs = ""; - expect(function() { - $compile('