I decided to port my favorite unit testing framework, Catch, to JavaScript. So far I'm focusing on test cases and sections. I'll add assertions in phase 2, and output in phase 3.
The goal for this phase was to get all the assertions in the following test code to pass:
function assert(value, message) {
console.log(value ? 'pass' : 'FAIL', message || '')
}
TEST_CASE('See if this works right', '[stuff]', function(){
var x = 2, y = 4;
console.log('============');
SECTION('halving a good time', function(){
x /= 2;
y /= 2;
assert(x == 1 && y == 2, 'halved values');
});
SECTION('double the fun', function(){
x *= 2;
y *= 2;
assert(x == 4 && y == 8, 'doubled values');
SECTION('up high', function(){
x += 5;
y += 5;
assert(x == 9 && y == 13, 'high five');
});
SECTION('down low', function(){
x -= 5;
y -= 5;
assert(x == -1 && y == 3, 'low five');
});
});
});
Notice that the entire test case should be run from the beginning, once for each "leaf" section, just like Catch (follow the second link in this question for a more complete explanation).
Here's what I've written so far. The documentation is very minimal right now; I'll be annotating and commenting properly later.
( /** @type function(Window) **/ function(global) {
'use strict';
var context = [],
testCases = [],
currentTestCase,
nextTask;
/** Section.
*/
function Section(name, body, tags) {
this.name = name;
this.tags = tags;
this.body = body;
this.subsections = [];
this.subsectionIndex = 0;
this.subsectionsEncountered = 0;
}
/** Run section.
*/
Section.prototype.run = function() {
context.unshift(this);
this.subsectionsEncountered = 0;
this.body();
context.shift();
}
/** Update subsection.
*/
Section.prototype.updateSubsection = function(name, body) {
var index = this.subsectionIndex,
subsection = this.subsections[index];
if (!subsection) {
subsection = new Section(name, body);
this.subsections[index] = subsection;
} else {
subsection.body = body;
}
return subsection;
}
/** Test case.
*/
global['TEST_CASE'] = function(name, tags, body) {
var next;
currentTestCase = new Section(name, body, tags);
testCases.push(currentTestCase);
currentTestCase.run();
while (nextTask) {
next = nextTask;
nextTask = null;
next();
}
};
/** Test case section.
*/
global['SECTION'] = function(name, body) {
var outer = context[0],
oldNext = nextTask;
if (outer.subsectionIndex == outer.subsectionsEncountered++) {
nextTask = function() {
if (++outer.subsectionIndex < outer.subsectionsEncountered) {
currentTestCase.run();
} else if (oldNext) {
oldNext();
}
};
outer.updateSubsection(name, body).run();
}
};
}(this));
I've kept the macro-style naming conventions from Catch, and dumped a bunch of things in the global scope. This may or may not change, but this is one thing I'm probably not interested in having reviewed, since it's easier for me to keep the names exactly the same while porting. The rest is fair game.
Update: phase 2 is mostly complete.