QUnit Async testing stop function isn't working - javascript

I'm having a problem with QUnit stop() basically it doesn't seem to be working at all. Here's an example of something I'm trying to do: http://jsfiddle.net/7hZMM/1/
If you remove the line:
testIframe.remove();
It works fine and runs the test and passes.
I presumed that calling stop() would halt test execution (as the docs say it should) but that doesn't seem to be the case as the remove() is being called before the load callback is completed.
What can I do to make sure the test is run before the iframe is removed from the page?
I don't want to put the iframe remove in a callback as I have a few instances of this and will cause callback hell if I have to make each equal run off each other.

This is not clear from the docs, but stop() just ensures that teardown for the test will not be started until start() has been called. It does not in any way postpone the statements in the current test case following the call to stop().
The correct answer to your question is the one provided by jhorback; put the removal in the teardown.

You could try putting the removal in the teardown of the module. This would wait until the start() method is called and the text execution is complete.
QUnit.module("new test", {
setup: function () {
var testIframe = $('<iframe id="testiframe" src="/" />').appendTo("body");
testIframe.load(function() {
console.log('done loading iframe');
});
},
teardown: function () {
testIframe.remove();
}
});
QUnit.stop only tells QUnit to wait so many miliseconds before checking the results. Also, the iframe load event may not be triggered if the iframe does not have a valid src.

Related

How to test event listeners using QUnit

I'm using pure Javascript (no JQuery) and I'm trying to get QUnit to test my function that is only invoked via an event, i.e. it's an event listener.
So the function I wish to test is of the form:
(function() {
function the_one_i_want_to_test() {
// do stuff
}
window.addEventListener('load', function() {
var some_element = ...;
some_element.addEventListener('click', the_one_i_want_to_test);
});
})();
I know I could expose the function to test it, but it is only ever used as an event listener and I don't want to pollute the global namespace.
That's why I am trying to kill two birds with one stone by manually triggering a "click" event and then checking for desired effects (in my tests).
The problem is that the event handler doesn't seem to be executing at all, either because QUnit doesn't wait for it before performing the effects checks, or for some other reason.
So my QUnit test code looks like this:
QUnit.test('test function', function(assert) {
var some_element = ...;
var event = document.createEvent('Event');
event.initEvent('click', true, true);
some_element.dispatchEvent(event);
assert.ok(...assert one or more effects of running "the_one_i_want_to_test"...);
});
I have tried including JQuery just for the tests to use ".trigger()", but that doesn't seem to help.
The event listener executes fine/normally on the production page, just not in the tests.
Any ideas on why the event listener doesn't seem to be running?
In Javascript, event handlers are not executed immediately when the event is raised. They are queued up on a list, and execute after the current code block is done. By that time, the test is over.
You have to use the setTimeout trick to queue up your validation code so that it executes after the handler. When the delay is 0 (or unspecified), the code will be queued for execution as soon as possible, meaning right after the event handler gets a chance to run.
As Amit says, you also need to use the assert.async() method to get a done() function that you'll call after your assertions. This makes QUnit.test wait until you invoke it before moving on to the next test.
Try something like this:
QUnit.test('test function', function(assert) {
var some_element = ...;
var event = document.createEvent('Event');
event.initEvent('click', true, true);
some_element.dispatchEvent(event);
var done = assert.async();
setTimeout(function() {
assert.ok(...assert one or more effects of running "the_one_i_want_to_test"...);
done();
});
});
Ensure that the code you wish to test is actually included in your web page.
I have been running in a headless environment and didn't get any runtime errors (apart from the failed assertions). With the help of #Amit (to bring me to my senses), I tried running the tests in a browser and discovered the code was not even being included in the page.

Requiring timeouts when testing Meteor with Velocity and Jasmine

Pretty new to meteor, velocity and jasmine so not sure if I am doing something wrong, using Jasmine for something it's not designed for, or this is just the way it works.
I am finding I need to set timeouts for pretty much all of my tests in order to get them to pass. Should this be the case or am I doing something incorrectly?
For example some tests I am running to check validation messages:
describe("add quote validation", function() {
beforeEach(function (done) {
Router.go('addQuote');
Tracker.afterFlush(function(){
done();
});
});
beforeEach(waitForRouter);
it("should show validation when Quote is missing", function(done) {
$('#quote').val('');
$('#author').val('Some author');
Meteor.setTimeout(function(){
$('#addQuoteBtn').click();
}, 500);
Meteor.setTimeout(function(){
expect($('.parsley-custom-error-message').text()).toEqual("Quote can't be empty.");
done();
}, 500);
});
}
OK, we've had this exact same problem and devised a pretty elegant solution to it, that doesn't require timeouts and is the fastest way to run your tests. Basically, we use one of two strategies, depending on what screen elements you're waiting for.
All code goes into tests/mocha/client/lib.coffee, not 100% what the Jasmine equivalent is, but it should be available to all client test code. I've left it in Coffeescript, but you can compile it on coffeescript.org into Javascript, it should work fine as well.
If whatever you do (routing or something else like changing a reactive variable) causes a Template to (re)render, you can use the Template.<your_template>.rendered hook to detect when it is finished rendering. So, we've added the following function in lib.coffee:
#afterRendered = (template,f)->
cb = template.rendered
template.rendered = ->
cb?()
template.rendered = cb
f?()
return
return
What does it do? It basically "remembers" the original rendered callback and temporarily replaces it with one that calls an extra function after the template is rendered and the original callback is called. It needs to do this sort of housekeeping to avoid breaking any code that may have depended on the rendered callback, as you're basically messing with the Meteor code directly.
In your test, you can then do something like this:
it.only "should check stuff after routing", (done)->
try
Router.go "<somewhere>"
afterRendered Template.<expected_template>, ->
<your tests here>
done()
catch e
done(e)
I'd recommend the try-catch as well, as I've noticed asynchronous errors don't always make it into the velocity system, merely giving you a timeout failure.
OK, then there are things that don't actually re-render, but are generated with JS or by some kind of "show/hide" mechanism. For that, you do need some kind of timeout, but you can reduce the "time cost" of the timeout by using a polling mechanism.
# evaluates if a JQuery element is visible or not
$.fn.visible = -> this.length > 0 and this.css('display') isnt 'none'
# This superduper JQuery helper function will trigger a function when an element becomes visible (display != none). If the element is already visible, it triggers immediately.
$.fn.onVisible = (fn,it)->
sel = this.selector
if this.visible()
console.log "Found immediately"
fn?(this)
else
counter = 0
timer = setInterval ->
counter++
el = $(sel)
if el.visible()
fn?(el)
clearInterval timer
console.log "Found on iteration #{counter}"
else
it?(el)
, 50
You can remove the console logging and secondary it iterator function if you like, they're not important. This allows you to do something like this in your test:
$('#modalId').onVisible (el)->
<tests here>
done()
, (el)->
console.log "Waiting for #{el.selector}"
You can remove the second function if you want, it is the it iterator function mentioned above. However, do note that this particular code works with "display: hidden" as the marker for invisibility (Bootstrap does this). Change it if your code uses another mechanism to hide/show parts.
Works like a charm for us!

How to put a sleep after clicking a button

I would like to put a delay after a button is pressed in order for the button to load the data from the cache before executing the next line of code. Would putting a sleep be the best way to do this?
Something like this or is there an alternative approach to best solve this problem?
setInterval(document.getElementById("generateButton"), 1000);
Don't use setInterval to do this. It doesn't have the functionality you seem to desire (it repeats). Instead, use jQuery and do something like this:
$("#generateButton").click(function(event){
setTimeout(function(){
//Do what the button normally does
}, 1000);
});
Or (without JQuery):
var generateButton = document.getElementById("generateButton");
generateButton.addEventListener("click", function(){
setTimeout(function(){
//Do what the button normally does
}, 1000);
});
Using setTimeout over setInterval is preferred in your case because setTimeout runs only once while setInterval runs multiple times.
I assume you have, in your html, <button id='generateButton' onclick='someFunction()'>Button Text</button>. Remove the onclick='someFunction() and put your someFunction() where I said (in the examples) "Do what the button normally does."
You can also add in the code that loads the cache a method that calls another method once the cache has been loaded (when the someFunction() from the button is called, it loads the cache, and at the end of the function (set this up using callbacks), once the cache has been loaded, it calls another method onCacheLoaded() that can be run once the cache has been loaded.
You should use callbacks, so the moment you loaded data from cache you can call it and continue executing the rest of the script.
You cannot use interval since you cannot be sure how much time is needed for the data to load. Though keep in mind the asynchronous nature of javascript and don't block the part of the script that does not depend on the data that's being loaded.
Try setTimeout:
myButton.addEventListener('click', function() {
setTimeout(delayed, 1e3); // Delay code
}, false);
function delayed() {
// Do whatever
}
Note setInterval runs a function periodically, setTimeout only once.
Also note that the delayed code must be a function (or a string which will be evaluated, but better avoid that). However, document.getElementById("generateButton") returns an html element (or null).

Run js function once when fired more than once

I have a Div that uses jQuery to load a file/contents with a javascript function..
function DoWork() {
// Do Stuff
}
Let's say the user can reload the Div and pull the same file/contents with the same js function DoWork(). The problem is, when the file is reloaded, the previous loaded function DoWork() is still running. How can I kill the previous fired DoWork() and restart it?
Javascript is single-threaded, which means only one thing can be executing at a given moment. If DoWork is already "running" it's either a) blocking all other JS code, and you have no choice but to let it finish since you have no way to execute any interruption code until it finishes on its own, or b) DoWork is scheduled to fire off on an interval via setTimeout() or setInterval().
If it's the latter case, setTimeout() and setInterval() return an ID. Store that ID somewhere and call clearTimeout(doWork_timeout_id) or clearInterval(doWork_interval_id) according to how you started it.
You can build a simple function that use: setTimeout and then each call to DoWork will call first to: clearTimeout. I don't really like this solution because you will waste CPU on setTimeout.
So another option will be to use web worker in DoWork (It will do lots of other good things for you in case you are working with big data as it's running in another thread) - then you get an option to send 'stop' message each time you start the work of DoWork().
Are you using ajax to load the div's contents? if so, the better way is as follows:
var doWorkAjax=null;
function DoWork(){
if (doWorkAjax) doWorkAjax.abort();
doWorkAjax = $.ajax(url, data, function(result){
....
doWorkAjax=null;
});
}

How can I tell when changes to jquery html() have finished?

I'm using jQuery to change the HTML of a tag, and the new HTML can be a very long string.
$("#divToChange").html(newHTML);
I then want to select elements created in the new HTML, but if I put the code immediately following the above line it seems to create a race condition with a long string where the changes that html() is making may not necessarily be finished rendering. In that case, trying to select the new elements won't always work.
What I want to know is, is there an event fired or some other way of being notified when changes to html() have finished rendering ? I came across the jQuery watch plugin, which works alright as workaround but it's not ideal. Is there a better way ?
As a commenter already mentioned, JavaScript is single threaded, so you can't get race conditions.
What may trip you up however, is the fact that the UI will not update itself based on JavaScript, until a thread is finished. This means that the entire method must finish, including all code after you call html(...), before the browser will render the content.
If your code after calling html(...) relies on the layout of the page being recalculated before continuing, you can do something like this:
$("#divToChange").html(newHTML);
setTimeout(function() {
// Insert code to be executed AFTER
// the page renders the markup
// added using html(...) here
}, 1);
Using setTimeout(...) with a time of 1 in JavaScript defers execution until after the current JavaScript code in the calling function finishes and the browser has updated the UI. This may solve your problem, though it is difficult to tell unless you can provide a reproducible example of the error you're getting.
use .ready jQuery function
$("#divToChange").html(newHTML).ready(function () {
// run when page is rendered
});
It's 7 years latter and I just ran into a scenario exactly like the one #mikel described, where I couldn't avoid a "timer based solution". So, I'm just sharing the solution I developed, in case anyone out there is still having issues with this.
I hate having setTimeouts and setIntervals in my code. So, I created a small plugin that you can put where you think it's best. I used setInterval, but you can change it to setTimeout or another solution you have in mind. The idea is simply to create a promise and keep checking for the element. We resolve the promise once it is ready.
// jquery.ensure.js
$.ensure = function (selector) {
var promise = $.Deferred();
var interval = setInterval(function () {
if ($(selector)[0]) {
clearInterval(interval);
promise.resolve();
}
}, 1);
return promise;
};
// my-app.js
function runWhenMyElementExists () {
// run the code that depends on #my-element
}
$.ensure('#my-element')
.then(runWhenMyElementExists);

Categories