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

fix(ngMock/$controller): respect $compileProvider.preAssignBindingsEnabled() #15395

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
66 changes: 42 additions & 24 deletions src/ngMock/angular-mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2184,6 +2184,10 @@ 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
*
Expand All @@ -2202,18 +2206,24 @@ angular.mock.$RootElementProvider = function() {
* // Controller definition ...
*
* myMod.controller('MyDirectiveController', ['$log', function($log) {
* $log.info(this.name);
* this.log = function() {
* $log.info(this.name);
* };
* }]);
*
*
* // In a test ...
*
* describe('myDirectiveController', function() {
* it('should write the bound name to the log', inject(function($controller, $log) {
* var ctrl = $controller('MyDirectiveController', { /* no locals */ }, { name: 'Clark Kent' });
* expect(ctrl.name).toEqual('Clark Kent');
* expect($log.info.logs).toEqual(['Clark Kent']);
* }));
* describe('log()', function() {
* it('should write the bound name to the log', inject(function($controller, $log) {
* var ctrl = $controller('MyDirectiveController', { /* no locals */ }, { name: 'Clark Kent' });
* ctrl.log();
*
* expect(ctrl.name).toEqual('Clark Kent');
* expect($log.info.logs).toEqual(['Clark Kent']);
* }));
* });
* });
*
* ```
Expand All @@ -2232,26 +2242,34 @@ angular.mock.$RootElementProvider = function() {
* to work correctly.
*
* @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 {Object=} bindings Properties to add to the controller instance. This is used to simulate
* the `bindToController` feature and simplify certain kinds of tests.
* @return {Object} Instance of given controller.
*/
angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
return function(expression, locals, later, ident) {
if (later && typeof later === 'object') {
var instantiate = $delegate(expression, locals, true, ident);
angular.extend(instantiate.instance, later);

var instance = instantiate();
if (instance !== instantiate.instance) {
angular.extend(instance, later);
function createControllerDecorator(compileProvider) {
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);
}

return instance;
}
return $delegate(expression, locals, later, ident);
};
}];

return instance;
}
return $delegate(expression, locals, later, ident);
};
}];
return angular.mock.$ControllerDecorator;
}

/**
* @ngdoc service
Expand Down Expand Up @@ -2360,11 +2378,11 @@ angular.module('ngMock', ['ng']).provider({
$httpBackend: angular.mock.$HttpBackendProvider,
$rootElement: angular.mock.$RootElementProvider,
$componentController: angular.mock.$ComponentControllerProvider
}).config(['$provide', function($provide) {
}).config(['$provide', '$compileProvider', function($provide, $compileProvider) {
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
$provide.decorator('$controller', angular.mock.$ControllerDecorator);
$provide.decorator('$controller', createControllerDecorator($compileProvider));
}]);

/**
Expand Down
146 changes: 116 additions & 30 deletions test/ngMock/angular-mocksSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2055,27 +2055,15 @@ describe('ngMock', function() {


describe('$controllerDecorator', function() {
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() {
called = true;
expect(this.data).toBe(data);
});
});
inject(function($controller, $rootScope) {
$controller('testCtrl', { scope: $rootScope }, { data: data });
expect(called).toBe(true);
});
});

it('should support assigning bindings when a value is returned from the constructor',
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 },
Expand All @@ -2084,40 +2072,138 @@ describe('ngMock', function() {
];
module(function($controllerProvider) {
$controllerProvider.register('testCtrl', function() {
called = true;
expect(this.data).toBe(data);

return {};
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 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);
});
});
}
);
});


if (/chrome/.test(window.navigator.userAgent)) {
it('should support assigning bindings to class-based controller', function() {
describe('with `preAssignBindingsEnabled(false)`', function() {

beforeEach(module(function($compileProvider) {
$compileProvider.preAssignBindingsEnabled(false);
}));


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) {
// eslint-disable-next-line no-eval
var TestCtrl = eval('(class { constructor() { called = true; } })');
$controllerProvider.register('testCtrl', TestCtrl);
$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 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);
});
});
}
});
});


Expand Down