Test a simple function in a directive, angular - angularjs

This is my directive:
angular.module('clientApp')
.directive('positionDropDowns', function (CommonFactory) {
return {
templateUrl: 'template/position-drop-downs/position-drop-downs.html',
restrict: 'E',
scope: {
districtsWithSubObjects: '='
},
link: function postLink(scope, element, attrs) {
scope.hello = function(name){
return 'hello ' + name;
}
}
};
});
How do I test the hello function? I tried this:
describe('Directive: positionsDropDowns', function () {
// load the directive's module
beforeEach(module('clientApp'));
beforeEach(module('template/position-drop-downs/position-drop-downs.html'));
var element,
scope;
beforeEach(inject(function ($rootScope) {
scope = $rootScope.$new();
element = angular.element('<position-drop-downs></position-drop-downs>');
$rootScope.$digest();
}));
it('fn hello', inject(function ($compile) {
expect(element.scope.hello('john')).toBe("hello john");
}));
});
I get TypeError: undefined is not a function

You need to compile your custom directive first:
beforeEach(inject(function ($rootScope) {
scope = $rootScope.$new();
element = $compile('<position-drop-downs></position-drop-downs>')(scope);
}));
After that the scope object should be populated with hello method:
it('fn hello', inject(function ($compile) {
expect(scope.$$childTail.hello('john')).toBe("hello john");
}));
URD. zeroflagL in comments gives more elegant way to access isolated directive scope. You can also do
expect(element.isolateScope().hello('john')).toBe("hello john");
Note, that you need to access isolated directive scope. You can do it with $$childTail reference.

Related

Testing angular directive scope method

So I can't seem to call a method in my test that is written on the internalScope of an angular directive.
Here is my test
describe('auto complete directive', function () {
var el, $scope, scope;
beforeEach(module('italic'));
beforeEach(module('stateMock'));
beforeEach(module('allTemplates'));
beforeEach(inject(function ($compile, $rootScope, UserService) {
spyOn(UserService, 'getCurrentUser').and.returnValue({});
$scope = $rootScope;
el = angular.element('<auto-complete collection="" input-value="" enter-event="" focus-on="" />');
$compile(el)($scope);
scope = el.isolateScope();
console.log(scope);
$scope.$apply();
}));
it('should', function () {
scope.matchSelected();
expect(scope.showPopup).toBe(false);
});
});
and my directive:
italic.directive('autoComplete', ['$timeout', function($timeout) {
return {
restrict: "E",
template: '',
scope: {
collection: '=',
inputValue: '=',
enterEvent: '=',
focusOn: '='
},
link: function(scope, element) {
scope.matchSelected = function (match) {
scope.inputValue = match;
scope.showPopup = false;
};
}
};
}]);
and the error:
undefined is not a function (called on scope.matchSelected in the test)
I believe that it is rooted in the fact that scope = el.isolateScope(); returns undefined.
It looks like the issue must be to do with two missing braces in the directive. Intead of }]); at the end it should be }}}]);. I'd recommend to take more care when indenting and using braces. If you use indents correctly it will minimise issues such as this. If you were indenting correctly the directive would look like:
italic.directive('autoComplete', ['$timeout', function($timeout) {
return {
restrict: "E",
template: '',
scope: {
collection: '=',
inputValue: '=',
enterEvent: '=',
focusOn: '='
},
link: function(scope, element) {
scope.matchSelected = function (match) {
scope.inputValue = match;
scope.showPopup = false;
};
}
};
}]);
It's best to create your directive in the actual it and not in before, that way you can control the scope properties set on the directive.
describe('auto complete directive', function () {
var $rootScope, $compile;
beforeEach(module('italic'));
beforeEach(inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
it('should', function () {
//Arrange
var element = $compile("<auto-complete collection=\"\" input-value=\"\" enter-event=\"\" focus-on=\"\" />")($rootScope);
var scope = element.isolateScope();
var match = "match";
//Act
$rootScope.$digest();
scope.matchSelected(match);
//Assert
expect(scope.showPopup).toBe(false);
});
});
Plunkr

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();
}));
});

Unit Test Directive with Isolated Scope

I want to unit test a directive which looks like this:
'use strict';
angular.module('app')
.directive('phone', function () {
return {
require: 'ngModel',
scope: {
user:'=',
},
link: function(scope, element, attrs, ctrl) {
ctrl.$parsers.unshift(function(viewValue) {
scope.user.division = null;
return viewValue;
});
}
};
});
Here is my failing test
'use strict';
describe('Directive: phone', function () {
// load the directive's module
beforeEach(module('app'));
var element,
scope;
beforeEach(inject(function ($compile, $rootScope) {
scope = $rootScope.$new();
element = angular.element('<form name="form"><input name="phone" ng-model="user.phone" phone></form>');
scope.user = {};
element = $compile(element)(scope);
element.scope().$digest();
}));
it('should work', function() {
scope.form.phone.$setViewValue('1');
});
});
But when I try to run it I get:
TypeError: 'undefined' is not an object (evaluating 'scope.user.division=null')
Your isolate scope implies that there should be a user attribute on the element that references a user entity on the parent $scope.
In you compiled HTML, though, there is no user attribute, therefore your isolate $scope's user property is undefined.
Your HTML should look like this:
<input ... user="user" />

Unit-testing a directive with isolated scope, bidirectional value and ngIf

I want to unit test a directive which looks like this:
angular.module('myApp', [])
.directive('myTest', function () {
return {
restrict: 'E',
scope: { message: '='},
replace: true,
template: '<div ng-if="message"><p>{{message}}</p></div>',
link: function (scope, element, attrs) {
}
};
});
Here is my failing test:
describe('myTest directive:', function () {
var scope, compile, validHTML;
validHTML = '<my-test message="message"></my-test>';
beforeEach(module('myApp'));
beforeEach(inject(function($compile, $rootScope){
scope = $rootScope.$new();
compile = $compile;
}));
function create() {
var elem, compiledElem;
elem = angular.element(validHTML);
compiledElem = compile(elem)(scope);
scope.$digest();
return compiledElem;
}
it('should have a scope on root element', function () {
scope.message = 'not empty';
var el = create();
console.log(el.text());
expect(el.text()).toBeDefined();
expect(el.text()).not.toBe('');
});
});
Can you spot why it's failing?
The corresponding jsFiddle
Thanks :)
console.log((new XMLSerializer()).serializeToString(el.get(0)));
returns
<!-- ngIf: message -->
because you are using replace without a parent element in validHTML with combinaison of ng-if .So either you change validHTML and add a div as parent.
or
test your expectations on the next sibling of el
el.next()
which will be
<div ng-if="message" message="message" class="ng-scope"><p class="ng-binding">yes</p></div>

Resources