import { CLIPBOARD_WHITELISTS } from "@html_editor/core/clipboard_plugin"; import { beforeEach, describe, expect, test } from "@odoo/hoot"; import { manuallyDispatchProgrammaticEvent as dispatch, press, waitFor } from "@odoo/hoot-dom"; import { animationFrame, tick } from "@odoo/hoot-mock"; import { onRpc, patchWithCleanup } from "@web/../tests/web_test_helpers"; import { setupEditor, testEditor } from "./_helpers/editor"; import { cleanLinkArtifacts, unformat } from "./_helpers/format"; import { getContent, setSelection } from "./_helpers/selection"; import { pasteHtml, pasteOdooEditorHtml, pasteText, undo } from "./_helpers/user_actions"; function isInline(node) { return ["I", "B", "U", "S", "EM", "STRONG", "IMG", "BR", "A", "FONT"].includes(node); } function toIgnore(node) { return ["TABLE", "THEAD", "TH", "TBODY", "TR", "TD", "IMG", "BR", "LI", ".fa"].includes(node); } describe("Html Paste cleaning - whitelist", () => { test("should keep whitelisted Tags tag", async () => { for (const node of CLIPBOARD_WHITELISTS.nodes) { if (!toIgnore(node)) { const html = isInline(node) ? `a<${node.toLowerCase()}>b${node.toLowerCase()}>c` : `a
<${node.toLowerCase()}>b${node.toLowerCase()}>c`; await testEditor({ contentBefore: "
123[]4
", stepFunction: async (editor) => { pasteHtml(editor, `a<${node.toLowerCase()}>b${node.toLowerCase()}>c`); }, contentAfter: "123" + html + "[]4
", }); } } }); test("should keep whitelisted Tags tag (2)", async () => { await testEditor({ contentBefore: "123[]
", stepFunction: async (editor) => { pasteHtml(editor, 'a123ad[]
123[]
", stepFunction: async (editor) => { pasteHtml( editor, "ah |
---|
b |
123a
h |
---|
b |
d[]
', }); }); test("should not keep span", async () => { await testEditor({ contentBefore: "123[]
", stepFunction: async (editor) => { pasteHtml(editor, "abcd"); }, contentAfter: "123abcd[]
", }); }); test("should not keep orphan LI", async () => { await testEditor({ contentBefore: "123[]
", stepFunction: async (editor) => { pasteHtml(editor, "a123a
bc
d[]
", }); }); test("should keep LI in UL", async () => { await testEditor({ contentBefore: "123[]
", stepFunction: async (editor) => { pasteHtml(editor, "a123a
d[]
", }); }); test("should keep P and B and not span", async () => { await testEditor({ contentBefore: "123[]xx
", stepFunction: async (editor) => { pasteHtml(editor, "abc
defgh"); }, contentAfter: "123a
bc
defgh[]xx
", }); }); test("should keep styled span", async () => { await testEditor({ contentBefore: "123[]
", stepFunction: async (editor) => { pasteHtml(editor, 'abcd'); }, contentAfter: "123abcd[]
", }); }); test("should remove unwanted styles and b tag when pasting from paragraph from gdocs", async () => { await testEditor({ contentBefore: "[]
test1
test2
` ); }, contentAfter: "test1
test2[]
", }); }); test("should remove b, keep p, and remove unwanted styles when pasting list from gdocs", async () => { await testEditor({ contentBefore: "[]
Test
test2
Test
test2[]
[]
[]abcd
", stepFunction: async (editor) => { pasteHtml(editor, "x"); }, contentAfter: "x[]abcd
", }); }); test("should paste a text in a p", async () => { await testEditor({ contentBefore: "ab[]cd
", stepFunction: async (editor) => { pasteText(editor, "x"); }, contentAfter: "abx[]cd
", }); await testEditor({ contentBefore: "ab[]cd
", stepFunction: async (editor) => { pasteText(editor, "xyz 123"); }, contentAfter: "abxyz 123[]cd
", }); await testEditor({ contentBefore: "ab[]cd
", stepFunction: async (editor) => { pasteText(editor, "x y"); }, contentAfter: "abx y[]cd
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[]cd
', stepFunction: async (editor) => { pasteText(editor, "x"); }, contentAfter: 'abx[]cd
', }); }); // TODO: We might want to have it consider \n as paragraph breaks // instead of linebreaks but that would be an opinionated choice. test("should paste text and understand \\n newlines", async () => { await testEditor({ // @phoenix content adapted to make it valid html contentBefore: "[]
a
' + 'b
' + 'c
' + "d[]
", }); }); test("should paste text and understand \\r\\n newlines", async () => { await testEditor({ // @phoenix content adapted to make it valid html contentBefore: "[]
a
' + 'b
' + 'c
' + "d[]
", }); }); test("should paste text and understand \\n newlines within UNBREAKABLE node", async () => { await testEditor({ contentBefore: "[]", stepFunction: async (editor) => { pasteText(editor, "a\nb\nc"); }, contentAfter: "
a", }); }); }); describe("range not collapsed", () => { test("should paste a text in a p", async () => { await testEditor({ contentBefore: "
b
c[]
a[bc]d
", stepFunction: async (editor) => { pasteText(editor, "x"); }, contentAfter: "ax[]d
", }); await testEditor({ contentBefore: "a[bc]d
", stepFunction: async (editor) => { pasteText(editor, "xyz 123"); }, contentAfter: "axyz 123[]d
", }); await testEditor({ contentBefore: "a[bc]d
", stepFunction: async (editor) => { pasteText(editor, "x y"); }, contentAfter: "ax y[]d
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[cd]ef
', stepFunction: async (editor) => { pasteText(editor, "x"); }, contentAfter: 'abx[]ef
', }); }); test("should paste a text when selection across two span", async () => { await testEditor({ contentBefore: 'ab[cd]ef
', stepFunction: async (editor) => { pasteText(editor, "x"); }, contentAfter: 'abx[]ef
', }); await testEditor({ contentBefore: 'ab[c- -d]ef
', stepFunction: async (editor) => { pasteText(editor, "y"); }, contentAfter: 'aby[]ef
', }); }); test("should paste a text when selection across two p (1)", async () => { await testEditor({ contentBefore: "b[c
d]e
fbx[]e
fb[c
- -d]e
fby[]e
fb[c
d]efbx[]ef
d]e
f[]abcd
", stepFunction: async (editor) => { pasteHtml(editor, simpleHtmlCharX); }, contentAfter: "x[]abcd
", }); }); test("should paste a text in a p", async () => { await testEditor({ contentBefore: "ab[]cd
", stepFunction: async (editor) => { pasteHtml(editor, simpleHtmlCharX); }, contentAfter: "abx[]cd
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[]cd
', stepFunction: async (editor) => { pasteHtml(editor, simpleHtmlCharX); }, contentAfter: 'abx[]cd
', }); }); }); describe("range not collapsed", () => { test("should paste a text in a p", async () => { await testEditor({ contentBefore: "a[bc]d
", stepFunction: async (editor) => { pasteHtml(editor, simpleHtmlCharX); }, contentAfter: "ax[]d
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[cd]ef
', stepFunction: async (editor) => { pasteHtml(editor, simpleHtmlCharX); }, contentAfter: 'abx[]ef
', }); }); test("should paste a text when selection across two span", async () => { await testEditor({ contentBefore: 'ab[cd]ef
', stepFunction: async (editor) => { pasteHtml(editor, simpleHtmlCharX); }, contentAfter: 'abx[]ef
', }); await testEditor({ contentBefore: 'ab[c- -d]ef
', stepFunction: async (editor) => { pasteHtml(editor, simpleHtmlCharX); }, contentAfter: 'abx[]ef
', }); }); test("should paste a text when selection across two p (2)", async () => { await testEditor({ contentBefore: "b[c
d]e
fbx[]e
fb[c
- -d]e
fbx[]e
fb[c
d]efbx[]ef
d]e
fb[c
d]e
fbx[]e
fx
"; describe("range collapsed", () => { test("should paste a text at the beginning of a p", async () => { await testEditor({ contentBefore: "[]abcd
", stepFunction: async (editor) => { pasteHtml(editor, simpleHtmlCharX); }, contentAfter: "x[]abcd
", }); }); test("should paste a text in a p", async () => { await testEditor({ contentBefore: "ab[]cd
", stepFunction: async (editor) => { pasteHtml(editor, simpleHtmlCharX); }, contentAfter: "abx[]cd
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[]cd
', stepFunction: async (editor) => { pasteHtml(editor, simpleHtmlCharX); }, contentAfter: 'abx[]cd
', }); }); }); describe("range not collapsed", () => { test("should paste a text in a p", async () => { await testEditor({ contentBefore: "a[bc]d
", stepFunction: async (editor) => { pasteHtml(editor, simpleHtmlCharX); }, contentAfter: "ax[]d
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[cd]ef
', stepFunction: async (editor) => { pasteHtml(editor, simpleHtmlCharX); }, contentAfter: 'abx[]ef
', }); }); test("should paste a text when selection across two span", async () => { await testEditor({ contentBefore: 'ab[cd]ef
', stepFunction: async (editor) => { pasteHtml(editor, simpleHtmlCharX); }, contentAfter: 'abx[]ef
', }); await testEditor({ contentBefore: 'ab[c- -d]ef
', stepFunction: async (editor) => { pasteHtml(editor, simpleHtmlCharX); }, contentAfter: 'abx[]ef
', }); }); test("should paste a text when selection across two p (3)", async () => { await testEditor({ contentBefore: "b[c
d]e
fbx[]e
fb[c
- -d]e
fbx[]e
fb[c
d]efbx[]ef
d]e
fb[c
d]e
fbx[]e
f[]
[]
[]
[]
[]
[]
[]
abc
def
ghi
jkl
abc
def
ghi
jkl[]
", }); await testEditor({ contentBefore: "[]
abc
def
ghi
jkl
mno
"); }, contentAfter: "abc
def
ghi
jkl
mno[]
", }); await testEditor({ contentBefore: "[]
abc
def
ghi
jkl
mno
"); }, contentAfter: "abc
def
ghi
jkl
mno[]
", }); await testEditor({ contentBefore: "[]
abc
def
ghi
abc
def
ghi[]
", }); }); test("should split multiple elements with[]
abc
def
abc
def
[]
abc
def[]
", }); }); }); describe("not breaking[]
[]
abc"); }, contentAfter: "
def
abc", }); }); test("should not split pre with
def[]
[]
abc"); }, contentAfter: "
def
abc", }); }); }); }); describe("Unwrapping html element", () => { test("should not unwrap a node when pasting on empty node", async () => { await testEditor({ contentBefore: "
def[]
[]
[]
[]
[]
[]
[]
[]
abc
abc
[]
mn[]op
", stepFunction: async (editor) => { pasteHtml(editor, "mn
op
", }); await testEditor({ contentBefore: "mn[]op
", stepFunction: async (editor) => { pasteHtml(editor, "mn
op
", }); await testEditor({ contentBefore: "mn[]op
", stepFunction: async (editor) => { pasteHtml(editor, "mn
op
", }); }); test("should unwrap a node when pasting in between same node", async () => { await testEditor({ contentBefore: "[]mn
", stepFunction: async (editor) => { pasteHtml(editor, "mn
", }); await testEditor({ contentBefore: "[]mn
", stepFunction: async (editor) => { pasteHtml(editor, "mn
", }); await testEditor({ contentBefore: "[]mn
", stepFunction: async (editor) => { pasteHtml(editor, "mn
", }); }); test("should unwrap a node when pasting at start of same node", async () => { await testEditor({ contentBefore: "mn[]
", stepFunction: async (editor) => { pasteHtml(editor, "mn
mn[]
", stepFunction: async (editor) => { pasteHtml(editor, "mn
mn[]
", stepFunction: async (editor) => { pasteHtml(editor, "mn
a[]
", stepFunction: async (editor) => { pasteHtml(editor, "a
[]
[]abcd
", stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: "123 4[]abcd
", }); }); test("should paste a text in a p", async () => { await testEditor({ contentBefore: "ab[]cd
", stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: "ab123 4[]cd
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[]cd
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab123 4[]cd
', }); }); }); describe("range not collapsed", () => { test("should paste a text in a p", async () => { await testEditor({ contentBefore: "a[bc]d
", stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: "a123 4[]d
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[cd]ef
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab123 4[]ef
', }); }); test("should paste a text when selection across two span", async () => { await testEditor({ contentBefore: 'ab[cd]ef
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab123 4[]ef
', }); await testEditor({ contentBefore: 'ab[c- -d]ef
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab123 4[]ef
', }); }); test("should paste a text when selection across two p (4)", async () => { await testEditor({ contentBefore: "b[c
d]e
fb123 4[]e
fb[c
- -d]e
fb123 4[]e
fb[c
d]efb123 4[]ef
d]e
fb[c
d]e
fb123 4[]e
f12
34
'; describe("range collapsed", () => { test("should paste a text at the beginning of a p", async () => { await testEditor({ contentBefore: "[]abcd
", stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: "12
34[]abcd
", }); }); test("should paste a text in a p", async () => { await testEditor({ contentBefore: "ab[]cd
", stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: "ab12
34[]cd
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[]cd
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab12
34[]cd
', }); }); }); describe("range not collapsed", () => { test("should paste a text in a p", async () => { await testEditor({ contentBefore: "a[bc]d
", stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: "a12
34[]d
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[cd]ef
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab12
34[]ef
', }); }); test("should paste a text when selection across two span (1)", async () => { await testEditor({ contentBefore: '1ab[cd]ef
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: '1ab12
34[]ef
', }); }); test("should paste a text when selection across two span (2)", async () => { await testEditor({ contentBefore: '2ab[c- -d]ef
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: '2ab12
34[]ef
', }); }); test("should paste a text when selection across two p (5)", async () => { await testEditor({ contentBefore: "b[c
d]e
fb12
34[]e
fb[c
- -d]e
fb12
34[]e
f1abc[de]f
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: '1abc12
34[]f
', }); await testEditor({ contentBefore: '2a[bc]def
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: '2a12
34[]def
', }); }); test("should paste a text when selection across two element (5)", async () => { await testEditor({ contentBefore: 'b[c
d]ef` is a tradeOff // Should we change it ? How ? Might warrant a discussion. // possible alt contentAfter :
b12
34[]efb12
34[]ef
d]e
f1X2
3X4
5X6
"; describe("range collapsed", () => { test("should paste a text at the beginning of a p", async () => { await testEditor({ contentBefore: "[]abcd
", stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: "1X2
3X4
5X6[]abcd
", }); }); test("should paste a text in a p", async () => { await testEditor({ contentBefore: "ab[]cd
", stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: "ab1X2
3X4
5X6[]cd
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[]cd
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab1X2
3X4
5X6[]cd
', }); }); }); describe("range not collapsed", () => { test("should paste a text in a p", async () => { await testEditor({ contentBefore: "a[bc]d
", stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: "a1X2
3X4
5X6[]d
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[cd]ef
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab1X2
3X4
5X6[]ef
', }); }); test("should paste a text when selection across two span (1)", async () => { await testEditor({ contentBefore: '1ab[cd]ef
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: '1ab1X2
3X4
5X6[]ef
', }); }); test("should paste a text when selection across two span (2)", async () => { await testEditor({ contentBefore: '2ab[c- -d]ef
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: '2ab1X2
3X4
5X6[]ef
', }); }); test("should paste a text when selection across two p (6)", async () => { await testEditor({ contentBefore: "b[c
d]e
fb1X2
3X4
5X6[]e
fb[c
- -d]e
fb1X2
3X4
5X6[]e
f3X4
5X6[]f3X4
5X6[]def1abc[de]f
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: '1abc1X2
3X4
5X6[]f
', }); await testEditor({ contentBefore: '2a[bc]def
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: '2a1X2
3X4
5X6[]def
', }); }); test("should paste a text when selection across two element (7)", async () => { await testEditor({ contentBefore: 'b[c
d]efb1X2
3X4
5X6[]ef
d]e
f3X4
5X6[]e12
ii
'; describe("range collapsed", () => { test("should paste a text at the beginning of a p", async () => { await testEditor({ contentBefore: "[]abcd
", stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: "12
ii[]abcd
", }); }); test("should paste a text in a p", async () => { await testEditor({ contentBefore: "ab[]cd
", stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: "ab12
ii[]cd
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[]cd
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab12
ii[]cd
', }); }); }); describe("range not collapsed", () => { test("should paste a text in a p", async () => { await testEditor({ contentBefore: "a[bc]d
", stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: "a12
ii[]d
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[cd]ef
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab12
ii[]ef
', }); }); test("should paste a text when selection across two span", async () => { await testEditor({ contentBefore: 'ab[cd]ef
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab12
ii[]ef
', }); await testEditor({ contentBefore: 'ab[cxd]ef
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab12
ii[]ef
', }); }); test("should paste a text when selection across two p (7)", async () => { await testEditor({ contentBefore: "b[c
d]e
fb12
ii[]e
fb[c
- -d]e
fb12
ii[]e
f1abc[de]f
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: '1abc12
ii[]f
', }); await testEditor({ contentBefore: '2a[bc]def
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: '2a12
ii[]def
', }); }); test("should paste a text when selection across two element (9)", async () => { await testEditor({ contentBefore: 'b[c
d]efb12
ii[]ef
d]e
f123
zzz
4567
"; describe("range collapsed", () => { test("should paste a text at the beginning of a p", async () => { await testEditor({ contentBefore: "[]abcd
", stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: "123
zzz
4567[]abcd
", }); }); test("should paste a text in a p", async () => { await testEditor({ contentBefore: "ab[]cd
", stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: "ab123
zzz
4567[]cd
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[]cd
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab123
zzz
4567[]cd
', }); }); }); describe("range not collapsed", () => { test("should paste a text in a p", async () => { await testEditor({ contentBefore: "a[bc]d
", stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: "a123
zzz
4567[]d
", }); }); test("should paste a text in a span", async () => { await testEditor({ contentBefore: 'ab[cd]ef
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab123
zzz
4567[]ef
', }); }); test("should paste a text when selection across two span", async () => { await testEditor({ contentBefore: 'ab[cd]ef
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab123
zzz
4567[]ef
', }); await testEditor({ contentBefore: 'ab[c- -d]ef
', stepFunction: async (editor) => { pasteHtml(editor, complexHtmlData); }, contentAfter: 'ab123
zzz
4567[]ef
', }); }); test("should paste a text when selection across two p (8)", async () => { await testEditor({ contentBefore: "b[c
d]e
fb123
zzz
4567[]e
fb[c
- -d]e
fb123
zzz
4567[]e
fzzz
4567[]fzzz
4567[]defb[c
d]efb123
zzz
4567[]ef
d]e
fzzz
4567[]e[]
abcdef
ghijkl
jklmno[]
', }); }); }); describe("Special cases", () => { describe("lists", () => { test("should paste a list in a p", async () => { await testEditor({ contentBefore: "12[]34
", stepFunction: async (editor) => { pasteHtml(editor, "12
34
", }); }); test("should paste the text of an li into another li", async () => { await testEditor({ contentBefore: "ghi
"); }, contentAfter: "abc
jkl
[]
Abc
def
Abc
def[]
abc
def
ghi
jkl
mno
"); }, contentAfter: "[]", stepFunction: async (editor) => { pasteHtml(editor, "
", }); await testEditor({ contentBefore: "abc
def
ghi[]
x[]", stepFunction: async (editor) => { pasteHtml(editor, "
x", }); await testEditor({ contentBefore: "abc
def
ghi[]
[]x", stepFunction: async (editor) => { pasteHtml(editor, "
", }); await testEditor({ contentBefore: "abc
def
ghi[]
x
x[]y", stepFunction: async (editor) => { pasteHtml(editor, "
x", }); }); }); describe("pasting within pre", () => { test("should paste paragraph releted elements within pre", async () => { await testEditor({ contentBefore: "abc
def
ghi[]
y
[]", stepFunction: async (editor) => { pasteHtml(editor, "
", }); await testEditor({ contentBefore: "abc
def
ghi[]
x[]", stepFunction: async (editor) => { pasteHtml(editor, "
x", }); await testEditor({ contentBefore: "abc
def
ghi[]
[]x", stepFunction: async (editor) => { pasteHtml(editor, "
", }); await testEditor({ contentBefore: "abc
def
ghi[]
x
x[]y", stepFunction: async (editor) => { pasteHtml(editor, "
x", }); }); }); const url = "https://www.odoo.com"; const imgUrl = "https://download.odoocdn.com/icons/website/static/description/icon.png"; const videoUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"; describe("link", () => { describe("range collapsed", () => { test("should paste and transform an URL in a p (collapsed)", async () => { await testEditor({ contentBefore: "abc
def
ghi[]
y
ab[]cd
", stepFunction: async (editor) => { pasteText(editor, "http://www.xyz.com"); }, contentAfter: 'abhttp://www.xyz.com[]cd
', }); }); test("should paste and transform an URL in a span (collapsed)", async () => { await testEditor({ contentBefore: 'ab[]cd
', stepFunction: async (editor) => { pasteText(editor, "http://www.xyz.com"); }, contentAfter: 'abhttp://www.xyz.com[]cd
', }); }); test("should paste and not transform an URL in a existing link (collapsed)", async () => { await testEditor({ contentBefore: 'ab[]cd
', stepFunction: async (editor) => { pasteText(editor, "http://www.xyz.com"); }, contentAfter: '', }); await testEditor({ contentBefore: 'ab[]cd
', stepFunction: async (editor) => { pasteText(editor, "random"); }, contentAfter: '', }); }); test("should paste and transform an URL in a existing link if pasting valid url (collapsed)", async () => { await testEditor({ contentBefore: 'a[]cd
', stepFunction: async (editor) => { pasteText(editor, "https://www.xyz.xdc"); }, contentAfter: '', }); await testEditor({ contentBefore: 'ab[].comd
', stepFunction: async (editor) => { pasteText(editor, "oom"); }, contentAfter: '', }); }); test("should replace link for new content when pasting in an empty link (collapsed)", async () => { await testEditor({ contentBefore: '', stepFunction: async (editor) => { pasteText(editor, "abc"); }, contentAfter: "abc[]
", }); }); test("should replace link for new content when pasting in an empty link (collapsed)(2)", async () => { await testEditor({ contentBefore: 'xy\u200B[]z
', stepFunction: async (editor) => { pasteText(editor, "abc"); }, contentAfter: "xyabc[]z
", }); }); test("should replace link for new content (url) when pasting in an empty link (collapsed)", async () => { const { el, editor } = await setupEditor( `xy\u200B[]z
` ); pasteText(editor, "http://odoo.com"); await animationFrame(); expect(".o-we-powerbox").toHaveCount(0); expect(cleanLinkArtifacts(getContent(el))).toBe( `xyhttp://odoo.com[]z
` ); }); test("should replace link for new content (imgUrl) when pasting in an empty link (collapsed) (1)", async () => { const { el, editor } = await setupEditor(`xy[]z
`); expect(getContent(el)).toBe( `xy\ufeff\ufeff[]\ufeffz
` ); pasteText(editor, imgUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); expect(getContent(el)).toBe(`xy${imgUrl}[]z
`); await press("Enter"); expect(getContent(el)).toBe(`xy[]z
xy\u200B[]z
` ); pasteText(editor, imgUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); expect(getContent(el)).toBe(`xy${imgUrl}[]z
`); await press("ArrowDown"); await press("Enter"); await animationFrame(); expect(cleanLinkArtifacts(getContent(el))).toBe( `xy${imgUrl}[]z
` ); }); test("should paste and transform plain text content over an empty link (collapsed)", async () => { await testEditor({ contentBefore: '', stepFunction: async (editor) => { pasteText(editor, "abc www.odoo.com xyz"); }, contentAfter: 'abc www.odoo.com xyz[]
', }); await testEditor({ contentBefore: '', stepFunction: async (editor) => { pasteText(editor, "odoo.com\ngoogle.com"); }, contentAfter: '' + '', }); }); test("should paste html content over an empty link (collapsed)", async () => { await testEditor({ contentBefore: '', stepFunction: async (editor) => { pasteHtml( editor, 'odoo.com[]
"); pasteText(editor, `abc ${url} def`); await animationFrame(); expect(".o-we-powerbox").toHaveCount(0); expect(cleanLinkArtifacts(getContent(el))).toBe( `abc ${url} def[]
` ); }); test("should paste and transform image URL among text (collapsed)", async () => { const { el, editor } = await setupEditor("[]
"); pasteText(editor, `abc ${imgUrl} def`); await animationFrame(); expect(".o-we-powerbox").toHaveCount(0); expect(cleanLinkArtifacts(getContent(el))).toBe( `abc ${imgUrl} def[]
` ); }); test("should paste and transform video URL among text (collapsed)", async () => { const { el, editor } = await setupEditor("[]
"); pasteText(editor, `abc ${videoUrl} def`); await animationFrame(); expect(".o-we-powerbox").toHaveCount(0); expect(cleanLinkArtifacts(getContent(el))).toBe( `abc ${videoUrl} def[]
` ); }); test("should paste and transform multiple URLs (collapsed) (1)", async () => { const { el, editor } = await setupEditor("[]
"); pasteText(editor, `${url} ${videoUrl} ${imgUrl}`); await animationFrame(); expect(".o-we-powerbox").toHaveCount(0); expect(cleanLinkArtifacts(getContent(el))).toBe( `${url} ${videoUrl} ${imgUrl}[]
` ); }); test("should paste and transform multiple URLs (collapsed) (2)", async () => { const { el, editor } = await setupEditor("[]
"); pasteText(editor, `${url} abc ${videoUrl} def ${imgUrl}`); await animationFrame(); expect(".o-we-powerbox").toHaveCount(0); expect(cleanLinkArtifacts(getContent(el))).toBe( `${url} abc ${videoUrl} def ${imgUrl}[]
` ); }); test("should paste plain text inside non empty link (collapsed)", async () => { await testEditor({ contentBefore: '', stepFunction: async (editor) => { pasteHtml(editor, "123"); }, contentAfter: '', }); }); test("should paste and not transform an URL in a pre tag", async () => { await testEditor({ contentBefore: "[]", stepFunction: async (editor) => { pasteText(editor, "http://www.xyz.com"); }, contentAfter: "
http://www.xyz.com[]", }); }); }); describe("range not collapsed", () => { test("should paste and transform an URL in a p (not collapsed)", async () => { await testEditor({ contentBefore: "
ab[xxx]cd
", stepFunction: async (editor) => { pasteText(editor, "http://www.xyz.com"); }, contentAfter: 'abhttp://www.xyz.com[]cd
', }); }); test("should paste and transform an URL in a span (not collapsed)", async () => { await testEditor({ contentBefore: 'ab[x546x]cd
', stepFunction: async (editor) => { pasteText(editor, "http://www.xyz.com"); }, contentAfter: 'abhttp://www.xyz.com[]cd
', }); }); test("should paste and not transform an URL in a existing link (not collapsed)", async () => { await testEditor({ contentBefore: '', stepFunction: async (editor) => { pasteText(editor, "http://www.xyz.com"); }, contentAfter: '', }); }); test("should restore selection when pasting plain text followed by UNDO (1) (not collapsed)", async () => { await testEditor({ contentBefore: "[abc]
", stepFunction: async (editor) => { pasteText(editor, "def"); undo(editor); }, contentAfter: "[abc]
", }); }); test("should restore selection when pasting plain text followed by UNDO (2) (not collapsed)", async () => { await testEditor({ contentBefore: "[abc]
", stepFunction: async (editor) => { pasteText(editor, "www.odoo.com"); undo(editor); }, contentAfter: "[abc]
", }); }); test("should restore selection when pasting plain text followed by UNDO (3) (not collapsed)", async () => { await testEditor({ contentBefore: "[abc]
", stepFunction: async (editor) => { pasteText(editor, "def www.odoo.com xyz"); undo(editor); }, contentAfter: "[abc]
", }); }); test("should restore selection after pasting HTML followed by UNDO (not collapsed)", async () => { await testEditor({ contentBefore: "[abc]
", stepFunction: async (editor) => { pasteHtml( editor, 'odoo.com[abc]
", }); }); test("should paste and transform URL among text (not collapsed)", async () => { const { el, editor } = await setupEditor("[xyz]
"); pasteText(editor, `abc ${url} def`); await animationFrame(); expect(".o-we-powerbox").toHaveCount(0); expect(cleanLinkArtifacts(getContent(el))).toBe( `abc ${url} def[]
` ); }); test("should paste and transform image URL among text (not collapsed)", async () => { const { el, editor } = await setupEditor("[xyz]
"); pasteText(editor, `abc ${imgUrl} def`); await animationFrame(); expect(".o-we-powerbox").toHaveCount(0); expect(cleanLinkArtifacts(getContent(el))).toBe( `abc ${imgUrl} def[]
` ); }); test("should paste and transform video URL among text (not collapsed)", async () => { const { el, editor } = await setupEditor("[xyz]
"); pasteText(editor, `abc ${videoUrl} def`); await animationFrame(); expect(".o-we-powerbox").toHaveCount(0); expect(cleanLinkArtifacts(getContent(el))).toBe( `abc ${videoUrl} def[]
` ); }); test("should paste and transform multiple URLs among text (not collapsed)", async () => { const { el, editor } = await setupEditor("[xyz]
"); pasteText(editor, `${url} ${videoUrl} ${imgUrl}`); await animationFrame(); expect(".o-we-powerbox").toHaveCount(0); expect(cleanLinkArtifacts(getContent(el))).toBe( `${url} ${videoUrl} ${imgUrl}[]
` ); }); test("should paste and transform URL over the existing url", async () => { await testEditor({ contentBefore: 'ab[http://www.xyz.com]cd
', stepFunction: async (editor) => { pasteText(editor, "https://www.xyz.xdc "); }, contentAfter: 'abhttps://www.xyz.xdc []cd
', }); }); test("should paste and transform URL over the existing url (not collapsed)", async () => { await testEditor({ contentBefore: 'ab[http://www.xyz.com]cd
', stepFunction: async (editor) => { pasteText(editor, "https://www.xyz.xdc "); }, contentAfter: 'abhttps://www.xyz.xdc []cd
', }); }); test("should paste plain text content over a link if all of its contents is selected (not collapsed)", async () => { await testEditor({ contentBefore: 'a[xyz]d
', stepFunction: async (editor) => { pasteText(editor, "bc"); }, contentAfter: "abc[]d
", }); }); test("should paste and transform plain text content over a link if all of its contents is selected (not collapsed)", async () => { await testEditor({ contentBefore: '', stepFunction: async (editor) => { pasteText(editor, "www.odoo.com"); }, contentAfter: '', }); await testEditor({ contentBefore: '', stepFunction: async (editor) => { pasteText(editor, "abc www.odoo.com xyz"); }, contentAfter: 'abc www.odoo.com xyz[]
', }); }); test("should paste and transform plain text content over an image link if all of its contents is selected (not collapsed) (1)", async () => { const { el, editor } = await setupEditor( `` ); pasteText(editor, imgUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); expect(getContent(el)).toBe( `abhttps://download.odoocdn.com/icons/website/static/description/icon.png[]cd
` ); await press("Enter"); expect(getContent(el)).toBe(`ab[]cd
abhttps://download.odoocdn.com/icons/website/static/description/icon.png[]cd
` ); await press("ArrowDown"); await press("Enter"); expect(cleanLinkArtifacts(getContent(el))).toBe( `ab${imgUrl}[]cd
` ); }); test("should paste html content over a link if all of its contents is selected (not collapsed)", async () => { await testEditor({ contentBefore: '', stepFunction: async (editor) => { pasteHtml( editor, 'odoo.comab[]cd
"); pasteText(editor, imgUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); await press("Enter"); expect(getContent(el)).toBe(`ab[]cd
ab[]cd
'); pasteText(editor, imgUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); await press("Enter"); expect(getContent(el)).toBe( `ab[]cd
ab[]cd
' ); pasteText(editor, imgUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(0); expect(cleanLinkArtifacts(getContent(el))).toBe( `ab[]cd
[]
"); pasteText(editor, imgUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Pick the second command (Paste as URL) await press("ArrowDown"); await press("Enter"); expect(cleanLinkArtifacts(getContent(el))).toBe( `` ); }); test("should not revert a history step when pasting an image URL as a link (1)", async () => { const { el, editor } = await setupEditor("[]
"); // paste text to have a history step recorded pasteText(editor, "*should not disappear*"); pasteText(editor, imgUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Pick the second command (Paste as URL) await press("ArrowDown"); await press("Enter"); expect(cleanLinkArtifacts(getContent(el))).toBe( `*should not disappear*${imgUrl}[]
` ); }); }); describe("range not collapsed", () => { test("should paste and transform an image URL in a p (2)", async () => { const { el, editor } = await setupEditor("ab[xxx]cd
"); pasteText(editor, imgUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); await press("Enter"); expect(cleanLinkArtifacts(getContent(el))).toBe(`ab[]cd
ab[x546x]cd
' ); pasteText(editor, imgUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); await press("Enter"); expect(getContent(el)).toBe( `ab[]cd
ab[]cd
ab[xxx]cd
"); pasteText(editor, imgUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Pick the second command (Paste as URL) await press("ArrowDown"); await press("Enter"); expect(cleanLinkArtifacts(getContent(el))).toBe( `ab${imgUrl}[]cd
` ); }); test("should not revert a history step when pasting an image URL as a link (2)", async () => { const { el, editor } = await setupEditor("[]
"); // paste text (to have a history step recorded) pasteText(editor, "abxxxcd"); // select xxx in "ab[xxx]cd
"" const p = editor.editable.querySelector("p"); const selection = { anchorNode: p.childNodes[1], anchorOffset: 2, focusNode: p.childNodes[1], focusOffset: 5, }; setSelection(selection); // paste url pasteText(editor, imgUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Pick the second command (Paste as URL) await press("ArrowDown"); await press("Enter"); expect(cleanLinkArtifacts(getContent(el))).toBe( `ab${imgUrl}[]cd
` ); }); test("should restore selection after pasting image URL followed by UNDO (1)", async () => { const { el, editor } = await setupEditor("[abc]
"); pasteText(editor, imgUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Pick first command (Embed image) await press("Enter"); // Undo undo(editor); expect(getContent(el)).toBe("[abc]
"); }); test("should restore selection after pasting image URL followed by UNDO (2)", async () => { const { el, editor } = await setupEditor("[abc]
"); pasteText(editor, imgUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Pick second command (Paste as URL) await press("ArrowDown"); await press("Enter"); // Undo undo(editor); expect(getContent(el)).toBe("[abc]
"); }); }); }); describe("youtube video", () => { describe("range collapsed", () => { beforeEach(() => { onRpc("/html_editor/video_url/data", async (request) => { const { params } = await request.json(); return { embed_url: params.video_url }; }); }); test("should paste and transform a youtube URL in a p (1)", async () => { const { el, editor } = await setupEditor("ab[]cd
"); pasteText(editor, videoUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Force powerbox validation on the default first choice await press("Enter"); // Wait for the getYoutubeVideoElement promise to resolve. await tick(); expect(getContent(el)).toBe( `ab
[]cd
` ); }); test("should paste and transform a youtube URL in a span (1)", async () => { const { el, editor } = await setupEditor('ab[]cd
'); pasteText(editor, "https://youtu.be/dQw4w9WgXcQ"); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Force powerbox validation on the default first choice await press("Enter"); // Wait for the getYoutubeVideoElement promise to resolve. await tick(); expect(getContent(el)).toBe( 'ab
[]cd
' ); }); test("should paste and not transform a youtube URL in a existing link", async () => { const { el, editor, plugins } = await setupEditor( 'ab[]cd
' ); pasteText(editor, "https://youtu.be/dQw4w9WgXcQ"); // Ensure the powerbox is active const powerbox = plugins.get("powerbox"); expect(powerbox.overlay.isOpen).not.toBe(true); expect(cleanLinkArtifacts(getContent(el))).toBe( 'abhttps://youtu.be/dQw4w9WgXcQ[]cd
' ); }); test("should paste a youtube URL as a link in a p (1)", async () => { const url = "https://youtu.be/dQw4w9WgXcQ"; const { el, editor } = await setupEditor("[]
"); pasteText(editor, url); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Pick the second command (Paste as URL) await press("ArrowDown"); await press("Enter"); expect(cleanLinkArtifacts(getContent(el))).toBe(`${url}[]
`); }); test("should not revert a history step when pasting a youtube URL as a link (1)", async () => { const url = "https://youtu.be/dQw4w9WgXcQ"; const { el, editor } = await setupEditor("[]
"); // paste text to have a history step recorded pasteText(editor, "*should not disappear*"); pasteText(editor, url); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Pick the second command (Paste as URL) await press("ArrowDown"); await press("Enter"); expect(cleanLinkArtifacts(getContent(el))).toBe( `*should not disappear*${url}[]
` ); }); }); describe("range not collapsed", () => { beforeEach(() => { onRpc("/html_editor/video_url/data", async (request) => { const { params } = await request.json(); return { embed_url: params.video_url }; }); }); test("should paste and transform a youtube URL in a p (2)", async () => { const { el, editor } = await setupEditor("ab[xxx]cd
"); pasteText(editor, "https://youtu.be/dQw4w9WgXcQ"); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Force powerbox validation on the default first choice await press("Enter"); // Wait for the getYoutubeVideoElement promise to resolve. await tick(); expect(getContent(el)).toBe( 'ab
[]cd
' ); }); test("should paste and transform a youtube URL in a span (2)", async () => { const { el, editor } = await setupEditor( 'ab[x546x]cd
' ); pasteText(editor, videoUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Force powerbox validation on the default first choice await press("Enter"); // Wait for the getYoutubeVideoElement promise to resolve. await tick(); expect(getContent(el)).toBe( `ab
[]cd
` ); }); test("should paste and not transform a youtube URL in a existing link", async () => { const { el, editor, plugins } = await setupEditor( '' ); pasteText(editor, videoUrl); // Ensure the powerbox is active const powerbox = plugins.get("powerbox"); expect(powerbox.overlay.isOpen).not.toBe(true); expect(cleanLinkArtifacts(getContent(el))).toBe( `` ); }); test("should paste a youtube URL as a link in a p (2)", async () => { const { el, editor } = await setupEditor("ab[xxx]cd
"); pasteText(editor, videoUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Pick the second command (Paste as URL) await press("ArrowDown"); await press("Enter"); expect(cleanLinkArtifacts(getContent(el))).toBe( `ab${videoUrl}[]cd
` ); }); test("should not revert a history step when pasting a youtube URL as a link (2)", async () => { const { el, editor } = await setupEditor("[]
"); // paste text (to have a history step recorded) pasteText(editor, "abxxxcd"); // select xxx in "ab[xxx]cd
" const p = editor.editable.querySelector("p"); const selection = { anchorNode: p.childNodes[1], anchorOffset: 2, focusNode: p.childNodes[1], focusOffset: 5, }; setSelection(selection); setSelection(selection); // paste url pasteText(editor, videoUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Pick the second command (Paste as URL) await press("ArrowDown"); await press("Enter"); expect(cleanLinkArtifacts(getContent(el))).toBe( `ab${videoUrl}[]cd
` ); }); test("should restore selection after pasting video URL followed by UNDO (1)", async () => { const { el, editor } = await setupEditor("[abc]
"); pasteText(editor, videoUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Force powerbox validation on the default first choice await press("Enter"); // Undo undo(editor); expect(getContent(el)).toBe("[abc]
"); }); test("should restore selection after pasting video URL followed by UNDO (2)", async () => { const { el, editor } = await setupEditor("[abc]
"); pasteText(editor, videoUrl); await animationFrame(); expect(".o-we-powerbox").toHaveCount(1); // Pick the second command (Paste as URL) await press("ArrowDown"); await press("Enter"); // Undo undo(editor); expect(getContent(el)).toBe("[abc]
"); }); }); }); describe("Odoo editor own html", () => { test("should paste html as is", async () => { await testEditor({ contentBefore: "a[]b
", stepFunction: async (editor) => { pasteOdooEditorHtml(editor, 'a
[]b
', }); }); test("should not paste unsafe content", async () => { await testEditor({ contentBefore: "a[]b
", stepFunction: async (editor) => { pasteOdooEditorHtml(editor, ``); }, contentAfter: "a[]b
", }); }); }); describe("editable in iframe", () => { test("should paste odoo-editor html", async () => { const { el, editor } = await setupEditor("[]
", { props: { iframe: true } }); pasteOdooEditorHtml(editor, `textbold textmore text
`); expect(getContent(el)).toBe("textbold textmore text[]
"); }); }); describe("Paste HTML tables", () => { // The tests below are very sensitive to whitespaces as they do represent actual // whitespace text nodes in the DOM. The tests will fail if those are removed. test("should keep all allowed style (Excel Online)", async () => { await testEditor({ contentBefore: "[]
", stepFunction: async (editor) => { pasteHtml( editor, `Italic then also BOLD | |
Just bold Just Italic | Bold underline |
Color text | |
Color background | Color text on color background |
14pt MONO TEXT |
Italic then also BOLD | |
Just bold Just Italic | Bold underline |
Color text | |
Color background | Color text on color background |
14pt MONO TEXT |
${" "} ${" "} []
`, }); }); test("should keep all allowed style (Google Sheets)", async () => { await testEditor({ contentBefore: "[]
", stepFunction: async (editor) => { pasteHtml( editor, `Italic then also BOLD | Italic strike |
Just Bold Just Italic | Bold underline |
Color text | Color strike and underline |
Color background | Color text on color background |
14pt MONO TEXT |
[]
`, }); }); test("should keep all allowed style (Libre Office)", async () => { await testEditor({ contentBefore: "[]
", stepFunction: async (editor) => { pasteHtml( editor, `Italic then also BOLD | |
Just bold Just italic | Bold underline |
Color text | |
Color background | Color text on color background |
14pt MONO TEXT |
Italic then also BOLD | |
Just bold Just italic | Bold underline |
Color text | |
Color background | Color text on color background |
14pt MONO TEXT |
[]
`, }); }); }); describe("onDrop", () => { test("should add text from htmlTransferItem", async () => { const { el } = await setupEditor("a[b]cd
"); const pElement = el.firstChild; const textNode = pElement.firstChild; patchWithCleanup(document, { caretPositionFromPoint: () => ({ offsetNode: textNode, offset: 3 }), }); const dropData = new DataTransfer(); dropData.setData("text/html", "b"); await dispatch(pElement, "drop", { dataTransfer: dropData }); await tick(); expect(getContent(el)).toBe("abcb[]d
"); }); test("should not be able to paste inside some branded node", async () => { const { el } = await setupEditor(`a[b]cd
`); const pElement = el.firstChild; const textNode = pElement.firstChild; patchWithCleanup(document, { caretPositionFromPoint: () => ({ offsetNode: textNode, offset: 3 }), }); const dropData = new DataTransfer(); dropData.setData("text/html", "x"); await dispatch(pElement, "drop", { dataTransfer: dropData }); await tick(); expect(getContent(el)).toBe(`a[b]cd
`); }); test("should add new images form fileTransferItems", async () => { const { el } = await setupEditor(`a[b]cd
`); const pElement = el.firstChild; const textNode = pElement.firstChild; patchWithCleanup(document, { caretPositionFromPoint: () => ({ offsetNode: textNode, offset: 3 }), }); const base64Image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII="; const blob = dataURItoBlob(base64Image); const dropData = new DataTransfer(); const f = new File([blob], "image.png", { type: blob.type }); dropData.items.add(f); await dispatch(pElement, "drop", { dataTransfer: dropData }); await waitFor("img"); expect(getContent(el)).toBe( `abc[]d
a[]bc
ab[]c