How to qunit test function tied to event being fired - qunit

js file
window.onload = function() {
document.getElementById('example').addEventListener('mousedown', myFunction, false);
};
function myFunction(e) {
x = e.clientX-parseInt(document.getElementById('example').offsetLeft);
window.addEventListener('mousemove', anotherFunction, true);
}
Trying to figure out how to test with QUnit. the page doesn't use jQuery, just straight JavaScsript. As you can see, onload includes an eventlistener being added to an element, and when that mouse event is fired, the myFunction function is called. qunit code please.

In my mind this should be two tests:
One: you want to test to make sure the event listener is attached correctly. For this you can just override myFunction (or use a spy like from sinon). Then trigger a mouse click (see MDN for triggering events).
Then for the next function, do the same only attach the spy or override anotherFunction, and then just call your method directly.
** EDIT FOR INFO ABOUT SPIES **
If you've got sinon loaded, and myFunction is available in scope, you make myFunction a spy.
From the docs for spies and event triggering
test('callback', function(){
sinon.spy(myFunction);
// or
var oldFunc = myFunction;
myFunction = function(){
ok(true, true, 'function called');
}
var event = new MouseEvent('down', {
'view': window,
'bubbles': true,
'cancelable': true
});
// trigger event
document.getElementById('example').dispatchEvent(event);
ok(myFunction.called, true, 'function called');
myFunction.restore();
//or
myFunction = oldFunc;
});

Related

Jasmine spy doesn't work on a function defined in a constructor

I want to spy on a function used as a click handler. The function is defined within the constructor of a closure.
var viewModel = function(){
var viewModel = function(){
var _this = this;
_this.internalClickHandler = function(){
console.log('I handled a click');
}
}
return viewModel;
}();
var testViewModel = new viewModel();
var theSpy = spyOn(testViewModel, 'internalClickHandler');
Even though Jasmine is happy that 'internalClickHandler' exists and creates the spy, it never gets called. In fact the original function (internalClickHandler) gets call instead.
I've created examples in codepen.io that show the problem. The failing test is the one trying to spy on a function in the constructor.
My event handler needs to be in the constructor as it needs access to instance of the object and I do want to test that the correct handler has been fired not just that the click event was triggered.
Any help would be greatly received. Thanks
You will not be able to execute that test because of the following reasons:
Your clickHandler actually gets reassigned to a different variable
on the DOM Element onClick, see this line
document.getElementById('testLink').addEventListener('click',
_this.internalClickHandler);
When a click trigger gets invoked, it actually executes the function
onClick and NOT internalClickHandler, though they are in actuality(code wise)
the same but they are being referenced by two different variables
i.e onClick & internalClickHandler.
You are better off trying something like this.
it('should spy on a event binding defined in constructor', function() {
var testViewModel = new viewModel();
var tl = document.getElementById('testLink');
var theSpy = spyOn(t1, 'onclick');
//$('#testLink').trigger('click');
var event = new MouseEvent('click', {
'view': window,
'bubbles': true,
'cancelable': true
});
tl.dispatchEvent(event);
expect(theSpy).toHaveBeenCalled();
tearDown();
});
Hope this helps.
I resolved this by using as the handler an anonymous function wrapping a reference to internalClickHandler. This way the original function still gets called and I'm able to spy on it.
var viewModel = function(){
var viewModel = function(){
var _this = this;
_this.internalClickHandler = function(){
console.log('I handled a click');
}
}
return viewModel;
}();
var theViewModel = new viewModel();
var theSpy = spyOn(testViewModel, 'internalClickHandler');
$('#testLink').on('click',
function(){
theViewModel.internalClickHandler(); //<-- The anonymous function calls internalClickHandler
});

Jasmine wait until HTML rendering after a function is done

Lets say we have a form with a jquery validation. Now we create a simple Jasmine spec and want to test if the error message is visible if we submit an empty form.
My first step is to trigger the submit form event after that jquery validate will work and show the error messages. The time window until the error message will be displayed is really small (2ms) but too big for a Jasmine test. Currently with a setTimeout() it works but I think that is a bad way :(
I am new to Jasmine and I think there must be a better way? Something with spy?
Dummy spec for example:
describe("Lorem Impsum: ", function () {
it("Form validation shows error messages.", function () {
$("#MyForm").submit();
expect($(".error")).toBeVisible();
});
});
Using setTimeout or setInterval for polling may not be a bad way. If the page is complicated, periodic checks are simpler, than using MutationObserver. (This is a unit test; not an application.) If you choose the polling interval short enough, the test will not be so slow. For example:
describe("Lorem Impsum: ", function () {
it("Form validation shows error messages.", function (done) {
$("#MyForm").submit();
waitForElement(".error", function () {
expect($(".error")).toBeVisible();
done();
});
});
});
function waitForElement(selector, callback) {
var interval;
if ($(selector).length) {
callback();
} else {
interval = setInterval(function () {
if ($(selector).length) {
clearInterval(interval);
callback();
}
}, 10);
}
}
You will need to declare and call the done callback, so that Jasmine gets notified, when the test spec has finished.

Backbone: Attaching event to this.$el and re rendering causes multiple events to be bound

I need to attach an event to the main view element, this.$el. In this case its an 'LI'. Then I need to re render this view sometimes. The problem is if i re render it, it attaches any events in the onRender method that is attached to this.$el each time its rendered. So if i call this.render() 3 times the handler gets attached 3 times. However, if i attach the event to a childNode of this.$el, this does not happen and the events seem to be automatically undelegated and added back on each render. The problem is I NEED to use the main this.$el element in this case.
Is this a bug? Shouldn't this.$el function like the childNodes? Should I not be attaching things to this.$el?
inside the view:
onRender: function(){
this.$el.on('click', function(){
// do something
});
If you're able to use the view's event hash, you could do the following:
var Bookmark = Backbone.View.extend({
events: {
'click': function() {
console.log('bound once')
}
}
...});
If for some reason that's not an option, you could explicitly remove any existing event listeners for this event in the render method, which will prevent the listener from being attached multiple times:
var Bookmark = Backbone.View.extend({
...
render: function(x) {
this.$el.off('click.render-click');
this.$el.html(this.template());
this.$el.on('click.render-click', function () {
console.log('only ever bound once');
});
return this;
}
});

How to use events and event handlers inside a jquery plugin?

I'm triyng to build a simple animation jQuery-plugin. The main idea is to take an element and manipulate it in some way repeatedly in a fixed intervall which would be the fps of the animation.
I wanted to accomplish this through events. Instead of using loops like for() or while() I want to repeat certain actions through triggering events. The idea behind this: I eventualy want to be able to call multiple actions on certain events, like starting a second animation when the first is done, or even starting it when one animation-sequence is on a certain frame.
Now I tried the following (very simplified version of the plugin):
(function($) {
$.fn.animation = function() {
obj = this;
pause = 1000 / 12; //-> 12fps
function setup(o) {
o.doSomething().trigger('allSetUp');
}
function doStep(o, dt) {
o.doSomething().delay(dt).trigger('stepDone');
}
function sequenceFinished(o) {
o.trigger('startOver');
}
function checkProgress(o) {
o.on({
'allSetup': function(event) {
console.log(event); //check event
doStep(o, pause);
},
'stepDone': function(event) {
console.log(event); //check event
doStep(o, pause);
},
'startOver': function(event) {
console.log(event); //check event
resetAll(o);
}
});
}
function resetAll(o) {
/*<-
reset stuff here
->*/
//then start over again
setup(o);
}
return this.each(function() {
setup(obj);
checkProgress(obj);
});
};
})(jQuery);
Then i call the animation like this:
$(document).ready(function() {
$('#object').animation();
});
And then – nothing happens. No events get fired. My question: why? Is it not possible to use events like this inside of a jQuery plugin? Do I have to trigger them 'manualy' in $(document).ready() (what I would not prefer, because it would be a completely different thing – controling the animation from outside the plugin. Instead I would like to use the events inside the plugin to have a certain level of 'self-control' inside the plugin).
I feel like I'm missing some fundamental thing about custom events (note: I'm still quite new to this) and how to use them...
Thx for any help.
SOLUTION:
The event handling and triggering actually works, I just had to call the checkProgress function first:
Instead of
return this.each(function() {
setup(obj);
checkProgress(obj);
});
I had to do this:
return this.each(function() {
checkProgress(obj);
setup(obj);
});
So the event listening function has to be called before any event gets triggered, what of course makes perfect sense...
You need set event on your DOM model for instance:
$('#foo').bind('custom', function(event, param1, param2) {
alert('My trigger')
});
$('#foo').on('click', function(){ $(this).trigger('custom');});​
You DOM element should know when he should fire your trigger.
Please note that in your plugin you don't call any internal function - ONLY DECLARATION

Backbone.js - event trigger not work after rendering other views

There's a addPost function in my router. I don't want to re-create the postAddView every time the function is invoked:
addPost: function () {
var that = this;
if (!this.postAddView) {
this.postAddView = new PostAddView({
model: new Post()
});
this.postAddView.on('back', function () {
that.navigate('#/post/list', { trigger: true });
});
}
this.elms['page-content'].html(this.postAddView.render().el);
}
Here's the PostAddView:
PostAddView = backbone.View.extend({
events: {
'click #post-add-back': 'back'
}
, back: function (e) {
e.preventDefault();
this.trigger('back');
}
});
The first time the postAddView is rendered, the event trigger works well. However, after rendering other views to page-content and render postAddView back, the event trigger won't be trigger anymore. The following version of addPost works well, though.
addPost: function () {
var that = this, view;
view = new PostAddView({
model: new Post()
});
this.elms['page-content'].html(view.render().el);
view.on('back', function () {
delete view;
that.navigate('#/post/list', { trigger: true });
});
}
Somewhere you are calling jQuery's remove and that
In addition to the elements themselves, all bound events and jQuery data associated with the elements are removed.
so the delegate call that Backbone uses to bind events to your postAddView.el will be lost. Then, when you re-add your postAddView.el, there are is no delegate attached anymore and no events are triggered. Note that Backbone.View's standard remove method calls jQuery's remove; a few other things in jQuery, just as empty will do similar things to event handlers. So the actual function call that is killing your delegate could be hidden deep inside something else.
You could try calling delegateEvents manually:
this.elms['page-content'].html(this.postAddView.render().el);
this.postAddView.delegateEvents();
or better, just throw the view away and create a new one every time you need it. Your view objects should be pretty light weight so creating new ones should be cheap and a lot less hassle than trying to keep track of the existing views by hand.
If you really want to reuse the current DOM and View you do not need to set again and again the element as you are doing, everything that you call .html() you are destroying the DOM of the View and generating again and losing events. Also I prefer always to add the "el" in the DOM before render the View. I will have your function in this way:
addPost: function () {
if (!this.postAddView) {
this.postAddView = new PostAddView({
model: new Post()
});
this.postAddView.on('back', this.onBack);
this.elms['page-content'].html(this.postAddView.el);
}
this.postAddView.render();
},
onBack : function () {
this.navigate('#/post/list', { trigger: true });
}
I'm not fan of the use of local variables to refer to "this". If all of your Views uses _.bindAll(this) in the initialize method you could bind your events to your view and could use this(check how I transformed onBack).
With my code there is not a need to manually call this.delegateEvents()

Resources