Undefined function in AngularJS directive unit test - javascript

I am trying to write a unit test for the toggleDetails function defined inside the following AngularJS directive:
angular.module('hadoopApp.cluster.cluster-directive', [])
.directive('cluster', [function() {
return {
templateUrl:'components/cluster/cluster.html',
restrict: 'E',
replace: true,
scope: {
clusterData: '=',
showDetails: '='
},
link: function(scope, element, attrs) {
scope.toggleDetails = function() {
console.log('Test');
scope.showDetails = !scope.showDetails;
};
},
// Default options
compile: function(tElement, tAttrs){
if (!tAttrs.showDetails) { tAttrs.showDetails = 'false'; }
}
};
}]);
And this is the unit test:
'use strict';
describe('hadoopApp.cluster module', function() {
// Given
beforeEach(module('hadoopApp.cluster.cluster-directive'));
var compile, mockBackend, rootScope;
beforeEach(inject(function($compile, $httpBackend, $rootScope) {
compile = $compile;
mockBackend = $httpBackend;
rootScope = $rootScope;
}));
var dummyCluster;
beforeEach(function() {
dummyCluster = {
id:"189",
name:"hadoop-189",
exitStatus:0
};
mockBackend.expectGET('components/cluster/cluster.html').respond(
'<div><div ng-bind="clusterData.name"></div></div>');
});
it('should toggle cluster details info', function() {
var scope = rootScope.$new();
scope.clusterData = dummyCluster;
// When
var element = compile('<cluster' +
' cluster-data="clusterData" />')(scope);
scope.$digest();
mockBackend.flush();
// Then
var compiledElementScope = element.isolateScope();
expect(compiledElementScope.showDetails).toEqual(false);
// When
console.log(compiledElementScope);
compiledElementScope.toggleDetails();
// Then
expect(compiledElementScope.showDetails).toEqual(true);
});
afterEach(function() {
mockBackend.verifyNoOutstandingExpectation();
mockBackend.verifyNoOutstandingRequest();
});
});
The test fails when calling compiledElementScope.toggleDetails() because the toggleDetails function is undefined:
TypeError: undefined is not a function
Printing the content of the isolated scope inside compiledElementScope I can see that in fact the function is not included in the object.
So, it looks like the toggleDetails function is not included in the isolated scope but I don't know why.

If you use the compile function within a directive, the link function is ignored. You should return the function within the compile method:
compile: function (tElement, tAttrs) {
if (!tAttrs.showDetails) {
tAttrs.showDetails = 'false';
}
return {
post: function (scope, element, attrs) {
console.log('Test');
scope.toggleDetails = function () {
console.log('Test');
scope.showDetails = !scope.showDetails;
};
}
};
}
Also, in order to make the test work, you should add:
scope.showDetails = false;
And the binding to the directive (because you require two values):
var element = compile('<cluster' +
' cluster-data="clusterData" show-details="showDetails" />')(scope);
Jsfiddle: http://jsfiddle.net/phu7sboz/

Related

AngularJS - $compile a Directive with an Object as an Attribute Parameter

When I use $compile to create and bind a directive, how can I also add a variable as an attribute? The variable is an object.
var data = {
name: 'Fred'
};
var dirCode = '<my-directive data-record="data"></my-directive>';
var el = $compile(dirCode)($scope);
$element.append(el);
And myDirective would be expecting:
...
scope: {
record: '='
},
...
I have tried doing
`var dirCode = '<my-directive data-record="' + data + '"></my-directive>';`
instead too.
It is quite easy, just create new scope and set data property on it.
angular.module('app', []);
angular
.module('app')
.directive('myDirective', function () {
return {
restrict: 'E',
template: 'record = {{record}}',
scope: {
record: '='
},
link: function (scope) {
console.log(scope.record);
}
};
});
angular
.module('app')
.directive('example', function ($compile) {
return {
restrict: 'E',
link: function (scope, element) {
var data = {
name: 'Fred'
};
var newScope = scope.$new(true);
newScope.data = data;
var dirCode = '<my-directive data-record="data"></my-directive>';
var el = $compile(dirCode)(newScope);
element.append(el);
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.js"></script>
<div ng-app="app">
<example></example>
</div>

How to access the scope of the directive in Jasmine Test

I cant get the scope of my directive (another option is that I get the scope but my function is not there). I am using Jasmine over Karma.
My directive:
angular.module('taskApp').directive('nkAlertDir', ['$http', function ($http) {
return {
template: "<span>{{msg}}</span>",
replace: true,
link: function (scope, elem, attrs) {
scope.values = {
canvas: canvasSize,
radius: size,
center: canvasSize / 2
};
$http.get('someUrl')
.suncces(function (data) {
scope.msg = data;
})
.error(function (err) {
//there is always be error because this url does nor exist
//but this is not the point in this exercise
});
scope.returnNumbers = function () {
return [2, 2];
}
}
}
}]);
My test:
describe('Unit: Directives', function () {
var element;
var scope;
beforeEach(module('taskApp'));
beforeEach(inject(function ($compile, $rootScope) {
scope = $rootScope;
element = angular.element('<div nk-alert-dir></div>');
scope.size = 100;
$compile(element)(scope);
scope.$digest();
}));
it('should bind an alert message of "task added!"', function () {
var dirScope = element.isolateScope();
console.log(dirScope);
});
});
Somehow I always get the dirScope is undefined
I have tried this:
replaced the digest() in $apply()
replaced the $rootScope in $rootScope.$new()
You don't have an isolated scope in your directive, so element.isolateScope() is going to return undefined.
Try just accessing the scope: element.scope()
If you want an isolate scope on your directive, then you have to set the scope property of your directive definition object to a JS object and add the properties there. Then you'd be able to use element.isolateScope to get access to the isolate scope.
return {
template: "<span>{{msg}}</span>",
replace: true,
scope : {}, // Any properties you want to pass in to the directive on scope would get defined here
link: function (scope, elem, attrs) {
scope.values = {
canvas: canvasSize,
radius: size,
center: canvasSize / 2
};
$http.get('someUrl')
.suncces(function (data) {
scope.msg = data;
})
.error(function (err) {
//there is always be error because this url does nor exist
//but this is not the point in this exercise
});
scope.returnNumbers = function () {
return [2, 2];
}
}
}

How to write unit test for file upload in directive with angularjs

I have a directive as following, the function of it is to read a file:
myapp.directive('onReadFile', function ($parse) {
return {
restrict: 'A',
scope: false,
link: function(scope, element, attrs) {
var fn = $parse(attrs.onReadFile);
element.on('change', function(onChangeEvent) {
var reader = new FileReader();
reader.onload = function(onLoadEvent) {
scope.$apply(function() {
fn(scope, {$fileContent:onLoadEvent.target.result});
});
};
reader.readAsText((onChangeEvent.srcElement || onChangeEvent.target).files[0]);
});
}
};
});
Html to load the file is as following:
<input id="fileInput" name="fileInput" type="file" accept='.csv' on-read-file="showContent($fileContent)" />
I try to write unit test to see whether it can parse the content of file successfully, I wrote a little bit and then get stuck.
describe.only('On Read File Directive Test', function () {
var el, scope, $rootScope, $compile, $parse;
beforeEach(module('somemoudle'));
beforeEach(function () {
inject([
'$compile',
'$rootScope',
'$parse',
function (_$compile, _$rootScope, _$parse) {
$compile = _$compile;
$rootScope = _$rootScope;
scope = $rootScope.$new();
$parse = _$parse;
}
]);
});
function loadDirective() {
var files = '<div></div>';
el = $compile(files)(scope);
$rootScope.$digest();
scope = el.isolateScope();
}
it('should load', function () {
loadDirective();
expect(el.length).to.equal(1);
});
});

Unable to bind my fake array to a scope variable in my test

I'm unable to bind my fake array to a scope variable in my directive test.
My test:
describe('Directive: report - section', function () {
// load the directive's module
beforeEach(module('ReportApp'));
beforeEach(module('Templates')); // The external template file referenced by templateUrl
var element, scope;
beforeEach(inject(function ($rootScope) {
scope = $rootScope.$new();
}));
it('should have 1 section available', inject(function ($compile) {
var testSections = [
{
id: 'Example01',
visible: true,
img: 'image1.jpg'
},
{
id: 'Example02',
visible: false,
img: 'image2.jpg'
}
];
scope.sections = testSections;
element = angular.element('<section></section>');
element = $compile(element)(scope);
scope.$digest();
expect(element.find('li').length).toEqual(1);
}));
});
My directive:
angular.module('ReportApp')
.directive('section', function (report, reportStatus) {
return {
templateUrl: 'src/report/views/parts/section.html',
restrict: 'E',
controller: function( $scope, $element, $attrs){
var sections = report.getDatabase().sections;
$scope.sections = sections;
reportStatus.setActiveSection(sections[0]);
},
link: function postLink(scope, element, attrs) {
}
};
});
My test result:
Chrome 36.0.1985 (Mac OS X 10.9.2) Directive: report - section should have 1 section available FAILED
Expected 4 to equal 1.
Error: Expected 4 to equal 1.
at null.<anonymous> (/Users/user/MyAPPs/temp/report/app/src/report/directives/tests/spec/section.js:77:39)
at Object.invoke (/Users/user/MyAPPs/temp/report/app/vendor/bower_components/angular/angular.js:3678:17)
at workFn (/Users/user/MyAPPs/temp/report/app/vendor/bower_components/angular-mocks/angular-mocks.js:2102:20)
My problem is that the fake sections(testSections) are not being applied. So, this result "Expected 4 to equal 1" is due to the original sections that are being used instead my fake one.
Why this scope does not work ?
scope.sections = testSections;
The reason is your scope.sections = testSections; is replaced by $scope.sections = sections; in your directive code.
In this case, you have to spy your report.getDatabase() to return your testSections
describe('Directive: report - section', function () {
// load the directive's module
beforeEach(module('ReportApp'));
beforeEach(module('Templates')); // The external template file referenced by templateUrl
var element, scope, report;
beforeEach(inject(function ($rootScope,_report_) {
scope = $rootScope.$new();
report = _report_; //inject the report object and store in a variable
}));
it('should have 1 section available', inject(function ($compile) {
var testSections = [
{
id: 'Example01',
visible: true,
img: 'image1.jpg'
},
{
id: 'Example02',
visible: false,
img: 'image2.jpg'
}
];
spyOn(report,"getDatabase").and.returnValue({ sections : testSections });//spy the getDatabase function
//we just need a stub, so we could also write this:
//report.getDatabase = function (){
// return { sections : testSections };
//}
element = angular.element('<section></section>');
element = $compile(element)(scope);
scope.$digest();
expect(element.find('li').length).toEqual(1);
}));
});

angularjs: access controller of directive

I'm unit testing one of my directives. Its basic structure is like this
angular.module('MyApp')
.directive('barFoo', function () {
return {
restrict: 'E',
scope: {},
controller: function ($scope, $element) {
this.checkSomething = function() { .... }
},
link: function(scope, element) { .... }
}
});
In my unit test I want to test the function 'checkSomething', so I tried
var element = $compile('<barFoo></barFoo>')(scope);
var controller = element.controller()
...
However, controller is undefined. Is it possible to access the controller of the directive ?
the glue is your scope so you can do
controller: function ($scope, $element) {
this.checkSomething = function() { .... }
$scope.controller = this;
},
but i think it would be best practice to attach every function to the scope like
controller: function ($scope, $element) {
$scope.checkSomething = function() { .... }
},
and then its
var element = $compile('<barFoo></barFoo>')(scope);
var checksomthingResult = scope.checkSomething ()

Categories