Odoo18-Base/addons/html_editor/static/tests/powerbox.test.js
2025-01-06 10:57:38 +07:00

767 lines
29 KiB
JavaScript

import { Plugin } from "@html_editor/plugin";
import { CORE_PLUGINS, MAIN_PLUGINS } from "@html_editor/plugin_sets";
import { describe, expect, test } from "@odoo/hoot";
import {
click,
hover,
manuallyDispatchProgrammaticEvent,
press,
queryAllTexts,
waitFor,
} from "@odoo/hoot-dom";
import { animationFrame, tick } from "@odoo/hoot-mock";
import {
applyConcurrentActions,
mergePeersSteps,
renderTextualSelection,
setupMultiEditor,
validateContent,
} from "./_helpers/collaboration";
import { setupEditor } from "./_helpers/editor";
import { getContent } from "./_helpers/selection";
import { insertText, redo, undo } from "./_helpers/user_actions";
import { patchWithCleanup } from "@web/../tests/web_test_helpers";
import { PowerboxPlugin } from "@html_editor/main/powerbox/powerbox_plugin";
import { SearchPowerboxPlugin } from "@html_editor/main/powerbox/search_powerbox_plugin";
import { withSequence } from "@html_editor/utils/resource";
import { execCommand } from "./_helpers/userCommands";
function commandNames() {
return queryAllTexts(".o-we-command-name");
}
test("should open the Powerbox on type `/`", async () => {
const { el, editor } = await setupEditor("<p>ab[]</p>");
expect(".o-we-powerbox").toHaveCount(0);
expect(getContent(el)).toBe("<p>ab[]</p>");
await insertText(editor, "/");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
});
test.tags("iframe");
test("in iframe: should open the Powerbox on type `/`", async () => {
const { el, editor } = await setupEditor("<p>ab[]</p>", { props: { iframe: true } });
expect("iframe").toHaveCount(1);
expect(".o-we-powerbox").toHaveCount(0);
expect(getContent(el)).toBe("<p>ab[]</p>");
await insertText(editor, "/");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
});
test("should correctly hint in iframes", async () => {
const { el } = await setupEditor("<p>[]<br></p>", { props: { iframe: true } });
expect(getContent(el)).toBe(
`<p placeholder='Type "/" for commands' class="o-we-hint">[]<br></p>`
);
});
test("should open the Powerbox on type `/`, but in an empty paragraph", async () => {
const { el, editor } = await setupEditor("<p>[]<br></p>");
expect(".o-we-powerbox").toHaveCount(0);
expect(getContent(el)).toBe(
`<p placeholder='Type "/" for commands' class="o-we-hint">[]<br></p>`
);
await press("/");
await insertText(editor, "/");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
});
describe("search", () => {
test("should filter the Powerbox contents with term", async () => {
const { el, editor } = await setupEditor("<p>ab[]</p>");
await insertText(editor, "/");
await animationFrame();
expect(commandNames(el).length).toBe(27);
await insertText(editor, "head");
await animationFrame();
expect(commandNames(el)).toEqual(["Heading 1", "Heading 2", "Heading 3"]);
});
test("should hide categories when you have a search term", async () => {
const { el, editor } = await setupEditor("<p>ab[]</p>");
await insertText(editor, "/");
await animationFrame();
expect(commandNames(el).length).toBe(27);
expect(".o-we-category").toHaveCount(8);
expect(queryAllTexts(".o-we-category")).toEqual([
"STRUCTURE",
"BANNER",
"FORMAT",
"MEDIA",
"NAVIGATION",
"WIDGET",
"AI TOOLS",
"BASIC BLOC",
]);
await insertText(editor, "h");
await animationFrame();
expect(commandNames(el).length).toBe(9);
expect(".o-we-category").toHaveCount(0);
});
test.tags("iframe");
test("should filter the Powerbox contents with term, in iframe", async () => {
const { el, editor } = await setupEditor("<p>ab[]</p>", { props: { iframe: true } });
await insertText(editor, "/");
await animationFrame();
expect(commandNames(el).length).toBe(27);
await insertText(editor, "head");
await animationFrame();
expect(commandNames(el)).toEqual(["Heading 1", "Heading 2", "Heading 3"]);
});
test("press 'backspace' should adapt adapt the search in the Powerbox", async () => {
class TestPlugin extends Plugin {
static id = "test";
resources = {
user_commands: { id: "testCommand", run: () => {} },
powerbox_categories: { id: "test", name: "Test" },
powerbox_items: [
{
title: "Test1",
description: "Test1",
categoryId: "test",
commandId: "testCommand",
},
{
title: "Test12",
description: "Test12",
categoryId: "test",
commandId: "testCommand",
},
],
};
}
const { editor, el } = await setupEditor(`<p>[]</p>`, {
config: { Plugins: [...MAIN_PLUGINS, TestPlugin] },
});
expect(".o-we-powerbox").toHaveCount(0);
await insertText(editor, "/test12");
await animationFrame();
expect(getContent(el)).toBe("<p>/test12[]</p>");
expect(".o-we-powerbox").toHaveCount(1);
expect(commandNames(el)).toEqual(["Test12"]);
expect(".active .o-we-command-name").toHaveText("Test12");
await press("backspace");
await animationFrame();
expect(getContent(el)).toBe("<p>/test1[]</p>");
expect(".o-we-powerbox").toHaveCount(1);
expect(commandNames(el)).toEqual(["Test1", "Test12"]);
expect(".active .o-we-command-name").toHaveText("Test1");
});
test("should filter the Powerbox contents with term, even after delete backward", async () => {
const { el, editor } = await setupEditor("<p>ab[]</p>");
await insertText(editor, "/");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
expect(commandNames(el).length).toBe(27);
await insertText(editor, "headx");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(0);
await press("Backspace");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
expect(commandNames(el)).toEqual(["Heading 1", "Heading 2", "Heading 3"]);
});
test("when the powerbox opens, the first command is selected by default", async () => {
const { el, editor } = await setupEditor("<p>ab[]</p>");
await insertText(editor, "/");
await animationFrame();
expect(".active .o-we-command-name").toHaveText(commandNames(el)[0]);
await insertText(editor, "head");
await animationFrame();
expect(".active .o-we-command-name").toHaveText(commandNames(el)[0]); // "Heading 1"
await insertText(editor, "/");
await animationFrame();
expect(".active .o-we-command-name").toHaveText(commandNames(el)[0]);
});
test("should filter the Powerbox contents with term, even after a second search and delete backward", async () => {
const { el, editor } = await setupEditor("<p>ab[]</p>");
await insertText(editor, "/head");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
expect(commandNames(el)).toEqual(["Heading 1", "Heading 2", "Heading 3"]);
expect(".active .o-we-command-name").toHaveText("Heading 1");
await insertText(editor, "/headx");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(0);
await press("backspace");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
expect(".active .o-we-command-name").toHaveText("Heading 1");
expect(commandNames(el)).toEqual(["Heading 1", "Heading 2", "Heading 3"]);
});
test("should not filter the powerbox contents when collaborator type on two different blocks", async () => {
const peerInfos = await setupMultiEditor({
peerIds: ["c1", "c2"],
contentBefore: "<p>a[c1}{c1]b</p><p>c[c2}{c2]d</p>",
});
applyConcurrentActions(peerInfos, {
c1: async (editor) => {
await insertText(editor, "/heading");
},
});
await animationFrame();
mergePeersSteps(peerInfos);
expect(".o-we-powerbox").toHaveCount(1);
expect(commandNames()).toEqual(["Heading 1", "Heading 2", "Heading 3"]);
applyConcurrentActions(peerInfos, {
c2: async (editor) => {
await insertText(editor, "g");
},
});
await animationFrame();
mergePeersSteps(peerInfos);
expect(".o-we-powerbox").toHaveCount(1);
expect(commandNames()).toEqual(["Heading 1", "Heading 2", "Heading 3"]);
applyConcurrentActions(peerInfos, {
c1: async (editor) => {
await insertText(editor, "1");
},
});
await animationFrame();
mergePeersSteps(peerInfos);
expect(".o-we-powerbox").toHaveCount(1);
expect(commandNames()).toEqual(["Heading 1"]);
renderTextualSelection(peerInfos);
validateContent(peerInfos, "<p>a/heading1[c1}{c1]b</p><p>cg[c2}{c2]d</p>");
});
test("powerbox doesn't need to be displayed to apply a command (fast search)", async () => {
const { el, editor } = await setupEditor("<p>ab[]</p>");
await insertText(editor, "/head");
expect(".o-we-powerbox").toHaveCount(0);
await press("enter");
expect(".o-we-powerbox").toHaveCount(0);
expect(getContent(el)).toBe("<h1>ab[]</h1>");
});
describe("search keywords", () => {
test("should search commands by optional keywords", async () => {
class TestPlugin extends Plugin {
static id = "test";
resources = {
user_commands: { id: "testCommand", run: () => {} },
powerbox_categories: { id: "test", name: "Test" },
powerbox_items: [
{
title: "Test1",
description: "Test1",
categoryId: "test",
commandId: "testCommand",
keywords: ["apple", "orange"],
},
{
title: "Test2",
description: "Test2 has apples and oranges in its description",
categoryId: "test",
commandId: "testCommand",
},
],
};
}
const { editor, el } = await setupEditor(`<p>[]</p>`, {
config: { Plugins: [...MAIN_PLUGINS, TestPlugin] },
});
expect(".o-we-powerbox").toHaveCount(0);
insertText(editor, "/apple");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
// Both commands should be found with the keyword "apple", being the first
// one with a higher score
expect(commandNames(el)).toEqual(["Test1", "Test2"]);
// Replace "apple" by "orange"
for (let i = 0; i < 5; i++) {
press("backspace");
}
insertText(editor, "/orange");
await animationFrame();
// Same as above
expect(commandNames(el)).toEqual(["Test1", "Test2"]);
insertText(editor, "s");
// "/oranges"
await animationFrame();
// It no longer matches anything in the Test1 command
expect(commandNames(el)).toEqual(["Test2"]);
});
test("match order: full match on keyword should come before partial matches on names or descriptions", async () => {
class TestPlugin extends Plugin {
static id = "test";
resources = {
user_commands: { id: "testCommand", run: () => {} },
powerbox_categories: { id: "test", name: "Test" },
powerbox_items: [
{
title: "Change direction", // "icon" fuzzy matches this
description: "test",
categoryId: "test",
commandId: "testCommand",
},
{
title: "Some command",
description: "add a big section", // "icon" fuzzy matches this
categoryId: "test",
commandId: "testCommand",
},
{
title: "Insert a pictogram",
description: "test",
categoryId: "test",
commandId: "testCommand",
keywords: ["icon"],
},
],
};
}
const { editor, el } = await setupEditor(`<p>[]</p>`, {
config: {
Plugins: [...CORE_PLUGINS, PowerboxPlugin, SearchPowerboxPlugin, TestPlugin],
},
});
expect(".o-we-powerbox").toHaveCount(0);
insertText(editor, "/icon");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
const matchedCommands = commandNames(el);
// All three commands are found, as they all match "icon" in some way.
expect(matchedCommands).toInclude("Change direction");
expect(matchedCommands).toInclude("Some command");
expect(matchedCommands).toInclude("Insert a pictogram");
// The one with the exact keyword match should come first.
expect(matchedCommands[0]).toBe("Insert a pictogram");
});
});
describe("close", () => {
test("should close powerbox if there is no result", async () => {
const { el, editor } = await setupEditor("<p>a[]</p>");
await insertText(editor, "/");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
await insertText(editor, "zxzxzxz");
await animationFrame();
expect(getContent(el)).toBe("<p>a/zxzxzxz[]</p>");
expect(".o-we-powerbox").toHaveCount(0);
});
test("should close powerbox typing a space", async () => {
const { el, editor } = await setupEditor("<p>a[]</p>");
await insertText(editor, "/");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
// We need to add another character (b) otherwise the space will be
// considered invisible in the getContent(el). This is a limitation
// of the test suite that does not transform the space into a nbsp.
await insertText(editor, " b");
await animationFrame();
expect(getContent(el)).toBe("<p>a/ b[]</p>");
expect(".o-we-powerbox").toHaveCount(0);
});
test("delete '/' should close the powerbox", async () => {
const { editor, el } = await setupEditor("<p>[]</p>");
await insertText(editor, "/");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
expect(getContent(el)).toBe("<p>/[]</p>");
await press("backspace");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(0);
expect(getContent(el)).toBe(
`<p placeholder='Type "/" for commands' class="o-we-hint">[]<br></p>`
);
await insertText(editor, "a");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(0);
expect(getContent(el)).toBe("<p>a[]<br></p>");
});
test("press escape should close the powerbox", async () => {
const { editor, el } = await setupEditor("<p>[]</p>");
await insertText(editor, "/");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
expect(getContent(el)).toBe("<p>/[]</p>");
await press("escape");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(0);
expect(getContent(el)).toBe(`<p>/[]</p>`);
await insertText(editor, "h");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(0);
expect(getContent(el)).toBe(`<p>/h[]</p>`);
});
});
});
test("should execute command and remove term and hot character on Enter", async () => {
const { el, editor } = await setupEditor("<p>ab[]</p>");
await insertText(editor, "/head");
await animationFrame();
expect(commandNames(el)).toEqual(["Heading 1", "Heading 2", "Heading 3"]);
expect(".o-we-powerbox").toHaveCount(1);
await press("Enter");
expect(getContent(el)).toBe("<h1>ab[]</h1>");
expect(".o-we-powerbox").toHaveCount(1);
// need 1 animation frame to close
await animationFrame();
expect(".o-we-powerbox").toHaveCount(0);
});
test("should execute command and remove term and hot character on Tab", async () => {
const { el, editor } = await setupEditor("<p>ab[]</p>");
await insertText(editor, "/head");
await animationFrame();
await press("Tab");
expect(getContent(el)).toBe("<h1>ab[]</h1>");
});
test.todo("should close the powerbox if keyup event is called on other block", async () => {
// ged: not sure i understand the goal of this test
const { editor } = await setupEditor("<p>ab</p><p>c[]d</p>");
await insertText(editor, "/");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
// await dispatch(editor.editable, "keyup");
expect(".o-we-powerbox").toHaveCount(1);
await animationFrame();
expect(".o-we-powerbox").toHaveCount(0);
});
test.tags("desktop");
test("should insert a 3x3 table on type `/table`", async () => {
const { el, editor } = await setupEditor("<p>[]</p>");
expect(getContent(el)).toBe(`<p placeholder='Type "/" for commands' class="o-we-hint">[]</p>`);
await insertText(editor, "/table");
await waitFor(".o-we-powerbox ");
await press("Enter");
await animationFrame();
await press("Enter");
await tick();
expect(getContent(el)).toBe(
`<table class="table table-bordered o_table"><tbody><tr><td><p placeholder='Type "/" for commands' class="o-we-hint">[]<br></p></td><td><p><br></p></td><td><p><br></p></td></tr><tr><td><p><br></p></td><td><p><br></p></td><td><p><br></p></td></tr><tr><td><p><br></p></td><td><p><br></p></td><td><p><br></p></td></tr></tbody></table><p><br></p>`
);
});
test.tags("mobile");
test("should insert a 3x3 table on type `/table` in mobile view", async () => {
const { el, editor } = await setupEditor("<p>[]<br></p>");
await insertText(editor, "/table");
await waitFor(".o-we-powerbox ");
await press("Enter");
await tick();
expect(getContent(el)).toBe(
`<table class="table table-bordered o_table"><tbody><tr><td><p placeholder='Type "/" for commands' class="o-we-hint">[]<br></p></td><td><p><br></p></td><td><p><br></p></td></tr><tr><td><p><br></p></td><td><p><br></p></td><td><p><br></p></td></tr><tr><td><p><br></p></td><td><p><br></p></td><td><p><br></p></td></tr></tbody></table><p><br></p>`
);
});
test("should toggle list on empty paragraph", async () => {
const { el, editor } = await setupEditor("<p>[]<br></p>");
/** @todo fix warnings */
patchWithCleanup(console, { warn: () => {} });
await insertText(editor, "/");
// @todo @phoenix: remove this once we manage inputs.
// Simulate <br> removal by contenteditable when something is inserted
el.querySelector("p > br").remove();
await insertText(editor, "checklist");
expect(getContent(el)).toBe("<p>/checklist[]</p>");
await animationFrame();
expect(commandNames(el)).toEqual(["Checklist"]);
expect(".o-we-powerbox").toHaveCount(1);
await press("Enter");
expect(getContent(el)).toBe(
`<ul class="o_checklist"><li placeholder="List" class="o-we-hint">[]<br></li></ul>`
);
// need 1 animation frame to close
await animationFrame();
expect(".o-we-powerbox").toHaveCount(0);
});
class NoOpPlugin extends Plugin {
static id = "noOp";
resources = {
user_commands: [
{
id: "noOp",
run: () => {},
},
],
powerbox_categories: { id: "no_op", name: "No-op" },
powerbox_items: [
{
title: "No-op",
description: "No-op",
categoryId: "no_op",
icon: "fa-header",
commandId: "noOp",
},
],
};
}
test("should restore state before /command insertion when command is executed (1)", async () => {
const { el, editor } = await setupEditor("<p>abc[]</p>", {
config: { Plugins: [...MAIN_PLUGINS, NoOpPlugin] },
});
await insertText(editor, "/no-op");
expect(getContent(el)).toBe("<p>abc/no-op[]</p>");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
expect(commandNames(el)).toEqual(["No-op"]);
await press("Enter");
expect(getContent(el)).toBe("<p>abc[]</p>");
});
test("should restore state before /command insertion when command is executed (2)", async () => {
const { el, editor } = await setupEditor("<p>[]<br></p>", {
config: { Plugins: [...MAIN_PLUGINS, NoOpPlugin] },
});
/** @todo fix warnings */
patchWithCleanup(console, { warn: () => {} });
expect(getContent(el)).toBe(
`<p placeholder='Type "/" for commands' class="o-we-hint">[]<br></p>`
);
await insertText(editor, "/");
// @todo @phoenix: remove this once we manage inputs.
// Simulate <br> removal by contenteditable when something is inserted
el.querySelector("p > br").remove();
await insertText(editor, "no-op");
expect(getContent(el)).toBe("<p>/no-op[]</p>");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
expect(commandNames(el)).toEqual(["No-op"]);
await press("Enter");
expect(getContent(el)).toBe(
`<p class="o-we-hint" placeholder='Type "/" for commands'>[]<br></p>`
);
});
test("should discard /command insertion from history when command is executed", async () => {
const { el, editor } = await setupEditor("<p>[]<br></p>");
/** @todo fix warnings */
patchWithCleanup(console, { warn: () => {} });
expect(getContent(el)).toBe(
`<p placeholder='Type "/" for commands' class="o-we-hint">[]<br></p>`
);
// @todo @phoenix: remove this once we manage inputs.
// Simulate <br> removal by contenteditable when something is inserted
el.querySelector("p > br").remove();
await insertText(editor, "abc/heading1");
expect(getContent(el)).toBe("<p>abc/heading1[]</p>");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
expect(commandNames(el)).toEqual(["Heading 1"]);
await press("Enter");
expect(getContent(el)).toBe("<h1>abc[]</h1>");
execCommand(editor, "historyUndo");
expect(getContent(el)).toBe("<p>abc[]</p>");
execCommand(editor, "historyRedo");
expect(getContent(el)).toBe("<h1>abc[]</h1>");
execCommand(editor, "historyUndo");
expect(getContent(el)).toBe("<p>abc[]</p>");
execCommand(editor, "historyUndo");
expect(getContent(el)).toBe("<p>ab[]</p>");
execCommand(editor, "historyUndo");
expect(getContent(el)).toBe("<p>a[]</p>");
execCommand(editor, "historyUndo");
expect(getContent(el)).toBe(
`<p class="o-we-hint" placeholder='Type "/" for commands'>[]<br></p>`
);
});
test("should adapt the search of the powerbox when undo/redo", async () => {
const { editor, el } = await setupEditor("<p>ab[]</p>");
await insertText(editor, "/heading1");
await animationFrame();
expect(commandNames(el)).toEqual(["Heading 1"]);
undo(editor);
await animationFrame();
expect(commandNames(el)).toEqual(["Heading 1", "Heading 2", "Heading 3"]);
redo(editor);
await animationFrame();
expect(commandNames(el)).toEqual(["Heading 1"]);
});
test("should open the Powerbox on type `/` in DIV", async () => {
const { editor } = await setupEditor(`<div>ab<br><br>[]</div>`);
expect(".o-we-powerbox").toHaveCount(0);
await insertText(editor, "/");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
});
test("press 'arrowdown' to navigate", async () => {
const { editor, el } = await setupEditor("<p>ab[]</p>");
await insertText(editor, "/head");
await animationFrame();
expect(commandNames(el)).toEqual(["Heading 1", "Heading 2", "Heading 3"]);
expect(".active .o-we-command-name").toHaveText("Heading 1");
await press("arrowdown");
await animationFrame();
expect(".active .o-we-command-name").toHaveText("Heading 2");
await press("arrowdown");
await animationFrame();
expect(".active .o-we-command-name").toHaveText("Heading 3");
await press("arrowdown");
await animationFrame();
expect(".active .o-we-command-name").toHaveText("Heading 1");
});
test("press 'arrowup' to navigate", async () => {
const { editor, el } = await setupEditor("<p>ab[]</p>");
await insertText(editor, "/head");
await animationFrame();
expect(commandNames(el)).toEqual(["Heading 1", "Heading 2", "Heading 3"]);
expect(".active .o-we-command-name").toHaveText("Heading 1");
await press("arrowup");
await animationFrame();
expect(".active .o-we-command-name").toHaveText("Heading 3");
await press("arrowup");
await animationFrame();
expect(".active .o-we-command-name").toHaveText("Heading 2");
await press("arrowup");
await animationFrame();
expect(".active .o-we-command-name").toHaveText("Heading 1");
});
test("press 'arrowleft' should close PowerBox", async () => {
const { editor } = await setupEditor("<p>ab[]c</p>");
await insertText(editor, "/head");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
await press("arrowleft");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(0);
});
test("press 'arrowright' should close PowerBox", async () => {
const { editor } = await setupEditor("<p>ab[]c</p>");
await insertText(editor, "/head");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(1);
await press("arrowright");
await animationFrame();
expect(".o-we-powerbox").toHaveCount(0);
});
test.tags("desktop");
test("select command with 'mouseenter'", async () => {
const { editor, el } = await setupEditor("<p>ab[]</p>");
// Hoot don't trigger a mousemove event at the start of an hover, if we don't hover
// another element before. So we need to do a first hover to set a previous element.
await hover(".odoo-editor-editable");
await insertText(editor, "/head");
await animationFrame();
expect(commandNames(el)).toEqual(["Heading 1", "Heading 2", "Heading 3"]);
expect(".active .o-we-command-name").toHaveText("Heading 1");
await hover(".o-we-command-name:last");
await animationFrame();
expect(".active .o-we-command-name").toHaveText("Heading 3");
await press("enter");
expect(getContent(el)).toBe("<h3>ab[]</h3>");
});
test("click on a command", async () => {
const { editor, el } = await setupEditor("<p>ab[]</p>");
await insertText(editor, "/head");
await animationFrame();
expect(commandNames(el)).toEqual(["Heading 1", "Heading 2", "Heading 3"]);
expect(".active .o-we-command-name").toHaveText("Heading 1");
await click(".o-we-command-name:last");
expect(getContent(el)).toBe("<h3>ab[]</h3>");
});
test("create a new <p> with press 'Enter' then apply a powerbox command", async () => {
const { editor, el } = await setupEditor("<p>ab[]cd</p>");
// Event trigger when you press "Enter" => create a new paragraph
await manuallyDispatchProgrammaticEvent(editor.editable, "beforeinput", {
inputType: "insertParagraph",
});
await insertText(editor, "/head");
await animationFrame();
await press("Enter");
expect(getContent(el)).toBe("<p>ab</p><h1>[]cd</h1>");
});
// @todo @phoenix Need a fix in hoot duplicate error are throw
test.todo("add plugins with the same powerboxCategory should crash", async () => {
expect.errors(1);
patchWithCleanup(console, {
warn: (msg) => expect.step(msg),
});
class Plugin1 extends Plugin {
resources = {
powerbox_categories: withSequence(10, { id: "test", name: "Test" }),
};
}
class Plugin2 extends Plugin {
resources = {
powerbox_categories: withSequence(10, { id: "test", name: "Test" }),
};
}
await expect(
setupEditor("<p>ab[]cd</p>", {
config: { Plugins: [...MAIN_PLUGINS, Plugin1, Plugin2] },
})
).rejects.toThrow();
expect(["Duplicate category id: test"]).toVerifyErrors();
expect([
"[Owl] Unhandled error. Destroying the root component",
"[Owl] Unhandled error. Destroying the root component",
"[Owl] Unhandled error. Destroying the root component",
]).toVerifySteps();
});