/**
 * Utilities that require a DOM to run.
 * @author Louis-Dominique Dubeau
 * @license MPL 2.0
 * @copyright Mangalam Research Center for Buddhist Languages
 */
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
    result["default"] = mod;
    return result;
};
define(["require", "exports", "blueimp-md5", "chai", "qs", "sinon", "wed", "./entry", "./util"], function (require, exports, blueimp_md5_1, chai_1, qs_1, sinon, wed_1, entry_1, util_1) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    blueimp_md5_1 = __importDefault(blueimp_md5_1);
    qs_1 = __importDefault(qs_1);
    sinon = __importStar(sinon);
    const { childByClass, childrenByClass } = wed_1.domutil;
    exports.devPath = "/base/build/dist/dev";
    exports.dataPath = `${exports.devPath}/lib/tests`;
    function activateContextMenu(editor, el) {
        // tslint:disable-next-line:no-any
        function computeValues() {
            el.scrollIntoView();
            const rect = el.getBoundingClientRect();
            const left = rect.left + (rect.width / 2);
            const top = rect.top + (rect.height / 2);
            const scrollTop = editor.window.document.body.scrollTop;
            const scrollLeft = editor.window.document.body.scrollLeft;
            return {
                which: 3,
                pageX: left + scrollLeft,
                pageY: top + scrollTop,
                clientX: left,
                clientY: top,
                target: el,
            };
        }
        editor.$guiRoot.trigger(new $.Event("mousedown", computeValues()));
        editor.$guiRoot.trigger(new $.Event("mouseup", computeValues()));
    }
    exports.activateContextMenu = activateContextMenu;
    function contextMenuHasOption(editor, pattern, expectedCount) {
        const menu = editor.window.document.getElementsByClassName("wed-context-menu")[0];
        chai_1.expect(menu, "the menu should exist").to.not.be.undefined;
        const items = menu.querySelectorAll("li>a");
        let found = 0;
        for (let i = 0; i < items.length; ++i) {
            const item = items[i];
            if (pattern.test(item.textContent.trim())) {
                found++;
            }
            if (expectedCount === undefined && found > 0) {
                break;
            }
        }
        if (expectedCount === undefined) {
            chai_1.expect(found).to.be.greaterThan(0);
        }
        else {
            chai_1.expect(found).to.equal(expectedCount, "should have seen the option a number of times \
equal to the expected count");
        }
    }
    exports.contextMenuHasOption = contextMenuHasOption;
    function firstGUI(container) {
        return childByClass(container, "_gui");
    }
    exports.firstGUI = firstGUI;
    function lastGUI(container) {
        const children = childrenByClass(container, "_gui");
        const last = children[children.length - 1];
        return last !== undefined ? last : null;
    }
    exports.lastGUI = lastGUI;
    function getElementNameFor(container, last = false) {
        const gui = last ? lastGUI(container) : firstGUI(container);
        return gui.getElementsByClassName("_element_name")[0];
    }
    exports.getElementNameFor = getElementNameFor;
    function getAttributeValuesFor(container) {
        return firstGUI(container).getElementsByClassName("_attribute_value");
    }
    exports.getAttributeValuesFor = getAttributeValuesFor;
    function getAttributeNamesFor(container) {
        return firstGUI(container).getElementsByClassName("_attribute_name");
    }
    exports.getAttributeNamesFor = getAttributeNamesFor;
    function caretCheck(editor, container, offset, msg) {
        const caret = editor.caretManager.caret;
        chai_1.expect(caret, "there should be a caret").to.not.be.undefined;
        if (offset !== null) {
            chai_1.expect(caret.toArray(), msg).to.deep.equal([container, offset]);
        }
        else {
            // A null offset means we are not interested in the specific offset.  We
            // just want to know that the caret is *inside* container either directly or
            // indirectly.
            chai_1.expect(container.contains(caret.node), msg).to.be.true;
        }
    }
    exports.caretCheck = caretCheck;
    function dataCaretCheck(editor, container, offset, msg) {
        const dataCaret = editor.caretManager.getDataCaret();
        chai_1.expect(dataCaret.toArray(), msg).to.deep.equal([container, offset]);
    }
    exports.dataCaretCheck = dataCaretCheck;
    function dataSelectionCheck(editor, start, end, msg) {
        const sel = editor.caretManager.sel;
        chai_1.expect(sel).to.be.not.be.undefined;
        const [selStart, selEnd] = sel.mustAsDataCarets();
        chai_1.expect(selStart.toArray(), msg).to.deep.equal(start.toArray());
        chai_1.expect(selEnd.toArray(), msg).to.deep.equal(end.toArray());
    }
    exports.dataSelectionCheck = dataSelectionCheck;
    // tslint:disable-next-line:completed-docs
    class WedServer {
        constructor(server) {
            this._saveRequests = [];
            this.emptyResponseOnSave = false;
            this.failOnSave = false;
            this.preconditionFailOnSave = false;
            this.tooOldOnSave = false;
            // tslint:disable-next-line:no-any
            const xhr = server.xhr;
            this.xhr = xhr;
            // We must save and restore the filter state ourselves because Sinon does
            // not do it. Fake servers don't restore it, nor do sandboxes.
            this.oldUseFilters = xhr.useFilters;
            // tslint:disable-next-line:no-any
            this.oldFilters = xhr.filters;
            xhr.useFilters = true;
            xhr.addFilter((_method, url) => !/^\/build\/ajax\//.test(url));
            server.respondImmediately = true;
            server.respondWith("POST", /^\/build\/ajax\/save\.txt$/, this.handleSave.bind(this));
            server.respondWith("POST", "/build/ajax/log.txt", [200, { "Content-Type": "application/json" }, "{}"]);
        }
        get saveRequests() {
            return this._saveRequests;
        }
        get lastSaveRequest() {
            const reqs = this.saveRequests;
            return reqs[reqs.length - 1];
        }
        reset() {
            this._saveRequests = [];
            this.emptyResponseOnSave = false;
            this.failOnSave = false;
            this.preconditionFailOnSave = false;
            this.tooOldOnSave = false;
        }
        restore() {
            const xhr = this.xhr;
            xhr.useFilters = this.oldUseFilters;
            // tslint:disable-next-line:no-any
            xhr.filters = this.oldFilters;
        }
        decode(request) {
            const contentType = request.requestHeaders["Content-Type"];
            const { requestBody } = request;
            switch (contentType) {
                case "application/x-www-form-urlencoded;charset=utf-8":
                    return qs_1.default.parse(requestBody);
                case "json":
                    return JSON.parse(requestBody);
                default:
                    throw new Error(`unknown content type: ${contentType}`);
            }
        }
        handleSave(request) {
            const decoded = this.decode(request);
            this._saveRequests.push(decoded);
            let status = 200;
            const headers = { "Content-Type": "application/json" };
            // tslint:disable-next-line:no-reserved-keywords
            const messages = [];
            function populateSaveResponse() {
                headers.ETag = btoa(blueimp_md5_1.default(decoded.data, undefined, true));
                messages.push({ type: "save_successful" });
            }
            switch (decoded.command) {
                case "check":
                    break;
                case "save":
                case "autosave":
                    if (!this.emptyResponseOnSave) {
                        if (this.tooOldOnSave) {
                            messages.push({ type: "version_too_old_error" });
                        }
                        if (this.preconditionFailOnSave) {
                            status = 412;
                        }
                        else if (this.failOnSave) {
                            status = 400;
                        }
                        else {
                            populateSaveResponse();
                        }
                    }
                    break;
                case "recover":
                    populateSaveResponse();
                    break;
                default:
                    status = 400;
            }
            request.respond(status, headers, JSON.stringify({ messages }));
        }
    }
    exports.WedServer = WedServer;
    function makeWedRoot(doc) {
        const wedroot = doc.createElement("div");
        wedroot.className = "wed-widget container";
        return wedroot;
    }
    exports.makeWedRoot = makeWedRoot;
    function errorCheck() {
        // We read the state, reset, and do the assertion later so that if the
        // assertion fails, we still have our reset.
        const wasTerminating = wed_1.onerror.isTerminating();
        // We don't reload our page so we need to do this.
        wed_1.onerror.__test.reset();
        chai_1.expect(wasTerminating)
            .to.equal(false, "test caused an unhandled exception to occur");
    }
    exports.errorCheck = errorCheck;
    function expectNotification(kind, content) {
        const notification = document.querySelector("[data-notify='container']");
        chai_1.expect(notification, "there should be a notification").to.not.be.null;
        chai_1.expect(notification.classList.contains(`alert-${kind}`), `the notification should be of kind ${kind}`).to.be.true;
        const message = notification.querySelector("[data-notify='message']");
        chai_1.expect(message).to.have.property("textContent").equal(content);
    }
    exports.expectNotification = expectNotification;
    // tslint:disable-next-line:completed-docs
    class EditorSetup {
        constructor(source, options, doc) {
            this.source = source;
            this.sandbox = sinon.createSandbox({
                useFakeServer: true,
            });
            this.server = new WedServer(this.sandbox.server);
            this.wedroot = makeWedRoot(document);
            doc.body.appendChild(this.wedroot);
            this.editor = entry_1.makeEditor(this.wedroot, options, {
                url: "/build/ajax/save.txt",
            });
        }
        init() {
            return __awaiter(this, void 0, void 0, function* () {
                const provider = this.constructor.provider;
                const data = yield provider.getText(this.source);
                return this.editor.init(data);
            });
        }
        reset() {
            const editor = this.editor;
            editor.undoAll();
            editor.resetLabelVisibilityLevel();
            editor.editingMenuManager.dismiss();
            // We set the caret to a position that will trigger some display changes
            // (e.g. hide attributes).
            editor.caretManager.setCaret(editor.caretManager.minCaret);
            // Then we clear the selection to reset the caret to undefined. The mark
            // will still be visible, but that's not an issue.
            editor.caretManager.clearSelection();
            editor.selectionMode = wed_1.SelectionMode.SPAN;
            this.server.reset();
            errorCheck();
            // Immediately destroy all notifications to prevent interfering with other
            // tests. ($.notifyClose is not drastic enough.)
            $("[data-notify=container]").remove();
        }
        restore() {
            if (this.editor !== undefined) {
                this.editor.destroy();
            }
            this.server.restore();
            errorCheck();
            if (this.wedroot !== undefined) {
                document.body.removeChild(this.wedroot);
            }
            if (this.sandbox !== undefined) {
                this.sandbox.restore();
            }
            // Immediately destroy all notifications to prevent interfering with other
            // tests. ($.notifyClose is not drastic enough.)
            $("[data-notify=container]").remove();
        }
        get spanSelectionButton() {
            return this.wedroot
                .querySelector(`[data-original-title=\
'Set selection mode to span']`);
        }
        get unitSelectionButton() {
            return this.wedroot
                .querySelector(`[data-original-title=\
'Set selection mode to unit']`);
        }
        expectSelectionModeIsSpan() {
            chai_1.expect(this.editor.selectionMode).to.equal(wed_1.SelectionMode.SPAN);
            chai_1.expect(this.spanSelectionButton.classList.contains("active"), "span selection button in toolbar should be active").to.be.true;
            chai_1.expect(this.unitSelectionButton.classList.contains("active"), "unit selection button in toolbar should not be active").to.be.false;
        }
        expectSelectionModeIsUnit() {
            chai_1.expect(this.editor.selectionMode).to.equal(wed_1.SelectionMode.UNIT);
            chai_1.expect(this.spanSelectionButton.classList.contains("active"), "span selection button in toolbar should not be active")
                .to.be.false;
            chai_1.expect(this.unitSelectionButton.classList.contains("active"), "unit selection button in toolbar should be active").to.be.true;
        }
    }
    EditorSetup.provider = new util_1.DataProvider("");
    exports.EditorSetup = EditorSetup;
});
//# sourceMappingURL=wed-test-util.js.map