Skip to content

Instantly share code, notes, and snippets.

@jmakeig
Last active October 3, 2016 16:53
Show Gist options
  • Select an option

  • Save jmakeig/9c7f8580d5fa9ac57b1d98bb68616466 to your computer and use it in GitHub Desktop.

Select an option

Save jmakeig/9c7f8580d5fa9ac57b1d98bb68616466 to your computer and use it in GitHub Desktop.
Minimal Tape implementation for MarkLogic 8
var harness = {
items: [],
register: function(test) {
this.items.push(test);
return this;
},
run: function() {
return new TestOutcomes(
this.items.map(function(test) {
//return test.call(null, 'Hi!');
return {
'test': test.name,
'outcomes': test.run()
}
})
);
}
};
function TestOutcomes(outcomes) {
this.value = outcomes;
}
TestOutcomes.prototype = {
toJSON: function(indent) {
return JSON.stringify(this.value, null, indent);
},
toTAP: function() {
function byType(type) {
return function(outcome) {
return type === outcome.outcome;
}
}
return this.value.map(function(test) {
return test.test
+ ' '
+ test.outcomes.filter(byType('pass')).length
+ ', ' +
+ test.outcomes.filter(byType('fail')).length
;
})
.join('\n');
}
}
/**
* Registers zero or more tests and returns the harness itself.
*
* @param {Iterable<Test>} tests
* @returns harness Singleton instance of the global harness
*/
function runner(/* ...tests */) {
for(var i = 0; i < arguments.length; i++) {
harness.register(arguments[i]);
}
return harness;
}
module.exports = runner;
function Test(name, impl) {
this.name = name || 'Test';
this.impl = impl;
this.planned = undefined;
this.tally = 0;
this.isEnded = false;
this.outcomes = [];
}
Test.prototype = {
/**
* Internal assertion.
*
* @param {string} operator
* @param {boolean} ok
* @param {any} actual
* @param {any} expected
* @param {string} [msg]
* @returns {void}
*/
assert: function(operator, ok, actual, expected, msg) {
var result;
if(this.isEnded) {
// result = {
// operator: 'end',
// outcome: 'fail',
// msg: 'Tried to assert ' + operator + ' after end() has been called'
// }
throw new Error('Tried to assert ' + operator + ' after end() has been called');
} else {
result = {
operator: operator,
outcome: true === ok ? 'pass' : 'fail',
expected: expected,
actual: actual,
message: msg
}
}
this.outcomes.push(result);
if(this.tally++ === this.planned) {
this.end();
}
},
/**
* @param {number} num
* @returns {void}
* @throws {TypeError}
*/
plan: function(num) {
if('number' !== typeof num || num < 1) {
throw new TypeError(num);
}
this.planned = num;
},
/**
* @returns {void}
*/
end: function() {
this.isEnded = true;
},
/**
* Runs the actual test, including clean-up to make sure `plan()`/`end()` have
* been called.
*
* @returns {Array<object>}
* @see assert
*/
run: function() {
this.impl.call(this, this);
if(this.planned) {
if(this.planned !== this.tally) {
var msg = 'Planned for ' + String(this.planned) + ' assertions, but got ' + String(this.tally);
this.assert('plan', false, this.tally, this.planned, msg);
} else {
this.end();
}
} else {
if(!this.isEnded) {
// TODO: Exception instead?
this.assert('end', this.isEnded, false, 'Never called end()');
}
}
return this.outcomes;
},
/********** Assertions ***************/
/**
* Whether the `value` is strictly (`===`) `true`.
*
* @param {any} value
* @param {string} [msg]
* @returns {void}
*/
true: function(value, msg) {
msg = msg || String(value) + ' is true?';
this.assert('true', true === value, true, value, msg);
},
/**
* Whether `actual` and `expected` are strictly (`===`) equal.
*
* @param {any} actual
* @param {any} expected
* @param {string} [msg]
* @returns {void}
*/
equal: function(actual, expected, msg) {
msg = msg || String(actual) + ' equals ' + String(expected);
this.assert('equal', expected === actual, actual, expected, msg);
},
/**
* @param {any} actual
* @param {any} expected
* @param {string} [msg]
* @returns {void}
*/
deepEqual: function(actual, expected, msg) {
msg = msg || 'deepEqual';
throw new Error('deepEqual() is not yet implemented');
},
/**
* @param {function} fct
* @param {Error|string} error
* @param {string} [msg]
* @returns {void}
*/
'throws': function(fct, error, msg) {
msg = msg || 'throws';
throw new Error('throws() is not yet implemented');
},
/**
* @param {string} [msg]
* @returns {void}
*/
skip: function(msg) {}
}
var runner = require('./runner');
/**
* Creates a new test and registers it with the harness.
*
* @example
* var test = require('/mltap/test');
* test('Uno', function(assert) { assert.plan(2); assert.true(true); assert.true(!!true); });
* test('Dos', function(assert) { assert.true(true); assert.end(); })
* .run().value;
*
* @param {string} name A short name describing the nature of the test
* @param {function(Test)} impl The implementation of the test
* @returns harness
*/
function test(name, impl) {
return runner(
new Test(name, impl)
);
}
module.exports = test;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment