import { DIRECTIONS } from "@html_editor/utils/position"; import { ensureFocus, getCursorDirection } from "@html_editor/utils/selection"; import { describe, expect, test } from "@odoo/hoot"; import { dispatch } from "@odoo/hoot-dom"; import { insertText, setupEditor, testEditor } from "../_helpers/editor"; import { unformat } from "../_helpers/format"; function getProcessSelection(selection) { const { anchorNode, anchorOffset, focusNode, focusOffset } = selection; return [anchorNode, anchorOffset, focusNode, focusOffset]; } describe("getTraversedNodes", () => { test("should return the anchor node of a collapsed selection", async () => { const { editor } = await setupEditor("
a[]bc
a[bc
a[bc
ab[]cd
"); const abcd = editable.firstChild.firstChild; const result = editor.shared.selection.getTraversedNodes(); expect(result).toEqual([abcd]); }); test("should find that a the range traverses the next paragraph as well", async () => { const { el: editable, editor } = await setupEditor("ab[cd
ef]gh
"); const p1 = editable.firstChild; const abcd = p1.firstChild; const p2 = editable.childNodes[1]; const efgh = p2.firstChild; const result = editor.shared.selection.getTraversedNodes(); expect(result).toEqual([p1, abcd, p2, efgh]); }); test("should find all traversed nodes in nested range", async () => { const { el: editable, editor } = await setupEditor( 'ab[cd
ef]gh
ab
cd
ab
]
cd
ab
cd]
ef
ab
cd
]ef
ab
cd
]
ab
cd
]
ef
ab[
cd
ef
]", stepFunction: (editor) => { const editable = editor.editable; const p1 = editable.firstChild; const ab = p1.firstChild; const br = ab.nextSibling; const cd = br.nextSibling; const p2 = editable.lastChild; const ef = p2.firstChild; const result = editor.shared.selection.getTraversedNodes(editable); expect(result).toEqual([p1, br, cd, p2, ef]); }, }); }); test("selection starts before a br in start of p element", async () => { await testEditor({ contentBefore: "[ab
cd
ef
]", stepFunction: (editor) => { const editable = editor.editable; const p1 = editable.firstChild; const ab = p1.firstChild; const br = ab.nextSibling; const cd = br.nextSibling; const p2 = editable.lastChild; const ef = p2.firstChild; const result = editor.shared.selection.getTraversedNodes(editable); expect(result).toEqual([p1, ab, br, cd, p2, ef]); }, }); }); test("selection starts after a br at end of p element", async () => { await testEditor({ contentBefore: "ab
[
cd
]", stepFunction: (editor) => { const editable = editor.editable; const p2 = editable.lastChild; const cd = p2.firstChild; const result = editor.shared.selection.getTraversedNodes(editable); expect(result).toEqual([p2, cd]); }, }); }); test("selection starts after a br in middle of p element", async () => { await testEditor({ contentBefore: "ab
[cd
ef
]", stepFunction: (editor) => { const editable = editor.editable; const p1 = editable.firstChild; const ab = p1.firstChild; const br = ab.nextSibling; const cd = br.nextSibling; const p2 = editable.lastChild; const ef = p2.firstChild; const result = editor.shared.selection.getTraversedNodes(editable); expect(result).toEqual([p1, cd, p2, ef]); }, }); }); test("selection starts between 2 br elements", async () => { await testEditor({ contentBefore: "ab
[
cd
ef
]", stepFunction: (editor) => { const editable = editor.editable; const p1 = editable.firstChild; const ab = p1.firstChild; const br1 = ab.nextSibling; const br2 = br1.nextSibling; const cd = br2.nextSibling; const p2 = editable.firstChild.nextSibling; const ef = p2.firstChild; const result = editor.shared.selection.getTraversedNodes(editable); expect(result).toEqual([p1, br2, cd, p2, ef]); }, }); }); test("selection within table cells 1", async () => { await testEditor({ contentBefore: "abcd[e | f]g |
abcd [ e | f]g |
[]
[]
focusWasConserved
[]
[]
[]
abc
"); const p = el.firstChild; const result = getProcessSelection( editor.shared.selection.setSelection({ anchorNode: p.firstChild, anchorOffset: 0, }) ); editor.shared.selection.focusEditable(); expect(result).toEqual([p.firstChild, 0, p.firstChild, 0]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([ p.firstChild, 0, p.firstChild, 0, ]); }); test("should collapse the cursor within an element", async () => { const { editor, el } = await setupEditor("abcd
"); const p = el.firstChild; const result = getProcessSelection( editor.shared.selection.setSelection({ anchorNode: p.firstChild, anchorOffset: 2, }) ); editor.shared.selection.focusEditable(); expect(result).toEqual([p.firstChild, 2, p.firstChild, 2]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([ p.firstChild, 2, p.firstChild, 2, ]); }); test("should collapse the cursor at the end of an element", async () => { const { editor, el } = await setupEditor("abc
"); const p = el.firstChild; const result = getProcessSelection( editor.shared.selection.setSelection({ anchorNode: p.firstChild, anchorOffset: 3, }) ); editor.shared.selection.focusEditable(); expect(result).toEqual([p.firstChild, 3, p.firstChild, 3]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([ p.firstChild, 3, p.firstChild, 3, ]); }); test("should collapse the cursor before a nested inline element", async () => { const { editor, el } = await setupEditor("abcdefghij
"); const p = el.firstChild; const cd = p.childNodes[1].firstChild; const result = getProcessSelection( editor.shared.selection.setSelection({ anchorNode: cd, anchorOffset: 2, }) ); editor.shared.selection.focusEditable(); expect(result).toEqual([cd, 2, cd, 2]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([cd, 2, cd, 2]); }); test("should collapse the cursor at the beginning of a nested inline element", async () => { const { editor, el } = await setupEditor("abcdefghij
"); const p = el.firstChild; const ef = p.childNodes[1].childNodes[1].firstChild; const result = getProcessSelection( editor.shared.selection.setSelection({ anchorNode: ef, anchorOffset: 0, }) ); editor.shared.selection.focusEditable(); expect(result).toEqual([ef, 0, ef, 0]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([ef, 0, ef, 0]); }); test("should collapse the cursor within a nested inline element", async () => { const { editor, el } = await setupEditor("abcdefghijkl
"); const p = el.firstChild; const efgh = p.childNodes[1].childNodes[1].firstChild; const result = getProcessSelection( editor.shared.selection.setSelection({ anchorNode: efgh, anchorOffset: 2, }) ); editor.shared.selection.focusEditable(); expect(result).toEqual([efgh, 2, efgh, 2]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([efgh, 2, efgh, 2]); }); test("should collapse the cursor at the end of a nested inline element", async () => { const { editor, el } = await setupEditor("abcdefghij
"); const p = el.firstChild; const ef = p.childNodes[1].childNodes[1].firstChild; const result = getProcessSelection( editor.shared.selection.setSelection({ anchorNode: ef, anchorOffset: 2, }) ); editor.shared.selection.focusEditable(); expect(result).toEqual([ef, 2, ef, 2]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([ef, 2, ef, 2]); }); test("should collapse the cursor after a nested inline element", async () => { const { editor, el } = await setupEditor("abcdefghij
"); const p = el.firstChild; const ef = p.childNodes[1].childNodes[1].firstChild; const gh = p.childNodes[1].lastChild; const result = getProcessSelection( editor.shared.selection.setSelection({ anchorNode: gh, anchorOffset: 0, }) ); editor.shared.selection.focusEditable(); expect(result).toEqual([ef, 2, ef, 2]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([ef, 2, ef, 2]); const nonNormalizedResult = getProcessSelection( editor.shared.selection.setSelection( { anchorNode: gh, anchorOffset: 0 }, { normalize: false } ) ); editor.shared.selection.focusEditable(); expect(nonNormalizedResult).toEqual([gh, 0, gh, 0]); const sel = document.getSelection(); expect([sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset]).toEqual([ gh, 0, gh, 0, ]); }); }); describe("forward", () => { test("should select the contents of an element", async () => { const { editor, el } = await setupEditor("abc
"); const p = el.firstChild; const result = getProcessSelection( editor.shared.selection.setSelection({ anchorNode: p.firstChild, anchorOffset: 0, focusNode: p.firstChild, focusOffset: 3, }) ); editor.shared.selection.focusEditable(); expect(result).toEqual([p.firstChild, 0, p.firstChild, 3]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([ p.firstChild, 0, p.firstChild, 3, ]); }); test("should make a complex selection", async () => { const { el, editor } = await setupEditor( "abcdefghij
klmnopqrst
" ); const [p1, p2] = el.childNodes; const ef = p1.childNodes[1].childNodes[1].firstChild; const qr = p2.childNodes[1].childNodes[2]; const st = p2.childNodes[2]; const result = getProcessSelection( editor.shared.selection.setSelection({ anchorNode: ef, anchorOffset: 1, focusNode: st, focusOffset: 0, }) ); editor.shared.selection.focusEditable(); expect(result).toEqual([ef, 1, qr, 2]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([ef, 1, qr, 2]); const nonNormalizedResult = getProcessSelection( editor.shared.selection.setSelection( { anchorNode: ef, anchorOffset: 1, focusNode: st, focusOffset: 0, }, { normalize: false } ) ); expect(nonNormalizedResult).toEqual([ef, 1, st, 0]); const sel = document.getSelection(); expect([sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset]).toEqual([ ef, 1, st, 0, ]); }); }); describe("backward", () => { test("should select the contents of an element", async () => { const { editor, el } = await setupEditor("abc
"); const p = el.firstChild; const result = getProcessSelection( editor.shared.selection.setSelection({ anchorNode: p.firstChild, anchorOffset: 3, focusNode: p.firstChild, focusOffset: 0, }) ); editor.shared.selection.focusEditable(); expect(result).toEqual([p.firstChild, 3, p.firstChild, 0]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([ p.firstChild, 3, p.firstChild, 0, ]); }); test("should make a complex selection", async () => { const { el, editor } = await setupEditor( "abcdefghij
klmnopqrst
" ); const [p1, p2] = el.childNodes; const ef = p1.childNodes[1].childNodes[1].firstChild; const qr = p2.childNodes[1].childNodes[2]; const st = p2.childNodes[2]; const result = getProcessSelection( editor.shared.selection.setSelection({ anchorNode: st, anchorOffset: 0, focusNode: ef, focusOffset: 1, }) ); editor.shared.selection.focusEditable(); expect(result).toEqual([qr, 2, ef, 1]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([qr, 2, ef, 1]); const nonNormalizedResult = getProcessSelection( editor.shared.selection.setSelection( { anchorNode: st, anchorOffset: 0, focusNode: ef, focusOffset: 1, }, { normalize: false } ) ); expect(nonNormalizedResult).toEqual([st, 0, ef, 1]); const sel = document.getSelection(); expect([sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset]).toEqual([ st, 0, ef, 1, ]); }); }); }); describe("setCursorStart", () => { test("should collapse the cursor at the beginning of an element", async () => { const { editor, el } = await setupEditor("abc
"); const p = el.firstChild; const result = getProcessSelection(editor.shared.selection.setCursorStart(p)); editor.shared.selection.focusEditable(); expect(result).toEqual([p.firstChild, 0, p.firstChild, 0]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([ p.firstChild, 0, p.firstChild, 0, ]); }); test("should collapse the cursor at the beginning of a nested inline element", async () => { const { editor, el } = await setupEditor("abcdefghij
"); const p = el.firstChild; const b = p.childNodes[1].childNodes[1]; const ef = b.firstChild; const result = getProcessSelection(editor.shared.selection.setCursorStart(b)); editor.shared.selection.focusEditable(); expect(result).toEqual([ef, 0, ef, 0]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([ef, 0, ef, 0]); }); test("should collapse the cursor after a nested inline element", async () => { const { editor, el } = await setupEditor("abcdefghij
"); const p = el.firstChild; const ef = p.childNodes[1].childNodes[1].firstChild; const gh = p.childNodes[1].lastChild; const result = getProcessSelection(editor.shared.selection.setCursorStart(gh)); editor.shared.selection.focusEditable(); expect(result).toEqual([ef, 2, ef, 2]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([ef, 2, ef, 2]); // @todo @phoenix normalize false is never use // const nonNormalizedResult = getProcessSelection(editor.shared.selection.setCursorStart(gh, false)); // expect(nonNormalizedResult).toEqual([gh, 0, gh, 0]); // const sel = document.getSelection(); // expect([sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset]).toEqual([ // gh, // 0, // gh, // 0, // ]); }); }); describe("setCursorEnd", () => { test("should collapse the cursor at the end of an element", async () => { const { editor, el } = await setupEditor("abc
"); const p = el.firstChild; const result = getProcessSelection(editor.shared.selection.setCursorEnd(p)); editor.shared.selection.focusEditable(); expect(result).toEqual([p.firstChild, 3, p.firstChild, 3]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([ p.firstChild, 3, p.firstChild, 3, ]); }); test("should collapse the cursor before a nested inline element", async () => { const { editor, el } = await setupEditor("abcdefghij
"); const p = el.firstChild; const cd = p.childNodes[1].firstChild; const result = getProcessSelection(editor.shared.selection.setCursorEnd(cd)); editor.shared.selection.focusEditable(); expect(result).toEqual([cd, 2, cd, 2]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([cd, 2, cd, 2]); }); test("should collapse the cursor at the end of a nested inline element", async () => { const { editor, el } = await setupEditor("abcdefghij
"); const p = el.firstChild; const b = p.childNodes[1].childNodes[1]; const ef = b.firstChild; const result = getProcessSelection(editor.shared.selection.setCursorEnd(b)); editor.shared.selection.focusEditable(); expect(result).toEqual([ef, 2, ef, 2]); const { anchorNode, anchorOffset, focusNode, focusOffset } = document.getSelection(); expect([anchorNode, anchorOffset, focusNode, focusOffset]).toEqual([ef, 2, ef, 2]); }); }); describe("getCursorDirection", () => { test("should identify a forward selection", async () => { await testEditor({ contentBefore: "a[bc]d
", stepFunction: (editor) => { const { anchorNode, anchorOffset, focusNode, focusOffset } = editor.document.getSelection(); expect(getCursorDirection(anchorNode, anchorOffset, focusNode, focusOffset)).toBe( DIRECTIONS.RIGHT ); }, }); }); test("should identify a backward selection", async () => { await testEditor({ contentBefore: "a]bc[d
", stepFunction: (editor) => { const { anchorNode, anchorOffset, focusNode, focusOffset } = editor.document.getSelection(); expect(getCursorDirection(anchorNode, anchorOffset, focusNode, focusOffset)).toBe( DIRECTIONS.LEFT ); }, }); }); test("should identify a collapsed selection", async () => { await testEditor({ contentBefore: "ab[]cd
", stepFunction: (editor) => { const { anchorNode, anchorOffset, focusNode, focusOffset } = editor.document.getSelection(); expect(getCursorDirection(anchorNode, anchorOffset, focusNode, focusOffset)).toBe( false ); }, }); }); }); describe("getSelectedNodes", () => { test("should return nothing if the range is collapsed", async () => { await testEditor({ contentBefore: "ab[]cd
", stepFunction: (editor) => { const result = editor.shared.selection.getSelectedNodes(); expect(result).toEqual([]); }, contentAfter: "ab[]cd
", }); }); test("should find that no node is fully selected", async () => { await testEditor({ contentBefore: "ab[c]d
", stepFunction: (editor) => { const result = editor.shared.selection.getSelectedNodes(); expect(result).toEqual([]); }, }); }); test("should find that no node is fully selected, across blocks", async () => { await testEditor({ contentBefore: "ab[cd
ef]gh
", stepFunction: (editor) => { const result = editor.shared.selection.getSelectedNodes(); expect(result).toEqual([]); }, }); }); test("should find that a text node is fully selected", async () => { await testEditor({ contentBefore: 'ab[cd]
', stepFunction: (editor) => { const editable = editor.editable; const result = editor.shared.selection.getSelectedNodes(); const cd = editable.firstChild.lastChild; expect(result).toEqual([cd]); }, }); }); test("should find that a block is fully selected", async () => { await testEditor({ contentBefore: "[ab
cd
ef]gh
", stepFunction: (editor) => { const editable = editor.editable; const result = editor.shared.selection.getSelectedNodes(); const ab = editable.firstChild.firstChild; const p2 = editable.childNodes[1]; const cd = p2.firstChild; expect(result).toEqual([ab, p2, cd]); }, }); }); test("should find all selected nodes in nested range", async () => { await testEditor({ contentBefore: 'ab[cd
ef]gh