diff --git a/src/TAP.mjs b/src/TAP.mjs index 32b605e..f5f90df 100644 --- a/src/TAP.mjs +++ b/src/TAP.mjs @@ -19,6 +19,32 @@ * @license Artistic-2.0 */ +/** @implements TapRenderer */ +class NodeRenderer { + out(text) { + import('node:process').then(loaded => {; + loaded.stdout.write(text + "\n"); + }) + } + + diag(lines) { + for (var line of lines) { + this.out('# ' + line); + } + } +} + +/** @implements TapRenderer */ +class BrowserRenderer { + out(text) { + // TODO(jeremy): + } + + diag(lines) { + // TODO(jeremy): + } +} + /** * The Tap Test Class helper. */ @@ -31,35 +57,29 @@ class Tap { passed = 0; /** @type Number */ failed = 0; - /** @type function(TestOutput) */ - #renderLine + /** @type TapRenderer */ + #renderer /** * Construct a new Tap Suite with a renderLine function. - * @param {function(string)} + * @param {TapRenderer} */ constructor(renderFunc) { - this.#renderLine = renderFunc; + this.#renderer = renderFunc; } static Browser() { - return new Tap(function(text) { - // TODO(zaphar): Handle output in a Browser context. - }); + return new Tap(new BrowserRenderer()); } static Node() { - return new Tap(function(text) { - import('node:process').then(loaded => {; - loaded.stdout.write(text + "\n"); - }) - }); + return new Tap(new NodeRenderer()); } /** Renders output for the test results */ out(text) { - this.#renderLine(text); + this.#renderer.out(text); }; /** @@ -78,18 +98,12 @@ class Tap { }; - /** Diagnostic formatter for TAP Output. - * - * @param msg {string} - */ diag(msg){ - if (!msg) { + if(!msg) { msg = " "; } var lines = msg.split("\n"); - for (var line of lines) { - this.out('# ' + msg); - } + this.#renderer.diag(lines); }; /** Render a pass TAP output message. @@ -241,11 +255,8 @@ class Tap { var pass = true; for (var i=1; i t.counter) { + t.diag('looks like you planned ' + t.planned + ' tests but only ran ' + + t.counter + ' tests'); + } else if (t.planned < t.counter) { + t.diag('looks like you planned ' + t.planned + ' tests but ran ' + + (t.counter - t.planned) + ' tests extra'); + } + t.diag('ran ' + t.counter + ' tests out of ' + t.planned); + t.diag('passed ' + t.passed + ' tests out of ' + t.planned); + t.diag('failed ' + t.failed + ' tests out of ' + t.planned); + } + catch (err) { + t.diag("Test Suite Crashed!!! (" + err + ")"); + } + + return t; +} + +/** + * Runs a set of TAP tests defined by a function. + * Uses the NodeRenderer for the test output. + * + * @param {string} testName + * @param {function(Tap)} test + */ +function runNodeTap(testName, test) { + var t = Tap.Node(); + return runTest(t, testName, test); +} + +/** + * Runs a set of TAP tests defined by a function. + * Uses the Browser renderer for the test output. + * + * @param {string} testName + * @param {function(Tap)} test + */ +function runBrowserTap(testName, test) { + var t = Tap.Browser(); + return runTest(t, testName, test); +} + // TODO(zaphar): The runner interface as well. -export { Tap }; +export { Tap, runNodeTap, runBrowserTap }; + diff --git a/src/Tap.d.js b/src/Tap.d.js new file mode 100644 index 0000000..46723bc --- /dev/null +++ b/src/Tap.d.js @@ -0,0 +1,16 @@ +/** @interface */ +class TapRenderer { + /** Renders output for the test results + * @param {string} text + */ + out(text) { + } + + /** Diagnostic formatter for TAP Output. + * + * @param {Array} lines + */ + diag(lines) { + } +} + diff --git a/tests/01_tap.t.js b/tests/01_tap.t.js index 823d034..39fdcdb 100644 --- a/tests/01_tap.t.js +++ b/tests/01_tap.t.js @@ -1,150 +1,154 @@ import('../src/TAP.mjs').then(m => { - var out = "nothing yet"; - var diag = ""; - var t = m.Tap.Node(); - t.plan(27); - - var testCan = function () { - // setup fake test object - var f = new m.Tap(function(newout) { out = newout }); // the TAP thats failing - f.out = function(newout) { out = newout }; - f.diag = function(newdiag) { diag += newdiag }; - f.plan(4); + var runNodeTap = m.runNodeTap; + + function tapSuite(t) { + var out = "nothing yet"; + var diag = ""; + t.plan(17); - //mock a fake object to run test against - var obj = new Object; - obj.run = function() {}; - var method = 'run'; - - // begin real tests! - f.can_ok(obj, 'not_there'); - t.like(out, /not ok 1 - object can \[ not_there \]/, 'can_ok failed'); - f.can_ok(obj, method); - diag = ''; - t.like(out, /ok 2 - object can \[ run \]/, 'can_ok passed'); - - //Now we need to test the whole prototype method assignment thing - - function MockObj() { - this.attr = 1; - } - - MockObj.prototype.fakeme = function () {}; - - f.can_ok(MockObj, 'fakeme'); - diag = ''; - t.like(out, /^ok .* \[ fakeme \]/, - 'can_ok recognized prototype methods'); - f.can_ok(MockObj, 'fakeme2'); - diag = ''; - t.like(out, /^not ok .* \[ fakeme2 \]/, - 'can_ok prototype recognization doesnt break methods'); - }; - - var testLike = function() { - // setup fake test object - var f = new m.Tap(function(newout) { out = newout }); // the TAP that's failing - f.out = function(newout) { out = newout }; - f.plan(1); - - // begin real tests! - f.like("hello", /hello/, "hello matches hello"); - t.like(out, /ok 1 - hello matches hello/, 'got description in TAP output'); - }; - - var testDiag = function() { - // setup fake test object - var f = new m.Tap(function(newout) { out = newout }); // the TAP that's failing - f.out = function(newout) { out = newout }; - f.plan(10); - // begin real tests! - f.diag("hello"); - t.like(out, /# hello/, 'got hello'); - }; - - var testException = function() { - // setup fake test object - var f = new m.Tap(function(newout) { out = newout }); // the TAP that's failing - f.out = function(newout) { out = newout }; - f.plan(2); - - // begin real tests! - f.throws_ok(function() {throw new Error('I made a boo boo')}, 'I made a boo boo'); - //t.diag(out); - t.like(out, /ok 1 - code threw \[Error: I made a boo boo\]/, 'uncaught exception'); - f.throws_ok(function() {}, 'I made a boo boo'); - //t.diag(out); - t.like(out, /not ok 2 - code threw \[ \]/, 'false failed'); - }; - testException(); - - var testFails = function() { - // setup fake test object - var f = new m.Tap(function(newout) { out = newout }); // the TAP that's failing - f.out = function(newout) { out = newout }; - f.plan(3); - - // begin real tests! - f.ok(false, 'false fails'); - t.like(out, /not ok 1 - false fails/, 'false failed'); - - f.ok(0, 'zero fails'); - t.like(out, /not ok 2 - zero fails/, '0 failed'); - - f.is(0, 1, 'zero is one'); - t.like(out, /not ok 3 - zero is one/, '0 != 1'); - }; - testFails(); - - var testPass = function() { - t.ok(true, 'true is true'); - t.is(1,1, '1 is 1'); - t.pass('pass passes'); - t.like("hello world", /hel+o/, 'regexen work'); - t.unlike("hello there", /world/, 'no world'); - }; - testPass(); - - var testPlan = function() { - // setup fake test object - var f = new m.Tap(function(newout) { out = newout }); // the TAP that's failing - f.out = function(newout) { out = newout }; - f.plan(2); - - // begin real tests! - f.ok(false, 'false fails'); - t.is(f.counter, 1, 'counter increments by one'); - t.is(f.planned, 2, 'planned = 2'); - }; - testPlan(); - - var testTodoSkip = function() { - var out; - t.can_ok(m.Tap, 'todo', 'skip'); - var f = new m.Tap(); // the TAP that's failing - f.out = function(newout) { out = newout }; - f.plan(4); - - f.todo(function() { - f.ok(true, 'true is true'); - }); - t.like(out, /ok 1 - # TODO: true is true/g, - 'the non todo output is suitably formatted'); - f.ok(!false, 'not false is true'); - t.like(out, /ok 2 -/g, 'the regular output is suitably formatted'); - - f.skip(true, 'because I said so', 1, - function() { - f.is(1, 2, 'one is two'); + var testCan = function () { + // setup fake test object + var f = new m.Tap(function(newout) { out = newout }); // the TAP thats failing + f.out = function(newout) { out = newout }; + f.diag = function(newdiag) { diag += newdiag }; + f.plan(4); + + //mock a fake object to run test against + var obj = new Object; + obj.run = function() {}; + var method = 'run'; + + // begin real tests! + f.can_ok(obj, 'not_there'); + t.like(out, /not ok 1 - object can \[ not_there \]/, 'can_ok failed'); + f.can_ok(obj, method); + diag = ''; + t.like(out, /ok 2 - object can \[ run \]/, 'can_ok passed'); + + //Now we need to test the whole prototype method assignment thing + + function MockObj() { + this.attr = 1; } - ); - t.like(out, /^not ok 3 - # SKIP because I said so$/, - 'the skipped output is suitably formatted'); - f.is(1, 1, 'one is one'); - t.like(out, /ok 4 - one is one/, - 'the non skipped output is suitable formatted'); - }; - testTodoSkip(); - - return t; + + MockObj.prototype.fakeme = function () {}; + + f.can_ok(MockObj, 'fakeme'); + diag = ''; + t.like(out, /^ok .* \[ fakeme \]/, + 'can_ok recognized prototype methods'); + f.can_ok(MockObj, 'fakeme2'); + diag = ''; + t.like(out, /^not ok .* \[ fakeme2 \]/, + 'can_ok prototype recognization doesnt break methods'); + }; + + var testLike = function() { + // setup fake test object + var f = new m.Tap(function(newout) { out = newout }); // the TAP that's failing + f.out = function(newout) { out = newout }; + f.plan(1); + + // begin real tests! + f.like("hello", /hello/, "hello matches hello"); + t.like(out, /ok 1 - hello matches hello/, 'got description in TAP output'); + }; + + var testDiag = function() { + // setup fake test object + var f = new m.Tap(function(newout) { out = newout }); // the TAP that's failing + f.out = function(newout) { out = newout }; + f.plan(10); + // begin real tests! + f.diag("hello"); + t.like(out, /# hello/, 'got hello'); + }; + + var testException = function() { + // setup fake test object + var f = new m.Tap(function(newout) { out = newout }); // the TAP that's failing + f.out = function(newout) { out = newout }; + f.plan(2); + + // begin real tests! + f.throws_ok(function() {throw new Error('I made a boo boo')}, 'I made a boo boo'); + //t.diag(out); + t.like(out, /ok 1 - code threw \[Error: I made a boo boo\]/, 'uncaught exception'); + f.throws_ok(function() {}, 'I made a boo boo'); + //t.diag(out); + t.like(out, /not ok 2 - code threw \[ \]/, 'false failed'); + }; + testException(); + + var testFails = function() { + // setup fake test object + var f = new m.Tap(function(newout) { out = newout }); // the TAP that's failing + f.out = function(newout) { out = newout }; + f.plan(3); + + // begin real tests! + f.ok(false, 'false fails'); + t.like(out, /not ok 1 - false fails/, 'false failed'); + + f.ok(0, 'zero fails'); + t.like(out, /not ok 2 - zero fails/, '0 failed'); + + f.is(0, 1, 'zero is one'); + t.like(out, /not ok 3 - zero is one/, '0 != 1'); + }; + testFails(); + + var testPass = function() { + t.ok(true, 'true is true'); + t.is(1,1, '1 is 1'); + t.pass('pass passes'); + t.like("hello world", /hel+o/, 'regexen work'); + t.unlike("hello there", /world/, 'no world'); + }; + testPass(); + + var testPlan = function() { + // setup fake test object + var f = new m.Tap(function(newout) { out = newout }); // the TAP that's failing + f.out = function(newout) { out = newout }; + f.plan(2); + + // begin real tests! + f.ok(false, 'false fails'); + t.is(f.counter, 1, 'counter increments by one'); + t.is(f.planned, 2, 'planned = 2'); + }; + testPlan(); + + var testTodoSkip = function() { + var out; + t.can_ok(m.Tap, 'todo', 'skip'); + var f = new m.Tap(); // the TAP that's failing + f.out = function(newout) { out = newout }; + f.plan(4); + + f.todo(function() { + f.ok(true, 'true is true'); + }); + t.like(out, /ok 1 - # TODO: true is true/g, + 'the non todo output is suitably formatted'); + f.ok(!false, 'not false is true'); + t.like(out, /ok 2 -/g, 'the regular output is suitably formatted'); + + f.skip(true, 'because I said so', 1, + function() { + f.is(1, 2, 'one is two'); + } + ); + t.like(out, /^not ok 3 - # SKIP because I said so$/, + 'the skipped output is suitably formatted'); + f.is(1, 1, 'one is one'); + t.like(out, /ok 4 - one is one/, + 'the non skipped output is suitable formatted'); + }; + testTodoSkip(); + + return t; + } + runNodeTap("Tap dogfood test suite", tapSuite); });