Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

feat(ngMock.$componentController): add helper to instantiate controllers for components #13711

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return this;
};

this.$$componentControllers = createMap();
/**
* @ngdoc method
* @name $compileProvider#component
Expand Down Expand Up @@ -1052,6 +1053,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
* See also {@link ng.$compileProvider#directive $compileProvider.directive()}.
*/
this.component = function registerComponent(name, options) {
var controller = options.controller || function() {};
var ident = identifierForController(options.controller) || options.controllerAs || '$ctrl';
this.$$componentControllers[name] = { controller: controller, ident: ident};

function factory($injector) {
function makeInjectable(fn) {
if (isFunction(fn) || isArray(fn)) {
Expand All @@ -1065,8 +1070,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {

var template = (!options.template && !options.templateUrl ? '' : options.template);
return {
controller: options.controller || function() {},
controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
controller: controller,
controllerAs: ident,
template: makeInjectable(template),
templateUrl: makeInjectable(options.templateUrl),
transclude: options.transclude,
Expand Down
31 changes: 30 additions & 1 deletion src/ngMock/angular-mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2161,6 +2161,34 @@ angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
};
}];

/**
* @ngdoc service
* @name $componentController
* @description
* A service that can be used to create instances of component controllers.
* <div class="alert alert-info">
* 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.
* </div>
* @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 controllerInfo = $compileProvider.$$componentControllers[componentName];
return $controller(controllerInfo.controller, locals, bindings, ident || controllerInfo.ident);
};
}]
};
}];


/**
* @ngdoc module
Expand All @@ -2184,7 +2212,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);
Expand Down
82 changes: 82 additions & 0 deletions test/ngMock/angular-mocksSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1857,6 +1857,88 @@ 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' });
expect($scope.$ctrl).toBe(ctrl);
});
});

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' });
expect($scope.$ctrl).toBe(ctrl);
});
});

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' });
expect($scope.$ctrl).toBe(ctrl);
});
});

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).toBe(ctrl);
});
});
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add a test with controller: 'TestController as test'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you like but the implementation is just proxying to $controller anyway so it doesn't test any new paths.

});


Expand Down