Short version: I have a directive that is using a "new scope" (ie, scope:true
; not an isolate scope) which appears to work just fine when I'm interacting with the application, but which does not appear to update the scope
in an observable way in the unit tests. Why are the changes to field
on the directive's scope not observable in the unit test?
Example in Plunker: http://plnkr.co/edit/gpzHsX?p=preview
Here's the directive:
angular.module('app').directive('ngxBlurry', [
function() {
return {
restrict:'C',
replace:false,
scope:true,
link: function(scope, element, attrs) {
element.bind('blur', function() {
var val = element.val();
scope.$apply(function(scope) {
scope.field = val;
});
});
scope.$watch('field', function(n, o) {
if (n !== o) {
scope.fields[n] = scope.fields[o];
delete scope.fields[o];
}
});
}
};
}
]);
It's getting used roughly as follows:
<div ng-controller="FooContoller">
<div ng-repeat="(field, value) in fields">
<input value="{{field}}" type="text" class="ngx-blurry"/>
<strong>"field" is «{{field}}»</strong>
</div>
</div>
Assuming that fields
looks something approximately like:
$scope.fields = {
'foo':true,
'bar':true,
'baz':true,
'':true
};
And here's the unit test:
describe('ngxBlurry', function() {
var scope,
element,
template = '<input value="{{field}}" type="text" class="ngx-blurry"/>';
beforeEach(module('app'));
beforeEach(inject(function($rootScope, $compile) {
scope = $rootScope.$new();
scope.fields = {'foo':true, '':true};
scope.field = '';
element = $compile(template)(scope);
}));
it('defers update until after blur', function() {
spyOn(scope, '$apply');
element.val('baz');
element.triggerHandler('blur');
// true:
expect(scope.$apply).toHaveBeenCalled();
// false!?
expect(scope.field).toBe('baz');
scope.$digest();
// still false:
expect(scope.field).toBe('baz');
element.scope().$digest();
// *still* false:
expect(scope.field).toBe('baz');
// also false:
expect(element.scope().field).toBe('baz');
});
});
Now, when interacting with the application:
input
and any updates to the keys in $scope.fields
are deferred until the blur event on that field. $apply
is happening but ... $apply
is (apparently) not getting called. $watch
expression get called. The directive itself in the context of the running application appears to operate just fine, but I cannot seem to find any reason why I cannot observe the changes with a unit test.
Barring a redesign of the directive (eg, to use isolate scope and $emit
events): is there anything that I have missed here? Something I should change about the directive? or else some trick to make these changes observable in the unit test?
In a nutshell: don't forget .andCallThrough()
on the spy.
Changing nothing about the directive itself, the working version of the test needed to be:
// everything about the `describe` stays the same...
it('defers update until after blur', function() {
spyOn(scope, '$apply').andCallThrough();
element.val('baz');
element.triggerHandler('blur');
expect(scope.$apply).toHaveBeenCalled();
expect(element.scope().field).toBe('baz');
});
So...
spy
needed andCallThrough()
element.scope()
to access the correct scope.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.