c2d98b834d30af50903306e3599988196c97ad27
[jquery.git] / test / lib / Test / tmp.js
1 // # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
2
3 // Set up namespace.
4 if (typeof self != 'undefined') {
5     // Browser
6     if (typeof Test == 'undefined') Test = {PLATFORM: 'browser'};
7     else Test.PLATFORM = 'browser';
8 } else if (typeof _global != 'undefined') {
9     //Director
10     if (typeof _global.Test == "undefined") _global.Test = {PLATFORM: 'director'};
11     else _global.Test.PLATFORM = 'director';
12 } else {
13     throw new Error("Test.More does not support your platform");
14 }
15
16 // Constructor.
17 Test.Builder = function () {
18     if (!Test.Builder.Test) {
19         Test.Builder.Test = this.reset();
20         Test.Builder.Instances.push(this);
21     }
22     return Test.Builder.Test;
23 };
24
25 // Static variables.
26 Test.Builder.VERSION = '0.11';
27 Test.Builder.Instances = [];
28 Test.Builder.lineEndingRx = /\r?\n|\r/g;
29 Test.Builder.StringOps = {
30     eq: '==',
31     ne: '!=',
32     lt: '<',
33     gt: '>',
34     ge: '>=',
35     le: '<='
36 };
37
38 // Stoopid IE.
39 Test.Builder.LF = typeof document != "undefined"
40                   && typeof document.all != "undefined"
41   ? "\r"
42   : "\n";
43
44 // Static methods.
45 Test.Builder.die = function (msg) {
46     throw new Error(msg);
47 };
48
49 Test.Builder._whoa = function (check, desc) {
50     if (!check) return;
51     Test.Builder.die("WHOA! " + desc + Test.Builder.LF +
52                      + "This should never happen! Please contact the author "
53                      + "immediately!");
54 };
55
56 Test.Builder.typeOf = function (object) {
57     var c = Object.prototype.toString.apply(object);
58     var name = c.substring(8, c.length - 1);
59     if (name != 'Object') return name;
60     // It may be a non-core class. Try to extract the class name from
61     // the constructor function. This may not work in all implementations.
62     if (/function ([^(\s]+)/.test(Function.toString.call(object.constructor))) {
63         return RegExp.$1;
64     }
65     // No idea. :-(
66     return name;
67 };
68
69 // Instance methods.
70 Test.Builder.create = function () {
71     var test = Test.Builder.Test;
72     Test.Builder.Test = null;
73     var ret = new Test.Builder();
74     Test.Builder.Test = test;
75     return ret.reset();
76 };
77
78 Test.Builder.prototype.reset = function () {
79     this.TestDied      = false;
80     this.HavePlan      = false;
81     this.NoPlan        = false;
82     this.CurrTest      = 0;
83     this.ExpectedTests = 0;
84     this.UseNums       = true;
85     this.NoHeader      = false;
86     this.NoEnding      = false;
87     this.TestResults   = [];
88     this.ToDo          = [];
89     this.Buffer       = [];
90     this.asyncs        = [0];
91     this.asyncID       = 0;
92     return this._setupOutput();
93 };
94
95 Test.Builder.prototype._print = function (msg) {
96     this.output().call(this, msg);
97 };
98
99 Test.Builder.prototype.warn = function (msg) {
100     this.warnOutput().apply(this, arguments);
101 };
102
103 Test.Builder.prototype.plan = function (arg) {
104     if (!arg) return;
105     if (this.HavePlan) Test.Builder.die("You tried to plan twice!");
106
107     if (!(arg instanceof Object))
108         Test.Builder.die("plan() doesn't understand " + arg);
109     for (var cmd in arg) {
110         if (cmd == 'tests') {
111             if (arg[cmd] == null) {
112                 TestBulder.die(
113                     "Got an undefined number of tests. Looks like you tried to "
114                     + "say how many tests you plan to run but made a mistake."
115                     + Test.Builder.LF
116                 );
117             } else if (!arg[cmd]) {
118                 Test.Builder.die(
119                     "You said to run 0 tests! You've got to run something."
120                     + Test.Builder.LF
121                 );
122             } else {
123                 this.expectedTests(arg[cmd]);
124             }
125         } else if (cmd == 'skipAll') {
126             this.skipAll(arg[cmd]);
127         } else if (cmd == 'noPlan' && arg[cmd]) {
128             this.noPlan();
129         } else {
130             Test.Builder.die("plan() doesn't understand "
131                              + cmd + (arg[cmd] ? (" " + arg[cmd]) : ''));
132         }
133     }
134 };
135
136 Test.Builder.prototype.expectedTests = function (max) {
137     if (max) {
138         if (isNaN(max)) {
139             Test.Builder.die(
140                 "Number of tests must be a postive integer. You gave it '"
141                 + max + "'." + Test.Builder.LF
142             );
143         }
144
145         this.ExpectedTests = max.valueOf();
146         this.HavePlan       = 1;
147         if (!this.noHeader()) this._print("1.." + max + Test.Builder.LF);
148     }
149     return this.ExpectedTests;
150 };
151
152 Test.Builder.prototype.noPlan = function () {
153     this.NoPlan   = 1;
154     this.HavePlan = 1;
155 };
156
157 Test.Builder.prototype.hasPlan = function () {
158     if (this.ExpectedTests) return this.ExpectedTests;
159     if (this.NoPlan) return 'noPlan';
160 };
161
162 Test.Builder.prototype.skipAll = function (reason) {
163     var out = "1..0";
164     if (reason) out += " # Skip " + reason;
165     out += Test.Builder.LF;
166     this.SkipAll = 1;
167     if (!this.noHeader()) this._print(out);
168     // Just throw and catch an exception.
169     window.onerror = function () { return true; }
170     throw new Error("__SKIP_ALL__");
171 };
172
173 Test.Builder.prototype.ok = function (test, desc) {
174     // test might contain an object that we don't want to accidentally
175     // store, so we turn it into a boolean.
176     test = !!test;
177
178     if (!this.HavePlan)
179         Test.Builder.die("You tried to run a test without a plan! Gotta have a plan.");
180
181     // I don't think we need to worry about threading in JavaScript.
182     this.CurrTest++;
183
184     // In case desc is a string overloaded object, force it to stringify.
185     if (desc) desc = desc.toString();
186
187     var startsNumber
188     if (desc != null && /^[\d\s]+$/.test(desc)) {
189         this.diag( "Your test description is '" + desc + "'. You shouldn't use",
190                    Test.Builder.LF,
191                    "numbers for your test names. Very confusing.");
192     }
193
194     var todo = this._todo();
195     // I don't think we need to worry about result beeing shared between
196     // threads.
197     var out = '';
198     var result = {};
199
200     if (test) {
201         result.ok        = true;
202         result.actual_ok = test;
203     } else {
204         out += 'not ';
205         result.ok        = todo ? true : false;
206         result.actual_ok = false;
207     }
208
209     out += 'ok';
210     if (this.useNumbers) out += ' ' + this.CurrTest;
211
212     if (desc == null) {
213         result.desc = '';
214     } else {
215         desc = desc.replace(Test.Builder.lineEndingRx, Test.Builder.LF + "# ");
216         // XXX Does this matter since we don't have a TestHarness?
217         desc.split('#').join('\\#'); // # # in a desc can confuse TestHarness.
218         out += ' - ' + desc;
219         result.desc = desc;
220     }
221
222     if (todo) {
223         todo = todo.replace(Test.Builder.lineEndingRx, Test.Builder.LF + "# ");
224         out += " # TODO " + todo;
225         result.reason = todo;
226         result.type   = 'todo';
227     } else {
228         result.reason = '';
229         result.type   = '';
230     }
231
232     this.TestResults[this.CurrTest - 1] = result;
233
234     out += Test.Builder.LF;
235     this._print(out);
236
237     if (!test) {
238         var msg = todo ? "Failed (TODO)" : "Failed";
239         // XXX Hrm, do I need this?
240         //$self_print_diag(Test.Builder.LF) if $ENV{HARNESS_ACTIVE};
241         this.diag("    " + msg + " test");
242     }
243     result.output = this.Buffer.splice(0).join('');
244     return test;
245 };
246
247 Test.Builder.prototype.isEq = function (got, expect, desc) {
248     if (got == null || expect == null) {
249         // undefined only matches undefined and nothing else
250         return this.isUndef(got, '==', expect, desc);
251     }
252     return this.cmpOK(got, '==', expect, desc);
253 };
254
255 Test.Builder.prototype.isNum = function (got, expect, desc) {
256     if (got == null || expect == null) {
257         // undefined only matches undefined and nothing else
258         return this.isUndef(got, '==', expect, desc);
259     }
260     return this.cmpOK(Number(got), '==', Number(expect), desc);
261 };
262
263 Test.Builder.prototype.isntEq = function (got, dontExpect, desc) {
264     if (got == null || dontExpect == null) {
265         // undefined only matches undefined and nothing else
266         return this.isUndef(got, '!=', dontExpect, desc);
267     }
268     return this.cmpOK(got, '!=', dontExpect, desc);
269 };
270
271 Test.Builder.prototype.isntNum = function (got, dontExpect, desc) {
272     if (got == null || dontExpect == null) {
273         // undefined only matches undefined and nothing else
274         return this.isUndef(got, '!=', dontExpect, desc);
275     }
276     return this.cmpOK(Number(got), '!=', Number(dontExpect), desc);
277 };
278
279 Test.Builder.prototype.like = function (val, regex, desc) {
280     return this._regexOK(val, regex, '=~', desc);
281 };
282
283 Test.Builder.prototype.unlike = function (val, regex, desc) {
284     return this._regexOK(val, regex, '!~', desc);
285 };
286
287 Test.Builder.prototype._regexOK = function (val, regex, cmp, desc) {
288     // Create a regex object.
289     var type = Test.Builder.typeOf(regex);
290     var ok;
291     if (type.toLowerCase() == 'string') {
292         // Create a regex object.
293         regex = new RegExp(regex);
294     } else {
295         if (type != 'RegExp') {
296             ok = this.ok(false, desc);
297             this.diag("'" + regex + "' doesn't look much like a regex to me.");
298             return ok;
299         }
300     }
301
302     if (val == null || typeof val != 'string') {
303         if (cmp == '=~') {
304             // The test fails.
305             ok = this.ok(false, desc);
306             this._diagLike(val, regex, cmp);
307         } else {
308             // undefined matches nothing (unlike in Perl, where undef =~ //).
309             ok = this.ok(true, desc);
310         }
311         return ok;
312     }
313
314     // Use val.match() instead of regex.test() in case they've set g.
315     var test = val.match(regex);
316     if (cmp == '!~') test = !test;
317     ok = this.ok(test, desc);
318     if (!ok) this._diagLike(val, regex, cmp);
319     return ok;
320 };
321
322 Test.Builder.prototype._diagLike = function (val, regex, cmp) {
323     var match = cmp == '=~' ? "doesn't match" : "      matches";
324     return this.diag(
325         "                  '" + val + "" + Test.Builder.LF +
326         "    " + match + " /" + regex.source + "/"
327     );
328 };
329
330 Test.Builder.prototype.cmpOK = function (got, op, expect, desc) {
331
332     var test;
333     if (Test.Builder.StringOps[op]) {
334         // Force string context.
335         test = eval("got.toString() " + Test.Builder.StringOps[op] + " expect.toString()");
336     } else {
337         test = eval("got " + op + " expect");
338     }
339
340     var ok = this.ok(test, desc);
341     if (!ok) {
342         if (/^(eq|==)$/.test(op)) {
343             this._isDiag(got, op, expect);
344         } else {
345             this._cmpDiag(got, op, expect);
346         }
347     }
348     return ok;
349 };
350
351 Test.Builder.prototype._cmpDiag = function (got, op, expect) {
352     if (got != null) got = "'" + got.toString() + "'";
353     if (expect != null) expect = "'" + expect.toString() + "'";
354     return this.diag("    " + got + Test.Builder.LF + "        " + op
355                      + Test.Builder.LF + "    " + expect);
356 };
357
358 Test.Builder.prototype._isDiag = function (got, op, expect) {
359     var args = [got, expect];
360     for (var i = 0; i < args.length; i++) {
361         if (args[i] != null) {
362             args[i] = op == 'eq' ? "'" + args[i].toString() + "'" : args[i].valueOf();
363         }
364     }
365
366     return this.diag(
367         "         got: " + args[0] + Test.Builder.LF +
368         "    expected: " + args[1] + Test.Builder.LF
369     );
370 };
371
372 Test.Builder.prototype.BAILOUT = function (reason) {
373     this._print("Bail out! " + reason);
374     // Just throw and catch an exception.
375     window.onerror = function () {
376         // XXX Do something to tell TestHarness it was a bailout?
377         return true;
378     }
379     throw new Error("__BAILOUT__");
380 };
381
382 Test.Builder.prototype.skip = function (why) {
383     if (!this.HavePlan)
384         Test.Builder.die("You tried to run a test without a plan! Gotta have a plan.");
385
386     // In case desc is a string overloaded object, force it to stringify.
387     if (why) why = why.toString().replace(Test.Builder.lineEndingRx,
388                                           Test.Builder.LF+ "# ");
389
390     this.CurrTest++;
391     this.TestResults[this.CurrTest - 1] = {
392         ok:        true,
393         actual_ok: true,
394         desc:      '',
395         type:      'skip',
396         reason:    why
397     };
398
399     var out = "ok";
400     if (this.useNumbers) out += ' ' + this.CurrTest;
401     out    += " # skip " + why + Test.Builder.LF;
402     this._print(out);
403     this.TestResults[this.CurrTest - 1].output =
404       this.Buffer.splice(0).join('');
405     return true;
406 };
407
408 Test.Builder.prototype.todoSkip = function (why) {
409     if (!this.HavePlan)
410         Test.Builder.die("You tried to run a test without a plan! Gotta have a plan.");
411
412     // In case desc is a string overloaded object, force it to stringify.
413     if (why) why = why.toString().replace(Test.Builder.lineEndingRx,
414                                           Test.Builder.LF + "# ");
415     
416
417     this.CurrTest++;
418     this.TestResults[this.CurrTest - 1] = {
419         ok:        true,
420         actual_ok: false,
421         desc:      '',
422         type:      'todo_skip',
423         reason:    why
424     };
425
426     var out = "not ok";
427     if (this.useNumbers) out += ' ' + this.CurrTest;
428     out    += " # TODO & SKIP " + why + Test.Builder.LF;
429     this._print(out);
430     this.TestResults[this.CurrTest - 1].output =
431       this.Buffer.splice(0).join('');
432     return true;
433 };
434
435 Test.Builder.prototype.skipRest = function (reason) {
436     var out = "# Skip";
437     if (reason) out += " " + reason;
438     out += Test.Builder.LF;
439     if (this.NoPlan) this.skip(reason);
440     else {
441         for (var i = this.CurrTest; i < this.ExpectedTests; i++) {
442             this.skip(reason);
443         }
444     }
445     // Just throw and catch an exception.
446     window.onerror = function () { return true; }
447     throw new Error("__SKIP_REST__");
448 };
449
450 Test.Builder.prototype.useNumbers = function (useNums) {
451     if (useNums != null) this.UseNums = useNums;
452     return this.UseNums;
453 };
454
455 Test.Builder.prototype.noHeader = function (noHeader) {
456     if (noHeader != null) this.NoHeader = !!noHeader;
457     return this.NoHeader;
458 };
459
460 Test.Builder.prototype.noEnding = function (noEnding) {
461     if (noEnding != null) this.NoEnding = !!noEnding;
462     return this.NoEnding;
463 };
464
465 Test.Builder.prototype.diag = function () {
466     if (!arguments.length) return;
467
468     var msg = '# ';
469     // Join each agument and escape each line with a #.
470     for (i = 0; i < arguments.length; i++) {
471         // Replace any newlines.
472         msg += arguments[i].toString().replace(Test.Builder.lineEndingRx,
473                                                Test.Builder.LF + "# ");
474     }
475
476     // Append a new line to the end of the message if there isn't one.
477     if (!(new RegExp(Test.Builder.LF + '$').test(msg)))
478         msg += Test.Builder.LF;
479     // Append the diag message to the most recent result.
480     return this._printDiag(msg);
481 };
482
483 Test.Builder.prototype._printDiag = function () {
484     var fn = this.todo() ? this.todoOutput() : this.failureOutput();
485     fn.apply(this, arguments);
486     return false;
487 };
488
489 Test.Builder.prototype.output = function (fn) {
490     if (fn != null) {
491         var buffer = this.Buffer;
492         this.Output = function (msg) { buffer.push(msg); fn(msg) };
493     }
494     return this.Output;
495 };
496
497 Test.Builder.prototype.failureOutput = function (fn) {
498     if (fn != null) {
499         var buffer = this.Buffer;
500         this.FailureOutput = function (msg) { buffer.push(msg); fn(msg) };
501     }
502     return this.FailureOutput;
503 };
504
505 Test.Builder.prototype.todoOutput = function (fn) {
506     if (fn != null) {
507         var buffer = this.Buffer;
508         this.TodoOutput = function (msg) { buffer.push(msg); fn(msg) };
509     }
510     return this.TodoOutput;
511 };
512
513 Test.Builder.prototype.endOutput = function (fn) {
514     if (fn != null) {
515         var buffer = this.Buffer;
516         this.EndOutput = function (msg) { buffer.push(msg); fn(msg) };
517     }
518     return this.EndOutput;
519 };
520
521 Test.Builder.prototype.warnOutput = function (fn) {
522     if (fn != null) {
523         var buffer = this.Buffer;
524         this.WarnOutput = function (msg) { buffer.push(msg); fn(msg) };
525     }
526     return this.WarnOutput;
527 };
528
529 Test.Builder.prototype._setupOutput = function () {
530     if (Test.PLATFORM == 'browser') {
531         var writer = function (msg) {
532             // I'm sure that there must be a more efficient way to do this,
533             // but if I store the node in a variable outside of this function
534             // and refer to it via the closure, then things don't work right
535             // --the order of output can become all screwed up (see
536             // buffer.html).  I have no idea why this is.
537             var node = document.getElementById("test");
538             if (node) {
539                 // This approach is neater, but causes buffering problems when
540                 // mixed with document.write. See tests/buffer.html.
541                 //node.appendChild(document.createTextNode(msg));
542                 //return;
543                 for (var i = 0; i < node.childNodes.length; i++) {
544                     if (node.childNodes[i].nodeType == 3 /* Text Node */) {
545                         // Append to the node and scroll down.
546                         node.childNodes[i].appendData(msg);
547                         window.scrollTo(0, document.body.offsetHeight
548                                         || document.body.scrollHeight);
549                         return;
550                     }
551                 }
552
553                 // If there was no text node, add one.
554                 node.appendChild(document.createTextNode(msg));
555                 window.scrollTo(0, document.body.offsetHeight
556                                 || document.body.scrollHeight);
557                 return;
558             }
559
560             // Default to the normal write and scroll down...
561             document.write(msg);
562             window.scrollTo(0, document.body.offsetHeight
563                             || document.body.scrollHeight);
564         };
565
566         this.output(writer);
567         this.failureOutput(writer);
568         this.todoOutput(writer);
569         this.endOutput(writer);
570
571         if (window) {
572             if (window.alert.apply) this.warnOutput(window.alert, window);
573             else this.warnOutput(function (msg) { window.alert(msg) });
574         }
575
576     } else if (Test.PLATFORM == 'director') {
577         // Macromedia-Adobe:Director MX 2004 Support
578         // XXX Is _player a definitive enough object?
579         // There may be an even more explicitly Director object.
580         this.output(trace);       
581         this.failureOutput(trace);
582         this.todoOutput(trace);
583         this.warnOutput(trace);
584     }
585
586     return this;
587 };
588
589 Test.Builder.prototype.currentTest = function (num) {
590     if (num == null) return this.CurrTest;
591
592     if (!this.HavePlan)
593         Test.Builder.die("Can't change the current test number without a plan!");
594     this.CurrTest = num;
595     if (num > this.TestResults.length ) {
596         var reason = 'incrementing test number';
597         for (i = this.TestResults.length; i < num; i++) {
598             this.TestResults[i] = {
599                 ok:        true, 
600                 actual_ok: null,
601                 reason:    reason,
602                 type:      'unknown', 
603                 name:      null,
604                 output:    'ok - ' + reason + Test.Builder.LF
605             };
606         }
607     } else if (num < this.TestResults.length) {
608         // IE requires the second argument to truncate the array.
609         this.TestResults.splice(num, this.TestResults.length);
610     }
611     return this.CurrTest;
612 };
613
614 Test.Builder.prototype.summary = function () {
615     var results = new Array(this.TestResults.length);
616     for (var i = 0; i < this.TestResults.length; i++) {
617         results[i] = this.TestResults[i]['ok'];
618     }
619     return results
620 };
621
622 Test.Builder.prototype.details = function () {
623     return this.TestResults;
624 };
625
626 Test.Builder.prototype.todo = function (why, howMany) {
627     if (howMany) this.ToDo = [why, howMany];
628     return this.ToDo[1];
629 };
630
631 Test.Builder.prototype._todo = function () {
632     if (this.ToDo[1]) {
633         if (this.ToDo[1]--) return this.ToDo[0];
634         this.ToDo = [];
635     }
636     return false;
637 };
638
639 Test.Builder.prototype._sanity_check = function () {
640     Test.Builder._whoa(
641         this.CurrTest < 0,
642         'Says here you ran a negative number of tests!'
643     );
644
645     Test.Builder._whoa(
646         !this.HavePlan && this.CurrTest, 
647         'Somehow your tests ran without a plan!'
648     );
649
650     Test.Builder._whoa(
651         this.CurrTest != this.TestResults.length,
652         'Somehow you got a different number of results than tests ran!'
653     );
654 };
655
656 Test.Builder.prototype._notifyHarness = function () {
657     // Special treatment for the browser harness.
658     if (typeof window != 'undefined' && window.parent
659         && window.parent.Test && window.parent.Test.Harness) {
660         window.parent.Test.Harness.Done++;
661     }
662 };
663
664 Test.Builder.prototype._ending = function () {
665     if (this.Ended) return;
666     this.Ended = true;
667     if (this.noEnding()) {
668         this._notifyHarness();
669         return;
670     }
671     this._sanity_check();
672     var out = this.endOutput();
673
674     // Figure out if we passed or failed and print helpful messages.
675     if( this.TestResults.length ) {
676         // The plan?  We have no plan.
677         if (this.NoPlan) {
678             if (!this.noHeader())
679                 this._print("1.." + this.CurrTest + Test.Builder.LF);
680             this.ExpectedTests = this.CurrTest;
681         }
682
683         var numFailed = 0;
684         for (var i = 0; i < this.TestResults.length; i++) {
685             if (!this.TestResults[i]) numFailed++;
686         }
687         numFailed += Math.abs(
688             this.ExpectedTests - this.TestResults.length
689         );
690
691         if (this.CurrTest < this.ExpectedTests) {
692             var s = this.ExpectedTests == 1 ? '' : 's';
693             out(
694                 "# Looks like you planned " + this.ExpectedTests + " test"
695                 + s + " but only ran " + this.CurrTest + "." + Test.Builder.LF
696             );
697         } else if (this.CurrTest > this.ExpectedTests) {
698            var numExtra = this.CurrTest - this.ExpectedTests;
699             var s = this.ExpectedTests == 1 ? '' : 's';
700             out(
701                 "# Looks like you planned " + this.ExpectedTests + " test"
702                 + s + " but ran " + numExtra + " extra." + Test.Builder.LF
703             );
704         } else if (numFailed) {
705             var s = numFailed == 1 ? '' : 's';
706             out(
707                 "# Looks like you failed " + numFailed + "test" + s + " of "
708                 + this.ExpectedTests + "." + Test.Builder.LF
709             );
710         }
711
712         if (this.TestDied) {
713             out(
714                 "# Looks like your test died just after " 
715                 + this.CurrTest + "." + Test.Builder.LF
716             );
717         }
718
719     } else if (!this.SkipAll) {
720         // skipAll requires no status output.
721         if (this.TestDied) {
722             out(
723                 "# Looks like your test died before it could output anything."
724                 + Test.Builder.LF
725             );
726         } else {
727             out("# No tests run!" + Test.Builder.LF);
728         }
729     }
730     this._notifyHarness();
731 };
732
733 Test.Builder.prototype.isUndef = function (got, op, expect, desc) {
734     // Undefined only matches undefined, so we don't need to cast anything.
735     var test = eval("got " + (Test.Builder.StringOps[op] || op) + " expect");
736     this.ok(test, desc);
737     if (!test) this._isDiag(got, op, expect);
738     return test;
739 };
740
741 if (window) {
742     // Set up an onload function to end all tests.
743     window.onload = function () {
744         for (var i = 0; i < Test.Builder.Instances.length; i++) {
745             // The main process is always async ID 0.
746             Test.Builder.Instances[i].endAsync(0);
747         }
748     };
749
750     // Set up an exception handler. This is so that we can capture deaths but
751     // still output information for TestHarness to pick up.
752     window.onerror = function (msg, url, line) {
753         // Output the exception.
754         Test.Builder.Test.TestDied = true;
755         Test.Builder.Test.diag("Error in " + url + " at line " + line + ": " + msg);
756         return true;
757     };
758 };
759
760 Test.Builder.prototype.beginAsync = function (timeout) {
761         var id = ++this.asyncID;
762     if (timeout && window && window.setTimeout) {
763         // Are there other ways of setting timeout in non-browser settings?
764         var aTest = this;
765         this.asyncs[id] = window.setTimeout(
766             function () { aTest.endAsync(id) }, timeout
767         );
768     } else {
769         // Make sure it's defined.
770         this.asyncs[id] = 0;
771     }
772         return id;
773 };
774
775 Test.Builder.prototype.endAsync = function (id) {
776     if (this.asyncs[id] == undefined) return;
777     if (this.asyncs[id]) {
778                 // Remove the timeout
779                 window.clearTimeout(this.asyncs[id]);
780         }
781     if (--this.asyncID < 0) this._ending();
782 };
783
784 Test.Builder.exporter = function (pkg, root) {
785     if (typeof root == 'undefined') {
786         if      (Test.PLATFORM == 'browser')  root = window;
787         else if (Test.PLATFORM == 'director') root = _global;
788         else throw new Error("Platform unknown");
789     }
790     for (var i = 0; i < pkg.EXPORT.length; i++) {
791         if (typeof root[pkg.EXPORT[i]] == 'undefined')
792             root[pkg.EXPORT[i]] = pkg[pkg.EXPORT[i]];
793     }
794 };// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
795 // Create a namespace for ourselves.
796
797 // Set up package.
798 if (typeof JSAN != 'undefined') new JSAN().use('Test.Builder');
799 else {
800     if (typeof Test == 'undefined' || typeof Test.Builder == 'undefined')
801         throw new Error(
802             "You must load either JSAN or Test.Builder "
803             + "before loading Test.More"
804         );
805 }
806
807 Test.More = {};
808 Test.More.EXPORT = [
809     'plan',
810     'ok', 'is', 'isnt',
811     'like', 'unlike',
812     'cmpOK', 'canOK', 'isaOK',
813     'pass', 'fail', 'diag', 'loadOK',
814     'skip', 'todo', 'todoSkip', 'skipRest',
815     'isDeeply', 'isSet', 'isa'
816 ];
817 Test.More.EXPORT_TAGS = { ':all': Test.More.EXPORT };
818 Test.More.VERSION     = '0.11';
819
820 Test.More.ShowDiag = true;
821 Test.Builder.DNE = { dne: 'Does not exist' };
822 Test.More.Test = new Test.Builder();
823 Test.More.builder = function () { return Test.More.Test; };
824
825 Test.More.plan = function (cmds) {
826     if (cmds.noDiag) {
827         Test.More.ShowDiag = false;
828         delete cmds.noDiag;
829     }
830     return Test.More.Test.plan.apply(Test.More.Test, [cmds]);
831 };
832
833 Test.More.ok = function (test, desc) {
834     return Test.More.Test.ok(test, desc);
835 };
836
837 Test.More.is = function (got, expect, desc) {
838     return Test.More.Test.isEq(got, expect, desc);
839 };
840
841 Test.More.isnt = function (got, expect, desc) {
842     return Test.More.Test.isntEq(got, expect, desc);
843 };
844
845 Test.More.like = function (val, regex, desc) {
846     return Test.More.Test.like(val, regex, desc);
847 };
848
849 Test.More.unlike = function (val, regex, desc) {
850     return Test.More.Test.unlike(val, regex, desc);
851 };
852
853 Test.More.cmpOK = function (got, op, expect, desc) {
854     return Test.More.Test.cmpOK(got, op, expect, desc);
855 };
856
857 Test.More.canOK = function (proto) {
858     var ok;
859     // Make sure they passed some method names for us to check.
860     if (!arguments.length > 1) {
861         ok = Test.More.Test.ok(false, clas + '.can(...)');
862         Test.More.Test.diag('    canOK() called with no methods');
863         return ok;
864     }
865
866     // Get the class name and the prototype.
867     var clas;
868     if (typeof proto == 'string') {
869         // We just have a class name.
870         clas = proto;
871         proto = eval(clas + '.prototype');
872     } else {
873         // We have an object or something that can be converted to an object.
874         clas = Test.Builder.typeOf(proto);
875         proto = proto.constructor.prototype;
876     }
877
878     var nok = [];
879     for (var i = 1; i < arguments.length; i++) {
880         var method = arguments[i];
881         if (typeof proto[method] != 'function') nok.push(method);
882     }
883
884     // There'es no can() method in JavaScript, but what the hell!
885     var desc = clas + ".can('" + (arguments.length == 2 ? arguments[1] : '...') + "')";
886     ok = Test.More.Test.ok(!nok.length, desc);
887     for (var i = 0; i < nok.length; i++) {
888         Test.More.Test.diag('    ' + clas + ".can('" + nok[i] + "') failed");
889     }
890     return ok;
891 };
892
893 Test.More.isaOK = function (object, clas, objName) {
894     var mesg;
895     if (objName == null) objName = 'The object';
896     var name = objName + ' isa ' + clas;
897     if (object == null) {
898         mesg = objName + " isn't defined";
899     } else if (!Test.More._isRef(object)) {
900         mesg = objName + " isn't a reference";
901     } else {
902         var ctor = eval(clas);
903         if (Object.isPrototypeOf) {
904             // With JavaScript 1.5, we can determine inheritance.
905             if (!ctor.prototype.isPrototypeOf(object)) {
906                 mesg = objName + " isn't a '" + clas + "' it's a '"
907                   + Test.Builder.typeOf(object) + "'";
908             }
909         } else {
910             // We can just determine what constructor was used. This will
911             // not work for inherited constructors.
912             if (object.constructor != ctor)
913                 mesg = objName + " isn't a '" + clas + "' it's a '"
914                   + Test.Builder.typeOf(object) + '"';
915         }
916     }
917     
918     var ok;
919     if (mesg) {
920         ok = Test.More.Test.ok(false, name);
921         Test.More.Test.diag('    ' + mesg);
922     } else {
923         ok = Test.More.Test.ok(true, name);
924     }
925
926     return ok;
927 };
928
929 Test.More.pass = function (name) {
930     return Test.More.Test.ok(true, name);
931 };
932
933 Test.More.fail = function (name) {
934     return Test.More.Test.ok(false, name);
935 };
936
937 Test.More.diag = function () {
938     if (!Test.More.ShowDiag) return;
939     return Test.More.Test.diag.apply(Test.More.Test, arguments);
940 };
941
942 // Use this instead of use_ok and require_ok.
943 Test.More.loadOK = function () {
944     // XXX What do I do here? Eval?
945     // XXX Just always fail for now, to keep people from using it just yet.
946     return false;
947 };
948
949 Test.More.skip = function (why, howMany) {
950     if (howMany == null) {
951         if (!Test.Builder.NoPlan)
952             Test.More.Test.warn("skip() needs to know howMany tests are in the block");
953         howMany = 1;
954     }
955     for (i = 0; i < howMany; i++) {
956         Test.More.Test.skip(why);
957     }
958 };
959
960 Test.More.todo = function (why, howMany) {
961     if (howMany == null) {
962         if (!Test.Builder.NoPlan)
963             Test.More.Test.warn("todo() needs to know howMany tests are in the block");
964         howMany = 1;
965     }
966     return Test.More.Test.todo(why, howMany);
967 };
968
969 Test.More.todoSkip = function (why, howMany) {
970     if (howMany == null) {
971         if (!Test.Builder.NoPlan)
972             Test.More.Test.warn("todoSkip() needs to know howMany tests are in the block");
973         howMany = 1;
974     }
975
976     for (i = 0; i < howMany; i++) {
977         Test.More.Test.todoSkip(why);
978     }
979 };
980
981 Test.More.skipRest = function (why) {
982     Test.More.Test.skipRest(why);
983 };
984
985 Test.More.isDeeply = function (it, as, name) {
986     if (arguments.length != 2 && arguments.length != 3) {
987         Test.More.Test.warn(
988             'isDeeply() takes two or three args, you gave '
989             + arguments.length + "."
990         );
991     }
992
993     var ok;
994     // ^ is the XOR operator.
995     if (Test.More._isRef(it) ^ Test.More._isRef(as)) {
996         // One's a reference, one isn't.
997         ok = false;
998     } else if (!Test.More._isRef(it) && !Test.More._isRef(as)) {
999         // Neither is an object.
1000         ok = Test.More.Test.isEq(it, as, name);
1001     } else {
1002         // We have two objects. Do a deep comparison.
1003         var stack = [], seen = [];
1004         if ( Test.More._deepCheck(it, as, stack, seen)) {
1005             ok = Test.More.Test.ok(true, name);
1006         } else {
1007             ok = Test.More.Test.ok(false, name);
1008             Test.More.Test.diag(Test.More._formatStack(stack));
1009         }
1010     }
1011     return ok;
1012 };
1013
1014 Test.More._deepCheck = function (e1, e2, stack, seen) {
1015     var ok = false;
1016     // Either they're both references or both not.
1017     var sameRef = !(!Test.More._isRef(e1) ^ !Test.More._isRef(e2));
1018     if (e1 == null && e2 == null) {
1019         ok = true;
1020     } else if (e1 != null ^ e2 != null) {
1021         ok = false;
1022     } else if (e1 == Test.More.DNE ^ e2 == Test.More.DNE) {
1023         ok = false;
1024     } else if (sameRef && e1 == e2) {
1025         // Handles primitives and any variables that reference the same
1026         // object, including functions.
1027         ok = true;
1028     } else if (isa(e1, 'Array') && isa(e2, 'Array')) {
1029         ok = Test.More._eqArray(e1, e2, stack, seen);
1030     } else if (typeof e1 == "object" && typeof e2 == "object") {
1031         ok = Test.More._eqAssoc(e1, e2, stack, seen);
1032     } else {
1033         // If we get here, they're not the same (function references must
1034         // always simply rererence the same function).
1035         stack.push({ vals: [e1, e2] });
1036         ok = false;
1037     }
1038     return ok;
1039 };
1040
1041 Test.More._isRef = function (object) {
1042     var type = typeof object;
1043     return type == 'object' || type == 'function';
1044 };
1045
1046 Test.More._formatStack = function (stack) {
1047     var variable = '$Foo';
1048     for (var i = 0; i < stack.length; i++) {
1049         var entry = stack[i];
1050         var type = entry['type'];
1051         var idx = entry['idx'];
1052         if (idx != null) {
1053             if (/^\d+$/.test(idx)) {
1054                 // Numeric array index.
1055                 variable += '[' + idx + ']';
1056             } else {
1057                 // Associative array index.
1058                 idx = idx.replace("'", "\\'");
1059                 variable += "['" + idx + "']";
1060             }
1061         }
1062     }
1063
1064     var vals = stack[stack.length-1]['vals'].slice(0, 2);
1065     var vars = [
1066         variable.replace('$Foo',     'got'),
1067         variable.replace('$Foo',     'expected')
1068     ];
1069
1070     var out = "Structures begin differing at:" + Test.Builder.LF;
1071     for (var i = 0; i < vals.length; i++) {
1072         var val = vals[i];
1073         if (val == null) {
1074             val = 'undefined';
1075         } else {
1076              val == Test.More.DNE ? "Does not exist" : "'" + val + "'";
1077         }
1078     }
1079
1080     out += vars[0] + ' = ' + vals[0] + Test.Builder.LF;
1081     out += vars[1] + ' = ' + vals[1] + Test.Builder.LF;
1082     
1083     return '    ' + out;
1084 };
1085
1086 /* Commented out per suggestion from Michael Schwern. It turned out to be
1087    confusing to Test::More users because it isn't atually a test. Use
1088    isDeeply() instead and don't worry about it.
1089
1090 Test.More.eqArray = function (a1, a2) {
1091     if (!isa(a1, 'Array') || !isa(a2, 'Array')) {
1092         Test.More.Test.warn("Non-array passed to eqArray()");
1093         return false;
1094     }
1095     return Test.More._eqArray(a1, a2, [], []);
1096 };
1097
1098 */
1099
1100 Test.More._eqArray = function (a1, a2, stack, seen) {
1101     // Return if they're the same object.
1102     if (a1 == a2) return true;
1103
1104     // JavaScript objects have no unique identifiers, so we have to store
1105     // references to them all in an array, and then compare the references
1106     // directly. It's slow, but probably won't be much of an issue in
1107     // practice. Start by making a local copy of the array to as to avoid
1108     // confusing a reference seen more than once (such as [a, a]) for a
1109     // circular reference.
1110     for (var j = 0; j < seen.length; j++) {
1111         if (seen[j][0] == a1) {
1112             return seen[j][1] == a2;
1113         }
1114     }
1115
1116     // If we get here, we haven't seen a1 before, so store it with reference
1117     // to a2.
1118     seen.push([ a1, a2 ]);
1119
1120     var ok = true;
1121     // Only examines enumerable attributes. Only works for numeric arrays!
1122     // Associative arrays return 0. So call _eqAssoc() for them, instead.
1123     var max = a1.length > a2.length ? a1.length : a2.length;
1124     if (max == 0) return Test.More._eqAssoc(a1, a2, stack, seen);
1125     for (var i = 0; i < max; i++) {
1126         var e1 = i > a1.length - 1 ? Test.More.DNE : a1[i];
1127         var e2 = i > a2.length - 1 ? Test.More.DNE : a2[i];
1128         stack.push({ type: 'Array', idx: i, vals: [e1, e2] });
1129         if (ok = Test.More._deepCheck(e1, e2, stack, seen)) {
1130             stack.pop();
1131         } else {
1132             break;
1133         }
1134     }
1135     return ok;
1136 };
1137
1138 /* Commented out per suggestion from Michael Schwern. It turned out to be
1139    confusing to Test::More users because it isn't atually a test. Use
1140    isDeeply() instead and don't worry about it.
1141
1142 Test.More.eqHash = function () {
1143     return eqAssoc.apply(this, arguments);
1144 };
1145
1146 Test.More.eqAssoc = function (o1, o2) {
1147     if (typeof o1 != "object" || typeof o2 != "object") {
1148         Test.More.Test.warn("Non-object passed to eqAssoc()");
1149         return false;
1150     } else if (   (isa(o1, 'Array') && o1.length > 0)
1151                || (isa(o2, 'Array') && o2.length > 0))
1152     {
1153         Test.More.Test.warn("Ordered array passed to eqAssoc()");
1154         return false;
1155     }
1156     return Test.More._eqAssoc(o1, o2, [], []);
1157 };
1158
1159 */
1160
1161 Test.More._eqAssoc = function (o1, o2, stack, seen) {
1162     // Return if they're the same object.
1163     if (o1 == o2) return true;
1164
1165     // JavaScript objects have no unique identifiers, so we have to store
1166     // references to them all in an array, and then compare the references
1167     // directly. It's slow, but probably won't be much of an issue in
1168     // practice. Start by making a local copy of the array to as to avoid
1169     // confusing a reference seen more than once (such as [a, a]) for a
1170     // circular reference.
1171     seen = seen.slice(0);
1172     for (var j = 0; j < seen.length; j++) {
1173         if (seen[j][0] == o1) {
1174             return seen[j][1] == o2;
1175         }
1176     }
1177
1178     // If we get here, we haven't seen o1 before, so store it with reference
1179     // to o2.
1180     seen.push([ o1, o2 ]);
1181
1182     // They should be of the same class.
1183
1184     var ok = true;
1185     // Only examines enumerable attributes.
1186     var o1Size = 0; for (var i in o1) o1Size++;
1187     var o2Size = 0; for (var i in o2) o2Size++;
1188     var bigger = o1Size > o2Size ? o1 : o2;
1189     for (var i in bigger) {
1190         var e1 = o1[i] == undefined ? Test.More.DNE : o1[i];
1191         var e2 = o2[i] == undefined ? Test.More.DNE : o2[i];
1192         stack.push({ type: 'Object', idx: i, vals: [e1, e2] });
1193         if (ok = Test.More._deepCheck(e1, e2, stack, seen)) {
1194             stack.pop();
1195         } else {
1196             break;
1197         }
1198     }
1199     return ok;
1200 };
1201
1202 Test.More._eqSet = function (a1, a2, stack, seen) {
1203     return Test.More._eqArray(a1.slice(0).sort(), a2.slice(0).sort(), stack, seen);
1204 };
1205
1206 Test.More.isSet = function (a1, a2, desc) {
1207     var stack = [], seen = [], ok = true;
1208     if (Test.More._eqSet(a1, a2, stack, seen)) {
1209         ok = Test.More.Test.ok(true, desc);
1210     } else {
1211         ok = Test.More.Test.ok(false, desc);
1212         Test.More.Test.diag(Test.More._formatStack(stack));
1213     }
1214     return ok;
1215 };
1216
1217 Test.More.isa = function (object, clas) {
1218     return Test.Builder.typeOf(object) == clas;
1219 };
1220
1221 // Handle exporting.
1222 if (typeof JSAN == 'undefined') Test.Builder.exporter(Test.More);
1223 // # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
1224
1225 // Set up namespace.
1226 if (typeof self != 'undefined') {
1227     //Browser
1228     if (typeof Test == 'undefined') Test = {PLATFORM: 'browser'};
1229     else Test.PLATFORM = 'browser';
1230 } else if (typeof _player != 'undefined'){
1231     //Director
1232     if (typeof _global.Test != "object") _global.Test = {PLATFORM: 'director'};
1233     else _global.Test.PLATFORM = 'director';
1234 } else {
1235     throw new Error("Test.Harness does not support your platform");
1236 }
1237
1238 Test.Harness = function () {};
1239 Test.Harness.VERSION = '0.11';
1240 Test.Harness.Done = 0;
1241
1242 // Stoopid IE.
1243 Test.Harness.LF = typeof document != "undefined"
1244                   && typeof document.all != "undefined"
1245   ? "\r"
1246   : "\n";
1247
1248 Test.Harness.prototype.isDone = Test.Harness.isDone;
1249
1250 /*
1251
1252     bonus           Number of individual todo tests unexpectedly passed
1253     ran             Number of individual tests ran
1254     ok              Number of individual tests passed
1255     subSkipped      Number of individual tests skipped
1256     todo            Number of individual todo tests
1257
1258     files           Number of test files ran
1259     good            Number of test files passed
1260     bad             Number of test files failed
1261     tests           Number of test files originally given
1262     skipped         Number of test files skipped
1263
1264 */
1265
1266 Test.Harness.prototype.bonus      = 0;
1267 Test.Harness.prototype.ran        = 0;
1268 Test.Harness.prototype.ok         = 0;
1269 Test.Harness.prototype.subSkipped = 0;
1270 Test.Harness.prototype.todo       = 0;
1271 Test.Harness.prototype.files      = 0;
1272 Test.Harness.prototype.good       = 0;
1273 Test.Harness.prototype.bad        = 0;
1274 Test.Harness.prototype.tests      = 0;
1275 Test.Harness.prototype.skipped    = 0;
1276 Test.Harness.prototype.failures   = [];
1277
1278 Test.Harness.runTests = function () {
1279     // XXX Can't handle inheritance, right? Or can we?
1280     var harness = new Test.Harness();
1281     harness.runTests.apply(harness, arguments);
1282 };
1283
1284 Test.Harness.prototype.outFileNames = function (files) {
1285     var len = 0;
1286     for (var i = 0; i < files.length; i++) {
1287         if (files[i].length > len) len = files[i].length;
1288     }
1289     len += 3;
1290     var ret = [];
1291     for (var i = 0; i < files.length; i++) {
1292         var outName = files[i];
1293         var add = len - files[i].length;
1294         // Where is Perl's x operator when I need it??
1295         for (var j = 0; j < add; j++) {
1296             outName += '.';
1297         }
1298         ret.push(outName);
1299     }
1300     return ret;
1301 };
1302
1303 Test.Harness.prototype.outputResults = function (test, file, fn, attrs) {
1304     this.tests++;
1305     this.ran += test.TestResults.length;
1306     var fails = [];
1307     var track = {
1308       fn:       file,
1309       total:    test.expectedTests,
1310       ok:       0,
1311       failList: []
1312     };
1313
1314     if (test.TestResults.length) {
1315         this.files++;
1316         var pass = true;
1317         for (var i = 0; i < test.TestResults.length; i++) {
1318             var ok = "ok";
1319             if (test.TestResults[i].ok) {
1320                 this.ok++;
1321                 track.ok++
1322                 if (test.TestResults[i].type == 'todo') {
1323                     // Handle unexpected pass.
1324                     if (test.TestResults[i].actualOK) this.bonus++;
1325                     this.todo ++;
1326                 } else if (test.TestResults[i].type == 'skip') this.subSkipped++;
1327             } else {
1328                 if (test.TestResults[i].type == 'todo') {
1329                     // Expected failure.
1330                     this.todo++;
1331                 } else {
1332                     pass = false;
1333                     track.failList.push(i + 1);
1334                 }
1335                 ok = "not ok"; // XXX Need to handle TODO and TODO Skipped.
1336             }
1337             
1338             if (!pass || attrs.verbose) fn(test.TestResults[i].output);
1339         }
1340         
1341         if (pass) {
1342             this.good++;
1343             fn("ok" + Test.Harness.LF);
1344         } else {
1345             this.bad++;
1346             var err = "NOK # Failed ";
1347             if (track.failList.length == 1) {
1348                 err += "test " + track.failList[0];
1349             } else {
1350                 err += "tests " + this._failList(track.failList);
1351             }
1352             fn(err + " in " + file + Test.Harness.LF);
1353         }
1354     } else if (test.SkipAll){
1355         // All tests skipped.
1356         this.skipped++;
1357         this.good++;
1358         fn("1..0 # Skip 1" + Test.Harness.LF);
1359     } else {
1360         // Wha happened? Tests ran, but no results!
1361         this.files++;
1362         this.bad++;
1363         fn("FAILED before any test output arrived" + Test.Harness.LF);
1364     }
1365     if (track.failList.length) this.failures.push(track);
1366 };
1367
1368 Test.Harness.prototype._allOK = function () {
1369     return this.bad == 0 && (this.ran || this.skipped) ? true : false;
1370 };
1371
1372 Test.Harness.prototype.outputSummary = function (fn, time) {
1373     var bonusmsg = this._bonusmsg();
1374     var pct;
1375     if (this._allOK()) {
1376         fn("All tests successful" + bonusmsg + '.' + Test.Harness.LF);
1377     } else if (!this.tests) {
1378         fn("FAILED—no tests were run for some reason." + Test.Harness.LF);
1379     } else if (!this.ran) {
1380         var blurb = this.tests == 1 ? "file" : "files";
1381         fn("FAILED—" + this.tests + " test " + blurb + " could be run, "
1382            + "alas—no output ever seen." + Test.Harness.LF);
1383     } else {
1384         pct = this.good / this.tests * 100;
1385         var pctOK = 100 * this.ok / this.ran;
1386         var subpct = (this.ran - this.ok) + "/" + this.ran
1387           + " subtests failed, " + pctOK.toPrecision(4) + "% okay.";
1388
1389         if (this.bad) {
1390             bonusmsg = bonusmsg.replace(/^,?\s*/, '');
1391             if (bonusmsg) fn(bonusmsg + '.' + Test.Harness.LF);
1392             fn("Failed " + this.bad + "/" + this.tests + " test scripts, "
1393                + pct.toPrecision(4) + "% okay. " + subpct + Test.Harness.LF);
1394         }
1395         this.formatFailures(fn);
1396     }
1397
1398     fn("Files=" + this.tests + ", Tests=" + this.ran + ", " + (time / 1000)
1399        + " seconds" + Test.Harness.LF);
1400 };
1401
1402 Test.Harness.prototype.formatFailures = function () {
1403     var table = '';
1404     var failedStr = "Failed Test";
1405     var middleStr = " Total Fail  Failed  ";
1406     var listStr = "List of Failed";
1407     var cols = 80;
1408
1409     // Figure out our longest name string for formatting purposes.
1410     var maxNamelen = failedStr.length;
1411     for (var i = 0; i < this.failures.length; i++) {
1412         var len = this.failures[i].length;
1413         if (len > maxNamelen) maxNamelen = len;
1414     }
1415
1416     var listLen = cols - middleStr.length - maxNamelen.length;
1417     if (listLen < listStr.length) {
1418         listLen = listStr.length;
1419         maxNamelen = cols - middleStr.length - listLen;
1420         if (maxNamelen < failedStr.length) {
1421             maxNamelen = failedStr.length;
1422             cols = maxNamelen + middleStr.length + listLen;
1423         }
1424     }
1425
1426     var out = failedStr;
1427     if (out.length < maxNamelen) {
1428         for (var j = out.length; j < maxNameLength; j++) {
1429             out += ' ';
1430         }
1431     }
1432     out += '  ' + middleStr;
1433     // XXX Need to finish implementing the text-only version of the failures
1434     // table.
1435 };
1436
1437 Test.Harness.prototype._bonusmsg = function () {
1438     var bonusmsg = '';
1439     if (this.bonus) {
1440         bonusmsg = (" (" + this.bonus + " subtest" + (this.bonus > 1 ? 's' : '')
1441           + " UNEXPECTEDLY SUCCEEDED)");
1442     }
1443
1444     if (this.skipped) {
1445         bonusmsg += ", " + this.skipped + " test"
1446           + (this.skipped != 1 ? 's' : '');
1447         if (this.subSkipped) {
1448             bonusmsg += " and " + this.subSkipped + " subtest"
1449               + (this.subSkipped != 1 ? 's' : '');
1450         }
1451         bonusmsg += ' skipped';
1452     } else if (this.subSkipped) {
1453         bonusmsg += ", " + this.subSkipped + " subtest"
1454           + (this.subSkipped != 1 ? 's' : '') + " skipped";
1455     }
1456
1457     return bonusmsg;
1458 }
1459
1460 Test.Harness.prototype._failList = function (fails) {
1461     var last = -1;
1462     var dash = '';
1463     var list = [];
1464     for (var i = 0; i < fails.length; i++) {
1465         if (dash) {
1466             // We're in a series of numbers.
1467             if (fails[i] - 1 == last) {
1468                 // We're still in it.
1469                 last = fails[i];
1470             } else {
1471                 // End of the line.
1472                 list[list.length-1] += dash + last;
1473                 last = -1;
1474                 list.push(fails[i]);
1475                 dash = '';
1476             }
1477         } else if (fails[i] - 1 == last) {
1478             // We're in a new series.
1479             last = fails[i];
1480             dash = '-';
1481         } else {
1482             // Not in a sequence.
1483             list.push(fails[i]);
1484             last = fails[i];
1485         }
1486     }
1487     if (dash) list[list.length-1] += dash + last;
1488     return list.join(' ');
1489 }
1490 // # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
1491
1492 if (typeof JSAN != 'undefined') new JSAN().use('Test.Harness');
1493
1494 Test.Harness.Browser = function () {};
1495 Test.Harness.Browser.VERSION = '0.11';
1496
1497 Test.Harness.Browser.runTests = function () {
1498     var harness = new Test.Harness.Browser();
1499     harness.runTests.apply(harness, arguments);
1500 };
1501
1502 Test.Harness.Browser.prototype = new Test.Harness();
1503 Test.Harness.Browser.prototype.interval = 100;
1504
1505 Test.Harness.Browser.prototype._setupFrame = function () {
1506     // Setup the iFrame to run the tests.
1507     var node = document.getElementById('buffer');
1508     if (node) return node.contentWindow;
1509     node = document.createElement("iframe");
1510     node.setAttribute("id", "buffer");
1511     node.setAttribute("name", "buffer");
1512     // Safari makes it impossible to do anything with the iframe if it's set
1513     // to display:none. See:
1514     // http://www.quirksmode.org/bugreports/archives/2005/02/hidden_iframes.html
1515     if (/Safari/.test(navigator.userAgent)) {
1516         node.style.visibility = "hidden";
1517         node.style.height = "0"; 
1518         node.style.width = "0";
1519     } else
1520         node.style.display = "none";
1521     document.body.appendChild(node);
1522     return node.contentWindow;
1523 };
1524
1525 Test.Harness.Browser.prototype._setupOutput = function () {
1526     // Setup the pre element for test output.
1527     var node = document.createElement("pre");
1528     node.setAttribute("id", "output");
1529     document.body.appendChild(node);
1530     return function (msg) {
1531         node.appendChild(document.createTextNode(msg));
1532         window.scrollTo(0, document.body.offsetHeight
1533                         || document.body.scrollHeight);
1534     };
1535 };
1536
1537 Test.Harness.Browser.prototype._setupSummary = function () {
1538     // Setup the div for the summary.
1539     var node = document.createElement("div");
1540     node.setAttribute("id", "summary");
1541     node.setAttribute("style", "white-space:pre; font-family: Verdana,Arial,serif;");
1542     document.body.appendChild(node);
1543     return function (msg) {
1544         node.appendChild(document.createTextNode(msg));
1545         window.scrollTo(0, document.body.offsetHeight
1546                         || document.body.scrollHeight);
1547     };
1548 };
1549
1550 Test.Harness.Browser.prototype.runTests = function () {
1551     var files = this.args.file
1552       ? typeof this.args.file == 'string' ? [this.args.file] : this.args.file
1553       : arguments;
1554     if (!files.length) return;
1555     var outfiles = this.outFileNames(files);
1556     var buffer = this._setupFrame();
1557     var harness = this;
1558     var ti = 0;
1559     var start;
1560     var node = document.getElementById('output');
1561     var output = this._setupOutput();
1562     var summaryOutput = this._setupSummary();
1563     // These depend on how we're watching for a test to finish.
1564     var finish = function () {}, runNext = function () {};
1565
1566     // This function handles most of the work of outputting results and
1567     // running the next test, if there is one.
1568     var runner = function () {
1569         harness.outputResults(
1570             buffer.Test.Builder.Test,
1571             files[ti],
1572             output,
1573             harness.args
1574         );
1575
1576         if (files[++ti]) {
1577             output(outfiles[ti] + (harness.args.verbose ? Test.Harness.LF : ''));
1578             buffer.location.href = files[ti];
1579             runNext();
1580         } else {
1581             harness.outputSummary(
1582                 summaryOutput,
1583                 new Date() - start
1584              );
1585             finish();
1586         }
1587     };
1588
1589     if (Object.watch) {
1590         // We can use the cool watch method, and avoid setting timeouts!
1591         // We just need to unwatch() when all tests are finished.
1592         finish = function () { Test.Harness.unwatch('Done') };
1593         Test.Harness.watch('Done', function (attr, prev, next) {
1594             if (next < buffer.Test.Builder.Instances.length) return next;
1595             runner();
1596             return 0;
1597         });
1598     } else {
1599         // Damn. We have to set timeouts. :-(
1600         var wait = function () {
1601             // Check Test.Harness.Done. If it's non-zero, then we know that
1602             // the buffer is fully loaded, because it has incremented
1603             // Test.Harness.Done.
1604             if (Test.Harness.Done > 0
1605                 && Test.Harness.Done >= buffer.Test.Builder.Instances.length)
1606             {
1607                 Test.Harness.Done = 0;
1608                 runner();
1609             } else {
1610                 window.setTimeout(wait, harness.interval);
1611             }
1612         };
1613         // We'll just have to set a timeout for the next test.
1614         runNext = function () { window.setTimeout(wait, harness.interval); };
1615         window.setTimeout(wait, this.interval);
1616     }
1617
1618     // Now start the first test.
1619     output(outfiles[ti] + (this.args.verbose ? Test.Harness.LF : ''));
1620     start = new Date();
1621     buffer.location.href = files[ti]; // replace() doesn't seem to work.
1622 };
1623
1624 // From "JavaScript: The Difinitive Guide 4ed", p 214.
1625 Test.Harness.Browser.prototype.args = {};
1626 var pairs = location.search.substring(1).split(",");
1627 for (var i = 0; i < pairs.length; i++) {
1628     var pos = pairs[i].indexOf('=');
1629     if (pos == -1) continue;
1630     var key = pairs[i].substring(0, pos);
1631     var val = pairs[i].substring(pos + 1); 
1632     if (Test.Harness.Browser.prototype.args[key]) {
1633         if (typeof Test.Harness.Browser.prototype.args[key] == 'string') {
1634             Test.Harness.Browser.prototype.args[key] =
1635                 [Test.Harness.Browser.prototype.args[key]];
1636         }
1637         Test.Harness.Browser.prototype.args[key].push(unescape(val));
1638     } else {
1639         Test.Harness.Browser.prototype.args[key] = unescape(val);
1640     }
1641 }
1642 delete pairs;
1643
1644 Test.Harness.Browser.prototype.formatFailures = function (fn) {
1645     // XXX append new element for table and then populate it.
1646     var failedStr = "Failed Test";
1647     var middleStr = " Total Fail  Failed  ";
1648     var listStr = "List of Failed";
1649     var table = '<table style=""><tr><th>Failed Test</th><th>Total</th>'
1650       + '<th>Fail</th><th>Failed</th></tr>';
1651     for (var i = 0; i < this.failures.length; i++) {
1652         var track = this.failures[i];
1653         table += '<tr><td>' + track.fn + '</td>'
1654           + '<td>' + track.total + '</td>'
1655           + '<td>' + track.total - track.ok + '</td>'
1656           + '<td>' + this._failList(track.failList) + '</td></tr>'
1657     };
1658     table += '</table>' + Test.Harness.LF;
1659     var node = document.getElementById('summary');
1660     node.innerHTML += table;
1661     window.scrollTo(0, document.body.offsetHeight || document.body.scrollHeight);
1662 };