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!

Suggestion for Emulating Namespaces in JavaScript

I've been giving some thought on how to emulate namespaces in JavaScript (at least until they're implemented in the language), and this is what I've come up with: use objects for namespaces. This was inspired by a glance at the prototype, where I noticed that Sam Stephenson was using objects to group related things into neat packages.

For example, say that you wanted to create a class for managing music on you CDs. Normally in JavaScript, you'd create a class named CDMusic. This is all well and fine, but if everyone creates classes with a single name, a JSAN repository would end with an awfully crowded list of classes. It allows for no effective hierarchical organization of code.

But if you use objects to represent namespaces, you can define a class something like this, instead (1990s-era example borrowed from Damian Conway's Object Oriented Perl):

if (CD == undefined) var CD = {}; // Make sure the base namespace exists.
CD.Music = function () {};        // Constructor definition.

// Class definition.
CD.Music.prototype = {
    name:      null,
    artist:    null,
    publisher: null,
    isbn:      null,
    tracks:    [],

    location: function (shelf, room) {
        if (room != null) this._room = room;
        if (shelf != null) this._shelf = shelf;
        return [this._room, this._shelf];
    },

    rating: function (rate) {
        if (rate != null) this._rating = rate;
        return this._rating;
    }
};

So now, to use this class, you just:

var music = new CD.Music();
music.name = "Renegades";
music.artist = "Rage Against the Machine";
music.tracks.push("Microphone Fiend");
music.location("basement", 3); // I use an iPod, so it's in storage!

Of course, the key part of this example is var music = new CD.Music();. Note how the class is defined as an attribute of the CD object. This allows us to have a namespace, CD.Music, that is subsumed under another namespace, namely CD. The nice thing about this is that, in the hypothetical JSAN repository, the class might be defined the file Music.js in the CD directory. A use() function as described by Michael Schwern might then be smart enough to look for /use/CD/Music.js when you write use("CD.Music").

I also kind of like how the use of the namespace and prototype allows my class definition to be indented by the creation of the prototype object. But to make these types of namespaces work, you must have that first statement: if (CD == undefined) var CD = {};. This allows you to assign to a CD object whether you have to create it (because your class stand on its own), or because some other JavaScript class has defined it. This is especially important to ensure that you don't stomp on someone else's work. Say someone is using two different JavaScript classes, your CD.Music and someone else's CD.Jukebox. The two classes might be completely unrelated to each other, but because they both define themselves under the CD top-level namespace using the if (CD == undefined) statement, they won't stomp on each other.

The only downside to this proposal, in my estimation, is the requirement it imposes for defining inherited classes. Say you wanted a subclass of CD.Music for classical music. You'd have to do it like this:

// CD.Music must be loaded already. Create the constructor.
CD.Music.Classical = function () {}

// Inherit from CD.Music.
CD.Music.Classical.prototype = new CD.Music(); // Inheritance.

// Add to the class and/or override as necessary.
CD.Music.Classical.prototype.composer  = null;
CD.Music.Classical.prototype.orchestra = null;
CD.Music.Classical.prototype.conductor = null;
CD.Music.Classical.prototype.soloist   = null;

So we don't get the block syntax, but in truth, that's no different from how one typically handles inheritance in JavaScript. The only difference is the use of the dot notation. Nevertheless, suggestions for how to use a block syntax would be warmly received.

So what do you think? Is this something that makes sense to you? Would you do it to better organize your JavaScript classes and modules (and yes, I am thinking that you could group functional libraries this way, too, and then implement an import() function to export functions to another namespace or the global object)? Leave your opinions in a comment. Thanks!

The Purpose of TestSimple

In response to my TestSimple 0.03 announcement, Bill N1VUX asked a number of important questions about TestSimple's purpose. Since this is just an alpha release and I'm still making my way though the port, I haven't created a project page or started to promote it much, yet. Once I get the harness part written and feel like it's stable and working well, I'll likely start to promote it as widely as possible.

But yes, TestSimple is designed for doing unit testing in JavaScript. Of all the JavaScript I've seen, I've never seen any decent unit tests. People sometimes write a few integration tests to test them in browsers, but don't write many tests that would ensure that their JS code runs where they need it to run and that would give them the freedom to refactor. There is generally very little coverage in JavaScript tests—if there are any tests at all.

While it's true that JavaScript is nearly always an embedded language (but see also Rhino and SpiderMonkey), that doesn't mean that one doesn't write a lot of JavaScript functions or classes that need testing. It's also important to have a lot of tests you can run in various browsers (just as you can run tests of a Perl module on various OSs and various versions of Perl). I started the port because, as I was learning JavaScript, I realized that I didn't want to write much without writing tests. The purpose is to ensure the quality of JavaScript code as it goes through the development process. And the freedom to refactor that tests offer is very important for my personal development style.

So, to answer N1VUX's questions:

Is the point to integration test the whole distributed front-ends of applications from the (EcmaScript compliant) browser?

Yes. And I expect that, as people write more JavaScript applications, there will be a lot more code that needs testing. However, unlike other JavaScript testing frameworks I've seen (all based on the xUnit framework), my suite doesn't assume that tests will be run in a browser. Ultimatelly, I'd like to be able to automate tests outside of browsers—or by scripting browsers. But in the meantime, it will produce TAP-compliant output in the browser, and I plan on implementing a harness that will run all of your test scripts in a single browser window and output the results, just like Test::Harness does for Perl modules on the command-line.

Or to unit test the client-side java-script as an entity, mocking the server??

Yes, I would like to be able to do that eventually. I will likely mock the server by mocking XMLHttpRequest and Microsoft.XMLHTTP to return XML strings that can be used for testing. Stuff like that.

Or is the point to Unit test JavaScript functions in the browser in vitro, mocking everything outside the current function?

If need be, yes. The point is that you have the freedom to do it the way that makes sense to your particular project. The testing framework itself doesn't care where it's run or how.

Or is this for Unit Testing of the Presentation Layer on the server from the Browser? (If so, how can a JavaScript arrange to Mock the Model layer?)

Probably not, but again, it depends on the model of your application. I've tried to make no assumptions, just provide you with the tools to easily test your application. I hope and expect that others will start creating the appropriate JavaScript libraries to start mocking whatever other APIs one needs to fully unit test JavaScript code that relies on such libraries.

Or is it more likely for driving Integration Testing from the browser with the scripting simplicity we've come to love, without resorting to OLE-stuffing the browser from Perl?

That was my initial impetus, yes.

Digging into the TAR file (which I normally wouldn't do before peaking at the web copy of the POD2HTML's) I think I understand it's for unit-testing JavaScript classes, which I hadn't even considered. (JavaScript has classes that fancy? *shudder* no wonder pages don't work between browser versions.) I hope I don't need to do that.

Yes, as more people follow Google's lead, more and more applications will be appearing in JavaScript, and they will require a lot of code. JavaScript already suppports a prototype-based object-orientation scheme, and if JavaScript 2.0 ever becomes a reality (please, please, please, please please!), then we'll have real classes, namespaces, and even import, include and use! Testing will become increasingly important as more organizations come to rely on growing amounts of production JavaScript code.

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)!

TestSimple 0.03 Released

I'm pleased to announce the third alpha release of TestSimple, the port of Test::Builder, Test::Simple, and Test::More to JavaScript. You can download it here. This release has the following changes:

  • Removed trailing commas from 3 arrays, since IE6/Win doesn't like them. And now everything works in IE. Thanks to Marshall Roch for tracking down and nailing this problem.
  • isNum() and isntNum() in TestBuilder.js now properly convert values to numbers using the global Number() function.
  • CurrentTest is now properly initialized to 0 when creating a new TestBuilder object.
  • Values passed to like() and unlike() that are not strings now always fail to match the regular expression.
  • plan() now outputs better error messages.
  • isDeeply() now works better with circular and repeating references.
  • diag() is now smarter about converting objects to strings before outputting them.
  • Changed isEq() and isntEq() to use simple equivalence checks (== and !=, respectively) instead of stringified comparisons, as the equivalence checks are more generally useful. Use cmpOk(got, "eq", expect) to explicitly compare stringified versions of values.
  • TestBuilder.create() now properly returns a new TestBuilder object instead of the singleton.
  • The useNumbers(), noHeader(), and noEnding() accessors will now properly assign a non-null value passed to them.
  • The arrays returned from summary() and details() now have the appropriate structures.
  • diag() now always properly adds a # character after newlines.
  • Added output(), failureOutput(), todoOutput(), warnOutput(), and endOutput() to TestBuilder to set up function reference to which to send output for various purposes. The first three each default to document.write, while warnOutput() defaults to window.alert and endOutout() defaults to the appendData function of a text element inside an element with the ID test or, failing that, window.write.
  • todo() and todoSkip() now properly add # after all newlines in their messages.
  • Fixed line ending escapes in diagnostics to be platform-independent. Bug reported by Marshall Roch.
  • Ported about a third of the tests from Test::Simple (which is how I caught most of the above issues). The remaining test from Test::Simple will be ported for the next release.

Many thanks to Marshall Roch for help debugging issues in IE.

Now, there is one outstanding issue I'd like to address before I would consider this production ready (aside from porting all the remaining tests from Test::Simple): how to harness the output. Harnessing breaks down into a number of issues:

How to run all tests in a single window. I might be able to write a build script that builds a single HTML file that includes all the other HTML files in iframes or some such. But then will each run in its own space without stomping on the others? And how would the harness pull in the results of each? It might be able to go into each of its children and grab the resultsfrom the TestBuilder objects...

More Feedback/advice/insults welcome!

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!

TestSimple 0.02 Released

I'm pleased to announce the second alpha release of TestSimple, the port of Test::Builder, Test::Simple, and Test::More to JavaScript. You can download it here. This release has the following changes:

  • Removed eqArray() and eqAssoc() functions from TestMore per suggestion from Michael Schwern. The problem is that these are not test functions, and so are inconsistent with the way the rest of the functions work. isDeeply() is the function that users really want.
  • Changed eqSet() to isSet() and made it into a real test function.
  • Implemented skip(), todoSkip(), and todo(). These are a bit different than the Perl originals originals so read the docs!
  • Implemented skipAll() and BAILOUT() using exceptions and an exception handler installed in window.onerror.
  • The final message of a test file now properly outputs in the proper place. Tests must be run inside an element its id attribute set to test, such as <pre id="test">. The window.onload handler will find it and append the final test information.
  • Implemented skipRest() in TestBuilder and TestMore. This method is stubbed out the Perl original, but not yet implemented there!

The only truly outstanding issues I see before I would consider these modules ready for production use are:

  • Figure out how to get at file names and line numbers for better diagnostic messages. Is this even possible in JavaScript?
  • Decide where to send test output, and where to allow other output to be sent. Test::Builder clones STDERR and STDOUT for this purpose. We'll probably have to do it by overriding document.write()>, but it'd be good to allow users to define alternate outputs (tests may not always run in a browser, eh?). Maybe we can use an output object? Currently, a browser and its DOM are expected to be present. I could really use some advice from real JavaScript gurus on this one.
  • Write tests!

Feedback/advice/insults welcome!

New JavaScript Testing Method: TestSimple

I'm pleased to announce the first alpha release of my port of Test::Simple/Test::More/Test::Builder to JavaScript. Download it now and let me know what you think!

You can see what the tests look like by loading the files in the tests/ directory into your Web browser. This is my first stab at what I hope becomes a complete port. I could use some feedback/ideas on a number of outstanding issues:

  • I have made no decisions as to where to output test results, diagnostics, etc. Currently, they're simply output to document.write(). This may well be the best place in the long run, though it might be nice to allow users to configure where output goes. It will also be easy to control the output, since the output functions can easily be replaced in JavaScript. Suggestions welcome.
  • I have no idea how to exit execution of tests other than by throwing an exception, which is only supported by JavaScript 1.5, anyway, AFAIK. As a result, skipAll(), BAILOUT(), and skipRest() do not work.
  • Skip and Todo tests currently don't work because named blocks (e.g., SKIP: and TODO:) are lexical in JavaScript. Therefore I cannot get at them from within a function called from within a block (at least not that I can tell). It might be that I need to just pass function references to skip() and todo(), instead. This is a rather different interface than that supported by Test::More, but it might work. Thoughts?
  • Currently, one must call Test._ending() to finish running tests. This is because there is no END block to grab on to in JavaScript. Suggestions for how to capture output and append the output of _ending() are welcome. It might work to have the onload event execute it, but then it will have to look for the proper context in which to append it (a <pre> tag, at this point).
  • Anyone have any idea how to get at the line number and file name in a JavaScript? Failures currently aren't too descriptive. As a result, I'm not sure if level() will have any part to play.
  • Is there threading in JavaScript?
  • I haven't written TestHarness yet. It may not make sense to even have such a thing in JavaScript; I'm not sure.
  • I'm using a Module::Build script to build a distribution. I don't think there's a standard for distributing JavaScript libraries, but I think that this works reasonably well. I have all of the documentation in POD, and the script generates HTML and text versions before creating the tarball. The Build.PL script of course is not included in the distribution. I started out trying to write the documentation in JSDoc, but abandoned it for all of the reasons I recounted last week.
  • Is there a way to dynamically load a JavaScript file? I'd like to use an approach to have TestMore.js and TestSimple.js load TestBuilder.js. I'd also like to use it to implement loadOk() (equivalent to Test::More's use_ok() and require_ok() subroutines).

More details are in the ToDo section of the TestBuilder docs.

Let me know what you think!

JSDoc Doesn't Quite do the Trick for Me

After my request for JavaScript documentation standards, I investigated the one I found myself: JSDoc. I went ahead and used its syntax to document a JavaScript class I'd written, and it seemed to work pretty well. Initially, my main complaint was that their was no easy way to include arbitrary documentation. Everything has to be associated with a constructor, attribute, or method. Bleh.

But then I started documenting two purely functional JavaScript files I'd written. These just create functions in the Global scope for general use. And here's where JSDoc started to really become a PITA. First, functions with the same names in the two files were declared to be pre-declared! They two files are part of the same project, but users will generally use one or the other, not both. But JSDoc has taken it upon itself to refuse to document functions that are in two different files in the same project. Surely that's the JavaScript interpreter's responsibility!

The next issue I ran into (after I commented out the code in JSDoc.pm that refused to document functions with the same names) was that it didn't recognize one of the files as having documentation, because there was no constructor. Well duh! A purely functional implementation doesn't have a constructor! It seems that Java's bias for OO-only implementations has unduly influenced JSDoc, even though JavaScript applications often define no classes at all!

The clincher in my decision to ditch JSDoc, however, came when I realized that, for most projects, I won't want the documentation in the same file as the code. While I generally prefer that they be in the same file, I will often have 4-10 times more documentation than actual code, and the bandwidth overhead seems unnecessary. JavaDoc and JSDoc of course require that any documentation be in the same files, since that's where they parse method signatures and such.

So I think I'll follow Chris Dolan's advice from my original post and fall back on Good 'ole POD. POD allows me to write as much or as little documentation as I like, with methods and functions documented in an order that makes sense to me, with headings even! I can write long descriptions, synopses, and even documentation completely unrelated to specifics of the interface. And all in a separate file, even!

This will do until someone formalizes a standard for JavaScript. Maybe it'll be KwiD?

Is there a JavaScript Library Documentation Standard?

Is there a JavaScript documentation standard? I've been working on a test framework for JavaScript and I'd like to integrate documentation so that others can use it.

If there isn't a documentation standard, I can see three possible options that I'd like to suggest:

Use XHTML.

Since JavaScript is mainly used for XHTML, it makes some sense to just use XHTML for its documentation. The downside to this is that there is currently no way to parse out the documentation, AFAIK. The format for putting the docs into comments would have to be standardized. I don't really see that happening.

Use POD.

JavaScript is a dynamic language; it'd make some sense to use the documentation format of an existing dynamic language. And POD is a proven format. The downside, of course, is that there is not a parser for pulling POD out of a .js file. Same problem as for XHTML, essentially.

Use JavaDoc

Since the syntax of JavaScript is roughly based on JavaScript, and JavaScript supports the same comment syntax, one could simply use the JavaDoc format. The javadoc application probably couldn't parse it out too well, since it parses the Java code (or byte code?) to automatically document method names, signatures, etc.

But a quick Googling yields JSDoc as a possible solution. The only downside to the JavaDoc/JSDoc solution is that it tends to allow authors to be too lazy. Since the application automatically documents the existence of functions and their signatures, often little else is documented. But that's mainly a personal issue; I don't have to be so lazy in my own documentation! I think I'll give that a shot.

Meanwhile, if anyone knows of something better/more widely used, let me know!

What JavaScript Book Should I Buy?

What with all the interest in Ajax, not to mention an increasing reliance on JavaScript in Bricolage (and a need to fix its bugs), I think it's high time I really learned more JavaScript. So, what JavaScript book shoud I buy? Bear in mind that I'm an experienced object-oriented application developer who wants to pick up a new language, not a script kiddie or CGI newbie.

Thanks!