I am new to AngularJS and Jasmine. Given the following controller, how do I test whether the allPanelsRetrieved() function is called when the $scope.getPanels is triggered?
angular.
module('panelList').
controller('PanelListController', ['Panel', 'PanelSelection', '$scope', '$location', '$uibModal', '$rootScope',
function PanelListController(PanelSelection, $scope, $location, $uibModal, $rootScope) {
$scope.maxAbv = 2;
$scope.minAbv = 12;
this.allPanelsRetrieved = (index, before, filterParams) => {
.....
};
$scope.getPanels = () => {
const filterParams = {};
filterParams.abv_lt = $scope.minAbv;
filterParams.abv_gt = $scope.maxAbv;
$scope.currentPagePanels = this.allPanelsRetrieved (1,[], filterParams);
};
}]).
component('panelList', {
templateUrl: '/components/panel-list/panel-list.template.html',
controller:'PanelListController',
});
Assuming you want allPanelsRetrived to be called, then simply use a boolean.
var bool = false
this.allPanelsRetrieved = (index, before, filterParams) => {
.....
bool=true;
};
$scope.getPanels = () => {
if (bool) {
const filterParams = {};
filterParams.abv_lt = $scope.minAbv;
filterParams.abv_gt = $scope.maxAbv;
$scope.currentPagePanels = this.allPanelsRetrieved (1,[], filterParams);
} else {
// allPanelsRetrieved was not called
}
};
I can see that allPanelsRetrieved seems to be a private(local) method and used inside that controller.
You need not test private(local) methods execution.
If you still want to check if the method is triggered or not you can use jasmine's toHaveBeenCalled() method
execept(myMethod).toHaveBeenCalled();
passes when method is called.
Related
I've a small controller written in angularJS.
1st function is actually calling a 2nd one to perform a calculation and return it back.
I want to mock the 2nd function in my testing, so that it returns value I've provided in mock instead of calling the function.
ABCService.js
var app = angular.module('mathModule', []);
app.controller('mathService', ['$scope', function($scope){
$scope.first = 0;
$scope.second = 0;
$scope.addTwoNumbers = function(x, y) {
return x + y;
};
$scope.callAddFunction = function() {
return $scope.addTwoNumbers(10, 20);
}
}]);
ABCServic.test.js
require('./mathService.js');
describe('Math service', function() {
beforeEach(
angular.mock.module('mathModule')
);
var $controller;
beforeEach(inject(function(_$controller_) {
$controller = _$controller_;
}));
describe('Test using 2 numbers', function() {
var $scope, controller;
beforeEach(function() {
$scope = {};
controller = $controller('mathService', { $scope: $scope });
});
it("Nested function", function() {
var total = $scope.callAddFunction();
expect(total).toEqual(31);
});
});
});
Here I want to mock addTwoNumbers() so that instead of calling we get value we've provided during testing.
Something like, Mock(addTwoNumbers(x,y)) = 0, so now callAddFunction will return 0 instead of 30, which it should be returning if not mocked.
I have an angular directive that shows payment history. By default, it shows the last 6 payments using the vm.numberOfPaymentsToDisplay variable. If you click view more, it adds 10. Now, when a user clicks on another section, there's a listener in the link function that is supposed to reset the number to 6, however vm is undefined.
Here's the code:
angular.module('nui.settings2.account')
.directive('paymentHistory', function(){
function PaymentHistoryController(paymentHistoryService, $filter, $window, $translate){
const filter = $filter('formatCurrency');
var vm = this;
vm.payments = paymentHistoryService.get();
vm.numberOfPaymentsToDisplay = 6;
vm.getLastPayment = getLastPayment;
vm.viewMorePayments = viewMorePayments;
vm.title = $translate.instant('NUI.SETTINGS.PAYMENT_HISTORY');
function getLastPayment(){
const lastTransaction = paymentHistoryService.getLastPayment();
return lastPaymentInfo = "amount (date)";
}
function viewMorePayments(){
vm.numberOfPaymentsToDisplay = vm.numberOfPaymentsToDisplay + 10;
return true;
}
}
function link(scope, element, attrs, [expander, paymentHistory]){
const containerEl = element.children();
expander.registerContentContainer(containerEl);
scope.$on(expander.COLLAPSE_EVENT, () => vm.numberOfPaymentsToDisplay = 6);
scope.$on("$destroy", () => scope.$emit(expander.CONTAINER_DEREGISTER_EVENT));
paymentHistory.cancel = () => expander.collapse();
}
return {
restrict: 'E',
templateUrl: 'nui/settings2/account/billing/payment-history.directive.html',
controller: PaymentHistoryController,
link: link,
require: ['^^settingExpander', 'paymentHistory'],
controllerAs: 'PaymentHistoryCtrl',
bindToController: true
};
});
How can I set vm.numberOfPaymentsToDisplay = 6 in the link function even though this knowledge is only known by the controller?
You can add a method to your PaymentHistoryController like setNumberOfPaymentsToDisplay as you inject your PaymentHistoryController into your link function you can call the method like this:
paymentHistory.setNumberOfPaymentsToDisplay(6);
Controller code:
function PaymentHistoryController(paymentHistoryService, $filter, $window, $translate){
const filter = $filter('formatCurrency');
var vm = this;
vm.payments = paymentHistoryService.get();
vm.numberOfPaymentsToDisplay = 6;
vm.getLastPayment = getLastPayment;
vm.viewMorePayments = viewMorePayments;
vm.setNumberOfPaymentsToDisplay = setNumberOfPaymentsToDisplay;
vm.title = $translate.instant('NUI.SETTINGS.PAYMENT_HISTORY');
function getLastPayment(){
const lastTransaction = paymentHistoryService.getLastPayment();
return lastPaymentInfo = "amount (date)";
}
function viewMorePayments(){
vm.numberOfPaymentsToDisplay = vm.numberOfPaymentsToDisplay + 10;
return true;
}
function setNumberOfPaymentsToDisplay(amount) {
vm.numberOfPaymentsToDisplay = amount;
}
}
link code:
function link(scope, element, attrs, [expander, paymentHistory]){
const containerEl = element.children();
expander.registerContentContainer(containerEl);
scope.$on(expander.COLLAPSE_EVENT, () => paymentHistory.setNumberOfPaymentsToDisplay(6));
scope.$on("$destroy", () => scope.$emit(expander.CONTAINER_DEREGISTER_EVENT));
paymentHistory.cancel = () => expander.collapse();
}
Actually you have more options.
the general approach to share data between components is to use a service that is a singleton , a single instance cached and injected by angular everytime you use it inside your component. another valid solution is to emit events.
Service:
.service('MyService', function(){
var data;
this.setData = function(newData){
data = newData;
}
this.getData = function(){
return data;
}
})
Events:
$rootScope.$broadcast('my.evt', data); //down in the scope chain, visible to any scope
$rootScope.$emit('my.evt', data); //up in the scope chain since is the rootscope only visible to rootScope
$scope.$emit //up in the scope chain
$scope.$broacast //down in the scope chain
to listen for events:
$rootScope.$on('my.evt', function(evt, data){ //do something }
or
$scope.$on('my.evt', function(evt, data){ //do something }
However in this case you're using the link function to modify your business logic and this is not the the conventional approach, usually the link is used only to handle dom events and to modify the dom. so my personal advice is to refactor your code and put the whole business logic inside the controller
I'm really new to AngularJS so this might be really obvious!
I've got a service which does some basic checks and then returns the information. I call this service in a controller and in a function.
When I call it in the controller it works fine without an error.
When I call it in the function I get
angular.js:9101 ReferenceError: systemService is not defined
Call to service in Controller that works:
myApp.controller('continueController', ["$scope", "$rootScope", 'systemService', function ($scope, $rootScope, systemService) {
$scope.ContinueAngularMethod = function () {
$rootScope.field = 'payment';
$scope.field = $rootScope.field;
$scope.notApp = '1';
console.log("I don't error in here");
$scope.ss = systemService.checkDropDownValue($scope.payment.type, $scope.notApp);
$rootScope.$apply();
}
}]);
Call to service in function that doesn't work:
function MyCtrl($scope) {
$scope.changeme = function () {
console.log("Drop down changes ideally do something here....");
//Call to service
$scope.ss = systemService.checkDropDownValue($scope.payment.type, $scope.notApp);
console.log($scope.ss.field);
console.log($scope.ss.notAppJ);
}
This is my code in full:
<script type='text/javascript'>
//Angular stuff
var myApp = angular.module('myApp', []);
//Set up my service
myApp.service('systemService', function () {
this.info = {}; //Declaring the object
this.checkDropDownValue = function (type, notAppJ) {
if (type != 'PayPal') {
console.log("I am not PayPal");
field = 'other'
notApp = '0';
}
else if (type == 'PayPal' && notApp == '1') {
console.log("i am in the else if - functionality later");
}
else {
console.log("i am in the else - functionality later");
}
this.info.field = type;
this.info.number = notApp;
return this.info;
};
});
myApp.controller('continueController', ["$scope", "$rootScope", 'systemService', function ($scope, $rootScope, systemService) {
$scope.ContinueAngularMethod = function () {
$rootScope.field = 'payment';
$scope.field = $rootScope.field;
$scope.notApp = '1';
console.log("I don't error in here");
$scope.ss = systemService.checkDropDownValue($scope.payment.type, $scope.notApp);
$rootScope.$apply();
}
}]);
function MyCtrl($scope) {
$scope.changeme = function () {
console.log("Drop down changes ideally do something here....");
//Call to service
$scope.ss = systemService.checkDropDownValue($scope.payment.type, $scope.notApp);
console.log($scope.ss.field);
console.log($scope.ss.notAppJ);
}
$scope.myFunct = function (keyEvent) {
if (keyEvent.which === 13) {
//They hit the enter key so ignore this
keyEvent.preventDefault();
}
//Becauase a new child scope is generated you can't use $scope as that refers to the parent . But this refers to the child.
var rPromise = findAll(this.softwareText);
}
}
</script>
I tried these without any luck:
angular Uncaught ReferenceError: Service is not defined
AngularJS - ReferenceError: $ is not defined
Initialize $scope variables for multiple controllers - AngularJS
Your use of defining the MyCtrl is deprecated, you need angular to inject the systemService depedency in order to use it.
Try defining the MyCtrl controller the same way you defined continueController, like this:
myApp.controller('MyCtrl', ["$scope", 'systemService', function ($scope, systemService) {
$scope.changeme = function () {
console.log("Drop down changes ideally do something here....");
//Call to service
$scope.ss = systemService.checkDropDownValue($scope.payment.type, $scope.notApp);
console.log($scope.ss.field);
console.log($scope.ss.notAppJ);
}
$scope.myFunct = function (keyEvent) {
if (keyEvent.which === 13) {
//They hit the enter key so ignore this
keyEvent.preventDefault();
}
//Becauase a new child scope is generated you can't use $scope as that refers to the parent . But this refers to the child.
var rPromise = findAll(this.softwareText);
}
}]);
MyCtrl shoud provide dependency to systemService with $inject property like
function MyCtrl($scope, systemService) {
}
MyCtrl.$inject = ['$scope', 'systemService']
i'm new in Ionic and Angular JS.
In trying to update a collection (array) in ng-repeat directive.
That's my code.
VIEW:
<div ng-repeat="e in testList">...</div>
CONTROLLER:
.controller('TestCtrl', function ($scope, $stateParams) {
var param1 = $stateParams.param1;
$scope.testList = TestFactory.getTest(param1);
});
FACTORY
.factory('TestFactory', ['Auth', function (Auth) {
auth = Auth.get();
listObj = {};
auth.on('custom-trigger', function(index, value){
addElement(index, value);
});
function addElement(index, value){
var obj = {}
obj.test=(value);
listObj[index].push(obj);
};
var TestFactory = {
getTest: function (index) {
if (typeof listObj[index] == 'undefined')
listObj[index] = [];
return listObj[index];
}
};
return TestFactory;
}])
The object testList is populated after 'custom-trigger' fire: it works fine because I can see the new value in the log.
This trigger is fired at the begin of application (many time and it works fine every time) and after server notifications (here is the problem!!).
The problem is that the view is not updated. I think is a data binding problem.
You likely need to notify your controller than addElement has been triggered. Try adding another $broadcast and listener:
factory
.factory('TestFactory', ['$rootScope', 'Auth', function ($rootScope, Auth) {
// ...
function addElement(index, value){
var obj = {}
obj.test=(value);
listObj[index].push(obj);
// notify the app that `addElement` has occurred
$rootScope.$broadcast('elementAdded');
};
// ...
}])
controller
.controller('TestCtrl', function ($scope, $stateParams) {
var param1 = $stateParams.param1;
$scope.testList = TestFactory.getTest(param1);
// listen for elementAdded event; re-get testList
$scope.$on('elementAdded', function(){
$scope.testList = TestFactory.getTest(param1);
});
});
I have a service in which values can change from outside Angular:
angularApp.service('WebSocketService', function() {
var serviceAlarms = [];
var iteration = 0;
this.renderMessages = function(alarms, socket) {
if (! angular.equals(serviceAlarms, alarms)) {
serviceAlarms = alarms;
iteration++;
}
};
this.getAlarms = function () {
return serviceAlarms;
};
this.iteration = function () {
return iteration;
};
this.socket = initSocketIO(this);
});
The initSocketIO function makes callbacks to this services renderMessages() function and serviceAlarms variable gets changed on a steady basis.
Now i am trying to watch for changes in this service like so:
controllers.controller('overviewController', ['$scope', 'WebSocketService', function ($scope, WebSocketService) {
$scope.$watch(
function () {
return WebSocketService.iteration();
},
function(newValue, oldValue) {
$scope.alarms = WebSocketService.getAlarms();
},
true
);
}]);
to no avail. The second function provided to $watch never gets executed except on controller initialization.
I have tried with and without true as third parameter.
You should use $rootScope.$watch (not $scope.$watch)
I ended up using the solution below since $watch didn't work as excpected.
I refactored the solution to use $rootScope in combination with:
angularApp.run(['$rootScope', function($rootScope){
$rootScope.socket = {};
$rootScope.socket.alarms = [];
$rootScope.socket.faults = [];
$rootScope.socket.renderErrors = function(faults, socket) {
var faultArray = [];
angular.forEach(faults, function(error) {
error.value ? faultArray.push(error) : null;
});
if (! angular.equals($rootScope.socket.faults, faultArray)) {
$rootScope.socket.faults = faultArray;
$rootScope.apply();
}
};
$rootScope.socket.renderMessages = function(alarms, socket) {
if (! angular.equals($rootScope.socket.alarms, alarms)) {
$rootScope.socket.alarms = alarms;
$rootScope.$apply();
}
};
$rootScope.socket.socket = initSocketIO($rootScope.socket);
}]);
Now i have my socket-updated-model in all scopes to use freely in controllers and views.
Controller example:
$scope.acknowledgeAlarm = function(alarm) {
$scope.socket.socket.emit('acknowledgeAlarm', {
hash:alarm.icon.hash,
id:alarm.id
});
};
View example:
<div ng-repeat="alarm in socket.alarms">
{{alarm.name}} {{alarm.icon.progress}}
</div>