I have a custom directive that uses an attribute to specify another control that it modifies.
Directive definition object:
{
restrict: 'E',
templateUrl: 'myTemplate.html',
scope: {
targetId: '@'
},
controller: MyController,
controllerAs: 'vm',
bindToController: true
}
A function on the directive's controller modifies the contents of the target element (an input field):
function onSelection (value) {
var $element = $('#' + vm.targetId);
$element.val('calculated stuff');
$element.trigger('input');
}
The unit tests (Jasmine/Karma/PhantomJS) currently append the element to the page. This works, but it seems like a code smell.
beforeEach(inject(function($rootScope, $compile) {
var elementHtml = '<my-directive target-id="bar"></my-directive>' +
'<input type="text" id="bar">';
scope = $rootScope.$new();
angularElement = angular.element(elementHtml);
angularElement.appendTo(document.body); // HELP ME KILL THIS!
element = $compile(angularElement)(scope);
scope.$digest();
}));
afterEach(function () {
angularElement.remove(); // HELP ME KILL THIS!
});
I've tried rewriting the controller function to avoid jQuery; this did not help.
How can I revise the directive or the tests to eliminate the appendTo/remove?
You can create a component fixture with TestBed. createComponent . Fixtures have access to a debugElement , which will give you access to the internals of the component fixture. Change detection isn't done automatically, so you'll call detectChanges on a fixture to tell Angular to run change detection.
We should Unit Test directives by mocking all dependencies with jasmine mocks and spies. We should also Shallow / Deep Test directives using concrete Components (Compiled DOM). A reasonable approach is to create TestComponent or pick up any component which uses the directive we want to test.
TestBed is the primary api for writing unit tests for Angular applications and libraries.
fixture is a wrapper for our component's environment so we can control things like change detection. To trigger change detection we call the function fixture.detectChanges() , now we can update our test spec to: Copy it('login button hidden when the user is authenticated', () => { expect(el. nativeElement. textContent.
Your best bet is to migrate the directive to an attribute instead of an element. This removes the need for the target-id
attribute and you don't need to hunt for the target element.
See http://jsfiddle.net/morloch/621rp33L/
Directive
angular.module('testApp', [])
.directive('myDirective', function() {
var targetElement;
function MyController() {
var vm = this;
vm.onSelection = function() {
targetElement.val('calculated stuff');
targetElement.trigger('input');
}
}
return {
template: '<div></div>',
restrict: 'A',
scope: {
targetId: '@'
},
link: function postLink(scope, element, attrs) {
targetElement = element;
},
controller: MyController,
controllerAs: 'vm',
bindToController: true
};
});
Test
describe('Directive: myDirective', function() {
// load the directive's module
beforeEach(module('testApp'));
var element, controller, scope;
beforeEach(inject(function($rootScope, $compile) {
scope = $rootScope.$new();
element = angular.element('<input my-directive type="text" id="bar">');
$compile(element)(scope);
scope.$digest();
controller = element.controller('myDirective');
}));
it('should have an empty val', inject(function() {
expect(element.val()).toBe('');
}));
it('should have a calculated val after select', inject(function() {
controller.onSelection();
expect(element.val()).toBe('calculated stuff');
}));
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With