"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Task_1 = require("@dojo/core/async/Task");
var Deferred_1 = require("./Deferred");
var Test_1 = require("./Test");
var Suite = (function () {
    function Suite(options) {
        var _this = this;
        this.publishAfterSetup = false;
        if (!options.name && !options.executor) {
            throw new Error('A Suite requires a name or executor');
        }
        Object.keys(options).filter(function (key) {
            return key !== 'tests';
        }).forEach(function (key) {
            _this[key] = options[key];
        });
        this.tests = [];
        if (options.tests) {
            options.tests.forEach(function (suiteOrTest) { return _this.add(suiteOrTest); });
        }
    }
    Object.defineProperty(Suite.prototype, "bail", {
        get: function () {
            return this._bail || (this.parent && this.parent.bail);
        },
        set: function (value) {
            this._bail = value;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Suite.prototype, "executor", {
        get: function () {
            return this._executor || (this.parent && this.parent.executor);
        },
        set: function (value) {
            if (this._executor) {
                throw new Error('AlreadyAssigned: an executor may only be set once per suite');
            }
            this._executor = value;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Suite.prototype, "grep", {
        get: function () {
            return this._grep || (this.parent && this.parent.grep) || /.*/;
        },
        set: function (value) {
            this._grep = value;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Suite.prototype, "id", {
        get: function () {
            var name = [];
            var object = this;
            do {
                object.name != null && name.unshift(object.name);
            } while ((object = object.parent));
            return name.join(' - ');
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Suite.prototype, "parentId", {
        get: function () {
            var parent = this.parent;
            if (parent) {
                return parent.id;
            }
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Suite.prototype, "remote", {
        get: function () {
            return (this.parent && this.parent.remote) ? this.parent.remote : this._remote;
        },
        set: function (value) {
            if (this._remote) {
                throw new Error('AlreadyAssigned: remote may only be set once per suite');
            }
            this._remote = value;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Suite.prototype, "sessionId", {
        get: function () {
            var parent = this.parent;
            if (parent) {
                return parent.sessionId;
            }
            if (this._sessionId) {
                return this._sessionId;
            }
            if (this.remote) {
                return this.remote.session.sessionId;
            }
        },
        set: function (value) {
            this._sessionId = value;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Suite.prototype, "numTests", {
        get: function () {
            return this.tests.reduce(function (numTests, suiteOrTest) {
                if (isSuite(suiteOrTest)) {
                    return numTests + suiteOrTest.numTests;
                }
                return numTests + 1;
            }, 0);
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Suite.prototype, "numFailedTests", {
        get: function () {
            return this.tests.reduce(function (numFailedTests, suiteOrTest) {
                if (isSuite(suiteOrTest)) {
                    return numFailedTests + suiteOrTest.numFailedTests;
                }
                else if (suiteOrTest.error) {
                    return numFailedTests + 1;
                }
                return numFailedTests;
            }, 0);
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Suite.prototype, "numSkippedTests", {
        get: function () {
            return this.tests.reduce(function (numSkippedTests, suiteOrTest) {
                if (isSuite(suiteOrTest)) {
                    return numSkippedTests + suiteOrTest.numSkippedTests;
                }
                else if (suiteOrTest.skipped) {
                    return numSkippedTests + 1;
                }
                return numSkippedTests;
            }, 0);
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Suite.prototype, "hasParent", {
        get: function () {
            return Boolean(this.parent);
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Suite.prototype, "timeout", {
        get: function () {
            if (this._timeout != null) {
                return this._timeout;
            }
            if (this.parent) {
                return this.parent.timeout;
            }
            return 30000;
        },
        set: function (value) {
            this._timeout = value;
        },
        enumerable: true,
        configurable: true
    });
    Suite.prototype.add = function (suiteOrTest) {
        if (!Test_1.isTest(suiteOrTest) && !isSuite(suiteOrTest)) {
            throw new Error('Tried to add invalid suite or test');
        }
        suiteOrTest.parent = this;
        this.tests.push(suiteOrTest);
    };
    Suite.prototype.run = function () {
        var _this = this;
        var startTime;
        var end = function () {
            _this.timeElapsed = Date.now() - startTime;
            return _this.executor.emit('suiteEnd', _this);
        };
        var runLifecycleMethod = function (suite, name) {
            var args = [];
            for (var _i = 2; _i < arguments.length; _i++) {
                args[_i - 2] = arguments[_i];
            }
            return new Task_1.default(function (resolve) {
                var dfd;
                var timeout;
                suite.async = function (_timeout) {
                    timeout = _timeout;
                    dfd = new Deferred_1.default();
                    suite.async = function () {
                        return dfd;
                    };
                    return dfd;
                };
                var suiteFunc = suite[name];
                var returnValue = suiteFunc && suiteFunc.call.apply(suiteFunc, [suite, suite].concat(args));
                if (dfd) {
                    if (timeout) {
                        var timer_1 = setTimeout(function () {
                            dfd.reject(new Error('Timeout reached on ' + suite.id + '#' + name));
                        }, timeout);
                        dfd.promise.catch(function (_error) { }).then(function () { return timer_1 && clearTimeout(timer_1); });
                    }
                    if (Task_1.isThenable(returnValue)) {
                        returnValue.then(function (value) { return dfd.resolve(value); }, function (error) { return dfd.reject(error); });
                    }
                    returnValue = dfd.promise;
                }
                resolve(returnValue);
            }).catch(function (error) {
                suite.async = undefined;
                if (error !== Test_1.SKIP) {
                    if (!_this.error) {
                        _this.error = error;
                    }
                    throw error;
                }
            });
        };
        var start = function () {
            return _this.executor.emit('suiteStart', _this).then(function () {
                startTime = Date.now();
            });
        };
        var before = function () {
            return runLifecycleMethod(_this, 'before');
        };
        var after = function () {
            return runLifecycleMethod(_this, 'after');
        };
        this.error = null;
        this.timeElapsed = null;
        var task;
        try {
            task = this.publishAfterSetup ? before().then(start) : start().then(before);
        }
        catch (error) {
            return Task_1.default.reject(error);
        }
        return task.then(function () {
            var i = 0;
            var tests = _this.tests;
            var current;
            var runTestLifecycle = function (name, test) {
                var orderMethod = name === 'beforeEach' ? 'push' : 'unshift';
                var suiteQueue = [];
                var suite = _this;
                do {
                    suiteQueue[orderMethod](suite);
                } while ((suite = suite.parent));
                var current;
                return new Task_1.default(function (resolve, reject) {
                    var firstError;
                    function handleError(error) {
                        if (name === 'afterEach') {
                            firstError = firstError || error;
                            next();
                        }
                        else {
                            reject(error);
                        }
                    }
                    function next() {
                        var suite = suiteQueue.pop();
                        if (!suite) {
                            firstError ? reject(firstError) : resolve();
                            return;
                        }
                        current = runLifecycleMethod(suite, name, test).then(next, handleError);
                    }
                    next();
                }, function () {
                    suiteQueue.splice(0, suiteQueue.length);
                    if (current) {
                        current.cancel();
                    }
                });
            };
            return new Task_1.default(function (resolve, reject) {
                var firstError;
                var next = function () {
                    var test = tests[i++];
                    if (!test) {
                        firstError ? reject(firstError) : resolve();
                        return;
                    }
                    var handleError = function (error) {
                        if (!error.relatedTest) {
                            error.relatedTest = test;
                        }
                    };
                    function runWithCatch() {
                        return test.run().catch(function (error) {
                            handleError(error);
                        });
                    }
                    if (_this.skipped != null) {
                        test.skipped = _this.skipped;
                    }
                    if (isSuite(test)) {
                        current = runWithCatch();
                    }
                    else {
                        if (!_this.grep.test(test.id)) {
                            test.skipped = 'grep';
                        }
                        if (test.skipped != null) {
                            current = _this.executor.emit('testEnd', test);
                        }
                        else {
                            current = runTestLifecycle('beforeEach', test)
                                .then(runWithCatch)
                                .finally(function () { return runTestLifecycle('afterEach', test); })
                                .catch(function (error) {
                                firstError = firstError || error;
                                return handleError(error);
                            });
                        }
                    }
                    current.then(function () {
                        var skipRestOfSuite = function () {
                            _this.skipped = _this.skipped != null ? _this.skipped : BAIL_REASON;
                        };
                        if (isSuite(test) && test.skipped === BAIL_REASON) {
                            skipRestOfSuite();
                        }
                        else if (test.error && _this.bail) {
                            skipRestOfSuite();
                        }
                        next();
                    });
                };
                next();
            }, function () {
                i = Infinity;
                if (current) {
                    current.cancel();
                }
            });
        }).finally(function () { return _this.publishAfterSetup ? end().then(after) : after().then(end); });
    };
    Suite.prototype.skip = function (message) {
        if (message === void 0) { message = 'suite skipped'; }
        this.skipped = message;
        throw Test_1.SKIP;
    };
    Suite.prototype.toJSON = function () {
        return {
            name: this.name,
            id: this.id,
            parentId: this.parentId,
            sessionId: this.sessionId,
            hasParent: Boolean(this.parent),
            tests: this.tests.map(function (test) {
                return test.toJSON();
            }),
            timeElapsed: this.timeElapsed,
            numTests: this.numTests,
            numFailedTests: this.numFailedTests,
            numSkippedTests: this.numSkippedTests,
            skipped: this.skipped,
            error: this.error ? {
                name: this.error.name,
                message: this.error.message,
                stack: this.error.stack,
                relatedTest: this.error.relatedTest === this ? undefined : this.error.relatedTest
            } : null
        };
    };
    return Suite;
}());
exports.default = Suite;
function isSuite(value) {
    return value.name && value.tests && typeof value.numTests === 'number' && typeof value.hasParent === 'boolean';
}
exports.isSuite = isSuite;
function isSuiteOptions(value) {
    return !(value instanceof Suite) && value.name && value.tests && (value.before != null ||
        value.beforeEach != null ||
        value.after != null ||
        value.afterEach != null);
}
exports.isSuiteOptions = isSuiteOptions;
var BAIL_REASON = 'bailed';
//# sourceMappingURL=Suite.js.map