488 lines
22 KiB
JavaScript
488 lines
22 KiB
JavaScript
// XxxEditor.attach(): the toolbar, the dialogs, source mode, undo, paste and the keys
|
||
"use strict";
|
||
|
||
const test = require("node:test");
|
||
const assert = require("node:assert/strict");
|
||
const { editorWindow, select, selectText, button, key } = require("./helpers");
|
||
|
||
// Type into the editor: replace its HTML and send the input event
|
||
function type(env, html, inputType = "insertText") {
|
||
env.box.dispatchEvent(new env.window.InputEvent("beforeinput", { inputType, bubbles: true, cancelable: true }));
|
||
env.box.innerHTML = html;
|
||
env.box.dispatchEvent(new env.window.InputEvent("input", { inputType, bubbles: true }));
|
||
}
|
||
|
||
function paste(env, data) {
|
||
const event = new env.window.Event("paste", { bubbles: true, cancelable: true });
|
||
event.clipboardData = { getData: (type) => data[type] || "" };
|
||
env.box.dispatchEvent(event);
|
||
return event;
|
||
}
|
||
|
||
test("the editor takes the place of the textarea in the form", () => {
|
||
const env = editorWindow("<p>Hello</p>");
|
||
const wrapper = env.document.querySelector("#textparent > .xed");
|
||
assert.ok(wrapper);
|
||
assert.equal(env.textarea.parentNode, wrapper);
|
||
assert.equal(env.textarea.hidden, true);
|
||
assert.equal(env.textarea.style.display, "none");
|
||
assert.equal(env.textarea.form.id, "form");
|
||
assert.equal(env.box.contentEditable, "true");
|
||
assert.equal(env.box.getAttribute("aria-label"), "Story:");
|
||
assert.equal(env.box.innerHTML, "<p>Hello</p>");
|
||
});
|
||
|
||
test("the toolbar has the TinyMCE buttons, in order", () => {
|
||
const env = editorWindow();
|
||
const labels = Array.from(env.document.querySelectorAll(".xed-btn"), (b) => b.getAttribute("aria-label"));
|
||
assert.deepEqual(labels, ["Bold", "Italic", "Align left", "Align center", "Align right", "Justify", "Blockquote",
|
||
"Decrease indent", "Increase indent", "Insert/edit link", "Remove link", "Special character", "Source code"]);
|
||
assert.equal(button(env.document, "Bold").title, "Bold (Ctrl+B)");
|
||
assert.equal(button(env.document, "Insert/edit link").title, "Insert/edit link (Ctrl+K)");
|
||
assert.equal(env.document.querySelectorAll(".xed-group").length, 6);
|
||
for (const b of env.document.querySelectorAll(".xed-btn")) {
|
||
assert.equal(b.type, "button");
|
||
assert.ok(b.querySelector("svg[aria-hidden=true] path"));
|
||
}
|
||
});
|
||
|
||
test("the story in the textarea is cleaned when the editor starts", () => {
|
||
const env = editorWindow('<p onclick="x()">a</p>loose <b>text</b>');
|
||
assert.equal(env.textarea.value, "<p>a</p>\n<p>loose <strong>text</strong></p>");
|
||
assert.equal(env.editor.getContent(), env.textarea.value);
|
||
});
|
||
|
||
test("an empty story starts with an empty paragraph", () => {
|
||
const env = editorWindow("");
|
||
assert.equal(env.box.innerHTML, "<p><br></p>");
|
||
assert.equal(env.textarea.value, "");
|
||
});
|
||
|
||
test("typing updates the textarea and tells the word counter", () => {
|
||
const env = editorWindow();
|
||
let inputs = 0;
|
||
env.textarea.addEventListener("input", () => inputs++);
|
||
type(env, "<p>Some words</p>");
|
||
assert.equal(env.textarea.value, "<p>Some words</p>");
|
||
assert.equal(inputs, 1);
|
||
type(env, "<p>Some words</p>");
|
||
assert.equal(inputs, 1, "no event when nothing changed");
|
||
});
|
||
|
||
test("text typed outside a paragraph is put in one", () => {
|
||
const env = editorWindow();
|
||
type(env, "loose <em>text</em><p>para</p>more");
|
||
assert.equal(env.textarea.value, "<p>loose <em>text</em></p>\n<p>para</p>\n<p>more</p>");
|
||
});
|
||
|
||
test("deleting everything starts again with a plain paragraph", () => {
|
||
const env = editorWindow("<p><strong>bold</strong></p>");
|
||
type(env, "<p><strong><br></strong></p>", "deleteContentBackward");
|
||
assert.equal(env.box.innerHTML, "<p><br></p>");
|
||
});
|
||
|
||
test("setContent and getContent", () => {
|
||
const env = editorWindow();
|
||
env.editor.setContent("<p>new <i>story</i></p>");
|
||
assert.equal(env.editor.getContent(), "<p>new <em>story</em></p>");
|
||
assert.equal(env.textarea.value, "<p>new <em>story</em></p>");
|
||
});
|
||
|
||
test("the form sends the editor's content", () => {
|
||
const env = editorWindow("<p>a</p>");
|
||
env.box.innerHTML = "<p>changed without an input event</p>";
|
||
env.textarea.form.dispatchEvent(new env.window.Event("submit", { cancelable: true }));
|
||
assert.equal(env.textarea.value, "<p>changed without an input event</p>");
|
||
});
|
||
|
||
test("bold and italic use the browser's commands", () => {
|
||
const env = editorWindow("<p>word</p>");
|
||
selectText(env.window, env.box.querySelector("p"));
|
||
button(env.document, "Bold").click();
|
||
button(env.document, "Italic").click();
|
||
assert.deepEqual(env.window.commands.filter((c) => c[0] !== "styleWithCSS" && c[0] !== "defaultParagraphSeparator"),
|
||
[["bold", null], ["italic", null]]);
|
||
});
|
||
|
||
test("the alignment buttons set and clear text-align", () => {
|
||
const env = editorWindow("<p>one</p><p>two</p>");
|
||
const [one, two] = env.box.querySelectorAll("p");
|
||
select(env.window, one.firstChild, 0, two.firstChild, 3);
|
||
button(env.document, "Align center").click();
|
||
assert.equal(env.textarea.value, '<p style="text-align: center;">one</p>\n<p style="text-align: center;">two</p>');
|
||
button(env.document, "Align right").click();
|
||
assert.equal(env.textarea.value, '<p style="text-align: right;">one</p>\n<p style="text-align: right;">two</p>');
|
||
button(env.document, "Align right").click();
|
||
assert.equal(env.textarea.value, "<p>one</p>\n<p>two</p>");
|
||
});
|
||
|
||
test("an old align attribute is replaced", () => {
|
||
const env = editorWindow('<p align="right">x</p>');
|
||
select(env.window, env.box.querySelector("p").firstChild, 0);
|
||
button(env.document, "Justify").click();
|
||
assert.equal(env.textarea.value, '<p style="text-align: justify;">x</p>');
|
||
});
|
||
|
||
test("indent steps by 30 pixels and never goes below zero", () => {
|
||
const env = editorWindow("<p>x</p>");
|
||
select(env.window, env.box.querySelector("p").firstChild, 0);
|
||
button(env.document, "Increase indent").click();
|
||
button(env.document, "Increase indent").click();
|
||
assert.equal(env.textarea.value, '<p style="padding-left: 60px;">x</p>');
|
||
button(env.document, "Decrease indent").click();
|
||
assert.equal(env.textarea.value, '<p style="padding-left: 30px;">x</p>');
|
||
button(env.document, "Decrease indent").click();
|
||
button(env.document, "Decrease indent").click();
|
||
assert.equal(env.textarea.value, "<p>x</p>");
|
||
});
|
||
|
||
test("indent in a list uses the browser's command", () => {
|
||
const env = editorWindow("<ul><li>a</li><li>b</li></ul>");
|
||
select(env.window, env.box.querySelectorAll("li")[1].firstChild, 0);
|
||
button(env.document, "Increase indent").click();
|
||
assert.deepEqual(env.window.commands.at(-1), ["indent", null]);
|
||
});
|
||
|
||
test("blockquote wraps the selected paragraphs, and unwraps them", () => {
|
||
const env = editorWindow("<p>one</p><p>two</p><p>three</p>");
|
||
const ps = env.box.querySelectorAll("p");
|
||
select(env.window, ps[0].firstChild, 1, ps[1].firstChild, 1);
|
||
button(env.document, "Blockquote").click();
|
||
assert.equal(env.textarea.value, "<blockquote>\n<p>one</p>\n<p>two</p>\n</blockquote>\n<p>three</p>");
|
||
select(env.window, ps[0].firstChild, 1);
|
||
button(env.document, "Blockquote").click();
|
||
assert.equal(env.textarea.value, "<p>one</p>\n<p>two</p>\n<p>three</p>");
|
||
});
|
||
|
||
test("the link dialog adds a link", () => {
|
||
const env = editorWindow("<p>go here</p>");
|
||
const text = env.box.querySelector("p").firstChild;
|
||
select(env.window, text, 3, text, 7);
|
||
button(env.document, "Insert/edit link").click();
|
||
const dialog = env.document.querySelector(".xed-window[role=dialog]");
|
||
assert.ok(dialog);
|
||
assert.equal(dialog.querySelector(".xed-title").textContent, "Insert link");
|
||
const inputs = dialog.querySelectorAll("input");
|
||
assert.equal(inputs.length, 3);
|
||
assert.equal(inputs[1].value, "here");
|
||
assert.equal(env.document.activeElement, inputs[0]);
|
||
inputs[0].value = "www.example.org";
|
||
inputs[1].value = "there";
|
||
inputs[2].value = "A title";
|
||
dialog.querySelector(".xed-primary").click();
|
||
assert.equal(env.document.querySelector(".xed-window"), null);
|
||
assert.equal(env.textarea.value, '<p>go <a href="http://www.example.org" title="A title">there</a></p>');
|
||
});
|
||
|
||
test("the link dialog makes email addresses into mailto links", () => {
|
||
const env = editorWindow("<p>x</p>");
|
||
select(env.window, env.box.querySelector("p").firstChild, 1);
|
||
button(env.document, "Insert/edit link").click();
|
||
const inputs = env.document.querySelectorAll(".xed-window input");
|
||
inputs[0].value = "ed@example.org";
|
||
key(env.window, inputs[0], "Enter");
|
||
assert.equal(env.textarea.value, '<p>x<a href="mailto:ed@example.org">ed@example.org</a></p>');
|
||
});
|
||
|
||
test("the link dialog refuses unsafe links", () => {
|
||
const env = editorWindow("<p>x</p>");
|
||
select(env.window, env.box.querySelector("p").firstChild, 1);
|
||
button(env.document, "Insert/edit link").click();
|
||
env.document.querySelector(".xed-window input").value = "javascript:alert(1)";
|
||
env.document.querySelector(".xed-primary").click();
|
||
assert.equal(env.textarea.value, "<p>x</p>");
|
||
});
|
||
|
||
test("the link dialog edits and removes a link", () => {
|
||
const env = editorWindow('<p><a href="https://old.example/">old</a></p>');
|
||
select(env.window, env.box.querySelector("a").firstChild, 1);
|
||
button(env.document, "Insert/edit link").click();
|
||
let inputs = env.document.querySelectorAll(".xed-window input");
|
||
assert.equal(inputs[0].value, "https://old.example/");
|
||
inputs[0].value = "https://new.example/";
|
||
env.document.querySelector(".xed-primary").click();
|
||
assert.equal(env.textarea.value, '<p><a href="https://new.example/">old</a></p>');
|
||
|
||
select(env.window, env.box.querySelector("a").firstChild, 1);
|
||
button(env.document, "Insert/edit link").click();
|
||
env.document.querySelector(".xed-window input").value = "";
|
||
env.document.querySelector(".xed-primary").click();
|
||
assert.equal(env.textarea.value, "<p>old</p>");
|
||
});
|
||
|
||
test("Escape and Cancel close a dialog without a change", () => {
|
||
const env = editorWindow("<p>x</p>");
|
||
select(env.window, env.box.querySelector("p").firstChild, 1);
|
||
button(env.document, "Insert/edit link").click();
|
||
key(env.window, env.document.querySelector(".xed-window input"), "Escape");
|
||
assert.equal(env.document.querySelector(".xed-window"), null);
|
||
assert.equal(env.document.querySelector(".xed-modal-block"), null);
|
||
button(env.document, "Insert/edit link").click();
|
||
env.document.querySelector(".xed-window input").value = "https://example.org/";
|
||
Array.from(env.document.querySelectorAll(".xed-button")).find((b) => b.textContent === "Cancel").click();
|
||
assert.equal(env.document.querySelector(".xed-window"), null);
|
||
assert.equal(env.textarea.value, "<p>x</p>");
|
||
});
|
||
|
||
test("a click beside the link dialog does not close it", () => {
|
||
const env = editorWindow("<p>x</p>");
|
||
button(env.document, "Insert/edit link").click();
|
||
env.document.querySelector(".xed-modal-block").click();
|
||
assert.ok(env.document.querySelector(".xed-window"));
|
||
});
|
||
|
||
test("remove link unwraps the link at the caret", () => {
|
||
const env = editorWindow('<p>a <a href="https://example.org/">link</a> b</p>');
|
||
select(env.window, env.box.querySelector("a").firstChild, 2);
|
||
button(env.document, "Remove link").click();
|
||
assert.equal(env.textarea.value, "<p>a link b</p>");
|
||
});
|
||
|
||
test("the special character window inserts a character", () => {
|
||
const env = editorWindow("<p>x</p>");
|
||
select(env.window, env.box.querySelector("p").firstChild, 1);
|
||
button(env.document, "Special character").click();
|
||
const cells = env.document.querySelectorAll(".xed-charmap td");
|
||
assert.equal(cells.length, 250);
|
||
assert.equal(env.document.querySelectorAll(".xed-charmap tr").length, 10);
|
||
const euro = Array.from(cells).find((c) => c.title === "euro sign");
|
||
euro.dispatchEvent(new env.window.MouseEvent("mouseover"));
|
||
assert.equal(env.document.querySelector(".xed-charmap-char").textContent, "€");
|
||
assert.equal(env.document.querySelector(".xed-charmap-name").textContent, "euro sign");
|
||
euro.click();
|
||
assert.equal(env.document.querySelector(".xed-window"), null);
|
||
assert.deepEqual(env.window.commands.at(-1), ["insertText", "€"]);
|
||
});
|
||
|
||
test("the special character window answers the keyboard", () => {
|
||
const env = editorWindow("<p>x</p>");
|
||
button(env.document, "Special character").click();
|
||
const cell = env.document.querySelector(".xed-charmap td");
|
||
assert.equal(cell.tabIndex, 0);
|
||
key(env.window, cell, "Enter");
|
||
assert.deepEqual(env.window.commands.at(-1), ["insertText", " "]);
|
||
});
|
||
|
||
test("a click outside the special character window closes it", () => {
|
||
const env = editorWindow("<p>x</p>");
|
||
button(env.document, "Special character").click();
|
||
const backdrop = env.document.querySelector(".xed-modal-block");
|
||
assert.ok(backdrop.classList.contains("xed-modal-light"));
|
||
backdrop.click();
|
||
assert.equal(env.document.querySelector(".xed-window"), null);
|
||
assert.equal(env.document.querySelector(".xed-modal-block"), null);
|
||
assert.equal(env.window.commands.filter((c) => c[0] === "insertText").length, 0);
|
||
});
|
||
|
||
test("the source button swaps the editor for the textarea and back", () => {
|
||
const env = editorWindow("<p>a</p>");
|
||
const code = button(env.document, "Source code");
|
||
code.click();
|
||
assert.equal(env.textarea.hidden, false);
|
||
assert.equal(env.textarea.style.display, "");
|
||
assert.equal(env.box.hidden, true);
|
||
assert.equal(code.getAttribute("aria-pressed"), "true");
|
||
assert.ok(code.classList.contains("xed-active"));
|
||
for (const b of env.document.querySelectorAll(".xed-btn"))
|
||
assert.equal(b.disabled, b !== code);
|
||
assert.equal(env.document.querySelector(".xed-window"), null, "no source window");
|
||
|
||
env.textarea.value = '<p onclick="x()">edited</p><script>bad()</script>';
|
||
assert.equal(env.editor.getContent(), env.textarea.value, "the textarea is the story in source mode");
|
||
code.click();
|
||
assert.equal(env.box.hidden, false);
|
||
assert.equal(env.textarea.hidden, true);
|
||
assert.equal(code.getAttribute("aria-pressed"), "false");
|
||
assert.ok(Array.from(env.document.querySelectorAll(".xed-btn")).every((b) => !b.disabled));
|
||
assert.equal(env.textarea.value, "<p>edited</p>");
|
||
});
|
||
|
||
test("submitting in source mode sends the source edits", (t) => {
|
||
const env = editorWindow("<p>Old story</p>");
|
||
t.after(() => env.window.close());
|
||
button(env.document, "Source code").click();
|
||
const source = "<p>Source edit</p>\n<p>Second paragraph.</p>";
|
||
env.textarea.value = source;
|
||
env.textarea.form.dispatchEvent(new env.window.Event("submit", { cancelable: true }));
|
||
assert.equal(env.textarea.value, source);
|
||
assert.equal(env.editor.getContent(), source);
|
||
assert.equal(new env.window.FormData(env.textarea.form).get("story"), source);
|
||
});
|
||
|
||
test("setContent leaves source mode", () => {
|
||
const env = editorWindow("<p>a</p>");
|
||
button(env.document, "Source code").click();
|
||
env.editor.setContent("<p>b</p>");
|
||
assert.equal(env.box.hidden, false);
|
||
assert.equal(env.editor.getContent(), "<p>b</p>");
|
||
});
|
||
|
||
test("undo and redo, with the keys", () => {
|
||
const env = editorWindow("<p>x</p>");
|
||
select(env.window, env.box.querySelector("p").firstChild, 0);
|
||
button(env.document, "Align center").click();
|
||
assert.equal(env.textarea.value, '<p style="text-align: center;">x</p>');
|
||
assert.ok(key(env.window, env.box, "z", { ctrlKey: true }).defaultPrevented);
|
||
assert.equal(env.textarea.value, "<p>x</p>");
|
||
key(env.window, env.box, "y", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, '<p style="text-align: center;">x</p>');
|
||
key(env.window, env.box, "z", { metaKey: true });
|
||
key(env.window, env.box, "Z", { ctrlKey: true, shiftKey: true });
|
||
assert.equal(env.textarea.value, '<p style="text-align: center;">x</p>');
|
||
});
|
||
|
||
test("undo keeps a run of typing as one step", () => {
|
||
const env = editorWindow("<p>a</p>");
|
||
type(env, "<p>ab</p>");
|
||
type(env, "<p>abc</p>");
|
||
key(env.window, env.box, "z", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, "<p>a</p>");
|
||
});
|
||
|
||
for (const pause of [999, 1000, 1001]) {
|
||
test(`undo groups typing only when the pause is below one second (${pause} ms)`, (t) => {
|
||
const env = editorWindow("<p>a</p>");
|
||
t.after(() => env.window.close());
|
||
let now = 10000;
|
||
t.mock.method(env.window.Date, "now", () => now);
|
||
type(env, "<p>ab</p>");
|
||
now += pause;
|
||
type(env, "<p>abc</p>");
|
||
key(env.window, env.box, "z", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, pause < 1000 ? "<p>a</p>" : "<p>ab</p>");
|
||
key(env.window, env.box, "y", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, "<p>abc</p>");
|
||
});
|
||
}
|
||
|
||
test("a change of input type starts a separate undo step", (t) => {
|
||
const env = editorWindow("<p>a</p>");
|
||
t.after(() => env.window.close());
|
||
t.mock.method(env.window.Date, "now", () => 10000);
|
||
type(env, "<p>abc</p>", "insertText");
|
||
type(env, "<p>ab</p>", "deleteContentBackward");
|
||
key(env.window, env.box, "z", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, "<p>abc</p>");
|
||
key(env.window, env.box, "z", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, "<p>a</p>");
|
||
});
|
||
|
||
test("a new edit after undo clears the old redo history", (t) => {
|
||
const env = editorWindow("<p>a</p>");
|
||
t.after(() => env.window.close());
|
||
let now = 10000;
|
||
t.mock.method(env.window.Date, "now", () => now);
|
||
type(env, "<p>ab</p>");
|
||
now += 1000;
|
||
type(env, "<p>abc</p>");
|
||
key(env.window, env.box, "z", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, "<p>ab</p>");
|
||
type(env, "<p>abX</p>");
|
||
key(env.window, env.box, "y", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, "<p>abX</p>");
|
||
key(env.window, env.box, "z", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, "<p>ab</p>");
|
||
key(env.window, env.box, "y", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, "<p>abX</p>");
|
||
});
|
||
|
||
test("undo keeps the latest 100 steps and redo restores them", (t) => {
|
||
const env = editorWindow("<p>0</p>");
|
||
t.after(() => env.window.close());
|
||
let now = 10000;
|
||
t.mock.method(env.window.Date, "now", () => now);
|
||
for (let i = 1; i <= 105; i++) {
|
||
now += 1000;
|
||
type(env, `<p>${i}</p>`);
|
||
}
|
||
for (let i = 104; i >= 5; i--) {
|
||
key(env.window, env.box, "z", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, `<p>${i}</p>`);
|
||
}
|
||
key(env.window, env.box, "z", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, "<p>5</p>", "the oldest five steps are discarded");
|
||
for (let i = 6; i <= 105; i++) {
|
||
key(env.window, env.box, "y", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, `<p>${i}</p>`);
|
||
}
|
||
key(env.window, env.box, "y", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, "<p>105</p>");
|
||
});
|
||
|
||
test("undo restores a selection across nodes and redo restores the caret", (t) => {
|
||
const original = "<p>alpha<em>beta</em></p>";
|
||
const env = editorWindow(original);
|
||
t.after(() => env.window.close());
|
||
select(env.window, env.box.querySelector("p").firstChild, 2, env.box.querySelector("em").firstChild, 3);
|
||
type(env, "<p>altered</p>");
|
||
select(env.window, env.box.querySelector("p").firstChild, 4);
|
||
key(env.window, env.box, "z", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, original);
|
||
let range = env.window.getSelection().getRangeAt(0);
|
||
assert.equal(range.startContainer, env.box.querySelector("p").firstChild);
|
||
assert.equal(range.startOffset, 2);
|
||
assert.equal(range.endContainer, env.box.querySelector("em").firstChild);
|
||
assert.equal(range.endOffset, 3);
|
||
assert.equal(range.toString(), "phabet");
|
||
key(env.window, env.box, "y", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, "<p>altered</p>");
|
||
range = env.window.getSelection().getRangeAt(0);
|
||
assert.equal(range.startContainer, env.box.querySelector("p").firstChild);
|
||
assert.equal(range.startOffset, 4);
|
||
assert.equal(range.collapsed, true);
|
||
});
|
||
|
||
test("redo from the browser's history input", (t) => {
|
||
const env = editorWindow("<p>a</p>");
|
||
t.after(() => env.window.close());
|
||
type(env, "<p>b</p>");
|
||
key(env.window, env.box, "z", { ctrlKey: true });
|
||
assert.equal(env.textarea.value, "<p>a</p>");
|
||
const event = new env.window.InputEvent("beforeinput", { inputType: "historyRedo", bubbles: true, cancelable: true });
|
||
env.box.dispatchEvent(event);
|
||
assert.ok(event.defaultPrevented);
|
||
assert.equal(env.textarea.value, "<p>b</p>");
|
||
});
|
||
|
||
test("undo from the browser's history input", () => {
|
||
const env = editorWindow("<p>a</p>");
|
||
type(env, "<p>b</p>");
|
||
const event = new env.window.InputEvent("beforeinput", { inputType: "historyUndo", bubbles: true, cancelable: true });
|
||
env.box.dispatchEvent(event);
|
||
assert.ok(event.defaultPrevented);
|
||
assert.equal(env.textarea.value, "<p>a</p>");
|
||
});
|
||
|
||
test("the shortcut keys run the toolbar commands", () => {
|
||
const env = editorWindow("<p>x</p>");
|
||
selectText(env.window, env.box.querySelector("p"));
|
||
assert.ok(key(env.window, env.box, "b", { ctrlKey: true }).defaultPrevented);
|
||
assert.deepEqual(env.window.commands.at(-1), ["bold", null]);
|
||
key(env.window, env.box, "i", { metaKey: true });
|
||
assert.deepEqual(env.window.commands.at(-1), ["italic", null]);
|
||
key(env.window, env.box, "k", { ctrlKey: true });
|
||
assert.ok(env.document.querySelector(".xed-window"));
|
||
});
|
||
|
||
test("other keys are left to the browser", () => {
|
||
const env = editorWindow("<p>x</p>");
|
||
assert.equal(key(env.window, env.box, "b").defaultPrevented, false);
|
||
assert.equal(key(env.window, env.box, "b", { ctrlKey: true, altKey: true }).defaultPrevented, false);
|
||
assert.equal(key(env.window, env.box, "c", { ctrlKey: true }).defaultPrevented, false);
|
||
});
|
||
|
||
test("pasted HTML is cleaned first", () => {
|
||
const env = editorWindow("<p>x</p>");
|
||
const event = paste(env, { "text/html": '<p style="color: red" class="MsoNormal"><b>Bold</b><script>x()</script></p>' });
|
||
assert.ok(event.defaultPrevented);
|
||
assert.deepEqual(env.window.commands.at(-1), ["insertHTML", "<p><strong>Bold</strong></p>"]);
|
||
});
|
||
|
||
test("pasted text keeps its paragraphs and line breaks", () => {
|
||
const env = editorWindow("<p>x</p>");
|
||
env.window.commands.length = 0;
|
||
paste(env, { "text/plain": "one\r\ntwo\n\nthree" });
|
||
assert.deepEqual(env.window.commands, [["insertText", "one"], ["insertLineBreak", null], ["insertText", "two"],
|
||
["insertParagraph", null], ["insertText", "three"]]);
|
||
});
|