diff --git a/src/ng/compile.js b/src/ng/compile.js index 92e271e44732..7c89444fcb5a 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1372,36 +1372,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { return debugInfoEnabled; }; - /** - * @ngdoc method - * @name $compileProvider#preAssignBindingsEnabled - * - * @param {boolean=} enabled update the preAssignBindingsEnabled state if provided, otherwise just return the - * current preAssignBindingsEnabled state - * @returns {*} current value if used as getter or itself (chaining) if used as setter - * - * @kind function - * - * @description - * Call this method to enable/disable whether directive controllers are assigned bindings before - * calling the controller's constructor. - * If enabled (true), the compiler assigns the value of each of the bindings to the - * properties of the controller object before the constructor of this object is called. - * - * If disabled (false), the compiler calls the constructor first before assigning bindings. - * - * The default value is true in AngularJS 1.5.x but will switch to false in AngularJS 1.6.x. - */ - var preAssignBindingsEnabled = false; - this.preAssignBindingsEnabled = function(enabled) { - if (isDefined(enabled)) { - preAssignBindingsEnabled = enabled; - return this; - } - return preAssignBindingsEnabled; - }; - - var TTL = 10; /** * @ngdoc method @@ -2722,33 +2692,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var controller = elementControllers[name]; var bindings = controllerDirective.$$bindings.bindToController; - if (preAssignBindingsEnabled) { - if (bindings) { - controller.bindingInfo = - initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); - } else { - controller.bindingInfo = {}; - } - - var controllerResult = controller(); - if (controllerResult !== controller.instance) { - // If the controller constructor has a return value, overwrite the instance - // from setupControllers - controller.instance = controllerResult; - $element.data('$' + controllerDirective.name + 'Controller', controllerResult); - if (controller.bindingInfo.removeWatches) { - controller.bindingInfo.removeWatches(); - } - controller.bindingInfo = - initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); - } - } else { - controller.instance = controller(); - $element.data('$' + controllerDirective.name + 'Controller', controller.instance); - controller.bindingInfo = - initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); + controller.instance = controller(); + $element.data('$' + controllerDirective.name + 'Controller', controller.instance); + controller.bindingInfo = + initializeDirectiveBindings(controllerScope, attrs, controller.instance, bindings, controllerDirective); } - } // Bind the required controllers to the controller, if `require` is an object and `bindToController` is truthy forEach(controllerDirectives, function(controllerDirective, name) { diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index 70adeb8f7843..6d3337fe9d98 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -2207,11 +2207,6 @@ angular.mock.$RootElementProvider = function() { * A decorator for {@link ng.$controller} with additional `bindings` parameter, useful when testing * controllers of directives that use {@link $compile#-bindtocontroller- `bindToController`}. * - * Depending on the value of - * {@link ng.$compileProvider#preAssignBindingsEnabled `preAssignBindingsEnabled()`}, the properties - * will be bound before or after invoking the constructor. - * - * * ## Example * * ```js @@ -2267,22 +2262,13 @@ angular.mock.$RootElementProvider = function() { * the `bindToController` feature and simplify certain kinds of tests. * @return {Object} Instance of given controller. */ -function createControllerDecorator(compileProvider) { +function createControllerDecorator() { angular.mock.$ControllerDecorator = ['$delegate', function($delegate) { return function(expression, locals, later, ident) { if (later && typeof later === 'object') { - var preAssignBindingsEnabled = compileProvider.preAssignBindingsEnabled(); - var instantiate = $delegate(expression, locals, true, ident); - if (preAssignBindingsEnabled) { - angular.extend(instantiate.instance, later); - } - var instance = instantiate(); - if (!preAssignBindingsEnabled || instance !== instantiate.instance) { - angular.extend(instance, later); - } - + angular.extend(instance, later); return instance; } return $delegate(expression, locals, later, ident); diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 84a5b8810f6f..8422fc767139 100644 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -169,17 +169,6 @@ describe('$compile', function() { inject(); }); - it('should allow preAssignBindingsEnabled to be configured', function() { - module(function($compileProvider) { - expect($compileProvider.preAssignBindingsEnabled()).toBe(false); // the default - $compileProvider.preAssignBindingsEnabled(true); - expect($compileProvider.preAssignBindingsEnabled()).toBe(true); - $compileProvider.preAssignBindingsEnabled(false); - expect($compileProvider.preAssignBindingsEnabled()).toBe(false); - }); - inject(); - }); - it('should allow onChangesTtl to be configured', function() { module(function($compileProvider) { expect($compileProvider.onChangesTtl()).toBe(10); // the default @@ -3937,6524 +3926,6412 @@ describe('$compile', function() { }); }); - forEach([true, false], function(preAssignBindingsEnabled) { - describe((preAssignBindingsEnabled ? 'with' : 'without') + ' pre-assigned bindings', function() { - beforeEach(module(function($compileProvider) { - $compileProvider.preAssignBindingsEnabled(preAssignBindingsEnabled); - })); - - describe('controller lifecycle hooks', function() { + describe('controller lifecycle hooks', function() { - describe('$onInit', function() { + describe('$onInit', function() { - it('should call `$onInit`, if provided, after all the controllers on the element have been initialized', function() { + it('should call `$onInit`, if provided, after all the controllers on the element have been initialized', function() { - function check() { - expect(this.element.controller('d1').id).toEqual(1); - expect(this.element.controller('d2').id).toEqual(2); - } + function check() { + expect(this.element.controller('d1').id).toEqual(1); + expect(this.element.controller('d2').id).toEqual(2); + } - function Controller1($element) { this.id = 1; this.element = $element; } - Controller1.prototype.$onInit = jasmine.createSpy('$onInit').and.callFake(check); + function Controller1($element) { this.id = 1; this.element = $element; } + Controller1.prototype.$onInit = jasmine.createSpy('$onInit').and.callFake(check); - function Controller2($element) { this.id = 2; this.element = $element; } - Controller2.prototype.$onInit = jasmine.createSpy('$onInit').and.callFake(check); + function Controller2($element) { this.id = 2; this.element = $element; } + Controller2.prototype.$onInit = jasmine.createSpy('$onInit').and.callFake(check); - angular.module('my', []) - .directive('d1', valueFn({ controller: Controller1 })) - .directive('d2', valueFn({ controller: Controller2 })); + angular.module('my', []) + .directive('d1', valueFn({ controller: Controller1 })) + .directive('d2', valueFn({ controller: Controller2 })); - module('my'); - inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); - expect(Controller1.prototype.$onInit).toHaveBeenCalledOnce(); - expect(Controller2.prototype.$onInit).toHaveBeenCalledOnce(); - }); - }); + module('my'); + inject(function($compile, $rootScope) { + element = $compile('
')($rootScope); + expect(Controller1.prototype.$onInit).toHaveBeenCalledOnce(); + expect(Controller2.prototype.$onInit).toHaveBeenCalledOnce(); + }); + }); - it('should continue to trigger other `$onInit` hooks if one throws an error', function() { - function ThrowingController() { - this.$onInit = function() { - throw new Error('bad hook'); - }; - } - function LoggingController($log) { - this.$onInit = function() { - $log.info('onInit'); - }; - } + it('should continue to trigger other `$onInit` hooks if one throws an error', function() { + function ThrowingController() { + this.$onInit = function() { + throw new Error('bad hook'); + }; + } + function LoggingController($log) { + this.$onInit = function() { + $log.info('onInit'); + }; + } - angular.module('my', []) - .component('c1', { - controller: ThrowingController, - bindings: {'prop': '<'} - }) - .component('c2', { - controller: LoggingController, - bindings: {'prop': '<'} - }) - .config(function($exceptionHandlerProvider) { - // We need to test with the exceptionHandler not rethrowing... - $exceptionHandlerProvider.mode('log'); - }); + angular.module('my', []) + .component('c1', { + controller: ThrowingController, + bindings: {'prop': '<'} + }) + .component('c2', { + controller: LoggingController, + bindings: {'prop': '<'} + }) + .config(function($exceptionHandlerProvider) { + // We need to test with the exceptionHandler not rethrowing... + $exceptionHandlerProvider.mode('log'); + }); - module('my'); - inject(function($compile, $rootScope, $exceptionHandler, $log) { + module('my'); + inject(function($compile, $rootScope, $exceptionHandler, $log) { - // Setup the directive with bindings that will keep updating the bound value forever - element = $compile('
')($rootScope); + // Setup the directive with bindings that will keep updating the bound value forever + element = $compile('
')($rootScope); - // The first component's error should be logged - expect($exceptionHandler.errors.pop()).toEqual(new Error('bad hook')); + // The first component's error should be logged + expect($exceptionHandler.errors.pop()).toEqual(new Error('bad hook')); - // The second component's hook should still be called - expect($log.info.logs.pop()).toEqual(['onInit']); - }); - }); + // The second component's hook should still be called + expect($log.info.logs.pop()).toEqual(['onInit']); }); + }); + }); - describe('$onDestroy', function() { + describe('$onDestroy', function() { - it('should call `$onDestroy`, if provided, on the controller when its scope is destroyed', function() { + it('should call `$onDestroy`, if provided, on the controller when its scope is destroyed', function() { - function TestController() { this.count = 0; } - TestController.prototype.$onDestroy = function() { this.count++; }; + function TestController() { this.count = 0; } + TestController.prototype.$onDestroy = function() { this.count++; }; - angular.module('my', []) - .directive('d1', valueFn({ scope: true, controller: TestController })) - .directive('d2', valueFn({ scope: {}, controller: TestController })) - .directive('d3', valueFn({ controller: TestController })); + angular.module('my', []) + .directive('d1', valueFn({ scope: true, controller: TestController })) + .directive('d2', valueFn({ scope: {}, controller: TestController })) + .directive('d3', valueFn({ controller: TestController })); - module('my'); - inject(function($compile, $rootScope) { + module('my'); + inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); + element = $compile('
')($rootScope); - $rootScope.$apply('show = [true, true, true]'); - var d1Controller = element.find('d1').controller('d1'); - var d2Controller = element.find('d2').controller('d2'); - var d3Controller = element.find('d3').controller('d3'); + $rootScope.$apply('show = [true, true, true]'); + var d1Controller = element.find('d1').controller('d1'); + var d2Controller = element.find('d2').controller('d2'); + var d3Controller = element.find('d3').controller('d3'); - expect([d1Controller.count, d2Controller.count, d3Controller.count]).toEqual([0,0,0]); - $rootScope.$apply('show = [false, true, true]'); - expect([d1Controller.count, d2Controller.count, d3Controller.count]).toEqual([1,0,0]); - $rootScope.$apply('show = [false, false, true]'); - expect([d1Controller.count, d2Controller.count, d3Controller.count]).toEqual([1,1,0]); - $rootScope.$apply('show = [false, false, false]'); - expect([d1Controller.count, d2Controller.count, d3Controller.count]).toEqual([1,1,1]); - }); - }); + expect([d1Controller.count, d2Controller.count, d3Controller.count]).toEqual([0,0,0]); + $rootScope.$apply('show = [false, true, true]'); + expect([d1Controller.count, d2Controller.count, d3Controller.count]).toEqual([1,0,0]); + $rootScope.$apply('show = [false, false, true]'); + expect([d1Controller.count, d2Controller.count, d3Controller.count]).toEqual([1,1,0]); + $rootScope.$apply('show = [false, false, false]'); + expect([d1Controller.count, d2Controller.count, d3Controller.count]).toEqual([1,1,1]); + }); + }); - it('should call `$onDestroy` top-down (the same as `scope.$broadcast`)', function() { - var log = []; - function ParentController() { log.push('parent created'); } - ParentController.prototype.$onDestroy = function() { log.push('parent destroyed'); }; - function ChildController() { log.push('child created'); } - ChildController.prototype.$onDestroy = function() { log.push('child destroyed'); }; - function GrandChildController() { log.push('grand child created'); } - GrandChildController.prototype.$onDestroy = function() { log.push('grand child destroyed'); }; + it('should call `$onDestroy` top-down (the same as `scope.$broadcast`)', function() { + var log = []; + function ParentController() { log.push('parent created'); } + ParentController.prototype.$onDestroy = function() { log.push('parent destroyed'); }; + function ChildController() { log.push('child created'); } + ChildController.prototype.$onDestroy = function() { log.push('child destroyed'); }; + function GrandChildController() { log.push('grand child created'); } + GrandChildController.prototype.$onDestroy = function() { log.push('grand child destroyed'); }; - angular.module('my', []) - .directive('parent', valueFn({ scope: true, controller: ParentController })) - .directive('child', valueFn({ scope: true, controller: ChildController })) - .directive('grandChild', valueFn({ scope: true, controller: GrandChildController })); + angular.module('my', []) + .directive('parent', valueFn({ scope: true, controller: ParentController })) + .directive('child', valueFn({ scope: true, controller: ChildController })) + .directive('grandChild', valueFn({ scope: true, controller: GrandChildController })); - module('my'); - inject(function($compile, $rootScope) { + module('my'); + inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - $rootScope.$apply('show = true'); - expect(log).toEqual(['parent created', 'child created', 'grand child created']); - log = []; - $rootScope.$apply('show = false'); - expect(log).toEqual(['parent destroyed', 'child destroyed', 'grand child destroyed']); - }); - }); + element = $compile('')($rootScope); + $rootScope.$apply('show = true'); + expect(log).toEqual(['parent created', 'child created', 'grand child created']); + log = []; + $rootScope.$apply('show = false'); + expect(log).toEqual(['parent destroyed', 'child destroyed', 'grand child destroyed']); }); + }); + }); - describe('$postLink', function() { + describe('$postLink', function() { - it('should call `$postLink`, if provided, after the element has completed linking (i.e. post-link)', function() { + it('should call `$postLink`, if provided, after the element has completed linking (i.e. post-link)', function() { - var log = []; + var log = []; - function Controller1() { } - Controller1.prototype.$postLink = function() { log.push('d1 view init'); }; + function Controller1() { } + Controller1.prototype.$postLink = function() { log.push('d1 view init'); }; - function Controller2() { } - Controller2.prototype.$postLink = function() { log.push('d2 view init'); }; + function Controller2() { } + Controller2.prototype.$postLink = function() { log.push('d2 view init'); }; - angular.module('my', []) - .directive('d1', valueFn({ - controller: Controller1, - link: { pre: function(s, e) { log.push('d1 pre: ' + e.text()); }, post: function(s, e) { log.push('d1 post: ' + e.text()); } }, - template: '' - })) - .directive('d2', valueFn({ - controller: Controller2, - link: { pre: function(s, e) { log.push('d2 pre: ' + e.text()); }, post: function(s, e) { log.push('d2 post: ' + e.text()); } }, - template: 'loaded' - })); + angular.module('my', []) + .directive('d1', valueFn({ + controller: Controller1, + link: { pre: function(s, e) { log.push('d1 pre: ' + e.text()); }, post: function(s, e) { log.push('d1 post: ' + e.text()); } }, + template: '' + })) + .directive('d2', valueFn({ + controller: Controller2, + link: { pre: function(s, e) { log.push('d2 pre: ' + e.text()); }, post: function(s, e) { log.push('d2 post: ' + e.text()); } }, + template: 'loaded' + })); - module('my'); - inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - expect(log).toEqual([ - 'd1 pre: loaded', - 'd2 pre: loaded', - 'd2 post: loaded', - 'd2 view init', - 'd1 post: loaded', - 'd1 view init' - ]); - }); - }); + module('my'); + inject(function($compile, $rootScope) { + element = $compile('')($rootScope); + expect(log).toEqual([ + 'd1 pre: loaded', + 'd2 pre: loaded', + 'd2 post: loaded', + 'd2 view init', + 'd1 post: loaded', + 'd1 view init' + ]); }); + }); + }); - describe('$doCheck', function() { - it('should call `$doCheck`, if provided, for each digest cycle, after $onChanges and $onInit', function() { - var log = []; + describe('$doCheck', function() { + it('should call `$doCheck`, if provided, for each digest cycle, after $onChanges and $onInit', function() { + var log = []; - function TestController() { } - TestController.prototype.$doCheck = function() { log.push('$doCheck'); }; - TestController.prototype.$onChanges = function() { log.push('$onChanges'); }; - TestController.prototype.$onInit = function() { log.push('$onInit'); }; + function TestController() { } + TestController.prototype.$doCheck = function() { log.push('$doCheck'); }; + TestController.prototype.$onChanges = function() { log.push('$onChanges'); }; + TestController.prototype.$onInit = function() { log.push('$onInit'); }; - angular.module('my', []) - .component('dcc', { - controller: TestController, - bindings: { 'prop1': '<' } - }); + angular.module('my', []) + .component('dcc', { + controller: TestController, + bindings: { 'prop1': '<' } + }); - module('my'); - inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - expect(log).toEqual([ - '$onChanges', - '$onInit', - '$doCheck' - ]); + module('my'); + inject(function($compile, $rootScope) { + element = $compile('')($rootScope); + expect(log).toEqual([ + '$onChanges', + '$onInit', + '$doCheck' + ]); - // Clear log - log = []; + // Clear log + log = []; - $rootScope.$apply(); - expect(log).toEqual([ - '$doCheck', - '$doCheck' - ]); + $rootScope.$apply(); + expect(log).toEqual([ + '$doCheck', + '$doCheck' + ]); - // Clear log - log = []; + // Clear log + log = []; - $rootScope.$apply('val = 2'); - expect(log).toEqual([ - '$doCheck', - '$onChanges', - '$doCheck' - ]); - }); + $rootScope.$apply('val = 2'); + expect(log).toEqual([ + '$doCheck', + '$onChanges', + '$doCheck' + ]); + }); + }); + + it('should work if $doCheck is provided in the constructor', function() { + var log = []; + + function TestController() { + this.$doCheck = function() { log.push('$doCheck'); }; + this.$onChanges = function() { log.push('$onChanges'); }; + this.$onInit = function() { log.push('$onInit'); }; + } + + angular.module('my', []) + .component('dcc', { + controller: TestController, + bindings: { 'prop1': '<' } }); - it('should work if $doCheck is provided in the constructor', function() { - var log = []; + module('my'); + inject(function($compile, $rootScope) { + element = $compile('')($rootScope); + expect(log).toEqual([ + '$onChanges', + '$onInit', + '$doCheck' + ]); - function TestController() { - this.$doCheck = function() { log.push('$doCheck'); }; - this.$onChanges = function() { log.push('$onChanges'); }; - this.$onInit = function() { log.push('$onInit'); }; - } + // Clear log + log = []; - angular.module('my', []) - .component('dcc', { - controller: TestController, - bindings: { 'prop1': '<' } - }); + $rootScope.$apply(); + expect(log).toEqual([ + '$doCheck', + '$doCheck' + ]); - module('my'); - inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - expect(log).toEqual([ - '$onChanges', - '$onInit', - '$doCheck' - ]); + // Clear log + log = []; - // Clear log - log = []; + $rootScope.$apply('val = 2'); + expect(log).toEqual([ + '$doCheck', + '$onChanges', + '$doCheck' + ]); + }); + }); + }); - $rootScope.$apply(); - expect(log).toEqual([ - '$doCheck', - '$doCheck' - ]); + describe('$onChanges', function() { - // Clear log - log = []; + it('should call `$onChanges`, if provided, when a one-way (`<`) or interpolation (`@`) bindings are updated', function() { + var log = []; + function TestController() { } + TestController.prototype.$onChanges = function(change) { log.push(change); }; - $rootScope.$apply('val = 2'); - expect(log).toEqual([ - '$doCheck', - '$onChanges', - '$doCheck' - ]); - }); + angular.module('my', []) + .component('c1', { + controller: TestController, + bindings: { 'prop1': '<', 'prop2': '<', 'other': '=', 'attr': '@' } }); - }); - describe('$onChanges', function() { + module('my'); + inject(function($compile, $rootScope) { + // Setup a watch to indicate some complicated updated logic + $rootScope.$watch('val', function(val, oldVal) { $rootScope.val2 = val * 2; }); + // Setup the directive with two bindings + element = $compile('')($rootScope); - it('should call `$onChanges`, if provided, when a one-way (`<`) or interpolation (`@`) bindings are updated', function() { - var log = []; - function TestController() { } - TestController.prototype.$onChanges = function(change) { log.push(change); }; + expect(log).toEqual([ + { + prop1: jasmine.objectContaining({currentValue: undefined}), + prop2: jasmine.objectContaining({currentValue: undefined}), + attr: jasmine.objectContaining({currentValue: ''}) + } + ]); - angular.module('my', []) - .component('c1', { - controller: TestController, - bindings: { 'prop1': '<', 'prop2': '<', 'other': '=', 'attr': '@' } - }); + // Clear the initial changes from the log + log = []; - module('my'); - inject(function($compile, $rootScope) { - // Setup a watch to indicate some complicated updated logic - $rootScope.$watch('val', function(val, oldVal) { $rootScope.val2 = val * 2; }); - // Setup the directive with two bindings - element = $compile('')($rootScope); - - expect(log).toEqual([ - { - prop1: jasmine.objectContaining({currentValue: undefined}), - prop2: jasmine.objectContaining({currentValue: undefined}), - attr: jasmine.objectContaining({currentValue: ''}) - } - ]); + // Update val to trigger the onChanges + $rootScope.$apply('val = 42'); - // Clear the initial changes from the log - log = []; + // Now we should have a single changes entry in the log + expect(log).toEqual([ + { + prop1: jasmine.objectContaining({currentValue: 42}), + prop2: jasmine.objectContaining({currentValue: 84}) + } + ]); - // Update val to trigger the onChanges - $rootScope.$apply('val = 42'); + // Clear the log + log = []; - // Now we should have a single changes entry in the log - expect(log).toEqual([ - { - prop1: jasmine.objectContaining({currentValue: 42}), - prop2: jasmine.objectContaining({currentValue: 84}) - } - ]); - - // Clear the log - log = []; - - // Update val to trigger the onChanges - $rootScope.$apply('val = 17'); - // Now we should have a single changes entry in the log - expect(log).toEqual([ - { - prop1: jasmine.objectContaining({previousValue: 42, currentValue: 17}), - prop2: jasmine.objectContaining({previousValue: 84, currentValue: 34}) - } - ]); - - // Clear the log - log = []; - - // Update val3 to trigger the "other" two-way binding - $rootScope.$apply('val3 = 63'); - // onChanges should not have been called - expect(log).toEqual([]); - - // Update val4 to trigger the "attr" interpolation binding - $rootScope.$apply('val4 = 22'); - // onChanges should not have been called - expect(log).toEqual([ - { - attr: jasmine.objectContaining({previousValue: '', currentValue: '22'}) - } - ]); - }); - }); + // Update val to trigger the onChanges + $rootScope.$apply('val = 17'); + // Now we should have a single changes entry in the log + expect(log).toEqual([ + { + prop1: jasmine.objectContaining({previousValue: 42, currentValue: 17}), + prop2: jasmine.objectContaining({previousValue: 84, currentValue: 34}) + } + ]); + // Clear the log + log = []; - it('should trigger `$onChanges` even if the inner value already equals the new outer value', function() { - var log = []; - function TestController() { } - TestController.prototype.$onChanges = function(change) { log.push(change); }; + // Update val3 to trigger the "other" two-way binding + $rootScope.$apply('val3 = 63'); + // onChanges should not have been called + expect(log).toEqual([]); - angular.module('my', []) - .component('c1', { - controller: TestController, - bindings: { 'prop1': '<' } - }); + // Update val4 to trigger the "attr" interpolation binding + $rootScope.$apply('val4 = 22'); + // onChanges should not have been called + expect(log).toEqual([ + { + attr: jasmine.objectContaining({previousValue: '', currentValue: '22'}) + } + ]); + }); + }); - module('my'); - inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - $rootScope.$apply('val = 1'); - expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: undefined, currentValue: 1})}); + it('should trigger `$onChanges` even if the inner value already equals the new outer value', function() { + var log = []; + function TestController() { } + TestController.prototype.$onChanges = function(change) { log.push(change); }; - element.isolateScope().$ctrl.prop1 = 2; - $rootScope.$apply('val = 2'); - expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: 1, currentValue: 2})}); - }); + angular.module('my', []) + .component('c1', { + controller: TestController, + bindings: { 'prop1': '<' } }); + module('my'); + inject(function($compile, $rootScope) { + element = $compile('')($rootScope); - it('should trigger `$onChanges` for literal expressions when expression input value changes (simple value)', function() { - var log = []; - function TestController() { } - TestController.prototype.$onChanges = function(change) { log.push(change); }; + $rootScope.$apply('val = 1'); + expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: undefined, currentValue: 1})}); - angular.module('my', []) - .component('c1', { - controller: TestController, - bindings: { 'prop1': '<' } - }); + element.isolateScope().$ctrl.prop1 = 2; + $rootScope.$apply('val = 2'); + expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: 1, currentValue: 2})}); + }); + }); - module('my'); - inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - $rootScope.$apply('val = 1'); - expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [undefined], currentValue: [1]})}); + it('should trigger `$onChanges` for literal expressions when expression input value changes (simple value)', function() { + var log = []; + function TestController() { } + TestController.prototype.$onChanges = function(change) { log.push(change); }; - $rootScope.$apply('val = 2'); - expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [1], currentValue: [2]})}); - }); + angular.module('my', []) + .component('c1', { + controller: TestController, + bindings: { 'prop1': '<' } }); + module('my'); + inject(function($compile, $rootScope) { + element = $compile('')($rootScope); - it('should trigger `$onChanges` for literal expressions when expression input value changes (complex value)', function() { - var log = []; - function TestController() { } - TestController.prototype.$onChanges = function(change) { log.push(change); }; + $rootScope.$apply('val = 1'); + expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [undefined], currentValue: [1]})}); - angular.module('my', []) - .component('c1', { - controller: TestController, - bindings: { 'prop1': '<' } - }); + $rootScope.$apply('val = 2'); + expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [1], currentValue: [2]})}); + }); + }); - module('my'); - inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - $rootScope.$apply('val = [1]'); - expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [undefined], currentValue: [[1]]})}); + it('should trigger `$onChanges` for literal expressions when expression input value changes (complex value)', function() { + var log = []; + function TestController() { } + TestController.prototype.$onChanges = function(change) { log.push(change); }; - $rootScope.$apply('val = [2]'); - expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [[1]], currentValue: [[2]]})}); - }); + angular.module('my', []) + .component('c1', { + controller: TestController, + bindings: { 'prop1': '<' } }); + module('my'); + inject(function($compile, $rootScope) { + element = $compile('')($rootScope); - it('should trigger `$onChanges` for literal expressions when expression input value changes instances, even when equal', function() { - var log = []; - function TestController() { } - TestController.prototype.$onChanges = function(change) { log.push(change); }; + $rootScope.$apply('val = [1]'); + expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [undefined], currentValue: [[1]]})}); - angular.module('my', []) - .component('c1', { - controller: TestController, - bindings: { 'prop1': '<' } - }); + $rootScope.$apply('val = [2]'); + expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [[1]], currentValue: [[2]]})}); + }); + }); - module('my'); - inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - $rootScope.$apply('val = [1]'); - expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [undefined], currentValue: [[1]]})}); + it('should trigger `$onChanges` for literal expressions when expression input value changes instances, even when equal', function() { + var log = []; + function TestController() { } + TestController.prototype.$onChanges = function(change) { log.push(change); }; - $rootScope.$apply('val = [1]'); - expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [[1]], currentValue: [[1]]})}); - }); + angular.module('my', []) + .component('c1', { + controller: TestController, + bindings: { 'prop1': '<' } }); + module('my'); + inject(function($compile, $rootScope) { + element = $compile('')($rootScope); - it('should pass the original value as `previousValue` even if there were multiple changes in a single digest', function() { - var log = []; - function TestController() { } - TestController.prototype.$onChanges = function(change) { log.push(change); }; + $rootScope.$apply('val = [1]'); + expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [undefined], currentValue: [[1]]})}); - angular.module('my', []) - .component('c1', { - controller: TestController, - bindings: { 'prop': '<' } - }); + $rootScope.$apply('val = [1]'); + expect(log.pop()).toEqual({prop1: jasmine.objectContaining({previousValue: [[1]], currentValue: [[1]]})}); + }); + }); - module('my'); - inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - // We add this watch after the compilation to ensure that it will run after the binding watchers - // therefore triggering the thing that this test is hoping to enforce - $rootScope.$watch('a', function(val) { $rootScope.b = val * 2; }); + it('should pass the original value as `previousValue` even if there were multiple changes in a single digest', function() { + var log = []; + function TestController() { } + TestController.prototype.$onChanges = function(change) { log.push(change); }; - expect(log).toEqual([{prop: jasmine.objectContaining({currentValue: undefined})}]); + angular.module('my', []) + .component('c1', { + controller: TestController, + bindings: { 'prop': '<' } + }); - // Clear the initial values from the log - log = []; + module('my'); + inject(function($compile, $rootScope) { + element = $compile('')($rootScope); - // Update val to trigger the onChanges - $rootScope.$apply('a = 42'); - // Now the change should have the real previous value (undefined), not the intermediate one (42) - expect(log).toEqual([{prop: jasmine.objectContaining({currentValue: 126})}]); + // We add this watch after the compilation to ensure that it will run after the binding watchers + // therefore triggering the thing that this test is hoping to enforce + $rootScope.$watch('a', function(val) { $rootScope.b = val * 2; }); - // Clear the log - log = []; + expect(log).toEqual([{prop: jasmine.objectContaining({currentValue: undefined})}]); - // Update val to trigger the onChanges - $rootScope.$apply('a = 7'); - // Now the change should have the real previous value (126), not the intermediate one, (91) - expect(log).toEqual([{prop: jasmine.objectContaining({previousValue: 126, currentValue: 21})}]); - }); - }); + // Clear the initial values from the log + log = []; + // Update val to trigger the onChanges + $rootScope.$apply('a = 42'); + // Now the change should have the real previous value (undefined), not the intermediate one (42) + expect(log).toEqual([{prop: jasmine.objectContaining({currentValue: 126})}]); - it('should trigger an initial onChanges call for each binding with the `isFirstChange()` returning true', function() { - var log = []; - function TestController() { } - TestController.prototype.$onChanges = function(change) { log.push(change); }; + // Clear the log + log = []; - angular.module('my', []) - .component('c1', { - controller: TestController, - bindings: { 'prop': '<', attr: '@' } - }); + // Update val to trigger the onChanges + $rootScope.$apply('a = 7'); + // Now the change should have the real previous value (126), not the intermediate one, (91) + expect(log).toEqual([{prop: jasmine.objectContaining({previousValue: 126, currentValue: 21})}]); + }); + }); - module('my'); - inject(function($compile, $rootScope) { - $rootScope.$apply('a = 7'); - element = $compile('')($rootScope); + it('should trigger an initial onChanges call for each binding with the `isFirstChange()` returning true', function() { + var log = []; + function TestController() { } + TestController.prototype.$onChanges = function(change) { log.push(change); }; - expect(log).toEqual([ - { - prop: jasmine.objectContaining({currentValue: 7}), - attr: jasmine.objectContaining({currentValue: '7'}) - } - ]); - expect(log[0].prop.isFirstChange()).toEqual(true); - expect(log[0].attr.isFirstChange()).toEqual(true); - - log = []; - $rootScope.$apply('a = 9'); - expect(log).toEqual([ - { - prop: jasmine.objectContaining({previousValue: 7, currentValue: 9}), - attr: jasmine.objectContaining({previousValue: '7', currentValue: '9'}) - } - ]); - expect(log[0].prop.isFirstChange()).toEqual(false); - expect(log[0].attr.isFirstChange()).toEqual(false); - }); + angular.module('my', []) + .component('c1', { + controller: TestController, + bindings: { 'prop': '<', attr: '@' } }); + module('my'); + inject(function($compile, $rootScope) { + + $rootScope.$apply('a = 7'); + element = $compile('')($rootScope); - it('should trigger an initial onChanges call for each binding even if the hook is defined in the constructor', function() { - var log = []; - function TestController() { - this.$onChanges = function(change) { log.push(change); }; + expect(log).toEqual([ + { + prop: jasmine.objectContaining({currentValue: 7}), + attr: jasmine.objectContaining({currentValue: '7'}) } + ]); + expect(log[0].prop.isFirstChange()).toEqual(true); + expect(log[0].attr.isFirstChange()).toEqual(true); - angular.module('my', []) - .component('c1', { - controller: TestController, - bindings: { 'prop': '<', attr: '@' } - }); + log = []; + $rootScope.$apply('a = 9'); + expect(log).toEqual([ + { + prop: jasmine.objectContaining({previousValue: 7, currentValue: 9}), + attr: jasmine.objectContaining({previousValue: '7', currentValue: '9'}) + } + ]); + expect(log[0].prop.isFirstChange()).toEqual(false); + expect(log[0].attr.isFirstChange()).toEqual(false); + }); + }); - module('my'); - inject(function($compile, $rootScope) { - $rootScope.$apply('a = 7'); - element = $compile('')($rootScope); - expect(log).toEqual([ - { - prop: jasmine.objectContaining({currentValue: 7}), - attr: jasmine.objectContaining({currentValue: '7'}) - } - ]); - expect(log[0].prop.isFirstChange()).toEqual(true); - expect(log[0].attr.isFirstChange()).toEqual(true); - - log = []; - $rootScope.$apply('a = 10'); - expect(log).toEqual([ - { - prop: jasmine.objectContaining({previousValue: 7, currentValue: 10}), - attr: jasmine.objectContaining({previousValue: '7', currentValue: '10'}) - } - ]); - expect(log[0].prop.isFirstChange()).toEqual(false); - expect(log[0].attr.isFirstChange()).toEqual(false); - }); + it('should trigger an initial onChanges call for each binding even if the hook is defined in the constructor', function() { + var log = []; + function TestController() { + this.$onChanges = function(change) { log.push(change); }; + } + + angular.module('my', []) + .component('c1', { + controller: TestController, + bindings: { 'prop': '<', attr: '@' } }); - it('should clean up `@`-binding observers when re-assigning bindings', function() { - var constructorSpy = jasmine.createSpy('constructor'); - var prototypeSpy = jasmine.createSpy('prototype'); + module('my'); + inject(function($compile, $rootScope) { + $rootScope.$apply('a = 7'); + element = $compile('')($rootScope); - function TestController() { - return {$onChanges: constructorSpy}; + expect(log).toEqual([ + { + prop: jasmine.objectContaining({currentValue: 7}), + attr: jasmine.objectContaining({currentValue: '7'}) } - TestController.prototype.$onChanges = prototypeSpy; + ]); + expect(log[0].prop.isFirstChange()).toEqual(true); + expect(log[0].attr.isFirstChange()).toEqual(true); - module(function($compileProvider) { - $compileProvider.component('test', { - bindings: {attr: '@'}, - controller: TestController - }); - }); + log = []; + $rootScope.$apply('a = 10'); + expect(log).toEqual([ + { + prop: jasmine.objectContaining({previousValue: 7, currentValue: 10}), + attr: jasmine.objectContaining({previousValue: '7', currentValue: '10'}) + } + ]); + expect(log[0].prop.isFirstChange()).toEqual(false); + expect(log[0].attr.isFirstChange()).toEqual(false); + }); + }); - inject(function($compile, $rootScope) { - var template = ''; - $rootScope.a = 'foo'; + it('should clean up `@`-binding observers when re-assigning bindings', function() { + var constructorSpy = jasmine.createSpy('constructor'); + var prototypeSpy = jasmine.createSpy('prototype'); - element = $compile(template)($rootScope); - $rootScope.$digest(); - expect(constructorSpy).toHaveBeenCalled(); - expect(prototypeSpy).not.toHaveBeenCalled(); + function TestController() { + return {$onChanges: constructorSpy}; + } + TestController.prototype.$onChanges = prototypeSpy; - constructorSpy.calls.reset(); - $rootScope.$apply('a = "bar"'); - expect(constructorSpy).toHaveBeenCalled(); - expect(prototypeSpy).not.toHaveBeenCalled(); - }); + module(function($compileProvider) { + $compileProvider.component('test', { + bindings: {attr: '@'}, + controller: TestController }); + }); - it('should not call `$onChanges` twice even when the initial value is `NaN`', function() { - var onChangesSpy = jasmine.createSpy('$onChanges'); - - module(function($compileProvider) { - $compileProvider.component('test', { - bindings: {prop: '<', attr: '@'}, - controller: function TestController() { - this.$onChanges = onChangesSpy; - } - }); - }); - - inject(function($compile, $rootScope) { - var template = '' + - ''; - $rootScope.a = 'foo'; - $rootScope.b = NaN; + inject(function($compile, $rootScope) { + var template = ''; + $rootScope.a = 'foo'; - element = $compile(template)($rootScope); - $rootScope.$digest(); + element = $compile(template)($rootScope); + $rootScope.$digest(); + expect(constructorSpy).toHaveBeenCalled(); + expect(prototypeSpy).not.toHaveBeenCalled(); - expect(onChangesSpy).toHaveBeenCalledTimes(2); - expect(onChangesSpy.calls.argsFor(0)[0]).toEqual({ - prop: jasmine.objectContaining({currentValue: 'foo'}), - attr: jasmine.objectContaining({currentValue: 'foo'}) - }); - expect(onChangesSpy.calls.argsFor(1)[0]).toEqual({ - prop: jasmine.objectContaining({currentValue: NaN}), - attr: jasmine.objectContaining({currentValue: 'NaN'}) - }); + constructorSpy.calls.reset(); + $rootScope.$apply('a = "bar"'); + expect(constructorSpy).toHaveBeenCalled(); + expect(prototypeSpy).not.toHaveBeenCalled(); + }); + }); - onChangesSpy.calls.reset(); - $rootScope.$apply('a = "bar"; b = 42'); + it('should not call `$onChanges` twice even when the initial value is `NaN`', function() { + var onChangesSpy = jasmine.createSpy('$onChanges'); - expect(onChangesSpy).toHaveBeenCalledTimes(2); - expect(onChangesSpy.calls.argsFor(0)[0]).toEqual({ - prop: jasmine.objectContaining({previousValue: 'foo', currentValue: 'bar'}), - attr: jasmine.objectContaining({previousValue: 'foo', currentValue: 'bar'}) - }); - expect(onChangesSpy.calls.argsFor(1)[0]).toEqual({ - prop: jasmine.objectContaining({previousValue: NaN, currentValue: 42}), - attr: jasmine.objectContaining({previousValue: 'NaN', currentValue: '42'}) - }); - }); + module(function($compileProvider) { + $compileProvider.component('test', { + bindings: {prop: '<', attr: '@'}, + controller: function TestController() { + this.$onChanges = onChangesSpy; + } }); + }); + inject(function($compile, $rootScope) { + var template = '' + + ''; + $rootScope.a = 'foo'; + $rootScope.b = NaN; - it('should only trigger one extra digest however many controllers have changes', function() { - var log = []; - function TestController1() { } - TestController1.prototype.$onChanges = function(change) { log.push(['TestController1', change]); }; - function TestController2() { } - TestController2.prototype.$onChanges = function(change) { log.push(['TestController2', change]); }; - - angular.module('my', []) - .component('c1', { - controller: TestController1, - bindings: {'prop': '<'} - }) - .component('c2', { - controller: TestController2, - bindings: {'prop': '<'} - }); + element = $compile(template)($rootScope); + $rootScope.$digest(); - module('my'); - inject(function($compile, $rootScope) { + expect(onChangesSpy).toHaveBeenCalledTimes(2); + expect(onChangesSpy.calls.argsFor(0)[0]).toEqual({ + prop: jasmine.objectContaining({currentValue: 'foo'}), + attr: jasmine.objectContaining({currentValue: 'foo'}) + }); + expect(onChangesSpy.calls.argsFor(1)[0]).toEqual({ + prop: jasmine.objectContaining({currentValue: NaN}), + attr: jasmine.objectContaining({currentValue: 'NaN'}) + }); - // Create a watcher to count the number of digest cycles - var watchCount = 0; - $rootScope.$watch(function() { watchCount++; }); + onChangesSpy.calls.reset(); + $rootScope.$apply('a = "bar"; b = 42'); - // Setup two sibling components with bindings that will change - element = $compile('
')($rootScope); + expect(onChangesSpy).toHaveBeenCalledTimes(2); + expect(onChangesSpy.calls.argsFor(0)[0]).toEqual({ + prop: jasmine.objectContaining({previousValue: 'foo', currentValue: 'bar'}), + attr: jasmine.objectContaining({previousValue: 'foo', currentValue: 'bar'}) + }); + expect(onChangesSpy.calls.argsFor(1)[0]).toEqual({ + prop: jasmine.objectContaining({previousValue: NaN, currentValue: 42}), + attr: jasmine.objectContaining({previousValue: 'NaN', currentValue: '42'}) + }); + }); + }); - // Clear out initial changes - log = []; - // Update val to trigger the onChanges - $rootScope.$apply('val1 = 42; val2 = 17'); + it('should only trigger one extra digest however many controllers have changes', function() { + var log = []; + function TestController1() { } + TestController1.prototype.$onChanges = function(change) { log.push(['TestController1', change]); }; + function TestController2() { } + TestController2.prototype.$onChanges = function(change) { log.push(['TestController2', change]); }; - expect(log).toEqual([ - ['TestController1', {prop: jasmine.objectContaining({currentValue: 42})}], - ['TestController2', {prop: jasmine.objectContaining({currentValue: 17})}] - ]); - // A single apply should only trigger three turns of the digest loop - expect(watchCount).toEqual(3); - }); + angular.module('my', []) + .component('c1', { + controller: TestController1, + bindings: {'prop': '<'} + }) + .component('c2', { + controller: TestController2, + bindings: {'prop': '<'} }); + module('my'); + inject(function($compile, $rootScope) { - it('should cope with changes occurring inside `$onChanges()` hooks', function() { - var log = []; - function OuterController() {} - OuterController.prototype.$onChanges = function(change) { - log.push(['OuterController', change]); - // Make a change to the inner component - this.b = this.prop1 * 2; - }; + // Create a watcher to count the number of digest cycles + var watchCount = 0; + $rootScope.$watch(function() { watchCount++; }); - function InnerController() { } - InnerController.prototype.$onChanges = function(change) { log.push(['InnerController', change]); }; + // Setup two sibling components with bindings that will change + element = $compile('
')($rootScope); - angular.module('my', []) - .component('outer', { - controller: OuterController, - bindings: {'prop1': '<'}, - template: '' - }) - .component('inner', { - controller: InnerController, - bindings: {'prop2': '<'} - }); + // Clear out initial changes + log = []; - module('my'); - inject(function($compile, $rootScope) { + // Update val to trigger the onChanges + $rootScope.$apply('val1 = 42; val2 = 17'); + + expect(log).toEqual([ + ['TestController1', {prop: jasmine.objectContaining({currentValue: 42})}], + ['TestController2', {prop: jasmine.objectContaining({currentValue: 17})}] + ]); + // A single apply should only trigger three turns of the digest loop + expect(watchCount).toEqual(3); + }); + }); - // Setup the directive with two bindings - element = $compile('')($rootScope); - // Clear out initial changes - log = []; + it('should cope with changes occurring inside `$onChanges()` hooks', function() { + var log = []; + function OuterController() {} + OuterController.prototype.$onChanges = function(change) { + log.push(['OuterController', change]); + // Make a change to the inner component + this.b = this.prop1 * 2; + }; - // Update val to trigger the onChanges - $rootScope.$apply('a = 42'); + function InnerController() { } + InnerController.prototype.$onChanges = function(change) { log.push(['InnerController', change]); }; - expect(log).toEqual([ - ['OuterController', {prop1: jasmine.objectContaining({previousValue: undefined, currentValue: 42})}], - ['InnerController', {prop2: jasmine.objectContaining({previousValue: NaN, currentValue: 84})}] - ]); - }); + angular.module('my', []) + .component('outer', { + controller: OuterController, + bindings: {'prop1': '<'}, + template: '' + }) + .component('inner', { + controller: InnerController, + bindings: {'prop2': '<'} }); + module('my'); + inject(function($compile, $rootScope) { + + // Setup the directive with two bindings + element = $compile('')($rootScope); - it('should throw an error if `$onChanges()` hooks are not stable', function() { - function TestController() {} - TestController.prototype.$onChanges = function(change) { - this.onChange(); - }; + // Clear out initial changes + log = []; - angular.module('my', []) - .component('c1', { - controller: TestController, - bindings: {'prop': '<', onChange: '&'} - }); + // Update val to trigger the onChanges + $rootScope.$apply('a = 42'); - module('my'); - inject(function($compile, $rootScope) { + expect(log).toEqual([ + ['OuterController', {prop1: jasmine.objectContaining({previousValue: undefined, currentValue: 42})}], + ['InnerController', {prop2: jasmine.objectContaining({previousValue: NaN, currentValue: 84})}] + ]); + }); + }); - // Setup the directive with bindings that will keep updating the bound value forever - element = $compile('')($rootScope); - // Update val to trigger the unstable onChanges, which will result in an error - expect(function() { - $rootScope.$apply('a = 42'); - }).toThrowMinErr('$compile', 'infchng'); + it('should throw an error if `$onChanges()` hooks are not stable', function() { + function TestController() {} + TestController.prototype.$onChanges = function(change) { + this.onChange(); + }; - dealoc(element); - element = $compile('')($rootScope); - $rootScope.$apply('b = 24'); - $rootScope.$apply('b = 48'); - }); + angular.module('my', []) + .component('c1', { + controller: TestController, + bindings: {'prop': '<', onChange: '&'} }); + module('my'); + inject(function($compile, $rootScope) { - it('should log an error if `$onChanges()` hooks are not stable', function() { - function TestController() {} - TestController.prototype.$onChanges = function(change) { - this.onChange(); - }; + // Setup the directive with bindings that will keep updating the bound value forever + element = $compile('')($rootScope); - angular.module('my', []) - .component('c1', { - controller: TestController, - bindings: {'prop': '<', onChange: '&'} - }) - .config(function($exceptionHandlerProvider) { - // We need to test with the exceptionHandler not rethrowing... - $exceptionHandlerProvider.mode('log'); - }); + // Update val to trigger the unstable onChanges, which will result in an error + expect(function() { + $rootScope.$apply('a = 42'); + }).toThrowMinErr('$compile', 'infchng'); + + dealoc(element); + element = $compile('')($rootScope); + $rootScope.$apply('b = 24'); + $rootScope.$apply('b = 48'); + }); + }); - module('my'); - inject(function($compile, $rootScope, $exceptionHandler) { - // Setup the directive with bindings that will keep updating the bound value forever - element = $compile('')($rootScope); + it('should log an error if `$onChanges()` hooks are not stable', function() { + function TestController() {} + TestController.prototype.$onChanges = function(change) { + this.onChange(); + }; - // Update val to trigger the unstable onChanges, which will result in an error - $rootScope.$apply('a = 42'); - expect($exceptionHandler.errors.length).toEqual(1); - expect($exceptionHandler.errors[0]). - toEqualMinErr('$compile', 'infchng', '10 $onChanges() iterations reached.'); - }); + angular.module('my', []) + .component('c1', { + controller: TestController, + bindings: {'prop': '<', onChange: '&'} + }) + .config(function($exceptionHandlerProvider) { + // We need to test with the exceptionHandler not rethrowing... + $exceptionHandlerProvider.mode('log'); }); + module('my'); + inject(function($compile, $rootScope, $exceptionHandler) { - it('should continue to trigger other `$onChanges` hooks if one throws an error', function() { - function ThrowingController() { - this.$onChanges = function(change) { - throw new Error('bad hook'); - }; - } - function LoggingController($log) { - this.$onChanges = function(change) { - $log.info('onChange'); - }; - } + // Setup the directive with bindings that will keep updating the bound value forever + element = $compile('')($rootScope); - angular.module('my', []) - .component('c1', { - controller: ThrowingController, - bindings: {'prop': '<'} - }) - .component('c2', { - controller: LoggingController, - bindings: {'prop': '<'} - }) - .config(function($exceptionHandlerProvider) { - // We need to test with the exceptionHandler not rethrowing... - $exceptionHandlerProvider.mode('log'); - }); + // Update val to trigger the unstable onChanges, which will result in an error + $rootScope.$apply('a = 42'); + expect($exceptionHandler.errors.length).toEqual(1); + expect($exceptionHandler.errors[0]). + toEqualMinErr('$compile', 'infchng', '10 $onChanges() iterations reached.'); + }); + }); - module('my'); - inject(function($compile, $rootScope, $exceptionHandler, $log) { - // Setup the directive with bindings that will keep updating the bound value forever - element = $compile('
')($rootScope); + it('should continue to trigger other `$onChanges` hooks if one throws an error', function() { + function ThrowingController() { + this.$onChanges = function(change) { + throw new Error('bad hook'); + }; + } + function LoggingController($log) { + this.$onChanges = function(change) { + $log.info('onChange'); + }; + } - // The first component's error should be logged - expect($exceptionHandler.errors.pop()).toEqual(new Error('bad hook')); + angular.module('my', []) + .component('c1', { + controller: ThrowingController, + bindings: {'prop': '<'} + }) + .component('c2', { + controller: LoggingController, + bindings: {'prop': '<'} + }) + .config(function($exceptionHandlerProvider) { + // We need to test with the exceptionHandler not rethrowing... + $exceptionHandlerProvider.mode('log'); + }); - // The second component's changes should still be called - expect($log.info.logs.pop()).toEqual(['onChange']); + module('my'); + inject(function($compile, $rootScope, $exceptionHandler, $log) { - $rootScope.$apply('a = 42'); + // Setup the directive with bindings that will keep updating the bound value forever + element = $compile('
')($rootScope); - // The first component's error should be logged - var errors = $exceptionHandler.errors.pop(); - expect(errors[0]).toEqual(new Error('bad hook')); + // The first component's error should be logged + expect($exceptionHandler.errors.pop()).toEqual(new Error('bad hook')); - // The second component's changes should still be called - expect($log.info.logs.pop()).toEqual(['onChange']); - }); - }); + // The second component's changes should still be called + expect($log.info.logs.pop()).toEqual(['onChange']); + $rootScope.$apply('a = 42'); - it('should collect up all `$onChanges` errors into one throw', function() { - function ThrowingController() { - this.$onChanges = function(change) { - throw new Error('bad hook: ' + this.prop); - }; - } + // The first component's error should be logged + var errors = $exceptionHandler.errors.pop(); + expect(errors[0]).toEqual(new Error('bad hook')); - angular.module('my', []) - .component('c1', { - controller: ThrowingController, - bindings: {'prop': '<'} - }) - .config(function($exceptionHandlerProvider) { - // We need to test with the exceptionHandler not rethrowing... - $exceptionHandlerProvider.mode('log'); - }); + // The second component's changes should still be called + expect($log.info.logs.pop()).toEqual(['onChange']); + }); + }); + + + it('should collect up all `$onChanges` errors into one throw', function() { + function ThrowingController() { + this.$onChanges = function(change) { + throw new Error('bad hook: ' + this.prop); + }; + } + + angular.module('my', []) + .component('c1', { + controller: ThrowingController, + bindings: {'prop': '<'} + }) + .config(function($exceptionHandlerProvider) { + // We need to test with the exceptionHandler not rethrowing... + $exceptionHandlerProvider.mode('log'); + }); - module('my'); - inject(function($compile, $rootScope, $exceptionHandler, $log) { + module('my'); + inject(function($compile, $rootScope, $exceptionHandler, $log) { - // Setup the directive with bindings that will keep updating the bound value forever - element = $compile('
')($rootScope); + // Setup the directive with bindings that will keep updating the bound value forever + element = $compile('
')($rootScope); - // Both component's errors should be logged - expect($exceptionHandler.errors.pop()).toEqual(new Error('bad hook: NaN')); - expect($exceptionHandler.errors.pop()).toEqual(new Error('bad hook: undefined')); + // Both component's errors should be logged + expect($exceptionHandler.errors.pop()).toEqual(new Error('bad hook: NaN')); + expect($exceptionHandler.errors.pop()).toEqual(new Error('bad hook: undefined')); - $rootScope.$apply('a = 42'); + $rootScope.$apply('a = 42'); - // Both component's error should be logged - var errors = $exceptionHandler.errors.pop(); - expect(errors.pop()).toEqual(new Error('bad hook: 84')); - expect(errors.pop()).toEqual(new Error('bad hook: 42')); - }); - }); + // Both component's error should be logged + var errors = $exceptionHandler.errors.pop(); + expect(errors.pop()).toEqual(new Error('bad hook: 84')); + expect(errors.pop()).toEqual(new Error('bad hook: 42')); }); }); + }); + }); - describe('isolated locals', function() { - var componentScope, regularScope; - - beforeEach(module(function() { - directive('myComponent', function() { - return { - scope: { - attr: '@', - attrAlias: '@attr', - $attrAlias: '@$attr$', - ref: '=', - refAlias: '= ref', - $refAlias: '= $ref$', - reference: '=', - optref: '=?', - optrefAlias: '=? optref', - $optrefAlias: '=? $optref$', - optreference: '=?', - colref: '=*', - colrefAlias: '=* colref', - $colrefAlias: '=* $colref$', - owRef: '<', - owRefAlias: '< owRef', - $owRefAlias: '< $owRef$', - owOptref: '
'); - $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 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('
'); + it('should not give the isolate scope to other directive template', function() { + module(function() { + directive('otherTplDir', function() { + return { + template: 'value: {{value}}' + }; + }); + }); - $rootScope.$apply(function() { - $rootScope.value = 'from-parent'; - }); + inject(function($rootScope) { + compile('
'); - expect(element.html()).toBe('value: from-parent'); - }); + $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('
'); + it('should not give the isolate scope to other directive template (with templateUrl)', function() { + module(function() { + directive('otherTplDir', function() { + return { + templateUrl: 'other.html' + }; + }); + }); - $rootScope.$apply(function() { - $rootScope.value = 'from-parent'; - }); + inject(function($rootScope, $templateCache) { + $templateCache.put('other.html', 'value: {{value}}'); + compile('
'); - expect(element.html()).toBe('value: from-parent'); - }); + $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'; - }); + it('should not give the isolate scope to regular child elements', function() { + inject(function($rootScope) { + compile('
value: {{value}}
'); - expect(element.html()).toBe('value: from-parent'); - }); + $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(); + 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); - })); + 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(); + 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); + $rootScope.$apply(function(scope) { scope.num = 64; }); + expect(isolateScope.reference).toBe(64); + })); + + + it('should be able to bind attribute names which are present in Object.prototype', function() { + module(function() { + directive('inProtoAttr', valueFn({ + scope: { + 'constructor': '@', + 'toString': '&', + + // Spidermonkey extension, may be obsolete in the future + 'watch': '=' + } })); + }); + inject(function($rootScope) { + expect(function() { + compile('
'); + }).not.toThrow(); + var isolateScope = element.isolateScope(); + + expect(typeof isolateScope.constructor).toBe('string'); + expect(isArray(isolateScope.watch)).toBe(true); + expect(typeof isolateScope.toString).toBe('function'); + expect($rootScope.value).toBeUndefined(); + isolateScope.toString(); + expect($rootScope.value).toBe(true); + }); + }); + it('should be able to interpolate attribute names which are present in Object.prototype', function() { + var attrs; + module(function() { + directive('attrExposer', valueFn({ + link: function($scope, $element, $attrs) { + attrs = $attrs; + } + })); + }); + inject(function($compile, $rootScope) { + $compile('
')($rootScope); + $rootScope.$apply(); + expect(attrs.toString).toBe('2'); + }); + }); - it('should be able to bind attribute names which are present in Object.prototype', function() { - module(function() { - directive('inProtoAttr', valueFn({ - scope: { - 'constructor': '@', - 'toString': '&', - // Spidermonkey extension, may be obsolete in the future - 'watch': '=' - } - })); - }); - inject(function($rootScope) { - expect(function() { - compile('
'); - }).not.toThrow(); - var isolateScope = element.isolateScope(); + it('should not initialize scope value if optional expression binding is not passed', inject(function($compile) { + compile('
'); + var isolateScope = element.isolateScope(); + expect(isolateScope.optExpr).toBeUndefined(); + })); - expect(typeof isolateScope.constructor).toBe('string'); - expect(isArray(isolateScope.watch)).toBe(true); - expect(typeof isolateScope.toString).toBe('function'); - expect($rootScope.value).toBeUndefined(); - isolateScope.toString(); - expect($rootScope.value).toBe(true); - }); - }); - it('should be able to interpolate attribute names which are present in Object.prototype', function() { - var attrs; - module(function() { - directive('attrExposer', valueFn({ - link: function($scope, $element, $attrs) { - attrs = $attrs; - } - })); - }); - inject(function($compile, $rootScope) { - $compile('
')($rootScope); - $rootScope.$apply(); - expect(attrs.toString).toBe('2'); - }); - }); + it('should not initialize scope value if optional expression binding with Object.prototype name is not passed', inject(function($compile) { + compile('
'); + var isolateScope = element.isolateScope(); + expect(isolateScope.constructor).toBe($rootScope.constructor); + })); - it('should not initialize scope value if optional expression binding is not passed', inject(function($compile) { - compile('
'); - var isolateScope = element.isolateScope(); - expect(isolateScope.optExpr).toBeUndefined(); - })); + it('should initialize scope value if optional expression binding is passed', inject(function($compile) { + compile('
'); + var isolateScope = element.isolateScope(); + expect(typeof isolateScope.optExpr).toBe('function'); + expect(isolateScope.optExpr()).toBe('did!'); + expect($rootScope.value).toBe('did!'); + })); - it('should not initialize scope value if optional expression binding with Object.prototype name is not passed', inject(function($compile) { - compile('
'); - var isolateScope = element.isolateScope(); - expect(isolateScope.constructor).toBe($rootScope.constructor); - })); + it('should initialize scope value if optional expression binding with Object.prototype name is passed', inject(function($compile) { + compile('
'); + var isolateScope = element.isolateScope(); + expect(typeof isolateScope.constructor).toBe('function'); + expect(isolateScope.constructor()).toBe('did!'); + expect($rootScope.value).toBe('did!'); + })); - it('should initialize scope value if optional expression binding is passed', inject(function($compile) { - compile('
'); - var isolateScope = element.isolateScope(); - expect(typeof isolateScope.optExpr).toBe('function'); - expect(isolateScope.optExpr()).toBe('did!'); - expect($rootScope.value).toBe('did!'); + it('should not overwrite @-bound property each digest when not present', function() { + module(function($compileProvider) { + $compileProvider.directive('testDir', valueFn({ + scope: {prop: '@'}, + controller: function($scope) { + $scope.prop = $scope.prop || 'default'; + this.getProp = function() { + return $scope.prop; + }; + }, + controllerAs: 'ctrl', + template: '

' })); + }); + inject(function($compile, $rootScope) { + element = $compile('
')($rootScope); + var scope = element.isolateScope(); + expect(scope.ctrl.getProp()).toBe('default'); + + $rootScope.$digest(); + expect(scope.ctrl.getProp()).toBe('default'); + }); + }); - it('should initialize scope value if optional expression binding with Object.prototype name is passed', inject(function($compile) { - compile('
'); - var isolateScope = element.isolateScope(); - expect(typeof isolateScope.constructor).toBe('function'); - expect(isolateScope.constructor()).toBe('did!'); - expect($rootScope.value).toBe('did!'); + it('should ignore optional "="-bound property if value is the empty string', function() { + module(function($compileProvider) { + $compileProvider.directive('testDir', valueFn({ + scope: {prop: '=?'}, + controller: function($scope) { + $scope.prop = $scope.prop || 'default'; + this.getProp = function() { + return $scope.prop; + }; + }, + controllerAs: 'ctrl', + template: '

' })); + }); + inject(function($compile, $rootScope) { + element = $compile('
')($rootScope); + var scope = element.isolateScope(); + expect(scope.ctrl.getProp()).toBe('default'); + $rootScope.$digest(); + expect(scope.ctrl.getProp()).toBe('default'); + scope.prop = 'foop'; + $rootScope.$digest(); + expect(scope.ctrl.getProp()).toBe('foop'); + }); + }); - it('should not overwrite @-bound property each digest when not present', function() { - module(function($compileProvider) { - $compileProvider.directive('testDir', valueFn({ - scope: {prop: '@'}, - controller: function($scope) { - $scope.prop = $scope.prop || 'default'; - this.getProp = function() { - return $scope.prop; - }; - }, - controllerAs: 'ctrl', - template: '

' - })); - }); - inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); - var scope = element.isolateScope(); - expect(scope.ctrl.getProp()).toBe('default'); + describe('bind-once', function() { - $rootScope.$digest(); - expect(scope.ctrl.getProp()).toBe('default'); + 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(6); // 4 -> template watch group, 2 -> '=' + $rootScope.$digest(); + expect(element.html()).toBe('1:;2:;3:;4:'); + expect(countWatches($rootScope)).toEqual(6); - it('should ignore optional "="-bound property if value is the empty string', function() { - module(function($compileProvider) { - $compileProvider.directive('testDir', valueFn({ - scope: {prop: '=?'}, - controller: function($scope) { - $scope.prop = $scope.prop || 'default'; - this.getProp = function() { - return $scope.prop; - }; - }, - controllerAs: 'ctrl', - template: '

' - })); - }); - inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); - var scope = element.isolateScope(); - expect(scope.ctrl.getProp()).toBe('default'); - $rootScope.$digest(); - expect(scope.ctrl.getProp()).toBe('default'); - scope.prop = 'foop'; - $rootScope.$digest(); - expect(scope.ctrl.getProp()).toBe('foop'); - }); - }); + $rootScope.foo = 'foo'; + $rootScope.$digest(); + expect(element.html()).toBe('1:foo;2:;3:foo;4:'); + expect(countWatches($rootScope)).toEqual(4); + $rootScope.foo = 'baz'; + $rootScope.bar = 'bar'; + $rootScope.$digest(); + expect(element.html()).toBe('1:foo;2:bar;3:foo;4:bar'); + expect(countWatches($rootScope)).toEqual(3); - describe('bind-once', function() { + $rootScope.bar = 'baz'; + $rootScope.$digest(); + expect(element.html()).toBe('1:foo;2:baz;3:foo;4:bar'); + }); + }); - 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}}' + }; + }); + }); - 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(6); // 4 -> template watch group, 2 -> {{ }} + $rootScope.$digest(); + expect(element.html()).toBe('1:;2:;3:;4:'); + expect(countWatches($rootScope)).toEqual(4); // (- 2) -> bind-once in template - inject(function($rootScope) { - compile('
'); - expect(countWatches($rootScope)).toEqual(6); // 4 -> template watch group, 2 -> '=' - $rootScope.$digest(); - expect(element.html()).toBe('1:;2:;3:;4:'); - expect(countWatches($rootScope)).toEqual(6); + $rootScope.foo = 'foo'; + $rootScope.$digest(); + expect(element.html()).toBe('1:foo;2:;3:;4:'); + expect(countWatches($rootScope)).toEqual(3); - $rootScope.foo = 'foo'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:;3:foo;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(3); - $rootScope.foo = 'baz'; - $rootScope.bar = 'bar'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:bar;3:foo;4:bar'); - expect(countWatches($rootScope)).toEqual(3); + $rootScope.bar = 'baz'; + $rootScope.$digest(); + expect(element.html()).toBe('1:foo;2:baz;3:;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' + }; }); + }); - 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, $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(6); // 4 -> template watch group, 2 -> '=' - inject(function($rootScope) { - compile('
'); - expect(countWatches($rootScope)).toEqual(6); // 4 -> template watch group, 2 -> {{ }} - $rootScope.$digest(); - expect(element.html()).toBe('1:;2:;3:;4:'); - expect(countWatches($rootScope)).toEqual(4); // (- 2) -> bind-once in template + $rootScope.foo = 'foo'; + $rootScope.$digest(); + expect(element.html()).toBe('1:foo;2:;3:foo;4:'); + expect(countWatches($rootScope)).toEqual(4); - $rootScope.foo = 'foo'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:;3:;4:'); - expect(countWatches($rootScope)).toEqual(3); + $rootScope.foo = 'baz'; + $rootScope.bar = 'bar'; + $rootScope.$digest(); + expect(element.html()).toBe('1:foo;2:bar;3:foo;4:bar'); + expect(countWatches($rootScope)).toEqual(3); - $rootScope.foo = 'baz'; - $rootScope.bar = 'bar'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:bar;3:;4:'); - expect(countWatches($rootScope)).toEqual(3); + $rootScope.bar = 'baz'; + $rootScope.$digest(); + expect(element.html()).toBe('1:foo;2:baz;3:foo;4:bar'); + }); + }); - $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' + }; }); + }); - 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(4); // (4 - 2) -> template watch group, 2 -> {{ }} - 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(6); // 4 -> template watch group, 2 -> '=' + $rootScope.foo = 'foo'; + $rootScope.$digest(); + expect(element.html()).toBe('1:foo;2:;3:;4:'); + expect(countWatches($rootScope)).toEqual(3); - $rootScope.foo = 'foo'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:;3:foo;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(3); - $rootScope.foo = 'baz'; - $rootScope.bar = 'bar'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:bar;3:foo;4:bar'); - expect(countWatches($rootScope)).toEqual(3); + $rootScope.bar = 'baz'; + $rootScope.$digest(); + expect(element.html()).toBe('1:foo;2:baz;3:;4:'); + }); + }); - $rootScope.bar = 'baz'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:baz;3:foo;4:bar'); - }); + it('should continue with a digets cycle when there is a two-way binding from the child to the parent', function() { + module(function() { + directive('hello', function() { + return { + restrict: 'E', + scope: { greeting: '=' }, + template: '', + link: function(scope) { + scope.setGreeting = function() { scope.greeting = 'Hello!'; }; + } + }; }); + }); - 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) { + compile('
' + + '

{{greeting}}

' + + '
' + + '
'); + $rootScope.$digest(); + browserTrigger(element.find('button'), 'click'); + expect(element.find('p').text()).toBe('Hello!'); + }); + }); - 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(4); // (4 - 2) -> template watch group, 2 -> {{ }} + }); - $rootScope.foo = 'foo'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:;3:;4:'); - expect(countWatches($rootScope)).toEqual(3); - $rootScope.foo = 'baz'; - $rootScope.bar = 'bar'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:bar;3:;4:'); - expect(countWatches($rootScope)).toEqual(3); + describe('attribute', function() { + it('should copy simple attribute', inject(function() { + compile('
'); - $rootScope.bar = 'baz'; - $rootScope.$digest(); - expect(element.html()).toBe('1:foo;2:baz;3:;4:'); - }); - }); + expect(componentScope.attr).toEqual('some text'); + expect(componentScope.attrAlias).toEqual('some text'); + expect(componentScope.$attrAlias).toEqual('some other text'); + expect(componentScope.attrAlias).toEqual(componentScope.attr); + })); - it('should continue with a digets cycle when there is a two-way binding from the child to the parent', function() { - module(function() { - directive('hello', function() { - return { - restrict: 'E', - scope: { greeting: '=' }, - template: '', - link: function(scope) { - scope.setGreeting = function() { scope.greeting = 'Hello!'; }; - } - }; - }); - }); + it('should copy an attribute with spaces', inject(function() { + compile('
'); - inject(function($rootScope) { - compile('
' + - '

{{greeting}}

' + - '
' + - '
'); - $rootScope.$digest(); - browserTrigger(element.find('button'), 'click'); - expect(element.find('p').text()).toBe('Hello!'); - }); - }); + expect(componentScope.attr).toEqual(' some text '); + expect(componentScope.attrAlias).toEqual(' some text '); + expect(componentScope.$attrAlias).toEqual(' some other 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'); + expect(componentScope.$attrAlias).toEqual('hi misko'); + })); + it('should update when interpolated attribute updates', inject(function() { + compile('
'); - describe('attribute', function() { - it('should copy simple attribute', inject(function() { - compile('
'); + $rootScope.name = 'igor'; + $rootScope.$apply(); - expect(componentScope.attr).toEqual('some text'); - expect(componentScope.attrAlias).toEqual('some text'); - expect(componentScope.$attrAlias).toEqual('some other text'); - expect(componentScope.attrAlias).toEqual(componentScope.attr); - })); + expect(componentScope.attr).toEqual('hello igor'); + expect(componentScope.attrAlias).toEqual('hello igor'); + expect(componentScope.$attrAlias).toEqual('hi igor'); + })); + }); - it('should copy an attribute with spaces', inject(function() { - compile('
'); - expect(componentScope.attr).toEqual(' some text '); - expect(componentScope.attrAlias).toEqual(' some text '); - expect(componentScope.$attrAlias).toEqual(' some other text '); - expect(componentScope.attrAlias).toEqual(componentScope.attr); - })); + describe('object reference', function() { + it('should update local when origin changes', inject(function() { + compile('
'); + expect(componentScope.ref).toBeUndefined(); + expect(componentScope.refAlias).toBe(componentScope.ref); + expect(componentScope.$refAlias).toBe(componentScope.ref); - 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'); - expect(componentScope.$attrAlias).toEqual('hi misko'); - })); + $rootScope.name = 'misko'; + $rootScope.$apply(); - it('should update when interpolated attribute updates', inject(function() { - compile('
'); + expect($rootScope.name).toBe('misko'); + expect(componentScope.ref).toBe('misko'); + expect(componentScope.refAlias).toBe('misko'); + expect(componentScope.$refAlias).toBe('misko'); - $rootScope.name = 'igor'; - $rootScope.$apply(); + $rootScope.name = {}; + $rootScope.$apply(); + expect(componentScope.ref).toBe($rootScope.name); + expect(componentScope.refAlias).toBe($rootScope.name); + expect(componentScope.$refAlias).toBe($rootScope.name); + })); - expect(componentScope.attr).toEqual('hello igor'); - expect(componentScope.attrAlias).toEqual('hello igor'); - expect(componentScope.$attrAlias).toEqual('hi igor'); - })); - }); + it('should update local when both change', inject(function() { + compile('
'); + $rootScope.name = {mark:123}; + componentScope.ref = 'misko'; - describe('object reference', function() { - it('should update local when origin changes', inject(function() { - compile('
'); - expect(componentScope.ref).toBeUndefined(); - expect(componentScope.refAlias).toBe(componentScope.ref); - expect(componentScope.$refAlias).toBe(componentScope.ref); + $rootScope.$apply(); + expect($rootScope.name).toEqual({mark:123}); + expect(componentScope.ref).toBe($rootScope.name); + expect(componentScope.refAlias).toBe($rootScope.name); + expect(componentScope.$refAlias).toBe($rootScope.name); - $rootScope.name = 'misko'; - $rootScope.$apply(); + $rootScope.name = 'igor'; + componentScope.ref = {}; + $rootScope.$apply(); + expect($rootScope.name).toEqual('igor'); + expect(componentScope.ref).toBe($rootScope.name); + expect(componentScope.refAlias).toBe($rootScope.name); + expect(componentScope.$refAlias).toBe($rootScope.name); + })); - expect($rootScope.name).toBe('misko'); - expect(componentScope.ref).toBe('misko'); - expect(componentScope.refAlias).toBe('misko'); - expect(componentScope.$refAlias).toBe('misko'); + it('should not break if local and origin both change to the same value', inject(function() { + $rootScope.name = 'aaa'; - $rootScope.name = {}; - $rootScope.$apply(); - expect(componentScope.ref).toBe($rootScope.name); - expect(componentScope.refAlias).toBe($rootScope.name); - expect(componentScope.$refAlias).toBe($rootScope.name); - })); + compile('
'); + //change both sides to the same item within the same digest cycle + componentScope.ref = 'same'; + $rootScope.name = 'same'; + $rootScope.$apply(); - it('should update local when both change', inject(function() { - compile('
'); - $rootScope.name = {mark:123}; - componentScope.ref = 'misko'; + //change origin back to its previous value + $rootScope.name = 'aaa'; + $rootScope.$apply(); - $rootScope.$apply(); - expect($rootScope.name).toEqual({mark:123}); - expect(componentScope.ref).toBe($rootScope.name); - expect(componentScope.refAlias).toBe($rootScope.name); - expect(componentScope.$refAlias).toBe($rootScope.name); + expect($rootScope.name).toBe('aaa'); + expect(componentScope.ref).toBe('aaa'); + })); - $rootScope.name = 'igor'; - componentScope.ref = {}; - $rootScope.$apply(); - expect($rootScope.name).toEqual('igor'); - expect(componentScope.ref).toBe($rootScope.name); - expect(componentScope.refAlias).toBe($rootScope.name); - expect(componentScope.$refAlias).toBe($rootScope.name); - })); + it('should complain on non assignable changes', inject(function() { + compile('
'); + $rootScope.name = 'world'; + $rootScope.$apply(); + expect(componentScope.ref).toBe('hello world'); - it('should not break if local and origin both change to the same value', inject(function() { - $rootScope.name = 'aaa'; + componentScope.ref = 'ignore me'; + expect(function() { $rootScope.$apply(); }). + toThrowMinErr('$compile', 'nonassign', 'Expression \'\'hello \' + name\' in attribute \'ref\' 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; - compile('
'); + $rootScope.name = 'misko'; + $rootScope.$apply(); + expect(componentScope.ref).toBe('hello misko'); + })); - //change both sides to the same item within the same digest cycle - componentScope.ref = 'same'; - $rootScope.name = 'same'; - $rootScope.$apply(); + it('should complain if assigning to undefined', inject(function() { + compile('
'); + $rootScope.$apply(); + expect(componentScope.ref).toBeUndefined(); - //change origin back to its previous value - $rootScope.name = 'aaa'; - $rootScope.$apply(); + componentScope.ref = 'ignore me'; + expect(function() { $rootScope.$apply(); }). + toThrowMinErr('$compile', 'nonassign', 'Expression \'undefined\' in attribute \'ref\' used with directive \'myComponent\' is non-assignable!'); + expect(componentScope.ref).toBeUndefined(); - expect($rootScope.name).toBe('aaa'); - expect(componentScope.ref).toBe('aaa'); - })); + $rootScope.$$phase = null; // reset since the exception was rethrown which prevented phase clearing + $rootScope.$apply(); + expect(componentScope.ref).toBeUndefined(); + })); - it('should complain on non assignable changes', inject(function() { - compile('
'); - $rootScope.name = 'world'; - $rootScope.$apply(); - expect(componentScope.ref).toBe('hello world'); + // regression + it('should stabilize model', inject(function() { + compile('
'); - componentScope.ref = 'ignore me'; - expect(function() { $rootScope.$apply(); }). - toThrowMinErr('$compile', 'nonassign', 'Expression \'\'hello \' + name\' in attribute \'ref\' 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; + var lastRefValueInParent; + $rootScope.$watch('name', function(ref) { + lastRefValueInParent = ref; + }); - $rootScope.name = 'misko'; - $rootScope.$apply(); - expect(componentScope.ref).toBe('hello misko'); - })); + $rootScope.name = 'aaa'; + $rootScope.$apply(); - it('should complain if assigning to undefined', inject(function() { - compile('
'); - $rootScope.$apply(); - expect(componentScope.ref).toBeUndefined(); + componentScope.reference = 'new'; + $rootScope.$apply(); - componentScope.ref = 'ignore me'; - expect(function() { $rootScope.$apply(); }). - toThrowMinErr('$compile', 'nonassign', 'Expression \'undefined\' in attribute \'ref\' used with directive \'myComponent\' is non-assignable!'); - expect(componentScope.ref).toBeUndefined(); + expect(lastRefValueInParent).toBe('new'); + })); - $rootScope.$$phase = null; // reset since the exception was rethrown which prevented phase clearing - $rootScope.$apply(); - expect(componentScope.ref).toBeUndefined(); - })); + describe('literal objects', function() { + it('should copy parent changes', inject(function() { + compile('
'); - // regression - it('should stabilize model', inject(function() { - compile('
'); + $rootScope.name = 'a'; + $rootScope.$apply(); + expect(componentScope.reference).toEqual({name: 'a'}); - var lastRefValueInParent; - $rootScope.$watch('name', function(ref) { - lastRefValueInParent = ref; - }); + $rootScope.name = 'b'; + $rootScope.$apply(); + expect(componentScope.reference).toEqual({name: 'b'}); + })); - $rootScope.name = 'aaa'; - $rootScope.$apply(); + it('should not change the component when parent does not change', inject(function() { + compile('
'); - componentScope.reference = 'new'; - $rootScope.$apply(); + $rootScope.name = 'a'; + $rootScope.$apply(); + var lastComponentValue = componentScope.reference; + $rootScope.$apply(); + expect(componentScope.reference).toBe(lastComponentValue); + })); - expect(lastRefValueInParent).toBe('new'); - })); + it('should complain when the component changes', inject(function() { + compile('
'); - describe('literal objects', function() { - it('should copy parent changes', inject(function() { - compile('
'); + $rootScope.name = 'a'; + $rootScope.$apply(); + componentScope.reference = {name: 'b'}; + expect(function() { + $rootScope.$apply(); + }).toThrowMinErr('$compile', 'nonassign', 'Expression \'{name: name}\' in attribute \'reference\' used with directive \'myComponent\' is non-assignable!'); - $rootScope.name = 'a'; - $rootScope.$apply(); - expect(componentScope.reference).toEqual({name: 'a'}); + })); - $rootScope.name = 'b'; - $rootScope.$apply(); - expect(componentScope.reference).toEqual({name: 'b'}); - })); + it('should work for primitive literals', inject(function() { + test('1', 1); + test('null', null); + test('undefined', undefined); + test('\'someString\'', 'someString'); + test('true', true); - it('should not change the component when parent does not change', inject(function() { - compile('
'); + function test(literalString, literalValue) { + compile('
'); - $rootScope.name = 'a'; - $rootScope.$apply(); - var lastComponentValue = componentScope.reference; - $rootScope.$apply(); - expect(componentScope.reference).toBe(lastComponentValue); - })); + $rootScope.$apply(); + expect(componentScope.reference).toBe(literalValue); + dealoc(element); + } + })); - 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}\' in attribute \'reference\' 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'); - test('true', true); + describe('optional object reference', function() { + it('should update local when origin changes', inject(function() { + compile('
'); + expect(componentScope.optRef).toBeUndefined(); + expect(componentScope.optRefAlias).toBe(componentScope.optRef); + expect(componentScope.$optRefAlias).toBe(componentScope.optRef); - function test(literalString, literalValue) { - compile('
'); + $rootScope.name = 'misko'; + $rootScope.$apply(); + expect(componentScope.optref).toBe($rootScope.name); + expect(componentScope.optrefAlias).toBe($rootScope.name); + expect(componentScope.$optrefAlias).toBe($rootScope.name); - $rootScope.$apply(); - expect(componentScope.reference).toBe(literalValue); - dealoc(element); - } - })); + $rootScope.name = {}; + $rootScope.$apply(); + expect(componentScope.optref).toBe($rootScope.name); + expect(componentScope.optrefAlias).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).toBeUndefined(); + expect(componentScope.optrefAlias).toBeUndefined(); + expect(componentScope.$optrefAlias).toBeUndefined(); + expect(componentScope.optreference).toBeUndefined(); + })); + }); - describe('optional object reference', function() { - it('should update local when origin changes', inject(function() { - compile('
'); - expect(componentScope.optRef).toBeUndefined(); - expect(componentScope.optRefAlias).toBe(componentScope.optRef); - expect(componentScope.$optRefAlias).toBe(componentScope.optRef); + describe('collection object reference', function() { + it('should update isolate scope when origin scope changes', inject(function() { + $rootScope.collection = [{ + name: 'Gabriel', + value: 18 + }, { + name: 'Tony', + value: 91 + }]; + $rootScope.query = ''; + $rootScope.$apply(); - $rootScope.name = 'misko'; - $rootScope.$apply(); - expect(componentScope.optref).toBe($rootScope.name); - expect(componentScope.optrefAlias).toBe($rootScope.name); - expect(componentScope.$optrefAlias).toBe($rootScope.name); + compile('
'); - $rootScope.name = {}; - $rootScope.$apply(); - expect(componentScope.optref).toBe($rootScope.name); - expect(componentScope.optrefAlias).toBe($rootScope.name); - expect(componentScope.$optrefAlias).toBe($rootScope.name); - })); + expect(componentScope.colref).toEqual($rootScope.collection); + expect(componentScope.colrefAlias).toEqual(componentScope.colref); + expect(componentScope.$colrefAlias).toEqual(componentScope.colref); - it('should not throw exception when reference does not exist', inject(function() { - compile('
'); + $rootScope.query = 'Gab'; + $rootScope.$apply(); - expect(componentScope.optref).toBeUndefined(); - expect(componentScope.optrefAlias).toBeUndefined(); - expect(componentScope.$optrefAlias).toBeUndefined(); - expect(componentScope.optreference).toBeUndefined(); - })); - }); + expect(componentScope.colref).toEqual([$rootScope.collection[0]]); + expect(componentScope.colrefAlias).toEqual([$rootScope.collection[0]]); + expect(componentScope.$colrefAlias).toEqual([$rootScope.collection[0]]); + })); + it('should update origin scope when isolate scope changes', inject(function() { + $rootScope.collection = [{ + name: 'Gabriel', + value: 18 + }, { + name: 'Tony', + value: 91 + }]; - describe('collection object reference', function() { - it('should update isolate scope when origin scope changes', inject(function() { - $rootScope.collection = [{ - name: 'Gabriel', - value: 18 - }, { - name: 'Tony', - value: 91 - }]; - $rootScope.query = ''; - $rootScope.$apply(); + compile('
'); - compile('
'); + var newItem = { + name: 'Pablo', + value: 10 + }; + componentScope.colref.push(newItem); + componentScope.$apply(); - expect(componentScope.colref).toEqual($rootScope.collection); - expect(componentScope.colrefAlias).toEqual(componentScope.colref); - expect(componentScope.$colrefAlias).toEqual(componentScope.colref); + expect($rootScope.collection[2]).toEqual(newItem); + })); + }); - $rootScope.query = 'Gab'; - $rootScope.$apply(); - expect(componentScope.colref).toEqual([$rootScope.collection[0]]); - expect(componentScope.colrefAlias).toEqual([$rootScope.collection[0]]); - expect(componentScope.$colrefAlias).toEqual([$rootScope.collection[0]]); - })); + describe('one-way binding', function() { + it('should update isolate when the identity of origin changes', inject(function() { + compile('
'); - it('should update origin scope when isolate scope changes', inject(function() { - $rootScope.collection = [{ - name: 'Gabriel', - value: 18 - }, { - name: 'Tony', - value: 91 - }]; + expect(componentScope.owRef).toBeUndefined(); + expect(componentScope.owRefAlias).toBe(componentScope.owRef); + expect(componentScope.$owRefAlias).toBe(componentScope.owRef); - compile('
'); + $rootScope.obj = {value: 'initial'}; + $rootScope.$apply(); - var newItem = { - name: 'Pablo', - value: 10 - }; - componentScope.colref.push(newItem); - componentScope.$apply(); + expect($rootScope.obj).toEqual({value: 'initial'}); + expect(componentScope.owRef).toEqual({value: 'initial'}); + expect(componentScope.owRefAlias).toBe(componentScope.owRef); + expect(componentScope.$owRefAlias).toBe(componentScope.owRef); - expect($rootScope.collection[2]).toEqual(newItem); - })); - }); + // This changes in both scopes because of reference + $rootScope.obj.value = 'origin1'; + $rootScope.$apply(); + expect(componentScope.owRef.value).toBe('origin1'); + expect(componentScope.owRefAlias.value).toBe('origin1'); + expect(componentScope.$owRefAlias.value).toBe('origin1'); + componentScope.owRef = {value: 'isolate1'}; + componentScope.$apply(); + expect($rootScope.obj.value).toBe('origin1'); - describe('one-way binding', function() { - it('should update isolate when the identity of origin changes', inject(function() { - compile('
'); + // Change does not propagate because object identity hasn't changed + $rootScope.obj.value = 'origin2'; + $rootScope.$apply(); + expect(componentScope.owRef.value).toBe('isolate1'); + expect(componentScope.owRefAlias.value).toBe('origin2'); + expect(componentScope.$owRefAlias.value).toBe('origin2'); - expect(componentScope.owRef).toBeUndefined(); - expect(componentScope.owRefAlias).toBe(componentScope.owRef); - expect(componentScope.$owRefAlias).toBe(componentScope.owRef); + // Change does propagate because object identity changes + $rootScope.obj = {value: 'origin3'}; + $rootScope.$apply(); + expect(componentScope.owRef.value).toBe('origin3'); + expect(componentScope.owRef).toBe($rootScope.obj); + expect(componentScope.owRefAlias).toBe($rootScope.obj); + expect(componentScope.$owRefAlias).toBe($rootScope.obj); + })); - $rootScope.obj = {value: 'initial'}; - $rootScope.$apply(); + it('should update isolate when both change', inject(function() { + compile('
'); - expect($rootScope.obj).toEqual({value: 'initial'}); - expect(componentScope.owRef).toEqual({value: 'initial'}); - expect(componentScope.owRefAlias).toBe(componentScope.owRef); - expect(componentScope.$owRefAlias).toBe(componentScope.owRef); + $rootScope.name = {mark:123}; + componentScope.owRef = 'misko'; - // This changes in both scopes because of reference - $rootScope.obj.value = 'origin1'; - $rootScope.$apply(); - expect(componentScope.owRef.value).toBe('origin1'); - expect(componentScope.owRefAlias.value).toBe('origin1'); - expect(componentScope.$owRefAlias.value).toBe('origin1'); + $rootScope.$apply(); + expect($rootScope.name).toEqual({mark:123}); + expect(componentScope.owRef).toBe($rootScope.name); + expect(componentScope.owRefAlias).toBe($rootScope.name); + expect(componentScope.$owRefAlias).toBe($rootScope.name); - componentScope.owRef = {value: 'isolate1'}; - componentScope.$apply(); - expect($rootScope.obj.value).toBe('origin1'); + $rootScope.name = 'igor'; + componentScope.owRef = {}; + $rootScope.$apply(); + expect($rootScope.name).toEqual('igor'); + expect(componentScope.owRef).toBe($rootScope.name); + expect(componentScope.owRefAlias).toBe($rootScope.name); + expect(componentScope.$owRefAlias).toBe($rootScope.name); + })); - // Change does not propagate because object identity hasn't changed - $rootScope.obj.value = 'origin2'; - $rootScope.$apply(); - expect(componentScope.owRef.value).toBe('isolate1'); - expect(componentScope.owRefAlias.value).toBe('origin2'); - expect(componentScope.$owRefAlias.value).toBe('origin2'); + describe('initialization', function() { + var component, log; - // Change does propagate because object identity changes - $rootScope.obj = {value: 'origin3'}; - $rootScope.$apply(); - expect(componentScope.owRef.value).toBe('origin3'); - expect(componentScope.owRef).toBe($rootScope.obj); - expect(componentScope.owRefAlias).toBe($rootScope.obj); - expect(componentScope.$owRefAlias).toBe($rootScope.obj); - })); + beforeEach(function() { + log = []; + angular.module('owComponentTest', []) + .component('owComponent', { + bindings: { input: '<' }, + controller: function() { + component = this; + this.input = 'constructor'; + log.push('constructor'); - it('should update isolate when both change', inject(function() { - compile('
'); + this.$onInit = function() { + this.input = '$onInit'; + log.push('$onInit'); + }; - $rootScope.name = {mark:123}; - componentScope.owRef = 'misko'; + this.$onChanges = function(changes) { + if (changes.input) { + log.push(['$onChanges', copy(changes.input)]); + } + }; + } + }); + }); - $rootScope.$apply(); - expect($rootScope.name).toEqual({mark:123}); - expect(componentScope.owRef).toBe($rootScope.name); - expect(componentScope.owRefAlias).toBe($rootScope.name); - expect(componentScope.$owRefAlias).toBe($rootScope.name); + it('should not update isolate again after $onInit if outer has not changed', function() { + module('owComponentTest'); + inject(function() { + $rootScope.name = 'outer'; + compile(''); - $rootScope.name = 'igor'; - componentScope.owRef = {}; - $rootScope.$apply(); - expect($rootScope.name).toEqual('igor'); - expect(componentScope.owRef).toBe($rootScope.name); - expect(componentScope.owRefAlias).toBe($rootScope.name); - expect(componentScope.$owRefAlias).toBe($rootScope.name); - })); + expect($rootScope.name).toEqual('outer'); + expect(component.input).toEqual('$onInit'); - describe('initialization', function() { - var component, log; - - beforeEach(function() { - log = []; - angular.module('owComponentTest', []) - .component('owComponent', { - bindings: { input: '<' }, - controller: function() { - component = this; - this.input = 'constructor'; - log.push('constructor'); - - this.$onInit = function() { - this.input = '$onInit'; - log.push('$onInit'); - }; + $rootScope.$digest(); - this.$onChanges = function(changes) { - if (changes.input) { - log.push(['$onChanges', copy(changes.input)]); - } - }; - } - }); - }); + expect($rootScope.name).toEqual('outer'); + expect(component.input).toEqual('$onInit'); - it('should not update isolate again after $onInit if outer has not changed', function() { - module('owComponentTest'); - inject(function() { - $rootScope.name = 'outer'; - compile(''); + expect(log).toEqual([ + 'constructor', + ['$onChanges', jasmine.objectContaining({ currentValue: 'outer' })], + '$onInit' + ]); + }); + }); - expect($rootScope.name).toEqual('outer'); - expect(component.input).toEqual('$onInit'); + it('should not update isolate again after $onInit if outer object reference has not changed', function() { + module('owComponentTest'); + inject(function() { + $rootScope.name = ['outer']; + compile(''); - $rootScope.$digest(); + expect($rootScope.name).toEqual(['outer']); + expect(component.input).toEqual('$onInit'); - expect($rootScope.name).toEqual('outer'); - expect(component.input).toEqual('$onInit'); + $rootScope.name[0] = 'inner'; + $rootScope.$digest(); - expect(log).toEqual([ - 'constructor', - ['$onChanges', jasmine.objectContaining({ currentValue: 'outer' })], - '$onInit' - ]); - }); - }); + expect($rootScope.name).toEqual(['inner']); + expect(component.input).toEqual('$onInit'); - it('should not update isolate again after $onInit if outer object reference has not changed', function() { - module('owComponentTest'); - inject(function() { - $rootScope.name = ['outer']; - compile(''); + expect(log).toEqual([ + 'constructor', + ['$onChanges', jasmine.objectContaining({ currentValue: ['outer'] })], + '$onInit' + ]); + }); + }); - expect($rootScope.name).toEqual(['outer']); - expect(component.input).toEqual('$onInit'); + it('should update isolate again after $onInit if outer object reference changes even if equal', function() { + module('owComponentTest'); + inject(function() { + $rootScope.name = ['outer']; + compile(''); - $rootScope.name[0] = 'inner'; - $rootScope.$digest(); + expect($rootScope.name).toEqual(['outer']); + expect(component.input).toEqual('$onInit'); - expect($rootScope.name).toEqual(['inner']); - expect(component.input).toEqual('$onInit'); + $rootScope.name = ['outer']; + $rootScope.$digest(); - expect(log).toEqual([ - 'constructor', - ['$onChanges', jasmine.objectContaining({ currentValue: ['outer'] })], - '$onInit' - ]); - }); - }); + expect($rootScope.name).toEqual(['outer']); + expect(component.input).toEqual(['outer']); - it('should update isolate again after $onInit if outer object reference changes even if equal', function() { - module('owComponentTest'); - inject(function() { - $rootScope.name = ['outer']; - compile(''); + expect(log).toEqual([ + 'constructor', + ['$onChanges', jasmine.objectContaining({ currentValue: ['outer'] })], + '$onInit', + ['$onChanges', jasmine.objectContaining({ previousValue: ['outer'], currentValue: ['outer'] })] + ]); + }); + }); - expect($rootScope.name).toEqual(['outer']); - expect(component.input).toEqual('$onInit'); + it('should not update isolate again after $onInit if outer is a literal', function() { + module('owComponentTest'); + inject(function() { + $rootScope.name = 'outer'; + compile(''); - $rootScope.name = ['outer']; - $rootScope.$digest(); + expect(component.input).toEqual('$onInit'); - expect($rootScope.name).toEqual(['outer']); - expect(component.input).toEqual(['outer']); + // No outer change + $rootScope.$apply('name = "outer"'); + expect(component.input).toEqual('$onInit'); - expect(log).toEqual([ - 'constructor', - ['$onChanges', jasmine.objectContaining({ currentValue: ['outer'] })], - '$onInit', - ['$onChanges', jasmine.objectContaining({ previousValue: ['outer'], currentValue: ['outer'] })] - ]); - }); - }); + // Outer change + $rootScope.$apply('name = "re-outer"'); + expect(component.input).toEqual(['re-outer']); - it('should not update isolate again after $onInit if outer is a literal', function() { - module('owComponentTest'); - inject(function() { - $rootScope.name = 'outer'; - compile(''); - - expect(component.input).toEqual('$onInit'); - - // No outer change - $rootScope.$apply('name = "outer"'); - expect(component.input).toEqual('$onInit'); - - // Outer change - $rootScope.$apply('name = "re-outer"'); - expect(component.input).toEqual(['re-outer']); - - expect(log).toEqual([ - 'constructor', - [ - '$onChanges', - jasmine.objectContaining({currentValue: ['outer']}) - ], - '$onInit', - [ - '$onChanges', - jasmine.objectContaining({previousValue: ['outer'], currentValue: ['re-outer']}) - ] - ]); - }); - }); + expect(log).toEqual([ + 'constructor', + [ + '$onChanges', + jasmine.objectContaining({currentValue: ['outer']}) + ], + '$onInit', + [ + '$onChanges', + jasmine.objectContaining({previousValue: ['outer'], currentValue: ['re-outer']}) + ] + ]); + }); + }); - it('should update isolate again after $onInit if outer has changed (before initial watchAction call)', function() { - module('owComponentTest'); - inject(function() { - $rootScope.name = 'outer1'; - compile(''); - - expect(component.input).toEqual('$onInit'); - $rootScope.$apply('name = "outer2"'); - - expect($rootScope.name).toEqual('outer2'); - expect(component.input).toEqual('outer2'); - expect(log).toEqual([ - 'constructor', - ['$onChanges', jasmine.objectContaining({ currentValue: 'outer1' })], - '$onInit', - ['$onChanges', jasmine.objectContaining({ currentValue: 'outer2', previousValue: 'outer1' })] - ]); - }); - }); + it('should update isolate again after $onInit if outer has changed (before initial watchAction call)', function() { + module('owComponentTest'); + inject(function() { + $rootScope.name = 'outer1'; + compile(''); - it('should update isolate again after $onInit if outer has changed (before initial watchAction call)', function() { - angular.module('owComponentTest') - .directive('changeInput', function() { - return function(scope, elem, attrs) { - scope.name = 'outer2'; - }; - }); - module('owComponentTest'); - inject(function() { - $rootScope.name = 'outer1'; - compile(''); + expect(component.input).toEqual('$onInit'); + $rootScope.$apply('name = "outer2"'); - expect(component.input).toEqual('$onInit'); - $rootScope.$digest(); + expect($rootScope.name).toEqual('outer2'); + expect(component.input).toEqual('outer2'); + expect(log).toEqual([ + 'constructor', + ['$onChanges', jasmine.objectContaining({ currentValue: 'outer1' })], + '$onInit', + ['$onChanges', jasmine.objectContaining({ currentValue: 'outer2', previousValue: 'outer1' })] + ]); + }); + }); - expect($rootScope.name).toEqual('outer2'); - expect(component.input).toEqual('outer2'); - expect(log).toEqual([ - 'constructor', - ['$onChanges', jasmine.objectContaining({ currentValue: 'outer1' })], - '$onInit', - ['$onChanges', jasmine.objectContaining({ currentValue: 'outer2', previousValue: 'outer1' })] - ]); - }); + it('should update isolate again after $onInit if outer has changed (before initial watchAction call)', function() { + angular.module('owComponentTest') + .directive('changeInput', function() { + return function(scope, elem, attrs) { + scope.name = 'outer2'; + }; }); - }); + module('owComponentTest'); + inject(function() { + $rootScope.name = 'outer1'; + compile(''); - it('should not break when isolate and origin both change to the same value', inject(function() { - $rootScope.name = 'aaa'; - compile('
'); + expect(component.input).toEqual('$onInit'); + $rootScope.$digest(); - //change both sides to the same item within the same digest cycle - componentScope.owRef = 'same'; - $rootScope.name = 'same'; - $rootScope.$apply(); + expect($rootScope.name).toEqual('outer2'); + expect(component.input).toEqual('outer2'); + expect(log).toEqual([ + 'constructor', + ['$onChanges', jasmine.objectContaining({ currentValue: 'outer1' })], + '$onInit', + ['$onChanges', jasmine.objectContaining({ currentValue: 'outer2', previousValue: 'outer1' })] + ]); + }); + }); + }); - //change origin back to its previous value - $rootScope.name = 'aaa'; - $rootScope.$apply(); + it('should not break when isolate and origin both change to the same value', inject(function() { + $rootScope.name = 'aaa'; + compile('
'); - expect($rootScope.name).toBe('aaa'); - expect(componentScope.owRef).toBe('aaa'); - })); + //change both sides to the same item within the same digest cycle + componentScope.owRef = 'same'; + $rootScope.name = 'same'; + $rootScope.$apply(); + //change origin back to its previous value + $rootScope.name = 'aaa'; + $rootScope.$apply(); - it('should not update origin when identity of isolate changes', inject(function() { - $rootScope.name = {mark:123}; - compile('
'); + expect($rootScope.name).toBe('aaa'); + expect(componentScope.owRef).toBe('aaa'); + })); - expect($rootScope.name).toEqual({mark:123}); - expect(componentScope.owRef).toBe($rootScope.name); - expect(componentScope.owRefAlias).toBe($rootScope.name); - expect(componentScope.$owRefAlias).toBe($rootScope.name); - componentScope.owRef = 'martin'; - $rootScope.$apply(); - expect($rootScope.name).toEqual({mark: 123}); - expect(componentScope.owRef).toBe('martin'); - expect(componentScope.owRefAlias).toEqual({mark: 123}); - expect(componentScope.$owRefAlias).toEqual({mark: 123}); - })); + it('should not update origin when identity of isolate changes', inject(function() { + $rootScope.name = {mark:123}; + compile('
'); + expect($rootScope.name).toEqual({mark:123}); + expect(componentScope.owRef).toBe($rootScope.name); + expect(componentScope.owRefAlias).toBe($rootScope.name); + expect(componentScope.$owRefAlias).toBe($rootScope.name); - it('should update origin when property of isolate object reference changes', inject(function() { - $rootScope.obj = {mark:123}; - compile('
'); + componentScope.owRef = 'martin'; + $rootScope.$apply(); + expect($rootScope.name).toEqual({mark: 123}); + expect(componentScope.owRef).toBe('martin'); + expect(componentScope.owRefAlias).toEqual({mark: 123}); + expect(componentScope.$owRefAlias).toEqual({mark: 123}); + })); - expect($rootScope.obj).toEqual({mark:123}); - expect(componentScope.owRef).toBe($rootScope.obj); - componentScope.owRef.mark = 789; - $rootScope.$apply(); - expect($rootScope.obj).toEqual({mark: 789}); - expect(componentScope.owRef).toBe($rootScope.obj); - })); + it('should update origin when property of isolate object reference changes', inject(function() { + $rootScope.obj = {mark:123}; + compile('
'); + expect($rootScope.obj).toEqual({mark:123}); + expect(componentScope.owRef).toBe($rootScope.obj); - it('should not throw on non assignable expressions in the parent', inject(function() { - compile('
'); + componentScope.owRef.mark = 789; + $rootScope.$apply(); + expect($rootScope.obj).toEqual({mark: 789}); + expect(componentScope.owRef).toBe($rootScope.obj); + })); - $rootScope.name = 'world'; - $rootScope.$apply(); - expect(componentScope.owRef).toBe('hello world'); - componentScope.owRef = 'ignore me'; - expect(componentScope.owRef).toBe('ignore me'); - expect($rootScope.name).toBe('world'); + it('should not throw on non assignable expressions in the parent', inject(function() { + compile('
'); - $rootScope.name = 'misko'; - $rootScope.$apply(); - expect(componentScope.owRef).toBe('hello misko'); - })); + $rootScope.name = 'world'; + $rootScope.$apply(); + expect(componentScope.owRef).toBe('hello world'); + componentScope.owRef = 'ignore me'; + expect(componentScope.owRef).toBe('ignore me'); + expect($rootScope.name).toBe('world'); - it('should not throw when assigning to undefined', inject(function() { - compile('
'); + $rootScope.name = 'misko'; + $rootScope.$apply(); + expect(componentScope.owRef).toBe('hello misko'); + })); - expect(componentScope.owRef).toBeUndefined(); - componentScope.owRef = 'ignore me'; - expect(componentScope.owRef).toBe('ignore me'); + it('should not throw when assigning to undefined', inject(function() { + compile('
'); - $rootScope.$apply(); - expect(componentScope.owRef).toBe('ignore me'); - })); + expect(componentScope.owRef).toBeUndefined(); + componentScope.owRef = 'ignore me'; + expect(componentScope.owRef).toBe('ignore me'); - it('should update isolate scope when "<"-bound NaN changes', inject(function() { - $rootScope.num = NaN; - compile('
'); + $rootScope.$apply(); + expect(componentScope.owRef).toBe('ignore me'); + })); - var isolateScope = element.isolateScope(); - expect(isolateScope.owRef).toBeNaN(); - $rootScope.num = 64; - $rootScope.$apply(); - expect(isolateScope.owRef).toBe(64); - })); + it('should update isolate scope when "<"-bound NaN changes', inject(function() { + $rootScope.num = NaN; + compile('
'); + var isolateScope = element.isolateScope(); + expect(isolateScope.owRef).toBeNaN(); - describe('literal objects', function() { - it('should copy parent changes', inject(function() { - compile('
'); + $rootScope.num = 64; + $rootScope.$apply(); + expect(isolateScope.owRef).toBe(64); + })); - $rootScope.name = 'a'; - $rootScope.$apply(); - expect(componentScope.owRef).toEqual({name: 'a'}); - $rootScope.name = 'b'; - $rootScope.$apply(); - expect(componentScope.owRef).toEqual({name: 'b'}); - })); + describe('literal objects', function() { + it('should copy parent changes', inject(function() { + compile('
'); + $rootScope.name = 'a'; + $rootScope.$apply(); + expect(componentScope.owRef).toEqual({name: 'a'}); - it('should not change the isolated scope when origin does not change', inject(function() { - compile('
'); + $rootScope.name = 'b'; + $rootScope.$apply(); + expect(componentScope.owRef).toEqual({name: 'b'}); + })); - $rootScope.name = 'a'; - $rootScope.$apply(); - var lastComponentValue = componentScope.owRef; - $rootScope.$apply(); - expect(componentScope.owRef).toBe(lastComponentValue); - })); + it('should not change the isolated scope when origin does not change', inject(function() { + compile('
'); - it('should watch input values to array literals', inject(function() { - $rootScope.name = 'georgios'; - $rootScope.obj = {name: 'pete'}; - compile('
'); + $rootScope.name = 'a'; + $rootScope.$apply(); + var lastComponentValue = componentScope.owRef; + $rootScope.$apply(); + expect(componentScope.owRef).toBe(lastComponentValue); + })); - expect(componentScope.owRef).toEqual([{name: 'georgios'}, {name: 'pete'}]); - $rootScope.name = 'lucas'; - $rootScope.obj = {name: 'martin'}; - $rootScope.$apply(); - expect(componentScope.owRef).toEqual([{name: 'lucas'}, {name: 'martin'}]); - })); + it('should watch input values to array literals', inject(function() { + $rootScope.name = 'georgios'; + $rootScope.obj = {name: 'pete'}; + compile('
'); + expect(componentScope.owRef).toEqual([{name: 'georgios'}, {name: 'pete'}]); - it('should watch input values object literals', inject(function() { - $rootScope.name = 'georgios'; - $rootScope.obj = {name: 'pete'}; - compile('
'); + $rootScope.name = 'lucas'; + $rootScope.obj = {name: 'martin'}; + $rootScope.$apply(); + expect(componentScope.owRef).toEqual([{name: 'lucas'}, {name: 'martin'}]); + })); - expect(componentScope.owRef).toEqual({name: 'georgios', item: {name: 'pete'}}); - $rootScope.name = 'lucas'; - $rootScope.obj = {name: 'martin'}; - $rootScope.$apply(); - expect(componentScope.owRef).toEqual({name: 'lucas', item: {name: 'martin'}}); - })); + it('should watch input values object literals', inject(function() { + $rootScope.name = 'georgios'; + $rootScope.obj = {name: 'pete'}; + compile('
'); + expect(componentScope.owRef).toEqual({name: 'georgios', item: {name: 'pete'}}); - it('should not complain when the isolated scope changes', inject(function() { - compile('
'); + $rootScope.name = 'lucas'; + $rootScope.obj = {name: 'martin'}; + $rootScope.$apply(); + expect(componentScope.owRef).toEqual({name: 'lucas', item: {name: 'martin'}}); + })); - $rootScope.name = 'a'; - $rootScope.$apply(); - componentScope.owRef = {name: 'b'}; - componentScope.$apply(); - expect(componentScope.owRef).toEqual({name: 'b'}); - expect($rootScope.name).toBe('a'); + it('should not complain when the isolated scope changes', inject(function() { + compile('
'); - $rootScope.name = 'c'; - $rootScope.$apply(); - expect(componentScope.owRef).toEqual({name: 'c'}); - })); + $rootScope.name = 'a'; + $rootScope.$apply(); + componentScope.owRef = {name: 'b'}; + componentScope.$apply(); - it('should work for primitive literals', inject(function() { - test('1', 1); - test('null', null); - test('undefined', undefined); - test('\'someString\'', 'someString'); - test('true', true); + expect(componentScope.owRef).toEqual({name: 'b'}); + expect($rootScope.name).toBe('a'); - function test(literalString, literalValue) { - compile('
'); + $rootScope.name = 'c'; + $rootScope.$apply(); + expect(componentScope.owRef).toEqual({name: 'c'}); + })); - expect(componentScope.owRef).toBe(literalValue); - dealoc(element); - } - })); + it('should work for primitive literals', inject(function() { + test('1', 1); + test('null', null); + test('undefined', undefined); + test('\'someString\'', 'someString'); + test('true', true); - describe('optional one-way binding', function() { - it('should update local when origin changes', inject(function() { - compile('
'); + function test(literalString, literalValue) { + compile('
'); - expect(componentScope.owOptref).toBeUndefined(); - expect(componentScope.owOptrefAlias).toBe(componentScope.owOptref); - expect(componentScope.$owOptrefAlias).toBe(componentScope.owOptref); + expect(componentScope.owRef).toBe(literalValue); + dealoc(element); + } + })); - $rootScope.name = 'misko'; - $rootScope.$apply(); - expect(componentScope.owOptref).toBe($rootScope.name); - expect(componentScope.owOptrefAlias).toBe($rootScope.name); - expect(componentScope.$owOptrefAlias).toBe($rootScope.name); + describe('optional one-way binding', function() { + it('should update local when origin changes', inject(function() { + compile('
'); - $rootScope.name = {}; - $rootScope.$apply(); - expect(componentScope.owOptref).toBe($rootScope.name); - expect(componentScope.owOptrefAlias).toBe($rootScope.name); - expect(componentScope.$owOptrefAlias).toBe($rootScope.name); - })); + expect(componentScope.owOptref).toBeUndefined(); + expect(componentScope.owOptrefAlias).toBe(componentScope.owOptref); + expect(componentScope.$owOptrefAlias).toBe(componentScope.owOptref); - it('should not throw exception when reference does not exist', inject(function() { - compile('
'); - - expect(componentScope.owOptref).toBeUndefined(); - expect(componentScope.owOptrefAlias).toBeUndefined(); - expect(componentScope.$owOptrefAlias).toBeUndefined(); - })); - }); - }); - }); - - describe('executable expression', function() { - it('should allow expression execution with locals', inject(function() { - compile('
'); - $rootScope.count = 2; + $rootScope.name = 'misko'; + $rootScope.$apply(); + expect(componentScope.owOptref).toBe($rootScope.name); + expect(componentScope.owOptrefAlias).toBe($rootScope.name); + expect(componentScope.$owOptrefAlias).toBe($rootScope.name); - expect(typeof componentScope.expr).toBe('function'); - expect(typeof componentScope.exprAlias).toBe('function'); - expect(typeof componentScope.$exprAlias).toBe('function'); + $rootScope.name = {}; + $rootScope.$apply(); + expect(componentScope.owOptref).toBe($rootScope.name); + expect(componentScope.owOptrefAlias).toBe($rootScope.name); + expect(componentScope.$owOptrefAlias).toBe($rootScope.name); + })); - expect(componentScope.expr({offset: 1})).toEqual(3); - expect($rootScope.count).toEqual(3); + it('should not throw exception when reference does not exist', inject(function() { + compile('
'); - expect(componentScope.exprAlias({offset: 10})).toEqual(13); - expect(componentScope.$exprAlias({offset: 10})).toEqual(23); - expect($rootScope.count).toEqual(23); + expect(componentScope.owOptref).toBeUndefined(); + expect(componentScope.owOptrefAlias).toBeUndefined(); + expect(componentScope.$owOptrefAlias).toBeUndefined(); })); }); + }); + }); - it('should throw on unknown definition', inject(function() { - expect(function() { - compile('
'); - }).toThrowMinErr('$compile', 'iscp', 'Invalid isolate scope definition for directive \'badDeclaration\'. Definition: {... attr: \'xxx\' ...}'); - })); + describe('executable expression', function() { + it('should allow expression execution with locals', inject(function() { + compile('
'); + $rootScope.count = 2; - it('should expose a $$isolateBindings property onto the scope', inject(function() { - compile('
'); - - expect(typeof componentScope.$$isolateBindings).toBe('object'); - - expect(componentScope.$$isolateBindings.attr.mode).toBe('@'); - expect(componentScope.$$isolateBindings.attr.attrName).toBe('attr'); - expect(componentScope.$$isolateBindings.attrAlias.attrName).toBe('attr'); - expect(componentScope.$$isolateBindings.$attrAlias.attrName).toBe('$attr$'); - expect(componentScope.$$isolateBindings.ref.mode).toBe('='); - expect(componentScope.$$isolateBindings.ref.attrName).toBe('ref'); - expect(componentScope.$$isolateBindings.refAlias.attrName).toBe('ref'); - expect(componentScope.$$isolateBindings.$refAlias.attrName).toBe('$ref$'); - expect(componentScope.$$isolateBindings.reference.mode).toBe('='); - expect(componentScope.$$isolateBindings.reference.attrName).toBe('reference'); - expect(componentScope.$$isolateBindings.owRef.mode).toBe('<'); - expect(componentScope.$$isolateBindings.owRef.attrName).toBe('owRef'); - expect(componentScope.$$isolateBindings.owRefAlias.attrName).toBe('owRef'); - expect(componentScope.$$isolateBindings.$owRefAlias.attrName).toBe('$owRef$'); - expect(componentScope.$$isolateBindings.expr.mode).toBe('&'); - expect(componentScope.$$isolateBindings.expr.attrName).toBe('expr'); - expect(componentScope.$$isolateBindings.exprAlias.attrName).toBe('expr'); - expect(componentScope.$$isolateBindings.$exprAlias.attrName).toBe('$expr$'); - - var firstComponentScope = componentScope, - first$$isolateBindings = componentScope.$$isolateBindings; + expect(typeof componentScope.expr).toBe('function'); + expect(typeof componentScope.exprAlias).toBe('function'); + expect(typeof componentScope.$exprAlias).toBe('function'); - dealoc(element); - compile('
'); - expect(componentScope).not.toBe(firstComponentScope); - expect(componentScope.$$isolateBindings).toBe(first$$isolateBindings); - })); + expect(componentScope.expr({offset: 1})).toEqual(3); + expect($rootScope.count).toEqual(3); + expect(componentScope.exprAlias({offset: 10})).toEqual(13); + expect(componentScope.$exprAlias({offset: 10})).toEqual(23); + expect($rootScope.count).toEqual(23); + })); + }); - it('should expose isolate scope variables on controller with controllerAs when bindToController is true (template)', function() { - var controllerCalled = false; - module(function($compileProvider) { - $compileProvider.directive('fooDir', valueFn({ - template: '

isolate

', - scope: { - 'data': '=dirData', - 'oneway': '
')($rootScope); - expect(controllerCalled).toBe(true); - }); - }); + 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.mode).toBe('@'); + expect(componentScope.$$isolateBindings.attr.attrName).toBe('attr'); + expect(componentScope.$$isolateBindings.attrAlias.attrName).toBe('attr'); + expect(componentScope.$$isolateBindings.$attrAlias.attrName).toBe('$attr$'); + expect(componentScope.$$isolateBindings.ref.mode).toBe('='); + expect(componentScope.$$isolateBindings.ref.attrName).toBe('ref'); + expect(componentScope.$$isolateBindings.refAlias.attrName).toBe('ref'); + expect(componentScope.$$isolateBindings.$refAlias.attrName).toBe('$ref$'); + expect(componentScope.$$isolateBindings.reference.mode).toBe('='); + expect(componentScope.$$isolateBindings.reference.attrName).toBe('reference'); + expect(componentScope.$$isolateBindings.owRef.mode).toBe('<'); + expect(componentScope.$$isolateBindings.owRef.attrName).toBe('owRef'); + expect(componentScope.$$isolateBindings.owRefAlias.attrName).toBe('owRef'); + expect(componentScope.$$isolateBindings.$owRefAlias.attrName).toBe('$owRef$'); + expect(componentScope.$$isolateBindings.expr.mode).toBe('&'); + expect(componentScope.$$isolateBindings.expr.attrName).toBe('expr'); + expect(componentScope.$$isolateBindings.exprAlias.attrName).toBe('expr'); + expect(componentScope.$$isolateBindings.$exprAlias.attrName).toBe('$expr$'); + + var firstComponentScope = componentScope, + first$$isolateBindings = componentScope.$$isolateBindings; + + dealoc(element); + compile('
'); + expect(componentScope).not.toBe(firstComponentScope); + expect(componentScope.$$isolateBindings).toBe(first$$isolateBindings); + })); - it('should not pre-assign bound properties to the controller if `preAssignBindingsEnabled` is disabled', function() { - var controllerCalled = false, onInitCalled = false; - module(function($compileProvider) { - $compileProvider.preAssignBindingsEnabled(false); - $compileProvider.directive('fooDir', valueFn({ - template: '

isolate

', - scope: { - 'data': '=dirData', - 'oneway': '
')($rootScope); - expect(controllerCalled).toBe(true); - expect(onInitCalled).toBe(true); - }); - }); - it('should pre-assign bound properties to the controller if `preAssignBindingsEnabled` is enabled', function() { - var controllerCalled = false, onInitCalled = false; - module(function($compileProvider) { - $compileProvider.preAssignBindingsEnabled(true); - $compileProvider.directive('fooDir', valueFn({ - template: '

isolate

', - scope: { - 'data': '=dirData', - 'oneway': 'isolate

', + scope: { + 'data': '=dirData', + 'oneway': '
')($rootScope); - expect(controllerCalled).toBe(true); - expect(onInitCalled).toBe(true); - }); - }); - - it('should eventually expose isolate scope variables on ES6 class controller with controllerAs when bindToController is true', function() { - if (!/chrome/i.test(window.navigator.userAgent)) return; - var controllerCalled = false; - // eslint-disable-next-line no-eval - var Controller = eval( - 'class Foo {\n' + - ' constructor($scope) {}\n' + - ' $onInit() { this.check(); }\n' + - ' check() {\n' + - ' expect(this.data).toEqualData({\n' + - ' \'foo\': \'bar\',\n' + - ' \'baz\': \'biz\'\n' + - ' });\n' + - ' expect(this.oneway).toEqualData({\n' + - ' \'foo\': \'bar\',\n' + - ' \'baz\': \'biz\'\n' + - ' });\n' + - ' expect(this.str).toBe(\'Hello, world!\');\n' + - ' expect(this.fn()).toBe(\'called!\');\n' + - ' controllerCalled = true;\n' + - ' }\n' + - '}'); - spyOn(Controller.prototype, '$onInit').and.callThrough(); + controllerCalled = true; + }, + controllerAs: 'test', + bindToController: true + })); + }); + inject(function($compile, $rootScope) { + $rootScope.fn = valueFn('called!'); + $rootScope.whom = 'world'; + $rootScope.remoteData = { + 'foo': 'bar', + 'baz': 'biz' + }; + element = $compile('
')($rootScope); + expect(controllerCalled).toBe(true); + }); + }); - module(function($compileProvider) { - $compileProvider.directive('fooDir', valueFn({ - template: '

isolate

', - scope: { - 'data': '=dirData', - 'oneway': 'isolate

', + scope: { + 'data': '=dirData', + 'oneway': '
')($rootScope); - expect(Controller.prototype.$onInit).toHaveBeenCalled(); - expect(controllerCalled).toBe(true); - }); - }); + }, + controllerAs: 'test', + bindToController: true + })); + }); + inject(function($compile, $rootScope) { + $rootScope.fn = valueFn('called!'); + $rootScope.whom = 'world'; + $rootScope.remoteData = { + 'foo': 'bar', + 'baz': 'biz' + }; + element = $compile('
')($rootScope); + expect(controllerCalled).toBe(true); + expect(onInitCalled).toBe(true); + }); + }); + it('should eventually expose isolate scope variables on ES6 class controller with controllerAs when bindToController is true', function() { + if (!/chrome/i.test(window.navigator.userAgent)) return; + var controllerCalled = false; + // eslint-disable-next-line no-eval + var Controller = eval( + 'class Foo {\n' + + ' constructor($scope) {}\n' + + ' $onInit() {\n' + + ' expect(this.data).toEqualData({\n' + + ' \'foo\': \'bar\',\n' + + ' \'baz\': \'biz\'\n' + + ' });\n' + + ' expect(this.oneway).toEqualData({\n' + + ' \'foo\': \'bar\',\n' + + ' \'baz\': \'biz\'\n' + + ' });\n' + + ' expect(this.str).toBe(\'Hello, world!\');\n' + + ' expect(this.fn()).toBe(\'called!\');\n' + + ' controllerCalled = true;\n' + + ' }\n' + + '}'); + spyOn(Controller.prototype, '$onInit').and.callThrough(); - it('should update @-bindings on controller when bindToController and attribute change observed', function() { - module(function($compileProvider) { - $compileProvider.directive('atBinding', valueFn({ - template: '

{{At.text}}

', - scope: { - text: '@atBinding' - }, - controller: function($scope) {}, - bindToController: true, - controllerAs: 'At' - })); - }); + module(function($compileProvider) { + $compileProvider.directive('fooDir', valueFn({ + template: '

isolate

', + scope: { + 'data': '=dirData', + 'oneway': '
')($rootScope); + expect(Controller.prototype.$onInit).toHaveBeenCalled(); + expect(controllerCalled).toBe(true); + }); + }); - inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); - var p = element.find('p'); - $rootScope.$digest(); - expect(p.text()).toBe('Test: '); - $rootScope.text = 'Kittens'; - $rootScope.$digest(); - expect(p.text()).toBe('Test: Kittens'); - }); - }); + it('should update @-bindings on controller when bindToController and attribute change observed', function() { + module(function($compileProvider) { + $compileProvider.directive('atBinding', valueFn({ + template: '

{{At.text}}

', + scope: { + text: '@atBinding' + }, + controller: function($scope) {}, + bindToController: true, + controllerAs: 'At' + })); + }); + inject(function($compile, $rootScope) { + element = $compile('
')($rootScope); + var p = element.find('p'); + $rootScope.$digest(); + expect(p.text()).toBe('Test: '); - it('should expose isolate scope variables on controller with controllerAs when bindToController is true (templateUrl)', function() { - var controllerCalled = false; - module(function($compileProvider) { - $compileProvider.directive('fooDir', valueFn({ - templateUrl: 'test.html', - scope: { - 'data': '=dirData', - 'oneway': 'isolate

'); - $rootScope.fn = valueFn('called!'); - $rootScope.whom = 'world'; - $rootScope.remoteData = { - 'foo': 'bar', - 'baz': 'biz' + $rootScope.text = 'Kittens'; + $rootScope.$digest(); + expect(p.text()).toBe('Test: Kittens'); + }); + }); + + + it('should expose isolate scope variables on controller with controllerAs when bindToController is true (templateUrl)', function() { + var controllerCalled = false; + module(function($compileProvider) { + $compileProvider.directive('fooDir', valueFn({ + templateUrl: 'test.html', + scope: { + 'data': '=dirData', + 'oneway': '
')($rootScope); - $rootScope.$digest(); - expect(controllerCalled).toBe(true); - }); - }); + controllerCalled = true; + }, + controllerAs: 'test', + bindToController: true + })); + }); + inject(function($compile, $rootScope, $templateCache) { + $templateCache.put('test.html', '

isolate

'); + $rootScope.fn = valueFn('called!'); + $rootScope.whom = 'world'; + $rootScope.remoteData = { + 'foo': 'bar', + 'baz': 'biz' + }; + element = $compile('
')($rootScope); + $rootScope.$digest(); + expect(controllerCalled).toBe(true); + }); + }); - it('should throw noctrl when missing controller', function() { - module(function($compileProvider) { - $compileProvider.directive('noCtrl', valueFn({ - templateUrl: 'test.html', - scope: { - 'data': '=dirData', - 'oneway': '')($rootScope); - }).toThrowMinErr('$compile', 'noctrl', - 'Cannot bind to controller without directive \'noCtrl\'s controller.'); - }); - }); + it('should throw noctrl when missing controller', function() { + module(function($compileProvider) { + $compileProvider.directive('noCtrl', valueFn({ + templateUrl: 'test.html', + scope: { + 'data': '=dirData', + 'oneway': '')($rootScope); + }).toThrowMinErr('$compile', 'noctrl', + 'Cannot bind to controller without directive \'noCtrl\'s controller.'); + }); + }); - it('should throw badrestrict on first compilation when restrict is invalid', function() { - module(function($compileProvider, $exceptionHandlerProvider) { - $compileProvider.directive('invalidRestrictBadString', valueFn({restrict: '"'})); - $compileProvider.directive('invalidRestrictTrue', valueFn({restrict: true})); - $compileProvider.directive('invalidRestrictObject', valueFn({restrict: {}})); - $compileProvider.directive('invalidRestrictNumber', valueFn({restrict: 42})); + it('should throw badrestrict on first compilation when restrict is invalid', function() { + module(function($compileProvider, $exceptionHandlerProvider) { + $compileProvider.directive('invalidRestrictBadString', valueFn({restrict: '"'})); + $compileProvider.directive('invalidRestrictTrue', valueFn({restrict: true})); + $compileProvider.directive('invalidRestrictObject', valueFn({restrict: {}})); + $compileProvider.directive('invalidRestrictNumber', valueFn({restrict: 42})); - // We need to test with the exceptionHandler not rethrowing... - $exceptionHandlerProvider.mode('log'); - }); + // We need to test with the exceptionHandler not rethrowing... + $exceptionHandlerProvider.mode('log'); + }); - inject(function($exceptionHandler, $compile, $rootScope) { - $compile('
')($rootScope); - expect($exceptionHandler.errors.length).toBe(1); - expect($exceptionHandler.errors[0]).toMatch(/\$compile.*badrestrict.*'true'/); + inject(function($exceptionHandler, $compile, $rootScope) { + $compile('
')($rootScope); + expect($exceptionHandler.errors.length).toBe(1); + expect($exceptionHandler.errors[0]).toMatch(/\$compile.*badrestrict.*'true'/); - $compile('
')($rootScope); - $compile('
')($rootScope); - expect($exceptionHandler.errors.length).toBe(2); - expect($exceptionHandler.errors[1]).toMatch(/\$compile.*badrestrict.*'"'/); + $compile('
')($rootScope); + $compile('
')($rootScope); + expect($exceptionHandler.errors.length).toBe(2); + expect($exceptionHandler.errors[1]).toMatch(/\$compile.*badrestrict.*'"'/); - $compile('
')($rootScope); - expect($exceptionHandler.errors.length).toBe(3); - expect($exceptionHandler.errors[2]).toMatch(/\$compile.*badrestrict.*'{}'/); + $compile('
')($rootScope); + expect($exceptionHandler.errors.length).toBe(3); + expect($exceptionHandler.errors[2]).toMatch(/\$compile.*badrestrict.*'{}'/); + + $compile('
')($rootScope); + expect($exceptionHandler.errors.length).toBe(4); + expect($exceptionHandler.errors[3]).toMatch(/\$compile.*badrestrict.*'42'/); + }); + }); - $compile('
')($rootScope); - expect($exceptionHandler.errors.length).toBe(4); - expect($exceptionHandler.errors[3]).toMatch(/\$compile.*badrestrict.*'42'/); - }); - }); + describe('should bind to controller via object notation', function() { + var controllerOptions = [{ + description: 'no controller identifier', + controller: 'myCtrl' + }, { + description: '"Ctrl as ident" syntax', + controller: 'myCtrl as myCtrl' + }, { + description: 'controllerAs setting', + controller: 'myCtrl', + controllerAs: 'myCtrl' + }], - describe('should bind to controller via object notation', function() { - var controllerOptions = [{ - description: 'no controller identifier', - controller: 'myCtrl' - }, { - description: '"Ctrl as ident" syntax', - controller: 'myCtrl as myCtrl' - }, { - description: 'controllerAs setting', - controller: 'myCtrl', - controllerAs: 'myCtrl' - }], + scopeOptions = [{ + description: 'isolate scope', + scope: {} + }, { + description: 'new scope', + scope: true + }, { + description: 'no scope', + scope: false + }], - scopeOptions = [{ - description: 'isolate scope', - scope: {} - }, { - description: 'new scope', - scope: true - }, { - description: 'no scope', - scope: false - }], - - templateOptions = [{ - description: 'inline template', - template: '

template

' - }, { - description: 'templateUrl setting', - templateUrl: 'test.html' - }, { - description: 'no template' - }]; - - forEach(controllerOptions, function(controllerOption) { - forEach(scopeOptions, function(scopeOption) { - forEach(templateOptions, function(templateOption) { - - var description = [], - ddo = { - bindToController: { - 'data': '=dirData', - 'oneway': 'template

' + }, { + description: 'templateUrl setting', + templateUrl: 'test.html' + }, { + description: 'no template' + }]; + + forEach(controllerOptions, function(controllerOption) { + forEach(scopeOptions, function(scopeOption) { + forEach(templateOptions, function(templateOption) { + + var description = [], + ddo = { + bindToController: { + 'data': '=dirData', + 'oneway': 'template

'); - $rootScope.fn = valueFn('called!'); - $rootScope.whom = 'world'; - $rootScope.remoteData = { + expect(this.oneway).toEqualData({ 'foo': 'bar', 'baz': 'biz' - }; - element = $compile('
')($rootScope); - $rootScope.$digest(); - expect(controllerCalled).toBe(true); - if (ddo.controllerAs || ddo.controller.indexOf(' as ') !== -1) { - if (ddo.scope) { - expect($rootScope.myCtrl).toBeUndefined(); - } else { - // The controller identifier was added to the containing scope. - expect($rootScope.myCtrl).toBeDefined(); - } - } - }); + }); + expect(this.str).toBe('Hello, world!'); + expect(this.fn()).toBe('called!'); + }; + controllerCalled = true; }); - + $compileProvider.directive('fooDir', valueFn(ddo)); }); - }); - }); - - }); - - - it('should bind to multiple directives controllers via object notation (no scope)', function() { - var controller1Called = false; - var controller2Called = false; - module(function($compileProvider, $controllerProvider) { - $compileProvider.directive('foo', valueFn({ - bindToController: { - 'data': '=fooData', - 'oneway': 'template

'); + $rootScope.fn = valueFn('called!'); + $rootScope.whom = 'world'; + $rootScope.remoteData = { + 'foo': 'bar', + 'baz': 'biz' }; - controller2Called = true; - if (preAssignBindingsEnabled) { - this.check(); - } else { - this.$onInit = this.check; + element = $compile('
')($rootScope); + $rootScope.$digest(); + expect(controllerCalled).toBe(true); + if (ddo.controllerAs || ddo.controller.indexOf(' as ') !== -1) { + if (ddo.scope) { + expect($rootScope.myCtrl).toBeUndefined(); + } else { + // The controller identifier was added to the containing scope. + expect($rootScope.myCtrl).toBeDefined(); + } } - } - })); - }); - inject(function($compile, $rootScope) { - $rootScope.fn = valueFn('called!'); - $rootScope.string = 'world'; - $rootScope.data = {'foo': 'bar','baz': 'biz'}; - $rootScope.fn2 = valueFn('second called!'); - $rootScope.string2 = 'second world'; - $rootScope.data2 = {'foo2': 'bar2', 'baz2': 'biz2'}; - element = $compile( - '
' + - '
')($rootScope); - $rootScope.$digest(); - expect(controller1Called).toBe(true); - expect(controller2Called).toBe(true); - }); - }); - + }); + }); - it('should bind to multiple directives controllers via object notation (new iso scope)', function() { - var controller1Called = false; - var controller2Called = false; - module(function($compileProvider, $controllerProvider) { - $compileProvider.directive('foo', valueFn({ - bindToController: { - 'data': '=fooData', - 'oneway': ' ' + - '
')($rootScope); - $rootScope.$digest(); - expect(controller1Called).toBe(true); - expect(controller2Called).toBe(true); }); }); + }); + }); - it('should bind to multiple directives controllers via object notation (new scope)', function() { - var controller1Called = false; - var controller2Called = false; - module(function($compileProvider, $controllerProvider) { - $compileProvider.directive('foo', valueFn({ - bindToController: { - 'data': '=fooData', - 'oneway': ' ' + - '
')($rootScope); - $rootScope.$digest(); - expect(controller1Called).toBe(true); - expect(controller2Called).toBe(true); - }); - }); - - - it('should evaluate against the correct scope, when using `bindToController` (new scope)', - function() { - module(function($compileProvider, $controllerProvider) { - $controllerProvider.register({ - 'ParentCtrl': function() { - this.value1 = 'parent1'; - this.value2 = 'parent2'; - this.value3 = function() { return 'parent3'; }; - this.value4 = 'parent4'; - }, - 'ChildCtrl': function() { - this.value1 = 'child1'; - this.value2 = 'child2'; - this.value3 = function() { return 'child3'; }; - this.value4 = 'child4'; - } - }); - - $compileProvider.directive('child', valueFn({ - scope: true, - controller: 'ChildCtrl as ctrl', - bindToController: { - fromParent1: '@', - fromParent2: '=', - fromParent3: '&', - fromParent4: '<' - }, - template: '' - })); - }); - inject(function($compile, $rootScope) { - element = $compile( - '
' + - '' + - '' + - '
')($rootScope); - $rootScope.$digest(); + it('should bind to multiple directives controllers via object notation (no scope)', function() { + var controller1Called = false; + var controller2Called = false; + module(function($compileProvider, $controllerProvider) { + $compileProvider.directive('foo', valueFn({ + bindToController: { + 'data': '=fooData', + 'oneway': ' ' + + '
')($rootScope); + $rootScope.$digest(); + expect(controller1Called).toBe(true); + expect(controller2Called).toBe(true); + }); + }); - var parentCtrl = element.controller('ngController'); - var childCtrl = element.find('child').controller('child'); - expect(childCtrl.fromParent1).toBe(parentCtrl.value1); - expect(childCtrl.fromParent1).not.toBe(childCtrl.value1); - expect(childCtrl.fromParent2).toBe(parentCtrl.value2); - expect(childCtrl.fromParent2).not.toBe(childCtrl.value2); - expect(childCtrl.fromParent3()()).toBe(parentCtrl.value3()); - expect(childCtrl.fromParent3()()).not.toBe(childCtrl.value3()); - expect(childCtrl.fromParent4).toBe(parentCtrl.value4); - expect(childCtrl.fromParent4).not.toBe(childCtrl.value4); + it('should bind to multiple directives controllers via object notation (new iso scope)', function() { + var controller1Called = false; + var controller2Called = false; + module(function($compileProvider, $controllerProvider) { + $compileProvider.directive('foo', valueFn({ + bindToController: { + 'data': '=fooData', + 'oneway': ' ' + + '
')($rootScope); + $rootScope.$digest(); + expect(controller1Called).toBe(true); + expect(controller2Called).toBe(true); + }); + }); - childCtrl.fromParent2 = 'modified'; - $rootScope.$digest(); - expect(parentCtrl.value2).toBe('modified'); - expect(childCtrl.value2).toBe('child2'); - }); + it('should bind to multiple directives controllers via object notation (new scope)', function() { + var controller1Called = false; + var controller2Called = false; + module(function($compileProvider, $controllerProvider) { + $compileProvider.directive('foo', valueFn({ + bindToController: { + 'data': '=fooData', + 'oneway': ' ' + + '
')($rootScope); + $rootScope.$digest(); + expect(controller1Called).toBe(true); + expect(controller2Called).toBe(true); + }); + }); - it('should evaluate against the correct scope, when using `bindToController` (new iso scope)', - function() { - module(function($compileProvider, $controllerProvider) { - $controllerProvider.register({ - 'ParentCtrl': function() { - this.value1 = 'parent1'; - this.value2 = 'parent2'; - this.value3 = function() { return 'parent3'; }; - this.value4 = 'parent4'; - }, - 'ChildCtrl': function() { - this.value1 = 'child1'; - this.value2 = 'child2'; - this.value3 = function() { return 'child3'; }; - this.value4 = 'child4'; - } - }); + it('should evaluate against the correct scope, when using `bindToController` (new scope)', + function() { + module(function($compileProvider, $controllerProvider) { + $controllerProvider.register({ + 'ParentCtrl': function() { + this.value1 = 'parent1'; + this.value2 = 'parent2'; + this.value3 = function() { return 'parent3'; }; + this.value4 = 'parent4'; + }, + 'ChildCtrl': function() { + this.value1 = 'child1'; + this.value2 = 'child2'; + this.value3 = function() { return 'child3'; }; + this.value4 = 'child4'; + } + }); - $compileProvider.directive('child', valueFn({ - scope: {}, - controller: 'ChildCtrl as ctrl', - bindToController: { - fromParent1: '@', - fromParent2: '=', - fromParent3: '&', - fromParent4: '<' - }, - template: '' - })); - }); + $compileProvider.directive('child', valueFn({ + scope: true, + controller: 'ChildCtrl as ctrl', + bindToController: { + fromParent1: '@', + fromParent2: '=', + fromParent3: '&', + fromParent4: '<' + }, + template: '' + })); + }); - inject(function($compile, $rootScope) { - element = $compile( - '
' + - '' + - '' + - '
')($rootScope); - $rootScope.$digest(); + inject(function($compile, $rootScope) { + element = $compile( + '
' + + '' + + '' + + '
')($rootScope); + $rootScope.$digest(); - var parentCtrl = element.controller('ngController'); - var childCtrl = element.find('child').controller('child'); + var parentCtrl = element.controller('ngController'); + var childCtrl = element.find('child').controller('child'); - expect(childCtrl.fromParent1).toBe(parentCtrl.value1); - expect(childCtrl.fromParent1).not.toBe(childCtrl.value1); - expect(childCtrl.fromParent2).toBe(parentCtrl.value2); - expect(childCtrl.fromParent2).not.toBe(childCtrl.value2); - expect(childCtrl.fromParent3()()).toBe(parentCtrl.value3()); - expect(childCtrl.fromParent3()()).not.toBe(childCtrl.value3()); - expect(childCtrl.fromParent4).toBe(parentCtrl.value4); - expect(childCtrl.fromParent4).not.toBe(childCtrl.value4); + expect(childCtrl.fromParent1).toBe(parentCtrl.value1); + expect(childCtrl.fromParent1).not.toBe(childCtrl.value1); + expect(childCtrl.fromParent2).toBe(parentCtrl.value2); + expect(childCtrl.fromParent2).not.toBe(childCtrl.value2); + expect(childCtrl.fromParent3()()).toBe(parentCtrl.value3()); + expect(childCtrl.fromParent3()()).not.toBe(childCtrl.value3()); + expect(childCtrl.fromParent4).toBe(parentCtrl.value4); + expect(childCtrl.fromParent4).not.toBe(childCtrl.value4); - childCtrl.fromParent2 = 'modified'; - $rootScope.$digest(); + childCtrl.fromParent2 = 'modified'; + $rootScope.$digest(); - expect(parentCtrl.value2).toBe('modified'); - expect(childCtrl.value2).toBe('child2'); - }); - } - ); + expect(parentCtrl.value2).toBe('modified'); + expect(childCtrl.value2).toBe('child2'); + }); + } + ); - it('should put controller in scope when controller identifier present but not using controllerAs', function() { - var controllerCalled = false; - var myCtrl; - module(function($compileProvider, $controllerProvider) { - $controllerProvider.register('myCtrl', function() { - controllerCalled = true; - myCtrl = this; - }); - $compileProvider.directive('fooDir', valueFn({ - templateUrl: 'test.html', - bindToController: {}, - scope: true, - controller: 'myCtrl as theCtrl' - })); - }); - inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('test.html', '

isolate

'); - element = $compile('
')($rootScope); - $rootScope.$digest(); - expect(controllerCalled).toBe(true); - var childScope = element.children().scope(); - expect(childScope).not.toBe($rootScope); - expect(childScope.theCtrl).toBe(myCtrl); + it('should evaluate against the correct scope, when using `bindToController` (new iso scope)', + function() { + module(function($compileProvider, $controllerProvider) { + $controllerProvider.register({ + 'ParentCtrl': function() { + this.value1 = 'parent1'; + this.value2 = 'parent2'; + this.value3 = function() { return 'parent3'; }; + this.value4 = 'parent4'; + }, + 'ChildCtrl': function() { + this.value1 = 'child1'; + this.value2 = 'child2'; + this.value3 = function() { return 'child3'; }; + this.value4 = 'child4'; + } }); + + $compileProvider.directive('child', valueFn({ + scope: {}, + controller: 'ChildCtrl as ctrl', + bindToController: { + fromParent1: '@', + fromParent2: '=', + fromParent3: '&', + fromParent4: '<' + }, + template: '' + })); }); + inject(function($compile, $rootScope) { + element = $compile( + '
' + + '' + + '' + + '
')($rootScope); + $rootScope.$digest(); - it('should re-install controllerAs and bindings for returned value from controller (new scope)', function() { - var controllerCalled = false; - var myCtrl; + var parentCtrl = element.controller('ngController'); + var childCtrl = element.find('child').controller('child'); - function MyCtrl() { - } - MyCtrl.prototype.test = function() { - expect(this.data).toEqualData({ - 'foo': 'bar', - 'baz': 'biz' - }); - expect(this.oneway).toEqualData({ - 'foo': 'bar', - 'baz': 'biz' - }); - expect(this.str).toBe('Hello, world!'); - expect(this.fn()).toBe('called!'); - }; + expect(childCtrl.fromParent1).toBe(parentCtrl.value1); + expect(childCtrl.fromParent1).not.toBe(childCtrl.value1); + expect(childCtrl.fromParent2).toBe(parentCtrl.value2); + expect(childCtrl.fromParent2).not.toBe(childCtrl.value2); + expect(childCtrl.fromParent3()()).toBe(parentCtrl.value3()); + expect(childCtrl.fromParent3()()).not.toBe(childCtrl.value3()); + expect(childCtrl.fromParent4).toBe(parentCtrl.value4); + expect(childCtrl.fromParent4).not.toBe(childCtrl.value4); - module(function($compileProvider, $controllerProvider) { - $controllerProvider.register('myCtrl', function() { - controllerCalled = true; - myCtrl = this; - return new MyCtrl(); - }); - $compileProvider.directive('fooDir', valueFn({ - templateUrl: 'test.html', - bindToController: { - 'data': '=dirData', - 'oneway': 'isolate

'); - $rootScope.fn = valueFn('called!'); - $rootScope.whom = 'world'; - $rootScope.remoteData = { - 'foo': 'bar', - 'baz': 'biz' - }; - element = $compile('
')($rootScope); - $rootScope.$digest(); - expect(controllerCalled).toBe(true); - var childScope = element.children().scope(); - expect(childScope).not.toBe($rootScope); - expect(childScope.theCtrl).not.toBe(myCtrl); - expect(childScope.theCtrl.constructor).toBe(MyCtrl); - childScope.theCtrl.test(); - }); + childCtrl.fromParent2 = 'modified'; + $rootScope.$digest(); + + expect(parentCtrl.value2).toBe('modified'); + expect(childCtrl.value2).toBe('child2'); }); + } + ); - it('should re-install controllerAs and bindings for returned value from controller (isolate scope)', function() { - var controllerCalled = false; - var myCtrl; + it('should put controller in scope when controller identifier present but not using controllerAs', function() { + var controllerCalled = false; + var myCtrl; + module(function($compileProvider, $controllerProvider) { + $controllerProvider.register('myCtrl', function() { + controllerCalled = true; + myCtrl = this; + }); + $compileProvider.directive('fooDir', valueFn({ + templateUrl: 'test.html', + bindToController: {}, + scope: true, + controller: 'myCtrl as theCtrl' + })); + }); + inject(function($compile, $rootScope, $templateCache) { + $templateCache.put('test.html', '

isolate

'); + element = $compile('
')($rootScope); + $rootScope.$digest(); + expect(controllerCalled).toBe(true); + var childScope = element.children().scope(); + expect(childScope).not.toBe($rootScope); + expect(childScope.theCtrl).toBe(myCtrl); + }); + }); - function MyCtrl() { - } - MyCtrl.prototype.test = function() { - expect(this.data).toEqualData({ - 'foo': 'bar', - 'baz': 'biz' - }); - expect(this.oneway).toEqualData({ - 'foo': 'bar', - 'baz': 'biz' - }); - expect(this.str).toBe('Hello, world!'); - expect(this.fn()).toBe('called!'); - }; - module(function($compileProvider, $controllerProvider) { - $controllerProvider.register('myCtrl', function() { - controllerCalled = true; - myCtrl = this; - return new MyCtrl(); - }); - $compileProvider.directive('fooDir', valueFn({ - templateUrl: 'test.html', - bindToController: true, - scope: { - 'data': '=dirData', - 'oneway': 'isolate

'); - $rootScope.fn = valueFn('called!'); - $rootScope.whom = 'world'; - $rootScope.remoteData = { - 'foo': 'bar', - 'baz': 'biz' - }; - element = $compile('
')($rootScope); - $rootScope.$digest(); - expect(controllerCalled).toBe(true); - var childScope = element.children().scope(); - expect(childScope).not.toBe($rootScope); - expect(childScope.theCtrl).not.toBe(myCtrl); - expect(childScope.theCtrl.constructor).toBe(MyCtrl); - childScope.theCtrl.test(); - }); + it('should re-install controllerAs and bindings for returned value from controller (new scope)', function() { + var controllerCalled = false; + var myCtrl; + + function MyCtrl() { + } + MyCtrl.prototype.test = function() { + expect(this.data).toEqualData({ + 'foo': 'bar', + 'baz': 'biz' + }); + expect(this.oneway).toEqualData({ + 'foo': 'bar', + 'baz': 'biz' }); + expect(this.str).toBe('Hello, world!'); + expect(this.fn()).toBe('called!'); + }; - describe('should not overwrite @-bound property each digest when not present', function() { - it('when creating new scope', function() { - module(function($compileProvider) { - $compileProvider.directive('testDir', valueFn({ - scope: true, - bindToController: { - prop: '@' - }, - controller: function() { - var self = this; - this.initProp = function() { - this.prop = this.prop || 'default'; - }; - if (preAssignBindingsEnabled) { - this.initProp(); - } else { - this.$onInit = this.initProp; - } - this.getProp = function() { - return self.prop; - }; - }, - controllerAs: 'ctrl', - template: '

' - })); - }); - inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); - var scope = element.scope(); - expect(scope.ctrl.getProp()).toBe('default'); + module(function($compileProvider, $controllerProvider) { + $controllerProvider.register('myCtrl', function() { + controllerCalled = true; + myCtrl = this; + return new MyCtrl(); + }); + $compileProvider.directive('fooDir', valueFn({ + templateUrl: 'test.html', + bindToController: { + 'data': '=dirData', + 'oneway': 'isolate

'); + $rootScope.fn = valueFn('called!'); + $rootScope.whom = 'world'; + $rootScope.remoteData = { + 'foo': 'bar', + 'baz': 'biz' + }; + element = $compile('
')($rootScope); + $rootScope.$digest(); + expect(controllerCalled).toBe(true); + var childScope = element.children().scope(); + expect(childScope).not.toBe($rootScope); + expect(childScope.theCtrl).not.toBe(myCtrl); + expect(childScope.theCtrl.constructor).toBe(MyCtrl); + childScope.theCtrl.test(); + }); + }); - $rootScope.$digest(); - expect(scope.ctrl.getProp()).toBe('default'); - }); - }); - it('when creating isolate scope', function() { - module(function($compileProvider) { - $compileProvider.directive('testDir', valueFn({ - scope: {}, - bindToController: { - prop: '@' - }, - controller: function() { - var self = this; - this.initProp = function() { - this.prop = this.prop || 'default'; - }; - this.getProp = function() { - return self.prop; - }; - if (preAssignBindingsEnabled) { - this.initProp(); - } else { - this.$onInit = this.initProp; - } - }, - controllerAs: 'ctrl', - template: '

' - })); - }); - inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); - var scope = element.isolateScope(); - expect(scope.ctrl.getProp()).toBe('default'); + it('should re-install controllerAs and bindings for returned value from controller (isolate scope)', function() { + var controllerCalled = false; + var myCtrl; - $rootScope.$digest(); - expect(scope.ctrl.getProp()).toBe('default'); - }); - }); + function MyCtrl() { + } + MyCtrl.prototype.test = function() { + expect(this.data).toEqualData({ + 'foo': 'bar', + 'baz': 'biz' + }); + expect(this.oneway).toEqualData({ + 'foo': 'bar', + 'baz': 'biz' }); + expect(this.str).toBe('Hello, world!'); + expect(this.fn()).toBe('called!'); + }; + module(function($compileProvider, $controllerProvider) { + $controllerProvider.register('myCtrl', function() { + controllerCalled = true; + myCtrl = this; + return new MyCtrl(); + }); + $compileProvider.directive('fooDir', valueFn({ + templateUrl: 'test.html', + bindToController: true, + scope: { + 'data': '=dirData', + 'oneway': 'isolate

'); + $rootScope.fn = valueFn('called!'); + $rootScope.whom = 'world'; + $rootScope.remoteData = { + 'foo': 'bar', + 'baz': 'biz' + }; + element = $compile('
')($rootScope); + $rootScope.$digest(); + expect(controllerCalled).toBe(true); + var childScope = element.children().scope(); + expect(childScope).not.toBe($rootScope); + expect(childScope.theCtrl).not.toBe(myCtrl); + expect(childScope.theCtrl.constructor).toBe(MyCtrl); + childScope.theCtrl.test(); + }); + }); - describe('require', 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); - } + describe('should not overwrite @-bound property each digest when not present', function() { + it('when creating new scope', function() { + module(function($compileProvider) { + $compileProvider.directive('testDir', valueFn({ + scope: true, + bindToController: { + prop: '@' + }, + controller: function() { + var self = this; + this.$onInit = function() { + this.prop = this.prop || 'default'; }; - }); - directive('other', function(log) { - return { - link: function(scope, element, attrs, controller) { - log(!!controller); // should be false - } + this.getProp = function() { + return self.prop; }; - }); - }); - inject(function(log, $compile, $rootScope) { - element = $compile('
')($rootScope); - expect(log).toEqual('false; dep:main; main'); - }); + }, + controllerAs: 'ctrl', + template: '

' + })); }); + inject(function($compile, $rootScope) { + element = $compile('
')($rootScope); + var scope = element.scope(); + expect(scope.ctrl.getProp()).toBe('default'); + $rootScope.$digest(); + expect(scope.ctrl.getProp()).toBe('default'); + }); + }); - it('should respect explicit return value from controller', function() { - var expectedController; - module(function() { - directive('logControllerProp', function(log) { - return { - controller: function($scope) { - this.foo = 'baz'; // value should not be used. - expectedController = {foo: 'bar'}; - return expectedController; - }, - link: function(scope, element, attrs, controller) { - expect(expectedController).toBeDefined(); - expect(controller).toBe(expectedController); - expect(controller.foo).toBe('bar'); - log('done'); - } + it('when creating isolate scope', function() { + module(function($compileProvider) { + $compileProvider.directive('testDir', valueFn({ + scope: {}, + bindToController: { + prop: '@' + }, + controller: function() { + var self = this; + this.$onInit = function() { + this.prop = this.prop || 'default'; }; - }); - }); - inject(function(log, $compile, $rootScope) { - element = $compile('')($rootScope); - expect(log).toEqual('done'); - expect(element.data('$logControllerPropController')).toBe(expectedController); - }); + this.getProp = function() { + return self.prop; + }; + }, + controllerAs: 'ctrl', + template: '

' + })); }); + inject(function($compile, $rootScope) { + element = $compile('
')($rootScope); + var scope = element.isolateScope(); + expect(scope.ctrl.getProp()).toBe('default'); + $rootScope.$digest(); + expect(scope.ctrl.getProp()).toBe('default'); + }); + }); + }); - it('should get explicit return value of required parent controller', function() { - var expectedController; - module(function() { - directive('nested', function(log) { - return { - require: '^^?nested', - controller: function() { - if (!expectedController) expectedController = {foo: 'bar'}; - return expectedController; - }, - link: function(scope, element, attrs, controller) { - if (element.parent().length) { - expect(expectedController).toBeDefined(); - expect(controller).toBe(expectedController); - expect(controller.foo).toBe('bar'); - log('done'); - } - } - }; - }); - }); - inject(function(log, $compile, $rootScope) { - element = $compile('
')($rootScope); - expect(log).toEqual('done'); - expect(element.data('$nestedController')).toBe(expectedController); - }); + }); + + describe('require', 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 respect explicit controller return value when using controllerAs', function() { - module(function() { - directive('main', function() { - return { - templateUrl: 'main.html', - scope: {}, - controller: function() { - this.name = 'lucas'; - return {name: 'george'}; - }, - controllerAs: 'mainCtrl' - }; - }); - }); - inject(function($templateCache, $compile, $rootScope) { - $templateCache.put('main.html', 'template:{{mainCtrl.name}}'); - element = $compile('
')($rootScope); - $rootScope.$apply(); - expect(element.text()).toBe('template:george'); - }); + it('should respect explicit return value from controller', function() { + var expectedController; + module(function() { + directive('logControllerProp', function(log) { + return { + controller: function($scope) { + this.foo = 'baz'; // value should not be used. + expectedController = {foo: 'bar'}; + return expectedController; + }, + link: function(scope, element, attrs, controller) { + expect(expectedController).toBeDefined(); + expect(controller).toBe(expectedController); + expect(controller.foo).toBe('bar'); + log('done'); + } + }; }); + }); + inject(function(log, $compile, $rootScope) { + element = $compile('')($rootScope); + expect(log).toEqual('done'); + expect(element.data('$logControllerPropController')).toBe(expectedController); + }); + }); - it('transcluded children should receive explicit return value of parent controller', function() { - var expectedController; - module(function() { - directive('nester', valueFn({ - transclude: true, - controller: function($transclude) { - this.foo = 'baz'; - expectedController = {transclude:$transclude, foo: 'bar'}; - return expectedController; - }, - link: function(scope, el, attr, ctrl) { - ctrl.transclude(cloneAttach); - function cloneAttach(clone) { - el.append(clone); - } + it('should get explicit return value of required parent controller', function() { + var expectedController; + module(function() { + directive('nested', function(log) { + return { + require: '^^?nested', + controller: function() { + if (!expectedController) expectedController = {foo: 'bar'}; + return expectedController; + }, + link: function(scope, element, attrs, controller) { + if (element.parent().length) { + expect(expectedController).toBeDefined(); + expect(controller).toBe(expectedController); + expect(controller.foo).toBe('bar'); + log('done'); } - })); - directive('nested', function(log) { - return { - require: '^^nester', - link: function(scope, element, attrs, controller) { - expect(controller).toBeDefined(); - expect(controller).toBe(expectedController); - log('done'); - } - }; - }); - }); - inject(function(log, $compile) { - element = $compile('
')($rootScope); - $rootScope.$apply(); - expect(log.toString()).toBe('done'); - expect(element.data('$nesterController')).toBe(expectedController); - }); + } + }; }); + }); + inject(function(log, $compile, $rootScope) { + element = $compile('
')($rootScope); + expect(log).toEqual('done'); + expect(element.data('$nestedController')).toBe(expectedController); + }); + }); - it('explicit controller return values are ignored if they are primitives', function() { - module(function() { - directive('logControllerProp', function(log) { - return { - controller: function($scope) { - this.foo = 'baz'; // value *will* be used. - return 'bar'; - }, - link: function(scope, element, attrs, controller) { - log(controller.foo); - } - }; - }); - }); - inject(function(log, $compile, $rootScope) { - element = $compile('')($rootScope); - expect(log).toEqual('baz'); - expect(element.data('$logControllerPropController').foo).toEqual('baz'); - }); + it('should respect explicit controller return value when using controllerAs', function() { + module(function() { + directive('main', function() { + return { + templateUrl: 'main.html', + scope: {}, + controller: function() { + this.name = 'lucas'; + return {name: 'george'}; + }, + controllerAs: 'mainCtrl' + }; }); + }); + inject(function($templateCache, $compile, $rootScope) { + $templateCache.put('main.html', 'template:{{mainCtrl.name}}'); + element = $compile('
')($rootScope); + $rootScope.$apply(); + expect(element.text()).toBe('template:george'); + }); + }); - it('should correctly assign controller return values for multiple directives', function() { - var directiveController, otherDirectiveController; - module(function() { + it('transcluded children should receive explicit return value of parent controller', function() { + var expectedController; + module(function() { + directive('nester', valueFn({ + transclude: true, + controller: function($transclude) { + this.foo = 'baz'; + expectedController = {transclude:$transclude, foo: 'bar'}; + return expectedController; + }, + link: function(scope, el, attr, ctrl) { + ctrl.transclude(cloneAttach); + function cloneAttach(clone) { + el.append(clone); + } + } + })); + directive('nested', function(log) { + return { + require: '^^nester', + link: function(scope, element, attrs, controller) { + expect(controller).toBeDefined(); + expect(controller).toBe(expectedController); + log('done'); + } + }; + }); + }); + inject(function(log, $compile) { + element = $compile('
')($rootScope); + $rootScope.$apply(); + expect(log.toString()).toBe('done'); + expect(element.data('$nesterController')).toBe(expectedController); + }); + }); - directive('myDirective', function(log) { - return { - scope: true, - controller: function($scope) { - directiveController = { - foo: 'bar' - }; - return directiveController; - } + + it('explicit controller return values are ignored if they are primitives', function() { + module(function() { + directive('logControllerProp', function(log) { + return { + controller: function($scope) { + this.foo = 'baz'; // value *will* be used. + return 'bar'; + }, + link: function(scope, element, attrs, controller) { + log(controller.foo); + } + }; + }); + }); + inject(function(log, $compile, $rootScope) { + element = $compile('')($rootScope); + expect(log).toEqual('baz'); + expect(element.data('$logControllerPropController').foo).toEqual('baz'); + }); + }); + + + it('should correctly assign controller return values for multiple directives', function() { + var directiveController, otherDirectiveController; + module(function() { + + directive('myDirective', function(log) { + return { + scope: true, + controller: function($scope) { + directiveController = { + foo: 'bar' }; - }); + return directiveController; + } + }; + }); - directive('myOtherDirective', function(log) { - return { - controller: function($scope) { - otherDirectiveController = { - baz: 'luh' - }; - return otherDirectiveController; - } + directive('myOtherDirective', function(log) { + return { + controller: function($scope) { + otherDirectiveController = { + baz: 'luh' }; - }); + return otherDirectiveController; + } + }; + }); - }); + }); - inject(function(log, $compile, $rootScope) { - element = $compile('')($rootScope); - expect(element.data('$myDirectiveController')).toBe(directiveController); - expect(element.data('$myOtherDirectiveController')).toBe(otherDirectiveController); - }); + inject(function(log, $compile, $rootScope) { + element = $compile('')($rootScope); + expect(element.data('$myDirectiveController')).toBe(directiveController); + expect(element.data('$myOtherDirectiveController')).toBe(otherDirectiveController); + }); + }); + + + it('should get required parent controller', function() { + module(function() { + directive('nested', function(log) { + return { + require: '^^?nested', + controller: function($scope) {}, + link: function(scope, element, attrs, controller) { + log(!!controller); + } + }; }); + }); + inject(function(log, $compile, $rootScope) { + element = $compile('
')($rootScope); + expect(log).toEqual('true; false'); + }); + }); - it('should get required parent controller', function() { - module(function() { - directive('nested', function(log) { - return { - require: '^^?nested', - controller: function($scope) {}, - link: function(scope, element, attrs, controller) { - log(!!controller); - } - }; - }); - }); - inject(function(log, $compile, $rootScope) { - element = $compile('
')($rootScope); - expect(log).toEqual('true; false'); - }); + it('should get required parent controller when the question mark precedes the ^^', function() { + module(function() { + directive('nested', function(log) { + return { + require: '?^^nested', + controller: function($scope) {}, + link: function(scope, element, attrs, controller) { + log(!!controller); + } + }; }); + }); + inject(function(log, $compile, $rootScope) { + element = $compile('
')($rootScope); + expect(log).toEqual('true; false'); + }); + }); - it('should get required parent controller when the question mark precedes the ^^', function() { - module(function() { - directive('nested', function(log) { - return { - require: '?^^nested', - controller: function($scope) {}, - link: function(scope, element, attrs, controller) { - log(!!controller); - } - }; - }); - }); - inject(function(log, $compile, $rootScope) { - element = $compile('
')($rootScope); - expect(log).toEqual('true; false'); - }); + it('should throw if required parent is not found', function() { + module(function() { + directive('nested', function() { + return { + require: '^^nested', + controller: function($scope) {}, + link: function(scope, element, attrs, controller) {} + }; }); + }); + inject(function($compile, $rootScope) { + expect(function() { + element = $compile('
')($rootScope); + }).toThrowMinErr('$compile', 'ctreq', 'Controller \'nested\', required by directive \'nested\', can\'t be found!'); + }); + }); - it('should throw if required parent is not found', function() { - module(function() { - directive('nested', function() { - return { - require: '^^nested', - controller: function($scope) {}, - link: function(scope, element, attrs, controller) {} - }; - }); - }); - inject(function($compile, $rootScope) { - expect(function() { - element = $compile('
')($rootScope); - }).toThrowMinErr('$compile', 'ctreq', 'Controller \'nested\', required by directive \'nested\', can\'t be found!'); - }); + 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 (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 bind the required controllers to the directive controller, if provided as an object and bindToController is truthy', function() { + var parentController, siblingController; - 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'); - }); + function ParentController() { this.name = 'Parent'; } + function SiblingController() { this.name = 'Sibling'; } + function MeController() { this.name = 'Me'; } + MeController.prototype.$onInit = function() { + parentController = this.container; + siblingController = this.friend; + }; + spyOn(MeController.prototype, '$onInit').and.callThrough(); + + angular.module('my', []) + .directive('me', function() { + return { + restrict: 'E', + scope: {}, + require: { container: '^parent', friend: 'sibling' }, + bindToController: true, + controller: MeController, + controllerAs: '$ctrl' + }; + }) + .directive('parent', function() { + return { + restrict: 'E', + scope: {}, + controller: ParentController + }; + }) + .directive('sibling', function() { + return { + controller: SiblingController + }; + }); + + module('my'); + inject(function($compile, $rootScope, meDirective) { + element = $compile('')($rootScope); + expect(MeController.prototype.$onInit).toHaveBeenCalled(); + expect(parentController).toEqual(jasmine.any(ParentController)); + expect(siblingController).toEqual(jasmine.any(SiblingController)); + }); + }); + + it('should use the key if the name of a required controller is omitted', function() { + function ParentController() { this.name = 'Parent'; } + function ParentOptController() { this.name = 'ParentOpt'; } + function ParentOrSiblingController() { this.name = 'ParentOrSibling'; } + function ParentOrSiblingOptController() { this.name = 'ParentOrSiblingOpt'; } + function SiblingController() { this.name = 'Sibling'; } + function SiblingOptController() { this.name = 'SiblingOpt'; } + + angular.module('my', []) + .component('me', { + require: { + parent: '^^', + parentOpt: '?^^', + parentOrSibling1: '^', + parentOrSiblingOpt1: '?^', + parentOrSibling2: '^', + parentOrSiblingOpt2: '?^', + sibling: '', + siblingOpt: '?' + } + }) + .directive('parent', function() { + return {controller: ParentController}; + }) + .directive('parentOpt', function() { + return {controller: ParentOptController}; + }) + .directive('parentOrSibling1', function() { + return {controller: ParentOrSiblingController}; + }) + .directive('parentOrSiblingOpt1', function() { + return {controller: ParentOrSiblingOptController}; + }) + .directive('parentOrSibling2', function() { + return {controller: ParentOrSiblingController}; + }) + .directive('parentOrSiblingOpt2', function() { + return {controller: ParentOrSiblingOptController}; + }) + .directive('sibling', function() { + return {controller: SiblingController}; + }) + .directive('siblingOpt', function() { + return {controller: SiblingOptController}; }); - it('should bind the required controllers to the directive controller, if provided as an object and bindToController is truthy', function() { - var parentController, siblingController; + module('my'); + inject(function($compile, $rootScope) { + var template = + '
' + + // With optional + '' + + '' + + '' + + // Without optional + '' + + '' + + '' + + '
'; + element = $compile(template)($rootScope); + + var ctrl1 = element.find('me').eq(0).controller('me'); + expect(ctrl1.parent).toEqual(jasmine.any(ParentController)); + expect(ctrl1.parentOpt).toEqual(jasmine.any(ParentOptController)); + expect(ctrl1.parentOrSibling1).toEqual(jasmine.any(ParentOrSiblingController)); + expect(ctrl1.parentOrSiblingOpt1).toEqual(jasmine.any(ParentOrSiblingOptController)); + expect(ctrl1.parentOrSibling2).toEqual(jasmine.any(ParentOrSiblingController)); + expect(ctrl1.parentOrSiblingOpt2).toEqual(jasmine.any(ParentOrSiblingOptController)); + expect(ctrl1.sibling).toEqual(jasmine.any(SiblingController)); + expect(ctrl1.siblingOpt).toEqual(jasmine.any(SiblingOptController)); + + var ctrl2 = element.find('me').eq(1).controller('me'); + expect(ctrl2.parent).toEqual(jasmine.any(ParentController)); + expect(ctrl2.parentOpt).toBe(null); + expect(ctrl2.parentOrSibling1).toEqual(jasmine.any(ParentOrSiblingController)); + expect(ctrl2.parentOrSiblingOpt1).toBe(null); + expect(ctrl2.parentOrSibling2).toEqual(jasmine.any(ParentOrSiblingController)); + expect(ctrl2.parentOrSiblingOpt2).toBe(null); + expect(ctrl2.sibling).toEqual(jasmine.any(SiblingController)); + expect(ctrl2.siblingOpt).toBe(null); + }); + }); - function ParentController() { this.name = 'Parent'; } - function SiblingController() { this.name = 'Sibling'; } - function MeController() { this.name = 'Me'; } - MeController.prototype.$onInit = function() { - parentController = this.container; - siblingController = this.friend; + + it('should not bind required controllers if bindToController is falsy', function() { + var parentController, siblingController; + + function ParentController() { this.name = 'Parent'; } + function SiblingController() { this.name = 'Sibling'; } + function MeController() { this.name = 'Me'; } + MeController.prototype.$onInit = function() { + parentController = this.container; + siblingController = this.friend; + }; + spyOn(MeController.prototype, '$onInit').and.callThrough(); + + angular.module('my', []) + .directive('me', function() { + return { + restrict: 'E', + scope: {}, + require: { container: '^parent', friend: 'sibling' }, + controller: MeController }; - spyOn(MeController.prototype, '$onInit').and.callThrough(); + }) + .directive('parent', function() { + return { + restrict: 'E', + scope: {}, + controller: ParentController + }; + }) + .directive('sibling', function() { + return { + controller: SiblingController + }; + }); - angular.module('my', []) - .directive('me', function() { - return { - restrict: 'E', - scope: {}, - require: { container: '^parent', friend: 'sibling' }, - bindToController: true, - controller: MeController, - controllerAs: '$ctrl' - }; - }) - .directive('parent', function() { - return { - restrict: 'E', - scope: {}, - controller: ParentController - }; - }) - .directive('sibling', function() { - return { - controller: SiblingController - }; - }); + module('my'); + inject(function($compile, $rootScope, meDirective) { + element = $compile('')($rootScope); + expect(MeController.prototype.$onInit).toHaveBeenCalled(); + expect(parentController).toBeUndefined(); + expect(siblingController).toBeUndefined(); + }); + }); - module('my'); - inject(function($compile, $rootScope, meDirective) { - element = $compile('')($rootScope); - expect(MeController.prototype.$onInit).toHaveBeenCalled(); - expect(parentController).toEqual(jasmine.any(ParentController)); - expect(siblingController).toEqual(jasmine.any(SiblingController)); - }); - }); - - it('should use the key if the name of a required controller is omitted', function() { - function ParentController() { this.name = 'Parent'; } - function ParentOptController() { this.name = 'ParentOpt'; } - function ParentOrSiblingController() { this.name = 'ParentOrSibling'; } - function ParentOrSiblingOptController() { this.name = 'ParentOrSiblingOpt'; } - function SiblingController() { this.name = 'Sibling'; } - function SiblingOptController() { this.name = 'SiblingOpt'; } - - angular.module('my', []) - .component('me', { - require: { - parent: '^^', - parentOpt: '?^^', - parentOrSibling1: '^', - parentOrSiblingOpt1: '?^', - parentOrSibling2: '^', - parentOrSiblingOpt2: '?^', - sibling: '', - siblingOpt: '?' - } - }) - .directive('parent', function() { - return {controller: ParentController}; - }) - .directive('parentOpt', function() { - return {controller: ParentOptController}; - }) - .directive('parentOrSibling1', function() { - return {controller: ParentOrSiblingController}; - }) - .directive('parentOrSiblingOpt1', function() { - return {controller: ParentOrSiblingOptController}; - }) - .directive('parentOrSibling2', function() { - return {controller: ParentOrSiblingController}; - }) - .directive('parentOrSiblingOpt2', function() { - return {controller: ParentOrSiblingOptController}; - }) - .directive('sibling', function() { - return {controller: SiblingController}; - }) - .directive('siblingOpt', function() { - return {controller: SiblingOptController}; - }); + it('should bind required controllers to controller that has an explicit constructor return value', function() { + var parentController, siblingController, meController; - module('my'); - inject(function($compile, $rootScope) { - var template = - '
' + - // With optional - '' + - '' + - '' + - // Without optional - '' + - '' + - '' + - '
'; - element = $compile(template)($rootScope); - - var ctrl1 = element.find('me').eq(0).controller('me'); - expect(ctrl1.parent).toEqual(jasmine.any(ParentController)); - expect(ctrl1.parentOpt).toEqual(jasmine.any(ParentOptController)); - expect(ctrl1.parentOrSibling1).toEqual(jasmine.any(ParentOrSiblingController)); - expect(ctrl1.parentOrSiblingOpt1).toEqual(jasmine.any(ParentOrSiblingOptController)); - expect(ctrl1.parentOrSibling2).toEqual(jasmine.any(ParentOrSiblingController)); - expect(ctrl1.parentOrSiblingOpt2).toEqual(jasmine.any(ParentOrSiblingOptController)); - expect(ctrl1.sibling).toEqual(jasmine.any(SiblingController)); - expect(ctrl1.siblingOpt).toEqual(jasmine.any(SiblingOptController)); - - var ctrl2 = element.find('me').eq(1).controller('me'); - expect(ctrl2.parent).toEqual(jasmine.any(ParentController)); - expect(ctrl2.parentOpt).toBe(null); - expect(ctrl2.parentOrSibling1).toEqual(jasmine.any(ParentOrSiblingController)); - expect(ctrl2.parentOrSiblingOpt1).toBe(null); - expect(ctrl2.parentOrSibling2).toEqual(jasmine.any(ParentOrSiblingController)); - expect(ctrl2.parentOrSiblingOpt2).toBe(null); - expect(ctrl2.sibling).toEqual(jasmine.any(SiblingController)); - expect(ctrl2.siblingOpt).toBe(null); - }); - }); - - - it('should not bind required controllers if bindToController is falsy', function() { - var parentController, siblingController; - - function ParentController() { this.name = 'Parent'; } - function SiblingController() { this.name = 'Sibling'; } - function MeController() { this.name = 'Me'; } - MeController.prototype.$onInit = function() { + function ParentController() { this.name = 'Parent'; } + function SiblingController() { this.name = 'Sibling'; } + function MeController() { + meController = { + name: 'Me', + $onInit: function() { parentController = this.container; siblingController = this.friend; - }; - spyOn(MeController.prototype, '$onInit').and.callThrough(); - - angular.module('my', []) - .directive('me', function() { - return { - restrict: 'E', - scope: {}, - require: { container: '^parent', friend: 'sibling' }, - controller: MeController - }; - }) - .directive('parent', function() { - return { - restrict: 'E', - scope: {}, - controller: ParentController - }; - }) - .directive('sibling', function() { - return { - controller: SiblingController - }; - }); + } + }; + spyOn(meController, '$onInit').and.callThrough(); + return meController; + } - module('my'); - inject(function($compile, $rootScope, meDirective) { - element = $compile('')($rootScope); - expect(MeController.prototype.$onInit).toHaveBeenCalled(); - expect(parentController).toBeUndefined(); - expect(siblingController).toBeUndefined(); - }); + angular.module('my', []) + .directive('me', function() { + return { + restrict: 'E', + scope: {}, + require: { container: '^parent', friend: 'sibling' }, + bindToController: true, + controller: MeController, + controllerAs: '$ctrl' + }; + }) + .directive('parent', function() { + return { + restrict: 'E', + scope: {}, + controller: ParentController + }; + }) + .directive('sibling', function() { + return { + controller: SiblingController + }; }); - it('should bind required controllers to controller that has an explicit constructor return value', function() { - var parentController, siblingController, meController; + module('my'); + inject(function($compile, $rootScope, meDirective) { + element = $compile('')($rootScope); + expect(meController.$onInit).toHaveBeenCalled(); + expect(parentController).toEqual(jasmine.any(ParentController)); + expect(siblingController).toEqual(jasmine.any(SiblingController)); + }); + }); - function ParentController() { this.name = 'Parent'; } - function SiblingController() { this.name = 'Sibling'; } - function MeController() { - meController = { - name: 'Me', - $onInit: function() { - parentController = this.container; - siblingController = this.friend; - } - }; - spyOn(meController, '$onInit').and.callThrough(); - return meController; - } - angular.module('my', []) - .directive('me', function() { - return { - restrict: 'E', - scope: {}, - require: { container: '^parent', friend: 'sibling' }, - bindToController: true, - controller: MeController, - controllerAs: '$ctrl' - }; - }) - .directive('parent', function() { - return { - restrict: 'E', - scope: {}, - controller: ParentController - }; - }) - .directive('sibling', function() { - return { - controller: SiblingController - }; - }); + it('should bind required controllers to controllers that return an explicit constructor return value', function() { + var parentController, containerController, siblingController, friendController, meController; - module('my'); - inject(function($compile, $rootScope, meDirective) { - element = $compile('')($rootScope); - expect(meController.$onInit).toHaveBeenCalled(); - expect(parentController).toEqual(jasmine.any(ParentController)); - expect(siblingController).toEqual(jasmine.any(SiblingController)); - }); + function MeController() { + this.name = 'Me'; + this.$onInit = function() { + containerController = this.container; + friendController = this.friend; + }; + } + function ParentController() { + parentController = { name: 'Parent' }; + return parentController; + } + function SiblingController() { + siblingController = { name: 'Sibling' }; + return siblingController; + } + + angular.module('my', []) + .directive('me', function() { + return { + priority: 1, // make sure it is run before sibling to test this case correctly + restrict: 'E', + scope: {}, + require: { container: '^parent', friend: 'sibling' }, + bindToController: true, + controller: MeController, + controllerAs: '$ctrl' + }; + }) + .directive('parent', function() { + return { + restrict: 'E', + scope: {}, + controller: ParentController + }; + }) + .directive('sibling', function() { + return { + controller: SiblingController + }; }); + module('my'); + inject(function($compile, $rootScope, meDirective) { + element = $compile('')($rootScope); + expect(containerController).toEqual(parentController); + expect(friendController).toEqual(siblingController); + }); + }); - it('should bind required controllers to controllers that return an explicit constructor return value', function() { - var parentController, containerController, siblingController, friendController, meController; + it('should require controller of an isolate directive from a non-isolate directive on the ' + + 'same element', function() { + var IsolateController = function() {}; + var isolateDirControllerInNonIsolateDirective; - function MeController() { - this.name = 'Me'; - this.$onInit = function() { - containerController = this.container; - friendController = this.friend; - }; - } - function ParentController() { - parentController = { name: 'Parent' }; - return parentController; - } - function SiblingController() { - siblingController = { name: 'Sibling' }; - return siblingController; - } + module(function() { + directive('isolate', function() { + return { + scope: {}, + controller: IsolateController + }; + }); + directive('nonIsolate', function() { + return { + require: 'isolate', + link: function(_, __, ___, isolateDirController) { + isolateDirControllerInNonIsolateDirective = isolateDirController; + } + }; + }); + }); - angular.module('my', []) - .directive('me', function() { - return { - priority: 1, // make sure it is run before sibling to test this case correctly - restrict: 'E', - scope: {}, - require: { container: '^parent', friend: 'sibling' }, - bindToController: true, - controller: MeController, - controllerAs: '$ctrl' - }; - }) - .directive('parent', function() { - return { - restrict: 'E', - scope: {}, - controller: ParentController - }; - }) - .directive('sibling', function() { - return { - controller: SiblingController - }; - }); + inject(function($compile, $rootScope) { + element = $compile('
')($rootScope); - module('my'); - inject(function($compile, $rootScope, meDirective) { - element = $compile('')($rootScope); - expect(containerController).toEqual(parentController); - expect(friendController).toEqual(siblingController); - }); + 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: '' + }; }); + }); - it('should require controller of an isolate directive from a non-isolate directive on the ' + - 'same element', function() { - var IsolateController = function() {}; - var isolateDirControllerInNonIsolateDirective; + inject(function($rootScope) { + compile('
'); - module(function() { - directive('isolate', function() { - return { - scope: {}, - controller: IsolateController - }; - }); - directive('nonIsolate', function() { - return { - require: 'isolate', - link: function(_, __, ___, isolateDirController) { - isolateDirControllerInNonIsolateDirective = isolateDirController; - } - }; - }); - }); + element = element.children().eq(0); + expect(element[0].checked).toBe(false); + element.isolateScope().model = true; + $rootScope.$digest(); + expect(element[0].checked).toBe(true); + }); + }); - inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); - expect(isolateDirControllerInNonIsolateDirective).toBeDefined(); - expect(isolateDirControllerInNonIsolateDirective instanceof IsolateController).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); - 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: '' - }; - }); - }); + expect(normalScope).toBe($rootScope); + expect(normalScope.name).toEqual(undefined); + expect(isolateScope.name).toEqual('WORKS'); + $rootScope.$digest(); + expect(element.text()).toEqual('WORKS'); + }); + }); - 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 (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(); - it('should share isolate scope with replaced directives (template)', function() { - var normalScope; - var isolateScope; + expect(normalScope).toBe($rootScope); + expect(normalScope.name).toEqual(undefined); + expect(isolateScope.name).toEqual('WORKS'); + expect(element.text()).toEqual('WORKS'); + }); + }); - 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); + it('should not get confused about where to use isolate scope when a replaced directive is used multiple times', + function() { - expect(normalScope).toBe($rootScope); - expect(normalScope.name).toEqual(undefined); - expect(isolateScope.name).toEqual('WORKS'); - $rootScope.$digest(); - expect(element.text()).toEqual('WORKS'); - }); + 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); - it('should share isolate scope with replaced directives (templateUrl)', function() { - var normalScope; - var isolateScope; + $rootScope.$digest(); + expect(log).toEqual('inside=isolate; ' + + 'outside replaced=non-isolate; ' + // outside + 'outside replaced=isolate; ' + // replaced + 'sibling=non-isolate'); + }); + }); - 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(); + it('should require controller of a non-isolate directive from an isolate directive on the ' + + 'same element', function() { + var NonIsolateController = function() {}; + var nonIsolateDirControllerInIsolateDirective; - expect(normalScope).toBe($rootScope); - expect(normalScope.name).toEqual(undefined); - expect(isolateScope.name).toEqual('WORKS'); - expect(element.text()).toEqual('WORKS'); - }); + 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); - 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')); - } - }; - }); - }); + expect(nonIsolateDirControllerInIsolateDirective).toBeDefined(); + expect(nonIsolateDirControllerInIsolateDirective instanceof NonIsolateController).toBe(true); + }); + }); - 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 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 require controller of a non-isolate directive from an isolate directive on the ' + - 'same element', function() { - var NonIsolateController = function() {}; - var nonIsolateDirControllerInIsolateDirective; + 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'); + }); + }); - 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 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 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 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 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 pass null if required controller can\'t be found and is optional',function() { + module(function() { + directive('dep', function(log) { + return { + require: '?^main', + link: function(scope, element, attrs, controller) { + log('dep:' + controller); + } + }; }); + }); + inject(function(log, $compile, $rootScope) { + $compile('
')($rootScope); + expect(log).toEqual('dep:null'); + }); + }); + it('should pass null if required controller can\'t be found and is optional with the question mark on the right',function() { + module(function() { + directive('dep', function(log) { + return { + require: '^?main', + link: function(scope, element, attrs, controller) { + log('dep:' + controller); + } + }; + }); + }); + inject(function(log, $compile, $rootScope) { + $compile('
')($rootScope); + expect(log).toEqual('dep:null'); + }); + }); + - 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 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 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 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 support multiple controllers as an object hash', 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: { myC1: '^c1', myC2: '^c2' }, + link: function(scope, element, attrs, controllers) { + log('dep:' + controllers.myC1.name + '-' + controllers.myC2.name); + } + }; + }); + }); + inject(function(log, $compile, $rootScope) { + element = $compile('
')($rootScope); + expect(log).toEqual('dep:c1-c2'); + }); + }); - it('should pass null if required controller can\'t be found and is optional',function() { - module(function() { - directive('dep', function(log) { - return { - require: '?^main', - link: function(scope, element, attrs, controller) { - log('dep:' + controller); - } - }; - }); - }); - inject(function(log, $compile, $rootScope) { - $compile('
')($rootScope); - expect(log).toEqual('dep:null'); + it('should support omitting the name of the required controller if it is the same as the key', + function() { + module(function() { + directive('myC1', valueFn({ + controller: function() { this.name = 'c1'; } + })); + directive('myC2', valueFn({ + controller: function() { this.name = 'c2'; } + })); + directive('dep', function(log) { + return { + require: { myC1: '^', myC2: '^' }, + link: function(scope, element, attrs, controllers) { + log('dep:' + controllers.myC1.name + '-' + controllers.myC2.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'); - it('should pass null if required controller can\'t be found and is optional with the question mark on the right',function() { - module(function() { - directive('dep', function(log) { - return { - require: '^?main', - link: function(scope, element, attrs, controller) { - log('dep:' + controller); - } - }; - }); - }); - inject(function(log, $compile, $rootScope) { - $compile('
')($rootScope); - expect(log).toEqual('dep:null'); - }); - }); + 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(); - 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'); - }); - }); + $templateCache.put('myDirectiveAsync.html', '
Hello!
'); + element = $compile('
' + + '' + + '' + + '
')($rootScope); + expect(syncCtrlSpy).not.toHaveBeenCalled(); + expect(asyncCtrlSpy).not.toHaveBeenCalled(); + $rootScope.$apply(); - 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'); - }); - }); + //expect(syncCtrlSpy).toHaveBeenCalledOnce(); + expect(asyncCtrlSpy).toHaveBeenCalledOnce(); + }); + }); - it('should support multiple controllers as an object hash', 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: { myC1: '^c1', myC2: '^c2' }, - link: function(scope, element, attrs, controllers) { - log('dep:' + controllers.myC1.name + '-' + controllers.myC2.name); - } - }; - }); - }); - inject(function(log, $compile, $rootScope) { - element = $compile('
')($rootScope); - expect(log).toEqual('dep:c1-c2'); - }); - }); - it('should support omitting the name of the required controller if it is the same as the key', - function() { - module(function() { - directive('myC1', valueFn({ - controller: function() { this.name = 'c1'; } - })); - directive('myC2', valueFn({ - controller: function() { this.name = 'c2'; } - })); - directive('dep', function(log) { - return { - require: { myC1: '^', myC2: '^' }, - link: function(scope, element, attrs, controllers) { - log('dep:' + controllers.myC1.name + '-' + controllers.myC2.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'); + it('should instantiate controllers in the parent->child order when transclusion, 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('myDirectiveSync', valueFn({ - template: '
Hello!
', - controller: syncCtrlSpy - })); - directive('myDirectiveAsync', valueFn({ - templateUrl: 'myDirectiveAsync.html', - controller: asyncCtrlSpy, - compile: function() { - return function() { - }; - } - })); - }); + 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, $compile, $rootScope) { - expect(syncCtrlSpy).not.toHaveBeenCalled(); - expect(asyncCtrlSpy).not.toHaveBeenCalled(); + inject(function($templateCache, log, $compile, $rootScope) { + $templateCache.put('parentDirective.html', '
parentTemplateText;
'); + $templateCache.put('childDirective.html', 'childTemplateText;'); - $templateCache.put('myDirectiveAsync.html', '
Hello!
'); - element = $compile('
' + - '' + - '' + - '
')($rootScope); - expect(syncCtrlSpy).not.toHaveBeenCalled(); - expect(asyncCtrlSpy).not.toHaveBeenCalled(); + element = $compile('
childContentText;
')($rootScope); + $rootScope.$apply(); + expect(log).toEqual('parentController; childController'); + expect(element.text()).toBe('childTemplateText;childContentText;'); + }); + }); - $rootScope.$apply(); - //expect(syncCtrlSpy).toHaveBeenCalledOnce(); - expect(asyncCtrlSpy).toHaveBeenCalledOnce(); - }); - }); + 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'; - it('should instantiate controllers in the parent->child order when transclusion, 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. + element = $compile('
')($rootScope); + $rootScope.$apply(); + expect(log).toEqual('myFoo=bar'); + }); + }); - 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;'); + 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); + }; - element = $compile('
childContentText;
')($rootScope); - $rootScope.$apply(); - expect(log).toEqual('parentController; childController'); - expect(element.text()).toBe('childTemplateText;childContentText;'); - }); + 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'; - 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); - }; + element = $compile('
')($rootScope); + $rootScope.$apply(); + expect(log).toEqual('myFoo=bar'); + }); + }); - directive('myDirective', function() { - return { - scope: { - myFoo: '=' - }, - template: '

Hello

', - controller: Ctrl - }; - }); - }); - inject(function($templateCache, $compile, $rootScope, log) { - $rootScope.foo = 'bar'; + it('should instantiate controllers in the parent->child->baby order when nested transclusion, 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 - element = $compile('
')($rootScope); - $rootScope.$apply(); - expect(log).toEqual('myFoo=bar'); - }); + 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;'); - 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); - }; + element = $compile('
' + + '
' + + 'childContentText;' + + '
babyContent;
' + + '
' + + '
')($rootScope); + $rootScope.$apply(); + expect(log).toEqual('parentController; childController; babyController'); + expect(element.text()).toBe('childContentText;babyTemplateText;'); + }); + }); - directive('myDirective', function() { + + 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 { - scope: { - myFoo: '=' - }, - templateUrl: 'hello.html', - controller: Ctrl + pre: function(scope, template, attr, ctrl) {}, + post: function() {} }; - }); - }); + } + }; + }); + }); - inject(function($templateCache, $compile, $rootScope, log) { - $templateCache.put('hello.html', '

Hello

'); - $rootScope.foo = 'bar'; + inject(function($templateCache, $compile, $rootScope, log) { + $templateCache.put('hello.html', '

Hello

'); - element = $compile('
')($rootScope); - $rootScope.$apply(); - expect(log).toEqual('myFoo=bar'); - }); - }); + element = $compile('
')($rootScope); + $rootScope.$apply(); + expect(log).toEqual('instance'); + expect(element.text()).toBe('Hello'); + }); + }); - it('should instantiate controllers in the parent->child->baby order when nested transclusion, 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() { + 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 { - require: '^childDirective', - templateUrl: 'babyDirective.html', - controller: function(log) { log('babyController'); } + pre: function(scope, template, attr, ctrl) {}, + post: function() {} }; - }); - }); + } + }; + }); + }); - inject(function($templateCache, log, $compile, $rootScope) { - $templateCache.put('parentDirective.html', '
parentTemplateText;
'); - $templateCache.put('childDirective.html', 'childTemplateText;'); - $templateCache.put('babyDirective.html', 'babyTemplateText;'); + inject(function($templateCache, $compile, $rootScope, log) { + element = $compile('
')($rootScope); + $rootScope.$apply(); - element = $compile('
' + - '
' + - 'childContentText;' + - '
babyContent;
' + - '
' + - '
')($rootScope); - $rootScope.$apply(); - expect(log).toEqual('parentController; childController; babyController'); - expect(element.text()).toBe('childContentText;babyTemplateText;'); - }); - }); + expect(log).toEqual('instance'); + expect(element.text()).toBe('Hello'); + }); + }); - it('should allow controller usage in pre-link directive functions with templateUrl', function() { - module(function() { - var Ctrl = function(log) { - log('instance'); - }; + 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!'); + }); + }); + }); - 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

'); + describe('transclude', function() { - element = $compile('
')($rootScope); - $rootScope.$apply(); + describe('content transclusion', function() { - expect(log).toEqual('instance'); - expect(element.text()).toBe('Hello'); + it('should support transclude directive', function() { + module(function() { + directive('trans', function() { + return { + transclude: 'content', + replace: true, + scope: {}, + link: function(scope) { + scope.x = 'iso'; + }, + template: '
  • W:{{x}}-{{$parent.$id}}-{{$id}};
' + }; }); }); + inject(function(log, $rootScope, $compile) { + element = $compile('
T:{{x}}-{{$parent.$id}}-{{$id}};
')($rootScope); + $rootScope.x = 'root'; + $rootScope.$apply(); + expect(element.text()).toEqual('W:iso-1-2;T:root-2-3;'); + expect(jqLite(jqLite(element.find('li')[1]).contents()[0]).text()).toEqual('T:root-2-3'); + expect(jqLite(element.find('span')[0]).text()).toEqual(';'); + }); + }); - 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() {} - }; - } - }; - }); - }); + 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(); - inject(function($templateCache, $compile, $rootScope, log) { - element = $compile('
')($rootScope); - $rootScope.$apply(); + expect(element.text()).toEqual('book-'); - expect(log).toEqual('instance'); - expect(element.text()).toBe('Hello'); - }); + $httpBackend.flush(); + $rootScope.$apply(); + expect(element.text()).toEqual('book-chapter-section-![(paragraph)]!'); }); + }); - 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!'); - }); + it('should not merge text elements from transcluded content', function() { + module(function() { + directive('foo', valueFn({ + transclude: 'content', + template: '
This is before {{before}}.
', + link: function(scope, element, attr, ctrls, $transclude) { + var futureParent = element.children().eq(0); + $transclude(function(clone) { + futureParent.append(clone); + }, futureParent); + }, + scope: true + })); + }); + inject(function($rootScope, $compile) { + element = $compile('
This is after {{after}}
')($rootScope); + $rootScope.before = 'BEFORE'; + $rootScope.after = 'AFTER'; + $rootScope.$apply(); + expect(element.text()).toEqual('This is before BEFORE. This is after AFTER'); + + $rootScope.before = 'Not-Before'; + $rootScope.after = 'AfTeR'; + $rootScope.$$childHead.before = 'BeFoRe'; + $rootScope.$$childHead.after = 'Not-After'; + $rootScope.$apply(); + expect(element.text()).toEqual('This is before BeFoRe. This is after AfTeR'); }); }); - describe('transclude', function() { + 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}}
', + link: function(scope, element, attr, ctrl) { + scope.x = 'iso'; + } + })); + directive('trans', valueFn({ + transclude: 'content', + link: function(scope, element, attr, ctrl, $transclude) { + $transclude(function(clone) { + element.append(clone); + }); + } + })); + }); + inject(function($rootScope, $compile) { + element = $compile('')($rootScope); + $rootScope.x = 'root'; + $rootScope.$apply(); + expect(element.text()).toEqual('iso'); + }); + }); - describe('content transclusion', function() { - it('should support transclude directive', function() { - module(function() { - directive('trans', function() { - return { - transclude: 'content', - replace: true, - scope: {}, - link: function(scope) { - scope.x = 'iso'; - }, - template: '
  • W:{{x}}-{{$parent.$id}}-{{$id}};
' - }; + //see issue https://github.com/angular/angular.js/issues/12936 + it('should use the proper scope when it is on the root element of a replaced directive template with child scope', function() { + module(function() { + directive('child', valueFn({ + scope: true, + replace: true, + template: '
{{x}}
', + link: function(scope, element, attr, ctrl) { + scope.x = 'child'; + } + })); + directive('trans', valueFn({ + transclude: 'content', + link: function(scope, element, attr, ctrl, $transclude) { + $transclude(function(clone) { + element.append(clone); }); - }); - inject(function(log, $rootScope, $compile) { - element = $compile('
T:{{x}}-{{$parent.$id}}-{{$id}};
')($rootScope); - $rootScope.x = 'root'; - $rootScope.$apply(); - expect(element.text()).toEqual('W:iso-1-2;T:root-2-3;'); - expect(jqLite(jqLite(element.find('li')[1]).contents()[0]).text()).toEqual('T:root-2-3'); - expect(jqLite(element.find('span')[0]).text()).toEqual(';'); - }); - }); + } + })); + }); + inject(function($rootScope, $compile) { + element = $compile('')($rootScope); + $rootScope.x = 'root'; + $rootScope.$apply(); + expect(element.text()).toEqual('child'); + }); + }); + it('should throw if a transcluded node is transcluded again', function() { + module(function() { + directive('trans', valueFn({ + transclude: true, + link: function(scope, element, attr, ctrl, $transclude) { + $transclude(); + $transclude(); + } + })); + }); + inject(function($rootScope, $compile) { + expect(function() { + $compile('')($rootScope); + }).toThrowMinErr('$compile', 'multilink', 'This element has already been linked.'); + }); + }); - 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(); + it('should not leak if two "element" transclusions are on the same element (with debug info)', function() { + if (jQuery) { + // jQuery 2.x doesn't expose the cache storage. + return; + } - expect(element.text()).toEqual('book-'); - $httpBackend.flush(); - $rootScope.$apply(); - expect(element.text()).toEqual('book-chapter-section-![(paragraph)]!'); - }); - }); + module(function($compileProvider) { + $compileProvider.debugInfoEnabled(true); + }); + inject(function($compile, $rootScope) { + var cacheSize = jqLiteCacheSize(); - it('should not merge text elements from transcluded content', function() { - module(function() { - directive('foo', valueFn({ - transclude: 'content', - template: '
This is before {{before}}.
', - link: function(scope, element, attr, ctrls, $transclude) { - var futureParent = element.children().eq(0); - $transclude(function(clone) { - futureParent.append(clone); - }, futureParent); - }, - scope: true - })); - }); - inject(function($rootScope, $compile) { - element = $compile('
This is after {{after}}
')($rootScope); - $rootScope.before = 'BEFORE'; - $rootScope.after = 'AFTER'; - $rootScope.$apply(); - expect(element.text()).toEqual('This is before BEFORE. This is after AFTER'); - - $rootScope.before = 'Not-Before'; - $rootScope.after = 'AfTeR'; - $rootScope.$$childHead.before = 'BeFoRe'; - $rootScope.$$childHead.after = 'Not-After'; - $rootScope.$apply(); - expect(element.text()).toEqual('This is before BeFoRe. This is after AfTeR'); - }); - }); + element = $compile('
{{x}}
')($rootScope); + expect(jqLiteCacheSize()).toEqual(cacheSize + 1); + $rootScope.$apply('xs = [0,1]'); + expect(jqLiteCacheSize()).toEqual(cacheSize + 2); - 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}}
', - link: function(scope, element, attr, ctrl) { - scope.x = 'iso'; - } - })); - directive('trans', valueFn({ - transclude: 'content', - link: function(scope, element, attr, ctrl, $transclude) { - $transclude(function(clone) { - element.append(clone); - }); - } - })); - }); - inject(function($rootScope, $compile) { - element = $compile('')($rootScope); - $rootScope.x = 'root'; - $rootScope.$apply(); - expect(element.text()).toEqual('iso'); - }); - }); + $rootScope.$apply('xs = []'); + expect(jqLiteCacheSize()).toEqual(cacheSize + 1); + element.remove(); + expect(jqLiteCacheSize()).toEqual(cacheSize + 0); + }); + }); - //see issue https://github.com/angular/angular.js/issues/12936 - it('should use the proper scope when it is on the root element of a replaced directive template with child scope', function() { - module(function() { - directive('child', valueFn({ - scope: true, - replace: true, - template: '
{{x}}
', - link: function(scope, element, attr, ctrl) { - scope.x = 'child'; - } - })); - directive('trans', valueFn({ - transclude: 'content', - link: function(scope, element, attr, ctrl, $transclude) { - $transclude(function(clone) { - element.append(clone); - }); - } - })); - }); - inject(function($rootScope, $compile) { - element = $compile('')($rootScope); - $rootScope.x = 'root'; - $rootScope.$apply(); - expect(element.text()).toEqual('child'); - }); - }); - it('should throw if a transcluded node is transcluded again', function() { - module(function() { - directive('trans', valueFn({ - transclude: true, - link: function(scope, element, attr, ctrl, $transclude) { - $transclude(); - $transclude(); - } - })); - }); - inject(function($rootScope, $compile) { - expect(function() { - $compile('')($rootScope); - }).toThrowMinErr('$compile', 'multilink', 'This element has already been linked.'); - }); - }); + it('should not leak if two "element" transclusions are on the same element (without debug info)', function() { + if (jQuery) { + // jQuery 2.x doesn't expose the cache storage. + return; + } - it('should not leak if two "element" transclusions are on the same element (with debug info)', function() { - if (jQuery) { - // jQuery 2.x doesn't expose the cache storage. - return; - } + module(function($compileProvider) { + $compileProvider.debugInfoEnabled(false); + }); - module(function($compileProvider) { - $compileProvider.debugInfoEnabled(true); - }); + inject(function($compile, $rootScope) { + var cacheSize = jqLiteCacheSize(); - inject(function($compile, $rootScope) { - var cacheSize = jqLiteCacheSize(); + element = $compile('
{{x}}
')($rootScope); + expect(jqLiteCacheSize()).toEqual(cacheSize); - element = $compile('
{{x}}
')($rootScope); - expect(jqLiteCacheSize()).toEqual(cacheSize + 1); + $rootScope.$apply('xs = [0,1]'); + expect(jqLiteCacheSize()).toEqual(cacheSize); - $rootScope.$apply('xs = [0,1]'); - expect(jqLiteCacheSize()).toEqual(cacheSize + 2); + $rootScope.$apply('xs = [0]'); + expect(jqLiteCacheSize()).toEqual(cacheSize); - $rootScope.$apply('xs = [0]'); - expect(jqLiteCacheSize()).toEqual(cacheSize + 1); + $rootScope.$apply('xs = []'); + expect(jqLiteCacheSize()).toEqual(cacheSize); - $rootScope.$apply('xs = []'); - expect(jqLiteCacheSize()).toEqual(cacheSize + 1); + element.remove(); + expect(jqLiteCacheSize()).toEqual(cacheSize); + }); + }); - element.remove(); - expect(jqLiteCacheSize()).toEqual(cacheSize + 0); - }); - }); + it('should not leak if two "element" transclusions are on the same element (with debug info)', function() { + if (jQuery) { + // jQuery 2.x doesn't expose the cache storage. + return; + } - it('should not leak if two "element" transclusions are on the same element (without debug info)', function() { - if (jQuery) { - // jQuery 2.x doesn't expose the cache storage. - return; - } + module(function($compileProvider) { + $compileProvider.debugInfoEnabled(true); + }); + inject(function($compile, $rootScope) { + var cacheSize = jqLiteCacheSize(); + element = $compile('
{{x}}
')($rootScope); - module(function($compileProvider) { - $compileProvider.debugInfoEnabled(false); - }); + $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(jqLiteCacheSize()).toEqual(cacheSize + 1); - inject(function($compile, $rootScope) { - var cacheSize = jqLiteCacheSize(); + $rootScope.$apply('val = true'); + // Now we have two concrete transcluded elements plus some comments so two more cache items + expect(jqLiteCacheSize()).toEqual(cacheSize + 3); - element = $compile('
{{x}}
')($rootScope); - expect(jqLiteCacheSize()).toEqual(cacheSize); + $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(jqLiteCacheSize()).toEqual(cacheSize + 1); - $rootScope.$apply('xs = [0,1]'); - expect(jqLiteCacheSize()).toEqual(cacheSize); + element.remove(); + // Now we've even removed the root element along with its cache + expect(jqLiteCacheSize()).toEqual(cacheSize + 0); + }); + }); - $rootScope.$apply('xs = [0]'); - expect(jqLiteCacheSize()).toEqual(cacheSize); + it('should not leak when continuing the compilation of elements on a scope that was destroyed', function() { + if (jQuery) { + // jQuery 2.x doesn't expose the cache storage. + return; + } - $rootScope.$apply('xs = []'); - expect(jqLiteCacheSize()).toEqual(cacheSize); + var linkFn = jasmine.createSpy('linkFn'); - element.remove(); - expect(jqLiteCacheSize()).toEqual(cacheSize); + module(function($controllerProvider, $compileProvider) { + $controllerProvider.register('Leak', function($scope, $timeout) { + $scope.code = 'red'; + $timeout(function() { + $scope.code = 'blue'; }); }); + $compileProvider.directive('isolateRed', function() { + return { + restrict: 'A', + scope: {}, + template: '
' + }; + }); + $compileProvider.directive('red', function() { + return { + restrict: 'A', + templateUrl: 'red.html', + scope: {}, + link: linkFn + }; + }); + }); + inject(function($compile, $rootScope, $httpBackend, $timeout, $templateCache) { + var cacheSize = jqLiteCacheSize(); + $httpBackend.whenGET('red.html').respond('

red.html

'); + var template = $compile( + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
'); + element = template($rootScope, noop); + $rootScope.$digest(); + $timeout.flush(); + $httpBackend.flush(); + expect(linkFn).not.toHaveBeenCalled(); + expect(jqLiteCacheSize()).toEqual(cacheSize + 2); - it('should not leak if two "element" transclusions are on the same element (with debug info)', function() { - if (jQuery) { - // jQuery 2.x doesn't expose the cache storage. - return; - } + $templateCache.removeAll(); + var destroyedScope = $rootScope.$new(); + destroyedScope.$destroy(); + var clone = template(destroyedScope, noop); + $rootScope.$digest(); + $timeout.flush(); + expect(linkFn).not.toHaveBeenCalled(); + clone.remove(); + }); + }); - module(function($compileProvider) { - $compileProvider.debugInfoEnabled(true); - }); + if (jQuery) { + describe('cleaning up after a replaced element', function() { + var $compile, xs; + beforeEach(inject(function(_$compile_) { + $compile = _$compile_; + xs = [0, 1]; + })); - inject(function($compile, $rootScope) { - var cacheSize = jqLiteCacheSize(); - 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(jqLiteCacheSize()).toEqual(cacheSize + 1); - - $rootScope.$apply('val = true'); - // Now we have two concrete transcluded elements plus some comments so two more cache items - expect(jqLiteCacheSize()).toEqual(cacheSize + 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(jqLiteCacheSize()).toEqual(cacheSize + 1); - - element.remove(); - // Now we've even removed the root element along with its cache - expect(jqLiteCacheSize()).toEqual(cacheSize + 0); - }); - }); + function testCleanup() { + var privateData, firstRepeatedElem; - it('should not leak when continuing the compilation of elements on a scope that was destroyed', function() { - if (jQuery) { - // jQuery 2.x doesn't expose the cache storage. - return; - } + element = $compile('
{{x}}
')($rootScope); - var linkFn = jasmine.createSpy('linkFn'); + $rootScope.$apply('xs = [' + xs + ']'); + firstRepeatedElem = element.children('.ng-scope').eq(0); - module(function($controllerProvider, $compileProvider) { - $controllerProvider.register('Leak', function($scope, $timeout) { - $scope.code = 'red'; - $timeout(function() { - $scope.code = 'blue'; - }); - }); - $compileProvider.directive('isolateRed', function() { - return { - restrict: 'A', - scope: {}, - template: '
' - }; - }); - $compileProvider.directive('red', function() { - return { - restrict: 'A', - templateUrl: 'red.html', - scope: {}, - link: linkFn - }; - }); - }); + expect(firstRepeatedElem.data('$scope')).toBeDefined(); + privateData = jQuery._data(firstRepeatedElem[0]); + expect(privateData.events).toBeDefined(); + expect(privateData.events.click).toBeDefined(); + expect(privateData.events.click[0]).toBeDefined(); - inject(function($compile, $rootScope, $httpBackend, $timeout, $templateCache) { - var cacheSize = jqLiteCacheSize(); - $httpBackend.whenGET('red.html').respond('

red.html

'); - var template = $compile( - '
' + - '
' + - '
' + - '
' + - '
' + - '
' + - '
'); - element = template($rootScope, noop); - $rootScope.$digest(); - $timeout.flush(); - $httpBackend.flush(); - expect(linkFn).not.toHaveBeenCalled(); - expect(jqLiteCacheSize()).toEqual(cacheSize + 2); + //Ensure the AngularJS $destroy event is still sent + var destroyCount = 0; + element.find('div').on('$destroy', function() { destroyCount++; }); - $templateCache.removeAll(); - var destroyedScope = $rootScope.$new(); - destroyedScope.$destroy(); - var clone = template(destroyedScope, noop); - $rootScope.$digest(); - $timeout.flush(); - expect(linkFn).not.toHaveBeenCalled(); - clone.remove(); - }); - }); + $rootScope.$apply('xs = null'); - if (jQuery) { - describe('cleaning up after a replaced element', function() { - var $compile, xs; - beforeEach(inject(function(_$compile_) { - $compile = _$compile_; - xs = [0, 1]; - })); + expect(destroyCount).toBe(2); + expect(firstRepeatedElem.data('$scope')).not.toBeDefined(); + privateData = jQuery._data(firstRepeatedElem[0]); + expect(privateData && privateData.events).not.toBeDefined(); + } - function testCleanup() { - var privateData, firstRepeatedElem; + it('should work without external libraries (except jQuery)', testCleanup); + + it('should work with another library patching jQuery.cleanData after AngularJS', function() { + var cleanedCount = 0; + var currentCleanData = jQuery.cleanData; + jQuery.cleanData = function(elems) { + cleanedCount += elems.length; + // Don't return the output and explicitly 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); + }; - element = $compile('
{{x}}
')($rootScope); + testCleanup(); - $rootScope.$apply('xs = [' + xs + ']'); - firstRepeatedElem = element.children('.ng-scope').eq(0); + // The ng-repeat template is removed/cleaned (the +1) + // and each clone of the ng-repeat template is also removed (xs.length) + expect(cleanedCount).toBe(xs.length + 1); - expect(firstRepeatedElem.data('$scope')).toBeDefined(); - privateData = jQuery._data(firstRepeatedElem[0]); - expect(privateData.events).toBeDefined(); - expect(privateData.events.click).toBeDefined(); - expect(privateData.events.click[0]).toBeDefined(); + // Restore the previous jQuery.cleanData. + jQuery.cleanData = currentCleanData; + }); + }); + } - //Ensure the AngularJS $destroy event is still sent - var destroyCount = 0; - element.find('div').on('$destroy', function() { destroyCount++; }); - $rootScope.$apply('xs = 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($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'); + }); + }); - expect(destroyCount).toBe(2); - 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 AngularJS', function() { - var cleanedCount = 0; - var currentCleanData = jQuery.cleanData; - jQuery.cleanData = function(elems) { - cleanedCount += elems.length; - // Don't return the output and explicitly 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); - }; + it('should clear contents of the ng-transclude element before appending transcluded content' + + ' if transcluded content exists', function() { + module(function() { + directive('trans', function() { + return { + transclude: true, + template: '
old stuff!
' + }; + }); + }); + inject(function($rootScope, $compile) { + element = $compile('
unicorn!
')($rootScope); + $rootScope.$apply(); + expect(sortedHtml(element.html())).toEqual('
unicorn!
'); + }); + }); - testCleanup(); + it('should NOT clear contents of the ng-transclude element before appending transcluded content' + + ' if transcluded content does NOT exist', function() { + module(function() { + directive('trans', function() { + return { + transclude: true, + template: '
old stuff!
' + }; + }); + }); + inject(function(log, $rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.$apply(); + expect(sortedHtml(element.html())).toEqual('
old stuff!
'); + }); + }); - // The ng-repeat template is removed/cleaned (the +1) - // and each clone of the ng-repeat template is also removed (xs.length) - expect(cleanedCount).toBe(xs.length + 1); - // Restore the previous jQuery.cleanData. - jQuery.cleanData = currentCleanData; - }); - }); - } + it('should clear the fallback content from the element during compile and before linking', function() { + module(function() { + directive('trans', function() { + return { + transclude: true, + template: '
fallback content
' + }; + }); + }); + inject(function(log, $rootScope, $compile) { + element = jqLite('
'); + var linkfn = $compile(element); + expect(element.html()).toEqual('
'); + linkfn($rootScope); + $rootScope.$apply(); + expect(sortedHtml(element.html())).toEqual('
fallback content
'); + }); + }); - 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($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 allow cloning of the fallback via ngRepeat', function() { + module(function() { + directive('trans', function() { + return { + transclude: true, + template: '
{{i}}
' + }; }); + }); + inject(function(log, $rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.$apply(); + expect(element.text()).toEqual('012'); + }); + }); - it('should clear contents of the ng-transclude element before appending transcluded content' + - ' if transcluded content exists', function() { - module(function() { - directive('trans', function() { - return { - transclude: true, - template: '
old stuff!
' - }; - }); - }); - inject(function($rootScope, $compile) { - element = $compile('
unicorn!
')($rootScope); - $rootScope.$apply(); - expect(sortedHtml(element.html())).toEqual('
unicorn!
'); - }); - }); + it('should not link the fallback content if transcluded content is provided', function() { + var linkSpy = jasmine.createSpy('postlink'); - it('should NOT clear contents of the ng-transclude element before appending transcluded content' + - ' if transcluded content does NOT exist', function() { - module(function() { - directive('trans', function() { - return { - transclude: true, - template: '
old stuff!
' - }; - }); - }); - inject(function(log, $rootScope, $compile) { - element = $compile('
')($rootScope); - $rootScope.$apply(); - expect(sortedHtml(element.html())).toEqual('
old stuff!
'); - }); + module(function() { + directive('inner', function() { + return { + restrict: 'E', + template: 'old stuff! ', + link: linkSpy + }; }); - - it('should clear the fallback content from the element during compile and before linking', function() { - module(function() { - directive('trans', function() { - return { - transclude: true, - template: '
fallback content
' - }; - }); - }); - inject(function(log, $rootScope, $compile) { - element = jqLite('
'); - var linkfn = $compile(element); - expect(element.html()).toEqual('
'); - linkfn($rootScope); - $rootScope.$apply(); - expect(sortedHtml(element.html())).toEqual('
fallback content
'); - }); + directive('trans', function() { + return { + transclude: true, + template: '
' + }; }); + }); + inject(function($rootScope, $compile) { + element = $compile('
unicorn!
')($rootScope); + $rootScope.$apply(); + expect(sortedHtml(element.html())).toEqual('
unicorn!
'); + expect(linkSpy).not.toHaveBeenCalled(); + }); + }); + it('should compile and link the fallback content if no transcluded content is provided', function() { + var linkSpy = jasmine.createSpy('postlink'); - it('should allow cloning of the fallback via ngRepeat', function() { - module(function() { - directive('trans', function() { - return { - transclude: true, - template: '
{{i}}
' - }; - }); - }); - inject(function(log, $rootScope, $compile) { - element = $compile('
')($rootScope); - $rootScope.$apply(); - expect(element.text()).toEqual('012'); - }); + module(function() { + directive('inner', function() { + return { + restrict: 'E', + template: 'old stuff! ', + link: linkSpy + }; }); + directive('trans', function() { + return { + transclude: true, + template: '
' + }; + }); + }); + inject(function(log, $rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.$apply(); + expect(sortedHtml(element.html())).toEqual('
old stuff!
'); + expect(linkSpy).toHaveBeenCalled(); + }); + }); - it('should not link the fallback content if transcluded content is provided', function() { - var linkSpy = jasmine.createSpy('postlink'); + it('should compile and link the fallback content if only whitespace transcluded content is provided', function() { + var linkSpy = jasmine.createSpy('postlink'); - module(function() { - directive('inner', function() { - return { - restrict: 'E', - template: 'old stuff! ', - link: linkSpy - }; - }); + module(function() { + directive('inner', function() { + return { + restrict: 'E', + template: 'old stuff! ', + link: linkSpy + }; + }); - directive('trans', function() { - return { - transclude: true, - template: '
' - }; - }); - }); - inject(function($rootScope, $compile) { - element = $compile('
unicorn!
')($rootScope); - $rootScope.$apply(); - expect(sortedHtml(element.html())).toEqual('
unicorn!
'); - expect(linkSpy).not.toHaveBeenCalled(); - }); + directive('trans', function() { + return { + transclude: true, + template: '
' + }; }); + }); + inject(function(log, $rootScope, $compile) { + element = $compile('
\n \n
')($rootScope); + $rootScope.$apply(); + expect(sortedHtml(element.html())).toEqual('
old stuff!
'); + expect(linkSpy).toHaveBeenCalled(); + }); + }); - it('should compile and link the fallback content if no transcluded content is provided', function() { - var linkSpy = jasmine.createSpy('postlink'); + it('should not link the fallback content if only whitespace and comments are provided as transclude content', function() { + var linkSpy = jasmine.createSpy('postlink'); - module(function() { - directive('inner', function() { - return { - restrict: 'E', - template: 'old stuff! ', - link: linkSpy - }; - }); + module(function() { + directive('inner', function() { + return { + restrict: 'E', + template: 'old stuff! ', + link: linkSpy + }; + }); - directive('trans', function() { - return { - transclude: true, - template: '
' - }; - }); - }); - inject(function(log, $rootScope, $compile) { - element = $compile('
')($rootScope); - $rootScope.$apply(); - expect(sortedHtml(element.html())).toEqual('
old stuff!
'); - expect(linkSpy).toHaveBeenCalled(); - }); + directive('trans', function() { + return { + transclude: true, + template: '
' + }; }); + }); + inject(function(log, $rootScope, $compile) { + element = $compile('
\n \n
')($rootScope); + $rootScope.$apply(); + expect(sortedHtml(element.html())).toEqual('
\n \n
'); + expect(linkSpy).not.toHaveBeenCalled(); + }); + }); - it('should compile and link the fallback content if only whitespace transcluded content is provided', function() { - var linkSpy = jasmine.createSpy('postlink'); + it('should compile and link the fallback content if an optional transclusion slot is not provided', function() { + var linkSpy = jasmine.createSpy('postlink'); - module(function() { - directive('inner', function() { - return { - restrict: 'E', - template: 'old stuff! ', - link: linkSpy - }; - }); + module(function() { + directive('inner', function() { + return { + restrict: 'E', + template: 'old stuff! ', + link: linkSpy + }; + }); - directive('trans', function() { - return { - transclude: true, - template: '
' - }; - }); - }); - inject(function(log, $rootScope, $compile) { - element = $compile('
\n \n
')($rootScope); - $rootScope.$apply(); - expect(sortedHtml(element.html())).toEqual('
old stuff!
'); - expect(linkSpy).toHaveBeenCalled(); - }); + directive('trans', function() { + return { + transclude: { optionalSlot: '?optional'}, + template: '
' + }; }); + }); + inject(function(log, $rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.$apply(); + expect(sortedHtml(element.html())).toEqual('
old stuff!
'); + expect(linkSpy).toHaveBeenCalled(); + }); + }); - it('should not link the fallback content if only whitespace and comments are provided as transclude content', function() { - var linkSpy = jasmine.createSpy('postlink'); + it('should cope if there is neither transcluded content nor fallback content', function() { + module(function() { + directive('trans', function() { + return { + transclude: true, + template: '
' + }; + }); + }); + inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.$apply(); + expect(sortedHtml(element.html())).toEqual('
'); + }); + }); - module(function() { - directive('inner', function() { - return { - restrict: 'E', - template: 'old stuff! ', - link: linkSpy - }; - }); + it('should throw on an ng-transclude element inside no transclusion directive', function() { + inject(function($rootScope, $compile) { + var error; - directive('trans', function() { - return { - transclude: true, - template: '
' - }; - }); - }); - inject(function(log, $rootScope, $compile) { - element = $compile('
\n \n
')($rootScope); - $rootScope.$apply(); - expect(sortedHtml(element.html())).toEqual('
\n \n
'); - expect(linkSpy).not.toHaveBeenCalled(); - }); - }); + try { + $compile('
')($rootScope); + } catch (e) { + error = e; + } - it('should compile and link the fallback content if an optional transclusion slot is not provided', function() { - var linkSpy = jasmine.createSpy('postlink'); + expect(error).toEqualMinErr('ngTransclude', 'orphan', + 'Illegal use of ngTransclude directive in the template! ' + + 'No parent directive that requires a transclusion found. ' + + 'Element:
' - }; - }); - }); - inject(function(log, $rootScope, $compile) { - element = $compile('
')($rootScope); - $rootScope.$apply(); - expect(sortedHtml(element.html())).toEqual('
old stuff!
'); - expect(linkSpy).toHaveBeenCalled(); - }); - }); + it('should not pass transclusion into a template directive when the directive didn\'t request transclusion', function() { - it('should cope if there is neither transcluded content nor fallback content', function() { - module(function() { - directive('trans', function() { - return { - transclude: true, - template: '
' - }; - }); - }); - inject(function($rootScope, $compile) { - element = $compile('
')($rootScope); - $rootScope.$apply(); - expect(sortedHtml(element.html())).toEqual('
'); - }); - }); + module(function($compileProvider) { - it('should throw on an ng-transclude element inside no transclusion directive', function() { - inject(function($rootScope, $compile) { - var error; + $compileProvider.directive('transFoo', valueFn({ + template: '
' + + '
' + + '
this one should get replaced with content
' + + '
' + + '
', + transclude: true - try { - $compile('
')($rootScope); - } catch (e) { - error = e; - } + })); - expect(error).toEqualMinErr('ngTransclude', 'orphan', - 'Illegal use of ngTransclude directive in the template! ' + - 'No parent directive that requires a transclusion found. ' + - 'Element:
' + + // This ng-transclude is invalid. It should throw an error. + '
' + + '
', + transclude: false + })); + }); - it('should not pass transclusion into a template directive when the directive didn\'t request transclusion', function() { + 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:
'); + }); + }); - module(function($compileProvider) { - $compileProvider.directive('transFoo', valueFn({ - template: '
' + - '
' + - '
this one should get replaced with content
' + - '
' + - '
', - transclude: true + it('should not pass transclusion into a templateUrl directive', function() { - })); + module(function($compileProvider) { - $compileProvider.directive('noTransBar', valueFn({ - template: '
' + - // This ng-transclude is invalid. It should throw an error. - '
' + - '
', - transclude: false + $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) { - 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:
'); - }); - }); + 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.$digest(); + }).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) { + 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); + }); + }; + } + })); + }); - $compileProvider.directive('transFoo', valueFn({ - template: '
' + - '
' + - '
this one should get replaced with content
' + - '
' + - '
', - transclude: true - })); + inject(function($compile, $rootScope, $templateCache) { + $templateCache.put('foo.html', '
whatever
'); - $compileProvider.directive('noTransBar', valueFn({ - templateUrl: 'noTransBar.html', - transclude: false - })); - }); + compile('
transcluded content
'); + $rootScope.$apply(); - inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('noTransBar.html', - '
' + - // This ng-transclude is invalid. It should throw an error. - '
' + - '
'); + expect(trim(element.text())).toBe('transcluded content'); + }); + }); - expect(function() { - element = $compile('
content
')($rootScope); - $rootScope.$digest(); - }).toThrowMinErr('ngTransclude', 'orphan', - 'Illegal use of ngTransclude directive in the template! ' + - 'No parent directive that requires a transclusion found. ' + - 'Element:
'); - }); + + 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 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); - }); - }; + 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', '
'); - inject(function($compile, $rootScope, $templateCache) { - $templateCache.put('foo.html', '
whatever
'); + element = $compile('
unicorn!
')($rootScope); + $rootScope.$apply(); + expect(log).toEqual('pre(); post(unicorn!)'); + }); + }); - compile('
transcluded content
'); - $rootScope.$apply(); - expect(trim(element.text())).toBe('transcluded content'); - }); + 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 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 *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 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. + 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 < cloneCount; i++) { + $transclude(cloneAttach); + } - 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', '
'); + function cloneAttach(clone) { + el.append(clone); + } + } + })); + }); + inject(function($compile) { + element = $compile('
')($rootScope); + var children = element.children(), i; + expect(transcludeCtrl).toBeDefined(); - element = $compile('
unicorn!
')($rootScope); - $rootScope.$apply(); - expect(log).toEqual('pre(); post(unicorn!)'); - }); - }); + expect(element.data('$transcludeController')).toBe(transcludeCtrl); + for (i = 0; i < cloneCount; i++) { + expect(children.eq(i).data('$transcludeController')).toBeUndefined(); + } + }); + }); + it('should provide the $transclude controller local as 5th argument to the pre and post-link function', function() { + var ctrlTransclude, preLinkTransclude, postLinkTransclude; + module(function() { + directive('transclude', valueFn({ + transclude: 'content', + controller: function($transclude) { + ctrlTransclude = $transclude; + }, + compile: function() { + return { + pre: function(scope, el, attr, ctrl, $transclude) { + preLinkTransclude = $transclude; + }, + post: function(scope, el, attr, ctrl, $transclude) { + postLinkTransclude = $transclude; + } + }; + } + })); + }); + inject(function($compile) { + element = $compile('
')($rootScope); + expect(ctrlTransclude).toBeDefined(); + expect(ctrlTransclude).toBe(preLinkTransclude); + expect(ctrlTransclude).toBe(postLinkTransclude); + }); + }); - 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() + ')'); - } - } - }; + 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(log, $rootScope, $compile) { - element = $compile('
unicorn!
')($rootScope); - $rootScope.$apply(); - expect(log).toEqual('pre(); post(unicorn!)'); - }); - }); + } + })); + }); + inject(function($compile) { + element = $compile('
{{$id}}
')($rootScope); + $rootScope.$apply(); + expect(element.text()).toBe('' + $rootScope.$id); + }); + }); - 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() + ')'); - } - } - }; + 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); }); - }); - inject(function(log, $rootScope, $compile, $templateCache) { - $templateCache.put('trans.html', '
'); + } + })); + directive('child', valueFn({ + require: '^transclude', + link: function(scope, element, attr, ctrl) { + capturedChildCtrl = ctrl; + } + })); + }); + inject(function($compile) { + element = $compile('
')($rootScope); + expect(capturedChildCtrl).toBeTruthy(); + }); + }); - 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 < cloneCount; i++) { - $transclude(cloneAttach); - } + // See issue https://github.com/angular/angular.js/issues/14924 + it('should not process top-level transcluded text nodes merged into their sibling', + function() { + module(function() { + directive('transclude', valueFn({ + template: '', + transclude: true, + scope: {} + })); + }); - function cloneAttach(clone) { - el.append(clone); - } - } - })); - }); - inject(function($compile) { - element = $compile('
')($rootScope); - var children = element.children(), i; - expect(transcludeCtrl).toBeDefined(); + inject(function($compile) { + element = jqLite('
'); + element[0].appendChild(document.createTextNode('1{{ value }}')); + element[0].appendChild(document.createTextNode('2{{ value }}')); + element[0].appendChild(document.createTextNode('3{{ value }}')); - expect(element.data('$transcludeController')).toBe(transcludeCtrl); - for (i = 0; i < cloneCount; i++) { - expect(children.eq(i).data('$transcludeController')).toBeUndefined(); - } - }); - }); + var initialWatcherCount = $rootScope.$countWatchers(); + $compile(element)($rootScope); + $rootScope.$apply('value = 0'); + var newWatcherCount = $rootScope.$countWatchers() - initialWatcherCount; - it('should provide the $transclude controller local as 5th argument to the pre and post-link function', function() { - var ctrlTransclude, preLinkTransclude, postLinkTransclude; - module(function() { - directive('transclude', valueFn({ - transclude: 'content', - controller: function($transclude) { - ctrlTransclude = $transclude; - }, - compile: function() { - return { - pre: function(scope, el, attr, ctrl, $transclude) { - preLinkTransclude = $transclude; - }, - post: function(scope, el, attr, ctrl, $transclude) { - postLinkTransclude = $transclude; - } - }; - } - })); - }); - inject(function($compile) { - element = $compile('
')($rootScope); - expect(ctrlTransclude).toBeDefined(); - expect(ctrlTransclude).toBe(preLinkTransclude); - expect(ctrlTransclude).toBe(postLinkTransclude); - }); + expect(element.text()).toBe('102030'); + expect(newWatcherCount).toBe(3); }); + } + ); - 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); - }); - }); + // see issue https://github.com/angular/angular.js/issues/9413 + describe('passing a parent bound transclude function to the link ' + + 'function returned from `$compile`', function() { - 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); + beforeEach(module(function() { + directive('lazyCompile', function($compile) { + return { + compile: function(tElement, tAttrs) { + var content = tElement.contents(); + tElement.empty(); + return function(scope, element, attrs, ctrls, transcludeFn) { + element.append(content); + $compile(content)(scope, undefined, { + parentBoundTranscludeFn: transcludeFn }); - } - })); - directive('child', valueFn({ - require: '^transclude', - link: function(scope, element, attr, ctrl) { - capturedChildCtrl = ctrl; - } - })); - }); - inject(function($compile) { - element = $compile('
')($rootScope); - expect(capturedChildCtrl).toBeTruthy(); - }); + }; + } + }; }); + directive('toggle', valueFn({ + scope: {t: '=toggle'}, + transclude: true, + template: '
' + })); + })); + it('should preserve the bound scope', function() { - // See issue https://github.com/angular/angular.js/issues/14924 - it('should not process top-level transcluded text nodes merged into their sibling', - function() { - module(function() { - directive('transclude', valueFn({ - template: '', - transclude: true, - scope: {} - })); - }); - - inject(function($compile) { - element = jqLite('
'); - element[0].appendChild(document.createTextNode('1{{ value }}')); - element[0].appendChild(document.createTextNode('2{{ value }}')); - element[0].appendChild(document.createTextNode('3{{ value }}')); - - var initialWatcherCount = $rootScope.$countWatchers(); - $compile(element)($rootScope); - $rootScope.$apply('value = 0'); - var newWatcherCount = $rootScope.$countWatchers() - initialWatcherCount; - - expect(element.text()).toBe('102030'); - expect(newWatcherCount).toBe(3); - }); - } - ); - + inject(function($compile, $rootScope) { + element = $compile( + '
' + + '
' + + '
' + + 'SuccessError' + + '
' + + '
')($rootScope); - // see issue https://github.com/angular/angular.js/issues/9413 - describe('passing a parent bound transclude function to the link ' + - 'function returned from `$compile`', function() { + $rootScope.$apply('t = false'); + expect($rootScope.$countChildScopes()).toBe(1); + expect(element.text()).toBe(''); - beforeEach(module(function() { - directive('lazyCompile', function($compile) { - return { - compile: function(tElement, tAttrs) { - var content = tElement.contents(); - tElement.empty(); - return function(scope, element, attrs, ctrls, transcludeFn) { - element.append(content); - $compile(content)(scope, undefined, { - parentBoundTranscludeFn: transcludeFn - }); - }; - } - }; - }); - directive('toggle', valueFn({ - scope: {t: '=toggle'}, - transclude: true, - template: '
' - })); - })); + $rootScope.$apply('t = true'); + expect($rootScope.$countChildScopes()).toBe(4); + expect(element.text()).toBe('Success'); - it('should preserve the bound scope', function() { + $rootScope.$apply('t = false'); + expect($rootScope.$countChildScopes()).toBe(1); + expect(element.text()).toBe(''); - inject(function($compile, $rootScope) { - element = $compile( - '
' + - '
' + - '
' + - 'SuccessError' + - '
' + - '
')($rootScope); - - $rootScope.$apply('t = false'); - expect($rootScope.$countChildScopes()).toBe(1); - expect(element.text()).toBe(''); - - $rootScope.$apply('t = true'); - expect($rootScope.$countChildScopes()).toBe(4); - expect(element.text()).toBe('Success'); - - $rootScope.$apply('t = false'); - expect($rootScope.$countChildScopes()).toBe(1); - expect(element.text()).toBe(''); - - $rootScope.$apply('t = true'); - expect($rootScope.$countChildScopes()).toBe(4); - expect(element.text()).toBe('Success'); - }); - }); + $rootScope.$apply('t = true'); + expect($rootScope.$countChildScopes()).toBe(4); + expect(element.text()).toBe('Success'); + }); + }); - it('should preserve the bound scope when using recursive transclusion', function() { + it('should preserve the bound scope when using recursive transclusion', function() { - directive('recursiveTransclude', valueFn({ - transclude: true, - template: '
' - })); + directive('recursiveTransclude', valueFn({ + transclude: true, + template: '
' + })); - inject(function($compile, $rootScope) { - element = $compile( - '
' + - '
' + - '
' + - '
' + - 'SuccessError' + - '
' + - '
' + - '
')($rootScope); - - $rootScope.$apply('t = false'); - expect($rootScope.$countChildScopes()).toBe(1); - expect(element.text()).toBe(''); - - $rootScope.$apply('t = true'); - expect($rootScope.$countChildScopes()).toBe(4); - expect(element.text()).toBe('Success'); - - $rootScope.$apply('t = false'); - expect($rootScope.$countChildScopes()).toBe(1); - expect(element.text()).toBe(''); - - $rootScope.$apply('t = true'); - expect($rootScope.$countChildScopes()).toBe(4); - expect(element.text()).toBe('Success'); - }); - }); - }); + inject(function($compile, $rootScope) { + element = $compile( + '
' + + '
' + + '
' + + '
' + + 'SuccessError' + + '
' + + '
' + + '
')($rootScope); + $rootScope.$apply('t = false'); + expect($rootScope.$countChildScopes()).toBe(1); + expect(element.text()).toBe(''); - // see issue https://github.com/angular/angular.js/issues/9095 - describe('removing a transcluded element', function() { + $rootScope.$apply('t = true'); + expect($rootScope.$countChildScopes()).toBe(4); + expect(element.text()).toBe('Success'); - beforeEach(module(function() { - directive('toggle', function() { - return { - transclude: true, - template: '
' - }; - }); - })); + $rootScope.$apply('t = false'); + expect($rootScope.$countChildScopes()).toBe(1); + expect(element.text()).toBe(''); + $rootScope.$apply('t = true'); + expect($rootScope.$countChildScopes()).toBe(4); + expect(element.text()).toBe('Success'); + }); + }); + }); - it('should not leak the transclude scope when the transcluded content is an element transclusion directive', - inject(function($compile, $rootScope) { - element = $compile( - '
' + - '
{{ msg }}
' + - '
' - )($rootScope); + // see issue https://github.com/angular/angular.js/issues/9095 + describe('removing a transcluded element', function() { - $rootScope.$apply('t = true'); - expect(element.text()).toContain('msg-1'); - // Expected scopes: $rootScope, ngIf, transclusion, ngRepeat - expect($rootScope.$countChildScopes()).toBe(3); - - $rootScope.$apply('t = false'); - expect(element.text()).not.toContain('msg-1'); - // Expected scopes: $rootScope - expect($rootScope.$countChildScopes()).toBe(0); - - $rootScope.$apply('t = true'); - expect(element.text()).toContain('msg-1'); - // Expected scopes: $rootScope, ngIf, transclusion, ngRepeat - expect($rootScope.$countChildScopes()).toBe(3); - - $rootScope.$apply('t = false'); - expect(element.text()).not.toContain('msg-1'); - // Expected scopes: $rootScope - expect($rootScope.$countChildScopes()).toBe(0); - })); + beforeEach(module(function() { + directive('toggle', function() { + return { + transclude: true, + template: '
' + }; + }); + })); - it('should not leak the transclude scope when the transcluded content is an multi-element transclusion directive', - inject(function($compile, $rootScope) { + it('should not leak the transclude scope when the transcluded content is an element transclusion directive', + inject(function($compile, $rootScope) { - element = $compile( - '
' + - '
{{ msg }}
' + - '
{{ msg }}
' + - '
' - )($rootScope); + element = $compile( + '
' + + '
{{ msg }}
' + + '
' + )($rootScope); + + $rootScope.$apply('t = true'); + expect(element.text()).toContain('msg-1'); + // Expected scopes: $rootScope, ngIf, transclusion, ngRepeat + expect($rootScope.$countChildScopes()).toBe(3); + + $rootScope.$apply('t = false'); + expect(element.text()).not.toContain('msg-1'); + // Expected scopes: $rootScope + expect($rootScope.$countChildScopes()).toBe(0); + + $rootScope.$apply('t = true'); + expect(element.text()).toContain('msg-1'); + // Expected scopes: $rootScope, ngIf, transclusion, ngRepeat + expect($rootScope.$countChildScopes()).toBe(3); + + $rootScope.$apply('t = false'); + expect(element.text()).not.toContain('msg-1'); + // Expected scopes: $rootScope + expect($rootScope.$countChildScopes()).toBe(0); + })); - $rootScope.$apply('t = true'); - expect(element.text()).toContain('msg-1msg-1'); - // Expected scopes: $rootScope, ngIf, transclusion, ngRepeat - expect($rootScope.$countChildScopes()).toBe(3); - - $rootScope.$apply('t = false'); - expect(element.text()).not.toContain('msg-1msg-1'); - // Expected scopes: $rootScope - expect($rootScope.$countChildScopes()).toBe(0); - - $rootScope.$apply('t = true'); - expect(element.text()).toContain('msg-1msg-1'); - // Expected scopes: $rootScope, ngIf, transclusion, ngRepeat - expect($rootScope.$countChildScopes()).toBe(3); - - $rootScope.$apply('t = false'); - expect(element.text()).not.toContain('msg-1msg-1'); - // Expected scopes: $rootScope - expect($rootScope.$countChildScopes()).toBe(0); - })); + it('should not leak the transclude scope when the transcluded content is an multi-element transclusion directive', + inject(function($compile, $rootScope) { - it('should not leak the transclude scope if the transcluded contains only comments', - inject(function($compile, $rootScope) { + element = $compile( + '
' + + '
{{ msg }}
' + + '
{{ msg }}
' + + '
' + )($rootScope); + + $rootScope.$apply('t = true'); + expect(element.text()).toContain('msg-1msg-1'); + // Expected scopes: $rootScope, ngIf, transclusion, ngRepeat + expect($rootScope.$countChildScopes()).toBe(3); + + $rootScope.$apply('t = false'); + expect(element.text()).not.toContain('msg-1msg-1'); + // Expected scopes: $rootScope + expect($rootScope.$countChildScopes()).toBe(0); + + $rootScope.$apply('t = true'); + expect(element.text()).toContain('msg-1msg-1'); + // Expected scopes: $rootScope, ngIf, transclusion, ngRepeat + expect($rootScope.$countChildScopes()).toBe(3); + + $rootScope.$apply('t = false'); + expect(element.text()).not.toContain('msg-1msg-1'); + // Expected scopes: $rootScope + expect($rootScope.$countChildScopes()).toBe(0); + })); - element = $compile( - '
' + - '' + - '
' - )($rootScope); - $rootScope.$apply('t = true'); - expect(element.html()).toContain('some comment'); - // Expected scopes: $rootScope, ngIf, transclusion - expect($rootScope.$countChildScopes()).toBe(2); - - $rootScope.$apply('t = false'); - expect(element.html()).not.toContain('some comment'); - // Expected scopes: $rootScope - expect($rootScope.$countChildScopes()).toBe(0); - - $rootScope.$apply('t = true'); - expect(element.html()).toContain('some comment'); - // Expected scopes: $rootScope, ngIf, transclusion - expect($rootScope.$countChildScopes()).toBe(2); - - $rootScope.$apply('t = false'); - expect(element.html()).not.toContain('some comment'); - // Expected scopes: $rootScope - expect($rootScope.$countChildScopes()).toBe(0); - })); + it('should not leak the transclude scope if the transcluded contains only comments', + inject(function($compile, $rootScope) { - it('should not leak the transclude scope if the transcluded contains only text nodes', - inject(function($compile, $rootScope) { + element = $compile( + '
' + + '' + + '
' + )($rootScope); + + $rootScope.$apply('t = true'); + expect(element.html()).toContain('some comment'); + // Expected scopes: $rootScope, ngIf, transclusion + expect($rootScope.$countChildScopes()).toBe(2); + + $rootScope.$apply('t = false'); + expect(element.html()).not.toContain('some comment'); + // Expected scopes: $rootScope + expect($rootScope.$countChildScopes()).toBe(0); + + $rootScope.$apply('t = true'); + expect(element.html()).toContain('some comment'); + // Expected scopes: $rootScope, ngIf, transclusion + expect($rootScope.$countChildScopes()).toBe(2); + + $rootScope.$apply('t = false'); + expect(element.html()).not.toContain('some comment'); + // Expected scopes: $rootScope + expect($rootScope.$countChildScopes()).toBe(0); + })); - element = $compile( - '
' + - 'some text' + - '
' - )($rootScope); + it('should not leak the transclude scope if the transcluded contains only text nodes', + inject(function($compile, $rootScope) { - $rootScope.$apply('t = true'); - expect(element.html()).toContain('some text'); - // Expected scopes: $rootScope, ngIf, transclusion - expect($rootScope.$countChildScopes()).toBe(2); - - $rootScope.$apply('t = false'); - expect(element.html()).not.toContain('some text'); - // Expected scopes: $rootScope - expect($rootScope.$countChildScopes()).toBe(0); - - $rootScope.$apply('t = true'); - expect(element.html()).toContain('some text'); - // Expected scopes: $rootScope, ngIf, transclusion - expect($rootScope.$countChildScopes()).toBe(2); - - $rootScope.$apply('t = false'); - expect(element.html()).not.toContain('some text'); - // Expected scopes: $rootScope - expect($rootScope.$countChildScopes()).toBe(0); - })); + element = $compile( + '
' + + 'some text' + + '
' + )($rootScope); + + $rootScope.$apply('t = true'); + expect(element.html()).toContain('some text'); + // Expected scopes: $rootScope, ngIf, transclusion + expect($rootScope.$countChildScopes()).toBe(2); + + $rootScope.$apply('t = false'); + expect(element.html()).not.toContain('some text'); + // Expected scopes: $rootScope + expect($rootScope.$countChildScopes()).toBe(0); + + $rootScope.$apply('t = true'); + expect(element.html()).toContain('some text'); + // Expected scopes: $rootScope, ngIf, transclusion + expect($rootScope.$countChildScopes()).toBe(2); + + $rootScope.$apply('t = false'); + expect(element.html()).not.toContain('some text'); + // Expected scopes: $rootScope + expect($rootScope.$countChildScopes()).toBe(0); + })); - it('should mark as destroyed all sub scopes of the scope being destroyed', - inject(function($compile, $rootScope) { + it('should mark as destroyed all sub scopes of the scope being destroyed', + inject(function($compile, $rootScope) { - element = $compile( - '
' + - '
{{ msg }}
' + - '
' - )($rootScope); + element = $compile( + '
' + + '
{{ msg }}
' + + '
' + )($rootScope); - $rootScope.$apply('t = true'); - var childScopes = getChildScopes($rootScope); + $rootScope.$apply('t = true'); + var childScopes = getChildScopes($rootScope); - $rootScope.$apply('t = false'); - for (var i = 0; i < childScopes.length; ++i) { - expect(childScopes[i].$$destroyed).toBe(true); - } - })); - }); + $rootScope.$apply('t = false'); + for (var i = 0; i < childScopes.length; ++i) { + expect(childScopes[i].$$destroyed).toBe(true); + } + })); + }); - describe('nested transcludes', function() { + describe('nested transcludes', function() { - beforeEach(module(function($compileProvider) { + beforeEach(module(function($compileProvider) { - $compileProvider.directive('noop', valueFn({})); + $compileProvider.directive('noop', valueFn({})); - $compileProvider.directive('sync', valueFn({ - template: '
', - transclude: true - })); + $compileProvider.directive('sync', valueFn({ + template: '
', + transclude: true + })); - $compileProvider.directive('async', valueFn({ - templateUrl: 'async', - transclude: true - })); + $compileProvider.directive('async', valueFn({ + templateUrl: 'async', + transclude: true + })); - $compileProvider.directive('syncSync', valueFn({ - template: '
', - transclude: true - })); + $compileProvider.directive('syncSync', valueFn({ + template: '
', + transclude: true + })); - $compileProvider.directive('syncAsync', valueFn({ - template: '
', - transclude: true - })); + $compileProvider.directive('syncAsync', valueFn({ + template: '
', + transclude: true + })); - $compileProvider.directive('asyncSync', valueFn({ - templateUrl: 'asyncSync', - transclude: true - })); + $compileProvider.directive('asyncSync', valueFn({ + templateUrl: 'asyncSync', + transclude: true + })); - $compileProvider.directive('asyncAsync', valueFn({ - templateUrl: 'asyncAsync', - transclude: true - })); + $compileProvider.directive('asyncAsync', valueFn({ + templateUrl: 'asyncAsync', + transclude: true + })); - })); + })); - beforeEach(inject(function($templateCache) { - $templateCache.put('async', '
'); - $templateCache.put('asyncSync', '
'); - $templateCache.put('asyncAsync', '
'); - })); + 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 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 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 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 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() { - inject(function($compile, $rootScope) { - var size, initialSize = jqLiteCacheSize(); + it('should not leak memory with nested transclusion', function() { + inject(function($compile, $rootScope) { + var size, initialSize = jqLiteCacheSize(); - element = jqLite('
  • {{n}} => EvenOdd
'); - $compile(element)($rootScope.$new()); + element = jqLite('
  • {{n}} => EvenOdd
'); + $compile(element)($rootScope.$new()); - $rootScope.nums = [0,1,2]; - $rootScope.$apply(); - size = jqLiteCacheSize(); + $rootScope.nums = [0,1,2]; + $rootScope.$apply(); + size = jqLiteCacheSize(); - $rootScope.nums = [3,4,5]; - $rootScope.$apply(); - expect(jqLiteCacheSize()).toEqual(size); + $rootScope.nums = [3,4,5]; + $rootScope.$apply(); + expect(jqLiteCacheSize()).toEqual(size); - element.remove(); - expect(jqLiteCacheSize()).toEqual(initialSize); - }); - }); + element.remove(); + expect(jqLiteCacheSize()).toEqual(initialSize); }); + }); + }); - describe('nested isolated scope transcludes', function() { - beforeEach(module(function($compileProvider) { + describe('nested isolated scope transcludes', function() { + beforeEach(module(function($compileProvider) { - $compileProvider.directive('trans', valueFn({ - restrict: 'E', - template: '
', - transclude: true - })); + $compileProvider.directive('trans', valueFn({ + restrict: 'E', + template: '
', + transclude: true + })); - $compileProvider.directive('transAsync', valueFn({ - restrict: 'E', - templateUrl: 'transAsync', - 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: {} - })); - })); + $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', ''); + })); - 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) { - 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'); + })); - $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) { - 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'); + })); - $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) { - 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'); + })); - $rootScope.val = 'transcluded content'; - element = $compile('')($rootScope); - $rootScope.$digest(); - expect(element.text()).toEqual('transcluded content'); - })); + }); - }); + describe('multiple siblings receiving transclusion', function() { - describe('multiple siblings receiving transclusion', function() { + it('should only receive transclude from parent', function() { - it('should only receive transclude from parent', function() { + module(function($compileProvider) { - 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 - $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); + 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); - }); - }); + element = $compile('

')($rootScope); + $rootScope.$digest(); + expect(element.text()).toEqual('myExample 1!has children'); + dealoc(element); }); }); + }); + }); - describe('element transclusion', function() { + 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);}); - }; - } + 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;'); - }); + } + }; }); + }); + 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', 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:
'); + }).toThrowMinErr('$compile', 'multidir', /Multiple directives \[first, second] asking for transclusion on:
template.html

'); + it('should only allow one element transclusion per element when async replace directive is in the mix', function() { + module(function() { + directive('template', valueFn({ + templateUrl: 'template.html', + replace: true + })); + directive('first', valueFn({ + transclude: 'element', + priority: 100 + })); + directive('second', valueFn({ + transclude: 'element' + })); + }); + inject(function($compile, $httpBackend) { + $httpBackend.expectGET('template.html').respond('

template.html

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

'); + $httpBackend.flush(); + }).toThrowMinErr('$compile', 'multidir', + 'Multiple directives [first, second] asking for transclusion on:

', - replace: true - })); - 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:

', + replace: true + })); + 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:

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 support transcluded element on root content', function() { + var comment; + module(function() { + directive('transclude', valueFn({ + transclude: 'element', + compile: function(element, attr, linker) { + return function(scope, element, attr) { + comment = element; + }; + } + })); + }); + inject(function($compile, $rootScope) { + var element = jqLite('
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 transclusion', 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 terminate compilation only for element transclusion', 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 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); + }); + } + }; }); - - 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(); - }); + 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 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 < cloneCount; i++) { - $transclude(cloneAttach); - } + 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(); + }); + }); - function cloneAttach(clone) { - el.after(clone); - } - } - })); - }); - inject(function($compile) { - element = $compile('
')($rootScope); - var children = element.children(), i; + 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 < cloneCount; i++) { - expect(children.eq(i).data('$transcludeController')).toBe(transcludeCtrl); + $transclude(cloneAttach); } - }); - }); - - it('should expose the directive controller to transcluded children', function() { - var capturedTranscludeCtrl; - module(function() { - directive('transclude', valueFn({ - transclude: 'element', - controller: function() { - }, - link: function(scope, element, attr, ctrl, $transclude) { - $transclude(scope, function(clone) { - element.after(clone); - }); - } - })); - directive('child', valueFn({ - require: '^transclude', - link: function(scope, element, attr, ctrl) { - capturedTranscludeCtrl = ctrl; - } - })); - }); - inject(function($compile) { - // We need to wrap the transclude directive's element in a parent element so that the - // cloned element gets deallocated/cleaned up correctly - element = $compile('
')($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); - }); - } - }; + function cloneAttach(clone) { + el.after(clone); + } + } + })); + }); + inject(function($compile) { + element = $compile('
')($rootScope); + var children = element.children(), i; + for (i = 0; i < cloneCount; i++) { + expect(children.eq(i).data('$transcludeController')).toBe(transcludeCtrl); + } + }); + }); + + it('should expose the directive controller to transcluded children', function() { + var capturedTranscludeCtrl; + module(function() { + directive('transclude', valueFn({ + transclude: 'element', + controller: function() { + }, + link: function(scope, element, attr, ctrl, $transclude) { + $transclude(scope, function(clone) { + element.after(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'); - }); - }); + } + })); + directive('child', valueFn({ + require: '^transclude', + link: function(scope, element, attr, ctrl) { + capturedTranscludeCtrl = ctrl; + } + })); }); + inject(function($compile) { + // We need to wrap the transclude directive's element in a parent element so that the + // cloned element gets deallocated/cleaned up correctly + element = $compile('
')($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(); + }); + }); - it('should be possible to change the scope of a directive using $provide', function() { - module(function($provide) { - directive('foo', function() { - return { - scope: {}, - template: '
' - }; - }); - $provide.decorator('fooDirective', function($delegate) { - var directive = $delegate[0]; - directive.scope.something = '='; - directive.template = '{{something}}'; - return $delegate; - }); + // 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); + }); + } + }; }); - inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); - $rootScope.bar = 'bar'; - $rootScope.$digest(); - expect(element.text()).toBe('bar'); + 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 distinguish different bindings with the same binding name', function() { - module(function() { - directive('foo', function() { - return { - scope: { - foo: '=', - bar: '=' - }, - template: '
{{foo}}
{{bar}}
' - }; - }); - }); - inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); - $rootScope.$digest(); - expect(element.text()).toBe('foobar'); - }); + it('should be possible to change the scope of a directive using $provide', function() { + module(function($provide) { + directive('foo', function() { + return { + scope: {}, + template: '
' + }; + }); + $provide.decorator('fooDirective', function($delegate) { + var directive = $delegate[0]; + directive.scope.something = '='; + directive.template = '{{something}}'; + return $delegate; }); + }); + inject(function($compile, $rootScope) { + element = $compile('
')($rootScope); + $rootScope.bar = 'bar'; + $rootScope.$digest(); + expect(element.text()).toBe('bar'); + }); + }); - 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(); + it('should distinguish different bindings with the same binding name', function() { + module(function() { + directive('foo', function() { + return { + scope: { + foo: '=', + bar: '=' + }, + template: '
{{foo}}
{{bar}}
' + }; + }); + }); + inject(function($compile, $rootScope) { + element = $compile('
')($rootScope); + $rootScope.$digest(); + expect(element.text()).toBe('foobar'); + }); + }); - expect(element.text()).toBe('-->|x|'); - })); + 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(); - describe('lazy compilation', function() { - // 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(element.text()).toBe('-->|x|'); + })); - expect(transclude).toEqual(jasmine.any(Function)); - transclude(function(child) { element.append(child); }); - } - }; - }); + describe('lazy compilation', function() { + // 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) { - directive('trans', function($timeout) { - return { - transclude: true, - link: function(scope, element, attrs, ctrl, transclude) { + expect(transclude).toEqual(jasmine.any(Function)); - // We use timeout here to simulate how ng-if works - $timeout(function() { - transclude(function(child) { element.append(child); }); - }); - } - }; - }); + transclude(function(child) { element.append(child); }); + } + }; + }); - directive('replaceWithTemplate', function() { - return { - templateUrl: 'template.html', - replace: true - }; - }); - }); + directive('trans', function($timeout) { + return { + transclude: true, + link: function(scope, element, attrs, ctrl, transclude) { - inject(function($compile, $rootScope, $templateCache, $timeout) { + // We use timeout here to simulate how ng-if works + $timeout(function() { + transclude(function(child) { element.append(child); }); + }); + } + }; + }); - $templateCache.put('template.html', '
Content To Be Transcluded
'); + directive('replaceWithTemplate', function() { + return { + templateUrl: 'template.html', + replace: true + }; + }); + }); - expect(function() { - element = $compile('
')($rootScope); - $timeout.flush(); - }).not.toThrow(); + inject(function($compile, $rootScope, $templateCache, $timeout) { - expect(element.text()).toEqual('Content To Be Transcluded'); - }); + $templateCache.put('template.html', '
Content To Be Transcluded
'); - }); + expect(function() { + element = $compile('
')($rootScope); + $timeout.flush(); + }).not.toThrow(); - it('should lazily compile the contents of directives that are transcluded', function() { - var innerCompilationCount = 0, transclude; + expect(element.text()).toEqual('Content To Be Transcluded'); + }); - module(function() { - directive('trans', valueFn({ - transclude: true, - controller: function($transclude) { - transclude = $transclude; - } - })); + }); - directive('inner', valueFn({ - template: 'FooBar', - compile: function() { - innerCompilationCount += 1; - } - })); - }); + it('should lazily compile the contents of directives that are transcluded', function() { + var innerCompilationCount = 0, transclude; - inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - expect(innerCompilationCount).toBe(0); - transclude(function(child) { element.append(child); }); - expect(innerCompilationCount).toBe(1); - expect(element.text()).toBe('FooBar'); - }); - }); + module(function() { + directive('trans', valueFn({ + transclude: true, + controller: function($transclude) { + transclude = $transclude; + } + })); - it('should lazily compile the contents of directives that are transcluded with a template', function() { - var innerCompilationCount = 0, transclude; + directive('inner', valueFn({ + template: 'FooBar', + compile: function() { + innerCompilationCount += 1; + } + })); + }); - module(function() { - directive('trans', valueFn({ - transclude: true, - template: '
Baz
', - controller: function($transclude) { - transclude = $transclude; - } - })); + inject(function($compile, $rootScope) { + element = $compile('')($rootScope); + expect(innerCompilationCount).toBe(0); + transclude(function(child) { element.append(child); }); + expect(innerCompilationCount).toBe(1); + expect(element.text()).toBe('FooBar'); + }); + }); - directive('inner', valueFn({ - template: 'FooBar', - compile: function() { - innerCompilationCount += 1; - } - })); - }); + it('should lazily compile the contents of directives that are transcluded with a template', function() { + var innerCompilationCount = 0, transclude; - inject(function($compile, $rootScope) { - element = $compile('')($rootScope); - expect(innerCompilationCount).toBe(0); - transclude(function(child) { element.append(child); }); - expect(innerCompilationCount).toBe(1); - expect(element.text()).toBe('BazFooBar'); - }); - }); + module(function() { + directive('trans', valueFn({ + transclude: true, + template: '
Baz
', + controller: function($transclude) { + transclude = $transclude; + } + })); - it('should lazily compile the contents of directives that are transcluded with a templateUrl', function() { - var innerCompilationCount = 0, transclude; + directive('inner', valueFn({ + template: 'FooBar', + compile: function() { + innerCompilationCount += 1; + } + })); + }); - module(function() { - directive('trans', valueFn({ - transclude: true, - templateUrl: 'baz.html', - controller: function($transclude) { - transclude = $transclude; - } - })); + inject(function($compile, $rootScope) { + element = $compile('')($rootScope); + expect(innerCompilationCount).toBe(0); + transclude(function(child) { element.append(child); }); + expect(innerCompilationCount).toBe(1); + expect(element.text()).toBe('BazFooBar'); + }); + }); - directive('inner', valueFn({ - template: 'FooBar', - compile: function() { - innerCompilationCount += 1; - } - })); - }); + it('should lazily compile the contents of directives that are transcluded with a templateUrl', function() { + var innerCompilationCount = 0, transclude; - inject(function($compile, $rootScope, $httpBackend) { - $httpBackend.expectGET('baz.html').respond('
Baz
'); - element = $compile('')($rootScope); - $httpBackend.flush(); + module(function() { + directive('trans', valueFn({ + transclude: true, + templateUrl: 'baz.html', + controller: function($transclude) { + transclude = $transclude; + } + })); - expect(innerCompilationCount).toBe(0); - transclude(function(child) { element.append(child); }); - expect(innerCompilationCount).toBe(1); - expect(element.text()).toBe('BazFooBar'); - }); - }); + directive('inner', valueFn({ + template: 'FooBar', + compile: function() { + innerCompilationCount += 1; + } + })); + }); - it('should lazily compile the contents of directives that are transclude element', function() { - var innerCompilationCount = 0, transclude; + inject(function($compile, $rootScope, $httpBackend) { + $httpBackend.expectGET('baz.html').respond('
Baz
'); + element = $compile('')($rootScope); + $httpBackend.flush(); - module(function() { - directive('trans', valueFn({ - transclude: 'element', - controller: function($transclude) { - transclude = $transclude; - } - })); + expect(innerCompilationCount).toBe(0); + transclude(function(child) { element.append(child); }); + expect(innerCompilationCount).toBe(1); + expect(element.text()).toBe('BazFooBar'); + }); + }); - directive('inner', valueFn({ - template: 'FooBar', - compile: function() { - innerCompilationCount += 1; - } - })); - }); + it('should lazily compile the contents of directives that are transclude element', function() { + var innerCompilationCount = 0, transclude; - inject(function($compile, $rootScope) { - element = $compile('
')($rootScope); - expect(innerCompilationCount).toBe(0); - transclude(function(child) { element.append(child); }); - expect(innerCompilationCount).toBe(1); - expect(element.text()).toBe('FooBar'); - }); - }); + module(function() { + directive('trans', valueFn({ + transclude: 'element', + controller: function($transclude) { + transclude = $transclude; + } + })); - it('should lazily compile transcluded directives with ngIf on them', function() { - var innerCompilationCount = 0, outerCompilationCount = 0, transclude; + directive('inner', valueFn({ + template: 'FooBar', + compile: function() { + innerCompilationCount += 1; + } + })); + }); - module(function() { - directive('outer', valueFn({ - transclude: true, - compile: function() { - outerCompilationCount += 1; - }, - controller: function($transclude) { - transclude = $transclude; - } - })); + inject(function($compile, $rootScope) { + element = $compile('
')($rootScope); + expect(innerCompilationCount).toBe(0); + transclude(function(child) { element.append(child); }); + expect(innerCompilationCount).toBe(1); + expect(element.text()).toBe('FooBar'); + }); + }); - directive('inner', valueFn({ - template: 'FooBar', - compile: function() { - innerCompilationCount += 1; - } - })); - }); + it('should lazily compile transcluded directives with ngIf on them', function() { + var innerCompilationCount = 0, outerCompilationCount = 0, transclude; - inject(function($compile, $rootScope) { - $rootScope.shouldCompile = false; - - element = $compile('
')($rootScope); - expect(outerCompilationCount).toBe(0); - expect(innerCompilationCount).toBe(0); - expect(transclude).toBeUndefined(); - $rootScope.$apply('shouldCompile=true'); - expect(outerCompilationCount).toBe(1); - expect(innerCompilationCount).toBe(0); - expect(transclude).toBeDefined(); - transclude(function(child) { element.append(child); }); - expect(outerCompilationCount).toBe(1); - expect(innerCompilationCount).toBe(1); - expect(element.text()).toBe('FooBar'); - }); - }); + module(function() { + directive('outer', valueFn({ + transclude: true, + compile: function() { + outerCompilationCount += 1; + }, + controller: function($transclude) { + transclude = $transclude; + } + })); - it('should eagerly compile multiple directives with transclusion and templateUrl/replace', function() { - var innerCompilationCount = 0; + directive('inner', valueFn({ + template: 'FooBar', + compile: function() { + innerCompilationCount += 1; + } + })); + }); - module(function() { - directive('outer', valueFn({ - transclude: true - })); + inject(function($compile, $rootScope) { + $rootScope.shouldCompile = false; - directive('outer', valueFn({ - templateUrl: 'inner.html', - replace: true - })); + element = $compile('
')($rootScope); + expect(outerCompilationCount).toBe(0); + expect(innerCompilationCount).toBe(0); + expect(transclude).toBeUndefined(); + $rootScope.$apply('shouldCompile=true'); + expect(outerCompilationCount).toBe(1); + expect(innerCompilationCount).toBe(0); + expect(transclude).toBeDefined(); + transclude(function(child) { element.append(child); }); + expect(outerCompilationCount).toBe(1); + expect(innerCompilationCount).toBe(1); + expect(element.text()).toBe('FooBar'); + }); + }); - directive('inner', valueFn({ - compile: function() { - innerCompilationCount += 1; - } - })); - }); + it('should eagerly compile multiple directives with transclusion and templateUrl/replace', function() { + var innerCompilationCount = 0; - inject(function($compile, $rootScope, $httpBackend) { - $httpBackend.expectGET('inner.html').respond(''); - element = $compile('')($rootScope); - $httpBackend.flush(); + module(function() { + directive('outer', valueFn({ + transclude: true + })); - expect(innerCompilationCount).toBe(1); - }); - }); + directive('outer', valueFn({ + templateUrl: 'inner.html', + replace: true + })); + + directive('inner', valueFn({ + compile: function() { + innerCompilationCount += 1; + } + })); }); + inject(function($compile, $rootScope, $httpBackend) { + $httpBackend.expectGET('inner.html').respond(''); + element = $compile('')($rootScope); + $httpBackend.flush(); + + expect(innerCompilationCount).toBe(1); + }); }); }); + }); describe('multi-slot transclude', function() { diff --git a/test/ngMock/angular-mocksSpec.js b/test/ngMock/angular-mocksSpec.js index 70b8a97fd98b..441509376561 100644 --- a/test/ngMock/angular-mocksSpec.js +++ b/test/ngMock/angular-mocksSpec.js @@ -2039,14 +2039,29 @@ describe('ngMock', function() { describe('$controllerDecorator', function() { - describe('with `preAssignBindingsEnabled(true)`', function() { - - beforeEach(module(function($compileProvider) { - $compileProvider.preAssignBindingsEnabled(true); - })); + it('should support creating controller with bindings', function() { + var called = false; + var data = [ + { name: 'derp1', id: 0 }, + { name: 'testname', id: 1 }, + { name: 'flurp', id: 2 } + ]; + module(function($controllerProvider) { + $controllerProvider.register('testCtrl', function() { + expect(this.data).toBeUndefined(); + called = true; + }); + }); + inject(function($controller, $rootScope) { + var ctrl = $controller('testCtrl', { scope: $rootScope }, { data: data }); + expect(ctrl.data).toBe(data); + expect(called).toBe(true); + }); + }); - it('should support creating controller with bindings', function() { + it('should support assigning bindings when a value is returned from the constructor', + function() { var called = false; var data = [ { name: 'derp1', id: 0 }, @@ -2055,8 +2070,9 @@ describe('ngMock', function() { ]; module(function($controllerProvider) { $controllerProvider.register('testCtrl', function() { - expect(this.data).toBe(data); + expect(this.data).toBeUndefined(); called = true; + return {}; }); }); inject(function($controller, $rootScope) { @@ -2064,64 +2080,12 @@ describe('ngMock', function() { expect(ctrl.data).toBe(data); expect(called).toBe(true); }); - }); - - - it('should support assigning bindings when a value is returned from the constructor', - function() { - var called = false; - var data = [ - { name: 'derp1', id: 0 }, - { name: 'testname', id: 1 }, - { name: 'flurp', id: 2 } - ]; - module(function($controllerProvider) { - $controllerProvider.register('testCtrl', function() { - expect(this.data).toBe(data); - called = true; - return {}; - }); - }); - inject(function($controller, $rootScope) { - var ctrl = $controller('testCtrl', { scope: $rootScope }, { data: data }); - expect(ctrl.data).toBe(data); - expect(called).toBe(true); - }); - } - ); - - - if (/chrome/.test(window.navigator.userAgent)) { - it('should support assigning bindings to class-based controller', function() { - var called = false; - var data = [ - { name: 'derp1', id: 0 }, - { name: 'testname', id: 1 }, - { name: 'flurp', id: 2 } - ]; - module(function($controllerProvider) { - // eslint-disable-next-line no-eval - var TestCtrl = eval('(class { constructor() { called = true; } })'); - $controllerProvider.register('testCtrl', TestCtrl); - }); - inject(function($controller, $rootScope) { - var ctrl = $controller('testCtrl', { scope: $rootScope }, { data: data }); - expect(ctrl.data).toBe(data); - expect(called).toBe(true); - }); - }); } - }); - + ); - describe('with `preAssignBindingsEnabled(false)`', function() { - beforeEach(module(function($compileProvider) { - $compileProvider.preAssignBindingsEnabled(false); - })); - - - it('should support creating controller with bindings', function() { + if (/chrome/.test(window.navigator.userAgent)) { + it('should support assigning bindings to class-based controller', function() { var called = false; var data = [ { name: 'derp1', id: 0 }, @@ -2129,10 +2093,9 @@ describe('ngMock', function() { { name: 'flurp', id: 2 } ]; module(function($controllerProvider) { - $controllerProvider.register('testCtrl', function() { - expect(this.data).toBeUndefined(); - called = true; - }); + // eslint-disable-next-line no-eval + var TestCtrl = eval('(class { constructor() { called = true; } })'); + $controllerProvider.register('testCtrl', TestCtrl); }); inject(function($controller, $rootScope) { var ctrl = $controller('testCtrl', { scope: $rootScope }, { data: data }); @@ -2140,53 +2103,7 @@ describe('ngMock', function() { expect(called).toBe(true); }); }); - - - it('should support assigning bindings when a value is returned from the constructor', - function() { - var called = false; - var data = [ - { name: 'derp1', id: 0 }, - { name: 'testname', id: 1 }, - { name: 'flurp', id: 2 } - ]; - module(function($controllerProvider) { - $controllerProvider.register('testCtrl', function() { - expect(this.data).toBeUndefined(); - called = true; - return {}; - }); - }); - inject(function($controller, $rootScope) { - var ctrl = $controller('testCtrl', { scope: $rootScope }, { data: data }); - expect(ctrl.data).toBe(data); - expect(called).toBe(true); - }); - } - ); - - - if (/chrome/.test(window.navigator.userAgent)) { - it('should support assigning bindings to class-based controller', function() { - var called = false; - var data = [ - { name: 'derp1', id: 0 }, - { name: 'testname', id: 1 }, - { name: 'flurp', id: 2 } - ]; - module(function($controllerProvider) { - // eslint-disable-next-line no-eval - var TestCtrl = eval('(class { constructor() { called = true; } })'); - $controllerProvider.register('testCtrl', TestCtrl); - }); - inject(function($controller, $rootScope) { - var ctrl = $controller('testCtrl', { scope: $rootScope }, { data: data }); - expect(ctrl.data).toBe(data); - expect(called).toBe(true); - }); - }); - } - }); + } });