I have directive myItem. I want to change one property that is passed from parent to directive, so I use controller in myItem where I divide value by 60.
Everything works fine on the website.
Directive
define(['angular',
'core',
'ui-bootstrap',
],
function(angular, coreModule, uiBootStrap) {
'use strict';
function myItemDirective(APPS_URL) {
return {
restrict: 'E',
replace: 'true',
scope: {
item: '='
},
templateUrl: APPS_URL.concat('item.tpl.html'),
controller: ['$scope', function($scope) {
$scope.item.property = Math.round($scope.item.property / 60);
}]
};
}
return angular.module('apps.myItemModule', [coreModule.name])
.directive('myItem', ['APPS_URL', myItemDirective]); });
Now I would like to write a test where I can check that rendered value in directive is value passed from parent divided by 60.
Unit Test
define([
'angular',
'angularMocks',
'apps/directives/mydirective' ], function (angular, mocks, myItemDirective) {
var $httpBackend, $controller, $rootScope, $scope, directive,
item = {
property: 60
};
describe('my directive', function () {
beforeEach(function() {
mocks.module(myItemDirective.name);
mocks.inject(function(_$rootScope_, $injector, $window) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$compile = $injector.get('$compile');
compileDirective();
});
});
afterEach(function() {
$scope.$destroy();
element.remove();
});
function compileDirective() {
element = angular.element('<my-item item=' + JSON.stringify(item) + '></my-item>');
directive = $compile(element)($scope);
$scope.$digest();
directiveScope = element.isolateScope();
}
it('test', function(){
// console.log(element.find('#item-holder').innerText)
// > 60
expect(element.find('#item-holder').innerText).toEqual(Math.round(item.propert/60));
// this will fail because item.property was not divided by 60
});
}); });
Problem
I am not able to render directive in unit test with value divided by 60. I can see in console that controller in directive has been called but the value is not changed.
The problem was related to tests using the same reference to object item.
To fix this:
moved item to beforeEach
changed the way to create element
changed the way to get directive scope
use $scope.$apply()
So test looks like:
define([
'angular',
'angularMocks',
'apps/directives/mydirective' ], function (angular, mocks, myItemDirective) {
var $httpBackend, $controller, $rootScope, $scope, directive,
item;
describe('my directive', function () {
beforeEach(function() {
item = {
property: 60
};
mocks.module(myItemDirective.name);
mocks.inject(function(_$rootScope_, $injector, $window) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$compile = $injector.get('$compile');
compileDirective();
});
});
afterEach(function() {
$scope.$destroy();
element.remove();
});
function compileDirective() {
$scope.item = item;
element = angular.element('<my-item item="item"></my-item>');
directive = $compile(element)($scope);
$scope.$apply();
directiveScope = directive.isolateScope();
}
it('test', function(){
// console.log(element.find('#item-holder').innerText)
// > 60
expect(element.find('#item-holder').innerText).toEqual(Math.round(item.propert/60));
// this will fail because item.property was not divided by 60
});
}); });
Related
I have directive with isolate scope and controller, Is it right to write unit tests for directive and test some functional in controller scope?
And if it right, how i can get access to controller scope?
angular.module('myApp').directive('myDirecive', function () {
return {
templateUrl: 'template.html',
scope: {
data: '='
},
controller: function ($scope) {
$scope.f= function () {
$scope.value = true;
};
},
link: function ($scope) {
// some logic
});
}
};
})
describe('myDirective', function () {
'use strict';
var element,
$compile,
$rootScope,
$scope;
beforeEach(module('myApp'));
beforeEach(inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
element = compileElement();
}));
function compileElement() {
var element = angular.element('<my-directive></my-directive>');
var compiledElement = $compile(element)($scope);
$scope.$digest();
return compiledElement;
}
it('should execute f', function () {
$scope.f();
expect($scope.val).toBe(true); // there we can access to isolate scope from directive;
});
});
Controller is given the isolated scope created by the directive. Your $scope, that you pass into compile function, is used as a parent for the directive's isolate scope.
Answering how to test it, you have two options:
Access the isolated scope from the element:
var isolated = element.isolateScope();
For that, you need to have $compileProvider.debugInfoEnabled(true);
Access the isolated scope from your scope:
var isolated = $scope.childHead;
Try this
describe('myDirective', function () {
'use strict';
var element,
dirCtrlScp,
$compile,
$rootScope,
$scope;
beforeEach(module('myApp'));
beforeEach(inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
element = compileElement();
}));
function compileElement() {
var element = angular.element('<my-directive></my-directive>');
var compiledElement = $compile(element)($scope);
dirCtrlScp = element.controller;
$scope.$digest();
return compiledElement;
}
it('should execute f', function () {
dirCtrlScp.f();
expect(dirCtrlScp.value).toBe(true); // there we can access to isolate scope from directive;
});
});
Have a look at this Article
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.
I'm trying to do unit testing on a directive that calls a service to retrieve posts and am confused on how to test the directive. Usually directive testing is easy with just compiling the directive element but this directive element is calling posts via a get call. How do I test this?
(function() {
'use strict';
describe('Post directive', function() {
var element, scope, postService, $rootScope, $compile, $httpBackend;
beforeEach(module('madkoffeeFrontend'));
beforeEach(inject(function(_postService_, _$rootScope_, _$compile_, _$httpBackend_) {
postService = _postService_;
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
$compile = _$compile_;
// $httpBackend.whenGET('http://madkoffee.com/wp-json/wp/v2/posts?per_page=3').passThrough();
scope = $rootScope.$new();
spyOn(postService, 'getPosts');
element = $compile('<posts post-num="3"></posts>')(scope);
scope.$digest();
}));
it('should get the posts successfully', function () {
expect(postService.getPosts).toHaveBeenCalled();
});
// it('should expect post to be present', function () {
// expect(element.html()).not.toEqual(null);
// });
});
})();
This is the controller:
(function() {
'use strict';
angular
.module('madkoffeeFrontend')
.directive('posts', postsDirective);
/** #ngInject */
function postsDirective() {
var directive = {
restrict: 'E',
scope: {
postNum: '='
},
templateUrl: 'app/components/posts/posts.html',
controller: PostController,
controllerAs: 'articles',
bindToController: true
};
return directive;
/** #ngInject */
function PostController($log, postService) {
var vm = this;
postService.getPosts(vm.postNum).then(function(data) {
$log.debug(data);
vm.posts = data;
}).catch(function (err) {
$log.debug(err);
});
}
}
})();
Don't call getPosts() from your test: that doesn't make sense.
Tell the spied service what to return. The fact that it uses http to get posts is irrelevant for the directive. You're unit-testing the directive, not the service. So you can just assume the service returns what it's supposed to return: a promise of posts.
Tell the spied postService what to return:
spyOn(postService, 'getPosts').and.returnValue(
$q.when(['some fake post', 'some other fake post']));
I facing difficulty in covering the inline controller function defined in a directive using Jasmine and Karma. Below is the code of that directive.
myApp.directive("list", function() {
return {
restrict: "E",
scope: {
items: "=",
click: "&onClick"
},
templateUrl: "directives/list.html",
controller: function($scope, $routeParams) {
$scope.selectItem = function(item) {
$scope.click({
item: item
});
}
}
}
});
Unit Test case is below:
describe('Directive:List', function () {
var element, $scope, template, controller;
beforeEach(inject(function ($rootScope, $injector, $compile, $templateCache, $routeParams) {
$httpBackend = $injector.get('$httpBackend');
$httpBackend.when("GET", "directives/list.html").respond({});
$scope = $rootScope.$new();
element = $compile('<list></list>')($scope);
template = $templateCache.get('directives/list.html');
$scope.$digest();
controller = element.controller($scope, $routeParams);
}));
it('Test Case-1: should contain the template', function () {
expect(element.html()).toMatch(template);
});
});
In my code coverage report, the controller function is not covered. Also I am not able to get the controller instance and test the selectItem method.
Any idea would be of great help!
I am trying to write a jasmine test that will test if an angular directive I've written is working.
Here is my spec file:
describe('blurb directive', function () {
var scope, httpMock, element, controller;
beforeEach(module('mdotTamcCouncil'));
beforeEach(module('mdotTamcCouncil.core'));
beforeEach(module('blurb'));
beforeEach(inject(function (_$httpBackend_, $rootScope, $compile) {
element = angular.element('<mcgi-blurb text-key="mainPageIntro"></mcgi-blurb>');
var httpResponse = '<textarea name="content" ng-model="content"></textarea>';
scope = $rootScope.$new();
httpMock = _$httpBackend_;
httpMock.whenGET('components/blurb/blurb.html').respond(httpResponse);
element = $compile(element)(scope);
scope.$digest();
}));
it('should have some content', function () {
expect(scope.content).toBeDefined();
});
});
The value "scope.content" is always undefined and when I look at the scope object it seems to be a generic scope object that doesn't have my custom attributes on it.
Here are the other related files:
blurb-directive.js
(function () {
'use strict';
angular.module('blurb')
.directive('mcgiBlurb', blurb);
function blurb() {
return {
restrict: 'E',
replace: true,
templateUrl: jsGlobals.componentsFolder + '/blurb/blurb.html',
controller: 'BlurbController',
controllerAs: 'blurb',
bindToController: false,
scope: {
textKey: "#"
}
};
};
})();
blurb-controller.js
(function () {
angular.module('blurb')
.controller('BlurbController', ['$scope', 'blurbsFactory', 'userFactory', function ($scope, blurbsFactory, userFactory) {
$scope.content = "";
$scope.blurbs = {};
$scope.currentUser = {};
this.editMode = false;
userFactory().success(function (data) {
$scope.currentUser = data;
});
blurbsFactory().success(function (data) {
$scope.blurbs = data;
$scope.content = $scope.blurbs[$scope.textKey];
});
this.enterEditMode = function () {
this.editMode = true;
};
this.saveEdits = function () {
this.editMode = false;
$scope.blurbs[$scope.textKey] = $scope.content;
};
}]);
})();
What am I doing wrong?
The directive has isolated scope, so the scope passed to its controller and link function (if there was one), is the isolated one, different than your scope.
You may have luck getting the scope of the directive using element.isolateScope(); you may not, because of the replace: true - try to make sure. You may also access the controller instance using element.controller('mcgiBlurb').