Source: core.js

// 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[&lt;Number/ms&gt;]
 *          , slow    : Maybe[&lt;Number/ms&gt;]
 *          , 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