import { describe, expect, test } from "@odoo/hoot"; import { manuallyDispatchProgrammaticEvent, press, queryOne } from "@odoo/hoot-dom"; import { animationFrame, tick } from "@odoo/hoot-mock"; import { patchWithCleanup } from "@web/../tests/web_test_helpers"; import { setupEditor, testEditor } from "../_helpers/editor"; import { unformat } from "../_helpers/format"; import { getContent } from "../_helpers/selection"; import { BOLD_TAGS, notStrong, span, strong } from "../_helpers/tags"; import { bold, simulateArrowKeyPress, tripleClick } from "../_helpers/user_actions"; const styleH1Bold = `h1 { font-weight: bold; }`; test("should make a few characters bold", async () => { await testEditor({ contentBefore: "

ab[cde]fg

", stepFunction: bold, contentAfter: `

ab${strong(`[cde]`)}fg

`, }); }); test("should make a few characters not bold", async () => { await testEditor({ contentBefore: `

${strong(`ab[cde]fg`)}

`, stepFunction: bold, contentAfter: `

${strong(`ab`)}[cde]${strong(`fg`)}

`, }); }); test("should make two paragraphs bold", async () => { await testEditor({ contentBefore: "

[abc

def]

", stepFunction: bold, contentAfter: `

${strong(`[abc`)}

${strong(`def]`)}

`, }); }); test("should make two paragraphs not bold", async () => { await testEditor({ contentBefore: `

${strong(`[abc`)}

${strong(`def]`)}

`, stepFunction: bold, contentAfter: `

[abc

def]

`, }); }); test("should make qweb tag bold", async () => { await testEditor({ contentBefore: `

[Test]

`, stepFunction: bold, contentAfter: `
[

Test

]
`, }); await testEditor({ contentBefore: `

[Test]

`, stepFunction: bold, contentAfter: `
[

Test

]
`, }); }); test("should make qweb tag bold even with partial selection", async () => { const { editor, el } = await setupEditor( `

T[e]st

` ); bold(editor); expect(getContent(el)).toBe( `
[

Test

]
` ); expect(queryOne(`p[contenteditable="false"]`).childNodes.length).toBe(1); }); test("should make a whole heading bold after a triple click", async () => { await testEditor({ styleContent: styleH1Bold, contentBefore: `

${notStrong(`[ab`)}

]cd

`, stepFunction: bold, contentAfter: `

[ab]

cd

`, }); }); test("should make a whole heading not bold after a triple click (heading is considered bold)", async () => { const { el, editor } = await setupEditor(`

[ab

]cd

`, { styleContent: styleH1Bold, }); await tripleClick(el.querySelector("h1")); bold(editor); expect(getContent(el)).toBe(`

${notStrong(`[ab]`)}

cd

`); }); test("should make a selection starting with bold text fully bold", async () => { await testEditor({ contentBefore: `

${strong(`[ab`)}

c]d

`, stepFunction: bold, contentAfter: `

${strong(`[ab`)}

${strong(`c]`)}d

`, }); }); test("should make a selection with bold text in the middle fully bold", async () => { await testEditor({ contentBefore: `

[a${strong(`b`)}

${strong(`c`)}d]e

`, stepFunction: bold, contentAfter: `

${strong(`[ab`)}

${strong(`cd]`)}e

`, }); }); test("should make a selection ending with bold text fully bold", async () => { await testEditor({ styleContent: styleH1Bold, contentBefore: `

${notStrong(`[ab`)}

${strong(`c]d`)}

`, stepFunction: bold, contentAfter: `

[ab

${strong(`c]d`)}

`, }); }); test("should get ready to type in bold", async () => { await testEditor({ contentBefore: "

ab[]cd

", stepFunction: bold, contentAfterEdit: `

ab${strong(`[]\u200B`, "first")}cd

`, contentAfter: `

ab[]cd

`, }); }); test("should get ready to type in not bold", async () => { await testEditor({ contentBefore: `

${strong(`ab[]cd`)}

`, stepFunction: bold, contentAfterEdit: `

${strong(`ab`)}${span(`[]\u200B`, "first")}${strong(`cd`)}

`, contentAfter: `

${strong(`ab[]cd`)}

`, }); }); test("should remove a bold tag that was redondant while performing the command", async () => { for (const tag of BOLD_TAGS) { await testEditor({ contentBefore: `

a${tag(`b${tag(`[c]`)}d`)}e

`, stepFunction: bold, contentAfter: `

a${tag("b")}[c]${tag("d")}e

`, }); } }); test("should remove a bold tag that was redondant with different tags while performing the command", async () => { await testEditor({ contentBefore: unformat(`

a b c[d]e f g

`), stepFunction: bold, contentAfter: unformat(`

a bc [d] ef g

`), }); }); test("should not format non-editable text (bold)", async () => { await testEditor({ contentBefore: '

[a

b

c]

', stepFunction: bold, contentAfter: `

${strong("[a")}

b

${strong( "c]" )}

`, }); }); test("should make a few characters bold inside table (bold)", async () => { await testEditor({ contentBefore: unformat(`

[abc

def

]







` ), stepFunction: bold, contentAfterEdit: unformat(`

${strong(`[abc`)}

${strong(`def`)}

${strong(`]
`)}







` ), }); }); test("should insert a span zws when toggling a formatting command twice", () => { return testEditor({ contentBefore: `

[]

`, stepFunction: async (editor) => { bold(editor); bold(editor); }, // todo: It would be better to remove the zws entirely so that // the P could have the "/" hint but that behavior might be // complex with the current implementation. contentAfterEdit: `

${span(`[]\u200B`, "first")}

`, }); }); // This test uses execCommand to reproduce as closely as possible the browser's // default behaviour when typing in a contenteditable=true zone. test("should type in bold", async () => { async function typeChar(editor, char) { await manuallyDispatchProgrammaticEvent(editor.editable, "keydown", { key: char }); await manuallyDispatchProgrammaticEvent(editor.editable, "beforeinput", { inputType: "insertText", data: char, }); // Simulate text insertion as done by the contenteditable. editor.document.execCommand("insertText", false, char); // Input event is dispatched and handlers are called synchronously. await manuallyDispatchProgrammaticEvent(editor.editable, "keyup", { key: char }); } const { editor, el } = await setupEditor("

ab[]cd

"); /** @todo fix warnings */ patchWithCleanup(console, { warn: () => {} }); // Toggle bold on. bold(editor); expect(getContent(el)).toBe(`

ab${strong("[]\u200B", "first")}cd

`); // Simulate text insertion as done by the contenteditable. await typeChar(editor, "x"); // Check that character was inserted inside the strong tag. expect(getContent(el)).toBe(`

ab${strong("x[]")}cd

`); // Keep typing. await typeChar(editor, "y"); expect(getContent(el)).toBe(`

ab${strong("xy[]")}cd

`); // Toggle bold off and type more. bold(editor); expect(getContent(el)).toBe(`

ab${strong("xy")}${span("[]\u200B", "first")}cd

`); await typeChar(editor, "z"); expect(getContent(el)).toBe(`

ab${strong("xy")}z[]cd

`); }); test.tags("desktop"); test("create bold with shortcut + selected with arrow", async () => { const { editor, el } = await setupEditor("

ab[]cd

"); await press(["control", "b"]); expect(getContent(el)).toBe(`

ab${strong("[]\u200B", "first")}cd

`); await simulateArrowKeyPress(editor, ["Shift", "ArrowRight"]); await tick(); // await selectionchange await animationFrame(); expect(".o-we-toolbar").toHaveCount(1); expect(getContent(el)).toBe(`

ab${strong("[\u200B", "first")}c]d

`); await simulateArrowKeyPress(editor, ["Shift", "ArrowLeft"]); await tick(); // await selectionchange await animationFrame(); expect(".o-we-toolbar").toHaveCount(0); expect(getContent(el)).toBe(`

ab${strong("[\u200B]", "first")}cd

`); }); const styleContentBold = `.boldClass { font-weight: bold; }`; describe("inside container or inline with class already bold", () => { test("should force the font-weight to normal with an inline with class", async () => { await testEditor({ styleContent: styleContentBold, contentBefore: `
a[b]c
`, stepFunction: bold, contentAfter: `
a[b]c
`, }); }); test("should force the font-weight to normal", async () => { await testEditor({ styleContent: styleContentBold, contentBefore: `
a[b]c
`, stepFunction: bold, contentAfter: `
a[b]c
`, }); }); test("should force the font-weight to normal while removing redundant tag", async () => { for (const tag of BOLD_TAGS) { await testEditor({ styleContent: styleContentBold, contentBefore: `
a${tag("[b]")}c
`, stepFunction: bold, contentAfter: `
a[b]c
`, }); } }); }); describe("inside container font-weight: 500 and strong being strong-weight: 500", () => { test("should remove the redundant strong style and add span with a bolder font-weight", async () => { await testEditor({ styleContent: `h1, strong {font-weight: 500;}`, contentBefore: `

a${strong(`[b]`)}c

`, stepFunction: bold, contentAfter: `

a[b]c

`, }); }); });