import { describe, expect, test } from "@odoo/hoot"; import { addLink, parseAndTransform } from "@mail/utils/common/format"; import { click, contains, defineMailModels, insertText, openDiscuss, start, startServer, } from "./mail_test_helpers"; import { useSequential } from "@mail/utils/common/hooks"; describe.current.tags("desktop"); defineMailModels(); test("add_link utility function", () => { const testInputs = { "http://admin:password@example.com:8/%2020": true, "https://admin:password@example.com/test": true, "www.example.com:8/test": true, "https://127.0.0.5:8069": true, "www.127.0.0.5": false, "should.notmatch": false, "fhttps://test.example.com/test": false, "https://www.transifex.com/odoo/odoo-11/translate/#fr/lunch?q=text%3A'La+Tartiflette'": true, "https://www.transifex.com/odoo/odoo-11/translate/#fr/$/119303430?q=text%3ATartiflette": true, "https://tenor.com/view/chỗgiặt-dog-smile-gif-13860250": true, "http://www.boîtenoire.be": true, "https://github.com/odoo/enterprise/compare/16.0...odoo-dev:enterprise:16.0-voip-fix_demo_data-tsm?expand=1": true, "https://github.com/odoo/enterprise/compare/16.0...16.0-voip-fix_demo_data-tsm?expand=1": true, "https://github.com/odoo/enterprise/compare/16.0...chỗgiặt-voip-fix_demo_data-tsm?expand=1": true, "https://github.com/odoo/enterprise/compare/chỗgiặt...chỗgiặt-voip-fix_demo_data-tsm?expand=1": true, "https://github.com/odoo/enterprise/compare/@...}-voip-fix_demo_data-tsm?expand=1": true, "https://x.com": true, }; for (const [content, willLinkify] of Object.entries(testInputs)) { const output = parseAndTransform(content, addLink); if (willLinkify) { expect(output.indexOf("")).toBe(output.length - 4); } else { expect(output.indexOf(" { const testInputs = { // textContent not unescaped "

https://example.com/?&currency_id

": '

https://example.com/?&currency_id

', // entities not unescaped "& &amp; > <": "& &amp; > <", // > and " not linkified since they are not in URL regex "

https://example.com/>

": '

https://example.com/>

', '

https://example.com/"hello">

': '

https://example.com/"hello">

', // & and ' linkified since they are in URL regex "

https://example.com/&hello

": '

https://example.com/&hello

', "

https://example.com/'yeah'

": '

https://example.com/\'yeah\'

', // normal character should not be escaped ":'(": ":'(", // special character in smileys should be escaped "<3": "<3", // Already encoded url should not be encoded twice "https://odoo.com/%5B%5D": `https://odoo.com/[]`, }; for (const [content, result] of Object.entries(testInputs)) { const output = parseAndTransform(content, addLink); expect(output).toBe(result); } }); test("addLink: linkify inside text node (1 occurrence)", async () => { const content = "

some text https://somelink.com

"; const linkified = parseAndTransform(content, addLink); expect(linkified.startsWith("

some text

")).toBe(true); // linkify may add some attributes. Since we do not care of their exact // stringified representation, we continue deeper assertion with query // selectors. const fragment = document.createDocumentFragment(); const div = document.createElement("div"); fragment.appendChild(div); div.innerHTML = linkified; expect(div).toHaveText("some text https://somelink.com"); await contains("a", { target: div }); expect(div.querySelector(":scope a")).toHaveText("https://somelink.com"); }); test("addLink: linkify inside text node (2 occurrences)", () => { // linkify may add some attributes. Since we do not care of their exact // stringified representation, we continue deeper assertion with query // selectors. const content = "

some text https://somelink.com and again https://somelink2.com ...

"; const linkified = parseAndTransform(content, addLink); const fragment = document.createDocumentFragment(); const div = document.createElement("div"); fragment.appendChild(div); div.innerHTML = linkified; expect(div).toHaveText("some text https://somelink.com and again https://somelink2.com ..."); expect(div.querySelectorAll(":scope a")).toHaveCount(2); expect(div.querySelectorAll(":scope a")[0]).toHaveText("https://somelink.com"); expect(div.querySelectorAll(":scope a")[1]).toHaveText("https://somelink2.com"); }); test("url", async () => { const pyEnv = await startServer(); const channelId = pyEnv["discuss.channel"].create({ name: "General" }); await start(); await openDiscuss(channelId); // see: https://www.ietf.org/rfc/rfc1738.txt const messageBody = "https://odoo.com?test=~^|`{}[]#"; await insertText(".o-mail-Composer-input", messageBody); await click("button[aria-label='Send']:enabled"); await contains(`.o-mail-Message a:contains(${messageBody})`); }); test("url with comma at the end", async () => { const pyEnv = await startServer(); const channelId = pyEnv["discuss.channel"].create({ name: "General" }); await start(); await openDiscuss(channelId); const messageBody = "Go to https://odoo.com, it's great!"; await insertText(".o-mail-Composer-input", messageBody); await click("button[aria-label='Send']:enabled"); await contains(".o-mail-Message a:contains(https://odoo.com)"); await contains(`.o-mail-Message-content:contains(${messageBody}`); }); test("url with dot at the end", async () => { const pyEnv = await startServer(); const channelId = pyEnv["discuss.channel"].create({ name: "General" }); await start(); await openDiscuss(channelId); const messageBody = "Go to https://odoo.com. It's great!"; await insertText(".o-mail-Composer-input", messageBody); await click("button[aria-label='Send']:enabled"); await contains(".o-mail-Message a:contains(https://odoo.com)"); await contains(`.o-mail-Message-content:contains(${messageBody})`); }); test("url with semicolon at the end", async () => { const pyEnv = await startServer(); const channelId = pyEnv["discuss.channel"].create({ name: "General" }); await start(); await openDiscuss(channelId); const messageBody = "Go to https://odoo.com; it's great!"; await insertText(".o-mail-Composer-input", messageBody); await click("button[aria-label='Send']:enabled"); await contains(".o-mail-Message a:contains(https://odoo.com)"); await contains(`.o-mail-Message-content:contains(${messageBody})`); }); test("url with ellipsis at the end", async () => { const pyEnv = await startServer(); const channelId = pyEnv["discuss.channel"].create({ name: "General" }); await start(); await openDiscuss(channelId); const messageBody = "Go to https://odoo.com... it's great!"; await insertText(".o-mail-Composer-input", messageBody); await click("button[aria-label='Send']:enabled"); await contains(".o-mail-Message a:contains(https://odoo.com)"); await contains(`.o-mail-Message-content:contains(${messageBody})`); }); test("url with number in subdomain", async () => { const pyEnv = await startServer(); const channelId = pyEnv["discuss.channel"].create({ name: "General" }); await start(); await openDiscuss(channelId); const messageBody = "https://www.45017478-master-all.runbot134.odoo.com/odoo"; await insertText(".o-mail-Composer-input", messageBody); await click("button[aria-label='Send']:enabled"); await contains( ".o-mail-Message a:contains(https://www.45017478-master-all.runbot134.odoo.com/odoo)" ); }); test("isSequential doesn't execute intermediate call.", async () => { const sequential = useSequential(); let index = 0; const sequence = () => { index++; const i = index; return sequential(async () => { expect.step(i.toString()); return new Promise((r) => setTimeout(() => r(i), 1)); }); }; const result = await Promise.all([sequence(), sequence(), sequence(), sequence(), sequence()]); expect(result).toEqual([1, undefined, undefined, undefined, 5]); expect.verifySteps(["1", "5"]); });