From 974cad9241e60fbb10ef55d39b81d98eaba12816 Mon Sep 17 00:00:00 2001 From: Georgios Kalpakas Date: Thu, 14 Apr 2016 23:52:57 +0300 Subject: [PATCH] fix(ngMock#$controller): properly assign bindings to all types of controllers (e.g. class-based) The decorated version of `$controller` is able to assign bindings to a controller instance prior to instantiation, emulating the behavior of `$compile` with directive controllers. There are cases, that the actual controller instance is different than the pre-populated one (e.g. when the controller constructor function returns a value or when the controller is an ES2015 Class). While `$compile` accounts for such situation, by re-asigning the bindings after the controller has been instantiated, `ngMock`'s `$controller` didn't. This commit fixes it, by re-applying the bindings if the actual controller instance is different than the original one (similar to how `$compile` would do it). Fixes #14437 --- src/ngMock/angular-mocks.js | 12 ++++++-- test/ngMock/angular-mocksSpec.js | 48 ++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index 99e80f40d2ea..41f378487ecb 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -2181,9 +2181,15 @@ angular.mock.$RootElementProvider = function() { angular.mock.$ControllerDecorator = ['$delegate', function($delegate) { return function(expression, locals, later, ident) { if (later && typeof later === 'object') { - var create = $delegate(expression, locals, true, ident); - angular.extend(create.instance, later); - return create(); + var instantiate = $delegate(expression, locals, true, ident); + angular.extend(instantiate.instance, later); + + var instance = instantiate(); + if (instance !== instantiate.instance) { + angular.extend(instance, later); + } + + return instance; } return $delegate(expression, locals, later, ident); }; diff --git a/test/ngMock/angular-mocksSpec.js b/test/ngMock/angular-mocksSpec.js index 985eabc375bc..7ee95eb593b2 100644 --- a/test/ngMock/angular-mocksSpec.js +++ b/test/ngMock/angular-mocksSpec.js @@ -1,5 +1,7 @@ 'use strict'; +/* globals support: false */ + describe('ngMock', function() { var noop = angular.noop; @@ -1888,6 +1890,52 @@ 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() { + called = true; + expect(this.data).toBe(data); + + 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(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) { + //jshint evil: true + var TestCtrl = eval('(class { constructor() { called = true; } })'); + //jshint evil: false + $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); + }); + }); + } });