There seems to be no way to synchronize these two processes, to have either an event listener or a wait function that would make sure assertions in the test cases are called after a certain script is reloaded.
Here is what I was looking for:
function loadScript(path) {
var script = document.createElement("script");
script.src = path;
document.body.appendChild(script);
JS_TOOLS.sleep(500);
}
function loadScripts(var_args) {
for (var i = 0; i < arguments.length; i++) {
loadScript(arguments[i]);
}
}
function testReloading_justOne() {
loadScript('scriptToLoad.js');
assertFalse(A_CERTAIN_VARIABLE_FROM_THE_SCRIPT === undefined);
}
The trick is to put the thread to sleep... for a little while.
I looked around on the internet, and found some cheap and some dear advices. In two words: Use Applet.
So I did. I wrote an applet, looking like this:
public class JsTools extends Applet {
/**
* Sleeps given number of milliseconds.
*
* @param timeToSleep time to sleep (ms)
*/
public void sleep(long timeToSleep) {
for (long wakeupTime = System.currentTimeMillis() + timeToSleep;
timeToSleep > 0;
timeToSleep = wakeupTime - System.currentTimeMillis()) {
try {
Thread.sleep(timeToSleep);
} catch (InterruptedException ie) {
// ignore it, just repeat until done
}
}
}
}
The idea is that I may want to add more functionality to this applet some time later.
Now, I compile this applet, and store it in jsapplet.jar (with the right directory structure, so that it could be found by the browser's jvm. Then I wrote some JavaScript code that loads the applet and exports a function,
sleep()
:
function jstools(location) {
var JSTOOLS_ID = "JSTOOLS_ID";
var userAgent = navigator.userAgent.toLowerCase();
var isIE = userAgent.indexOf('msie') != -1 &&
userAgent.indexOf('opera') < 0;
function _(var_args) {
for (var i = 0; i < arguments.length; i++) {
document.write(arguments[i]);
}
};
function openElement(name, attributes) {
_('<', name);
for (var id in attributes) {
_(' ', id, '="', attributes[id], '"');
}
_('>');
}
function closeElement(name) {
_('', name, '>');
}
function addParameters(parameters) {
for (var name in parameters) {
openElement('param', {name: name, value: parameters[name]});
}
}
openElement('object',
isIE ?
{
classid: "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93",
style: "border-width:0;",
codebase: "http://java.sun.com/products/plugin/autodl/jinstall-1_4_1-windows-i586.cab#version=1,4,1",
name: JSTOOLS_ID,
id: JSTOOLS_ID
} :
{
type: "application/x-java-applet;version=1.4.1",
name: JSTOOLS_ID,
id: JSTOOLS_ID
});
addParameters({
archive: (location ? (location + '/') : '') + 'jstools.jar',
code: "com.google.javascript.keyboard.JsTools",
mayscript: "yes",
scriptable: "true",
name: "jsapplet"});
closeElement('object');
return {
sleep: function sleep(n) {
document[JSTOOLS_ID].sleep(n);
}
};
};
This is kind of sad that I have to run it before the document ocntent is finalized; there must be a way to use DOM to build the applet element when it is needed - but the straightforward solution does not seem to be working, and what the heck, it is a unittest, where, according to josh kerievsky, everything is allowed.
There's a tricky parameter that one has to pass to
tools()
: applet location; we may later decide to store it somewhere else, outside our main directory. So, in my unittests, I instantiate my JS_TOOLS variable like this:
var JS_TOOLS = jstools("tools"); // the jar is in tools directory
Well, that's it. The script loads, and the variable instantiates.
Someone would complain about 0.3 second delay in running the test, right? Not clean, not "small", and flaky. Okay, suggest something else if you are really worried about .3 second delay in getting your test results.
P.S. You can try it here.