// Copyright (c) 2013-2014 Quildreen Motta <quildreen@gmail.com>
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/**
* Represents and handles tests.
*
* @module hifive/core
*/
// -- Dependencies -----------------------------------------------------
var Future = require('data.future');
var Maybe = require('data.maybe');
var Bacon = require('baconjs');
var rx = require('rx');
var Async = require('control.async');
var Monads = require('control.monads');
var Lambda = require('core.lambda');
var adt = require('adt-simple');
var result = require('./result');
// -- Aliases ----------------------------------------------------------
var toArray = Function.call.bind([].slice);
var toNode = Async.toNode;
var timeout = Async.timeout;
var choice = Async.choice;
var sequence = Monads.sequence;
var constant = Lambda.constant;
var LogEntry = result.LogEntry;
var Duration = result.Duration;
var Result = result.Result;
// -- Helpers ----------------------------------------------------------
/**
* Captures things being output in `console.log`.
*
* @summary Void → Void → [LogEntry]
*/
function captureLogs() {
return typeof console == 'undefined' ? noop() : 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 [];
};
}
}
// -- Hooks ------------------------------------------------------------
/**
* Represents a list of actions.
*
* @class
* @summary
* { actions: Array[Future[Error, Void]] }
*/
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 = 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 → Bacon[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));
};
// -- Signals ----------------------------------------------------------
/**
* Signals we might have mixed with results
*
* @class
* @summary
* | TestResult: { value: Result }
* | Started: { value: Test }
* | Finished: { value: Test }
*/
var Signal = function () {
function Signal$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;
function Started$2(value) {
if (!(this instanceof Started$2)) {
return new Started$2(value);
}
if (value instanceof Test) {
this.value = value;
} else {
throw new TypeError('Unexpected type for field: Signal.Started.value');
}
}
Started$2.prototype = new Signal$2();
Started$2.prototype.constructor = Started$2;
function Finished$2(value) {
if (!(this instanceof Finished$2)) {
return new Finished$2(value);
}
if (value instanceof Test) {
this.value = value;
} else {
throw new TypeError('Unexpected type for field: Signal.Finished.value');
}
}
Finished$2.prototype = new Signal$2();
Finished$2.prototype.constructor = Finished$2;
var derived = adt.Cata.derive(adt.Base.derive({
name: 'Signal',
constructor: Signal$2,
prototype: Signal$2.prototype,
variants: [
{
name: 'TestResult',
constructor: TestResult$2,
prototype: TestResult$2.prototype,
fields: ['value']
},
{
name: 'Started',
constructor: Started$2,
prototype: Started$2.prototype,
fields: ['value']
},
{
name: 'Finished',
constructor: Finished$2,
prototype: Finished$2.prototype,
fields: ['value']
}
]
}));
Signal$2.TestResult = derived.variants[0].constructor;
Signal$2.Started = derived.variants[1].constructor;
Signal$2.Finished = derived.variants[2].constructor;
return Signal$2;
}();
var TestResult = Signal.TestResult;
var Started = Signal.Started;
var Finished = Signal.Finished;
// -- 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]
* }
*/
var 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');
}
if (test instanceof Future) {
this.test = test;
} else {
throw new TypeError('Unexpected type for field: Test.Case.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:hifive/core~Test
* @summary [String], Config → Bacon[Error, Signal]
*/
Case.prototype.run = function (path, config) {
var title = path.concat([this.name]);
var enabled = this.enabled.orElse(function (a) {
return new Maybe.Just(config.runOnly);
}).get();
return enabled(this) ? rx.Observable.fromNodeCallback(toNode(runTest(this)))() : rx.Observable.return(TestResult(Ignored(title)));
function runTest(testCase) {
return new Future(function (_, resolve) {
var mSlow = testCase.slow.orElse(function (a) {
return new Maybe.Just(config.slowThreshold);
});
var mTimeout = testCase.timeout.orElse(function (a) {
return new 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) {
return 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());
function execute(test) {
return this.beforeEach.run().concat(rx.Observable.return(Started(test))).concat(test.run(path.concat([suite.name]), config)).concat(rx.Observable.return(Finished(test))).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