"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Deferred_1 = require("./Deferred");
var Task_1 = require("@dojo/core/async/Task");
var lang_1 = require("@dojo/core/lang");
var Test = (function () {
    function Test(options) {
        this.hasPassed = false;
        this.isAsync = false;
        this.timeElapsed = 0;
        this._usesRemote = false;
        if (!options.name && !options.test) {
            throw new Error('A Test requires a name and a test function');
        }
        lang_1.mixin(this, options);
    }
    Object.defineProperty(Test.prototype, "executor", {
        get: function () {
            return this.parent && this.parent.executor;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Test.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(Test.prototype, "parentId", {
        get: function () {
            return this.parent.id;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Test.prototype, "remote", {
        get: function () {
            this._usesRemote = true;
            return this.parent.remote;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Test.prototype, "sessionId", {
        get: function () {
            return this.parent.sessionId;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Test.prototype, "timeout", {
        get: function () {
            if (this._timeout != null) {
                return this._timeout;
            }
            else if (this.parent) {
                return this.parent.timeout;
            }
            else {
                return 30000;
            }
        },
        set: function (value) {
            this._timeout = value;
        },
        enumerable: true,
        configurable: true
    });
    Test.prototype.async = function (timeout, numCallsUntilResolution) {
        this.isAsync = true;
        if (timeout != null) {
            this.timeout = timeout;
        }
        if (!numCallsUntilResolution) {
            numCallsUntilResolution = 1;
        }
        var dfd = new Deferred_1.default();
        var oldResolve = dfd.resolve;
        dfd.resolve = function () {
            --numCallsUntilResolution;
            if (numCallsUntilResolution === 0) {
                oldResolve.apply(this, arguments);
            }
            else if (numCallsUntilResolution < 0) {
                throw new Error('resolve called too many times');
            }
        };
        this.async = function () {
            return dfd;
        };
        return dfd;
    };
    Test.prototype.restartTimeout = function (timeout) {
        var _this = this;
        timeout = timeout == null ? this.timeout : timeout;
        if (this._runTask) {
            clearTimeout(this._timer);
            var timer = setTimeout(function () {
                if (_this._runTask) {
                    _this._runTask.cancel();
                }
            }, timeout);
            this._timer = timer;
        }
        else {
            this.timeout = timeout;
        }
    };
    Test.prototype.run = function () {
        var _this = this;
        var startTime;
        this.async = Object.getPrototypeOf(this).async;
        this._usesRemote = false;
        this.hasPassed = false;
        this.isAsync = false;
        this.error = null;
        this.skipped = null;
        this.timeElapsed = null;
        return this.executor.emit('testStart', this)
            .then(function () {
            startTime = Date.now();
        })
            .then(function () {
            var result = _this.test(_this);
            if (_this.isAsync) {
                if (!Task_1.isThenable(result)) {
                    result = _this.async().promise;
                }
                else {
                    result = Task_1.default.race([_this.async().promise, result]);
                }
            }
            if (Task_1.isThenable(result)) {
                _this.isAsync = true;
                _this._runTask = new Task_1.default(function (resolve, reject) {
                    if (Task_1.isThenable(result)) {
                        result.then(resolve, reject);
                    }
                    if (Task_1.isTask(result)) {
                        result.finally(reject).catch(function (_error) { });
                    }
                }, function () {
                    if (Task_1.isTask(result)) {
                        result.cancel();
                    }
                });
                _this.restartTimeout();
                return _this._runTask;
            }
        })
            .finally(function () {
            _this.timeElapsed = Date.now() - startTime;
            clearTimeout(_this._timer);
            _this._timer = _this._runTask = null;
        })
            .then(function () {
            if (_this._usesRemote && !_this.isAsync) {
                throw new Error('Remote used in synchronous test! Tests using this.remote must ' +
                    'return a promise or resolve a this.async deferred.');
            }
            _this.hasPassed = true;
        }, function (error) {
            if (error === exports.SKIP) {
                if (!_this.skipped) {
                    _this.skipped = error.message;
                }
            }
            else {
                _this.error = error;
                throw error;
            }
        })
            .finally(function () { return _this.executor.emit('testEnd', _this); });
    };
    Test.prototype.skip = function (message) {
        if (message === void 0) { message = 'skipped'; }
        this.skipped = message;
        throw exports.SKIP;
    };
    Test.prototype.toJSON = function () {
        var error = null;
        if (this.error) {
            error = {
                name: this.error.name,
                message: this.error.message,
                stack: this.error.stack,
                showDiff: !!this.error.showDiff
            };
            if (this.error.showDiff) {
                error.actual = this.error.actual;
                error.expected = this.error.expected;
            }
        }
        return {
            error: error,
            id: this.id,
            parentId: this.parentId,
            name: this.name,
            sessionId: this.sessionId,
            timeElapsed: this.timeElapsed,
            timeout: this.timeout,
            hasPassed: this.hasPassed,
            skipped: this.skipped
        };
    };
    return Test;
}());
exports.default = Test;
function isTest(value) {
    return typeof value.hasPassed === 'boolean' && typeof value.timeElapsed === 'number';
}
exports.isTest = isTest;
function isTestOptions(value) {
    return !(value instanceof Test) && value.name != null && value.test != null;
}
exports.isTestOptions = isTestOptions;
exports.SKIP = {};
//# sourceMappingURL=Test.js.map