Just a Theory

By David E. Wheeler

Posts about Internet Explorer

IE DOM Help

I got Test.Harness.Browser working with IE 6 SP 2 today, but decided to spend a bit of time trying to get it working with the DOM script inclusion approach instead of the XMLHttpRequest approach. The code that causes the problem is this (pre is a pre element generated with the DOM API):

el = doc.createElement("script");
el.type = "text/javascript";
// XXX IE chokes on this line.
el.appendChild(doc.createTextNode("window.onload(null, Test)"));
pre.appendChild(el);

This works great in Firefox, but IE 6 doesn’t like the call to appendChild(). It says, “Unexpected call to method or property access.” So I tried to replace that line with:

el.innerHTML = "window.onload(null, Test);";

Firefox is still happy, but now IE 6 says, “Unknown runtime error.” If I try to just append a script tag to pre.innerHTML, I get no error, but the code doesn’t seem to execute, either. In fact, pre.innerHTML appears to be empty!

Anyone have any idea how I can dynamically write to a script element that I’ve created via the DOM?

Looking for the comments? Try the old layout.

Test.Simple 0.20 Released

It gives me great pleasure, not to mention a significant amount of pride, to announce the release of Test.Simple 0.20. There are quite a few changes in this release, including a few that break backwards compatibility—but only you’re writing your own Test.Builder-based test libraries (and I don’t think anyone has done so yet) or if you’re subclassing Test.Harness (and there’s only one of those, that I know of).

The biggest change is that Test.Harness.Browser now supports pure .js script files in addition to the original .html files. This works best in Firefox, of course, but with a lot of help from Pawel Chmielowski (“prefiks” on #jsan), it also works in Safari, Opera, and IE 6 (though not in XP Service Pack 2; I’ll work on that after I get my new PC in the next few days). The trick with Firefox (and hopefully other browsers in the future, since it feels lest hackish to me), is that it uses the DOM to create a new HTML document in a hidden iframe, and that document loads the .js. Essentially, it just uses the DOM to mimic the structure of a typical .html test file. For the other browsers, the hidden iframe uses XMLHttpRequest to load and eval the .js test file. Check it out (verbosely)!

I think that this will greatly enhance the benefits of Test.Simple, as it makes writing tests really simple. All you have to do is create a single .html file that looks something like this:

<html>
<head>
    <script type="text/javascript" src="./lib/JSAN.js"></script>
</head>
<body>
<script type="text/javascript">
    new JSAN("../lib").use("Test.Harness.Browser");
    new Test.Harness.Browser('./lib/JSAN.js').encoding('utf-8').runTests(
        'foo.js',
        'bar.js'
    );
</script>
</body>
</html>

In fact, that’s pretty much exactly what Test.Simple’s new harness looks like, now that I’ve moved all of the old tests into .js files (although there is still a simpl.html test file to ensure that .html test files still work!). Here I’m using JSAN to dynamically load the libraries I need. I use it to load Test.Harness.Browser (which then uses it to load Test.Harness), and then I tell the Test.Harness.Browser object where it is so that it can load it for each .js script. The test script itself can then look something like this:

new JSAN('../lib').use('Test.Simple');
plan({tests: 3});
ok(1, 'compile');
ok(1);
ok(1, 'foo');

And that’s it! Just use JSAN to load the appropriate test library or libraries and go! I know that JSAN is already loaded because Test.Harness.Browser loads it for me before it loads and runs my .js test script. Nice, eh?

Of course, you don’t have to use JSAN to run pure .js tests, although it can be convenient. Instead, you can just pass a list of files to the harness to have it load them for each test:

<html>
<head>
    <script type="text/javascript" src="./lib/Test/Harness.js"></script>
    <script type="text/javascript" src="./lib/Test/Harness/Browser.js"></script>
</head>
<body>
<script type="text/javascript">
    new Test.Harness.Browser(
        'lib/Test/Builder.js',
        'lib/Test/More.js',
        '../lib/MY/Library.js'
    ).runTests(
        'foo.js',
        'bar.js'
    );
</script>
</body>
</html>

This example tells Test.Harness.Browser to load Test.Builder and Test.More, and then to run the tests in foo.js and bar.js. No need for JSAN if you don’t want it. The test script is exactly the same as the above, only without the line with JSAN loading your test library.

Now, as I’ve said, this is imperfect. It’s surprisingly difficult to get browsers to do this properly, and it’s likely that it won’t work at all in many browsers. I’m sure that I broke the Directory harness, too. Nevertheless, I’m pleased that I got as many to work as I did (again, with great thanks to Pawel Chmielowski for all the great hacks), but at this point, I’ll probably only focus on adding support for Windows XP Service Pack 2. But as you might imagine, I’d welcome patches from anyone who wants to add support for other browsers.

There are a lot of other changes in this release. Here’s the complete list:

  • Fixed verbose test output to be complete in the harness in Safari and IE.
  • Fixed plan() so that it doesn’t die if the object is passed with an unknown attribute. This can happen when JS code has altered Object.prototype (shame on it!). Reported by Rob Kinyon.
  • Fixed some errors in the POD documentation.
  • Updated JSAN to 0.10.
  • Added documentation for Test.Harness.Director, complements of Gordon McCreight.
  • Fixed line endings in Konqueror and Opera and any other browser other than MSIE that supports document.all. Reported by Rob Kinyon.
  • Added support to Test.Harness.Browser for .js test files in addition to .html test files. Thanks to Pawel Chmielowski for helping me to overcome the final obstacles to actually getting this feature to work.
  • Added missing variable declarations. Patch from Pawel Chmielowski.
  • More portable fetching of the body element in Test.Builder. Based on patch from Pawel Chmielowski.
  • Added an encoding attribute to Test.Harness. This is largely to support pure JS tests, so that the browser harness can set up the proper encoding for the script elements it creates.
  • Added support for Opera, with thanks to Pawel Chmielowski.
  • Fixed the output from skipAll in the test harness.
  • Fixed display of summary of failed tests after all tests have been run by the browser harness. They are now displayed in a nicely formatted table without a NaN stuck where it doesn’t belong.
  • COMPATIBILITY CHANGE: The browser harness now outputs failure information bold-faced and red. This required changing the output argument to the outputResults() method to an object with two methods, pass() and fail(). Anyone using Test.Harness.outputResults() will want to make any changes accordingly.
  • COMPATIBILITY CHANGE: new Test.Builder() now always returns a new Test.Builder object instead of a singleton. If you want the singleton, call Test.Builder.instance(). Test.Builder.create() has been deprecated and will be removed in a future release. This is different from how Perl’s Test::Builder works, but is more JavaScript-like and sensible, so we felt it was best to break things early on rather than later. Suggested by Bob Ippolito.
  • Added beginAsync() and endAsync() functions to Test.More. Suggested by Bob Ippolito.

As always, feedback/comments/suggestions/winges welcome. Enjoy!

Looking for the comments? Try the old layout.

Quirks of IE's JavaScript Implementation

Just a few notes about the quirks of IE’s JavaScript implementation that I had to figure out and work around to get TestSimple working in IE.:

  • IE doesn’t like serial commas. In other words, If I create an object like this:

    var obj = {
        foo: "yow",
        bar: "bat",
    };
    

    IE will complain. It seems it doesn’t like that last comma, but it doesn’t give you a decent diagnostic message to help you figure out that that’s what it doesn’t like. Fortunately, I didn’t have to figure this one out; Marshall did And now I know to expect that IE thinks that its JavaScript should parse like SQL. Whatever!

  • You can’t truncate an array using a single argument to splice(). In Firefox, ary.splice(0) will truncate the array, but in IE, you must provide the second argument, like this: ary.splice(0, ary.length)—or else it won’t actually truncate the array.

  • Many IE JavaScript functions don’t seem to actually inherit from the Function class! I discovered this when I tried to call document.write.apply() and it failed. Not only does the apply() method not exist, but I can’t even add it! I came up with a decent workaround for this problem in TestBuilder, but I still don’t have a general solution to the problem. I did find a page that might have a general solution, but it sure is ugly.

  • IE automatically converts line endings in to the platform specific alternatives whenever you assign a JavaScript string to a text element. When Marshall showed me output that wasn’t properly adding “#” after all line endings, this was my immediate suspicion, and a quick Googling confirmed the issue. So I had to add regular expressions to look for all variations on the line endings.

I’m sure I’ll notice other issues as I work more with JavaScript, but feel free to chime in here with any gotchas you’ve noticed, and then I won’t have to work so hard to figure them out on my own in the future (and neither will you)!

Looking for the comments? Try the old layout.

How do I Add apply() to IE JavaScript Functions

This is really bugging me. I’ve added a feature to my TestSimple JavaScript library where one can specify a function to which to send test output. It executes the function, along with an object, if necessary, by calling its apply() method. If you don’t specify a function for output, it uses document.write by default:

if (!fn) {
    fn = document.write;
    obj = document;
}
var output = function () { fn.apply(obj, arguments) };

This works great in Firefox, as I can then just call fn.apply(this, arguments) and the arguments are properly passed on through to the function.

However, Internet Explorer doesn’t seem to have an apply() method on its write() function. If I execute document.write.apply(document ['foo']) in Firefox, it outputs “foo” to the browser. In Internet Explorer for Windows, however, it yields an error: “Object doesn’t support this property or method.” Wha??

I thought I could get around it by just adding the apply() method to document.write, but that doesn’t work, either. This code:

document.write.apply = Function.prototype.apply;
document.write.apply(document, ['foo']);

Yields the same error. Curiously, so does this code:

document.write.apply2 = Function.prototype.apply;
document.write.apply2(document, ['foo']);

So it seems that assigning a function to document.write is a no-op in IE. WTF?

So does anyone know a workaround for this bug? I found a page that says, “Beware that some native functions in IE were made to look like objects instead of functions.” This might explain why apply() doesn’t exist for the document.write object, but not why I can’t add it.

Help!

Looking for the comments? Try the old layout.