import { describe, expect, test } from "@odoo/hoot"; import { click, fill, getActiveElement, press, queryAllTexts, queryFirst, queryOne, queryText, select, waitFor, waitUntil, } from "@odoo/hoot-dom"; import { animationFrame, tick } from "@odoo/hoot-mock"; import { markup } from "@odoo/owl"; import { contains, onRpc } from "@web/../tests/web_test_helpers"; import { setupEditor } from "../_helpers/editor"; import { cleanLinkArtifacts } from "../_helpers/format"; import { getContent, setContent, setSelection } from "../_helpers/selection"; import { insertLineBreak, insertText, splitBlock, undo } from "../_helpers/user_actions"; const base64Img = "data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA\n AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO\n 9TXL0Y4OHwAAAABJRU5ErkJggg=="; describe("should open a popover", () => { test("should open a popover when the selection is inside a link and close outside of a link", async () => { const { el } = await setupEditor("

this is a link

"); expect(".o-we-linkpopover").toHaveCount(0); // selection inside a link setContent(el, "

this is a li[]nk

"); await waitFor(".o-we-linkpopover"); expect(".o-we-linkpopover").toHaveCount(1); // selection outside a link setContent(el, "

this []is a link

"); await waitUntil(() => !queryFirst(".o-we-linkpopover"), { timeout: 1500 }); expect(".o-we-linkpopover").toHaveCount(0); }); test("link popover should have input field for href when the link doesn't have href", async () => { await setupEditor("

this is a li[]nk

"); await waitFor(".o-we-linkpopover"); expect(".o-we-linkpopover").toHaveCount(1); expect(".o_we_label_link").toHaveValue("link"); expect(".o_we_href_input_link").toHaveValue(""); }); test("link popover should have buttons for link operation when the link has href", async () => { await setupEditor('

this is a li[]nk

'); await waitFor(".o-we-linkpopover"); expect(".o-we-linkpopover").toHaveCount(1); expect(".o_we_copy_link").toHaveCount(1); expect(".o_we_edit_link").toHaveCount(1); expect(".o_we_remove_link").toHaveCount(1); }); test("link popover should not repositioned when clicking in the input field", async () => { await setupEditor("

this is a li[]nk

"); await waitFor(".o_we_href_input_link"); const style = queryOne(".o-we-linkpopover").parentElement.style.cssText; queryOne(".o_we_href_input_link").focus(); await animationFrame(); expect(queryOne(".o-we-linkpopover").parentElement).toHaveAttribute("style", style); }); }); describe("popover should switch UI depending on editing state", () => { test("after clicking on edit button, the popover should switch to editing mode", async () => { await setupEditor('

this is a li[]nk

'); await waitFor(".o-we-linkpopover"); await click(".o_we_edit_link"); await waitFor(".o_we_href_input_link"); expect(".o_we_label_link").toHaveValue("link"); expect(".o_we_href_input_link").toHaveValue("http://test.com/"); }); test("after clicking on apply button, the selection should be restored", async () => { const { el } = await setupEditor('

this is a li[]nk

'); await waitFor(".o-we-linkpopover"); await click(".o_we_edit_link"); await waitFor(".o_we_href_input_link"); await click(".o_we_href_input_link"); await click(".o_we_apply_link"); await waitFor(".o_we_edit_link"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

this is a li[]nk

' ); }); test("after editing the URL and clicking on apply button, the selection should be restored", async () => { const { el } = await setupEditor('

this is a li[]nk

'); await waitFor(".o-we-linkpopover"); await click(".o_we_edit_link"); await waitFor(".o_we_href_input_link"); // This changes the selection to outside the editor. await click(".o_we_href_input_link"); await tick(); await press("a"); await click(".o_we_apply_link"); await waitFor(".o_we_edit_link"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

this is a li[]nk

' ); }); test("after clicking on apply button, the popover should be with the non editing mode, e.g. with three buttons", async () => { await setupEditor("

this is a li[]nk

"); await waitFor(".o-we-linkpopover"); await click(".o_we_href_input_link"); await click(".o_we_apply_link"); await waitFor(".o_we_edit_link"); expect(".o-we-linkpopover").toHaveCount(1); expect(".o_we_copy_link").toHaveCount(1); expect(".o_we_edit_link").toHaveCount(1); expect(".o_we_remove_link").toHaveCount(1); }); }); describe("popover should edit,copy,remove the link", () => { test("after apply url on a link without href, the link element should be updated", async () => { const { el } = await setupEditor("

this is a li[]nk

"); await contains(".o-we-linkpopover input.o_we_href_input_link").edit("http://test.com/"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

this is a li[]nk

' ); }); test("after clicking on remove button, the link element should be unwrapped", async () => { const { el } = await setupEditor('

this is a li[]nk

'); await waitFor(".o-we-linkpopover"); await click(".o_we_remove_link"); await waitUntil(() => !queryFirst(".o-we-linkpopover"), { timeout: 1500 }); expect(getContent(el)).toBe("

this is a li[]nk

"); }); test("after edit the label, the text of the link should be updated", async () => { const { el } = await setupEditor('

this is a li[]nk

'); await waitFor(".o-we-linkpopover"); await click(".o_we_edit_link"); await contains(".o-we-linkpopover input.o_we_label_link").fill("new"); await click(".o_we_apply_link"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

this is a linknew[]

' ); }); test("when the label is empty, it should be set as the URL", async () => { const { el } = await setupEditor('

this is a li[]nk

'); await waitFor(".o-we-linkpopover"); await click(".o_we_edit_link"); await waitFor(".o_we_apply_link"); await contains(".o-we-linkpopover input.o_we_label_link").clear(); await click(".o_we_apply_link"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

this is a http://test.com/[]

' ); }); test("after clicking on copy button, the url should be copied to clipboard", async () => { await setupEditor('

this is a li[]nk

'); await waitFor(".o-we-linkpopover"); await click(".o_we_copy_link"); await waitFor(".o_notification_bar.bg-success"); const notifications = queryAllTexts(".o_notification_body"); expect(notifications).toInclude("Link copied to clipboard."); await animationFrame(); expect(".o-we-linkpopover").toHaveCount(0); await expect(navigator.clipboard.readText()).resolves.toBe("http://test.com/"); }); test("when edit a link's label and URL to '', the link should be removed", async () => { const { el } = await setupEditor('

this is a li[]nk

'); await waitFor(".o-we-linkpopover"); await click(".o_we_edit_link"); await contains(".o-we-linkpopover input.o_we_label_link").clear(); await contains(".o-we-linkpopover input.o_we_href_input_link").clear(); expect(getContent(el)).toBe("

this is a []

"); }); }); describe("Incorrect URL should be corrected", () => { test("when edit a link's URL to 'test.com', the link's URL should be corrected", async () => { const { el } = await setupEditor('

this is a li[]nk

'); await waitFor(".o-we-linkpopover"); await click(".o_we_edit_link"); await contains(".o-we-linkpopover input.o_we_href_input_link").edit("newtest.com"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

this is a li[]nk

' ); }); test("when a link's URL is an email, the link's URL should start with mailto:", async () => { const { el } = await setupEditor("

this is a li[]nk

"); await contains(".o-we-linkpopover input.o_we_href_input_link").edit("test@test.com"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

this is a li[]nk

' ); }); test("when a link's URL is an phonenumber, the link's URL should start with tel://:", async () => { const { el } = await setupEditor("

this is a li[]nk

"); await contains(".o-we-linkpopover input.o_we_href_input_link").edit("+1234567890"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

this is a li[]nk

' ); }); }); describe("Link creation", () => { describe("Creation by space", () => { test("typing valid URL + space should convert to link", async () => { const { editor, el } = await setupEditor("

[]

"); await insertText(editor, "http://google.co.in"); await insertText(editor, " "); expect(cleanLinkArtifacts(getContent(el))).toBe( '

http://google.co.in []

' ); }); test("typing invalid URL + space should not convert to link", async () => { const { editor, el } = await setupEditor("

[]

"); await insertText(editor, "www.odoo"); await insertText(editor, " "); expect(cleanLinkArtifacts(getContent(el))).toBe("

www.odoo []

"); }); }); describe("Creation by powerbox", () => { test("click on link command in powerbox should create a link element and open the linkpopover", async () => { const { editor, el } = await setupEditor("

ab[]

"); await insertText(editor, "/link"); await animationFrame(); expect(".active .o-we-command-name").toHaveText("Link"); await click(".o-we-command-name:first"); expect(cleanLinkArtifacts(getContent(el))).toBe("

ab[]

"); await waitFor(".o-we-linkpopover"); expect(".o-we-linkpopover").toHaveCount(1); expect(getActiveElement()).toBe(queryOne(".o-we-linkpopover input.o_we_label_link"), { message: "should focus label input by default, when we don't have a label", }); }); test("when create a new link by powerbox and not input anything, the link should be removed", async () => { const { editor, el } = await setupEditor("

ab[]

"); await insertText(editor, "/link"); await animationFrame(); await click(".o-we-command-name:first"); await waitFor(".o-we-linkpopover"); expect(".o-we-linkpopover").toHaveCount(1); expect(cleanLinkArtifacts(getContent(el))).toBe("

ab

"); const pNode = queryOne("p"); setSelection({ anchorNode: pNode, anchorOffset: 0, focusNode: pNode, focusOffset: 0, }); await animationFrame(); expect(cleanLinkArtifacts(getContent(el))).toBe("

[]ab

"); }); test("when create a new link by powerbox and only input the URL, the link should be created with corrected URL", async () => { const { editor, el } = await setupEditor("

ab[]

"); await insertText(editor, "/link"); await animationFrame(); await click(".o-we-command-name:first"); await contains(".o-we-linkpopover input.o_we_href_input_link").fill("test.com"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

abtest.com[]

' ); }); test("Should be able to insert link on empty p", async () => { const { editor, el } = await setupEditor("

[]

"); await insertText(editor, "/link"); await animationFrame(); await click(".o-we-command-name:first"); await contains(".o-we-linkpopover input.o_we_href_input_link").fill("#"); expect(cleanLinkArtifacts(getContent(el))).toBe('

#[]

'); }); test("should insert a link and preserve spacing", async () => { const { editor, el } = await setupEditor("

a []  b

"); await insertText(editor, "/link"); await animationFrame(); await click(".o-we-command-name:first"); await contains(".o-we-linkpopover input.o_we_label_link").fill("link"); await contains(".o-we-linkpopover input.o_we_href_input_link").fill("#"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

a link[]  b

' ); }); test("should insert a link then create a new

", async () => { const { editor, el } = await setupEditor("

ab[]

"); await insertText(editor, "/link"); await animationFrame(); await click(".o-we-command-name:first"); await contains(".o-we-linkpopover input.o_we_label_link").fill("link"); await contains(".o-we-linkpopover input.o_we_href_input_link").fill("#"); // press("Enter"); splitBlock(editor); expect(cleanLinkArtifacts(getContent(el))).toBe( `

ablink

[]

` ); }); test("should insert a link then create a new

, and another character", async () => { const { editor, el } = await setupEditor("

a[]b

"); await insertText(editor, "/link"); await animationFrame(); await click(".o-we-command-name:first"); await contains(".o-we-linkpopover input.o_we_label_link").fill("link"); await contains(".o-we-linkpopover input.o_we_href_input_link").fill("#"); // press("Enter"); splitBlock(editor); await insertText(editor, "D"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

alink

D[]b

' ); }); test("should insert a link then insert a
", async () => { const { editor, el } = await setupEditor("

a[]b

"); await insertText(editor, "/link"); await animationFrame(); await click(".o-we-command-name:first"); await contains(".o-we-linkpopover input.o_we_label_link").fill("link"); await contains(".o-we-linkpopover input.o_we_href_input_link").fill("#"); // press("Enter"); insertLineBreak(editor); expect(cleanLinkArtifacts(getContent(el))).toBe('

alink
[]b

'); }); test("should insert a link then insert a
and another character", async () => { const { editor, el } = await setupEditor("

a[]b

"); await insertText(editor, "/link"); await animationFrame(); await click(".o-we-command-name:first"); await contains(".o-we-linkpopover input.o_we_label_link").fill("link"); await contains(".o-we-linkpopover input.o_we_href_input_link").fill("#"); // press("Enter"); insertLineBreak(editor); await insertText(editor, "D"); expect(cleanLinkArtifacts(getContent(el))).toBe('

alink
D[]b

'); }); }); describe("Creation by toolbar", () => { test("should convert all selected text to link", async () => { const { el } = await setupEditor("

[Hello]

"); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-link"); await contains(".o-we-linkpopover input.o_we_href_input_link", { timeout: 1500 }).edit( "#" ); expect(cleanLinkArtifacts(getContent(el))).toBe('

Hello[]

'); }); test("should set the link on two existing characters", async () => { const { el } = await setupEditor("

H[el]lo

"); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-link"); await contains(".o-we-linkpopover input.o_we_href_input_link", { timeout: 1500 }).edit( "#" ); expect(cleanLinkArtifacts(getContent(el))).toBe('

Hel[]lo

'); }); test("when you open link popover with a label, url input should be focus by default ", async () => { const { el } = await setupEditor("

[Hello]

"); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-link"); await waitFor(".o-we-linkpopover", { timeout: 1500 }); expect(getActiveElement()).toBe( queryOne(".o-we-linkpopover input.o_we_href_input_link") ); await fill("test.com"); await click(".o_we_apply_link"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

Hello[]

' ); }); test("should be correctly unlink/link", async () => { const { el } = await setupEditor('

aaaa[bcde]f

'); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-unlink"); await tick(); expect(cleanLinkArtifacts(getContent(el))).toBe("

aaaa[bcde]f

"); const pNode = queryOne("p"); setSelection({ anchorNode: pNode.childNodes[1], anchorOffset: 1, focusNode: pNode.childNodes[0], focusOffset: 3, }); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-link"); await contains(".o-we-linkpopover input.o_we_href_input_link", { timeout: 1500 }).edit( "#" ); expect(cleanLinkArtifacts(getContent(el))).toBe('

aaaab[]cdef

'); }); test("should remove link when click away without inputting url", async () => { const { el } = await setupEditor("

H[el]lo

"); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-link"); await waitFor(".o-we-linkpopover", { timeout: 1500 }); const pNode = queryOne("p"); setSelection({ anchorNode: pNode, anchorOffset: 0, focusNode: pNode, focusOffset: 0, }); await tick(); expect(cleanLinkArtifacts(getContent(el))).toBe("

[]Hello

"); }); test("when selection includes partially a link and click the link icon in toolbar, the link should be extended", async () => { const { el } = await setupEditor('

a[bc]def

'); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-link"); await waitFor(".o-we-linkpopover", { timeout: 1500 }); expect(".o-we-linkpopover").toHaveCount(1); expect(cleanLinkArtifacts(getContent(el))).toBe( '

abcd[]ef

' ); }); test("when selection includes a link and click the link icon in toolbar, the link should be extended", async () => { const { el } = await setupEditor('

a[bcde]f

'); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-link"); await waitFor(".o-we-linkpopover", { timeout: 1500 }); expect(".o-we-linkpopover").toHaveCount(1); expect(cleanLinkArtifacts(getContent(el))).toBe( '

abcde[]f

' ); }); test("when selection includes another block and the link extending stays inside of the block", async () => { const { el } = await setupEditor( '

a[bcdef

gh]

' ); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-link"); await waitFor(".o-we-linkpopover", { timeout: 1500 }); expect(".o-we-linkpopover").toHaveCount(1); expect(cleanLinkArtifacts(getContent(el))).toBe( '

abcdef[]

gh

' ); }); test("when create a link on selection which doesn't include a link, it should create a new one", async () => { await setupEditor( '

abcdete[st m]e

' ); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-link"); await waitFor(".o-we-linkpopover", { timeout: 1500 }); expect(".o_we_label_link").toHaveValue("st m"); expect(".o_we_href_input_link").toHaveValue(""); }); test("create a link and undo it", async () => { const { el, editor } = await setupEditor("

[Hello]

"); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-link"); await contains(".o-we-linkpopover input.o_we_href_input_link", { timeout: 1500 }).edit( "#" ); expect(cleanLinkArtifacts(getContent(el))).toBe('

Hello[]

'); undo(editor); expect(cleanLinkArtifacts(getContent(el))).toBe("

[Hello]

"); }); test("extend a link on selection and undo it", async () => { const { el, editor } = await setupEditor( `

[Hello my friend]

` ); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-link"); await waitFor(".o-we-linkpopover", { timeout: 1500 }); expect(queryFirst(".o-we-linkpopover a").href).toBe("https://www.test.com/"); expect(cleanLinkArtifacts(getContent(el))).toBe( `

Hello my friend[]

` ); undo(editor); expect(cleanLinkArtifacts(getContent(el))).toBe( `

[Hello my friend]

` ); }); }); }); describe.tags("desktop"); describe("Link formatting in the popover", () => { test("click on link, the link popover should load the current format correctly", async () => { await setupEditor( '

link2[]

' ); await waitFor(".o-we-linkpopover"); await click(".o_we_edit_link"); const linkPreviewEl = await waitFor("#link-preview"); expect(linkPreviewEl).toHaveClass([ "btn", "btn-outline-primary", "rounded-circle", "btn-lg", ]); expect(queryOne('select[name="link_type"]').selectedIndex).toBe(1); expect(queryOne('select[name="link_style_size"]').selectedIndex).toBe(2); expect(queryOne('select[name="link_style_shape"]').selectedIndex).toBe(3); }); test("after changing the link format, the link preview should be updated", async () => { await setupEditor( '

link2[]

' ); await waitFor(".o-we-linkpopover"); await click(".o_we_edit_link"); const linkPreviewEl = await waitFor("#link-preview"); expect(linkPreviewEl).toHaveClass([ "btn", "rounded-circle", "btn-fill-secondary", "btn-sm", ]); expect(queryOne('select[name="link_type"]').selectedIndex).toBe(2); expect(queryOne('select[name="link_style_size"]').selectedIndex).toBe(0); expect(queryOne('select[name="link_style_shape"]').selectedIndex).toBe(1); await click('select[name="link_type"'); await select("primary"); await click('select[name="link_style_size"'); await select("lg"); await click('select[name="link_style_shape"'); await select("fill,rounded-circle"); await animationFrame(); expect(linkPreviewEl).toHaveClass(["btn", "btn-fill-primary", "rounded-circle", "btn-lg"]); }); test("after applying the link format, the link's format should be updated", async () => { const { el } = await setupEditor('

link2[]

'); await waitFor(".o-we-linkpopover"); await click(".o_we_edit_link"); await waitFor("#link-preview"); expect(queryOne('select[name="link_type"]').selectedIndex).toBe(0); await click('select[name="link_type"'); await select("secondary"); await animationFrame(); await click('select[name="link_style_shape"'); await select("outline,rounded-circle"); await animationFrame(); const linkPreviewEl = queryOne("#link-preview"); expect(linkPreviewEl).toHaveClass(["btn", "btn-outline-secondary", "rounded-circle"]); await click(".o_we_apply_link"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

link2[]

' ); }); test("no preview of the link when the url is empty", async () => { await setupEditor("

link2[]

"); await waitFor(".o-we-linkpopover"); expect("#link-preview").toHaveCount(0); }); test("when no label input, the link preview should have the content of the url", async () => { const { editor } = await setupEditor("

ab[]

"); await insertText(editor, "/link"); await animationFrame(); await click(".o-we-command-name:first"); await waitFor(".o-we-linkpopover"); queryOne(".o_we_href_input_link").focus(); for (const char of "newtest.com") { await press(char); } await animationFrame(); const linkPreviewEl = queryOne("#link-preview"); expect(linkPreviewEl).toHaveText("newtest.com"); }); }); describe("shortcut", () => { test("create link shortcut should be at the first", async () => { const { editor } = await setupEditor(`

[]

`); editor.services.command.add("A test command", () => {}, { hotkey: "alt+k", category: "app", }); await press(["ctrl", "k"]); await animationFrame(); expect(queryText(".o_command_name:first")).toBe("Create link"); }); test("create a link with shortcut", async () => { const { el } = await setupEditor(`

[]

`); await press(["ctrl", "k"]); await animationFrame(); await click(".o_command_name:first"); await contains(".o-we-linkpopover input.o_we_href_input_link").edit("test.com"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

test.com[]

' ); }); test("should be able to create link with ctrl+k and ctrl+k", async () => { const { el } = await setupEditor(`

[]

`); await press(["ctrl", "k"]); await animationFrame(); await press(["ctrl", "k"]); await contains(".o-we-linkpopover input.o_we_href_input_link").edit("test.com"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

test.com[]

' ); }); test("should be able to create link with ctrl+k , and should make link on two existing characters", async () => { const { el } = await setupEditor(`

H[el]lo

`); await press(["ctrl", "k"]); await animationFrame(); await press(["ctrl", "k"]); await contains(".o-we-linkpopover input.o_we_href_input_link").edit("test.com"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

Hel[]lo

' ); }); test("Press enter to apply when create a link", async () => { const { el } = await setupEditor(`

li[]nk

`); await contains(".o-we-linkpopover input.o_we_href_input_link").fill("test.com"); await press("Enter"); expect(cleanLinkArtifacts(getContent(el))).toBe( '

li[]nk

' ); }); }); describe("link preview", () => { test("test internal link preview", async () => { onRpc("/html_editor/link_preview_internal", () => { return { description: markup("Test description"), link_preview_name: "Task name | Project name", }; }); onRpc("/odoo/project/1/tasks/8", () => new Response("", { status: 200 })); const { editor, el } = await setupEditor(`

[]

`); await insertText(editor, "/link"); await animationFrame(); await click(".o-we-command-name:first"); await contains(".o-we-linkpopover input.o_we_href_input_link").fill( window.location.origin + "/odoo/project/1/tasks/8" ); await animationFrame(); expect(".o_we_replace_title_btn").toHaveCount(1); expect(".o_we_url_link").toHaveText("Task name | Project name"); expect(".o_we_description_link_preview").toHaveText("Test description"); await click(".o_we_replace_title_btn"); await animationFrame(); expect(".o_we_replace_title_btn").toHaveCount(0); expect(cleanLinkArtifacts(el.textContent)).toBe("Task name | Project name"); }); test("test external link preview", async () => { onRpc("/html_editor/link_preview_external", () => { return { og_description: "From ERP to CRM, eCommerce and CMS. Download Odoo or use it in the cloud. Grow Your Business.", og_image: "https://www.odoo.com/web/image/41207129-1abe7a15/homepage-seo.png", og_title: "Open Source ERP and CRM | Odoo", og_type: "website", og_site_name: "Odoo", source_url: "http://odoo.com/", }; }); const { editor } = await setupEditor(`

[]

`); await insertText(editor, "/link"); await animationFrame(); await click(".o-we-command-name:first"); await contains(".o-we-linkpopover input.o_we_href_input_link").fill("http://odoo.com/"); await animationFrame(); expect(".o_we_replace_title_btn").toHaveCount(1); expect(".o_extra_info_card").toHaveCount(1); expect(".o_we_url_link").toHaveText("Open Source ERP and CRM | Odoo"); expect(".o_we_description_link_preview").toHaveText( "From ERP to CRM, eCommerce and CMS. Download Odoo or use it in the cloud. Grow Your Business." ); }); test("test internal metadata cached correctly", async () => { onRpc("/html_editor/link_preview_internal", () => { expect.step("/html_editor/link_preview_internal"); return { description: markup("

Test description

"), link_preview_name: "Task name | Project name", }; }); onRpc("/odoo/cachetest/8", () => new Response("", { status: 200 })); const { editor } = await setupEditor(`

abc[]

`); await insertText(editor, "/link"); await animationFrame(); await click(".o-we-command-name:first"); await contains(".o-we-linkpopover input.o_we_href_input_link").fill( window.location.origin + "/odoo/cachetest/8" ); await animationFrame(); expect.verifySteps(["/html_editor/link_preview_internal"]); expect(".o_we_url_link").toHaveText("Task name | Project name"); const pNode = queryOne("p"); setSelection({ anchorNode: pNode, anchorOffset: 1, focusNode: pNode, focusOffset: 1, }); await waitUntil(() => !queryFirst(".o-we-linkpopover"), { timeout: 1500 }); const linkNode = queryOne("a"); setSelection({ anchorNode: linkNode, anchorOffset: 1, focusNode: linkNode, focusOffset: 1, }); await waitFor(".o-we-linkpopover"); expect.verifySteps([]); }); test("test external metadata cached correctly", async () => { onRpc("/html_editor/link_preview_external", () => { expect.step("/html_editor/link_preview_external"); return { og_description: "From ERP to CRM, eCommerce and CMS. Download Odoo or use it in the cloud. Grow Your Business.", og_image: "https://www.odoo.com/web/image/41207129-1abe7a15/homepage-seo.png", og_title: "Open Source ERP and CRM | Odoo", og_type: "website", og_site_name: "Odoo", source_url: "http://odoo.com/", }; }); const { editor } = await setupEditor(`

[]

`); await insertText(editor, "/link"); await animationFrame(); await click(".o-we-command-name:first"); await contains(".o-we-linkpopover input.o_we_href_input_link").fill("http://odoo.com/"); await animationFrame(); expect.verifySteps(["/html_editor/link_preview_external"]); expect(".o_we_url_link").toHaveText("Open Source ERP and CRM | Odoo"); const pNode = queryOne("p"); setSelection({ anchorNode: pNode, anchorOffset: 1, focusNode: pNode, focusOffset: 1, }); await waitUntil(() => !queryFirst(".o-we-linkpopover"), { timeout: 1500 }); const linkNode = queryOne("a"); setSelection({ anchorNode: linkNode, anchorOffset: 1, focusNode: linkNode, focusOffset: 1, }); await waitFor(".o-we-linkpopover"); expect.verifySteps([]); }); }); describe("link in templates", () => { test("Should not remove a link with t-attf-href", async () => { const { el } = await setupEditor('

testli[]nk

'); await waitFor(".o-we-linkpopover"); expect(".o-we-linkpopover").toHaveCount(1); const pNode = queryOne("p"); setSelection({ anchorNode: pNode, anchorOffset: 0, focusNode: pNode, focusOffset: 0, }); await waitUntil(() => !queryFirst(".o-we-linkpopover"), { timeout: 1500 }); expect(cleanLinkArtifacts(getContent(el))).toBe( '

[]testlink

' ); }); test("Should not remove a link with t-att-href", async () => { const { el } = await setupEditor('

testli[]nk

'); await waitFor(".o-we-linkpopover"); expect(".o-we-linkpopover").toHaveCount(1); const pNode = queryOne("p"); setSelection({ anchorNode: pNode, anchorOffset: 0, focusNode: pNode, focusOffset: 0, }); await waitUntil(() => !queryFirst(".o-we-linkpopover"), { timeout: 1500 }); expect(cleanLinkArtifacts(getContent(el))).toBe( '

[]testlink

' ); }); }); describe("links with inline image", () => { test("can add link to inline image + text", async () => { const { el } = await setupEditor(`

ab[cdef]g

`); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-link"); await contains(".o-we-linkpopover input.o_we_href_input_link", { timeout: 1500 }).edit("#"); expect(cleanLinkArtifacts(getContent(el))).toBe( `

abcdef[]g

` ); }); test("can undo add link to inline image + text", async () => { const { editor, el } = await setupEditor(`

ab[cdef]g

`); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-link"); await contains(".o-we-linkpopover input.o_we_href_input_link", { timeout: 1500 }).edit("#"); expect(cleanLinkArtifacts(getContent(el))).toBe( `

abcdef[]g

` ); undo(editor); await animationFrame(); expect(cleanLinkArtifacts(getContent(el))).toBe(`

ab[cdef]g

`); }); test("can remove link from an inline image", async () => { const { el } = await setupEditor(`

abcdefg

`); await click("img"); await waitFor(".o-we-toolbar"); expect("button[name='unlink']").toHaveCount(1); await click("button[name='unlink']"); await animationFrame(); expect(cleanLinkArtifacts(getContent(el))).toBe( `

abcd[]efg

` ); expect(".o-we-linkpopover").toHaveCount(0); }); test("can remove link from a selection of an inline image + text", async () => { const { el } = await setupEditor( `

abc[de]fg

` ); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-unlink"); expect(cleanLinkArtifacts(getContent(el))).toBe( `

abc[de]fg

` ); }); test("can remove link from a selection (ltr) with multiple inline images", async () => { const { el } = await setupEditor( `

abc[def]gh

` ); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-unlink"); expect(cleanLinkArtifacts(getContent(el))).toBe( `

abc[def]gh

` ); }); test("can remove link from a selection (rtl) with multiple inline images", async () => { const { el } = await setupEditor( `

abc]def[gh

` ); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-unlink"); expect(cleanLinkArtifacts(getContent(el))).toBe( `

abc]def[gh

` ); }); test("can remove link from a selection (ltr) with multiple inline images acrossing different links", async () => { const { el } = await setupEditor( `

abc[dexxfg]hi

` ); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-unlink"); expect(cleanLinkArtifacts(getContent(el))).toBe( `

abc[dexxfg]hi

` ); }); test("can remove link from a selection (rtl) with multiple inline images acrossing different links", async () => { const { el } = await setupEditor( `

abc]dexxfg[hi

` ); await waitFor(".o-we-toolbar"); await click(".o-we-toolbar .fa-unlink"); expect(cleanLinkArtifacts(getContent(el))).toBe( `

abc]dexxfg[hi

` ); }); });