3721 lines
164 KiB
JavaScript
3721 lines
164 KiB
JavaScript
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</p><${node.toLowerCase()}>b</${node.toLowerCase()}><p>c`;
|
||
|
||
await testEditor({
|
||
contentBefore: "<p>123[]4</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, `a<${node.toLowerCase()}>b</${node.toLowerCase()}>c`);
|
||
},
|
||
contentAfter: "<p>123" + html + "[]4</p>",
|
||
});
|
||
}
|
||
}
|
||
});
|
||
|
||
test("should keep whitelisted Tags tag (2)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>123[]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, 'a<img src="http://www.imgurl.com/img.jpg">d');
|
||
},
|
||
contentAfter: '<p>123a<img src="http://www.imgurl.com/img.jpg">d[]</p>',
|
||
});
|
||
});
|
||
|
||
test("should keep tables Tags tag and add classes", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>123[]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
"a<table><thead><tr><th>h</th></tr></thead><tbody><tr><td>b</td></tr></tbody></table>d"
|
||
);
|
||
},
|
||
contentAfter:
|
||
'<p>123a</p><table class="table table-bordered"><thead><tr><th>h</th></tr></thead><tbody><tr><td>b</td></tr></tbody></table><p>d[]</p>',
|
||
});
|
||
});
|
||
|
||
test("should not keep span", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>123[]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "a<span>bc</span>d");
|
||
},
|
||
contentAfter: "<p>123abcd[]</p>",
|
||
});
|
||
});
|
||
|
||
test("should not keep orphan LI", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>123[]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "a<li>bc</li>d");
|
||
},
|
||
contentAfter: "<p>123a</p><p>bc</p><p>d[]</p>",
|
||
});
|
||
});
|
||
|
||
test("should keep LI in UL", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>123[]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "a<ul><li>bc</li></ul>d");
|
||
},
|
||
contentAfter: "<p>123a</p><ul><li>bc</li></ul><p>d[]</p>",
|
||
});
|
||
});
|
||
|
||
test("should keep P and B and not span", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>123[]xx</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "a<p>bc</p>d<span>e</span>f<b>g</b>h");
|
||
},
|
||
contentAfter: "<p>123a</p><p>bc</p><p>def<b>g</b>h[]xx</p>",
|
||
});
|
||
});
|
||
|
||
test("should keep styled span", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>123[]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, 'a<span style="text-decoration: underline">bc</span>d');
|
||
},
|
||
contentAfter: "<p>123abcd[]</p>",
|
||
});
|
||
});
|
||
|
||
test("should remove unwanted styles and b tag when pasting from paragraph from gdocs", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
`<meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-ddad60c5-7fff-0a8f-fdd5-c1107201fe26"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">test1</span></p><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">test2</span></p></b>`
|
||
);
|
||
},
|
||
contentAfter: "<p>test1</p><p>test2[]</p>",
|
||
});
|
||
});
|
||
|
||
test("should remove b, keep p, and remove unwanted styles when pasting list from gdocs", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
'<meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-5d8bcf85-7fff-ebec-8604-eedd96f2d601"><ul style="margin-top:0;margin-bottom:0;padding-inline-start:48px;"><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;" role="presentation"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Google</span></p></li><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;" role="presentation"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Test</span></p></li><li dir="ltr" style="list-style-type:disc;font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;" role="presentation"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">test2</span></p></li></ul></b>'
|
||
);
|
||
},
|
||
contentAfter:
|
||
"<ul><li><p>Google</p></li><li><p>Test</p></li><li><p>test2[]</p></li></ul>",
|
||
});
|
||
});
|
||
|
||
test("should remove unwanted styles and keep tags when pasting list from gdoc", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
'<meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-477946a8-7fff-f959-18a4-05014997e161"><ul style="margin-top:0;margin-bottom:0;padding-inline-start:48px;"><li dir="ltr" style="list-style-type:disc;font-size:20pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1"><h1 dir="ltr" style="line-height:1.38;margin-top:20pt;margin-bottom:0pt;" role="presentation"><span style="font-size:20pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Google</span></h1></li><li dir="ltr" style="list-style-type:disc;font-size:20pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1"><h1 dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:6pt;" role="presentation"><span style="font-size:20pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Test</span></h1></li><li dir="ltr" style="list-style-type:disc;font-size:20pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1"><h1 dir="ltr" style="line-height:1.38;margin-top:20pt;margin-bottom:0pt;" role="presentation"><span style="font-size:20pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">test2</span></h1></li></ul></b>'
|
||
);
|
||
},
|
||
contentAfter:
|
||
"<ul><li><h1>Google</h1></li><li><h1>Test</h1></li><li><h1>test2[]</h1></li></ul>",
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("Simple text", () => {
|
||
describe("range collapsed", () => {
|
||
test("should paste a text at the beginning of a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]abcd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "x");
|
||
},
|
||
contentAfter: "<p>x[]abcd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>ab[]cd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "x");
|
||
},
|
||
contentAfter: "<p>abx[]cd</p>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>ab[]cd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "xyz 123");
|
||
},
|
||
contentAfter: "<p>abxyz 123[]cd</p>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>ab[]cd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "x y");
|
||
},
|
||
contentAfter: "<p>abx y[]cd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[]c</span>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "x");
|
||
},
|
||
contentAfter: '<p>a<span class="a">bx[]c</span>d</p>',
|
||
});
|
||
});
|
||
// 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: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "a\nb\nc\nd");
|
||
},
|
||
contentAfter:
|
||
'<p style="margin-bottom: 0px;">a</p>' +
|
||
'<p style="margin-bottom: 0px;">b</p>' +
|
||
'<p style="margin-bottom: 0px;">c</p>' +
|
||
"<p>d[]</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste text and understand \\r\\n newlines", async () => {
|
||
await testEditor({
|
||
// @phoenix content adapted to make it valid html
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "a\r\nb\r\nc\r\nd");
|
||
},
|
||
contentAfter:
|
||
'<p style="margin-bottom: 0px;">a</p>' +
|
||
'<p style="margin-bottom: 0px;">b</p>' +
|
||
'<p style="margin-bottom: 0px;">c</p>' +
|
||
"<p>d[]</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste text and understand \\n newlines within UNBREAKABLE node", async () => {
|
||
await testEditor({
|
||
contentBefore: "<div>[]<br></div>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "a\nb\nc\nd");
|
||
},
|
||
contentAfter: "<div>a<br>b<br>c<br>d[]</div>",
|
||
});
|
||
});
|
||
|
||
test("should paste text and understand \\n newlines within UNBREAKABLE node(2)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div><span style="font-size: 9px;">a[]</span></div>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "b\nc\nd");
|
||
},
|
||
contentAfter: '<div><span style="font-size: 9px;">ab<br>c<br>d[]</span></div>',
|
||
});
|
||
});
|
||
|
||
test("should paste text and understand \\n newlines within PRE element", async () => {
|
||
await testEditor({
|
||
contentBefore: "<pre>[]<br></pre>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "a\nb\nc");
|
||
},
|
||
contentAfter: "<pre>a<br>b<br>c[]</pre>",
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("range not collapsed", () => {
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>a[bc]d</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "x");
|
||
},
|
||
contentAfter: "<p>ax[]d</p>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>a[bc]d</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "xyz 123");
|
||
},
|
||
contentAfter: "<p>axyz 123[]d</p>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>a[bc]d</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "x y");
|
||
},
|
||
contentAfter: "<p>ax y[]d</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[cd]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "x");
|
||
},
|
||
contentAfter: '<p>a<span class="a">bx[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[c</span><span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "x");
|
||
},
|
||
contentAfter: '<p>a<span class="a">bx[]e</span>f</p>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[c</span>- -<span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "y");
|
||
},
|
||
contentAfter: '<p>a<span class="a">by[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two p (1)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<div>a<p>b[c</p><p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "x");
|
||
},
|
||
contentAfter: "<div>a<p>bx[]e</p>f</div>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<div>a<p>b[c</p>- -<p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "y");
|
||
},
|
||
contentAfter: "<div>a<p>by[]e</p>f</div>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection leave a span (1)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>ab<span class="a">c[d</span>e]f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "x");
|
||
},
|
||
contentAfter: '<div>ab<span class="a">cx[]</span>f</div>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<div>a[b<span class="a">c]d</span>ef</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "y");
|
||
},
|
||
contentAfter: '<div>ay[]<span class="a">d</span>ef</div>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two element (1)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>1a<p>b[c</p><span class="a">d]e</span>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "x");
|
||
},
|
||
contentAfter: '<div>1a<p>bx[]<span class="a">e</span>f</p></div>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<div>2a<span class="a">b[c</span><p>d]e</p>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "x");
|
||
},
|
||
contentAfter: '<div>2a<span class="a">bx[]</span>e<br>f</div>',
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("Simple html span", () => {
|
||
const simpleHtmlCharX =
|
||
'<span style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">x</span>';
|
||
|
||
describe("range collapsed", () => {
|
||
test("should paste a text at the beginning of a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]abcd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: "<p>x[]abcd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>ab[]cd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: "<p>abx[]cd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[]c</span>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<p>a<span class="a">bx[]c</span>d</p>',
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("range not collapsed", () => {
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>a[bc]d</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: "<p>ax[]d</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[cd]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<p>a<span class="a">bx[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[c</span><span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<p>a<span class="a">bx[]e</span>f</p>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[c</span>- -<span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<p>a<span class="a">bx[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two p (2)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<div>1a<p>b[c</p><p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: "<div>1a<p>bx[]e</p>f</div>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<div>2a<p>b[c</p>- -<p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: "<div>2a<p>bx[]e</p>f</div>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection leave a span (2)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>ab<span class="a">c[d</span>e]f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<div>ab<span class="a">cx[]</span>f</div>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<div>a[b<span class="a">c]d</span>ef</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<div>ax[]<span class="a">d</span>ef</div>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two element (2)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>1a<p>b[c</p><span class="a">d]e</span>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<div>1a<p>bx[]<span class="a">e</span>f</p></div>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<div>2a<span class="a">b[c</span><p>d]e</p>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<div>2a<span class="a">bx[]</span>e<br>f</div>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<div>3a<p>b[c</p><p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: "<div>3a<p>bx[]e</p>f</div>",
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("Simple html p", () => {
|
||
const simpleHtmlCharX = "<p>x</p>";
|
||
|
||
describe("range collapsed", () => {
|
||
test("should paste a text at the beginning of a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]abcd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: "<p>x[]abcd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>ab[]cd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: "<p>abx[]cd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[]c</span>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<p>a<span class="a">bx[]c</span>d</p>',
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("range not collapsed", () => {
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>a[bc]d</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: "<p>ax[]d</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[cd]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<p>a<span class="a">bx[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[c</span><span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<p>a<span class="a">bx[]e</span>f</p>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[c</span>- -<span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<p>a<span class="a">bx[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two p (3)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<div>1a<p>b[c</p><p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: "<div>1a<p>bx[]e</p>f</div>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<div>2a<p>b[c</p>- -<p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: "<div>2a<p>bx[]e</p>f</div>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection leave a span (3)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>ab<span class="a">c[d</span>e]f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<div>ab<span class="a">cx[]</span>f</div>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<div>a[b<span class="a">c]d</span>ef</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<div>ax[]<span class="a">d</span>ef</div>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two element (3)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>1a<p>b[c</p><span class="a">d]e</span>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<div>1a<p>bx[]<span class="a">e</span>f</p></div>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<div>2a<span class="a">b[c</span><p>d]e</p>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: '<div>2a<span class="a">bx[]</span>e<br>f</div>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<div>3a<p>b[c</p><p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, simpleHtmlCharX);
|
||
},
|
||
contentAfter: "<div>3a<p>bx[]e</p>f</div>",
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("Simple html elements containing <br>", () => {
|
||
describe("breaking <br> elements", () => {
|
||
test("should split h1 with <br> into seperate h1 elements", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc<br>def<br>ghi<br>jkl</h1>");
|
||
},
|
||
contentAfter: "<h1>abc</h1><h1>def</h1><h1>ghi</h1><h1>jkl[]</h1>",
|
||
});
|
||
});
|
||
|
||
test("should split h2 with <br> into seperate h2 elements", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h2>abc<br>def<br>ghi<br>jkl</h2>");
|
||
},
|
||
contentAfter: "<h2>abc</h2><h2>def</h2><h2>ghi</h2><h2>jkl[]</h2>",
|
||
});
|
||
});
|
||
|
||
test("should split h3 with <br> into seperate h3 elements", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h3>abc<br>def<br>ghi<br>jkl</h3>");
|
||
},
|
||
contentAfter: "<h3>abc</h3><h3>def</h3><h3>ghi</h3><h3>jkl[]</h3>",
|
||
});
|
||
});
|
||
|
||
test("should split h4 with <br> into seperate h4 elements", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h4>abc<br>def<br>ghi<br>jkl</h4>");
|
||
},
|
||
contentAfter: "<h4>abc</h4><h4>def</h4><h4>ghi</h4><h4>jkl[]</h4>",
|
||
});
|
||
});
|
||
|
||
test("should split h5 with <br> into seperate h5 elements", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h5>abc<br>def<br>ghi<br>jkl</h5>");
|
||
},
|
||
contentAfter: "<h5>abc</h5><h5>def</h5><h5>ghi</h5><h5>jkl[]</h5>",
|
||
});
|
||
});
|
||
|
||
test("should split h6 with <br> into seperate h6 elements", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h6>abc<br>def<br>ghi<br>jkl</h6>");
|
||
},
|
||
contentAfter: "<h6>abc</h6><h6>def</h6><h6>ghi</h6><h6>jkl[]</h6>",
|
||
});
|
||
});
|
||
|
||
test("should split p with <br> into seperate p elements", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<p>abc<br>def<br>ghi<br>jkl</p>");
|
||
},
|
||
contentAfter: "<p>abc</p><p>def</p><p>ghi</p><p>jkl[]</p>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<p>abc<br>def<br>ghi<br>jkl</p><p>mno</p>");
|
||
},
|
||
contentAfter: "<p>abc</p><p>def</p><p>ghi</p><p>jkl</p><p>mno[]</p>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<p>abc<br>def<br>ghi<br>jkl</p><p><br></p><p>mno</p>");
|
||
},
|
||
contentAfter: "<p>abc</p><p>def</p><p>ghi</p><p>jkl</p><p><br></p><p>mno[]</p>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<p>abc<br>def<br><br><br>ghi</p>");
|
||
},
|
||
contentAfter: "<p>abc</p><p>def</p><p><br></p><p><br></p><p>ghi[]</p>",
|
||
});
|
||
});
|
||
|
||
test("should split multiple elements with <br>", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
"<p>abc<br>def</p><h1>ghi<br>jkl</h1><h2><br></h2><h3>mno<br>pqr</h3>"
|
||
);
|
||
},
|
||
contentAfter:
|
||
"<p>abc</p><p>def</p><h1>ghi</h1><h1>jkl</h1><h2><br></h2><h3>mno</h3><h3>pqr[]</h3>",
|
||
});
|
||
});
|
||
|
||
test("should split div with <br>", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<div>abc<br>def</div>");
|
||
},
|
||
contentAfter: "<p>abc</p><p>def[]</p>",
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("not breaking <br> elements", () => {
|
||
test("should not split li with <br>", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<ul><li>abc<br>def</li></ul>");
|
||
},
|
||
contentAfter: "<ul><li>abc<br>def[]</li></ul>",
|
||
});
|
||
});
|
||
|
||
test("should not split blockquote with <br>", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<blockquote>abc<br>def</blockquote>");
|
||
},
|
||
contentAfter: "<blockquote>abc<br>def[]</blockquote>",
|
||
});
|
||
});
|
||
|
||
test("should not split pre with <br>", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<pre>abc<br>def</pre>");
|
||
},
|
||
contentAfter: "<pre>abc<br>def[]</pre>",
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("Unwrapping html element", () => {
|
||
test("should not unwrap a node when pasting on empty node", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1>");
|
||
},
|
||
contentAfter: "<h1>abc[]</h1>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h2>abc</h2>");
|
||
},
|
||
contentAfter: "<h2>abc[]</h2>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h3>abc</h3>");
|
||
},
|
||
contentAfter: "<h3>abc[]</h3>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2>");
|
||
},
|
||
contentAfter: "<h1>abc</h1><h2>def[]</h2>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2><h3>ghi</h3>");
|
||
},
|
||
contentAfter: "<h1>abc</h1><h2>def</h2><h3>ghi[]</h3>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p><p><br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h3>abc</h3>");
|
||
},
|
||
contentAfter: "<h3>abc[]</h3><p><br></p>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<p>abc</p><p><br></p><p><br></p>");
|
||
},
|
||
contentAfter: "<p>abc</p><p><br></p><p>[]<br></p>",
|
||
});
|
||
});
|
||
test("should not unwrap a node when pasting in between different node", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>mn[]op</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1>");
|
||
},
|
||
contentAfter: "<p>mn</p><h1>abc[]</h1><p>op</p>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>mn[]op</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2>");
|
||
},
|
||
contentAfter: "<p>mn</p><h1>abc</h1><h2>def[]</h2><p>op</p>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>mn[]op</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2><h3>ghi</h3>");
|
||
},
|
||
contentAfter: "<p>mn</p><h1>abc</h1><h2>def</h2><h3>ghi[]</h3><p>op</p>",
|
||
});
|
||
});
|
||
test("should unwrap a node when pasting in between same node", async () => {
|
||
await testEditor({
|
||
contentBefore: "<h1>mn[]op</h1>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1>");
|
||
},
|
||
contentAfter: "<h1>mnabc[]op</h1>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<h1>mn[]op</h1>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2>");
|
||
},
|
||
contentAfter: "<h1>mnabc</h1><h2>def[]</h2><h1>op</h1>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<h2>mn[]op</h2>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2>");
|
||
},
|
||
contentAfter: "<h2>mn</h2><h1>abc</h1><h2>def[]op</h2>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<h1>mn[]op</h1>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h1>def</h1><h1>ghi</h1>");
|
||
},
|
||
contentAfter: "<h1>mnabc</h1><h1>def</h1><h1>ghi[]op</h1>",
|
||
});
|
||
});
|
||
test("should not unwrap a node when pasting at start of different node", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]mn</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1>");
|
||
},
|
||
contentAfter: "<h1>abc[]</h1><p>mn</p>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>[]mn</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2>");
|
||
},
|
||
contentAfter: "<h1>abc</h1><h2>def[]</h2><p>mn</p>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>[]mn</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2><h3>ghi</h3>");
|
||
},
|
||
contentAfter: "<h1>abc</h1><h2>def</h2><h3>ghi[]</h3><p>mn</p>",
|
||
});
|
||
});
|
||
test("should unwrap a node when pasting at start of same node", async () => {
|
||
await testEditor({
|
||
contentBefore: "<h1>[]mn</h1>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1>");
|
||
},
|
||
contentAfter: "<h1>abc[]mn</h1>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<h1>[]mn</h1>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h2>abc</h2><h1>def</h1>");
|
||
},
|
||
contentAfter: "<h2>abc</h2><h1>def[]mn</h1>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<h1><font style="background-color: rgb(255, 0, 0);">[]mn</font></h1>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h1>def</h1><h1>ghi</h1>");
|
||
},
|
||
contentAfter:
|
||
'<h1>abc</h1><h1>def</h1><h1><font style="background-color: rgb(255, 0, 0);">ghi[]mn</font></h1>',
|
||
});
|
||
});
|
||
test("should not unwrap a node when pasting at end of different node", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>mn[]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1>");
|
||
},
|
||
contentAfter: "<p>mn</p><h1>abc[]</h1>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>mn[]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2>");
|
||
},
|
||
contentAfter: "<p>mn</p><h1>abc</h1><h2>def[]</h2>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<p>mn[]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2><h3>ghi</h3>");
|
||
},
|
||
contentAfter: "<p>mn</p><h1>abc</h1><h2>def</h2><h3>ghi[]</h3>",
|
||
});
|
||
});
|
||
test("should unwrap a node when pasting at end of same node", async () => {
|
||
await testEditor({
|
||
contentBefore: "<h1>mn[]</h1>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1>");
|
||
},
|
||
contentAfter: "<h1>mnabc[]</h1>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<h1>mn[]</h1>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2>");
|
||
},
|
||
contentAfter: "<h1>mnabc</h1><h2>def[]</h2>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<h1><font style="background-color: rgb(255, 0, 0);">mn[]</font></h1>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h1>def</h1><h1>ghi</h1>");
|
||
},
|
||
contentAfter:
|
||
'<h1><font style="background-color: rgb(255, 0, 0);">mnabc</font></h1><h1>def</h1><h1>ghi[]</h1>',
|
||
});
|
||
});
|
||
test("should not unwrap empty block nodes even when pasting on same node", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>a[]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<p><br></p><p><br></p><p><br></p>");
|
||
},
|
||
contentAfter: "<p>a</p><p><br></p><p><br></p><p>[]<br></p>",
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("Complex html span", () => {
|
||
const complexHtmlData =
|
||
'<span style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">1</span><b style="box-sizing: border-box; font-weight: bolder; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">23</b><span style="font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;"><span> </span>4</span>';
|
||
|
||
describe("range collapsed", () => {
|
||
test("should paste a text at the beginning of a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]abcd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<p>1<b>23</b> 4[]abcd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>ab[]cd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<p>ab1<b>23</b> 4[]cd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[]c</span>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<p>a<span class="a">b1<b>23</b> 4[]c</span>d</p>',
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("range not collapsed", () => {
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>a[bc]d</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<p>a1<b>23</b> 4[]d</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[cd]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<p>a<span class="a">b1<b>23</b> 4[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[c</span><span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<p>a<span class="a">b1<b>23</b> 4[]e</span>f</p>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[c</span>- -<span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<p>a<span class="a">b1<b>23</b> 4[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two p (4)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<div>a<p>b[c</p><p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<div>a<p>b1<b>23</b> 4[]e</p>f</div>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<div>a<p>b[c</p>- -<p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<div>a<p>b1<b>23</b> 4[]e</p>f</div>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection leave a span (4)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>ab<span class="a">c[d</span>e]f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<div>ab<span class="a">c1<b>23</b> 4[]</span>f</div>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<div>a[b<span class="a">c]d</span>ef</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<div>a1<b>23</b> 4[]<span class="a">d</span>ef</div>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two element (4)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>1a<p>b[c</p><span class="a">d]e</span>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<div>1a<p>b1<b>23</b> 4[]<span class="a">e</span>f</p></div>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<div>2a<span class="a">b[c</span><p>d]e</p>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<div>2a<span class="a">b1<b>23</b> 4[]</span>e<br>f</div>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<div>3a<p>b[c</p><p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<div>3a<p>b1<b>23</b> 4[]e</p>f</div>",
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("Complex html p", () => {
|
||
const complexHtmlData =
|
||
'<p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 1rem; color: rgb(0, 0, 0); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">12</p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 1rem; color: rgb(0, 0, 0); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">34</p>';
|
||
|
||
describe("range collapsed", () => {
|
||
test("should paste a text at the beginning of a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]abcd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<p>12</p><p>34[]abcd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>ab[]cd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<p>ab12</p><p>34[]cd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[]c</span>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>a<span class="a">b12</span></p><p><span class="a">34[]c</span>d</p>',
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("range not collapsed", () => {
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>a[bc]d</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<p>a12</p><p>34[]d</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[cd]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>a<span class="a">b12</span></p><p><span class="a">34[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two span (1)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>1a<span class="a">b[c</span><span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>1a<span class="a">b12</span></p><p><span class="a">34[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two span (2)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>2a<span class="a">b[c</span>- -<span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>2a<span class="a">b12</span></p><p><span class="a">34[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two p (5)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<div>a<p>b[c</p><p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<div>a<p>b12</p><p>34[]e</p>f</div>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<div>a<p>b[c</p>- -<p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<div>a<p>b12</p><p>34[]e</p>f</div>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection leave a span (5)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>1ab<span class="a">c[d</span>e]f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<div>1ab<span class="a">c12<br>34[]</span>f</div>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<div>2a[b<span class="a">c]d</span>ef</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<div>2a12<br>34[]<span class="a">d</span>ef</div>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection leave a span (6)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>1ab<span class="a">c[d</span>e]f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>1ab<span class="a">c12</span></p><p><span class="a">34[]</span>f</p>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<p>2a[b<span class="a">c]d</span>ef</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<p>2a12</p><p>34[]<span class="a">d</span>ef</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two element (5)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>1a<p>b[c</p><span class="a">d]e</span>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
// FIXME: Bringing `e` and `f` into the `<p>` is a tradeOff
|
||
// Should we change it ? How ? Might warrant a discussion.
|
||
// possible alt contentAfter : <div>1a<p>b12</p>34[]<span>e</span>f</div>
|
||
contentAfter: '<div>1a<p>b12</p><p>34[]<span class="a">e</span>f</p></div>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two element (6)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>2a<span class="a">b[c</span><p>d]e</p>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<div>2a<span class="a">b12<br>34[]</span>e<br>f</div>',
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("Complex html 3 p", () => {
|
||
const complexHtmlData = "<p>1<i>X</i>2</p><p>3<i>X</i>4</p><p>5<i>X</i>6</p>";
|
||
|
||
describe("range collapsed", () => {
|
||
test("should paste a text at the beginning of a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]abcd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<p>1<i>X</i>2</p><p>3<i>X</i>4</p><p>5<i>X</i>6[]abcd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>ab[]cd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<p>ab1<i>X</i>2</p><p>3<i>X</i>4</p><p>5<i>X</i>6[]cd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[]c</span>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>a<span class="a">b1<i>X</i>2</span></p><p>3<i>X</i>4</p><p><span class="a">5<i>X</i>6[]c</span>d</p>',
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("range not collapsed", () => {
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>a[bc]d</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<p>a1<i>X</i>2</p><p>3<i>X</i>4</p><p>5<i>X</i>6[]d</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[cd]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>a<span class="a">b1<i>X</i>2</span></p><p>3<i>X</i>4</p><p><span class="a">5<i>X</i>6[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two span (1)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>1a<span class="a">b[c</span><span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>1a<span class="a">b1<i>X</i>2</span></p><p>3<i>X</i>4</p><p><span class="a">5<i>X</i>6[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two span (2)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>2a<span class="a">b[c</span>- -<span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>2a<span class="a">b1<i>X</i>2</span></p><p>3<i>X</i>4</p><p><span class="a">5<i>X</i>6[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two p (6)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<div>a<p>b[c</p><p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
"<div>a<p>b1<i>X</i>2</p><p>3<i>X</i>4</p><p>5<i>X</i>6[]e</p>f</div>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<div>a<p>b[c</p>- -<p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
"<div>a<p>b1<i>X</i>2</p><p>3<i>X</i>4</p><p>5<i>X</i>6[]e</p>f</div>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection leave a span (7)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>1ab<span class="a">c[d</span>e]f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<div>1ab<span class="a">c1<i>X</i>2</span><p>3<i>X</i>4</p><span class="a">5<i>X</i>6[]</span>f</div>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<div>2a[b<span class="a">c]d</span>ef</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<div>2a1<i>X</i>2<p>3<i>X</i>4</p>5<i>X</i>6[]<span class="a">d</span>ef</div>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection leave a span (8)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>1ab<span class="a">c[d</span>e]f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>1ab<span class="a">c1<i>X</i>2</span></p><p>3<i>X</i>4</p><p><span class="a">5<i>X</i>6[]</span>f</p>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<p>2a[b<span class="a">c]d</span>ef</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>2a1<i>X</i>2</p><p>3<i>X</i>4</p><p>5<i>X</i>6[]<span class="a">d</span>ef</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two element (7)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>1a<p>b[c</p><span class="a">d]e</span>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<div>1a<p>b1<i>X</i>2</p><p>3<i>X</i>4</p><p>5<i>X</i>6[]<span class="a">e</span>f</p></div>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two element (8)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>2a<span class="a">b[c</span><p>d]e</p>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<div>2a<span class="a">b1<i>X</i>2</span><p>3<i>X</i>4</p><span class="a">5<i>X</i>6[]</span>e<br>f</div>',
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("Complex html p+i", () => {
|
||
const complexHtmlData =
|
||
'<p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 1rem; color: rgb(0, 0, 0); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">12</p><p style="box-sizing: border-box; margin-top: 0px; margin-bottom: 1rem; color: rgb(0, 0, 0); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><i style="box-sizing: border-box;">ii</i></p>';
|
||
|
||
describe("range collapsed", () => {
|
||
test("should paste a text at the beginning of a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]abcd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<p>12</p><p><i>ii</i>[]abcd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>ab[]cd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<p>ab12</p><p><i>ii</i>[]cd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[]c</span>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>a<span class="a">b12</span></p><p><span class="a"><i>ii</i>[]c</span>d</p>',
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("range not collapsed", () => {
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>a[bc]d</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<p>a12</p><p><i>ii</i>[]d</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[cd]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>a<span class="a">b12</span></p><p><span class="a"><i>ii</i>[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[c</span><span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>a<span class="a">b12</span></p><p><span class="a"><i>ii</i>[]e</span>f</p>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[c</span>x<span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>a<span class="a">b12</span></p><p><span class="a"><i>ii</i>[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two p (7)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<div>a<p>b[c</p><p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<div>a<p>b12</p><p><i>ii</i>[]e</p>f</div>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<div>a<p>b[c</p>- -<p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<div>a<p>b12</p><p><i>ii</i>[]e</p>f</div>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection leave a span (9)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>1ab<span class="a">c[d</span>e]f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<div>1ab<span class="a">c12<i><br>ii</i>[]</span>f</div>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<div>2a[b<span class="a">c]d</span>ef</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<div>2a12<i><br>ii</i>[]<span class="a">d</span>ef</div>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection leave a span (10)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>1ab<span class="a">c[d</span>e]f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>1ab<span class="a">c12</span></p><p><span class="a"><i>ii</i>[]</span>f</p>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<p>2a[b<span class="a">c]d</span>ef</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<p>2a12</p><p><i>ii</i>[]<span class="a">d</span>ef</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two element (9)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>1a<p>b[c</p><span class="a">d]e</span>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<div>1a<p>b12</p><p><i>ii</i>[]<span class="a">e</span>f</p></div>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two element (10)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>2a<span class="a">b[c</span><p>d]e</p>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<div>2a<span class="a">b12<i><br>ii</i>[]</span>e<br>f</div>',
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("Complex html 3p+b", () => {
|
||
const complexHtmlData = "<p>1<b>23</b></p><p>zzz</p><p>45<b>6</b>7</p>";
|
||
|
||
describe("range collapsed", () => {
|
||
test("should paste a text at the beginning of a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]abcd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<p>1<b>23</b></p><p>zzz</p><p>45<b>6</b>7[]abcd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>ab[]cd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<p>ab1<b>23</b></p><p>zzz</p><p>45<b>6</b>7[]cd</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[]c</span>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>a<span class="a">b1<b>23</b></span></p><p>zzz</p><p><span class="a">45<b>6</b>7[]c</span>d</p>',
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("range not collapsed", () => {
|
||
test("should paste a text in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>a[bc]d</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<p>a1<b>23</b></p><p>zzz</p><p>45<b>6</b>7[]d</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text in a span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[cd]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>a<span class="a">b1<b>23</b></span></p><p>zzz</p><p><span class="a">45<b>6</b>7[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two span", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[c</span><span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>a<span class="a">b1<b>23</b></span></p><p>zzz</p><p><span class="a">45<b>6</b>7[]e</span>f</p>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[c</span>- -<span class="a">d]e</span>f</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<p>a<span class="a">b1<b>23</b></span></p><p>zzz</p><p><span class="a">45<b>6</b>7[]e</span>f</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two p (8)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<div>a<p>b[c</p><p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<div>a<p>b1<b>23</b></p><p>zzz</p><p>45<b>6</b>7[]e</p>f</div>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<div>a<p>b[c</p>- -<p>d]e</p>f</div>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: "<div>a<p>b1<b>23</b></p><p>zzz</p><p>45<b>6</b>7[]e</p>f</div>",
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection leave a span (11)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>1ab<span class="a">c[d</span>e]f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<div>1ab<span class="a">c1<b>23</b></span><p>zzz</p><span class="a">45<b>6</b>7[]</span>f</div>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<div>2a[b<span class="a">c]d</span>ef</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<div>2a1<b>23</b><p>zzz</p>45<b>6</b>7[]<span class="a">d</span>ef</div>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two element (11)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>1a<p>b[c</p><span class="a">d]e</span>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<div>1a<p>b1<b>23</b></p><p>zzz</p><p>45<b>6</b>7[]<span class="a">e</span>f</p></div>',
|
||
});
|
||
});
|
||
|
||
test("should paste a text when selection across two element (12)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<div>2a<span class="a">b[c</span><p>d]e</p>f</div>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter:
|
||
'<div>2a<span class="a">b1<b>23</b></span><p>zzz</p><span class="a">45<b>6</b>7[]</span>e<br>f</div>',
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("Complex html div", () => {
|
||
const complexHtmlData = `<div><div><span style="color: #fb4934;">abc</span><span style="color: #ebdbb2;">def</span></div><div dir="rtl"><span style="color: #fb4934;">ghi</span><span style="color: #fe8019;">jkl</span></div><div><span style="color: #fb4934;">jkl</span><span style="color: #ebdbb2;">mno</span></div></div>`;
|
||
test("should convert div to p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, complexHtmlData);
|
||
},
|
||
contentAfter: '<p>abcdef</p><p dir="rtl">ghijkl</p><p>jklmno[]</p>',
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("Special cases", () => {
|
||
describe("lists", () => {
|
||
test("should paste a list in a p", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>12[]34</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<ul><li>abc</li><li>def</li><li>ghi</li></ul>");
|
||
},
|
||
contentAfter: "<p>12</p><ul><li>abc</li><li>def</li><li>ghi[]</li></ul><p>34</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste the text of an li into another li", async () => {
|
||
await testEditor({
|
||
contentBefore: "<ul><li>abc</li><li>de[]f</li><li>ghi</li></ul>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<ul><li>123</li></ul>");
|
||
},
|
||
contentAfter: "<ul><li>abc</li><li>de123[]f</li><li>ghi</li></ul>",
|
||
});
|
||
});
|
||
|
||
test("should paste the text of an li into another li, and the text of another li into the next li", async () => {
|
||
await testEditor({
|
||
contentBefore: "<ul><li>abc</li><li>de[]f</li><li>ghi</li></ul>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<ul><li>123</li><li>456</li></ul>");
|
||
},
|
||
contentAfter: "<ul><li>abc</li><li>de123</li><li>456[]f</li><li>ghi</li></ul>",
|
||
});
|
||
});
|
||
|
||
test("should paste the text of an li into another li, insert a new li, and paste the text of a third li into the next li", async () => {
|
||
await testEditor({
|
||
contentBefore: "<ul><li>abc</li><li>de[]f</li><li>ghi</li></ul>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<ul><li>123</li><li>456</li><li>789</li></ul>");
|
||
},
|
||
contentAfter:
|
||
"<ul><li>abc</li><li>de123</li><li>456</li><li>789[]f</li><li>ghi</li></ul>",
|
||
});
|
||
});
|
||
|
||
test("should paste the text of an li into another li and insert a new li at the end of a list", async () => {
|
||
await testEditor({
|
||
contentBefore: "<ul><li>abc</li><li>def</li><li>ghi[]</li></ul>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<ul><li>123</li><li>456</li></ul>");
|
||
},
|
||
contentAfter: "<ul><li>abc</li><li>def</li><li>ghi123</li><li>456[]</li></ul>",
|
||
});
|
||
});
|
||
|
||
test("should insert a new li at the beginning of a list and paste the text of another li into the next li", async () => {
|
||
await testEditor({
|
||
contentBefore: "<ul><li>[]abc</li><li>def</li><li>ghi</li></ul>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<ul><li>123</li><li>456</li></ul>");
|
||
},
|
||
contentAfter: "<ul><li>123</li><li>456[]abc</li><li>def</li><li>ghi</li></ul>",
|
||
});
|
||
});
|
||
|
||
test("should insert a list and a p tag inside a new list", async () => {
|
||
await testEditor({
|
||
contentBefore: "<ul><li>[]<br></li></ul>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<ul><li>abc</li><li>def</li></ul><p>ghi</p>");
|
||
},
|
||
contentAfter: "<ul><li>abc</li><li>def</li><li>ghi[]</li></ul>",
|
||
});
|
||
});
|
||
|
||
test("should insert content ending with a list inside a new list", async () => {
|
||
await testEditor({
|
||
contentBefore: "<ul><li>[]<br></li></ul>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<p>abc</p><ul><li>def</li><li>ghi</li></ul>");
|
||
},
|
||
contentAfter: "<ul><li>abc</li><li>def</li><li>ghi[]</li></ul>",
|
||
});
|
||
});
|
||
|
||
test("should convert a mixed list containing a paragraph into a checklist", async () => {
|
||
await testEditor({
|
||
contentBefore: `<ul class="o_checklist"><li>[]<br></li></ul>`,
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
unformat(`
|
||
<ul>
|
||
<li>abc</li>
|
||
<li>def</li>
|
||
<li>ghi</li>
|
||
</ul>
|
||
<p>jkl</p>
|
||
<ol>
|
||
<li>mno</li>
|
||
<li>pqr</li>
|
||
<li>stu</li>
|
||
</ol>
|
||
`)
|
||
);
|
||
},
|
||
contentAfter: unformat(`
|
||
<ul class="o_checklist">
|
||
<li>abc</li>
|
||
<li>def</li>
|
||
<li>ghi</li>
|
||
<li>jkl</li>
|
||
<li>mno</li>
|
||
<li>pqr</li>
|
||
<li>stu[]</li>
|
||
</ul>
|
||
`),
|
||
});
|
||
});
|
||
|
||
test("should not unwrap a list twice when pasting on new list", async () => {
|
||
await testEditor({
|
||
contentBefore: "<ul><li>[]<br></li></ul>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<ul><ul><li>abc</li><li>def</li></ul></ul>");
|
||
},
|
||
contentAfter: `<ul><li class="oe-nested"><ul><li>abc</li><li>def[]</li></ul></li></ul>`,
|
||
});
|
||
});
|
||
|
||
test("should paste a nested list into another list", async () => {
|
||
await testEditor({
|
||
contentBefore: "<ol><li>Alpha</li><li>[]<br></li></ol>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
unformat(`
|
||
<ul>
|
||
<li>abc</li>
|
||
<li>def</li>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li>123</li>
|
||
<li>456</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
`)
|
||
);
|
||
},
|
||
contentAfter: unformat(`
|
||
<ol>
|
||
<li>Alpha</li>
|
||
<li>abc</li>
|
||
<li>def</li>
|
||
<li class="oe-nested">
|
||
<ol>
|
||
<li>123</li>
|
||
<li>456[]</li>
|
||
</ol>
|
||
</li>
|
||
</ol>
|
||
`),
|
||
});
|
||
});
|
||
|
||
test("should paste a nested list into another list (2)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<ul><li>Alpha</li><li>[]<br></li></ul>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
unformat(`
|
||
<ol>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li class="oe-nested">
|
||
<ol>
|
||
<li class="oe-nested">
|
||
<ul class="o_checklist">
|
||
<li>abc</li>
|
||
</ul>
|
||
</li>
|
||
<li>def</li>
|
||
</ol>
|
||
</li>
|
||
<li>ghi</li>
|
||
</ul>
|
||
</li>
|
||
<li>jkl</li>
|
||
</ol>
|
||
`)
|
||
);
|
||
},
|
||
contentAfter: unformat(`
|
||
<ul>
|
||
<li>Alpha</li>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li>abc</li>
|
||
</ul>
|
||
</li>
|
||
<li>def</li>
|
||
</ul>
|
||
</li>
|
||
<li>ghi</li>
|
||
</ul>
|
||
</li>
|
||
<li>jkl[]</li>
|
||
</ul>
|
||
`),
|
||
});
|
||
});
|
||
|
||
test("should convert a mixed list into a ordered list", async () => {
|
||
await testEditor({
|
||
contentBefore: "<ol><li>[]<br></li></ol>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
unformat(`
|
||
<ul>
|
||
<li>ab</li>
|
||
<li>cd</li>
|
||
<li class="oe-nested">
|
||
<ol>
|
||
<li>ef</li>
|
||
<li>gh</li>
|
||
<li class="oe-nested">
|
||
<ul class="o_checklist">
|
||
<li>ij</li>
|
||
<li>kl</li>
|
||
</ul>
|
||
</li>
|
||
</ol>
|
||
</li>
|
||
</ul>
|
||
`)
|
||
);
|
||
},
|
||
contentAfter: unformat(`
|
||
<ol>
|
||
<li>ab</li>
|
||
<li>cd</li>
|
||
<li class="oe-nested">
|
||
<ol>
|
||
<li>ef</li>
|
||
<li>gh</li>
|
||
<li class="oe-nested">
|
||
<ol>
|
||
<li>ij</li>
|
||
<li>kl[]</li>
|
||
</ol>
|
||
</li>
|
||
</ol>
|
||
</li>
|
||
</ol>
|
||
`),
|
||
});
|
||
});
|
||
|
||
test("should convert a mixed list starting with bullet list into a bullet list", async () => {
|
||
await testEditor({
|
||
contentBefore: "<ul><li>[]<br></li></ul>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
unformat(`
|
||
<ul>
|
||
<li>ab</li>
|
||
<li>cd</li>
|
||
<li class="oe-nested">
|
||
<ol>
|
||
<li>ef</li>
|
||
<li>gh</li>
|
||
<li class="oe-nested">
|
||
<ul class="o_checklist">
|
||
<li>ij</li>
|
||
<li>kl</li>
|
||
</ul>
|
||
</li>
|
||
</ol>
|
||
</li>
|
||
</ul>
|
||
`)
|
||
);
|
||
},
|
||
contentAfter: unformat(`
|
||
<ul>
|
||
<li>ab</li>
|
||
<li>cd</li>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li>ef</li>
|
||
<li>gh</li>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li>ij</li>
|
||
<li>kl[]</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
`),
|
||
});
|
||
});
|
||
|
||
test("should paste a mixed list starting with deeply nested bullet list into a bullet list", async () => {
|
||
await testEditor({
|
||
contentBefore: "<ul><li>[]<br></li></ul>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
unformat(`
|
||
<ul>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li>ab</li>
|
||
<li>cd</li>
|
||
</ul>
|
||
</li>
|
||
<li>ef</li>
|
||
<li>gh</li>
|
||
</ul>
|
||
</li>
|
||
<li>ij</li>
|
||
<li>kl</li>
|
||
</ul>
|
||
</li>
|
||
<li>mn</li>
|
||
<li>op</li>
|
||
</ul>
|
||
`)
|
||
);
|
||
},
|
||
contentAfter: unformat(`
|
||
<ul>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li>ab</li>
|
||
<li>cd</li>
|
||
</ul>
|
||
</li>
|
||
<li>ef</li>
|
||
<li>gh</li>
|
||
</ul>
|
||
</li>
|
||
<li>ij</li>
|
||
<li>kl</li>
|
||
</ul>
|
||
</li>
|
||
<li>mn</li>
|
||
<li>op[]</li>
|
||
</ul>
|
||
`),
|
||
});
|
||
});
|
||
|
||
test("should paste a deeply nested list copied outside from odoo", async () => {
|
||
await testEditor({
|
||
contentBefore: "<ul><li>[]<br></li></ul>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
unformat(`
|
||
<ol>
|
||
<li>ab</li>
|
||
<ol>
|
||
<li>cd</li>
|
||
<li>ef</li>
|
||
<ul>
|
||
<li>gh</li>
|
||
<li>ij</li>
|
||
</ul>
|
||
<ol>
|
||
<li>kl</li>
|
||
<li>mn</li>
|
||
</ol>
|
||
</ol>
|
||
<ul>
|
||
<li>op</li>
|
||
<li>qr</li>
|
||
<ol>
|
||
<li>st</li>
|
||
<li>uv</li>
|
||
</ol>
|
||
</ul>
|
||
</ol>
|
||
`)
|
||
);
|
||
},
|
||
contentAfter: unformat(`
|
||
<ul>
|
||
<li>ab</li>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li>cd</li>
|
||
<li>ef</li>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li>gh</li>
|
||
<li>ij</li>
|
||
<li>kl</li>
|
||
<li>mn</li>
|
||
</ul>
|
||
</li>
|
||
<li>op</li>
|
||
<li>qr</li>
|
||
<li class="oe-nested">
|
||
<ul>
|
||
<li>st</li>
|
||
<li>uv[]</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
`),
|
||
});
|
||
});
|
||
|
||
test("should paste checklist from gdoc", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]<br></p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
unformat(`
|
||
<b style="font-weight:normal;" id="docs-internal-guid-5c9e50d3-7fff-c129-6dcc-e76588942722">
|
||
<ul style="margin-top:0;margin-bottom:0;padding-inline-start:28px;">
|
||
<li dir="ltr" role="checkbox" aria-checked="false" style="list-style-type:none;font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1">
|
||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAA1ElEQVR4Ae3bMQ4BURSFYY2xBuwQ7BIkTGxFRj9Oo9RdkXn5TvL3L19u+2ZmZmZmZhVbpH26pFcaJ9IrndMudb/CWadHGiden1bll9MIzqd79SUd0thY20qga4NA50qgoUGgoRJo/NL/V/N+QIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEyFeEZyXQpUGgUyXQrkGgTSVQl/qGcG5pnkq3Sn0jOMv0k3Vpm05pmNjfsGPalFyOmZmZmdkbSS9cKbtzhxMAAAAASUVORK5CYII=" width="17.599999999999998px" height="17.599999999999998px" alt="unchecked" aria-roledescription="checkbox" style="margin-right:3px;" />
|
||
<p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;display:inline-block;vertical-align:top;margin-top:0;" role="presentation"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Abc</span></p>
|
||
</li>
|
||
<li dir="ltr" role="checkbox" aria-checked="false" style="list-style-type:none;font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;" aria-level="1">
|
||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAA1ElEQVR4Ae3bMQ4BURSFYY2xBuwQ7BIkTGxFRj9Oo9RdkXn5TvL3L19u+2ZmZmZmZhVbpH26pFcaJ9IrndMudb/CWadHGiden1bll9MIzqd79SUd0thY20qga4NA50qgoUGgoRJo/NL/V/N+QIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIECBAgQIAAAQIEyFeEZyXQpUGgUyXQrkGgTSVQl/qGcG5pnkq3Sn0jOMv0k3Vpm05pmNjfsGPalFyOmZmZmdkbSS9cKbtzhxMAAAAASUVORK5CYII=" width="17.599999999999998px" height="17.599999999999998px" alt="checked" aria-roledescription="checkbox" style="margin-right:3px;" />
|
||
<p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;display:inline-block;vertical-align:top;margin-top:0;" role="presentation"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">def</span></p>
|
||
</li>
|
||
</ul>
|
||
</b>
|
||
`)
|
||
);
|
||
},
|
||
contentAfter: `<ul class="o_checklist"><li><p>Abc</p></li><li class="o_checked"><p>def[]</p></li></ul>`,
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("paragraphs", () => {
|
||
test("should paste multiple paragraphs into a list", async () => {
|
||
await testEditor({
|
||
contentBefore: "<ul><li>[]<br></li></ul>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<p>abc</p><p>def</p><p>ghi</p><p>jkl</p><p>mno</p>");
|
||
},
|
||
contentAfter:
|
||
"<ul><li>abc</li><li>def</li><li>ghi</li><li>jkl</li><li>mno[]</li></ul>",
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("pasting within blockquote", () => {
|
||
test("should paste paragraph related elements within blockquote", async () => {
|
||
await testEditor({
|
||
contentBefore: "<blockquote>[]<br></blockquote>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2><h3>ghi</h3>");
|
||
},
|
||
contentAfter: "<blockquote><h1>abc</h1><h2>def</h2><h3>ghi[]</h3></blockquote>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<blockquote>x[]</blockquote>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2><h3>ghi</h3>");
|
||
},
|
||
contentAfter: "<blockquote>x<h1>abc</h1><h2>def</h2><h3>ghi[]</h3></blockquote>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<blockquote>[]x</blockquote>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2><h3>ghi</h3>");
|
||
},
|
||
contentAfter: "<blockquote><h1>abc</h1><h2>def</h2><h3>ghi[]</h3>x</blockquote>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<blockquote>x[]y</blockquote>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2><h3>ghi</h3>");
|
||
},
|
||
contentAfter: "<blockquote>x<h1>abc</h1><h2>def</h2><h3>ghi[]</h3>y</blockquote>",
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("pasting within pre", () => {
|
||
test("should paste paragraph releted elements within pre", async () => {
|
||
await testEditor({
|
||
contentBefore: "<pre>[]<br></pre>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2><h3>ghi</h3>");
|
||
},
|
||
contentAfter: "<pre><h1>abc</h1><h2>def</h2><h3>ghi[]</h3></pre>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<pre>x[]</pre>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2><h3>ghi</h3>");
|
||
},
|
||
contentAfter: "<pre>x<h1>abc</h1><h2>def</h2><h3>ghi[]</h3></pre>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<pre>[]x</pre>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2><h3>ghi</h3>");
|
||
},
|
||
contentAfter: "<pre><h1>abc</h1><h2>def</h2><h3>ghi[]</h3>x</pre>",
|
||
});
|
||
await testEditor({
|
||
contentBefore: "<pre>x[]y</pre>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<h1>abc</h1><h2>def</h2><h3>ghi</h3>");
|
||
},
|
||
contentAfter: "<pre>x<h1>abc</h1><h2>def</h2><h3>ghi[]</h3>y</pre>",
|
||
});
|
||
});
|
||
});
|
||
|
||
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: "<p>ab[]cd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "http://www.xyz.com");
|
||
},
|
||
contentAfter: '<p>ab<a href="http://www.xyz.com">http://www.xyz.com</a>[]cd</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste and transform an URL in a span (collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<span class="a">b[]c</span>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "http://www.xyz.com");
|
||
},
|
||
contentAfter:
|
||
'<p>a<span class="a">b<a href="http://www.xyz.com">http://www.xyz.com</a>[]c</span>d</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste and not transform an URL in a existing link (collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<a href="http://existing.com">b[]c</a>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "http://www.xyz.com");
|
||
},
|
||
contentAfter: '<p>a<a href="http://existing.com">bhttp://www.xyz.com[]c</a>d</p>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<p>a<a href="http://existing.com">b[]c</a>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "random");
|
||
},
|
||
contentAfter: '<p>a<a href="http://existing.com">brandom[]c</a>d</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste and transform an URL in a existing link if pasting valid url (collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<a href="http://existing.com">[]c</a>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "https://www.xyz.xdc");
|
||
},
|
||
contentAfter: '<p>a<a href="https://www.xyz.xdcc">https://www.xyz.xdc[]c</a>d</p>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<p>a<a href="http://existing.com">b[].com</a>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "oom");
|
||
},
|
||
contentAfter: '<p>a<a href="http://boom.com">boom[].com</a>d</p>',
|
||
});
|
||
});
|
||
|
||
test("should replace link for new content when pasting in an empty link (collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p><a href="#" oe-zws-empty-inline="">[]\u200B</a></p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "abc");
|
||
},
|
||
contentAfter: "<p>abc[]</p>",
|
||
});
|
||
});
|
||
test("should replace link for new content when pasting in an empty link (collapsed)(2)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>xy<a href="#" oe-zws-empty-inline="">\u200B[]</a>z</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "abc");
|
||
},
|
||
contentAfter: "<p>xyabc[]z</p>",
|
||
});
|
||
});
|
||
|
||
test("should replace link for new content (url) when pasting in an empty link (collapsed)", async () => {
|
||
const { el, editor } = await setupEditor(
|
||
`<p>xy<a href="#" oe-zws-empty-inline="">\u200B[]</a>z</p>`
|
||
);
|
||
pasteText(editor, "http://odoo.com");
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(0);
|
||
expect(cleanLinkArtifacts(getContent(el))).toBe(
|
||
`<p>xy<a href="http://odoo.com">http://odoo.com</a>[]z</p>`
|
||
);
|
||
});
|
||
|
||
test("should replace link for new content (imgUrl) when pasting in an empty link (collapsed) (1)", async () => {
|
||
const { el, editor } = await setupEditor(`<p>xy<a href="#">[]</a>z</p>`);
|
||
expect(getContent(el)).toBe(
|
||
`<p>xy\ufeff<a href="#" class="o_link_in_selection">\ufeff[]</a>\ufeffz</p>`
|
||
);
|
||
pasteText(editor, imgUrl);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(1);
|
||
expect(getContent(el)).toBe(`<p>xy${imgUrl}[]z</p>`);
|
||
|
||
await press("Enter");
|
||
expect(getContent(el)).toBe(`<p>xy<img src="${imgUrl}">[]z</p>`);
|
||
});
|
||
|
||
test("should replace link for new content (url) when pasting in an empty link (collapsed) (2)", async () => {
|
||
const { el, editor } = await setupEditor(
|
||
`<p>xy<a href="#" oe-zws-empty-inline="">\u200B[]</a>z</p>`
|
||
);
|
||
pasteText(editor, imgUrl);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(1);
|
||
expect(getContent(el)).toBe(`<p>xy${imgUrl}[]z</p>`);
|
||
|
||
await press("ArrowDown");
|
||
await press("Enter");
|
||
|
||
await animationFrame();
|
||
expect(cleanLinkArtifacts(getContent(el))).toBe(
|
||
`<p>xy<a href="${imgUrl}">${imgUrl}</a>[]z</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste and transform plain text content over an empty link (collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p><a href="#">[]\u200B</a></p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "abc www.odoo.com xyz");
|
||
},
|
||
contentAfter: '<p>abc <a href="http://www.odoo.com">www.odoo.com</a> xyz[]</p>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<p><a href="#">[]\u200B</a></p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "odoo.com\ngoogle.com");
|
||
},
|
||
contentAfter:
|
||
'<p style="margin-bottom: 0px;"><a href="http://odoo.com">odoo.com</a></p>' +
|
||
'<p><a href="http://google.com">google.com</a>[]</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste html content over an empty link (collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p><a href="#">[]\u200B</a></p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
'<a href="www.odoo.com">odoo.com</a><br><a href="www.google.com">google.com</a>'
|
||
);
|
||
},
|
||
contentAfter:
|
||
'<p><a href="www.odoo.com">odoo.com</a></p><p><a href="www.google.com">google.com</a>[]</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste and transform URL among text (collapsed)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[]</p>");
|
||
pasteText(editor, `abc ${url} def`);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(0);
|
||
expect(cleanLinkArtifacts(getContent(el))).toBe(
|
||
`<p>abc <a href="${url}">${url}</a> def[]</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste and transform image URL among text (collapsed)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[]</p>");
|
||
pasteText(editor, `abc ${imgUrl} def`);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(0);
|
||
expect(cleanLinkArtifacts(getContent(el))).toBe(
|
||
`<p>abc <a href="${imgUrl}">${imgUrl}</a> def[]</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste and transform video URL among text (collapsed)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[]</p>");
|
||
pasteText(editor, `abc ${videoUrl} def`);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(0);
|
||
expect(cleanLinkArtifacts(getContent(el))).toBe(
|
||
`<p>abc <a href="${videoUrl}">${videoUrl}</a> def[]</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste and transform multiple URLs (collapsed) (1)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[]</p>");
|
||
pasteText(editor, `${url} ${videoUrl} ${imgUrl}`);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(0);
|
||
expect(cleanLinkArtifacts(getContent(el))).toBe(
|
||
`<p><a href="${url}">${url}</a> <a href="${videoUrl}">${videoUrl}</a> <a href="${imgUrl}">${imgUrl}</a>[]</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste and transform multiple URLs (collapsed) (2)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[]</p>");
|
||
pasteText(editor, `${url} abc ${videoUrl} def ${imgUrl}`);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(0);
|
||
expect(cleanLinkArtifacts(getContent(el))).toBe(
|
||
`<p><a href="${url}">${url}</a> abc <a href="${videoUrl}">${videoUrl}</a> def <a href="${imgUrl}">${imgUrl}</a>[]</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste plain text inside non empty link (collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p><a href="#">a[]b</a></p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(editor, "<span>123</span>");
|
||
},
|
||
contentAfter: '<p><a href="#">a123[]b</a></p>',
|
||
});
|
||
});
|
||
|
||
test("should paste and not transform an URL in a pre tag", async () => {
|
||
await testEditor({
|
||
contentBefore: "<pre>[]<br></pre>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "http://www.xyz.com");
|
||
},
|
||
contentAfter: "<pre>http://www.xyz.com[]</pre>",
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("range not collapsed", () => {
|
||
test("should paste and transform an URL in a p (not collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>ab[xxx]cd</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "http://www.xyz.com");
|
||
},
|
||
contentAfter: '<p>ab<a href="http://www.xyz.com">http://www.xyz.com</a>[]cd</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste and transform an URL in a span (not collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore:
|
||
'<p>a<span class="a">b[x<a href="http://existing.com">546</a>x]c</span>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "http://www.xyz.com");
|
||
},
|
||
contentAfter:
|
||
'<p>a<span class="a">b<a href="http://www.xyz.com">http://www.xyz.com</a>[]c</span>d</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste and not transform an URL in a existing link (not collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<a href="http://existing.com">b[qsdqsd]c</a>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "http://www.xyz.com");
|
||
},
|
||
contentAfter: '<p>a<a href="http://existing.com">bhttp://www.xyz.com[]c</a>d</p>',
|
||
});
|
||
});
|
||
|
||
test("should restore selection when pasting plain text followed by UNDO (1) (not collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[abc]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "def");
|
||
undo(editor);
|
||
},
|
||
contentAfter: "<p>[abc]</p>",
|
||
});
|
||
});
|
||
|
||
test("should restore selection when pasting plain text followed by UNDO (2) (not collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[abc]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "www.odoo.com");
|
||
undo(editor);
|
||
},
|
||
contentAfter: "<p>[abc]</p>",
|
||
});
|
||
});
|
||
|
||
test("should restore selection when pasting plain text followed by UNDO (3) (not collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[abc]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "def www.odoo.com xyz");
|
||
undo(editor);
|
||
},
|
||
contentAfter: "<p>[abc]</p>",
|
||
});
|
||
});
|
||
|
||
test("should restore selection after pasting HTML followed by UNDO (not collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[abc]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
'<a href="www.odoo.com">odoo.com</a><br><a href="www.google.com">google.com</a>'
|
||
);
|
||
undo(editor);
|
||
},
|
||
contentAfter: "<p>[abc]</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste and transform URL among text (not collapsed)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[xyz]</p>");
|
||
pasteText(editor, `abc ${url} def`);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(0);
|
||
expect(cleanLinkArtifacts(getContent(el))).toBe(
|
||
`<p>abc <a href="${url}">${url}</a> def[]</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste and transform image URL among text (not collapsed)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[xyz]</p>");
|
||
pasteText(editor, `abc ${imgUrl} def`);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(0);
|
||
expect(cleanLinkArtifacts(getContent(el))).toBe(
|
||
`<p>abc <a href="${imgUrl}">${imgUrl}</a> def[]</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste and transform video URL among text (not collapsed)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[xyz]</p>");
|
||
pasteText(editor, `abc ${videoUrl} def`);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(0);
|
||
expect(cleanLinkArtifacts(getContent(el))).toBe(
|
||
`<p>abc <a href="${videoUrl}">${videoUrl}</a> def[]</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste and transform multiple URLs among text (not collapsed)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[xyz]</p>");
|
||
pasteText(editor, `${url} ${videoUrl} ${imgUrl}`);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(0);
|
||
expect(cleanLinkArtifacts(getContent(el))).toBe(
|
||
`<p><a href="${url}">${url}</a> <a href="${videoUrl}">${videoUrl}</a> <a href="${imgUrl}">${imgUrl}</a>[]</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste and transform URL over the existing url", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>ab[<a href="http://www.xyz.com">http://www.xyz.com</a>]cd</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "https://www.xyz.xdc ");
|
||
},
|
||
contentAfter: '<p>ab<a href="https://www.xyz.xdc">https://www.xyz.xdc</a> []cd</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste and transform URL over the existing url (not collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>ab[<a href="http://www.xyz.com">http://www.xyz.com</a>]cd</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "https://www.xyz.xdc ");
|
||
},
|
||
contentAfter: '<p>ab<a href="https://www.xyz.xdc">https://www.xyz.xdc</a> []cd</p>',
|
||
});
|
||
});
|
||
|
||
test("should paste plain text content over a link if all of its contents is selected (not collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p>a<a href="#">[xyz]</a>d</p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "bc");
|
||
},
|
||
contentAfter: "<p>abc[]d</p>",
|
||
});
|
||
});
|
||
|
||
test("should paste and transform plain text content over a link if all of its contents is selected (not collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p><a href="#">[xyz]</a></p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "www.odoo.com");
|
||
},
|
||
contentAfter: '<p><a href="http://www.odoo.com">www.odoo.com</a>[]</p>',
|
||
});
|
||
await testEditor({
|
||
contentBefore: '<p><a href="#">[xyz]</a></p>',
|
||
stepFunction: async (editor) => {
|
||
pasteText(editor, "abc www.odoo.com xyz");
|
||
},
|
||
contentAfter: '<p>abc <a href="http://www.odoo.com">www.odoo.com</a> xyz[]</p>',
|
||
});
|
||
});
|
||
|
||
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(
|
||
`<p>ab<a href="http://www.xyz.com">[http://www.xyz.com]</a>cd</p>`
|
||
);
|
||
pasteText(editor, imgUrl);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(1);
|
||
expect(getContent(el)).toBe(
|
||
`<p>abhttps://download.odoocdn.com/icons/website/static/description/icon.png[]cd</p>`
|
||
);
|
||
await press("Enter");
|
||
expect(getContent(el)).toBe(`<p>ab<img src="${imgUrl}">[]cd</p>`);
|
||
});
|
||
|
||
test("should paste and transform plain text content over an image link if all of its contents is selected (not collapsed) (2)", async () => {
|
||
const { el, editor } = await setupEditor(
|
||
`<p>ab<a href="http://www.xyz.com">[http://www.xyz.com]</a>cd</p>`
|
||
);
|
||
pasteText(editor, imgUrl);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(1);
|
||
expect(getContent(el)).toBe(
|
||
`<p>abhttps://download.odoocdn.com/icons/website/static/description/icon.png[]cd</p>`
|
||
);
|
||
await press("ArrowDown");
|
||
await press("Enter");
|
||
expect(cleanLinkArtifacts(getContent(el))).toBe(
|
||
`<p>ab<a href="${imgUrl}">${imgUrl}</a>[]cd</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste html content over a link if all of its contents is selected (not collapsed)", async () => {
|
||
await testEditor({
|
||
contentBefore: '<p><a href="#">[xyz]</a></p>',
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
'<a href="www.odoo.com">odoo.com</a><br><a href="www.google.com">google.com</a>'
|
||
);
|
||
},
|
||
contentAfter:
|
||
'<p><a href="www.odoo.com">odoo.com</a></p><p><a href="www.google.com">google.com</a>[]</p>',
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("images", () => {
|
||
describe("range collapsed", () => {
|
||
test("should paste and transform an image URL in a p (1)", async () => {
|
||
const { el, editor } = await setupEditor("<p>ab[]cd</p>");
|
||
pasteText(editor, imgUrl);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(1);
|
||
await press("Enter");
|
||
expect(getContent(el)).toBe(`<p>ab<img src="${imgUrl}">[]cd</p>`);
|
||
});
|
||
|
||
test("should paste and transform an image URL in a span", async () => {
|
||
const { el, editor } = await setupEditor('<p>a<span class="a">b[]c</span>d</p>');
|
||
pasteText(editor, imgUrl);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(1);
|
||
await press("Enter");
|
||
expect(getContent(el)).toBe(
|
||
`<p>a<span class="a">b<img src="${imgUrl}">[]c</span>d</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste and transform an image URL in an existing link", async () => {
|
||
const { el, editor } = await setupEditor(
|
||
'<p>a<a href="http://existing.com">b[]c</a>d</p>'
|
||
);
|
||
|
||
pasteText(editor, imgUrl);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(0);
|
||
expect(cleanLinkArtifacts(getContent(el))).toBe(
|
||
`<p>a<a href="http://existing.com">b<img src="${imgUrl}">[]c</a>d</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste an image URL as a link in a p (1)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[]</p>");
|
||
|
||
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(
|
||
`<p><a href="${imgUrl}">${imgUrl}</a>[]</p>`
|
||
);
|
||
});
|
||
|
||
test("should not revert a history step when pasting an image URL as a link (1)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[]</p>");
|
||
|
||
// 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(
|
||
`<p>*should not disappear*<a href="${imgUrl}">${imgUrl}</a>[]</p>`
|
||
);
|
||
});
|
||
});
|
||
|
||
describe("range not collapsed", () => {
|
||
test("should paste and transform an image URL in a p (2)", async () => {
|
||
const { el, editor } = await setupEditor("<p>ab[xxx]cd</p>");
|
||
|
||
pasteText(editor, imgUrl);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(1);
|
||
await press("Enter");
|
||
expect(cleanLinkArtifacts(getContent(el))).toBe(`<p>ab<img src="${imgUrl}">[]cd</p>`);
|
||
});
|
||
|
||
test("should paste and transform an image URL in a span", async () => {
|
||
const { el, editor } = await setupEditor(
|
||
'<p>a<span class="a">b[x<a href="http://existing.com">546</a>x]c</span>d</p>'
|
||
);
|
||
|
||
pasteText(editor, imgUrl);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(1);
|
||
await press("Enter");
|
||
expect(getContent(el)).toBe(
|
||
`<p>a<span class="a">b<img src="${imgUrl}">[]c</span>d</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste and transform an image URL inside an existing link", async () => {
|
||
const { el, editor } = await setupEditor(
|
||
'<p>a<a href="http://existing.com">b[qsdqsd]c</a>d</p>'
|
||
);
|
||
|
||
pasteText(editor, imgUrl);
|
||
await animationFrame();
|
||
expect(".o-we-powerbox").toHaveCount(0);
|
||
expect(cleanLinkArtifacts(getContent(el))).toBe(
|
||
`<p>a<a href="http://existing.com">b<img src="${imgUrl}">[]c</a>d</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste an image URL as a link in a p (2)", async () => {
|
||
const { el, editor } = await setupEditor("<p>ab[xxx]cd</p>");
|
||
|
||
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(
|
||
`<p>ab<a href="${imgUrl}">${imgUrl}</a>[]cd</p>`
|
||
);
|
||
});
|
||
|
||
test("should not revert a history step when pasting an image URL as a link (2)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[]</p>");
|
||
|
||
// paste text (to have a history step recorded)
|
||
pasteText(editor, "abxxxcd");
|
||
// select xxx in "<p>ab[xxx]cd</p>""
|
||
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(
|
||
`<p>ab<a href="${imgUrl}">${imgUrl}</a>[]cd</p>`
|
||
);
|
||
});
|
||
|
||
test("should restore selection after pasting image URL followed by UNDO (1)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[abc]</p>");
|
||
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("<p>[abc]</p>");
|
||
});
|
||
|
||
test("should restore selection after pasting image URL followed by UNDO (2)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[abc]</p>");
|
||
|
||
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("<p>[abc]</p>");
|
||
});
|
||
});
|
||
});
|
||
|
||
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("<p>ab[]cd</p>");
|
||
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(
|
||
`<p>ab</p><div data-oe-expression="${videoUrl}" class="media_iframe_video" contenteditable="false"><div class="css_editable_mode_display"></div><div class="media_iframe_video_size" contenteditable="false"></div><iframe frameborder="0" contenteditable="false" allowfullscreen="allowfullscreen" src="${videoUrl}"></iframe></div><p>[]cd</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste and transform a youtube URL in a span (1)", async () => {
|
||
const { el, editor } = await setupEditor('<p>a<span class="a">b[]c</span>d</p>');
|
||
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(
|
||
'<p>a<span class="a">b</span></p><div data-oe-expression="https://youtu.be/dQw4w9WgXcQ" class="media_iframe_video" contenteditable="false"><div class="css_editable_mode_display"></div><div class="media_iframe_video_size" contenteditable="false"></div><iframe frameborder="0" contenteditable="false" allowfullscreen="allowfullscreen" src="https://youtu.be/dQw4w9WgXcQ"></iframe></div><p><span class="a">[]c</span>d</p>'
|
||
);
|
||
});
|
||
|
||
test("should paste and not transform a youtube URL in a existing link", async () => {
|
||
const { el, editor, plugins } = await setupEditor(
|
||
'<p>a<a href="http://existing.com">b[]c</a>d</p>'
|
||
);
|
||
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(
|
||
'<p>a<a href="http://existing.com">bhttps://youtu.be/dQw4w9WgXcQ[]c</a>d</p>'
|
||
);
|
||
});
|
||
|
||
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("<p>[]</p>");
|
||
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(`<p><a href="${url}">${url}</a>[]</p>`);
|
||
});
|
||
|
||
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("<p>[]</p>");
|
||
// 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(
|
||
`<p>*should not disappear*<a href="${url}">${url}</a>[]</p>`
|
||
);
|
||
});
|
||
});
|
||
|
||
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("<p>ab[xxx]cd</p>");
|
||
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(
|
||
'<p>ab</p><div data-oe-expression="https://youtu.be/dQw4w9WgXcQ" class="media_iframe_video" contenteditable="false"><div class="css_editable_mode_display"></div><div class="media_iframe_video_size" contenteditable="false"></div><iframe frameborder="0" contenteditable="false" allowfullscreen="allowfullscreen" src="https://youtu.be/dQw4w9WgXcQ"></iframe></div><p>[]cd</p>'
|
||
);
|
||
});
|
||
|
||
test("should paste and transform a youtube URL in a span (2)", async () => {
|
||
const { el, editor } = await setupEditor(
|
||
'<p>a<span class="a">b[x<a href="http://existing.com">546</a>x]c</span>d</p>'
|
||
);
|
||
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(
|
||
`<p>a<span class="a">b</span></p><div data-oe-expression="${videoUrl}" class="media_iframe_video" contenteditable="false"><div class="css_editable_mode_display"></div><div class="media_iframe_video_size" contenteditable="false"></div><iframe frameborder="0" contenteditable="false" allowfullscreen="allowfullscreen" src="${videoUrl}"></iframe></div><p><span class="a">[]c</span>d</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste and not transform a youtube URL in a existing link", async () => {
|
||
const { el, editor, plugins } = await setupEditor(
|
||
'<p>a<a href="http://existing.com">b[qsdqsd]c</a>d</p>'
|
||
);
|
||
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(
|
||
`<p>a<a href="http://existing.com">b${videoUrl}[]c</a>d</p>`
|
||
);
|
||
});
|
||
|
||
test("should paste a youtube URL as a link in a p (2)", async () => {
|
||
const { el, editor } = await setupEditor("<p>ab[xxx]cd</p>");
|
||
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(
|
||
`<p>ab<a href="${videoUrl}">${videoUrl}</a>[]cd</p>`
|
||
);
|
||
});
|
||
|
||
test("should not revert a history step when pasting a youtube URL as a link (2)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[]</p>");
|
||
// paste text (to have a history step recorded)
|
||
pasteText(editor, "abxxxcd");
|
||
// select xxx in "<p>ab[xxx]cd</p>"
|
||
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(
|
||
`<p>ab<a href="${videoUrl}">${videoUrl}</a>[]cd</p>`
|
||
);
|
||
});
|
||
|
||
test("should restore selection after pasting video URL followed by UNDO (1)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[abc]</p>");
|
||
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("<p>[abc]</p>");
|
||
});
|
||
|
||
test("should restore selection after pasting video URL followed by UNDO (2)", async () => {
|
||
const { el, editor } = await setupEditor("<p>[abc]</p>");
|
||
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("<p>[abc]</p>");
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("Odoo editor own html", () => {
|
||
test("should paste html as is", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>a[]b</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteOdooEditorHtml(editor, '<div class="custom-paste">b</div>');
|
||
},
|
||
contentAfter: '<p>a</p><div class="custom-paste">b</div><p>[]b</p>',
|
||
});
|
||
});
|
||
|
||
test("should not paste unsafe content", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>a[]b</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteOdooEditorHtml(editor, `<script>console.log('xss attack')</script>`);
|
||
},
|
||
contentAfter: "<p>a[]b</p>",
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("editable in iframe", () => {
|
||
test("should paste odoo-editor html", async () => {
|
||
const { el, editor } = await setupEditor("<p>[]</p>", { props: { iframe: true } });
|
||
pasteOdooEditorHtml(editor, `<p>text<b>bold text</b>more text</p>`);
|
||
expect(getContent(el)).toBe("<p>text<b>bold text</b>more text[]</p>");
|
||
});
|
||
});
|
||
|
||
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: "<p>[]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
`<div ccp_infra_version='3' ccp_infra_timestamp='1684505961078' ccp_infra_user_hash='540904553' ccp_infra_copy_id=''
|
||
data-ccp-timestamp='1684505961078'>
|
||
<html>
|
||
|
||
<head>
|
||
<meta http-equiv=Content-Type content="text/html; charset=utf-8">
|
||
<meta name=ProgId content=Excel.Sheet>
|
||
<meta name=Generator content="Microsoft Excel 15">
|
||
<style>
|
||
table {
|
||
mso-displayed-decimal-separator: "\\,";
|
||
mso-displayed-thousand-separator: "\\.";
|
||
}
|
||
|
||
tr {
|
||
mso-height-source: auto;
|
||
}
|
||
|
||
col {
|
||
mso-width-source: auto;
|
||
}
|
||
|
||
td {
|
||
padding-top: 1px;
|
||
padding-right: 1px;
|
||
padding-left: 1px;
|
||
mso-ignore: padding;
|
||
color: black;
|
||
font-size: 11.0pt;
|
||
font-weight: 400;
|
||
font-style: normal;
|
||
text-decoration: none;
|
||
font-family: Calibri, sans-serif;
|
||
mso-font-charset: 0;
|
||
text-align: general;
|
||
vertical-align: bottom;
|
||
border: none;
|
||
white-space: nowrap;
|
||
mso-rotate: 0;
|
||
}
|
||
|
||
.font12 {
|
||
color: #495057;
|
||
font-size: 10.0pt;
|
||
font-weight: 400;
|
||
font-style: italic;
|
||
text-decoration: none;
|
||
font-family: "Odoo Unicode Support Noto";
|
||
mso-generic-font-family: auto;
|
||
mso-font-charset: 0;
|
||
}
|
||
|
||
.font13 {
|
||
color: #495057;
|
||
font-size: 10.0pt;
|
||
font-weight: 700;
|
||
font-style: italic;
|
||
text-decoration: none;
|
||
font-family: "Odoo Unicode Support Noto";
|
||
mso-generic-font-family: auto;
|
||
mso-font-charset: 0;
|
||
}
|
||
|
||
.font33 {
|
||
color: #495057;
|
||
font-size: 10.0pt;
|
||
font-weight: 700;
|
||
font-style: normal;
|
||
text-decoration: none;
|
||
font-family: "Odoo Unicode Support Noto";
|
||
mso-generic-font-family: auto;
|
||
mso-font-charset: 0;
|
||
}
|
||
|
||
.xl87 {
|
||
font-size: 14.0pt;
|
||
font-family: "Roboto Mono";
|
||
mso-generic-font-family: auto;
|
||
mso-font-charset: 1;
|
||
text-align: center;
|
||
}
|
||
|
||
.xl88 {
|
||
color: #495057;
|
||
font-size: 10.0pt;
|
||
font-style: italic;
|
||
font-family: "Odoo Unicode Support Noto";
|
||
mso-generic-font-family: auto;
|
||
mso-font-charset: 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.xl89 {
|
||
color: #495057;
|
||
font-size: 10.0pt;
|
||
font-style: italic;
|
||
font-family: Arial;
|
||
mso-generic-font-family: auto;
|
||
mso-font-charset: 1;
|
||
text-align: center;
|
||
}
|
||
|
||
.xl90 {
|
||
color: #495057;
|
||
font-size: 10.0pt;
|
||
font-weight: 700;
|
||
font-family: "Odoo Unicode Support Noto";
|
||
mso-generic-font-family: auto;
|
||
mso-font-charset: 0;
|
||
text-align: center;
|
||
}
|
||
|
||
.xl91 {
|
||
color: #495057;
|
||
font-size: 10.0pt;
|
||
font-weight: 700;
|
||
text-decoration: underline;
|
||
text-underline-style: single;
|
||
font-family: Arial;
|
||
mso-generic-font-family: auto;
|
||
mso-font-charset: 1;
|
||
text-align: center;
|
||
}
|
||
|
||
.xl92 {
|
||
color: red;
|
||
font-size: 10.0pt;
|
||
font-family: Arial;
|
||
mso-generic-font-family: auto;
|
||
mso-font-charset: 1;
|
||
text-align: center;
|
||
}
|
||
|
||
.xl93 {
|
||
color: red;
|
||
font-size: 10.0pt;
|
||
text-decoration: underline;
|
||
text-underline-style: single;
|
||
font-family: Arial;
|
||
mso-generic-font-family: auto;
|
||
mso-font-charset: 1;
|
||
text-align: center;
|
||
}
|
||
|
||
.xl94 {
|
||
color: #495057;
|
||
font-size: 10.0pt;
|
||
font-family: "Odoo Unicode Support Noto";
|
||
mso-generic-font-family: auto;
|
||
mso-font-charset: 1;
|
||
text-align: center;
|
||
background: yellow;
|
||
mso-pattern: black none;
|
||
}
|
||
|
||
.xl95 {
|
||
color: red;
|
||
font-size: 10.0pt;
|
||
font-family: Arial;
|
||
mso-generic-font-family: auto;
|
||
mso-font-charset: 1;
|
||
text-align: center;
|
||
background: yellow;
|
||
mso-pattern: black none;
|
||
white-space: normal;
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body link="#0563C1" vlink="#954F72">
|
||
<table width=398 style='border-collapse:collapse;width:299pt'><!--StartFragment-->
|
||
<col width=187 style='width:140pt'>
|
||
<col width=211 style='width:158pt'>
|
||
<tr height=20 style='height:15.0pt'>
|
||
<td width=187 height=20 class=xl88 dir=LTR style='width:140pt;height:15.0pt'><span class=font12>Italic
|
||
then also </span><span class=font13>BOLD</span></td>
|
||
<td width=211 class=xl89 dir=LTR style='width:158pt'><s>Italic strike</s></td>
|
||
</tr>
|
||
<tr height=20 style='height:15.0pt'>
|
||
<td height=20 class=xl90 dir=LTR style='height:15.0pt'><span class=font33>Just bold </span><span
|
||
class=font12>Just Italic</span></td>
|
||
<td class=xl91 dir=LTR>Bold underline</td>
|
||
</tr>
|
||
<tr height=20 style='height:15.0pt'>
|
||
<td height=20 class=xl92 dir=LTR style='height:15.0pt'>Color text</td>
|
||
<td class=xl93 dir=LTR><s>Color strike and underline</s></td>
|
||
</tr>
|
||
<tr height=20 style='height:15.0pt'>
|
||
<td height=20 class=xl94 dir=LTR style='height:15.0pt'>Color background</td>
|
||
<td width=211 class=xl95 dir=LTR style='width:158pt'>Color text on color background</td>
|
||
</tr>
|
||
<tr height=27 style='height:20.25pt'>
|
||
<td colspan=2 width=398 height=27 class=xl87 dir=LTR style='width:299pt;height:20.25pt'>14pt MONO TEXT
|
||
</td>
|
||
</tr><!--EndFragment-->
|
||
</table>
|
||
</body>
|
||
|
||
</html>
|
||
</div>`
|
||
);
|
||
},
|
||
contentAfter: `<table class="table table-bordered">
|
||
${" "}
|
||
${" "}
|
||
<tbody><tr>
|
||
<td>Italic
|
||
then also BOLD</td>
|
||
<td><s>Italic strike</s></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Just bold Just Italic</td>
|
||
<td>Bold underline</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Color text</td>
|
||
<td><s>Color strike and underline</s></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Color background</td>
|
||
<td>Color text on color background</td>
|
||
</tr>
|
||
<tr>
|
||
<td>14pt MONO TEXT
|
||
</td>
|
||
</tr>
|
||
</tbody></table><p>
|
||
${" "}
|
||
|
||
${" "}
|
||
[]</p>`,
|
||
});
|
||
});
|
||
|
||
test("should keep all allowed style (Google Sheets)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
`<google-sheets-html-origin>
|
||
<style type="text/css">
|
||
td {
|
||
border: 1px solid #cccccc;
|
||
}
|
||
|
||
br {
|
||
mso-data-placement: same-cell;
|
||
}
|
||
</style>
|
||
<table xmlns="http://www.w3.org/1999/xhtml" cellspacing="0" cellpadding="0" dir="ltr" border="1"
|
||
style="table-layout:fixed;font-size:10pt;font-family:Arial;width:0px;border-collapse:collapse;border:none">
|
||
<colgroup>
|
||
<col width="170" />
|
||
<col width="187" />
|
||
</colgroup>
|
||
<tbody>
|
||
<tr style="height:21px;">
|
||
<td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-family:Odoo Unicode Support Noto;font-weight:normal;font-style:italic;color:#495057;"
|
||
data-sheets-value="{"1":2,"2":"Italic then also BOLD"}"
|
||
data-sheets-textstyleruns="{"1":0,"2":{"3":"Arial"}}{"1":17,"2":{"3":"Arial","5":1}}">
|
||
<span style="font-size:10pt;font-family:Arial;font-style:italic;color:#495057;">Italic then also
|
||
</span><span
|
||
style="font-size:10pt;font-family:Arial;font-weight:bold;font-style:italic;color:#495057;">BOLD</span>
|
||
</td>
|
||
<td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-style:italic;text-decoration:line-through;color:#495057;"
|
||
data-sheets-value="{"1":2,"2":"Italic strike"}">Italic strike</td>
|
||
</tr>
|
||
<tr style="height:21px;">
|
||
<td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-family:Odoo Unicode Support Noto;font-weight:bold;color:#495057;"
|
||
data-sheets-value="{"1":2,"2":"Just bold Just italic"}"
|
||
data-sheets-textstyleruns="{"1":0,"2":{"3":"Arial"}}{"1":10,"2":{"3":"Arial","5":0,"6":1}}">
|
||
<span
|
||
style="font-size:10pt;font-family:Arial;font-weight:bold;font-style:normal;color:#495057;">Just
|
||
Bold </span><span style="font-size:10pt;font-family:Arial;font-style:italic;color:#495057;">Just
|
||
Italic</span>
|
||
</td>
|
||
<td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-weight:bold;text-decoration:underline;color:#495057;"
|
||
data-sheets-value="{"1":2,"2":"Bold underline"}">Bold underline</td>
|
||
</tr>
|
||
<tr style="height:21px;">
|
||
<td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;"
|
||
data-sheets-value="{"1":2,"2":"Color text"}"><span style="color:#ff0000;">Color text</span></td>
|
||
<td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;text-decoration:underline line-through;color:#ff0000;"
|
||
data-sheets-value="{"1":2,"2":"Color strike and underline"}">Color
|
||
strike and underline</td>
|
||
</tr>
|
||
<tr style="height:21px;">
|
||
<td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;background-color:#ffff00;font-family:Odoo Unicode Support Noto;font-weight:normal;color:#495057;"
|
||
data-sheets-value="{"1":2,"2":"Color background"}">Color background
|
||
</td>
|
||
<td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;background-color:#ffff00;color:#ff0000;"
|
||
data-sheets-value="{"1":2,"2":"Color text on color background"}">Color
|
||
text on color background</td>
|
||
</tr>
|
||
<tr style="height:21px;">
|
||
<td style="overflow:hidden;padding:2px 3px 2px 3px;vertical-align:bottom;font-family:Roboto Mono;font-size:14pt;font-weight:normal;text-align:center;"
|
||
rowspan="1" colspan="2"
|
||
data-sheets-value="{"1":2,"2":"14pt MONO TEXT"}">14pt MONO TEXT</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</google-sheets-html-origin>`
|
||
);
|
||
},
|
||
contentAfter: `<table class="table table-bordered">
|
||
${" "}
|
||
${" "}
|
||
${" "}
|
||
${" "}
|
||
<tbody>
|
||
<tr>
|
||
<td>
|
||
Italic then also
|
||
BOLD
|
||
</td>
|
||
<td>Italic strike</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
Just
|
||
Bold Just
|
||
Italic
|
||
</td>
|
||
<td>Bold underline</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Color text</td>
|
||
<td>Color
|
||
strike and underline</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Color background
|
||
</td>
|
||
<td>Color
|
||
text on color background</td>
|
||
</tr>
|
||
<tr>
|
||
<td>14pt MONO TEXT</td>
|
||
</tr>
|
||
</tbody>
|
||
</table><p>
|
||
[]</p>`,
|
||
});
|
||
});
|
||
|
||
test("should keep all allowed style (Libre Office)", async () => {
|
||
await testEditor({
|
||
contentBefore: "<p>[]</p>",
|
||
stepFunction: async (editor) => {
|
||
pasteHtml(
|
||
editor,
|
||
`<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||
<html>
|
||
|
||
<head>
|
||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||
<title></title>
|
||
<meta name="generator" content="LibreOffice 6.4.7.2 (Linux)" />
|
||
<style type="text/css">
|
||
body,
|
||
div,
|
||
table,
|
||
thead,
|
||
tbody,
|
||
tfoot,
|
||
tr,
|
||
th,
|
||
td,
|
||
p {
|
||
font-family: "Arial";
|
||
font-size: x-small
|
||
}
|
||
|
||
a.comment-indicator:hover+comment {
|
||
background: #ffd;
|
||
position: absolute;
|
||
display: block;
|
||
border: 1px solid black;
|
||
padding: 0.5em;
|
||
}
|
||
|
||
a.comment-indicator {
|
||
background: red;
|
||
display: inline-block;
|
||
border: 1px solid black;
|
||
width: 0.5em;
|
||
height: 0.5em;
|
||
}
|
||
|
||
comment {
|
||
display: none;
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
<table cellspacing="0" border="0">
|
||
<colgroup width="212"></colgroup>
|
||
<colgroup width="209"></colgroup>
|
||
<tr>
|
||
<td style="border-top: 1px solid #000000; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: 1px solid #000000"
|
||
height="20" align="left"><i>Italic then also BOLD</i></td>
|
||
<td style="border-top: 1px solid #000000; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: 1px solid #000000"
|
||
align="left"><i><s>Italic strike</s></i></td>
|
||
</tr>
|
||
<tr>
|
||
<td style="border-top: 1px solid #000000; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: 1px solid #000000"
|
||
height="20" align="left"><b>Just bold Just italic</b></td>
|
||
<td style="border-top: 1px solid #000000; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: 1px solid #000000"
|
||
align="left"><b><u>Bold underline</u></b></td>
|
||
</tr>
|
||
<tr>
|
||
<td style="border-top: 1px solid #000000; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: 1px solid #000000"
|
||
height="20" align="left">
|
||
<font color="#FF0000">Color text</font>
|
||
</td>
|
||
<td style="border-top: 1px solid #000000; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: 1px solid #000000"
|
||
align="left"><u><s>
|
||
<font color="#FF0000">Color strike and underline</font>
|
||
</s></u></td>
|
||
</tr>
|
||
<tr>
|
||
<td style="border-top: 1px solid #000000; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: 1px solid #000000"
|
||
height="20" align="left" bgcolor="#FFFF00">Color background</td>
|
||
<td style="border-top: 1px solid #000000; border-bottom: 1px solid #000000; border-left: 1px solid #000000; border-right: 1px solid #000000"
|
||
align="left" bgcolor="#FFFF00">
|
||
<font color="#FF0000">Color text on color background</font>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td colspan=2 height="26" align="center" valign=middle>
|
||
<font face="Andale Mono" size=4>14pt MONO TEXT</font>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</body>
|
||
|
||
</html>`
|
||
);
|
||
},
|
||
contentAfter: `<table class="table table-bordered">
|
||
${" "}
|
||
${" "}
|
||
<tbody><tr>
|
||
<td><i>Italic then also BOLD</i></td>
|
||
<td><i><s>Italic strike</s></i></td>
|
||
</tr>
|
||
<tr>
|
||
<td><b>Just bold Just italic</b></td>
|
||
<td><b><u>Bold underline</u></b></td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
Color text
|
||
</td>
|
||
<td><u><s>
|
||
Color strike and underline
|
||
</s></u></td>
|
||
</tr>
|
||
<tr>
|
||
<td>Color background</td>
|
||
<td>
|
||
Color text on color background
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
14pt MONO TEXT
|
||
</td>
|
||
</tr>
|
||
</tbody></table><p>
|
||
|
||
|
||
[]</p>`,
|
||
});
|
||
});
|
||
});
|
||
|
||
describe("onDrop", () => {
|
||
test("should add text from htmlTransferItem", async () => {
|
||
const { el } = await setupEditor("<p>a[b]cd</p>");
|
||
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("<p>abcb[]d</p>");
|
||
});
|
||
test("should not be able to paste inside some branded node", async () => {
|
||
const { el } = await setupEditor(`<p data-oe-model="foo" data-oe-type="text">a[b]cd</p>`);
|
||
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(`<p data-oe-model="foo" data-oe-type="text">a[b]cd</p>`);
|
||
});
|
||
test("should add new images form fileTransferItems", async () => {
|
||
const { el } = await setupEditor(`<p>a[b]cd</p>`);
|
||
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(
|
||
`<p>abc<img class="img-fluid" data-file-name="image.png" src="${base64Image}">[]d</p>`
|
||
);
|
||
});
|
||
test("should move an image if it originated from the editor", async () => {
|
||
const base64Image =
|
||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=";
|
||
|
||
const { el } = await setupEditor(
|
||
`<p>a[<img class="img-fluid" data-file-name="image.png" src="${base64Image}">]bc</p>`
|
||
);
|
||
const pElement = el.firstChild;
|
||
const imgElement = pElement.childNodes[1];
|
||
const bcTextNode = pElement.childNodes[2];
|
||
|
||
patchWithCleanup(document, {
|
||
caretPositionFromPoint: () => ({ offsetNode: bcTextNode, offset: 1 }),
|
||
});
|
||
|
||
const dragdata = new DataTransfer();
|
||
await dispatch(imgElement, "dragstart", { dataTransfer: dragdata });
|
||
await animationFrame();
|
||
const imageHTML = dragdata.getData("application/vnd.odoo.odoo-editor-node");
|
||
expect(imageHTML).toBe(
|
||
`<img class="img-fluid" data-file-name="image.png" src="${base64Image}">`
|
||
);
|
||
|
||
const dropData = new DataTransfer();
|
||
dropData.setData(
|
||
"text/html",
|
||
`<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"><img src="${base64Image}">`
|
||
);
|
||
// Simulate the application/vnd.odoo.odoo-editor-node data that the browser would do.
|
||
dropData.setData("application/vnd.odoo.odoo-editor-node", imageHTML);
|
||
await dispatch(pElement, "drop", { dataTransfer: dropData });
|
||
await animationFrame();
|
||
|
||
expect(getContent(el)).toBe(
|
||
`<p>ab<img class="img-fluid" data-file-name="image.png" src="${base64Image}">[]c</p>`
|
||
);
|
||
});
|
||
});
|
||
|
||
function dataURItoBlob(dataURI) {
|
||
const binary = atob(dataURI.split(",")[1]);
|
||
const array = [];
|
||
const mimeString = dataURI.split(",")[0].split(":")[1].split(";")[0];
|
||
for (let i = 0; i < binary.length; i++) {
|
||
array.push(binary.charCodeAt(i));
|
||
}
|
||
return new Blob([new Uint8Array(array)], { type: mimeString });
|
||
}
|