Note: for support questions, please use one of these channels: https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#question. This repository's issues are reserved for feature requests and bug reports.
Do you want to request a feature or report a bug?
Bug
What is the current behavior?
When unit testing the $postLink method with a $timeout, when invoking .flush the binding to the controllers 'this' seem to be undone.
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem via https://plnkr.co or similar (template: http://plnkr.co/edit/tpl:yBpEi4).
https://plnkr.co/edit/Frj8WvGM4bh0gRAKJhUS?p=preview
What is the expected behavior?
The bindings should remain bound after the .flush method is triggered in the unit test
What is the motivation / use case for changing the behavior?
expected functionality
Which versions of AngularJS, and which browser / OS are affected by this issue? Did this work in previous versions of AngularJS? Please also test with the latest stable and snapshot (https://code.angularjs.org/snapshot/) versions.
Angular 1.5.8 Chrome latest OSX
Other information (e.g. stacktraces, related issues, suggestions how to fix)
I'm not completely sure why you're using this:
LOCALS.$element = angular.element('<post-link-test></post-link-test>');
LOCALS.$element = $compile(LOCALS.$element)(LOCALS.$scope);
but removing it makes ur tests succeed: https://plnkr.co/edit/sINU3GvNGehJ8uDmUGqk?p=preview
@frederikprijck This is a contrived example in my complete unit test of the component I am injecting the $element so i need to reproduce it and from what I understand $element is not injectable its instead an instance of the compiled template.
``` $ctrl.$postLink = function() {
$timeout(function() {
var img = new Image();
img.src = $ctrl.image.url;
img.classList.toggle('hidden');
imageContainer = $element[0].querySelector('.image-editor__container');
cropResultContainer = $element[0].querySelector('.image-editor__crop-result');
imageContainer.append(img);
cropper = new Cropper(img, {
aspectRatio: ASPECT_RATIO[$ctrl.mode]['4/3'],
ready: function() {
$ctrl.isLoading = false;
}
});
});
};
@stenmuchow Why do you need to inject $element
? Not sure if this is expected behavior or not when injecting $element
the way you're doing it.
If I want to test the component's html (so not the controller), I tend to use $compile
for my tests. Not sure if this could be of any help tho.
As far as I understand, being able to use the combo of $postLink and $element is the way to replace the previous link function with the params scope, element, attr in the directive.
Unsure about it, so I'll leave it to @Narretz / @gkalpak !
@stenmuchow, the "this" is not "undone". There are two instances of controllers: one for the compiled <post-link-test>
and one the use instantiate manually via $componentController
. You don't pass any bindings to the first one, so its properties are undefined.
When you can $timeout.flush()
, there are two tasks to flush, one for each controllers. Because the first controller's task/fn fails (the error caught by jasmine has more details), the "flushing" is aborted and you never see the output logged by the second controller instance (which has its bindings set as expected).
Closing, since everything works as expcted.
As a sidenote, your setup seems very suspicious. Compiling a whole component (along with its corresponding controller) just so that you can inject $element
into a manually instantiated controller doesn't make sense.
The point of $componentController
is to save the overhead of creating/compiling a DOM element when not necessary. If you do need a proper $element
, then you should just use $compile
instead. E.g.:
var elem = $compile(...)(scope);
var ctrl = elem.controller('postLinkTest');
// Use `ctrl` to verify expectations...
Thanks for the feedback, would be great to have some better documented behavior somewhere. Nevertheless thanks a bunch for the info!
var elem = $compile(...)(scope);
var ctrl = elem.controller('postLinkTest');
Using this then would mean I would have to actually define a controller and not just use an anonymous named function right?
.component('x, {
bindings: {},
controller: function() { }
})
or
.component('x, {
bindings: {},
controller:XController
})
function XController() { }
vs
.component('x, {
controller: 'XController'
})
.controller('XController', function() {})
Most helpful comment
@stenmuchow, the "this" is not "undone". There are two instances of controllers: one for the compiled
<post-link-test>
and one the use instantiate manually via$componentController
. You don't pass any bindings to the first one, so its properties are undefined.When you can
$timeout.flush()
, there are two tasks to flush, one for each controllers. Because the first controller's task/fn fails (the error caught by jasmine has more details), the "flushing" is aborted and you never see the output logged by the second controller instance (which has its bindings set as expected).Closing, since everything works as expcted.
#
As a sidenote, your setup seems very suspicious. Compiling a whole component (along with its corresponding controller) just so that you can inject
$element
into a manually instantiated controller doesn't make sense.The point of
$componentController
is to save the overhead of creating/compiling a DOM element when not necessary. If you do need a proper$element
, then you should just use$compile
instead. E.g.: