From ca376fae2a82451e09d31b0cc6650a39b426f873 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Fri, 8 Jan 2016 14:35:59 +0000 Subject: [PATCH 1/2] feat(ngMock.$componentController): add helper to instantiate controllers for components Closes #13683 --- src/ng/compile.js | 6 ++- src/ngMock/angular-mocks.js | 25 +++++++++- test/ngMock/angular-mocksSpec.js | 78 ++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index 98c233206f65..0cf3641f556a 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -928,6 +928,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { return this; }; + this.$$componentControllers = createMap(); /** * @ngdoc method * @name $compileProvider#component @@ -1052,6 +1053,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { * See also {@link ng.$compileProvider#directive $compileProvider.directive()}. */ this.component = function registerComponent(name, options) { + var controller = options.controller || function() {}; + this.$$componentControllers[name] = controller; + function factory($injector) { function makeInjectable(fn) { if (isFunction(fn) || isArray(fn)) { @@ -1065,7 +1069,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var template = (!options.template && !options.templateUrl ? '' : options.template); return { - controller: options.controller || function() {}, + controller: controller, controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', template: makeInjectable(template), templateUrl: makeInjectable(options.templateUrl), diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index 6997d83a5dd0..f8397dc7d1d5 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -2161,6 +2161,28 @@ angular.mock.$ControllerDecorator = ['$delegate', function($delegate) { }; }]; +/** + * @ngdoc service + * @name $componentController + * @description + * A service that can be used to create instances of component controllers. + * @param {string} componentName the name of the component whose controller we want to instantiate + * @param {Object} locals Injection locals for Controller. + * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used + * to simulate the `bindToController` feature and simplify certain kinds of tests. + * @return {Object} Instance of requested controller. + */ +angular.mock.$ComponentControllerProvider = ['$compileProvider', function($compileProvider) { + return { + $get: ['$controller', function($controller) { + return function $componentController(componentName, locals, bindings, ident) { + var controller = $compileProvider.$$componentControllers[componentName]; + return $controller(controller, locals, bindings, ident); + }; + }] + }; +}]; + /** * @ngdoc module @@ -2184,7 +2206,8 @@ angular.module('ngMock', ['ng']).provider({ $log: angular.mock.$LogProvider, $interval: angular.mock.$IntervalProvider, $httpBackend: angular.mock.$HttpBackendProvider, - $rootElement: angular.mock.$RootElementProvider + $rootElement: angular.mock.$RootElementProvider, + $componentController: angular.mock.$ComponentControllerProvider }).config(['$provide', function($provide) { $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); $provide.decorator('$$rAF', angular.mock.$RAFDecorator); diff --git a/test/ngMock/angular-mocksSpec.js b/test/ngMock/angular-mocksSpec.js index 14fb543eb374..3ec7ccc1e0aa 100644 --- a/test/ngMock/angular-mocksSpec.js +++ b/test/ngMock/angular-mocksSpec.js @@ -1857,6 +1857,84 @@ describe('ngMock', function() { }); }); }); + + + describe('$componentController', function() { + it('should instantiate a simple controller defined inline in a component', function() { + function TestController($scope, a, b) { + this.$scope = $scope; + this.a = a; + this.b = b; + } + module(function($compileProvider) { + $compileProvider.component('test', { + controller: TestController + }); + }); + inject(function($componentController, $rootScope) { + var $scope = {}; + var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); + expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' }); + }); + }); + + it('should instantiate a controller with $$inject annotation defined inline in a component', function() { + function TestController(x, y, z) { + this.$scope = x; + this.a = y; + this.b = z; + } + TestController.$inject = ['$scope', 'a', 'b']; + module(function($compileProvider) { + $compileProvider.component('test', { + controller: TestController + }); + }); + inject(function($componentController, $rootScope) { + var $scope = {}; + var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); + expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' }); + }); + }); + + it('should instantiate a named controller defined in a component', function() { + function TestController($scope, a, b) { + this.$scope = $scope; + this.a = a; + this.b = b; + } + module(function($controllerProvider, $compileProvider) { + $controllerProvider.register('TestController', TestController); + $compileProvider.component('test', { + controller: 'TestController' + }); + }); + inject(function($componentController, $rootScope) { + var $scope = {}; + var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); + expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' }); + }); + }); + + it('should instantiate a named controller with `controller as` syntax defined in a component', function() { + function TestController($scope, a, b) { + this.$scope = $scope; + this.a = a; + this.b = b; + } + module(function($controllerProvider, $compileProvider) { + $controllerProvider.register('TestController', TestController); + $compileProvider.component('test', { + controller: 'TestController as testCtrl' + }); + }); + inject(function($componentController, $rootScope) { + var $scope = {}; + var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); + expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' }); + expect($scope.testCtrl instanceof TestController).toBe(true); + }); + }); }); }); From c2270e1ec068022e19414b30a1458173632c2515 Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Sun, 10 Jan 2016 17:09:54 +0000 Subject: [PATCH 2/2] feat(ngMock.$componentController): add helper to instantiate controllers for components Closes #13683 --- src/ng/compile.js | 5 +++-- src/ngMock/angular-mocks.js | 10 ++++++++-- test/ngMock/angular-mocksSpec.js | 8 ++++++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index 0cf3641f556a..7a6121f6e98f 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1054,7 +1054,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { */ this.component = function registerComponent(name, options) { var controller = options.controller || function() {}; - this.$$componentControllers[name] = controller; + var ident = identifierForController(options.controller) || options.controllerAs || '$ctrl'; + this.$$componentControllers[name] = { controller: controller, ident: ident}; function factory($injector) { function makeInjectable(fn) { @@ -1070,7 +1071,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { var template = (!options.template && !options.templateUrl ? '' : options.template); return { controller: controller, - controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl', + controllerAs: ident, template: makeInjectable(template), templateUrl: makeInjectable(options.templateUrl), transclude: options.transclude, diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index f8397dc7d1d5..3910cca15baa 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -2166,18 +2166,24 @@ angular.mock.$ControllerDecorator = ['$delegate', function($delegate) { * @name $componentController * @description * A service that can be used to create instances of component controllers. + *
+ * Be aware that the controller will be instantiated and attached to the scope as specified in + * the component definition object. That means that you must always provide a `$scope` object + * in the `locals` param. + *
* @param {string} componentName the name of the component whose controller we want to instantiate * @param {Object} locals Injection locals for Controller. * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used * to simulate the `bindToController` feature and simplify certain kinds of tests. + * @param {string=} ident Override the property name to use when attaching the controller to the scope. * @return {Object} Instance of requested controller. */ angular.mock.$ComponentControllerProvider = ['$compileProvider', function($compileProvider) { return { $get: ['$controller', function($controller) { return function $componentController(componentName, locals, bindings, ident) { - var controller = $compileProvider.$$componentControllers[componentName]; - return $controller(controller, locals, bindings, ident); + var controllerInfo = $compileProvider.$$componentControllers[componentName]; + return $controller(controllerInfo.controller, locals, bindings, ident || controllerInfo.ident); }; }] }; diff --git a/test/ngMock/angular-mocksSpec.js b/test/ngMock/angular-mocksSpec.js index 3ec7ccc1e0aa..434394143e24 100644 --- a/test/ngMock/angular-mocksSpec.js +++ b/test/ngMock/angular-mocksSpec.js @@ -1875,6 +1875,7 @@ describe('ngMock', function() { var $scope = {}; var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' }); + expect($scope.$ctrl).toBe(ctrl); }); }); @@ -1894,6 +1895,7 @@ describe('ngMock', function() { var $scope = {}; var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' }); + expect($scope.$ctrl).toBe(ctrl); }); }); @@ -1913,6 +1915,7 @@ describe('ngMock', function() { var $scope = {}; var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' }); + expect($scope.$ctrl).toBe(ctrl); }); }); @@ -1932,9 +1935,10 @@ describe('ngMock', function() { var $scope = {}; var ctrl = $componentController('test', { $scope: $scope, a: 'A', b: 'B' }, { x: 'X', y: 'Y' }); expect(ctrl).toEqual({ $scope: $scope, a: 'A', b: 'B', x: 'X', y: 'Y' }); - expect($scope.testCtrl instanceof TestController).toBe(true); + expect($scope.testCtrl).toBe(ctrl); }); - }); }); + }); + }); });