Testing JavaScript

14. January, 2008

If you’re test mad like me, then the <script> tag in HTML was probably one sore spot for you as it was for me: How to test the damn thing? Well, now, there is a way: John Resig wrote a small script which you can source into Rhino 1.6R6 (or later; 1.6R5 won’t work, though. You’ll get “missing : after property id”). Afterwards, you’ll have window, document, nagivation, even XMLHttpRequest!

Yes, you can actually test AJAX within unit tests, now! TDD fans, start your engines!

Unfortunately, it doesn’t emulate browser bugs, yet ;-> But you can fix that. I, for example, had problems to get the code load HTML 3.2 files. Especially this code made the SAX parser vomit:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 //EN">

The fix here is to download some XHTML DTD (like XHTML 1 Strict), put it somewhere (along with the three entity files xhtml-lat1.ent,
xhtml-special.ent and xhtml-symbol.ent) and change the DTD to point to the new file:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 //EN" "html/xhtml1-strict.dtd">

In my case, I’ve put the files in a subdirectory “html/” of the directory I start the tests from. (Hm … shouldn’t this path be raltive to the HTML file?? Well, it isn’t.)

Also, the env.js supplied doesn’t support forms. Here is my which fix:

function collectForms() {
    document.forms = document.body.getElementsByTagName('FORM');
    
    for (var i=0; i<document.forms.length; i++) {
        var f = document.forms[i];
        f.name = f.attributes['name'];
        //print('Form '+f.name);
        f.elements = f.getElementsByTagName('INPUT');
        
        for(var j=0; j<f.elements.length; j++) {
            var e = f.elements[j];
            var attr = e.attributes;
            
            forEach(attr, print);
            e.type = attr['type'];
            e.name = attr['name'];
            e.className = attr['class'];
            
            _elements[ f.name + '.' + e.name ] = e;
        }
        //print(f.elements);
    }
}

Note: I also had to remove the calls to toLowerCase() for the tag names (*not* the attributes!), too. Otherwise, document.body would return UNDEFINED for me. But that’s because I’m stuck with old HTML; If you can convert all tag and attribute names to lowercase, then you’re safe.

Lastly, the loading of the document is asynchronous. To fix this, we need a class to synchronize the Java and the JavaScript thread. That one is simple:

package test.js;

import org.mozilla.javascript.ScriptableObject;

import test.ShouldNotHappenException;

public class JSJSynchronize extends ScriptableObject
{
    public Object data;
    public Object lock = new Object ();
    
    @Override
    public String getClassName () {
        return "JSJSynchronize";
    }
    
    public Object jsGet_data() {
        synchronized (lock) {
            try {
                lock.wait ();
            }
            catch (InterruptedException e) {
                throw new ShouldNotHappenException(e);
            }
            
            return data;
        }
    }

    public void jsSet_data(Object data) {
        synchronized (lock) {
            this.data = data;
            lock.notify ();
        }
    }
    
    public Object getData() {
        synchronized (lock) {
            try {
                lock.wait ();
            }
            catch (InterruptedException e) {
                throw new ShouldNotHappenException(e);
            }
            
            return data;
        }
    }

    public void setData(Object data) {
        synchronized (lock) {
            this.data = data;
            lock.notify ();
        }
    }
    
}

ShouldNotHappenException is derived from RuntimeException. After registering that with

        jsjSynchronize = (JSJSynchronize)cx.newObject (scope, "JSJSynchronize");
        scope.put("jsjSynchronize", scope, jsjSynchronize);

in the test, I can use the new jsjSynchronize global variable in JavaScript in wondow.onload:

    public void testAddTextFilters() throws Exception
    {
        setupContext ();
        addScript(cx, scope, new File ("html/env.js"));
        cx.evaluateString(scope, 
                "window.location = 'file:///d:/devm2/globus/abs/webapp/html/testAbsSkuOutput.html';n" +
      "window.onload = function(){n" +
      "    jsjSynchronize.data = window;n" +
      "};n" +
      "", "", 1, null);
        
        ScriptableObject window = (ScriptableObject)jsjSynchronize.getData();
        System.out.println ("window="+window);

At this point, the document has been loaded and you can access all the fields, elements, etc. Good luck!


Silence the HD

1. January, 2008

Some follow up to yesterday’s post. Using a Sharkoon HDD Vibe-Fixer in a case which doesn’t require screws to install drives doesn’t work; the Vibe-Fixer consists of several independent parts which are only loosely coupled. So when the drive starts to work, you’ll hear a clatter between the U shaped Vibe-Fixer and the slot in the case.

One solution would be to force some felt between the parts and the slot but it’s hard to get there and I’m not sure if the results are worthwhile. So I’ve bought a ichbinleise® Box HDD 10. This is basically a set of four aluminium pieces held together by four screws which keep the HD between two sheets of damping foam. The whole thing is then glued to the case with a thick foam piece to isolate the vibrations.

I had to use hdparm -M 128 /dev/sda to switch the drive into silent mode, though, to reduce the noise to a low murmur.

The main disadvantage of this setup is heat: I’ve applied some thermo transfer goo following a tip of the nice guys at ichbinleise.ch. Also, the HD case is in the flow of the front fan. Still the drive reports temperatures between 60°C and 80°C according to smartctl. I hope this is a bug but I’ll keep an eye on it.

Another disadvantage is that I can’t easily remove the 3.5″ drive cage; it’s riveted into the case and I’d have to use a drill to get it out. So I glued it in front of the mainboard which means it blocks the lowest two PCI slots. Since I don’t need them, it’s not a big deal but still …