');
+
+ $compile(el)($rootScope);
+
+ expect(el.find('[my-double]').length).toBe(2);
+ });
+ });
+
+ it('sets directive attributes element to comment', function() {
+ var injector = makeInjectorWithDirectives({
+ myTranscluder: function() {
+ return {
+ transclude: 'element',
+ link: function(scope, element, attrs, ctrl, transclude) {
+ attrs.$set('testing', '42');
+ element.after(transclude());
+ }
+ };
+ }
+ });
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
');
+
+ $compile(el)($rootScope);
+
+ expect(el.find('[my-transcluder]').attr('testing')).toBeUndefined();
+ });
+ });
+
+ it('supports requiring controllers', function() {
+ var MyController = function() { };
+ var gotCtrl;
+ var injector = makeInjectorWithDirectives({
+ myCtrlDirective: function() {
+ return {controller: MyController};
+ },
+ myTranscluder: function() {
+ return {
+ transclude: 'element',
+ link: function(scope, el, attrs, ctrl, transclude) {
+ el.after(transclude());
+ }
+ };
+ },
+ myOtherDirective: function() {
+ return {
+ require: '^myCtrlDirective',
+ link: function(scope, el, attrs, ctrl, transclude) {
+ gotCtrl = ctrl;
+ }
+ };
+ }
+ });
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
');
+
+ $compile(el)($rootScope);
+
+ expect(gotCtrl).toBeDefined();
+ expect(gotCtrl instanceof MyController).toBe(true);
+ });
+ });
+
+ });
+
+ describe('interpolation', function() {
+
+ it('is done for text nodes', function() {
+ var injector = makeInjectorWithDirectives({});
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
My expression: {{myExpr}}
');
+ $compile(el)($rootScope);
+
+ $rootScope.$apply();
+ expect(el.html()).toEqual('My expression: ');
+
+ $rootScope.myExpr = 'Hello';
+ $rootScope.$apply();
+ expect(el.html()).toEqual('My expression: Hello');
+ });
+ });
+
+ it('adds binding class to text node parents', function() {
+ var injector = makeInjectorWithDirectives({});
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
My expression: {{myExpr}}
');
+ $compile(el)($rootScope);
+
+ expect(el.hasClass('ng-binding')).toBe(true);
+ });
+ });
+
+ it('adds binding data to text node parents', function() {
+ var injector = makeInjectorWithDirectives({});
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
{{myExpr}} and {{myOtherExpr}}
');
+ $compile(el)($rootScope);
+
+ expect(el.data('$binding')).toEqual(['myExpr', 'myOtherExpr']);
+ });
+ });
+
+ it('adds binding data to parent from multiple text nodes', function() {
+ var injector = makeInjectorWithDirectives({});
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
{{myExpr}} and {{myOtherExpr}}
');
+ $compile(el)($rootScope);
+
+ expect(el.data('$binding')).toEqual(['myExpr', 'myOtherExpr']);
+ });
+ });
+
+ it('is done for attributes', function() {
+ var injector = makeInjectorWithDirectives({});
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
');
+ $compile(el)($rootScope);
+
+ $rootScope.$apply();
+ expect(el.attr('alt')).toEqual('');
+
+ $rootScope.myAltText = 'My favourite photo';
+ $rootScope.$apply();
+ expect(el.attr('alt')).toEqual('My favourite photo');
+ });
+ });
+
+ it('fires observers on attribute expression changes', function() {
+ var observerSpy = jasmine.createSpy();
+ var injector = makeInjectorWithDirectives({
+ myDirective: function() {
+ return {
+ link: function(scope, element, attrs) {
+ attrs.$observe('alt', observerSpy);
+ }
+ };
+ }
+ });
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
');
+ $compile(el)($rootScope);
+
+ $rootScope.myAltText = 'My favourite photo';
+ $rootScope.$apply();
+ expect(observerSpy.calls.mostRecent().args[0]).toEqual('My favourite photo');
+ });
+ });
+
+ it('fires observers just once upon registration', function() {
+ var observerSpy = jasmine.createSpy();
+ var injector = makeInjectorWithDirectives({
+ myDirective: function() {
+ return {
+ link: function(scope, element, attrs) {
+ attrs.$observe('alt', observerSpy);
+ }
+ };
+ }
+ });
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
');
+ $compile(el)($rootScope);
+ $rootScope.$apply();
+
+ expect(observerSpy.calls.count()).toBe(1);
+ });
+ });
+
+ it('is done for attributes by the time other directive is linked', function() {
+ var gotMyAttr;
+ var injector = makeInjectorWithDirectives({
+ myDirective: function() {
+ return {
+ link: function(scope, element, attrs) {
+ gotMyAttr = attrs.myAttr;
+ }
+ };
+ }
+ });
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
');
+ $rootScope.myExpr = 'Hello';
+ $compile(el)($rootScope);
+
+ expect(gotMyAttr).toEqual('Hello');
+ });
+ });
+
+ it('is done for attributes by the time bound to iso scope', function() {
+ var gotMyAttr;
+ var injector = makeInjectorWithDirectives({
+ myDirective: function() {
+ return {
+ scope: {myAttr: '@'},
+ link: function(scope, element, attrs) {
+ gotMyAttr = scope.myAttr;
+ }
+ };
+ }
+ });
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
');
+ $rootScope.myExpr = 'Hello';
+ $compile(el)($rootScope);
+
+ expect(gotMyAttr).toEqual('Hello');
+ });
+ });
+
+ it('is done for attributes so that changes during compile are reflected', function() {
+ var injector = makeInjectorWithDirectives({
+ myDirective: function() {
+ return {
+ compile: function(element, attrs) {
+ attrs.$set('myAttr', '{{myDifferentExpr}}');
+ }
+ };
+ }
+ });
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
');
+ $rootScope.myExpr = 'Hello';
+ $rootScope.myDifferentExpr = 'Other Hello';
+ $compile(el)($rootScope);
+ $rootScope.$apply();
+
+ expect(el.attr('my-attr')).toEqual('Other Hello');
+ });
+ });
+
+ it('is done for attributes so that removal during compile is reflected', function() {
+ var injector = makeInjectorWithDirectives({
+ myDirective: function() {
+ return {
+ compile: function(element, attrs) {
+ attrs.$set('myAttr', null);
+ }
+ };
+ }
+ });
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
');
+ $rootScope.myExpr = 'Hello';
+ $compile(el)($rootScope);
+ $rootScope.$apply();
+
+ expect(el.attr('my-attr')).toBeFalsy();
+ });
+ });
+
+ it('cannot be done for event handler attributes', function() {
+ var injector = makeInjectorWithDirectives({});
+ injector.invoke(function($compile, $rootScope) {
+ $rootScope.myFunction = function() { };
+ var el = $('
');
+ expect(function() {
+ $compile(el)($rootScope);
+ }).toThrow();
+ });
+ });
+
+ it('denormalizes directive templates', function() {
+ var injector = createInjector(['ng', function($interpolateProvider, $compileProvider) {
+ $interpolateProvider.startSymbol('[[').endSymbol(']]');
+ $compileProvider.directive('myDirective', function() {
+ return {
+ template: 'Value is {{myExpr}}'
+ };
+ });
+ }]);
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
');
+ $rootScope.myExpr = 42;
+ $compile(el)($rootScope);
+ $rootScope.$apply();
+
+ expect(el.html()).toEqual('Value is 42');
+ });
+ });
+
});
});
diff --git a/test/directives/ng_click_spec.js b/test/directives/ng_click_spec.js
new file mode 100644
index 00000000..98729fc4
--- /dev/null
+++ b/test/directives/ng_click_spec.js
@@ -0,0 +1,51 @@
+'use strict';
+
+var $ = require('jquery');
+var publishExternalAPI = require('../../src/angular_public');
+var createInjector = require('../../src/injector');
+
+describe('ngClick', function() {
+
+ var $compile, $rootScope;
+
+ beforeEach(function() {
+ delete window.angular;
+ publishExternalAPI();
+ var injector = createInjector(['ng']);
+ $compile = injector.get('$compile');
+ $rootScope = injector.get('$rootScope');
+ });
+
+ it('starts a digest on click', function() {
+ var watchSpy = jasmine.createSpy();
+ $rootScope.$watch(watchSpy);
+
+ var button = $('
');
+ $compile(button)($rootScope);
+
+ button.click();
+ expect(watchSpy).toHaveBeenCalled();
+ });
+
+ it('evaluates given expression on click', function() {
+ $rootScope.doSomething = jasmine.createSpy();
+ var button = $('
');
+ $compile(button)($rootScope);
+
+ button.click();
+ expect($rootScope.doSomething).toHaveBeenCalled();
+ });
+
+ it('passes $event to expression', function() {
+ $rootScope.doSomething = jasmine.createSpy();
+ var button = $('
');
+ $compile(button)($rootScope);
+
+ button.click();
+ var evt = $rootScope.doSomething.calls.mostRecent().args[0];
+ expect(evt).toBeDefined();
+ expect(evt.type).toBe('click');
+ expect(evt.target).toBeDefined();
+ });
+
+});
diff --git a/test/directives/ng_transclude_spec.js b/test/directives/ng_transclude_spec.js
new file mode 100644
index 00000000..f0f087ae
--- /dev/null
+++ b/test/directives/ng_transclude_spec.js
@@ -0,0 +1,69 @@
+'use strict';
+
+var $ = require('jquery');
+var publishExternalAPI = require('../../src/angular_public');
+var createInjector = require('../../src/injector');
+
+describe('ngTransclude', function() {
+
+ beforeEach(function() {
+ delete window.angular;
+ publishExternalAPI();
+ });
+
+ function createInjectorWithTranscluderTemplate(template) {
+ return createInjector(['ng', function($compileProvider) {
+ $compileProvider.directive('myTranscluder', function() {
+ return {
+ transclude: true,
+ template: template
+ };
+ });
+ }]);
+ }
+
+ it('transcludes the parent directive transclusion', function() {
+ var injector = createInjectorWithTranscluderTemplate(
+ '
'
+ );
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
Hello
');
+ $compile(el)($rootScope);
+ expect(el.find('> [ng-transclude]').html()).toEqual('Hello');
+ });
+ });
+
+ it('empties existing contents', function() {
+ var injector = createInjectorWithTranscluderTemplate(
+ '
Existing contents
'
+ );
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
Hello
');
+ $compile(el)($rootScope);
+ expect(el.find('> [ng-transclude]').html()).toEqual('Hello');
+ });
+ });
+
+ it('may be used as element', function() {
+ var injector = createInjectorWithTranscluderTemplate(
+ '
Existing contents'
+ );
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
Hello
');
+ $compile(el)($rootScope);
+ expect(el.find('> ng-transclude').html()).toEqual('Hello');
+ });
+ });
+
+ it('may be used as class', function() {
+ var injector = createInjectorWithTranscluderTemplate(
+ '
Existing contents
'
+ );
+ injector.invoke(function($compile, $rootScope) {
+ var el = $('
Hello
');
+ $compile(el)($rootScope);
+ expect(el.find('> .ng-transclude').html()).toEqual('Hello');
+ });
+ });
+
+});
diff --git a/test/interpolate_spec.js b/test/interpolate_spec.js
new file mode 100644
index 00000000..fe809f2b
--- /dev/null
+++ b/test/interpolate_spec.js
@@ -0,0 +1,187 @@
+'use strict';
+
+var publishExternalAPI = require('../src/angular_public');
+var createInjector = require('../src/injector');
+
+describe('$interpolate', function() {
+
+ beforeEach(function() {
+ delete window.angular;
+ publishExternalAPI();
+ });
+
+ it('should exist', function() {
+ var injector = createInjector(['ng']);
+ expect(injector.has('$interpolate')).toBe(true);
+ });
+
+ it('produces an identity function for static content', function() {
+ var injector = createInjector(['ng']);
+ var $interpolate = injector.get('$interpolate');
+
+ var interp = $interpolate('hello');
+ expect(interp instanceof Function).toBe(true);
+ expect(interp()).toEqual('hello');
+ });
+
+ it('evaluates a single expression', function() {
+ var injector = createInjector(['ng']);
+ var $interpolate = injector.get('$interpolate');
+
+ var interp = $interpolate('{{anAttr}}');
+ expect(interp({anAttr: '42'})).toEqual('42');
+ });
+
+ it('evaluates many expressions', function() {
+ var injector = createInjector(['ng']);
+ var $interpolate = injector.get('$interpolate');
+
+ var interp = $interpolate('First {{anAttr}}, then {{anotherAttr}}!');
+ expect(interp({anAttr: '42', anotherAttr: '43'})).toEqual('First 42, then 43!');
+ });
+
+ it('passes through ill-defined interpolations', function() {
+ var injector = createInjector(['ng']);
+ var $interpolate = injector.get('$interpolate');
+
+ var interp = $interpolate('why u no }}work{{');
+ expect(interp({})).toEqual('why u no }}work{{');
+ });
+
+ it('turns nulls into empty strings', function() {
+ var injector = createInjector(['ng']);
+ var $interpolate = injector.get('$interpolate');
+
+ var interp = $interpolate('{{aNull}}');
+ expect(interp({aNull: null})).toEqual('');
+ });
+
+ it('turns undefineds into empty strings', function() {
+ var injector = createInjector(['ng']);
+ var $interpolate = injector.get('$interpolate');
+
+ var interp = $interpolate('{{anUndefined}}');
+ expect(interp({})).toEqual('');
+ });
+
+ it('turns numbers into strings', function() {
+ var injector = createInjector(['ng']);
+ var $interpolate = injector.get('$interpolate');
+
+ var interp = $interpolate('{{aNumber}}');
+ expect(interp({aNumber: 42})).toEqual('42');
+ });
+
+ it('turns booleans into strings', function() {
+ var injector = createInjector(['ng']);
+ var $interpolate = injector.get('$interpolate');
+
+ var interp = $interpolate('{{aBoolean}}');
+ expect(interp({aBoolean: true})).toEqual('true');
+ });
+
+ it('turns arrays into JSON strings', function() {
+ var injector = createInjector(['ng']);
+ var $interpolate = injector.get('$interpolate');
+
+ var interp = $interpolate('{{anArray}}');
+ expect(interp({anArray: [1, 2, [3]]})).toEqual('[1,2,[3]]');
+ });
+
+ it('turns objects into JSON strings', function() {
+ var injector = createInjector(['ng']);
+ var $interpolate = injector.get('$interpolate');
+
+ var interp = $interpolate('{{anObject}}');
+ expect(interp({anObject: {a: 1, b: '2'}})).toEqual('{"a":1,"b":"2"}');
+ });
+
+ it('unescapes escaped sequences', function() {
+ var injector = createInjector(['ng']);
+ var $interpolate = injector.get('$interpolate');
+
+ var interp = $interpolate('\\{\\{expr\\}\\} {{expr}} \\{\\{expr\\}\\}');
+ expect(interp({expr: 'value'})).toEqual('{{expr}} value {{expr}}');
+ });
+
+ it('does not return function for when flagged and no expressions', function() {
+ var injector = createInjector(['ng']);
+ var $interpolate = injector.get('$interpolate');
+
+ var interp = $interpolate('static content only', true);
+ expect(interp).toBeFalsy();
+ });
+
+ it('returns function when flagged and has expressions', function() {
+ var injector = createInjector(['ng']);
+ var $interpolate = injector.get('$interpolate');
+
+ var interp = $interpolate('has an {{expr}}', true);
+ expect(interp).not.toBeFalsy();
+ });
+
+ it('uses a watch delegate', function() {
+ var injector = createInjector(['ng']);
+ var $interpolate = injector.get('$interpolate');
+ var interp = $interpolate('has an {{expr}}');
+ expect(interp.$$watchDelegate).toBeDefined();
+ });
+
+ it('correctly returns new and old value when watched', function() {
+ var injector = createInjector(['ng']);
+ var $interpolate = injector.get('$interpolate');
+ var $rootScope = injector.get('$rootScope');
+
+ var interp = $interpolate('{{expr}}');
+ var listenerSpy = jasmine.createSpy();
+
+ $rootScope.$watch(interp, listenerSpy);
+ $rootScope.expr = 42;
+
+ $rootScope.$apply();
+ expect(listenerSpy.calls.mostRecent().args[0]).toEqual('42');
+ expect(listenerSpy.calls.mostRecent().args[1]).toEqual('42');
+
+ $rootScope.expr++;
+ $rootScope.$apply();
+ expect(listenerSpy.calls.mostRecent().args[0]).toEqual('43');
+ expect(listenerSpy.calls.mostRecent().args[1]).toEqual('42');
+ });
+
+ it('allows configuring start and end symbols', function() {
+ var injector = createInjector(['ng', function($interpolateProvider) {
+ $interpolateProvider.startSymbol('FOO').endSymbol('OOF');
+ }]);
+ var $interpolate = injector.get('$interpolate');
+ expect($interpolate.startSymbol()).toEqual('FOO');
+ expect($interpolate.endSymbol()).toEqual('OOF');
+ });
+
+ it('works with start and end symbols that differ from default', function() {
+ var injector = createInjector(['ng', function($interpolateProvider) {
+ $interpolateProvider.startSymbol('FOO').endSymbol('OOF');
+ }]);
+ var $interpolate = injector.get('$interpolate');
+ var interpFn = $interpolate('FOOmyExprOOF');
+ expect(interpFn({myExpr: 42})).toEqual('42');
+ });
+
+ it('does not work with default start and end symbols when reconfigured', function() {
+ var injector = createInjector(['ng', function($interpolateProvider) {
+ $interpolateProvider.startSymbol('FOO').endSymbol('OOF');
+ }]);
+ var $interpolate = injector.get('$interpolate');
+ var interpFn = $interpolate('{{myExpr}}');
+ expect(interpFn({myExpr: 42})).toEqual('{{myExpr}}');
+ });
+
+ it('supports unescaping for reconfigured symbols', function() {
+ var injector = createInjector(['ng', function($interpolateProvider) {
+ $interpolateProvider.startSymbol('FOO').endSymbol('OOF');
+ }]);
+ var $interpolate = injector.get('$interpolate');
+ var interpFn = $interpolate('\\F\\O\\OmyExpr\\O\\O\\F');
+ expect(interpFn({})).toEqual('FOOmyExprOOF');
+ });
+
+});