Commit 4f66753d authored by Daniel Shumway's avatar Daniel Shumway

test(distilled): add quick self-tests

- probably could be *considerably* cleaned up and reorganized
- strong evidence that `result` *should* be exposed as a promise
parent a539e705
......@@ -11,7 +11,7 @@
],
"main": "src/node_modules/Distilled.js",
"scripts": {
"test": "node src/tests/tape.js"
"test": "node src/tests/distilled.js && node src/tests/tape.js"
},
"repository": {
"type": "git",
......
var Distilled = require('Distilled');
/*
* Attach singleton since that's all we need
* In larger libraries, you'd probably want to build a more complicated harness
*/
Distilled.suite = new Distilled();
console.log('Distilled self-test: ');
require('./distilled/test.js');
require('./distilled/callback.js');
require('./distilled/assertions.js');
require('./distilled/recursion.js');
require('./distilled/reporter.js');
process.on('exit', function (err) {
console.log();
if (process.exitCode === 1) {
console.log('Done: test suite failed!');
} else {
console.log('Done: test suite passed');
}
console.log();
});
var Distilled = require('Distilled');
var assert = require('assert');
var suite = Distilled.suite;
function deferred () {
var result = {};
result.promise = new Promise(function (res, rej) {
result.resolve = res;
result.reject = rej;
});
return result;
}
/*
* BASIC TEST RESOLUTION
*----------------------------------------------------------
*
* This checks that logic for tests passing/failing works correctly
* It also does shallow tests for recursion
*
* Ways to resolve a test include:
* - passing nothing
* - passing a (failed) promise
* - passing false
*
* If a function or a resolved promise are passed:
* - the returned/resolved result is recursively evaluated with the above rules
*/
suite.test('basic types', function () {
var def = deferred();
var _suite = new Distilled(function (test) {
if (test === _reject) {
def.resolve(assertion);
}
});
var _null = _suite.test('null', null),
_undefined = _suite.test('undefined', undefined),
_true = _suite.test('true', true),
_false = _suite.test('false', false),
_reject = _suite.test('reject', Promise.reject('error'));
function assertion () {
this.test('undefined', function () {
assert.strictEqual(_null.status, Distilled.STATUS.PASSED, 'Accept `null`');
assert.strictEqual(_undefined.status, Distilled.STATUS.PASSED, 'Accept `undefined`');
});
this.test('boolean', function () {
assert.strictEqual(_true.status, Distilled.STATUS.PASSED, 'Accept `true`');
assert.strictEqual(_false.status, Distilled.STATUS.FAILED, 'Reject `false`');
});
this.test('rejected promises', function () {
assert.strictEqual(_reject.status, Distilled.STATUS.FAILED, 'Reject rejected promises');
assert.strictEqual(_reject.error, 'error', '`test.error` is the rejected promise\'s error');
});
}
return def;
});
suite.test('advanced recursion', function () {
var def = deferred();
var _suite = new Distilled(function (test) {
if (test === _func_exception) {
def.resolve(assertion);
}
});
var _res_undefined = _suite.test('res undefined', Promise.resolve()),
_res_false = _suite.test('res false', Promise.resolve(false)),
_func_undefined = _suite.test('func undefined', function () {}),
_func_false = _suite.test('func false', function () { return false; }),
_func_exception = _suite.test('func exception', function () { throw 'error'; });
function assertion () {
this.test('promises', function () {
assert.strictEqual(_res_undefined.status, Distilled.STATUS.PASSED, 'Promise can resolve to undefined');
assert.strictEqual(_res_false.status, Distilled.STATUS.FAILED, 'Promise can resolve to boolean');
this.test('functions', function () {
var def = deferred();
var _res_function = _suite.test('res function', function (_test) {
assert.strictEqual(_test, _res_function, 'resulting function is passed test');
assert.strictEqual(this, _res_function, '`this` is set to the resulting test/suite');
def.resolve();
});
return def.promise;
});
});
this.test('functions', function () {
assert.strictEqual(_func_undefined.status, Distilled.STATUS.PASSED, 'Function can resolve to undefined');
assert.strictEqual(_func_false.status, Distilled.STATUS.FAILED, 'Function can resolve to boolean');
assert.strictEqual(_func_exception.status, Distilled.STATUS.FAILED, 'Throwing an error causes the test to fail');
assert.strictEqual(_func_exception.error, 'error', '`test.error` is set to the thrown error');
this.test('context', function () {
var def = deferred();
var _ctx = _suite.test('ctx', function (_test) {
assert.strictEqual(_test, _ctx, 'function is passed test');
assert.strictEqual(this, _ctx, '`this` is set to the resulting test/suite');
def.resolve();
});
return def;
});
});
}
return def.promise;
});
var Distilled = require('Distilled');
var assert = require('assert');
var suite = Distilled.suite;
function deferred () {
var result = {};
result.promise = new Promise(function (res, rej) {
result.resolve = res;
result.reject = rej;
});
return result;
}
/*
* CALLBACK TESTS
*-----------------------------------------------------------
*
* Whenever a test resolves, a callback is called:
* - this is primarily a place to do reporting and logging
* - it can also be adapted into a harness runner
* - it can also be used to extend Distilled and add new features
*
* You don't need to understand how Promises work to use Distilled:
* - but, if you do understand Promises, that will probably make you better
* - tests are executed as promises, and when they resolve, the callback is called
*/
suite.test('Basic callback', function (test) {
var def = deferred();
var _suite = new Distilled(function (_test) {
def.resolve(assertion.bind(this, _test));
});
function assertion (_test) {
var that = this;
test.test('Callback references', function () {
assert.strictEqual(_test, _suite, 'Callback is passed the resulting test/suite as a parameter');
assert.strictEqual(that, _suite, '`this` is set to the resulting test/suite');
});
}
return def.promise;
});
suite.test('Multiple tests', function () {
var def = deferred(),
calls = [],
tests = [];
var _suite = new Distilled(function () {
calls.push(this);
if (calls.length === tests.length) {
def.resolve(assertion);
}
});
tests.push(_suite);
tests.push(_suite.test('A'));
tests.push(_suite.test('B'));
function assertion () {
this.test('Correct test/suite passed into each callback', function () {
assert.deepEqual(calls, tests);
});
}
return def.promise;
});
suite.test('Nested tests', function () {
var def = deferred(),
calls = [],
tests = [];
var _suite = new Distilled(function () {
calls.push(this);
if (calls.length === tests.length) {
def.resolve(assertion);
}
});
var A = _suite.test('A'),
B = A.test('B'),
C = B.test('C');
tests.push(_suite);
tests.push(A);
tests.push(B);
tests.push(C);
function assertion () {
this.test('Correct test/suite passed into each callback', function () {
assert.deepEqual(calls, tests);
});
}
return def.promise;
});
suite.test('Test statuses are set correctly', function () {
this.test('for passed tests', function () {
var def = deferred(),
pass;
var _suite = new Distilled(function () {
if (this === pass) {
def.resolve();
}
});
pass = _suite.test('A');
function assertion () {
assert.strictEqual(pass.label, 'A');
assert.strictEqual(pass.status, Distilled.STATUS.PASSED, '`test.status` is set to `STATUS.PASSED`');
assert.equal(pass.error, null);
}
return def.promise;
});
this.test('for failed tests', function () {
var def = deferred(),
fail,
error = new Error('error');
var _suite = new Distilled(function () {
if (this === fail) {
def.resolve(assertion);
}
});
fail = _suite.test('A', Promise.reject(error));
function assertion () {
assert.strictEqual(fail.label, 'A');
assert.strictEqual(fail.status, Distilled.STATUS.FAILED, '`test.status` is set to `STATUS.FAILED`');
assert.strictEqual(fail.error, error, '`test.error` is set to the error the test failed with');
}
return def.promise;
});
});
suite.test('Skip children of failed tests', function () {
var def = deferred(),
calls = [];
var _suite = new Distilled(function () {
calls.push(this);
if (this === _b) {
def.resolve(assertion);
}
});
var _a = _suite.test('A', false).test('a').test('_a'),
_b = _suite.test('B').test('b').test('_b');
function assertion () {
assert.ok(calls.indexOf(_a) === -1, '`_a` should not be called');
}
return def.promise;
});
var Distilled = require('Distilled');
var assert = require('assert');
var suite = Distilled.suite;
function deferred () {
var result = {};
result.promise = new Promise(function (res, rej) {
result.resolve = res;
result.reject = rej;
});
return result;
}
/*
* RESOLUTION RECURSION
*-------------------------------------------------
* Short tests to provide some evidence that resolution recursion is actually infinite
*/
suite.test('Recursively resolving function results', function () {
var def = deferred();
var _suite = new Distilled(function () {
if (this === _b) {
def.resolve(assertion);
}
});
var _a = _suite.test('A', function () {
return function () {
return function () {
return function () {
return function () {
return true;
};
};
};
};
});
var _b = _suite.test('B', function () {
return function () {
return function () {
return function () {
return function () {
return false;
};
};
};
};
});
function assertion () {
assert.strictEqual(_a.status, Distilled.STATUS.PASSED, 'nested functions pass correctly');
assert.strictEqual(_b.status, Distilled.STATUS.FAILED, 'nested functions fail correctly');
}
return def.promise;
});
suite.test('Recursively resolving promise results', function (t) {
var def = deferred();
var _suite = new Distilled(function () {
if (this === _b) {
def.resolve(assertion);
}
});
var _a = _suite.test('A', Promise.resolve(
Promise.resolve(
Promise.resolve(
Promise.resolve(
Promise.resolve(true)
)
)
)
));
var _b = _suite.test('B', Promise.resolve(
Promise.resolve(
Promise.resolve(
Promise.resolve(
Promise.resolve(false)
)
)
)
));
function assertion () {
assert.strictEqual(_a.status, Distilled.STATUS.PASSED, 'nested promises pass correctly');
assert.strictEqual(_b.status, Distilled.STATUS.FAILED, 'nested promises fail correctly');
}
return def.promise;
});
suite.test('Recursively resolving "weird" results', function () {
var def = deferred();
var _suite = new Distilled(function () {
if (this === _b) {
def.resolve(assertion);
}
});
var _a = _suite.test('A', function () {
return Promise.resolve(function () {
return Promise.resolve(function () {
return true;
});
});
});
var _b = _suite.test('B', function () {
return Promise.resolve(function () {
return Promise.resolve(function () {
return false;
});
});
});
function assertion () {
assert.strictEqual(_a.status, Distilled.STATUS.PASSED, 'nested promises pass correctly');
assert.strictEqual(_b.status, Distilled.STATUS.FAILED, 'nested promises fail correctly');
}
return def.promise;
});
suite.test('Exceptions from functions within promises (#9)', function () {
var def = deferred();
var _suite = new Distilled(function () {
if (this === _a) {
def.resolve(assertion);
}
});
var _a = _suite.test('A', function () {
return Promise.resolve(function () {
throw 'error';
});
});
function assertion () {
assert.strictEqual(_a.status, Distilled.STATUS.FAILED, 'Exceptiosn from recursively called functions are caught correctly');
}
return def.promise;
});
var Distilled = require('Distilled');
var assert = require('assert');
var suite = Distilled.suite;
var reporter = Distilled.REPORTERS.default;
function deferred () {
var result = {};
result.promise = new Promise(function (res, rej) {
result.resolve = res;
result.reject = rej;
});
return result;
}
var output;
function mock (setup, callback) {
return function (t) {
output = ''; //Mock
var write = process.stdout.write;
process.stdout.write = function (message) {
output = output + message;
};
var processCode = process.exitCode;
setup.call(this, t);
process.stdout.write = write; //Unmock
callback.call(this, t);
process.exitCode = processCode; //Final unmock
};
};
/*
* DEFAULT REPORTER
*------------------------------------
* A limited test of the default reporter (only deals with node side of things)
*
* - temporarily suppresses output while supporter is running (console.log will not work)
* - these tests don't go super in-depth, because honestly there's not a ton of risk here.
*/
suite.test('Root test is skipped', mock(function () {
var test = {};
reporter.call(test, test);
}, function () {
assert.strictEqual(output, '', 'Root test is skipped');
}));
suite.test('Passed tests', mock(function () {
var test = {
status : Distilled.STATUS.PASSED,
parent : {},
};
reporter.call(test, test);
}, function () {
assert.strictEqual(output, '.', 'Passed tests are logged as `.`');
}));
suite.test('Failed tests', mock (function () {
var test = {
status : Distilled.STATUS.FAILED,
label : 'child',
error : {
expected : 'expected',
actual : 'actual',
stack : 'stack'
},
parent : {
label : 'parent',
parent : {}
}
};
reporter.call(test, test);
}, function () {
assert.strictEqual(output,
'X\n\n()(parent)(child) failed!\nExpected: expected\nActual: actual\nstack\n\n',
'Failed tests write the full error');
assert.strictEqual(process.exitCode, 1, 'Failed tests set `process.exitCode` to 1');
}));
var Distilled = require('Distilled');
var assert = require('assert');
var suite = Distilled.suite;
function deferred () {
var result = {};
result.promise = new Promise(function (res, rej) {
result.resolve = res;
result.reject = rej;
});
return result;
}
/*
* BASIC TEST BEHAVIOR
*-----------------------------------------
*
* Tests can be infinitely nested and chained.
* - structure can be accessed through `parent` and `children`
* - children of failed tests are not called/resolved
*/
suite = suite.test('Basic Test Behavior');
suite.test('Tests/suites expose a `test` method', function () {
var _suite = new Distilled(function () {});
assert.strictEqual(typeof suite.test, 'function');
this.test('Calling `test` returns a new test/suite of the same type', function () {
assert.strictEqual(Object.getPrototypeOf(_suite), Object.getPrototypeOf(_suite.test()));
});
});
/*
* Returning promises and taking advantage of recursively resolving test results:
* - this allows you to separate your setup logic and your testing logic, improving readability.
*/
suite.test('Child tests/suites are chained off of their parents', function () {
var def = deferred(),
calls = [];
//Setup
var _suite = new Distilled(function () {});
var _parent = _suite.test('A', function () {
calls.push('A');
});
var _child = _parent.test('a', function () {
calls.push('a');
def.resolve(assertions); //Ready to run assertions
});
_suite.test('B', function () {
calls.push('B');
});
//Tests
function assertions () {
/*
* Promise resolution order means that `a` should resolve last.
* - promises that are chained will resolve after promises that are added synchronously.
*/
assert.deepEqual(calls, ['A', 'B', 'a'], 'Child tests are resolved in the correct order');
this.test('Children/parents are attached to each other', function () {
assert.strictEqual(_child.parent, _parent, '`parent` points to parent test');
assert.strictEqual(_parent.children[0], _child, '`child` is added to `children array on parent test/suite`');
this.test('Child is resolved only once parent has resolved', function () {
assert.strictEqual(_parent.status, Distilled.STATUS.PASSED);
});
});
}
return def.promise;
});
suite.test('Ignore children of failed tests', function (t) {
var def = deferred(),
calls = [];
var _suite = new Distilled(function () {});
_suite.test('A', false).test('a', function () {
calls.push('a');
});
_suite.test('B').test('b').test('_b', function () {
def.resolve(assertions); //once again using promise resolution order (tested above) to make sure everything has run.
});
function assertions () {
assert.equal(calls.length, 0, 'Children of failed tests should never be resolved');
}
return def.promise;
});
console.log('Tape third-party test:');
require('./tape/test.js');
require('./tape/assertions.js');
require('./tape/callback.js');
......
......@@ -101,11 +101,6 @@ tape('test statuses are set correctly for failed test', function (t) {
fail = suite.test('A', Promise.reject(error));
});
/*
* This test relies on promise resolution order:
* - it's possible this is not reliable.
* - consider codifying promise resolution order, if it is actually predictable and possible to do so.
*/
tape('Skip children of failed tests', function (t) {
t.plan(1);
......@@ -114,7 +109,7 @@ tape('Skip children of failed tests', function (t) {
++calls;
if (this.label === '_a') {
t.pass('label: ' + this.status);
t.fail('label: ' + this.status);
}
if (this.label === '_b') {
......
......@@ -14,7 +14,6 @@ var output;
function mock (setup, callback) {
return function (t) {
output = ''; //Mock
console.log('output: ', output);
var write = process.stdout.write;
process.stdout.write = function (message) {
output = output + message;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment