Why can't i access directive's scope in jasmine test? - javascript

Here's my directive:
angular.module('app')
.directive('statusFilter',statusFilter);
function statusFilter() {
return {
restrict: 'E',
replace: true,
templateUrl: 'app/directives/status-filter.html',
scope: {
flags: '='
},
controller: function($scope, $element, $timeout, $document) {
function isChildElement(el) {
return $.contains($element[0], el);
}
function close(event) {
if (!isChildElement(event.target)) {
$scope.$apply(function() {
$scope.isOpen = false;
});
$document.off('mouseup', close);
}
}
function updateFlags(value) {
for (var prop in $scope.flagsClone) {
$scope.flagsClone[prop] = value;
}
}
function pullFlags() {
$scope.flagsClone = $.extend(true, {}, $scope.flags);
}
function pushFlags() {
for (var prop in $scope.flagsClone) {
$scope.flags[prop] = $scope.flagsClone[prop];
}
}
$scope.isOpen = false;
$scope.flagsClone = {};
pullFlags();
$scope.apply = function() {
pushFlags();
$scope.isOpen = false;
};
$scope.selectAll = function() {
updateFlags(true);
};
$scope.selectNone = function() {
updateFlags(false);
};
$scope.open = function() {
if (!$scope.isOpen) {
pullFlags();
$scope.isOpen = true;
$timeout(function() {
$document.on('mouseup', close);
});
}
};
}
};
}
Here's a simple test i wrote for it:
describe('status-filter directive', function() {
beforeEach(module('app'));
var template = '<status-filter flags="filters"></status-filter>';
var scope, element;
beforeEach(inject(function($rootScope, $compile) {
scope = $rootScope.$new();
var ngElement = angular.element(template);
element = $compile(ngElement)(scope);
scope.$digest();
}));
it('Should open when isOpen is true', function() {
scope.open();
scope.$digest();
expect(scope.isOpen).toBe(true);
});
});
I cannot access the directive's scope no matter how i try. Like in the example above, .isolateScope(), element.scope(). With anything i try i get open() is undefined error. What is wrong in my code?

The reason why I couldn't access the scope was that I didn't create the filters variable. So this will work:
describe('status-filter directive', function() {
beforeEach(module('app'));
var template = '<status-filter flags="filters"></status-filter>';
var scope, element;
beforeEach(inject(function($rootScope, $compile) {
scope = $rootScope.$new();
scope.filters = {
filter1:true;
}
var ngElement = angular.element(template);
element = $compile(ngElement)(scope);
scope.$digest();
}));
it('Should open when isOpen is true', function() {
scope.open();
scope.$digest();
expect(scope.isOpen).toBe(true);
});
});

Related

scope method in AngularJS directive is not a function, when unit testing

I have this Mocha test:
'use strict';
///////////////////////////////////
describe('all admin page directives', function () {
let scope, $compile, element, tmp;
beforeEach(module('app'));
beforeEach(module('templates'));
afterEach(function () {
scope.$destroy();
});
describe('category', function () {
beforeEach(inject(function ($injector) {
$compile = $injector.get('$compile');
var $rootScope = $injector.get('$rootScope');
scope = $rootScope.$new();
scope.rightSidebarData = {};
$compile('<create-category right-sidebar-data="rightSidebarData"></create-category>')(scope);
return scope.$digest();
}));
it('should do something', function () {
scope.updateModel(); // <<<<<< ERROR HERE
});
});
});
here is my directive:
/* globals angular */
angular.module('app').directive('createCategory',
['UserService', 'AssignmentService', 'NotificationService', 'USER', 'UserInfoService', 'AlertService', '$window',
function (UserService, AssignmentService, NotificationService, USER, UserInfoService, AlertService, $window) {
return {
scope: {
rightSidebarData: '=',
},
restrict: 'EA',
templateUrl: "pages/admin/views/templates/category/create-category.html",
link: function ($scope, el, attrs) {
$scope.rightSidebarData.setOrReset = function () {
$scope.setOrReset();
};
},
controller: function ($scope, FUNCTIONAL_TEAM_ENUM, CATEGORY_ENUM, CategoryService) {
const rsd = $scope.rsd = $scope.rightSidebarData;
$scope.setOrReset = function () {...};
$scope.updateModel = function () {...};
$scope.saveModel = function () {...};
},
};
}
]);
I am getting this error:
TypeError: scope.updateModel is not a function
Does anyone know what I need to do in my test to fix this?
Also, how do I know if I need to use $rootScope.$new() or if I should be passing the parent controller's scope?
I had to add another preprocessor in my karma config file:
preprocessors: {
'./public/pages/**/views/**/*.html':['ng-html2js'], // this
},
and then using the following, it worked:
'use strict';
describe('all admin page directives', function () {
let scope, el, tmp, isoScope;
beforeEach(module('app'));
beforeEach(module('ngMockE2E'));
beforeEach(module('templates'));
afterEach(function () {
scope.$destroy();
});
describe('category', function () {
beforeEach(inject(function ($injector) {
const $compile = $injector.get('$compile');
const $rootScope = $injector.get('$rootScope');
const $templateCache = $injector.get('$templateCache');
console.log('create category template =>', $templateCache.get('pages/admin/views/templates/category/create-category.html'));
scope = $rootScope.$new();
scope.rightSidebarData = {};
scope.rightSidebarData.model = {};
let element = angular.element(`<create-category right-sidebar-data="rightSidebarData"></create-category>`);
el = $compile(element)(scope);
$rootScope.$digest();
scope.$digest();
el.isolateScope().setOrReset();
}));
it('should do something', function () {
el.isolateScope().updateModel();
});
});
});

AngularJS new value does not arrive in directive

I have the following directive riding a modal. File: login.component.js
(function() {
'use strict';
var app = angular.module('myapp');
app.directive('loginComponent', ['loginService', function(loginService){
return {
templateUrl: 'app/components/login/login.html',
restrict: 'E',
replace: true,
controller: 'homeCrotroller',
link: function ($scope) {
$scope.submit = function() {
$scope.login();
$("#modal-login").modal('hide');
};
$scope.cancel = function() {
$scope.loggingIn = false;
$("#modal-login").modal('hide');
};
$scope.$watch('loggingIn', function() {
if ($scope.loggingIn) {
$("#modal-login").modal('show');
};
});
}
};
}]);
})();
And the next controller. File: home.controller.js
(function() {
'use strict';
var app = angular.module('myapp');
app.controller('homeCrotroller', ['$scope', function($scope){
$scope.loggedIn = false;
$scope.loggingIn = false;
$scope.showLogin = function () {
$scope.loggingIn = true;
};
$scope.logout = function () {
$scope.user = null;
$scope.loggedIn = false;
};
$scope.login = function () {
$scope.loggingIn = false;
$scope.loggedIn = true;
};
}]);
})();
And in my view I call showLogin()
<a class="login" id="btn-login" href="javascript:void(0);" ng-click="showLogin()" title="Entrar">Entrar</a>
This function changes the value of $scope.logging In to true, only this value is not reaching policy. Only reaches the first status (loading screen) that is false

how do we do a unit test a function in an angularJS controller

here is the code:
(function(){
"use strict";
angular.module("dataModule")
.controller("panelController", ["$scope", "$state", "$timeout", "$modal", panelController]);
function panelController($scope, $state, $timeout, $modal){
$scope.property = "panelController";
//how do we do unit test on openCancelWarning.
//i did not find a way to get openCancelWarning function in Jasmine.
function openCancelWarning () {
var cancelModal = $modal.open({
animation: true,
backdrop: "static",
templateUrl: "pages/data/cancel-warning.html",
controller: "cancelWarningController",
size: "sm",
resolve: {
items : function() {
return {
warningTitle : "Are you Sure?",
warningMessage: "There are unsaved changes on this page. are you sure you want to navigate away from this page?Click OK to continue or Cancel to stay on this page"
};
}
}
});
return cancelModal;
}
var resultPromise = openCancelWarning();
var result;
resultPromise.result.then(function(response){
result = response;
});
}
angular.module("dataModule")
.controller("cancelWarningController", ["$scope", "$modalInstance", "items", cancelWarningController]);
function cancelWarningController($scope, $modalInstance, items){
$scope.warningTitle = items.warningTitle;
$scope.warningMessage = items.warningMessage;
$scope.cancel = function() {
$modalInstance.close(false);
};
$scope.ok = function() {
$modalInstance.close(true);
};
}
}());
here is my jasmine unit test code.
describe("Controller: panelController", function () {
beforeEach(module("dataModule"));
var panelController, scope;
var fakeModal = {
result : {
then: function(confirmCallback) {
this.confirmCallback = confirmCallback;
}
},
close: function(confirmResult) {
this.result.confirmCallback(confirmResult);
}
};
beforeEach(inject(function($modal) {
spyOn($modal, "open").andReturn(fakeModal);
}));
beforeEach(inject(function ($controller, $rootScope, _$modal_) {
scope = $rootScope.$new();
panelController = $controller("panelController", {
$scope: scope,
$modal: _$modal_
});
}));
it('test should be true', function () {
var test;
var testResult = panelController.openCancelWarning();
testResult.close(true);
testResult.then(function(response){
test=response;
});
expect(test).toBe(true);
});
});
i wrote above unit test code with the help from Mocking $modal in AngularJS unit tests
i always get below error.
TypeError: 'undefined' is not a function (evaluating 'panelController.openCancelWarning()')
could anyone help this?

AngularJS unit tests: Initialize scope of a directive's controller

I have the following code for a directive using a separated controller with the "controller as" syntax:
'use strict';
angular.module('directives.featuredTable', [])
.controller('FeaturedTableCtrl',
['$scope',
function ($scope){
var controller = this;
controller.activePage = 1;
controller.changePaginationCallback =
controller.changePaginationCallback || function(){};
controller.density = 10;
controller.itemsArray = controller.itemsArray || [];
controller.metadataArray = controller.metadataArray || [];
controller.numberOfItems = controller.numberOfItems || 0;
controller.numberOfPages = 1;
controller.options = controller.options || {
'pagination': false
};
controller.changePaginationDensity = function(){
controller.activePage = 1;
controller.numberOfPages =
computeNumberOfPages(controller.numberOfItems, controller.density);
controller.changePaginationCallback({
'page': controller.activePage,
'perPage': controller.density
});
};
controller.getProperty = function(object, propertyName) {
var parts = propertyName.split('.');
for (var i = 0 ; i < parts.length; i++){
object = object[parts[i]];
}
return object;
};
controller.setActivePage = function(newActivePage){
if(newActivePage !== controller.activePage &&
newActivePage >= 1 && newActivePage <= controller.numberOfPages){
controller.activePage = newActivePage;
controller.changePaginationCallback({
'page': controller.activePage,
'perPage': controller.density
});
}
};
initialize();
$scope.$watch(function () {
return controller.numberOfItems;
}, function () {
controller.numberOfPages =
computeNumberOfPages(controller.numberOfItems, controller.density);
});
function computeNumberOfPages(numberOfItems, density){
var ceilPage = Math.ceil(numberOfItems / density);
return ceilPage !== 0 ? ceilPage : 1;
}
function initialize(){
if(controller.options.pagination){
console.log('paginate');
controller.changePaginationCallback({
'page': controller.activePage,
'perPage': controller.density
});
}
}
}]
)
.directive('featuredTable', [function() {
return {
'restrict': 'E',
'scope': {
'metadataArray': '=',
'itemsArray': '=',
'options': '=',
'numberOfItems': '=',
'changePaginationCallback': '&'
},
'controller': 'FeaturedTableCtrl',
'bindToController': true,
'controllerAs': 'featuredTable',
'templateUrl': 'directives/featuredTable/featuredTable.tpl.html'
};
}]);
You can see at the beginning of the controller that I'm initializing its properties with the attributes passed by the directive or providing default values:
controller.activePage = 1;
controller.changePaginationCallback =
controller.changePaginationCallback || function(){};
controller.density = 10;
controller.itemsArray = controller.itemsArray || [];
controller.metadataArray = controller.metadataArray || [];
controller.numberOfItems = controller.numberOfItems || 0;
controller.numberOfPages = 1;
controller.options = controller.options || {
'pagination': false
};
At the end I'm executing the initialize(); function that will execute the callback according to the options:
function initialize(){
if(controller.options.pagination){
controller.changePaginationCallback({
'page': controller.activePage,
'perPage': controller.density
});
}
}
I'm now trying to unit test this controller (with karma and jasmine) and I need to "simulate" the parameters passed by the directive, I tried the following:
'use strict';
describe('Controller: featured table', function () {
beforeEach(module('directives.featuredTable'));
var scope;
var featuredTable;
var createCtrlFn;
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
createCtrlFn = function(){
featuredTable = $controller('FeaturedTableCtrl', {
'$scope': scope
});
scope.$digest();
};
}));
it('should initialize controller', function () {
createCtrlFn();
expect(featuredTable.activePage).toEqual(1);
expect(featuredTable.changePaginationCallback)
.toEqual(jasmine.any(Function));
expect(featuredTable.density).toEqual(10);
expect(featuredTable.itemsArray).toEqual([]);
expect(featuredTable.metadataArray).toEqual([]);
expect(featuredTable.numberOfPages).toEqual(1);
expect(featuredTable.numberOfItems).toEqual(0);
expect(featuredTable.options).toEqual({
'pagination': false
});
});
it('should initialize controller with pagination', function () {
scope.changePaginationCallback = function(){};
spyOn(scope, 'changePaginationCallback').and.callThrough();
scope.options = {
'pagination': true
};
createCtrlFn();
expect(featuredTable.activePage).toEqual(1);
expect(featuredTable.changePaginationCallback)
.toEqual(jasmine.any(Function));
expect(featuredTable.density).toEqual(10);
expect(featuredTable.itemsArray).toEqual([]);
expect(featuredTable.metadataArray).toEqual([]);
expect(featuredTable.numberOfPages).toEqual(1);
expect(featuredTable.numberOfItems).toEqual(0);
expect(featuredTable.options).toEqual({
'pagination': true
});
expect(featuredTable.changePaginationCallback).toHaveBeenCalledWith({
'page': 1,
'perPage': 10
});
});
});
And got the following error, meaning that scope is not well initialized:
Expected Object({ pagination: false }) to equal Object({ pagination: true })
at test/spec/app/rightPanel/readView/historyTab/historyTab.controller.spec.js:56
Simulating the bindings would be non-trivial - after all, it's hard to really know what compiling and linking a directive does with the data passed to it...unless you just do it yourself!
The angular.js documentation offers a guide on how to compile and link a directive for unit testing - https://docs.angularjs.org/guide/unit-testing#testing-directives. After doing that, you'd just need to get the controller from the resulting element(see the documentation for the controller() method here - https://docs.angularjs.org/api/ng/function/angular.element) and perform your tests. ControllerAs would be irrelevant here - you would be testing the controller directly, instead of manipulating the scope.
Here's an example module:
var app = angular.module('plunker', []);
app.controller('FooCtrl', function($scope) {
var ctrl = this;
ctrl.concatFoo = function () {
return ctrl.foo + ' world'
}
})
app.directive('foo', function () {
return {
scope: {
foo: '#'
},
controller: 'FooCtrl',
controllerAs: 'blah',
bindToController: true,
}
})
And test setup:
describe('Testing a Hello World controller', function() {
ctrl = null;
//you need to indicate your module in a test
beforeEach(module('plunker'));
beforeEach(inject(function($rootScope, $compile) {
var $scope = $rootScope.$new();
var template = '<div foo="hello"></div>'
var element = $compile(template)($scope)
ctrl = element.controller('foo')
}));
it('should produce hello world', function() {
expect(ctrl.concatFoo()).toEqual('hello world')
});
});
(Live demo: http://plnkr.co/edit/xoGv9q2vkmilHKAKCwFJ?p=preview)

Strange behavior passing scope to directive

I have created a directive below:
html:
<div image-upload></div>
directive:
angular.module('app.directives.imageTools', [
"angularFileUpload"
])
.directive('imageUpload', function () {
// Directive used to display a badge.
return {
restrict: 'A',
replace: true,
templateUrl: "/static/html/partials/directives/imageToolsUpload.html",
controller: function ($scope) {
var resetScope = function () {
$scope.imageUpload = {};
$scope.imageUpload.error = false;
$scope.imageUpload['image_file'] = undefined;
$scope.$parent.imageUpload = $scope.imageUpload
};
$scope.onImageSelect = function ($files) {
resetScope();
$scope.imageUpload.image_file = $files[0];
var safe_file_types = ['image/jpeg', 'image/jpg']
if (safe_file_types.indexOf($scope.imageUpload.image_file.type) >= 0) {
$scope.$parent.imageUpload = $scope.imageUpload
}
else {
$scope.imageUpload.error = true
}
};
// Init function.
$scope.init = function () {
resetScope();
};
$scope.init();
}
}
});
This directive works fine and in my controller I access $scope.imageUpload as I required.
Next, I tried to pass into the directive a current image but when I do this $scope.imageUpload is undefined and things get weird...
html:
<div image-upload current="project.thumbnail_small"></div>
This is the updated code that gives the error, note the new current.
angular.module('app.directives.imageTools', [
"angularFileUpload"
])
.directive('imageUpload', function () {
// Directive used to display a badge.
return {
restrict: 'A',
replace: true,
scope: {
current: '='
},
templateUrl: "/static/html/partials/directives/imageToolsUpload.html",
controller: function ($scope) {
var resetScope = function () {
$scope.imageUpload = {};
$scope.imageUpload.error = false;
$scope.imageUpload['image_file'] = undefined;
$scope.$parent.imageUpload = $scope.imageUpload
if ($scope.current != undefined){
$scope.hasCurrentImage = true;
}
else {
$scope.hasCurrentImage = true;
}
};
$scope.onImageSelect = function ($files) {
resetScope();
$scope.imageUpload.image_file = $files[0];
var safe_file_types = ['image/jpeg', 'image/jpg']
if (safe_file_types.indexOf($scope.imageUpload.image_file.type) >= 0) {
$scope.$parent.imageUpload = $scope.imageUpload
}
else {
$scope.imageUpload.error = true
}
};
// Init function.
$scope.init = function () {
resetScope();
};
$scope.init();
}
}
});
What is going on here?
scope: {
current: '='
},
Everything works again but I don't get access to the current value.
Maybe I'm not using scope: { correctly.
in your updated code you use an isolated scope by defining scope: {current: '=' } so the controller in the directive will only see the isolated scope and not the original scope.
you can read more about this here: http://www.ng-newsletter.com/posts/directives.html in the scope section

Categories

Resources