Odoo18-Base/addons/mail/static/tests/chat_bubble/chat_bubble.test.js
2025-01-06 10:57:38 +07:00

450 lines
18 KiB
JavaScript

import { describe, expect, test } from "@odoo/hoot";
import { leave } from "@odoo/hoot-dom";
import { withUser } from "@web/../tests/_framework/mock_server/mock_server";
import { Command, serverState } from "@web/../tests/web_test_helpers";
import { rpc } from "@web/core/network/rpc";
import {
assertSteps,
click,
contains,
defineMailModels,
hover,
insertText,
onRpcBefore,
start,
startServer,
step,
triggerHotkey,
} from "../mail_test_helpers";
describe.current.tags("desktop");
defineMailModels();
test("Folded chat windows are displayed as chat bubbles", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create([
{
name: "Channel A",
channel_member_ids: [
Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
],
},
{
name: "Channel B",
channel_member_ids: [
Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
],
},
]);
await start();
await contains(".o-mail-ChatBubble", { count: 2 });
await click(".o-mail-ChatBubble", { count: 2 });
await contains(".o-mail-ChatBubble", { count: 1 });
await contains(".o-mail-ChatWindow", { count: 1 });
});
test.tags("focus required");
test("'New message' chat window can only be open", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "John" });
pyEnv["res.users"].create({ partner_id: partnerId });
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-MessagingMenu button", { text: "New Message" });
await contains(".o-mail-ChatWindow", { count: 1 });
await contains("[title='Fold']", { count: 0 });
await insertText(".o-discuss-ChannelSelector input", "John");
await click(".o-discuss-ChannelSelector-suggestion");
triggerHotkey("Enter");
await contains(".o-mail-ChatWindow");
await contains(".o-mail-Thread-empty", { text: "The conversation is empty." });
await click(".o-mail-ChatWindow-command[title='Fold']");
await contains(".o-mail-ChatBubble", { count: 1 }); // can fold chat
});
test("No duplicated chat bubbles", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "John" });
pyEnv["res.users"].create({ partner_id: partnerId });
await start();
// Make bubble of "John" chat
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-MessagingMenu button", { text: "New Message" });
await insertText(".o-discuss-ChannelSelector input", "John");
await click(".o-discuss-ChannelSelector-suggestion");
triggerHotkey("Enter");
await contains(".o-mail-ChatWindow", { text: "John" });
await contains(".o-mail-ChatWindow", { text: "The conversation is empty." }); // wait fully loaded
await click("button[title='Fold']");
await contains(".o-mail-ChatBubble[name='John']");
// Make bubble of "John" chat again
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-MessagingMenu button", { text: "New Message" });
await insertText(".o-discuss-ChannelSelector input", "John");
await click(".o-discuss-ChannelSelector-suggestion");
triggerHotkey("Enter");
await contains(".o-mail-ChatBubble[name='John']", { count: 0 });
await contains(".o-mail-ChatWindow", { text: "John" });
await click(".o-mail-ChatWindow-command[title='Fold']");
// Make again from click messaging menu item
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
await contains(".o-mail-ChatBubble[name='John']", { count: 0 });
await contains(".o-mail-ChatWindow", { text: "John" });
});
test("Up to 7 chat bubbles", async () => {
const pyEnv = await startServer();
for (let i = 1; i <= 8; i++) {
pyEnv["discuss.channel"].create({
name: String(i),
channel_member_ids: [
Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
],
});
}
await start();
for (let i = 8; i > 1; i--) {
await contains(`.o-mail-ChatBubble[name='${String(i)}']`);
}
await contains(".o-mail-ChatBubble[name='1']", { count: 0 });
await contains(".o-mail-ChatHub-hiddenBtn", { text: "+1" });
await hover(".o-mail-ChatHub-hiddenBtn");
await contains(".o-mail-ChatHub-hiddenItem[name='1']");
await contains(".o-mail-ChatWindow", { count: 0 });
await click(".o-mail-ChatHub-hiddenItem");
await contains(".o-mail-ChatWindow", { count: 1 });
await contains(".o-mail-ChatHub-hiddenBtn", { count: 0 });
});
test("Ordering of chat bubbles is consistent and seems logical.", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const userId = pyEnv["res.users"].create({ partner_id: partnerId });
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
Command.create({ fold_state: "folded", partner_id: partnerId }),
],
channel_type: "chat",
});
for (let i = 1; i <= 7; i++) {
pyEnv["discuss.channel"].create({
name: String(i),
channel_member_ids: [
Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
],
});
}
await start();
// FIXME: expect arbitrary order 7, 6, 5, 4, 3, 2, 1
await contains(":nth-child(1 of .o-mail-ChatBubble)[name='7']");
await contains(":nth-child(2 of .o-mail-ChatBubble)[name='6']");
await contains(":nth-child(3 of .o-mail-ChatBubble)[name='5']");
await contains(":nth-child(4 of .o-mail-ChatBubble)[name='4']");
await contains(":nth-child(5 of .o-mail-ChatBubble)[name='3']");
await contains(":nth-child(6 of .o-mail-ChatBubble)[name='2']");
await contains(":nth-child(7 of .o-mail-ChatBubble)[name='1']");
await contains(".o-mail-ChatBubble[name='Demo']", { count: 0 });
await contains(".o-mail-ChatWindow", { count: 0 });
await click(".o-mail-ChatBubble[name='3']");
await contains(".o-mail-ChatWindow", { text: "3" });
await contains(":nth-child(7 of .o-mail-ChatBubble)[name='Demo']");
await click(".o-mail-ChatWindow-command[title='Fold']");
await contains(".o-mail-ChatBubble[name='Demo']", { count: 0 });
await click(".o-mail-ChatBubble[name='4']");
await contains(":nth-child(1 of .o-mail-ChatBubble)[name='3']");
await contains(":nth-child(2 of .o-mail-ChatBubble)[name='7']");
await contains(":nth-child(3 of .o-mail-ChatBubble)[name='6']");
await contains(":nth-child(7 of .o-mail-ChatBubble)[name='Demo']");
await click(".o-mail-ChatWindow-command[title='Fold']");
await contains(".o-mail-ChatWindow", { count: 0 });
// no reorder on receiving new message
withUser(userId, () =>
rpc("/mail/message/post", {
post_data: { body: "test", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
);
await hover(".o-mail-ChatHub-hiddenBtn");
await contains(".o-mail-ChatHub-hiddenItem[name='Demo']");
});
test("Hover on chat bubble shows chat name + last message preview", async () => {
const pyEnv = await startServer();
const marcPartnerId = pyEnv["res.partner"].create({ name: "Marc" });
const marcChannelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
Command.create({ fold_state: "folded", partner_id: marcPartnerId }),
],
channel_type: "chat",
});
pyEnv["mail.message"].create({
body: "Hello!",
model: "discuss.channel",
author_id: marcPartnerId,
res_id: marcChannelId,
});
const demoPartnerId = pyEnv["res.partner"].create({ name: "Demo" });
const demoChannelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
Command.create({ fold_state: "folded", partner_id: demoPartnerId }),
],
channel_type: "chat",
});
await start();
await hover(".o-mail-ChatBubble[name='Marc']");
await contains(".o-mail-ChatBubble-preview", { text: "MarcHello!" });
await leave();
await contains(".o-mail-ChatBubble-preview", { count: 0 });
await hover(".o-mail-ChatBubble[name='Demo']");
await contains(".o-mail-ChatBubble-preview", { text: "Demo" });
await leave();
rpc("/mail/message/post", {
post_data: { body: "Hi", message_type: "comment" },
thread_id: demoChannelId,
thread_model: "discuss.channel",
});
await hover(".o-mail-ChatBubble[name='Demo']");
await contains(".o-mail-ChatBubble-preview", { text: "DemoYou: Hi" });
});
test("Chat bubble preview works on author as email address", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "TestPartner" });
const messageId = pyEnv["mail.message"].create({
author_id: null,
body: "Some email message",
email_from: "md@oilcompany.fr",
model: "res.partner",
needaction: true,
res_id: partnerId,
});
pyEnv["mail.notification"].create({
mail_message_id: messageId,
notification_status: "sent",
notification_type: "inbox",
res_partner_id: serverState.partnerId,
});
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
await click(".o-mail-ChatWindow [title='Fold']");
await hover(".o-mail-ChatBubble");
await contains(".o-mail-ChatBubble-preview", { text: "md@oilcompany.fr: Some email message" });
});
test("chat bubbles are synced between tabs", async () => {
const pyEnv = await startServer();
const marcPartnerId = pyEnv["res.partner"].create({ name: "Marc" });
pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
Command.create({ fold_state: "folded", partner_id: marcPartnerId }),
],
channel_type: "chat",
});
const tab1 = await start({ asTab: true });
const tab2 = await start({ asTab: true });
await contains(".o-mail-ChatBubble", { target: tab1 });
await contains(".o-mail-ChatBubble", { target: tab2 });
await click(".o-mail-ChatBubble[name='Marc']", { target: tab1 });
await contains(".o-mail-ChatWindow", { target: tab2 }); // open sync
await click(".o-mail-ChatWindow-command[title='Fold']", { target: tab2 });
await contains(".o-mail-ChatWindow", { target: tab1, count: 0 }); // fold sync
await click(".o-mail-ChatBubble[name='Marc'] .o-mail-ChatBubble-close", { target: tab1 });
await contains(".o-mail-ChatBubble[name='Marc']", { target: tab2, count: 0 }); // close sync
});
test("Chat bubbles do not fetch messages until becoming open", async () => {
const pyEnv = await startServer();
const [channeId1, channelId2] = pyEnv["discuss.channel"].create([
{
name: "Orange",
channel_member_ids: [
Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
],
},
{
name: "Apple",
channel_member_ids: [
Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
],
},
]);
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",
},
]);
onRpcBefore("/discuss/channel/messages", () => expect.step("fetch_messages"));
await start();
await contains(".o-mail-ChatBubble[name='Orange']");
expect.verifySteps([]);
await click(".o-mail-ChatBubble[name='Orange']");
await contains(".o-mail-ChatWindow");
await contains(".o-mail-Message-content", { text: "Orange" });
await contains(".o-mail-Message-content", { count: 0, text: "Apple" });
expect.verifySteps(["fetch_messages"]); // from "Orange" becoming open
});
test("More than 7 actually folded chat windows shows a 'hidden' chat bubble menu", async () => {
const pyEnv = await startServer();
for (let i = 1; i <= 8; i++) {
pyEnv["discuss.channel"].create({
name: String(i),
channel_member_ids: [
Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
],
});
}
await start();
// Can make chat from hidden menu
await hover(".o-mail-ChatHub-hiddenBtn");
await click(".o-mail-ChatHub-hiddenItem");
await leave(); // FIXME: hover is persistent otherwise
await contains(".o-mail-ChatHub-hiddenItem", { count: 0 });
await contains(".o-mail-ChatHub-hiddenBtn", { count: 0 });
await contains(".o-mail-ChatWindow");
await click(".o-mail-ChatWindow-command[title='Fold']");
// Can open hidden chat from messaging menu
await click("i[aria-label='Messages']");
await click(".o-mail-NotificationItem", { text: "2" });
await contains(".o-mail-ChatHub-hiddenItem", { count: 0 });
await contains(".o-mail-ChatHub-hiddenBtn", { count: 0 });
await contains(".o-mail-ChatWindow");
await click(".o-mail-ChatWindow-command[title='Fold']");
// Can close chat from hidden menu.
await hover(".o-mail-ChatHub-hiddenBtn");
await hover(".o-mail-ChatHub-hiddenItem");
await click(".o-mail-ChatHub-hiddenClose");
await contains(".o-mail-ChatHub-hiddenItem", { count: 0 });
await contains(".o-mail-ChatHub-hiddenBtn", { count: 0 });
await contains(".o-mail-ChatWindow", { count: 0 });
});
test("Can close all chat windows at once", async () => {
const closed = new Set();
onRpcBefore("/discuss/channel/fold", (args) => {
if (args.state === "closed") {
closed.add(args.channel_id);
}
if (closed.size === 20) {
step("ALL_CLOSED");
}
});
const pyEnv = await startServer();
const channelIds = pyEnv["discuss.channel"].create(
Array(20)
.keys()
.map((i) => ({
name: String(i),
channel_member_ids: [
Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
],
}))
);
await start();
await contains(".o-mail-ChatBubble", { count: 8 }); // max reached
await contains(".o-mail-ChatBubble", { text: "+13" });
await hover(".o-mail-ChatHub-hiddenBtn");
await click("button.fa.fa-ellipsis-h[title='Chat Options']");
await click("button.o-mail-ChatHub-option", { text: "Close all conversations" });
await contains(".o-mail-ChatBubble", { count: 0 });
await assertSteps(["ALL_CLOSED"]);
const members = pyEnv["discuss.channel.member"].search_read([
["channel_id", "in", channelIds],
["partner_id", "=", serverState.partnerId],
]);
expect(members.map((member) => member.fold_state)).toEqual(
[...Array(20).keys()].map(() => "closed")
);
});
test("Can compact chat hub", async () => {
// allows to temporarily reduce footprint of chat windows on UI
const pyEnv = await startServer();
for (let i = 1; i <= 20; i++) {
pyEnv["discuss.channel"].create({
name: String(i),
channel_member_ids: [
Command.create({ fold_state: "folded", partner_id: serverState.partnerId }),
],
});
}
await start();
await contains(".o-mail-ChatBubble", { count: 8 }); // max reached
await contains(".o-mail-ChatBubble", { text: "+13" });
await hover(".o-mail-ChatHub-hiddenBtn");
await click("button.fa.fa-ellipsis-h[title='Chat Options']");
await click("button.o-mail-ChatHub-option", { text: "Hide all conversations" });
await contains(".o-mail-ChatBubble i.fa.fa-commenting");
await click(".o-mail-ChatBubble i.fa.fa-commenting");
await contains(".o-mail-ChatBubble", { count: 8 });
// alternative compact: click hidden button
await click(".o-mail-ChatBubble", { text: "+13" });
await contains(".o-mail-ChatBubble i.fa.fa-commenting");
});
test("Compacted chat hub shows badge with amount of hidden chats with important messages", async () => {
const pyEnv = await startServer();
for (let i = 1; i <= 20; i++) {
const partner_id = pyEnv["res.partner"].create({ name: `partner_${i}` });
const chatId = pyEnv["discuss.channel"].create({
name: String(i),
channel_member_ids: [
Command.create({
fold_state: "folded",
partner_id: serverState.partnerId,
}),
Command.create({ partner_id }),
],
channel_type: "chat",
});
if (i < 10) {
pyEnv["mail.message"].create({
body: "Hello!",
model: "discuss.channel",
author_id: partner_id,
res_id: chatId,
});
}
}
await start();
await contains(".o-mail-ChatBubble", { count: 8 }); // max reached
await contains(".o-mail-ChatBubble", { text: "+13" });
await click(".o-mail-ChatHub-hiddenBtn");
await contains(".o-mail-ChatBubble i.fa.fa-commenting");
await contains(".o-mail-ChatBubble .o-discuss-badge", { text: "9" });
});
test("Show IM status", async () => {
const pyEnv = await startServer();
const demoId = pyEnv["res.partner"].create({ name: "Demo User", im_status: "online" });
pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({
fold_state: "folded",
partner_id: serverState.partnerId,
}),
Command.create({ partner_id: demoId }),
],
channel_type: "chat",
});
await start();
await contains(".o-mail-ChatBubble .fa-circle.text-success[aria-label='User is online']");
});