import { describe, expect, test } from "@odoo/hoot"; import { setupEditor } from "../_helpers/editor"; import { cleanTextNode, fillEmpty, splitTextNode, wrapInlinesInBlocks, } from "@html_editor/utils/dom"; import { getContent } from "../_helpers/selection"; import { parseHTML } from "@html_editor/utils/html"; import { unformat } from "../_helpers/format"; describe("splitAroundUntil", () => { test("should split a slice of text from its inline ancestry (1)", async () => { const { editor, el } = await setupEditor("

abcdefg

"); const [p] = el.childNodes; const cde = p.childNodes[1].childNodes[1].firstChild; // We want to test with "cde" being three separate text nodes. splitTextNode(cde, 2); const cd = cde.previousSibling; splitTextNode(cd, 1); const d = cd; const result = editor.shared.split.splitAroundUntil(d, p.childNodes[1]); expect(result.tagName === "FONT").toBe(true); expect(p.outerHTML).toBe( "

abcdefg

" ); }); test("should split a slice of text from its inline ancestry (2)", async () => { const { editor, el } = await setupEditor("

abcdefghi

"); const [p] = el.childNodes; const cdefg = p.childNodes[1].childNodes[1].firstChild; // We want to test with "cdefg" being five separate text nodes. splitTextNode(cdefg, 4); const cdef = cdefg.previousSibling; splitTextNode(cdef, 3); const cde = cdef.previousSibling; splitTextNode(cde, 2); const cd = cde.previousSibling; splitTextNode(cd, 1); const d = cd; const result = editor.shared.split.splitAroundUntil( [d, d.nextSibling.nextSibling], p.childNodes[1] ); expect(result.tagName === "FONT").toBe(true); expect(p.outerHTML).toBe( "

abcdefghi

" ); }); test("should split from a textNode that has no siblings", async () => { const { editor, el } = await setupEditor("

abcdefg

"); const [p] = el.childNodes; const font = p.querySelector("font"); const cde = p.querySelector("span").firstChild; const result = editor.shared.split.splitAroundUntil(cde, font); expect(result.tagName === "FONT" && result !== font).toBe(true); expect(p.outerHTML).toBe( "

abcdefg

" ); }); test("should not do anything (nothing to split)", async () => { const { editor, el } = await setupEditor("

abcde

"); const [p] = el.childNodes; const bcd = p.querySelector("span").firstChild; const result = editor.shared.split.splitAroundUntil(bcd, p.childNodes[1]); expect(result === p.childNodes[1]).toBe(true); expect(p.outerHTML).toBe("

abcde

"); }); }); describe("cleanTextNode", () => { test("should remove ZWS before cursor and preserve it", async () => { const { editor, el } = await setupEditor("

\u200B[]text

"); const cursors = editor.shared.selection.preserveSelection(); cleanTextNode(el.querySelector("p").firstChild, "\u200B", cursors); cursors.restore(); expect(getContent(el)).toBe("

[]text

"); }); test("should remove ZWS before cursor and preserve it (2)", async () => { const { editor, el } = await setupEditor("

\u200Bt[]ext

"); const cursors = editor.shared.selection.preserveSelection(); cleanTextNode(el.querySelector("p").firstChild, "\u200B", cursors); cursors.restore(); expect(getContent(el)).toBe("

t[]ext

"); }); test("should remove ZWS after cursor and preserve it", async () => { const { editor, el } = await setupEditor("

text[]\u200B

"); const cursors = editor.shared.selection.preserveSelection(); cleanTextNode(el.querySelector("p").firstChild, "\u200B", cursors); cursors.restore(); expect(getContent(el)).toBe("

text[]

"); }); test("should remove ZWS after cursor and preserve it (2)", async () => { const { editor, el } = await setupEditor("

t[]ext\u200B

"); const cursors = editor.shared.selection.preserveSelection(); cleanTextNode(el.querySelector("p").firstChild, "\u200B", cursors); cursors.restore(); expect(getContent(el)).toBe("

t[]ext

"); }); test("should remove multiple ZWS preserving cursor", async () => { const { editor, el } = await setupEditor("

\u200Bt\u200Be[]\u200Bxt\u200B

"); const cursors = editor.shared.selection.preserveSelection(); cleanTextNode(el.querySelector("p").firstChild, "\u200B", cursors); cursors.restore(); expect(getContent(el)).toBe("

te[]xt

"); }); test("should remove multiple ZWNBSP preserving cursor", async () => { const { editor, el } = await setupEditor("

\uFEFFt\uFEFFe[]\uFEFFxt\uFEFF

"); const cursors = editor.shared.selection.preserveSelection(); cleanTextNode(el.querySelector("p").firstChild, "\uFEFF", cursors); cursors.restore(); expect(getContent(el)).toBe("

te[]xt

"); }); }); describe("wrapInlinesInBlocks", () => { test("should wrap text node in P", async () => { const div = document.createElement("div"); div.innerHTML = "text"; wrapInlinesInBlocks(div); expect(div.innerHTML).toBe("

text

"); }); test("should wrap inline element in P", async () => { const div = document.createElement("div"); div.innerHTML = "text"; wrapInlinesInBlocks(div); expect(div.innerHTML).toBe("

text

"); }); test("should not do anything to block element", async () => { const div = document.createElement("div"); div.innerHTML = "

text

"; wrapInlinesInBlocks(div); expect(div.innerHTML).toBe("

text

"); }); test("should wrap inlines in P", async () => { const div = document.createElement("div"); div.innerHTML = "textnodeinline

p

"; wrapInlinesInBlocks(div); expect(div.innerHTML).toBe("

textnodeinline

p

"); }); test("should wrap inlines in P (2)", async () => { const div = document.createElement("div"); div.innerHTML = "inline

p

textnode"; wrapInlinesInBlocks(div); expect(div.innerHTML).toBe("

inline

p

textnode

"); }); test("should turn a BR into a paragraph break", async () => { const div = document.createElement("div"); div.innerHTML = "abc
def"; wrapInlinesInBlocks(div); expect(div.innerHTML).toBe("

abc

def

"); }); test("should remove a BR that has no effect", async () => { const div = document.createElement("div"); div.innerHTML = "abc
def
"; wrapInlinesInBlocks(div); expect(div.innerHTML).toBe("

abc

def

"); }); test("empty lines should become empty paragraphs", async () => { const div = document.createElement("div"); div.innerHTML = "abc

def"; wrapInlinesInBlocks(div); expect(div.innerHTML).toBe("

abc


def

"); }); test("empty lines should become empty paragraphs (2)", async () => { const div = document.createElement("div"); div.innerHTML = "
"; wrapInlinesInBlocks(div); expect(div.innerHTML).toBe("


"); }); test("empty lines should become empty paragraphs (3)", async () => { const div = document.createElement("div"); div.innerHTML = "
abc"; wrapInlinesInBlocks(div); expect(div.innerHTML).toBe("


abc

"); }); test("mix: handle blocks, inlines and BRs", async () => { const div = document.createElement("div"); div.innerHTML = "a
b

c


d

e


"; wrapInlinesInBlocks(div); expect(div.innerHTML).toBe( "

a

b

c


d

e


" ); }); test("wrap block with display style inline in div", async () => { // Second part should be wrapped automatically during initElementForEdition const { el, editor } = await setupEditor( `

inline
` ); const div = el.querySelector("div"); editor.shared.selection.setSelection({ anchorNode: div, anchorOffset: 0 }); editor.shared.selection.focusEditable(); editor.shared.dom.insert( parseHTML( editor.document, `
inline
` ) ); // First part (inserted content) is wrapped manually wrapInlinesInBlocks(div); expect(getContent(el)).toBe( unformat(`
inline
[]
inline
`) ); }); test("wrap a mix of inline elements in div", async () => { // Second part should be wrapped automatically during initElementForEdition const { el, editor } = await setupEditor( `

text
inline
span` ); const div = el.querySelector("div"); editor.shared.selection.setSelection({ anchorNode: div, anchorOffset: 0 }); editor.shared.selection.focusEditable(); editor.shared.dom.insert( parseHTML( editor.document, `text
inline
span` ) ); // First part (inserted content) is wrapped manually wrapInlinesInBlocks(div); expect(getContent(el)).toBe( unformat(`
text
inline
span
[]
text
inline
span
`) ); }); test("wrap a mix of inline elements in div with br", async () => { // Second part should be wrapped automatically during initElementForEdition const { el, editor } = await setupEditor( `
[]
text
inline

span` ); const div = el.querySelector("div"); editor.shared.selection.setSelection({ anchorNode: div, anchorOffset: 0 }); editor.shared.dom.insert( parseHTML( editor.document, `text
inline

span` ) ); // First part (inserted content) is wrapped manually wrapInlinesInBlocks(div); expect(getContent(el)).toBe( unformat(`

text

inline

span

[]

text

inline

span

`) ); }); }); describe("fillEmpty", () => { test("should not add fill a shrunk protected block, nor add a ZWS to it", async () => { const { el } = await setupEditor('
'); expect(el.innerHTML).toBe('
'); const div = el.firstChild; fillEmpty(div); expect(el.innerHTML).toBe('
'); }); });