Errors thrown when unit testing directive with Angular - angularjs

I have a custom directive that uses a service. When I run my unit tests, I keep getting the thrown error 'Library does not exist on window'. How can I avoid getting that error in my Unit test?
example service
angular.module('example')
.factory('thirdParty', ['$window', function($window) {
if (typeof $window.thirdParty === 'undefined') {
throw new Error('Library does not exist on window');
} else {
return $window.thirdParty;
}
}]);
custom directive
angular.module('example')
.directive('customDirective', ['thirdParty',
function(thirdParty) {
var defaults, link;
link = function(scope, element, attrs, ctrls) {
// do something with thirdParty
};
return {
restrict: 'A',
require: 'ngModel',
link: link,
};
}]);
test
describe('customDirective', function() {
var element, compile, scope;
beforeEach(module('example'));
beforeEach(inject(function($compile, $rootScope) {
compile = $compile;
scope = $rootScope.$new();
}));
// Manually compile and link our directive
function getCompiledElement(template) {
var compiledElement;
var validTemplate = '<input ng-model="example.data" custom-directive />';
compiledElement = compile(template || validTemplate)(scope);
scope.$digest();
return compiledElement;
}
it('should do something', function() {
element = getCompiledElement();
// expects
});
});

You need to inject $window (stub that out too, for good measure) and stub/mock out the thirdParty lib.
var $window;
beforeEach(function () {
$window = {};
module('yourmodule', function ($provide) {
$provide.value('$window', $window);
});
$window.thirdParty = {};
});
it('throws', function () {
delete $window.thirdParty;
function fn () {
getCompiledElement();
}
expect(fn).to.throw(/does not exist on window/i);
});
it('just works™', function () {
function fn () {
getCompiledElement();
}
expect(fn).to.not.throw;
});

Related

Test directive that sets focus

Directive:
angular
.module('tdsapp')
.directive('focus', focus);
focus.$inject = ['$timeout'];
// Used to set focus on element
function focus($timeout) {
return {
scope: { focus: '#'},
link: function (scope, element) {
scope.$watch('focus',
function (value) {
if (value) {
$timeout(function () {
element[0].focus();
console.log('focus called');
});
}
}
);
}
};
Test Spec:
describe('directive: focus', function () {
var $timeout, element, $scope, $compile;
beforeEach(function () {
module('tdsapp');
inject(function (_$timeout_, $rootScope, _$compile_) {
$timeout = _$timeout_;
$compile = _$compile_;
$scope = $rootScope.$new();
});
});
it('should call focus on element', function () {
var elm = angular.element('<input type="text" name="second" focus="true" />');
spyOn(elm[0], 'focus');
$timeout.flush();
expect(elm[0].focus).toHaveBeenCalled();
});
});
I have looked at AngularJS: how to test directive which gives focus to element? and at How do I check if my element has been focussed in a unit test and neither works. I either get the expect failing or $timeout.flush() - No deferred tasks to be flushed.
EDIT:
As I stated in the text - the above two examples do not work.

How to write unit test case for scope and timeout function inside directive

I am using isolate scope in custom directive. I have updated plunker link. http://plnkr.co/edit/NBQqjxW8xvqMgfW9AVek?p=preview
Can someone help me in writing unit test case for script.js file.
script.js
var app = angular.module('app', [])
app.directive('myDirective', function($timeout) {
return {
restrict: 'A',
scope: {
content: '='
},
templateUrl: 'my-directive.html',
link: function(scope, element, attr) {
$timeout(function() {
element = element[0].querySelectorAll('div.outerDiv div.innerDiv3 p.myClass');
var height = element[0].offsetHeight;
if (height > 40) {
angular.element(element).addClass('expandable');
scope.isShowMore = true;
}
})
scope.showMore = function() {
angular.element(element).removeClass('expandable');
scope.isShowMore = false;
};
scope.showLess = function() {
angular.element(element).addClass('expandable');
scope.isShowMore = true;
};
}
}
})
(function() {
'use strict';
describe('Unit testing directive', function() {
var $compile, scope, element, compiledDirective, $rootScope, $timeout;
beforeEach(module("app"));
beforeEach(inject(function(_$compile_, _$rootScope_, _$timeout_) {
$compile = _$compile_;
scope = _$rootScope_.$new();
$timeout = _$timeout_;
element = angular.element(' <div my-directive content="content"></div>');
compiledDirective = $compile(element)(scope);
scope.$digest();
}));
it('should apply template', function() {
expect(compiledDirective.html()).toBe('');
});
it('check for timeout', function() {
$timeout.flush();
});
});
})();
Use $timeout.flush() function for writing testcase for $timeout
it('check for timeout', function() {
scope.digest();
// flush timeout(s) for all code under test.
$timeout.flush();
// this will throw an exception if there are any pending timeouts.
$timeout.verifyNoPendingTasks();
expect(scope.isShowMore).toBeTruthy();
});
Check this article for better understanding.

Testing an Angular directive with isolate scope and parent controller

I have a directive which creates an isolate scope and has a parent directive controller through the require option. In the link function I am adding some methods to the scope.
When I am trying to compile the directive in my test, I can't seem to get access to the methods I added in the link function, even though the link function is definitely executing.
The scope I have just seems to be an empty child scope of $rootScope. I have tried using element.isolateScope() but this just seems to give me the scope of the parent directive.
I am probably compiling something wrong, can someone help?
parent-directive.js
angular.module("app").directive("sortHead", function() {
return {
restrict: "A",
controller: function ($scope) {
$scope.sortField = undefined;
$scope.reverse = false;
this.setSortField = function(value) {
$scope.sortField = value;
};
this.setReverse = function(value) {
$scope.reverse = value;
};
this.getSortField = function(value) {
return $scope.sortField;
};
this.getReverse = function(value) {
return $scope.reverse;
};
}
};
});
directive-to-test.js
angular.module("app").directive("sortHeader", function() {
return {
restrict: "A",
templateUrl: "templates/sortHeader.html",
scope: {
title: "#",
sort: "#"
},
require: "^sortHead",
link: function(scope, element, attrs, controller) {
scope.sortBy = function(name) {
if (controller.getSortField() === name) {
controller.setReverse(!controller.getReverse());
} else {
controller.setSortField(name);
controller.setReverse(false);
}
};
scope.getSortField = function() {
return controller.getSortField();
};
scope.getReverse = function() {
return controller.getReverse();
};
}
};
});
test.js
beforeEach(inject(function ($rootScope, $compile) {
scope = $rootScope.$new();
element = angular.element("<th sort-header title='Name' sort='name'></th>");
$compile(element)(scope);
scope.$digest();
}));
The test doesn't seem to be workable in its current form.
Here is a plunker with fixed spec
beforeEach(module('app'));
beforeEach(inject(function ($rootScope, $compile, $templateCache, sortHeaderDirective) {
scope = $rootScope.$new();
$templateCache.put(sortHeaderDirective[0].templateUrl, '');
element = angular.element("<th sort-header title='Name' sort='name'></th>");
element.data('$sortHeadController', {});
$compile(element)(scope);
scope.$digest();
}));
it("should do something", inject(function () {
expect(element.isolateScope().title).toEqual('Name');
}));

How do I spyOn a function defined in the link function of a directive?

I want to add spyOn in a function defined inside the link function of a directive. I tried spyOn(element, function_name) after compiling the element, but it doesn't work.
Expose the function on the directive's scope.
function link (scope, element, attrs) {
// this can be spied on
scope.myFunction = function () {
};
// this won't be able to be spied on
function myPrivateFunction () {
}
}
Retrieve the directive's scope.
var directiveScope = angular.element('#myElement').scope();
// or .isolateScope() for isolate scope directives
Spy on the function.
spyOn(directiveScope, 'myFunction');
You can achieve this by using an isolateScope
//--directive.js--//
(function(angular) {
angular.module('module')
.directive('directive', directive);
directive.$inject = ['dv'];
function directive(dv) {
var directive = {
link: link,
replace: true,
restrict: "E",
scope: {
bindto: '=bindto'
}
};
return directive;
function link(scope, element, attrs) {
scope.generate = generate;
scope.generate();
function generate() {
/**
* Generate code goes here
**/
}
}
}
})(angular);
//--directive.spec.js--//
describe('Unit Test: Line-chart directive', directiveTest);
function directiveTest() {
var dvFactory;
var $compile;
var $scope;
var element;
var compiledElement;
var $isolateScope;
beforeEach(module('module'));
beforeEach(inject(function(_$compile_, _$rootScope_, _dv_) {
$compile = _$compile_;
$scope = _$rootScope_.$new();
dvFactory = _dv_;
element = '<directive></directive>';
//Compile the directive element
compiledElement = $compile(element)($scope);
//Obtain an isolateScope of the compiled element
$isolateScope = compiledElement.isolateScope();
}));
it('should call and spy on generate', function() {
//spyOn a generate() in $isolateScope
spyOn($isolateScope, 'generate').and.callThrough();
//Call the method
$isolateScope.generate();
//Check it is called
expect($isolateScope.generate).toHaveBeenCalled();
});
}

Test Pre-Compile function on directive

How do I unit test a precompile function on a directive? I can just check the scope post compile but was wondering if there was a better way.
angular.module('app')
.directive('mailcheck', function () {
return {
compile: function() {
return {
pre: function(scope, element, attributes, controller) {
scope.mailcheck = controller;
}
post: ...
}
}
};
});
Test:
'use strict';
describe('Directive: mailcheck', function () {
// load the directive's module
beforeEach(module('8SecondsToTradeGuiApp'));
var element,
scope;
beforeEach(inject(function ($rootScope) {
scope = $rootScope.$new();
element = '<input mailcheck>';
element = $compile(element)(scope);
scope.$digest();
}));
});

Resources