trying to test my directive with jasmine but is not failing where it should because of the wrong date(.demo):
describe("Unit: Testing Directives - ", function() {
var $compile, $rootScope;
beforeEach(module('app'));
beforeEach(inject(function(_$compile_, _$rootScope_){
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
describe("Date Validation Directive - ", function(){
it('should show an date as valid', function(){
$rootScope.demo = '10/01/881';
var templateHTML = angular.element('<input class="blah" type="tel" ng-model="demo" my-date />');
var element = $compile(templateHTML)($rootScope);
$rootScope.$digest();
expect(element.hasClass('ng-valid')).toBe(true);
expect(element.hasClass('ng-invalid')).toBe(false);
});
});
});
this is what my directive looks like:
var app = angular.module('app', []);
app.directive("myDate", function () {
return {
restrict: "A", //only activate on element attribute
require: "ngModel", //get hold of NgModelController
link: function (scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function (viewValue) {
var date_regexp = /^(0?[1-9]|[12][0-9]|3[01])[\/\-](0?[1-9]|1[012])[\/\-]\d{4}$/;
if (date_regexp.test(viewValue)) {
// it is valid
ctrl.$setValidity("myDate", true);
return viewValue;
} else {
// it is invalid, return undefined (no model update)
ctrl.$setValidity("myDate", false);
return undefined;
}
});
}
};
});
how can I get it working?
plunkr:http://plnkr.co/edit/GYBvynRqdTTqXxnk7thN?p=preview
This is because of the programmatic assignment of the model value. $parsers run when he value is modified through DOM. When you change the model value programatically it is $formatters that run. So in your directive if you change $parsers to $formatters it will fail the test as you expected.
Plnkr
However you may need both of them in your directive and your real testing should be to test the logic inside the parsers/formatters, not how it is run (It has already been tested by angular) or how the classes are added by angular. If your validations are exposed through say a validator service, or even your directive's controller which provides the validation that would be a good target to test.
Related
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.
I am planning to unit test a angular directive using jasmine. My directive looks like this
angular.module('xyz.directive').directive('sizeListener', function ($scope) {
return {
link: function(scope, element, attrs) {
scope.$watch(
function() {
return Math.max(element[0].offsetHeight, element[0].scrollHeight);
},
function(newValue, oldValue) {
$scope.sizeChanged(newValue);
},
true
);
}
};
});
My unit test case is as follows
describe('Size listener directive', function () {
var $rootScope, $compile, $scope, element;
beforeEach(inject(function(_$rootScope_, _$compile_) {
$rootScope = _$rootScope_;
$compile = _$compile_;
$scope = $rootScope.$new();
element = angular.element('<span size-listener><p></p></span>');
$compile(element)($scope);
$scope.$digest();
}));
describe("Change in size", function () {
it("should call sizeChanged method", function () {
element[0].offsetHeight = 1;
$compile(element)($scope);
$scope.$digest();
$scope.$apply(function() {});
expect($scope.sizeChanged).toHaveBeenCalled();
});
});
});
The code works fine. But the unit test fails. The watch function gets called but element[0].offsetHeight in watch always returns 0. How can we update the element so that watch can observe the new height. Is there a way to even test this, because we don't really have a DOM here. Please guide me on changes that need to be done with unit test.
To get the offsetHeight of an element, it needs to be on the page, so you need to attach it to the document:
it("should call sizeChanged method", function () {
element[0].style.height = '10px';
$compile(element)($scope);
angular.element($document[0].body).append(element);
$scope.$apply();
expect($scope.sizeChanged).toHaveBeenCalled();
});
By the way, offsetHeight is read-only property so use style.height.
For more info: https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetHeight
This post helped me find the answer: Set element height in PhantomJS
My webapp is using Angular 1.4.8. I have a directive that validates a form input using $validators. Only input that starts with number 5, 6 and 9 and contains 8 numbers are valid.
angular
.module('directive.customNumber', [])
.directive('customNumber', customNumber);
function customNumber() {
var REGEXP = /^([569][0-9]{7})/;
return {
require: ['ngModel', '^form'],
link: function(scope, elm, attrs, ctrl) {
ctrl.$validators.customNumber = function(modelValue, viewValue) {
if(ctrl.$isEmpty(modelValue)) {
// consider empty models to be valid
return true;
}
return REGEXP.test(viewValue);
};
}
};
}
Usage:
<form name="form">
<input type="text" name="myInput" custom-number>
</form>
Now I want to write a unit test for this directive using Jasmine. This is my test case:
describe('Directive', function() {
var $scope;
beforeEach(function() {
module('directive.customNumber');
inject(function($rootScope, $compile) {
$scope = $rootScope;
var template = '<form name="form"><input type="text" name="myInput" custom-number></form>';
$compile(template)($scope);
$scope.digest();
});
});
it('should not accept invalid input', function() {
var form = $scope.form;
form.myInput.$setViewValue('abc');
expect(form.$valid).toBeFalsy();
expect(form.myInput.$error.mobile).toBeDefined();
});
});
Running this throws an error "TypeError: Cannot set property 'customNumber' of undefined at this line:
ctrl.$validators.customNumber = function(....
I am not sure why $validators become undefined in the test but works fine in normal environment. Even if I get rid of this error by manually creating $validators object before use, the test fails because the customNumber validator is never being run (know by logging), so form.$valid is always true.
How can I properly test this directive?
On your directive ctrl is an array whare ctrl[0] is the ngModel and ctrl[1] is the formController
I am relatively new to jasmine tests, and I've got some problem with it. I try to test this directive :
DIRECTIVE
myApp.LoadingsDirective = function() {
return {
restrict: 'E',
replace: true,
template: '<div class="loading"><img src="http://www.nasa.gov/multimedia/videogallery/ajax-loader.gif" width="20" height="20" /></div>',
link: function (scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.show);
},
function(val) {
if (val){
$(element).show();
}
else{
$(element).hide();
}
})
}
}
}
myApp.directive('loading', myApp.LoadingsDirective);
This directive just show a loading icon until the result of a asynchronious request replace it.
I try something like this :
TEST
describe('Testing directives', function() {
var $scope, $compile, element;
beforeEach(function() {
module('myApp');
inject(function($rootScope, _$compile_) {
$scope = $rootScope.$new();
$compile = _$compile_;
});
});
it('ensures directive show the loading when show attribut is true', function() {
// GIVEN
var element = $compile('<div><loading show="true"> </loading></div>')($scope);
var loadingScope = element.find('loading').scope();
// WHEN
loadingScope.$watch();
// THEN
expect(loadingScope.show).toBe('true');
});
});
What is the best way to test this type of directive ? How to get access to attributs and test it ?
I always do it this way (coffeescript, but you'll get the idea):
'use strict';
describe 'Directive: yourDirective', ->
beforeEach module('yourApp')
# angular specific stuff
$rootScope = $compile = $scope = undefined
beforeEach inject (_$rootScope_, _$compile_) ->
$rootScope = _$rootScope_
$scope = $rootScope.$new()
$compile = _$compile_
# object specific stuff
element = createElement = undefined
beforeEach inject () ->
createElement = () ->
element = angular.element("<your-directive></your-directive>")
$compile(element)($scope)
$scope.$digest()
it "should have a headline", ->
createElement()
element.find("a").click()
$scope.$apply()
expect(element.find("input").val()).toEqual("foobar")
expect($scope.inputModel).toEqual("foobar")
And this could be the directive:
<your-directive>
<a ng-click="spanModel='foobar'">set inputModel</a>
<input ng-model="inputModel">
</your-directive>
First, I extract the creation of your element into a function. This allows you to do some initial setup before the directive is created.
Then I perform some actions on my directive. If you want to apply this actions into your scope (remember in jasmine you are NOT inside angulars' digest circle), you have to call $scope.$apply() or $scope.$digest() (can't remember right now what the exact difference was).
In the example above, you click on the <a> element, and this has a ng-click attached. This sets the inputModel scope variable.
Not tested, but you'll get the idea
I have a directive as below which i want to cover as part of my jasmine unit test but not sure how to get the template value and the values inside the link in my test case. This is the first time i am trying to unit test a directive.
angular.module('newFrame', ['ngResource'])
.directive('newFrame', [
function () {
function onAdd() {
$log.info('Clicked onAdd()');
}
return {
restrict: 'E',
replace: 'true',
transclude: true,
scope: {
filter: '=',
expand: '='
},
template:
'<div class="voice ">' +
'<section class="module">' +
'<h3>All Frames (00:11) - Summary View</h3>' +
'<button class="btn" ng-disabled="isDisabled" ng-hide="isReadOnly" ng-click="onAdd()">Add a frame</button>' +
'</section>' +
'</div>',
link: function (scope) {
scope.isDisabled = false;
scope.isReadOnly = false;
scope.onAdd = onAdd();
}
};
}
]);
Here is an example with explanation:
describe('newFrame', function() {
var $compile,
$rootScope,
$scope,
$log,
getElement;
beforeEach(function() {
// Load module and wire up $log correctly
module('newFrame', function($provide) {
$provide.value('$log', console);
});
// Retrieve needed services
inject(function(_$compile_, _$rootScope_, _$log_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$log = _$log_;
});
// Function to retrieve a compiled element linked to passed scope
getCompiledElement = function(scope) {
var element = $compile('<new-frame></new-frame>')(scope);
$rootScope.$digest();
return element;
}
// Set up spies
spyOn($log, 'info').and.callThrough();
});
it('test', function() {
// Prepare scope for the specific test
$scope.filter = 'Filter';
$scope.expand = false;
// This will be the compiled element wrapped in jqLite
// To get reference to the DOM element do: element[0]
var element = getCompiledElement($scope);
// Get a reference to the button element wrapped in jqLite
var button = element.find('button');
// Verify button is not hidden by ng-hide
expect(button.hasClass('ng-hide')).toBe(false);
// Verify button is not disabled
expect(button.attr('disabled')).toBeFalsy();
// Click the button and verify that it generated a call to $log.info
button.triggerHandler('click');
expect($log.info).toHaveBeenCalled();
});
});
Demo: http://plnkr.co/edit/tOJ0puOd6awgVvRLmfAD?p=preview
Note that I changed the code for the directive:
Injected the $log service
Changed scope.onAdd = onAdd(); to scope.onAdd = onAdd;
After reading the angular documentation for directives,i was able to solve this. Since the restrict is marked as E, the directive can only be injected through a element name. Earlier i was trying through div like below.
angular.element('<div new-frame></div>')
This will work if restrict is marked as A (attributes). Now i changed my injection in he spec file to match the directive with element name.
angular.element('<new-frame></new-frame>')
Now i was able to get the template and scope attributes in my spec. Just to be sure to accept everything, the combination of A (aatributes), E (elements) and C (class name) can be used in the restrict or any 2 as needed.