`
),
});
});
test.tags("focus required");
test("add a caption to an image surrounded by text and focus it", async () => {
const captionId = 1;
await testEditor({
config: configWithEmbeddedCaption,
contentBefore: `
abcd
`,
stepFunction: async (editor) => {
await toggleCaption();
await waitFor("figcaption > input");
const input = queryOne("figure > figcaption > input");
expect(input.value).toBe("");
expect(editor.document.activeElement).toBe(input);
// Remove the editor selection for the test because it's irrelevant
// since the focus is not in it.
const selection = editor.document.getSelection();
selection.removeAllRanges();
},
contentAfterEdit: unformat(
`
ab
cd
`
),
});
});
test("saving an image with a caption replaces the input with plain text", async () => {
const captionId = 1;
const caption = "Hello";
await testEditor({
config: configWithEmbeddedCaption,
contentBefore: unformat(
`${caption}`
),
contentBeforeEdit: unformat(
// Paragraphs get added to ensure we can write before/after the figure.
`
`
),
// Unchanged.
contentAfterEdit: unformat(
`
`
),
// Cleaned up for screen readers.
contentAfter: unformat(
`
${caption}
`
),
});
});
test.tags("focus required");
test("loading an image with a caption embeds it", async () => {
const { editor } = await setupEditorWithEmbeddedCaption(`
Hello
`);
const image = queryOne("img");
expect(image.getAttribute("data-caption")).toBe("Hello");
const input = queryOne("figure > figcaption > input");
expect(input.value).toBe("Hello");
// Do not focus the input when loading the page.
expect(editor.document.activeElement).not.toBe(input);
});
test.tags("focus required");
test("clicking the caption button on an image with a caption removes the caption", async () => {
const caption = "Hello";
await testEditor({
config: configWithEmbeddedCaption,
contentBefore: unformat(
`${caption}`
),
stepFunction: async (editor) => {
const input = queryOne("figure > figcaption > input");
await toggleCaption();
expect(editor.document.activeElement).not.toBe(input);
await expectElementCount(".o-we-toolbar", 1);
},
contentAfterEdit: unformat(
`
`
),
});
});
test.tags("desktop", "focus required");
test("can't use the toolbar in a caption", async () => {
// TODO: The toolbar should not _always_ be usable in mobile!
await testEditor({
config: configWithEmbeddedCaption,
contentBefore: `
[]Heading
`,
stepFunction: async (editor) => {
await toggleCaption();
await waitFor("figcaption > input");
const input = queryOne("figure > figcaption > input");
expect(editor.document.activeElement).toBe(input);
await animationFrame();
await expectElementCount(".o-we-toolbar", 0);
input.select();
// Check that the contents of the input were indeed selected by
// inserting text.
editor.document.execCommand("insertText", false, "a");
expect(input.value).toBe("a");
await click("h1"); // Blur the input.
await animationFrame(); // Wait for the focus event to trigger a step.
editor.shared.selection.setCursorStart(queryOne("h1"));
},
contentAfter: unformat(
`
a
[]Heading
`
),
});
});
test.tags("focus required");
test("undo in a caption undoes the last caption action then returns to regular editor undo", async () => {
const caption = "Hello";
await testEditor({
config: configWithEmbeddedCaption,
contentBefore: `
[]Heading
`,
stepFunction: async (editor) => {
await insertText(editor, "a");
const heading = queryOne("h1");
expect(heading.textContent).toBe("aHeading");
await toggleCaption();
await waitFor("figcaption > input");
const input = queryOne("figure > figcaption > input");
expect(editor.document.activeElement).toBe(input);
// Using native execCommand so the input's native history works.
await editor.document.execCommand("insertText", false, "b");
await editor.document.execCommand("insertText", false, "c");
await editor.document.execCommand("insertText", false, "d");
await editor.document.execCommand("delete", false, null); // Backspace.
expect(input.value).toBe(`${caption}bc`);
// We simulate undo with Ctrl+Z because we want to see how it
// interacts with native browser behavior.
const ctrlZ = async (target, shouldApplyNativeUndo) => {
const keydown = await manuallyDispatchProgrammaticEvent(target, "keydown", {
key: "z",
ctrlKey: true,
});
if (keydown.defaultPrevented) {
return;
}
let valueBeforeUndo;
if (target === input) {
valueBeforeUndo = input.value;
// This is supposed to happen only after "beforeinput" but
// beforeinput doesn't happen at all if there is nothing to
// undo and this allows us to determine if that is the case.
editor.document.execCommand("undo", false, null);
}
if (shouldApplyNativeUndo) {
// The native undo should have changed the value of the
// input.
expect(input.value).not.toBe(valueBeforeUndo);
} else if (target === input) {
// The native undo should not have changed the value of the input.
expect(input.value).toBe(valueBeforeUndo);
}
if (target !== input || input.value !== valueBeforeUndo) {
// The input events don't get triggered if the input has
// nothing to undo.
const beforeInput = await manuallyDispatchProgrammaticEvent(
target,
"beforeinput",
{
inputType: "historyUndo",
}
);
// --> Here the editor should do its own UNDO.
if (beforeInput.defaultPrevented) {
return;
}
const inputEvent = await manuallyDispatchProgrammaticEvent(target, "input", {
inputType: "historyUndo",
});
if (inputEvent.defaultPrevented) {
return;
}
}
await manuallyDispatchProgrammaticEvent(target, "keyup", {
key: "z",
ctrlKey: true,
});
};
// Native input undo undoes backspace in the input.
expect(editor.document.activeElement).toBe(input);
await ctrlZ(input, true);
expect(input.value).toBe(`${caption}bcd`);
expect(heading.textContent).toBe("aHeading");
// Native input undo undoes all the other key presses in the input.
expect(editor.document.activeElement).toBe(input);
await ctrlZ(input, true);
expect(input.value).toBe(caption);
expect(heading.textContent).toBe("aHeading");
// Editor undo removes the caption.
expect(editor.document.activeElement).toBe(input);
await ctrlZ(input, false);
expect(input.isConnected).toBe(false);
expect(heading.textContent).toBe("aHeading");
// Editor undo removes the key press in the heading.
expect(editor.document.activeElement).not.toBe(input);
const anchor = editor.document.getSelection().anchorNode;
await ctrlZ(closestElement(anchor), false);
expect(heading.textContent).toBe("Heading");
},
contentAfter: unformat(
`
[]Heading
`
),
});
});
test("remove an image with a caption", async () => {
await testEditor({
config: configWithEmbeddedCaption,
contentBefore: unformat(
`Hello
`
),
});
});
test("add a link to an image with a caption", async () => {
await testEditor({
config: configWithEmbeddedCaption,
contentBefore: unformat(
`Hello
`
),
});
});
test.tags("focus required");
test("add a caption to an image with a link", async () => {
await testEditor({
config: configWithEmbeddedCaption,
contentBefore: unformat(
`
[]Heading
`
),
stepFunction: async (editor) => {
await toggleCaption();
await waitFor("figcaption > input");
const input = queryOne("figure > figcaption > input");
expect(editor.document.activeElement).toBe(input);
// Remove the editor selection for the test because it's irrelevant
// since the focus is not in it.
const selection = editor.document.getSelection();
selection.removeAllRanges();
},
contentAfter: unformat(
`
Heading
`
),
});
});
test("add a caption then a link to an image surrounded by text", async () => {
await testEditor({
config: configWithEmbeddedCaption,
contentBefore: `
`
),
});
});
test("add a link then a caption to an image surrounded by text", async () => {
await testEditor({
config: configWithEmbeddedCaption,
contentBefore: `
abcd
`,
stepFunction: async (editor) => {
await addLinkToImage("odoo.com");
await animationFrame();
await toggleCaption("Hello");
// Blur the input to commit the caption.
await click("p"); // Blur the input.
await animationFrame(); // Wait for the focus event to trigger a step.
editor.shared.selection.setCursorStart(editor.document.querySelectorAll("p")[1]);
},
contentAfter: unformat(
`