1 // # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
4 if (typeof self != 'undefined') {
6 if (typeof Test == 'undefined') Test = {PLATFORM: 'browser'};
7 else Test.PLATFORM = 'browser';
8 } else if (typeof _global != 'undefined') {
10 if (typeof _global.Test == "undefined") _global.Test = {PLATFORM: 'director'};
11 else _global.Test.PLATFORM = 'director';
13 throw new Error("Test.More does not support your platform");
17 Test.Builder = function () {
18 if (!Test.Builder.Test) {
19 Test.Builder.Test = this.reset();
20 Test.Builder.Instances.push(this);
22 return Test.Builder.Test;
26 Test.Builder.VERSION = '0.11';
27 Test.Builder.Instances = [];
28 Test.Builder.lineEndingRx = /\r?\n|\r/g;
29 Test.Builder.StringOps = {
39 Test.Builder.LF = typeof document != "undefined"
40 && typeof document.all != "undefined"
45 Test.Builder.die = function (msg) {
49 Test.Builder._whoa = function (check, desc) {
51 Test.Builder.die("WHOA! " + desc + Test.Builder.LF +
52 + "This should never happen! Please contact the author "
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))) {
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;
78 Test.Builder.prototype.reset = function () {
79 this.TestDied = false;
80 this.HavePlan = false;
83 this.ExpectedTests = 0;
85 this.NoHeader = false;
86 this.NoEnding = false;
87 this.TestResults = [];
92 return this._setupOutput();
95 Test.Builder.prototype._print = function (msg) {
96 this.output().call(this, msg);
99 Test.Builder.prototype.warn = function (msg) {
100 this.warnOutput().apply(this, arguments);
103 Test.Builder.prototype.plan = function (arg) {
105 if (this.HavePlan) Test.Builder.die("You tried to plan twice!");
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) {
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."
117 } else if (!arg[cmd]) {
119 "You said to run 0 tests! You've got to run something."
123 this.expectedTests(arg[cmd]);
125 } else if (cmd == 'skipAll') {
126 this.skipAll(arg[cmd]);
127 } else if (cmd == 'noPlan' && arg[cmd]) {
130 Test.Builder.die("plan() doesn't understand "
131 + cmd + (arg[cmd] ? (" " + arg[cmd]) : ''));
136 Test.Builder.prototype.expectedTests = function (max) {
140 "Number of tests must be a postive integer. You gave it '"
141 + max + "'." + Test.Builder.LF
145 this.ExpectedTests = max.valueOf();
147 if (!this.noHeader()) this._print("1.." + max + Test.Builder.LF);
149 return this.ExpectedTests;
152 Test.Builder.prototype.noPlan = function () {
157 Test.Builder.prototype.hasPlan = function () {
158 if (this.ExpectedTests) return this.ExpectedTests;
159 if (this.NoPlan) return 'noPlan';
162 Test.Builder.prototype.skipAll = function (reason) {
164 if (reason) out += " # Skip " + reason;
165 out += Test.Builder.LF;
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__");
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.
179 Test.Builder.die("You tried to run a test without a plan! Gotta have a plan.");
181 // I don't think we need to worry about threading in JavaScript.
184 // In case desc is a string overloaded object, force it to stringify.
185 if (desc) desc = desc.toString();
188 if (desc != null && /^[\d\s]+$/.test(desc)) {
189 this.diag( "Your test description is '" + desc + "'. You shouldn't use",
191 "numbers for your test names. Very confusing.");
194 var todo = this._todo();
195 // I don't think we need to worry about result beeing shared between
202 result.actual_ok = test;
205 result.ok = todo ? true : false;
206 result.actual_ok = false;
210 if (this.useNumbers) out += ' ' + this.CurrTest;
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.
223 todo = todo.replace(Test.Builder.lineEndingRx, Test.Builder.LF + "# ");
224 out += " # TODO " + todo;
225 result.reason = todo;
226 result.type = 'todo';
232 this.TestResults[this.CurrTest - 1] = result;
234 out += Test.Builder.LF;
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");
243 result.output = this.Buffer.splice(0).join('');
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);
252 return this.cmpOK(got, '==', expect, desc);
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);
260 return this.cmpOK(Number(got), '==', Number(expect), desc);
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);
268 return this.cmpOK(got, '!=', dontExpect, desc);
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);
276 return this.cmpOK(Number(got), '!=', Number(dontExpect), desc);
279 Test.Builder.prototype.like = function (val, regex, desc) {
280 return this._regexOK(val, regex, '=~', desc);
283 Test.Builder.prototype.unlike = function (val, regex, desc) {
284 return this._regexOK(val, regex, '!~', desc);
287 Test.Builder.prototype._regexOK = function (val, regex, cmp, desc) {
288 // Create a regex object.
289 var type = Test.Builder.typeOf(regex);
291 if (type.toLowerCase() == 'string') {
292 // Create a regex object.
293 regex = new RegExp(regex);
295 if (type != 'RegExp') {
296 ok = this.ok(false, desc);
297 this.diag("'" + regex + "' doesn't look much like a regex to me.");
302 if (val == null || typeof val != 'string') {
305 ok = this.ok(false, desc);
306 this._diagLike(val, regex, cmp);
308 // undefined matches nothing (unlike in Perl, where undef =~ //).
309 ok = this.ok(true, desc);
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);
322 Test.Builder.prototype._diagLike = function (val, regex, cmp) {
323 var match = cmp == '=~' ? "doesn't match" : " matches";
325 " '" + val + "" + Test.Builder.LF +
326 " " + match + " /" + regex.source + "/"
330 Test.Builder.prototype.cmpOK = function (got, op, expect, desc) {
333 if (Test.Builder.StringOps[op]) {
334 // Force string context.
335 test = eval("got.toString() " + Test.Builder.StringOps[op] + " expect.toString()");
337 test = eval("got " + op + " expect");
340 var ok = this.ok(test, desc);
342 if (/^(eq|==)$/.test(op)) {
343 this._isDiag(got, op, expect);
345 this._cmpDiag(got, op, expect);
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);
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();
367 " got: " + args[0] + Test.Builder.LF +
368 " expected: " + args[1] + Test.Builder.LF
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?
379 throw new Error("__BAILOUT__");
382 Test.Builder.prototype.skip = function (why) {
384 Test.Builder.die("You tried to run a test without a plan! Gotta have a plan.");
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+ "# ");
391 this.TestResults[this.CurrTest - 1] = {
400 if (this.useNumbers) out += ' ' + this.CurrTest;
401 out += " # skip " + why + Test.Builder.LF;
403 this.TestResults[this.CurrTest - 1].output =
404 this.Buffer.splice(0).join('');
408 Test.Builder.prototype.todoSkip = function (why) {
410 Test.Builder.die("You tried to run a test without a plan! Gotta have a plan.");
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 + "# ");
418 this.TestResults[this.CurrTest - 1] = {
427 if (this.useNumbers) out += ' ' + this.CurrTest;
428 out += " # TODO & SKIP " + why + Test.Builder.LF;
430 this.TestResults[this.CurrTest - 1].output =
431 this.Buffer.splice(0).join('');
435 Test.Builder.prototype.skipRest = function (reason) {
437 if (reason) out += " " + reason;
438 out += Test.Builder.LF;
439 if (this.NoPlan) this.skip(reason);
441 for (var i = this.CurrTest; i < this.ExpectedTests; i++) {
445 // Just throw and catch an exception.
446 window.onerror = function () { return true; }
447 throw new Error("__SKIP_REST__");
450 Test.Builder.prototype.useNumbers = function (useNums) {
451 if (useNums != null) this.UseNums = useNums;
455 Test.Builder.prototype.noHeader = function (noHeader) {
456 if (noHeader != null) this.NoHeader = !!noHeader;
457 return this.NoHeader;
460 Test.Builder.prototype.noEnding = function (noEnding) {
461 if (noEnding != null) this.NoEnding = !!noEnding;
462 return this.NoEnding;
465 Test.Builder.prototype.diag = function () {
466 if (!arguments.length) return;
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 + "# ");
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);
483 Test.Builder.prototype._printDiag = function () {
484 var fn = this.todo() ? this.todoOutput() : this.failureOutput();
485 fn.apply(this, arguments);
489 Test.Builder.prototype.output = function (fn) {
491 var buffer = this.Buffer;
492 this.Output = function (msg) { buffer.push(msg); fn(msg) };
497 Test.Builder.prototype.failureOutput = function (fn) {
499 var buffer = this.Buffer;
500 this.FailureOutput = function (msg) { buffer.push(msg); fn(msg) };
502 return this.FailureOutput;
505 Test.Builder.prototype.todoOutput = function (fn) {
507 var buffer = this.Buffer;
508 this.TodoOutput = function (msg) { buffer.push(msg); fn(msg) };
510 return this.TodoOutput;
513 Test.Builder.prototype.endOutput = function (fn) {
515 var buffer = this.Buffer;
516 this.EndOutput = function (msg) { buffer.push(msg); fn(msg) };
518 return this.EndOutput;
521 Test.Builder.prototype.warnOutput = function (fn) {
523 var buffer = this.Buffer;
524 this.WarnOutput = function (msg) { buffer.push(msg); fn(msg) };
526 return this.WarnOutput;
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");
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));
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);
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);
560 // Default to the normal write and scroll down...
562 window.scrollTo(0, document.body.offsetHeight
563 || document.body.scrollHeight);
567 this.failureOutput(writer);
568 this.todoOutput(writer);
569 this.endOutput(writer);
572 if (window.alert.apply) this.warnOutput(window.alert, window);
573 else this.warnOutput(function (msg) { window.alert(msg) });
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.
581 this.failureOutput(trace);
582 this.todoOutput(trace);
583 this.warnOutput(trace);
589 Test.Builder.prototype.currentTest = function (num) {
590 if (num == null) return this.CurrTest;
593 Test.Builder.die("Can't change the current test number without a plan!");
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] = {
604 output: 'ok - ' + reason + Test.Builder.LF
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);
611 return this.CurrTest;
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'];
622 Test.Builder.prototype.details = function () {
623 return this.TestResults;
626 Test.Builder.prototype.todo = function (why, howMany) {
627 if (howMany) this.ToDo = [why, howMany];
631 Test.Builder.prototype._todo = function () {
633 if (this.ToDo[1]--) return this.ToDo[0];
639 Test.Builder.prototype._sanity_check = function () {
642 'Says here you ran a negative number of tests!'
646 !this.HavePlan && this.CurrTest,
647 'Somehow your tests ran without a plan!'
651 this.CurrTest != this.TestResults.length,
652 'Somehow you got a different number of results than tests ran!'
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++;
664 Test.Builder.prototype._ending = function () {
665 if (this.Ended) return;
667 if (this.noEnding()) {
668 this._notifyHarness();
671 this._sanity_check();
672 var out = this.endOutput();
674 // Figure out if we passed or failed and print helpful messages.
675 if( this.TestResults.length ) {
676 // The plan? We have no plan.
678 if (!this.noHeader())
679 this._print("1.." + this.CurrTest + Test.Builder.LF);
680 this.ExpectedTests = this.CurrTest;
684 for (var i = 0; i < this.TestResults.length; i++) {
685 if (!this.TestResults[i]) numFailed++;
687 numFailed += Math.abs(
688 this.ExpectedTests - this.TestResults.length
691 if (this.CurrTest < this.ExpectedTests) {
692 var s = this.ExpectedTests == 1 ? '' : 's';
694 "# Looks like you planned " + this.ExpectedTests + " test"
695 + s + " but only ran " + this.CurrTest + "." + Test.Builder.LF
697 } else if (this.CurrTest > this.ExpectedTests) {
698 var numExtra = this.CurrTest - this.ExpectedTests;
699 var s = this.ExpectedTests == 1 ? '' : 's';
701 "# Looks like you planned " + this.ExpectedTests + " test"
702 + s + " but ran " + numExtra + " extra." + Test.Builder.LF
704 } else if (numFailed) {
705 var s = numFailed == 1 ? '' : 's';
707 "# Looks like you failed " + numFailed + "test" + s + " of "
708 + this.ExpectedTests + "." + Test.Builder.LF
714 "# Looks like your test died just after "
715 + this.CurrTest + "." + Test.Builder.LF
719 } else if (!this.SkipAll) {
720 // skipAll requires no status output.
723 "# Looks like your test died before it could output anything."
727 out("# No tests run!" + Test.Builder.LF);
730 this._notifyHarness();
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");
737 if (!test) this._isDiag(got, op, expect);
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);
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);
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?
765 this.asyncs[id] = window.setTimeout(
766 function () { aTest.endAsync(id) }, timeout
769 // Make sure it's defined.
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]);
781 if (--this.asyncID < 0) this._ending();
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");
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]];
794 };// # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
795 // Create a namespace for ourselves.
798 if (typeof JSAN != 'undefined') new JSAN().use('Test.Builder');
800 if (typeof Test == 'undefined' || typeof Test.Builder == 'undefined')
802 "You must load either JSAN or Test.Builder "
803 + "before loading Test.More"
812 'cmpOK', 'canOK', 'isaOK',
813 'pass', 'fail', 'diag', 'loadOK',
814 'skip', 'todo', 'todoSkip', 'skipRest',
815 'isDeeply', 'isSet', 'isa'
817 Test.More.EXPORT_TAGS = { ':all': Test.More.EXPORT };
818 Test.More.VERSION = '0.11';
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; };
825 Test.More.plan = function (cmds) {
827 Test.More.ShowDiag = false;
830 return Test.More.Test.plan.apply(Test.More.Test, [cmds]);
833 Test.More.ok = function (test, desc) {
834 return Test.More.Test.ok(test, desc);
837 Test.More.is = function (got, expect, desc) {
838 return Test.More.Test.isEq(got, expect, desc);
841 Test.More.isnt = function (got, expect, desc) {
842 return Test.More.Test.isntEq(got, expect, desc);
845 Test.More.like = function (val, regex, desc) {
846 return Test.More.Test.like(val, regex, desc);
849 Test.More.unlike = function (val, regex, desc) {
850 return Test.More.Test.unlike(val, regex, desc);
853 Test.More.cmpOK = function (got, op, expect, desc) {
854 return Test.More.Test.cmpOK(got, op, expect, desc);
857 Test.More.canOK = function (proto) {
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');
866 // Get the class name and the prototype.
868 if (typeof proto == 'string') {
869 // We just have a class name.
871 proto = eval(clas + '.prototype');
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;
879 for (var i = 1; i < arguments.length; i++) {
880 var method = arguments[i];
881 if (typeof proto[method] != 'function') nok.push(method);
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");
893 Test.More.isaOK = function (object, clas, objName) {
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";
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) + "'";
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) + '"';
920 ok = Test.More.Test.ok(false, name);
921 Test.More.Test.diag(' ' + mesg);
923 ok = Test.More.Test.ok(true, name);
929 Test.More.pass = function (name) {
930 return Test.More.Test.ok(true, name);
933 Test.More.fail = function (name) {
934 return Test.More.Test.ok(false, name);
937 Test.More.diag = function () {
938 if (!Test.More.ShowDiag) return;
939 return Test.More.Test.diag.apply(Test.More.Test, arguments);
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.
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");
955 for (i = 0; i < howMany; i++) {
956 Test.More.Test.skip(why);
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");
966 return Test.More.Test.todo(why, howMany);
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");
976 for (i = 0; i < howMany; i++) {
977 Test.More.Test.todoSkip(why);
981 Test.More.skipRest = function (why) {
982 Test.More.Test.skipRest(why);
985 Test.More.isDeeply = function (it, as, name) {
986 if (arguments.length != 2 && arguments.length != 3) {
988 'isDeeply() takes two or three args, you gave '
989 + arguments.length + "."
994 // ^ is the XOR operator.
995 if (Test.More._isRef(it) ^ Test.More._isRef(as)) {
996 // One's a reference, one isn't.
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);
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);
1007 ok = Test.More.Test.ok(false, name);
1008 Test.More.Test.diag(Test.More._formatStack(stack));
1014 Test.More._deepCheck = function (e1, e2, stack, seen) {
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) {
1020 } else if (e1 != null ^ e2 != null) {
1022 } else if (e1 == Test.More.DNE ^ e2 == Test.More.DNE) {
1024 } else if (sameRef && e1 == e2) {
1025 // Handles primitives and any variables that reference the same
1026 // object, including functions.
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);
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] });
1041 Test.More._isRef = function (object) {
1042 var type = typeof object;
1043 return type == 'object' || type == 'function';
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'];
1053 if (/^\d+$/.test(idx)) {
1054 // Numeric array index.
1055 variable += '[' + idx + ']';
1057 // Associative array index.
1058 idx = idx.replace("'", "\\'");
1059 variable += "['" + idx + "']";
1064 var vals = stack[stack.length-1]['vals'].slice(0, 2);
1066 variable.replace('$Foo', 'got'),
1067 variable.replace('$Foo', 'expected')
1070 var out = "Structures begin differing at:" + Test.Builder.LF;
1071 for (var i = 0; i < vals.length; i++) {
1076 val == Test.More.DNE ? "Does not exist" : "'" + val + "'";
1080 out += vars[0] + ' = ' + vals[0] + Test.Builder.LF;
1081 out += vars[1] + ' = ' + vals[1] + Test.Builder.LF;
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.
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()");
1095 return Test.More._eqArray(a1, a2, [], []);
1100 Test.More._eqArray = function (a1, a2, stack, seen) {
1101 // Return if they're the same object.
1102 if (a1 == a2) return true;
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;
1116 // If we get here, we haven't seen a1 before, so store it with reference
1118 seen.push([ a1, a2 ]);
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)) {
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.
1142 Test.More.eqHash = function () {
1143 return eqAssoc.apply(this, arguments);
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()");
1150 } else if ( (isa(o1, 'Array') && o1.length > 0)
1151 || (isa(o2, 'Array') && o2.length > 0))
1153 Test.More.Test.warn("Ordered array passed to eqAssoc()");
1156 return Test.More._eqAssoc(o1, o2, [], []);
1161 Test.More._eqAssoc = function (o1, o2, stack, seen) {
1162 // Return if they're the same object.
1163 if (o1 == o2) return true;
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;
1178 // If we get here, we haven't seen o1 before, so store it with reference
1180 seen.push([ o1, o2 ]);
1182 // They should be of the same class.
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)) {
1202 Test.More._eqSet = function (a1, a2, stack, seen) {
1203 return Test.More._eqArray(a1.slice(0).sort(), a2.slice(0).sort(), stack, seen);
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);
1211 ok = Test.More.Test.ok(false, desc);
1212 Test.More.Test.diag(Test.More._formatStack(stack));
1217 Test.More.isa = function (object, clas) {
1218 return Test.Builder.typeOf(object) == clas;
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 $
1225 // Set up namespace.
1226 if (typeof self != 'undefined') {
1228 if (typeof Test == 'undefined') Test = {PLATFORM: 'browser'};
1229 else Test.PLATFORM = 'browser';
1230 } else if (typeof _player != 'undefined'){
1232 if (typeof _global.Test != "object") _global.Test = {PLATFORM: 'director'};
1233 else _global.Test.PLATFORM = 'director';
1235 throw new Error("Test.Harness does not support your platform");
1238 Test.Harness = function () {};
1239 Test.Harness.VERSION = '0.11';
1240 Test.Harness.Done = 0;
1243 Test.Harness.LF = typeof document != "undefined"
1244 && typeof document.all != "undefined"
1248 Test.Harness.prototype.isDone = Test.Harness.isDone;
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
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
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 = [];
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);
1284 Test.Harness.prototype.outFileNames = function (files) {
1286 for (var i = 0; i < files.length; i++) {
1287 if (files[i].length > len) len = files[i].length;
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++) {
1303 Test.Harness.prototype.outputResults = function (test, file, fn, attrs) {
1305 this.ran += test.TestResults.length;
1309 total: test.expectedTests,
1314 if (test.TestResults.length) {
1317 for (var i = 0; i < test.TestResults.length; i++) {
1319 if (test.TestResults[i].ok) {
1322 if (test.TestResults[i].type == 'todo') {
1323 // Handle unexpected pass.
1324 if (test.TestResults[i].actualOK) this.bonus++;
1326 } else if (test.TestResults[i].type == 'skip') this.subSkipped++;
1328 if (test.TestResults[i].type == 'todo') {
1329 // Expected failure.
1333 track.failList.push(i + 1);
1335 ok = "not ok"; // XXX Need to handle TODO and TODO Skipped.
1338 if (!pass || attrs.verbose) fn(test.TestResults[i].output);
1343 fn("ok" + Test.Harness.LF);
1346 var err = "NOK # Failed ";
1347 if (track.failList.length == 1) {
1348 err += "test " + track.failList[0];
1350 err += "tests " + this._failList(track.failList);
1352 fn(err + " in " + file + Test.Harness.LF);
1354 } else if (test.SkipAll){
1355 // All tests skipped.
1358 fn("1..0 # Skip 1" + Test.Harness.LF);
1360 // Wha happened? Tests ran, but no results!
1363 fn("FAILED before any test output arrived" + Test.Harness.LF);
1365 if (track.failList.length) this.failures.push(track);
1368 Test.Harness.prototype._allOK = function () {
1369 return this.bad == 0 && (this.ran || this.skipped) ? true : false;
1372 Test.Harness.prototype.outputSummary = function (fn, time) {
1373 var bonusmsg = this._bonusmsg();
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);
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.";
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);
1395 this.formatFailures(fn);
1398 fn("Files=" + this.tests + ", Tests=" + this.ran + ", " + (time / 1000)
1399 + " seconds" + Test.Harness.LF);
1402 Test.Harness.prototype.formatFailures = function () {
1404 var failedStr = "Failed Test";
1405 var middleStr = " Total Fail Failed ";
1406 var listStr = "List of Failed";
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;
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;
1426 var out = failedStr;
1427 if (out.length < maxNamelen) {
1428 for (var j = out.length; j < maxNameLength; j++) {
1432 out += ' ' + middleStr;
1433 // XXX Need to finish implementing the text-only version of the failures
1437 Test.Harness.prototype._bonusmsg = function () {
1440 bonusmsg = (" (" + this.bonus + " subtest" + (this.bonus > 1 ? 's' : '')
1441 + " UNEXPECTEDLY SUCCEEDED)");
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' : '');
1451 bonusmsg += ' skipped';
1452 } else if (this.subSkipped) {
1453 bonusmsg += ", " + this.subSkipped + " subtest"
1454 + (this.subSkipped != 1 ? 's' : '') + " skipped";
1460 Test.Harness.prototype._failList = function (fails) {
1464 for (var i = 0; i < fails.length; i++) {
1466 // We're in a series of numbers.
1467 if (fails[i] - 1 == last) {
1468 // We're still in it.
1472 list[list.length-1] += dash + last;
1474 list.push(fails[i]);
1477 } else if (fails[i] - 1 == last) {
1478 // We're in a new series.
1482 // Not in a sequence.
1483 list.push(fails[i]);
1487 if (dash) list[list.length-1] += dash + last;
1488 return list.join(' ');
1490 // # $Id: Kinetic.pm 1493 2005-04-07 19:20:18Z theory $
1492 if (typeof JSAN != 'undefined') new JSAN().use('Test.Harness');
1494 Test.Harness.Browser = function () {};
1495 Test.Harness.Browser.VERSION = '0.11';
1497 Test.Harness.Browser.runTests = function () {
1498 var harness = new Test.Harness.Browser();
1499 harness.runTests.apply(harness, arguments);
1502 Test.Harness.Browser.prototype = new Test.Harness();
1503 Test.Harness.Browser.prototype.interval = 100;
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";
1520 node.style.display = "none";
1521 document.body.appendChild(node);
1522 return node.contentWindow;
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);
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);
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
1554 if (!files.length) return;
1555 var outfiles = this.outFileNames(files);
1556 var buffer = this._setupFrame();
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 () {};
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,
1577 output(outfiles[ti] + (harness.args.verbose ? Test.Harness.LF : ''));
1578 buffer.location.href = files[ti];
1581 harness.outputSummary(
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;
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)
1607 Test.Harness.Done = 0;
1610 window.setTimeout(wait, harness.interval);
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);
1618 // Now start the first test.
1619 output(outfiles[ti] + (this.args.verbose ? Test.Harness.LF : ''));
1621 buffer.location.href = files[ti]; // replace() doesn't seem to work.
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]];
1637 Test.Harness.Browser.prototype.args[key].push(unescape(val));
1639 Test.Harness.Browser.prototype.args[key] = unescape(val);
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>'
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);