var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./Reporter"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    var Reporter_1 = require("./Reporter");
    /**
     * The Html reporter displays an HTML report in the browser.
     */
    var Html = (function (_super) {
        __extends(Html, _super);
        function Html(executor, options) {
            if (options === void 0) { options = {}; }
            var _this = _super.call(this, executor, options) || this;
            // Array of td elements in 'summaryNode'
            _this._summaryNodes = [];
            // Accumulator for total number of suites
            _this._suiteCount = 0;
            // Accumulator for total number of tests
            _this._testCount = 0;
            // Tests in the current suite
            _this._testsInSuite = 0;
            // Current test index
            _this._testIndex = 0;
            // ID's of tests that have been processed
            _this._processedTests = {};
            // Accumulator for total number of skipped tests
            _this._skippedCount = 0;
            // Accumulator for total number of failed tests
            _this._failCount = 0;
            _this._failedFilter = null;
            _this._fragment = document.createDocumentFragment();
            _this._indentLevel = 0;
            _this._runningSuites = {};
            if (!_this.document) {
                _this.document = window.document;
            }
            return _this;
        }
        Html.prototype._generateSummary = function (suite) {
            var document = this.document;
            if (this._summaryNodes.length === 0) {
                return;
            }
            var duration = suite.timeElapsed;
            var percentPassed = Math.round((1 - (this._failCount / this._testCount || 0)) * 10000) / 100;
            var rowInfo = [
                this._suiteCount,
                this._testCount,
                formatDuration(duration),
                this._skippedCount,
                this._failCount,
                percentPassed + '%'
            ];
            for (var i = 0; i < rowInfo.length; ++i) {
                this._summaryNodes[i].appendChild(document.createTextNode(rowInfo[i]));
            }
            // Create a toggle to only show failed tests
            if (this._failCount) {
                var failedFilter = this._failedFilter = document.createElement('div');
                failedFilter.className = 'failedFilter';
                var failedToggle = document.createElement('input');
                failedToggle.id = 'failedToggle';
                failedToggle.type = 'checkbox';
                var failedLabel = document.createElement('label');
                failedLabel.htmlFor = 'failedToggle';
                failedLabel.innerHTML = 'Show only failed tests';
                failedFilter.appendChild(failedToggle);
                failedFilter.appendChild(failedLabel);
                failedToggle.onclick = function () {
                    if (this.checked) {
                        document.body.className += ' hidePassed';
                    }
                    else {
                        document.body.className = document.body.className.replace(/\bhidePassed\b/, ' ');
                    }
                };
            }
        };
        Html.prototype._injectCSS = function () {
            var document = this.document;
            // Prevent FOUC
            var style = document.createElement('style');
            style.innerHTML = 'body { visibility: hidden; }';
            var link = document.createElement('link');
            link.rel = 'stylesheet';
            link.href = this.executor.config.internPath + 'lib/reporters/html/html.css';
            document.head.appendChild(style);
            document.head.appendChild(link);
        };
        Html.prototype._getIndentLevel = function (node) {
            // second child always has a class of indentN
            var child = node.children[1];
            // get the indentN class
            var indent = child.className.split(' ').filter(function (name) {
                return name.indexOf('indent') >= 0;
            })[0];
            return indent ? parseInt(indent.slice('indent'.length), 10) : 0;
        };
        /**
         * Set the collapsed state of a node and return the new state.
         *
         * @param {DOMNode} node A suite node
         * @param {boolean} collapsed Set the collapsed state, or toggle if undefined
         */
        Html.prototype._setCollapsed = function (node, collapsed) {
            if (collapsed === void 0) { collapsed = false; }
            var indentDelta;
            var initialIndent = this._getIndentLevel(node);
            // Use the given collapsed state or toggle the existing state
            collapsed = collapsed == null ? containsClass(node, 'collapsed') : collapsed;
            if (collapsed) {
                addClass(node, 'collapsed');
            }
            else {
                removeClass(node, 'collapsed');
            }
            // node won't exist after the last test in a suite
            while ((node = node.nextSibling)) {
                indentDelta = this._getIndentLevel(node) - initialIndent;
                // Stop looping when we encounter a row that's not indented more than the suite being updated
                if (indentDelta === 0) {
                    break;
                }
                // Child suites of the suite being updated should always be collapsed
                if (containsClass(node, 'suite')) {
                    addClass(node, 'collapsed');
                }
                // Only show children one level under the suite being updated when expanding
                node.style.display = (!collapsed && indentDelta === 1) ? null : 'none';
            }
        };
        Html.prototype.error = function (error) {
            var document = this.document;
            var htmlError = this.formatError(error).replace(/&/g, '&amp;').replace(/</g, '&lt;');
            var errorNode = document.createElement('div');
            errorNode.style.cssText = 'color: red; font-family: sans-serif;';
            errorNode.innerHTML = '<h1>Fatal error</h1>' +
                '<pre style="padding: 1em; background-color: #f0f0f0;">' + htmlError + '</pre>';
            document.body.appendChild(errorNode);
        };
        Html.prototype.runStart = function () {
            var document = this.document;
            this._reportContainer = document.createElement('div');
            var headerNode = document.createElement('h1');
            var tableNode;
            var tmpNode;
            var rowNode;
            var cellNode;
            var summaryHeaders = [
                'Suites',
                'Tests',
                'Duration',
                'Skipped',
                'Failed',
                'Success Rate'
            ];
            var fragment = this._fragment;
            // Page header
            var headerTitle = document.createElement('span');
            headerTitle.className = 'headerTitle';
            headerTitle.innerHTML = 'Intern Test Report';
            headerNode.className = 'reportHeader';
            var headerLogo = document.createElement('img');
            headerLogo.className = 'headerLogo';
            headerLogo.src = 'data:image/png;base64, ';
            headerNode.appendChild(headerLogo);
            headerNode.appendChild(headerTitle);
            fragment.appendChild(headerNode);
            // Report container
            this._reportContainer.className = 'internReportContainer';
            this._fragment.appendChild(this._reportContainer);
            // Summary table
            tableNode = document.createElement('table');
            tableNode.className = 'summary';
            this._summaryNode = document.createElement('div');
            tmpNode = document.createElement('thead');
            rowNode = document.createElement('tr');
            for (var i = 0; i < summaryHeaders.length; i++) {
                cellNode = document.createElement('td');
                var cellContent = document.createElement('div');
                cellContent.className = 'summaryContent ' +
                    summaryHeaders[i].toLowerCase().replace(/\s(.)/g, function (_, char) {
                        return char.toUpperCase();
                    });
                var cellTitle = document.createElement('span');
                cellTitle.className = 'summaryTitle';
                cellTitle.appendChild(document.createTextNode(summaryHeaders[i]));
                var cellData = document.createElement('div');
                cellData.className = 'summaryData';
                this._summaryNodes[i] = document.createElement('span');
                this._summaryNode.appendChild(this._summaryNodes[i]);
                cellData.appendChild(this._summaryNodes[i]);
                cellContent.appendChild(cellTitle);
                cellContent.appendChild(cellData);
                cellNode.appendChild(cellContent);
                rowNode.appendChild(cellNode);
            }
            tmpNode.appendChild(rowNode);
            tableNode.appendChild(tmpNode);
            this._reportContainer.appendChild(tableNode);
            // Controls
            this._reportControls = document.createElement('div');
            this._reportControls.className = 'reportControls';
            this._reportControls.appendChild(document.createElement('div'));
            this._reportControls.appendChild(document.createElement('div'));
            this._reportContainer.appendChild(this._reportControls);
            // Report table
            tableNode = document.createElement('table');
            tableNode.className = 'report';
            this._reportNode = document.createElement('tbody');
            tableNode.appendChild(this._reportNode);
            this._reportContainer.appendChild(tableNode);
        };
        Html.prototype.suiteStart = function (suite) {
            // There's a top-level Suite that contains all user-created suites
            // We want to skip it
            if (!suite.parent) {
                return;
            }
            this._testsInSuite = suite.tests.length;
            this._testIndex = 0;
            this._processedTests = {};
            var document = this.document;
            var rowNode = document.createElement('tr');
            // Status cell
            var cellNode = document.createElement('td');
            cellNode.className = 'testStatus';
            rowNode.appendChild(cellNode);
            cellNode = document.createElement('td');
            this._suiteCount++;
            cellNode.className = 'title';
            cellNode.appendChild(document.createTextNode(suite.name));
            rowNode.className = 'suite';
            rowNode.appendChild(cellNode);
            this._reportNode.appendChild(rowNode);
            if (this._indentLevel) {
                cellNode.className += ' indent' + Math.min(this._indentLevel, 5);
                rowNode.className += ' indent';
            }
            this._runningSuites[suite.id] = { node: rowNode };
            ++this._indentLevel;
        };
        Html.prototype.suiteEnd = function (suite) {
            var _this = this;
            var document = this.document;
            if (!suite.hasParent) {
                this._generateSummary(suite);
                // Load styles via webpack
                require('./html/html.styl');
                document.body.innerHTML = '';
                document.body.className = '';
                document.body.appendChild(this._fragment);
                var expandToggle = document.createElement('div');
                expandToggle.className = 'linkButton';
                expandToggle.textContent = 'Expand/collapse all';
                this._reportControls.firstElementChild.appendChild(expandToggle);
                expandToggle.addEventListener('click', function () {
                    var shouldExpand = _this._reportNode.querySelector('.collapsed') != null;
                    var suites = _this._reportNode.querySelectorAll('.suite');
                    for (var i = 0; i < suites.length; i++) {
                        _this._setCollapsed(suites[i], !shouldExpand);
                    }
                });
                if (this._failedFilter) {
                    this._reportControls.lastElementChild.appendChild(this._failedFilter);
                }
                else {
                    var failedNode = document.querySelector('.failed');
                    failedNode.className = 'summaryContent failed success';
                }
                // Skip for the top-level suite
                return;
            }
            var rowNode = this._runningSuites[suite.id].node;
            var numTests = suite.numTests;
            var numFailedTests = suite.numFailedTests;
            var numSkippedTests = suite.numSkippedTests;
            var numPassedTests = numTests - numFailedTests - numSkippedTests;
            // Mark a suite as failed if any of its child tests failed, and
            addClass(rowNode, numFailedTests > 0 ? 'failed' : 'passed');
            // Only suites with failed tests will be initially expanded
            this._setCollapsed(rowNode, numFailedTests === 0);
            var cellNode = document.createElement('td');
            if (numPassedTests > 0) {
                cellNode.appendChild(document.createTextNode('Passed: '));
                var testsPassed = document.createElement('span');
                testsPassed.className = 'success';
                testsPassed.innerHTML = "" + numPassedTests;
                cellNode.appendChild(testsPassed);
            }
            if (numFailedTests > 0) {
                cellNode.appendChild(document.createTextNode('Failed: '));
                var testsFailed = document.createElement('span');
                testsFailed.className = 'failed';
                testsFailed.innerHTML = "" + numFailedTests;
                cellNode.appendChild(testsFailed);
            }
            if (numSkippedTests > 0) {
                cellNode.appendChild(document.createTextNode('Skipped: '));
                var testsSkipped = document.createElement('span');
                testsSkipped.innerHTML = "" + numSkippedTests;
                cellNode.appendChild(testsSkipped);
            }
            rowNode.addEventListener('click', function (event) {
                _this._setCollapsed(event.currentTarget);
            });
            cellNode.className = 'column-info';
            rowNode.appendChild(cellNode);
            // Duration cell
            cellNode = document.createElement('td');
            cellNode.className = 'numeric duration';
            cellNode.appendChild(document.createTextNode(formatDuration(suite.timeElapsed)));
            rowNode.appendChild(cellNode);
            --this._indentLevel;
            // Only update the global tracking variables for top-level suites
            if (!this._indentLevel) {
                this._testCount += numTests;
                this._failCount += numFailedTests;
                this._skippedCount += numSkippedTests;
            }
            this._runningSuites[suite.id] = null;
        };
        Html.prototype.testEnd = function (test) {
            if (test.id in this._processedTests) {
                return;
            }
            this._processedTests[test.id] = true;
            this._testIndex++;
            var document = this.document;
            var rowNode = document.createElement('tr');
            // Status cell
            var cellNode = document.createElement('td');
            cellNode.className = 'testStatus';
            rowNode.appendChild(cellNode);
            cellNode = document.createElement('td');
            if (this._indentLevel) {
                cellNode.className += ' indent' + this._indentLevel;
            }
            cellNode.appendChild(document.createTextNode(test.name));
            rowNode.appendChild(cellNode);
            cellNode = document.createElement('td');
            cellNode.className = 'column-info';
            if (test.error) {
                rowNode.className = 'testResult failed';
                cellNode.appendChild(document.createTextNode(test.error.message));
            }
            else if (test.skipped != null) {
                rowNode.className = 'testResult skipped';
                cellNode.appendChild(document.createTextNode(test.skipped || ''));
            }
            else {
                rowNode.className = 'testResult passed';
            }
            if (this._testIndex === this._testsInSuite) {
                rowNode.className = rowNode.className + ' lastTest';
            }
            rowNode.appendChild(cellNode);
            cellNode = document.createElement('td');
            cellNode.className = 'numeric duration';
            cellNode.appendChild(document.createTextNode(test.skipped ? 'Skipped' : formatDuration(test.timeElapsed)));
            rowNode.appendChild(cellNode);
            this._reportNode.appendChild(rowNode);
        };
        __decorate([
            Reporter_1.eventHandler()
        ], Html.prototype, "error", null);
        __decorate([
            Reporter_1.eventHandler()
        ], Html.prototype, "runStart", null);
        __decorate([
            Reporter_1.eventHandler()
        ], Html.prototype, "suiteStart", null);
        __decorate([
            Reporter_1.eventHandler()
        ], Html.prototype, "suiteEnd", null);
        __decorate([
            Reporter_1.eventHandler()
        ], Html.prototype, "testEnd", null);
        return Html;
    }(Reporter_1.default));
    exports.default = Html;
    function containsClass(node, cls) {
        var classes = node.className.split(/\s+/);
        return classes.indexOf(cls) !== -1;
    }
    function addClass(node, cls) {
        var classes = node.className.split(/\s+/);
        if (classes.indexOf(cls) !== -1) {
            return;
        }
        classes.push(cls);
        node.className = classes.join(' ');
    }
    function removeClass(node, cls) {
        var classes = node.className.split(/\s+/);
        var index = classes.indexOf(cls);
        if (index === -1) {
            return;
        }
        classes.splice(index, 1);
        node.className = classes.join(' ');
    }
    function pad(value, size) {
        var padded = String(value);
        while (padded.length < size) {
            padded = '0' + padded;
        }
        return padded;
    }
    // Format a millisecond value to m:ss.SSS
    // If duration is greater than 60 minutes, value will be HHHH:mm:ss.SSS
    // (the hours value will not be converted to days)
    function formatDuration(duration) {
        var hours = Math.floor(duration / 3600000);
        var minutes = Math.floor(duration / 60000) - (hours * 60);
        var seconds = Math.floor(duration / 1000) - (hours * 3600) - (minutes * 60);
        var milliseconds = duration - (hours * 3600000) - (minutes * 60000) - (seconds * 1000);
        var formattedValue = '';
        if (hours) {
            formattedValue = hours + ':';
            minutes = pad(minutes, 2);
        }
        formattedValue += minutes + ':' + pad(seconds, 2) + '.' + pad(milliseconds, 3);
        return formattedValue;
    }
});
//# sourceMappingURL=Html.js.map