import { parseHTML } from "@html_editor/utils/html"; import { describe, expect, test } from "@odoo/hoot"; import { tick } from "@odoo/hoot-mock"; import { setupEditor, testEditor } from "../_helpers/editor"; import { unformat } from "../_helpers/format"; import { getContent } from "../_helpers/selection"; import { dispatchClean } from "../_helpers/dispatch"; function span(text) { const span = document.createElement("span"); span.innerText = text; span.classList.add("a"); return span; } describe("collapsed selection", () => { test("should insert html in an empty paragraph / empty editable", async () => { await testEditor({ contentBefore: "

[]

", stepFunction: async (editor) => { editor.shared.dom.insert( parseHTML(editor.document, '') ); editor.shared.history.addStep(); }, contentAfterEdit: '

\u200b[]

', contentAfter: '

[]

', }); }); test("should insert html after an empty paragraph", async () => { await testEditor({ // This scenario is only possible with the allowInlineAtRoot option. contentBefore: "


[]", stepFunction: async (editor) => { editor.shared.dom.insert( parseHTML(editor.document, '') ); editor.shared.history.addStep(); }, contentAfterEdit: '


\u200b[]', contentAfter: '


[]', config: { allowInlineAtRoot: true }, }); }); test("should insert html between two letters", async () => { await testEditor({ contentBefore: "

a[]b

", stepFunction: async (editor) => { editor.shared.dom.insert( parseHTML(editor.document, '') ); editor.shared.history.addStep(); }, contentAfterEdit: '

a\u200b[]b

', contentAfter: '

a[]b

', }); }); test("should insert html in between naked text in the editable", async () => { await testEditor({ contentBefore: "

a[]b

", stepFunction: async (editor) => { editor.shared.dom.insert( parseHTML(editor.document, '') ); editor.shared.history.addStep(); }, contentAfterEdit: '

a\u200b[]b

', contentAfter: '

a[]b

', }); }); test("should insert several html nodes in between naked text in the editable", async () => { await testEditor({ contentBefore: "

a[]e

", stepFunction: async (editor) => { editor.shared.dom.insert(parseHTML(editor.document, "

b

c

d

")); editor.shared.history.addStep(); }, contentAfter: "

ab

c

d[]e

", }); }); test("should keep a paragraph after a div block", async () => { await testEditor({ contentBefore: "

[]

", stepFunction: async (editor) => { editor.shared.dom.insert(parseHTML(editor.document, "

content

")); editor.shared.history.addStep(); }, contentAfter: "

content

[]

", }); }); test("should not split a pre to insert another pre but just insert the text", async () => { await testEditor({ contentBefore: "
abc[]
ghi
", stepFunction: async (editor) => { editor.shared.dom.insert(parseHTML(editor.document, "
def
")); editor.shared.history.addStep(); }, contentAfter: "
abcdef[]
ghi
", }); }); test('should keep an "empty" block which contains fontawesome nodes when inserting multiple nodes', async () => { await testEditor({ contentBefore: "

content[]

", stepFunction: async (editor) => { editor.shared.dom.insert( parseHTML( editor.document, '

unwrapped

culprit

after

' ) ); editor.shared.history.addStep(); }, contentAfter: '

contentunwrapped

culprit

after[]

', }); }); test("should not unwrap single node if the selection anchorNode is the editable", async () => { await testEditor({ contentBefore: "

content

", stepFunction: async (editor) => { editor.shared.selection.setCursorEnd(editor.editable, false); editor.shared.selection.focusEditable(); editor.shared.dom.insert(parseHTML(editor.document, "

def

")); editor.shared.history.addStep(); }, contentAfter: "

content

def[]

", }); }); test("should not unwrap nodes if the selection anchorNode is the editable", async () => { await testEditor({ contentBefore: "

content

", stepFunction: async (editor) => { editor.shared.selection.setCursorEnd(editor.editable, false); editor.shared.selection.focusEditable(); await tick(); editor.shared.dom.insert(parseHTML(editor.document, "
abc

def

")); editor.shared.history.addStep(); }, contentAfter: "

content

abc

def[]

", }); }); test('should insert an "empty" block', async () => { await testEditor({ contentBefore: "

abcd[]

", stepFunction: async (editor) => { editor.shared.dom.insert(parseHTML(editor.document, "

efgh

")); editor.shared.history.addStep(); }, contentAfter: "

abcdefgh

[]

", }); }); test("never unwrap tables in breakable paragrap", async () => { // P elements' content can only be "phrasing" content // Adding a table within p is not possible // We have split the p and insert the table unwrapped in between // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p // https://developer.mozilla.org/en-US/docs/Web/HTML/Content_categories#phrasing_content const { editor } = await setupEditor(`

cont[]ent

`, {}); editor.shared.dom.insert( parseHTML(editor.document, "
") ); expect(getContent(editor.editable)).toBe( `

cont

[]ent

` ); }); test("should not unwrap table in unbreakable paragraph find a suitable spot to insert table element", async () => { // P elements' content can only be "phrasing" content // Adding a table within an unbreakable p is not possible // We have to find a better spot to insert the table // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p // https://developer.mozilla.org/en-US/docs/Web/HTML/Content_categories#phrasing_content const { editor } = await setupEditor(`

cont[]ent

`, {}); editor.shared.dom.insert( parseHTML(editor.document, "
") ); expect(getContent(editor.editable)).toBe( `

content[]

` ); }); test("stops at boundary when inserting unfit content", async () => { // P elements' content can only be "phrasing" content // This test forces to stop at the

// This test is a bit odd and whitebox but this is because multiple // parameters of the use case are interacting const { editor } = await setupEditor( `

cont[]ent

`, {} ); editor.shared.dom.insert( parseHTML(editor.document, "
") ); expect(getContent(editor.editable)).toBe( `

content[]

` ); }); test("insert inline in empty paragraph", async () => { const { el, editor } = await setupEditor(`

[]

`); editor.shared.dom.insert(parseHTML(editor.document, `a`)); editor.shared.history.addStep(); expect(getContent(el)).toBe(`

a[]

`); }); test("insert inline at the end of a paragraph", async () => { const { el, editor } = await setupEditor(`

b[]

`); editor.shared.dom.insert(parseHTML(editor.document, `a`)); editor.shared.history.addStep(); expect(getContent(el)).toBe(`

ba[]

`); }); test("insert inline at the start of a paragraph", async () => { const { el, editor } = await setupEditor(`

[]b

`); editor.shared.dom.insert(parseHTML(editor.document, `a`)); editor.shared.history.addStep(); expect(getContent(el)).toBe(`

a[]b

`); }); test("insert inline at the middle of a paragraph", async () => { const { el, editor } = await setupEditor(`

b[]c

`); editor.shared.dom.insert(parseHTML(editor.document, `a`)); editor.shared.history.addStep(); expect(getContent(el)).toBe(`

ba[]c

`); }); test("insert block in empty paragraph", async () => { const { el, editor } = await setupEditor(`

[]

`); editor.shared.dom.insert(parseHTML(editor.document, `
a
`)); editor.shared.history.addStep(); dispatchClean(editor); expect(getContent(el)).toBe(`
a

[]

`); }); test("insert block at the end of a paragraph", async () => { const { el, editor } = await setupEditor(`

b[]

`); editor.shared.dom.insert(parseHTML(editor.document, `
a
`)); editor.shared.history.addStep(); dispatchClean(editor); expect(getContent(el)).toBe(`

b

a

[]

`); }); test("insert block at the start of a paragraph", async () => { const { el, editor } = await setupEditor(`

[]b

`); editor.shared.dom.insert(parseHTML(editor.document, `
a
`)); editor.shared.history.addStep(); expect(getContent(el)).toBe(`
a

[]b

`); }); test("insert block at the middle of a paragraph", async () => { const { el, editor } = await setupEditor(`

b[]c

`); editor.shared.dom.insert(parseHTML(editor.document, `
a
`)); editor.shared.history.addStep(); expect(getContent(el)).toBe(`

b

a

[]c

`); }); }); describe("not collapsed selection", () => { test("should delete selection and insert html in its place", async () => { await testEditor({ contentBefore: "

[a]

", stepFunction: async (editor) => { editor.shared.dom.insert( parseHTML(editor.document, '') ); }, contentAfterEdit: '

\u200b[]

', contentAfter: '

[]

', }); }); test("should delete selection and insert html in its place (2)", async () => { await testEditor({ contentBefore: "

a[b]c

", stepFunction: async (editor) => { editor.shared.dom.insert( parseHTML(editor.document, '') ); editor.shared.history.addStep(); }, contentAfterEdit: '

a\u200b[]c

', contentAfter: '

a[]c

', }); }); test("should remove a fully selected table then insert a span before it", async () => { await testEditor({ contentBefore: unformat( `

a[b

cdef
ghij

k]l

` ), stepFunction: (editor) => { editor.shared.dom.insert(span("TEST")); editor.shared.history.addStep(); }, contentAfter: '

aTEST[]l

', }); }); test("should only remove the text content of cells in a partly selected table", async () => { await testEditor({ contentBefore: unformat( `
cde[fgh
ijk]lmn
opqrst
` ), stepFunction: (editor) => { editor.shared.dom.insert(span("TEST")); editor.shared.history.addStep(); }, contentAfter: unformat( `
cdTEST[]gh
ij
mn
opqrst
` ), }); }); test("should remove some text and a table (even if the table is partly selected)", async () => { await testEditor({ contentBefore: unformat( `

a[b

cdef
g]hij

kl

` ), stepFunction: (editor) => { editor.shared.dom.insert(span("TEST")); editor.shared.history.addStep(); }, contentAfter: unformat( `

aTEST[]

kl

` ), }); }); test("should remove a table and some text (even if the table is partly selected)", async () => { await testEditor({ contentBefore: unformat( `

ab

cdef
ghi[j

k]l

` ), stepFunction: (editor) => { editor.shared.dom.insert(span("TEST")); editor.shared.history.addStep(); }, contentAfter: unformat( `

ab

TEST[]l

` ), }); }); test("should remove some text, a table and some more text", async () => { await testEditor({ contentBefore: unformat( `

a[b

cdef
ghij

k]l

` ), stepFunction: (editor) => { editor.shared.dom.insert(span("TEST")); editor.shared.history.addStep(); }, contentAfter: `

aTEST[]l

`, }); }); test("should remove a selection of several tables", async () => { await testEditor({ contentBefore: unformat( `
cde[f
ghij
cdef
ghij
cde]f
ghij
` ), stepFunction: (editor) => { editor.shared.dom.insert(span("TEST")); editor.shared.history.addStep(); }, contentAfter: `

TEST[]

`, }); }); test("should remove a selection including several tables", async () => { await testEditor({ contentBefore: unformat( `

0[1

cdef
ghij

23

cdef
ghij

45

cdef
ghij

67]

` ), stepFunction: (editor) => { editor.shared.dom.insert(span("TEST")); editor.shared.history.addStep(); }, contentAfter: `

0TEST[]

`, }); }); test("should remove everything, including several tables", async () => { await testEditor({ contentBefore: unformat( `

[01

cdef
ghij

23

cdef
ghij

45

cdef
ghij

67]

` ), stepFunction: (editor) => { editor.shared.dom.insert(span("TEST")); editor.shared.history.addStep(); }, contentAfter: `

TEST[]

`, }); }); test("should insert html containing ZWNBSP", async () => { await testEditor({ contentBefore: "

[]

", stepFunction: async (editor) => { editor.shared.dom.insert( parseHTML( editor.document, '

\uFEFF\uFEFFlink\uFEFF\uFEFF

\uFEFF\uFEFFlink\uFEFF\uFEFF

' ) ); editor.shared.history.addStep(); }, contentAfter: '

link

link[]

', }); }); });