[IMP] mail, *: tests: use contains in click and insertText

* = account, calendar, im_livechat, mrp, project_todo, sms, snailmail,
    test_mail, web, website_livechat

`contains` is more efficient than `afterNextRender` as it does not wait
for several extra animation frames, and it is functionally more
meaningful.

closes odoo/odoo#130451

Related: odoo/enterprise#44999
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Sébastien Theys
2023-08-21 21:00:03 +02:00
parent 9a1efcbde7
commit d08a18b791
97 changed files with 4661 additions and 5123 deletions
@@ -1,19 +1,20 @@
/** @odoo-module **/
/* @odoo-module */
import { start, startServer } from "@mail/../tests/helpers/test_utils";
import { triggerHotkey } from "@web/../tests/helpers/utils";
import { accountMove as accountMoveService} from '@account/components/account_move_service/account_move_service';
import { accountMove as accountMoveService } from "@account/components/account_move_service/account_move_service";
QUnit.module("Views", {}, function (hooks) {
QUnit.module('MoveFormView');
import { click, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { makeDeferred, triggerHotkey } from "@web/../tests/helpers/utils";
QUnit.module("Views", {}, function () {
QUnit.module("MoveFormView");
QUnit.test("When I switch tabs, it saves", async (assert) => {
const pyEnv = await startServer();
const accountMove = pyEnv['account.move'].create([{ name: "move0" }]);
const accountMove = pyEnv["account.move"].create([{ name: "move0" }]);
const views = {
'account.move,false,form':
`<form js_class='account_move_form'>
"account.move,false,form": `<form js_class='account_move_form'>
<sheet>
<notebook>
<page id="invoice_tab" name="invoice_tab" string="Invoice Lines">
@@ -24,28 +25,28 @@ QUnit.module("Views", {}, function (hooks) {
</sheet>
</form>`,
};
const { click, insertText, openView } = await start({
const def = makeDeferred();
const { openFormView } = await start({
serverData: { views },
services: {
'account_move': accountMoveService,
account_move: accountMoveService,
},
async mockRPC(route) {
if (route === "/web/dataset/call_kw/account.move/write") {
assert.step("tab saved");
def.resolve();
}
},
});
await openView({
res_id: accountMove,
res_model: 'account.move',
views: [[false, 'form']],
});
openFormView("account.move", accountMove);
await insertText("[name='name'] input", "somebody save me!");
triggerHotkey("Enter")
triggerHotkey("Enter");
await click('a[name="aml_tab"]');
assert.verifySteps(["tab saved"],
"When clicking on a tab, the saving method should be called and succeed");
await def;
assert.verifySteps(
["tab saved"],
"When clicking on a tab, the saving method should be called and succeed"
);
});
});
@@ -1,6 +1,7 @@
/** @odoo-module **/
/* @odoo-module */
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { start, startServer, click } from "@mail/../tests/helpers/test_utils";
import { getFixture, patchDate, patchWithCleanup } from "@web/../tests/helpers/utils";
let target;
@@ -29,7 +30,7 @@ QUnit.test("activity menu widget:today meetings", async function (assert) {
},
]);
const { env } = await start();
assert.containsOnce(target, ".o_menu_systray i[aria-label='Activities']");
await contains(".o_menu_systray i[aria-label='Activities']");
await click(".o_menu_systray i[aria-label='Activities']");
patchWithCleanup(env.services.action, {
doAction(action) {
@@ -37,10 +38,10 @@ QUnit.test("activity menu widget:today meetings", async function (assert) {
assert.step("action");
},
});
assert.containsOnce(target, ".o-mail-ActivityGroup:contains(Today's Meetings)");
assert.containsN(target, ".o-mail-ActivityGroup .o-calendar-metting", 2);
assert.containsOnce(target, ".o-calendar-metting:contains(meeting1)");
assert.containsOnce(target, ".o-calendar-metting:contains(meeting2)");
await contains(".o-mail-ActivityGroup:contains(Today's Meetings)");
await contains(".o-mail-ActivityGroup .o-calendar-metting", 2);
await contains(".o-calendar-metting:contains(meeting1)");
await contains(".o-calendar-metting:contains(meeting2)");
assert.hasClass($(target).find("span:contains(meeting1)"), "fw-bold");
assert.doesNotHaveClass($(target).find("span:contains(meeting2)"), "fw-bold");
await click(".o-mail-ActivityMenu .o-mail-ActivityGroup");
+5 -14
View File
@@ -1,17 +1,8 @@
/** @odoo-module **/
/* @odoo-module */
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { getFixture } from "@web/../tests/helpers/utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
let target;
QUnit.module("activity", {
async beforeEach() {
target = getFixture();
},
});
QUnit.test("activity click on Reschedule", async function (assert) {
QUnit.test("activity click on Reschedule", async () => {
const pyEnv = await startServer();
const resPartnerId = pyEnv["res.partner"].create({});
const meetingActivityTypeId = pyEnv["mail.activity.type"].create({
@@ -36,7 +27,7 @@ QUnit.test("activity click on Reschedule", async function (assert) {
calendar_event_id: calendaMeetingId,
});
const { openFormView } = await start();
await openFormView("res.partner", resPartnerId);
openFormView("res.partner", resPartnerId);
await click(".btn:contains('Reschedule')");
assert.containsOnce(target, ".o_calendar_view");
await contains(".o_calendar_view");
});
@@ -1,6 +1,6 @@
/** @odoo-module **/
/* @odoo-module */
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
import { ListController } from "@web/views/list/list_controller";
@@ -65,6 +65,6 @@ QUnit.test("list activity widget: reschedule button in dropdown", async (assert)
assert.strictEqual($(".o-mail-ListActivity-summary")[0].innerText, "OXP");
await click(".o-mail-ActivityButton"); // open the popover
assert.containsNone($, ".o-mail-ActivityListPopoverItem-editbtn .fa-pencil");
assert.containsOnce($, ".o-mail-ActivityListPopoverItem-editbtn .fa-calendar");
await contains(".o-mail-ActivityListPopoverItem-editbtn .fa-pencil", 0);
await contains(".o-mail-ActivityListPopoverItem-editbtn .fa-calendar");
});
@@ -3,11 +3,11 @@
import { startServer } from "@bus/../tests/helpers/mock_python_environment";
import { Command } from "@mail/../tests/helpers/command";
import { click, start } from "@mail/../tests/helpers/test_utils";
import { click, contains, start } from "@mail/../tests/helpers/test_utils";
QUnit.module("Channel invite");
QUnit.test("Can invite a partner to a livechat channel", async (assert) => {
QUnit.test("Can invite a partner to a livechat channel", async () => {
const pyEnv = await startServer();
const userId = pyEnv["res.users"].create({ name: "James" });
pyEnv["res.partner"].create({
@@ -25,12 +25,13 @@ QUnit.test("Can invite a partner to a livechat channel", async (assert) => {
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("button[title='Add Users']");
await click(".o-discuss-ChannelInvitation-selectable:contains(James) input");
await click("button:contains(Invite)");
await click("button:contains(Invite):not(:disabled)");
await contains(".o-discuss-ChannelInvitation", 0);
await click("button[title='Show Member List']");
assert.containsOnce($, ".o-discuss-ChannelMember:contains(James)");
await contains(".o-discuss-ChannelMember:contains(James)");
});
QUnit.test("Available operators come first", async (assert) => {
@@ -60,7 +61,7 @@ QUnit.test("Available operators come first", async (assert) => {
const { openDiscuss } = await start();
await openDiscuss(channelId);
await click("button[title='Add Users']");
const partnerSuggestions = document.querySelectorAll(".o-discuss-ChannelInvitation-selectable");
const partnerSuggestions = await contains(".o-discuss-ChannelInvitation-selectable", 2);
assert.ok(partnerSuggestions[0].textContent.includes("Ron"));
assert.ok(partnerSuggestions[1].textContent.includes("Harry"));
});
@@ -101,10 +102,10 @@ QUnit.test("Partners invited most frequently by the current user come first", as
await click(".o-mail-DiscussSidebarChannel:contains(Visitor #1)");
await click("button[title='Add Users']");
await click(".o-discuss-ChannelInvitation-selectable:contains(John) input");
await click("button:contains(Invite)");
await click("button:contains(Invite):not(:disabled)");
await click(".o-mail-DiscussSidebarChannel:contains(Visitor #2)");
await click("button[title='Add Users']");
const partnerSuggestions = document.querySelectorAll(".o-discuss-ChannelInvitation-selectable");
const partnerSuggestions = await contains(".o-discuss-ChannelInvitation-selectable", 2);
assert.ok(partnerSuggestions[0].textContent.includes("John"));
assert.ok(partnerSuggestions[1].textContent.includes("Albert"));
});
@@ -1,6 +1,6 @@
/* @odoo-module */
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("chat window (patch)");
@@ -18,9 +18,9 @@ QUnit.test("No call buttons", async (assert) => {
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-ChatWindow");
assert.containsNone($, ".o-mail-ChatWindow-header .o-mail-ChatWindow-command i.fa-phone");
assert.containsNone($, ".o-mail-ChatWindow-header .o-mail-ChatWindow-command i.fa-gear");
await contains(".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow-header .o-mail-ChatWindow-command i.fa-phone", 0);
await contains(".o-mail-ChatWindow-header .o-mail-ChatWindow-command i.fa-gear", 0);
});
QUnit.test("closing a chat window with no message from admin side unpins it", async (assert) => {
@@ -5,12 +5,13 @@ import { startServer } from "@bus/../tests/helpers/mock_python_environment";
import {
afterNextRender,
click,
contains,
dragenterFiles,
insertText,
start,
} from "@mail/../tests/helpers/test_utils";
import { editInput, nextTick, triggerHotkey } from "@web/../tests/helpers/utils";
import { nextTick, triggerHotkey } from "@web/../tests/helpers/utils";
QUnit.module("composer (patch)");
@@ -22,8 +23,8 @@ QUnit.test("No add attachments button", async (assert) => {
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-Composer");
assert.containsNone($, "button[title='Attach files']");
await contains(".o-mail-Composer");
await contains("button[title='Attach files']", 0);
});
QUnit.test("Attachment upload via drag and drop disabled", async (assert) => {
@@ -36,10 +37,10 @@ QUnit.test("Attachment upload via drag and drop disabled", async (assert) => {
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-Composer");
await contains(".o-mail-Composer");
dragenterFiles($(".o-mail-Composer-input")[0]);
await nextTick();
assert.containsNone($, ".o-mail-Dropzone");
await contains(".o-mail-Dropzone", 0);
});
QUnit.test("Can execute help command on livechat channels", async (assert) => {
@@ -64,9 +65,8 @@ QUnit.test("Can execute help command on livechat channels", async (assert) => {
});
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
await editInput(document.body, ".o-mail-Composer-input", "/help");
await insertText(".o-mail-Composer-input", "/help");
triggerHotkey("Enter");
await nextTick();
assert.verifySteps(["execute_command_help"]);
});
@@ -94,10 +94,10 @@ QUnit.test('Receives visitor typing status "is typing"', async (assert) => {
})
)
);
assert.containsOnce($, ".o-discuss-Typing:contains(Visitor 20 is typing...)");
await contains(".o-discuss-Typing:contains(Visitor 20 is typing...)");
});
QUnit.test('display canned response suggestions on typing ":"', async (assert) => {
QUnit.test('display canned response suggestions on typing ":"', async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
anonymous_name: "Mario",
@@ -114,9 +114,10 @@ QUnit.test('display canned response suggestions on typing ":"', async (assert) =
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
await contains(".o-mail-Composer-input");
await contains(".o-mail-Composer-suggestionList .o-open", 0);
await insertText(".o-mail-Composer-input", ":");
assert.containsOnce($, ".o-mail-Composer-suggestionList .o-open");
await contains(".o-mail-Composer-suggestionList .o-open");
});
QUnit.test("use a canned response", async (assert) => {
@@ -136,11 +137,12 @@ QUnit.test("use a canned response", async (assert) => {
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
await contains(".o-mail-Composer-suggestionList");
await contains(".o-mail-Composer-suggestionList .o-open", 0);
assert.strictEqual($(".o-mail-Composer-input").val(), "");
await insertText(".o-mail-Composer-input", ":");
assert.containsOnce($, ".o-mail-Composer-suggestion");
await click(".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-suggestion", 0);
assert.strictEqual(
$(".o-mail-Composer-input").val().replace(/\s/, " "),
"Hello! How are you? ",
@@ -165,13 +167,14 @@ QUnit.test("use a canned response some text", async (assert) => {
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-suggestionList");
await contains(".o-mail-Composer-suggestion", 0);
assert.strictEqual($(".o-mail-Composer-input").val(), "");
await insertText(".o-mail-Composer-input", "bluhbluh ");
assert.strictEqual($(".o-mail-Composer-input").val(), "bluhbluh ");
await insertText(".o-mail-Composer-input", ":");
assert.containsOnce($, ".o-mail-Composer-suggestion");
await click(".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-suggestion", 0);
assert.strictEqual(
$(".o-mail-Composer-input").val().replace(/\s/, " "),
"bluhbluh Hello! How are you? ",
@@ -196,11 +199,12 @@ QUnit.test("add an emoji after a canned response", async (assert) => {
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-suggestionList");
await contains(".o-mail-Composer-suggestion", 0);
assert.strictEqual($(".o-mail-Composer-input").val(), "");
await insertText(".o-mail-Composer-input", ":");
assert.containsOnce($, ".o-mail-Composer-suggestion");
await click(".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-suggestion", 0);
assert.strictEqual(
$(".o-mail-Composer-input").val().replace(/\s/, " "),
"Hello! How are you? ",
@@ -208,6 +212,7 @@ QUnit.test("add an emoji after a canned response", async (assert) => {
);
await click("button[aria-label='Emojis']");
await click(".o-Emoji:contains(😊)");
await contains(".o-Emoji", 0);
assert.strictEqual(
$(".o-mail-Composer-input").val().replace(/\s/, " "),
"Hello! How are you? 😊"
@@ -4,6 +4,7 @@ import { Command } from "@mail/../tests/helpers/command";
import {
afterNextRender,
click,
contains,
insertText,
start,
startServer,
@@ -128,7 +129,7 @@ QUnit.test("reaction button should not be present on livechat", async (assert) =
const { insertText, openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "Test");
await click(".o-mail-Composer-send");
await click(".o-mail-Composer-send:not(:disabled)");
await click(".o-mail-Message");
assert.containsNone($, "[title='Add a Reaction']");
});
@@ -189,14 +190,14 @@ QUnit.test(
]);
const { openDiscuss } = await start();
await openDiscuss();
assert.strictEqual($(".o-mail-DiscussSidebarChannel:eq(0)").text(), "Visitor 12");
assert.strictEqual($(".o-mail-DiscussSidebarChannel:eq(1)").text(), "Visitor 11");
await contains(".o-mail-DiscussSidebarChannel:eq(0):contains(Visitor 12)");
await contains(".o-mail-DiscussSidebarChannel:eq(1):contains(Visitor 11)");
// post a new message on the last channel
await click(".o-mail-DiscussSidebarChannel:eq(1)");
await insertText(".o-mail-Composer-input", "Blabla");
await click(".o-mail-Composer-send");
assert.containsN($, ".o-mail-DiscussSidebarChannel", 2);
assert.strictEqual($(".o-mail-DiscussSidebarChannel:eq(0)").text(), "Visitor 11");
assert.strictEqual($(".o-mail-DiscussSidebarChannel:eq(1)").text(), "Visitor 12");
await click(".o-mail-Composer-send:not(:disabled)");
await contains(".o-mail-DiscussSidebarChannel", 2);
await contains(".o-mail-DiscussSidebarChannel:eq(0):contains(Visitor 11)");
await contains(".o-mail-DiscussSidebarChannel:eq(1):contains(Visitor 12)");
}
);
@@ -7,11 +7,9 @@ import { start, setCookie, loadDefaultConfig } from "@im_livechat/../tests/embed
import { Command } from "@mail/../tests/helpers/command";
import { contains } from "@mail/../tests/helpers/test_utils";
import { nextTick } from "@web/../tests/helpers/utils";
QUnit.module("autopopup");
QUnit.test("persisted session", async (assert) => {
QUnit.test("persisted session", async () => {
const pyEnv = await startServer();
const livechatChannelId = await loadDefaultConfig();
const channelId = pyEnv["discuss.channel"].create({
@@ -29,10 +27,10 @@ QUnit.test("persisted session", async (assert) => {
await contains(".o-mail-ChatWindow");
});
QUnit.test("rule received in init", async (assert) => {
QUnit.test("rule received in init", async () => {
await startServer();
await loadDefaultConfig();
await start({
start({
mockRPC(route) {
if (route === "/im_livechat/init") {
return {
@@ -42,6 +40,5 @@ QUnit.test("rule received in init", async (assert) => {
}
},
});
await nextTick();
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
});
@@ -5,26 +5,26 @@ import { startServer } from "@bus/../tests/helpers/mock_python_environment";
import { RATING } from "@im_livechat/embed/core/livechat_service";
import { loadDefaultConfig, start } from "@im_livechat/../tests/embed/helper/test_utils";
import { afterNextRender, click, insertText } from "@mail/../tests/helpers/test_utils";
import { click, contains, insertText } from "@mail/../tests/helpers/test_utils";
import { triggerHotkey } from "@web/../tests/helpers/utils";
QUnit.module("feedback panel");
QUnit.test("Do not ask feedback if empty", async (assert) => {
QUnit.test("Do not ask feedback if empty", async () => {
await startServer();
await loadDefaultConfig();
await start();
start();
await click(".o-livechat-LivechatButton");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
await click("[title='Close Chat Window']");
assert.containsNone($, ".o-livechat-LivechatButton");
await contains(".o-livechat-LivechatButton", 0);
});
QUnit.test("Close without feedback", async (assert) => {
await startServer();
await loadDefaultConfig();
await start({
start({
mockRPC(route) {
if (route === "/im_livechat/visitor_leave_session") {
assert.step(route);
@@ -35,19 +35,20 @@ QUnit.test("Close without feedback", async (assert) => {
},
});
await click(".o-livechat-LivechatButton");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
await insertText(".o-mail-Composer-input", "Hello World!");
await afterNextRender(() => triggerHotkey("Enter"));
triggerHotkey("Enter");
await contains(".o-mail-Message:contains(Hello World!)");
await click("[title='Close Chat Window']");
await click("button:contains('Close')");
assert.containsNone($, ".o-livechat-LivechatButton");
await contains(".o-livechat-LivechatButton", 0);
assert.verifySteps(["/im_livechat/visitor_leave_session"]);
});
QUnit.test("Feedback with rating and comment", async (assert) => {
await startServer();
await loadDefaultConfig();
await start({
start({
mockRPC(route, args) {
if (route === "/im_livechat/visitor_leave_session") {
assert.step(route);
@@ -60,28 +61,30 @@ QUnit.test("Feedback with rating and comment", async (assert) => {
},
});
await click(".o-livechat-LivechatButton");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
await insertText(".o-mail-Composer-input", "Hello World!");
await afterNextRender(() => triggerHotkey("Enter"));
triggerHotkey("Enter");
await contains(".o-mail-Message:contains(Hello World!)");
await click("[title='Close Chat Window']");
assert.verifySteps(["/im_livechat/visitor_leave_session"]);
await click(`img[data-alt="${RATING.GOOD}"]`);
await insertText("textarea[placeholder='Explain your note']", "Good job!");
await click("button:contains(Send)");
assert.containsOnce($, "p:contains(Thank you for your feedback)");
await click("button:contains(Send):not(:disabled)");
await contains("p:contains(Thank you for your feedback)");
assert.verifySteps(["/im_livechat/feedback"]);
});
QUnit.test("Closing folded chat window should open it with feedback", async (assert) => {
QUnit.test("Closing folded chat window should open it with feedback", async () => {
await startServer();
await loadDefaultConfig();
await start();
await click(".o-livechat-LivechatButton");
await insertText(".o-mail-Composer-input", "Hello World!");
await afterNextRender(() => triggerHotkey("Enter"));
triggerHotkey("Enter");
await contains(".o-mail-Message:contains(Hello World!)");
await click("[title='Fold']");
assert.containsOnce($, ".o-mail-ChatWindow.o-folded");
await contains(".o-mail-ChatWindow.o-folded");
await click("[title='Close Chat Window']");
assert.containsNone($, ".o-mail-ChatWindow.o-folded");
assert.containsOnce($, ".o-mail-ChatWindow:contains(Did we correctly answer your question?)");
await contains(".o-mail-ChatWindow.o-folded", 0);
await contains(".o-mail-ChatWindow:contains(Did we correctly answer your question?)");
});
@@ -4,46 +4,47 @@ import { startServer } from "@bus/../tests/helpers/mock_python_environment";
import { loadDefaultConfig, start } from "@im_livechat/../tests/embed/helper/test_utils";
import { afterNextRender, click, insertText } from "@mail/../tests/helpers/test_utils";
import { click, contains, insertText } from "@mail/../tests/helpers/test_utils";
import { triggerHotkey } from "@web/../tests/helpers/utils";
QUnit.test("open/close temporary channel", async (assert) => {
QUnit.test("open/close temporary channel", async () => {
await startServer();
await loadDefaultConfig();
await start();
assert.containsOnce($, ".o-livechat-LivechatButton");
start();
await click(".o-livechat-LivechatButton");
assert.containsOnce($, ".o-mail-ChatWindow");
assert.containsNone($, ".o-livechat-LivechatButton");
await contains(".o-mail-ChatWindow");
await contains(".o-livechat-LivechatButton", 0);
await click("[title='Close Chat Window']");
assert.containsNone($, ".o-mail-ChatWindow");
assert.containsNone($, ".o-livechat-LivechatButton");
await contains(".o-mail-ChatWindow", 0);
await contains(".o-livechat-LivechatButton", 0);
});
QUnit.test("open/close persisted channel", async (assert) => {
QUnit.test("open/close persisted channel", async () => {
await startServer();
await loadDefaultConfig();
await start();
assert.containsOnce($, ".o-livechat-LivechatButton");
start();
await click(".o-livechat-LivechatButton");
await insertText(".o-mail-Composer-input", "Hello");
await afterNextRender(() => triggerHotkey("Enter"));
await insertText(".o-mail-Composer-input", "How can I help?");
triggerHotkey("Enter");
await contains(".o-mail-Message:contains(How can I help?)");
await click("[title='Close Chat Window']");
await contains(".o-mail-ChatWindow-content:contains(Did we correctly answer your question?)");
await click("[title='Close Chat Window']");
assert.containsNone($, ".o-mail-ChatWindow");
assert.containsNone($, ".o-livechat-LivechatButton");
await contains(".o-mail-ChatWindow", 0);
await contains(".o-livechat-LivechatButton", 0);
});
QUnit.test("livechat not available", async (assert) => {
QUnit.test("livechat not available", async () => {
await startServer();
await loadDefaultConfig();
await start({
start({
mockRPC(route) {
if (route === "/im_livechat/init") {
return { available_for_me: false };
}
},
});
assert.containsNone($, ".o-livechat-LivechatButton");
await contains(".o-mail-ChatWindowContainer");
await contains(".o-livechat-LivechatButton", 0);
});
@@ -5,11 +5,11 @@ import { startServer } from "@bus/../tests/helpers/mock_python_environment";
import { loadDefaultConfig, setCookie, start } from "@im_livechat/../tests/embed/helper/test_utils";
import { Command } from "@mail/../tests/helpers/command";
import { click } from "@mail/../tests/helpers/test_utils";
import { click, contains } from "@mail/../tests/helpers/test_utils";
QUnit.module("livechat service");
QUnit.test("persisted session history", async (assert) => {
QUnit.test("persisted session history", async () => {
const pyEnv = await startServer();
const livechatChannelId = await loadDefaultConfig();
const channelId = pyEnv["discuss.channel"].create({
@@ -30,19 +30,18 @@ QUnit.test("persisted session history", async (assert) => {
model: "discuss.channel",
message_type: "comment",
});
await start();
assert.containsOnce($, ".o-mail-Message:contains(Old message in history)");
start();
await contains(".o-mail-Message:contains(Old message in history)");
});
QUnit.test("previous operator prioritized", async (assert) => {
QUnit.test("previous operator prioritized", async () => {
const pyEnv = await startServer();
const livechatChannelId = await loadDefaultConfig();
const userId = pyEnv["res.users"].create({ name: "John Doe", im_status: "online" });
const previousOperatorId = pyEnv["res.partner"].create({ user_ids: [userId] });
pyEnv["im_livechat.channel"].write([livechatChannelId], { user_ids: [Command.link(userId)] });
setCookie("im_livechat_previous_operator_pid", JSON.stringify(previousOperatorId));
await start();
await click(".o-livechat-LivechatButton");
assert.containsOnce($, ".o-mail-Message-author:contains(John Doe)");
start();
click(".o-livechat-LivechatButton");
await contains(".o-mail-Message-author:contains(John Doe)");
});
@@ -6,13 +6,13 @@ import { loadDefaultConfig, setCookie, start } from "@im_livechat/../tests/embed
import { LivechatButton } from "@im_livechat/embed/core_ui/livechat_button";
import { Command } from "@mail/../tests/helpers/command";
import { afterNextRender, click, insertText } from "@mail/../tests/helpers/test_utils";
import { click, contains, insertText } from "@mail/../tests/helpers/test_utils";
import { mockTimeout, triggerHotkey } from "@web/../tests/helpers/utils";
QUnit.module("livechat session");
QUnit.test("Unsuccessful message post shows session expired", async (assert) => {
QUnit.test("Unsuccessful message post shows session expired", async () => {
const pyEnv = await startServer();
const livechatChannelId = await loadDefaultConfig();
const channelId = pyEnv["discuss.channel"].create({
@@ -26,7 +26,7 @@ QUnit.test("Unsuccessful message post shows session expired", async (assert) =>
});
const [channelInfo] = pyEnv.mockServer._mockDiscussChannelChannelInfo([channelId]);
setCookie("im_livechat_session", JSON.stringify(channelInfo));
await start({
start({
mockRPC(route) {
if (route === "/im_livechat/chat_post") {
return false;
@@ -34,16 +34,16 @@ QUnit.test("Unsuccessful message post shows session expired", async (assert) =>
},
});
await insertText(".o-mail-Composer-input", "Hello World!");
await afterNextRender(() => triggerHotkey("Enter"));
assert.containsOnce($, ".o_notification:contains(Session expired)");
assert.containsNone($, ".o-mail-ChatWindow");
triggerHotkey("Enter");
await contains(".o_notification:contains(Session expired)");
await contains(".o-mail-ChatWindow", 0);
});
QUnit.test("Session is reset after failing to persist the channel", async (assert) => {
await startServer();
await loadDefaultConfig();
const { advanceTime } = mockTimeout();
await start({
start({
mockRPC(route, args) {
if (route === "/im_livechat/get_session" && args.persisted) {
return false;
@@ -52,15 +52,12 @@ QUnit.test("Session is reset after failing to persist the channel", async (asser
});
await click(".o-livechat-LivechatButton");
await insertText(".o-mail-Composer-input", "Hello World!");
await afterNextRender(() => triggerHotkey("Enter"));
assert.containsOnce(
$,
".o_notification:contains(No available collaborator, please try again later.)"
);
assert.containsOnce($, ".o-livechat-LivechatButton");
triggerHotkey("Enter");
await contains(".o_notification:contains(No available collaborator, please try again later.)");
await contains(".o-livechat-LivechatButton");
await advanceTime(LivechatButton.DEBOUNCE_DELAY + 10);
await click(".o-livechat-LivechatButton");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
});
QUnit.test("Thread state is saved on the session", async (assert) => {
@@ -68,10 +65,13 @@ QUnit.test("Thread state is saved on the session", async (assert) => {
await loadDefaultConfig();
const env = await start();
await click(".o-livechat-LivechatButton");
await contains(".o-mail-ChatWindow-content");
assert.strictEqual(env.services["im_livechat.livechat"].sessionCookie.state, "open");
await click(".o-mail-ChatWindow-header");
await contains(".o-mail-ChatWindow-content", 0);
assert.strictEqual(env.services["im_livechat.livechat"].sessionCookie.state, "folded");
await click(".o-mail-ChatWindow-header");
await contains(".o-mail-ChatWindow-content");
assert.strictEqual(env.services["im_livechat.livechat"].sessionCookie.state, "open");
});
@@ -82,7 +82,8 @@ QUnit.test("Seen message is saved on the session", async (assert) => {
await click(".o-livechat-LivechatButton");
assert.notOk(env.services["im_livechat.livechat"].sessionCookie.seen_message_id);
await insertText(".o-mail-Composer-input", "Hello World!");
await afterNextRender(() => triggerHotkey("Enter"));
triggerHotkey("Enter");
await contains(".o-mail-Message", 2);
assert.strictEqual(
env.services["im_livechat.livechat"].sessionCookie.seen_message_id,
env.services["im_livechat.livechat"].thread.newestMessage.id
@@ -4,7 +4,7 @@ import { startServer } from "@bus/../tests/helpers/mock_python_environment";
import { loadDefaultConfig, start } from "@im_livechat/../tests/embed/helper/test_utils";
import { afterNextRender, click, insertText } from "@mail/../tests/helpers/test_utils";
import { click, contains, insertText } from "@mail/../tests/helpers/test_utils";
import { triggerHotkey } from "@web/../tests/helpers/utils";
@@ -13,7 +13,7 @@ QUnit.module("transcript sender");
QUnit.test("send", async (assert) => {
await startServer();
await loadDefaultConfig();
await start({
start({
mockRPC(route, args) {
if (route === "/im_livechat/email_livechat_transcript") {
assert.step(`send_transcript - ${args.email}`);
@@ -22,20 +22,21 @@ QUnit.test("send", async (assert) => {
});
await click(".o-livechat-LivechatButton");
await insertText(".o-mail-Composer-input", "Hello World!");
await afterNextRender(() => triggerHotkey("Enter"));
triggerHotkey("Enter");
await contains(".o-mail-Message:contains(Hello World!)");
await click(".o-mail-ChatWindow-command[title*='Close']");
assert.containsOnce($, ".form-text:contains(Receive a copy of this conversation)");
assert.containsOnce($, "button[data-action='sendTranscript']:disabled");
await contains(".form-text:contains(Receive a copy of this conversation)");
await contains("button[data-action='sendTranscript']:disabled");
await insertText("input[placeholder='mail@example.com']", "odoobot@odoo.com");
await click("button[data-action='sendTranscript']");
await click("button[data-action='sendTranscript']:not(:disabled)");
await contains(".form-text:contains(The conversation was sent)");
assert.verifySteps(["send_transcript - odoobot@odoo.com"]);
assert.containsOnce($, ".form-text:contains(The conversation was sent)");
});
QUnit.test("send failed", async (assert) => {
QUnit.test("send failed", async () => {
await startServer();
await loadDefaultConfig();
await start({
start({
mockRPC(route) {
if (route === "/im_livechat/email_livechat_transcript") {
throw new Error();
@@ -44,9 +45,10 @@ QUnit.test("send failed", async (assert) => {
});
await click(".o-livechat-LivechatButton");
await insertText(".o-mail-Composer-input", "Hello World!");
await afterNextRender(() => triggerHotkey("Enter"));
triggerHotkey("Enter");
await contains(".o-mail-Message:contains(Hello World!)");
await click(".o-mail-ChatWindow-command[title*='Close']");
await insertText("input[placeholder='mail@example.com']", "odoobot@odoo.com");
await click("button[data-action='sendTranscript']");
assert.containsOnce($, ".form-text:contains(An error occurred. Please try again.)");
await click("button[data-action='sendTranscript']:not(:disabled)");
await contains(".form-text:contains(An error occurred. Please try again.)");
});
@@ -1,11 +1,11 @@
/* @odoo-module */
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { patchUiSize } from "@mail/../tests/helpers/patch_ui_size";
QUnit.module("messaging menu (patch)");
QUnit.test('livechats should be in "chat" filter', async (assert) => {
QUnit.test('livechats should be in "chat" filter', async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
@@ -18,15 +18,14 @@ QUnit.test('livechats should be in "chat" filter', async (assert) => {
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-MessagingMenu button:contains(All)");
assert.hasClass($(".o-mail-MessagingMenu button:contains(All)"), "fw-bolder");
assert.containsOnce($, ".o-mail-NotificationItem:contains(Visitor 11)");
await contains(".o-mail-MessagingMenu button:contains(All).fw-bolder");
await contains(".o-mail-NotificationItem:contains(Visitor 11)");
await click(".o-mail-MessagingMenu button:contains(Chat)");
assert.hasClass($(".o-mail-MessagingMenu button:contains(Chat)"), "fw-bolder");
assert.containsOnce($, ".o-mail-NotificationItem:contains(Visitor 11)");
await contains(".o-mail-MessagingMenu button:contains(Chat).fw-bolder");
await contains(".o-mail-NotificationItem:contains(Visitor 11)");
});
QUnit.test('livechats should be in "livechat" tab in mobile', async (assert) => {
QUnit.test('livechats should be in "livechat" tab in mobile', async () => {
patchUiSize({ height: 360, width: 640 });
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
@@ -41,7 +40,7 @@ QUnit.test('livechats should be in "livechat" tab in mobile', async (assert) =>
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click("button:contains(Livechat)");
assert.containsOnce($, ".o-mail-NotificationItem:contains(Visitor 11)");
await contains(".o-mail-NotificationItem:contains(Visitor 11)");
await click("button:contains(Chat)");
assert.containsNone($, ".o-mail-NotificationItem:contains(Visitor 11)");
await contains(".o-mail-NotificationItem:contains(Visitor 11)", 0);
});
@@ -1,7 +1,7 @@
/* @odoo-module */
import { patchUiSize, SIZES } from "@mail/../tests/helpers/patch_ui_size";
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("mobile messaging menu (patch)", {
beforeEach() {
@@ -9,29 +9,26 @@ QUnit.module("mobile messaging menu (patch)", {
},
});
QUnit.test("Livechat button is not present when there is no livechat thread", async (assert) => {
QUnit.test("Livechat button is not present when there is no livechat thread", async () => {
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-MessagingMenu");
assert.containsNone($, ".o-mail-MessagingMenu-navbar:contains(Livechat)");
await contains(".o-mail-MessagingMenu");
await contains(".o-mail-MessagingMenu-navbar:contains(Livechat)", 0);
});
QUnit.test(
"Livechat button is present when there is at least one livechat thread",
async (assert) => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-MessagingMenu");
assert.containsOnce($, ".o-mail-MessagingMenu-navbar:contains(Livechat)");
}
);
QUnit.test("Livechat button is present when there is at least one livechat thread", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await contains(".o-mail-MessagingMenu");
await contains(".o-mail-MessagingMenu-navbar:contains(Livechat)");
});
@@ -1,13 +1,12 @@
/* @odoo-module */
import { afterNextRender, click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { makeFakeNotificationService } from "@web/../tests/helpers/mock_services";
import { nextTick } from "@web/../tests/helpers/utils";
QUnit.module("discuss sidebar (patch)");
QUnit.test("Unknown visitor", async (assert) => {
QUnit.test("Unknown visitor", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
@@ -19,12 +18,12 @@ QUnit.test("Unknown visitor", async (assert) => {
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce($, ".o-mail-DiscussSidebar .o-mail-DiscussSidebarCategory-livechat");
assert.containsOnce($, ".o-mail-DiscussSidebarChannel:contains(Visitor 11)");
openDiscuss();
await contains(".o-mail-DiscussSidebar .o-mail-DiscussSidebarCategory-livechat");
await contains(".o-mail-DiscussSidebarChannel:contains(Visitor 11)");
});
QUnit.test("Known user with country", async (assert) => {
QUnit.test("Known user with country", async () => {
const pyEnv = await startServer();
const countryId = pyEnv["res.country"].create({
code: "be",
@@ -43,11 +42,11 @@ QUnit.test("Known user with country", async (assert) => {
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce($, ".o-mail-DiscussSidebarChannel:contains(Jean (Belgium))");
openDiscuss();
await contains(".o-mail-DiscussSidebarChannel:contains(Jean (Belgium))");
});
QUnit.test("Do not show channel when visitor is typing", async (assert) => {
QUnit.test("Do not show channel when visitor is typing", async () => {
const pyEnv = await startServer();
pyEnv["res.users"].write([pyEnv.currentUserId], { im_status: "online" });
const livechatChannelId = pyEnv["im_livechat.channel"].create({
@@ -70,8 +69,9 @@ QUnit.test("Do not show channel when visitor is typing", async (assert) => {
livechat_operator_id: pyEnv.currentPartnerId,
});
const { env, openDiscuss } = await start();
await openDiscuss();
assert.containsNone($, ".o-mail-DiscussSidebarCategory-livechat");
openDiscuss();
await contains(".o-mail-DiscussSidebarCategory", 2);
await contains(".o-mail-DiscussSidebarCategory-livechat", 0);
// simulate livechat visitor typing
const channel = pyEnv["discuss.channel"].searchRead([["id", "=", channelId]])[0];
await pyEnv.withUser(pyEnv.publicUserId, () =>
@@ -80,8 +80,9 @@ QUnit.test("Do not show channel when visitor is typing", async (assert) => {
uuid: channel.uuid,
})
);
// weak test, no guaranteed that we waited long enough for the livechat to potentially appear
await nextTick();
assert.containsNone($, ".o-mail-DiscussSidebarCategory-livechat");
await contains(".o-mail-DiscussSidebarCategory-livechat", 0);
});
QUnit.test("Close should update the value on the server", async (assert) => {
@@ -101,7 +102,7 @@ QUnit.test("Close should update the value on the server", async (assert) => {
});
const currentUserId = pyEnv.currentUserId;
const { env, openDiscuss } = await start();
await openDiscuss();
openDiscuss();
const initalSettings = await env.services.orm.call(
"res.users.settings",
"_find_or_create_for_user",
@@ -134,7 +135,7 @@ QUnit.test("Open should update the value on the server", async (assert) => {
});
const currentUserId = pyEnv.currentUserId;
const { env, openDiscuss } = await start();
await openDiscuss();
openDiscuss();
const initalSettings = await env.services.orm.call(
"res.users.settings",
"_find_or_create_for_user",
@@ -150,7 +151,7 @@ QUnit.test("Open should update the value on the server", async (assert) => {
assert.ok(newSettings.is_discuss_sidebar_category_livechat_open);
});
QUnit.test("Open from the bus", async (assert) => {
QUnit.test("Open from the bus", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
@@ -166,26 +167,19 @@ QUnit.test("Open from the bus", async (assert) => {
is_discuss_sidebar_category_livechat_open: false,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
$,
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel"
);
await afterNextRender(() => {
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.record/insert", {
"res.users.settings": {
id: settingsId,
is_discuss_sidebar_category_livechat_open: true,
},
});
openDiscuss();
await contains(".o-mail-DiscussSidebarCategory-livechat");
await contains(".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel", 0);
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.record/insert", {
"res.users.settings": {
id: settingsId,
is_discuss_sidebar_category_livechat_open: true,
},
});
assert.containsOnce(
$,
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel"
);
await contains(".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel");
});
QUnit.test("Close from the bus", async (assert) => {
QUnit.test("Close from the bus", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
@@ -201,26 +195,18 @@ QUnit.test("Close from the bus", async (assert) => {
is_discuss_sidebar_category_livechat_open: true,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
$,
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel"
);
await afterNextRender(() => {
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.record/insert", {
"res.users.settings": {
id: settingsId,
is_discuss_sidebar_category_livechat_open: false,
},
});
openDiscuss();
await contains(".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel");
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.record/insert", {
"res.users.settings": {
id: settingsId,
is_discuss_sidebar_category_livechat_open: false,
},
});
assert.containsNone(
$,
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel"
);
await contains(".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel", 0);
});
QUnit.test("Smiley face avatar for an anonymous livechat item", async (assert) => {
QUnit.test("Smiley face avatar for an anonymous livechat item", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
@@ -232,15 +218,13 @@ QUnit.test("Smiley face avatar for an anonymous livechat item", async (assert) =
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.strictEqual(
$(".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel img")[0].dataset
.src,
"/mail/static/src/img/smiley/avatar.jpg"
openDiscuss();
await contains(
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel img[data-src='/mail/static/src/img/smiley/avatar.jpg']"
);
});
QUnit.test("Partner profile picture for livechat item linked to a partner", async (assert) => {
QUnit.test("Partner profile picture for livechat item linked to a partner", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Jean" });
const channelId = pyEnv["discuss.channel"].create({
@@ -252,15 +236,13 @@ QUnit.test("Partner profile picture for livechat item linked to a partner", asyn
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.strictEqual(
$(".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel img")[0].dataset
.src,
`/web/image/res.partner/${partnerId}/avatar_128`
openDiscuss(channelId);
await contains(
`.o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel img[data-src='/web/image/res.partner/${partnerId}/avatar_128']`
);
});
QUnit.test("No counter if the category is unfolded and with unread messages", async (assert) => {
QUnit.test("No counter if the category is unfolded and with unread messages", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
@@ -279,15 +261,12 @@ QUnit.test("No counter if the category is unfolded and with unread messages", as
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
$,
".o-mail-DiscussSidebarCategory-livechat .o-mail-Discuss-category-counter",
"should not have a counter if the category is unfolded and with unread messages"
);
openDiscuss();
await contains(".o-mail-DiscussSidebarCategory-livechat");
await contains(".o-mail-DiscussSidebarCategory-livechat .o-mail-Discuss-category-counter", 0);
});
QUnit.test("No counter if category is folded and without unread messages", async (assert) => {
QUnit.test("No counter if category is folded and without unread messages", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
@@ -303,17 +282,14 @@ QUnit.test("No counter if category is folded and without unread messages", async
is_discuss_sidebar_category_livechat_open: false,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
$,
".o-mail-DiscussSidebarCategory-livechat .o-discuss-badge",
"should not have a counter if the category is unfolded and with unread messages"
);
openDiscuss();
await contains(".o-mail-DiscussSidebarCategory-livechat");
await contains(".o-mail-DiscussSidebarCategory-livechat .o-discuss-badge", 0);
});
QUnit.test(
"Counter should have correct value of unread threads if category is folded and with unread messages",
async (assert) => {
async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
@@ -336,15 +312,12 @@ QUnit.test(
is_discuss_sidebar_category_livechat_open: false,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.strictEqual(
$(".o-mail-DiscussSidebarCategory-livechat .o-discuss-badge").text(),
"1"
);
openDiscuss();
await contains(".o-mail-DiscussSidebarCategory-livechat .o-discuss-badge:contains(1)");
}
);
QUnit.test("Close manually by clicking the title", async (assert) => {
QUnit.test("Close manually by clicking the title", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
@@ -360,18 +333,14 @@ QUnit.test("Close manually by clicking the title", async (assert) => {
is_discuss_sidebar_category_livechat_open: true,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
$,
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel",
"Category is unfolded initially"
);
openDiscuss();
await contains(".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel");
// fold the livechat category
await click(".o-mail-DiscussSidebarCategory-livechat .btn");
assert.containsNone($, ".o-mail-DiscussSidebarChannel");
await contains(".o-mail-DiscussSidebarChannel", 0);
});
QUnit.test("Open manually by clicking the title", async (assert) => {
QUnit.test("Open manually by clicking the title", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
@@ -387,21 +356,15 @@ QUnit.test("Open manually by clicking the title", async (assert) => {
is_discuss_sidebar_category_livechat_open: false,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone(
$,
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel",
"Category is folded initially"
);
openDiscuss();
await contains(".o-mail-DiscussSidebarCategory-livechat");
await contains(".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel", 0);
// open the livechat category
await click(".o-mail-DiscussSidebarCategory-livechat .btn");
assert.containsOnce(
$,
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel"
);
await contains(".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel");
});
QUnit.test("Category item should be invisible if the category is closed", async (assert) => {
QUnit.test("Category item should be invisible if the category is closed", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
@@ -413,51 +376,13 @@ QUnit.test("Category item should be invisible if the category is closed", async
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
$,
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel"
);
openDiscuss();
await contains(".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel");
await click(".o-mail-DiscussSidebarCategory-livechat .btn");
assert.containsNone(
$,
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel"
);
await contains(".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel", 0);
});
QUnit.test(
"Active category item should be visible even if the category is closed",
async (assert) => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
$,
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel"
);
await click(".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel");
assert.containsOnce(
$,
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel.o-active"
);
await click(".o-mail-DiscussSidebarCategory-livechat .btn");
assert.containsOnce(
$,
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel"
);
}
);
QUnit.test("Clicking on unpin button unpins the channel", async (assert) => {
QUnit.test("Active category item should be visible even if the category is closed", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
@@ -468,17 +393,35 @@ QUnit.test("Clicking on unpin button unpins the channel", async (assert) => {
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start({
services: {
notification: makeFakeNotificationService((message) => assert.step(message)),
},
});
await openDiscuss();
await click(".o-mail-DiscussSidebarChannel [title='Unpin Conversation']");
assert.verifySteps(["You unpinned your conversation with Visitor 11"]);
const { openDiscuss } = await start();
openDiscuss();
await contains(".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel");
await click(".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel");
await contains(
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel.o-active"
);
await click(".o-mail-DiscussSidebarCategory-livechat .btn");
await contains(".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel");
});
QUnit.test("Message unread counter", async (assert) => {
QUnit.test("Clicking on unpin button unpins the channel", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
channel_member_ids: [
[0, 0, { partner_id: pyEnv.currentPartnerId }],
[0, 0, { partner_id: pyEnv.publicPartnerId }],
],
channel_type: "livechat",
livechat_operator_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
openDiscuss();
await click(".o-mail-DiscussSidebarChannel [title='Unpin Conversation']");
await contains(".o_notification:contains(You unpinned your conversation with Visitor 11)");
});
QUnit.test("Message unread counter", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
anonymous_name: "Visitor 11",
@@ -490,14 +433,12 @@ QUnit.test("Message unread counter", async (assert) => {
livechat_operator_id: pyEnv.currentPartnerId,
});
const { env, openDiscuss } = await start();
await openDiscuss();
await afterNextRender(() =>
pyEnv.withUser(pyEnv.publicUserId, () =>
env.services.rpc("/im_livechat/chat_post", {
message_content: "hu",
uuid: pyEnv["discuss.channel"].searchRead([["id", "=", channelId]])[0].uuid,
})
)
openDiscuss();
pyEnv.withUser(pyEnv.publicUserId, () =>
env.services.rpc("/im_livechat/chat_post", {
message_content: "hu",
uuid: pyEnv["discuss.channel"].searchRead([["id", "=", channelId]])[0].uuid,
})
);
assert.containsOnce($, ".o-mail-DiscussSidebarChannel .badge:contains(1)");
await contains(".o-mail-DiscussSidebarChannel .badge:contains(1)");
});
@@ -1,11 +1,11 @@
/* @odoo-module */
import { Command } from "@mail/../tests/helpers/command";
import { insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { contains, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("suggestion");
QUnit.test("Suggestions are shown after delimiter was used in text (:)", async (assert) => {
QUnit.test("Suggestions are shown after delimiter was used in text (:)", async () => {
const pyEnv = await startServer();
pyEnv["mail.shortcode"].create({
source: "hello",
@@ -22,10 +22,10 @@ QUnit.test("Suggestions are shown after delimiter was used in text (:)", async (
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", ":");
assert.containsOnce($, ".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-suggestion");
await insertText(".o-mail-Composer-input", ")");
assert.containsNone($, ".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-suggestion", 0);
await insertText(".o-mail-Composer-input", " ");
await insertText(".o-mail-Composer-input", ":");
assert.containsOnce($, ".o-mail-Composer-suggestion:contains(hello)");
await contains(".o-mail-Composer-suggestion:contains(hello)");
});
@@ -3,11 +3,11 @@
import { startServer } from "@bus/../tests/helpers/mock_python_environment";
import { Command } from "@mail/../tests/helpers/command";
import { click, start } from "@mail/../tests/helpers/test_utils";
import { click, contains, start } from "@mail/../tests/helpers/test_utils";
QUnit.module("Thread model");
QUnit.test("Thread name unchanged when inviting new users", async (assert) => {
QUnit.test("Thread name unchanged when inviting new users", async () => {
const pyEnv = await startServer();
const userId = pyEnv["res.users"].create({ name: "James" });
pyEnv["res.partner"].create({
@@ -25,11 +25,12 @@ QUnit.test("Thread name unchanged when inviting new users", async (assert) => {
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-Discuss-threadName[title='Visitor #20']");
await contains(".o-mail-Discuss-threadName[title='Visitor #20']");
await click("button[title='Add Users']");
await click(".o-discuss-ChannelInvitation-selectable:contains(James) input");
await click("button:contains(Invite)");
await click("button:contains(Invite):not(:disabled)");
await contains(".o-discuss-ChannelInvitation", 0);
await click("button[title='Show Member List']");
assert.containsOnce($, ".o-discuss-ChannelMember:contains(James)");
assert.containsOnce($, ".o-mail-Discuss-threadName[title='Visitor #20']");
await contains(".o-discuss-ChannelMember:contains(James)");
await contains(".o-mail-Discuss-threadName[title='Visitor #20']");
});
@@ -132,9 +132,9 @@ export class Attachment {
return assignDefined(
{},
{
access_token: this.accessToken,
access_token: this.accessToken || undefined,
filename: this.name || undefined,
unique: this.checksum,
unique: this.checksum || undefined,
}
);
}
@@ -495,7 +495,7 @@ export class Composer extends Component {
const el = this.ref.el;
const attachments = this.props.composer.attachments;
if (
el.value.trim() ||
this.props.composer.textInputContent.trim() ||
(attachments.length > 0 && attachments.every(({ uploading }) => !uploading)) ||
(this.message && this.message.attachments.length > 0)
) {
@@ -503,7 +503,7 @@ export class Composer extends Component {
return;
}
this.state.active = false;
await cb(el.value);
await cb(this.props.composer.textInputContent);
if (this.props.onPostCallback) {
this.props.onPostCallback();
}
@@ -556,7 +556,10 @@ export class Composer extends Component {
}
async editMessage() {
if (this.ref.el.value || this.props.composer.message.attachments.length > 0) {
if (
this.props.composer.textInputContent ||
this.props.composer.message.attachments.length > 0
) {
await this.processMessage(async (value) =>
this.messageService.edit(
this.props.composer.message,
@@ -577,7 +580,7 @@ export class Composer extends Component {
}
addEmoji(str) {
const textContent = this.ref.el.value;
const textContent = this.props.composer.textInputContent;
const firstPart = textContent.slice(0, this.props.composer.selection.start);
const secondPart = textContent.slice(this.props.composer.selection.end, textContent.length);
this.props.composer.textInputContent = firstPart + str + secondPart;
@@ -39,7 +39,7 @@
/>
</t>
</div>
<div class="o-mail-Discuss-header flex-shrink-0 d-flex align-items-center ms-1">
<div class="flex-shrink-0 d-flex align-items-center ms-1">
<t t-foreach="threadActions.actions" t-as="action" t-key="action.id">
<button class="btn px-2" t-attf-class="{{ action.isActive ? 'o-isActive' : '' }}" t-att-disabled="action.disabledCondition" t-att-title="action.name" t-att-name="action.id" t-on-click="() => action.onSelect()">
<i t-if="action.iconLarge" t-att-class="action.iconLarge"/> <span t-if="action.text" t-esc="action.text"/>
@@ -8,7 +8,6 @@ import { useService } from "@web/core/utils/hooks";
export function useSuggestion() {
const comp = useComponent();
const sequential = useSequential();
let pendingFetchCount = 0;
/** @type {import("@mail/core/common/suggestion_service").SuggestionService} */
const suggestionService = useService("mail.suggestion");
const self = {
@@ -34,14 +33,10 @@ export function useSuggestion() {
if (selectionStart !== selectionEnd) {
// avoid interfering with multi-char selection
self.clearSearch();
return;
}
const candidatePositions = [];
// keep the current delimiter if it is still valid
if (self.search.position !== undefined && self.search.position < selectionStart) {
candidatePositions.push(self.search.position);
}
// consider the chars before the current cursor position if the
// current delimiter is no longer valid (or if there is none)
// consider the chars before the current cursor position
let numberOfSpaces = 0;
for (let index = selectionStart - 1; index >= 0; --index) {
if (/\s/.test(content[index])) {
@@ -56,6 +51,10 @@ export function useSuggestion() {
}
candidatePositions.push(index);
}
// keep the current delimiter if it is still valid
if (self.search.position !== undefined && self.search.position < selectionStart) {
candidatePositions.push(self.search.position);
}
const supportedDelimiters = suggestionService.getSupportedDelimiters(self.thread);
for (const candidatePosition of candidatePositions) {
if (candidatePosition < 0 || candidatePosition >= content.length) {
@@ -122,7 +121,7 @@ export function useSuggestion() {
count: 0,
items: undefined,
}),
update({ clearSearchOnEmpty = true } = {}) {
update() {
if (!self.search.delimiter) {
return;
}
@@ -133,11 +132,7 @@ export function useSuggestion() {
);
const { type, mainSuggestions, extraSuggestions = [] } = suggestions;
if (!mainSuggestions.length && !extraSuggestions.length) {
if (clearSearchOnEmpty) {
self.clearSearch();
} else {
self.state.items = undefined;
}
self.state.items = undefined;
return;
}
// arbitrary limit to avoid displaying too many elements at once
@@ -152,27 +147,35 @@ export function useSuggestion() {
},
};
useEffect(
() => {
self.update({ clearSearchOnEmpty: false });
(delimiter, position, term) => {
self.update();
if (self.search.position === undefined || !self.search.delimiter) {
return; // nothing else to fetch
}
sequential(async () => {
if (self.search.position === undefined || !self.search.delimiter) {
if (
self.search.delimiter !== delimiter ||
self.search.position !== position ||
self.search.term !== term
) {
return; // ignore obsolete call
}
pendingFetchCount++;
try {
await suggestionService.fetchSuggestions(self.search, {
thread: self.thread,
onFetched() {
if (owl.status(comp) === "destroyed") {
return;
}
self.update({ clearSearchOnEmpty: pendingFetchCount === 0 });
},
});
} finally {
pendingFetchCount--;
await suggestionService.fetchSuggestions(self.search, {
thread: self.thread,
});
if (owl.status(comp) === "destroyed") {
return;
}
self.update();
if (
self.search.delimiter === delimiter &&
self.search.position === position &&
self.search.term === term &&
!self.state.items?.mainSuggestions.length &&
!self.state.items?.extraSuggestions.length
) {
self.clearSearch();
}
self.update({ clearSearchOnEmpty: pendingFetchCount === 0 });
});
},
() => {
@@ -23,15 +23,15 @@ export class SuggestionService {
return [["@"], ["#"]];
}
async fetchSuggestions({ delimiter, term }, { thread, onFetched } = {}) {
async fetchSuggestions({ delimiter, term }, { thread } = {}) {
const cleanedSearchTerm = cleanTerm(term);
switch (delimiter) {
case "@": {
this.fetchPartners(cleanedSearchTerm, thread).then(onFetched);
await this.fetchPartners(cleanedSearchTerm, thread);
break;
}
case "#":
this.fetchThreads(cleanedSearchTerm).then(onFetched);
await this.fetchThreads(cleanedSearchTerm);
break;
}
}
@@ -61,6 +61,9 @@ export class SuggestionService {
});
}
/**
* @param {string} term
*/
async fetchThreads(term) {
const suggestedThreads = await this.orm.silent.call(
"discuss.channel",
@@ -34,6 +34,7 @@
* To by-pass SCSS min() shadowing CSS min(), we rely on SCSS being case-sensitive while CSS isn't.
*/
max-width: Min(400px, 95vw);
max-height: Min(500px, 95vh);
}
.o-mail-Follower-avatar {
@@ -63,10 +63,7 @@ export class ChannelSelector extends Component {
);
useEffect(
() => {
this.state.navigableListProps.isLoading = true;
this.fetchSuggestions().then(
() => (this.state.navigableListProps.isLoading = false)
);
this.fetchSuggestions();
},
() => [this.state.value]
);
@@ -81,11 +78,14 @@ export class ChannelSelector extends Component {
["name", "ilike", cleanedTerm],
];
const fields = ["name"];
const results = await this.sequential(() =>
this.orm.searchRead("discuss.channel", domain, fields, {
const results = await this.sequential(async () => {
this.state.navigableListProps.isLoading = true;
const res = await this.orm.searchRead("discuss.channel", domain, fields, {
limit: 10,
})
);
});
this.state.navigableListProps.isLoading = false;
return res;
});
if (!results) {
this.state.navigableListProps.options = [];
return;
@@ -106,13 +106,16 @@ export class ChannelSelector extends Component {
return;
}
if (this.props.category.id === "chats") {
const results = await this.sequential(() =>
this.orm.call("res.partner", "im_search", [
const results = await this.sequential(async () => {
this.state.navigableListProps.isLoading = true;
const res = await this.orm.call("res.partner", "im_search", [
cleanedTerm,
10,
this.state.selectedPartners,
])
);
]);
this.state.navigableListProps.isLoading = false;
return res;
});
if (!results) {
this.state.navigableListProps.options = [];
return;
@@ -8,9 +8,9 @@ patch(Composer.prototype, {
get placeholder() {
if (this.thread && this.thread.model !== "discuss.channel") {
if (this.props.type === "message") {
return _t("Send a message to followers...");
return _t("Send a message to followers…");
} else {
return _t("Log an internal note...");
return _t("Log an internal note…");
}
}
return super.placeholder;
@@ -1,12 +1,12 @@
/* @odoo-module */
import { click, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
import { makeDeferred, patchWithCleanup } from "@web/../tests/helpers/utils";
QUnit.module("activity mark as done popover");
QUnit.test("activity mark done popover simplest layout", async (assert) => {
QUnit.test("activity mark done popover simplest layout", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["mail.activity"].create({
@@ -16,23 +16,20 @@ QUnit.test("activity mark done popover simplest layout", async (assert) => {
res_model: "res.partner",
});
const { openView } = await start();
await openView({
openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
await click(".btn:contains('Mark Done')");
assert.containsOnce($, ".o-mail-ActivityMarkAsDone");
assert.containsOnce($, ".o-mail-ActivityMarkAsDone textarea[placeholder='Write Feedback']");
assert.containsOnce(
$,
".o-mail-ActivityMarkAsDone button[aria-label='Done and Schedule Next']"
);
assert.containsOnce($, ".o-mail-ActivityMarkAsDone button[aria-label='Done']");
assert.containsOnce($, ".o-mail-ActivityMarkAsDone button:contains(Discard)");
await contains(".o-mail-ActivityMarkAsDone");
await contains(".o-mail-ActivityMarkAsDone textarea[placeholder='Write Feedback']");
await contains(".o-mail-ActivityMarkAsDone button[aria-label='Done and Schedule Next']");
await contains(".o-mail-ActivityMarkAsDone button[aria-label='Done']");
await contains(".o-mail-ActivityMarkAsDone button:contains(Discard)");
});
QUnit.test("activity with force next mark done popover simplest layout", async (assert) => {
QUnit.test("activity with force next mark done popover simplest layout", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["mail.activity"].create({
@@ -43,20 +40,17 @@ QUnit.test("activity with force next mark done popover simplest layout", async (
res_model: "res.partner",
});
const { openView } = await start();
await openView({
openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
await click(".btn:contains('Mark Done')");
assert.containsOnce($, ".o-mail-ActivityMarkAsDone");
assert.containsOnce($, ".o-mail-ActivityMarkAsDone textarea[placeholder='Write Feedback']");
assert.containsOnce(
$,
".o-mail-ActivityMarkAsDone button[aria-label='Done and Schedule Next']"
);
assert.containsNone($, ".o-mail-ActivityMarkAsDone button[aria-label='Done']");
assert.containsOnce($, ".o-mail-ActivityMarkAsDone button:contains(Discard)");
await contains(".o-mail-ActivityMarkAsDone");
await contains(".o-mail-ActivityMarkAsDone textarea[placeholder='Write Feedback']");
await contains(".o-mail-ActivityMarkAsDone button[aria-label='Done and Schedule Next']");
await contains(".o-mail-ActivityMarkAsDone button[aria-label='Done']", 0);
await contains(".o-mail-ActivityMarkAsDone button:contains(Discard)");
});
QUnit.test("activity mark done popover mark done without feedback", async (assert) => {
@@ -127,17 +121,16 @@ QUnit.test("activity mark done popover mark done with feedback", async (assert)
}
},
});
await openView({
openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
await click(".btn:contains('Mark Done')");
insertText(
await insertText(
".o-mail-ActivityMarkAsDone textarea[placeholder='Write Feedback']",
"This task is done"
).catch(() => {}); // no render
await nextTick();
);
await click(".o-mail-ActivityMarkAsDone button[aria-label='Done']");
assert.verifySteps(["action_feedback"]);
});
@@ -169,7 +162,7 @@ QUnit.test("activity mark done popover mark done and schedule next", async (asse
}
},
});
await openView({
openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
@@ -183,11 +176,10 @@ QUnit.test("activity mark done popover mark done and schedule next", async (asse
},
});
await click(".btn:contains('Mark Done')");
insertText(
await insertText(
".o-mail-ActivityMarkAsDone textarea[placeholder='Write Feedback']",
"This task is done"
).catch(() => {}); // no render
await nextTick();
);
await click(".o-mail-ActivityMarkAsDone button[aria-label='Done and Schedule Next']");
assert.verifySteps(["action_feedback_schedule_next"]);
});
@@ -208,13 +200,15 @@ QUnit.test("[technical] activity mark done & schedule next with new action", asy
}
},
});
await openView({
openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
const def = makeDeferred();
patchWithCleanup(env.services.action, {
doAction(action) {
def.resolve();
assert.step("activity_action");
assert.deepEqual(
action,
@@ -225,5 +219,6 @@ QUnit.test("[technical] activity mark done & schedule next with new action", asy
});
await click(".btn:contains('Mark Done')");
await click(".o-mail-ActivityMarkAsDone button[aria-label='Done and Schedule Next']");
await def;
assert.verifySteps(["activity_action"]);
});
@@ -1,6 +1,6 @@
/* @odoo-module */
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("activity menu");
@@ -14,5 +14,6 @@ QUnit.test("should update activities when opening the activity menu", async (ass
res_model: "res.partner",
});
await click(".o_menu_systray i[aria-label='Activities']");
await contains(".o-mail-ActivityMenu-counter:contains(1)");
assert.strictEqual($(".o-mail-ActivityMenu-counter").text(), "1");
});
@@ -1,13 +1,6 @@
/* @odoo-module */
import {
afterNextRender,
click,
contains,
createFile,
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
import { click, contains, createFile, start, startServer } from "@mail/../tests/helpers/test_utils";
import { date_to_str } from "@web/legacy/js/core/time";
import {
@@ -33,7 +26,7 @@ const views = {
QUnit.module("activity");
QUnit.test("activity upload document is available", async (assert) => {
QUnit.test("activity upload document is available", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const [activityTypeId] = pyEnv["mail.activity.type"].search([["name", "=", "Upload Document"]]);
@@ -44,18 +37,14 @@ QUnit.test("activity upload document is available", async (assert) => {
res_id: partnerId,
res_model: "res.partner",
});
const { openView } = await start();
await openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity-info:contains('Upload Document')");
assert.containsOnce($, ".btn .fa-upload");
assert.containsOnce($, ".o-mail-Activity .o_input_file");
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity-info:contains('Upload Document')");
await contains(".btn .fa-upload");
await contains(".o-mail-Activity .o_input_file");
});
QUnit.test("activity can upload a document", async (assert) => {
QUnit.test("activity can upload a document", async () => {
const pyEnv = await startServer();
const fakeId = pyEnv["res.partner"].create({});
const [activityTypeId] = pyEnv["mail.activity.type"].search([["name", "=", "Upload Document"]]);
@@ -67,42 +56,38 @@ QUnit.test("activity can upload a document", async (assert) => {
res_model: "res.partner",
});
const { openFormView } = await start({ serverData: { views } });
await openFormView("res.partner", fakeId);
openFormView("res.partner", fakeId);
const file = await createFile({
content: "hello, world",
contentType: "text/plain",
name: "text.txt",
});
assert.containsOnce($, ".o-mail-Activity-info:contains('Upload Document')");
await contains(".o-mail-Activity-info:contains('Upload Document')");
inputFiles($(".o-mail-Activity .o_input_file")[0], [file]);
await contains(".o-mail-Activity-info:contains('Upload Document')", 0);
await contains("button[aria-label='Attach files']:contains(1)");
});
QUnit.test("activity simplest layout", async (assert) => {
QUnit.test("activity simplest layout", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["mail.activity"].create({
res_id: partnerId,
res_model: "res.partner",
});
const { openView } = await start();
await openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".o-mail-Activity-sidebar");
assert.containsOnce($, ".o-mail-Activity-user");
assert.containsOnce($, ".o-mail-Activity-info");
assert.containsNone($, ".o-mail-Activity-note");
assert.containsNone($, ".o-mail-Activity-details");
assert.containsNone($, ".o-mail-Activity-mailTemplates");
assert.containsNone($, ".btn:contains('Edit')");
assert.containsNone($, ".o-mail-Activity span:contains(Cancel)");
assert.containsNone($, ".btn:contains('Mark Done')");
assert.containsNone($, ".o-mail-Activity-info:contains('Upload Document')");
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity");
await contains(".o-mail-Activity-sidebar");
await contains(".o-mail-Activity-user");
await contains(".o-mail-Activity-info");
await contains(".o-mail-Activity-note", 0);
await contains(".o-mail-Activity-details", 0);
await contains(".o-mail-Activity-mailTemplates", 0);
await contains(".btn:contains('Edit')", 0);
await contains(".o-mail-Activity span:contains(Cancel)", 0);
await contains(".btn:contains('Mark Done')", 0);
await contains(".o-mail-Activity-info:contains('Upload Document')", 0);
});
QUnit.test("activity with note layout", async (assert) => {
@@ -113,18 +98,14 @@ QUnit.test("activity with note layout", async (assert) => {
res_id: partnerId,
res_model: "res.partner",
});
const { openView } = await start();
await openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".o-mail-Activity-note");
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity");
await contains(".o-mail-Activity-note");
assert.strictEqual($(".o-mail-Activity-note").text(), "There is no good or bad note");
});
QUnit.test("activity info layout when planned after tomorrow", async (assert) => {
QUnit.test("activity info layout when planned after tomorrow", async () => {
patchDate(2023, 0, 11, 12, 0, 0);
const today = new Date();
const fiveDaysFromNow = new Date();
@@ -137,18 +118,14 @@ QUnit.test("activity info layout when planned after tomorrow", async (assert) =>
res_model: "res.partner",
state: "planned",
});
const { openView } = await start();
await openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".o-mail-Activity .text-success");
assert.containsOnce($, ".o-mail-Activity:contains('Due in 5 days:')");
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity");
await contains(".o-mail-Activity .text-success");
await contains(".o-mail-Activity:contains('Due in 5 days:')");
});
QUnit.test("activity info layout when planned tomorrow", async (assert) => {
QUnit.test("activity info layout when planned tomorrow", async () => {
patchDate(2023, 0, 11, 12, 0, 0);
const today = new Date();
const tomorrow = new Date();
@@ -161,18 +138,14 @@ QUnit.test("activity info layout when planned tomorrow", async (assert) => {
res_model: "res.partner",
state: "planned",
});
const { openView } = await start();
await openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".o-mail-Activity .text-success");
assert.containsOnce($, ".o-mail-Activity:contains('Tomorrow:')");
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity");
await contains(".o-mail-Activity .text-success");
await contains(".o-mail-Activity:contains('Tomorrow:')");
});
QUnit.test("activity info layout when planned today", async (assert) => {
QUnit.test("activity info layout when planned today", async () => {
patchDate(2023, 0, 11, 12, 0, 0);
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
@@ -182,18 +155,14 @@ QUnit.test("activity info layout when planned today", async (assert) => {
res_model: "res.partner",
state: "today",
});
const { openView } = await start();
await openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".o-mail-Activity .text-warning");
assert.containsOnce($, ".o-mail-Activity:contains('Today:')");
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity");
await contains(".o-mail-Activity .text-warning");
await contains(".o-mail-Activity:contains('Today:')");
});
QUnit.test("activity info layout when planned yesterday", async (assert) => {
QUnit.test("activity info layout when planned yesterday", async () => {
patchDate(2023, 0, 11, 12, 0, 0);
const today = new Date();
const yesterday = new Date();
@@ -206,18 +175,14 @@ QUnit.test("activity info layout when planned yesterday", async (assert) => {
res_model: "res.partner",
state: "overdue",
});
const { openView } = await start();
await openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".o-mail-Activity .text-danger");
assert.containsOnce($, ".o-mail-Activity:contains('Yesterday:')");
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity");
await contains(".o-mail-Activity .text-danger");
await contains(".o-mail-Activity:contains('Yesterday:')");
});
QUnit.test("activity info layout when planned before yesterday", async (assert) => {
QUnit.test("activity info layout when planned before yesterday", async () => {
patchDate(2023, 0, 11, 12, 0, 0);
const today = new Date();
const fiveDaysBeforeNow = new Date();
@@ -230,15 +195,11 @@ QUnit.test("activity info layout when planned before yesterday", async (assert)
res_model: "res.partner",
state: "overdue",
});
const { openView } = await start();
await openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".o-mail-Activity .text-danger");
assert.containsOnce($, ".o-mail-Activity:contains('5 days overdue:')");
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity");
await contains(".o-mail-Activity .text-danger");
await contains(".o-mail-Activity:contains('5 days overdue:')");
});
/**
@@ -246,7 +207,7 @@ QUnit.test("activity info layout when planned before yesterday", async (assert)
* Pass locally or if triggered on runbot manually, but fail on ci build.
* The hook/runbot environment might not support this test, so skipped for now.
*/
QUnit.skip("activity info layout change at midnight", async (assert) => {
QUnit.skip("activity info layout change at midnight", async () => {
const mock = mockTimeout();
patchDate(2023, 11, 7, 23, 59, 59);
const today = new Date();
@@ -260,24 +221,20 @@ QUnit.skip("activity info layout change at midnight", async (assert) => {
res_model: "res.partner",
state: "planned",
});
const { openView } = await start();
await openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".o-mail-Activity .text-success");
assert.containsOnce($, ".o-mail-Activity:contains('Tomorrow:')");
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity");
await contains(".o-mail-Activity .text-success");
await contains(".o-mail-Activity:contains('Tomorrow:')");
patchDate(2023, 11, 8, 0, 0, 1); // OXP is coming!
await mock.advanceTime(2000);
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".o-mail-Activity .text-warning");
assert.containsOnce($, ".o-mail-Activity:contains('Today:')");
await contains(".o-mail-Activity");
await contains(".o-mail-Activity .text-warning");
await contains(".o-mail-Activity:contains('Today:')");
});
QUnit.test("activity with a summary layout", async (assert) => {
QUnit.test("activity with a summary layout", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["mail.activity"].create({
@@ -285,16 +242,12 @@ QUnit.test("activity with a summary layout", async (assert) => {
res_model: "res.partner",
summary: "test summary",
});
const { openView } = await start();
await openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity-info:contains('test summary')");
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity-info:contains('test summary')");
});
QUnit.test("activity without summary layout", async (assert) => {
QUnit.test("activity without summary layout", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["mail.activity"].create({
@@ -302,16 +255,12 @@ QUnit.test("activity without summary layout", async (assert) => {
res_id: partnerId,
res_model: "res.partner",
});
const { openView } = await start();
await openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity-info:contains('Email')");
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity-info:contains('Email')");
});
QUnit.test("activity details toggle", async (assert) => {
QUnit.test("activity details toggle", async () => {
patchDate(2023, 0, 11, 12, 0, 0);
const today = new Date();
const tomorrow = new Date();
@@ -326,21 +275,17 @@ QUnit.test("activity details toggle", async (assert) => {
res_id: partnerId,
res_model: "res.partner",
});
const { openView } = await start();
await openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsNone($, ".o-mail-Activity-details");
assert.containsOnce($, ".o-mail-Activity-info i[aria-label='Info']");
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity");
await contains(".o-mail-Activity-details", 0);
await contains(".o-mail-Activity-info i[aria-label='Info']");
await click(".o-mail-Activity-info i[aria-label='Info']");
assert.containsOnce($, ".o-mail-Activity-details");
await contains(".o-mail-Activity-details");
await click(".o-mail-Activity-info i[aria-label='Info']");
assert.containsNone($, ".o-mail-Activity-details");
await contains(".o-mail-Activity-details", 0);
});
QUnit.test("activity with mail template layout", async (assert) => {
@@ -354,19 +299,15 @@ QUnit.test("activity with mail template layout", async (assert) => {
res_id: partnerId,
res_model: "res.partner",
});
const { openView } = await start();
await openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".o-mail-Activity-sidebar");
assert.containsOnce($, ".o-mail-Activity-mailTemplates");
assert.containsOnce($, ".o-mail-ActivityMailTemplate-name");
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity");
await contains(".o-mail-Activity-sidebar");
await contains(".o-mail-Activity-mailTemplates");
await contains(".o-mail-ActivityMailTemplate-name:contains(Dummy mail template)");
assert.strictEqual($(".o-mail-ActivityMailTemplate-name").text(), "Dummy mail template");
assert.containsOnce($, ".o-mail-ActivityMailTemplate-preview");
assert.containsOnce($, ".o-mail-ActivityMailTemplate-send");
await contains(".o-mail-ActivityMailTemplate-preview");
await contains(".o-mail-ActivityMailTemplate-send");
});
QUnit.test("activity with mail template: preview mail", async (assert) => {
@@ -380,12 +321,8 @@ QUnit.test("activity with mail template: preview mail", async (assert) => {
res_id: partnerId,
res_model: "res.partner",
});
const { env, openView } = await start();
await openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
const { env, openFormView } = await start();
openFormView("res.partner", partnerId);
patchWithCleanup(env.services.action, {
doAction(action) {
assert.step("do_action");
@@ -396,10 +333,10 @@ QUnit.test("activity with mail template: preview mail", async (assert) => {
assert.strictEqual(action.res_model, "mail.compose.message");
},
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".o-mail-ActivityMailTemplate-preview");
await contains(".o-mail-Activity");
await contains(".o-mail-ActivityMailTemplate-preview");
$(".o-mail-ActivityMailTemplate-preview")[0].click();
await click(".o-mail-ActivityMailTemplate-preview");
assert.verifySteps(["do_action"]);
});
@@ -414,7 +351,7 @@ QUnit.test("activity with mail template: send mail", async (assert) => {
res_id: partnerId,
res_model: "res.partner",
});
const { openView } = await start({
const { openFormView } = await start({
async mockRPC(route, args) {
if (args.method === "activity_send_mail") {
assert.step("activity_send_mail");
@@ -426,19 +363,15 @@ QUnit.test("activity with mail template: send mail", async (assert) => {
}
},
});
await openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".o-mail-ActivityMailTemplate-send");
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity");
await contains(".o-mail-ActivityMailTemplate-send");
click(".o-mail-ActivityMailTemplate-send").catch(() => {});
await click(".o-mail-ActivityMailTemplate-send");
assert.verifySteps(["activity_send_mail"]);
});
QUnit.test("activity click on mark as done", async (assert) => {
QUnit.test("activity click on mark as done", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const activityTypeId = pyEnv["mail.activity.type"].search([["name", "=", "Email"]])[0];
@@ -449,20 +382,16 @@ QUnit.test("activity click on mark as done", async (assert) => {
res_id: partnerId,
res_model: "res.partner",
});
const { openView } = await start();
await openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".btn:contains('Mark Done')");
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity");
await contains(".btn:contains('Mark Done')");
await click(".btn:contains('Mark Done')");
assert.containsOnce($, ".o-mail-ActivityMarkAsDone");
await contains(".o-mail-ActivityMarkAsDone");
await click(".btn:contains('Mark Done')");
assert.containsNone($, ".o-mail-ActivityMarkAsDone");
await contains(".o-mail-ActivityMarkAsDone", 0);
});
QUnit.test(
@@ -478,18 +407,16 @@ QUnit.test(
res_id: partnerId,
res_model: "res.partner",
});
const { openView } = await start();
await openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".btn:contains('Mark Done')");
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity");
await contains(".btn:contains('Mark Done')");
await click(".btn:contains('Mark Done')");
assert.strictEqual(
$(".o-mail-ActivityMarkAsDone textarea[placeholder='Write Feedback']")[0],
(
await contains(".o-mail-ActivityMarkAsDone textarea[placeholder='Write Feedback']")
)[0],
document.activeElement
);
}
@@ -507,12 +434,8 @@ QUnit.test("activity click on edit", async (assert) => {
res_id: partnerId,
res_model: "res.partner",
});
const { env, openView } = await start();
await openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
const { env, openFormView } = await start();
openFormView("res.partner", partnerId);
patchWithCleanup(env.services.action, {
doAction(action) {
assert.step("do_action");
@@ -524,10 +447,7 @@ QUnit.test("activity click on edit", async (assert) => {
return super.doAction(...arguments);
},
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".btn:contains('Edit')");
await click(".btn:contains('Edit')");
await click(".o-mail-Activity .btn:contains('Edit')");
assert.verifySteps(["do_action"]);
});
@@ -541,7 +461,7 @@ QUnit.test("activity click on cancel", async (assert) => {
res_id: partnerId,
res_model: "res.partner",
});
const { openView } = await start({
const { openFormView } = await start({
async mockRPC(route, args) {
if (route === "/web/dataset/call_kw/mail.activity/unlink") {
assert.step("unlink");
@@ -550,20 +470,13 @@ QUnit.test("activity click on cancel", async (assert) => {
}
},
});
await openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Activity");
assert.containsOnce($, ".o-mail-Activity span:contains(Cancel)");
openFormView("res.partner", partnerId);
await click(".o-mail-Activity span:contains(Cancel)");
await contains(".o-mail-Activity", 0);
assert.verifySteps(["unlink"]);
assert.containsNone($, ".o-mail-Activity");
});
QUnit.test("activity mark done popover close on ESCAPE", async (assert) => {
QUnit.test("activity mark done popover close on ESCAPE", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const activityTypeId = pyEnv["mail.activity.type"].search([["name", "=", "Email"]])[0];
@@ -574,21 +487,17 @@ QUnit.test("activity mark done popover close on ESCAPE", async (assert) => {
res_id: partnerId,
res_model: "res.partner",
});
const { openView } = await start();
await openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await click(".btn:contains('Mark Done')");
assert.containsOnce($, ".o-mail-ActivityMarkAsDone");
await contains(".o-mail-ActivityMarkAsDone");
await afterNextRender(() => triggerHotkey("Escape"));
assert.containsNone($, ".o-mail-ActivityMarkAsDone");
triggerHotkey("Escape");
await contains(".o-mail-ActivityMarkAsDone", 0);
});
QUnit.test("activity mark done popover click on discard", async (assert) => {
QUnit.test("activity mark done popover click on discard", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const activityTypeId = pyEnv["mail.activity.type"].search([["name", "=", "Email"]])[0];
@@ -599,21 +508,15 @@ QUnit.test("activity mark done popover click on discard", async (assert) => {
res_id: partnerId,
res_model: "res.partner",
});
const { openView } = await start();
await openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
const { openFormView } = await start();
openFormView("res.partner", partnerId);
await click(".btn:contains('Mark Done')");
assert.containsOnce($, ".o-mail-ActivityMarkAsDone");
assert.containsOnce($, ".o-mail-ActivityMarkAsDone button:contains(Discard)");
await click(".o-mail-ActivityMarkAsDone button:contains(Discard)");
assert.containsNone($, ".o-mail-ActivityMarkAsDone");
await contains(".o-mail-ActivityMarkAsDone", 0);
});
QUnit.test("Activity are sorted by deadline", async (assert) => {
QUnit.test("Activity are sorted by deadline", async () => {
patchDate(2023, 0, 11, 12, 0, 0);
const today = new Date();
const dateBefore = new Date();
@@ -641,8 +544,8 @@ QUnit.test("Activity are sorted by deadline", async (assert) => {
state: "overdue",
});
const { openFormView } = await start();
await openFormView("res.partner", partnerId);
assert.containsOnce($, ".o-mail-Activity:eq(0):contains(5 days overdue:)");
assert.containsOnce($, ".o-mail-Activity:eq(1):contains(Today:)");
assert.containsOnce($, ".o-mail-Activity:eq(2):contains(Due in 4 days:)");
openFormView("res.partner", partnerId);
await contains(".o-mail-Activity:eq(0):contains(5 days overdue:)");
await contains(".o-mail-Activity:eq(1):contains(Today:)");
await contains(".o-mail-Activity:eq(2):contains(Due in 4 days:)");
});
@@ -13,7 +13,7 @@ QUnit.module("chat window manager");
QUnit.test("chat window does not fetch messages if hidden", async (assert) => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create([
const [channeId1, channelId2, channelId3] = pyEnv["discuss.channel"].create([
{
channel_member_ids: [
Command.create({ is_minimized: true, partner_id: pyEnv.currentPartnerId }),
@@ -30,6 +30,26 @@ QUnit.test("chat window does not fetch messages if hidden", async (assert) => {
],
},
]);
pyEnv["mail.message"].create([
{
body: "Orange",
res_id: channeId1,
message_type: "comment",
model: "discuss.channel",
},
{
body: "Apple",
res_id: channelId2,
message_type: "comment",
model: "discuss.channel",
},
{
body: "Banana",
res_id: channelId3,
message_type: "comment",
model: "discuss.channel",
},
]);
patchUiSize({ width: 900 });
assert.ok(
CHAT_WINDOW_END_GAP_WIDTH * 2 + CHAT_WINDOW_WIDTH * 2 + CHAT_WINDOW_INBETWEEN_WIDTH < 900
@@ -45,14 +65,17 @@ QUnit.test("chat window does not fetch messages if hidden", async (assert) => {
}
},
});
assert.containsN($, ".o-mail-ChatWindow", 2);
assert.containsOnce($, ".o-mail-ChatWindowHiddenToggler");
await contains(".o-mail-ChatWindow", 2);
await contains(".o-mail-ChatWindowHiddenToggler");
await contains(".o-mail-Message:contains(Orange)");
await contains(".o-mail-Message:contains(Apple)", 0);
await contains(".o-mail-Message:contains(Banana)");
assert.verifySteps(["fetch_messages", "fetch_messages"]);
});
QUnit.test("click on hidden chat window should fetch its messages", async (assert) => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create([
const [channeId1, channelId2, channelId3] = pyEnv["discuss.channel"].create([
{
channel_member_ids: [
Command.create({ is_minimized: true, partner_id: pyEnv.currentPartnerId }),
@@ -69,6 +92,26 @@ QUnit.test("click on hidden chat window should fetch its messages", async (asser
],
},
]);
pyEnv["mail.message"].create([
{
body: "Orange",
res_id: channeId1,
message_type: "comment",
model: "discuss.channel",
},
{
body: "Apple",
res_id: channelId2,
message_type: "comment",
model: "discuss.channel",
},
{
body: "Banana",
res_id: channelId3,
message_type: "comment",
model: "discuss.channel",
},
]);
patchUiSize({ width: 900 });
assert.ok(
CHAT_WINDOW_END_GAP_WIDTH * 2 + CHAT_WINDOW_WIDTH * 2 + CHAT_WINDOW_INBETWEEN_WIDTH < 900
@@ -84,11 +127,17 @@ QUnit.test("click on hidden chat window should fetch its messages", async (asser
}
},
});
assert.containsN($, ".o-mail-ChatWindow", 2);
assert.containsOnce($, ".o-mail-ChatWindowHiddenToggler");
await contains(".o-mail-ChatWindow", 2);
await contains(".o-mail-ChatWindowHiddenToggler");
await contains(".o-mail-Message:contains(Orange)");
await contains(".o-mail-Message:contains(Apple)", 0);
await contains(".o-mail-Message:contains(Banana)");
assert.verifySteps(["fetch_messages", "fetch_messages"]);
await click(".o-mail-ChatWindowHiddenToggler");
await click(".o-mail-ChatWindowHiddenMenu-item .o-mail-ChatWindow-command[title='Open']");
await contains(".o-mail-Message:contains(Orange)");
await contains(".o-mail-Message:contains(Apple)");
await contains(".o-mail-Message:contains(Banana)", 0);
assert.verifySteps(["fetch_messages"]);
});
@@ -125,14 +174,14 @@ QUnit.test(
await contains(
".o-mail-ChatWindow-header:contains(channel-D) .o-mail-ChatWindow-command[title='Close Chat Window']"
);
assert.containsN($, ".o-mail-ChatWindow", 2);
assert.containsOnce($, ".o-mail-ChatWindow:eq(0):contains(channel-A)");
assert.containsOnce($, ".o-mail-ChatWindow:eq(1):contains(channel-D)");
assert.containsOnce($, ".o-mail-ChatWindowHiddenToggler:contains(2)");
await contains(".o-mail-ChatWindow", 2);
await contains(".o-mail-ChatWindow:eq(0):contains(channel-A)");
await contains(".o-mail-ChatWindow:eq(1):contains(channel-D)");
await contains(".o-mail-ChatWindowHiddenToggler:contains(2)");
await click(
".o-mail-ChatWindow-header:contains(channel-D) .o-mail-ChatWindow-command[title='Close Chat Window']"
);
assert.containsOnce($, ".o-mail-ChatWindow:eq(0):contains(channel-A)");
assert.containsOnce($, ".o-mail-ChatWindow:eq(1):contains(channel-C)");
await contains(".o-mail-ChatWindow:eq(0):contains(channel-A)");
await contains(".o-mail-ChatWindow:eq(1):contains(channel-C)");
}
);
@@ -19,7 +19,7 @@ import {
startServer,
} from "@mail/../tests/helpers/test_utils";
import { nextTick, triggerEvent, triggerHotkey } from "@web/../tests/helpers/utils";
import { triggerHotkey } from "@web/../tests/helpers/utils";
import { file } from "@web/../tests/legacy/helpers/test_utils";
const { inputFiles } = file;
@@ -28,7 +28,7 @@ QUnit.module("chat window");
QUnit.test(
"Mobile: chat window shouldn't open automatically after receiving a new message",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const userId = pyEnv["res.users"].create({ partner_id: partnerId });
@@ -41,7 +41,8 @@ QUnit.test(
});
patchUiSize({ size: SIZES.SM });
const { env } = await start();
await contains(".o_menu_systray i[aria-label='Messages']");
await contains(".o-mail-MessagingMenu-counter", 0);
// simulate receiving a message
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
@@ -50,14 +51,14 @@ QUnit.test(
thread_model: "discuss.channel",
})
);
await nextAnimationFrame();
assert.containsNone($, ".o-mail-ChatWindow");
await contains(".o-mail-MessagingMenu-counter:contains(1)");
await contains(".o-mail-ChatWindow", 0);
}
);
QUnit.test(
'chat window: post message on channel with "CTRL-Enter" keyboard shortcut for small screen size',
async (assert) => {
async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
channel_member_ids: [
@@ -69,8 +70,8 @@ QUnit.test(
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
await insertText(".o-mail-ChatWindow .o-mail-Composer-input", "Test");
await afterNextRender(() => triggerHotkey("control+Enter"));
assert.containsOnce($, ".o-mail-Message");
triggerHotkey("control+Enter");
await contains(".o-mail-Message");
}
);
@@ -94,26 +95,19 @@ QUnit.test("Message post in chat window of chatter should log a note", async (as
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-ChatWindow");
assert.containsOnce(
$,
".o-mail-Message:contains(A needaction message to have it in messaging menu)"
);
assert.containsOnce(
$(".o-mail-Message:contains(A needaction message to have it in messaging menu)"),
".o-mail-Message-bubble.border" // bordered bubble = "Send message" mode
);
assert.containsOnce($, ".o-mail-Composer [placeholder='Log an internal note...']");
await contains(".o-mail-ChatWindow");
await contains(".o-mail-Message:contains(A needaction message to have it in messaging menu)");
await contains(
".o-mail-Message:contains(A needaction message to have it in messaging menu) .o-mail-Message-bubble.border"
); // bordered bubble = "Send message" mode
await contains(".o-mail-Composer [placeholder='Log an internal note…']");
await insertText(".o-mail-ChatWindow .o-mail-Composer-input", "Test");
await afterNextRender(() => triggerHotkey("control+Enter"));
assert.containsOnce($, ".o-mail-Message:contains(Test)");
assert.containsNone(
$(".o-mail-Message:contains(Test)"),
".o-mail-Message-bubble.border" // non-bordered bubble = "Log note" mode
);
triggerHotkey("control+Enter");
await contains(".o-mail-Message:contains(Test)");
await contains(".o-mail-Message:contains(Test) .o-mail-Message-bubble.border", 0); // non-bordered bubble = "Log note" mode
});
QUnit.test("load messages from opening chat window from messaging menu", async (assert) => {
QUnit.test("load messages from opening chat window from messaging menu", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
@@ -130,7 +124,7 @@ QUnit.test("load messages from opening chat window from messaging menu", async (
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
assert.containsN($, ".o-mail-Message", 21);
await contains(".o-mail-Message", 21);
});
QUnit.test("chat window: basic rendering", async (assert) => {
@@ -139,45 +133,45 @@ QUnit.test("chat window: basic rendering", async (assert) => {
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-ChatWindow");
assert.containsOnce($, ".o-mail-ChatWindow-header");
await contains(".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow-header");
assert.containsOnce($(".o-mail-ChatWindow-header"), ".o-mail-ChatWindow-threadAvatar");
assert.containsOnce($, ".o-mail-ChatWindow-name:contains(General)");
assert.containsN($, ".o-mail-ChatWindow-command", 4);
assert.containsOnce($, "[title='Start a Call']");
assert.containsOnce($, "[title='Open Actions Menu']");
assert.containsOnce($, "[title='Fold']");
assert.containsOnce($, "[title='Close Chat Window']");
assert.containsOnce($, ".o-mail-ChatWindow-content .o-mail-Thread");
await contains(".o-mail-ChatWindow-name:contains(General)");
await contains(".o-mail-ChatWindow-command", 4);
await contains("[title='Start a Call']");
await contains("[title='Open Actions Menu']");
await contains("[title='Fold']");
await contains("[title='Close Chat Window']");
await contains(".o-mail-ChatWindow-content .o-mail-Thread");
assert.strictEqual(
$(".o-mail-ChatWindow-content .o-mail-Thread").text().trim(),
"There are no messages in this conversation."
);
await click("[title='Open Actions Menu']");
assert.containsN($, ".o-mail-ChatWindow-command", 9);
assert.containsOnce($, "[title='Pinned Messages']");
assert.containsOnce($, "[title='Add Users']");
assert.containsOnce($, "[title='Show Member List']");
assert.containsOnce($, "[title='Show Call Settings']");
assert.containsOnce($, "[title='Open in Discuss']");
await contains(".o-mail-ChatWindow-command", 9);
await contains("[title='Pinned Messages']");
await contains("[title='Add Users']");
await contains("[title='Show Member List']");
await contains("[title='Show Call Settings']");
await contains("[title='Open in Discuss']");
});
QUnit.test("Fold state of chat window is sync among browser tabs", async (assert) => {
QUnit.test("Fold state of chat window is sync among browser tabs", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({ name: "General" });
const tab1 = await start({ asTab: true });
const tab2 = await start({ asTab: true });
await tab1.click(".o_menu_systray i[aria-label='Messages']");
await tab1.click(".o-mail-NotificationItem");
await tab1.click(".o-mail-ChatWindow-header"); // Fold
assert.containsNone(tab1.target, ".o-mail-ChatWindow .o-mail-ChatWindow-content");
assert.containsNone(tab2.target, ".o-mail-ChatWindow .o-mail-ChatWindow-content");
await tab2.click(".o-mail-ChatWindow-header"); // Unfold
assert.containsOnce(tab1.target, ".o-mail-ChatWindow .o-mail-ChatWindow-content");
assert.containsOnce(tab2.target, ".o-mail-ChatWindow .o-mail-ChatWindow-content");
await tab1.click("[title='Close Chat Window']");
assert.containsNone(tab1.target, ".o-mail-ChatWindow");
assert.containsNone(tab2.target, ".o-mail-ChatWindow");
await click(".o_menu_systray i[aria-label='Messages']", { target: tab1.target });
await click(".o-mail-NotificationItem", { target: tab1.target });
await click(".o-mail-ChatWindow-header", { target: tab1.target }); // Fold
await contains(".o-mail-ChatWindow .o-mail-ChatWindow-content", 0, { target: tab1.target });
await contains(".o-mail-ChatWindow .o-mail-ChatWindow-content", 0, { target: tab2.target });
await click(".o-mail-ChatWindow-header", { target: tab2.target }); // Unfold
await contains(".o-mail-ChatWindow .o-mail-ChatWindow-content", 1, { target: tab1.target });
await contains(".o-mail-ChatWindow .o-mail-ChatWindow-content", 1, { target: tab2.target });
await click("[title='Close Chat Window']", { target: tab1.target });
await contains(".o-mail-ChatWindow", 0, { target: tab1.target });
await contains(".o-mail-ChatWindow", 0, { target: tab2.target });
});
QUnit.test(
@@ -193,6 +187,7 @@ QUnit.test(
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
await click(".o-mail-ChatWindow");
const [member] = pyEnv["discuss.channel.member"].searchRead([
["channel_id", "=", channelId],
["partner_id", "=", pyEnv.currentPartnerId],
@@ -214,18 +209,18 @@ QUnit.test("chat window: fold", async (assert) => {
// Open Thread
await click("button i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-ChatWindow .o-mail-Thread");
await contains(".o-mail-ChatWindow .o-mail-Thread");
assert.verifySteps(["rpc:channel_fold/open"]);
// Fold chat window
await click(".o-mail-ChatWindow-command[title='Fold']");
await contains(".o-mail-ChatWindow .o-mail-Thread", 0);
assert.verifySteps(["rpc:channel_fold/folded"]);
assert.containsNone($, ".o-mail-ChatWindow .o-mail-Thread");
// Unfold chat window
await click(".o-mail-ChatWindow-command[title='Open']");
await contains(".o-mail-ChatWindow .o-mail-Thread");
assert.verifySteps(["rpc:channel_fold/open"]);
assert.containsOnce($, ".o-mail-ChatWindow .o-mail-Thread");
});
QUnit.test("chat window: open / close", async (assert) => {
@@ -238,20 +233,20 @@ QUnit.test("chat window: open / close", async (assert) => {
}
},
});
assert.containsNone($, ".o-mail-ChatWindow");
await click("button i[aria-label='Messages']");
await contains(".o-mail-ChatWindow", 0);
await click(".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
assert.verifySteps(["rpc:channel_fold/open"]);
await click(".o-mail-ChatWindow-command[title='Close Chat Window']");
assert.containsNone($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow", 0);
assert.verifySteps(["rpc:channel_fold/closed"]);
// Reopen chat window
await click("button i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
assert.verifySteps(["rpc:channel_fold/open"]);
});
@@ -263,7 +258,7 @@ QUnit.test("open chat on very narrow device should work", async (assert) => {
assert.ok(CHAT_WINDOW_WIDTH > 200, "Device is narrower than usual chat window width"); // scenario where this might fail
await click("button i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
});
QUnit.test(
@@ -279,9 +274,9 @@ QUnit.test(
await start();
await click("button i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
await click("[title='Close Chat Window']");
assert.containsNone($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow", 0);
const [member] = pyEnv["discuss.channel.member"].searchRead([
["channel_id", "=", channelId],
["partner_id", "=", pyEnv.currentPartnerId],
@@ -304,17 +299,17 @@ QUnit.test("chat window: close on ESCAPE", async (assert) => {
}
},
});
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
$(".o-mail-Composer-input")[0].focus();
await afterNextRender(() => triggerHotkey("Escape"));
assert.containsNone($, ".o-mail-ChatWindow");
triggerHotkey("Escape");
await contains(".o-mail-ChatWindow", 0);
assert.verifySteps(["rpc:channel_fold/closed"]);
});
QUnit.test(
"Close composer suggestions in chat window with ESCAPE does not also close the chat window",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({
email: "testpartner@odoo.com",
@@ -330,14 +325,15 @@ QUnit.test(
});
await start();
await insertText(".o-mail-Composer-input", "@");
await afterNextRender(() => triggerHotkey("Escape"));
assert.containsOnce($, ".o-mail-ChatWindow");
triggerHotkey("Escape");
await contains(".o-mail-ChatWindow");
}
);
QUnit.test(
"Close emoji picker in chat window with ESCAPE does not also close the chat window",
async (assert) => {
async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
name: "general",
@@ -347,9 +343,9 @@ QUnit.test(
});
await start();
await click("button[aria-label='Emojis']");
await afterNextRender(() => triggerHotkey("Escape"));
assert.containsNone($, ".o-EmojiPicker");
assert.containsOnce($, ".o-mail-ChatWindow");
triggerHotkey("Escape");
await contains(".o-EmojiPicker", 0);
await contains(".o-mail-ChatWindow");
}
);
@@ -364,8 +360,8 @@ QUnit.test("open 2 different chat windows: enough screen width [REQUIRE FOCUS]",
await start();
await click("button i[aria-label='Messages']");
await click(".o-mail-NotificationItem:contains(Channel_1)");
assert.containsOnce($, ".o-mail-ChatWindow");
assert.containsOnce($, ".o-mail-ChatWindow-header:contains(Channel_1)");
await contains(".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow-header:contains(Channel_1)");
assert.strictEqual(
document.activeElement,
$(".o-mail-ChatWindow-header:contains(Channel_1)")
@@ -375,9 +371,9 @@ QUnit.test("open 2 different chat windows: enough screen width [REQUIRE FOCUS]",
await click("button i[aria-label='Messages']");
await click(".o-mail-NotificationItem:contains(Channel_2)");
assert.containsN($, ".o-mail-ChatWindow", 2);
assert.containsOnce($, ".o-mail-ChatWindow-header:contains(Channel_2)");
assert.containsOnce($, ".o-mail-ChatWindow-header:contains(Channel_1)");
await contains(".o-mail-ChatWindow", 2);
await contains(".o-mail-ChatWindow-header:contains(Channel_2)");
await contains(".o-mail-ChatWindow-header:contains(Channel_1)");
assert.strictEqual(
document.activeElement,
$(".o-mail-ChatWindow-header:contains(Channel_2)")
@@ -408,20 +404,20 @@ QUnit.test("open 3 different chat windows: not enough screen width", async (asse
// open, from systray menu, chat windows of channels with Id 1, 2, then 3
await click("button i[aria-label='Messages']");
await click(".o-mail-NotificationItem:contains(Channel_1)");
assert.containsOnce($, ".o-mail-ChatWindow");
assert.containsNone($, ".o-mail-ChatWindowHiddenToggler");
await contains(".o-mail-ChatWindow");
await contains(".o-mail-ChatWindowHiddenToggler", 0);
await click("button i[aria-label='Messages']");
await click(".o-mail-NotificationItem:contains(Channel_2)");
assert.containsN($, ".o-mail-ChatWindow", 2);
assert.containsNone($, ".o-mail-ChatWindowHiddenToggler");
await contains(".o-mail-ChatWindow", 2);
await contains(".o-mail-ChatWindowHiddenToggler", 0);
await click("button i[aria-label='Messages']");
await click(".o-mail-NotificationItem:contains(Channel_3)");
assert.containsN($, ".o-mail-ChatWindow", 2);
assert.containsOnce($, ".o-mail-ChatWindowHiddenToggler");
assert.containsOnce($, ".o-mail-ChatWindow-header:contains(Channel_1)");
assert.containsOnce($, ".o-mail-ChatWindow-header:contains(Channel_3)");
await contains(".o-mail-ChatWindow", 2);
await contains(".o-mail-ChatWindowHiddenToggler");
await contains(".o-mail-ChatWindow-header:contains(Channel_1)");
await contains(".o-mail-ChatWindow-header:contains(Channel_3)");
assert.strictEqual(
document.activeElement,
$(".o-mail-ChatWindow-header:contains(Channel_3)")
@@ -458,15 +454,15 @@ QUnit.test("closing hidden chat window", async (assert) => {
await click("i[aria-label='Messages']");
await click(".o-mail-NotificationItem:contains(Ch_4)");
await click(".o-mail-ChatWindowHiddenToggler");
assert.containsOnce($, ":not(.o-mail-ChatWindowHiddenMenu) .o-mail-ChatWindow:contains(Ch_1)");
assert.containsOnce($, ".o-mail-ChatWindowHiddenMenu .o-mail-ChatWindow:contains(Ch_2)");
assert.containsOnce($, ".o-mail-ChatWindowHiddenMenu .o-mail-ChatWindow:contains(Ch_3)");
assert.containsOnce($, ":not(.o-mail-ChatWindowHiddenMenu) .o-mail-ChatWindow:contains(Ch_4)");
await contains(":not(.o-mail-ChatWindowHiddenMenu) .o-mail-ChatWindow:contains(Ch_1)");
await contains(".o-mail-ChatWindowHiddenMenu .o-mail-ChatWindow:contains(Ch_2)");
await contains(".o-mail-ChatWindowHiddenMenu .o-mail-ChatWindow:contains(Ch_3)");
await contains(":not(.o-mail-ChatWindowHiddenMenu) .o-mail-ChatWindow:contains(Ch_4)");
await click(".o-mail-ChatWindow:contains(Ch_2) [title='Close Chat Window']");
assert.containsOnce($, ":not(.o-mail-ChatWindowHiddenMenu) .o-mail-ChatWindow:contains(Ch_1)");
assert.containsNone($, ".o-mail-ChatWindow:contains(Ch_2)");
assert.containsOnce($, ".o-mail-ChatWindowHiddenMenu .o-mail-ChatWindow:contains(Ch_3)");
assert.containsOnce($, ":not(.o-mail-ChatWindowHiddenMenu) .o-mail-ChatWindow:contains(Ch_4)");
await contains(":not(.o-mail-ChatWindowHiddenMenu) .o-mail-ChatWindow:contains(Ch_1)");
await contains(".o-mail-ChatWindow:contains(Ch_2)", 0);
await contains(".o-mail-ChatWindowHiddenMenu .o-mail-ChatWindow:contains(Ch_3)");
await contains(":not(.o-mail-ChatWindowHiddenMenu) .o-mail-ChatWindow:contains(Ch_4)");
});
QUnit.test(
@@ -510,14 +506,14 @@ QUnit.test(
"should have enough space to open 2 chat windows simultaneously"
);
await start();
assert.containsN($, ".o-mail-ChatWindow .o-mail-Composer-input", 2);
await contains(".o-mail-ChatWindow .o-mail-Composer-input", 2);
$(".o-mail-ChatWindow-name:contains(MyTeam)")
.closest(".o-mail-ChatWindow")
.find(".o-mail-Composer-input")[0]
.focus();
await afterNextRender(() => triggerHotkey("Escape"));
assert.containsOnce($, ".o-mail-ChatWindow");
triggerHotkey("Escape");
await contains(".o-mail-ChatWindow");
assert.strictEqual(
document.activeElement,
$(".o-mail-ChatWindow-name:contains(General)")
@@ -527,7 +523,7 @@ QUnit.test(
}
);
QUnit.test("chat window: switch on TAB", async (assert) => {
QUnit.test("chat window: switch on TAB [REQUIRE FOCUS]", async (assert) => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create([{ name: "channel1" }, { name: "channel2" }]);
patchUiSize({ width: 1920 });
@@ -538,43 +534,19 @@ QUnit.test("chat window: switch on TAB", async (assert) => {
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem:contains(channel1)");
assert.containsOnce($, ".o-mail-ChatWindow");
assert.containsOnce($, ".o-mail-ChatWindow-name:contains(channel1)");
assert.strictEqual(
document.activeElement,
$(".o-mail-ChatWindow-name:contains(channel1)")
.closest(".o-mail-ChatWindow")
.find(".o-mail-Composer-input")[0]
);
await contains(".o-mail-ChatWindow", 1);
await contains(".o-mail-ChatWindow:contains(channel1) .o-mail-Composer-input:focus");
await afterNextRender(() => triggerHotkey("Tab"));
assert.strictEqual(
document.activeElement,
$(".o-mail-ChatWindow-name:contains(channel1)")
.closest(".o-mail-ChatWindow")
.find(".o-mail-Composer-input")[0]
);
triggerHotkey("Tab");
await contains(".o-mail-ChatWindow:contains(channel1) .o-mail-Composer-input:focus");
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem:contains(channel2)");
assert.containsN($, ".o-mail-ChatWindow", 2);
assert.containsOnce($, ".o-mail-ChatWindow-name:contains(channel1)");
assert.containsOnce($, ".o-mail-ChatWindow-name:contains(channel2)");
assert.strictEqual(
document.activeElement,
$(".o-mail-ChatWindow-name:contains(channel2)")
.closest(".o-mail-ChatWindow")
.find(".o-mail-Composer-input")[0]
);
await contains(".o-mail-ChatWindow", 2);
await contains(".o-mail-ChatWindow:contains(channel2) .o-mail-Composer-input:focus");
await afterNextRender(() => triggerHotkey("Tab"));
assert.containsN($, ".o-mail-ChatWindow", 2);
assert.strictEqual(
document.activeElement,
$(".o-mail-ChatWindow-name:contains(channel1)")
.closest(".o-mail-ChatWindow")
.find(".o-mail-Composer-input")[0]
);
triggerHotkey("Tab");
await contains(".o-mail-ChatWindow:contains(channel1) .o-mail-Composer-input:focus");
});
QUnit.test("chat window: TAB cycle with 3 open chat windows [REQUIRE FOCUS]", async (assert) => {
@@ -631,40 +603,22 @@ QUnit.test("chat window: TAB cycle with 3 open chat windows [REQUIRE FOCUS]", as
);
await start();
// FIXME: assumes ordering: MyProject, MyTeam, General
assert.containsN($, ".o-mail-ChatWindow .o-mail-Composer-input", 3);
await contains(".o-mail-ChatWindow .o-mail-Composer-input", 3);
(await contains(".o-mail-ChatWindow:contains(MyProject) .o-mail-Composer-input"))[0].focus();
$(".o-mail-ChatWindow-name:contains(MyProject)")
.closest(".o-mail-ChatWindow")
.find(".o-mail-Composer-input")[0]
.focus();
await afterNextRender(() => triggerHotkey("Tab"));
assert.strictEqual(
document.activeElement,
$(".o-mail-ChatWindow-name:contains(MyTeam)")
.closest(".o-mail-ChatWindow")
.find(".o-mail-Composer-input")[0]
);
triggerHotkey("Tab");
await contains(".o-mail-ChatWindow:contains(MyTeam) .o-mail-Composer-input:focus");
await afterNextRender(() => triggerHotkey("Tab"));
assert.strictEqual(
document.activeElement,
$(".o-mail-ChatWindow-name:contains(General)")
.closest(".o-mail-ChatWindow")
.find(".o-mail-Composer-input")[0]
);
triggerHotkey("Tab");
await contains(".o-mail-ChatWindow:contains(General) .o-mail-Composer-input:focus");
await afterNextRender(() => triggerHotkey("Tab"));
assert.strictEqual(
document.activeElement,
$(".o-mail-ChatWindow-name:contains(MyProject)")
.closest(".o-mail-ChatWindow")
.find(".o-mail-Composer-input")[0]
);
triggerHotkey("Tab");
await contains(".o-mail-ChatWindow:contains(MyProject) .o-mail-Composer-input:focus");
});
QUnit.test(
"new message separator is shown in a chat window of a chat on receiving new message if there is a history of conversation",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const userId = pyEnv["res.users"].create({ name: "Foreigner user", partner_id: partnerId });
@@ -695,24 +649,22 @@ QUnit.test(
pyEnv["discuss.channel.member"].write([memberId], { seen_message_id: messageId });
const { env } = await start();
// simulate receiving a message
await afterNextRender(async () =>
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "hu", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
)
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "hu", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
);
assert.containsOnce($, ".o-mail-ChatWindow");
assert.containsN($, ".o-mail-Message", 2);
assert.containsOnce($, "hr + span:contains(New messages)");
await contains(".o-mail-ChatWindow");
await contains(".o-mail-Message", 2);
await contains("hr + span:contains(New messages)");
}
);
QUnit.test(
"new message separator is shown in chat window of chat on receiving new message when there was no history",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const userId = pyEnv["res.users"].create({
@@ -728,20 +680,18 @@ QUnit.test(
});
const { env } = await start();
// simulate receiving a message
await afterNextRender(async () =>
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "hu", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
)
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "hu", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
);
assert.containsOnce($, "hr + span:contains(New messages)");
await contains("hr + span:contains(New messages)");
}
);
QUnit.test("chat window should open when receiving a new DM", async (assert) => {
QUnit.test("chat window should open when receiving a new DM", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const userId = pyEnv["res.users"].create({ partner_id: partnerId });
@@ -753,20 +703,18 @@ QUnit.test("chat window should open when receiving a new DM", async (assert) =>
channel_type: "chat",
});
const { env } = await start();
// simulate receiving the first message on chat
await afterNextRender(() =>
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "new message", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
)
await contains(".o-mail-ChatWindowContainer");
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "new message", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
);
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
});
QUnit.test("chat window should not open when receiving a new DM from odoobot", async (assert) => {
QUnit.test("chat window should not open when receiving a new DM from odoobot", async () => {
const pyEnv = await startServer();
const userId = pyEnv["res.users"].create({ partner_id: pyEnv.odoobotId });
const channelId = pyEnv["discuss.channel"].create({
@@ -777,17 +725,15 @@ QUnit.test("chat window should not open when receiving a new DM from odoobot", a
channel_type: "chat",
});
const { env } = await start();
// simulate receiving new message from odoobot
await afterNextRender(() =>
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "new message", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
)
await contains(".o-mail-ChatWindowContainer");
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "new message", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
);
assert.containsNone($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow", 0);
});
QUnit.test(
@@ -816,16 +762,12 @@ QUnit.test(
}
await start();
await insertText(".o-mail-Composer-input", "WOLOLO");
await afterNextRender(() =>
triggerEvent(document.body, ".o-mail-Composer-input", "keydown", {
key: "Enter",
})
);
triggerHotkey("Enter");
assert.ok(isScrolledToBottom($(".o-mail-Thread")[0]));
}
);
QUnit.test("chat window should remain folded when new message is received", async (assert) => {
QUnit.test("chat window should remain folded when new message is received", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const userId = pyEnv["res.users"].create({
@@ -848,8 +790,8 @@ QUnit.test("chat window should remain folded when new message is received", asyn
channel_type: "chat",
});
const { env } = await start();
assert.hasClass($(".o-mail-ChatWindow"), "o-folded");
await contains(".o-mail-ChatWindow.o-folded");
await contains(".o-mail-ChatWindow-counter", 0);
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "New Message", message_type: "comment" },
@@ -857,8 +799,8 @@ QUnit.test("chat window should remain folded when new message is received", asyn
thread_model: "discuss.channel",
})
);
await nextTick();
assert.hasClass($(".o-mail-ChatWindow"), "o-folded");
await contains(".o-mail-ChatWindow-counter:contains(1)");
await contains(".o-mail-ChatWindow.o-folded");
});
QUnit.test(
@@ -884,7 +826,7 @@ QUnit.test(
"should not have enough space to open 3 chat windows simultaneously"
);
const { openDiscuss } = await start();
await openDiscuss();
openDiscuss();
// open, from systray menu, chat windows of channels with id 1, 2, 3
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem-name:contains(Channel-1)");
@@ -893,11 +835,9 @@ QUnit.test(
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem-name:contains(Channel-3)");
// simulate resize to go into mobile
await afterNextRender(() => {
patchUiSize({ size: SIZES.SM });
window.dispatchEvent(new UIEvent("resize"));
});
assert.containsNone($, ".o-mail-ChatWindowHiddenToggler");
patchUiSize({ size: SIZES.SM });
window.dispatchEvent(new UIEvent("resize"));
await contains(".o-mail-ChatWindowHiddenToggler", 0);
}
);
@@ -909,7 +849,7 @@ QUnit.test("chat window: composer state conservation on toggle discuss", async (
await click(".o-mail-NotificationItem");
// Set content of the composer of the chat window
await insertText(".o-mail-Composer-input", "XDU for the win !");
assert.containsNone($, ".o-mail-Composer-footer .o-mail-AttachmentList .o-mail-AttachmentCard");
await contains(".o-mail-Composer-footer .o-mail-AttachmentList .o-mail-AttachmentCard", 0);
// Set attachments of the composer
const files = [
await createFile({
@@ -940,7 +880,7 @@ QUnit.test("chat window: composer state conservation on toggle discuss", async (
QUnit.test(
"focusing a chat window of a chat should make new message separator disappear [REQUIRE FOCUS]",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const userId = pyEnv["res.users"].create({
@@ -978,18 +918,16 @@ QUnit.test(
const { env } = await start();
$(".o-mail-Composer-input")[0].blur();
// simulate receiving a message
await afterNextRender(() =>
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "hu", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
)
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "hu", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
);
assert.containsOnce($, "hr + span:contains(New messages)");
await afterNextRender(() => $(".o-mail-Composer-input")[0].focus());
assert.containsNone($, "hr + span:contains(New messages)");
await contains("hr + span:contains(New messages)");
$(".o-mail-Composer-input")[0].focus();
await contains("hr + span:contains(New messages)", 0);
}
);
@@ -1005,17 +943,21 @@ QUnit.test("chat window: scroll conservation on toggle discuss", async (assert)
}
const { openDiscuss, openView } = await start();
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
await click(".o-mail-NotificationItem");
$(".o-mail-Thread")[0].scrollTop = 142;
await openDiscuss(null, { waitUntilMessagesLoaded: false });
assert.containsNone($, ".o-mail-ChatWindow");
/**
* The afterNextRender is necessary because otherwise useAutoScroll would
* set the scroll to bottom after the manually set value from this test.
*/
await afterNextRender(async () => await click(".o-mail-NotificationItem"));
(await contains(".o-mail-ChatWindow .o-mail-Thread"))[0].scrollTop = 142;
openDiscuss(null, { waitUntilMessagesLoaded: false });
await contains(".o-mail-ChatWindow", 0);
await openView({
openView({
res_id: channelId,
res_model: "discuss.channel",
views: [[false, "list"]],
});
assert.strictEqual($(".o-mail-Thread")[0].scrollTop, 142);
assert.strictEqual((await contains(".o-mail-ChatWindow .o-mail-Thread"))[0].scrollTop, 142);
});
QUnit.test(
@@ -1032,17 +974,19 @@ QUnit.test(
}
await start();
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
await click(".o-mail-NotificationItem");
// Set a scroll position to chat window
$(".o-mail-Thread")[0].scrollTop = 142;
assert.strictEqual($(".o-mail-Thread")[0].scrollTop, 142);
/**
* The afterNextRender is necessary because otherwise useAutoScroll would
* set the scroll to bottom after the manually set value from this test.
*/
await afterNextRender(async () => await click(".o-mail-NotificationItem"));
(await contains(".o-mail-ChatWindow .o-mail-Thread"))[0].scrollTop = 142;
// fold chat window
await click(".o-mail-ChatWindow-command[title='Fold']");
assert.containsNone($, ".o-mail-Thread");
await contains(".o-mail-ChatWindow .o-mail-Thread", 0);
// unfold chat window
await click(".o-mail-ChatWindow-command[title='Open']");
assert.strictEqual($(".o-mail-Thread")[0].scrollTop, 142);
assert.strictEqual((await contains(".o-mail-ChatWindow .o-mail-Thread"))[0].scrollTop, 142);
}
);
@@ -1060,27 +1004,30 @@ QUnit.test(
}
const { openDiscuss, openView } = await start();
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
await click(".o-mail-NotificationItem");
/**
* The afterNextRender is necessary because otherwise useAutoScroll would
* set the scroll to bottom after the manually set value from this test.
*/
await afterNextRender(async () => await click(".o-mail-NotificationItem"));
(await contains(".o-mail-ChatWindow .o-mail-Thread"))[0].scrollTop = 142;
// Set a scroll position to chat window
$(".o-mail-Thread")[0].scrollTop = 142;
// fold chat window
await click(".o-mail-ChatWindow-command[title='Fold']");
await openDiscuss(null, { waitUntilMessagesLoaded: false });
assert.containsNone($, ".o-mail-ChatWindow");
openDiscuss(null, { waitUntilMessagesLoaded: false });
await contains(".o-mail-ChatWindow", 0);
await openView({
openView({
res_id: channelId,
res_model: "discuss.channel",
views: [[false, "list"]],
});
// unfold chat window
await click(".o-mail-ChatWindow-command[title='Open']");
assert.strictEqual($(".o-mail-Thread")[0].scrollTop, 142);
assert.strictEqual((await contains(".o-mail-ChatWindow .o-mail-Thread"))[0].scrollTop, 142);
}
);
QUnit.test("folded chat window should hide member-list and settings buttons", async (assert) => {
QUnit.test("folded chat window should hide member-list and settings buttons", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({});
await start();
@@ -1088,24 +1035,26 @@ QUnit.test("folded chat window should hide member-list and settings buttons", as
await click("button i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
await click("[title='Open Actions Menu']");
assert.containsOnce($, "[title='Show Member List']");
assert.containsOnce($, "[title='Show Call Settings']");
await contains("[title='Show Member List']");
await contains("[title='Show Call Settings']");
await click(".o-mail-ChatWindow-header"); // click away to close the more menu
await contains("[title='Show Member List']", 0);
// Fold chat window
await click(".o-mail-ChatWindow-command[title='Fold']");
assert.containsNone($, "[title='Open Actions Menu']");
assert.containsNone($, "[title='Show Member List']");
assert.containsNone($, "[title='Show Call Settings']");
await contains("[title='Open Actions Menu']", 0);
await contains("[title='Show Member List']", 0);
await contains("[title='Show Call Settings']", 0);
// Unfold chat window
await click(".o-mail-ChatWindow-command[title='Open']");
await click("[title='Open Actions Menu']");
assert.containsOnce($, "[title='Show Member List']");
assert.containsOnce($, "[title='Show Call Settings']");
await contains("[title='Show Member List']");
await contains("[title='Show Call Settings']");
});
QUnit.test("Chat window in mobile are not foldable", async (assert) => {
QUnit.test("Chat window in mobile are not foldable", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
channel_member_ids: [
@@ -1116,13 +1065,13 @@ QUnit.test("Chat window in mobile are not foldable", async (assert) => {
await start();
await click("button i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
assert.containsNone($, ".o-mail-ChatWindow-header.cursor-pointer");
await contains(".o-mail-ChatWindow-header.cursor-pointer", 0);
click(".o-mail-ChatWindow-header").catch(() => {});
await nextAnimationFrame();
assert.containsOnce($, ".o-mail-ChatWindow-content"); // content => non-folded
await contains(".o-mail-ChatWindow-content"); // content => non-folded
});
QUnit.test("Server-synced chat windows should not open at page load on mobile", async (assert) => {
QUnit.test("Server-synced chat windows should not open at page load on mobile", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
channel_member_ids: [
@@ -1135,23 +1084,24 @@ QUnit.test("Server-synced chat windows should not open at page load on mobile",
});
patchUiSize({ size: SIZES.SM });
await start();
assert.containsNone($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindowContainer");
await contains(".o-mail-ChatWindow", 0);
});
QUnit.test("chat window of channels should not have 'Open in Discuss' (mobile)", async (assert) => {
QUnit.test("chat window of channels should not have 'Open in Discuss' (mobile)", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({ name: "General" });
patchUiSize({ size: SIZES.SM });
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-ChatWindow");
assert.containsOnce($, "[title='Open Actions Menu']");
await contains(".o-mail-ChatWindow");
await contains("[title='Open Actions Menu']");
await click("[title='Open Actions Menu']");
assert.containsNone($, "[title='Open in Discuss']");
await contains("[title='Open in Discuss']", 0);
});
QUnit.test("Open chat window of new inviter", async (assert) => {
QUnit.test("Open chat window of new inviter", async () => {
const pyEnv = await startServer();
await start();
const partnerId = pyEnv["res.partner"].create({ name: "Newbie" });
@@ -1162,15 +1112,14 @@ QUnit.test("Open chat window of new inviter", async (assert) => {
partnerId,
});
await contains(".o-mail-ChatWindow:contains(Newbie)");
assert.containsOnce(
$,
await contains(
".o_notification:contains(Newbie connected. This is their first connection. Wish them luck.)"
);
});
QUnit.test(
"keyboard navigation ArrowUp/ArrowDown on message action dropdown in chat window",
async (assert) => {
async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
pyEnv["mail.message"].create({
@@ -1182,22 +1131,22 @@ QUnit.test(
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
await afterNextRender(() => {
$(".o-mail-Message")[0].dispatchEvent(new window.MouseEvent("mouseenter"));
});
await click(".o-mail-Message [title='Expand']");
$(".o-mail-Message [title='Expand']")[0].focus(); // necessary otherwise focus is in composer input
assert.containsOnce($, ".o-mail-Message-moreMenu.dropdown-menu");
await triggerHotkey("ArrowDown");
assert.containsOnce($, ".o-mail-Message-moreMenu .dropdown-item:eq(0).focus");
await triggerHotkey("ArrowDown");
assert.containsOnce($, ".o-mail-Message-moreMenu .dropdown-item:eq(1).focus");
await contains(".o-mail-ChatWindow .o-mail-Composer-input:focus");
const expand = await click(".o-mail-Message [title='Expand']");
await contains(".o-mail-Message-moreMenu.dropdown-menu");
expand.focus(); // necessary otherwise focus is in composer input
triggerHotkey("ArrowDown");
await contains(".o-mail-Message-moreMenu .dropdown-item:eq(0).focus");
triggerHotkey("ArrowDown");
await contains(".o-mail-Message-moreMenu .dropdown-item:eq(1).focus");
}
);
QUnit.test(
"Close dropdown in chat window with ESCAPE does not also close the chat window",
async (assert) => {
async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
pyEnv["mail.message"].create({
@@ -1209,14 +1158,11 @@ QUnit.test(
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
await afterNextRender(() => {
$(".o-mail-Message")[0].dispatchEvent(new window.MouseEvent("mouseenter"));
});
await click(".o-mail-Message [title='Expand']");
$(".o-mail-Message [title='Expand']")[0].focus(); // necessary otherwise focus is in composer input
assert.containsOnce($, ".o-mail-Message-moreMenu.dropdown-menu");
await triggerHotkey("Escape");
assert.containsNone($, ".o-mail-Message-moreMenu.dropdown-menu");
assert.containsOnce($, ".o-mail-ChatWindow");
const expand = await click(".o-mail-Message [title='Expand']");
await contains(".o-mail-Message-moreMenu.dropdown-menu");
expand.focus(); // necessary otherwise focus is in composer input
triggerHotkey("Escape");
await contains(".o-mail-Message-moreMenu.dropdown-menu", 0);
await contains(".o-mail-ChatWindow");
}
);
File diff suppressed because it is too large Load Diff
@@ -1,7 +1,7 @@
/* @odoo-module */
import { click, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
import { click, contains, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { makeDeferred, patchWithCleanup } from "@web/../tests/helpers/utils";
const views = {
"res.fake,false,form": `
@@ -16,7 +16,7 @@ const views = {
QUnit.module("suggested_recipients");
QUnit.test("with 3 or less suggested recipients: no 'show more' button", async (assert) => {
QUnit.test("with 3 or less suggested recipients: no 'show more' button", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({
display_name: "John Jane",
@@ -27,9 +27,10 @@ QUnit.test("with 3 or less suggested recipients: no 'show more' button", async (
partner_ids: [partnerId],
});
const { openFormView } = await start();
await openFormView("res.fake", fakeId);
openFormView("res.fake", fakeId);
await click("button:contains(Send message)");
assert.containsNone($, "button:contains(Show more)");
await contains(".o-mail-SuggestedRecipient", 2);
await contains("button:contains(Show more)", 0);
});
QUnit.test(
@@ -45,7 +46,8 @@ QUnit.test(
partner_ids: [partnerId],
});
const { env, openFormView } = await start();
await openFormView("res.fake", fakeId);
openFormView("res.fake", fakeId);
const def = makeDeferred();
patchWithCleanup(env.services.action, {
doAction(action) {
assert.step("do-action");
@@ -59,12 +61,13 @@ QUnit.test(
johnTestPartnerId,
partnerId,
]);
def.resolve();
return Promise.resolve();
},
});
await click("button:contains(Send message)");
assert.containsOnce($, ".o-mail-SuggestedRecipient:contains(john@test.be)");
assert.containsOnce($, ".o-mail-SuggestedRecipient:contains(John Jane)");
await contains(".o-mail-SuggestedRecipient:contains(john@test.be)");
await contains(".o-mail-SuggestedRecipient:contains(John Jane)");
assert.ok(
$(".o-mail-SuggestedRecipient:contains(john@test.be) input[type=checkbox]")[0].checked
);
@@ -72,6 +75,7 @@ QUnit.test(
$(".o-mail-SuggestedRecipient:contains(John Jane) input[type=checkbox]")[0].checked
);
await click("button[title='Full composer']");
await def;
assert.verifySteps(["do-action"]);
}
);
@@ -89,19 +93,21 @@ QUnit.test(
partner_ids: [partnerId],
});
const { env, openFormView } = await start();
await openFormView("res.fake", fakeId);
openFormView("res.fake", fakeId);
const def = makeDeferred();
patchWithCleanup(env.services.action, {
doAction(action) {
assert.step("do-action");
assert.strictEqual(action.name, "Log note");
assert.strictEqual(action.context.default_subtype_xmlid, "mail.mt_note");
assert.deepEqual(action.context.default_partner_ids, []);
def.resolve();
return Promise.resolve();
},
});
await click("button:contains(Send message)");
assert.containsOnce($, ".o-mail-SuggestedRecipient:contains(john@test.be)");
assert.containsOnce($, ".o-mail-SuggestedRecipient:contains(John Jane)");
await contains(".o-mail-SuggestedRecipient:contains(john@test.be)");
await contains(".o-mail-SuggestedRecipient:contains(John Jane)");
assert.ok(
$(".o-mail-SuggestedRecipient:contains(john@test.be) input[type=checkbox]")[0].checked
);
@@ -110,33 +116,31 @@ QUnit.test(
);
await click("button:contains(Log note)");
await click("button[title='Full composer']");
await def;
assert.verifySteps(["do-action"]);
}
);
QUnit.test(
"more than 3 suggested recipients: display only 3 and 'show more' button",
async (assert) => {
const pyEnv = await startServer();
const [partnerId_1, partnerId_2, partnerId_3, partnerId_4] = pyEnv["res.partner"].create([
{ display_name: "John Jane", email: "john@jane.be" },
{ display_name: "Jack Jone", email: "jack@jone.be" },
{ display_name: "jack sparrow", email: "jsparrow@blackpearl.bb" },
{ display_name: "jolly Roger", email: "Roger@skullflag.com" },
]);
const fakeId = pyEnv["res.fake"].create({
partner_ids: [partnerId_1, partnerId_2, partnerId_3, partnerId_4],
});
const { openFormView } = await start({ serverData: { views } });
await openFormView("res.fake", fakeId);
await click("button:contains(Send message)");
assert.containsOnce($, "button:contains(Show more)");
}
);
QUnit.test("more than 3 suggested recipients: display only 3 and 'show more' button", async () => {
const pyEnv = await startServer();
const [partnerId_1, partnerId_2, partnerId_3, partnerId_4] = pyEnv["res.partner"].create([
{ display_name: "John Jane", email: "john@jane.be" },
{ display_name: "Jack Jone", email: "jack@jone.be" },
{ display_name: "jack sparrow", email: "jsparrow@blackpearl.bb" },
{ display_name: "jolly Roger", email: "Roger@skullflag.com" },
]);
const fakeId = pyEnv["res.fake"].create({
partner_ids: [partnerId_1, partnerId_2, partnerId_3, partnerId_4],
});
const { openFormView } = await start({ serverData: { views } });
openFormView("res.fake", fakeId);
await click("button:contains(Send message)");
await contains("button:contains(Show more)");
});
QUnit.test(
"more than 3 suggested recipients: show all of them on click 'show more' button",
async (assert) => {
async () => {
const pyEnv = await startServer();
const [partnerId_1, partnerId_2, partnerId_3, partnerId_4] = pyEnv["res.partner"].create([
{ display_name: "John Jane", email: "john@jane.be" },
@@ -148,16 +152,16 @@ QUnit.test(
partner_ids: [partnerId_1, partnerId_2, partnerId_3, partnerId_4],
});
const { openFormView } = await start({ serverData: { views } });
await openFormView("res.fake", fakeId);
openFormView("res.fake", fakeId);
await click("button:contains(Send message)");
await click("button:contains(Show more)");
assert.containsN($, ".o-mail-SuggestedRecipient", 4);
await contains(".o-mail-SuggestedRecipient", 4);
}
);
QUnit.test(
"more than 3 suggested recipients -> click 'show more' -> 'show less' button",
async (assert) => {
async () => {
const pyEnv = await startServer();
const [partnerId_1, partnerId_2, partnerId_3, partnerId_4] = pyEnv["res.partner"].create([
{ display_name: "John Jane", email: "john@jane.be" },
@@ -169,16 +173,16 @@ QUnit.test(
partner_ids: [partnerId_1, partnerId_2, partnerId_3, partnerId_4],
});
const { openFormView } = await start({ serverData: { views } });
await openFormView("res.fake", fakeId);
openFormView("res.fake", fakeId);
await click("button:contains(Send message)");
await click("button:contains(Show more)");
assert.containsOnce($, "button:contains(Show less)");
await contains("button:contains(Show less)");
}
);
QUnit.test(
"suggested recipients list display 3 suggested recipient and 'show more' button when 'show less' button is clicked",
async (assert) => {
async () => {
const pyEnv = await startServer();
const [partnerId_1, partnerId_2, partnerId_3, partnerId_4] = pyEnv["res.partner"].create([
{ display_name: "John Jane", email: "john@jane.be" },
@@ -190,12 +194,12 @@ QUnit.test(
partner_ids: [partnerId_1, partnerId_2, partnerId_3, partnerId_4],
});
const { openFormView } = await start({ serverData: { views } });
await openFormView("res.fake", fakeId);
openFormView("res.fake", fakeId);
await click("button:contains(Send message)");
await click("button:contains(Show more)");
await click("button:contains(Show less)");
assert.containsN($, ".o-mail-SuggestedRecipient", 3);
assert.containsOnce($, "button:contains(Show more)");
await contains(".o-mail-SuggestedRecipient", 3);
await contains("button:contains(Show more)");
}
);
@@ -212,9 +216,9 @@ QUnit.test(
partner_ids: [partnerId],
});
const { openFormView } = await start({ serverData: { views } });
await openFormView("res.fake", fakeId);
openFormView("res.fake", fakeId);
await click("button:contains(Send message)");
assert.containsN($, ".o-mail-SuggestedRecipient input:checked", 2);
await contains(".o-mail-SuggestedRecipient input:checked", 2);
assert.ok(
$(".o-mail-SuggestedRecipient:not([data-partner-id]) input[type=checkbox]")[0].checked
);
@@ -233,10 +237,10 @@ QUnit.test("display reason for suggested recipient on mouse over", async (assert
});
const fakeId = pyEnv["res.fake"].create({ partner_ids: [partnerId] });
const { openFormView } = await start({ serverData: { views } });
await openFormView("res.fake", fakeId);
openFormView("res.fake", fakeId);
await click("button:contains(Send message)");
const partnerTitle = $(
`.o-mail-SuggestedRecipient[data-partner-id="${partnerId}"]`
const partnerTitle = (
await contains(`.o-mail-SuggestedRecipient[data-partner-id="${partnerId}"]`)
)[0].getAttribute("title");
assert.strictEqual(partnerTitle, "Add as recipient and follower (reason: Email partner)");
});
@@ -258,10 +262,11 @@ QUnit.test(
}
},
});
await openFormView("res.fake", fakeId);
openFormView("res.fake", fakeId);
await click("button:contains(Log note)");
await insertText(".o-mail-Composer-input", "Dummy Message");
await click(".o-mail-Composer-send");
await click(".o-mail-Composer-send:not(:disabled)");
await contains(".o-mail-Message");
}
);
@@ -277,10 +282,11 @@ QUnit.test(
const { openFormView } = await start({
serverData: { views },
});
await openFormView("res.fake", fakeId);
openFormView("res.fake", fakeId);
await click("button:contains(Send message)");
await insertText(".o-mail-Composer-input", "Dummy Message");
await click(".o-mail-Composer-send");
await click(".o-mail-Composer-send:not(:disabled)");
await contains(".o-mail-Message");
assert.strictEqual($(".o-mail-Followers-counter").text(), "1");
}
);
@@ -1,33 +1,27 @@
/* @odoo-module */
import {
afterNextRender,
click,
contains,
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { triggerHotkey, patchWithCleanup } from "@web/../tests/helpers/utils";
import { patchWithCleanup, triggerHotkey } from "@web/../tests/helpers/utils";
QUnit.module("crosstab");
QUnit.test("Messages are received cross-tab", async (assert) => {
QUnit.test("Messages are received cross-tab", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
name: "General",
});
const tab1 = await start({ asTab: true });
const tab2 = await start({ asTab: true });
await tab1.openDiscuss(channelId);
await tab2.openDiscuss(channelId);
tab1.openDiscuss(channelId);
tab2.openDiscuss(channelId);
await tab1.insertText(".o-mail-Composer-input", "Hello World!");
await tab1.click("button:contains(Send)");
assert.containsOnce(tab1.target, ".o-mail-Message:contains(Hello World!)");
assert.containsOnce(tab2.target, ".o-mail-Message:contains(Hello World!)");
await click("button:contains(Send):not(:disabled)", { target: tab1.target });
await contains(".o-mail-Message:contains(Hello World!)", 1, { target: tab1.target });
await contains(".o-mail-Message:contains(Hello World!)", 1, { target: tab2.target });
});
QUnit.test("Delete starred message updates counter", async (assert) => {
QUnit.test("Delete starred message updates counter", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
name: "General",
@@ -40,71 +34,71 @@ QUnit.test("Delete starred message updates counter", async (assert) => {
});
const tab1 = await start({ asTab: true });
const tab2 = await start({ asTab: true });
await tab1.openDiscuss(channelId);
await tab2.openDiscuss(channelId);
assert.containsOnce(tab2.target, "button:contains(Starred1)");
await afterNextRender(() =>
tab1.env.services.rpc("/mail/message/update_content", {
message_id: messageId,
body: "",
attachment_ids: [],
})
);
assert.containsNone(tab2.target, "button:contains(Starred1)");
tab1.openDiscuss(channelId);
tab2.openDiscuss(channelId);
await contains("button:contains(Starred1)", 1, { target: tab2.target });
tab1.env.services.rpc("/mail/message/update_content", {
message_id: messageId,
body: "",
attachment_ids: [],
});
await contains("button:contains(Starred1)", 0, { target: tab2.target });
});
QUnit.test("Thread rename", async (assert) => {
QUnit.test("Thread rename", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
create_uid: pyEnv.currentPartnerId,
create_uid: pyEnv.currentUserId,
name: "General",
});
const tab1 = await start({ asTab: true });
const tab2 = await start({ asTab: true });
await tab1.openDiscuss(channelId);
await tab2.openDiscuss(channelId);
await tab1.insertText(".o-mail-Discuss-threadName", "Sales", { replace: true });
await afterNextRender(() => triggerHotkey("Enter"));
assert.containsOnce(tab2.target, ".o-mail-Discuss-threadName[title='Sales']");
assert.containsOnce(tab2.target, ".o-mail-DiscussSidebarChannel:contains(Sales)");
tab1.openDiscuss(channelId);
tab2.openDiscuss(channelId);
await tab1.insertText(".o-mail-Discuss-threadName:not(:disabled)", "Sales", { replace: true });
triggerHotkey("Enter");
await contains(".o-mail-Discuss-threadName[title='Sales']", 1, { target: tab2.target });
await contains(".o-mail-DiscussSidebarChannel:contains(Sales)", 1, { target: tab2.target });
});
QUnit.test("Thread description update", async (assert) => {
QUnit.test("Thread description update", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
create_uid: pyEnv.currentPartnerId,
create_uid: pyEnv.currentUserId,
name: "General",
});
const tab1 = await start({ asTab: true });
const tab2 = await start({ asTab: true });
await tab1.openDiscuss(channelId);
await tab2.openDiscuss(channelId);
tab1.openDiscuss(channelId);
tab2.openDiscuss(channelId);
await tab1.insertText(".o-mail-Discuss-threadDescription", "The very best channel", {
replace: true,
});
await afterNextRender(() => triggerHotkey("Enter"));
assert.containsOnce(
tab2.target,
".o-mail-Discuss-threadDescription[title='The very best channel']"
);
triggerHotkey("Enter");
await contains(".o-mail-Discuss-threadDescription[title='The very best channel']", 1, {
target: tab2.target,
});
});
QUnit.test("Channel subscription is renewed when channel is added from invite", async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "Sales", channel_member_ids: [] });
const [, channelId] = pyEnv["discuss.channel"].create([
{ name: "R&D" },
{ name: "Sales", channel_member_ids: [] },
]);
const { env, openDiscuss } = await start();
patchWithCleanup(env.services["bus_service"], {
forceUpdateChannels() {
assert.step("update-channels");
},
});
await openDiscuss();
// simulate receiving invite
await afterNextRender(() => {
env.services.orm.call("discuss.channel", "add_members", [[channelId]], {
partner_ids: [pyEnv.currentPartnerId],
});
openDiscuss();
await contains(".o-mail-DiscussSidebarChannel");
env.services.orm.call("discuss.channel", "add_members", [[channelId]], {
partner_ids: [pyEnv.currentPartnerId],
});
await contains(".o-mail-DiscussSidebarChannel", 2);
await new Promise((resolve) => setTimeout(resolve)); // update of channels is debounced
assert.verifySteps(["update-channels"]);
});
@@ -117,12 +111,14 @@ QUnit.test("Channel subscription is renewed when channel is left", async (assert
assert.step("update-channels");
},
});
await openDiscuss();
openDiscuss();
await click(".o-mail-DiscussSidebarChannel .btn[title='Leave this channel']");
await contains(".o-mail-DiscussSidebarChannel", 0);
await new Promise((resolve) => setTimeout(resolve)); // update of channels is debounced
assert.verifySteps(["update-channels"]);
});
QUnit.test("Adding attachments", async (assert) => {
QUnit.test("Adding attachments", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "Hogwarts Legacy" });
const messageId = pyEnv["mail.message"].create({
@@ -133,23 +129,21 @@ QUnit.test("Adding attachments", async (assert) => {
});
const tab1 = await start({ asTab: true });
const tab2 = await start({ asTab: true });
await tab1.openDiscuss(channelId);
await tab2.openDiscuss(channelId);
tab1.openDiscuss(channelId);
tab2.openDiscuss(channelId);
const attachmentId = pyEnv["ir.attachment"].create({
name: "test.txt",
mimetype: "text/plain",
});
await afterNextRender(() =>
tab1.env.services.rpc("/mail/message/update_content", {
body: "Hello world!",
attachment_ids: [attachmentId],
message_id: messageId,
})
);
assert.containsOnce(tab2.target, ".o-mail-AttachmentCard:contains(test.txt)");
tab1.env.services.rpc("/mail/message/update_content", {
body: "Hello world!",
attachment_ids: [attachmentId],
message_id: messageId,
});
await contains(".o-mail-AttachmentCard:contains(test.txt)", 1, { target: tab2.target });
});
QUnit.test("Remove attachment from message", async (assert) => {
QUnit.test("Remove attachment from message", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const attachmentId = pyEnv["ir.attachment"].create({
@@ -165,15 +159,15 @@ QUnit.test("Remove attachment from message", async (assert) => {
});
const tab1 = await start({ asTab: true });
const tab2 = await start({ asTab: true });
await tab1.openDiscuss(channelId);
await tab2.openDiscuss(channelId);
assert.containsOnce(tab1.target, ".o-mail-AttachmentCard:contains(test.txt)");
await tab2.click(".o-mail-AttachmentCard-unlink");
await tab2.click(".modal-footer .btn:contains(Ok)");
assert.containsNone(tab1.target, ".o-mail-AttachmentCard:contains(test.txt)");
tab1.openDiscuss(channelId);
tab2.openDiscuss(channelId);
await contains(".o-mail-AttachmentCard:contains(test.txt)", 1, { target: tab1.target });
await click(".o-mail-AttachmentCard-unlink", { target: tab2.target });
await click(".modal-footer .btn:contains(Ok)", { target: tab2.target });
await contains(".o-mail-AttachmentCard:contains(test.txt)", 0, { target: tab1.target });
});
QUnit.test("Message delete notification", async (assert) => {
QUnit.test("Message delete notification", async () => {
const pyEnv = await startServer();
const messageId = pyEnv["mail.message"].create({
body: "Needaction message",
@@ -190,8 +184,8 @@ QUnit.test("Message delete notification", async (assert) => {
});
const { openDiscuss } = await start();
openDiscuss();
(await contains("[title='Expand']")).click();
(await contains("[title='Mark as Todo']")).click();
await click("[title='Expand']");
await click("[title='Mark as Todo']");
await contains("button:contains(Inbox) .badge");
await contains("button:contains(Starred) .badge");
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.message/delete", {
@@ -1,13 +1,13 @@
/* @odoo-module */
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { browser } from "@web/core/browser/browser";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
QUnit.module("call setting");
QUnit.test("Renders the call settings", async (assert) => {
QUnit.test("Renders the call settings", async () => {
patchWithCleanup(browser, {
navigator: {
...browser.navigator,
@@ -31,19 +31,19 @@ QUnit.test("Renders the call settings", async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "test" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-Discuss-header .fa-gear");
assert.containsOnce($, ".o-discuss-CallSettings", "Should have a call settings menu");
assert.containsOnce($, "label[aria-label='Input device']");
assert.containsOnce($, "option[value=mockAudioDeviceId]");
assert.containsNone($, "option[value=mockVideoDeviceId]");
assert.containsOnce($, "input[title='toggle push-to-talk']");
assert.containsOnce($, "label[aria-label='Voice detection threshold']");
assert.containsOnce($, "input[title='Show video participants only']");
assert.containsOnce($, "input[title='Blur video background']");
await contains(".o-discuss-CallSettings");
await contains("label[aria-label='Input device']");
await contains("option[value=mockAudioDeviceId]");
await contains("option[value=mockVideoDeviceId]", 0);
await contains("input[title='toggle push-to-talk']");
await contains("label[aria-label='Voice detection threshold']");
await contains("input[title='Show video participants only']");
await contains("input[title='Blur video background']");
});
QUnit.test("activate push to talk", async (assert) => {
QUnit.test("activate push to talk", async () => {
patchWithCleanup(browser, {
navigator: {
...browser.navigator,
@@ -55,15 +55,15 @@ QUnit.test("activate push to talk", async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "test" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-Discuss-header .fa-gear");
await click("input[title='toggle push-to-talk']");
assert.containsOnce($, "i[aria-label='Register new key']");
assert.containsOnce($, "label[aria-label='Delay after releasing push-to-talk']");
assert.containsNone($, "label[aria-label='Voice detection threshold']");
await contains("i[aria-label='Register new key']");
await contains("label[aria-label='Delay after releasing push-to-talk']");
await contains("label[aria-label='Voice detection threshold']", 0);
});
QUnit.test("activate blur", async (assert) => {
QUnit.test("activate blur", async () => {
patchWithCleanup(browser, {
navigator: {
...browser.navigator,
@@ -74,24 +74,25 @@ QUnit.test("activate blur", async (assert) => {
});
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "test" });
const { click, openDiscuss } = await start();
await openDiscuss(channelId);
const { openDiscuss } = await start();
openDiscuss(channelId);
await click(".o-mail-Discuss-header .fa-gear");
await click("input[title='Blur video background']");
assert.containsOnce($, "label[aria-label='Background blur intensity']");
assert.containsOnce($, "label[aria-label='Edge blur intensity']");
await contains("label[aria-label='Background blur intensity']");
await contains("label[aria-label='Edge blur intensity']");
});
QUnit.test("Inbox should not have any call settings menu", async (assert) => {
QUnit.test("Inbox should not have any call settings menu", async () => {
await startServer();
const { openDiscuss } = await start();
await openDiscuss("mail.box_inbox");
assert.containsNone($, "button[title='Show Call Settings']");
openDiscuss("mail.box_inbox");
await contains(".o-mail-Thread");
await contains("button[title='Show Call Settings']", 0);
});
QUnit.test(
"Call settings menu should not be visible on selecting a mailbox (from being open)",
async (assert) => {
async () => {
patchWithCleanup(browser, {
navigator: {
...browser.navigator,
@@ -103,10 +104,10 @@ QUnit.test(
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("button[title='Show Call Settings']");
await click("button:contains(Inbox)");
assert.containsNone($, "button[title='Hide Call Settings']");
assert.containsNone($, ".o-discuss-CallSettings");
await contains("button[title='Hide Call Settings']", 0);
await contains(".o-discuss-CallSettings", 0);
}
);
@@ -15,32 +15,32 @@ import { nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
QUnit.module("call");
QUnit.test("basic rendering", async (assert) => {
QUnit.test("basic rendering", async () => {
mockGetMedia();
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
name: "General",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-Discuss-header button[title='Start a Call']");
assert.containsOnce($, ".o-discuss-Call");
assert.containsOnce($, ".o-discuss-CallParticipantCard[aria-label='Mitchell Admin']");
assert.containsOnce($, ".o-discuss-CallActionList");
assert.containsOnce($, ".o-discuss-CallMenu-buttonContent");
assert.containsN($, ".o-discuss-CallActionList button", 5);
assert.containsOnce($, "button[aria-label='Unmute'], button[aria-label='Mute']"); // FIXME depends on current browser permission
assert.containsOnce($, ".o-discuss-CallActionList button[aria-label='Deafen']");
assert.containsOnce($, ".o-discuss-CallActionList button[aria-label='Turn camera on']");
assert.containsOnce($, "button[title='More']");
assert.containsOnce($, ".o-discuss-CallActionList button[aria-label='Disconnect']");
await contains(".o-discuss-Call");
await contains(".o-discuss-CallParticipantCard[aria-label='Mitchell Admin']");
await contains(".o-discuss-CallActionList");
await contains(".o-discuss-CallMenu-buttonContent");
await contains(".o-discuss-CallActionList button", 5);
await contains("button[aria-label='Unmute'], button[aria-label='Mute']"); // FIXME depends on current browser permission
await contains(".o-discuss-CallActionList button[aria-label='Deafen']");
await contains(".o-discuss-CallActionList button[aria-label='Turn camera on']");
await contains("button[title='More']");
await contains(".o-discuss-CallActionList button[aria-label='Disconnect']");
await click("button[title='More']");
assert.containsOnce($, "[title='Raise Hand']");
assert.containsOnce($, "[title='Share Screen']");
assert.containsOnce($, "[title='Enter Full Screen']");
await contains("[title='Raise Hand']");
await contains("[title='Share Screen']");
await contains("[title='Enter Full Screen']");
});
QUnit.test("no call with odoobot", async (assert) => {
QUnit.test("no call with odoobot", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
@@ -50,43 +50,37 @@ QUnit.test("no call with odoobot", async (assert) => {
channel_type: "chat",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Discuss-header button[title='Start a Call']");
openDiscuss(channelId);
await contains(".o-mail-Discuss-header");
await contains(".o-mail-Discuss-header button[title='Start a Call']", 0);
});
QUnit.test("should not display call UI when no more members (self disconnect)", async (assert) => {
QUnit.test("should not display call UI when no more members (self disconnect)", async () => {
mockGetMedia();
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { openDiscuss } = await start();
openDiscuss(channelId);
(await contains(".o-mail-Discuss-header button[title='Start a Call']")).click();
await click(".o-mail-Discuss-header button[title='Start a Call']");
await contains(".o-discuss-Call");
click(".o-discuss-CallActionList button[aria-label='Disconnect']");
await click(".o-discuss-CallActionList button[aria-label='Disconnect']");
await contains(".o-discuss-Call", 0);
});
QUnit.test("show call UI in chat window when in call", async (assert) => {
QUnit.test("show call UI in chat window when in call", async () => {
mockGetMedia();
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({ name: "General" });
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem:contains(General)");
assert.containsOnce($, ".o-mail-ChatWindow");
assert.containsNone($, ".o-discuss-Call");
assert.containsOnce(
$,
".o-mail-ChatWindow-header .o-mail-ChatWindow-command[title='Start a Call']"
);
await contains(".o-mail-ChatWindow");
await contains(".o-discuss-Call", 0);
await contains(".o-mail-ChatWindow-header .o-mail-ChatWindow-command[title='Start a Call']");
await click(".o-mail-ChatWindow-header .o-mail-ChatWindow-command[title='Start a Call']");
assert.containsOnce($, ".o-discuss-Call");
assert.containsNone(
$,
".o-mail-ChatWindow-header .o-mail-ChatWindow-command[title='Start a Call']"
);
await contains(".o-discuss-Call");
await contains(".o-mail-ChatWindow-header .o-mail-ChatWindow-command[title='Start a Call']", 0);
});
QUnit.test("should disconnect when closing page while in call", async (assert) => {
@@ -94,7 +88,7 @@ QUnit.test("should disconnect when closing page while in call", async (assert) =
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
patchWithCleanup(browser, {
navigator: {
...browser.navigator,
@@ -110,7 +104,7 @@ QUnit.test("should disconnect when closing page while in call", async (assert) =
});
await click(".o-mail-Discuss-header button[title='Start a Call']");
assert.containsOnce($, ".o-discuss-Call");
await contains(".o-discuss-Call");
// simulate page close
await afterNextRender(() => window.dispatchEvent(new Event("pagehide"), { bubble: true }));
await nextTick();
@@ -140,69 +134,64 @@ QUnit.test("should display invitations", async (assert) => {
channel_id: channelId,
});
await start();
// Simulate receive call invitation
await afterNextRender(() => {
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.record/insert", {
Thread: {
id: channelId,
model: "discuss.channel",
rtcInvitingSession: { id: sessionId, channelMember: { id: memberId } },
},
});
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.record/insert", {
Thread: {
id: channelId,
model: "discuss.channel",
rtcInvitingSession: { id: sessionId, channelMember: { id: memberId } },
},
});
assert.containsOnce($, ".o-discuss-CallInvitation");
await contains(".o-discuss-CallInvitation");
assert.verifySteps(["play_sound_effect"]);
// Simulate stop receiving call invitation
await afterNextRender(() => {
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.record/insert", {
Thread: {
id: channelId,
model: "discuss.channel",
rtcInvitingSession: [["unlink"]],
},
});
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.record/insert", {
Thread: {
id: channelId,
model: "discuss.channel",
rtcInvitingSession: [["unlink"]],
},
});
assert.containsNone($, ".o-discuss-CallInvitation");
await contains(".o-discuss-CallInvitation", 0);
assert.verifySteps(["pause_sound_effect"]);
});
QUnit.test("can share screen", async (assert) => {
QUnit.test("can share screen", async () => {
mockGetMedia();
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
name: "General",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-Discuss-header button[title='Start a Call']");
await click(".o-discuss-CallActionList [title='More']");
await click("[title='Share Screen']");
assert.containsOnce($, ".o-discuss-CallParticipantCard video");
await contains(".o-discuss-CallParticipantCard video");
await click(".o-discuss-CallActionList [title='More']");
await click("[title='Stop Sharing Screen']");
assert.containsNone($, ".o-discuss-CallParticipantCard video");
await contains(".o-discuss-CallParticipantCard video", 0);
});
QUnit.test("can share user camera", async (assert) => {
QUnit.test("can share user camera", async () => {
mockGetMedia();
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
name: "General",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-Discuss-header button[title='Start a Call']");
await click(".o-discuss-CallActionList button[title='Turn camera on']");
assert.containsOnce($, ".o-discuss-CallParticipantCard video");
await contains(".o-discuss-CallParticipantCard video");
await click(".o-discuss-CallActionList button[title='Stop camera']");
assert.containsNone($, ".o-discuss-CallParticipantCard video");
await contains(".o-discuss-CallParticipantCard video", 0);
});
QUnit.test("Create a direct message channel when clicking on start a meeting", async () => {
mockGetMedia();
const { openDiscuss } = await start();
openDiscuss();
(await contains("button:contains(Start a meeting)")).click();
await click("button:contains(Start a meeting)");
await contains(".o-mail-DiscussSidebarChannel:contains(Mitchell Admin)");
await contains(".o-discuss-Call");
await contains(".o-discuss-ChannelInvitation");
@@ -1,33 +1,29 @@
/* @odoo-module */
import { afterNextRender, click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { editInput, triggerEvent } from "@web/../tests/helpers/utils";
import { triggerHotkey } from "@web/../tests/helpers/utils";
QUnit.module("call");
QUnit.test("no default rtc after joining a chat conversation", async (assert) => {
QUnit.test("no default rtc after joining a chat conversation", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Mario" });
pyEnv["res.users"].create({ partner_id: partnerId });
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone($, ".o-mail-DiscussSidebarChannel");
openDiscuss();
await click(".o-mail-DiscussSidebar i[title='Start a conversation']");
await afterNextRender(() =>
editInput(document.body, ".o-discuss-ChannelSelector input", "mario")
);
await contains(".o-mail-DiscussSidebarChannel", 0);
await insertText(".o-discuss-ChannelSelector input", "mario");
await click(".o-discuss-ChannelSelector-suggestion");
await triggerEvent(document.body, ".o-discuss-ChannelSelector input", "keydown", {
key: "Enter",
});
assert.containsOnce($, ".o-mail-DiscussSidebarChannel");
assert.containsNone($, ".o-mail-Discuss-content .o-mail-Message");
assert.containsNone($, ".o-discuss-Call");
await contains(".o-discuss-ChannelSelector-suggestion", 0);
triggerHotkey("Enter");
await contains(".o-mail-DiscussSidebarChannel");
await contains(".o-mail-Discuss-content .o-mail-Message", 0);
await contains(".o-discuss-Call", 0);
});
QUnit.test("no default rtc after joining a group conversation", async (assert) => {
QUnit.test("no default rtc after joining a group conversation", async () => {
const pyEnv = await startServer();
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
{ name: "Mario" },
@@ -35,21 +31,15 @@ QUnit.test("no default rtc after joining a group conversation", async (assert) =
]);
pyEnv["res.users"].create([{ partner_id: partnerId_1 }, { partner_id: partnerId_2 }]);
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone($, ".o-mail-DiscussSidebarChannel");
openDiscuss();
await click(".o-mail-DiscussSidebar i[title='Start a conversation']");
await afterNextRender(() =>
editInput(document.body, ".o-discuss-ChannelSelector input", "mario")
);
await click(".o-discuss-ChannelSelector-suggestion");
await afterNextRender(() =>
editInput(document.body, ".o-discuss-ChannelSelector input", "luigi")
);
await click(".o-discuss-ChannelSelector-suggestion");
await triggerEvent(document.body, ".o-discuss-ChannelSelector input", "keydown", {
key: "Enter",
});
assert.containsOnce($, ".o-mail-DiscussSidebarChannel");
assert.containsNone($, ".o-mail-Discuss-content .o-mail-Message");
assert.containsNone($, ".o-discuss-Call");
await contains(".o-mail-DiscussSidebarChannel", 0);
await insertText(".o-discuss-ChannelSelector input", "mario");
await click(".o-discuss-ChannelSelector-suggestion:contains(Mario)");
await insertText(".o-discuss-ChannelSelector input", "luigi", { replace: true });
await click(".o-discuss-ChannelSelector-suggestion:contains(Luigi)");
triggerHotkey("Enter");
await contains(".o-mail-DiscussSidebarChannel");
await contains(".o-mail-Discuss-content .o-mail-Message", 0);
await contains(".o-discuss-Call", 0);
});
@@ -1,13 +1,13 @@
/* @odoo-module */
import { Command } from "@mail/../tests/helpers/command";
import { click, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("channel invitation form");
QUnit.test(
"should display the channel invitation form after clicking on the invite button of a chat",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({
email: "testpartner@odoo.com",
@@ -23,13 +23,13 @@ QUnit.test(
channel_type: "channel",
});
const { openDiscuss } = await start({ hasTimeControl: true });
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-Discuss-header button[title='Add Users']");
assert.containsOnce($, ".o-discuss-ChannelInvitation");
await contains(".o-discuss-ChannelInvitation");
}
);
QUnit.test("can invite users in channel from chat window", async (assert) => {
QUnit.test("can invite users in channel from chat window", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({
email: "testpartner@odoo.com",
@@ -46,49 +46,45 @@ QUnit.test("can invite users in channel from chat window", async (assert) => {
await start();
await click("[title='Open Actions Menu']");
await click("[title='Add Users']");
assert.containsOnce($, ".o-discuss-ChannelInvitation");
await contains(".o-discuss-ChannelInvitation");
await click(
".o-discuss-ChannelInvitation-selectable:contains(TestPartner) input[type='checkbox']"
);
await click("[title='Invite to Channel']");
assert.containsNone($, ".o-discuss-ChannelInvitation");
assert.containsOnce(
$,
await click("[title='Invite to Channel']:not(:disabled)");
await contains(".o-discuss-ChannelInvitation", 0);
await contains(
".o-mail-Thread .o-mail-NotificationMessage:contains(Mitchell Admin invited TestPartner to the channel)"
);
});
QUnit.test(
"should be able to search for a new user to invite from an existing chat",
async (assert) => {
const pyEnv = await startServer();
const partnerId_1 = pyEnv["res.partner"].create({
email: "testpartner@odoo.com",
name: "TestPartner",
});
const partnerId_2 = pyEnv["res.partner"].create({
email: "testpartner2@odoo.com",
name: "TestPartner2",
});
pyEnv["res.users"].create({ partner_id: partnerId_1 });
pyEnv["res.users"].create({ partner_id: partnerId_2 });
const channelId = pyEnv["discuss.channel"].create({
name: "TestChanel",
channel_member_ids: [
Command.create({ partner_id: pyEnv.currentPartnerId }),
Command.create({ partner_id: partnerId_1 }),
],
channel_type: "channel",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
await click(".o-mail-Discuss-header button[title='Add Users']");
await insertText(".o-discuss-ChannelInvitation-search", "TestPartner2");
assert.strictEqual($(".o-discuss-ChannelInvitation-selectable").text(), "TestPartner2");
}
);
QUnit.test("should be able to search for a new user to invite from an existing chat", async () => {
const pyEnv = await startServer();
const partnerId_1 = pyEnv["res.partner"].create({
email: "testpartner@odoo.com",
name: "TestPartner",
});
const partnerId_2 = pyEnv["res.partner"].create({
email: "testpartner2@odoo.com",
name: "TestPartner2",
});
pyEnv["res.users"].create({ partner_id: partnerId_1 });
pyEnv["res.users"].create({ partner_id: partnerId_2 });
const channelId = pyEnv["discuss.channel"].create({
name: "TestChanel",
channel_member_ids: [
Command.create({ partner_id: pyEnv.currentPartnerId }),
Command.create({ partner_id: partnerId_1 }),
],
channel_type: "channel",
});
const { openDiscuss } = await start();
openDiscuss(channelId);
await click(".o-mail-Discuss-header button[title='Add Users']");
await insertText(".o-discuss-ChannelInvitation-search", "TestPartner2");
await contains(".o-discuss-ChannelInvitation-selectable:contains(TestPartner2)");
});
QUnit.test("Invitation form should display channel group restriction", async (assert) => {
QUnit.test("Invitation form should display channel group restriction", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({
email: "testpartner@odoo.com",
@@ -105,15 +101,12 @@ QUnit.test("Invitation form should display channel group restriction", async (as
group_public_id: groupId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-Discuss-header button[title='Add Users']");
assert.containsOnce(
$,
'.o-discuss-ChannelInvitation:contains(Access restricted to group "testGroup")'
);
await contains('.o-discuss-ChannelInvitation:contains(Access restricted to group "testGroup")');
});
QUnit.test("should be able to create a new group chat from an existing chat", async (assert) => {
QUnit.test("should be able to create a new group chat from an existing chat", async () => {
const pyEnv = await startServer();
const partnerId_1 = pyEnv["res.partner"].create({
email: "testpartner@odoo.com",
@@ -134,13 +127,12 @@ QUnit.test("should be able to create a new group chat from an existing chat", as
channel_type: "chat",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-Discuss-header button[title='Add Users']");
await insertText(".o-discuss-ChannelInvitation-search", "TestPartner2");
await click(".form-check-input");
await click("button[title='Create Group Chat']");
assert.strictEqual(
$(".o-mail-Discuss-threadName").val(),
"Mitchell Admin, TestPartner, and TestPartner2"
await click(".o-discuss-ChannelInvitation-selectable:contains(TestPartner2)");
await click("button[title='Create Group Chat']:not(:disabled)");
await contains(
".o-mail-DiscussSidebarChannel:contains(Mitchell Admin, TestPartner, and TestPartner2)"
);
});
@@ -2,7 +2,7 @@
import { createLocalId } from "@mail/utils/common/misc";
import { Command } from "@mail/../tests/helpers/command";
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { nextTick } from "@web/../tests/helpers/utils";
@@ -10,7 +10,7 @@ QUnit.module("channel member list");
QUnit.test(
"there should be a button to show member list in the thread view topbar initially",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const channelId = pyEnv["discuss.channel"].create({
@@ -22,14 +22,14 @@ QUnit.test(
channel_type: "channel",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, "[title='Show Member List']");
openDiscuss(channelId);
await contains("[title='Show Member List']");
}
);
QUnit.test(
"should show member list when clicking on show member list button in thread view topbar",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const channelId = pyEnv["discuss.channel"].create({
@@ -41,13 +41,13 @@ QUnit.test(
channel_type: "channel",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("[title='Show Member List']");
assert.containsOnce($, ".o-discuss-ChannelMemberList");
await contains(".o-discuss-ChannelMemberList");
}
);
QUnit.test("should have correct members in member list", async (assert) => {
QUnit.test("should have correct members in member list", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const channelId = pyEnv["discuss.channel"].create({
@@ -59,16 +59,16 @@ QUnit.test("should have correct members in member list", async (assert) => {
channel_type: "channel",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("[title='Show Member List']");
assert.containsN($, ".o-discuss-ChannelMember", 2);
assert.containsOnce($, `.o-discuss-ChannelMember:contains("${pyEnv.currentPartner.name}")`);
assert.containsOnce($, ".o-discuss-ChannelMember:contains('Demo')");
await contains(".o-discuss-ChannelMember", 2);
await contains(`.o-discuss-ChannelMember:contains("${pyEnv.currentPartner.name}")`);
await contains(".o-discuss-ChannelMember:contains('Demo')");
});
QUnit.test(
"there should be a button to hide member list in the thread view topbar when the member list is visible",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const channelId = pyEnv["discuss.channel"].create({
@@ -80,9 +80,9 @@ QUnit.test(
channel_type: "channel",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("[title='Show Member List']");
assert.containsOnce($, "[title='Hide Member List']");
await contains("[title='Hide Member List']");
}
);
@@ -99,33 +99,30 @@ QUnit.test("chat with member should be opened after clicking on channel member",
channel_type: "channel",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("[title='Show Member List']");
await click(".o-discuss-ChannelMember.cursor-pointer");
assert.containsOnce($, ".o-mail-AutoresizeInput[title='Demo']");
await contains(".o-mail-AutoresizeInput[title='Demo']");
});
QUnit.test(
"should show a button to load more members if they are not all loaded",
async (assert) => {
// Test assumes at most 100 members are loaded at once.
const pyEnv = await startServer();
const channel_member_ids = [];
for (let i = 0; i < 101; i++) {
const partnerId = pyEnv["res.partner"].create({ name: "name" + i });
channel_member_ids.push(Command.create({ partner_id: partnerId }));
}
const channelId = pyEnv["discuss.channel"].create({
name: "TestChanel",
channel_type: "channel",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
pyEnv["discuss.channel"].write([channelId], { channel_member_ids });
await click("[title='Show Member List']");
assert.containsOnce($, "button:contains(Load more)");
QUnit.test("should show a button to load more members if they are not all loaded", async () => {
// Test assumes at most 100 members are loaded at once.
const pyEnv = await startServer();
const channel_member_ids = [];
for (let i = 0; i < 101; i++) {
const partnerId = pyEnv["res.partner"].create({ name: "name" + i });
channel_member_ids.push(Command.create({ partner_id: partnerId }));
}
);
const channelId = pyEnv["discuss.channel"].create({
name: "TestChanel",
channel_type: "channel",
});
const { openDiscuss } = await start();
openDiscuss(channelId);
pyEnv["discuss.channel"].write([channelId], { channel_member_ids });
await click("[title='Show Member List']");
await contains("button:contains(Load more)");
});
QUnit.test("Load more button should load more members", async (assert) => {
// Test assumes at most 100 members are loaded at once.
@@ -140,11 +137,11 @@ QUnit.test("Load more button should load more members", async (assert) => {
channel_type: "channel",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
pyEnv["discuss.channel"].write([channelId], { channel_member_ids });
await click("[title='Show Member List']");
await click("[title='Load more']");
assert.containsN($, ".o-discuss-ChannelMember", 102);
await contains(".o-discuss-ChannelMember", 102);
});
QUnit.test("Channel member count update after user joined", async (assert) => {
@@ -152,15 +149,16 @@ QUnit.test("Channel member count update after user joined", async (assert) => {
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const userId = pyEnv["res.users"].create({ name: "Harry" });
pyEnv["res.partner"].create({ name: "Harry", user_ids: [userId] });
const { env, openDiscuss } = await start();
await openDiscuss(channelId);
const thread = env.services["mail.store"].threads[createLocalId("discuss.channel", channelId)];
assert.strictEqual(thread.memberCount, 1);
const { openDiscuss } = await start();
openDiscuss(channelId);
await click("[title='Show Member List']");
await contains(".o-discuss-ChannelMemberList:contains(Offline - 1)");
await click("[title='Add Users']");
await click(".o-discuss-ChannelInvitation-selectable:contains(Harry)");
await click("[title='Invite to Channel']");
assert.strictEqual(thread.memberCount, 2);
await click("[title='Invite to Channel']:not(:disabled)");
await contains(".o-discuss-ChannelInvitation", 0);
await click("[title='Show Member List']");
await contains(".o-discuss-ChannelMemberList:contains(Offline - 2)");
});
QUnit.test("Channel member count update after user left", async (assert) => {
@@ -175,7 +173,7 @@ QUnit.test("Channel member count update after user left", async (assert) => {
],
});
const { env, openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
const thread = env.services["mail.store"].threads[createLocalId("discuss.channel", channelId)];
assert.strictEqual(thread.memberCount, 2);
await pyEnv.withUser(userId, () =>
@@ -1,7 +1,7 @@
/* @odoo-module */
import { Composer } from "@mail/core/common/composer";
import { click, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
@@ -26,7 +26,7 @@ QUnit.test('do not send typing notification on typing "/" command', async (asser
}
},
});
await openDiscuss(channelId);
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "/");
assert.verifySteps([], "No rpc done");
});
@@ -43,9 +43,10 @@ QUnit.test(
}
},
});
await openDiscuss(channelId);
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "/");
await click(".o-mail-Composer-suggestion");
await click(".o-mail-Composer-suggestion:eq(0)");
await contains(".o-mail-Composer-suggestion", 0);
await insertText(".o-mail-Composer-input", " is user?");
assert.verifySteps([], "No rpc done");
}
@@ -58,11 +59,11 @@ QUnit.test("add an emoji after a command", async (assert) => {
channel_type: "channel",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
assert.strictEqual($(".o-mail-Composer-input").val(), "");
openDiscuss(channelId);
assert.strictEqual((await contains(".o-mail-Composer-input")).val(), "");
await insertText(".o-mail-Composer-input", "/");
await click(".o-mail-Composer-suggestion");
await click(".o-mail-Composer-suggestion:eq(0)");
await contains(".o-mail-Composer-suggestion", 0);
assert.strictEqual(
$(".o-mail-Composer-input").val().replace(/\s/, " "),
"/who ",
@@ -71,5 +72,6 @@ QUnit.test("add an emoji after a command", async (assert) => {
await click("button[aria-label='Emojis']");
await click(".o-Emoji:contains(😊)");
await contains(".o-Emoji", 0);
assert.strictEqual($(".o-mail-Composer-input").val().replace(/\s/, " "), "/who 😊");
});
@@ -1,27 +1,28 @@
/* @odoo-module */
import { Command } from "@mail/../tests/helpers/command";
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("crosstab");
QUnit.test("Add member to channel", async (assert) => {
QUnit.test("Add member to channel", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const userId = pyEnv["res.users"].create({ name: "Harry" });
pyEnv["res.partner"].create({ name: "Harry", user_ids: [userId] });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("[title='Show Member List']");
assert.containsOnce($, ".o-discuss-ChannelMember:contains(Mitchell Admin)");
await contains(".o-discuss-ChannelMember:contains(Mitchell Admin)");
await click("[title='Add Users']");
await click(".o-discuss-ChannelInvitation-selectable:contains(Harry)");
await click("[title='Invite to Channel']");
await click("[title='Invite to Channel']:not(:disabled)");
await contains(".o-discuss-ChannelInvitation", 0);
await click("[title='Show Member List']");
assert.containsOnce($, ".o-discuss-ChannelMember:contains(Harry)");
await contains(".o-discuss-ChannelMember:contains(Harry)");
});
QUnit.test("Remove member from channel", async (assert) => {
QUnit.test("Remove member from channel", async () => {
const pyEnv = await startServer();
const userId = pyEnv["res.users"].create({ name: "Harry" });
const partnerId = pyEnv["res.partner"].create({
@@ -36,10 +37,11 @@ QUnit.test("Remove member from channel", async (assert) => {
],
});
const { env, openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("[title='Show Member List']");
assert.containsOnce($, ".o-discuss-ChannelMember:contains(Harry)");
await contains(".o-discuss-ChannelMember:contains(Harry)");
pyEnv.withUser(userId, () =>
env.services.orm.call("discuss.channel", "action_unfollow", [channelId])
);
await contains(".o-discuss-ChannelMember:contains(Harry)", 0);
});
@@ -1,17 +1,17 @@
/* @odoo-module */
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("discuss");
QUnit.test("Member list and settings menu are exclusive", async (assert) => {
QUnit.test("Member list and settings menu are exclusive", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("[title='Show Member List']");
assert.containsOnce($, ".o-discuss-ChannelMemberList");
await contains(".o-discuss-ChannelMemberList");
await click("[title='Show Call Settings']");
assert.containsOnce($, ".o-discuss-CallSettings");
assert.containsNone($, ".o-discuss-ChannelMemberList");
await contains(".o-discuss-CallSettings");
await contains(".o-discuss-ChannelMemberList", 0);
});
@@ -2,13 +2,7 @@
import { Composer } from "@mail/core/common/composer";
import { Command } from "@mail/../tests/helpers/command";
import {
afterNextRender,
click,
insertText,
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
import { click, contains, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
@@ -23,28 +17,31 @@ QUnit.module("suggestion", {
},
});
QUnit.test('display command suggestions on typing "/"', async (assert) => {
QUnit.test('display command suggestions on typing "/"', async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
name: "General",
channel_type: "channel",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
openDiscuss(channelId);
await contains(".o-mail-Composer-suggestionList");
await contains(".o-mail-Composer-suggestionList .o-open", 0);
await insertText(".o-mail-Composer-input", "/");
assert.containsOnce($, ".o-mail-Composer-suggestionList .o-open");
await contains(".o-mail-Composer-suggestionList .o-open");
});
QUnit.test("use a command for a specific channel type", async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ channel_type: "chat" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
openDiscuss(channelId);
await contains(".o-mail-Composer-suggestionList");
await contains(".o-mail-Composer-suggestionList .o-open", 0);
assert.strictEqual($(".o-mail-Composer-input").val(), "");
await insertText(".o-mail-Composer-input", "/");
await click(".o-mail-Composer-suggestion");
await click(".o-mail-Composer-suggestion:eq(0)");
await contains(".o-mail-Composer-suggestion", 0);
assert.strictEqual(
$(".o-mail-Composer-input").val().replace(/\s/, " "),
"/who ",
@@ -61,13 +58,14 @@ QUnit.test(
channel_type: "channel",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
openDiscuss(channelId);
await contains(".o-mail-Composer-suggestionList");
await contains(".o-mail-Composer-suggestionList .o-open", 0);
assert.strictEqual($(".o-mail-Composer-input").val(), "");
await insertText(".o-mail-Composer-input", "bluhbluh ");
assert.strictEqual($(".o-mail-Composer-input").val(), "bluhbluh ");
await insertText(".o-mail-Composer-input", "/");
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
await contains(".o-mail-Composer-suggestionList .o-open", 0);
}
);
@@ -117,21 +115,21 @@ QUnit.test("Sort partner suggestions by recent chats", async (assert) => {
},
]);
const { openDiscuss } = await start();
await openDiscuss();
openDiscuss();
await click(".o-mail-DiscussSidebarChannel:contains('User 2')");
await insertText(".o-mail-Composer-input", "This is a test");
await click(".o-mail-Composer-send:not(:disabled)");
assert.containsOnce($, ".o-mail-Message:contains('This is a test')");
await contains(".o-mail-Message:contains('This is a test')");
await click(".o-mail-DiscussSidebarChannel:contains('General')");
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input[placeholder='Message #General…']", "@");
await insertText(".o-mail-Composer-input", "User");
assert.containsN($, ".o-mail-Composer-suggestion", 3);
await contains(".o-mail-Composer-suggestion", 3);
assert.strictEqual($(".o-mail-Composer-suggestion").eq(0).text(), "User 2");
assert.strictEqual($(".o-mail-Composer-suggestion").eq(1).text(), "User 1");
assert.strictEqual($(".o-mail-Composer-suggestion").eq(2).text(), "User 3");
});
QUnit.test("suggestion are shown after deleting a character", async (assert) => {
QUnit.test("mention suggestion are shown after deleting a character", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
const channelId = pyEnv["discuss.channel"].create({
@@ -144,13 +142,35 @@ QUnit.test("suggestion are shown after deleting a character", async (assert) =>
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input", "John Da");
assert.containsNone($, ".o-mail-Composer-suggestion:contains(John Doe)");
await insertText(".o-mail-Composer-input", "@John D");
await contains(".o-mail-Composer-suggestion:contains(John Doe)");
await insertText(".o-mail-Composer-input", "a");
await contains(".o-mail-Composer-suggestion:contains(John D)", 0);
// Simulate pressing backspace
await afterNextRender(() => {
const textarea = document.querySelector(".o-mail-Composer-input");
textarea.value = textarea.value.slice(0, -1);
});
assert.containsOnce($, ".o-mail-Composer-suggestion:contains(John Doe)");
const textarea = document.querySelector(".o-mail-Composer-input");
textarea.value = textarea.value.slice(0, -1);
await contains(".o-mail-Composer-suggestion:contains(John Doe)");
});
QUnit.test("command suggestion are shown after deleting a character", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
const channelId = pyEnv["discuss.channel"].create({
name: "General",
channel_type: "channel",
channel_member_ids: [
Command.create({ partner_id: pyEnv.currentPartnerId }),
Command.create({ partner_id: partnerId }),
],
});
const { openDiscuss } = await start();
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "/he");
await contains(".o-mail-Composer-suggestion:contains(help)");
await insertText(".o-mail-Composer-input", "e");
await contains(".o-mail-Composer-suggestion:contains(help)", 0);
// Simulate pressing backspace
const textarea = document.querySelector(".o-mail-Composer-input");
textarea.value = textarea.value.slice(0, -1);
await contains(".o-mail-Composer-suggestion:contains(help)");
});
@@ -10,6 +10,7 @@ import { patchUiSize } from "@mail/../tests/helpers/patch_ui_size";
import {
afterNextRender,
click,
contains,
insertText,
start,
startServer,
@@ -23,55 +24,52 @@ QUnit.test("basic rendering", async (assert) => {
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click("button:contains(New Message)");
assert.containsOnce($, ".o-mail-ChatWindow");
assert.containsOnce($, ".o-mail-ChatWindow-header");
assert.containsOnce($, ".o-mail-ChatWindow-header .o-mail-ChatWindow-name");
await contains(".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow-header");
await contains(".o-mail-ChatWindow-header .o-mail-ChatWindow-name");
assert.strictEqual(
$(".o-mail-ChatWindow-header .o-mail-ChatWindow-name").text(),
"New message"
);
assert.containsN($, ".o-mail-ChatWindow-header .o-mail-ChatWindow-command", 2);
assert.containsOnce($, ".o-mail-ChatWindow-header .o-mail-ChatWindow-command[title='Fold']");
await contains(".o-mail-ChatWindow-header .o-mail-ChatWindow-command", 2);
await contains(".o-mail-ChatWindow-header .o-mail-ChatWindow-command[title='Fold']");
assert.containsOnce(
$,
".o-mail-ChatWindow-header .o-mail-ChatWindow-command[title='Close Chat Window']"
);
assert.containsOnce($, "span:contains('To :')");
assert.containsOnce($, ".o-discuss-ChannelSelector");
await contains("span:contains('To :')");
await contains(".o-discuss-ChannelSelector");
});
QUnit.test("focused on open [REQUIRE FOCUS]", async (assert) => {
QUnit.test("focused on open [REQUIRE FOCUS]", async () => {
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click("button:contains(New Message)");
assert.strictEqual(
document.activeElement,
$(".o-mail-ChatWindow .o-discuss-ChannelSelector input")[0]
);
await contains(".o-mail-ChatWindow .o-discuss-ChannelSelector input:focus");
});
QUnit.test("close", async (assert) => {
QUnit.test("close", async () => {
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click("button:contains(New Message)");
await click(".o-mail-ChatWindow-header .o-mail-ChatWindow-command[title='Close Chat Window']");
assert.containsNone($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow", 0);
});
QUnit.test("fold", async (assert) => {
QUnit.test("fold", async () => {
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click("button:contains(New Message)");
assert.containsOnce($, ".o-mail-ChatWindow-content");
assert.containsOnce($, ".o-discuss-ChannelSelector");
await contains(".o-mail-ChatWindow-content");
await contains(".o-discuss-ChannelSelector");
await click(".o-mail-ChatWindow-command[title='Fold']");
assert.containsNone($, ".o-mail-ChatWindow .o-mail-ChatWindow-content");
assert.containsNone($, ".o-mail-ChatWindow .o-discuss-ChannelSelector");
await contains(".o-mail-ChatWindow .o-mail-ChatWindow-content", 0);
await contains(".o-mail-ChatWindow .o-discuss-ChannelSelector", 0);
await click(".o-mail-ChatWindow-command[title='Open']");
assert.containsOnce($, ".o-mail-ChatWindow .o-mail-ChatWindow-content");
assert.containsOnce($, ".o-discuss-ChannelSelector");
await contains(".o-mail-ChatWindow .o-mail-ChatWindow-content");
await contains(".o-discuss-ChannelSelector");
});
QUnit.test(
@@ -110,14 +108,12 @@ QUnit.test(
}
},
});
assert.containsNone($, ".o-mail-ChatWindow-header:contains(New message)");
// open "new message" chat window
await click(".o_menu_systray i[aria-label='Messages']");
await click("button:contains(New Message)");
assert.containsOnce($, ".o-mail-ChatWindow-header:contains(New message)");
assert.containsN($, ".o-mail-ChatWindow", 2);
assert.containsOnce($, ".o-mail-ChatWindow .o-discuss-ChannelSelector");
await contains(".o-mail-ChatWindow-header:contains(New message)");
await contains(".o-mail-ChatWindow", 2);
await contains(".o-mail-ChatWindow .o-discuss-ChannelSelector");
assert.ok(
Array.from(document.querySelectorAll(".o-mail-ChatWindow"))
.pop()
@@ -127,7 +123,7 @@ QUnit.test(
// open channel-2
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem .o-mail-NotificationItem-name:contains(channel-2)");
assert.containsN($, ".o-mail-ChatWindow", 3);
await contains(".o-mail-ChatWindow", 3);
assert.ok(
Array.from(document.querySelectorAll(".o-mail-ChatWindow"))[1].textContent.includes(
"New message"
@@ -139,19 +135,19 @@ QUnit.test(
await insertText(".o-discuss-ChannelSelector input", "131");
await imSearchDef;
});
assert.containsOnce($, ".o-discuss-ChannelSelector-suggestion a");
await contains(".o-discuss-ChannelSelector-suggestion a");
const $link = $(".o-discuss-ChannelSelector-suggestion a");
assert.strictEqual($link.text(), "Partner 131");
await click($link);
assert.containsNone($, ".o-mail-ChatWindow-header:contains(New message)");
await contains(".o-mail-ChatWindow-header:contains(New message)", 0);
assert.strictEqual($(".o-mail-ChatWindow-name:eq(1)").text(), "Partner 131");
}
);
QUnit.test(
"new message chat window should close on selecting the user if chat with the user is already open",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Partner 131" });
pyEnv["res.users"].create({ partner_id: partnerId });
@@ -176,30 +172,18 @@ QUnit.test(
await click("button:contains(New Message)");
await insertText(".o-discuss-ChannelSelector", "131");
await click(".o-discuss-ChannelSelector-suggestion a");
assert.containsNone($, ".o-mail-ChatWindow-header:contains(New message)");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow-header:contains(New message)", 0);
await contains(".o-mail-ChatWindow");
}
);
QUnit.test("new message autocomplete should automatically select first result", async (assert) => {
QUnit.test("new message autocomplete should automatically select first result", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Partner 131" });
pyEnv["res.users"].create({ partner_id: partnerId });
const imSearchDef = makeDeferred();
await start({
mockRPC(route, args) {
if (args.method === "im_search") {
imSearchDef.resolve();
}
},
});
// open "new message" chat window
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click("button:contains(New Message)");
// search for a user in "new message" autocomplete
await afterNextRender(async () => {
await insertText(".o-discuss-ChannelSelector", "131");
await imSearchDef;
});
assert.hasClass($(".o-discuss-ChannelSelector-suggestion a"), "o-mail-NavigableList-active");
await insertText(".o-discuss-ChannelSelector", "131");
await contains(".o-discuss-ChannelSelector-suggestion a.o-mail-NavigableList-active");
});
@@ -1,7 +1,7 @@
/* @odoo-module */
import { afterNextRender, click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { Command } from "@mail/../tests/helpers/command";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { commandService } from "@web/core/commands/command_service";
import { registry } from "@web/core/registry";
@@ -22,18 +22,18 @@ QUnit.module("command palette", {
},
});
QUnit.test("open the chatWindow of a user from the command palette", async (assert) => {
QUnit.test("open the chatWindow of a user from the command palette", async () => {
const { advanceTime } = await start({ hasTimeControl: true });
triggerHotkey("control+k");
await nextTick();
// Switch to partners
await editSearchBar("@");
await afterNextRender(() => advanceTime(commandSetupRegistry.get("@").debounceDelay));
advanceTime(commandSetupRegistry.get("@").debounceDelay);
await click(".o_command.focused");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
});
QUnit.test("open the chatWindow of a channel from the command palette", async (assert) => {
QUnit.test("open the chatWindow of a channel from the command palette", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({ name: "general" });
pyEnv["discuss.channel"].create({ name: "project" });
@@ -42,16 +42,16 @@ QUnit.test("open the chatWindow of a channel from the command palette", async (a
await nextTick();
// Switch to channels
await editSearchBar("#");
await afterNextRender(() => advanceTime(commandSetupRegistry.get("#").debounceDelay));
assert.containsOnce($, ".o_command:contains(general)");
assert.containsOnce($, ".o_command:contains(project)");
advanceTime(commandSetupRegistry.get("#").debounceDelay);
await contains(".o_command:contains(general)");
await contains(".o_command:contains(project)");
await click(".o_command.focused");
assert.containsOnce($, ".o-mail-ChatWindow");
assert.containsOnce($, ".o-mail-ChatWindow-name:contains(general)");
await contains(".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow-name:contains(general)");
});
QUnit.test("Channel mentions in the command palette of Discuss app with @", async (assert) => {
QUnit.test("Channel mentions in the command palette of Discuss app with @", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Mario" });
const channelId = pyEnv["discuss.channel"].create({
@@ -77,40 +77,33 @@ QUnit.test("Channel mentions in the command palette of Discuss app with @", asyn
triggerHotkey("control+k");
await nextTick();
await editSearchBar("@");
await afterNextRender(() => advanceTime(commandSetupRegistry.get("@").debounceDelay));
assert.containsOnce($, ".o_command_palette:contains('Mentions')");
assert.containsOnce(
$(".o_command_category:contains('Mentions')"),
".o_command:contains('Mitchell Admin and Mario')"
advanceTime(commandSetupRegistry.get("@").debounceDelay);
await contains(".o_command_palette:contains('Mentions')");
await contains(
".o_command_category:contains('Mentions') .o_command:contains('Mitchell Admin and Mario')"
);
assert.containsOnce(
$(".o_command_category:not(:contains('Mentions'))"),
".o_command:contains('Mario')"
await contains(
$(".o_command_category:not(:contains('Mentions')) .o_command:contains('Mario')")
);
assert.containsOnce(
$(".o_command_category:not(:contains('Mentions'))"),
".o_command:contains('Mitchell Admin')"
await contains(
$(".o_command_category:not(:contains('Mentions')) .o_command:contains('Mitchell Admin')")
);
assert.containsOnce($, ".o_command.focused:contains('Mitchell Admin and Mario')");
await contains(".o_command.focused:contains('Mitchell Admin and Mario')");
await click(".o_command.focused");
assert.containsOnce($, ".o-mail-ChatWindow:contains('Mitchell Admin and Mario')");
await contains(".o-mail-ChatWindow:contains('Mitchell Admin and Mario')");
});
QUnit.test(
"Max 3 most recent channels in command palette of Discuss app with #",
async (assert) => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({ name: "channel_1" });
pyEnv["discuss.channel"].create({ name: "channel_2" });
pyEnv["discuss.channel"].create({ name: "channel_3" });
pyEnv["discuss.channel"].create({ name: "channel_4" });
const { advanceTime } = await start({ hasTimeControl: true });
triggerHotkey("control+k");
await nextTick();
await editSearchBar("#");
await afterNextRender(() => advanceTime(commandSetupRegistry.get("#").debounceDelay));
assert.containsOnce($, ".o_command_palette:contains('Recent')");
assert.containsN($(".o_command_category:contains('Recent')"), ".o_command", 3);
}
);
QUnit.test("Max 3 most recent channels in command palette of Discuss app with #", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({ name: "channel_1" });
pyEnv["discuss.channel"].create({ name: "channel_2" });
pyEnv["discuss.channel"].create({ name: "channel_3" });
pyEnv["discuss.channel"].create({ name: "channel_4" });
const { advanceTime } = await start({ hasTimeControl: true });
triggerHotkey("control+k");
await nextTick();
await editSearchBar("#");
advanceTime(commandSetupRegistry.get("#").debounceDelay);
await contains(".o_command_palette:contains('Recent')");
await contains(".o_command_category:contains('Recent') .o_command", 3);
});
@@ -1,14 +1,8 @@
/* @odoo-module */
import {
afterNextRender,
click,
insertText,
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
import { click, contains, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { triggerHotkey, patchWithCleanup } from "@web/../tests/helpers/utils";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
QUnit.module("crosstab");
@@ -21,9 +15,11 @@ QUnit.test("Channel subscription is renewed when channel is manually added", asy
assert.step("update-channels");
},
});
await openDiscuss();
openDiscuss();
await click("[title='Add or join a channel']");
await insertText(".o-discuss-ChannelSelector", "General");
await afterNextRender(() => triggerHotkey("Enter"));
await click(".o-discuss-ChannelSelector-suggestion:eq(0)");
await contains(".o-mail-DiscussSidebarChannel", 1);
await new Promise((resolve) => setTimeout(resolve)); // update of channels is debounced
assert.verifySteps(["update-channels"]);
});
@@ -1,14 +1,8 @@
/* @odoo-module */
import {
afterNextRender,
click,
insertText,
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
import { click, contains, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { editInput, triggerEvent, triggerHotkey } from "@web/../tests/helpers/utils";
import { triggerHotkey } from "@web/../tests/helpers/utils";
QUnit.module("discuss");
@@ -28,16 +22,13 @@ QUnit.test("can create a new channel [REQUIRE FOCUS]", async (assert) => {
}
},
});
await openDiscuss();
assert.containsNone($, ".o-mail-DiscussSidebarChannel");
openDiscuss();
await click(".o-mail-DiscussSidebar i[title='Add or join a channel']");
await afterNextRender(() =>
editInput(document.body, ".o-discuss-ChannelSelector input", "abc")
);
await contains(".o-mail-DiscussSidebarChannel", 0);
await insertText(".o-discuss-ChannelSelector input", "abc");
await click(".o-discuss-ChannelSelector-suggestion");
assert.containsOnce($, ".o-mail-DiscussSidebarChannel");
assert.containsNone($, ".o-mail-Discuss-content .o-mail-Message");
await contains(".o-mail-DiscussSidebarChannel");
await contains(".o-mail-Discuss-content .o-mail-Message", 0);
assert.verifySteps([
"/mail/init_messaging",
"/mail/load_message_failures",
@@ -50,35 +41,24 @@ QUnit.test("can create a new channel [REQUIRE FOCUS]", async (assert) => {
QUnit.test(
"do not close channel selector when creating chat conversation after selection",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Mario" });
pyEnv["res.users"].create({ partner_id: partnerId });
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone($, ".o-mail-DiscussSidebarChannel");
openDiscuss();
await click("i[title='Start a conversation']");
await afterNextRender(() =>
editInput(document.body, ".o-discuss-ChannelSelector input", "mario")
);
await insertText(".o-discuss-ChannelSelector input", "mario");
await click(".o-discuss-ChannelSelector-suggestion");
assert.containsOnce($, ".o-discuss-ChannelSelector span[title='Mario']");
assert.containsNone($, ".o-mail-DiscussSidebarChannel");
await triggerEvent(document.body, ".o-discuss-ChannelSelector input", "keydown", {
key: "Backspace",
});
assert.containsNone($, ".o-discuss-ChannelSelector span[title='Mario']");
await afterNextRender(() =>
editInput(document.body, ".o-discuss-ChannelSelector input", "mario")
);
await triggerEvent(document.body, ".o-discuss-ChannelSelector input", "keydown", {
key: "Enter",
});
assert.containsOnce($, ".o-discuss-ChannelSelector span[title='Mario']");
assert.containsNone($, ".o-mail-DiscussSidebarChannel");
await contains(".o-discuss-ChannelSelector span[title='Mario']");
await contains(".o-mail-DiscussSidebarChannel", 0);
triggerHotkey("Backspace");
await contains(".o-discuss-ChannelSelector span[title='Mario']", 0);
await insertText(".o-discuss-ChannelSelector input", "mario");
await contains(".o-discuss-ChannelSelector-suggestion");
triggerHotkey("Enter");
await contains(".o-discuss-ChannelSelector span[title='Mario']");
await contains(".o-mail-DiscussSidebarChannel", 0);
}
);
@@ -100,19 +80,15 @@ QUnit.test("can join a chat conversation", async (assert) => {
}
},
});
await openDiscuss();
assert.containsNone($, ".o-mail-DiscussSidebarChannel");
openDiscuss();
await click(".o-mail-DiscussSidebar i[title='Start a conversation']");
await afterNextRender(() =>
editInput(document.body, ".o-discuss-ChannelSelector input", "mario")
);
await contains(".o-mail-DiscussSidebarChannel", 0);
await insertText(".o-discuss-ChannelSelector input", "mario");
await click(".o-discuss-ChannelSelector-suggestion");
await triggerEvent(document.body, ".o-discuss-ChannelSelector input", "keydown", {
key: "Enter",
});
assert.containsOnce($, ".o-mail-DiscussSidebarChannel");
assert.containsNone($, ".o-mail-Message");
await contains(".o-discuss-ChannelSelector-suggestion", 0);
triggerHotkey("Enter");
await contains(".o-mail-DiscussSidebarChannel");
await contains(".o-mail-Message", 0);
assert.verifySteps([
"/mail/init_messaging",
"/mail/load_message_failures",
@@ -122,7 +98,7 @@ QUnit.test("can join a chat conversation", async (assert) => {
]);
});
QUnit.test("can create a group chat conversation", async (assert) => {
QUnit.test("can create a group chat conversation", async () => {
const pyEnv = await startServer();
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
{ name: "Mario" },
@@ -130,18 +106,18 @@ QUnit.test("can create a group chat conversation", async (assert) => {
]);
pyEnv["res.users"].create([{ partner_id: partnerId_1 }, { partner_id: partnerId_2 }]);
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone($, ".o-mail-DiscussSidebarChannel");
openDiscuss();
await click(".o-mail-DiscussSidebar i[title='Start a conversation']");
await contains(".o-mail-DiscussSidebarChannel", 0);
await insertText(".o-discuss-ChannelSelector input", "Mario");
await click(".o-discuss-ChannelSelector-suggestion");
await contains(".o-discuss-ChannelSelector-suggestion", 0);
await insertText(".o-discuss-ChannelSelector input", "Luigi");
await click(".o-discuss-ChannelSelector-suggestion");
await triggerEvent(document.body, ".o-discuss-ChannelSelector input", "keydown", {
key: "Enter",
});
assert.containsN($, ".o-mail-DiscussSidebarChannel", 1);
assert.containsNone($, ".o-mail-Message");
await contains(".o-discuss-ChannelSelector-suggestion", 0);
triggerHotkey("Enter");
await contains(".o-mail-DiscussSidebarChannel");
await contains(".o-mail-Message", 0);
});
QUnit.test("should create DM chat when adding self and another user", async (assert) => {
@@ -149,73 +125,75 @@ QUnit.test("should create DM chat when adding self and another user", async (ass
const partner_id = pyEnv["res.partner"].create([{ name: "Mario", im_status: "online" }]);
pyEnv["res.users"].create({ partner_id });
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone($, ".o-mail-DiscussSidebarChannel");
openDiscuss();
await click(".o-mail-DiscussSidebar i[title='Start a conversation']");
await contains(".o-mail-DiscussSidebarChannel", 0);
await insertText(".o-discuss-ChannelSelector input", "Mi"); // Mitchell Admin
await click(".o-discuss-ChannelSelector-suggestion");
await contains(".o-discuss-ChannelSelector-suggestion", 0);
await insertText(".o-discuss-ChannelSelector input", "Mario");
await click(".o-discuss-ChannelSelector-suggestion");
await triggerEvent(document.body, ".o-discuss-ChannelSelector input", "keydown", {
key: "Enter",
});
assert.strictEqual($(".o-mail-DiscussSidebarChannel:contains(Mario)").text(), "Mario");
await contains(".o-discuss-ChannelSelector-suggestion", 0);
triggerHotkey("Enter");
assert.strictEqual(
(await contains(".o-mail-DiscussSidebarChannel:contains(Mario)")).text(),
"Mario"
);
});
QUnit.test("chat search should display no result when no matches found", async (assert) => {
QUnit.test("chat search should display no result when no matches found", async () => {
const { openDiscuss } = await start();
await openDiscuss();
openDiscuss();
await click(".o-mail-DiscussSidebar i[title='Start a conversation']");
await insertText(".o-discuss-ChannelSelector", "Rainbow Panda");
assert.containsOnce($, ".o-discuss-ChannelSelector-suggestion:contains(No results found)");
await contains(".o-discuss-ChannelSelector-suggestion:contains(No results found)");
});
QUnit.test(
"chat search should not be visible when clicking outside of the field",
async (assert) => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Panda" });
pyEnv["res.users"].create({ partner_id: partnerId });
const { openDiscuss } = await start();
await openDiscuss();
assert.containsNone($, ".o-mail-DiscussSidebarChannel");
await click(".o-mail-DiscussSidebar i[title='Start a conversation']");
await insertText(".o-discuss-ChannelSelector", "Panda");
assert.containsOnce($, ".o-discuss-ChannelSelector-suggestion");
await click(".o-mail-DiscussSidebar");
assert.containsNone($, ".o-discuss-ChannelSelector-suggestion");
}
);
QUnit.test("chat search should not be visible when clicking outside of the field", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Panda" });
pyEnv["res.users"].create({ partner_id: partnerId });
const { openDiscuss } = await start();
openDiscuss();
await click(".o-mail-DiscussSidebar i[title='Start a conversation']");
await insertText(".o-discuss-ChannelSelector", "Panda");
await contains(".o-discuss-ChannelSelector-suggestion");
await click(".o-mail-DiscussSidebar");
await contains(".o-discuss-ChannelSelector-suggestion", 0);
});
QUnit.test("sidebar: add channel", async (assert) => {
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce(
$,
".o-mail-DiscussSidebarCategory-channel .o-mail-DiscussSidebarCategory-add"
);
openDiscuss();
await contains(".o-mail-DiscussSidebarCategory-channel .o-mail-DiscussSidebarCategory-add");
assert.hasAttrValue(
$(".o-mail-DiscussSidebarCategory-channel .o-mail-DiscussSidebarCategory-add")[0],
"title",
"Add or join a channel"
);
await click(".o-mail-DiscussSidebarCategory-channel .o-mail-DiscussSidebarCategory-add");
assert.containsOnce($, ".o-discuss-ChannelSelector");
assert.containsOnce($, ".o-discuss-ChannelSelector input[placeholder='Add or join a channel']");
await contains(".o-discuss-ChannelSelector");
await contains(".o-discuss-ChannelSelector input[placeholder='Add or join a channel']");
});
QUnit.test("Chat is added to discuss on other tab that the one that joined", async (assert) => {
QUnit.test("Chat is added to discuss on other tab that the one that joined", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Jerry Golay" });
pyEnv["res.users"].create({ partner_id: partnerId });
const tab1 = await start({ asTab: true });
const tab2 = await start({ asTab: true });
await tab1.openDiscuss();
await tab2.openDiscuss();
await tab1.click(".o-mail-DiscussSidebarCategory-chat .o-mail-DiscussSidebarCategory-add");
tab1.openDiscuss();
tab2.openDiscuss();
await click(".o-mail-DiscussSidebarCategory-chat .o-mail-DiscussSidebarCategory-add", {
target: tab1.target,
});
await tab1.insertText(".o-discuss-ChannelSelector input", "Jer");
await tab1.click(".o-discuss-ChannelSelector-suggestion");
await afterNextRender(() => triggerHotkey("Enter"));
assert.containsOnce(tab1.target, ".o-mail-DiscussSidebarChannel:contains(Jerry Golay)");
assert.containsOnce(tab2.target, ".o-mail-DiscussSidebarChannel:contains(Jerry Golay)");
await click(".o-discuss-ChannelSelector-suggestion", { target: tab1.target });
triggerHotkey("Enter");
await contains(".o-mail-DiscussSidebarChannel:contains(Jerry Golay)", 1, {
target: tab1.target,
});
await contains(".o-mail-DiscussSidebarChannel:contains(Jerry Golay)", 1, {
target: tab2.target,
});
});
@@ -1,62 +1,59 @@
/* @odoo-module */
import {
afterNextRender,
click,
insertText,
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
import { click, contains, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { patchUiSize } from "@mail/../tests/helpers/patch_ui_size";
import { triggerHotkey } from "@web/../tests/helpers/utils";
QUnit.module("messaging menu");
QUnit.test('"Start a conversation" item selection opens chat', async (assert) => {
QUnit.test('"Start a conversation" item selection opens chat', async () => {
patchUiSize({ height: 360, width: 640 });
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Gandalf" });
pyEnv["res.users"].create({ partner_id: partnerId });
const { openDiscuss } = await start();
await openDiscuss();
openDiscuss();
await click("button:contains(Chat)");
await click("button:contains(Start a conversation)");
await insertText("input[placeholder='Start a conversation']", "Gandalf");
await click(".o-discuss-ChannelSelector-suggestion");
await afterNextRender(() => triggerHotkey("Enter"));
assert.containsOnce($, ".o-mail-ChatWindow-name[title='Gandalf']");
await contains(".o-discuss-ChannelSelector-suggestion", 0);
triggerHotkey("Enter");
await contains(".o-mail-ChatWindow-name[title='Gandalf']");
});
QUnit.test('"New channel" item selection opens channel (existing)', async (assert) => {
QUnit.test('"New channel" item selection opens channel (existing)', async () => {
patchUiSize({ height: 360, width: 640 });
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({ name: "Gryffindors" });
const { openDiscuss } = await start();
await openDiscuss();
openDiscuss();
await click("button:contains(Channel)");
await click("button:contains(New Channel)");
await insertText("input[placeholder='Add or join a channel']", "Gryff");
await click(".o-discuss-ChannelSelector-suggestion");
assert.containsOnce($, ".o-mail-ChatWindow-name[title='Gryffindors']");
await click(".o-discuss-ChannelSelector-suggestion:eq(0)");
await contains(".o-discuss-ChannelSelector-suggestion", 0);
await contains(".o-mail-ChatWindow-name[title='Gryffindors']");
});
QUnit.test('"New channel" item selection opens channel (new)', async (assert) => {
QUnit.test('"New channel" item selection opens channel (new)', async () => {
patchUiSize({ height: 360, width: 640 });
const { openDiscuss } = await start();
await openDiscuss();
openDiscuss();
await click("button:contains(Channel)");
await click("button:contains(New Channel)");
await insertText("input[placeholder='Add or join a channel']", "slytherins");
await click(".o-discuss-ChannelSelector-suggestion");
assert.containsOnce($, ".o-mail-ChatWindow-name[title='slytherins']");
await contains(".o-discuss-ChannelSelector-suggestion", 0);
await contains(".o-mail-ChatWindow-name[title='slytherins']");
});
QUnit.test("new message [REQUIRE FOCUS]", async (assert) => {
QUnit.test("new message [REQUIRE FOCUS]", async () => {
await start();
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
await click('.o-mail-MessagingMenu button:contains("New Message")');
assert.containsOnce($, ".o-mail-ChatWindow");
assert.containsOnce($, ".o-mail-ChatWindow .o-discuss-ChannelSelector");
assert.containsOnce($, ".o-discuss-ChannelSelector input:focus");
await contains(".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow .o-discuss-ChannelSelector");
await contains(".o-discuss-ChannelSelector input:focus");
});
@@ -1,19 +1,11 @@
/* @odoo-module */
import { Command } from "@mail/../tests/helpers/command";
import {
click,
insertText,
nextAnimationFrame,
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
import { makeDeferred } from "@web/../tests/helpers/utils";
import { click, contains, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("discuss sidebar");
QUnit.test("sidebar find shows channels matching search term", async (assert) => {
QUnit.test("sidebar find shows channels matching search term", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
channel_member_ids: [],
@@ -21,29 +13,20 @@ QUnit.test("sidebar find shows channels matching search term", async (assert) =>
group_public_id: false,
name: "test",
});
const def = makeDeferred();
const { openDiscuss } = await start({
async mockRPC(route, args) {
if (args.method === "search_read") {
def.resolve();
}
},
});
await openDiscuss();
await click(".o-mail-DiscussSidebarCategory-add");
const { openDiscuss } = await start();
openDiscuss();
await click(".o-mail-DiscussSidebarCategory-add:eq(0)");
await insertText(".o-discuss-ChannelSelector input", "test");
await def;
await nextAnimationFrame(); // ensures search_read rpc is rendered.
// When searching for a single existing channel, the results list will have at least 2 lines:
// One for the existing channel itself
// One for creating a channel with the search term
assert.containsN($, ".o-mail-NavigableList-item", 2);
assert.containsN($, ".o-mail-NavigableList-item:contains(test)", 2);
await contains(".o-mail-NavigableList-item", 2);
await contains(".o-mail-NavigableList-item:contains(test)", 2);
});
QUnit.test(
"sidebar find shows channels matching search term even when user is member",
async (assert) => {
async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({
channel_member_ids: [Command.create({ partner_id: pyEnv.currentPartnerId })],
@@ -51,23 +34,14 @@ QUnit.test(
group_public_id: false,
name: "test",
});
const def = makeDeferred();
const { openDiscuss } = await start({
async mockRPC(route, args) {
if (args.method === "search_read") {
def.resolve();
}
},
});
await openDiscuss();
await click(".o-mail-DiscussSidebarCategory-add");
const { openDiscuss } = await start();
openDiscuss();
await click(".o-mail-DiscussSidebarCategory-add:eq(0)");
await insertText(".o-discuss-ChannelSelector input", "test");
await def;
await nextAnimationFrame(); // ensures search_read rpc is rendered.
// When searching for a single existing channel, the results list will have at least 2 lines:
// One for the existing channel itself
// One for creating a channel with the search term
assert.containsN($, ".o-mail-NavigableList-item", 2);
assert.containsN($, ".o-mail-NavigableList-item:contains(test)", 2);
await contains(".o-mail-NavigableList-item", 2);
await contains(".o-mail-NavigableList-item:contains(test)", 2);
}
);
@@ -1,12 +1,12 @@
/* @odoo-module */
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
QUnit.module("pinned messages");
QUnit.test("Pin message", async (assert) => {
QUnit.test("Pin message", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
pyEnv["mail.message"].create({
@@ -15,16 +15,15 @@ QUnit.test("Pin message", async (assert) => {
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-Discuss-header button[title='Pinned Messages']");
assert.containsOnce(
$,
await contains(
".o-discuss-PinnedMessagesPanel:contains(This channel doesn't have any pinned messages.)"
);
await click(".o-mail-Message [title='Expand']");
await click(".dropdown-item:contains(Pin)");
await click(".modal-footer button:contains(pin it)");
assert.containsOnce($, ".o-discuss-PinnedMessagesPanel .o-mail-Message:contains(Hello world)");
await contains(".o-discuss-PinnedMessagesPanel .o-mail-Message:contains(Hello world)");
});
QUnit.test("Unpin message", async (assert) => {
@@ -37,13 +36,13 @@ QUnit.test("Unpin message", async (assert) => {
pinned_at: "2023-03-30 11:27:11",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-Discuss-header button[title='Pinned Messages']");
assert.containsOnce($, ".o-discuss-PinnedMessagesPanel .o-mail-Message");
await contains(".o-discuss-PinnedMessagesPanel .o-mail-Message");
await click(".o-mail-Message [title='Expand']");
await click(".dropdown-item:contains(Unpin)");
await click(".modal-footer button:contains(Yes)");
assert.containsNone($, ".o-discuss-PinnedMessagesPanel .o-mail-Message");
await contains(".o-discuss-PinnedMessagesPanel .o-mail-Message", 0);
});
QUnit.test("Deleted messages are not pinned", async (assert) => {
@@ -57,13 +56,13 @@ QUnit.test("Deleted messages are not pinned", async (assert) => {
pinned_at: "2023-03-30 11:27:11",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-Discuss-header button[title='Pinned Messages']");
assert.containsOnce($, ".o-discuss-PinnedMessagesPanel .o-mail-Message");
await contains(".o-discuss-PinnedMessagesPanel .o-mail-Message");
await click(".o-mail-Message [title='Expand']");
await click(".dropdown-item:contains(Delete)");
await click("button:contains(Confirm)");
assert.containsNone($, ".o-discuss-PinnedMessagesPanel .o-mail-Message");
await contains(".o-discuss-PinnedMessagesPanel .o-mail-Message", 0);
});
QUnit.test("Open pinned panel from notification", async (assert) => {
@@ -75,13 +74,13 @@ QUnit.test("Open pinned panel from notification", async (assert) => {
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-Message:eq(0) [title='Expand']");
await click(".dropdown-item:contains(Pin)");
await click(".modal-footer button:contains(pin it)");
assert.containsNone($, ".o-discuss-PinnedMessagesPanel");
await contains(".o-discuss-PinnedMessagesPanel", 0);
await click(".o_mail_notification a:contains(See all pinned messages)");
assert.containsOnce($, ".o-discuss-PinnedMessagesPanel");
await contains(".o-discuss-PinnedMessagesPanel");
});
QUnit.test("Jump to message", async (assert) => {
@@ -108,7 +107,7 @@ QUnit.test("Jump to message", async (assert) => {
});
}
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-Discuss-header button[title='Pinned Messages']");
await click(".o-discuss-PinnedMessagesPanel button:contains(Jump)");
await nextTick();
@@ -138,11 +137,11 @@ QUnit.test("Jump to message from notification", async (assert) => {
});
}
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-Message:eq(0) [title='Expand']");
await click(".dropdown-item:contains(Pin)");
await click(".modal-footer button:contains(pin it)");
await click(".o_mail_notification a:contains(message)");
await click(".o_mail_notification a:contains(message):eq(0)");
await nextTick();
assert.isVisible($(".o-mail-Message:contains(Hello world!)"));
});
@@ -119,7 +119,7 @@ function patchAudio() {
};
}
QUnit.test("make voice message in chat", async (assert) => {
QUnit.test("make voice message in chat", async () => {
const file = await createFile({
content: Array(500).map(() => new Int8Array()), // some non-empty content
contentType: "audio/mp3",
@@ -158,26 +158,23 @@ QUnit.test("make voice message in chat", async (assert) => {
channel_type: "chat",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, "button[title='Voice Message']");
openDiscuss(channelId);
await click("button[title='Voice Message']");
assert.containsOnce($, ".o-mail-VoiceRecorder");
assert.containsOnce($, ".o-mail-VoiceRecorder:contains(00 : 00)");
await contains(".o-mail-VoiceRecorder:contains(00 : 00)");
await nextAnimationFrame();
// simulate 10 sec elapsed
patchDate(2023, 6, 31, 13, 0, 11); // +1 so exactly 10 sec elapsed
// simulate some microphone data
audioProcessor.process([[new Float32Array(128)]]);
await contains(".o-mail-VoiceRecorder:contains(00 : 10)");
assert.containsOnce($, "button[title='Stop Recording']");
await click("button[title='Stop Recording']");
assert.containsOnce($, ".o-mail-VoicePlayer");
await contains(".o-mail-VoicePlayer");
// wait for audio stream decode + drawing of waves
await voicePlayerDrawing;
await nextAnimationFrame();
assert.containsOnce($, ".o-mail-VoicePlayer button[title='Play']");
assert.containsN($, ".o-mail-VoicePlayer canvas", 2); // 1 for global waveforms, 1 for played waveforms
assert.containsOnce($, ".o-mail-VoicePlayer:contains(00 : 04)"); // duration of call_02_in_.mp3
assert.containsOnce($, ".o-mail-VoiceRecorder button[title='Voice Message']");
assert.ok($(".o-mail-VoiceRecorder button[title='Voice Message']")[0].disabled);
await contains(".o-mail-VoicePlayer button[title='Play']");
await contains(".o-mail-VoicePlayer canvas", 2); // 1 for global waveforms, 1 for played waveforms
await contains(".o-mail-VoicePlayer:contains(00 : 04)"); // duration of call_02_in_.mp3
await contains(".o-mail-VoiceRecorder button[title='Voice Message']:disabled");
cleanUp();
});
File diff suppressed because it is too large Load Diff
@@ -4,13 +4,13 @@ import { UPDATE_BUS_PRESENCE_DELAY } from "@bus/im_status_service";
import { createLocalId } from "@mail/utils/common/misc";
import { Command } from "@mail/../tests/helpers/command";
import { afterNextRender, click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { nextTick } from "@web/../tests/helpers/utils";
QUnit.module("im status");
QUnit.test("initially online", async (assert) => {
QUnit.test("initially online", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo", im_status: "online" });
const channelId = pyEnv["discuss.channel"].create({
@@ -21,11 +21,11 @@ QUnit.test("initially online", async (assert) => {
channel_type: "chat",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-ImStatus i[title='Online']");
openDiscuss(channelId);
await contains(".o-mail-ImStatus i[title='Online']");
});
QUnit.test("initially offline", async (assert) => {
QUnit.test("initially offline", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo", im_status: "offline" });
const channelId = pyEnv["discuss.channel"].create({
@@ -36,11 +36,11 @@ QUnit.test("initially offline", async (assert) => {
channel_type: "chat",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-ImStatus i[title='Offline']");
openDiscuss(channelId);
await contains(".o-mail-ImStatus i[title='Offline']");
});
QUnit.test("initially away", async (assert) => {
QUnit.test("initially away", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo", im_status: "away" });
const channelId = pyEnv["discuss.channel"].create({
@@ -51,11 +51,11 @@ QUnit.test("initially away", async (assert) => {
channel_type: "chat",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-ImStatus i[title='Idle']");
openDiscuss(channelId);
await contains(".o-mail-ImStatus i[title='Idle']");
});
QUnit.test("change icon on change partner im_status", async (assert) => {
QUnit.test("change icon on change partner im_status", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo", im_status: "online" });
const channelId = pyEnv["discuss.channel"].create({
@@ -66,20 +66,20 @@ QUnit.test("change icon on change partner im_status", async (assert) => {
channel_type: "chat",
});
const { advanceTime, openDiscuss } = await start({ hasTimeControl: true });
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-ImStatus i[title='Online']");
openDiscuss(channelId);
await contains(".o-mail-ImStatus i[title='Online']");
pyEnv["res.partner"].write([partnerId], { im_status: "offline" });
await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY));
assert.containsOnce($, ".o-mail-ImStatus i[title='Offline']");
advanceTime(UPDATE_BUS_PRESENCE_DELAY);
await contains(".o-mail-ImStatus i[title='Offline']");
pyEnv["res.partner"].write([partnerId], { im_status: "away" });
await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY));
assert.containsOnce($, ".o-mail-ImStatus i[title='Idle']");
advanceTime(UPDATE_BUS_PRESENCE_DELAY);
await contains(".o-mail-ImStatus i[title='Idle']");
pyEnv["res.partner"].write([partnerId], { im_status: "online" });
await afterNextRender(() => advanceTime(UPDATE_BUS_PRESENCE_DELAY));
assert.containsOnce($, ".o-mail-ImStatus i[title='Online']");
advanceTime(UPDATE_BUS_PRESENCE_DELAY);
await contains(".o-mail-ImStatus i[title='Online']");
});
QUnit.test("Can handle im_status of unknown partner", async (assert) => {
@@ -98,7 +98,7 @@ QUnit.test("Can handle im_status of unknown partner", async (assert) => {
assert.ok(persona.im_status === "online");
});
QUnit.test("show im status in messaging menu preview of chat", async (assert) => {
QUnit.test("show im status in messaging menu preview of chat", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo", im_status: "online" });
pyEnv["discuss.channel"].create({
@@ -110,8 +110,5 @@ QUnit.test("show im status in messaging menu preview of chat", async (assert) =>
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce(
$,
".o-mail-NotificationItem:contains(Demo) i[aria-label='User is online']"
);
await contains(".o-mail-NotificationItem:contains(Demo) i[aria-label='User is online']");
});
@@ -3,12 +3,18 @@
import {
afterNextRender,
click,
contains,
insertText,
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
import { patchWithCleanup, triggerHotkey, mockTimeout } from "@web/../tests/helpers/utils";
import {
patchWithCleanup,
triggerHotkey,
mockTimeout,
makeDeferred,
} from "@web/../tests/helpers/utils";
QUnit.module("discuss inbox");
@@ -29,13 +35,13 @@ QUnit.test("reply: discard on reply button toggle", async (assert) => {
res_partner_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce($, ".o-mail-Message");
openDiscuss();
await contains(".o-mail-Message");
await click("[title='Reply']");
assert.containsOnce($, ".o-mail-Composer");
await contains(".o-mail-Composer");
await click("[title='Reply']");
assert.containsNone($, ".o-mail-Composer");
await contains(".o-mail-Composer", 0);
});
QUnit.test("reply: discard on pressing escape", async (assert) => {
@@ -58,29 +64,29 @@ QUnit.test("reply: discard on pressing escape", async (assert) => {
res_partner_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce($, ".o-mail-Message");
openDiscuss();
await contains(".o-mail-Message");
await click(".o-mail-Message [title='Reply']");
assert.containsOnce($, ".o-mail-Composer");
await contains(".o-mail-Composer");
// Escape on emoji picker does not stop replying
await click(".o-mail-Composer button[aria-label='Emojis']");
assert.containsOnce($, ".o-EmojiPicker");
await afterNextRender(() => triggerHotkey("Escape"));
assert.containsNone($, ".o-EmojiPicker");
assert.containsOnce($, ".o-mail-Composer");
await contains(".o-EmojiPicker");
triggerHotkey("Escape");
await contains(".o-EmojiPicker", 0);
await contains(".o-mail-Composer");
// Escape on suggestion prompt does not stop replying
await insertText(".o-mail-Composer-input", "@");
assert.containsOnce($, ".o-mail-Composer-suggestionList .o-open");
await afterNextRender(() => triggerHotkey("Escape"));
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
assert.containsOnce($, ".o-mail-Composer");
await contains(".o-mail-Composer-suggestionList .o-open");
triggerHotkey("Escape");
await contains(".o-mail-Composer-suggestionList .o-open", 0);
await contains(".o-mail-Composer");
click(".o-mail-Composer-input").catch(() => {});
await afterNextRender(() => triggerHotkey("Escape"));
assert.containsNone($, ".o-mail-Composer");
triggerHotkey("Escape");
await contains(".o-mail-Composer", 0);
});
QUnit.test(
@@ -111,13 +117,13 @@ QUnit.test(
}
},
});
await openDiscuss();
assert.containsOnce($, ".o-mail-Message");
openDiscuss();
await contains(".o-mail-Message");
await click("[title='Reply']");
assert.containsOnce($, ".o-mail-Composer [placeholder='Log an internal note...']");
await contains(".o-mail-Composer [placeholder='Log an internal note…']");
await insertText(".o-mail-Composer-input", "Test");
await click(".o-mail-Composer-send");
await click(".o-mail-Composer-send:not(:disabled):contains(Log)");
await contains(".o-mail-Composer", 0);
assert.verifySteps(["/mail/message/post"]);
}
);
@@ -150,19 +156,18 @@ QUnit.test(
}
},
});
await openDiscuss();
assert.containsOnce($, ".o-mail-Message");
openDiscuss();
await contains(".o-mail-Message");
await click("[title='Reply']");
assert.strictEqual($(".o-mail-Composer-send").text().trim(), "Send");
await contains(".o-mail-Composer [placeholder='Send a message to followers…']");
await insertText(".o-mail-Composer-input", "Test");
await click(".o-mail-Composer-send");
await click(".o-mail-Composer-send:not(:disabled):contains(Send)");
await contains(".o-mail-Composer-send", 0);
assert.verifySteps(["/mail/message/post"]);
}
);
QUnit.test("show subject of message in Inbox", async (assert) => {
QUnit.test("show subject of message in Inbox", async () => {
const pyEnv = await startServer();
const messageId = pyEnv["mail.message"].create({
body: "not empty",
@@ -178,12 +183,12 @@ QUnit.test("show subject of message in Inbox", async (assert) => {
res_partner_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message:contains(Subject: Salutations, voyageur)");
openDiscuss();
await contains(".o-mail-Message");
await contains(".o-mail-Message:contains(Subject: Salutations, voyageur)");
});
QUnit.test("show subject of message in history", async (assert) => {
QUnit.test("show subject of message in history", async () => {
const pyEnv = await startServer();
const messageId = pyEnv["mail.message"].create({
body: "not empty",
@@ -199,38 +204,36 @@ QUnit.test("show subject of message in history", async (assert) => {
res_partner_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss("mail.box_history");
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message:contains(Subject: Salutations, voyageur)");
openDiscuss("mail.box_history");
await contains(".o-mail-Message");
await contains(".o-mail-Message:contains(Subject: Salutations, voyageur)");
});
QUnit.test("subject should not be shown when subject is the same as the thread name", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "Salutations, voyageur" });
const messageId = pyEnv["mail.message"].create({
body: "not empty",
model: "discuss.channel",
res_id: channelId,
needaction: true,
subject: "Salutations, voyageur",
});
pyEnv["mail.notification"].create({
mail_message_id: messageId,
notification_status: "sent",
notification_type: "inbox",
res_partner_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
openDiscuss("mail.box_inbox");
await contains(".o-mail-Message-content");
await contains(".o-mail-Message-content:contains(Salutations, voyageur)", 0);
});
QUnit.test(
"subject should not be shown when subject is the same as the thread name",
async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "Salutations, voyageur" });
const messageId = pyEnv["mail.message"].create({
body: "not empty",
model: "discuss.channel",
res_id: channelId,
needaction: true,
subject: "Salutations, voyageur",
});
pyEnv["mail.notification"].create({
mail_message_id: messageId,
notification_status: "sent",
notification_type: "inbox",
res_partner_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss("mail.box_inbox");
assert.containsNone($, ".o-mail-Message-content:contains(Salutations, voyageur)");
}
);
QUnit.test(
"subject should not be shown when subject is the same as the thread name and both have the same prefix",
async (assert) => {
async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "Re: Salutations, voyageur" });
const messageId = pyEnv["mail.message"].create({
@@ -247,14 +250,15 @@ QUnit.test(
res_partner_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss("mail.box_inbox");
assert.containsNone($, ".o-mail-Message-content:contains(Salutations, voyageur)");
openDiscuss("mail.box_inbox");
await contains(".o-mail-Message-content");
await contains(".o-mail-Message-content:contains(Salutations, voyageur)", 0);
}
);
QUnit.test(
'subject should not be shown when subject differs from thread name only by the "Re:" prefix',
async (assert) => {
async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "Salutations, voyageur" });
const messageId = pyEnv["mail.message"].create({
@@ -271,14 +275,15 @@ QUnit.test(
res_partner_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss("mail.box_inbox");
assert.containsNone($, ".o-mail-Message-content:contains(Salutations, voyageur)");
openDiscuss("mail.box_inbox");
await contains(".o-mail-Message-content");
await contains(".o-mail-Message-content:contains(Salutations, voyageur)", 0);
}
);
QUnit.test(
'subject should not be shown when subject differs from thread name only by the "Fw:" and "Re:" prefix',
async (assert) => {
async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "Salutations, voyageur" });
const messageId = pyEnv["mail.message"].create({
@@ -295,14 +300,15 @@ QUnit.test(
res_partner_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss("mail.box_inbox");
assert.containsNone($, ".o-mail-Message-contente:contains(Salutations, voyageur)");
openDiscuss("mail.box_inbox");
await contains(".o-mail-Message-content");
await contains(".o-mail-Message-content:contains(Salutations, voyageur)", 0);
}
);
QUnit.test(
"subject should be shown when the thread name has an extra prefix compared to subject",
async (assert) => {
async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "Re: Salutations, voyageur" });
const messageId = pyEnv["mail.message"].create({
@@ -319,14 +325,15 @@ QUnit.test(
res_partner_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss("mail.box_inbox");
assert.containsNone($, ".o-mail-Message-content:contains(Salutations, voyageur)");
openDiscuss("mail.box_inbox");
await contains(".o-mail-Message-content");
await contains(".o-mail-Message-content:contains(Salutations, voyageur)", 0);
}
);
QUnit.test(
'subject should not be shown when subject differs from thread name only by the "fw:" prefix and both contain another common prefix',
async (assert) => {
async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "Re: Salutations, voyageur" });
const messageId = pyEnv["mail.message"].create({
@@ -343,14 +350,15 @@ QUnit.test(
res_partner_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss("mail.box_inbox");
assert.containsNone($, ".o-mail-Message-content:contains(Salutations, voyageur)");
openDiscuss("mail.box_inbox");
await contains(".o-mail-Message-content");
await contains(".o-mail-Message-content:contains(Salutations, voyageur)", 0);
}
);
QUnit.test(
'subject should not be shown when subject differs from thread name only by the "Re: Re:" prefix',
async (assert) => {
async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "Salutations, voyageur" });
const messageId = pyEnv["mail.message"].create({
@@ -367,8 +375,9 @@ QUnit.test(
res_partner_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss("mail.box_inbox");
assert.containsNone($, ".o-mail-Message-content:contains(Salutations, voyageur)");
openDiscuss("mail.box_inbox");
await contains(".o-mail-Message-content");
await contains(".o-mail-Message-content:contains(Salutations, voyageur)", 0);
}
);
@@ -402,16 +411,16 @@ QUnit.test("inbox: mark all messages as read", async (assert) => {
},
]);
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce($, "button:contains(Inbox) .badge:contains(2)");
assert.containsOnce($, ".o-mail-DiscussSidebarChannel:contains(General) .badge:contains(2)");
assert.containsN($, ".o-mail-Discuss-content .o-mail-Message", 2);
openDiscuss();
await contains("button:contains(Inbox) .badge:contains(2)");
await contains(".o-mail-DiscussSidebarChannel:contains(General) .badge:contains(2)");
await contains(".o-mail-Discuss-content .o-mail-Message", 2);
assert.notOk($("button:contains(Mark all read)")[0].disabled);
await click(".o-mail-Discuss-header button:contains(Mark all read)");
assert.containsNone($, "button:contains(Inbox) .badge");
assert.containsNone($, ".o-mail-DiscussSidebarChannel:contains(General) .badge");
assert.containsNone($, ".o-mail-Message");
await contains("button:contains(Inbox) .badge", 0);
await contains(".o-mail-DiscussSidebarChannel:contains(General) .badge", 0);
await contains(".o-mail-Message", 0);
assert.ok($("button:contains(Mark all read)")[0].disabled);
});
@@ -434,7 +443,8 @@ QUnit.test(
res_partner_id: pyEnv.currentPartnerId,
});
const { env, openDiscuss } = await start();
await openDiscuss();
openDiscuss();
const def = makeDeferred();
patchWithCleanup(env.services.action, {
doAction(action) {
// Callback of doing an action (action manager).
@@ -445,18 +455,18 @@ QUnit.test(
assert.deepEqual(action.views, [[false, "form"]]);
assert.strictEqual(action.res_model, "res.fake");
assert.strictEqual(action.res_id, fakeId);
def.resolve();
return Promise.resolve();
},
});
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message-header a:contains(Some record)");
click(".o-mail-Message-header a:contains(Some record)").catch(() => {});
await contains(".o-mail-Message");
await click(".o-mail-Message-header a:contains(Some record)");
await def;
assert.verifySteps(["do-action"]);
}
);
QUnit.test("inbox messages are never squashed", async (assert) => {
QUnit.test("inbox messages are never squashed", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const channelId = pyEnv["discuss.channel"].create({ name: "test" });
@@ -497,15 +507,15 @@ QUnit.test("inbox messages are never squashed", async (assert) => {
},
]);
const { openDiscuss } = await start();
await openDiscuss();
assert.containsN($, ".o-mail-Message", 2);
assert.doesNotHaveClass($(".o-mail-Message:contains(body1)"), "o-squashed");
assert.doesNotHaveClass($(".o-mail-Message:contains(body2)"), "o-squashed");
openDiscuss();
await contains(".o-mail-Message", 2);
await contains(".o-mail-Message:contains(body1):not(.o-squashed)");
await contains(".o-mail-Message:contains(body2):not(.o-squashed)");
await click(".o-mail-DiscussSidebarChannel:contains(test)");
assert.hasClass($(".o-mail-Message:contains(body2)"), "o-squashed");
await contains(".o-mail-Message:contains(body2).o-squashed");
});
QUnit.test("reply: stop replying button click", async (assert) => {
QUnit.test("reply: stop replying button click", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const messageId = pyEnv["mail.message"].create({
@@ -522,18 +532,18 @@ QUnit.test("reply: stop replying button click", async (assert) => {
res_partner_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce($, ".o-mail-Message");
openDiscuss();
await contains(".o-mail-Message");
await click("[title='Reply']");
assert.containsOnce($, ".o-mail-Composer");
assert.containsOnce($, "i[title='Stop replying']");
await contains(".o-mail-Composer");
await contains("i[title='Stop replying']");
await click("i[title='Stop replying']");
assert.containsNone($, ".o-mail-Composer");
await contains(".o-mail-Composer", 0);
});
QUnit.test("error notifications should not be shown in Inbox", async (assert) => {
QUnit.test("error notifications should not be shown in Inbox", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo User" });
const messageId = pyEnv["mail.message"].create({
@@ -550,17 +560,16 @@ QUnit.test("error notifications should not be shown in Inbox", async (assert) =>
res_partner_id: pyEnv.currentPartnerId,
});
const { openDiscuss } = await start();
await openDiscuss();
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message-header:contains(on Demo User)");
assert.containsOnce(
$,
openDiscuss();
await contains(".o-mail-Message");
await contains(".o-mail-Message-header:contains(on Demo User)");
await contains(
`.o-mail-Message-header a:contains(Demo User)[href*='/web#model=res.partner&id=${partnerId}']`
);
assert.containsNone($, ".o-mail-Message-notification");
await contains(".o-mail-Message-notification", 0);
});
QUnit.test("emptying inbox displays rainbow man in inbox", async (assert) => {
QUnit.test("emptying inbox displays rainbow man in inbox", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const messageId1 = pyEnv["mail.message"].create([
@@ -579,12 +588,12 @@ QUnit.test("emptying inbox displays rainbow man in inbox", async (assert) => {
},
]);
const { openDiscuss } = await start();
await openDiscuss();
openDiscuss();
await click("button:contains(Mark all read)");
assert.containsOnce($, ".o_reward_rainbow");
await contains(".o_reward_rainbow");
});
QUnit.test("emptying inbox doesn't display rainbow man in another thread", async (assert) => {
QUnit.test("emptying inbox doesn't display rainbow man in another thread", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const partnerId = pyEnv["res.partner"].create({});
@@ -603,13 +612,13 @@ QUnit.test("emptying inbox doesn't display rainbow man in another thread", async
},
]);
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, "button:contains(Inbox) .badge:contains(1)");
openDiscuss(channelId);
await contains("button:contains(Inbox) .badge:contains(1)");
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.message/mark_as_read", {
message_ids: [messageId],
needaction_inbox_counter: 0,
});
await afterNextRender(() => mockTimeout().execRegisteredTimeouts);
assert.containsNone($, "button:contains(Inbox) .badge");
assert.containsNone($, ".o_reward_rainbow");
await contains("button:contains(Inbox) .badge", 0);
await contains(".o_reward_rainbow", 0);
});
@@ -2,8 +2,8 @@
import { PRESENT_THRESHOLD } from "@mail/core/common/thread";
import {
afterNextRender,
click,
contains,
nextAnimationFrame,
start,
startServer,
@@ -36,16 +36,21 @@ QUnit.test("Basic jump to present when scrolling to outdated messages", async (a
});
}
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
assert.ok(
$(".o-mail-Thread")[0].scrollHeight > PRESENT_THRESHOLD,
(await contains(".o-mail-Thread"))[0].scrollHeight > PRESENT_THRESHOLD,
"should have enough scroll height to trigger jump to present"
);
await afterNextRender(() => $(".o-mail-Thread").scrollTop(0));
assert.containsOnce($, ".o-mail-Thread:contains(You're viewing older messagesJump to Present)");
/**
* The nextAnimationFrame is necessary because otherwise useAutoScroll would
* set the scroll to bottom after the manually set value from this test.
*/
await nextAnimationFrame();
$(".o-mail-Thread").scrollTop(0);
await contains(".o-mail-Thread:contains(You're viewing older messagesJump to Present)");
await click(".o-mail-Thread-jumpPresent");
await nextTick();
assert.containsNone($, ".o-mail-Thread:contains(You're viewing older messagesJump to Present)");
await contains(".o-mail-Thread:contains(You're viewing older messagesJump to Present)", 0);
assert.ok(
$(".o-mail-Thread")[0].scrollHeight - $(".o-mail-Thread")[0].scrollTop <=
$(".o-mail-Thread")[0].clientHeight
@@ -88,18 +93,18 @@ QUnit.test("Jump to old reply should prompt jump to presence", async (assert) =>
parent_id: oldestMessageId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-MessageInReply .cursor-pointer");
assert.isVisible($(".o-mail-Message:contains(Hello world!):eq(0)")[0]);
await nextAnimationFrame(); // wait for scroll into view
assert.isVisible((await contains(".o-mail-Message:contains(Hello world!):eq(0)"))[0]);
assert.strictEqual(
$(".o-mail-Message-body:contains(Hello world!):eq(0)").text(),
"Hello world!",
"should correctly execute HTML tags in parent message when using 'load around' feature"
);
assert.containsOnce($, ".o-mail-Thread:contains(You're viewing older messagesJump to Present)");
await contains(".o-mail-Thread:contains(You're viewing older messagesJump to Present)");
await click(".o-mail-Thread-jumpPresent");
await nextAnimationFrame();
assert.containsNone($, ".o-mail-Thread:contains(You're viewing older messagesJump to Present)");
await contains(".o-mail-Thread:contains(You're viewing older messagesJump to Present)", 0);
assert.ok(
$(".o-mail-Thread")[0].scrollHeight - $(".o-mail-Thread")[0].scrollTop <=
$(".o-mail-Thread")[0].clientHeight
File diff suppressed because it is too large Load Diff
+53 -73
View File
@@ -1,107 +1,93 @@
/* @odoo-module */
import { EMOJI_PER_ROW } from "@web/core/emoji_picker/emoji_picker";
import {
afterNextRender,
click,
insertText,
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
import { click, contains, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { triggerHotkey } from "@web/../tests/helpers/utils";
QUnit.module("emoji");
QUnit.test("search emoji from keywords", async (assert) => {
QUnit.test("search emoji from keywords", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("button[aria-label='Emojis']");
await insertText("input[placeholder='Search for an emoji']", "mexican");
assert.containsOnce($, ".o-Emoji:contains(🌮)");
await contains(".o-Emoji:contains(🌮)");
});
QUnit.test("search emoji from keywords should be case insensitive", async (assert) => {
QUnit.test("search emoji from keywords should be case insensitive", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("button[aria-label='Emojis']");
await insertText("input[placeholder='Search for an emoji']", "ok");
assert.containsOnce($, ".o-Emoji:contains(🆗)"); // all search terms are uppercase OK
await contains(".o-Emoji:contains(🆗)"); // all search terms are uppercase OK
});
QUnit.test("search emoji from keywords with special regex character", async (assert) => {
QUnit.test("search emoji from keywords with special regex character", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("button[aria-label='Emojis']");
await insertText("input[placeholder='Search for an emoji']", "(blood");
assert.containsOnce($, ".o-Emoji:contains(🆎)");
await contains(".o-Emoji:contains(🆎)");
});
QUnit.test("Press Escape in emoji picker closes the emoji picker", async (assert) => {
QUnit.test("Press Escape in emoji picker closes the emoji picker", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("button[aria-label='Emojis']");
await afterNextRender(() => triggerHotkey("Escape"));
assert.containsNone($, ".o-EmojiPicker");
triggerHotkey("Escape");
await contains(".o-EmojiPicker", 0);
});
QUnit.test("Basic keyboard navigation", async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("button[aria-label='Emojis']");
assert.containsOnce($, ".o-EmojiPicker-content .o-Emoji[data-index=0].bg-200"); // bg-200 means active
await afterNextRender(() => triggerHotkey("ArrowRight"));
assert.containsOnce($, ".o-EmojiPicker-content .o-Emoji[data-index=1].bg-200");
await afterNextRender(() => triggerHotkey("ArrowDown"));
assert.containsOnce(
$,
`.o-EmojiPicker-content .o-Emoji[data-index=${EMOJI_PER_ROW + 1}].bg-200`
);
await afterNextRender(() => triggerHotkey("ArrowLeft"));
assert.containsOnce(
$,
`.o-EmojiPicker-content .o-Emoji[data-index=${EMOJI_PER_ROW}].bg-200`
);
await afterNextRender(() => triggerHotkey("ArrowUp"));
assert.containsOnce($, ".o-EmojiPicker-content .o-Emoji[data-index=0].bg-200");
const codepoints = $(".o-EmojiPicker-content .o-Emoji[data-index=0].bg-200").data(
"codepoints"
);
await afterNextRender(() => triggerHotkey("Enter"));
await contains(".o-EmojiPicker-content .o-Emoji[data-index=0].bg-200"); // bg-200 means active
triggerHotkey("ArrowRight");
await contains(".o-EmojiPicker-content .o-Emoji[data-index=1].bg-200");
triggerHotkey("ArrowDown");
await contains(`.o-EmojiPicker-content .o-Emoji[data-index=${EMOJI_PER_ROW + 1}].bg-200`);
triggerHotkey("ArrowLeft");
await contains(`.o-EmojiPicker-content .o-Emoji[data-index=${EMOJI_PER_ROW}].bg-200`);
triggerHotkey("ArrowUp");
await contains(".o-EmojiPicker-content .o-Emoji[data-index=0].bg-200");
const codepoints = $(".o-EmojiPicker-content .o-Emoji[data-index=0].bg-200").data("codepoints");
triggerHotkey("Enter");
await contains(".o-EmojiPicker", 0);
assert.strictEqual($(".o-mail-Composer-input").val(), codepoints);
});
QUnit.test("recent category (basic)", async (assert) => {
QUnit.test("recent category (basic)", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("button[aria-label='Emojis']");
assert.containsNone($, ".o-EmojiPicker-navbar [title='Frequently used']");
await contains(".o-EmojiPicker-navbar [title='Frequently used']", 0);
await click(".o-EmojiPicker-content .o-Emoji:contains(😀)");
await click("button[aria-label='Emojis']");
assert.containsOnce($, ".o-EmojiPicker-navbar [title='Frequently used']");
assert.containsOnce(
$,
await contains(".o-EmojiPicker-navbar [title='Frequently used']");
await contains(
"span:contains(Frequently used) ~ .o-Emoji:contains(😀) ~ span:contains(Smileys & Emotion)"
);
});
QUnit.test("emoji usage amount orders frequent emojis", async (assert) => {
QUnit.test("emoji usage amount orders frequent emojis", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("button[aria-label='Emojis']");
await click(".o-EmojiPicker-content .o-Emoji:contains(😀)");
await click("button[aria-label='Emojis']");
@@ -109,56 +95,51 @@ QUnit.test("emoji usage amount orders frequent emojis", async (assert) => {
await click("button[aria-label='Emojis']");
await click(".o-EmojiPicker-content .o-Emoji:contains(👽)");
await click("button[aria-label='Emojis']");
assert.containsOnce(
$,
await contains(
"span:contains(Frequently used) ~ .o-Emoji:contains(😀) ~ span:contains(Smileys & Emotion)"
);
assert.containsOnce(
$,
await contains(
"span:contains(Frequently used) ~ .o-Emoji:contains(👽) ~ span:contains(Smileys & Emotion)"
);
assert.containsOnce(
$,
await contains(
"span:contains(Frequently used) ~ .o-Emoji:contains(👽) ~ .o-Emoji:contains(😀) ~ span:contains(Smileys & Emotion)"
);
});
QUnit.test("posting :wink: in message should impact recent", async (assert) => {
QUnit.test("posting :wink: in message should impact recent", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", ":wink:");
await click(".o-mail-Composer button[aria-label='Send']");
await click(".o-mail-Composer-send:not(:disabled)");
await click("button[aria-label='Emojis']");
assert.containsOnce(
$,
await contains(
"span:contains(Frequently used) ~ .o-Emoji:contains(😉) ~ span:contains(Smileys & Emotion)"
);
});
QUnit.test("posting :snowman: in message should impact recent", async (assert) => {
QUnit.test("posting :snowman: in message should impact recent", async () => {
// the snowman emoji is composed of two codepoints, making it a corner case
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", ":snowman:");
await click(".o-mail-Composer button[aria-label='Send']");
await click(".o-mail-Composer-send:not(:disabled)");
await click("button[aria-label='Emojis']");
assert.containsOnce(
$,
await contains(
"span:contains(Frequently used) ~ .o-Emoji:contains(☃️) ~ span:contains(Smileys & Emotion)"
);
});
QUnit.test("first category should be highlight by default", async (assert) => {
QUnit.test("first category should be highlighted by default", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("button[aria-label='Emojis']");
assert.containsOnce($, ".o-EmojiPicker-navbar .o-Emoji:eq(0).bg-300");
await contains(".o-EmojiPicker-navbar .o-Emoji:eq(0).bg-300");
});
QUnit.test(
@@ -167,14 +148,13 @@ QUnit.test(
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("button[aria-label='Emojis']");
await afterNextRender(() =>
$(".o-EmojiPicker-content .o-Emoji:contains(👺)")[0].dispatchEvent(
new MouseEvent("click", { shiftKey: true })
)
(await contains(".o-EmojiPicker-content .o-Emoji:contains(👺)"))[0].dispatchEvent(
new MouseEvent("click", { shiftKey: true })
);
assert.containsOnce($, ".o-EmojiPicker");
await contains(".o-EmojiPicker-navbar [title='Frequently used']");
await contains(".o-EmojiPicker");
assert.strictEqual($(".o-mail-Composer-input").val(), "👺");
}
);
@@ -82,24 +82,24 @@ const rpc = {
QUnit.module("GIF picker");
QUnit.test("composer should display a GIF button", async (assert) => {
QUnit.test("composer should display a GIF button", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, "button[aria-label='GIFs']");
openDiscuss(channelId);
await contains("button[aria-label='GIFs']");
});
QUnit.test("Composer GIF button should open the GIF picker", async (assert) => {
QUnit.test("Composer GIF button should open the GIF picker", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click("button[aria-label='GIFs']");
assert.containsOnce($, ".o-discuss-GifPicker");
await contains(".o-discuss-GifPicker");
});
QUnit.test("Searching for a GIF", async (assert) => {
QUnit.test("Searching for a GIF", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start({
@@ -110,7 +110,7 @@ QUnit.test("Searching for a GIF", async (assert) => {
},
});
openDiscuss(channelId);
(await contains("button[aria-label='GIFs']")).click();
await click("button[aria-label='GIFs']");
insertText((await contains("input[placeholder='Search for a gif']"))[0], "search");
await contains("i[aria-label='back']");
await contains(".o-discuss-Gif", 2);
@@ -130,8 +130,8 @@ QUnit.test("Open a GIF category trigger the search for the category", async (ass
},
});
openDiscuss(channelId);
(await contains("button[aria-label='GIFs']")).click();
(await contains("img[data-src='https://media.tenor.com/6uIlQAHIkNoAAAAM/cry.gif']")).click();
await click("button[aria-label='GIFs']");
await click("img[data-src='https://media.tenor.com/6uIlQAHIkNoAAAAM/cry.gif']");
await contains(".o-discuss-Gif", 2);
assert.strictEqual(
document.querySelector("input[placeholder='Search for a gif']").value,
@@ -139,7 +139,7 @@ QUnit.test("Open a GIF category trigger the search for the category", async (ass
);
});
QUnit.test("Reopen GIF category list when going back", async (assert) => {
QUnit.test("Reopen GIF category list when going back", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start({
@@ -152,14 +152,14 @@ QUnit.test("Reopen GIF category list when going back", async (assert) => {
}
},
});
await openDiscuss(channelId);
openDiscuss(channelId);
await click("button[aria-label='GIFs']");
await click("img[data-src='https://media.tenor.com/6uIlQAHIkNoAAAAM/cry.gif']");
await click("i[aria-label='back']");
assert.containsOnce($, ".o-discuss-GifPicker div[aria-label='list']");
await contains(".o-discuss-GifPicker div[aria-label='list']");
});
QUnit.test("Add GIF to favorite", async (assert) => {
QUnit.test("Add GIF to favorite", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
const { openDiscuss } = await start({
@@ -172,35 +172,35 @@ QUnit.test("Add GIF to favorite", async (assert) => {
}
},
});
await openDiscuss(channelId);
openDiscuss(channelId);
await click("button[aria-label='GIFs']");
await click("img[data-src='https://media.tenor.com/6uIlQAHIkNoAAAAM/cry.gif']");
await click($(".o-discuss-Gif .fa-star-o"));
assert.containsOnce($, ".o-discuss-Gif .fa-star");
await click(".o-discuss-Gif .fa-star-o:eq(0)");
await contains(".o-discuss-Gif .fa-star");
await click("i[aria-label='back']");
await click(".o-discuss-GifPicker div[aria-label='list-item']:contains(Favorites)");
assert.containsOnce($, ".o-discuss-Gif");
await contains(".o-discuss-Gif");
});
QUnit.test("Chatter should not have the GIF button", async (assert) => {
QUnit.test("Chatter should not have the GIF button", async () => {
const { openFormView, pyEnv } = await start();
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
await openFormView("res.partner", partnerId);
openFormView("res.partner", partnerId);
await click("button:contains(Log note)");
assert.containsNone($, "button[aria-label='GIFs']");
await contains("button[aria-label='GIFs']", 0);
});
QUnit.test(
"Composer GIF button should open the GIF picker keyboard for mobile device",
async (assert) => {
async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "channel" });
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
patchUiSize({ size: SIZES.SM });
const { openDiscuss } = await start();
await openDiscuss(channelId, { waitUntilMessagesLoaded: false });
await click("span:contains(channel)");
openDiscuss(channelId, { waitUntilMessagesLoaded: false });
await click("span:contains(General)");
await click("button[aria-label='GIFs']");
assert.containsNone($, ".popover .o-discuss-GifPicker");
assert.containsOnce($, ".o-mail-Composer-footer .o-discuss-GifPicker");
await contains(".popover .o-discuss-GifPicker", 0);
await contains(".o-mail-Composer-footer .o-discuss-GifPicker");
}
);
@@ -199,7 +199,7 @@ patch(MockServer.prototype, {
);
const otherMembers = this.getRecords("discuss.channel.member", [
["channel_id", "=", channel.id],
["id", "!=", memberOfCurrentUser?.id],
["id", "!=", memberOfCurrentUser?.id || false],
]);
for (const member of otherMembers) {
this.pyEnv["discuss.channel.member"].write([member.id], {
@@ -666,7 +666,7 @@ patch(MockServer.prototype, {
let is_editable;
switch (channel.channel_type) {
case "channel":
is_editable = channel.create_uid === this.pyEnv.currentPartnerId;
is_editable = channel.create_uid === this.pyEnv.currentUserId;
break;
case "group":
is_editable = memberOfCurrentUser;
+61 -37
View File
@@ -125,23 +125,6 @@ function getAfterEvent({ messagingBus }) {
};
}
function getClick({ target, afterNextRender }) {
return async function click(selector) {
await afterNextRender(() => {
if (typeof selector === "string") {
$(target ?? document.body)
.find(selector)[0]
.click();
} else if (selector instanceof HTMLElement) {
selector.click();
} else {
// jquery
selector[0].click();
}
});
};
}
function getMouseenter({ afterNextRender }) {
return async function mouseenter(selector) {
await afterNextRender(() =>
@@ -431,7 +414,6 @@ async function start(param0 = {}) {
advanceTime,
afterEvent,
afterNextRender,
click: getClick({ target, afterNextRender }),
env: webClient.env,
insertText,
mouseenter: getMouseenter({ afterNextRender }),
@@ -538,19 +520,24 @@ function pasteFiles(el, files) {
*/
export async function insertText(target, content, { replace = false } = {}) {
if (typeof target === "string") {
target = document.querySelector(target);
target = (await contains(target))[0];
}
await afterNextRender(() => {
if (replace) {
target.value = "";
}
target.focus();
for (const char of content) {
document.execCommand("insertText", false, char);
target.dispatchEvent(new window.KeyboardEvent("keydown", { key: char }));
target.dispatchEvent(new window.KeyboardEvent("keyup", { key: char }));
}
});
if (replace) {
target.value = "";
}
target.focus();
for (const char of content) {
document.execCommand("insertText", false, char);
target.dispatchEvent(new window.KeyboardEvent("keydown", { key: char }));
target.dispatchEvent(new window.KeyboardEvent("keyup", { key: char }));
target.dispatchEvent(new window.InputEvent("input"));
target.dispatchEvent(new window.InputEvent("change"));
}
if (!content) {
target.dispatchEvent(new window.InputEvent("input"));
target.dispatchEvent(new window.InputEvent("change"));
}
return $(target);
}
//------------------------------------------------------------------------------
@@ -671,36 +658,62 @@ export {
startServer,
};
export const click = getClick({ afterNextRender });
export async function click(selector, { target = document.body } = {}) {
if (typeof selector === "string") {
const $e = await contains(selector, 1, { target });
$e[0].click();
return $($e[0]);
} else if (selector instanceof HTMLElement) {
selector.click();
return Promise.resolve($(selector));
} else {
// jquery
selector[0].click();
return Promise.resolve($(selector[0]));
}
}
let hasUsedContainsPositively = false;
QUnit.testStart(() => (hasUsedContainsPositively = false));
/**
* Function that wait until a selector is present in the DOM
* Function that waits until a selector is present in the DOM.
*
* @param {string} selector
* @param {number} [count=1]
* @returns {JQuery<HTMLElement>}
*/
export function until(selector, count = 1) {
const res = $(selector);
export function contains(selector, count = 1, { target = document.body } = {}) {
if (count) {
hasUsedContainsPositively = true;
} else if (!hasUsedContainsPositively) {
throw new Error(
`Starting a test with "contains" of count 0 for selector "${selector}" is useless because it might immediately resolve. Start the test by checking that an expected element actually exists.`
);
}
const res = $(target).find(selector);
const resMessage = `Found ${count} occurrence(s) of ${selector}`;
if (res.length === count) {
QUnit.assert.ok(true, resMessage);
return Promise.resolve(res);
}
return new Promise((resolve, reject) => {
let done = false;
const timer = setTimeout(() => {
observer.disconnect();
const res = $(selector);
const res = $(target).find(selector);
const message = `Waited 5 second for ${count} occurrence(s) of ${selector}. Found ${res.length} instead.`;
QUnit.assert.ok(false, message);
reject(new Error(message));
done = true;
}, 5000);
const observer = new MutationObserver(() => {
const res = $(selector);
const res = $(target).find(selector);
if (res.length === count) {
observer.disconnect();
QUnit.assert.ok(true, resMessage);
resolve(res);
observer.disconnect();
clearTimeout(timer);
done = true;
}
});
observer.observe(document.body, {
@@ -708,5 +721,16 @@ export function until(selector, count = 1) {
childList: true,
subtree: true,
});
registerCleanup(() => {
if (!done) {
observer.disconnect();
const res = $(target).find(selector);
const message = `Test ended while waiting for ${count} occurrence(s) of ${selector}. Found ${res.length} instead.`;
QUnit.assert.ok(false, message);
reject(new Error(message));
clearTimeout(timer);
done = true;
}
});
});
}
+29 -30
View File
@@ -1,7 +1,7 @@
/* @odoo-module */
import { addLink, parseAndTransform } from "@mail/utils/common/format";
import { start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("Mail utils");
@@ -97,76 +97,75 @@ QUnit.test("addLink: linkify inside text node (2 occurrences)", function (assert
assert.strictEqual(div.querySelectorAll(":scope a")[1].textContent, "https://somelink2.com");
});
QUnit.test("url", async (assert) => {
QUnit.test("url", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { click, insertText, openDiscuss } = await start();
const { insertText, openDiscuss } = 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:contains(Send)");
assert.containsOnce($, `.o-mail-Message a:contains(${messageBody})`);
await click("button:contains(Send):not(:disabled)");
await contains(`.o-mail-Message a:contains(${messageBody})`);
});
QUnit.test("url with comma at the end", async (assert) => {
QUnit.test("url with comma at the end", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { click, insertText, openDiscuss } = await start();
const { insertText, openDiscuss } = 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:contains(Send)");
assert.containsOnce($, `.o-mail-Message a:contains(https://odoo.com)`);
assert.containsOnce($, `.o-mail-Message:contains(${messageBody})`);
await click("button:contains(Send):not(:disabled)");
await contains(`.o-mail-Message a:contains(https://odoo.com)`);
await contains(`.o-mail-Message:contains(${messageBody})`);
});
QUnit.test("url with dot at the end", async (assert) => {
QUnit.test("url with dot at the end", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { click, insertText, openDiscuss } = await start();
const { insertText, openDiscuss } = 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:contains(Send)");
assert.containsOnce($, `.o-mail-Message a:contains(https://odoo.com)`);
assert.containsOnce($, `.o-mail-Message:contains(${messageBody})`);
await click("button:contains(Send):not(:disabled)");
await contains(`.o-mail-Message a:contains(https://odoo.com)`);
await contains(`.o-mail-Message:contains(${messageBody})`);
});
QUnit.test("url with semicolon at the end", async (assert) => {
QUnit.test("url with semicolon at the end", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { click, insertText, openDiscuss } = await start();
const { insertText, openDiscuss } = 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:contains(Send)");
assert.containsOnce($, `.o-mail-Message a:contains(https://odoo.com)`);
assert.containsOnce($, `.o-mail-Message:contains(${messageBody})`);
await click("button:contains(Send):not(:disabled)");
await contains(`.o-mail-Message a:contains(https://odoo.com)`);
await contains(`.o-mail-Message:contains(${messageBody})`);
});
QUnit.test("url with ellipsis at the end", async (assert) => {
QUnit.test("url with ellipsis at the end", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { click, insertText, openDiscuss } = await start();
const { insertText, openDiscuss } = 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:contains(Send)");
assert.containsOnce($, `.o-mail-Message a:contains(https://odoo.com)`);
assert.containsOnce($, `.o-mail-Message:contains(${messageBody})`);
await click("button:contains(Send):not(:disabled)");
await contains(`.o-mail-Message a:contains(https://odoo.com)`);
await contains(`.o-mail-Message:contains(${messageBody})`);
});
QUnit.test("url with number in subdomain", async (assert) => {
QUnit.test("url with number in subdomain", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { click, insertText, openDiscuss } = await start();
const { insertText, openDiscuss } = await start();
await openDiscuss(channelId);
const messageBody = "https://www.45017478-master-all.runbot134.odoo.com/web";
await insertText(".o-mail-Composer-input", messageBody);
await click("button:contains(Send)");
assert.containsOnce(
$,
await click("button:contains(Send):not(:disabled)");
await contains(
`.o-mail-Message a:contains(https://www.45017478-master-all.runbot134.odoo.com/web)`
);
});
@@ -1,10 +1,16 @@
/* @odoo-module */
import { click, nextAnimationFrame, start, startServer } from "@mail/../tests/helpers/test_utils";
import {
click,
contains,
nextAnimationFrame,
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
QUnit.module("link preview");
QUnit.test("auto layout with link preview list", async (assert) => {
QUnit.test("auto layout with link preview list", async () => {
const pyEnv = await startServer();
const linkPreviewId = pyEnv["mail.link.preview"].create({
og_description: "test description",
@@ -23,11 +29,11 @@ QUnit.test("auto layout with link preview list", async (assert) => {
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-Message .o-mail-LinkPreviewList");
openDiscuss(channelId);
await contains(".o-mail-Message .o-mail-LinkPreviewList");
});
QUnit.test("auto layout with link preview as gif", async (assert) => {
QUnit.test("auto layout with link preview as gif", async () => {
const pyEnv = await startServer();
const linkPreviewId = pyEnv["mail.link.preview"].create({
og_description: "test description",
@@ -46,11 +52,11 @@ QUnit.test("auto layout with link preview as gif", async (assert) => {
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-LinkPreviewImage");
openDiscuss(channelId);
await contains(".o-mail-LinkPreviewImage");
});
QUnit.test("simplest card layout", async (assert) => {
QUnit.test("simplest card layout", async () => {
const pyEnv = await startServer();
const linkPreviewId = pyEnv["mail.link.preview"].create({
og_description: "Description",
@@ -67,13 +73,13 @@ QUnit.test("simplest card layout", async (assert) => {
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-LinkPreviewCard");
assert.containsOnce($, ".o-mail-LinkPreviewCard:contains(Article title)");
assert.containsOnce($, ".o-mail-LinkPreviewCard:contains(Description)");
openDiscuss(channelId);
await contains(".o-mail-LinkPreviewCard");
await contains(".o-mail-LinkPreviewCard:contains(Article title)");
await contains(".o-mail-LinkPreviewCard:contains(Description)");
});
QUnit.test("simplest card layout with image", async (assert) => {
QUnit.test("simplest card layout with image", async () => {
const pyEnv = await startServer();
const linkPreviewId = pyEnv["mail.link.preview"].create({
og_description: "Description",
@@ -91,14 +97,14 @@ QUnit.test("simplest card layout with image", async (assert) => {
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-LinkPreviewCard");
assert.containsOnce($, ".o-mail-LinkPreviewCard:contains(Article title)");
assert.containsOnce($, ".o-mail-LinkPreviewCard:contains(Description)");
assert.containsOnce($, ".o-mail-LinkPreviewCard img");
openDiscuss(channelId);
await contains(".o-mail-LinkPreviewCard");
await contains(".o-mail-LinkPreviewCard:contains(Article title)");
await contains(".o-mail-LinkPreviewCard:contains(Description)");
await contains(".o-mail-LinkPreviewCard img");
});
QUnit.test("Link preview video layout", async (assert) => {
QUnit.test("Link preview video layout", async () => {
const pyEnv = await startServer();
const linkPreviewId = pyEnv["mail.link.preview"].create({
og_description: "Description",
@@ -116,14 +122,14 @@ QUnit.test("Link preview video layout", async (assert) => {
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-LinkPreviewVideo");
assert.containsOnce($, ".o-mail-LinkPreviewVideo:contains(video title)");
assert.containsOnce($, ".o-mail-LinkPreviewVideo:contains(Description)");
assert.containsOnce($, ".o-mail-LinkPreviewVideo-overlay");
openDiscuss(channelId);
await contains(".o-mail-LinkPreviewVideo");
await contains(".o-mail-LinkPreviewVideo:contains(video title)");
await contains(".o-mail-LinkPreviewVideo:contains(Description)");
await contains(".o-mail-LinkPreviewVideo-overlay");
});
QUnit.test("Link preview image layout", async (assert) => {
QUnit.test("Link preview image layout", async () => {
const pyEnv = await startServer();
const linkPreviewId = pyEnv["mail.link.preview"].create({
image_mimetype: "image/jpg",
@@ -139,11 +145,11 @@ QUnit.test("Link preview image layout", async (assert) => {
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-LinkPreviewImage");
openDiscuss(channelId);
await contains(".o-mail-LinkPreviewImage");
});
QUnit.test("Remove link preview Gif", async (assert) => {
QUnit.test("Remove link preview Gif", async () => {
const pyEnv = await startServer();
const linkPreviewId = pyEnv["mail.link.preview"].create({
og_description: "test description",
@@ -162,14 +168,14 @@ QUnit.test("Remove link preview Gif", async (assert) => {
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-LinkPreviewImage button[aria-label='Remove']");
assert.containsOnce($, "p:contains(Do you really want to delete this preview?)");
await contains("p:contains(Do you really want to delete this preview?)");
await click(".modal-footer button:contains(Ok)");
assert.containsNone($, ".o-mail-LinkPreviewImage");
await contains(".o-mail-LinkPreviewImage", 0);
});
QUnit.test("Remove link preview card", async (assert) => {
QUnit.test("Remove link preview card", async () => {
const pyEnv = await startServer();
const linkPreviewId = pyEnv["mail.link.preview"].create({
og_description: "Description",
@@ -186,14 +192,14 @@ QUnit.test("Remove link preview card", async (assert) => {
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-LinkPreviewCard button[aria-label='Remove']");
assert.containsOnce($, "p:contains(Do you really want to delete this preview?)");
await contains("p:contains(Do you really want to delete this preview?)");
await click(".modal-footer button:contains(Ok)");
assert.containsNone($, ".o-mail-LinkPreviewCard");
await contains(".o-mail-LinkPreviewCard", 0);
});
QUnit.test("Remove link preview video", async (assert) => {
QUnit.test("Remove link preview video", async () => {
const pyEnv = await startServer();
const linkPreviewId = pyEnv["mail.link.preview"].create({
og_description: "Description",
@@ -211,14 +217,14 @@ QUnit.test("Remove link preview video", async (assert) => {
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-LinkPreviewVideo button[aria-label='Remove']");
assert.containsOnce($, "p:contains(Do you really want to delete this preview?)");
await contains("p:contains(Do you really want to delete this preview?)");
await click(".modal-footer button:contains(Ok)");
assert.containsNone($, ".o-mail-LinkPreviewVideo");
await contains(".o-mail-LinkPreviewVideo", 0);
});
QUnit.test("Remove link preview image", async (assert) => {
QUnit.test("Remove link preview image", async () => {
const pyEnv = await startServer();
const linkPreviewId = pyEnv["mail.link.preview"].create({
image_mimetype: "image/jpg",
@@ -234,11 +240,11 @@ QUnit.test("Remove link preview image", async (assert) => {
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-LinkPreviewImage button[aria-label='Remove']");
assert.containsOnce($, "p:contains(Do you really want to delete this preview?)");
await contains("p:contains(Do you really want to delete this preview?)");
await click(".modal-footer button:contains(Ok)");
assert.containsNone($, ".o-mail-LinkPreviewImage");
await contains(".o-mail-LinkPreviewImage", 0);
});
QUnit.test("No crash on receiving link preview of non-known message", async (assert) => {
@@ -257,7 +263,7 @@ QUnit.test("No crash on receiving link preview of non-known message", async (ass
res_id: channelId,
});
const { env, openDiscuss } = await start();
await openDiscuss();
openDiscuss();
env.services.rpc("/mail/link_preview", { message_id: messageId });
await nextAnimationFrame();
assert.ok(true);
@@ -268,7 +274,7 @@ QUnit.test("No crash on receiving link preview of non-known message", async (ass
QUnit.test(
"Squash the message and the link preview when the link preview is an image and the link is the only text in the message",
async (assert) => {
async () => {
const pyEnv = await startServer();
const linkPreviewId = pyEnv["mail.link.preview"].create({
image_mimetype: "image/jpg",
@@ -284,14 +290,15 @@ QUnit.test(
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Message-bubble");
openDiscuss(channelId);
await contains(".o-mail-LinkPreviewImage");
await contains(".o-mail-Message-bubble", 0);
}
);
QUnit.test(
"Link preview and message should not be squashed when the link preview is not an image",
async (assert) => {
async () => {
const pyEnv = await startServer();
const linkPreviewId = pyEnv["mail.link.preview"].create({
og_description: "Description",
@@ -308,14 +315,14 @@ QUnit.test(
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-Message-bubble");
openDiscuss(channelId);
await contains(".o-mail-Message-bubble");
}
);
QUnit.test(
"Link preview and message should not be squashed when there is more than the link in the message",
async (assert) => {
async () => {
const pyEnv = await startServer();
const linkPreviewId = pyEnv["mail.link.preview"].create({
image_mimetype: "image/jpg",
@@ -331,7 +338,7 @@ QUnit.test(
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-Message-bubble");
openDiscuss(channelId);
await contains(".o-mail-Message-bubble");
}
);
@@ -1,12 +1,12 @@
/* @odoo-module */
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
QUnit.module("message reply");
QUnit.test("click on message in reply to highlight the parent message", async (assert) => {
QUnit.test("click on message in reply to highlight the parent message", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
const messageId = pyEnv["mail.message"].create({
@@ -23,9 +23,9 @@ QUnit.test("click on message in reply to highlight the parent message", async (a
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-Message:contains(Reply to Hey) .o-mail-MessageInReply-message");
assert.containsOnce($, ".o-mail-Message:contains(Hey lol).o-highlighted");
await contains(".o-mail-Message:contains(Hey lol).o-highlighted");
});
QUnit.test("click on message in reply to scroll to the parent message", async (assert) => {
@@ -55,7 +55,7 @@ QUnit.test("click on message in reply to scroll to the parent message", async (a
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(
".o-mail-Message:contains(Response to first message) .o-mail-MessageInReply-message"
);
File diff suppressed because it is too large Load Diff
@@ -83,7 +83,7 @@ QUnit.test(
const { openDiscuss, openFormView } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "test https://www.odoo.com/");
await click(".o-mail-Composer-send");
await click(".o-mail-Composer-send:not(:disabled)");
// leaving discuss.
await openFormView("res.partner", partnerId);
assert.containsNone($, ".o-mail-ChatWindow-header:contains(Dumbledore)");
@@ -17,23 +17,23 @@ import { patchWithCleanup, triggerEvent } from "@web/../tests/helpers/utils";
QUnit.module("messaging menu");
QUnit.test("should have messaging menu button in systray", async (assert) => {
QUnit.test("should have messaging menu button in systray", async () => {
await start();
assert.containsOnce($, ".o_menu_systray i[aria-label='Messages']");
assert.containsNone($, ".o-mail-MessagingMenu", "messaging menu closed by default");
assert.hasClass($(".o_menu_systray i[aria-label='Messages']"), "fa-comments");
await contains(".o_menu_systray i[aria-label='Messages']");
await contains(".o-mail-MessagingMenu", 0);
await contains(".o_menu_systray i[aria-label='Messages'].fa-comments");
});
QUnit.test("messaging menu should have topbar buttons", async (assert) => {
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-MessagingMenu");
assert.containsN($, ".o-mail-MessagingMenu-header button", 4);
assert.containsOnce($, "button:contains(All)");
assert.containsOnce($, "button:contains(Chat)");
assert.containsOnce($, "button:contains(Channel)");
assert.containsOnce($, "button:contains(New Message)");
assert.hasClass($("button:contains(All)"), "fw-bolder");
await contains(".o-mail-MessagingMenu");
await contains(".o-mail-MessagingMenu-header button", 4);
await contains("button:contains(All)");
await contains("button:contains(Chat)");
await contains("button:contains(Channel)");
await contains("button:contains(New Message)");
await contains("button:contains(All).fw-bolder");
assert.doesNotHaveClass($("button:contains(Chat)"), "fw-bolder");
assert.doesNotHaveClass($("button:contains(Channel)"), "fw-bolder");
});
@@ -59,17 +59,17 @@ QUnit.test("counter is taking into account failure notification", async (assert)
notification_type: "email",
});
await start();
assert.containsOnce($, ".o-mail-MessagingMenu-counter");
assert.strictEqual($(".o-mail-MessagingMenu-counter.badge").text(), "1");
await contains(".o-mail-MessagingMenu-counter");
assert.strictEqual($(".o-mail-MessagingMenu-counter").text(), "1");
});
QUnit.test("rendering with OdooBot has a request (default)", async (assert) => {
patchBrowserNotification("default");
await start();
assert.containsOnce($, ".o-mail-MessagingMenu-counter");
await contains(".o-mail-MessagingMenu-counter");
assert.strictEqual($(".o-mail-MessagingMenu-counter").text(), "1");
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem");
assert.ok(
$(".o-mail-NotificationItem img")
.data("src")
@@ -78,20 +78,20 @@ QUnit.test("rendering with OdooBot has a request (default)", async (assert) => {
assert.strictEqual($(".o-mail-NotificationItem-name").text().trim(), "OdooBot has a request");
});
QUnit.test("rendering without OdooBot has a request (denied)", async (assert) => {
QUnit.test("rendering without OdooBot has a request (denied)", async () => {
patchBrowserNotification("denied");
await start();
assert.containsNone($, ".o-mail-MessagingMenu-counter-badge");
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsNone($, ".o-mail-NotificationItem");
await contains(".o-mail-MessagingMenu-counter", 0);
await contains(".o-mail-NotificationItem", 0);
});
QUnit.test("rendering without OdooBot has a request (accepted)", async (assert) => {
QUnit.test("rendering without OdooBot has a request (accepted)", async () => {
patchBrowserNotification("granted");
await start();
assert.containsNone($, ".o-mail-MessagingMenu-counter-badge");
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsNone($, ".o-mail-NotificationItem");
await contains(".o-mail-MessagingMenu-counter", 0);
await contains(".o-mail-NotificationItem", 0);
});
QUnit.test("respond to notification prompt (denied)", async (assert) => {
@@ -106,9 +106,9 @@ QUnit.test("respond to notification prompt (denied)", async (assert) => {
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
assert.verifySteps(["confirmation_denied_toast"]);
assert.containsNone($, ".o-mail-MessagingMenu-counter-badge");
await contains(".o-mail-MessagingMenu-counter", 0);
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsNone($, ".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem", 0);
});
QUnit.test("respond to notification prompt (granted)", async (assert) => {
@@ -125,44 +125,44 @@ QUnit.test("respond to notification prompt (granted)", async (assert) => {
assert.verifySteps(["confirmation_granted_toast"]);
});
QUnit.test("no 'OdooBot has a request' in mobile app", async (assert) => {
QUnit.test("no 'OdooBot has a request' in mobile app", async () => {
patchBrowserNotification("default");
// simulate Android Odoo App
patchWithCleanup(browser.navigator, {
userAgent: "Chrome/0.0.0 Android (OdooMobile; Linux; Android 13; Odoo TestSuite)",
});
await start();
assert.containsNone($, ".o-mail-MessagingMenu-counter");
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsNone($, ".o-mail-NotificationItem");
await contains(".o-mail-MessagingMenu-counter", 0);
await contains(".o-mail-NotificationItem", 0);
});
QUnit.test("Is closed after clicking on new message", async (assert) => {
QUnit.test("Is closed after clicking on new message", async () => {
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click("button:contains(New Message)");
assert.containsNone($, ".o-mail-MessagingMenu");
await contains(".o-mail-MessagingMenu", 0);
});
QUnit.test("no 'New Message' button when discuss is open", async (assert) => {
QUnit.test("no 'New Message' button when discuss is open", async () => {
const { openDiscuss, openView } = await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, "button:contains(New Message)");
await contains("button:contains(New Message)");
await openDiscuss();
assert.containsNone($, "button:contains(New Message)");
await contains("button:contains(New Message)", 0);
await openView({
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, "button:contains(New Message)");
await contains("button:contains(New Message)");
await openDiscuss();
assert.containsNone($, "button:contains(New Message)");
await contains("button:contains(New Message)", 0);
});
QUnit.test("grouped notifications by document", async (assert) => {
QUnit.test("grouped notifications by document", async () => {
const pyEnv = await startServer();
const [messageId_1, messageId_2] = pyEnv["mail.message"].create([
{
@@ -192,12 +192,12 @@ QUnit.test("grouped notifications by document", async (assert) => {
]);
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-NotificationItem:contains(Partner) .badge:contains(2)");
assert.containsNone($, ".o-mail-ChatWindow");
await contains(".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem:contains(Partner) .badge:contains(2)");
await contains(".o-mail-ChatWindow", 0);
await click(".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
});
QUnit.test("grouped notifications by document model", async (assert) => {
@@ -252,7 +252,7 @@ QUnit.test("grouped notifications by document model", async (assert) => {
},
});
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-NotificationItem:contains(Partner) .badge:contains(2)");
await contains(".o-mail-NotificationItem:contains(Partner) .badge:contains(2)");
$(".o-mail-NotificationItem")[0].click();
assert.verifySteps(["do_action"]);
@@ -300,13 +300,13 @@ QUnit.test(
]);
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsN($, ".o-mail-NotificationItem", 2);
await contains(".o-mail-NotificationItem", 2);
assert.ok($(".o-mail-NotificationItem:eq(0)").text().includes("Company"));
assert.ok($(".o-mail-NotificationItem:eq(1)").text().includes("Partner"));
}
);
QUnit.test("non-failure notifications are ignored", async (assert) => {
QUnit.test("non-failure notifications are ignored", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const messageId = pyEnv["mail.message"].create({
@@ -321,7 +321,7 @@ QUnit.test("non-failure notifications are ignored", async (assert) => {
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsNone($, ".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem", 0);
});
QUnit.test("mark unread channel as read", async (assert) => {
@@ -350,15 +350,15 @@ QUnit.test("mark unread channel as read", async (assert) => {
},
});
await click(".o_menu_systray i[aria-label='Messages']");
await triggerEvent($(".o-mail-NotificationItem")[0], null, "mouseenter");
assert.containsOnce($, ".o-mail-NotificationItem [title='Mark As Read']");
await triggerEvent((await contains(".o-mail-NotificationItem"))[0], null, "mouseenter");
await contains(".o-mail-NotificationItem [title='Mark As Read']");
await click(".o-mail-NotificationItem [title='Mark As Read']");
assert.verifySteps(["set_last_seen_message"]);
assert.hasClass($(".o-mail-NotificationItem"), "o-muted");
await contains(".o-mail-NotificationItem.o-muted");
await triggerEvent($(".o-mail-NotificationItem")[0], null, "mouseenter");
assert.containsNone($, ".o-mail-NotificationItem [title='Mark As Read']");
assert.containsNone($, ".o-mail-ChatWindow");
await contains(".o-mail-NotificationItem [title='Mark As Read']", 0);
await contains(".o-mail-ChatWindow", 0);
});
QUnit.test("mark failure as read", async (assert) => {
@@ -380,23 +380,20 @@ QUnit.test("mark failure as read", async (assert) => {
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-NotificationItem:contains(Channel)");
assert.containsOnce(
$,
".o-mail-NotificationItem:contains(An error occurred when sending an email)"
);
await contains(".o-mail-NotificationItem:contains(Channel)");
await contains(".o-mail-NotificationItem:contains(An error occurred when sending an email)");
await triggerEvent($(".o-mail-NotificationItem:contains(Channel)")[0], null, "mouseenter");
assert.containsOnce($, ".o-mail-NotificationItem:contains(Channel) [title='Mark As Read']");
await contains(".o-mail-NotificationItem:contains(Channel) [title='Mark As Read']");
await click(".o-mail-NotificationItem [title='Mark As Read']");
assert.containsNone($, ".o-mail-NotificationItem:contains(Channel)");
await contains(".o-mail-NotificationItem:contains(Channel)", 0);
assert.containsNone(
$,
".o-mail-NotificationItem:contains(An error occurred when sending an email)"
);
});
QUnit.test("different discuss.channel are not grouped", async (assert) => {
QUnit.test("different discuss.channel are not grouped", async () => {
const pyEnv = await startServer();
const [channelId_1, channelId_2] = pyEnv["discuss.channel"].create([
{ name: "Channel_1" },
@@ -440,75 +437,72 @@ QUnit.test("different discuss.channel are not grouped", async (assert) => {
]);
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsN($, ".o-mail-NotificationItem", 4);
await contains(".o-mail-NotificationItem", 4);
const group_1 = $(".o-mail-NotificationItem:contains(Channel):first");
await click(group_1);
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
});
QUnit.test("mobile: active icon is highlighted", async (assert) => {
QUnit.test("mobile: active icon is highlighted", async () => {
patchUiSize({ size: SIZES.SM });
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-MessagingMenu-tab:contains(Chat)");
assert.hasClass($(".o-mail-MessagingMenu-tab:contains(Chat)"), "fw-bolder");
await contains(".o-mail-MessagingMenu-tab:contains(Chat).fw-bolder");
});
QUnit.test("open chat window from preview", async (assert) => {
QUnit.test("open chat window from preview", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({ name: "test" });
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
});
QUnit.test(
'"Start a conversation" in mobile shows channel selector (+ click away)',
async (assert) => {
patchUiSize({ height: 360, width: 640 });
const { openDiscuss } = await start();
await openDiscuss();
await click("button:contains(Chat)");
assert.containsOnce($, "button:contains(Start a conversation)");
assert.containsNone($, "input[placeholder='Start a conversation']");
QUnit.test('"Start a conversation" in mobile shows channel selector (+ click away)', async () => {
patchUiSize({ height: 360, width: 640 });
const { openDiscuss } = await start();
await openDiscuss();
await click("button:contains(Chat)");
await contains("button:contains(Start a conversation)");
await contains("input[placeholder='Start a conversation']", 0);
await click("button:contains(Start a conversation)");
assert.containsNone($, "button:contains(Start a conversation)");
assert.containsOnce($, "input[placeholder='Start a conversation']");
await click("button:contains(Start a conversation)");
await contains("button:contains(Start a conversation)", 0);
await contains("input[placeholder='Start a conversation']");
await click(".o-mail-MessagingMenu");
assert.containsOnce($, "button:contains(Start a conversation)");
assert.containsNone($, "input[placeholder='Start a conversation']");
}
);
QUnit.test('"New Channel" in mobile shows channel selector (+ click away)', async (assert) => {
await click(".o-mail-MessagingMenu");
await contains("button:contains(Start a conversation)");
await contains("input[placeholder='Start a conversation']", 0);
});
QUnit.test('"New Channel" in mobile shows channel selector (+ click away)', async () => {
patchUiSize({ height: 360, width: 640 });
const { openDiscuss } = await start();
await openDiscuss();
await click("button:contains(Channel)");
assert.containsOnce($, "button:contains(New Channel)");
assert.containsNone($, "input[placeholder='Add or join a channel']");
await contains("button:contains(New Channel)");
await contains("input[placeholder='Add or join a channel']", 0);
await click("button:contains(New Channel)");
assert.containsNone($, "button:contains(New Channel)");
assert.containsOnce($, "input[placeholder='Add or join a channel']");
await contains("button:contains(New Channel)", 0);
await contains("input[placeholder='Add or join a channel']");
await click(".o-mail-MessagingMenu");
assert.containsOnce($, "button:contains(New Channel)");
assert.containsNone($, "input[placeholder='Add or join a channel']");
await contains("button:contains(New Channel)");
await contains("input[placeholder='Add or join a channel']", 0);
});
QUnit.test("'New Message' button should open a chat window in mobile", async (assert) => {
QUnit.test("'New Message' button should open a chat window in mobile", async () => {
patchUiSize({ height: 360, width: 640 });
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click("button:contains(New Message)");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
});
QUnit.test("Counter is updated when receiving new message", async (assert) => {
QUnit.test("Counter is updated when receiving new message", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Albert" });
const userId = pyEnv["res.users"].create({ partner_id: partnerId });
@@ -536,7 +530,7 @@ QUnit.test("Counter is updated when receiving new message", async (assert) => {
})
)
);
assert.containsOnce($, ".o-mail-MessagingMenu-counter.badge:contains(1)");
await contains(".o-mail-MessagingMenu-counter:contains(1)");
});
QUnit.test("basic rendering", async (assert) => {
@@ -547,27 +541,27 @@ QUnit.test("basic rendering", async (assert) => {
},
});
await start();
assert.containsOnce($, ".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
await contains(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
assert.doesNotHaveClass(
$('.o_menu_systray .dropdown-toggle:has(i[aria-label="Messages"])'),
"show"
);
assert.containsOnce($, ".o_menu_systray i[aria-label='Messages']");
assert.hasClass($('.o_menu_systray i[aria-label="Messages"]'), "fa-comments");
assert.containsNone($, ".o-mail-MessagingMenu");
await contains(".o_menu_systray i[aria-label='Messages']");
await contains('.o_menu_systray i[aria-label="Messages"].fa-comments');
await contains(".o-mail-MessagingMenu", 0);
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
assert.hasClass($('.o_menu_systray .dropdown:has(i[aria-label="Messages"])'), "show");
assert.containsOnce($, ".o-mail-MessagingMenu");
assert.containsOnce($, ".o-mail-MessagingMenu-header");
assert.containsN($, ".o-mail-MessagingMenu-header button", 4);
assert.containsOnce($, '.o-mail-MessagingMenu button:contains("All")');
assert.containsOnce($, '.o-mail-MessagingMenu button:contains("Chats")');
assert.containsOnce($, '.o-mail-MessagingMenu button:contains("Channels")');
assert.hasClass($('.o-mail-MessagingMenu button:contains("All")'), "fw-bolder");
assert.doesNotHaveClass($('.o-mail-MessagingMenu button:contains("Chats")'), "fw-bolder");
assert.doesNotHaveClass($('.o-mail-MessagingMenu button:contains("Channels")'), "fw-bolder");
assert.containsOnce($, "button:contains(New Message)");
assert.containsOnce($, '.o-mail-MessagingMenu:contains("No conversation yet...")');
await contains('.o_menu_systray .dropdown:has(i[aria-label="Messages"]).show');
await contains(".o-mail-MessagingMenu");
await contains(".o-mail-MessagingMenu-header");
await contains(".o-mail-MessagingMenu-header button", 4);
await contains('.o-mail-MessagingMenu button:contains("All")');
await contains('.o-mail-MessagingMenu button:contains("Chats")');
await contains('.o-mail-MessagingMenu button:contains("Channels")');
await contains('.o-mail-MessagingMenu button:contains("All").fw-bolder');
await contains('.o-mail-MessagingMenu button:contains("Chats"):not(.fw-bolder)');
await contains('.o-mail-MessagingMenu button:contains("Channels"):not(.fw-bolder)');
await contains("button:contains(New Message)");
await contains('.o-mail-MessagingMenu:contains("No conversation yet...")');
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
assert.doesNotHaveClass(
$('.o_menu_systray .dropdown-toggle:has(i[aria-label="Messages"])'),
@@ -575,30 +569,30 @@ QUnit.test("basic rendering", async (assert) => {
);
});
QUnit.test("switch tab", async (assert) => {
QUnit.test("switch tab", async () => {
await start();
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
assert.containsOnce($, '.o-mail-MessagingMenu button:contains("All")');
assert.containsOnce($, '.o-mail-MessagingMenu button:contains("Chats")');
assert.containsOnce($, '.o-mail-MessagingMenu button:contains("Channels")');
assert.hasClass($('.o-mail-MessagingMenu button:contains("All")'), "fw-bolder");
assert.doesNotHaveClass($('.o-mail-MessagingMenu button:contains("Chats")'), "fw-bolder");
assert.doesNotHaveClass($('.o-mail-MessagingMenu button:contains("Channels")'), "fw-bolder");
await contains('.o-mail-MessagingMenu button:contains("All")');
await contains('.o-mail-MessagingMenu button:contains("Chats")');
await contains('.o-mail-MessagingMenu button:contains("Channels")');
await contains('.o-mail-MessagingMenu button:contains("All").fw-bolder');
await contains('.o-mail-MessagingMenu button:contains("Chats"):not(.fw-bolder)');
await contains('.o-mail-MessagingMenu button:contains("Channels"):not(.fw-bolder)');
await click('.o-mail-MessagingMenu button:contains("Chats")');
assert.doesNotHaveClass($('.o-mail-MessagingMenu button:contains("All")'), "fw-bolder");
assert.hasClass($('.o-mail-MessagingMenu button:contains("Chats")'), "fw-bolder");
assert.doesNotHaveClass($('.o-mail-MessagingMenu button:contains("Channels")'), "fw-bolder");
await contains('.o-mail-MessagingMenu button:contains("All"):not(.fw-bolder)');
await contains('.o-mail-MessagingMenu button:contains("Chats").fw-bolder');
await contains('.o-mail-MessagingMenu button:contains("Channels"):not(.fw-bolder)');
await click('.o-mail-MessagingMenu button:contains("Channels")');
assert.doesNotHaveClass($('.o-mail-MessagingMenu button:contains("All")'), "fw-bolder");
assert.doesNotHaveClass($('.o-mail-MessagingMenu button:contains("Chats")'), "fw-bolder");
assert.hasClass($('.o-mail-MessagingMenu button:contains("Channels")'), "fw-bolder");
await contains('.o-mail-MessagingMenu button:contains("All"):not(.fw-bolder)');
await contains('.o-mail-MessagingMenu button:contains("Chats"):not(.fw-bolder)');
await contains('.o-mail-MessagingMenu button:contains("Channels").fw-bolder');
await click('.o-mail-MessagingMenu button:contains("All")');
assert.hasClass($('.o-mail-MessagingMenu button:contains("All")'), "fw-bolder");
assert.doesNotHaveClass($('.o-mail-MessagingMenu button:contains("Chats")'), "fw-bolder");
assert.doesNotHaveClass($('.o-mail-MessagingMenu button:contains("Channels")'), "fw-bolder");
await contains('.o-mail-MessagingMenu button:contains("All").fw-bolder');
await contains('.o-mail-MessagingMenu button:contains("Chats"):not(.fw-bolder)');
await contains('.o-mail-MessagingMenu button:contains("Channels"):not(.fw-bolder)');
});
QUnit.test("channel preview: basic rendering", async (assert) => {
QUnit.test("channel preview: basic rendering", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const channelId = pyEnv["discuss.channel"].create({
@@ -612,13 +606,13 @@ QUnit.test("channel preview: basic rendering", async (assert) => {
});
await start();
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
assert.containsOnce($, ".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-NotificationItem img");
assert.containsOnce($, '.o-mail-NotificationItem:contains("General")');
assert.containsOnce($, '.o-mail-NotificationItem:contains("Demo: test")');
await contains(".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem img");
await contains('.o-mail-NotificationItem:contains("General")');
await contains('.o-mail-NotificationItem:contains("Demo: test")');
});
QUnit.test("filtered previews", async (assert) => {
QUnit.test("filtered previews", async () => {
const pyEnv = await startServer();
const [channelId_1, channelId_2] = pyEnv["discuss.channel"].create([
{ channel_type: "chat" },
@@ -636,18 +630,18 @@ QUnit.test("filtered previews", async (assert) => {
]);
await start();
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
assert.containsN($, ".o-mail-NotificationItem", 2);
assert.containsOnce($, '.o-mail-NotificationItem:contains("Mitchell Admin")');
assert.containsOnce($, '.o-mail-NotificationItem:contains("channel1")');
await contains(".o-mail-NotificationItem", 2);
await contains('.o-mail-NotificationItem:contains("Mitchell Admin")');
await contains('.o-mail-NotificationItem:contains("channel1")');
await click('.o-mail-MessagingMenu button:contains("Chats")');
assert.containsOnce($, '.o-mail-NotificationItem:contains("Mitchell Admin")');
await contains('.o-mail-NotificationItem:contains("Mitchell Admin")');
await click('.o-mail-MessagingMenu button:contains("Channels")');
assert.containsOnce($, '.o-mail-NotificationItem:contains("channel1")');
await contains('.o-mail-NotificationItem:contains("channel1")');
await click('.o-mail-MessagingMenu button:contains("All")');
assert.containsN($, ".o-mail-NotificationItem", 2);
assert.containsOnce($, '.o-mail-NotificationItem:contains("Mitchell Admin")');
await contains(".o-mail-NotificationItem", 2);
await contains('.o-mail-NotificationItem:contains("Mitchell Admin")');
await click('.o-mail-MessagingMenu button:contains("Channels")');
assert.containsOnce($, '.o-mail-NotificationItem:contains("channel1")');
await contains('.o-mail-NotificationItem:contains("channel1")');
});
QUnit.test("no code injection in message body preview", async (assert) => {
@@ -660,7 +654,7 @@ QUnit.test("no code injection in message body preview", async (assert) => {
});
await start();
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
assert.containsOnce($, ".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem");
assert.strictEqual(
$(".o-mail-NotificationItem-text").text().replace(/\s/g, ""),
"You:&shoulnotberaisedthrownewError('CodeInjectionError');"
@@ -678,8 +672,8 @@ QUnit.test("no code injection in message body preview from sanitized message", a
});
await start();
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
assert.containsOnce($, ".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-NotificationItem-text");
await contains(".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem-text");
assert.strictEqual(
$(".o-mail-NotificationItem-text").text().replace(/\s/g, ""),
"You:<em>&shoulnotberaised</em><script>thrownewError('CodeInjectionError');</script>"
@@ -697,8 +691,8 @@ QUnit.test("<br/> tags in message body preview are transformed in spaces", async
});
await start();
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
assert.containsOnce($, ".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-NotificationItem-text");
await contains(".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem-text");
assert.strictEqual($(".o-mail-NotificationItem-text").text(), "You: a b c d");
});
@@ -722,7 +716,10 @@ QUnit.test(
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.strictEqual($(".o-mail-NotificationItem").text().split("Demo User").length - 1, 1);
assert.strictEqual(
(await contains(".o-mail-NotificationItem")).text().split("Demo User").length - 1,
1
);
}
);
@@ -734,11 +731,11 @@ QUnit.test(
await start();
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Messages'])");
await click('.o-mail-MessagingMenu button:contains("Chats")');
assert.containsOnce($, ".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem");
}
);
QUnit.test("click on preview should mark as read and open the thread", async (assert) => {
QUnit.test("click on preview should mark as read and open the thread", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Frodo Baggins" });
const messageId = pyEnv["mail.message"].create({
@@ -757,12 +754,12 @@ QUnit.test("click on preview should mark as read and open the thread", async (as
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-NotificationItem:contains(Frodo Baggins)");
assert.containsNone($, ".o-mail-ChatWindow");
await contains(".o-mail-NotificationItem:contains(Frodo Baggins)");
await contains(".o-mail-ChatWindow", 0);
await click(".o-mail-NotificationItem:contains(Frodo Baggins)");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsNone($, ".o-mail-NotificationItem:contains(Frodo Baggins)");
await contains(".o-mail-NotificationItem:contains(Frodo Baggins)", 0);
});
QUnit.test(
@@ -795,14 +792,14 @@ QUnit.test(
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem:contains(Frodo Baggins)");
await click(".o-mail-ChatWindow-command i.fa-expand");
assert.containsNone($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow", 0);
assert.verifySteps(["do_action"], "should have done an action to open the form view");
}
);
QUnit.test(
"preview should display last needaction message preview even if there is a more recent message that is not needaction in the thread",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Stranger" });
const messageId = pyEnv["mail.message"].create({
@@ -827,7 +824,7 @@ QUnit.test(
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-NotificationItem:contains(I am the oldest but needaction)");
await contains(".o-mail-NotificationItem:contains(I am the oldest but needaction)");
}
);
@@ -865,14 +862,14 @@ QUnit.test(
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsN($, ".o-mail-NotificationItem", 1);
await contains(".o-mail-NotificationItem");
assert.ok($(".o-mail-NotificationItem").text().includes("Test"));
assert.ok($(".o-mail-NotificationItem .badge").text().includes("1"));
assert.ok($(".o-mail-NotificationItem").text().includes("Message with needaction"));
}
);
QUnit.test("chat should show unread counter on receiving new messages", async (assert) => {
QUnit.test("chat should show unread counter on receiving new messages", async () => {
// unread and needaction are conceptually the same in chat
// however message_needaction_counter is not updated
// so special care for chat to simulate needaction with unread
@@ -905,7 +902,7 @@ QUnit.test("chat should show unread counter on receiving new messages", async (a
await contains(".o-mail-NotificationItem .badge:contains('1')");
});
QUnit.test("preview for channel should show latest non-deleted message", async (assert) => {
QUnit.test("preview for channel should show latest non-deleted message", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Partner1" });
const channelId = pyEnv["discuss.channel"].create({ name: "Test" });
@@ -925,7 +922,7 @@ QUnit.test("preview for channel should show latest non-deleted message", async (
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-NotificationItem:contains(message-2)");
await contains(".o-mail-NotificationItem:contains(message-2)");
// Simulate deletion of message-2
await afterNextRender(() =>
env.services.rpc("/mail/message/update_content", {
@@ -934,10 +931,10 @@ QUnit.test("preview for channel should show latest non-deleted message", async (
attachment_ids: [],
})
);
assert.containsOnce($, ".o-mail-NotificationItem:contains(message-1)");
await contains(".o-mail-NotificationItem:contains(message-1)");
});
QUnit.test("failure notifications are shown before channel preview", async (assert) => {
QUnit.test("failure notifications are shown before channel preview", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Partner1" });
const failedMessageId = pyEnv["mail.message"].create({
@@ -964,23 +961,19 @@ QUnit.test("failure notifications are shown before channel preview", async (asse
pyEnv["discuss.channel.member"].write([memberId], { seen_message_id: messageId });
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce(
$,
".o-mail-NotificationItem:contains(An error occurred when sending an email)"
);
assert.containsOnce($, ".o-mail-NotificationItem:contains(message)");
assert.containsOnce(
$,
await contains(".o-mail-NotificationItem:contains(An error occurred when sending an email)");
await contains(".o-mail-NotificationItem:contains(message)");
await contains(
".o-mail-NotificationItem:contains(An error occurred when sending an email) ~ .o-mail-NotificationItem:contains(message)"
);
});
QUnit.test("messaging menu should show new needaction messages from chatter", async (assert) => {
QUnit.test("messaging menu should show new needaction messages from chatter", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Frodo Baggins" });
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsNone($, ".o-mail-NotificationItem:contains(@Mitchell Admin)");
await contains(".o-mail-NotificationItem:contains(@Mitchell Admin)", 0);
// simulate receiving a new needaction message
await afterNextRender(() => {
@@ -993,5 +986,5 @@ QUnit.test("messaging menu should show new needaction messages from chatter", as
record_name: "Frodo Baggins",
});
});
assert.containsOnce($, ".o-mail-NotificationItem:contains(@Mitchell Admin)");
await contains(".o-mail-NotificationItem:contains(@Mitchell Admin)");
});
@@ -1,8 +1,8 @@
/* @odoo-module */
import { afterNextRender, click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { Command } from "@mail/../tests/helpers/command";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { browser } from "@web/core/browser/browser";
import { patchWithCleanup, triggerEvent } from "@web/../tests/helpers/utils";
QUnit.module("notification");
@@ -30,14 +30,13 @@ QUnit.test("basic layout", async (assert) => {
]);
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-NotificationItem-name:contains(Channel)");
assert.containsOnce($, ".o-mail-NotificationItem-counter:contains(2)");
await contains(".o-mail-NotificationItem-name:contains(Channel)");
await contains(".o-mail-NotificationItem-counter:contains(2)");
assert.containsOnce(
$(".o-mail-NotificationItem-name:contains(Channel)").closest(".o-mail-NotificationItem"),
".o-mail-NotificationItem-date:contains(now)"
);
assert.containsOnce(
$,
await contains(
".o-mail-NotificationItem-text:contains(An error occurred when sending an email)"
);
});
@@ -59,7 +58,9 @@ QUnit.test("mark as read", async (assert) => {
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await triggerEvent(
$(".o-mail-NotificationItem-name:contains(Channel)").closest(".o-mail-NotificationItem")[0],
(
await contains(".o-mail-NotificationItem-name:contains(Channel)")
).closest(".o-mail-NotificationItem")[0],
null,
"mouseenter"
);
@@ -179,14 +180,10 @@ QUnit.test("different discuss.channel are not grouped", async (assert) => {
]);
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsN(
$,
".o-mail-NotificationItem:contains(An error occurred when sending an email)",
2
);
await contains(".o-mail-NotificationItem:contains(An error occurred when sending an email)", 2);
});
QUnit.test("multiple grouped notifications by model", async (assert) => {
QUnit.test("multiple grouped notifications by model", async () => {
const pyEnv = await startServer();
const [messageId_1, messageId_2] = pyEnv["mail.message"].create([
{
@@ -226,11 +223,11 @@ QUnit.test("multiple grouped notifications by model", async (assert) => {
]);
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsN($, ".o-mail-NotificationItem", 2);
assert.containsN($, ".o-mail-NotificationItem-counter:contains(2)", 2);
await contains(".o-mail-NotificationItem", 2);
await contains(".o-mail-NotificationItem-counter:contains(2)", 2);
});
QUnit.test("non-failure notifications are ignored", async (assert) => {
QUnit.test("non-failure notifications are ignored", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const messageId = pyEnv["mail.message"].create({
@@ -245,7 +242,7 @@ QUnit.test("non-failure notifications are ignored", async (assert) => {
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsNone($, ".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem", 0);
});
QUnit.test(
@@ -270,13 +267,13 @@ QUnit.test(
]);
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsN($, ".o-mail-NotificationItem-name", 2);
await contains(".o-mail-NotificationItem-name", 2);
assert.strictEqual($(".o-mail-NotificationItem-name:eq(0)").text(), "Channel 2020");
assert.strictEqual($(".o-mail-NotificationItem-name:eq(1)").text(), "Channel 2019");
}
);
QUnit.test("thread notifications are re-ordered on receiving a new message", async (assert) => {
QUnit.test("thread notifications are re-ordered on receiving a new message", async () => {
const pyEnv = await startServer();
const [channelId_1, channelId_2] = pyEnv["discuss.channel"].create([
{ name: "Channel 2019" },
@@ -296,45 +293,56 @@ QUnit.test("thread notifications are re-ordered on receiving a new message", asy
]);
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsN($, ".o-mail-NotificationItem", 2);
await contains(".o-mail-NotificationItem", 2);
const channel_1 = pyEnv["discuss.channel"].searchRead([["id", "=", channelId_1]])[0];
await afterNextRender(() => {
pyEnv["bus.bus"]._sendone(channel_1, "discuss.channel/new_message", {
id: channelId_1,
message: {
author: { id: 7, name: "Demo User" },
body: "<p>New message !</p>",
date: "2020-03-23 10:00:00",
id: 44,
message_type: "comment",
model: "discuss.channel",
record_name: "Channel 2019",
res_id: channelId_1,
},
});
pyEnv["bus.bus"]._sendone(channel_1, "discuss.channel/new_message", {
id: channelId_1,
message: {
author: { id: 7, name: "Demo User" },
body: "<p>New message !</p>",
date: "2020-03-23 10:00:00",
id: 44,
message_type: "comment",
model: "discuss.channel",
record_name: "Channel 2019",
res_id: channelId_1,
},
});
assert.containsN($, ".o-mail-NotificationItem", 2);
assert.containsOnce(
$(".o-mail-NotificationItem:eq(0)"),
".o-mail-NotificationItem-name:contains(Channel 2019)"
await contains(".o-mail-NotificationItem", 2);
await contains(
".o-mail-NotificationItem:eq(0) .o-mail-NotificationItem-name:contains(Channel 2019)"
);
assert.containsOnce(
$(".o-mail-NotificationItem:eq(1)"),
".o-mail-NotificationItem-name:contains(Channel 2020)"
await contains(
".o-mail-NotificationItem:eq(1) .o-mail-NotificationItem-name:contains(Channel 2020)"
);
});
QUnit.test(
"messaging menu counter should ignore unread messages in channels that are unpinned",
async (assert) => {
patchWithCleanup(browser, {
Notification: {
...browser.Notification,
permission: "denied",
},
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({
is_pinned: false,
message_unread_counter: 1,
partner_id: pyEnv.currentPartnerId,
}),
Command.create({ partner_id: partnerId }),
],
});
pyEnv["mail.message"].create([
{
model: "discuss.channel",
res_id: channelId,
author_id: partnerId,
message_type: "email",
},
]);
await start();
assert.containsNone($, ".o-mail-MessagingMenu-counter-badge");
await contains(".o_menu_systray i[aria-label='Messages']");
await contains(".o-mail-MessagingMenu-counter", 0);
}
);
@@ -1,20 +1,20 @@
/* @odoo-module */
import { patchUiSize } from "@mail/../tests/helpers/patch_ui_size";
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("mobile");
QUnit.test("auto-select 'Inbox' when discuss had channel as active thread", async (assert) => {
QUnit.test("auto-select 'Inbox' when discuss had channel as active thread", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "test" });
patchUiSize({ height: 360, width: 640 });
const { openDiscuss } = await start();
await openDiscuss(channelId, { waitUntilMessagesLoaded: false });
assert.containsOnce($, ".o-mail-MessagingMenu-tab.text-primary.fw-bolder:contains(Channel)");
openDiscuss(channelId, { waitUntilMessagesLoaded: false });
await contains(".o-mail-MessagingMenu-tab.text-primary.fw-bolder:contains(Channel)");
await click("button:contains(Mailboxes)");
assert.containsOnce($, ".o-mail-MessagingMenu-tab.text-primary.fw-bolder:contains(Mailboxes)");
assert.containsOnce($, "button:contains(Inbox).active");
await contains(".o-mail-MessagingMenu-tab.text-primary.fw-bolder:contains(Mailboxes)");
await contains("button:contains(Inbox).active");
});
@@ -2,14 +2,9 @@
import { Composer } from "@mail/core/common/composer";
import { Command } from "@mail/../tests/helpers/command";
import { click, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import {
makeDeferred,
nextTick,
patchWithCleanup,
triggerHotkey,
} from "@web/../tests/helpers/utils";
import { makeDeferred, nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
QUnit.module("suggestion", {
async beforeEach() {
@@ -22,7 +17,7 @@ QUnit.module("suggestion", {
},
});
QUnit.test('display partner mention suggestions on typing "@"', async (assert) => {
QUnit.test('display partner mention suggestions on typing "@"', async () => {
const pyEnv = await startServer();
const partnerId_1 = pyEnv["res.partner"].create({
email: "testpartner@odoo.com",
@@ -42,16 +37,17 @@ QUnit.test('display partner mention suggestions on typing "@"', async (assert) =
],
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestion");
openDiscuss(channelId);
await contains(".o-mail-Composer-input");
await contains(".o-mail-Composer-suggestion", 0);
await insertText(".o-mail-Composer-input", "@");
assert.containsN($, ".o-mail-Composer-suggestion", 3);
await contains(".o-mail-Composer-suggestion", 3);
});
QUnit.test(
'post a first message then display partner mention suggestions on typing "@"',
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId_1 = pyEnv["res.partner"].create({
email: "testpartner@odoo.com",
@@ -71,28 +67,28 @@ QUnit.test(
],
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestion");
openDiscuss(channelId);
await contains(".o-mail-Composer-input");
await contains(".o-mail-Composer-suggestion", 0);
await insertText(".o-mail-Composer-input", "first message");
triggerHotkey("Enter");
await nextTick();
await click("button:contains(Send):not(:disabled)");
await contains(".o-mail-Message");
await insertText(".o-mail-Composer-input", "@");
assert.containsN($, ".o-mail-Composer-suggestion", 3);
await contains(".o-mail-Composer-suggestion", 3);
}
);
QUnit.test('display partner mention suggestions on typing "@" in chatter', async (assert) => {
QUnit.test('display partner mention suggestions on typing "@" in chatter', async () => {
const pyEnv = await startServer();
const { openFormView } = await start();
await openFormView("res.partner", pyEnv.currentPartnerId);
openFormView("res.partner", pyEnv.currentPartnerId);
await click("button:contains(Send message)");
assert.containsNone($, ".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-suggestion", 0);
await insertText(".o-mail-Composer-input", "@");
assert.containsOnce($, ".o-mail-Composer-suggestion:contains(Mitchell Admin)");
await contains(".o-mail-Composer-suggestion:contains(Mitchell Admin)");
});
QUnit.test("show other channel member in @ mention", async (assert) => {
QUnit.test("show other channel member in @ mention", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({
email: "testpartner@odoo.com",
@@ -106,9 +102,9 @@ QUnit.test("show other channel member in @ mention", async (assert) => {
],
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "@");
assert.containsOnce($, ".o-mail-Composer-suggestion:contains(TestPartner)");
await contains(".o-mail-Composer-suggestion:contains(TestPartner)");
});
QUnit.test("select @ mention insert mention text in composer", async (assert) => {
@@ -125,23 +121,25 @@ QUnit.test("select @ mention insert mention text in composer", async (assert) =>
],
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "@");
await click(".o-mail-Composer-suggestion:contains(TestPartner)");
await contains(".o-mail-Composer-suggestion", 0);
assert.strictEqual($(".o-mail-Composer-input").val().trim(), "@TestPartner");
});
QUnit.test('display channel mention suggestions on typing "#"', async (assert) => {
QUnit.test('display channel mention suggestions on typing "#"', async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
name: "General",
channel_type: "channel",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
openDiscuss(channelId);
await contains(".o-mail-Composer-suggestionList");
await contains(".o-mail-Composer-suggestionList .o-open", 0);
await insertText(".o-mail-Composer-input", "#");
assert.containsOnce($, ".o-mail-Composer-suggestionList .o-open");
await contains(".o-mail-Composer-suggestionList .o-open");
});
QUnit.test("mention a channel", async (assert) => {
@@ -151,12 +149,13 @@ QUnit.test("mention a channel", async (assert) => {
channel_type: "channel",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
openDiscuss(channelId);
await contains(".o-mail-Composer-suggestionList");
await contains(".o-mail-Composer-suggestionList .o-open", 0);
assert.strictEqual($(".o-mail-Composer-input").val(), "");
await insertText(".o-mail-Composer-input", "#");
assert.containsOnce($, ".o-mail-Composer-suggestion");
await click(".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-suggestion", 0);
assert.strictEqual(
$(".o-mail-Composer-input").val().replace(/\s/, " "),
"#General ",
@@ -179,7 +178,7 @@ QUnit.test("Channel suggestions do not crash after rpc returns", async (assert)
return originalFn(args, params);
},
});
await openDiscuss(channelId);
openDiscuss(channelId);
pyEnv["discuss.channel"].create({ name: "foo" });
insertText(".o-mail-Composer-input", "#");
await nextTick();
@@ -188,30 +187,59 @@ QUnit.test("Channel suggestions do not crash after rpc returns", async (assert)
assert.verifySteps(["get_mention_suggestions"]);
});
QUnit.test("Suggestions are shown after delimiter was used in text (@)", async (assert) => {
QUnit.test("Suggestions are shown after delimiter was used in text (@)", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "@");
assert.containsOnce($, ".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-suggestion");
await insertText(".o-mail-Composer-input", "NonExistingUser");
assert.containsNone($, ".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-suggestion", 0);
await insertText(".o-mail-Composer-input", " ");
await insertText(".o-mail-Composer-input", "@");
assert.containsOnce($, ".o-mail-Composer-suggestion:contains(Mitchell Admin)");
await contains(".o-mail-Composer-suggestion:contains(Mitchell Admin)");
});
QUnit.test("Suggestions are shown after delimiter was used in text (#)", async (assert) => {
QUnit.test("Suggestions are shown after delimiter was used in text (#)", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "#");
assert.containsOnce($, ".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-suggestion");
await insertText(".o-mail-Composer-input", "NonExistingChannel");
assert.containsNone($, ".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-suggestion", 0);
await insertText(".o-mail-Composer-input", " ");
await insertText(".o-mail-Composer-input", "#");
assert.containsOnce($, ".o-mail-Composer-suggestion:contains(General)");
await contains(".o-mail-Composer-suggestion:contains(General)");
});
QUnit.test("display partner mention when typing more than 2 words if they match", async () => {
const pyEnv = await startServer();
pyEnv["res.partner"].create([
{
email: "test1@example.com",
name: "My Best Partner",
},
{
email: "test2@example.com",
name: "My Test User",
},
{
email: "test3@example.com",
name: "My Test Partner",
},
]);
const { openFormView } = await start();
openFormView("res.partner", pyEnv.currentPartnerId);
await click("button:contains(Send message)");
await contains(".o-mail-Composer-suggestion", 0);
await insertText(".o-mail-Composer-input", "@My ");
await contains(".o-mail-Composer-suggestion", 3);
await insertText(".o-mail-Composer-input", "Test ");
await contains(".o-mail-Composer-suggestion", 2);
await insertText(".o-mail-Composer-input", "Partner");
await contains(".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-suggestion:contains(My Test Partner)");
});
@@ -1,7 +1,8 @@
/* @odoo-module */
import { afterNextRender, click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { getOrigin } from "@web/core/utils/urls";
import { nextTick } from "@web/../tests/helpers/utils";
QUnit.module("attachment list");
@@ -24,18 +25,18 @@ QUnit.test("simplest layout", async (assert) => {
message_type: "comment",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-Message .o-mail-AttachmentList");
openDiscuss(channelId);
await contains(".o-mail-Message .o-mail-AttachmentList");
assert.hasAttrValue($(".o-mail-AttachmentCard"), "title", "test.txt");
assert.containsOnce($, ".o-mail-AttachmentCard-image");
await contains(".o-mail-AttachmentCard-image");
assert.hasClass($(".o-mail-AttachmentCard-image"), "o_image"); // required for mimetype.scss style
assert.hasAttrValue($(".o-mail-AttachmentCard-image"), "data-mimetype", "text/plain"); // required for mimetype.scss style
assert.containsN($, ".o-mail-AttachmentCard-aside button", 2);
assert.containsOnce($, ".o-mail-AttachmentCard-unlink");
assert.containsOnce($, ".o-mail-AttachmentCard-aside button[title='Download']");
await contains(".o-mail-AttachmentCard-unlink");
await contains(".o-mail-AttachmentCard-aside button[title='Download']");
});
QUnit.test("layout with card details and filename and extension", async (assert) => {
QUnit.test("layout with card details and filename and extension", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
@@ -53,9 +54,9 @@ QUnit.test("layout with card details and filename and extension", async (assert)
message_type: "comment",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-AttachmentCard:contains('test.txt')");
assert.containsOnce($, ".o-mail-AttachmentCard small:contains('txt')");
openDiscuss(channelId);
await contains(".o-mail-AttachmentCard:contains('test.txt')");
await contains(".o-mail-AttachmentCard small:contains('txt')");
});
QUnit.test(
@@ -84,18 +85,17 @@ QUnit.test(
}
},
});
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-AttachmentCard-unlink");
await afterNextRender(() => {
$(".modal-footer .btn-primary")[0].click();
$(".modal-footer .btn-primary")[0].click();
$(".modal-footer .btn-primary")[0].click();
});
click(".modal-footer .btn-primary");
click(".modal-footer .btn-primary");
click(".modal-footer .btn-primary");
await contains(".o-mail-AttachmentCard-unlink", 0);
assert.verifySteps(["attachment_unlink"], "The unlink method must be called once");
}
);
QUnit.test("view attachment", async (assert) => {
QUnit.test("view attachment", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
@@ -113,13 +113,13 @@ QUnit.test("view attachment", async (assert) => {
message_type: "comment",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-AttachmentImage img");
openDiscuss(channelId);
await contains(".o-mail-AttachmentImage img");
await click(".o-mail-AttachmentImage");
assert.containsOnce($, ".o-FileViewer");
await contains(".o-FileViewer");
});
QUnit.test("close attachment viewer", async (assert) => {
QUnit.test("close attachment viewer", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
@@ -137,14 +137,14 @@ QUnit.test("close attachment viewer", async (assert) => {
message_type: "comment",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-AttachmentImage img");
openDiscuss(channelId);
await contains(".o-mail-AttachmentImage img");
await click(".o-mail-AttachmentImage");
assert.containsOnce($, ".o-FileViewer");
await contains(".o-FileViewer");
await click(".o-FileViewer div[aria-label='Close']");
assert.containsNone($, ".o-FileViewer");
await contains(".o-FileViewer", 0);
});
QUnit.test(
@@ -176,9 +176,9 @@ QUnit.test(
message_type: "comment",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await click(".o-mail-AttachmentImage");
const image = $(".o-FileViewer-viewImage")[0];
const image = (await contains(".o-FileViewer-viewImage"))[0];
await click(".o-FileViewer div[aria-label='Close']");
// Simulate image becoming loaded.
let successfulLoad;
@@ -193,7 +193,7 @@ QUnit.test(
}
);
QUnit.test("plain text file is viewable", async (assert) => {
QUnit.test("plain text file is viewable", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
@@ -211,11 +211,11 @@ QUnit.test("plain text file is viewable", async (assert) => {
message_type: "comment",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.hasClass($(".o-mail-AttachmentCard"), "o-viewable");
openDiscuss(channelId);
await contains(".o-mail-AttachmentCard.o-viewable");
});
QUnit.test("HTML file is viewable", async (assert) => {
QUnit.test("HTML file is viewable", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
@@ -233,11 +233,11 @@ QUnit.test("HTML file is viewable", async (assert) => {
message_type: "comment",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.hasClass($(".o-mail-AttachmentCard"), "o-viewable");
openDiscuss(channelId);
await contains(".o-mail-AttachmentCard.o-viewable");
});
QUnit.test("ODT file is not viewable", async (assert) => {
QUnit.test("ODT file is not viewable", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
@@ -255,11 +255,11 @@ QUnit.test("ODT file is not viewable", async (assert) => {
message_type: "comment",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.doesNotHaveClass($(".o-mail-AttachmentCard"), "o-viewable");
openDiscuss(channelId);
await contains(".o-mail-AttachmentCard:not(.o-viewable)");
});
QUnit.test("DOCX file is not viewable", async (assert) => {
QUnit.test("DOCX file is not viewable", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
@@ -277,8 +277,8 @@ QUnit.test("DOCX file is not viewable", async (assert) => {
message_type: "comment",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.doesNotHaveClass($(".o-mail-AttachmentCard"), "o-viewable");
openDiscuss(channelId);
await contains(".o-mail-AttachmentCard:not(.o-viewable)");
});
QUnit.test(
@@ -307,22 +307,22 @@ QUnit.test(
message_type: "comment",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-AttachmentImage[title='test.png']");
assert.containsOnce($, ".o-mail-AttachmentCard:contains(test.odt)");
openDiscuss(channelId);
await contains(".o-mail-AttachmentImage[title='test.png']");
await contains(".o-mail-AttachmentCard:contains(test.odt)");
assert.hasClass($(".o-mail-AttachmentImage[title='test.png'] img"), "o-viewable");
assert.doesNotHaveClass($(".o-mail-AttachmentCard:contains(test.odt)"), "o-viewable");
click(".o-mail-AttachmentCard:contains(test.odt)").catch(() => {});
await nextTick();
assert.containsNone($, ".o-FileViewer");
await contains(".o-FileViewer", 0);
await click(".o-mail-AttachmentImage[title='test.png']");
assert.containsOnce($, ".o-FileViewer");
await contains(".o-FileViewer");
}
);
QUnit.test("img file has proper src in discuss.channel", async (assert) => {
QUnit.test("img file has proper src in discuss.channel", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
@@ -342,10 +342,8 @@ QUnit.test("img file has proper src in discuss.channel", async (assert) => {
message_type: "comment",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.ok(
$(".o-mail-AttachmentImage[title='test.png'] img")
.data("src")
.includes(`/discuss/channel/${channelId}/image`)
openDiscuss(channelId);
await contains(
`.o-mail-AttachmentImage[title='test.png'] img[data-src='${getOrigin()}/discuss/channel/${channelId}/image/${attachmentId}?filename=test.png&width=1920&height=300']`
);
});
@@ -1,6 +1,6 @@
/* @odoo-module */
import { afterNextRender, click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { editInput } from "@web/../tests/helpers/utils";
import { file } from "@web/../tests/legacy/helpers/test_utils";
@@ -9,7 +9,7 @@ const { createFile } = file;
QUnit.module("file upload");
QUnit.test("no conflicts between file uploads", async (assert) => {
QUnit.test("no conflicts between file uploads", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const channelId = pyEnv["discuss.channel"].create({});
@@ -20,7 +20,7 @@ QUnit.test("no conflicts between file uploads", async (assert) => {
});
const { openView } = await start();
// Uploading file in the first thread: res.partner chatter.
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
@@ -31,9 +31,8 @@ QUnit.test("no conflicts between file uploads", async (assert) => {
content: "hello, world",
contentType: "text/plain",
});
await afterNextRender(() =>
editInput(document.body, ".o-mail-Chatter .o-mail-Composer input[type=file]", file1)
);
await contains(".o-mail-Chatter .o-mail-Composer input[type=file]");
editInput(document.body, ".o-mail-Chatter .o-mail-Composer input[type=file]", file1);
// Uploading file in the second thread: discuss.channel in chatWindow.
await click("i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
@@ -42,14 +41,13 @@ QUnit.test("no conflicts between file uploads", async (assert) => {
content: "hello, world",
contentType: "text/plain",
});
await afterNextRender(() =>
editInput(document.body, ".o-mail-ChatWindow input[type=file]", file2)
);
assert.containsOnce($, ".o-mail-Chatter .o-mail-AttachmentCard");
assert.containsOnce($, ".o-mail-ChatWindow .o-mail-AttachmentCard");
await contains(".o-mail-ChatWindow .o-mail-Composer input[type=file]");
editInput(document.body, ".o-mail-ChatWindow input[type=file]", file2);
await contains(".o-mail-Chatter .o-mail-AttachmentCard");
await contains(".o-mail-ChatWindow .o-mail-AttachmentCard");
});
QUnit.test("Attachment shows spinner during upload", async (assert) => {
QUnit.test("Attachment shows spinner during upload", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "channel_1" });
const { openDiscuss } = await start({
@@ -66,8 +64,6 @@ QUnit.test("Attachment shows spinner during upload", async (assert) => {
content: "hello, world",
contentType: "text/plain",
});
await afterNextRender(() =>
editInput(document.body, ".o-mail-Composer input[type=file]", file)
);
assert.containsOnce($, ".o-mail-AttachmentCard .fa-spinner");
editInput(document.body, ".o-mail-Composer input[type=file]", file);
await contains(".o-mail-AttachmentCard .fa-spinner");
});
+265 -260
View File
@@ -16,17 +16,15 @@ import {
import { config as transitionConfig } from "@web/core/transition";
import {
editInput,
makeDeferred,
nextTick,
patchWithCleanup,
triggerEvents,
triggerHotkey,
} from "@web/../tests/helpers/utils";
QUnit.module("thread");
QUnit.test("dragover files on thread with composer", async (assert) => {
QUnit.test("dragover files on thread with composer", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
@@ -34,12 +32,12 @@ QUnit.test("dragover files on thread with composer", async (assert) => {
name: "General",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
await afterNextRender(() => dragenterFiles($(".o-mail-Thread")[0]));
assert.containsOnce($, ".o-mail-Dropzone");
openDiscuss(channelId);
dragenterFiles((await contains(".o-mail-Thread"))[0]);
await contains(".o-mail-Dropzone");
});
QUnit.test("load more messages from channel (auto-load on scroll)", async (assert) => {
QUnit.test("load more messages from channel (auto-load on scroll)", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
@@ -54,55 +52,53 @@ QUnit.test("load more messages from channel (auto-load on scroll)", async (asser
});
}
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsN($, "button:contains(Load More) ~ .o-mail-Message", 30);
await afterNextRender(() => ($(".o-mail-Thread")[0].scrollTop = 0));
assert.containsN($, ".o-mail-Message", 60);
openDiscuss(channelId);
await contains("button:contains(Load More) ~ .o-mail-Message", 30);
/**
* The nextAnimationFrame is necessary because otherwise useAutoScroll would
* set the scroll to bottom after the manually set value from this test.
*/
await nextAnimationFrame();
$(".o-mail-Thread")[0].scrollTop = 0;
await contains(".o-mail-Message", 60);
});
QUnit.test(
"show message subject when subject is not the same as the thread name",
async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
group_public_id: false,
name: "General",
});
pyEnv["mail.message"].create({
body: "not empty",
model: "discuss.channel",
res_id: channelId,
subject: "Salutations, voyageur",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message:contains(Subject: Salutations, voyageur)");
}
);
QUnit.test("show message subject when subject is not the same as the thread name", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
group_public_id: false,
name: "General",
});
pyEnv["mail.message"].create({
body: "not empty",
model: "discuss.channel",
res_id: channelId,
subject: "Salutations, voyageur",
});
const { openDiscuss } = await start();
openDiscuss(channelId);
await contains(".o-mail-Message");
await contains(".o-mail-Message:contains(Subject: Salutations, voyageur)");
});
QUnit.test(
"do not show message subject when subject is the same as the thread name",
async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
group_public_id: false,
name: "Salutations, voyageur",
});
pyEnv["mail.message"].create({
body: "not empty",
model: "discuss.channel",
res_id: channelId,
subject: "Salutations, voyageur",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Message:contains(Salutations, voyageur)");
}
);
QUnit.test("do not show message subject when subject is the same as the thread name", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
group_public_id: false,
name: "Salutations, voyageur",
});
pyEnv["mail.message"].create({
body: "not empty",
model: "discuss.channel",
res_id: channelId,
subject: "Salutations, voyageur",
});
const { openDiscuss } = await start();
openDiscuss(channelId);
await contains(".o-mail-Message:not(:contains(Salutations, voyageur))");
});
QUnit.test("auto-scroll to bottom of thread on load", async (assert) => {
const pyEnv = await startServer();
@@ -115,12 +111,12 @@ QUnit.test("auto-scroll to bottom of thread on load", async (assert) => {
});
}
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsN($, ".o-mail-Message", 25);
openDiscuss(channelId);
await contains(".o-mail-Message", 25);
assert.ok(isScrolledToBottom($(".o-mail-Thread")[0]));
});
QUnit.test("display day separator before first message of the day", async (assert) => {
QUnit.test("display day separator before first message of the day", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
pyEnv["mail.message"].create([
@@ -136,11 +132,11 @@ QUnit.test("display day separator before first message of the day", async (asser
},
]);
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-Thread-date");
openDiscuss(channelId);
await contains(".o-mail-Thread-date");
});
QUnit.test("do not display day separator if all messages of the day are empty", async (assert) => {
QUnit.test("do not display day separator if all messages of the day are empty", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "" });
pyEnv["mail.message"].create({
@@ -149,8 +145,9 @@ QUnit.test("do not display day separator if all messages of the day are empty",
res_id: channelId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Thread-date");
openDiscuss(channelId);
await contains(".o-mail-Thread-empty");
await contains(".o-mail-Thread-date", 0);
});
QUnit.test(
@@ -172,15 +169,29 @@ QUnit.test(
}))
);
const { openDiscuss } = await start();
await openDiscuss(channelId_1);
openDiscuss(channelId_1);
await contains(".o-mail-Thread");
/**
* The nextAnimationFrame is necessary because otherwise useAutoScroll would
* set the scroll to bottom after the manually set value from this test.
*/
await nextAnimationFrame();
const scrolltop_1 = $(".o-mail-Thread")[0].scrollHeight / 2;
$(".o-mail-Thread")[0].scrollTo({ top: scrolltop_1 });
await click(".o-mail-DiscussSidebarChannel:contains(channel-2)");
await contains(".o-mail-DiscussSidebarChannel:contains(channel-2).o-active");
/**
* The nextAnimationFrame is necessary because otherwise useAutoScroll would
* set the scroll to bottom after the manually set value from this test.
*/
await nextAnimationFrame();
const scrolltop_2 = $(".o-mail-Thread")[0].scrollHeight / 3;
$(".o-mail-Thread")[0].scrollTo({ top: scrolltop_2 });
await click(".o-mail-DiscussSidebarChannel:contains(channel-1)");
await contains(".o-mail-DiscussSidebarChannel:contains(channel-1).o-active");
assert.ok(isScrolledTo($(".o-mail-Thread")[0], scrolltop_1));
await click(".o-mail-DiscussSidebarChannel:contains(channel-2)");
await contains(".o-mail-DiscussSidebarChannel:contains(channel-2).o-active");
assert.ok(isScrolledTo($(".o-mail-Thread")[0], scrolltop_2));
}
);
@@ -199,11 +210,19 @@ QUnit.test("thread is still scrolling after scrolling up then to bottom", async
}))
);
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await contains(".o-mail-Thread");
/**
* The nextAnimationFrame is necessary because otherwise useAutoScroll would
* set the scroll to bottom after the manually set value from this test.
*/
await nextAnimationFrame();
assert.ok(isScrolledToBottom($(".o-mail-Thread")[0]));
$(".o-mail-Thread")[0].scrollTo({ top: $(".o-mail-Thread")[0].scrollHeight / 2 });
$(".o-mail-Thread")[0].scrollTo({ top: $(".o-mail-Thread")[0].scrollHeight });
await insertText(".o-mail-Composer-input", "123");
await click(".o-mail-Composer-send");
await click(".o-mail-Composer-send:not(:disabled)");
await contains(".o-mail-Message", 21);
assert.ok(isScrolledToBottom($(".o-mail-Thread")[0]));
});
@@ -211,11 +230,12 @@ QUnit.test("mention a channel with space in the name", async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General good boy" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "#");
await click(".o-mail-Composer-suggestion");
await click(".o-mail-Composer-send");
assert.containsOnce($(".o-mail-Message-body"), ".o_channel_redirect");
await contains(".o-mail-Composer-suggestion", 0);
await click(".o-mail-Composer-send:not(:disabled)");
await contains(".o-mail-Message-body .o_channel_redirect");
assert.strictEqual($(".o_channel_redirect").text(), "#General good boy");
});
@@ -223,11 +243,12 @@ QUnit.test('mention a channel with "&" in the name', async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General & good" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "#");
await click(".o-mail-Composer-suggestion");
await click(".o-mail-Composer-send");
assert.containsOnce($(".o-mail-Message-body"), ".o_channel_redirect");
await contains(".o-mail-Composer-suggestion", 0);
await click(".o-mail-Composer-send:not(:disabled)");
await contains(".o-mail-Message-body .o_channel_redirect");
assert.strictEqual($(".o_channel_redirect").text(), "#General & good");
});
@@ -302,8 +323,8 @@ QUnit.test(
}
},
});
await openDiscuss(channelId);
$(".o-mail-Composer-input")[0].focus();
openDiscuss(channelId);
(await contains(".o-mail-Composer-input"))[0].focus();
// simulate receiving a message
await pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
@@ -339,7 +360,7 @@ QUnit.test(
const { env } = await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
assert.ok(isScrolledToBottom($(".o-mail-Thread")[0]));
assert.ok(isScrolledToBottom((await contains(".o-mail-Thread"))[0]));
// simulate receiving a message
await afterNextRender(() =>
@@ -376,13 +397,13 @@ QUnit.test(
}
const { env } = await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
assert.ok(isScrolledToBottom($(".o-mail-Thread")[0]));
/**
* The afterNextRender is necessary because otherwise useAutoScroll would
* set the scroll to bottom after the manually set value from this test.
*/
await afterNextRender(() => click(".o-mail-NotificationItem"));
assert.ok(isScrolledToBottom((await contains(".o-mail-Thread"))[0]));
$(".o-mail-Thread").scrollTop(0);
await nextAnimationFrame();
assert.strictEqual($(".o-mail-Thread")[0].scrollTop, 0);
// simulate receiving a message
await afterNextRender(() =>
pyEnv.withUser(userId, () =>
@@ -397,23 +418,23 @@ QUnit.test(
}
);
QUnit.test("show empty placeholder when thread contains no message", async (assert) => {
QUnit.test("show empty placeholder when thread contains no message", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-Thread:contains(There are no messages in this conversation.)");
assert.containsNone($, ".o-mail-Message");
openDiscuss(channelId);
await contains(".o-mail-Thread:contains(There are no messages in this conversation.)");
await contains(".o-mail-Message", 0);
});
QUnit.test("show empty placeholder when thread contains only empty messages", async (assert) => {
QUnit.test("show empty placeholder when thread contains only empty messages", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
pyEnv["mail.message"].create({ model: "discuss.channel", res_id: channelId });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-Thread:contains(There are no messages in this conversation.)");
assert.containsNone($, ".o-mail-Message");
openDiscuss(channelId);
await contains(".o-mail-Thread:contains(There are no messages in this conversation.)");
await contains(".o-mail-Message", 0);
});
QUnit.test(
@@ -436,10 +457,10 @@ QUnit.test(
pyEnv["mail.message"].create({ model: "discuss.channel", res_id: channelId });
}
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
// initial load: +30 empty ; (auto) load more: +20 empty +10 non-empty
assert.containsN($, ".o-mail-Message", 10);
assert.containsOnce($, "button:contains(Load More)"); // still 40 non-empty
await contains(".o-mail-Message", 10);
await contains("button:contains(Load More)"); // still 40 non-empty
}
);
@@ -465,22 +486,20 @@ QUnit.test(
]);
pyEnv["discuss.channel.member"].write([memberId], { seen_message_id: messageId });
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-Message");
assert.containsNone($, "hr + span:contains(New messages)");
openDiscuss(channelId);
await contains(".o-mail-Message");
await contains("hr + span:contains(New messages)", 0);
await insertText(".o-mail-Composer-input", "hey!");
await afterNextRender(() => {
// need to remove focus from text area to avoid set_last_seen_message
$(".o-mail-Composer-send")[0].focus();
$(".o-mail-Composer-send")[0].click();
});
assert.containsN($, ".o-mail-Message", 2);
assert.containsNone($, "hr + span:contains(New messages)");
// need to remove focus from text area to avoid set_last_seen_message
(await contains(".o-mail-Composer-send:not(:disabled)"))[0].focus();
await click(".o-mail-Composer-send:not(:disabled)");
await contains(".o-mail-Message", 2);
await contains("hr + span:contains(New messages)", 0);
}
);
QUnit.test("new messages separator on receiving new message [REQUIRE FOCUS]", async (assert) => {
QUnit.test("new messages separator on receiving new message [REQUIRE FOCUS]", async () => {
patchWithCleanup(transitionConfig, { disabled: true });
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Foreigner partner" });
@@ -507,9 +526,9 @@ QUnit.test("new messages separator on receiving new message [REQUIRE FOCUS]", as
]);
pyEnv["discuss.channel.member"].write([memberId], { seen_message_id: messageId });
const { env, openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-Message", "should have an initial message");
assert.containsNone($, "hr + span:contains(New messages)");
openDiscuss(channelId);
await contains(".o-mail-Message");
await contains("hr + span:contains(New messages)", 0);
$(".o-mail-Composer-input")[0].blur();
// simulate receiving a message
@@ -522,16 +541,16 @@ QUnit.test("new messages separator on receiving new message [REQUIRE FOCUS]", as
})
)
);
assert.containsN($, ".o-mail-Message", 2);
assert.containsOnce($, "hr + span:contains(New messages)");
assert.containsOnce($, ".o-mail-Thread-newMessage ~ .o-mail-Message:contains(hu)");
await contains(".o-mail-Message", 2);
await contains("hr + span:contains(New messages)");
await contains(".o-mail-Thread-newMessage ~ .o-mail-Message:contains(hu)");
$(".o-mail-Composer-input")[0].focus();
await nextTick();
assert.containsNone($, "hr + span:contains(New messages)");
await contains("hr + span:contains(New messages)", 0);
});
QUnit.test("no new messages separator on posting message (no message history)", async (assert) => {
QUnit.test("no new messages separator on posting message (no message history)", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
@@ -541,14 +560,15 @@ QUnit.test("no new messages separator on posting message (no message history)",
name: "General",
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsNone($, ".o-mail-Message");
assert.containsNone($, "hr + span:contains(New messages)");
openDiscuss(channelId);
await contains(".o-mail-Composer-input");
await contains(".o-mail-Message", 0);
await contains("hr + span:contains(New messages)", 0);
await insertText(".o-mail-Composer-input", "hey!");
await click(".o-mail-Composer-send");
assert.containsOnce($, ".o-mail-Message");
assert.containsNone($, "hr + span:contains(New messages)");
await click(".o-mail-Composer-send:not(:disabled)");
await contains(".o-mail-Message");
await contains("hr + span:contains(New messages)", 0);
});
QUnit.test("Mention a partner with special character (e.g. apostrophe ')", async (assert) => {
@@ -565,18 +585,18 @@ QUnit.test("Mention a partner with special character (e.g. apostrophe ')", async
],
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input", "Pyn");
await click(".o-mail-Composer-suggestion");
await click(".o-mail-Composer-send");
assert.containsOnce(
$(".o-mail-Message-body"),
`.o_mail_redirect[data-oe-id="${partnerId}"][data-oe-model="res.partner"]:contains("@Pynya's spokesman")`
await contains(".o-mail-Composer-suggestion", 0);
await click(".o-mail-Composer-send:not(:disabled)");
await contains(
`.o-mail-Message-body .o_mail_redirect[data-oe-id="${partnerId}"][data-oe-model="res.partner"]:contains("@Pynya's spokesman")`
);
});
QUnit.test("mention 2 different partners that have the same name", async (assert) => {
QUnit.test("mention 2 different partners that have the same name", async () => {
const pyEnv = await startServer();
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
{
@@ -597,22 +617,19 @@ QUnit.test("mention 2 different partners that have the same name", async (assert
],
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input", "Te");
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "@Te");
await click(".o-mail-Composer-suggestion:eq(0)");
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input", "Te");
await contains(".o-mail-Composer-suggestion", 0);
await insertText(".o-mail-Composer-input", "@Te");
await click(".o-mail-Composer-suggestion:eq(1)");
await click(".o-mail-Composer-send");
assert.containsOnce($, ".o-mail-Message-body");
assert.containsOnce(
$(".o-mail-Message-body"),
`.o_mail_redirect[data-oe-id="${partnerId_1}"][data-oe-model="res.partner"]:contains("@TestPartner")`
await contains(".o-mail-Composer-suggestion", 0);
await click(".o-mail-Composer-send:not(:disabled)");
await contains(
`.o-mail-Message-body .o_mail_redirect[data-oe-id="${partnerId_1}"][data-oe-model="res.partner"]:contains("@TestPartner")`
);
assert.containsOnce(
$(".o-mail-Message-body"),
`.o_mail_redirect[data-oe-id="${partnerId_2}"][data-oe-model="res.partner"]:contains("@TestPartner")`
await contains(
`.o-mail-Message-body .o_mail_redirect[data-oe-id="${partnerId_2}"][data-oe-model="res.partner"]:contains("@TestPartner")`
);
});
@@ -620,34 +637,34 @@ QUnit.test("mention a channel on a second line when the first line contains #",
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General good" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "#blabla\n");
await insertText(".o-mail-Composer-input", "#");
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "#blabla\n#");
await click(".o-mail-Composer-suggestion");
await click(".o-mail-Composer-send");
assert.containsOnce($(".o-mail-Message-body"), ".o_channel_redirect");
await contains(".o-mail-Composer-suggestion", 0);
await click(".o-mail-Composer-send:not(:disabled)");
await contains(".o-mail-Message-body .o_channel_redirect");
assert.strictEqual($(".o_channel_redirect").text(), "#General good");
});
QUnit.test(
"mention a channel when replacing the space after the mention by another char",
async (assert) => {
async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General good" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "#");
await click(".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-suggestion", 0);
const text = $(".o-mail-Composer-input").val();
$(".o-mail-Composer-input").val(text.slice(0, -1));
await insertText(".o-mail-Composer-input", ", test");
await click(".o-mail-Composer-send");
assert.containsOnce($(".o-mail-Message-body"), ".o_channel_redirect");
assert.strictEqual($(".o_channel_redirect").text(), "#General good");
await click(".o-mail-Composer-send:not(:disabled)");
await contains(".o-mail-Message-body .o_channel_redirect:contains(#General good)");
}
);
QUnit.test("mention 2 different channels that have the same name", async (assert) => {
QUnit.test("mention 2 different channels that have the same name", async () => {
const pyEnv = await startServer();
const [channelId_1, channelId_2] = pyEnv["discuss.channel"].create([
{
@@ -661,28 +678,25 @@ QUnit.test("mention 2 different channels that have the same name", async (assert
},
]);
const { openDiscuss } = await start();
await openDiscuss(channelId_1);
await insertText(".o-mail-Composer-input", "#");
await insertText(".o-mail-Composer-input", "m");
openDiscuss(channelId_1);
await insertText(".o-mail-Composer-input", "#m");
await click(".o-mail-Composer-suggestion:eq(0)");
await insertText(".o-mail-Composer-input", "#");
await insertText(".o-mail-Composer-input", "m");
await contains(".o-mail-Composer-suggestion", 0);
await insertText(".o-mail-Composer-input", "#m");
await click(".o-mail-Composer-suggestion:eq(1)");
await click(".o-mail-Composer-send");
assert.containsOnce($, ".o-mail-Message-body");
assert.containsOnce(
$(".o-mail-Message-body"),
`.o_channel_redirect[data-oe-id="${channelId_1}"][data-oe-model="discuss.channel"]:contains("#my channel")`
await contains(".o-mail-Composer-suggestion", 0);
await click(".o-mail-Composer-send:not(:disabled)");
await contains(
`.o-mail-Message-body .o_channel_redirect[data-oe-id="${channelId_1}"][data-oe-model="discuss.channel"]:contains("#my channel")`
);
assert.containsOnce(
$(".o-mail-Message-body"),
`.o_channel_redirect[data-oe-id="${channelId_2}"][data-oe-model="discuss.channel"]:contains("#my channel")`
await contains(
`.o-mail-Message-body .o_channel_redirect[data-oe-id="${channelId_2}"][data-oe-model="discuss.channel"]:contains("#my channel")`
);
});
QUnit.test(
"Post a message containing an email address followed by a mention on another line",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({
email: "testpartner@odoo.com",
@@ -696,20 +710,18 @@ QUnit.test(
],
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "email@odoo.com\n");
await insertText(".o-mail-Composer-input", "@");
await insertText(".o-mail-Composer-input", "Te");
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "email@odoo.com\n@Te");
await click(".o-mail-Composer-suggestion");
await click(".o-mail-Composer-send");
assert.containsOnce(
$(".o-mail-Message-body"),
`.o_mail_redirect[data-oe-id="${partnerId}"][data-oe-model="res.partner"]:contains("@TestPartner")`
await contains(".o-mail-Composer-suggestion", 0);
await click(".o-mail-Composer-send:not(:disabled)");
await contains(
`.o-mail-Message-body .o_mail_redirect[data-oe-id="${partnerId}"][data-oe-model="res.partner"]:contains("@TestPartner")`
);
}
);
QUnit.test("basic rendering of canceled notification", async (assert) => {
QUnit.test("basic rendering of canceled notification", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "test" });
const partnerId = pyEnv["res.partner"].create({ name: "Someone" });
@@ -727,18 +739,18 @@ QUnit.test("basic rendering of canceled notification", async (assert) => {
res_partner_id: partnerId,
});
const { openDiscuss } = await start();
await openDiscuss(channelId);
assert.containsOnce($, ".o-mail-Message-notification .fa-envelope-o");
openDiscuss(channelId);
await contains(".o-mail-Message-notification .fa-envelope-o");
await click(".o-mail-Message-notification");
assert.containsOnce($, ".o-mail-MessageNotificationPopover");
assert.containsOnce($, ".o-mail-MessageNotificationPopover .fa-trash-o");
assert.containsOnce($, ".o-mail-MessageNotificationPopover:contains(Someone)");
await contains(".o-mail-MessageNotificationPopover");
await contains(".o-mail-MessageNotificationPopover .fa-trash-o");
await contains(".o-mail-MessageNotificationPopover:contains(Someone)");
});
QUnit.test(
"first unseen message should be directly preceded by the new message separator if there is a transient message just before it while composer is not focused [REQUIRE FOCUS]",
async (assert) => {
async () => {
// The goal of removing the focus is to ensure the thread is not marked as seen automatically.
// Indeed that would trigger set_last_seen_message no matter what, which is already covered by other tests.
// The goal of this test is to cover the conditions specific to transient messages,
@@ -766,25 +778,24 @@ QUnit.test(
},
]);
const { openDiscuss, env } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
// send a command that leads to receiving a transient message
await insertText(".o-mail-Composer-input", "/who");
await click(".o-mail-Composer-send");
await click(".o-mail-Composer-send:not(:disabled)");
await contains(".o-mail-Message", 2);
// composer is focused by default, we remove that focus
$(".o-mail-Composer-input")[0].blur();
// simulate receiving a message
await afterNextRender(() =>
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "test", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
)
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "test", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
);
assert.containsN($, ".o-mail-Message", 3);
assert.containsOnce($, "hr + span:contains(New messages)");
assert.containsOnce($, ".o-mail-Message[aria-label='Note'] + .o-mail-Thread-newMessage");
await contains(".o-mail-Message", 3);
await contains("hr + span:contains(New messages)");
await contains(".o-mail-Message[aria-label='Note'] + .o-mail-Thread-newMessage");
}
);
@@ -794,39 +805,36 @@ QUnit.test(
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "test" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "Dummy Message");
await click(".o-mail-Composer-send");
await click(".o-mail-Composer-send:not(:disabled)");
assert.strictEqual(document.activeElement, $(".o-mail-Composer-input")[0]);
}
);
QUnit.test(
"chat window header should not have unread counter for non-channel thread",
async (assert) => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "test" });
const messageId = pyEnv["mail.message"].create({
author_id: partnerId,
body: "not empty",
model: "res.partner",
needaction: true,
needaction_partner_ids: [pyEnv.currentPartnerId],
res_id: partnerId,
});
pyEnv["mail.notification"].create({
mail_message_id: messageId,
notification_status: "sent",
notification_type: "inbox",
res_partner_id: pyEnv.currentPartnerId,
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem-name");
assert.containsOnce($, ".o-mail-ChatWindow");
assert.containsNone($, ".o-mail-ChatWindow-header:contains('(1)')");
}
);
QUnit.test("chat window header should not have unread counter for non-channel thread", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "test" });
const messageId = pyEnv["mail.message"].create({
author_id: partnerId,
body: "not empty",
model: "res.partner",
needaction: true,
needaction_partner_ids: [pyEnv.currentPartnerId],
res_id: partnerId,
});
pyEnv["mail.notification"].create({
mail_message_id: messageId,
notification_status: "sent",
notification_type: "inbox",
res_partner_id: pyEnv.currentPartnerId,
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem-name");
await contains(".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow-header:contains('(1)')", 0);
});
QUnit.test(
"[technical] opening a non-channel chat window should not call channel_fold",
@@ -861,11 +869,11 @@ QUnit.test(
},
});
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-NotificationItem");
assert.containsNone($, ".o-mail-ChatWindow");
await contains(".o-mail-NotificationItem");
await contains(".o-mail-ChatWindow", 0);
await click(".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
}
);
@@ -893,11 +901,11 @@ QUnit.test("Thread messages are only loaded once", async (assert) => {
},
]);
openDiscuss();
(await contains(".o-mail-DiscussSidebarChannel:eq(0)")).click();
await click(".o-mail-DiscussSidebarChannel:eq(0)");
await contains(".o-mail-Message:contains(channel1)");
(await contains(".o-mail-DiscussSidebarChannel:eq(1)")).click();
await click(".o-mail-DiscussSidebarChannel:eq(1)");
await contains(".o-mail-Message:contains(channel2)");
(await contains(".o-mail-DiscussSidebarChannel:eq(0)")).click();
await click(".o-mail-DiscussSidebarChannel:eq(0)");
await contains(".o-mail-Message:contains(channel1)");
assert.verifySteps([`load messages - ${channelIds[0]}`, `load messages - ${channelIds[1]}`]);
});
@@ -919,10 +927,11 @@ QUnit.test(
}
},
});
await openDiscuss(channelId);
openDiscuss(channelId);
// ensure focusout is triggered on composers' textarea
await triggerEvents($(".o-mail-Composer-input")[0], null, ["blur", "focusout"]);
triggerEvents((await contains(".o-mail-Composer-input"))[0], null, ["blur", "focusout"]);
await click("button:contains(Inbox)");
await contains("h4:contains(Congratulations, your inbox is empty)");
const messageId = pyEnv["mail.message"].create({
author_id: partnerId,
body: "@Mitchel Admin",
@@ -941,10 +950,7 @@ QUnit.test(
const [formattedMessage] = await env.services.orm.call("mail.message", "message_format", [
[messageId],
]);
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "discuss.channel/new_message", {
id: channelId,
message: formattedMessage,
});
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.message/inbox", formattedMessage);
await contains("button:contains(Inbox) .badge:contains(1)");
click("button:contains(General)");
await contains(".o-discuss-badge", 0);
@@ -965,7 +971,7 @@ QUnit.test(
}
},
});
await openDiscuss(channelId);
openDiscuss(channelId);
await click("button:contains(Inbox)");
await env.services.rpc("/mail/message/post", {
post_data: {
@@ -981,7 +987,7 @@ QUnit.test(
}
);
QUnit.test("can be marked as read while loading", async function (assert) {
QUnit.test("can be marked as read while loading", async function () {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const channelId = pyEnv["discuss.channel"].create({
@@ -1005,47 +1011,46 @@ QUnit.test("can be marked as read while loading", async function (assert) {
}
},
});
await openDiscuss(undefined, { waitUntilMessagesLoaded: false });
assert.containsOnce($, ".o-discuss-badge:contains(1)");
openDiscuss(undefined, { waitUntilMessagesLoaded: false });
await contains(".o-discuss-badge:contains(1)");
await click(".o-mail-DiscussSidebarChannel:contains(Demo)");
await afterNextRender(loadDeferred.resolve);
assert.containsNone($, ".o-discuss-badge");
await contains(".o-discuss-badge", 0);
});
QUnit.test(
"New message separator not appearing after showing composer on thread",
async (assert) => {
const pyEnv = await startServer();
pyEnv["mail.message"].create([
{
model: "res.partner",
res_id: pyEnv.currentPartnerId,
body: "Message on partner",
},
{
model: "res.partner",
res_id: pyEnv.currentPartnerId,
body: "Message on partner",
},
]);
const { openFormView } = await start();
await openFormView("res.partner", pyEnv.currentPartnerId);
assert.containsNone($, ".o-mail-Thread-newMessage");
await click("button:contains(Log note)");
assert.containsNone($, ".o-mail-Thread-newMessage");
}
);
QUnit.test("New message separator not appearing after showing composer on thread", async () => {
const pyEnv = await startServer();
pyEnv["mail.message"].create([
{
model: "res.partner",
res_id: pyEnv.currentPartnerId,
body: "Message on partner",
},
{
model: "res.partner",
res_id: pyEnv.currentPartnerId,
body: "Message on partner",
},
]);
const { openFormView } = await start();
openFormView("res.partner", pyEnv.currentPartnerId);
await contains("button:contains(Log note)");
await contains(".o-mail-Thread-newMessage", 0);
await click("button:contains(Log note)");
await contains(".o-mail-Thread-newMessage", 0);
});
QUnit.test("Transient messages are added at the end of the thread", async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { openDiscuss } = await start();
await openDiscuss(channelId);
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "Dummy Message");
await click(".o-mail-Composer-send");
assert.containsOnce($, ".o-mail-Message");
await editInput(document.body, ".o-mail-Composer-input", "/help");
await afterNextRender(() => triggerHotkey("Enter"));
await click(".o-mail-Composer-send:not(:disabled)");
await contains(".o-mail-Message");
await insertText(".o-mail-Composer-input", "/help");
await click(".o-mail-Composer-send:not(:disabled)");
await contains(".o-mail-Message", 2);
const lastMessage = document.querySelectorAll(".o-mail-Message")[1];
assert.ok(lastMessage.innerText.includes("You are in channel #General"));
});
@@ -64,7 +64,7 @@ registry.category("web_tour.tours").add("mail/static/tests/tours/discuss_public_
},
{
content: "Send message",
trigger: ".o-mail-Composer-send",
trigger: ".o-mail-Composer-send:not(:disabled)",
},
{
content: "Check message is shown",
@@ -1,6 +1,6 @@
/* @odoo-module */
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { ROUTES_TO_IGNORE } from "@mail/../tests/helpers/webclient_setup";
import { ListController } from "@web/views/list/list_controller";
@@ -41,7 +41,7 @@ QUnit.test("list activity widget with no activity", async (assert) => {
views: [[false, "list"]],
});
assert.containsOnce($, ".o-mail-ActivityButton i.text-muted");
assert.strictEqual($(".o-mail-ListActivity-summary")[0].innerText, "");
await contains(".o-mail-ListActivity-summary:eq(0):contains()");
assert.verifySteps(["/web/dataset/call_kw/res.users/unity_web_search_read"]);
});
@@ -141,7 +141,7 @@ QUnit.test("list activity widget with exception", async (assert) => {
views: [[false, "list"]],
});
assert.containsOnce($, ".o-mail-ActivityButton i.text-warning.fa-warning");
assert.strictEqual($(".o-mail-ListActivity-summary")[0].innerText, "Warning");
await contains(".o-mail-ListActivity-summary:eq(0):contains(Warning)");
assert.verifySteps(["/web/dataset/call_kw/res.users/unity_web_search_read"]);
});
@@ -220,12 +220,12 @@ QUnit.test("list activity widget: open dropdown", async (assert) => {
res_model: "res.users",
views: [[false, "list"]],
});
assert.strictEqual($(".o-mail-ListActivity-summary")[0].innerText, "Call with Al");
await contains(".o-mail-ListActivity-summary:eq(0):contains(Call with Al)");
await click(".o-mail-ActivityButton"); // open the popover
await click(".o-mail-ActivityListPopoverItem-markAsDone"); // mark the first activity as done
await click(".o-mail-ActivityListPopoverItem-markAsDone:eq(0)"); // mark the first activity as done
await click(".o-mail-ActivityMarkAsDone button[aria-label='Done']"); // confirm
assert.strictEqual($(".o-mail-ListActivity-summary")[0].innerText, "Meet FP");
await contains(".o-mail-ListActivity-summary:eq(0):contains(Meet FP)");
assert.verifySteps(["unity_web_search_read", "activity_format", "action_feedback", "web_read"]);
});
@@ -1,11 +1,17 @@
/* @odoo-module */
import { patchUiSize, SIZES } from "@mail/../tests/helpers/patch_ui_size";
import { click, nextAnimationFrame, start, startServer } from "@mail/../tests/helpers/test_utils";
import {
click,
contains,
nextAnimationFrame,
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
QUnit.module("attachment box");
QUnit.test("base empty rendering", async (assert) => {
QUnit.test("base empty rendering", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const views = {
@@ -19,17 +25,17 @@ QUnit.test("base empty rendering", async (assert) => {
`,
};
const { openView } = await start({ serverData: { views } });
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-AttachmentBox");
assert.containsOnce($, "button:contains('Attach files')");
assert.containsNone($, ".o-mail-Chatter .o-mail-AttachmentImage");
await contains(".o-mail-AttachmentBox");
await contains("button:contains('Attach files')");
await contains(".o-mail-Chatter .o-mail-AttachmentImage", 0);
});
QUnit.test("base non-empty rendering", async (assert) => {
QUnit.test("base non-empty rendering", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["ir.attachment"].create([
@@ -57,18 +63,18 @@ QUnit.test("base non-empty rendering", async (assert) => {
`,
};
const { openView } = await start({ serverData: { views } });
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-AttachmentBox");
assert.containsOnce($, "button:contains('Attach files')");
assert.containsOnce($, ".o-mail-Chatter input[type='file']");
assert.containsOnce($, ".o-mail-AttachmentList");
await contains(".o-mail-AttachmentBox");
await contains("button:contains('Attach files')");
await contains(".o-mail-Chatter input[type='file']");
await contains(".o-mail-AttachmentList");
});
QUnit.test("remove attachment should ask for confirmation", async (assert) => {
QUnit.test("remove attachment should ask for confirmation", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["ir.attachment"].create({
@@ -88,23 +94,23 @@ QUnit.test("remove attachment should ask for confirmation", async (assert) => {
`,
};
const { openView } = await start({ serverData: { views } });
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-AttachmentCard");
assert.containsOnce($, "button[title='Remove']");
await contains(".o-mail-AttachmentCard");
await contains("button[title='Remove']");
await click("button[title='Remove']");
assert.containsOnce($, ".modal-body:contains('Do you really want to delete \"Blah.txt\"?')");
await contains(".modal-body:contains('Do you really want to delete \"Blah.txt\"?')");
// Confirm the deletion
await click(".modal-footer .btn-primary");
assert.containsNone($, ".o-mail-AttachmentImage");
await contains(".o-mail-AttachmentImage", 0);
});
QUnit.test("view attachments", async (assert) => {
QUnit.test("view attachments", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["ir.attachment"].create([
@@ -130,22 +136,22 @@ QUnit.test("view attachments", async (assert) => {
</form>`,
};
const { openView } = await start({ serverData: { views } });
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
await click('.o-mail-AttachmentCard[aria-label="Blah.txt"] .o-mail-AttachmentCard-image');
assert.containsOnce($, ".o-FileViewer");
assert.containsOnce($, ".o-FileViewer-header:contains(Blah.txt)");
assert.containsOnce($, ".o-FileViewer div[aria-label='Next']");
await contains(".o-FileViewer");
await contains(".o-FileViewer-header:contains(Blah.txt)");
await contains(".o-FileViewer div[aria-label='Next']");
await click(".o-FileViewer div[aria-label='Next']");
assert.containsOnce($, ".o-FileViewer-header:contains(Blu.txt)");
assert.containsOnce($, ".o-FileViewer div[aria-label='Next']");
await contains(".o-FileViewer-header:contains(Blu.txt)");
await contains(".o-FileViewer div[aria-label='Next']");
await click(".o-FileViewer div[aria-label='Next']");
assert.containsOnce($, ".o-FileViewer-header:contains(Blah.txt)");
await contains(".o-FileViewer-header:contains(Blah.txt)");
});
QUnit.test("scroll to attachment box when toggling on", async (assert) => {
@@ -166,7 +172,7 @@ QUnit.test("scroll to attachment box when toggling on", async (assert) => {
res_model: "res.partner",
});
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
@@ -177,7 +183,7 @@ QUnit.test("scroll to attachment box when toggling on", async (assert) => {
assert.isVisible($(".o-mail-AttachmentBox"));
});
QUnit.test("attachment box should order attachments from newest to oldest", async (assert) => {
QUnit.test("attachment box should order attachments from newest to oldest", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const resData = { res_id: partnerId, res_model: "res.partner" };
@@ -187,14 +193,14 @@ QUnit.test("attachment box should order attachments from newest to oldest", asyn
{ name: "C.txt", mimetype: "text/plain", ...resData },
]);
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Chatter [aria-label='Attach files']:contains(3)");
await contains(".o-mail-Chatter [aria-label='Attach files']:contains(3)");
await click(".o-mail-Chatter [aria-label='Attach files']"); // open attachment box
assert.containsOnce($, ".o-mail-AttachmentCard:eq(0):contains(C.txt)");
assert.containsOnce($, ".o-mail-AttachmentCard:eq(1):contains(B.txt)");
assert.containsOnce($, ".o-mail-AttachmentCard:eq(2):contains(A.txt)");
await contains(".o-mail-AttachmentCard:eq(0):contains(C.txt)");
await contains(".o-mail-AttachmentCard:eq(1):contains(B.txt)");
await contains(".o-mail-AttachmentCard:eq(2):contains(A.txt)");
});
+186 -163
View File
@@ -9,12 +9,13 @@ import {
dropFiles,
insertText,
isScrolledTo,
nextAnimationFrame,
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
import { DELAY_FOR_SPINNER } from "@mail/core/web/chatter";
import { editInput, triggerHotkey } from "@web/../tests/helpers/utils";
import { triggerHotkey } from "@web/../tests/helpers/utils";
import { file } from "@web/../tests/legacy/helpers/test_utils";
const { createFile } = file;
@@ -30,9 +31,9 @@ QUnit.test("simple chatter on a record", async (assert) => {
},
});
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
await openFormView("res.partner", partnerId);
assert.containsOnce($, ".o-mail-Chatter-topbar");
assert.containsOnce($, ".o-mail-Thread");
openFormView("res.partner", partnerId);
await contains(".o-mail-Chatter-topbar");
await contains(".o-mail-Thread");
assert.verifySteps([
"/mail/init_messaging",
"/mail/load_message_failures",
@@ -67,17 +68,18 @@ QUnit.test("can post a message on a record thread", async (assert) => {
}
},
});
await openFormView("res.partner", partnerId);
assert.containsNone($, ".o-mail-Composer");
openFormView("res.partner", partnerId);
await contains("button:contains(Send message)");
await contains(".o-mail-Composer", 0);
await click("button:contains(Send message)");
assert.containsOnce($, ".o-mail-Composer");
await contains(".o-mail-Composer");
await editInput(document.body, ".o-mail-Composer-input", "hey");
assert.containsNone($, ".o-mail-Message");
await insertText(".o-mail-Composer-input", "hey");
await contains(".o-mail-Message", 0);
await click(".o-mail-Composer button:contains(Send)");
assert.containsOnce($, ".o-mail-Message");
await click(".o-mail-Composer button:contains(Send):not(:disabled)");
await contains(".o-mail-Message");
assert.verifySteps(["/mail/message/post"]);
});
@@ -107,30 +109,31 @@ QUnit.test("can post a note on a record thread", async (assert) => {
}
},
});
await openFormView("res.partner", partnerId);
assert.containsNone($, ".o-mail-Composer");
openFormView("res.partner", partnerId);
await contains("button:contains(Log note)");
await contains(".o-mail-Composer", 0);
await click("button:contains(Log note)");
assert.containsOnce($, ".o-mail-Composer");
await contains(".o-mail-Composer");
await editInput(document.body, ".o-mail-Composer-input", "hey");
assert.containsNone($, ".o-mail-Message");
await insertText(".o-mail-Composer-input", "hey");
await contains(".o-mail-Message", 0);
await click(".o-mail-Composer button:contains(Log)");
assert.containsOnce($, ".o-mail-Message");
await click(".o-mail-Composer button:contains(Log):not(:disabled)");
await contains(".o-mail-Message");
assert.verifySteps(["/mail/message/post"]);
});
QUnit.test("No attachment loading spinner when creating records", async (assert) => {
QUnit.test("No attachment loading spinner when creating records", async () => {
const { openFormView } = await start();
await openFormView("res.partner");
assert.containsOnce($, "button[aria-label='Attach files']");
assert.containsNone($, "button[aria-label='Attach files'] .fa-spin");
openFormView("res.partner");
await contains("button[aria-label='Attach files']");
await contains("button[aria-label='Attach files'] .fa-spin", 0);
});
QUnit.test(
"No attachment loading spinner when switching from loading record to creation of record",
async (assert) => {
async () => {
const { advanceTime, openFormView, pyEnv } = await start({
hasTimeControl: true,
async mockRPC(route) {
@@ -140,20 +143,21 @@ QUnit.test(
},
});
const partnerId = pyEnv["res.partner"].create({ name: "John" });
await openFormView("res.partner", partnerId, { waitUntilDataLoaded: false });
openFormView("res.partner", partnerId, { waitUntilDataLoaded: false });
await contains("button[aria-label='Attach files']");
await advanceTime(DELAY_FOR_SPINNER);
await waitUntil("button[aria-label='Attach files'] .fa-spin");
await click(".o_form_button_create");
assert.containsNone($, "button[aria-label='Attach files'] .fa-spin");
await contains("button[aria-label='Attach files'] .fa-spin");
await click(".o_form_button_create:eq(0)");
await contains("button[aria-label='Attach files'] .fa-spin", 0);
}
);
QUnit.test("Composer toggle state is kept when switching from aside to bottom", async (assert) => {
QUnit.test("Composer toggle state is kept when switching from aside to bottom", async () => {
patchUiSize({ size: SIZES.XXL });
const { openFormView, pyEnv } = await start();
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
openFormView("res.partner", partnerId);
(await contains("button:contains(Send message)")).click();
await click("button:contains(Send message)");
await contains(".o-mail-Form-chatter.o-aside .o-mail-Composer-input");
patchUiSize({ size: SIZES.LG });
window.dispatchEvent(new Event("resize"));
@@ -165,9 +169,9 @@ QUnit.test("Textarea content is kept when switching from aside to bottom", async
const { openFormView, pyEnv } = await start();
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
openFormView("res.partner", partnerId);
(await contains("button:contains(Send message)")).click();
await click("button:contains(Send message)");
await contains(".o-mail-Form-chatter.o-aside .o-mail-Composer-input");
await editInput(document.body, ".o-mail-Composer-input", "Hello world !");
await insertText(".o-mail-Composer-input", "Hello world !");
patchUiSize({ size: SIZES.LG });
window.dispatchEvent(new Event("resize"));
await contains(".o-mail-Form-chatter:not(.o-aside) .o-mail-Composer-input");
@@ -178,7 +182,7 @@ QUnit.test("Composer type is kept when switching from aside to bottom", async (a
patchUiSize({ size: SIZES.XXL });
const { openFormView, pyEnv } = await start();
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
await openFormView("res.partner", partnerId);
openFormView("res.partner", partnerId);
await click("button:contains(Log note)");
patchUiSize({ size: SIZES.LG });
window.dispatchEvent(new Event("resize"));
@@ -195,7 +199,7 @@ QUnit.test("chatter: drop attachments", async (assert) => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
@@ -213,8 +217,8 @@ QUnit.test("chatter: drop attachments", async (assert) => {
}),
];
await afterNextRender(() => dragenterFiles($(".o-mail-Chatter")[0]));
assert.containsOnce($, ".o-mail-Dropzone");
assert.containsNone($, ".o-mail-AttachmentCard");
await contains(".o-mail-Dropzone");
await contains(".o-mail-AttachmentCard", 0);
await afterNextRender(() => dropFiles($(".o-mail-Dropzone")[0], files));
assert.containsN($, ".o-mail-AttachmentCard", 2);
@@ -231,7 +235,7 @@ QUnit.test("chatter: drop attachments", async (assert) => {
assert.containsN($, ".o-mail-AttachmentCard", 3);
});
QUnit.test("should display subject when subject isn't infered from the record", async (assert) => {
QUnit.test("should display subject when subject isn't infered from the record", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["mail.message"].create({
@@ -241,15 +245,15 @@ QUnit.test("should display subject when subject isn't infered from the record",
subject: "Salutations, voyageur",
});
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Message:contains(Subject: Salutations, voyageur)");
await contains(".o-mail-Message:contains(Subject: Salutations, voyageur)");
});
QUnit.test("should not display user notification messages in chatter", async (assert) => {
QUnit.test("should not display user notification messages in chatter", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["mail.message"].create({
@@ -258,29 +262,29 @@ QUnit.test("should not display user notification messages in chatter", async (as
res_id: partnerId,
});
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsNone($, ".o-mail-Message");
await contains(".o-mail-Thread-empty");
await contains(".o-mail-Message", 0);
});
QUnit.test('post message with "CTRL-Enter" keyboard shortcut in chatter', async (assert) => {
QUnit.test('post message with "CTRL-Enter" keyboard shortcut in chatter', async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsNone($, ".o-mail-Message");
await click("button:contains(Send message)");
await contains(".o-mail-Message", 0);
await insertText(".o-mail-Composer-input", "Test");
await afterNextRender(() => triggerHotkey("control+Enter"));
assert.containsOnce($, ".o-mail-Message");
triggerHotkey("control+Enter");
await contains(".o-mail-Message");
});
QUnit.test("base rendering when chatter has no attachment", async (assert) => {
@@ -294,36 +298,36 @@ QUnit.test("base rendering when chatter has no attachment", async (assert) => {
});
}
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Chatter");
assert.containsOnce($, ".o-mail-Chatter-topbar");
assert.containsNone($, ".o-mail-AttachmentBox");
assert.containsOnce($, ".o-mail-Thread");
await contains(".o-mail-Chatter");
await contains(".o-mail-Chatter-topbar");
await contains(".o-mail-AttachmentBox", 0);
await contains(".o-mail-Thread");
assert.containsN($, ".o-mail-Message", 30);
});
QUnit.test("base rendering when chatter has no record", async (assert) => {
const { openView } = await start();
await openView({
openView({
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Chatter");
assert.containsOnce($, ".o-mail-Chatter-topbar");
assert.containsNone($, ".o-mail-AttachmentBox");
assert.containsOnce($, ".o-mail-Chatter .o-mail-Thread");
assert.containsOnce($, ".o-mail-Message");
await contains(".o-mail-Chatter");
await contains(".o-mail-Chatter-topbar");
await contains(".o-mail-AttachmentBox", 0);
await contains(".o-mail-Chatter .o-mail-Thread");
await contains(".o-mail-Message");
assert.strictEqual($(".o-mail-Message-body").text(), "Creating a new record...");
assert.containsNone($, "button:contains(Load More)");
assert.containsOnce($, ".o-mail-Message-actions");
assert.containsNone($, ".o-mail-Message-actions i");
await contains("button:contains(Load More)", 0);
await contains(".o-mail-Message-actions");
await contains(".o-mail-Message-actions i", 0);
});
QUnit.test("base rendering when chatter has attachments", async (assert) => {
QUnit.test("base rendering when chatter has attachments", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["ir.attachment"].create([
@@ -341,17 +345,17 @@ QUnit.test("base rendering when chatter has attachments", async (assert) => {
},
]);
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Chatter");
assert.containsOnce($, ".o-mail-Chatter-topbar");
assert.containsNone($, ".o-mail-AttachmentBox");
await contains(".o-mail-Chatter");
await contains(".o-mail-Chatter-topbar");
await contains(".o-mail-AttachmentBox", 0);
});
QUnit.test("show attachment box", async (assert) => {
QUnit.test("show attachment box", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["ir.attachment"].create([
@@ -369,91 +373,88 @@ QUnit.test("show attachment box", async (assert) => {
},
]);
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Chatter");
assert.containsOnce($, ".o-mail-Chatter-topbar");
assert.containsOnce($, "button[aria-label='Attach files']");
assert.containsOnce($, "button[aria-label='Attach files']:contains(2)");
assert.containsNone($, ".o-mail-AttachmentBox");
await contains(".o-mail-Chatter");
await contains(".o-mail-Chatter-topbar");
await contains("button[aria-label='Attach files']");
await contains("button[aria-label='Attach files']:contains(2)");
await contains(".o-mail-AttachmentBox", 0);
await click("button[aria-label='Attach files']");
assert.containsOnce($, ".o-mail-AttachmentBox");
await contains(".o-mail-AttachmentBox");
});
QUnit.test("composer show/hide on log note/send message [REQUIRE FOCUS]", async (assert) => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, "button:contains(Send message)");
assert.containsOnce($, "button:contains(Log note)");
assert.containsNone($, ".o-mail-Composer");
await contains("button:contains(Send message)");
await contains("button:contains(Log note)");
await contains(".o-mail-Composer", 0);
await click("button:contains(Send message)");
assert.containsOnce($, ".o-mail-Composer");
await contains(".o-mail-Composer");
assert.strictEqual(document.activeElement, $(".o-mail-Composer-input")[0]);
await click("button:contains(Log note)");
assert.containsOnce($, ".o-mail-Composer");
await contains(".o-mail-Composer");
assert.strictEqual(document.activeElement, $(".o-mail-Composer-input")[0]);
await click("button:contains(Log note)");
assert.containsNone($, ".o-mail-Composer");
await contains(".o-mail-Composer", 0);
await click("button:contains(Send message)");
assert.containsOnce($, ".o-mail-Composer");
await contains(".o-mail-Composer");
await click("button:contains(Send message)");
assert.containsNone($, ".o-mail-Composer");
await contains(".o-mail-Composer", 0);
});
QUnit.test('do not post message with "Enter" keyboard shortcut', async (assert) => {
QUnit.test('do not post message with "Enter" keyboard shortcut', async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsNone($, ".o-mail-Message");
await click("button:contains(Send message)");
await contains(".o-mail-Message", 0);
await insertText(".o-mail-Composer-input", "Test");
await triggerHotkey("Enter");
assert.containsNone($, ".o-mail-Message");
triggerHotkey("Enter");
// weak test, no guarantee that we waited long enough for the potential message to be posted
await contains(".o-mail-Message", 0);
});
QUnit.test(
"should not display subject when subject is the same as the thread name",
async (assert) => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({
name: "Salutations, voyageur",
});
pyEnv["mail.message"].create({
body: "not empty",
model: "res.partner",
res_id: partnerId,
subject: "Salutations, voyageur",
});
const { openView } = await start();
await openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsNone($, ".o-mail-Message:contains(Salutations, voyageur)");
}
);
QUnit.test("should not display subject when subject is the same as the thread name", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({
name: "Salutations, voyageur",
});
pyEnv["mail.message"].create({
body: "not empty",
model: "res.partner",
res_id: partnerId,
subject: "Salutations, voyageur",
});
const { openView } = await start();
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
await contains(".o-mail-Message:not(:contains(Salutations, voyageur))");
});
QUnit.test("scroll position is kept when navigating from one record to another", async (assert) => {
patchUiSize({ size: SIZES.XXL });
@@ -472,20 +473,42 @@ QUnit.test("scroll position is kept when navigating from one record to another",
}))
);
const { openFormView } = await start();
await openFormView("res.partner", partnerId_1);
openFormView("res.partner", partnerId_1);
await contains(".o_breadcrumb:contains(Harry Potter)");
await contains(".o-mail-Chatter");
/**
* The nextAnimationFrame is necessary because otherwise useAutoScroll would
* set the scroll to bottom after the manually set value from this test.
*/
await nextAnimationFrame();
const scrolltop_1 = $(".o-mail-Chatter")[0].scrollHeight / 2;
$(".o-mail-Chatter")[0].scrollTo({ top: scrolltop_1 });
await openFormView("res.partner", partnerId_2);
openFormView("res.partner", partnerId_2);
await contains(".o_breadcrumb:contains(Ron Weasley)");
/**
* The nextAnimationFrame is necessary because otherwise useAutoScroll would
* set the scroll to bottom after the manually set value from this test.
*/
await nextAnimationFrame();
const scrolltop_2 = $(".o-mail-Chatter")[0].scrollHeight / 3;
$(".o-mail-Chatter")[0].scrollTo({ top: scrolltop_2 });
await openFormView("res.partner", partnerId_1);
openFormView("res.partner", partnerId_1);
await contains(".o_breadcrumb:contains(Harry Potter)");
/**
* The nextAnimationFrame is necessary to give time for scroll to be restored.
*/
await nextAnimationFrame();
assert.ok(isScrolledTo($(".o-mail-Chatter")[0], scrolltop_1));
await openFormView("res.partner", partnerId_2);
openFormView("res.partner", partnerId_2);
await contains(".o_breadcrumb:contains(Ron Weasley)");
/**
* The nextAnimationFrame is necessary to give time for scroll to be restored.
*/
await nextAnimationFrame();
assert.ok(isScrolledTo($(".o-mail-Chatter")[0], scrolltop_2));
});
QUnit.test("basic chatter rendering", async (assert) => {
QUnit.test("basic chatter rendering", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ display_name: "second partner" });
const views = {
@@ -498,15 +521,15 @@ QUnit.test("basic chatter rendering", async (assert) => {
</form>`,
};
const { openView } = await start({ serverData: { views } });
await openView({
openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Chatter");
await contains(".o-mail-Chatter");
});
QUnit.test("basic chatter rendering without activities", async (assert) => {
QUnit.test("basic chatter rendering without activities", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ display_name: "second partner" });
const views = {
@@ -522,22 +545,22 @@ QUnit.test("basic chatter rendering without activities", async (assert) => {
</form>`,
};
const { openView } = await start({ serverData: { views } });
await openView({
openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Chatter");
assert.containsOnce($, ".o-mail-Chatter-topbar");
assert.containsOnce($, "button[aria-label='Attach files']");
assert.containsNone($, "button:contains(Activities)");
assert.containsOnce($, ".o-mail-Followers");
assert.containsOnce($, ".o-mail-Thread");
await contains(".o-mail-Chatter");
await contains(".o-mail-Chatter-topbar");
await contains("button[aria-label='Attach files']");
await contains("button:contains(Activities)", 0);
await contains(".o-mail-Followers");
await contains(".o-mail-Thread");
});
QUnit.test(
'chatter just contains "creating a new record" message during the creation of a new record after having displayed a chatter for an existing record',
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const views = {
@@ -552,20 +575,20 @@ QUnit.test(
</form>`,
};
const { openView } = await start({ serverData: { views } });
await openView({
openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
await click(".o_form_button_create");
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message-body:contains(Creating a new record...)");
await click(".o_form_button_create:eq(0)");
await contains(".o-mail-Message");
await contains(".o-mail-Message-body:contains(Creating a new record...)");
}
);
QUnit.test(
"should not display subject when subject is the same as the default subject",
async (assert) => {
async () => {
const pyEnv = await startServer();
const fakeId = pyEnv["res.fake"].create({ name: "Salutations, voyageur" });
pyEnv["mail.message"].create({
@@ -575,14 +598,14 @@ QUnit.test(
subject: "Custom Default Subject", // default subject for res.fake, set on the model
});
const { openFormView } = await start();
await openFormView("res.fake", fakeId);
assert.containsNone($, ".o-mail-Message:contains(Custom Default Subject)");
openFormView("res.fake", fakeId);
await contains(".o-mail-Message:not(:contains(Custom Default Subject))");
}
);
QUnit.test(
"should not display subject when subject is the same as the thread name with custom default subject",
async (assert) => {
async () => {
const pyEnv = await startServer();
const fakeId = pyEnv["res.fake"].create({ name: "Salutations, voyageur" });
pyEnv["mail.message"].create({
@@ -592,8 +615,8 @@ QUnit.test(
subject: "Salutations, voyageur",
});
const { openFormView } = await start();
await openFormView("res.fake", fakeId);
assert.containsNone($, ".o-mail-Message:contains(Custom Default Subject)");
openFormView("res.fake", fakeId);
await contains(".o-mail-Message:not(:contains(Custom Default Subject))");
}
);
@@ -613,21 +636,21 @@ QUnit.test("basic chatter rendering without followers", async (assert) => {
</form>`,
};
const { openView } = await start({ serverData: { views } });
await openView({
openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Chatter");
assert.containsOnce($, ".o-mail-Chatter-topbar");
assert.containsOnce($, "button[aria-label='Attach files']");
assert.containsOnce($, "button:contains(Activities)");
await contains(".o-mail-Chatter");
await contains(".o-mail-Chatter-topbar");
await contains("button[aria-label='Attach files']");
await contains("button:contains(Activities)");
assert.containsNone(
$,
".o-mail-Followers",
"there should be no followers menu because the 'message_follower_ids' field is not present in 'oe_chatter'"
);
assert.containsOnce($, ".o-mail-Chatter .o-mail-Thread");
await contains(".o-mail-Chatter .o-mail-Thread");
});
QUnit.test("basic chatter rendering without messages", async (assert) => {
@@ -646,16 +669,16 @@ QUnit.test("basic chatter rendering without messages", async (assert) => {
</form>`,
};
const { openView } = await start({ serverData: { views } });
await openView({
openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Chatter");
assert.containsOnce($, ".o-mail-Chatter-topbar");
assert.containsOnce($, "button[aria-label='Attach files']");
assert.containsOnce($, "button:contains(Activities)");
assert.containsOnce($, ".o-mail-Followers");
await contains(".o-mail-Chatter");
await contains(".o-mail-Chatter-topbar");
await contains("button[aria-label='Attach files']");
await contains("button:contains(Activities)");
await contains(".o-mail-Followers");
assert.containsNone(
$,
".o-mail-Chatter .o-mail-Thread",
@@ -686,14 +709,14 @@ QUnit.test("chatter updating", async (assert) => {
</form>`,
};
const { openFormView } = await start({ serverData: { views } });
await openFormView("res.partner", partnerId_1, {
openFormView("res.partner", partnerId_1, {
props: { resIds: [partnerId_1, partnerId_2] },
});
await click(".o_pager_next");
assert.containsOnce($, ".o-mail-Message");
await contains(".o-mail-Message");
});
QUnit.test("post message on draft record", async (assert) => {
QUnit.test("post message on draft record", async () => {
const views = {
"res.partner,false,form": `
<form string="Partners">
@@ -706,20 +729,20 @@ QUnit.test("post message on draft record", async (assert) => {
</form>`,
};
const { openView } = await start({ serverData: { views } });
await openView({
openView({
res_model: "res.partner",
views: [[false, "form"]],
});
await click("button:contains(Send message)");
await editInput(document.body, ".o-mail-Composer-input", "Test");
await click(".o-mail-Composer button:contains(Send)");
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message:contains(Test)");
await insertText(".o-mail-Composer-input", "Test");
await click(".o-mail-Composer button:contains(Send):not(:disabled)");
await contains(".o-mail-Message");
await contains(".o-mail-Message:contains(Test)");
});
QUnit.test(
"schedule activities on draft record should prompt with scheduling an activity (proceed with action)",
async (assert) => {
async () => {
const views = {
"res.partner,false,form": `
<form string="Partners">
@@ -731,14 +754,14 @@ QUnit.test(
</div>
</form>`,
};
const { openView } = await start({ serverData: { views } });
await openView({ res_model: "res.partner", views: [[false, "form"]] });
const { openFormView } = await start({ serverData: { views } });
openFormView("res.partner");
await click("button:contains(Activities)");
assert.containsOnce($, ".o_dialog:contains(Schedule Activity)");
await contains(".o_dialog:contains(Schedule Activity)");
}
);
QUnit.test("upload attachment on draft record", async (assert) => {
QUnit.test("upload attachment on draft record", async () => {
const views = {
"res.partner,false,form": `
<form string="Partners">
@@ -1,12 +1,11 @@
/* @odoo-module */
import {
afterNextRender,
click,
contains,
nextAnimationFrame,
start,
startServer,
waitUntil,
} from "@mail/../tests/helpers/test_utils";
import { DELAY_FOR_SPINNER } from "@mail/core/web/chatter";
@@ -14,22 +13,21 @@ import { makeDeferred } from "@web/../tests/helpers/utils";
QUnit.module("chatter topbar");
QUnit.test("base rendering", async (assert) => {
QUnit.test("base rendering", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Chatter-topbar");
assert.containsOnce($, "button:contains(Send message)");
assert.containsOnce($, "button:contains(Log note)");
assert.containsOnce($, "button:contains(Activities)");
assert.containsOnce($, "button[aria-label='Attach files']");
assert.containsOnce($, ".o-mail-Followers");
await contains(".o-mail-Chatter-topbar");
await contains("button:contains(Send message)");
await contains("button:contains(Log note)");
await contains("button:contains(Activities)");
await contains("button[aria-label='Attach files']");
await contains(".o-mail-Followers");
});
QUnit.test("rendering with multiple partner followers", async (assert) => {
@@ -52,109 +50,106 @@ QUnit.test("rendering with multiple partner followers", async (assert) => {
},
]);
const { openView } = await start();
await openView({
openView({
res_id: partnerId_3,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Followers");
assert.containsOnce($, ".o-mail-Followers-button");
await contains(".o-mail-Followers");
await contains(".o-mail-Followers-button");
await click(".o-mail-Followers-button");
assert.containsOnce($, ".o-mail-Followers-dropdown");
assert.containsN($, ".o-mail-Follower", 2);
await contains(".o-mail-Followers-dropdown");
await contains(".o-mail-Follower", 2);
assert.strictEqual($(".o-mail-Follower:eq(0)").text().trim(), "Jean Michang");
assert.strictEqual($(".o-mail-Follower:eq(1)").text().trim(), "Eden Hazard");
});
QUnit.test("log note toggling", async (assert) => {
QUnit.test("log note toggling", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, "button:contains(Log note)");
assert.doesNotHaveClass($("button:contains(Log note)"), "active");
assert.containsNone($, ".o-mail-Composer");
await contains("button:contains(Log note)");
await contains("button:contains(Log note):not(.active)");
await contains(".o-mail-Composer", 0);
await click("button:contains(Log note)");
assert.hasClass($("button:contains(Log note)"), "active");
assert.containsOnce(
$,
".o-mail-Composer .o-mail-Composer-input[placeholder='Log an internal note...']"
);
await contains("button:contains(Log note).active");
await contains(".o-mail-Composer .o-mail-Composer-input[placeholder='Log an internal note…']");
await click("button:contains(Log note)");
assert.doesNotHaveClass($("button:contains(Log note)"), "active");
assert.containsNone($, ".o-mail-Composer");
await contains("button:contains(Log note):not(.active)");
await contains(".o-mail-Composer", 0);
});
QUnit.test("send message toggling", async (assert) => {
QUnit.test("send message toggling", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, "button:contains(Send message)");
assert.doesNotHaveClass($("button:contains(Send message)"), "active");
assert.containsNone($, ".o-mail-Composer");
await contains("button:contains(Send message)");
await contains("button:contains(Send message):not(.active)");
await contains(".o-mail-Composer", 0);
await click("button:contains(Send message)");
assert.hasClass($("button:contains(Send message)"), "active");
assert.containsOnce($, ".o-mail-Composer-input[placeholder='Send a message to followers...']");
await contains("button:contains(Send message).active");
await contains(".o-mail-Composer-input[placeholder='Send a message to followers…']");
await click("button:contains(Send message)");
assert.doesNotHaveClass($("button:contains(Send message)"), "active");
assert.containsNone($, ".o-mail-Composer");
await contains("button:contains(Send message):not(.active)");
await contains(".o-mail-Composer", 0);
});
QUnit.test("log note/send message switching", async (assert) => {
QUnit.test("log note/send message switching", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, "button:contains(Send message)");
assert.doesNotHaveClass($("button:contains(Send message)"), "active");
assert.containsOnce($, "button:contains(Log note)");
assert.doesNotHaveClass($("button:contains(Log note)"), "active");
assert.containsNone($, ".o-mail-Composer");
await contains("button:contains(Send message)");
await contains("button:contains(Send message):not(.active)");
await contains("button:contains(Log note)");
await contains("button:contains(Log note):not(.active)");
await contains(".o-mail-Composer", 0);
await click("button:contains(Send message)");
assert.hasClass($("button:contains(Send message)"), "active");
assert.doesNotHaveClass($("button:contains(Log note)"), "active");
assert.containsOnce($, ".o-mail-Composer-input[placeholder='Send a message to followers...']");
await contains("button:contains(Send message).active");
await contains("button:contains(Log note):not(.active)");
await contains(".o-mail-Composer-input[placeholder='Send a message to followers…']");
await click("button:contains(Log note)");
assert.doesNotHaveClass($("button:contains(Send message)"), "active");
assert.hasClass($("button:contains(Log note)"), "active");
assert.containsOnce($, ".o-mail-Composer-input[placeholder='Log an internal note...']");
await contains("button:contains(Send message):not(.active)");
await contains("button:contains(Log note).active");
await contains(".o-mail-Composer-input[placeholder='Log an internal note…']");
});
QUnit.test("attachment counter without attachments", async (assert) => {
QUnit.test("attachment counter without attachments", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, "button[aria-label='Attach files']");
assert.containsNone($, "button[aria-label='Attach files']:contains(0)");
await contains("button[aria-label='Attach files']");
await contains("button[aria-label='Attach files']:contains(0)", 0);
});
QUnit.test("attachment counter with attachments", async (assert) => {
QUnit.test("attachment counter with attachments", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["ir.attachment"].create([
@@ -172,15 +167,15 @@ QUnit.test("attachment counter with attachments", async (assert) => {
},
]);
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, "button[aria-label='Attach files']:contains(2)");
await contains("button[aria-label='Attach files']:contains(2)");
});
QUnit.test("attachment counter while loading attachments", async (assert) => {
QUnit.test("attachment counter while loading attachments", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const { advanceTime, openView } = await start({
@@ -191,17 +186,18 @@ QUnit.test("attachment counter while loading attachments", async (assert) => {
}
},
});
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
await contains("button[aria-label='Attach files']");
await advanceTime(DELAY_FOR_SPINNER);
await waitUntil("button[aria-label='Attach files'] .fa-spin");
assert.containsNone($, "button[aria-label='Attach files']:contains(0)");
await contains("button[aria-label='Attach files'] .fa-spin");
await contains("button[aria-label='Attach files']:contains(0)", 0);
});
QUnit.test("attachment counter transition when attachments become loaded", async (assert) => {
QUnit.test("attachment counter transition when attachments become loaded", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const deferred = makeDeferred();
@@ -213,37 +209,37 @@ QUnit.test("attachment counter transition when attachments become loaded", async
}
},
});
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
await contains("button[aria-label='Attach files']");
await advanceTime(DELAY_FOR_SPINNER);
await waitUntil("button[aria-label='Attach files'] .fa-spin");
await afterNextRender(() => deferred.resolve());
assert.containsNone($, "button[aria-label='Attach files'] .fa-spin");
await contains("button[aria-label='Attach files'] .fa-spin");
deferred.resolve();
await contains("button[aria-label='Attach files'] .fa-spin", 0);
});
QUnit.test(
"attachment icon open directly the file uploader if there is no attachment yet",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Chatter-fileUploader");
assert.containsNone($, ".o-mail-AttachmentBox");
await contains(".o-mail-Chatter-fileUploader");
await contains(".o-mail-AttachmentBox", 0);
}
);
QUnit.test(
"attachment icon open the attachment box when there is at least 1 attachment",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["ir.attachment"].create([
@@ -255,33 +251,36 @@ QUnit.test(
},
]);
const { openView } = await start();
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsNone($, ".o-mail-Chatter-fileUploader");
await contains("button[aria-label='Attach files']");
await contains(".o-mail-AttachmentBox", 0);
await contains(".o-mail-Chatter-fileUploader", 0);
await click("button[aria-label='Attach files']");
assert.containsOnce($, ".o-mail-AttachmentBox");
await contains(".o-mail-AttachmentBox");
await contains(".o-mail-Chatter-fileUploader");
}
);
QUnit.test("composer state conserved when clicking on another topbar button", async (assert) => {
QUnit.test("composer state conserved when clicking on another topbar button", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const { openFormView } = await start();
await openFormView("res.partner", partnerId);
assert.containsOnce($, ".o-mail-Chatter-topbar");
assert.containsOnce($, "button:contains(Send message)");
assert.containsOnce($, "button:contains(Log note)");
assert.containsOnce($, "button[aria-label='Attach files']");
await contains(".o-mail-Chatter-topbar");
await contains("button:contains(Send message)");
await contains("button:contains(Log note)");
await contains("button[aria-label='Attach files']");
await click("button:contains(Log note)");
assert.containsOnce($, "button:contains(Log note).active");
assert.containsNone($, "button:contains(Send message).active");
await contains("button:contains(Log note).active");
await contains("button:contains(Send message).active", 0);
$(`button[aria-label='Attach files']`)[0].click();
await nextAnimationFrame();
assert.containsOnce($, "button:contains(Log note).active");
assert.containsNone($, "button:contains(Send message).active");
await contains("button:contains(Log note).active");
await contains("button:contains(Send message).active", 0);
});
@@ -1,7 +1,7 @@
/* @odoo-module */
import { Avatar } from "@mail/views/web/fields/avatar/avatar";
import { click, start } from "@mail/../tests/helpers/test_utils";
import { click, contains, start } from "@mail/../tests/helpers/test_utils";
import { getFixture, mount } from "@web/../tests/helpers/utils";
@@ -17,13 +17,13 @@ QUnit.test("basic rendering", async (assert) => {
displayName: "User display name",
},
});
assert.containsOnce($, ".o-mail-Avatar");
assert.containsOnce($, ".o-mail-Avatar img");
await contains(".o-mail-Avatar");
await contains(".o-mail-Avatar img");
assert.strictEqual($(".o-mail-Avatar img")[0].dataset.src, "/web/image/res.users/2/avatar_128");
assert.containsOnce($, ".o-mail-Avatar span");
await contains(".o-mail-Avatar span");
assert.strictEqual($(".o-mail-Avatar span")[0].innerText, "User display name");
assert.containsNone($, ".o-mail-ChatWindow");
await click(".o-mail-Avatar img");
assert.containsOnce($, ".o-mail-ChatWindow");
await contains(".o-mail-ChatWindow");
});
@@ -1,20 +1,20 @@
/* @odoo-module */
import { afterNextRender, click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("follow button");
QUnit.test("base rendering not editable", async (assert) => {
QUnit.test("base rendering not editable", async () => {
const { openView, pyEnv } = await start();
await openView({
openView({
res_id: pyEnv.currentPartnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Chatter-follow");
await contains(".o-mail-Chatter-follow");
});
QUnit.test("hover following button", async (assert) => {
QUnit.test("hover following button", async () => {
const pyEnv = await startServer();
const threadId = pyEnv["res.partner"].create({});
pyEnv["mail.followers"].create({
@@ -24,37 +24,35 @@ QUnit.test("hover following button", async (assert) => {
res_model: "res.partner",
});
const { openView } = await start();
await openView({
openView({
res_id: threadId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, "button:contains(Following)");
assert.containsNone($, ".fa-times + span:contains(Following)");
assert.containsOnce($, ".fa-check + span:contains(Following)");
await contains("button:contains(Following)");
await contains(".fa-times + span:contains(Following)", 0);
await contains(".fa-check + span:contains(Following)");
await afterNextRender(() => {
$("button:contains(Following)")[0].dispatchEvent(new window.MouseEvent("mouseenter"));
});
assert.containsOnce($, "button:contains(Unfollow)");
assert.containsOnce($, ".fa-times + span:contains(Unfollow)");
assert.containsNone($, ".fa-check + span:contains(Unfollow)");
$("button:contains(Following)")[0].dispatchEvent(new window.MouseEvent("mouseenter"));
await contains("button:contains(Unfollow)");
await contains(".fa-times + span:contains(Unfollow)");
await contains(".fa-check + span:contains(Unfollow)", 0);
});
QUnit.test('click on "follow" button', async (assert) => {
QUnit.test('click on "follow" button', async () => {
const { openView, pyEnv } = await start();
await openView({
openView({
res_id: pyEnv.currentPartnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, "button:contains(Follow)");
await contains("button:contains(Follow)");
await click("button:contains(Follow)");
assert.containsOnce($, "button:contains(Following)");
await contains("button:contains(Following)");
});
QUnit.test('Click on "follow" button should save draft record', async (assert) => {
QUnit.test('Click on "follow" button should save draft record', async () => {
const views = {
"res.partner,false,form": `
<form string="Partners">
@@ -67,15 +65,14 @@ QUnit.test('Click on "follow" button should save draft record', async (assert) =
</form>`,
};
const { openFormView } = await start({ serverData: { views } });
await openFormView("res.partner");
assert.containsOnce($, "button:contains(Follow)");
assert.containsOnce($, "div.o_field_char");
openFormView("res.partner");
await contains("button:contains(Follow)");
await contains("div.o_field_char");
await click("button:contains(Follow)");
assert.containsOnce($, "div.o_field_invalid");
await contains("div.o_field_invalid");
});
QUnit.test('click on "unfollow" button', async (assert) => {
QUnit.test('click on "unfollow" button', async () => {
const pyEnv = await startServer();
const threadId = pyEnv["res.partner"].create({});
pyEnv["mail.followers"].create({
@@ -85,13 +82,11 @@ QUnit.test('click on "unfollow" button', async (assert) => {
res_model: "res.partner",
});
const { openView } = await start();
await openView({
openView({
res_id: threadId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, "button:contains(Following)");
await click("button:contains(Following)");
assert.containsOnce($, "button:contains(Follow)");
await contains("button:contains(Follow)");
});
@@ -1,29 +1,31 @@
/* @odoo-module */
import { afterNextRender, click, start, startServer } from "@mail/../tests/helpers/test_utils";
import {
click,
contains,
nextAnimationFrame,
start,
startServer,
} from "@mail/../tests/helpers/test_utils";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
import { nextTick } from "@web/../tests/legacy/helpers/test_utils";
QUnit.module("follower list menu");
QUnit.test("base rendering not editable", async (assert) => {
QUnit.test("base rendering not editable", async () => {
const { openView } = await start();
await openView(
openView(
{
res_model: "res.partner",
views: [[false, "form"]],
},
{ mode: "edit" }
);
assert.containsOnce($, ".o-mail-Followers");
assert.containsOnce($, ".o-mail-Followers-button");
assert.ok($(".o-mail-Followers-button")[0].disabled);
assert.containsNone($, ".o-mail-Followers-dropdown");
$(".o-mail-Followers-button")[0].click();
await nextTick();
assert.containsNone($, ".o-mail-Followers-dropdown");
await contains(".o-mail-Followers");
await contains(".o-mail-Followers-button:disabled");
await contains(".o-mail-Followers-dropdown", 0);
await click(".o-mail-Followers-button");
await contains(".o-mail-Followers-dropdown", 0);
});
QUnit.test("base rendering editable", async (assert) => {
@@ -39,18 +41,18 @@ QUnit.test("base rendering editable", async (assert) => {
}
},
});
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-Followers");
assert.containsOnce($, ".o-mail-Followers-button");
await contains(".o-mail-Followers");
await contains(".o-mail-Followers-button");
assert.notOk($(".o-mail-Followers-button")[0].disabled);
assert.containsNone($, ".o-mail-Followers-dropdown");
await contains(".o-mail-Followers-dropdown", 0);
await click(".o-mail-Followers-button");
assert.containsOnce($, ".o-mail-Followers-dropdown");
await contains(".o-mail-Followers-dropdown");
});
QUnit.test('click on "add followers" button', async (assert) => {
@@ -78,7 +80,7 @@ QUnit.test('click on "add followers" button', async (assert) => {
}
},
});
await openView({
openView({
res_id: partnerId_1,
res_model: "res.partner",
views: [[false, "form"]],
@@ -102,21 +104,21 @@ QUnit.test('click on "add followers" button', async (assert) => {
},
});
assert.containsOnce($, ".o-mail-Followers");
assert.containsOnce($, ".o-mail-Followers-button");
await contains(".o-mail-Followers");
await contains(".o-mail-Followers-button");
assert.strictEqual($(".o-mail-Followers-counter").text(), "1");
await click(".o-mail-Followers-button");
assert.containsOnce($, ".o-mail-Followers-dropdown");
assert.containsOnce($, "a:contains(Add Followers)");
await contains(".o-mail-Followers-dropdown");
await contains("a:contains(Add Followers)");
await click("a:contains(Add Followers)");
assert.containsNone($, ".o-mail-Followers-dropdown");
await contains(".o-mail-Followers-dropdown", 0);
assert.verifySteps(["action:open_view"]);
assert.strictEqual($(".o-mail-Followers-counter").text(), "2");
await click(".o-mail-Followers-button");
assert.containsN($, ".o-mail-Follower", 2);
await contains(".o-mail-Follower", 2);
assert.strictEqual($(".o-mail-Follower").text(), "François PerussePartner3");
});
@@ -148,24 +150,24 @@ QUnit.test("click on remove follower", async (assert) => {
}
},
});
await openView({
openView({
res_id: partnerId_1,
res_model: "res.partner",
views: [[false, "form"]],
});
await click(".o-mail-Followers-button");
assert.containsOnce($, ".o-mail-Follower");
assert.containsOnce($, "button[title='Remove this follower']");
await contains(".o-mail-Follower");
await contains("button[title='Remove this follower']");
await click("button[title='Remove this follower']");
assert.verifySteps(["message_unsubscribe"]);
assert.containsNone($, ".o-mail-Follower");
await contains(".o-mail-Follower", 0);
});
QUnit.test(
'Hide "Add follower" and subtypes edition/removal buttons except own user on read only record',
async (assert) => {
async () => {
const pyEnv = await startServer();
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
{ name: "Partner1" },
@@ -195,23 +197,22 @@ QUnit.test(
}
},
});
await openView({
openView({
res_id: partnerId_1,
res_model: "res.partner",
views: [[false, "form"]],
});
await click(".o-mail-Followers-button");
assert.containsNone($, "a:contains(Add Followers)");
const $followers = $(".o-mail-Follower");
assert.containsOnce($followers[0], "button[title='Edit subscription']");
assert.containsOnce($followers[0], "button[title='Remove this follower']");
assert.containsNone($followers[1], "button[title='Edit subscription']");
assert.containsNone($followers[1], "button[title='Remove this follower']");
await contains("a:contains(Add Followers)", 0);
await contains(".o-mail-Follower:eq(0) button[title='Edit subscription']");
await contains(".o-mail-Follower:eq(0) button[title='Remove this follower']");
await contains(".o-mail-Follower:eq(1):not(:has(button[title='Edit subscription']))");
await contains(".o-mail-Follower:eq(1):not(:has(button[title='Remove this follower']))");
}
);
QUnit.test("Load 100 followers at once", async (assert) => {
QUnit.test("Load 100 followers at once", async () => {
const pyEnv = await startServer();
const partnerIds = pyEnv["res.partner"].create(
[...Array(210).keys()].map((i) => ({ display_name: `Partner${i}`, name: `Partner${i}` }))
@@ -228,22 +229,17 @@ QUnit.test("Load 100 followers at once", async (assert) => {
);
const { openFormView } = await start();
await openFormView("res.partner", partnerIds[0]);
assert.containsOnce($, "button[title='Show Followers']:contains(210)");
await contains("button[title='Show Followers']:contains(210)");
await click("button[title='Show Followers']");
assert.containsOnce($, ".o-mail-Follower:contains(Mitchell Admin)");
assert.containsN($, ".o-mail-Follower", 100);
assert.containsOnce($, ".o-mail-Followers-dropdown span:contains(Load more)");
await afterNextRender(() =>
$(".o-mail-Followers-dropdown span:contains(Load more)")[0].scrollIntoView()
);
assert.containsN($, ".o-mail-Follower", 200);
assert.containsOnce($, ".o-mail-Followers-dropdown span:contains(Load more)");
await afterNextRender(() =>
$(".o-mail-Followers-dropdown span:contains(Load more)")[0].scrollIntoView()
);
assert.containsN($, ".o-mail-Follower", 210);
assert.containsNone($, ".o-mail-Followers-dropdown span:contains(Load more)");
await contains(".o-mail-Follower:contains(Mitchell Admin)");
await contains(".o-mail-Follower", 100);
$(".o-mail-Followers-dropdown span:contains(Load more)")[0].scrollIntoView();
await contains(".o-mail-Follower", 200);
await nextAnimationFrame(); // give enough time for the Load more button to scroll out of view
$(".o-mail-Followers-dropdown span:contains(Load more)")[0].scrollIntoView();
await contains(".o-mail-Follower", 210);
await contains(".o-mail-Followers-dropdown span:contains(Load more)", 0);
});
QUnit.test("Load 100 recipients at once", async (assert) => {
@@ -267,26 +263,18 @@ QUnit.test("Load 100 recipients at once", async (assert) => {
);
const { openFormView } = await start();
await openFormView("res.partner", partnerIds[0]);
assert.containsOnce($, "button[title='Show Followers']:contains(210)");
await contains("button[title='Show Followers']:contains(210)");
await click("button:contains(Send message)");
assert.containsOnce(
$,
".o-mail-Chatter:contains('me, partner1, partner2, partner3, partner4, …')"
);
assert.containsOnce($, "button[title='Show all recipients']");
await contains(".o-mail-Chatter:contains('me, partner1, partner2, partner3, partner4, …')");
await contains("button[title='Show all recipients']");
await click("button[title='Show all recipients']");
assert.containsN($, ".o-mail-RecipientList li", 100);
assert.containsOnce($, ".o-mail-RecipientList span:contains(Load more)");
await afterNextRender(() =>
$(".o-mail-RecipientList span:contains(Load more)")[0].scrollIntoView()
);
assert.containsN($, ".o-mail-RecipientList li", 200);
assert.containsOnce($, ".o-mail-RecipientList span:contains(Load more)");
await afterNextRender(() =>
$(".o-mail-RecipientList span:contains(Load more)")[0].scrollIntoView()
);
assert.containsN($, ".o-mail-RecipientList li", 210);
assert.containsNone($, ".o-mail-RecipientList span:contains(Load more)");
await contains(".o-mail-RecipientList li", 100);
$(".o-mail-RecipientList span:contains(Load more)")[0].scrollIntoView();
await contains(".o-mail-RecipientList li", 200);
await nextAnimationFrame(); // give enough time for the Load more button to scroll out of view
$(".o-mail-RecipientList span:contains(Load more)")[0].scrollIntoView();
await contains(".o-mail-RecipientList li", 210);
await contains(".o-mail-RecipientList span:contains(Load more)", 0);
});
QUnit.test(
@@ -321,14 +309,14 @@ QUnit.test(
}
},
});
await openView({
openView({
res_id: partnerId_1,
res_model: "res.partner",
views: [[false, "form"]],
});
await click(".o-mail-Followers-button");
assert.containsOnce($, "a:contains(Add Followers)");
await contains("a:contains(Add Followers)");
const $followers = $(".o-mail-Follower");
assert.containsOnce($followers[0], "button[title='Edit subscription']");
assert.containsOnce($followers[0], "button[title='Remove this follower']");
@@ -352,13 +340,13 @@ QUnit.test(
}
},
});
await openView({
openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
await click(".o-mail-Followers-button");
assert.containsOnce($, "div:contains(No Followers).disabled");
await contains("div:contains(No Followers).disabled");
}
);
@@ -1,12 +1,10 @@
/* @odoo-module */
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { makeFakeNotificationService } from "@web/../tests/helpers/mock_services";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("follower subtype");
QUnit.test("simplest layout of a followed subtype", async (assert) => {
QUnit.test("simplest layout of a followed subtype", async () => {
const pyEnv = await startServer();
const subtypeId = pyEnv["mail.message.subtype"].create({
default: true,
@@ -31,128 +29,114 @@ QUnit.test("simplest layout of a followed subtype", async (assert) => {
}
},
});
await openView({
openView({
res_model: "res.partner",
res_id: pyEnv.currentPartnerId,
views: [[false, "form"]],
});
await click(".o-mail-Followers-button");
await click("button[title='Edit subscription']");
assert.containsOnce($, ".o-mail-FollowerSubtypeDialog-subtype:contains(TestSubtype)");
assert.containsOnce($(".o-mail-FollowerSubtypeDialog-subtype:contains(TestSubtype)"), "label");
assert.containsOnce(
$(".o-mail-FollowerSubtypeDialog-subtype:contains(TestSubtype)"),
"input[type='checkbox']"
await contains(
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id=${subtypeId}] label:contains(TestSubtype)`
);
assert.strictEqual(
$(".o-mail-FollowerSubtypeDialog-subtype:contains(TestSubtype) label").text(),
"TestSubtype"
);
assert.ok(
$(".o-mail-FollowerSubtypeDialog-subtype:contains(TestSubtype) input[type='checkbox']")[0]
.checked
await contains(
$(
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id=${subtypeId}] input[type='checkbox']:checked`
)
);
});
QUnit.test("simplest layout of a not followed subtype", async (assert) => {
const pyEnv = await startServer();
pyEnv["mail.message.subtype"].create({
default: true,
name: "TestSubtype",
});
const followerId = pyEnv["mail.followers"].create({
display_name: "François Perusse",
partner_id: pyEnv.currentPartnerId,
res_model: "res.partner",
res_id: pyEnv.currentPartnerId,
});
pyEnv["res.partner"].write([pyEnv.currentPartnerId], { message_follower_ids: [followerId] });
const { openView } = await start({
// FIXME: should adapt mock server code to provide "hasWriteAccess"
async mockRPC(route, args, performRPC) {
if (route === "/mail/thread/data") {
// mimic user with write access
const res = await performRPC(...arguments);
res["hasWriteAccess"] = true;
return res;
}
},
});
await openView({
res_model: "res.partner",
res_id: pyEnv.currentPartnerId,
views: [[false, "form"]],
});
await click(".o-mail-Followers-button");
await click("button[title='Edit subscription']");
assert.notOk(
$(".o-mail-FollowerSubtypeDialog-subtype:contains(TestSubtype) input[type='checkbox']")[0]
.checked,
"checkbox should not be checked as follower subtype is not followed"
);
});
QUnit.test("toggle follower subtype checkbox", async (assert) => {
const pyEnv = await startServer();
pyEnv["mail.message.subtype"].create({
default: true,
name: "TestSubtype",
});
const followerId = pyEnv["mail.followers"].create({
display_name: "François Perusse",
partner_id: pyEnv.currentPartnerId,
res_model: "res.partner",
res_id: pyEnv.currentPartnerId,
});
pyEnv["res.partner"].write([pyEnv.currentPartnerId], { message_follower_ids: [followerId] });
const { openView } = await start({
// FIXME: should adapt mock server code to provide "hasWriteAccess"
async mockRPC(route, args, performRPC) {
if (route === "/mail/thread/data") {
// mimic user with write access
const res = await performRPC(...arguments);
res["hasWriteAccess"] = true;
return res;
}
},
});
await openView({
res_model: "res.partner",
res_id: pyEnv.currentPartnerId,
views: [[false, "form"]],
});
await click(".o-mail-Followers-button");
await click("button[title='Edit subscription']");
assert.notOk(
$(".o-mail-FollowerSubtypeDialog-subtype:contains(TestSubtype) input[type='checkbox']")[0]
.checked,
"checkbox should not be checked as follower subtype is not followed"
);
await click(
".o-mail-FollowerSubtypeDialog-subtype:contains(TestSubtype) input[type='checkbox']"
);
assert.ok(
$(".o-mail-FollowerSubtypeDialog-subtype:contains(TestSubtype) input[type='checkbox']")[0]
.checked
);
await click(
".o-mail-FollowerSubtypeDialog-subtype:contains(TestSubtype) input[type='checkbox']"
);
assert.notOk(
$(".o-mail-FollowerSubtypeDialog-subtype:contains(TestSubtype) input[type='checkbox']")[0]
.checked
);
});
QUnit.test("follower subtype apply", async (assert) => {
QUnit.test("simplest layout of a not followed subtype", async () => {
const pyEnv = await startServer();
const subtypeId = pyEnv["mail.message.subtype"].create({
default: true,
name: "TestSubtype",
});
const followerId = pyEnv["mail.followers"].create({
display_name: "François Perusse",
partner_id: pyEnv.currentPartnerId,
res_model: "res.partner",
res_id: pyEnv.currentPartnerId,
});
pyEnv["res.partner"].write([pyEnv.currentPartnerId], { message_follower_ids: [followerId] });
const { openView } = await start({
// FIXME: should adapt mock server code to provide "hasWriteAccess"
async mockRPC(route, args, performRPC) {
if (route === "/mail/thread/data") {
// mimic user with write access
const res = await performRPC(...arguments);
res["hasWriteAccess"] = true;
return res;
}
},
});
openView({
res_model: "res.partner",
res_id: pyEnv.currentPartnerId,
views: [[false, "form"]],
});
await click(".o-mail-Followers-button");
await click("button[title='Edit subscription']");
await contains(
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id=${subtypeId}] input[type='checkbox']:not(:checked)`
);
});
QUnit.test("toggle follower subtype checkbox", async () => {
const pyEnv = await startServer();
const subtypeId = pyEnv["mail.message.subtype"].create({
default: true,
name: "TestSubtype",
});
const followerId = pyEnv["mail.followers"].create({
display_name: "François Perusse",
partner_id: pyEnv.currentPartnerId,
res_model: "res.partner",
res_id: pyEnv.currentPartnerId,
});
pyEnv["res.partner"].write([pyEnv.currentPartnerId], { message_follower_ids: [followerId] });
const { openView } = await start({
// FIXME: should adapt mock server code to provide "hasWriteAccess"
async mockRPC(route, args, performRPC) {
if (route === "/mail/thread/data") {
// mimic user with write access
const res = await performRPC(...arguments);
res["hasWriteAccess"] = true;
return res;
}
},
});
openView({
res_model: "res.partner",
res_id: pyEnv.currentPartnerId,
views: [[false, "form"]],
});
await click(".o-mail-Followers-button");
await click("button[title='Edit subscription']");
await contains(
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id=${subtypeId}] input[type='checkbox']:not(:checked)`
);
await click(
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id=${subtypeId}] input[type='checkbox']`
);
await contains(
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id=${subtypeId}] input[type='checkbox']:checked`
);
await click(
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id=${subtypeId}] input[type='checkbox']`
);
await contains(
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id=${subtypeId}] input[type='checkbox']:not(:checked)`
);
});
QUnit.test("follower subtype apply", async () => {
const pyEnv = await startServer();
const subtypeId1 = pyEnv["mail.message.subtype"].create({
default: true,
name: "TestSubtype1",
});
pyEnv["mail.message.subtype"].create({
const subtypeId2 = pyEnv["mail.message.subtype"].create({
default: true,
name: "TestSubtype2",
});
@@ -161,20 +145,11 @@ QUnit.test("follower subtype apply", async (assert) => {
partner_id: pyEnv.currentPartnerId,
res_model: "res.partner",
res_id: pyEnv.currentPartnerId,
subtype_ids: [subtypeId],
subtype_ids: [subtypeId1],
});
pyEnv["res.partner"].write([pyEnv.currentPartnerId], { message_follower_ids: [followerId] });
const { openView } = await start({
services: {
notification: makeFakeNotificationService((message) => {
assert.strictEqual(
message,
"The subscription preferences were successfully applied."
);
}),
},
});
await openView({
const { openView } = await start();
openView({
res_model: "res.partner",
res_id: pyEnv.currentPartnerId,
views: [[false, "form"]],
@@ -182,15 +157,26 @@ QUnit.test("follower subtype apply", async (assert) => {
await click(".o-mail-Followers-button");
await click("button[title='Edit subscription']");
const subtype1 =
".o-mail-FollowerSubtypeDialog-subtype:contains(TestSubtype1) input[type='checkbox']";
const subtype2 =
".o-mail-FollowerSubtypeDialog-subtype:contains(TestSubtype2) input[type='checkbox']";
assert.ok($(subtype1)[0].checked);
assert.notOk($(subtype2)[0].checked);
await click(subtype1);
assert.notOk($(subtype1)[0].checked);
await click(subtype2);
assert.ok($(subtype2)[0].checked);
await contains(
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id=${subtypeId1}] input[type='checkbox']:checked`
);
await contains(
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id=${subtypeId2}] input[type='checkbox']:not(:checked)`
);
await click(
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id=${subtypeId1}] input[type='checkbox']`
);
await contains(
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id=${subtypeId1}] input[type='checkbox']:not(:checked)`
);
await click(
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id=${subtypeId2}] input[type='checkbox']`
);
await contains(
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id=${subtypeId2}] input[type='checkbox']:checked`
);
await click(".modal-footer button:contains(Apply)");
await contains(
".o_notification:contains(The subscription preferences were successfully applied.)"
);
});
+38 -38
View File
@@ -1,12 +1,12 @@
/* @odoo-module */
import { click, nextAnimationFrame, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { editInput, makeDeferred, patchWithCleanup } from "@web/../tests/helpers/utils";
QUnit.module("follower");
QUnit.test("base rendering not editable", async (assert) => {
QUnit.test("base rendering not editable", async () => {
const pyEnv = await startServer();
const [threadId, partnerId] = pyEnv["res.partner"].create([{}, {}]);
pyEnv["mail.followers"].create({
@@ -25,19 +25,19 @@ QUnit.test("base rendering not editable", async (assert) => {
}
},
});
await openView({
openView({
res_id: threadId,
res_model: "res.partner",
views: [[false, "form"]],
});
await click(".o-mail-Followers-button");
assert.containsOnce($, ".o-mail-Follower");
assert.containsOnce($, ".o-mail-Follower-details");
assert.containsOnce($, ".o-mail-Follower-avatar");
assert.containsNone($, ".o-mail-Follower-action");
await contains(".o-mail-Follower");
await contains(".o-mail-Follower-details");
await contains(".o-mail-Follower-avatar");
await contains(".o-mail-Follower-action", 0);
});
QUnit.test("base rendering editable", async (assert) => {
QUnit.test("base rendering editable", async () => {
const pyEnv = await startServer();
const [threadId, partnerId] = pyEnv["res.partner"].create([{}, {}]);
pyEnv["mail.followers"].create({
@@ -47,18 +47,18 @@ QUnit.test("base rendering editable", async (assert) => {
res_model: "res.partner",
});
const { openView } = await start();
await openView({
openView({
res_id: threadId,
res_model: "res.partner",
views: [[false, "form"]],
});
await click(".o-mail-Followers-button");
assert.containsOnce($, ".o-mail-Follower");
assert.containsOnce($, ".o-mail-Follower-details");
assert.containsOnce($, ".o-mail-Follower-avatar");
assert.containsOnce($, ".o-mail-Follower");
assert.containsOnce($, "button[title='Edit subscription']");
assert.containsOnce($, "button[title='Remove this follower']");
await contains(".o-mail-Follower");
await contains(".o-mail-Follower-details");
await contains(".o-mail-Follower-avatar");
await contains(".o-mail-Follower");
await contains("button[title='Edit subscription']");
await contains("button[title='Remove this follower']");
});
QUnit.test("click on partner follower details", async (assert) => {
@@ -72,7 +72,7 @@ QUnit.test("click on partner follower details", async (assert) => {
});
const openFormDef = makeDeferred();
const { env, openView } = await start();
await openView({
openView({
res_id: threadId,
res_model: "res.partner",
views: [[false, "form"]],
@@ -87,8 +87,8 @@ QUnit.test("click on partner follower details", async (assert) => {
},
});
await click(".o-mail-Followers-button");
assert.containsOnce($, ".o-mail-Follower");
assert.containsOnce($, ".o-mail-Follower-details");
await contains(".o-mail-Follower");
await contains(".o-mail-Follower-details");
$(".o-mail-Follower-details")[0].click();
await openFormDef;
@@ -111,19 +111,19 @@ QUnit.test("click on edit follower", async (assert) => {
}
},
});
await openView({
openView({
res_id: threadId,
res_model: "res.partner",
views: [[false, "form"]],
});
await click(".o-mail-Followers-button");
assert.containsOnce($, ".o-mail-Follower");
assert.containsOnce($, "button[title='Edit subscription']");
await contains(".o-mail-Follower");
await contains("button[title='Edit subscription']");
await click("button[title='Edit subscription']");
assert.containsNone($, ".o-mail-Follower");
await contains(".o-mail-Follower", 0);
assert.verifySteps(["fetch_subtypes"]);
assert.containsOnce($, ".o-mail-FollowerSubtypeDialog");
await contains(".o-mail-FollowerSubtypeDialog");
});
QUnit.test("edit follower and close subtype dialog", async (assert) => {
@@ -142,21 +142,21 @@ QUnit.test("edit follower and close subtype dialog", async (assert) => {
}
},
});
await openView({
openView({
res_id: threadId,
res_model: "res.partner",
views: [[false, "form"]],
});
await click(".o-mail-Followers-button");
assert.containsOnce($, ".o-mail-Follower");
assert.containsOnce($, "button[title='Edit subscription']");
await contains(".o-mail-Follower");
await contains("button[title='Edit subscription']");
await click("button[title='Edit subscription']");
await contains(".o-mail-FollowerSubtypeDialog");
assert.verifySteps(["fetch_subtypes"]);
assert.containsOnce($, ".o-mail-FollowerSubtypeDialog");
await click(".o-mail-FollowerSubtypeDialog button:contains(Cancel)");
assert.containsNone($, ".o-mail-FollowerSubtypeDialog");
await contains(".o-mail-FollowerSubtypeDialog", 0);
});
QUnit.test("remove a follower in a dirty form view", async (assert) => {
@@ -181,19 +181,17 @@ QUnit.test("remove a follower in a dirty form view", async (assert) => {
</form>`,
};
const { openFormView } = await start({ serverData: { views } });
await openFormView("res.partner", threadId);
click(".o_field_many2many_tags[name='channel_ids'] input").catch(() => {});
await nextAnimationFrame();
click(".dropdown-item:contains(General)").catch(() => {});
await nextAnimationFrame();
assert.containsOnce($, ".o_tag:contains(General)");
assert.strictEqual($(".o-mail-Followers-counter")[0].innerText, "1");
openFormView("res.partner", threadId);
await click(".o_field_many2many_tags[name='channel_ids'] input");
await click(".dropdown-item:contains(General)");
await contains(".o_tag:contains(General)");
await contains(".o-mail-Followers-counter:contains(1)");
await editInput(document.body, ".o_field_char[name=name] input", "some value");
await click(".o-mail-Followers-button");
await click("button[title='Remove this follower']");
assert.strictEqual($(".o-mail-Followers-counter")[0].innerText, "0");
await contains(".o-mail-Followers-counter:contains(0)");
assert.strictEqual($(".o_field_char[name=name] input").val(), "some value");
assert.containsOnce($, ".o_tag:contains(General)");
await contains(".o_tag:contains(General)");
});
QUnit.test("removing a follower should reload form view", async function (assert) {
@@ -212,9 +210,11 @@ QUnit.test("removing a follower should reload form view", async function (assert
}
},
});
await openFormView("res.partner", threadId);
openFormView("res.partner", threadId);
await contains(".o-mail-Followers-button");
assert.verifySteps([`read ${threadId}`]);
await click(".o-mail-Followers-button");
await click("button[title='Remove this follower']");
await contains(".o-mail-Followers-counter:contains(0)");
assert.verifySteps([`read ${threadId}`]);
});
@@ -1,37 +1,10 @@
/* @odoo-module */
import { patchUiSize, SIZES } from "@mail/../tests/helpers/patch_ui_size";
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("Form renderer");
QUnit.test(
"schedule activities on draft record should prompt with scheduling an activity (proceed with action)",
async (assert) => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const views = {
"res.partner,false,form": `
<form string="Partners">
<sheet>
<field name="name"/>
</sheet>
<div class="oe_chatter">
<field name="activity_ids"/>
</div>
</form>`,
};
const { openView } = await start({ serverData: { views } });
await openView({
res_id: partnerId,
res_model: "res.partner",
views: [[false, "form"]],
});
await click("button:contains(Activities)");
assert.containsOnce($, ".o_dialog:contains(Schedule Activity)");
}
);
QUnit.test("Form view not scrolled when switching record", async (assert) => {
const pyEnv = await startServer();
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
@@ -62,7 +35,7 @@ QUnit.test("Form view not scrolled when switching record", async (assert) => {
};
patchUiSize({ size: SIZES.LG });
const { openView } = await start({ serverData: { views } });
await openView(
openView(
{
res_model: "res.partner",
res_id: partnerId_1,
@@ -70,12 +43,12 @@ QUnit.test("Form view not scrolled when switching record", async (assert) => {
},
{ resIds: [partnerId_1, partnerId_2] }
);
assert.strictEqual($(".o_breadcrumb .active").text(), "Partner 1");
await contains(".o_breadcrumb .active:contains(Partner 1)");
assert.strictEqual($(".o_content")[0].scrollTop, 0);
$(".o_content")[0].scrollTop = 150;
await click(".o_pager_next");
assert.strictEqual($(".o_breadcrumb .active").text(), "Partner 2");
await contains(".o_breadcrumb .active:contains(Partner 2)");
assert.strictEqual($(".o_content")[0].scrollTop, 0);
await click(".o_pager_previous");
@@ -84,7 +57,7 @@ QUnit.test("Form view not scrolled when switching record", async (assert) => {
QUnit.test(
"Attachments that have been unlinked from server should be visually unlinked from record",
async (assert) => {
async () => {
// Attachments that have been fetched from a record at certain time and then
// removed from the server should be reflected on the UI when the current
// partner accesses this record again.
@@ -117,7 +90,7 @@ QUnit.test(
</form>`,
};
const { openView } = await start({ serverData: { views } });
await openView(
openView(
{
res_model: "res.partner",
res_id: partnerId_1,
@@ -128,21 +101,21 @@ QUnit.test(
resIds: [partnerId_1, partnerId_2],
}
);
assert.containsOnce($, ".o-mail-Chatter button:contains(2)");
await contains("button[aria-label='Attach files']:contains(2)");
// The attachment links are updated on (re)load,
// so using pager is a way to reload the record "Partner1".
await click(".o_pager_next");
await contains("button[aria-label='Attach files']:not(:has(span))");
// Simulate unlinking attachment 1 from Partner 1.
pyEnv["ir.attachment"].write([attachmentId_1], { res_id: 0 });
await click(".o_pager_previous");
assert.containsOnce($, ".o-mail-Chatter button:contains(1)");
await contains("button[aria-label='Attach files']:contains(1)");
}
);
QUnit.test(
"read more/less links are not duplicated when switching from read to edit mode",
async (assert) => {
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["mail.message"].create({
@@ -179,10 +152,10 @@ QUnit.test(
res_id: partnerId,
views: [[false, "form"]],
};
await openView(openViewAction);
assert.containsOnce($, ".o-mail-Chatter");
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-read-more-less");
openView(openViewAction);
await contains(".o-mail-Chatter");
await contains(".o-mail-Message");
await contains(".o-mail-read-more-less");
}
);
@@ -225,18 +198,18 @@ QUnit.test("read more links becomes read less after being clicked", async (asser
res_id: partnerId,
views: [[false, "form"]],
};
await openView(openViewAction);
assert.containsOnce($, ".o-mail-Chatter");
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-read-more-less:contains(Read More)");
openView(openViewAction);
await contains(".o-mail-Chatter");
await contains(".o-mail-Message");
await contains(".o-mail-read-more-less:contains(Read More)");
await click(".o-mail-read-more-less");
assert.containsOnce($, ".o-mail-read-more-less:contains(Read Less)");
await contains(".o-mail-read-more-less:contains(Read Less)");
});
QUnit.test(
"[TECHNICAL] unfolded read more/less links should not fold on message click besides those button links",
async (assert) => {
async () => {
// message click triggers a re-render. Before writing of this test, the
// insertion of read more/less links were done during render. This meant
// any re-render would re-insert the read more/less links. If some button
@@ -277,22 +250,22 @@ QUnit.test(
</form>`,
};
const { openView } = await start({ serverData: { views } });
await openView({
openView({
res_model: "res.partner",
res_id: partnerId,
views: [[false, "form"]],
});
assert.containsOnce($, ".o-mail-read-more-less:contains(Read More)");
await contains(".o-mail-read-more-less:contains(Read More)");
await click(".o-mail-read-more-less");
assert.containsOnce($, ".o-mail-read-more-less:contains(Read Less)");
await contains(".o-mail-read-more-less:contains(Read Less)");
await click(".o-mail-Message");
assert.containsOnce($, ".o-mail-read-more-less:contains(Read Less)");
await contains(".o-mail-read-more-less:contains(Read Less)");
}
);
QUnit.test("read more/less links on message of type notification", async (assert) => {
QUnit.test("read more/less links on message of type notification", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["mail.message"].create({
@@ -330,6 +303,6 @@ QUnit.test("read more/less links on message of type notification", async (assert
res_id: partnerId,
views: [[false, "form"]],
};
await openView(openViewAction);
assert.containsOnce($, ".o-mail-Message a:contains(Read More)");
openView(openViewAction);
await contains(".o-mail-Message a:contains(Read More)");
});
@@ -1,20 +1,16 @@
/** @odoo-module **/
/* @odoo-module */
import testUtils from '@web/../tests/legacy/helpers/test_utils';
import { registry } from "@web/core/registry";
import {
getFixture,
nextTick,
} from '@web/../tests/helpers/utils';
import { setupViewRegistries } from "@web/../tests/views/helpers";
import { contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { addModelNamesToFetch } from "@bus/../tests/helpers/model_definitions_helpers";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { fileUploadService } from "@web/core/file_upload/file_upload_service";
import { registry } from "@web/core/registry";
import testUtils from "@web/../tests/legacy/helpers/test_utils";
import { getFixture, nextTick } from "@web/../tests/helpers/utils";
import { setupViewRegistries } from "@web/../tests/views/helpers";
import { addModelNamesToFetch } from '@bus/../tests/helpers/model_definitions_helpers';
addModelNamesToFetch([
'mrp.document',
]);
addModelNamesToFetch(["mrp.document"]);
const serviceRegistry = registry.category("services");
@@ -223,9 +219,9 @@ QUnit.module('MrpDocumentsKanbanView', {
res_model: "mrp.document",
views: [[false, "kanban"]],
});
(await contains(".o_kanban_previewer")).click();
await click(".o_kanban_previewer");
await contains(".o-FileViewer");
(await contains(".o-FileViewer-headerButton .fa-times")).click();
await click(".o-FileViewer-headerButton .fa-times");
await contains(".o-FileViewer", 0);
});
});
@@ -1,75 +1,76 @@
/** @odoo-module **/
/* @odoo-module */
import { click, start } from "@mail/../tests/helpers/test_utils";
import { getFixture, editInput, patchDate } from "@web/../tests/helpers/utils";
import { click, contains, insertText, start } from "@mail/../tests/helpers/test_utils";
let target;
import { editInput, patchDate } from "@web/../tests/helpers/utils";
QUnit.module("todo activity menu", {
async beforeEach() {
target = getFixture();
},
});
QUnit.test("create todo from activity menu without date", async function (assert) {
QUnit.test("create todo from activity menu without date", async function () {
await start();
assert.containsOnce(target, ".o_menu_systray i[aria-label='Activities']");
assert.containsNone(target, ".o-mail-ActivityMenu-counter");
await contains(".o_menu_systray i[aria-label='Activities']");
await contains(".o-mail-ActivityMenu-counter", 0);
await click(".o_menu_systray i[aria-label='Activities']");
assert.containsOnce(
target,
await contains(
".o-mail-ActivityMenu:contains(Congratulations, you're done with your activities.)"
);
assert.containsOnce(target, ".btn:contains(Add a To-do)");
await click(".btn:contains(Add a To-do)");
assert.containsNone(target, ".btn:contains(Add a To-do)");
assert.containsOnce(target, ".o-mail-ActivityMenu-show");
await editInput(target, "input.o-mail-ActivityMenu-input", "New To-do");
await contains(".btn:contains(Add a To-do)", 0);
await contains(".o-mail-ActivityMenu-show");
await insertText("input.o-mail-ActivityMenu-input", "New To-do");
await click(".btn:contains(SAVE)");
await contains(
".o_notification:contains(Your to-do has been successfully added to your tasks and scheduled for completion.)"
);
// Need to reopen systray as it automatically closes when a todo is created
await click(".o_menu_systray i[aria-label='Activities']");
await contains(".o-mail-ActivityMenu-counter:contains(1)");
await contains(".o-mail-ActivityGroup");
await contains(".o-mail-ActivityGroup:contains(project.task)");
await contains(".btn:contains(1 Today)");
await contains(".btn:contains(Add a To-do)");
await contains(".o-mail-ActivityMenu-show", 0);
await contains(".o-mail-ActivityMenu-input", 0);
await click(".btn:contains(Add a To-do)");
await contains(".btn:contains(Add a To-do)", 0);
await insertText("input.o-mail-ActivityMenu-input", "New To-do");
await click(".btn:contains(SAVE)");
// Need to reopen systray as it automatically closes when a todo is created
assert.containsOnce(target, ".o_menu_systray i[aria-label='Activities']");
await contains(
".o_notification:contains(Your to-do has been successfully added to your tasks and scheduled for completion.)",
2
);
await click(".o_menu_systray i[aria-label='Activities']");
assert.containsOnce(target, ".o-mail-ActivityMenu-counter:contains(1)");
assert.containsOnce(target, ".o-mail-ActivityGroup");
assert.containsOnce(target, ".o-mail-ActivityGroup:contains(project.task)");
assert.containsOnce(target, ".btn:contains(1 Today)");
assert.containsOnce(target, ".btn:contains(Add a To-do)");
assert.containsNone(target, ".o-mail-ActivityMenu-show");
assert.containsNone(target, ".o-mail-ActivityMenu-input");
await click(".btn:contains(Add a To-do)");
assert.containsNone(target, ".btn:contains(Add a To-do)");
await editInput(target, "input.o-mail-ActivityMenu-input", "New To-do");
await click(".btn:contains(SAVE)");
// Need to reopen systray as it automatically closes when a todo is created
assert.containsOnce(target, ".o_menu_systray i[aria-label='Activities']");
await click(".o_menu_systray i[aria-label='Activities']");
assert.containsOnce(target, ".o-mail-ActivityMenu-counter:contains(2)");
assert.containsOnce(target, ".btn:contains(2 Today)");
await contains(".o-mail-ActivityMenu-counter:contains(2)");
await contains(".btn:contains(2 Today)");
});
QUnit.test("create to-do from activity menu with date", async function (assert) {
QUnit.test("create to-do from activity menu with date", async function () {
patchDate(2023, 1, 1, 6, 0, 0);
const { DateTime } = luxon;
const today = DateTime.utc();
const futureDay = today.plus({ days: 2 });
await start();
assert.containsOnce(target, ".o_menu_systray i[aria-label='Activities']");
assert.containsNone(target, ".o-mail-ActivityMenu-counter");
await contains(".o_menu_systray i[aria-label='Activities']");
await contains(".o-mail-ActivityMenu-counter", 0);
await click(".o_menu_systray i[aria-label='Activities']");
await click(".btn:contains(Add a To-do)");
await editInput(target, "input.o-mail-ActivityMenu-input", "New To-do");
await insertText("input.o-mail-ActivityMenu-input", "New To-do");
await editInput(
target,
document.body,
"input.o_datetime_input",
futureDay.toString(luxon.DateTime.DATE_SHORT)
futureDay.toString(luxon.DateTime.DATE_SHORT),
{
replace: true,
}
);
await click(".btn:contains(SAVE)");
await contains(
".o_notification:contains(Your to-do has been successfully added to your tasks and scheduled for completion.)"
);
// Need to reopen systray as it automatically closes when a todo is created
assert.containsOnce(target, ".o_menu_systray i[aria-label='Activities']");
await click(".o_menu_systray i[aria-label='Activities']");
assert.containsOnce(target, ".o-mail-ActivityMenu-counter:contains(1)");
assert.containsOnce(target, ".o-mail-ActivityGroup");
assert.containsOnce(target, ".o-mail-ActivityGroup:contains(project.task)");
assert.containsOnce(target, ".btn:contains(1 Future)");
await contains(".o-mail-ActivityMenu-counter:contains(1)");
await contains(".o-mail-ActivityGroup");
await contains(".o-mail-ActivityGroup:contains(project.task)");
await contains(".btn:contains(1 Future)");
});
@@ -1,11 +1,12 @@
/** @odoo-module **/
/* @odoo-module */
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { start, startServer, click } from "@mail/../tests/helpers/test_utils";
import { patchWithCleanup, triggerEvent } from "@web/../tests/helpers/utils";
QUnit.module("messaging menu (patch)");
QUnit.test("mark as read", async (assert) => {
QUnit.test("mark as read", async () => {
const pyEnv = await startServer();
const messageId = pyEnv["mail.message"].create({
message_type: "sms",
@@ -20,15 +21,12 @@ QUnit.test("mark as read", async (assert) => {
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem");
await triggerEvent($(".o-mail-NotificationItem")[0], null, "mouseenter");
assert.containsOnce($, ".o-mail-NotificationItem [title='Mark As Read']");
assert.containsOnce(
$,
".o-mail-NotificationItem:contains(An error occurred when sending an SMS)"
);
await contains(".o-mail-NotificationItem [title='Mark As Read']");
await contains(".o-mail-NotificationItem:contains(An error occurred when sending an SMS)");
await click(".o-mail-NotificationItem [title='Mark As Read']");
assert.containsNone($, ".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem", 0);
});
QUnit.test("notifications grouped by notification_type", async (assert) => {
@@ -72,7 +70,7 @@ QUnit.test("notifications grouped by notification_type", async (assert) => {
]);
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsN($, ".o-mail-NotificationItem", 2);
await contains(".o-mail-NotificationItem", 2);
const items = $(".o-mail-NotificationItem");
assert.ok(items[0].textContent.includes("Partner"));
assert.ok(items[0].textContent.includes("2")); // counter
@@ -135,8 +133,8 @@ QUnit.test("grouped notifications by document model", async (assert) => {
});
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-NotificationItem:contains(Partner) .badge:contains(2)");
await contains(".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem:contains(Partner) .badge:contains(2)");
await click(".o-mail-NotificationItem");
assert.verifySteps(["do_action"]);
});
@@ -1,6 +1,7 @@
/** @odoo-module **/
/* @odoo-module */
import { click, contains, startServer, start } from "@mail/../tests/helpers/test_utils";
import { startServer, start, click } from "@mail/../tests/helpers/test_utils";
import { makeDeferred, patchWithCleanup } from "@web/../tests/helpers/utils";
QUnit.module("message (patch)");
@@ -21,17 +22,17 @@ QUnit.test("Notification Sent", async (assert) => {
res_partner_id: partnerId,
});
const { openFormView } = await start();
await openFormView("res.partner", partnerId);
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message-notification");
assert.containsOnce($, ".o-mail-Message-notification i");
openFormView("res.partner", partnerId);
await contains(".o-mail-Message");
await contains(".o-mail-Message-notification");
await contains(".o-mail-Message-notification i");
assert.hasClass($(".o-mail-Message-notification i"), "fa-mobile");
await click(".o-mail-Message-notification");
assert.containsOnce($, ".o-mail-MessageNotificationPopover");
assert.containsOnce($, ".o-mail-MessageNotificationPopover i");
await contains(".o-mail-MessageNotificationPopover");
await contains(".o-mail-MessageNotificationPopover i");
assert.hasClass($(".o-mail-MessageNotificationPopover i"), "fa-check");
assert.containsOnce($, ".o-mail-MessageNotificationPopover:contains(Someone)");
await contains(".o-mail-MessageNotificationPopover:contains(Someone)");
});
QUnit.test("Notification Error", async (assert) => {
@@ -51,7 +52,7 @@ QUnit.test("Notification Error", async (assert) => {
res_partner_id: partnerId,
});
const { env, openFormView } = await start();
await openFormView("res.partner", partnerId);
openFormView("res.partner", partnerId);
patchWithCleanup(env.services.action, {
doAction(action, options) {
assert.step("do_action");
@@ -61,11 +62,11 @@ QUnit.test("Notification Error", async (assert) => {
},
});
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message-notification");
assert.containsOnce($, ".o-mail-Message-notification i");
await contains(".o-mail-Message");
await contains(".o-mail-Message-notification");
await contains(".o-mail-Message-notification i");
assert.hasClass($(".o-mail-Message-notification i"), "fa-mobile");
click(".o-mail-Message-notification").catch(() => {});
await click(".o-mail-Message-notification");
await openResendActionDef;
assert.verifySteps(["do_action"]);
});
@@ -1,6 +1,7 @@
/** @odoo-module **/
/* @odoo-module */
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { start, startServer, click } from "@mail/../tests/helpers/test_utils";
import { makeDeferred, patchWithCleanup } from "@web/../tests/helpers/utils";
QUnit.module("message (patch)");
@@ -24,15 +25,15 @@ QUnit.test("Sent", async (assert) => {
res_partner_id: partnerId,
});
const { openFormView } = await start();
await openFormView("res.partner", partnerId);
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message-notification");
assert.containsOnce($, ".o-mail-Message-notification i");
openFormView("res.partner", partnerId);
await contains(".o-mail-Message");
await contains(".o-mail-Message-notification");
await contains(".o-mail-Message-notification i");
assert.hasClass($(".o-mail-Message-notification i"), "fa-paper-plane");
await click(".o-mail-Message-notification");
assert.containsOnce($, ".o-snailmail-SnailmailNotificationPopover");
assert.containsOnce($, ".o-snailmail-SnailmailNotificationPopover i");
await contains(".o-snailmail-SnailmailNotificationPopover");
await contains(".o-snailmail-SnailmailNotificationPopover i");
assert.hasClass($(".o-snailmail-SnailmailNotificationPopover i"), "fa-check");
assert.strictEqual($(".o-snailmail-SnailmailNotificationPopover").text(), "Sent");
});
@@ -56,15 +57,15 @@ QUnit.test("Canceled", async (assert) => {
res_partner_id: partnerId,
});
const { openFormView } = await start();
await openFormView("res.partner", partnerId);
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message-notification");
assert.containsOnce($, ".o-mail-Message-notification i");
openFormView("res.partner", partnerId);
await contains(".o-mail-Message");
await contains(".o-mail-Message-notification");
await contains(".o-mail-Message-notification i");
assert.hasClass($(".o-mail-Message-notification i"), "fa-paper-plane");
await click(".o-mail-Message-notification");
assert.containsOnce($, ".o-snailmail-SnailmailNotificationPopover");
assert.containsOnce($, ".o-snailmail-SnailmailNotificationPopover i");
await contains(".o-snailmail-SnailmailNotificationPopover");
await contains(".o-snailmail-SnailmailNotificationPopover i");
assert.hasClass($(".o-snailmail-SnailmailNotificationPopover i"), "fa-trash-o");
assert.strictEqual($(".o-snailmail-SnailmailNotificationPopover").text(), "Canceled");
});
@@ -88,15 +89,15 @@ QUnit.test("Pending", async (assert) => {
res_partner_id: partnerId,
});
const { openFormView } = await start();
await openFormView("res.partner", partnerId);
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message-notification");
assert.containsOnce($, ".o-mail-Message-notification i");
openFormView("res.partner", partnerId);
await contains(".o-mail-Message");
await contains(".o-mail-Message-notification");
await contains(".o-mail-Message-notification i");
assert.hasClass($(".o-mail-Message-notification i"), "fa-paper-plane");
await click(".o-mail-Message-notification");
assert.containsOnce($, ".o-snailmail-SnailmailNotificationPopover");
assert.containsOnce($, ".o-snailmail-SnailmailNotificationPopover i");
await contains(".o-snailmail-SnailmailNotificationPopover");
await contains(".o-snailmail-SnailmailNotificationPopover i");
assert.hasClass($(".o-snailmail-SnailmailNotificationPopover i"), "fa-clock-o");
assert.strictEqual($(".o-snailmail-SnailmailNotificationPopover").text(), "Awaiting Dispatch");
});
@@ -131,21 +132,21 @@ QUnit.test("No Price Available", async (assert) => {
}
},
});
await openFormView("res.partner", partnerId);
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message-notification");
assert.containsOnce($, ".o-mail-Message-notification i");
openFormView("res.partner", partnerId);
await contains(".o-mail-Message");
await contains(".o-mail-Message-notification");
await contains(".o-mail-Message-notification i");
assert.hasClass($(".o-mail-Message-notification i"), "fa-paper-plane");
await click(".o-mail-Message-notification");
assert.containsOnce($, ".o-snailmail-SnailmailError");
await contains(".o-snailmail-SnailmailError");
assert.strictEqual(
$(".o-snailmail-SnailmailError .modal-body").text().trim(),
"The country to which you want to send the letter is not supported by our service."
);
assert.containsOnce($, "button:contains(Cancel letter)");
await contains("button:contains(Cancel letter)");
await click("button:contains(Cancel letter)");
assert.containsNone($, ".o-snailmail-SnailmailError");
await contains(".o-snailmail-SnailmailError", 0);
assert.verifySteps(["cancel_letter"]);
});
@@ -179,22 +180,22 @@ QUnit.test("Credit Error", async (assert) => {
}
},
});
await openFormView("res.partner", partnerId);
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message-notification");
assert.containsOnce($, ".o-mail-Message-notification i");
openFormView("res.partner", partnerId);
await contains(".o-mail-Message");
await contains(".o-mail-Message-notification");
await contains(".o-mail-Message-notification i");
assert.hasClass($(".o-mail-Message-notification i"), "fa-paper-plane");
await click(".o-mail-Message-notification");
assert.containsOnce($, ".o-snailmail-SnailmailError");
await contains(".o-snailmail-SnailmailError");
assert.strictEqual(
$(".o-snailmail-SnailmailError p").text().trim(),
"The letter could not be sent due to insufficient credits on your IAP account."
);
assert.containsOnce($, "button:contains(Re-send letter)");
assert.containsOnce($, "button:contains(Cancel letter)");
await contains("button:contains(Re-send letter)");
await contains("button:contains(Cancel letter)");
await click("button:contains(Re-send letter)");
assert.containsNone($, ".o-snailmail-SnailmailError");
await contains(".o-snailmail-SnailmailError", 0);
assert.verifySteps(["send_letter"]);
});
@@ -228,22 +229,22 @@ QUnit.test("Trial Error", async (assert) => {
}
},
});
await openFormView("res.partner", partnerId);
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message-notification");
assert.containsOnce($, ".o-mail-Message-notification i");
openFormView("res.partner", partnerId);
await contains(".o-mail-Message");
await contains(".o-mail-Message-notification");
await contains(".o-mail-Message-notification i");
assert.hasClass($(".o-mail-Message-notification i"), "fa-paper-plane");
await click(".o-mail-Message-notification");
assert.containsOnce($, ".o-snailmail-SnailmailError");
await contains(".o-snailmail-SnailmailError");
assert.strictEqual(
$(".o-snailmail-SnailmailError p").text().trim(),
"You need credits on your IAP account to send a letter."
);
assert.containsOnce($, "button:contains(Re-send letter)");
assert.containsOnce($, "button:contains(Cancel letter)");
await contains("button:contains(Re-send letter)");
await contains("button:contains(Cancel letter)");
await click("button:contains(Re-send letter)");
assert.containsNone($, ".o-snailmail-SnailmailError");
await contains(".o-snailmail-SnailmailError", 0);
assert.verifySteps(["send_letter"]);
});
@@ -268,7 +269,7 @@ QUnit.test("Format Error", async (assert) => {
res_partner_id: partnerId,
});
const { env, openFormView } = await start();
await openFormView("res.partner", partnerId);
openFormView("res.partner", partnerId);
patchWithCleanup(env.services.action, {
doAction(action, options) {
assert.step("do_action");
@@ -277,9 +278,9 @@ QUnit.test("Format Error", async (assert) => {
openFormatErrorActionDef.resolve();
},
});
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message-notification");
assert.containsOnce($, ".o-mail-Message-notification i");
await contains(".o-mail-Message");
await contains(".o-mail-Message-notification");
await contains(".o-mail-Message-notification i");
assert.hasClass($(".o-mail-Message-notification i"), "fa-paper-plane");
click(".o-mail-Message-notification").then(() => {});
@@ -307,7 +308,7 @@ QUnit.test("Missing Required Fields", async (assert) => {
message_id: messageId,
});
const { env, openFormView } = await start();
await openFormView("res.partner", partnerId);
openFormView("res.partner", partnerId);
patchWithCleanup(env.services.action, {
doAction(action, options) {
assert.step("do_action");
@@ -316,9 +317,9 @@ QUnit.test("Missing Required Fields", async (assert) => {
openRequiredFieldsActionDef.resolve();
},
});
assert.containsOnce($, ".o-mail-Message");
assert.containsOnce($, ".o-mail-Message-notification");
assert.containsOnce($, ".o-mail-Message-notification i");
await contains(".o-mail-Message");
await contains(".o-mail-Message-notification");
await contains(".o-mail-Message-notification i");
assert.hasClass($(".o-mail-Message-notification i"), "fa-paper-plane");
click(".o-mail-Message-notification").then(() => {});
@@ -1,11 +1,12 @@
/** @odoo-module **/
/* @odoo-module */
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { start, startServer, click } from "@mail/../tests/helpers/test_utils";
import { patchWithCleanup, triggerEvent } from "@web/../tests/helpers/utils";
QUnit.module("messaging menu (patch)");
QUnit.test("mark as read", async (assert) => {
QUnit.test("mark as read", async () => {
const pyEnv = await startServer();
const messageId = pyEnv["mail.message"].create([
{
@@ -22,15 +23,14 @@ QUnit.test("mark as read", async (assert) => {
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem");
await triggerEvent($(".o-mail-NotificationItem")[0], null, "mouseenter");
assert.containsOnce($, ".o-mail-NotificationItem [title='Mark As Read']");
assert.containsOnce(
$,
await contains(".o-mail-NotificationItem [title='Mark As Read']");
await contains(
".o-mail-NotificationItem:contains(An error occurred when sending a letter with Snailmail.)"
);
await click(".o-mail-NotificationItem [title='Mark As Read']");
assert.containsNone($, ".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem", 0);
});
QUnit.test("notifications grouped by notification_type", async (assert) => {
@@ -74,7 +74,7 @@ QUnit.test("notifications grouped by notification_type", async (assert) => {
]);
await start();
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsN($, ".o-mail-NotificationItem", 2);
await contains(".o-mail-NotificationItem", 2);
assert.ok($(".o-mail-NotificationItem:eq(0)").text().includes("Partner"));
assert.ok($(".o-mail-NotificationItem:eq(0)").text().includes("2")); // counter
assert.ok(
@@ -144,9 +144,9 @@ QUnit.test("grouped notifications by document model", async (assert) => {
},
});
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsOnce($, ".o-mail-NotificationItem");
assert.containsOnce($, ".o-mail-NotificationItem:contains(Partner)");
assert.containsOnce($, ".o-mail-NotificationItem:contains(2)"); // counter
await contains(".o-mail-NotificationItem");
await contains(".o-mail-NotificationItem:contains(Partner)");
await contains(".o-mail-NotificationItem:contains(2)"); // counter
await click(".o-mail-NotificationItem");
assert.verifySteps(["do_action"]);
});
@@ -1,6 +1,6 @@
/** @odoo-module **/
/* @odoo-module */
import { start, startServer, click } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
QUnit.module("properties field");
@@ -63,7 +63,7 @@ async function testPropertyFieldAvatarOpenChat(assert, propertyType) {
);
assert.verifySteps(["read res.users"]);
const chatter = document.querySelectorAll(".o-mail-ChatWindow-name");
const chatter = await contains(".o-mail-ChatWindow-name");
assert.strictEqual(chatter.length, 1);
assert.strictEqual(chatter[0].innerText, "Partner Test");
}
@@ -1,14 +1,14 @@
/** @odoo-module **/
/* @odoo-module */
import { start, startServer, click } from "@mail/../tests/helpers/test_utils";
import { click, contains, start, startServer } from "@mail/../tests/helpers/test_utils";
import { session } from "@web/session";
import { date_to_str } from "@web/legacy/js/core/time";
import { session } from "@web/session";
import { patchWithCleanup } from "@web/../tests/helpers/utils";
QUnit.module("activity menu");
QUnit.test("menu with no records", async (assert) => {
QUnit.test("menu with no records", async () => {
await start({
async mockRPC(route, args) {
if (args.method === "systray_get_activities") {
@@ -17,13 +17,12 @@ QUnit.test("menu with no records", async (assert) => {
},
});
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Activities'])");
assert.containsOnce(
$,
await contains(
".o-mail-ActivityMenu:contains(Congratulations, you're done with your activities.)"
);
});
QUnit.test("do not show empty text when at least some future activities", async (assert) => {
QUnit.test("do not show empty text when at least some future activities", async () => {
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const pyEnv = await startServer();
@@ -37,9 +36,9 @@ QUnit.test("do not show empty text when at least some future activities", async
]);
await start();
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Activities'])");
assert.containsNone(
$,
".o-mail-ActivityMenu:contains(Congratulations, you're done with your activities.)"
await contains(
".o-mail-ActivityMenu:contains(Congratulations, you're done with your activities.)",
0
);
});
@@ -71,9 +70,9 @@ QUnit.test("activity menu widget: activity menu with 2 models", async (assert) =
},
]);
const { env } = await start();
assert.containsOnce($, ".o_menu_systray i[aria-label='Activities']");
assert.containsOnce($, ".o-mail-ActivityMenu-counter");
assert.containsOnce($, ".o-mail-ActivityMenu-counter:contains(5)");
await contains(".o_menu_systray i[aria-label='Activities']");
await contains(".o-mail-ActivityMenu-counter");
await contains(".o-mail-ActivityMenu-counter:contains(5)");
let context = {};
patchWithCleanup(env.services.action, {
doAction(action) {
@@ -85,22 +84,24 @@ QUnit.test("activity menu widget: activity menu with 2 models", async (assert) =
search_default_activities_overdue: 1,
};
await click(".o_menu_systray i[aria-label='Activities']");
assert.containsOnce($, ".o-mail-ActivityMenu");
assert.containsN($, ".o-mail-ActivityMenu .o-mail-ActivityGroup", 2);
await contains(".o-mail-ActivityMenu");
await contains(".o-mail-ActivityMenu .o-mail-ActivityGroup", 2);
await click(".o-mail-ActivityMenu .o-mail-ActivityGroup button:contains('Late')");
assert.containsNone($, ".o-mail-ActivityMenu");
await contains(".o-mail-ActivityMenu", 0);
context = {
force_search_count: 1,
search_default_activities_today: 1,
};
await click(".o_menu_systray .dropdown-toggle:has(i[aria-label='Activities'])");
await click(".o-mail-ActivityMenu .o-mail-ActivityGroup button:contains('Today')");
await click(".o-mail-ActivityMenu .o-mail-ActivityGroup button:contains('Today'):eq(0)");
await contains(".o-mail-ActivityMenu", 0);
context = {
force_search_count: 1,
search_default_activities_upcoming_all: 1,
};
await click(".o_menu_systray i[aria-label='Activities']");
await click(".o-mail-ActivityMenu .o-mail-ActivityGroup button:contains('Future')");
await contains(".o-mail-ActivityMenu", 0);
context = {
force_search_count: 1,
search_default_activities_overdue: 1,
@@ -108,6 +109,7 @@ QUnit.test("activity menu widget: activity menu with 2 models", async (assert) =
};
await click(".o_menu_systray i[aria-label='Activities']");
await click(".o-mail-ActivityMenu .o-mail-ActivityGroup:contains('mail.test.activity')");
await contains(".o-mail-ActivityMenu", 0);
});
QUnit.test("activity menu widget: activity view icon", async (assert) => {
@@ -139,7 +141,7 @@ QUnit.test("activity menu widget: activity view icon", async (assert) => {
]);
const { env } = await start();
await click(".o_menu_systray i[aria-label='Activities']");
assert.containsN($, "button[title='Summary']", 2);
await contains("button[title='Summary']", 2);
const first = $(".o-mail-ActivityGroup:contains('res.partner') button[title='Summary']");
const second = $(
".o-mail-ActivityGroup:contains('mail.test.activity') button[title='Summary']"
@@ -161,7 +163,7 @@ QUnit.test("activity menu widget: activity view icon", async (assert) => {
},
});
await click(".o-mail-ActivityGroup:contains('mail.test.activity') button[title='Summary']");
assert.containsNone($, ".o-dropdown-menu");
await contains(".o-dropdown-menu", 0);
await click(".o_menu_systray i[aria-label='Activities']");
await click(".o-mail-ActivityGroup:contains('res.partner') button[title='Summary']");
assert.verifySteps(["do_action:mail.test.activity", "do_action:res.partner"]);
@@ -170,7 +172,7 @@ QUnit.test("activity menu widget: activity view icon", async (assert) => {
QUnit.test("activity menu widget: close on messaging menu click", async (assert) => {
await start();
await click(".o_menu_systray i[aria-label='Activities']");
assert.containsOnce($, ".o-mail-ActivityMenu");
await contains(".o-mail-ActivityMenu");
await click(".o_menu_systray i[aria-label='Messages']");
assert.containsNone($, ".o-mail-ActivityMenu");
await contains(".o-mail-ActivityMenu", 0);
});
@@ -1,16 +1,14 @@
/** @odoo-module **/
/* @odoo-module */
import { click, start, startServer } from "@mail/../tests/helpers/test_utils";
import { click, contains, insertText, start, startServer } from "@mail/../tests/helpers/test_utils";
import {
editInput,
editSelect,
selectDropdownItem,
patchWithCleanup,
patchTimeZone,
getFixture,
} from "@web/../tests/helpers/utils";
import session from "web.session";
import testUtils from "@web/../tests/legacy/helpers/test_utils";
@@ -56,172 +54,139 @@ QUnit.module("tracking value", {
},
});
QUnit.test("basic rendering of tracking value (float type)", async function (assert) {
QUnit.test("basic rendering of tracking value (float type)", async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({ float_field: 12.3 });
await this.start({ res_id: mailTestTrackAllId1 });
await editInput(target, "div[name=float_field] input", 45.67);
await insertText("div[name=float_field] input", "45.67", { replace: true });
await click(".o_form_button_save");
assert.containsOnce(target, ".o-mail-Message-tracking");
assert.containsOnce(target, ".o-mail-Message-trackingField");
assert.strictEqual(
target.querySelector(".o-mail-Message-trackingField").textContent,
"(Float)"
);
assert.containsOnce(target, ".o-mail-Message-trackingOld");
assert.strictEqual(target.querySelector(".o-mail-Message-trackingOld").textContent, "12.30");
assert.containsOnce(target, ".o-mail-Message-trackingSeparator");
assert.containsOnce(target, ".o-mail-Message-trackingNew");
assert.strictEqual(target.querySelector(".o-mail-Message-trackingNew").textContent, "45.67");
await contains(".o-mail-Message-tracking");
await contains(".o-mail-Message-trackingField");
await contains(".o-mail-Message-trackingField:contains('(Float)')");
await contains(".o-mail-Message-trackingOld");
await contains(".o-mail-Message-trackingOld:contains('12.30')");
await contains(".o-mail-Message-trackingSeparator");
await contains(".o-mail-Message-trackingNew");
await contains(".o-mail-Message-trackingNew:contains('45.67')");
});
QUnit.test("rendering of tracked field of type float: from non-0 to 0", async function (assert) {
QUnit.test("rendering of tracked field of type float: from non-0 to 0", async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
float_field: 1,
});
await this.start({ res_id: mailTestTrackAllId1 });
await editInput(target, "div[name=float_field] input", 0);
await insertText("div[name=float_field] input", "0", { replace: true });
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"1.000.00(Float)"
);
await contains(".o-mail-Message-tracking:contains('1.000.00(Float)')");
});
QUnit.test("rendering of tracked field of type float: from 0 to non-0", async function (assert) {
QUnit.test("rendering of tracked field of type float: from 0 to non-0", async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
float_field: 0,
});
await this.start({ res_id: mailTestTrackAllId1 });
await editInput(target, "div[name=float_field] input", 1);
await insertText("div[name=float_field] input", "1", { replace: true });
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"0.001.00(Float)"
);
await contains(".o-mail-Message-tracking:contains('0.001.00(Float)')");
});
QUnit.test("rendering of tracked field of type integer: from non-0 to 0", async function (assert) {
QUnit.test("rendering of tracked field of type integer: from non-0 to 0", async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
integer_field: 1,
});
await this.start({ res_id: mailTestTrackAllId1 });
await editInput(target, "div[name=integer_field] input", 0);
await insertText("div[name=integer_field] input", "0", { replace: true });
await click(".o_form_button_save");
assert.strictEqual(target.querySelector(".o-mail-Message-tracking").textContent, "10(Integer)");
await contains(".o-mail-Message-tracking:contains('10(Integer)')");
});
QUnit.test("rendering of tracked field of type integer: from 0 to non-0", async function (assert) {
QUnit.test("rendering of tracked field of type integer: from 0 to non-0", async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
integer_field: 0,
});
await this.start({ res_id: mailTestTrackAllId1 });
await editInput(target, "div[name=integer_field] input", 1);
await insertText("div[name=integer_field] input", "1", { replace: true });
await click(".o_form_button_save");
assert.strictEqual(target.querySelector(".o-mail-Message-tracking").textContent, "01(Integer)");
await contains(".o-mail-Message-tracking:contains('01(Integer)')");
});
QUnit.test("rendering of tracked field of type monetary: from non-0 to 0", async function (assert) {
QUnit.test("rendering of tracked field of type monetary: from non-0 to 0", async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
monetary_field: 1,
});
await this.start({ res_id: mailTestTrackAllId1 });
await editInput(target, "div[name=monetary_field] input", 0);
await insertText("div[name=monetary_field] input", "0", { replace: true });
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"1.000.00(Monetary)"
);
await contains(".o-mail-Message-tracking:contains('1.000.00(Monetary)')");
});
QUnit.test("rendering of tracked field of type monetary: from 0 to non-0", async function (assert) {
QUnit.test("rendering of tracked field of type monetary: from 0 to non-0", async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
monetary_field: 0,
});
await this.start({ res_id: mailTestTrackAllId1 });
await editInput(target, "div[name=monetary_field] input", 1);
await insertText("div[name=monetary_field] input", "1", { replace: true });
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"0.001.00(Monetary)"
);
await contains(".o-mail-Message-tracking:contains('0.001.00(Monetary)')");
});
QUnit.test("rendering of tracked field of type boolean: from true to false", async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
boolean_field: true,
});
await this.start({ res_id: mailTestTrackAllId1 });
click(".o_field_boolean input").catch(() => {});
await click(".o_form_button_save");
await contains(".o-mail-Message-tracking:contains('YesNo(Boolean)')");
});
QUnit.test("rendering of tracked field of type boolean: from false to true", async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({});
await this.start({ res_id: mailTestTrackAllId1 });
click(".o_field_boolean input").catch(() => {});
await click(".o_form_button_save");
await contains(".o-mail-Message-tracking:contains('NoYes(Boolean)')");
});
QUnit.test(
"rendering of tracked field of type boolean: from true to false",
async function (assert) {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
boolean_field: true,
});
await this.start({ res_id: mailTestTrackAllId1 });
click(".o_field_boolean input").catch(() => {});
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"YesNo(Boolean)"
);
}
);
QUnit.test(
"rendering of tracked field of type boolean: from false to true",
async function (assert) {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({});
await this.start({ res_id: mailTestTrackAllId1 });
click(".o_field_boolean input").catch(() => {});
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"NoYes(Boolean)"
);
}
);
QUnit.test(
"rendering of tracked field of type char: from a string to empty string",
async function (assert) {
async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
char_field: "Marc",
});
await this.start({ res_id: mailTestTrackAllId1 });
await editInput(target, "div[name=char_field] input", "");
await insertText("div[name=char_field] input", "", { replace: true });
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"MarcNone(Char)"
);
await contains(".o-mail-Message-tracking:contains('MarcNone(Char)')");
}
);
QUnit.test(
"rendering of tracked field of type char: from empty string to a string",
async function (assert) {
async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
char_field: "",
});
await this.start({ res_id: mailTestTrackAllId1 });
await editInput(target, "div[name=char_field] input", "Marc");
await insertText("div[name=char_field] input", "Marc", { replace: true });
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"NoneMarc(Char)"
);
await contains(".o-mail-Message-tracking:contains('NoneMarc(Char)')");
}
);
QUnit.test(
"rendering of tracked field of type date: from no date to a set date",
async function (assert) {
async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
date_field: false,
@@ -233,16 +198,13 @@ QUnit.test(
["change"]
);
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"None12/14/2018(Date)"
);
await contains(".o-mail-Message-tracking:contains('None12/14/2018(Date)')");
}
);
QUnit.test(
"rendering of tracked field of type date: from a set date to no date",
async function (assert) {
async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
date_field: "2018-12-14",
@@ -254,10 +216,7 @@ QUnit.test(
["change"]
);
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"12/14/2018None(Date)"
);
await contains(".o-mail-Message-tracking:contains('12/14/2018None(Date)')");
}
);
@@ -276,20 +235,17 @@ QUnit.test(
["change"]
);
await click(".o_form_button_save");
await contains(".o-mail-Message-tracking:contains('None12/14/2018 13:42:28(Datetime)')");
const savedRecord = pyEnv
.getData()
["mail.test.track.all"].records.find(({ id }) => id === mailTestTrackAllId1);
assert.strictEqual(savedRecord.datetime_field, "2018-12-14 10:42:28");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"None12/14/2018 13:42:28(Datetime)"
);
}
);
QUnit.test(
"rendering of tracked field of type datetime: from a set date and time to no date and time",
async function (assert) {
async function () {
patchTimeZone(180);
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
@@ -302,50 +258,35 @@ QUnit.test(
["change"]
);
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"12/14/2018 16:42:28None(Datetime)"
);
await contains(".o-mail-Message-tracking:contains('12/14/2018 16:42:28None(Datetime)')");
}
);
QUnit.test(
"rendering of tracked field of type text: from some text to empty",
async function (assert) {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
text_field: "Marc",
});
await this.start({ res_id: mailTestTrackAllId1 });
await editInput(target, "div[name=text_field] textarea", "");
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"MarcNone(Text)"
);
}
);
QUnit.test("rendering of tracked field of type text: from some text to empty", async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
text_field: "Marc",
});
await this.start({ res_id: mailTestTrackAllId1 });
await insertText("div[name=text_field] textarea", "", { replace: true });
await click(".o_form_button_save");
await contains(".o-mail-Message-tracking:contains('MarcNone(Text)')");
});
QUnit.test(
"rendering of tracked field of type text: from empty to some text",
async function (assert) {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
text_field: "",
});
await this.start({ res_id: mailTestTrackAllId1 });
await editInput(target, "div[name=text_field] textarea", "Marc");
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"NoneMarc(Text)"
);
}
);
QUnit.test("rendering of tracked field of type text: from empty to some text", async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
text_field: "",
});
await this.start({ res_id: mailTestTrackAllId1 });
await insertText("div[name=text_field] textarea", "Marc", { replace: true });
await click(".o_form_button_save");
await contains(".o-mail-Message-tracking:contains('NoneMarc(Text)')");
});
QUnit.test(
"rendering of tracked field of type selection: from a selection to no selection",
async function (assert) {
async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
selection_field: "first",
@@ -353,58 +294,46 @@ QUnit.test(
await this.start({ res_id: mailTestTrackAllId1 });
await editSelect(target, "div[name=selection_field] select", false);
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"firstNone(Selection)"
);
await contains(".o-mail-Message-tracking:contains('firstNone(Selection)')");
}
);
QUnit.test(
"rendering of tracked field of type selection: from no selection to a selection",
async function (assert) {
async function () {
const pyEnv = await startServer();
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({});
await this.start({ res_id: mailTestTrackAllId1 });
await editSelect(target, "div[name=selection_field] select", '"first"');
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"Nonefirst(Selection)"
);
await contains(".o-mail-Message-tracking:contains('Nonefirst(Selection)')");
}
);
QUnit.test(
"rendering of tracked field of type many2one: from having a related record to no related record",
async function (assert) {
async function () {
const pyEnv = await startServer();
const resPartnerId1 = pyEnv["res.partner"].create({ display_name: "Marc" });
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({
many2one_field_id: resPartnerId1,
});
await this.start({ res_id: mailTestTrackAllId1 });
await editInput(target, ".o_field_many2one_selection input", "");
await insertText(".o_field_many2one_selection input", "", { replace: true });
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"MarcNone(Many2one)"
);
await contains(".o-mail-Message-tracking:contains('MarcNone(Many2one)')");
}
);
QUnit.test(
"rendering of tracked field of type many2one: from no related record to having a related record",
async function (assert) {
async function () {
const pyEnv = await startServer();
pyEnv["res.partner"].create({ display_name: "Marc" });
const mailTestTrackAllId1 = pyEnv["mail.test.track.all"].create({});
await this.start({ res_id: mailTestTrackAllId1 });
await selectDropdownItem(target, "many2one_field_id", "Marc");
await click(".o_form_button_save");
assert.strictEqual(
target.querySelector(".o-mail-Message-tracking").textContent,
"NoneMarc(Many2one)"
);
await contains(".o-mail-Message-tracking:contains('NoneMarc(Many2one)')");
}
);
@@ -6,6 +6,7 @@ import {
Component,
onMounted,
onPatched,
onWillDestroy,
onWillPatch,
onWillStart,
onWillUnmount,
@@ -128,7 +129,6 @@ export class EmojiPicker extends Component {
static props = ["onSelect", "className?", "close?", "onClose?", "storeScroll?"];
static template = "web.EmojiPicker";
recent = [];
categories = null;
emojis = null;
shouldScrollElem = null;
@@ -140,8 +140,20 @@ export class EmojiPicker extends Component {
this.state = useState({
activeEmojiIndex: 0,
categoryId: null,
recent: JSON.parse(browser.localStorage.getItem("web.emoji.frequent") || "{}"),
searchStr: "",
});
const onStorage = (ev) => {
if (ev.key === "web.emoji.frequent") {
this.state.recent = ev.newValue ? JSON.parse(ev.newValue) : {};
} else if (ev.key === null) {
this.state.recent = {};
}
};
browser.addEventListener("storage", onStorage);
onWillDestroy(() => {
browser.removeEventListener("storage", onStorage);
});
onWillStart(async () => {
const { categories, emojis } = await loadEmoji();
this.categories = categories;
@@ -150,7 +162,6 @@ export class EmojiPicker extends Component {
this.emojis.map((emoji) => [emoji.codepoints, emoji])
);
this.state.categoryId = this.categories[0]?.sortId;
this.recent = JSON.parse(browser.localStorage.getItem("web.emoji.frequent") || "{}");
this.recentCategory = {
name: "Frequently used",
displayName: _t("Frequently used"),
@@ -211,7 +222,7 @@ export class EmojiPicker extends Component {
}
get recentEmojis() {
return Object.entries(this.recent)
return Object.entries(this.state.recent)
.sort(([, usage_1], [, usage_2]) => usage_2 - usage_1)
.slice(0, 42)
.map(([codepoints]) => this.emojiByCodepoints[codepoints]);
@@ -289,9 +300,9 @@ export class EmojiPicker extends Component {
selectEmoji(ev) {
const codepoints = ev.currentTarget.dataset.codepoints;
this.props.onSelect(codepoints);
this.recent[codepoints] ??= 0;
this.recent[codepoints]++;
browser.localStorage.setItem("web.emoji.frequent", JSON.stringify(this.recent));
this.state.recent[codepoints] ??= 0;
this.state.recent[codepoints]++;
browser.localStorage.setItem("web.emoji.frequent", JSON.stringify(this.state.recent));
this.gridRef.el.scrollTop = 0;
if (!ev.shiftKey) {
this.props.close?.();
+2
View File
@@ -173,6 +173,8 @@ addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js
# Whitelist mail
!addons/mail/
!addons/mail/**/*
!addons/test_mail/
!addons/test_mail/**/*
# Whitelist im_livechat
!addons/im_livechat/
+2 -1
View File
@@ -41,7 +41,8 @@
"@pos_sale/*": ["addons/pos_sale/static/src/*"],
"@pos_sale_product_configurator/*": ["addons/pos_sale_product_configurator/static/src/*"],
"@pos_six/*": ["addons/pos_six/static/src/*"],
"@pos_stripe/*": ["addons/pos_stripe/static/src/*"]
"@pos_stripe/*": ["addons/pos_stripe/static/src/*"],
"@test_mail/*": ["addons/test_mail/static/src/*"]
}
},
"include": ["**/*.js", "**/*.ts"],
@@ -69,7 +69,7 @@ export const feedback = [
},
{
content: "Send the feedback",
trigger: "button:contains(Send)",
trigger: "button:contains(Send):not(:disabled)",
},
{
content: "Check if feedback has been sent",