UNPKG

specify-core

Version:

Describe, structure and runs tests for the Specify framework.

388 lines 13 kB
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