unit testing directive with link using controller - angularjs

I'm trying to unit test my directive that set form validity depending on a controller variable.
My directive code :
angular.module('myModule',[])
.directive('myDirective', function() {
return {
restrict: 'A',
link: function(scope, element, attr, ctrl) {
scope.$watch("mailExist", function(){
if(scope.mailExist) {
ctrl.$setValidity('existingMailValidator', false);
} else {
ctrl.$setValidity('existingMailValidator', true);
}
});
}
};
});
When trying to unit test this directive, I'm trying to isolate the controller ctrl with this code:
describe('directive module unit test implementation', function() {
var $scope,
ctrl,
form;
beforeEach(module('myModule'));
beforeEach(inject(function($compile, $rootScope) {
$scope = $rootScope;
var element =angular.element(
'<form name="testform">' +
'<input name="testinput" user-mail-check>' +
'</form>'
);
var ctrl = element.controller('userMailCheck');
$compile(element)($scope);
$scope.$digest();
form = $scope.testform;
}));
describe('userMailCheck directive test', function() {
it('should test initial state', function() {
expect(form.testinput.$valid).toBe(true);
});
});
});
Running this test, I still obtain:
Cannot read property '$setValidity' of undefined
that's mean I haven't really inject a controller.
What is wrong in my test?

Finally in found the solution:
first in code I have add :
require: 'ngModel',
and then modified the unit test as follow:
describe('directive module unit test implementation', function() {
var scope,
ngModel,
form;
beforeEach(module('myModule'));
beforeEach(inject(function($compile, $rootScope) {
scope = $rootScope.$new();
var element =angular.element(
'<form name="testform">' +
'<input name="testinput" ng-model="model" user-mail-check>' +
'</form>'
);
var input = $compile(element)(scope);
ngModel = input.controller('ngModel');
scope.$digest();
form = scope.testform;
}));
describe('userMailCheck directive test', function() {
it('should test initial state', function() {
expect(form.testinput.$valid).toBe(true);
});
});
});
and everything works fined.

Related

check attribute from html - angular directive testing jasmine karma

how to check attribute is present in HTML and match its value. this is a test spec.js I wrote,
define(['angular',
'angularMocks',
'site-config',
'ng_detector',
],
function(angular,
mock,
$app,
ng_detector) {
describe('ng-detector controller', function() {
beforeEach(angular.mock.module("webapp"));
var $compile, $rootScope, tpl, $scope, elm, templateAsHtml;
beforeEach(angular.mock.inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
// $scope = _$rootScope_.$new();
}));
it('should initialize the ng-detector directive', inject(function() {
var tpl = $compile("<div ng-detector ></div>")($rootScope);
$rootScope.$digest();
console.log(tpl) // Log: r{0: <div ng-detector="" class="ng-scope" ng-verison="1.6.4"></div>, length: 1}
templateAsHtml = tpl[0].outerHTML;
expect(templateAsHtml.attr('ng-version')).toEqual(angular.version.full);
}));
});
});
directive. that adds angular version to attribute ng-version
'use strict';
define(['app-module'], function(ng) {
$app.info('ng detector initialized. {file: directives/ng-detector.js}');
ng.directive('ngDetector', function() {
return {
restrict: "A",
link: function(scope, elm, attr) {
elm.attr('ng-version', angular.version.full);
}
};
});
return ng;
});
I want to get a ng-version attribute set by the directive and match the attribute value.
I figured out myself. I was looking at the different place.
it('should check the angular version number', angular.mock.inject(function() {
expect(tpl.attr('ng-version')).toEqual(angular.version.full);
}));

Angular $scope property undefined

I'm trying to set the $scope.sang property to an instance of SangService in a directive. Problem is, it doesn't appear to be getting set.
The Setup
sang-player.directive.js:
angular.module('sang').directive('sangPlayer', [
'SangService',
function(SangService) {
return {
restrict: 'EA',
scope: true,
link: function(scope, _elem, attr) {
window.console.log('link');
scope.sang = SangService.$new();
}
};
}]);
sang-player.directive.spec.js:
describe('The Sang Directive', function() {
var compile, scope, element, sang;
beforeEach(function() {
sang = jasmine.createSpyObj('sang',
['play', 'pause', 'playPause', 'previous', 'next', 'seek']);
module(function($provide) {
$provide.value('Sang', sang);
});
inject(function($compile, $rootScope) {
compile = $compile;
scope = $rootScope.$new();
element = getCompiledElement();
});
});
function getCompiledElement() {
var element = angular.element('<div sang-player></div>');
var compiledElement = compile(element)(scope);
scope.$digest();
return compiledElement;
}
it('creates a scope.sang object', function() {
expect(scope.sang).toBeDefined();
expect(typeof scope.sang).toBe('object');
});
});
Output from failed Jasmine spec run:
The Sang Directive
X creates a scope.sang object
Expected undefined to be defined. (1)
Expected 'undefined' to be 'object'. (2)
Not sure why there's no output from the link callback. I would expect this to get called within compile(element)(scope) or scope.$digest().
The Deets
Angular 1.4.1
Jasmine 2.4.1
grunt-contrib-jasmine 0.9.1
The full project repo lives here if you want to tinker.

How to test function within directive link

I have a directive for which i am trying to write some unit test for:
return {
restrict: 'E',
replace: true,
controllerAs: 'vm',
transclude: true,
template: '<ul>' +
'<li ng-show="vm.hideItem">Home</li>' +
'<li ng-show="vm.hideItem">Products</li>' +
'<li ng-show="!vm.hideItem">Cart</li>' +
'<li ng-show="vm.hideItem">Contact Us</li>' +
'</ul>',
link: function(scope, element, attrs, vm) {
function getData(index) {
if (index){
vm.hideItem = true
}
else {
var li = element.find("li");
li.attr("context-driven", "");
}
}
function getIndex(){
index = myService.getIndex();
getData(index);
}
getIndex();
},
controller: function(){}
};
I have the following, which pass:
describe('<-- myDirective Spec ------>', function () {
var scope, $compile, element, myService;
beforeEach(angular.mock.module('MyApp'));
beforeEach(inject(function (_$rootScope_, _$compile_, _myService_) {
scope = _$rootScope_.$new();
$compile = _$compile_;
myService = _myService_;
var html = '<my-directive></my-directive>';
element = $compile(angular.element(html))(scope);
scope.$digest();
}));
it('should return an unordered list', function () {
var ul = element.find('ul');
expect(ul).toBeDefined();
});
How can i test the call of getIndex, getData and ensure myService has been called?
The key to successful directive testing is moving all view-related logic to controller, i.e.
this.getIndex = function () {
index = myService.getIndex();
getData(index);
}
After the element was compiled in spec, the controller instance can be retrieved and spied with
var ctrl = element.controller('myDirective');
spyOn(ctrl, 'getIndex').and.callThrough();
The good thing about writing specs is that they show design flaws. In current case it is DOM manual operation in getData. It is not clear from the code what context-driven attribute is for, but the same thing has to be achieved in Angular (data binding) and not jQuery (DOM manipulation) fashion in order to be test-friendly.

Mocking formController in directive

I have a directive that checks for the validity of the form before doing something. It's working, but my unit tests keep on failing because I can't mock the formController in my karma tests.
I have the following directive:
.directive('directiveName', function() {
return {
restrict: 'A',
require: 'form',
link: function(scope, element, attrs, ctrl) {
element.bind('submit', function(event) {
if(!ctrl.$valid) ...do something here...
}
}
};
});
This is my test initialization:
var form, element, $scope;
beforeEach(inject(function($rootScope, $compile, $q) {
$scope = $rootScope.$new();
var template = angular.element('<form fl-submit="mockFunction()">' +
'<button type="submit"> </button> </form>');
$scope.mockFunction = function() {
return promise.promise;
};
element = $compile(template)($scope);
form = $scope.form;
}));
describe('and the form is not valid', function() {
form.$valid = false;
...expect something here...
});
Thanks very much for the help!

Unit testing Angular directive with $http

I have an Angular directive that, when attached to an <input>, waits one second after user input before querying an endpoint with $http. In short, it's meant to check username uniqueness.
It looks like this:
.directive('sgValidUsername', ['$http', function(http) {
var waitTimer;
var checkIfUserExists = function(e, ctrl) {
http.get('/publicapi/users/' + e.target.value + '/exists')
.success(function(data) {
ctrl.$setValidity('unique', !data.exists);
});
};
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ctrl) {
element.on('blur keyup', function(e) {
if (e.target.value) {
clearInterval(waitTimer);
waitTimer = setTimeout(function() {
checkIfUserExists(e, ctrl);
}, 1000);
}
});
}
};
}]);
I'm trying my best to write a good comprehensive Jasmine unit test suite, but it's not working out because I couldn't find an appropriate example to learn from. I end up reconstructing the directive in test form rather than actually testing the directive. Also, I get a 'no pending request to flush' error.
Any suggestions for the below?
describe('SignupForm directives', function () {
var form, // el to which directive is applied
$scope,
$httpBackend, // $http mock
usernameExistsHandler;
beforeEach(function() {
module('signupform.directives');
});
beforeEach(function() {
inject(function ($injector, $rootScope, $compile, $q, $timeout) {
$scope = $rootScope;
$httpBackend = $injector.get('$httpBackend');
usernameExistsHandler = $httpBackend.whenGET(/\/publicapi\/users\/.+?\/exists/);
var el = angular.element('<form name="form"><input type="text" name="username" ng-model="user.username" sg-username-is-valid /></form>');
$scope.user = { username: null };
$compile(el)($scope);
form = $scope.form;
});
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should invalidate with existing usernames', function() {
form.username.$setViewValue('username_in_use');
$scope.$digest();
expect($scope.user.username).toEqual('username_in_use');
usernameExistsHandler.respond('200', { exists: true });
$httpBackend.expectGET('/publicapi/users/' + $scope.user.username + '/exists/');
$httpBackend.flush();
expect(form.username.$valid).toBe(false);
});

Resources