mail/static/tests/thread/thread_tests.js
Данил Воробьев 6e6f15d803 initial commit
2024-05-03 09:40:35 +00:00

1061 lines
43 KiB
JavaScript

/* @odoo-module */
import { startServer } from "@bus/../tests/helpers/mock_python_environment";
import { Command } from "@mail/../tests/helpers/command";
import { start } from "@mail/../tests/helpers/test_utils";
import { config as transitionConfig } from "@web/core/transition";
import { makeDeferred, nextTick, patchWithCleanup } from "@web/../tests/helpers/utils";
import {
click,
contains,
createFile,
dragenterFiles,
focus,
insertText,
scroll,
triggerEvents,
} from "@web/../tests/utils";
QUnit.module("thread");
QUnit.test("dragover files on thread with composer", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
group_public_id: false,
name: "General",
});
const { openDiscuss } = await start();
openDiscuss(channelId);
const files = [
await createFile({
content: "hello, world",
contentType: "text/plain",
name: "text3.txt",
}),
];
await dragenterFiles(".o-mail-Thread", files);
await contains(".o-mail-Dropzone");
});
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",
group_public_id: false,
name: "General",
});
for (let i = 0; i <= 60; i++) {
pyEnv["mail.message"].create({
body: i.toString(),
model: "discuss.channel",
res_id: channelId,
});
}
const { openDiscuss } = await start();
openDiscuss(channelId);
await contains("button", { text: "Load More", before: [".o-mail-Message", { count: 30 }] });
await contains(".o-mail-Thread", { scroll: "bottom" });
await scroll(".o-mail-Thread", 0);
await contains(".o-mail-Message", { count: 60 });
await contains(".o-mail-Message", { text: "30", after: [".o-mail-Message", { text: "29" }] });
});
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", { text: "Subject: Salutations, voyageurnot empty" });
});
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", { text: "not empty" });
await contains(".o-mail-Message", {
count: 0,
text: "Subject: Salutations, voyageurnot empty",
});
});
QUnit.test("auto-scroll to bottom of thread on load", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
for (let i = 1; i <= 25; i++) {
pyEnv["mail.message"].create({
body: "not empty",
model: "discuss.channel",
res_id: channelId,
});
}
const { openDiscuss } = await start();
openDiscuss(channelId);
await contains(".o-mail-Message", { count: 25 });
await contains(".o-mail-Thread", { scroll: "bottom" });
});
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([
{
body: "not empty",
model: "discuss.channel",
res_id: channelId,
},
{
body: "not empty",
model: "discuss.channel",
res_id: channelId,
},
]);
const { openDiscuss } = await start();
openDiscuss(channelId);
await contains(".o-mail-DateSection");
});
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({
body: "",
model: "discuss.channel",
res_id: channelId,
});
const { openDiscuss } = await start();
openDiscuss(channelId);
await contains(".o-mail-Thread", { text: "There are no messages in this conversation." });
await contains(".o-mail-DateSection", { count: 0 });
});
QUnit.test("scroll position is kept when navigating from one channel to another", async () => {
const pyEnv = await startServer();
const channelId_1 = pyEnv["discuss.channel"].create({ name: "channel-1" });
const channelId_2 = pyEnv["discuss.channel"].create({ name: "channel-2" });
// Fill both channels with random messages in order for the scrollbar to
// appear.
pyEnv["mail.message"].create(
Array(50)
.fill(0)
.map((_, index) => ({
body: "Non Empty Body ".repeat(25),
message_type: "comment",
model: "discuss.channel",
res_id: index < 20 ? channelId_1 : channelId_2,
}))
);
const { openDiscuss } = await start();
openDiscuss(channelId_1);
await contains(".o-mail-Message", { count: 20 });
const scrollValue1 = $(".o-mail-Thread")[0].scrollHeight / 2;
await contains(".o-mail-Thread", { scroll: "bottom" });
await scroll(".o-mail-Thread", scrollValue1);
await click(".o-mail-DiscussSidebarChannel", { text: "channel-2" });
await contains(".o-mail-Message", { count: 30 });
const scrollValue2 = $(".o-mail-Thread")[0].scrollHeight / 3;
await contains(".o-mail-Thread", { scroll: "bottom" });
await scroll(".o-mail-Thread", scrollValue2);
await click(".o-mail-DiscussSidebarChannel", { text: "channel-1" });
await contains(".o-mail-Message", { count: 20 });
await contains(".o-mail-Thread", { scroll: scrollValue1 });
await click(".o-mail-DiscussSidebarChannel", { text: "channel-2" });
await contains(".o-mail-Message", { count: 30 });
await contains(".o-mail-Thread", { scroll: scrollValue2 });
});
QUnit.test("thread is still scrolling after scrolling up then to bottom", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "channel-1" });
pyEnv["mail.message"].create(
Array(20)
.fill(0)
.map(() => ({
body: "Non Empty Body ".repeat(25),
message_type: "comment",
model: "discuss.channel",
res_id: channelId,
}))
);
const { openDiscuss } = await start();
openDiscuss(channelId);
await contains(".o-mail-Message", { count: 20 });
await contains(".o-mail-Thread", { scroll: "bottom" });
await scroll(".o-mail-Thread", $(".o-mail-Thread")[0].scrollHeight / 2);
await scroll(".o-mail-Thread", "bottom");
await insertText(".o-mail-Composer-input", "123");
await click(".o-mail-Composer-send:enabled");
await contains(".o-mail-Message", { count: 21 });
await contains(".o-mail-Thread", { scroll: "bottom" });
});
QUnit.test("Can mention other channels in a group-restricted channel", async () => {
const pyEnv = await startServer();
const groupId = pyEnv["res.groups"].create({
name: "Mario Group",
});
const [channelId1] = pyEnv["discuss.channel"].create([
{
channel_type: "channel",
group_public_id: groupId,
name: "Marios",
},
{
channel_type: "channel",
group_public_id: false,
name: "Link and Zelda",
},
]);
const { openDiscuss } = await start();
openDiscuss(channelId1);
await insertText(".o-mail-Composer-input", "#");
await contains(".o-mail-Composer-suggestion", { text: "#Marios" });
await contains(".o-mail-Composer-suggestion", { text: "#Link and Zelda" });
await click(".o-mail-Composer-suggestion", { text: "#Link and Zelda" });
await contains(".o-mail-Composer-input", { value: "#Link and Zelda " });
await click(".o-mail-Composer-send:enabled");
await contains(".o-mail-Message-body .o_channel_redirect", { text: "#Link and Zelda" });
});
QUnit.test("mention a channel with space in the name", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General good boy" });
const { openDiscuss } = await start();
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "#");
await click(".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-input", { value: "#General good boy " });
await click(".o-mail-Composer-send:enabled");
await contains(".o-mail-Message-body .o_channel_redirect", { text: "#General good boy" });
});
QUnit.test('mention a channel with "&" in the name', async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General & good" });
const { openDiscuss } = await start();
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "#");
await click(".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-input", { value: "#General & good " });
await click(".o-mail-Composer-send:enabled");
await contains(".o-mail-Message-body .o_channel_redirect", { text: "#General & good" });
});
QUnit.test(
"mark channel as fetched when a new message is loaded and as seen when focusing composer [REQUIRE FOCUS]",
async (assert) => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({
email: "fred@example.com",
name: "Fred",
});
const userId = pyEnv["res.users"].create({ partner_id: partnerId });
const channelId = pyEnv["discuss.channel"].create({
name: "test",
channel_member_ids: [
Command.create({ partner_id: pyEnv.currentPartnerId }),
Command.create({ partner_id: partnerId }),
],
channel_type: "chat",
});
const { env } = await start({
mockRPC(route, args) {
if (args.method === "channel_fetched") {
assert.strictEqual(args.args[0][0], channelId);
assert.strictEqual(args.model, "discuss.channel");
assert.step("rpc:channel_fetch");
} else if (route === "/discuss/channel/set_last_seen_message") {
assert.strictEqual(args.channel_id, channelId);
assert.step("rpc:set_last_seen_message");
}
},
});
await contains(".o_menu_systray i[aria-label='Messages']");
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "Hello!", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
);
await contains(".o-mail-Message");
assert.verifySteps(["rpc:channel_fetch"]);
await contains(".o-mail-Thread-newMessage hr + span", { text: "New messages" });
await focus(".o-mail-Composer-input");
await contains(".o-mail-Thread-newMessage hr + span", { count: 0, text: "New messages" });
assert.verifySteps(["rpc:set_last_seen_message"]);
}
);
QUnit.test(
"mark channel as fetched and seen when a new message is loaded if composer is focused [REQUIRE FOCUS]",
async (assert) => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const userId = pyEnv["res.users"].create({ partner_id: partnerId });
const channelId = pyEnv["discuss.channel"].create({
name: "test",
channel_member_ids: [
Command.create({ partner_id: pyEnv.currentPartnerId }),
Command.create({ partner_id: partnerId }),
],
});
const { env, openDiscuss } = await start({
async mockRPC(route, args) {
if (args.method === "channel_fetched" && args.args[0] === channelId) {
throw new Error(
"'channel_fetched' RPC must not be called for created channel as message is directly seen"
);
} else if (route === "/discuss/channel/set_last_seen_message") {
assert.strictEqual(args.channel_id, channelId);
assert.step("rpc:set_last_seen_message");
}
},
});
openDiscuss(channelId);
await focus(".o-mail-Composer-input");
// simulate receiving a message
await pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "<p>Some new message</p>", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
);
await contains(".o-mail-Message");
assert.verifySteps(["rpc:set_last_seen_message"]);
}
);
QUnit.test(
"should scroll to bottom on receiving new message if the list is initially scrolled to bottom (asc order)",
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Foreigner partner" });
const userId = pyEnv["res.users"].create({ name: "Foreigner user", partner_id: partnerId });
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ partner_id: pyEnv.currentPartnerId }),
Command.create({ partner_id: partnerId }),
],
});
for (let i = 0; i <= 10; i++) {
pyEnv["mail.message"].create({
body: "not empty",
model: "discuss.channel",
res_id: channelId,
});
}
const { env } = await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
await contains(".o-mail-Message", { count: 11 });
await contains(".o-mail-Thread", { scroll: "bottom" });
// simulate receiving a message
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "hello", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
);
await contains(".o-mail-Message", { count: 12 });
await contains(".o-mail-Thread", { scroll: "bottom" });
}
);
QUnit.test(
"should not scroll on receiving new message if the list is initially scrolled anywhere else than bottom (asc order)",
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Foreigner partner" });
const userId = pyEnv["res.users"].create({ name: "Foreigner user", partner_id: partnerId });
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ partner_id: pyEnv.currentPartnerId }),
Command.create({ partner_id: partnerId }),
],
});
for (let i = 0; i <= 10; i++) {
pyEnv["mail.message"].create({
body: "not empty",
model: "discuss.channel",
res_id: channelId,
});
}
const { env } = await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
await contains(".o-mail-Message", { count: 11 });
await contains(".o-mail-Thread", { scroll: "bottom" });
await scroll(".o-mail-Thread", 0);
// simulate receiving a message
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "hello", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
);
await contains(".o-mail-Message", { count: 12 });
await contains(".o-mail-ChatWindow .o-mail-Thread", { scroll: 0 });
}
);
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();
openDiscuss(channelId);
await contains(".o-mail-Thread", { text: "There are no messages in this conversation." });
await contains(".o-mail-Message", { count: 0 });
});
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();
openDiscuss(channelId);
await contains(".o-mail-Thread", { text: "There are no messages in this conversation." });
await contains(".o-mail-Message", { count: 0 });
});
QUnit.test(
"message list with a full page of empty messages should load more messages until there are some non-empty",
async () => {
// Technical assumptions :
// - /discuss/channel/messages fetching exactly 30 messages,
// - empty messages not being displayed
// - auto-load more being triggered on scroll, not automatically when the 30 first messages are empty
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
for (let i = 0; i < 50; i++) {
pyEnv["mail.message"].create({
body: "not empty",
model: "discuss.channel",
res_id: channelId,
});
}
for (let i = 0; i < 50; i++) {
pyEnv["mail.message"].create({ model: "discuss.channel", res_id: channelId });
}
const { openDiscuss } = await start();
openDiscuss(channelId);
// initial load: +30 empty ; (auto) load more: +20 empty +10 non-empty
await contains(".o-mail-Message", { count: 10 });
await contains("button", { text: "Load More" }); // still 40 non-empty
}
);
QUnit.test("no new messages separator on posting message (some message history)", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ message_unread_counter: 0, partner_id: pyEnv.currentPartnerId }),
],
channel_type: "channel",
name: "General",
});
const messageId = pyEnv["mail.message"].create({
body: "first message",
model: "discuss.channel",
res_id: channelId,
});
const [memberId] = pyEnv["discuss.channel.member"].search([
["channel_id", "=", channelId],
["partner_id", "=", pyEnv.currentPartnerId],
]);
pyEnv["discuss.channel.member"].write([memberId], { seen_message_id: messageId });
const { openDiscuss } = await start();
openDiscuss(channelId);
await contains(".o-mail-Message");
await contains(".o-mail-Thread-newMessage hr + span", { count: 0, text: "New messages" });
await insertText(".o-mail-Composer-input", "hey!");
await click(".o-mail-Composer-send:enabled");
await contains(".o-mail-Message", { count: 2 });
await contains(".o-mail-Thread-newMessage hr + span", { count: 0, text: "New messages" });
});
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" });
const userId = pyEnv["res.users"].create({
name: "Foreigner user",
partner_id: partnerId,
});
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ message_unread_counter: 0, partner_id: pyEnv.currentPartnerId }),
Command.create({ partner_id: partnerId }),
],
channel_type: "channel",
name: "General",
});
const messageId = pyEnv["mail.message"].create({
body: "blah",
model: "discuss.channel",
res_id: channelId,
});
const [memberId] = pyEnv["discuss.channel.member"].search([
["channel_id", "=", channelId],
["partner_id", "=", pyEnv.currentPartnerId],
]);
pyEnv["discuss.channel.member"].write([memberId], { seen_message_id: messageId });
const { env, openDiscuss } = await start();
openDiscuss(channelId);
await contains(".o-mail-Message");
await contains(".o-mail-Thread-newMessage hr + span", { count: 0, text: "New messages" });
$(".o-mail-Composer-input")[0].blur();
// simulate receiving a message
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "hu", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
);
await contains(".o-mail-Message", { count: 2 });
await contains(".o-mail-Thread-newMessage hr + span", { text: "New messages" });
await contains(".o-mail-Thread-newMessage ~ .o-mail-Message", { text: "hu" });
await focus(".o-mail-Composer-input");
await nextTick();
await contains(".o-mail-Thread-newMessage hr + span", { count: 0, text: "New messages" });
});
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: [
Command.create({ message_unread_counter: 0, partner_id: pyEnv.currentPartnerId }),
],
channel_type: "channel",
name: "General",
});
const { openDiscuss } = await start();
openDiscuss(channelId);
await contains(".o-mail-Composer-input");
await contains(".o-mail-Message", { count: 0 });
await contains(".o-mail-Thread-newMessage hr + span", { count: 0, text: "New messages" });
await insertText(".o-mail-Composer-input", "hey!");
await click(".o-mail-Composer-send:enabled");
await contains(".o-mail-Message");
await contains(".o-mail-Thread-newMessage hr + span", { count: 0, text: "New messages" });
});
QUnit.test("Mention a partner with special character (e.g. apostrophe ')", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({
email: "usatyi@example.com",
name: "Pynya's spokesman",
});
const channelId = pyEnv["discuss.channel"].create({
name: "test",
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", "@");
await insertText(".o-mail-Composer-input", "Pyn");
await click(".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-input", { value: "@Pynya's spokesman " });
await click(".o-mail-Composer-send:enabled");
await contains(
`.o-mail-Message-body .o_mail_redirect[data-oe-id="${partnerId}"][data-oe-model="res.partner"]`,
{ text: "@Pynya's spokesman" }
);
});
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([
{
email: "partner1@example.com",
name: "TestPartner",
},
{
email: "partner2@example.com",
name: "TestPartner",
},
]);
const channelId = pyEnv["discuss.channel"].create({
name: "test",
channel_member_ids: [
Command.create({ partner_id: pyEnv.currentPartnerId }),
Command.create({ partner_id: partnerId_1 }),
Command.create({ partner_id: partnerId_2 }),
],
});
const { openDiscuss } = await start();
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "@Te");
await click(":nth-child(1 of .o-mail-Composer-suggestion");
await contains(".o-mail-Composer-input", { value: "@TestPartner " });
await insertText(".o-mail-Composer-input", "@Te");
await click(":nth-child(2 of .o-mail-Composer-suggestion");
await contains(".o-mail-Composer-input", { value: "@TestPartner @TestPartner " });
await click(".o-mail-Composer-send:enabled");
await contains(
`.o-mail-Message-body .o_mail_redirect[data-oe-id="${partnerId_1}"][data-oe-model="res.partner"]`,
{ text: "@TestPartner" }
);
await contains(
`.o-mail-Message-body .o_mail_redirect[data-oe-id="${partnerId_2}"][data-oe-model="res.partner"]`,
{ text: "@TestPartner" }
);
});
QUnit.test("mention a channel on a second line when the first line contains #", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General good" });
const { openDiscuss } = await start();
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "#blabla\n#");
await click(".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-input", { value: "#blabla\n#General good " });
await click(".o-mail-Composer-send:enabled");
await contains(".o-mail-Message-body .o_channel_redirect", { text: "#General good" });
});
QUnit.test(
"mention a channel when replacing the space after the mention by another char",
async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General good" });
const { openDiscuss } = await start();
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "#");
await click(".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-input", { value: "#General good " });
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:enabled");
await contains(".o-mail-Message-body .o_channel_redirect", { text: "#General good" });
}
);
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([
{
channel_type: "channel",
group_public_id: false,
name: "my channel",
},
{
channel_type: "channel",
name: "my channel",
},
]);
const { openDiscuss } = await start();
openDiscuss(channelId_1);
await insertText(".o-mail-Composer-input", "#m");
await click(":nth-child(1 of .o-mail-Composer-suggestion)");
await contains(".o-mail-Composer-input", { value: "#my channel " });
await insertText(".o-mail-Composer-input", "#m");
await click(":nth-child(2 of .o-mail-Composer-suggestion");
await contains(".o-mail-Composer-input", { value: "#my channel #my channel " });
await click(".o-mail-Composer-send:enabled");
await contains(
`.o-mail-Message-body .o_channel_redirect[data-oe-id="${channelId_1}"][data-oe-model="discuss.channel"]`,
{ text: "#my channel" }
);
await contains(
`.o-mail-Message-body .o_channel_redirect[data-oe-id="${channelId_2}"][data-oe-model="discuss.channel"]`,
{ text: "#my channel" }
);
});
QUnit.test(
"Post a message containing an email address followed by a mention on another line",
async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({
email: "testpartner@odoo.com",
name: "TestPartner",
});
const channelId = pyEnv["discuss.channel"].create({
name: "test",
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", "email@odoo.com\n@Te");
await click(".o-mail-Composer-suggestion");
await contains(".o-mail-Composer-input", { value: "email@odoo.com\n@TestPartner " });
await click(".o-mail-Composer-send:enabled");
await contains(
`.o-mail-Message-body .o_mail_redirect[data-oe-id="${partnerId}"][data-oe-model="res.partner"]`,
{ text: "@TestPartner" }
);
}
);
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" });
const messageId = pyEnv["mail.message"].create({
body: "not empty",
message_type: "email",
model: "discuss.channel",
res_id: channelId,
});
pyEnv["mail.notification"].create({
failure_type: "SMTP",
mail_message_id: messageId,
notification_status: "canceled",
notification_type: "email",
res_partner_id: partnerId,
});
const { openDiscuss } = await start();
openDiscuss(channelId);
await contains(".o-mail-Message-notification .fa-envelope-o");
await click(".o-mail-Message-notification");
await contains(".o-mail-MessageNotificationPopover");
await contains(".o-mail-MessageNotificationPopover .fa-trash-o");
await contains(".o-mail-MessageNotificationPopover", { text: "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 () => {
// 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,
// and the conditions from focus would otherwise shadow them.
const pyEnv = await startServer();
// Needed partner & user to allow simulation of message reception
const partnerId = pyEnv["res.partner"].create({ name: "Foreigner partner" });
const userId = pyEnv["res.users"].create({
name: "Foreigner user",
partner_id: partnerId,
});
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "General",
channel_member_ids: [
Command.create({ partner_id: partnerId }),
Command.create({ partner_id: pyEnv.currentPartnerId }),
],
});
pyEnv["mail.message"].create([
{
body: "not empty",
model: "discuss.channel",
res_id: channelId,
},
]);
const { openDiscuss, env } = await start();
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:enabled");
await contains(".o-mail-Message", { count: 2 });
// composer is focused by default, we remove that focus
$(".o-mail-Composer-input")[0].blur();
// simulate receiving a message
pyEnv.withUser(userId, () =>
env.services.rpc("/mail/message/post", {
post_data: { body: "test", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
);
await contains(".o-mail-Message", { count: 3 });
await contains(".o-mail-Thread-newMessage hr + span", { text: "New messages" });
await contains(".o-mail-Message[aria-label='Note'] + .o-mail-Thread-newMessage");
}
);
QUnit.test(
"composer should be focused automatically after clicking on the send button",
async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "test" });
const { openDiscuss } = await start();
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "Dummy Message");
await click(".o-mail-Composer-send:enabled");
await contains(".o-mail-Composer-input:focus");
}
);
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");
await contains(".o-mail-ChatWindow-counter", { count: 0, text: "1" });
});
QUnit.test(
"[technical] opening a non-channel chat window should not call channel_fold",
async (assert) => {
// channel_fold should not be called when opening non-channels in chat
// window, because there is no server sync of fold state for them.
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({
async mockRPC(route, args) {
if (route.includes("channel_fold")) {
const message =
"should not call channel_fold when opening a non-channel chat window";
assert.step(message);
console.error(message);
throw Error(message);
}
},
});
await click(".o_menu_systray i[aria-label='Messages']");
await contains(".o-mail-NotificationItem");
await contains(".o-mail-ChatWindow", { count: 0 });
await click(".o-mail-NotificationItem");
await contains(".o-mail-ChatWindow");
}
);
QUnit.test("Thread messages are only loaded once", async (assert) => {
const pyEnv = await startServer();
const channelIds = pyEnv["discuss.channel"].create([{ name: "General" }, { name: "Sales" }]);
const { openDiscuss } = await start({
mockRPC(route, args, originalRPC) {
if (route === "/discuss/channel/messages") {
assert.step(`load messages - ${args["channel_id"]}`);
}
return originalRPC(route, args);
},
});
pyEnv["mail.message"].create([
{
model: "discuss.channel",
res_id: channelIds[0],
body: "Message on channel1",
},
{
model: "discuss.channel",
res_id: channelIds[1],
body: "Message on channel2",
},
]);
openDiscuss();
await click(":nth-child(1 of .o-mail-DiscussSidebarChannel");
await contains(".o-mail-Message-content", { text: "Message on channel1" });
await click(":nth-child(2 of .o-mail-DiscussSidebarChannel)");
await contains(".o-mail-Message-content", { text: "Message on channel2" });
await click(":nth-child(1 of .o-mail-DiscussSidebarChannel)");
await contains(".o-mail-Message-content", { text: "Message on channel1" });
assert.verifySteps([`load messages - ${channelIds[0]}`, `load messages - ${channelIds[1]}`]);
});
QUnit.test(
"Opening thread with needaction messages should mark all messages of thread as read",
async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const { env, openDiscuss } = await start({
mockRPC(route, args) {
if (args.model === "mail.message" && args.method === "mark_all_as_read") {
assert.step("mark-all-messages-as-read");
assert.deepEqual(args.args[0], [
["model", "=", "discuss.channel"],
["res_id", "=", channelId],
]);
}
},
});
openDiscuss(channelId);
await contains(".o-mail-Composer-input");
await triggerEvents(".o-mail-Composer-input", ["blur", "focusout"]);
await click("button", { text: "Inbox" });
await contains("h4", { text: "Congratulations, your inbox is empty" });
const messageId = pyEnv["mail.message"].create({
author_id: partnerId,
body: "@Mitchel Admin",
needaction: true,
model: "discuss.channel",
res_id: channelId,
needaction_partner_ids: [pyEnv.currentPartnerId],
});
pyEnv["mail.notification"].create({
mail_message_id: messageId,
notification_status: "sent",
notification_type: "inbox",
res_partner_id: pyEnv.currentPartnerId,
});
// simulate receiving a new needaction message
const [formattedMessage] = await env.services.orm.call("mail.message", "message_format", [
[messageId],
]);
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.message/inbox", formattedMessage);
await contains("button", { text: "Inbox", contains: [".badge", { text: "1" }] });
await click("button", { text: "General" });
await contains(".o-discuss-badge", { count: 0 });
await contains("button", { text: "Inbox", contains: [".badge", { count: 0 }] });
assert.verifySteps(["mark-all-messages-as-read"]);
}
);
QUnit.test(
"[technical] Opening thread without needaction messages should not mark all messages of thread as read",
async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { env, openDiscuss } = await start({
mockRPC(route, args) {
if (args.model === "mail.message" && args.method === "mark_all_as_read") {
assert.step("mark-all-messages-as-read");
}
},
});
openDiscuss(channelId);
await click("button", { text: "Inbox" });
await env.services.rpc("/mail/message/post", {
post_data: {
body: "Hello world!",
attachment_ids: [],
},
thread_id: channelId,
thread_model: "discuss.channel",
});
await click("button", { text: "General" });
await nextTick();
assert.verifySteps([]);
}
);
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({
channel_member_ids: [
Command.create({ message_unread_counter: 1, partner_id: pyEnv.currentPartnerId }),
Command.create({ partner_id: partnerId }),
],
channel_type: "chat",
});
pyEnv["mail.message"].create({
author_id: partnerId,
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
});
const loadDeferred = makeDeferred();
const { openDiscuss } = await start({
async mockRPC(route) {
if (route === "/discuss/channel/messages") {
await loadDeferred;
}
},
});
openDiscuss(undefined);
await contains(".o-discuss-badge", { text: "1" });
await click(".o-mail-DiscussSidebarChannel", { text: "Demo" });
loadDeferred.resolve();
await contains(".o-discuss-badge", { count: 0 });
});
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", { text: "Log note" });
await contains(".o-mail-Thread-newMessage", { count: 0 });
await click("button", { text: "Log note" });
await contains(".o-mail-Thread-newMessage", { count: 0 });
});
QUnit.test("Transient messages are added at the end of the thread", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const { openDiscuss } = await start();
openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "Dummy Message");
await click(".o-mail-Composer-send:enabled");
await contains(".o-mail-Message");
await insertText(".o-mail-Composer-input", "/help");
await click(".o-mail-Composer-send:enabled");
await contains(".o-mail-Message", { count: 2 });
await contains(":nth-child(1 of .o-mail-Message)", { text: "Mitchell Admin" });
await contains(":nth-child(2 of .o-mail-Message)", { text: "OdooBot" });
});