specify-core
Version:
Describe, structure and runs tests for the Specify framework.
388 lines • 13 kB
JavaScript
var /**
* Represents and handles tests.
*
* @module specify-core/lib/core
*/
// -- Dependencies -----------------------------------------------------
Future = require('data.future');
var Maybe = require('data.maybe');
var rx = require('rx');
var Async = require('control.async')(Future);
var Monads = require('control.monads');
var Lambda = require('core.lambda');
var adt = require('adt-simple');
var result = require('./result');
var // -- Aliases ----------------------------------------------------------
toArray = Function.call.bind([].slice);
var toNode = Async.toNode;
var timeout = Async.timeout;
var choice = Async.choice;
var sequence = Monads.sequence;
var LogEntry = result.LogEntry;
var Duration = result.Duration;
var Result = result.Result;
var Success = Result.Success;
var Failure = Result.Failure;
var Ignored = Result.Ignored;
function captureLogs() {
return typeof console == 'undefined' ? noop() : /* otherwise */
wrapConsole();
function wrapConsole() {
var logs = [];
var old = console.log;
console.log = function () {
logs.push(LogEntry(new Date(), toArray(arguments)));
};
return function releaseConsole() {
console.log = old;
return logs;
};
}
function noop() {
return function releaseConsole() {
return [];
};
}
}
var Hook = function () {
function Hook$2(actions) {
if (!(this instanceof Hook$2)) {
return new Hook$2(actions);
}
if (Array.isArray ? Array.isArray(actions) : Object.prototype.toString.call(actions) === '[object Array]') {
this.actions = actions;
} else {
throw new TypeError('Unexpected type for field: Hook.actions');
}
}
var derived = // -- Hooks ------------------------------------------------------------
/**
* Represents a list of actions.
*
* @class
* @summary
* { actions: Array[Future[Error, Void]] }
*/
adt.Base.derive({
name: 'Hook',
constructor: Hook$2,
prototype: Hook$2.prototype,
variants: [{
name: 'Hook',
constructor: Hook$2,
prototype: Hook$2.prototype,
fields: ['actions']
}]
});
return derived.constructor;
}();
/**
* Runs a Hook
*
* @summary @Hook => Void → Rx.Observable[Error, α]
*/
Hook.prototype.run = function () {
return rx.Observable.create(function (observer) {
sequence(Future, this.actions).fork(function (error) {
observer.onError(error);
observer.onCompleted();
}, function () {
observer.onCompleted();
});
}.bind(this));
};
var // -- Signals ----------------------------------------------------------
/**
* Signals we might have mixed with results
*
* @class
* @summary
* | TestResult: { value: Result }
* | Started: { value: Test, path: Array[String] }
* | Finished: { value: Test, path: Array[String] }
*/
Signal = function () {
function Signal$2() {
}
function Started$2(value, path) {
if (!(this instanceof Started$2)) {
return new Started$2(value, path);
}
if (value instanceof Test) {
this.value = value;
} else {
throw new TypeError('Unexpected type for field: Signal.Started.value');
}
if (Array.isArray ? Array.isArray(path) : Object.prototype.toString.call(path) === '[object Array]') {
this.path = path;
} else {
throw new TypeError('Unexpected type for field: Signal.Started.path');
}
}
Started$2.prototype = new Signal$2();
Started$2.prototype.constructor = Started$2;
function Finished$2(value, path) {
if (!(this instanceof Finished$2)) {
return new Finished$2(value, path);
}
if (value instanceof Test) {
this.value = value;
} else {
throw new TypeError('Unexpected type for field: Signal.Finished.value');
}
if (Array.isArray ? Array.isArray(path) : Object.prototype.toString.call(path) === '[object Array]') {
this.path = path;
} else {
throw new TypeError('Unexpected type for field: Signal.Finished.path');
}
}
Finished$2.prototype = new Signal$2();
Finished$2.prototype.constructor = Finished$2;
function TestResult$2(value) {
if (!(this instanceof TestResult$2)) {
return new TestResult$2(value);
}
if (value instanceof Result) {
this.value = value;
} else {
throw new TypeError('Unexpected type for field: Signal.TestResult.value');
}
}
TestResult$2.prototype = new Signal$2();
TestResult$2.prototype.constructor = TestResult$2;
var derived = adt.Cata.derive(adt.Base.derive({
name: 'Signal',
constructor: Signal$2,
prototype: Signal$2.prototype,
variants: [
{
name: 'Started',
constructor: Started$2,
prototype: Started$2.prototype,
fields: [
'value',
'path'
]
},
{
name: 'Finished',
constructor: Finished$2,
prototype: Finished$2.prototype,
fields: [
'value',
'path'
]
},
{
name: 'TestResult',
constructor: TestResult$2,
prototype: TestResult$2.prototype,
fields: ['value']
}
]
}));
Signal$2.Started = derived.variants[0].constructor;
Signal$2.Finished = derived.variants[1].constructor;
Signal$2.TestResult = derived.variants[2].constructor;
return Signal$2;
}();
var Started = Signal.Started;
var Finished = Signal.Finished;
var TestResult = Signal.TestResult;
/**
* Returns the full path of a Signal.
*
* @summary @Signal => Void → String
*/
Signal.prototype.fullTitle = function () {
return this.path.concat([this.value.name]).join(' ');
};
TestResult.prototype.fullTitle = function () {
return this.value.fullTitle();
};
var // -- Tests ------------------------------------------------------------
/**
* Models each possibility in a test case.
*
* @class
* @summary
* | Suite: { name : String
* , tests : Array[Test]
* , beforeAll : Hook
* , afterAll : Hook
* , beforeEach : Hook
* , afterEach : Hook
* }
* | Case: { name : String
* , test : Future[Error, Void]
* , timeout : Maybe[<Number/ms>]
* , slow : Maybe[<Number/ms>]
* , enabled : Maybe[Case → Boolean]
* }
*/
Test = function () {
function Test$2() {
}
function Suite$2(name, tests, beforeAll, afterAll, beforeEach, afterEach) {
if (!(this instanceof Suite$2)) {
return new Suite$2(name, tests, beforeAll, afterAll, beforeEach, afterEach);
}
if (typeof name === 'string' || Object.prototype.toString.call(name) === '[object String]') {
this.name = name;
} else {
throw new TypeError('Unexpected type for field: Test.Suite.name');
}
if (Array.isArray ? Array.isArray(tests) : Object.prototype.toString.call(tests) === '[object Array]') {
this.tests = tests;
} else {
throw new TypeError('Unexpected type for field: Test.Suite.tests');
}
if (beforeAll instanceof Hook) {
this.beforeAll = beforeAll;
} else {
throw new TypeError('Unexpected type for field: Test.Suite.beforeAll');
}
if (afterAll instanceof Hook) {
this.afterAll = afterAll;
} else {
throw new TypeError('Unexpected type for field: Test.Suite.afterAll');
}
if (beforeEach instanceof Hook) {
this.beforeEach = beforeEach;
} else {
throw new TypeError('Unexpected type for field: Test.Suite.beforeEach');
}
if (afterEach instanceof Hook) {
this.afterEach = afterEach;
} else {
throw new TypeError('Unexpected type for field: Test.Suite.afterEach');
}
}
Suite$2.prototype = new Test$2();
Suite$2.prototype.constructor = Suite$2;
function Case$2(name, test, timeout$2, slow, enabled) {
if (!(this instanceof Case$2)) {
return new Case$2(name, test, timeout$2, slow, enabled);
}
if (typeof name === 'string' || Object.prototype.toString.call(name) === '[object String]') {
this.name = name;
} else {
throw new TypeError('Unexpected type for field: Test.Case.name');
}
this.test = test;
this.timeout = timeout$2;
this.slow = slow;
this.enabled = enabled;
}
Case$2.prototype = new Test$2();
Case$2.prototype.constructor = Case$2;
var derived = adt.Cata.derive(adt.Base.derive({
name: 'Test',
constructor: Test$2,
prototype: Test$2.prototype,
variants: [
{
name: 'Suite',
constructor: Suite$2,
prototype: Suite$2.prototype,
fields: [
'name',
'tests',
'beforeAll',
'afterAll',
'beforeEach',
'afterEach'
]
},
{
name: 'Case',
constructor: Case$2,
prototype: Case$2.prototype,
fields: [
'name',
'test',
'timeout',
'slow',
'enabled'
]
}
]
}));
Test$2.Suite = derived.variants[0].constructor;
Test$2.Case = derived.variants[1].constructor;
return Test$2;
}();
var Suite = Test.Suite;
var Case = Test.Case;
/**
* Runs a Test.
*
* @name run
* @memberof module:specify-core/lib/core~Test
* @summary [String], Config → Rx.Observable[Error, Signal]
*/
Case.prototype.run = function (path, config) {
var title = path.concat([this.name]);
var enabled = this.enabled.orElse(function (a) {
return Maybe.Just(config.runOnly);
}).get();
return enabled(this) ? rx.Observable.fromNodeCallback(toNode(runTest(this)))() : /* otherwise */
rx.Observable.return(TestResult(Ignored(title)));
function runTest(testCase) {
return new Future(function (_, resolve) {
var mSlow = testCase.slow.orElse(function (a) {
return Maybe.Just(config.slowThreshold);
});
var mTimeout = testCase.timeout.orElse(function (a) {
return Maybe.Just(config.timeout);
});
var started = new Date();
var releaseConsole = captureLogs();
choice([
timeout(mTimeout.get()),
testCase.test
]).fork(function (error) {
resolve(TestResult(Failure.create({
title: title,
exception: error,
duration: getDuration(),
log: releaseConsole()
})));
}, function () {
resolve(TestResult(Success.create({
title: title,
duration: getDuration(),
log: releaseConsole()
})));
});
function getDuration() {
return Duration.create({
started: started,
finished: new Date(),
slowThreshold: mSlow.get()
});
}
});
}
};
Suite.prototype.run = function (path, config) {
var thisPath = path.concat([this.name]);
return rx.Observable.return(Started(this, path)).concat(this.beforeAll.run()).concat(this.tests.map(execute.bind(this)).reduce(function (a, b) {
return a.concat(b);
}, rx.Observable.empty())).concat(this.afterAll.run()).concat(rx.Observable.return(Finished(this, path)));
function execute(test) {
return this.beforeEach.run().concat(test.isCase ? rx.Observable.return(Started(test, thisPath)) : rx.Observable.empty()).concat(test.run(thisPath, config)).concat(test.isCase ? rx.Observable.return(Finished(test, thisPath)) : rx.Observable.empty()).concat(this.afterEach.run());
}
};
// -- Exports ----------------------------------------------------------
module.exports = {
Test: Test,
Result: Result,
Duration: Duration,
LogEntry: LogEntry,
Signal: Signal,
Hook: Hook,
_Future: Future,
_Maybe: Maybe
};
//# sourceMappingURL=core.js.map