335 lines
12 KiB
JavaScript
335 lines
12 KiB
JavaScript
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: "<p>ab[cde]fg</p>",
|
|
stepFunction: bold,
|
|
contentAfter: `<p>ab${strong(`[cde]`)}fg</p>`,
|
|
});
|
|
});
|
|
|
|
test("should make a few characters not bold", async () => {
|
|
await testEditor({
|
|
contentBefore: `<p>${strong(`ab[cde]fg`)}</p>`,
|
|
stepFunction: bold,
|
|
contentAfter: `<p>${strong(`ab`)}[cde]${strong(`fg`)}</p>`,
|
|
});
|
|
});
|
|
|
|
test("should make two paragraphs bold", async () => {
|
|
await testEditor({
|
|
contentBefore: "<p>[abc</p><p>def]</p>",
|
|
stepFunction: bold,
|
|
contentAfter: `<p>${strong(`[abc`)}</p><p>${strong(`def]`)}</p>`,
|
|
});
|
|
});
|
|
|
|
test("should make two paragraphs not bold", async () => {
|
|
await testEditor({
|
|
contentBefore: `<p>${strong(`[abc`)}</p><p>${strong(`def]`)}</p>`,
|
|
stepFunction: bold,
|
|
contentAfter: `<p>[abc</p><p>def]</p>`,
|
|
});
|
|
});
|
|
|
|
test("should make qweb tag bold", async () => {
|
|
await testEditor({
|
|
contentBefore: `<div><p t-esc="'Test'" contenteditable="false">[Test]</p></div>`,
|
|
stepFunction: bold,
|
|
contentAfter: `<div>[<p t-esc="'Test'" contenteditable="false" style="font-weight: bolder;">Test</p>]</div>`,
|
|
});
|
|
await testEditor({
|
|
contentBefore: `<div><p t-field="record.name" contenteditable="false">[Test]</p></div>`,
|
|
stepFunction: bold,
|
|
contentAfter: `<div>[<p t-field="record.name" contenteditable="false" style="font-weight: bolder;">Test</p>]</div>`,
|
|
});
|
|
});
|
|
|
|
test("should make qweb tag bold even with partial selection", async () => {
|
|
const { editor, el } = await setupEditor(
|
|
`<div><p t-esc="'Test'" contenteditable="false">T[e]st</p></div>`
|
|
);
|
|
bold(editor);
|
|
expect(getContent(el)).toBe(
|
|
`<div>[<p t-esc="'Test'" contenteditable="false" style="font-weight: bolder;">Test</p>]</div>`
|
|
);
|
|
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: `<h1>${notStrong(`[ab`)}</h1><p>]cd</p>`,
|
|
stepFunction: bold,
|
|
contentAfter: `<h1>[ab]</h1><p>cd</p>`,
|
|
});
|
|
});
|
|
|
|
test("should make a whole heading not bold after a triple click (heading is considered bold)", async () => {
|
|
const { el, editor } = await setupEditor(`<h1>[ab</h1><p>]cd</p>`, {
|
|
styleContent: styleH1Bold,
|
|
});
|
|
await tripleClick(el.querySelector("h1"));
|
|
bold(editor);
|
|
expect(getContent(el)).toBe(`<h1>${notStrong(`[ab]`)}</h1><p>cd</p>`);
|
|
});
|
|
|
|
test("should make a selection starting with bold text fully bold", async () => {
|
|
await testEditor({
|
|
contentBefore: `<p>${strong(`[ab`)}</p><p>c]d</p>`,
|
|
stepFunction: bold,
|
|
contentAfter: `<p>${strong(`[ab`)}</p><p>${strong(`c]`)}d</p>`,
|
|
});
|
|
});
|
|
|
|
test("should make a selection with bold text in the middle fully bold", async () => {
|
|
await testEditor({
|
|
contentBefore: `<p>[a${strong(`b`)}</p><p>${strong(`c`)}d]e</p>`,
|
|
stepFunction: bold,
|
|
contentAfter: `<p>${strong(`[ab`)}</p><p>${strong(`cd]`)}e</p>`,
|
|
});
|
|
});
|
|
|
|
test("should make a selection ending with bold text fully bold", async () => {
|
|
await testEditor({
|
|
styleContent: styleH1Bold,
|
|
contentBefore: `<h1>${notStrong(`[ab`)}</h1><p>${strong(`c]d`)}</p>`,
|
|
stepFunction: bold,
|
|
contentAfter: `<h1>[ab</h1><p>${strong(`c]d`)}</p>`,
|
|
});
|
|
});
|
|
|
|
test("should get ready to type in bold", async () => {
|
|
await testEditor({
|
|
contentBefore: "<p>ab[]cd</p>",
|
|
stepFunction: bold,
|
|
contentAfterEdit: `<p>ab${strong(`[]\u200B`, "first")}cd</p>`,
|
|
contentAfter: `<p>ab[]cd</p>`,
|
|
});
|
|
});
|
|
|
|
test("should get ready to type in not bold", async () => {
|
|
await testEditor({
|
|
contentBefore: `<p>${strong(`ab[]cd`)}</p>`,
|
|
stepFunction: bold,
|
|
contentAfterEdit: `<p>${strong(`ab`)}${span(`[]\u200B`, "first")}${strong(`cd`)}</p>`,
|
|
contentAfter: `<p>${strong(`ab[]cd`)}</p>`,
|
|
});
|
|
});
|
|
|
|
test("should remove a bold tag that was redondant while performing the command", async () => {
|
|
for (const tag of BOLD_TAGS) {
|
|
await testEditor({
|
|
contentBefore: `<p>a${tag(`b${tag(`[c]`)}d`)}e</p>`,
|
|
stepFunction: bold,
|
|
contentAfter: `<p>a${tag("b")}[c]${tag("d")}e</p>`,
|
|
});
|
|
}
|
|
});
|
|
|
|
test("should remove a bold tag that was redondant with different tags while performing the command", async () => {
|
|
await testEditor({
|
|
contentBefore: unformat(`<p>
|
|
a
|
|
<span style="font-weight: bolder;">
|
|
b
|
|
<strong>c<b>[d]</b>e</strong>
|
|
f
|
|
</span>
|
|
g
|
|
</p>`),
|
|
stepFunction: bold,
|
|
contentAfter: unformat(`<p>
|
|
a
|
|
<span style="font-weight: bolder;">b<strong>c</strong></span>
|
|
[d]
|
|
<span style="font-weight: bolder;"><strong>e</strong>f</span>
|
|
g
|
|
</p>`),
|
|
});
|
|
});
|
|
|
|
test("should not format non-editable text (bold)", async () => {
|
|
await testEditor({
|
|
contentBefore: '<p>[a</p><p contenteditable="false">b</p><p>c]</p>',
|
|
stepFunction: bold,
|
|
contentAfter: `<p>${strong("[a")}</p><p contenteditable="false">b</p><p>${strong(
|
|
"c]"
|
|
)}</p>`,
|
|
});
|
|
});
|
|
|
|
test("should make a few characters bold inside table (bold)", async () => {
|
|
await testEditor({
|
|
contentBefore: unformat(`
|
|
<table class="table table-bordered o_table o_selected_table">
|
|
<tbody>
|
|
<tr>
|
|
<td class="o_selected_td"><p>[abc</p></td>
|
|
<td class="o_selected_td"><p>def</p></td>
|
|
<td class="o_selected_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>`
|
|
),
|
|
stepFunction: bold,
|
|
contentAfterEdit: unformat(`
|
|
<table class="table table-bordered o_table o_selected_table">
|
|
<tbody>
|
|
<tr>
|
|
<td class="o_selected_td"><p>${strong(`[abc`)}</p></td>
|
|
<td class="o_selected_td"><p>${strong(`def`)}</p></td>
|
|
<td class="o_selected_td"><p>${strong(`]<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>`
|
|
),
|
|
});
|
|
});
|
|
|
|
test("should insert a span zws when toggling a formatting command twice", () => {
|
|
return testEditor({
|
|
contentBefore: `<p>[]<br></p>`,
|
|
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: `<p>${span(`[]\u200B`, "first")}</p>`,
|
|
});
|
|
});
|
|
|
|
// 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("<p>ab[]cd</p>");
|
|
|
|
/** @todo fix warnings */
|
|
patchWithCleanup(console, { warn: () => {} });
|
|
|
|
// Toggle bold on.
|
|
bold(editor);
|
|
expect(getContent(el)).toBe(`<p>ab${strong("[]\u200B", "first")}cd</p>`);
|
|
|
|
// 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(`<p>ab${strong("x[]")}cd</p>`);
|
|
|
|
// Keep typing.
|
|
await typeChar(editor, "y");
|
|
expect(getContent(el)).toBe(`<p>ab${strong("xy[]")}cd</p>`);
|
|
|
|
// Toggle bold off and type more.
|
|
bold(editor);
|
|
expect(getContent(el)).toBe(`<p>ab${strong("xy")}${span("[]\u200B", "first")}cd</p>`);
|
|
await typeChar(editor, "z");
|
|
expect(getContent(el)).toBe(`<p>ab${strong("xy")}z[]cd</p>`);
|
|
});
|
|
|
|
test.tags("desktop");
|
|
test("create bold with shortcut + selected with arrow", async () => {
|
|
const { editor, el } = await setupEditor("<p>ab[]cd</p>");
|
|
await press(["control", "b"]);
|
|
expect(getContent(el)).toBe(`<p>ab${strong("[]\u200B", "first")}cd</p>`);
|
|
|
|
await simulateArrowKeyPress(editor, ["Shift", "ArrowRight"]);
|
|
await tick(); // await selectionchange
|
|
await animationFrame();
|
|
expect(".o-we-toolbar").toHaveCount(1);
|
|
expect(getContent(el)).toBe(`<p>ab${strong("[\u200B", "first")}c]d</p>`);
|
|
|
|
await simulateArrowKeyPress(editor, ["Shift", "ArrowLeft"]);
|
|
await tick(); // await selectionchange
|
|
await animationFrame();
|
|
expect(".o-we-toolbar").toHaveCount(0);
|
|
expect(getContent(el)).toBe(`<p>ab${strong("[\u200B]", "first")}cd</p>`);
|
|
});
|
|
|
|
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: `<div>a<span class="boldClass">[b]</span>c</div>`,
|
|
stepFunction: bold,
|
|
contentAfter: `<div>a<span class="boldClass"><span style="font-weight: normal;">[b]</span></span>c</div>`,
|
|
});
|
|
});
|
|
|
|
test("should force the font-weight to normal", async () => {
|
|
await testEditor({
|
|
styleContent: styleContentBold,
|
|
contentBefore: `<div class="boldClass">a[b]c</div>`,
|
|
stepFunction: bold,
|
|
contentAfter: `<div class="boldClass">a<span style="font-weight: normal;">[b]</span>c</div>`,
|
|
});
|
|
});
|
|
|
|
test("should force the font-weight to normal while removing redundant tag", async () => {
|
|
for (const tag of BOLD_TAGS) {
|
|
await testEditor({
|
|
styleContent: styleContentBold,
|
|
contentBefore: `<div class="boldClass">a${tag("[b]")}c</div>`,
|
|
stepFunction: bold,
|
|
contentAfter: `<div class="boldClass">a<span style="font-weight: normal;">[b]</span>c</div>`,
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
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: `<h1>a${strong(`[b]`)}c</h1>`,
|
|
stepFunction: bold,
|
|
contentAfter: `<h1>a<span style="font-weight: bolder;">[b]</span>c</h1>`,
|
|
});
|
|
});
|
|
});
|