Need another set of eyes to debug angular unit tests - angularjs

I am getting a series of error messages on my unit tests and Its been a few hours since I have made any progress. If anyone has the time to take a look it would be much appreciated. This is part of a larger controller so if anyone wants to see more code please let me know but I think this should cover it.
The errors I am getting are:
1)
Test 'CampaginLinksController getCampaignLinks():is loading' failed
Expected undefined to be true.
2)
Test 'CampaginLinksController getCampaignLinks():has a webSiteId that is not 0 and a buyRequestId that is not "" ' failed
Expected spy mockCampaginLinkSrv.getWebSiteBuyRequestLinks to have been called with [ 54, 8, 432, 200, { nextRowKey : 'fdsf2', nextPartitionKey : '5432gee' } ] but it was never called.
CampaginLinksCtrlSpec.js
describe('CampaignLinksController', function () {
//make module avalible to tests
beforeEach(module('pb.campaignLinks.controllers'));
beforeEach(module('ui.router'));
beforeEach(module('ui.bootstrap'));
var $controller;
var mockPromiseObj;
var length = 200;
var continuationToken = {
nextRowKey: 'fdsf2',
nextPartitionKey: '5432gee'
};
var mockCampaignLinkService = {
//all but delete must return a promiseObj
getCampaignLinks: jasmine.createSpy('mockCampaignLinkService.getCampaignLinks').and.returnValue(mockPromiseObj),
getCampaignBuyRequestLinks: jasmine.createSpy('mockCampaignLinkService.getCampaignBuyRequestLinks').and.returnValue(mockPromiseObj),
getWebSiteBuyRequestLinks: jasmine.createSpy('mockCampaignLinkService.getWebSiteBuyRequestLinks').and.returnValue(mockPromiseObj),
deleteCampaignLink: jasmine.createSpy('mockCampaignLinkService.deleteCampaignLinks').and.returnValue(mockPromiseObj)
};
var mockGlobal = {
activeOrganizationId: 54
};
var mockCampaignLinks = true;
var mockCurrentView;
beforeEach(inject(function (_$controller_) {
$controller = _$controller_;
}));
beforeEach(inject(function ($rootScope, $q) {
scope = $rootScope.$new();
var mockPromiseObj = {
hello: "world",
then: function (orgId, entityId) {
var defer = $q.defer();
defer.resolve(this.account);
return defer.promise;
}
}
controller = $controller('CampaignLinksController',
{
$scope: scope,
//$stateParams: mockStateParams,
global: mockGlobal,
campaignLinks: mockCampaignLinks,
campaignLinkService: mockCampaignLinkService,
currentVeiw: mockCurrentView,
promiseObj: mockPromiseObj
});
}));
describe('getCampaignLinks()', function () {
beforeEach(function () {
mockCurrentView = {
campaignId: 32,
webSiteId: 8
};
});
//describing loading
it('is loading', function () {
scope.getCampaignLinks(mockCurrentView, length, continuationToken);
expect(mockCampaignLinks.loading).toBe(true);
});
it('is not loading', function () {
mockCampaignLinks = false;
scope.getCampaignLinks(mockCurrentView, length, continuationToken);
expect(mockCampaignLinks.loading).toEqual(undefined);
});
it('has a webSiteId that is not 0 and a buyRequestId that is not "" ', function () {
mockCurrentView.buyRequestId = 432;
scope.getCampaignLinks(mockCurrentView, length, continuationToken);
expect(mockCampaignLinkService.getWebSiteBuyRequestLinks).toHaveBeenCalledWith(mockGlobal.activeOrganizationId, mockCurrentView.webSiteId, mockCurrentView.buyRequestId, length, continuationToken);
// must check that what is returned is a promise
expect(scope.promiseObj).toEqual(mockPromiseObj);
});
});
CampaginLinksController.js
$scope.getCampaignLinks = function (currentView, length, continuationToken) {
// When loading list items, display loading image
if ($scope.campaignLinks) $scope.campaignLinks.loading = true;
var promiseObj = null;
if (currentView.campaignId && currentView.campaignId !== 0 && !currentView.buyRequestId) {
promiseObj = campaignLinkService.getCampaignLinks(global.activeOrganizationId, currentView.campaignId, length, continuationToken)
} else if (currentView.campaignId && currentView.buyRequestId && currentView.campaignId !== 0 && currentView.buyRequestId !== '') {
promiseObj = campaignLinkService.getCampaignBuyRequestLinks(global.activeOrganizationId, currentView.campaignId, currentView.buyRequestId, length, continuationToken);
} else if (currentView.webSiteId && currentView.buyRequestId && currentView.webSiteId !== 0 && currentView.buyRequestId !== '') {
promiseObj = campaignLinkService.getWebSiteBuyRequestLinks(global.activeOrganizationId, currentView.webSiteId, currentView.buyRequestId, length, continuationToken);
}
if (promiseObj) {
promiseObj.then(function (data) {
// If there are already some campaign links being displayed, add newly loaded list to the end
if ($scope.campaignLinks) {
$scope.campaignLinks.continuationToken = data.continuationToken;
$scope.campaignLinks.total += data.total;
$.each(data.items, function (index) {
$scope.campaignLinks.items.push(data.items[index]);
});
} else {
// Otherwise add loaded list to scope
$scope.campaignLinks = data;
}
// When done loading, hide loading image
$scope.campaignLinks.loading = false;
});
}
};

First test: When you initialize mockCampaignLinks, you should give it an object, not true. Also, you probably should initialize it in beforeEach since you mutate it in the tests.
Second test: getCampaignBuyRequestLinks is the method being called.

Related

AngularJS check object/array is empty

I've read many questions and answers, noone has helped me. I've this function:
var model = {};
var mediaReproductionApp = angular.module("mediaReproductionApp",["ui.event",'ngAnimate']);
mediaReproductionApp.run(function ($http) {
$http.get("movimenti_per_totem.json").success(function (data) {
model.items = data;
});
});
mediaReproductionApp.controller("MediaReproductionCtrl", function($scope, $http, $timeout) {
$scope.item = model;
$scope.playVideo = function(media) {
return media ? "../gallery/video/" + media : null;
}
$scope.reproductionCodeIsEmpty = function() {
return Object.keys($scope.item).length == 0;
}
$scope.endVideo = function() {
$timeout(function() {
$http.get("php/delete_record.php").success(function () {
$http.get("movimenti_per_totem.json").success(function (data) {
$scope.item.items = data;
});
});
if($scope.reproductionCodeIsEmpty()) {
prelevaDati('../json/52.json', 'spot_creator', 'sc1', modello_SC, {});
$scope.checkMediaData();
}
},1800);
}
$scope.checkMediaData = function() {
$http.get("movimenti_per_totem.json").success(function (data) {
$scope.item.items = data;
});
if($scope.reproductionCodeIsEmpty()) {
$timeout(function(){$scope.checkMediaData();},2000);
}
}
$scope.checkMediaData();
});
This is my JSON file when it is not empty:
[ {"media":"zafferano_VP8.webm"}, {"media":"amaretti_VP8.webm"}, {"media":"passata_VP8.webm"}]
It never return true when it is empty. I've tried also:
$scope.reproductionCodeIsEmpty = function() {
return $scope.item.length == 0;
}
$scope.reproductionCodeIsEmpty = function() {
return $scope.item == {};
}
$scope.reproductionCodeIsEmpty = function() {
return angular.isUndefined($scope.item) || $scope.item === null;
}
$scope.reproductionCodeIsEmpty = function() {
return angular.isUndefined($scope.item.items) || $scope.item.items === null;
}
Nothing works... can u tell me why?
Thank you!
After you added to your question:
You define model as: model.items = data;
So, you empty model is: model = { items: [] }.
That's why it isn't empty. You need to test for model.items being empty.
If you need a tested way to tell that the object is empty, I'd recommend lodash.isEmpty(). You can use it for "any Array-like values such as arguments objects, arrays, buffers, strings, or jQuery-like collections".
https://lodash.com/docs/4.15.0#isEmpty
Since I don't know what your model is, this would cover the most possible data types.
_.isEmpty(null);
// => true
_.isEmpty(true);
// => true
_.isEmpty(1);
// => true
_.isEmpty([1, 2, 3]);
// => false
_.isEmpty({ 'a': 1 });
// => false
If you want to check if object/array is empty, I use:
angular.equals({}, yourObject);
angular.equals([], yourArray);

Delay loading data in Angular JS

I have code like this
(function (app) {
app.controller('productListController', productListController)
productListController.$inject = ['$scope', 'apiService', 'notificationService', '$ngBootbox', '$filter'];
function productListController($scope, apiService, notificationService, $ngBootbox, $filter) {
$scope.products = [];
$scope.page = 0;
$scope.pagesCount = 0;
$scope.getProducts = getProducts;
$scope.keyword = '';
$scope.search = search;
$scope.deleteProduct = deleteProduct;
$scope.selectAll = selectAll;
$scope.deleteMultiple = deleteMultiple;
function deleteMultiple() {
var listId = [];
$.each($scope.selected, function (i, item) {
listId.push(item.ID);
});
var config = {
params: {
checkedProducts: JSON.stringify(listId)
}
}
apiService.del('/api/product/deletemulti', config, function (result) {
notificationService.displaySuccess('Deleted successfully ' + result.data + 'record(s).');
search();
}, function (error) {
notificationService.displayError('Can not delete product.');
});
}
$scope.isAll = false;
function selectAll() {
if ($scope.isAll === false) {
angular.forEach($scope.products, function (item) {
item.checked = true;
});
$scope.isAll = true;
} else {
angular.forEach($scope.products, function (item) {
item.checked = false;
});
$scope.isAll = false;
}
}
$scope.$watch("products", function (n, o) {
var checked = $filter("filter")(n, { checked: true });
if (checked.length) {
$scope.selected = checked;
$('#btnDelete').removeAttr('disabled');
} else {
$('#btnDelete').attr('disabled', 'disabled');
}
}, true);
function deleteProduct(id) {
$ngBootbox.confirm('Are you sure to detele?').then(function () {
var config = {
params: {
id: id
}
}
apiService.del('/api/product/delete', config, function () {
notificationService.displaySuccess('The product hase been deleted successfully!');
search();
}, function () {
notificationService.displayError('Can not delete product');
})
});
}
function search() {
getProducts();
}
function getProducts(page) {
page = page || 0;
var config = {
params: {
keyword: $scope.keyword,
page: page,
pageSize: 20
}
}
apiService.get('/api/product/getall', config, function (result) {
if (result.data.TotalCount == 0) {
notificationService.displayWarning('Can not find any record.');
}
$scope.products = result.data.Items;
$scope.page = result.data.Page;
$scope.pagesCount = result.data.TotalPages;
$scope.totalCount = result.data.TotalCount;
}, function () {
console.log('Load product failed.');
});
}
$scope.getProducts();
}
})(angular.module('THTCMS.products'));
So my problem is when i loading data the application take me some time to load data.
I need load data as soon as
Is the any solution for this?
Since you are loading data via api call, there will be a delay. To handle this delay, you should display a loading screen. Once the data is loaded, the loading screen gets hidden and your main screen is visible. You can achieve this using $http interceptors.
See : Showing Spinner GIF during $http request in angular
The api-call is almost certainly causing the delay. Data may be received slowly via the api-call so you could display any sort of loading text/image to notify the use that the data is being loaded.
If u want the data ready at the time when controller inits, u can add a resolve param and pass the api call as a $promise in the route configuration for this route.

object doesn't support property or method 'slice' jasmine

this is my controller code
$scope.loadMajorObjects = function () {
var appId = $scope.currentAppId;
var type = $scope.majorObject.type;
if (_.isEmpty(appId))
return;
var cacheKey = { key: cacheKeys.objectTypeList($scope.asCacheOptions({ ObjectType: type })) };
return dataFactory.get("/MajorObject/All?applicationId=" + appId + "&type=" + type, { cache: cacheKey })
.then(function (result) {
$scope.majorObjects = result.data.data;
$scope.majorObjectsList = $scope.majorObjects.slice(0, $scope.itemsPerPage);
$scope.totalItems = $scope.majorObjects.length;
$scope.isLoad = true;
});
};
and this is test case and in this i am calling $scope.loadMajorObject
describe("Testing MajorObjectsCtrl", function () {
//checking dataFactory.get called only once
it("should call dataFactory.get and called only once", function () {
scope.currentAppId = mockApplicationId;
scope.applications = applicationsMockData;
scope.itemsPerPage = 10;
$controller('AppSettingCtrl',
{
$scope: scope,
dataFactory: mainmockdataFactory
});
expect(mockdataFactory.get.calls.count()).toBe(0);
scope.currentAppId = mockApplicationId;
scope.majorObjects.slice();
scope.loadMajorObjects();
scope.$digest();
expect(mockdataFactory.get).toHaveBeenCalled();
expect(mockdataFactory.get.calls.count()).toBe(1);
});
when i call this function it is throwing and exception as object doesn't support property or method 'slice', how to rectify this?

$watch not updating scope variable

First I want to say that I am a complete beginner in AngularJS and just attempting to understand the basic concepts. I have a background in Java and PHP.
I am building a part of a website. Right now the angular app only consists of opening and closing 2 drop down menus registrationDropDown and loginDropDown. I want them to work so that only one can be open at a time ie. if I open one, and the other is already open, the older one is forced to close.
I have a service to manage the variables that determine whether the drop downs should be open or closed and 2 controllers, one for login and one for registration, both include $watch for the respective variables.
THE PROBLEM
I want the app to work so that only one of the drop downs can be open at one time.
JSFIDDLE: http://jsfiddle.net/F5p6m/3/
angular.module("ftApp", [])
.factory('dropDownService', function () {
var loginDropDownStatus = false;
var registrationDropDownStatus = false;
return {
getLoginDropDownStatus: function () {
return loginDropDownStatus;
},
showLoginDropDown: function () {
console.log("showing login drop down");
registrationDropDownStatus = false;
loginDropDownStatus = true;
console.log("loginDropDownStatus" + loginDropDownStatus + "registrationDropDownStatus" + registrationDropDownStatus);
},
hideLoginDropDown: function () {
console.log("hiding login drop down");
loginDropDownStatus = false;
console.log("loginDropDownStatus" + loginDropDownStatus);
},
getRegistrationDropDownStatus: function () {
return registrationDropDownStatus;
},
showRegistrationDropDown: function () {
console.log("showing registration drop down");
registrationDropDownStatus = true;
loginDropDownStatus = false;
console.log("registrationDropDownStatus" + registrationDropDownStatus);
},
hideRegistrationDropDown: function () {
console.log("hiding registration drop down");
registrationDropDownStatus = false;
console.log("registrationDropDownStatus" + registrationDropDownStatus);
}
};
}) .controller("LoginDropDownController", function ($scope, dropDownService) {
$scope.loginDropDownStatus = dropDownService.getLoginDropDownStatus();
$scope.$watchCollection('loginDropDownStatus', function(newValue, oldValue) {
console.log("watcher is working");
console.log("value is " + newValue + oldValue);
console.log("LOGIN new value is " + newValue);
$scope.loginDropDownStatus = newValue;
});
$scope.toggleDropDown = function () {
if ( $scope.loginDropDownStatus == false ) {
dropDownService.showLoginDropDown();
dropDownService.hideRegistrationDropDown();
$scope.loginDropDownStatus = true;
} else if ( $scope.loginDropDownStatus == true ) {
dropDownService.hideLoginDropDown();
$scope.loginDropDownStatus = false;
}
};
})
.controller("RegistrationDropDownController", function ($scope, dropDownService) {
$scope.registrationDropDownStatus = dropDownService.getRegistrationDropDownStatus();
$scope.$watch('registrationDropDownStatus', function(newValue, oldValue) {
console.log("watcher is working");
console.log("value is " + newValue + oldValue);
console.log("new value is " + newValue);
$scope.registrationDropDownStatus = newValue;
});
$scope.toggleDropDown = function () {
if ( $scope.registrationDropDownStatus == false ) {
dropDownService.showRegistrationDropDown();
dropDownService.hideLoginDropDown();
$scope.registrationDropDownStatus = true;
} else if ( $scope.registrationDropDownStatus == true ) {
dropDownService.hideRegistrationDropDown();
$scope.registrationDropDownStatus = false;
}
};
})
Edit:
Here is probably the shortest option:
angular.module("ftApp", [])
.controller("ctrl", function ($scope) {
$scope.toggle = function(menu){
$scope.active = $scope.active === menu ? null : menu;
}
})
FIDDLE
One controller, no service.
Previous Answer:
I think you have quite a bit of code to get something very simple done. Here is my solution:
angular.module("ftApp", [])
.service('dropDownService', function () {
this.active = null;
this.toggle = function(menu){
this.active = this.active === menu ? null : menu;
}
})
.controller("LoginDropDownController", function ($scope, dropDownService) {
$scope.status = dropDownService;
$scope.toggleDropDown = function () {
dropDownService.toggle("login");
};
})
.controller("RegistrationDropDownController", function ($scope, dropDownService) {
$scope.status = dropDownService;
$scope.toggleDropDown = function () {
dropDownService.toggle("reg");
};
})
FIDDLE
You can make it even shorter by only using one controller. You wouldn't even need the service then.
You are overcomplicating things. All you need your service to hold is a property indicating which dorpdown should be active.
Then you can change that property's value from the controller and check the value in the view to determine if a dropdown should be shown or hidden.
Something like this:
<!-- In the VIEW -->
<li ng-controller="XyzController">
<a ng-click="toggleDropdown()">Xyz</a>
<div ng-show="isActive()">Dropdown</div>
</li>
/* In the SERVICE */
.factory('DropdownService', function () {
return {
activeDropDown: null
};
})
/* In the CONTROLLER */
controller("XyzDropdownController", function ($scope, DropdownService) {
var dropdownName = 'xyz';
var dds = DropdownService;
$scope.isActive = function () {
return dropdownName === dds.activeDropdown;
};
$scope.toggleDropdown = function () {
dds.activeDropdown = (dds.activeDropdown === dropdownName) ?
null :
dropdownName;
};
})
See, also, this short demo.
Based on what exactly you are doing, there might be other approaches possible/preferrable:
E.g. you could use just on controller to control all dropdowns
or you could use two instances of the same controller to control each dropdown.
See my updated fiddle. I simplified the code and removed the service. Because you just used two variables to control visibility, you don't need a service nor $watch. You need to keep variables in the $rootScope, otherwise changes in a controller is not visible to another controller due to isolated scopes.
angular.module("ftApp", [])
.controller("LoginDropDownController", function ($scope, $rootScope) {
$rootScope.loginDropDownStatus = false;
$scope.toggleDropDown = function () {
if ($rootScope.loginDropDownStatus == false) {
$rootScope.registrationDropDownStatus = false;
$rootScope.loginDropDownStatus = true;
} else if ($rootScope.loginDropDownStatus == true) {
$rootScope.loginDropDownStatus = false;
}
};
}).controller("RegistrationDropDownController", function ($scope, $rootScope) {
$rootScope.registrationDropDownStatus = false;
$scope.toggleDropDown = function () {
if ($rootScope.registrationDropDownStatus === false) {
$rootScope.loginDropDownStatus = false;
$rootScope.registrationDropDownStatus = true;
} else if ($scope.registrationDropDownStatus === true) {
$rootScope.registrationDropDownStatus = false;
}
};
})
This code can be simplified further. I'll leave that to you.

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