')
($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 prevent multiple templates per element', inject(function($compile) {
try {
$compile('
')
fail();
} catch(e) {
expect(e.message).toMatch(/Multiple directives .* asking for template/);
}
}));
it('should play nice with repeater when inline', inject(function($compile, $rootScope) {
element = $compile(
'
')($rootScope);
$rootScope.$digest();
expect(element.text()).toEqual('Hello: 1; Hello: 2; ');
}));
it('should play nice with repeater when append', inject(function($compile, $rootScope) {
element = $compile(
'
')($rootScope);
$rootScope.$digest();
expect(element.text()).toEqual('Hello: 1; Hello: 2; ');
}));
});
describe('async templates', function() {
beforeEach(module(
function($compileProvider) {
$compileProvider.directive('hello', valueFn({ restrict: 'CAM', templateUrl: 'hello.html' }));
$compileProvider.directive('cau', valueFn({ restrict: 'CAM', templateUrl:'cau.html' }));
$compileProvider.directive('cError', valueFn({
restrict: 'CAM',
templateUrl:'error.html',
compile: function() {
throw Error('cError');
}
}));
$compileProvider.directive('lError', valueFn({
restrict: 'CAM',
templateUrl: 'error.html',
compile: function() {
throw Error('lError');
}
}));
$compileProvider.directive('iHello', valueFn({
restrict: 'CAM',
replace: true,
templateUrl: 'hello.html'
}));
$compileProvider.directive('iCau', valueFn({
restrict: 'CAM',
replace: true,
templateUrl:'cau.html'
}));
$compileProvider.directive('iCError', valueFn({
restrict: 'CAM',
replace: true,
templateUrl:'error.html',
compile: function() {
throw Error('cError');
}
}));
$compileProvider.directive('iLError', valueFn({
restrict: 'CAM',
replace: true,
templateUrl: 'error.html',
compile: function() {
throw Error('lError');
}
}));
}
));
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('
ignore ignore
')($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('
ignore ignore
')($rootScope);
expect(sortedHtml(element)).
toEqual('
');
$rootScope.$digest();
expect(sortedHtml(element)).
toEqual('
Cau!
');
$httpBackend.flush();
expect(sortedHtml(element)).
toEqual('
Hello! Cau!
');
}
));
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)).
toEqual('
Hello, Elvis!
');
}
));
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)).
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 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 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();
}).toThrow('Failed to load template: hello.html');
expect(sortedHtml(element)).toBe('
');
}
));
it('should prevent multiple templates per element', function() {
module(function($compileProvider) {
$compileProvider.directive('sync', valueFn({
restrict: 'C',
template: '
'
}));
$compileProvider.directive('async', valueFn({
restrict: 'C',
templateUrl: 'template.html'
}));
});
inject(function($compile){
expect(function() {
$compile('
');
}).toThrow('Multiple directives [sync, async] asking for template on: <'+
(msie <= 8 ? 'DIV' : 'div') + ' class="sync async">');
});
});
describe('delay compile / linking functions until after template is resolved', function(){
var template;
beforeEach(module(function($compileProvider) {
function directive (name, priority, options) {
$compileProvider.directive(name, function(log) {
return (extend({
priority: priority,
compile: function() {
log(name + '-C');
return function() { log(name + '-L'); }
}
}, options || {}));
});
}
directive('first', 10);
directive('second', 5, { templateUrl: 'second.html' });
directive('third', 3);
directive('last', 0);
directive('iFirst', 10, {replace: true});
directive('iSecond', 5, {replace: true, templateUrl: 'second.html' });
directive('iThird', 3, {replace: true});
directive('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; ' +
'third-L; first-L; second-L; last-L');
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-L; iSecond-L; iThird-L; iLast-L');
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; ' +
'third-L; first-L; second-L; last-L');
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-L; iSecond-L; iThird-L; iLast-L');
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 check that template has root element', inject(function($compile, $httpBackend) {
$httpBackend.expect('GET', 'hello.html').respond('before
mid after');
$compile('
');
expect(function(){
$httpBackend.flush();
}).toThrow('Template must have exactly one root element: before
mid after');
}));
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 widget is in 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 widget is 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;');
}
));
});
describe('scope', function() {
var iscope;
beforeEach(module(function($compileProvider) {
forEach(['', 'a', 'b'], function(name) {
$compileProvider.directive('scope' + uppercase(name), function(log) {
return {
scope: true,
restrict: 'CA',
compile: function() {
return function (scope, element) {
log(scope.$id);
expect(element.data('$scope')).toBe(scope);
};
}
};
});
$compileProvider.directive('iscope' + uppercase(name), function(log) {
return {
scope: {},
restrict: 'CA',
compile: function() {
return function (scope, element) {
iscope = scope;
log(scope.$id);
expect(element.data('$scope')).toBe(scope);
};
}
};
});
$compileProvider.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('$scope')).toBe(scope);
};
}
};
});
});
$compileProvider.directive('log', function(log) {
return {
restrict: 'CA',
link: function(scope) {
log('log-' + scope.$id + '-' + scope.$parent.$id);
}
};
});
}));
it('should allow creation of new scopes', inject(function($rootScope, $compile, log) {
element = $compile('
')($rootScope);
expect(log).toEqual('LOG; log-002-001; 002');
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; log-002-001; 002');
$rootScope.name = 'abc';
expect(iscope.$parent).toBe($rootScope);
expect(iscope.name).toBeUndefined();
}));
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; log-002-001; 002');
$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('LOG; log-003-002; 003; LOG; log-002-001; 002; LOG; log-004-001; 004');
})
);
it('should allow more then one scope creation per element', inject(
function($rootScope, $compile, log) {
$compile('
')($rootScope);
expect(log).toEqual('001; 001');
})
);
it('should not allow more then one isolate scope creation per element', inject(
function($rootScope, $compile) {
expect(function(){
$compile('
');
}).toThrow('Multiple directives [iscopeA, scopeB] asking for isolated scope on: ' +
'<' + (msie < 9 ? 'DIV' : 'div') +
' class="iscope-a; scope-b ng-isolate-scope ng-scope">');
})
);
it('should not allow more then one isolate scope creation per element', inject(
function($rootScope, $compile) {
expect(function(){
$compile('
');
}).toThrow('Multiple directives [iscopeA, iscopeB] asking for isolated scope on: ' +
'<' + (msie < 9 ? 'DIV' : 'div') +
' class="iscope-a; iscope-b ng-isolate-scope ng-scope">');
})
);
it('should treat new scope on new template as noop', inject(
function($rootScope, $compile, log) {
element = $compile('
')($rootScope);
expect(log).toEqual('001');
})
);
});
});
});
describe('interpolation', function() {
var observeSpy, attrValueDuringLinking;
beforeEach(module(function($compileProvider) {
$compileProvider.directive('observer', function() {
return function(scope, elm, attr) {
observeSpy = jasmine.createSpy('$observe attr');
attr.$observe('someAttr', observeSpy);
attrValueDuringLinking = attr.someAttr;
};
});
}));
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 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 set interpolated attrs to undefined', inject(function($rootScope, $compile) {
attrValueDuringLinking = null;
$compile('
')($rootScope);
expect(attrValueDuringLinking).toBeUndefined();
}));
it('should not call observer of non-interpolated attr', inject(function($rootScope, $compile) {
$compile('
')($rootScope);
expect(attrValueDuringLinking).toBe('nonBound');
$rootScope.$digest();
expect(observeSpy).not.toHaveBeenCalled();
}));
it('should delegate exceptions to $exceptionHandler', function() {
observeSpy = jasmine.createSpy('$observe attr').andThrow('ERROR');
module(function($compileProvider, $exceptionHandlerProvider) {
$exceptionHandlerProvider.mode('log');
$compileProvider.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('
Greet {{name}}! ')($rootScope)
$rootScope.$digest();
expect(sortedHtml(element).replace(' selected="true"', '')).
toEqual('
' +
'Greet ! ' +
' ');
$rootScope.name = 'Misko';
$rootScope.$digest();
expect(sortedHtml(element).replace(' selected="true"', '')).
toEqual('
' +
'Greet Misko! ' +
' ');
}));
});
describe('link phase', function() {
beforeEach(module(function($compileProvider) {
forEach(['a', 'b', 'c'], function(name) {
$compileProvider.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; postA; postB');
}
));
it('should support link function on directive object', function() {
module(function($compileProvider) {
$compileProvider.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');
});
});
});
describe('attrs', function() {
it('should allow setting of attributes', function() {
module(function($compileProvider) {
$compileProvider.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($compileProvider) {
$compileProvider.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($compileProvider) {
$compileProvider.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 allow setting of attributes', function() {
module(function($compileProvider) {
$compileProvider.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 create new instance of attr for each template stamping', function() {
module(function($compileProvider, $provide) {
var state = { first: [], second: [] };
$provide.value('state', state);
$compileProvider.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);
});
});
describe('$set', function() {
var attr;
beforeEach(function(){
module(function($compileProvider) {
$compileProvider.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', '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 set boolean attributes', function() {
attr.$set('disabled', 'true');
attr.$set('readOnly', 'true');
expect(element.attr('disabled')).toEqual('disabled');
expect(element.attr('readonly')).toEqual('readonly');
attr.$set('disabled', 'false');
expect(element.attr('disabled')).toEqual(undefined);
attr.$set('disabled', false);
attr.$set('readOnly', false);
expect(element.attr('disabled')).toEqual(undefined);
expect(element.attr('readonly')).toEqual(undefined);
});
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);
})
});
});
describe('locals', function() {
it('should marshal to locals', function() {
module(function($compileProvider) {
$compileProvider.directive('widget', function(log) {
return {
scope: {
attr: 'attribute',
prop: 'evaluate',
bind: 'bind',
assign: 'accessor',
read: 'accessor',
exp: 'expression',
nonExist: 'accessor',
nonExistExpr: 'expression'
},
link: function(scope, element, attrs) {
scope.nonExist(); // noop
scope.nonExist(123); // noop
scope.nonExistExpr(); // noop
scope.nonExistExpr(123); // noop
log(scope.attr);
log(scope.prop);
log(scope.assign());
log(scope.read());
log(scope.assign('ng'));
scope.exp({myState:'OK'});
expect(function() { scope.read(undefined); }).
toThrow("Expression ''D'' not assignable.");
scope.$watch('bind', log);
}
};
});
});
inject(function(log, $compile, $rootScope) {
$rootScope.myProp = 'B';
$rootScope.bi = {nd: 'C'};
$rootScope.name = 'C';
element = $compile(
'
')
($rootScope);
expect(log).toEqual('A; B; C; D; ng');
expect($rootScope.name).toEqual('ng');
expect($rootScope.state).toEqual('OK');
log.reset();
$rootScope.$apply();
expect(element.text()).toEqual('C');
expect(log).toEqual('C');
$rootScope.bi.nd = 'c';
$rootScope.$apply();
expect(log).toEqual('C; c');
});
});
});
describe('controller', function() {
it('should inject locals to controller', function() {
module(function($compileProvider) {
$compileProvider.directive('widget', function(log) {
return {
controller: function(attr, prop, assign, read, exp){
log(attr);
log(prop);
log(assign());
log(read());
log(assign('ng'));
exp();
expect(function() { read(undefined); }).
toThrow("Expression ''D'' not assignable.");
this.result = 'OK';
},
inject: {
attr: 'attribute',
prop: 'evaluate',
assign: 'accessor',
read: 'accessor',
exp: 'expression'
},
link: function(scope, element, attrs, controller) {
log(controller.result);
}
};
});
});
inject(function(log, $compile, $rootScope) {
$rootScope.myProp = 'B';
$rootScope.bi = {nd: 'C'};
$rootScope.name = 'C';
element = $compile(
'
')
($rootScope);
expect(log).toEqual('A; B; C; D; ng; OK');
expect($rootScope.name).toEqual('ng');
});
});
it('should get required controller', function() {
module(function($compileProvider) {
$compileProvider.directive('main', function(log) {
return {
priority: 2,
controller: function() {
this.name = 'main';
},
link: function(scope, element, attrs, controller) {
log(controller.name);
}
};
});
$compileProvider.directive('dep', function(log) {
return {
priority: 1,
require: 'main',
link: function(scope, element, attrs, controller) {
log('dep:' + controller.name);
}
};
});
$compileProvider.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('main; dep:main; false');
});
});
it('should require controller on parent element',function() {
module(function($compileProvider) {
$compileProvider.directive('main', function(log) {
return {
controller: function() {
this.name = 'main';
}
};
});
$compileProvider.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 have optional controller on current element', function() {
module(function($compileProvider) {
$compileProvider.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($compileProvider) {
$compileProvider.directive('c1', valueFn({
controller: function() { this.name = 'c1'; }
}));
$compileProvider.directive('c2', valueFn({
controller: function() { this.name = 'c2'; }
}));
$compileProvider.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');
});
});
});
describe('transclude', function() {
it('should compile get templateFn', function() {
module(function($compileProvider) {
$compileProvider.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('
')
($rootScope);
$rootScope.$apply();
expect(log).toEqual('compile: ; HIGH; link; LOG; LOG');
expect(element.text()).toEqual('001-002;001-003;');
});
});
it('should support transclude directive', function() {
module(function($compileProvider) {
$compileProvider.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:001-002;T:001-003;');
expect(jqLite(element.find('span')[0]).text()).toEqual('T:001-003');
expect(jqLite(element.find('span')[1]).text()).toEqual(';');
});
});
it('should transclude transcluded content', function() {
module(function($compileProvider) {
$compileProvider.directive('book', valueFn({
transclude: 'content',
template: '
'
}));
$compileProvider.directive('chapter', valueFn({
transclude: 'content',
templateUrl: 'chapter.html'
}));
$compileProvider.directive('section', valueFn({
transclude: 'content',
template: '
'
}));
return function($httpBackend) {
$httpBackend.
expect('GET', 'chapter.html').
respond('
');
}
});
inject(function(log, $rootScope, $compile, $httpBackend) {
element = $compile('
')($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 transclude per element', function() {
module(function($compileProvider) {
$compileProvider.directive('first', valueFn({
scope: {},
restrict: 'CA',
transclude: 'content'
}));
$compileProvider.directive('second', valueFn({
restrict: 'CA',
transclude: 'content'
}));
});
inject(function($compile) {
expect(function() {
$compile('
');
}).toThrow('Multiple directives [first, second] asking for transclusion on: <' +
(msie <= 8 ? 'DIV' : 'div') + ' class="first second ng-isolate-scope ng-scope">');
});
});
it('should remove transclusion scope, when the DOM is destroyed', function() {
module(function($compileProvider) {
$compileProvider.directive('box', valueFn({
transclude: 'content',
scope: { name: 'evaluate', show: 'accessor' },
template: '
',
link: function(scope, element) {
scope.$watch(
function() { return scope.show(); },
function(show) {
if (!show) {
element.find('div').find('div').remove();
}
}
);
}
}));
});
inject(function($compile, $rootScope) {
$rootScope.username = 'Misko';
$rootScope.select = true;
element = $compile(
'
')
($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);
});
});
});
});