Creating jasmine unit test for angular directive - angularjs

The below directive checks/loads the template with value "pass, fail, required".
The conditions are
if(parent value == required){
1. if the value is true --> $scope.msg = "fail" --> loads the template with {{msg}} value
2. if the value is false --> $scope.msg = "pass" --> loads the template with {{msg}} value
}
In detail,
loading Template: [showerror.html]
<div>{{msg}}</div>
Directive Call:
<div show-error="obj"></div>
(obj contains as obj.error and obj.required )
Directive:
angular.module("dsf").directive('showError', [
function () {
'use strict';
return {
scope: {
obj: '=showError'
},
link: function ($scope) {
$scope.showError = {};
$scope.msg = "";
function setTemplate(filename) {
$scope.showError.template = 'app/path/' + filename + '.html';
}
$scope.$watch(function () {
if ($scope.obj.required === true) {
if ($scope.obj.error === false) {
$scope.msg = "Pass";
} else if ($scope.obj.error === "required") {
$scope.msg = "Required";
} else if ($scope.obj.error === true) {
$scope.msg = "fail";
}
} else {
if ($scope.obj.error === true) {
$scope.msg = "fail";
} else if ($scope.obj.error === false) {
$scope.msg = "Pass";
}
}
setTemplate("showerror");
});
},
template: '<div ng-include="showError.template"></div>'
};
}
]);
As i am new to jasmine test, how can i write the test for this directive? any suggestions?
Ok. I have written the unit test for this directive. What is the wrong now?
describe('showError', function () {
'use strict';
var compile, $scope, element;
beforeEach(module('dsf'));
beforeEach(inject(function ($compile, $rootScope) {
element = angular.element('<div show-error="obj"></div>');
$scope = $rootScope.$new();
compile = function (obj) {
$scope.obj = obj;
$compile(element)($scope);
$scope.$digest();
};
}));
it('updates the element when obj validation changes', function () {
var obj;
obj = {};
$scope = compile(obj);
$scope.apply(function () {
obj.required = true;
obj.error = true;
});
expect($scope.obj.msg).toContain('fail');
$scope.apply(function () {
obj.required = true;
obj.error = false;
});
expect($scope.obj.msg).toContain('Pass');
$scope.apply(function () {
obj.required = true;
obj.error = "required";
});
expect($scope.obj.msg).toContain('Required');
$scope.apply(function () {
obj.required = false;
obj.error = true;
});
expect($scope.obj.msg).toContain('Pass');
$scope.apply(function () {
obj.required = false;
obj.error = false;
});
expect($scope.obj.msg).toContain('fail');
});
});
I am getting error:
undefined is not an object (evaluating $scopr.apply) error

The command you're looking for is $scope.$apply() not $scope.apply().

Related

Directive's scope value is not getting updated when outer scope value changes in angularjs

My html code is as follows.
<div class="panel-heading">
Select areas to be visited by the operator
<multiselect ng-model="selection" options="areanames" show-search="true"></multiselect>
</div>
My application's controller code is as follows. The function getArea is being called when a user inputs some details in the form (not included here).
this.getArea = function(){
$scope.areanames = [];
$http({
method: "GET",
url: "http://xx.xx.xx.xx/abc",
params:{city:$scope.city,circle:$scope.circle}
}).then(function(success){
for (i = 0; i < success.data.length; i++)
$scope.areanames.push(success.data[i].area);
},function(error){
console.log('error ' + JSON.stringify(error));
});
}
The directive multiselect is written as follows.
multiselect.directive('multiselect', ['$filter', '$document', '$log', function ($filter, $document, $log) {
return {
restrict: 'AE',
scope: {
options: '=',
displayProp: '#',
idProp: '#',
searchLimit: '=?',
selectionLimit: '=?',
showSelectAll: '=?',
showUnselectAll: '=?',
showSearch: '=?',
searchFilter: '=?',
disabled: '=?ngDisabled'
},
replace:true,
require: 'ngModel',
templateUrl: 'multiselect.html',
link: function ($scope, $element, $attrs, $ngModelCtrl) {
$scope.selectionLimit = $scope.selectionLimit || 0;
$scope.searchLimit = $scope.searchLimit || 25;
$scope.searchFilter = '';
if (typeof $scope.options !== 'function') {
$scope.resolvedOptions = $scope.options;
}
if (typeof $attrs.disabled != 'undefined') {
$scope.disabled = true;
}
$scope.toggleDropdown = function () {
console.log('toggleDown');
$scope.open = !$scope.open;
};
var closeHandler = function (event) {
console.log('closeHandler');
if (!$element[0].contains(event.target)) {
$scope.$apply(function () {
$scope.open = false;
});
}
};
$document.on('click', closeHandler);
var updateSelectionLists = function () {
console.log('updateSelectionList');
if (!$ngModelCtrl.$viewValue) {
if ($scope.selectedOptions) {
$scope.selectedOptions = [];
}
$scope.unselectedOptions = $scope.resolvedOptions.slice(); // Take a copy
} else {
$scope.selectedOptions = $scope.resolvedOptions.filter(function (el) {
var id = $scope.getId(el);
for (var i = 0; i < $ngModelCtrl.$viewValue.length; i++) {
var selectedId = $scope.getId($ngModelCtrl.$viewValue[i]);
if (id === selectedId) {
return true;
}
}
return false;
});
$scope.unselectedOptions = $scope.resolvedOptions.filter(function (el) {
return $scope.selectedOptions.indexOf(el) < 0;
});
}
};
$ngModelCtrl.$render = function () {
console.log('render called');
updateSelectionLists();
};
$ngModelCtrl.$viewChangeListeners.push(function () {
console.log('viewChangeListener');
updateSelectionLists();
});
$ngModelCtrl.$isEmpty = function (value) {
console.log('isEmpty');
if (value) {
return (value.length === 0);
} else {
return true;
}
};
var watcher = $scope.$watch('selectedOptions', function () {
$ngModelCtrl.$setViewValue(angular.copy($scope.selectedOptions));
}, true);
$scope.$on('$destroy', function () {
console.log('destroy');
$document.off('click', closeHandler);
if (watcher) {
watcher(); // Clean watcher
}
});
$scope.getButtonText = function () {
console.log('getButtonText');
if ($scope.selectedOptions && $scope.selectedOptions.length === 1) {
return $scope.getDisplay($scope.selectedOptions[0]);
}
if ($scope.selectedOptions && $scope.selectedOptions.length > 1) {
var totalSelected;
totalSelected = angular.isDefined($scope.selectedOptions) ? $scope.selectedOptions.length : 0;
if (totalSelected === 0) {
return 'Select';
} else {
return totalSelected + ' ' + 'selected';
}
} else {
return 'Select';
}
};
$scope.selectAll = function () {
console.log('selectAll');
$scope.selectedOptions = $scope.resolvedOptions;
$scope.unselectedOptions = [];
};
$scope.unselectAll = function () {
console.log('unSelectAll');
$scope.selectedOptions = [];
$scope.unselectedOptions = $scope.resolvedOptions;
};
$scope.toggleItem = function (item) {
console.log('toggleItem');
if (typeof $scope.selectedOptions === 'undefined') {
$scope.selectedOptions = [];
}
var selectedIndex = $scope.selectedOptions.indexOf(item);
var currentlySelected = (selectedIndex !== -1);
if (currentlySelected) {
$scope.unselectedOptions.push($scope.selectedOptions[selectedIndex]);
$scope.selectedOptions.splice(selectedIndex, 1);
} else if (!currentlySelected && ($scope.selectionLimit === 0 || $scope.selectedOptions.length < $scope.selectionLimit)) {
var unselectedIndex = $scope.unselectedOptions.indexOf(item);
$scope.unselectedOptions.splice(unselectedIndex, 1);
$scope.selectedOptions.push(item);
}
};
$scope.getId = function (item) {
console.log('getID');
if (angular.isString(item)) {
return item;
} else if (angular.isObject(item)) {
if ($scope.idProp) {
return multiselect.getRecursiveProperty(item, $scope.idProp);
} else {
$log.error('Multiselect: when using objects as model, a idProp value is mandatory.');
return '';
}
} else {
return item;
}
};
$scope.getDisplay = function (item) {
console.log('getDisplay');
if (angular.isString(item)) {
return item;
} else if (angular.isObject(item)) {
if ($scope.displayProp) {
return multiselect.getRecursiveProperty(item, $scope.displayProp);
} else {
$log.error('Multiselect: when using objects as model, a displayProp value is mandatory.');
return '';
}
} else {
return item;
}
};
$scope.isSelected = function (item) {
console.log('isSelected');
if (!$scope.selectedOptions) {
return false;
}
var itemId = $scope.getId(item);
for (var i = 0; i < $scope.selectedOptions.length; i++) {
var selectedElement = $scope.selectedOptions[i];
if ($scope.getId(selectedElement) === itemId) {
return true;
}
}
return false;
};
$scope.updateOptions = function () {
console.log('updateOptions');
if (typeof $scope.options === 'function') {
$scope.options().then(function (resolvedOptions) {
$scope.resolvedOptions = resolvedOptions;
updateSelectionLists();
});
}
};
// This search function is optimized to take into account the search limit.
// Using angular limitTo filter is not efficient for big lists, because it still runs the search for
// all elements, even if the limit is reached
$scope.search = function () {
console.log('search');
var counter = 0;
return function (item) {
if (counter > $scope.searchLimit) {
return false;
}
var displayName = $scope.getDisplay(item);
if (displayName) {
var result = displayName.toLowerCase().indexOf($scope.searchFilter.toLowerCase()) > -1;
if (result) {
counter++;
}
return result;
}
}
};
}
};
}]);
When areanames is getting updated asynchronously its values are not getting displayed in multiselect. The inner scope's options value is becoming undefined though I am using '=' with same attribute name i.e., options in the html code.

How to check whether an object is empty in a span?

I have some objects in my angularjs controller.
var app = angular.module("myApp", []);
app.controller("noteCtrl", function ($scope) {
$scope.draft = {};
$scope.notes = [];
$scope.note = {};
$scope.submit = function() {
$scope.notes.push($scope.note);
$scope.note = {};
};
$scope.save = function() {
if ($scope.button == "Save") {
$scope.draft = angular.copy($scope.note);
} else {
$scope.note = $scope.draft;
}
};
$scope.cancel = function() {
$scope.note = {};
}
});
I need to check whether the draft object is empty, and output the save successfully information.
<span data-ng-hide="draft == {}" style="color:green">Your note has been saved.</span>
I have also tried:
<span data-ng-hide="draft.length == -1" style="color:green">Your note has been saved.</span>
or
<span data-ng-hide="draft == ''" style="color:green">Your note has been saved.</span>
But all of them are failed.
Add function isEmptyObject in your controller:
var app = angular.module("myApp", []);
app.controller("noteCtrl", function ($scope) {
$scope.draft = {};
$scope.note = {};
$scope.notes = [];
$scope.save = function() {
if ($scope.button == "Save") {
$scope.draft = angular.copy($scope.note);
} else {
$scope.note = $scope.draft;
}
};
$scope.cancel = function() {
$scope.note = {};
}
$scope.isEmptyObject = function (obj) {
for (var i in obj) if (obj.hasOwnProperty(i)) return false;
return true;
};
});
Or you can use this function:
$scope.isEmptyObject = function (obj) {
return Object.keys(obj).length === 0;
}
HTML:
<span data-ng-hide="isEmptyObject(draft)" style="color:green">Your note has been saved.</span>
Please initialize null instead of empty object
$scope.draft = null;
And html should be
<span data-ng-hide="!draft" style="color:green">Your note has been saved.</span>
You can chek the condition inline in your span.
HTML:
<span data-ng-hide="Object.keys(draft).length === 0" style="color:green">Your note has been saved.</span>

Decorating a directive by adding a function that will call the directive's controller

I use a directive that is declared like this :
(function (directives) {
var FilterDirective = (function () {
function FilterDirective() {
var directive = {};
directive.restrict = 'A';
directive.scope = true;
directive.controller = elasticui.controllers.FilterController;
directive.link = function (scope, element, attrs, filterCtrl) {
scope.$watch(element.attr('eui-filter') + " | euiCached", function (val) { return scope.filter.filter = val; });
var enabled = false;
var enabledAttr = element.attr('eui-enabled');
if (enabledAttr) {
scope.$watch(enabledAttr, function (val) { return scope.filter.enabled = val; });
enabled = scope.$eval(enabledAttr);
}
scope.filter = {
filter: scope.$eval(element.attr('eui-filter') + " | euiCached"),
enabled: enabled
};
filterCtrl.init();
};
return directive;
}
return FilterDirective;
})();
directives.FilterDirective = FilterDirective;
directives.directives.directive('euiFilter', FilterDirective);
})
The controller of the directive is :
(function (controllers) {
var FilterController = (function () {
function FilterController($scope) {
this.scope = $scope;
}
FilterController.prototype.init = function () {
var _this = this;
if (this.scope.filter.filter) {
var isEnabled = this.scope.filters.contains(this.scope.filter.filter);
if (!isEnabled && this.scope.filter.enabled) {
this.scope.filters.add(this.scope.filter.filter);
isEnabled = true;
}
}
this.scope.filter.enabled = isEnabled;
this.scope.$watch('filter.enabled', function (newVal, oldVal) {
if (newVal !== oldVal) {
_this.updateFilter();
}
});
this.scope.$watch('filter.filter', function (newVal, oldVal) {
if (!elasticui.util.EjsTool.equals(oldVal, newVal)) {
if (oldVal) {
_this.scope.filters.remove(oldVal);
}
_this.updateFilter();
}
});
};
FilterController.prototype.updateFilter = function () {
if (!this.scope.filter.filter) {
return;
}
if (this.scope.filter.enabled) {
this.scope.filters.add(this.scope.filter.filter);
}
else {
this.scope.filters.remove(this.scope.filter.filter);
}
};
FilterController.$inject = ['$scope'];
return FilterController;
})();
controllers.FilterController = FilterController;
})
Actually, the directive has a scope containing a filter object which contains two attributes filter : { enabled : boolean, filter : object} and the directive is used like this :
<label class="btn" ng-model="filter.enabled"
eui-filter="ejs.TermFilter('field','value')" btn-checkbox>
when the button is clicked the filter.enabled is set. My purpose is to add a behavior that will permit to change filter.enabled value via a function external to the directive.
The directive will look like this :
<label class="btn" ng-model="filter.enabled"
eui-filter="ejs.TermFilter('field','value')" eui-enable-fn="fn(somevariable)" btn-checkbox>
where fn will take the somevariable and set it to the filter.enabled.
Thanks in advance,
If you want to enable/disable a filter through the pressure of a button why not declare a filter with the property eui-enabled set to a custom toggling variable?
In other words it would result as:
HTML:
<label class="btn" eui-filter="..." eui-enabled="my_toggling_variable">
<button type=button ng-click="toggleVar()"></button>
JS:
myApp.controller('myCtrl', ['$scope', function($scope) {
$scope.my_toggling_variable = false;
$scope. toggleVar = function(){
$scope.my_toggling_variable = !$scope.my_toggling_variable;
};
}]);
Hope to have understood well the topic.

Splitting a controller in 2 different files. Angular

I am getting a headache trying to do this, I have a very big controller which I need to split, this controller has 177 lines already so I need 2 controllers. Once I try to split it my app breaks down.
Maybe you could help by giving suggestions on how to start and what I have to evaluate first. I am new to Angular and I thought it would be easier.
'use strict';
angular.module('capilleira.clickAndGambleMobile.controllers')
.controller('LinesController', function($scope, $timeout, $state,
$stateParams, $ionicLoading, $rootScope, LinesFactory, BetSlipFactory) {
$scope.picksCount = false;
var validateSanitizeLineParams = function() {
var lineParamsOk = false;
if (validator.isAlphanumeric($stateParams.customerId) &&
validator.isNumeric($stateParams.familyGameId) &&
validator.isNumeric($stateParams.games) &&
validator.isNumeric($stateParams.top) &&
validator.isNumeric($stateParams.sports) &&
validator.isLength($stateParams.leagues.trim(), 1) &&
validator.isAscii($stateParams.leagues.trim()) &&
validator.isLength($stateParams.periods.trim(), 1) &&
validator.isAscii($stateParams.periods.trim())) {
lineParamsOk = true;
_.each($stateParams.periods.split(','), function(periodId) {
if (!validator.isAlpha(periodId)) {
lineParamsOk = false;
}
});
_.each($stateParams.leagues.split(','), function(leagueId) {
if (!validator.isNumeric(leagueId)) {
lineParamsOk = false;
}
});
}
return lineParamsOk;
};
$scope.lineItems = [];
$rootScope.spinnerTitle = 'Loading lines';
$ionicLoading.show({
templateUrl: 'templates/loaders.html',
scope: $rootScope
});
$scope.doRefresh = function() {
if (validateSanitizeLineParams()) {
LinesFactory.getLines($stateParams).then(function(linesPerLeague) {
_.each(linesPerLeague, function(linesPerParent) {
_.each(linesPerParent, function(lines) {
_.each(lines, function(line) {
_.each(line.rows, function(row) {
if (!row.noSpread) {
line.displaySpreadButton = true;
}
if (!row.noTotal) {
line.displayTotalButton = true;
}
if (!row.noMoneyLine) {
line.displayMoneyLineButton = true;
}
});
if (line.displaySpreadButton) {
line.displaySpread = true;
} else if (line.displayTotalButton) {
line.displayTotal = true;
} else if (line.displayMoneyLineButton) {
line.displayMoneyLine = true;
}
});
});
});
$scope.lineItems = linesPerLeague;
if (!$scope.lineItems[0].length) {
$state.go('app.noLines');
}
$ionicLoading.hide();
$scope.addLineSelections();
}, function(err) {
console.log(err);
$rootScope.spinnerTitle = 'Wrong Params';
$ionicLoading.show({
templateUrl: 'templates/loaders.html',
scope: $rootScope
});
$timeout(function() {
$ionicLoading.hide();
$state.go('app.login');
}, 1500);
});
}else {
$rootScope.spinnerTitle = 'Wrong Params';
$ionicLoading.show({
templateUrl: 'templates/loaders.html',
scope: $rootScope
});
$timeout(function() {
$ionicLoading.hide();
$state.go('app.login');
}, 1500);
}
$scope.$broadcast('scroll.refreshComplete');
};
$scope.doRefresh();
$scope.showLine = function(lineType, line) {
switch (lineType) {
case 'spread':
line.displayTotal = false;
line.displayMoneyLine = false;
line.displaySpread = false;
$timeout(function() {
line.displaySpread = true;
}, 50);
break;
case 'total':
line.displaySpread = false;
line.displayMoneyLine = false;
line.displayTotal = false;
$timeout(function() {
line.displayTotal = true;
}, 50);
break;
case 'moneyline':
line.displaySpread = false;
line.displayTotal = false;
line.displayMoneyLine = false;
$timeout(function() {
line.displayMoneyLine = true;
}, 50);
break;
}
};
$scope.addLineToBetSlip = function(line, row, type) {
var spreadSelected = (row.spreadSelected && type === 'spread'),
totalSelected = (row.totalSelected && type === 'total'),
moneyLineSelected = (row.moneyLineSelected && type === 'moneyline');
if (spreadSelected || totalSelected || moneyLineSelected) {
BetSlipFactory.remove(line, row, type);
}else {
BetSlipFactory.add(line, row, type);
}
$scope.picksCount = !$scope.picksCount;
};
$scope.addLineSelections = function() {
BetSlipFactory.getBetSlip().then(function(betSlip) {
var flattenLines = _.flatten($scope.lineItems),
lineFound, row;
_.each(betSlip, function(slip) {
lineFound = _.find(flattenLines, function(line) {
return line.gameId === slip.gameId &&
line.part === slip.part &&
line.lineTypeName === slip.lineTypeName;
});
if (lineFound) {
row = _.find(lineFound.rows, function(row) {
return row.nss === slip.nss;
});
if (row) {
switch (slip.type) {
case 'spread':
row.spreadSelected = true;
break;
case 'total':
row.totalSelected = true;
break;
case 'moneyline':
row.moneyLineSelected = true;
break;
}
}
}
});
});
};
});
This works as expected.
var app = angular.module('plunker', []);
var FatCtrl1 = function($scope){
$scope.stuff1 = this.hello;
};
var FatCtrl2 = function($scope){
$scope.stuff2 = "World";
};
app.controller('FatCtrl', function($scope) {
this.hello = "Hello";
FatCtrl1.apply(this, arguments);
FatCtrl2.apply(this, arguments);
});
Beware of closures. Variables within FatCtrl are not available in the partitions. 'this' is used to share data.

Why is my Factory not getting instantiated/injected?

Why is readingController not able to use the Romanize service? It always says Romanize is undefined inside the function. How can I get the service in scope?
var readingController = function (scope, Romanize){
scope.currentMaterial = scope.sections[scope.sectionNumber].tutorials[scope.tutorialNumber].material;
Romanize;
}
var app = angular.module('Tutorials', ['functions', 'tutorials']).controller('getAnswers', function ($scope, $element) {
$scope.sectionNumber = 0;
$scope.tutorialNumber = 0;
$scope.questionNumber = 0;
$scope.sections = sections;
$scope.loadFromMenu = function (sec, tut, first) {
if (tut === $scope.tutorialNumber && sec === $scope.sectionNumber && !first) {//if clicked on already playing tut
return;
}
if (tut !== undefined && sec !== undefined) {
$scope.tutorialNumber = tut;
$scope.sectionNumber = sec;
}
for (var x in sections) {
sections[x].active = "inactive";
for (var y in sections[x].tutorials){
sections[x].tutorials[y].active = "inactive";
}
}
var section = sections[$scope.sectionNumber];
section.active = "active";
section.tutorials[$scope.tutorialNumber].active = "active";
$scope.questionNumber = 0;
$scope.currentTutorialName = sections[$scope.sectionNumber].tutorials[$scope.tutorialNumber].name;
$scope.$apply();
if ($scope.sectionNumber === 0){
readingController($scope, app.Romanize);
}else if ($scope.sectionNumber === 1){
conjugationController($scope);
}
};
$scope.loadFromMenu(0,0, true);
var conjugationController = function (){
var loadNewVerbs = function (scope) {
scope.currentVerbSet = scope.sections[scope.sectionNumber].tutorials[scope.tutorialNumber].verbs;
if (scope.currentVerbSet === undefined) {
alert("Out of new questions");
return
}
scope.verbs = conjugate(scope.currentVerbSet[scope.questionNumber]);
scope.correct = scope.verbs.conjugations[0].text;
fisherYates(scope.verbs.conjugations);
scope.$apply();
};
loadNewVerbs($scope);
$scope.checkAnswer = function (answer) {
if($scope.sectionNumber === 0 && $scope.tutorialNumber === 0 && $("video")[0].currentTime < 160){
$scope.message = "Not yet!";
$(".message").show(300).delay(900).hide(300);
return;
}
answer.colorReveal = "reveal-color";
if (answer.text === $scope.correct) { //if correct skip to congratulations
$scope.questionNumber++;
setTimeout(function () {
loadNewVerbs($scope);
$scope.$apply();
}, 2000);
} else { //if incorrect skip to try again msg
if ($scope.sectionNumber === 0 && $scope.tutorialNumber === 0) {
start(160.5);
pause(163.8)
}
}
};
};
});
app.factory('Romanize', ['$http', function($http){
return{
get: function(){
$http.get(scope.sections[scope.sectionNumber].romanizeService).success(function(data) {
$scope.romanized = data;
});
}
};
}])
Update: based on comments/discussion below:
For a service to be instantiated, it has to be injected somewhere – not just anywhere – somewhere where Angular accepts injectables. Just inject it into your getAnswers controller – .controller('getAnswers', function ($scope, $element, Romanize) – then pass it to your "controller" function: readingController($scope, Romanize).
Since I don't think readingController is a real Angular controller, you can name the arguments whatever you want, so scope should be fine.
Original attempt at an answer:
Inject $scope not scope into your controller:
var readingController = function ($scope, Romanize){
Then I don't get any errors: Plunker.

Resources