import { describe, expect, test } from "@odoo/hoot"; import { getActiveElement, press, queryFirst, queryOne } from "@odoo/hoot-dom"; import { animationFrame, tick } from "@odoo/hoot-mock"; import { Component, xml } from "@odoo/owl"; import { patchWithCleanup } from "@web/../tests/web_test_helpers"; import { useAutofocus } from "@web/core/utils/hooks"; import { Plugin } from "../src/plugin"; import { MAIN_PLUGINS } from "../src/plugin_sets"; import { setupEditor } from "./_helpers/editor"; import { getContent } from "./_helpers/selection"; import { insertText, tripleClick } from "./_helpers/user_actions"; test("getEditableSelection should work, even if getSelection returns null", async () => { const { editor } = await setupEditor("
a[b]
"); let selection = editor.shared.selection.getEditableSelection(); expect(selection.startOffset).toBe(1); expect(selection.endOffset).toBe(2); // it happens sometimes in firefox that the selection is null patchWithCleanup(document, { getSelection: () => null, }); selection = editor.shared.selection.getEditableSelection(); expect(selection.startOffset).toBe(1); expect(selection.endOffset).toBe(2); }); test("plugins should be notified when ranges are removed", async () => { let count = 0; class TestPlugin extends Plugin { static id = "test"; resources = { selectionchange_handlers: () => count++, }; } const { el } = await setupEditor("a[b]
", { config: { Plugins: [...MAIN_PLUGINS, TestPlugin] }, }); const countBefore = count; document.getSelection().removeAllRanges(); await animationFrame(); expect(count).toBe(countBefore + 1); expect(getContent(el)).toBe("ab
"); }); test("triple click outside of the Editor", async () => { const { el } = await setupEditor("[]abc
d
", {}); const anchorNode = el.parentElement; await tripleClick(el.parentElement); expect(document.getSelection().anchorNode).toBe(anchorNode); expect(getContent(el)).toBe("abc
d
"); const p = el.querySelector("p"); await tripleClick(p); expect(document.getSelection().anchorNode).toBe(p.childNodes[0]); expect(getContent(el)).toBe("[abc]
d
"); }); test("correct selection after triple click with bold", async () => { const { el } = await setupEditor("[]abcd
efg
", {}); await tripleClick(queryFirst("p").firstChild); expect(getContent(el)).toBe("[abcd]
efg
"); }); test("fix selection P in the beggining being a direct child of the editable p after selection", async () => { const { el } = await setupEditor("b
"); expect(getContent(el)).toBe(`[]b
`); }); test("fix selection P in the beggining being a direct child of the editable p before selection", async () => { const { el } = await setupEditor("a
[]a[]
a[]b
"); const selectionData = editor.shared.selection.getSelectionData(); expect(selectionData.documentSelectionIsInEditable).toBe(true); }); test("documentSelectionIsInEditable should be false when it is set outside the editable", async () => { const { editor } = await setupEditor("ab
"); const selectionData = editor.shared.selection.getSelectionData(); expect(selectionData.documentSelectionIsInEditable).toBe(false); }); test("documentSelectionIsInEditable should be false when it is set outside the editable after retrieving it", async () => { const { editor } = await setupEditor("ab[]
"); const selection = document.getSelection(); let selectionData = editor.shared.selection.getSelectionData(); expect(selectionData.documentSelectionIsInEditable).toBe(true); selection.setPosition(document.body); // value is updated directly ! selectionData = editor.shared.selection.getSelectionData(); expect(selectionData.documentSelectionIsInEditable).toBe(false); }); }); test("setEditableSelection should not crash if getSelection returns null", async () => { const { editor } = await setupEditor("a[b]
"); let selection = editor.shared.selection.getEditableSelection(); expect(selection.startOffset).toBe(1); expect(selection.endOffset).toBe(2); // it happens sometimes in firefox that the selection is null patchWithCleanup(document, { getSelection: () => null, }); selection = editor.shared.selection.setSelection({ anchorNode: editor.editable.firstChild, anchorOffset: 0, }); // Selection could not be set, so it remains unchanged. expect(selection.startOffset).toBe(1); expect(selection.endOffset).toBe(2); }); test("modifySelection should not crash if getSelection returns null", async () => { const { editor } = await setupEditor("a[b]
"); let selection = editor.shared.selection.getEditableSelection(); expect(selection.startOffset).toBe(1); expect(selection.endOffset).toBe(2); // it happens sometimes in firefox that the selection is null patchWithCleanup(document, { getSelection: () => null, }); selection = editor.shared.selection.modifySelection("extend", "backward", "word"); // Selection could not be modified. expect(selection.startOffset).toBe(1); expect(selection.endOffset).toBe(2); }); test("setSelection should not set the selection outside the editable", async () => { const { editor, el } = await setupEditor("a[b]
"); editor.document.getSelection().setPosition(document.body); await tick(); const selection = editor.shared.selection.setSelection( editor.shared.selection.getEditableSelection() ); expect(el.contains(selection.anchorNode)).toBe(true); }); test("press 'ctrl+a' in 'oe_structure' child should only select his content", async () => { const { el } = await setupEditor(`a[]b
cd
[ab]
cd
a[]b
cd
[ab]
cd
te[]st
", { config: { Plugins: [...MAIN_PLUGINS, TestPlugin] }, }); await insertText(editor, "/test"); await press("enter"); await animationFrame(); expect(getActiveElement()).toBe(queryOne("input.test")); // Something trigger restore const cursors = editor.shared.selection.preserveSelection(); cursors.restore(); expect(getActiveElement()).toBe(queryOne("input.test")); }); test("set a collapse selection in a contenteditable false should move it after this node", async () => { const { el, editor } = await setupEditor(`abcdef
`); editor.shared.selection.setSelection({ anchorNode: queryOne("span[contenteditable='false']"), anchorOffset: 1, }); editor.shared.selection.focusEditable(); expect(getContent(el)).toBe(`abcd[]ef
`); }); test("preserveSelection's restore should always set the selection, even if it's the same as the current one", async () => { /** * There seems to be a bug in Chrome that renders the selection in a * different position than the one returned by document.getSelection(). * Setting the selection (even if it's the same as the current one) seems to * solve the issue. * * A concrete example: *abc some link def[]
* press shift + enter * The selection (in Chrome) is rendered at the following position if * setBaseAndExtent is skipped when setting the selection after a restore: *abc some link[] def
ab[]cd
"); patchWithCleanup(editor.document.getSelection(), { setBaseAndExtent: () => { expect.step("setBaseAndExtent"); }, }); const cursors = editor.shared.selection.preserveSelection(); cursors.restore(); expect.verifySteps(["setBaseAndExtent"]); });