When unit testing angular directives, how can I grab an instance of the directive controller and assert certain data bindings on the controller?
function myDirective() {
return {
restrict: 'E',
replace: true,
templateUrl: 'tpl.html',
scope: { },
controller: function($scope) {
$scope.text = 'Some text';
}
};
}
angular
.module('example')
.directive('myDirective', [
myDirective
]);
unit test
describe('<my-directive>', function() {
var element;
var $compile;
var $scope;
beforeEach(module('example'));
beforeEach(function() {
inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$scope = _$rootScope_.$new();
});
});
describe('basic functionality', function() {
beforeEach(function() {
element = $compile('<my-directive></my-directive')($scope);
$scope.$digest();
});
it('should bind the correct text', function() {
//?
});
});
});
element = $compile('<my-directive></my-directive')($scope);
$scope.$digest();
ctrl = element.controller('myDirective');
Call element.controller with $scope like element.controller($scope). Some proof of concept bellow.
angular
.module('example', [])
.directive('myDirective', [
function myDirective() {
return {
restrict: 'E',
replace: true,
//template: ['<div>{{ text }}</div>'].join(''),
scope: {},
controller: function($scope) {
$scope.text = 'Some text';
}
};
}
]);
describe('<my-directive>', function() {
var element;
var $compile;
var $scope;
beforeEach(module('example'));
beforeEach(function() {
inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$scope = _$rootScope_.$new();
});
});
describe('basic functionality', function() {
beforeEach(function() {
element = $compile('<my-directive></my-directive')($scope);
$scope.$digest();
});
it('should bind the correct text', function() {
expect(element.controller($scope).scope().$$childTail.text).toEqual('Some text')
});
});
});
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-mocks.js"></script>
Related
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);
}));
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 am taking baby steps in Jasmine, please bear with me for any blatant mistakes..I am writing test cases to check if a controller method - transformData gets called, the details below
My Directive
angular.module('myModule')
.directive('myDirective', [ function ()
{
'use strict';
return {
restrict: 'E',
templateUrl: '/static/quality/scripts/directives/hh-star-rating.html',
scope: {
varA:'#',
},
controller: [
'$scope', '$controller',
function ($scope, $controller) {
$controller('otherController', {$scope: $scope})
.columns(
$scope.x,
$scope.y,
$scope.series
);
$scope.transformData = function(data)
{
/// does something;
return data;
};
}
],
My Spec
describe('directive - hh-star-ratings', function() {
'use strict';
angular.module('myModule', [])
.directive('myContainer', function() {
return {
restrict: 'E',
priority: 100000,
terminal: true,
template: '<div></div>',
controller: function($scope) {
$scope.loadingData = true;
this.stopLoading = function() {
$scope.loadingData = false;
};
}
}
});
var result_set = {
//some data-transform-req
};
beforeEach(module('myModule'));
var element, scope, controller;
beforeEach(inject(function($rootScope, $compile) {
scope = $rootScope.$new();
element = angular.element(
'<my-container> <my-directive> </my-directive> </my-container>'
);
$compile(element)(scope);
scope.$digest();
controller = element.controller('myDirective');
}));
it('should call the transformData', function() {
controller.transformData(result_set);
scope.$apply();
scope.transformData.should.have.been.called;
});
})
Issue: When I run the test, I get the following error
TypeError: 'undefined' is not an object (evaluating 'controller.transformData')
What am I doing wrong? Thanks in advance for your time.
Your define the function transformData in the scope - NOT in the controller
controller: [
'$scope', '$controller',
function ($scope, $controller) {
$controller('otherController', {$scope: $scope})
.columns(
$scope.x,
$scope.y,
$scope.series
);
this.transformData = function(data) // <= change to this (controller)
{
/// does something;
return data;
};
}
],
Trying to test a directive but I'm at a loss. Basically I don't know how to set the test up and I can't find any examples to step by step me. Can someone provide an explanation of how this should be set up? Right now the error I'm getting is
TypeError: 'undefined' is not an object (evaluating 'current.name')
directives.directive('convenienceNav', function(){
return {
restrict: 'A',
template: '<button class="btn btn-success" ui-sref=" {{$state.current.name}}.add"><i class="fa fa-user-plus"></i>Add {{$state.current.params.singular_title}}</button>'
};
});
describe('directive: convenienceNav', function() {
var element, scope, stateParams, state;
beforeEach(module('app.directives'));
beforeEach(module('ui.router'));
beforeEach(inject(function($rootScope, $compile, $state) {
scope = $rootScope.$new();
stateParams = {'api_resource_name': 'people'};
state = $state;
state.current.name = 'admin.people';
// state.current.params.singular_title = 'Person';
element ='<div convenience-nav></div>';
element = $compile(element)(scope);
scope.$digest();
}));
it('should have state.current.name = admin.people', function(){
expect(element.html()).toBe('<button class="btn btn-success" ui-sref="admin.people.add"><i class="fa fa-user-plus"></i>Add Person</button>');
});
});
Below are the examples of how you can test angular directives and routes navigation(ui router) in jasmine.
Button with name
var namedButtonModule = angular.module('namedButtonModule', []);
namedButtonModule.directive('namedButton', function() {
return {
restrict: 'AE',
scope: {
name: "=?"
},
template: '<button class="btn btn-success">I am {{name}} Button</button>',
controller: function($scope) {
$scope.name = $scope.name || 'simple';
}
};
});
describe('directive: namedButton', function() {
beforeEach(module('namedButtonModule'));
beforeEach(inject(function($rootScope, $compile) {
this.$rootScope = $rootScope;
this.$compile = $compile;
this.testContainer = document.getElementById('test-container');
this.compileDirective = function(template, scope) {
var element = this.$compile(template)(scope);
this.testContainer.appendChild(element[0]);
scope.$digest();
return element;
}
}));
afterEach(function() {
this.testContainer.innerHTML = '';
});
it('button should show default name', function() {
var template = '<div named-button></div>';
var scope = this.$rootScope.$new();
var element = this.compileDirective(template, scope);
expect(element.text()).toBe('I am simple Button');
});
it('button should show passed name to the scope', function() {
var template = '<div named-button name="inputName"></div>';
var scope = this.$rootScope.$new();
scope.inputName = "Angular Test";
var element = this.compileDirective(template, scope);
expect(element.text()).toBe('I am Angular Test Button');
});
});
<!-- jasmine -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine-html.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/boot.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine.min.css" rel="stylesheet" />
<!-- angular -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-mocks.js"></script>
<div id="test-container"></div>
Navigation Button
on clicking button, app will redirect to that state.
var navigationButtonsModule = angular.module('navigationButtonsModule', ['ui.router']);
navigationButtonsModule.config(['$stateProvider',
function($stateProvider) {
$stateProvider.state('home', {
url: '/home',
templateUrl: 'home.html'
});
}
]);
navigationButtonsModule.directive('navigationButton', function() {
return {
restrict: 'AE',
scope: {
state: "=?",
name: "=?"
},
template: '<button class="btn btn-success" ui-sref="{{state}}">Go to {{name}}</button>',
};
});
describe('directive: navigationButton', function() {
beforeEach(module('navigationButtonsModule'));
beforeEach(inject(function($rootScope, $compile, $state, $templateCache, $timeout) {
this.$rootScope = $rootScope;
this.$compile = $compile;
this.$state = $state;
this.$templateCache = $templateCache;
this.$timeout = $timeout;
this.testContainer = document.getElementById('test-container');
this.compileDirective = function(template, scope) {
var element = this.$compile(template)(scope);
this.testContainer.appendChild(element[0]);
scope.$digest();
return element;
}
}));
afterEach(function() {
this.testContainer.innerHTML = '';
});
it('navigation button should show passed name and ui-sref state', function() {
var template = '<navigation-button state="state" name="name"></navigation-button>';
var scope = this.$rootScope.$new();
scope.state = 'home';
scope.name = 'Home';
var element = this.compileDirective(template, scope);
expect(element.text()).toBe('Go to Home');
expect(element.find('button').attr('ui-sref')).toBe('home');
});
it('will show home href', function() {
expect(this.$state.href('home')).toEqual('#/home');
});
it('on button click browser should go to home state', function() {
var template = '<navigation-button state="state" name="name"></navigation-button>';
var scope = this.$rootScope.$new();
scope.state = 'home';
scope.name = 'Home';
var element = this.compileDirective(template, scope);
// mimicking home.html template
this.$templateCache.put('home.html', '');
this.$timeout(function() {
element.find('button')[0].click();
});
this.$timeout.flush();
this.$rootScope.$digest();
expect(this.$state.current.name).toBe('home');
});
});
<!-- jasmine -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine-html.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/boot.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.2.1/jasmine.min.css" rel="stylesheet" />
<!-- angular -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.15/angular-ui-router.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular-mocks.js"></script>
<div id="test-container"></div>
JSFiddle
I have kept replicated code as it is, so it can be easy to read and understand
Here's Plunker Link
Updated Plunker Updated Plunker
I have written a directive which hide and shows the div based on ajax call.
How i can i test a behaviour of Directive written ?Its shows the spinner on Ajax Call.
Spec
describe('loader directive', function() {
var $compile, scope;
beforeEach(module('plunker'));
beforeEach(inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
scope = _$rootScope_.$new();
}));
it('check if div shows on', function() {
var element = $compile('<div loading>Something</div>')(scope);
scope.$apply();
});
});
Directive
app.directive('loading', ['$http' ,function ($http)
{
return {
restrict: 'A',
link: function (scope, elm, attrs)
{
scope.isLoading = function () {
return $http.pendingRequests.length > 0;
};
scope.$watch(scope.isLoading, function (v) {
if(v) {
elm.show();
} else {
elm.hide();
}
});
}
};
}]);
You can just check for an .is(':visible').
describe('loader directive', function() {
var $compile, scope, $httpBackend, $http, element;
beforeEach(module('plunker'));
beforeEach(inject(function(_$compile_, _$rootScope_, _$httpBackend_, _$http_) {
$compile = _$compile_;
scope = _$rootScope_.$new();
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', '/test').respond(200, {
test: true
});
$http = _$http_;
}));
afterEach(function() {
$httpBackend.flush();
if (element && element.length) {
element.remove();
}
});
it('check if div shows on http call', function() {
element = $compile('<div class="loading-bar" loading>Something</div>')(scope);
element.appendTo('body');
$http.get('/test');
scope.$apply();
expect(element.is(':visible')).toBe(true);
});
});
To mock out the $http calls you will need to use $httpBackend service.
Plunker Demo