home :: computers :: programming :: javascript

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?

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!

Plea for Help from JavaScript Geniuses

I’ve been working for some time to get pure .js test files working with Test.Simple. In principal, it’s simple: The Browser harness simply constructs a hidden iframe, and if the test script ends in .js, it uses document.write() for that iframe to write out a new HTML document that includes the test script via a script tag. It can also load other scripts specified by the user in the index.html file that runs the harness.

I got it working, and then converted all of Test.Simple’s own tests over to .js files, at which point it got all weird. The tests were dying after the third test file was loaded and run. After weeks of on and off debugging, I’ve reduced the problem enough to find the following:

  • I’m using JSAN to load dependencies in the test scripts themselves. The browser harness loads it by writing a script tag into the head section of the iframe.
  • The tests always use JSAN to load Test.Builder, either directly or by loading a library that depends on it (like Test.More.
  • Test.Builder detects when it’s being run in a browser and sets an onload event handler to end the execution of tests.
  • For the first two test files, Test.Builder loads and runs, and sets up the onload event handler, and it properly executes when the test finish.
  • But in the third test, the onload event handler seems to run before Test.Builder has finished execution or even loading! As such, it cannot get access to the Test.Builder class to finish the tests, and throws an exception: Error: this.Test has no properties, where this is the iframe window object.

The only thing I can guess is that it’s retaining the onload event handler for the previous test file, even if I put delete buffer.onload before writing out the HTML to load the test file! (Note that buffer is the name of the variable that is holding the contentWindow attribute of the iframe object.) You can observe this behavior for yourself by running the tests now. They don’t work in Safari at all (I code to Firefox and then port to the other browsers), but Firefox demonstrates the issue. I have alert()s that run just before Test.Builder sets up the onload event handler, and then inside the event handler, either when its run or when it catches an exception (but before it rethrows it). The order of execution, you’ll note, is as follows:

  • async.js.......... output to browser
  • alert("Setup: buffer") during the execution of Test.Builder for async.js, where "buffer" is the name of the iframe element.
  • alert("Onload: buffer") for async.js, during the execution of the onload event
  • bad_plan.js....... output to browser
  • alert("Setup: buffer") during the execution of Test.Builder for bad_plan.js
  • alert("Onload: buffer") for bad_plan.js, during the execution of the onload event
  • buffer.js........ output to browser
  • alert("Catch: buffer") output from catching the exception in the onload handler for buffer.js, which, of course, Test.builder hasn’t set up yet!

If I don’t rethrow the exception, it then runs the code in Test.Builder that sets up the onload handler. In other words, the onload handler runs before it has been created. Huh?

The nearest to a workaround that I’ve found is to delete the iframe element after each test and create a new one. The errors are still thrown, but all tests seem to pass anyway. It doesn’t seem to like the old-style .html test files, though; it hangs on them without throwing any error at all. Grrrr.

So, are you a JavaScript genius? Do you know how the browser, DOM, and frames work and interact better than you know your own family? If so, please, please give me a hint as to what I can do to fix this problem so that I can get a new version of Test.Simple out ASAP. Thanks!

Update: I forgot to mention that the in-progress source code for Test.Simple with support for .js test files can be downloaded here, so that you can play with it on your own system. Thanks!

Test.Simple 0.11 Released

I’m pleased to announce the release of Test.Simple 0.11. This release fixes a number of bugs in the framework’s IE and Safari support, adds JSAN support, and includes an experimental harness for Macromedia^Adobe Director. You can download it from JSAN, and all future releases will be available on JSAN. See the harness in action here (or verbosely!). This release has the following changes:

  • The browser harness now works more reliably in IE and Safari.
  • Fixed syntax errors in tests/harness.html that IE and Safari care about.
  • Various tweaks for Director compatibility from Gordon McCreight.
  • Removed debugging output from Test.More.canOK().
  • Fixed default output so that it doesn’t re-open a closed browser document when there is a test element.
  • Added experimental Test.Harness.Director, complements of Gordon McCreight. This harness is subject to change.
  • Added Test.PLATFORM, containing a string defining the platform. At the moment, the only platforms listed are browser or director.
  • Added support for Casey West’s JSAN. All releases of Test.Simple will be available on JSAN from now on.
  • The iframe in the browser harness is no longer visible in IE. Thanks to Marshall Roch for the patch.
  • Noted addition of Test.Harness and Test.Harness.Browser in the README.

I’ve been getting more and more excited about Casey West’s work on JSAN. It gets better every day, and I hope that it attracts a lot of hackers who want to distribute open source JavaScript modules. You should check it out! I’ve been working on a Perl module to simplify the creation of JSAN distributions. Look for it on CPAN soonish.

Test.Simple 0.10 Released

I’m pleased to announce the first beta release of Test.Simple, the port of Test::Builder, Test::Simple, Test::More, and Test::Harness to JavaScript. You can download it here. See the harness in action here (or verbosely!). This release has the following changes:

  • Changed the signature of functions passed to output() and friends to accept a single argument rather than a list of arguments. This allows custom functions to be much simpler.
  • Added support for Macromedia Director. Patch from Gordon McCreight.
  • Backwards Incompatibility change: moved all modules into Test namespace by using an object for the Test namespace and assigning the Build() constructor to it. See http://xrl.us/fy4h for a description of this approach.
  • Fixed the typeOf() class method in Test.Builder to just return the value returned by the typeof operator if the class constructor is an anonymous function.
  • Changed for (var in in someArray) to for (var i = 0; i < someArray.length; i++) for iterating through arrays, since the former method will break if someone has changed the prototype for arrays. Thanks to Bob Ippolito for the spot!
  • The default output in browsers is now to append to an element with the ID test or, failing that, to use document.write. The use of the test element allows output to continue to be written to the browser window even after the document has been closed. Reported by Adam Kennedy.
  • Changed the default endOutput() method to be the same as the other outputs.
  • Backwards incompatibility change: Changed semantics of plan() so that it takes an object for an argument. This allows multiple commands to be passed, where the object attribute keys are the command and their values are the arguments.
  • Backwards incompatibility change: Changed the no_plan, skip_all, and no_diag (in Test.More only) options to plan() to their studlyCap alternatives, noPlan, skipAll, and noDiag. This makes them consistent with JavaScript attribute naming convention.
  • Added beginAsync() and endAsync() methods to Test.Builder to allow users to put off the ending of a script until after asynchronous tests have been run. Suggested by Adam Kennedy.
  • Backwards incompatibility change: Changed the signature for the output() method and friends to take only a single anonymous function as its argument. If you still need to call a method, pass an anonymous function that calls it appropriately.
  • Changed handling of line-endings to be browser-specific. That is, if the current environment is Internet Explorer, we use \r for line endings. Otherwise we use \n. Although IE properly interprets \n as a line ending when it’s passed to document.write(), it doesn’t when passed to a DOM text node. No idea why not.
  • Added a browser harness. Now you can run all of your tests in a single browser window and get a summary at the end, including a list of failed tests and the time spent running the tests.
  • Fixed calls to warn() in Test.More.
  • Output to the browser now causes the window to scroll when the length of the output is greater than the height of the window.
  • Backwards incompatibility change: Changed all instances of Ok to OK. So this means that the new Test.More function names are canOK(), isaOK(), and cmpOK(). Sorry ‘bout that, won’t happen again.
  • Ported to Safari (though there are issues—see the Bugs section of the Test.Harness.Browser docs for details).

Obviously this is a big release. I bumped up the version number because there are a fair number of backwards incompatibilities. But I’m reasonably confident that they wont’ change so much in the future. And with the addition of the harness, it’s getting ready for prime time!

Next up, I’ll finish porting the test from Test::Harness (really!) and add support for JSAN (look for a JSAN announcement soon). But in the meantime, feedback, bug reports, kudos, complaints, etc.warmly welcomed!

Powered by KinoSearch