mirror of
https://github.com/zaphar/test-tap.git
synced 2025-07-21 20:19:49 -04:00
refactor: Extract the rendering into an interface
This commit is contained in:
parent
8841ca71bd
commit
d4da1d7dee
115
src/TAP.mjs
115
src/TAP.mjs
@ -19,6 +19,32 @@
|
|||||||
* @license Artistic-2.0
|
* @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.
|
* The Tap Test Class helper.
|
||||||
*/
|
*/
|
||||||
@ -31,35 +57,29 @@ class Tap {
|
|||||||
passed = 0;
|
passed = 0;
|
||||||
/** @type Number */
|
/** @type Number */
|
||||||
failed = 0;
|
failed = 0;
|
||||||
/** @type function(TestOutput) */
|
/** @type TapRenderer */
|
||||||
#renderLine
|
#renderer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new Tap Suite with a renderLine function.
|
* Construct a new Tap Suite with a renderLine function.
|
||||||
* @param {function(string)}
|
* @param {TapRenderer}
|
||||||
*/
|
*/
|
||||||
constructor(renderFunc) {
|
constructor(renderFunc) {
|
||||||
this.#renderLine = renderFunc;
|
this.#renderer = renderFunc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static Browser() {
|
static Browser() {
|
||||||
return new Tap(function(text) {
|
return new Tap(new BrowserRenderer());
|
||||||
// TODO(zaphar): Handle output in a Browser context.
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Node() {
|
static Node() {
|
||||||
return new Tap(function(text) {
|
return new Tap(new NodeRenderer());
|
||||||
import('node:process').then(loaded => {;
|
|
||||||
loaded.stdout.write(text + "\n");
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Renders output for the test results */
|
/** Renders output for the test results */
|
||||||
out(text) {
|
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){
|
diag(msg){
|
||||||
if (!msg) {
|
if(!msg) {
|
||||||
msg = " ";
|
msg = " ";
|
||||||
}
|
}
|
||||||
var lines = msg.split("\n");
|
var lines = msg.split("\n");
|
||||||
for (var line of lines) {
|
this.#renderer.diag(lines);
|
||||||
this.out('# ' + msg);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Render a pass TAP output message.
|
/** Render a pass TAP output message.
|
||||||
@ -241,11 +255,8 @@ class Tap {
|
|||||||
var pass = true;
|
var pass = true;
|
||||||
for (var i=1; i<arguments.length; i++) {
|
for (var i=1; i<arguments.length; i++) {
|
||||||
if (typeof(obj[arguments[i]]) != 'function') {
|
if (typeof(obj[arguments[i]]) != 'function') {
|
||||||
//this.diag('TypeOf ' + arguments[i] + ' method is: ' + typeof(obj[arguments[i]]) );
|
|
||||||
//this.diag('TypeOf prototype is: ' + typeof(obj.prototype) );
|
|
||||||
if (typeof(obj.prototype) != 'undefined') {
|
if (typeof(obj.prototype) != 'undefined') {
|
||||||
var result = typeof eval('obj.prototype.' + arguments[i]);
|
var result = typeof eval('obj.prototype.' + arguments[i]);
|
||||||
//this.diag('TypeOf prototype method is: ' + result);
|
|
||||||
if (result == 'undefined') {
|
if (result == 'undefined') {
|
||||||
pass = false;
|
pass = false;
|
||||||
this.diag('Missing ' + arguments[i] + ' method');
|
this.diag('Missing ' + arguments[i] + ' method');
|
||||||
@ -319,5 +330,59 @@ class Tap {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a test and render a summarization.
|
||||||
|
*
|
||||||
|
* @param {Tap} t
|
||||||
|
* @param {string} testName
|
||||||
|
* @param {function(Tap)} test
|
||||||
|
*/
|
||||||
|
function runTest(t, testName, test) {
|
||||||
|
t.diag('running ' + testName + ' tests');
|
||||||
|
try {
|
||||||
|
test(t);
|
||||||
|
if (t.planned > 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.
|
// TODO(zaphar): The runner interface as well.
|
||||||
export { Tap };
|
export { Tap, runNodeTap, runBrowserTap };
|
||||||
|
|
||||||
|
16
src/Tap.d.js
Normal file
16
src/Tap.d.js
Normal file
@ -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<string>} lines
|
||||||
|
*/
|
||||||
|
diag(lines) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,150 +1,154 @@
|
|||||||
import('../src/TAP.mjs').then(m => {
|
import('../src/TAP.mjs').then(m => {
|
||||||
var out = "nothing yet";
|
var runNodeTap = m.runNodeTap;
|
||||||
var diag = "";
|
|
||||||
var t = m.Tap.Node();
|
function tapSuite(t) {
|
||||||
t.plan(27);
|
var out = "nothing yet";
|
||||||
|
var diag = "";
|
||||||
var testCan = function () {
|
t.plan(17);
|
||||||
// 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 testCan = function () {
|
||||||
var obj = new Object;
|
// setup fake test object
|
||||||
obj.run = function() {};
|
var f = new m.Tap(function(newout) { out = newout }); // the TAP thats failing
|
||||||
var method = 'run';
|
f.out = function(newout) { out = newout };
|
||||||
|
f.diag = function(newdiag) { diag += newdiag };
|
||||||
// begin real tests!
|
f.plan(4);
|
||||||
f.can_ok(obj, 'not_there');
|
|
||||||
t.like(out, /not ok 1 - object can \[ not_there \]/, 'can_ok failed');
|
//mock a fake object to run test against
|
||||||
f.can_ok(obj, method);
|
var obj = new Object;
|
||||||
diag = '';
|
obj.run = function() {};
|
||||||
t.like(out, /ok 2 - object can \[ run \]/, 'can_ok passed');
|
var method = 'run';
|
||||||
|
|
||||||
//Now we need to test the whole prototype method assignment thing
|
// begin real tests!
|
||||||
|
f.can_ok(obj, 'not_there');
|
||||||
function MockObj() {
|
t.like(out, /not ok 1 - object can \[ not_there \]/, 'can_ok failed');
|
||||||
this.attr = 1;
|
f.can_ok(obj, method);
|
||||||
}
|
diag = '';
|
||||||
|
t.like(out, /ok 2 - object can \[ run \]/, 'can_ok passed');
|
||||||
MockObj.prototype.fakeme = function () {};
|
|
||||||
|
//Now we need to test the whole prototype method assignment thing
|
||||||
f.can_ok(MockObj, 'fakeme');
|
|
||||||
diag = '';
|
function MockObj() {
|
||||||
t.like(out, /^ok .* \[ fakeme \]/,
|
this.attr = 1;
|
||||||
'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$/,
|
MockObj.prototype.fakeme = function () {};
|
||||||
'the skipped output is suitably formatted');
|
|
||||||
f.is(1, 1, 'one is one');
|
f.can_ok(MockObj, 'fakeme');
|
||||||
t.like(out, /ok 4 - one is one/,
|
diag = '';
|
||||||
'the non skipped output is suitable formatted');
|
t.like(out, /^ok .* \[ fakeme \]/,
|
||||||
};
|
'can_ok recognized prototype methods');
|
||||||
testTodoSkip();
|
f.can_ok(MockObj, 'fakeme2');
|
||||||
|
diag = '';
|
||||||
return t;
|
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);
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user