How would I test this ng-click method? - javascript

I have the following method bound to an ng-click on my scope by using the controllerAs syntax:
vm.setCap = function(cap) {
playersService.setCap(
{playerId: playerId},
{limit: cap}
).$promise.then(function() {
alert('success');
}, function() {
alert('error');
});
};
All I want to test is that when I call vm.setCap, playersService.setCap is called once with the correct parameters.
This is what I have tried, but I keep getting the error further down:
it('should call a setCap method', function() {
spyOn(playersService, 'setCap');
expect(PlayersController.setCap).toBeDefined();
PlayersController.setCap(1000);
expect(playersService.setCap).toHaveBeenCalled();
});
error:
TypeError: 'undefined' is not an object (evaluating 'playersService.setCap({playerId:playerId},{limit:cap}).$promise')

In your controller attach playersService to the vm and then do this instead:
spyOn(PlayersController.playerService, 'setCap');
So your controller will look like this:
vm.playersService = playersService;
vm.setCap = function (cap) {
playersService.setCap({
playerId : playerId
}, {
limit : cap
}).$promise.then(function () {
alert('success');
}, function () {
alert('error');
});
};
The test does not know about your playerService variable as it is local to your controller, by attaching it to the vm you can run your test.

Related

Why this code got error "Hi is not a function"

When user clicks on .step, the jquery will execute the hello function which works fine. The hello function will call hi function => I got error here: Hi is not a function
jQuery(document).ready(function( $ ) {
const form = {
init: function() {
$('.step').on('click', this.hello)
},
hello: function() {
console.log('Index: ', $(this).index()); // I can get the right index
this.hi(); // ERROR: Hi is not a function!
},
hi: function() {
console.log('HI')
},
}
form.init()
})
When I put the code like this, it works fine
jQuery(document).ready(function( $ ) {
const form = {
init: function() {
$('.step').on('click', this.hi) // Look here
},
hi: function() {
console.log('HI')
},
}
form.init()
})
Can you guys explain why?
Hi is not a function
Can any one explain why?
In this code
init: function() {
$('.step').on('click', this.hello)
},
hello: function() {
console.log('Index: ', $(this).index()); // I can get the right index
this.hi(); // ERROR: Hi is not a function!
},
you setup form.hello as an event handler for the click event. Within event handlers, this is the element that was clicked, as in:
$("button").click(function() { console.log(this.id); }
the button will (given this code) not have a .hi method.
How can we avoid the binding using Es6 or arrow function?
If you don't need to know which button was clicked, you can use an arrow function when defining the event handler:
init: function() {
$('.step').on('click', () => this.hello())
},
Example: https://jsfiddle.net/mcuh5awt/2/
Ideally, you'll want to access both the button and the form class, this is possible in two ways, the first is to change how you define your class, but keeping with how you've done it, we can add a new property self to store a reference to its ...well... self...
jQuery(document).ready(function( $ ) {
const form = {
self : {},
init: function() {
self = this;
$('.step').on('click', self.hello)
},
hello: function() {
console.log('Index: ', $(this).index());
self.hi();
},
hi: function() {
console.log('HI')
},
}
form.init()
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<button class='step' type='button'>
Step 1
</button>
<button class='step' type='button'>
Step 2
</button>
</div>
As this becomes a public property of your class, you may prefer a different name in this case eg form to match the class name.

Javascript cannot call THIS in code

I'm am using module js syntax and I have some code like this:
var myModule = {
settings: {
myage: 25
},
init: function() {
//init code here
},
someFunction1: function(param1) {
//function code here
},
someFunction2: function() {
myModule.someFunction1(myparam);
}
}
The above will work fine but if I tried:
someFunction2: function() {
this.someFunction1(myparam);
}
It will not find the function.
Can't I use this.someFunction1... ?
this is who called the function.
myModule.someFunction2() will have this=myModule
someFunction2 = myModule.someFunction2; someFunction2() will have this=window
(you didn't show how you're calling it though)

How to test this with Jasmine test (Behaviour Driven Development)?

I've just developed this JavaScript/Backbone module as a part of a web page I am developing. I would like to create a Jasmine test for it, but I am brand new to Jasmine, therefore I am not sure what should I be testing in this class. What should be the "skeleton" of the test? In order to avoid redundancy in tests, what parts will you test?
editdestinationview.js:
define([
'common/jqueryex',
'backbone',
'marionette',
'handlebars',
'text!education/eet/templates/editdestination.hb',
'text!common/templates/validationerror.hb',
'lang/languageinclude',
'common/i18nhelper'
], function ($, Backbone, Marionette, Handlebars, templateSource, errorTemplateSource, i18n) {
'use strict';
var errorTemplate = Handlebars.compile(errorTemplateSource),
EditDestinationView = Marionette.ItemView.extend({
initialize: function (options) {
this._destinationTypes = options.destinationTypes;
},
onRender: function () {
this.stickit();
this._bindValidation();
},
_bindValidation: function () {
Backbone.Validation.bind(this, {
valid: this._validAttributeCallback,
invalid: this._invalidAttributeCallback,
forceUpdate: true
});
},
_validAttributeCallback: function (view, attr) {
view.$('#error-message-' + attr).remove();
},
_invalidAttributeCallback: function (view, attr, error) {
view.$('#error-message-' + attr).remove();
view.$('#destinationTypes').parent('div').append(errorTemplate({
attr: attr,
error: error
}));
},
template: Handlebars.compile(templateSource),
ui: {
saveAnchor: '#ed_eetSaveDestinationAnchor',
deleteAnchor: '#ed_eetDeleteDestinationIcon'
},
triggers: {
'click #ui.saveAnchor': 'click:saveDestination',
'click #ui.deleteAnchor': 'click:deleteDestination'
},
bindings: {
'select#destinationTypes': {
observe: 'destinationTypeId',
selectOptions: {
collection: function () {
return this._destinationTypes;
},
labelPath: 'description',
valuePath: 'destinationTypeId',
defaultOption: {label: i18n.EDUCATION_EET_SELECT_INTENDED_DESTINATION, value: null}
}
}
}
});
return EditDestinationView;
});
Thanks everyone!
UPDATE:
After thinking a lot about it, I think that I should try these aspects:
-Triggers: Check if they can be clicked.
-"_validAttributeCallback" and "_invalidAttributeCallback": Check if they behave accordingly to the code.
-Template: Spy on it to check if it is performing it's mission. (Optional test)
So, the test skeleton will be:
define([
'education/eet/views/editdestinationview'
], function (EditDestinationView) {
describe('description...', function () {
beforeEach(function () {
//EditDestinationView.triggers
});
describe('blablabla', function () {
beforeEach(function () {
// ...
});
it('blablabla', function () {
// blablabla
});
});
});
});
Any help on how to test this please?
One common pattern is to use two describe statements, one for the class and one for the method being tested, and then an it statement for each thing you want to test about that method. The rspec people have a convention (which I use in my JS tests) of using a '#' on the method describe for an instance method, and a "." for a describe of a static method.
Now, if you adopt all of the above, and you want to test (for instance) that your View's click-handling method triggers a certain event on the View's Model, it would look something like this:
define([
'education/eet/views/editdestinationview'
], function (EditDestinationView) {
describe('EditDestinationView', function () {
var view;
beforeEach(function () {
// do setup work that applies to all EditDestinationView tests
view = new EditDestinationView({model: new Backbone.Model()});
});
describe('#handleClick', function () {
beforeEach(function () {
// do setup work that applies only to handleClick tests
});
it('triggers a foo event', function () {
var wasTriggered;
view.model.on('foo', function() {
wasTriggered = true;
});
view.handleClick();
expect(wasTriggered).toBe(true);
});
});
});
});
P.S. Instead of creating a fake "foo" handler like I did, most people use a mocking library like Sinon. Using that library our "it" statement could instead be:
it('triggers a foo event', function () {
var triggerStub = sinon.stub(view.model, 'trigger');
view.handleClick();
expect(triggerStub.calledOnce).toBe(true);
expect(triggerStub.args[0][0]).toBe('foo');
//NOTE: args[0][0] == first arg of first call
});

Extjs MVC nested views events and async function

I'm developing an extJS 4.2 MVC app.
I've this context menu view object defined:
Ext.define('XApp.view.message.inbox.CtxMenu', {
extend : 'Ext.menu.Menu',
alias : 'widget.inboxctxmenu',
items : [ {
itemId : 'buttonSetFlags',
text : 'ToRead'
}]
});
this context menu is builded when i'm creating this grid (and other my extended grids):
Ext.define('XApp.view.message.inbox.Grid', {
extend: 'Ext.grid.Panel',
alias: 'widget.inboxgrid',
store: 'message.Inbox',
initComponent : function(){
this.menu = this.buildMenu();
this.callParent(arguments);
this.on({
scope : this,
itemcontextmenu : this.onItemContextMenu
});
},
onItemContextMenu : function(grid, record, item, index, e, eOpts) {
console.log('onItemContextMenu');
e.stopEvent();
this.menu.showAt(e.getXY());
},
onDestroy : function(){
console.log('destroy grid and menu');
this.menu.destroy();
this.callParent(arguments);
},
buildMenu : function(){
return Ext.widget('inboxctxmenu');
}
});
this code is extracted from Sencha blog on point 2 to avoid memory leak on nested object.
Now in my controller i want to listen
Ext.define('XApp.controller.Inbox', {
extend : 'Ext.app.Controller',
init : function(application) {
this.control({
"inboxctxmenu #buttonSetFlags" : {
click : this.onFlagsSetter
}
});
},
onFlagsSetter : function(button, e, eOpts) {
this.getController('Message').SetMessageStatus(1,"ToRead",this.getStore('message.Inbox').load);
}
});
in this controller, i call another controller function and i want to reload 'message.Inbox' store:
Ext.define('XApp.controller.Message', {
extend : 'Ext.app.Controller',
SetMessageStatus: function(id,statusToSet,callback) {
Ext.Ajax.request({
url : XApp.util.Util.serverUrl + 'api/message/SetMessageStatus/' + id + "/" + statusToSet,
method : "GET",
failure : function(response, options) {
console.log('Failure' + response);
},
success : function(conn, response, options, eOpts) {
console.log('Success');
if (callback && typeof(callback) === "function") {
console.log('Calling callback');
callback();
}
}
});
}
});
in this function, i've an async call with AJAX, and i want to reload store of InboxController after ajax response, but with this notation, console throw an error.
There are best practices to call async function and launch a callback after success or failure?
Another question is:
what is the best pratices with ExtJs MVC to listen on nested view event (in example my ctxmenu is nested in a grid)? i read for fireevent and bubbleevent but i'm confused...Please bring me back to the right way...
JFYI the context menu in your example is not nested in the grid. Menus are floating objects, and as such they are outside of the usual component hierarchy.
The error you're having is because you're not passing a callback to SetMessageStatus, you're passing the result of expression this.getStore('message.Inbox').load - which evaluates to a function, but without a scope bound to it it's useless. Read this question's answers for more explanations on what the function scope is.
With a naïve head-on approach, the fix would look thusly:
onFlagsSetter: function(button, e) {
var me = this; // Important for the closure below
this.getController('Message').SetMessageStatus(1, 'ToRead', function() {
// Note that within the callback function, `this` is an object
// entirely different from `this` in the above line, so we call
// `getStore` on the captured scope instead.
me.getStore('message.Inbox').load();
});
}
However, a much better approach is to use Controller events:
Ext.define('XApp.controller.Inbox', {
extend: 'Ext.app.Controller',
init: function() {
this.listen({
component: {
'inboxctxmenu #buttonSetFlags': {
click: this.onFlagsSetter
}
},
controller: {
'*': {
statusmessage: this.onStatusMessage
}
}
});
},
onFlagsSetter: function(button) {
this.fireEvent('setstatus', 1, 'ToRead');
},
onStatusMessage: function(success, response) {
if (success) {
this.getStore('message.Inbox').load();
}
}
});
Ext.define('Xapp.controller.Message', {
extend: 'Ext.app.Controller',
init: function() {
this.listen({
controller: {
'*': {
setstatus: this.setMessageStatus
}
}
});
},
setMessageStatus: function(id, statusToSet) {
Ext.Ajax.request({
url: ...,
method: 'GET',
failure: function(response) {
this.fireEvent('statusmessage', false, response);
},
success: function(connection, response) {
this.fireEvent('statusmessage', true, response);
},
// We are setting the above callbacks' scope to `this` here,
// so they would be bound to the Controller instance
scope: this
});
}
});
As you can see, by using Controller events we have decoupled Inbox controller from the Message controller; they are no longer calling each other's methods directly but are passing information instead. The code is much cleaner, and concerns are properly separated.

Calling function immedietaly at AngularJS

I am totally newbie to AngularJs.
I have pretty good example of ngClick which is calling a Facebook function.
I would like to change it and programmatically to call registerWithFacebook function after my controller will be ready. What is the way for it?
Here is the sample: http://jsfiddle.net/IgorMinar/Hxbqd/5/
angular.module('HomePageModule', []).service('facebookConnect', function($rootScope) {
this.askFacebookForAuthentication = function(fail, success) {
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', function() { $rootScope.$apply(success) });
} else {
$rootScope.$apply(function() {
fail('User cancelled login or did not fully authorize.')
});
}
});
}
});
function ConnectCtrl(facebookConnect, $scope, $resource) {
$scope.user = {}
$scope.error = null;
$scope.registerWithFacebook = function() {
facebookConnect.askFacebookForAuthentication(
function(reason) { // fail
$scope.error = reason;
}, function(user) { // success
$scope.user = user
});
}
// Tried this but no success
// $scope.registerWithFacebook();
}
ConnectCtrl.$inject = ['facebookConnect', '$scope', '$resource'];
Thanks
If you want to load the FB library asynchronously, you need to let angular know after it initializes. Here is one way to do it:
http://plnkr.co/edit/YO4duxL1Mh3dwYEEpprV?p=preview

Categories