mirror of
https://github.com/bringout/oca-ocb-mail.git
synced 2026-04-22 11:42:06 +02:00
19.0 vanilla
This commit is contained in:
parent
5df8c07b59
commit
daa394e8b0
2114 changed files with 564841 additions and 299642 deletions
|
|
@ -0,0 +1,41 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
mockGetMedia,
|
||||
setupChatHub,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
|
||||
import { defineLivechatModels } from "@im_livechat/../tests/livechat_test_helpers";
|
||||
|
||||
import { test } from "@odoo/hoot";
|
||||
import { mockDate } from "@odoo/hoot-mock";
|
||||
|
||||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
defineLivechatModels();
|
||||
|
||||
test.tags("desktop");
|
||||
test("should display started a call message with operator livechat username", async () => {
|
||||
mockDate("2025-01-01 12:00:00", +1);
|
||||
mockGetMedia();
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].write(serverState.partnerId, {
|
||||
user_livechat_username: "mitchell boss",
|
||||
});
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
setupChatHub({ opened: [channelId] });
|
||||
await start();
|
||||
await contains(".o-mail-ChatWindow", { text: "Visitor" });
|
||||
await click("[title='Start Call']");
|
||||
await contains(".o-mail-NotificationMessage", { text: "mitchell boss started a call.1:00 PM" });
|
||||
});
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
import { click, contains, openDiscuss, start, startServer } from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { defineLivechatModels } from "./livechat_test_helpers";
|
||||
import { mockDate } from "@odoo/hoot-mock";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("Can invite a partner to a livechat channel", async () => {
|
||||
mockDate("2023-01-03 12:00:00", +1);
|
||||
const pyEnv = await startServer();
|
||||
const langIds = pyEnv["res.lang"].create([
|
||||
{ code: "en", name: "English" },
|
||||
{ code: "fr", name: "French" },
|
||||
{ code: "de", name: "German" },
|
||||
]);
|
||||
const expertiseIds = pyEnv["im_livechat.expertise"].create([
|
||||
{ name: "pricing" },
|
||||
{ name: "events" },
|
||||
]);
|
||||
pyEnv["res.partner"].write([serverState.partnerId], { user_livechat_username: "Mitch (FR)" });
|
||||
const userId = pyEnv["res.users"].create({
|
||||
name: "James",
|
||||
livechat_lang_ids: langIds,
|
||||
livechat_expertise_ids: expertiseIds,
|
||||
});
|
||||
pyEnv["res.partner"].create({
|
||||
lang: "en",
|
||||
name: "James",
|
||||
user_ids: [userId],
|
||||
});
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 20" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "Visitor 20",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
last_interest_dt: "2021-01-03 12:00:00",
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({
|
||||
guest_id: guestId,
|
||||
last_interest_dt: "2021-01-03 12:00:00",
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-livechat-ChannelInfoList"); // wait for auto-open of this panel
|
||||
await click("button[title='Invite People']");
|
||||
await click("input", {
|
||||
parent: [".o-discuss-ChannelInvitation-selectable", { text: "James" }],
|
||||
});
|
||||
await contains(
|
||||
".o-discuss-ChannelInvitation-selectable:contains('James English French German pricing events')"
|
||||
);
|
||||
await click("button:enabled", { text: "Invite" });
|
||||
await contains(".o-mail-NotificationMessage", {
|
||||
text: "Mitch (FR) invited James to the channel1:00 PM",
|
||||
});
|
||||
await contains(".o-discuss-ChannelInvitation", { count: 0 });
|
||||
await click("button[title='Members']");
|
||||
await contains(".o-discuss-ChannelMember", { text: "James" });
|
||||
});
|
||||
|
||||
test("Available operators come first", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].create({
|
||||
name: "Harry",
|
||||
im_status: "offline",
|
||||
user_ids: [pyEnv["res.users"].create({ name: "Harry" })],
|
||||
});
|
||||
const ronId = pyEnv["res.partner"].create({
|
||||
name: "Ron",
|
||||
im_status: "online",
|
||||
user_ids: [pyEnv["res.users"].create({ name: "Available operator" })],
|
||||
});
|
||||
pyEnv["im_livechat.channel"].create({
|
||||
available_operator_ids: [Command.create({ partner_id: ronId })],
|
||||
});
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor #1" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-livechat-ChannelInfoList"); // wait for auto-open of this panel
|
||||
await click("button[title='Invite People']");
|
||||
await contains(".o-discuss-ChannelInvitation-selectable", { count: 2 });
|
||||
await contains(":nth-child(1 of .o-discuss-ChannelInvitation-selectable)", { text: "Ron" });
|
||||
await contains(":nth-child(2 of .o-discuss-ChannelInvitation-selectable)", { text: "Harry" });
|
||||
});
|
||||
|
||||
test("Partners invited most frequently by the current user come first", async () => {
|
||||
mockDate("2023-01-03 12:00:00");
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].create({
|
||||
name: "John",
|
||||
im_status: "offline",
|
||||
user_ids: [pyEnv["res.users"].create({ name: "John" })],
|
||||
});
|
||||
pyEnv["res.partner"].create({
|
||||
name: "Albert",
|
||||
im_status: "offline",
|
||||
user_ids: [pyEnv["res.users"].create({ name: "Albert" })],
|
||||
});
|
||||
const guestId_1 = pyEnv["mail.guest"].create({ name: "Visitor #1" });
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
last_interest_dt: "2021-01-03 12:00:00",
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({
|
||||
guest_id: guestId_1,
|
||||
last_interest_dt: "2021-01-03 12:00:00",
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
const guestId_2 = pyEnv["mail.guest"].create({ name: "Visitor #2" });
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
last_interest_dt: "2021-01-03 11:00:00",
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({
|
||||
guest_id: guestId_2,
|
||||
last_interest_dt: "2021-01-03 11:00:00",
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await click(".o-mail-DiscussSidebarChannel", { text: "Visitor #1" });
|
||||
await contains(".o-livechat-ChannelInfoList"); // wait for auto-open of this panel
|
||||
await click("button[title='Invite People']");
|
||||
await click("input", { parent: [".o-discuss-ChannelInvitation-selectable", { text: "John" }] });
|
||||
await click("button:enabled", { text: "Invite" });
|
||||
await click(".o-mail-DiscussSidebarChannel", { text: "Visitor #2" });
|
||||
await click("button[title='Invite People']");
|
||||
await contains(".o-discuss-ChannelInvitation-selectable", { count: 2 });
|
||||
await contains(":nth-child(1 of .o-discuss-ChannelInvitation-selectable)", { text: "John" });
|
||||
await contains(":nth-child(2 of .o-discuss-ChannelInvitation-selectable)", { text: "Albert" });
|
||||
});
|
||||
|
||||
test("shows operators are in call", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor #1" });
|
||||
const [bobPartnerId] = pyEnv["res.partner"].create([
|
||||
{ name: "bob", user_ids: [Command.create({ name: "bob" })] },
|
||||
{ name: "john", user_ids: [Command.create({ name: "john" })] },
|
||||
]);
|
||||
const bobChannelId = pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: bobPartnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
});
|
||||
const [bobMemberId] = pyEnv["discuss.channel.member"].search([
|
||||
["partner_id", "=", bobPartnerId],
|
||||
["channel_id", "=", bobChannelId],
|
||||
]);
|
||||
pyEnv["discuss.channel.rtc.session"].create({
|
||||
channel_id: bobChannelId,
|
||||
channel_member_id: bobMemberId,
|
||||
});
|
||||
pyEnv["res.partner"]._compute_is_in_call();
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-livechat-ChannelInfoList"); // wait for auto-open of this panel
|
||||
await click("[title='Invite People']");
|
||||
await contains(".o-discuss-ChannelInvitation-selectable:contains('bob in a call')");
|
||||
await contains(".o-discuss-ChannelInvitation-selectable:contains('john')");
|
||||
await contains(".o-discuss-ChannelInvitation-selectable:contains('john in a call')", {
|
||||
count: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test("Operator invite shows livechat_username", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].create({
|
||||
name: "John",
|
||||
im_status: "offline",
|
||||
user_ids: [pyEnv["res.users"].create({ name: "John" })],
|
||||
user_livechat_username: "Johnny",
|
||||
});
|
||||
const guestId_1 = pyEnv["mail.guest"].create({ name: "Visitor #1" });
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
last_interest_dt: "2021-01-03 12:00:00",
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({
|
||||
guest_id: guestId_1,
|
||||
last_interest_dt: "2021-01-03 12:00:00",
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await click(".o-mail-DiscussSidebarChannel", { text: "Visitor #1" });
|
||||
await contains(".o-livechat-ChannelInfoList"); // wait for auto-open of this panel
|
||||
await click("button[title='Invite People']");
|
||||
await contains("input", {
|
||||
parent: [".o-discuss-ChannelInvitation-selectable", { text: "Johnny" }],
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { defineLivechatModels } from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
openDiscuss,
|
||||
setupChatHub,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { withGuest } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { serializeDate, today } from "@web/core/l10n/dates";
|
||||
import { livechatLastAgentLeaveFromChatWindow } from "./im_livechat_shared_tests";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("from the discuss app", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write([serverState.userId], {
|
||||
group_ids: pyEnv["res.groups"]
|
||||
.search_read([["id", "=", serverState.groupLivechatId]])
|
||||
.map(({ id }) => id),
|
||||
});
|
||||
const [guestId_1, guestId_2] = pyEnv["mail.guest"].create([
|
||||
{ name: "guest_1" },
|
||||
{ name: "guest_2" },
|
||||
]);
|
||||
const livechatChannelId = pyEnv["im_livechat.channel"].create({
|
||||
name: "HR",
|
||||
user_ids: [serverState.userId],
|
||||
});
|
||||
pyEnv["discuss.channel"].create([
|
||||
{
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId_1, livechat_member_type: "visitor" }),
|
||||
],
|
||||
livechat_end_dt: false,
|
||||
livechat_channel_id: livechatChannelId,
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
create_uid: serverState.publicUserId,
|
||||
},
|
||||
{
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId_2, livechat_member_type: "visitor" }),
|
||||
],
|
||||
livechat_end_dt: serializeDate(today()),
|
||||
livechat_channel_id: livechatChannelId,
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
create_uid: serverState.publicUserId,
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains(
|
||||
".o-mail-DiscussSidebarCategory-livechat:has(:text('HR')) .fa-circle[title='You have joined this live chat channel']"
|
||||
);
|
||||
await click("[title='Leave HR']", {
|
||||
parent: [".o-mail-DiscussSidebarCategory-livechat", { text: "HR" }],
|
||||
});
|
||||
await contains(
|
||||
".o-mail-DiscussSidebarCategory-livechat:has(:text('HR')) .fa-circle[title='You have joined this live chat channel']",
|
||||
{ count: 0 }
|
||||
);
|
||||
await click("[title='Join HR']", {
|
||||
parent: [".o-mail-DiscussSidebarCategory-livechat", { text: "HR" }],
|
||||
});
|
||||
await contains(
|
||||
".o-mail-DiscussSidebarCategory-livechat:has(:text('HR')) .fa-circle[title='You have joined this live chat channel']"
|
||||
);
|
||||
await click("[title='Chat Actions']", {
|
||||
parent: [".o-mail-DiscussSidebarChannel", { text: "guest_1" }],
|
||||
});
|
||||
await click(".o-dropdown-item:contains('Leave Channel')");
|
||||
await click("button:contains(Leave Conversation)");
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "guest_1", count: 0 });
|
||||
await click("[title='Chat Actions']", {
|
||||
parent: [".o-mail-DiscussSidebarChannel", { text: "guest_2" }],
|
||||
});
|
||||
await click(".o-dropdown-item:contains('Leave Channel')");
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "guest_2", count: 0 });
|
||||
await click("[title='Leave HR']", {
|
||||
parent: [".o-mail-DiscussSidebarCategory-livechat", { text: "HR" }],
|
||||
});
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechat", { text: "HR", count: 0 });
|
||||
});
|
||||
|
||||
test("from the command palette", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write([serverState.userId], {
|
||||
group_ids: pyEnv["res.groups"]
|
||||
.search_read([["id", "=", serverState.groupLivechatId]])
|
||||
.map(({ id }) => id),
|
||||
});
|
||||
pyEnv["im_livechat.channel"].create({ name: "HR", user_ids: [serverState.userId] });
|
||||
await start();
|
||||
await triggerHotkey("control+k");
|
||||
await click(".o_command", { text: "Leave HR" });
|
||||
await contains(".o_notification", { text: "You left HR." });
|
||||
await contains(".o_command", { text: "HR", count: 0 });
|
||||
await triggerHotkey("control+k");
|
||||
await click(".o_command", { text: "Join HR" });
|
||||
await contains(".o_notification", { text: "You joined HR." });
|
||||
});
|
||||
|
||||
test("from chat window", livechatLastAgentLeaveFromChatWindow);
|
||||
|
||||
test("visitor leaving ends the livechat conversation", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write([serverState.userId], {
|
||||
group_ids: pyEnv["res.groups"]
|
||||
.search_read([["id", "=", serverState.groupLivechatId]])
|
||||
.map(({ id }) => id),
|
||||
});
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor" });
|
||||
const livechatChannelId = pyEnv["im_livechat.channel"].create({
|
||||
name: "HR",
|
||||
user_ids: [serverState.userId],
|
||||
});
|
||||
const channel_id = pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
livechat_channel_id: livechatChannelId,
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
create_uid: serverState.publicUserId,
|
||||
});
|
||||
setupChatHub({ opened: [channel_id] });
|
||||
await start();
|
||||
await contains(".o-mail-ChatWindow");
|
||||
// simulate visitor leaving
|
||||
await withGuest(guestId, () => rpc("/im_livechat/visitor_leave_session", { channel_id }));
|
||||
await contains("span", { text: "This livechat conversation has ended" });
|
||||
await click("button[title*='Close Chat Window']");
|
||||
await contains(".o-mail-ChatWindow", { count: 0 });
|
||||
});
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { click, contains, openDiscuss, start, startServer } from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { defineLivechatModels } from "@im_livechat/../tests/livechat_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("display country in channel member list", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "James" });
|
||||
pyEnv["res.partner"].create({
|
||||
name: "James",
|
||||
user_ids: [userId],
|
||||
});
|
||||
const countryId = pyEnv["res.country"].create({ code: "be", name: "Belgium" });
|
||||
const guestId = pyEnv["mail.guest"].create({
|
||||
name: "Visitor #20",
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
country_id: countryId,
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-ActionPanel:contains(Information)");
|
||||
await click(".o-mail-DiscussContent-header button[name='member-list']");
|
||||
await contains(".o-discuss-ChannelMember span", { text: "Belgium", count: 2 });
|
||||
});
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { click, contains } from "@mail/../tests/mail_test_helpers_contains";
|
||||
import { defineLivechatModels } from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import { setupChatHub, start, startServer } from "@mail/../tests/mail_test_helpers";
|
||||
import { withGuest } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
|
||||
defineLivechatModels();
|
||||
test("Do not open chat windows automatically when chat hub is compact", async () => {
|
||||
const pyEnv = await startServer();
|
||||
setupChatHub({ folded: [pyEnv["discuss.channel"].create({ name: "General" })] });
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
create_uid: serverState.publicUserId,
|
||||
});
|
||||
await start();
|
||||
await click("button[title='Chat Options']");
|
||||
await click(".o-dropdown-item", { text: "Hide all conversations" });
|
||||
await contains(".o-mail-ChatHub-bubbleBtn .fa-comments");
|
||||
await withGuest(guestId, () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: {
|
||||
body: "I need help!",
|
||||
message_type: "comment",
|
||||
subtype_xmlid: "mail.mt_comment",
|
||||
},
|
||||
thread_id: channelId,
|
||||
thread_model: "discuss.channel",
|
||||
})
|
||||
);
|
||||
await contains(".o-mail-ChatHub-bubbleBtn .badge", { text: "1" });
|
||||
await click("button.o-mail-ChatHub-bubbleBtn");
|
||||
await contains(".o-mail-ChatBubble[name=Visitor] .badge", { text: "1" });
|
||||
await contains(".o-mail-ChatWindow", { count: 0, text: "Visitor" });
|
||||
await click(".o-mail-ChatBubble[name=Visitor] .o-mail-ChatHub-bubbleBtn");
|
||||
await contains(".o-mail-ChatWindow", { text: "Visitor" });
|
||||
});
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
openDiscuss,
|
||||
openFormView,
|
||||
setupChatHub,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { withGuest } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
import { test } from "@odoo/hoot";
|
||||
import { animationFrame } from "@odoo/hoot-mock";
|
||||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { defineLivechatModels } from "./livechat_test_helpers";
|
||||
import { serializeDate, today } from "@web/core/l10n/dates";
|
||||
|
||||
defineLivechatModels();
|
||||
|
||||
test.tags("mobile");
|
||||
test("can fold livechat chat windows in mobile", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Visitor" });
|
||||
pyEnv["res.users"].create([{ partner_id: partnerId }]);
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
unpin_dt: false,
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ partner_id: partnerId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await click(".o-mail-NotificationItem", { text: "Visitor" });
|
||||
await click(".o-mail-ChatWindow-header [title*='Fold']", {
|
||||
parent: [".o-mail-ChatWindow", { text: "Visitor" }],
|
||||
});
|
||||
await contains(".o-mail-ChatBubble");
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("closing a chat window with no message from admin side unpins it", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
|
||||
{ name: "Partner 1" },
|
||||
{ name: "Partner 2" },
|
||||
]);
|
||||
pyEnv["res.users"].create([{ partner_id: partnerId_1 }, { partner_id: partnerId_2 }]);
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
unpin_dt: false,
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ partner_id: partnerId_1, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
unpin_dt: false,
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ partner_id: partnerId_2, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_end_dt: serializeDate(today()),
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await click(".o-mail-NotificationItem", { text: "Partner 2" });
|
||||
await click(".o-mail-ChatWindow-header [title*='Close Chat Window']", {
|
||||
parent: [".o-mail-ChatWindow", { text: "Partner 2" }],
|
||||
});
|
||||
await openDiscuss();
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "Partner 1" });
|
||||
await contains(".o-mail-DiscussSidebarChannel", { count: 0, text: "Partner 2" });
|
||||
});
|
||||
|
||||
test.tags("desktop", "focus required");
|
||||
test("Focus should not be stolen when a new livechat open", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 12" });
|
||||
const channelIds = pyEnv["discuss.channel"].create([
|
||||
{ name: "general" },
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await click(".o-mail-NotificationItem", { text: "general" });
|
||||
await contains(".o-mail-ChatWindow", { text: "general" });
|
||||
await contains(".o-mail-Composer-input[placeholder='Message #general…']:focus");
|
||||
withGuest(guestId, () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: {
|
||||
body: "hu",
|
||||
message_type: "comment",
|
||||
subtype_xmlid: "mail.mt_comment",
|
||||
},
|
||||
thread_id: channelIds[1],
|
||||
thread_model: "discuss.channel",
|
||||
})
|
||||
);
|
||||
await contains(".o-mail-ChatWindow", { text: "Visitor 12" });
|
||||
await animationFrame();
|
||||
await contains(".o-mail-Composer-input[placeholder='Message #general…']:focus");
|
||||
});
|
||||
|
||||
test("do not ask confirmation if other operators are present", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor #12" });
|
||||
const otherOperatorId = pyEnv["res.partner"].create({ name: "John" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
Command.create({ partner_id: otherOperatorId, livechat_member_type: "agent" }),
|
||||
],
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
channel_type: "livechat",
|
||||
});
|
||||
setupChatHub({ opened: [channelId] });
|
||||
await start();
|
||||
await contains(".o-mail-ChatWindow");
|
||||
await click("[title*='Close Chat Window']");
|
||||
await contains(".o-mail-ChatWindow", { count: 0 });
|
||||
});
|
||||
|
||||
test.tags("desktop");
|
||||
test("Show livechats with new message in chat hub even when in discuss app)", async () => {
|
||||
// Chat hub show conversations with new message only when outside of discuss app by default.
|
||||
// Live chats are special in that agents are expected to see their ongoing conversations at all
|
||||
// time. Closing chat window ends the conversation. Hence the livechat always are shown on chat hub.
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
const [livechatId, channelId] = pyEnv["discuss.channel"].create([
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ guest_id: guestId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
},
|
||||
{
|
||||
channel_member_ids: [Command.create({ partner_id: serverState.partnerId })],
|
||||
channel_type: "channel",
|
||||
name: "general",
|
||||
},
|
||||
]);
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "<p>Test</p>",
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message:contains('Test')");
|
||||
// simulate livechat visitor sending a message
|
||||
await withGuest(guestId, () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: {
|
||||
body: "Hello, I need help!",
|
||||
message_type: "comment",
|
||||
subtype_xmlid: "mail.mt_comment",
|
||||
},
|
||||
thread_id: livechatId,
|
||||
thread_model: "discuss.channel",
|
||||
})
|
||||
);
|
||||
await contains(".o-mail-DiscussSidebar-item:contains('Visitor 11') .badge", { text: "1" });
|
||||
await openFormView("res.partner", serverState.partnerId);
|
||||
await contains(".o-mail-ChatWindow-header:contains('Visitor 11')");
|
||||
});
|
||||
|
||||
test("livechat: non-member can close immediately", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor ABC" });
|
||||
const PartnerId = pyEnv["res.partner"].create({ name: "Agent" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: PartnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
livechat_operator_id: PartnerId,
|
||||
channel_type: "livechat",
|
||||
});
|
||||
await start();
|
||||
setupChatHub({ opened: [channelId] });
|
||||
await contains(".o-mail-ChatWindow");
|
||||
await click("[title*='Close Chat Window']");
|
||||
await contains(".o-mail-ChatWindow", { count: 0 });
|
||||
});
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
insertText,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { withGuest } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import {
|
||||
asyncStep,
|
||||
Command,
|
||||
onRpc,
|
||||
serverState,
|
||||
waitForSteps,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
import { defineLivechatModels } from "./livechat_test_helpers";
|
||||
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("Can execute help command on livechat channels", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
onRpc("discuss.channel", "execute_command_help", () => {
|
||||
asyncStep("execute_command_help");
|
||||
return true;
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await insertText(".o-mail-Composer-input", "/help");
|
||||
await click(".o-mail-Composer button[title='Send']:enabled");
|
||||
await waitForSteps(["execute_command_help"]);
|
||||
});
|
||||
|
||||
test('Receives visitor typing status "is typing"', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 20" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-discuss-Typing", { text: "" });
|
||||
const channel = pyEnv["discuss.channel"].search_read([["id", "=", channelId]])[0];
|
||||
// simulate receive typing notification from livechat visitor "is typing"
|
||||
withGuest(guestId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
is_typing: true,
|
||||
channel_id: channel.id,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "Visitor 20 is typing..." });
|
||||
});
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
insertText,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { withGuest } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { mockDate } from "@odoo/hoot-mock";
|
||||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { defineLivechatModels } from "./livechat_test_helpers";
|
||||
import { press } from "@odoo/hoot-dom";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("add livechat in the sidebar on visitor sending first message", async () => {
|
||||
mockDate("2023-01-03 12:00:00"); // so that it's after last interest (mock server is in 2019 by default!)
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write([serverState.userId], { im_status: "online" });
|
||||
const countryId = pyEnv["res.country"].create({ code: "be", name: "Belgium" });
|
||||
const livechatChannelId = pyEnv["im_livechat.channel"].create({
|
||||
user_ids: [serverState.userId],
|
||||
});
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
unpin_dt: "2021-01-01 12:00:00",
|
||||
last_interest_dt: "2021-01-01 10:00:00",
|
||||
livechat_member_type: "agent",
|
||||
partner_id: serverState.partnerId,
|
||||
}),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
country_id: countryId,
|
||||
livechat_channel_id: livechatChannelId,
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains(".o-mail-DiscussSidebar");
|
||||
// simulate livechat visitor sending a message
|
||||
withGuest(guestId, () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: {
|
||||
body: "Hello, I need help!",
|
||||
message_type: "comment",
|
||||
subtype_xmlid: "mail.mt_comment",
|
||||
},
|
||||
thread_id: channelId,
|
||||
thread_model: "discuss.channel",
|
||||
})
|
||||
);
|
||||
await contains(
|
||||
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel-container",
|
||||
{
|
||||
text: "Visitor (Belgium)",
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("invite button should be present on livechat", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Discuss button[title='Invite People']");
|
||||
});
|
||||
|
||||
test("livechats are sorted by last activity time in the sidebar: most recent at the top", async () => {
|
||||
mockDate("2023-01-03 12:00:00"); // so that it's after last interest (mock server is in 2019 by default!)
|
||||
const pyEnv = await startServer();
|
||||
const guestId_1 = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
const guestId_2 = pyEnv["mail.guest"].create({ name: "Visitor 12" });
|
||||
pyEnv["discuss.channel"].create([
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
last_interest_dt: "2021-01-01 10:00:00",
|
||||
livechat_member_type: "agent",
|
||||
partner_id: serverState.partnerId,
|
||||
}),
|
||||
Command.create({ guest_id: guestId_1, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
},
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
last_interest_dt: "2021-02-01 10:00:00",
|
||||
livechat_member_type: "agent",
|
||||
partner_id: serverState.partnerId,
|
||||
}),
|
||||
Command.create({ guest_id: guestId_2, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains(".o-mail-DiscussSidebarChannel", { count: 2 });
|
||||
await contains(":nth-child(1 of .o-mail-DiscussSidebarChannel-container)", {
|
||||
text: "Visitor 12",
|
||||
});
|
||||
await click(".o-mail-DiscussSidebarChannel", { text: "Visitor 11" });
|
||||
await insertText(".o-mail-Composer-input", "Blabla");
|
||||
await press("Enter");
|
||||
await contains(":nth-child(1 of .o-mail-DiscussSidebarChannel-container)", {
|
||||
text: "Visitor 11",
|
||||
});
|
||||
await contains(":nth-child(2 of .o-mail-DiscussSidebarChannel-container)", {
|
||||
text: "Visitor 12",
|
||||
});
|
||||
});
|
||||
|
||||
test("sidebar search finds livechats", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await click("input[placeholder='Search conversations']");
|
||||
await click("a", { text: "Visitor 11" });
|
||||
await contains(".o-mail-DiscussContent-threadName[title='Visitor 11']");
|
||||
});
|
||||
|
||||
test("open visitor's partner profile if visitor has one", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const livechatPartner = pyEnv["res.partner"].create({ name: "Joel Willis" });
|
||||
const channel = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ partner_id: livechatPartner, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channel);
|
||||
await click("a[title='View Contact']");
|
||||
await contains("div.o_field_widget > input:value(Joel Willis)");
|
||||
});
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import {
|
||||
defineLivechatModels,
|
||||
loadDefaultEmbedConfig,
|
||||
} from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import { contains, setupChatHub, start, startServer } from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { Command, patchWithCleanup, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { mailDataHelpers } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("persisted session", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const livechatChannelId = await loadDefaultEmbedConfig();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ guest_id: guestId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_channel_id: livechatChannelId,
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
setupChatHub({ opened: [channelId] });
|
||||
await start({
|
||||
authenticateAs: { ...pyEnv["mail.guest"].read(guestId)[0], _name: "mail.guest" },
|
||||
});
|
||||
await contains(".o-mail-ChatWindow");
|
||||
});
|
||||
|
||||
test("rule received in init", async () => {
|
||||
const pyEnv = await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
const autopopupRuleId = pyEnv["im_livechat.channel.rule"].create({
|
||||
auto_popup_timer: 0,
|
||||
action: "auto_popup",
|
||||
});
|
||||
patchWithCleanup(mailDataHelpers, {
|
||||
_process_request_for_all(store) {
|
||||
super._process_request_for_all(...arguments);
|
||||
store.add(pyEnv["im_livechat.channel.rule"].browse(autopopupRuleId), {
|
||||
action: "auto_popup",
|
||||
auto_popup_timer: 0,
|
||||
});
|
||||
store.add({ livechat_rule: autopopupRuleId });
|
||||
},
|
||||
});
|
||||
await start({ authenticateAs: false });
|
||||
await contains(".o-mail-ChatWindow");
|
||||
});
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import {
|
||||
defineLivechatModels,
|
||||
loadDefaultEmbedConfig,
|
||||
} from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import { contains, setupChatHub, start, startServer } from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { Command, makeMockEnv, serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("Do not show bot IM status", async () => {
|
||||
const pyEnv = await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
await makeMockEnv({ embedLivechat: true });
|
||||
const partnerId1 = pyEnv["res.partner"].create({ name: "Mitchell", im_status: "online" });
|
||||
pyEnv["res.users"].create({ partner_id: partnerId1 });
|
||||
const channelId1 = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: partnerId1, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "chat",
|
||||
});
|
||||
const partnerId2 = pyEnv["res.partner"].create({ name: "Dummy" });
|
||||
const channelId2 = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "visitor" }),
|
||||
Command.create({ partner_id: partnerId2, livechat_member_type: "bot" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: partnerId2,
|
||||
});
|
||||
setupChatHub({ folded: [channelId1, channelId2] });
|
||||
await start({ authenticateAs: false });
|
||||
await contains(".o-mail-ChatBubble[name='Mitchell'] .o-mail-ImStatus");
|
||||
await contains(".o-mail-ChatBubble[name='Dummy']");
|
||||
await contains(".o-mail-ChatBubble[name='Dummy'] .o-mail-ImStatus", { count: 0 });
|
||||
});
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
import {
|
||||
defineLivechatModels,
|
||||
loadDefaultEmbedConfig,
|
||||
} from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import {
|
||||
assertChatBubbleAndWindowImStatus,
|
||||
click,
|
||||
contains,
|
||||
inputFiles,
|
||||
insertText,
|
||||
mockGetMedia,
|
||||
onRpcBefore,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { asyncStep, serverState, waitForSteps, withUser } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { deserializeDateTime } from "@web/core/l10n/dates";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { getOrigin } from "@web/core/utils/urls";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("internal users can upload file to temporary thread", async () => {
|
||||
const pyEnv = await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
const [partnerUser] = pyEnv["res.users"].search_read([["id", "=", serverState.partnerId]]);
|
||||
await start({ authenticateAs: partnerUser });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
const file = new File(["hello, world"], "text.txt", { type: "text/plain" });
|
||||
await contains(".o-mail-Composer");
|
||||
await click(".o-mail-Composer button[title='More Actions']");
|
||||
await contains(".dropdown-item:contains('Attach files')");
|
||||
await inputFiles(".o-mail-Composer .o_input_file", [file]);
|
||||
await contains(".o-mail-AttachmentContainer:not(.o-isUploading):contains(text.txt) .fa-check");
|
||||
await triggerHotkey("Enter");
|
||||
await contains(".o-mail-Message .o-mail-AttachmentContainer:contains(text.txt)");
|
||||
});
|
||||
|
||||
test("Conversation name is operator livechat user name", async () => {
|
||||
const pyEnv = await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
pyEnv["res.partner"].write(serverState.partnerId, { user_livechat_username: "MitchellOp" });
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-ChatWindow-header", { text: "MitchellOp" });
|
||||
});
|
||||
|
||||
test("Portal users should not be able to start a call", async () => {
|
||||
mockGetMedia();
|
||||
const pyEnv = await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
const joelUid = pyEnv["res.users"].create({
|
||||
name: "Joel",
|
||||
share: true,
|
||||
login: "joel",
|
||||
password: "joel",
|
||||
});
|
||||
const joelPid = pyEnv["res.partner"].create({
|
||||
name: "Joel",
|
||||
user_ids: [joelUid],
|
||||
});
|
||||
pyEnv["res.partner"].write(serverState.partnerId, { user_livechat_username: "MitchellOp" });
|
||||
await start({ authenticateAs: { login: "joel", password: "joel" } });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-ChatWindow-header:text('MitchellOp')");
|
||||
await insertText(".o-mail-Composer-input", "Hello MitchellOp!");
|
||||
await triggerHotkey("Enter");
|
||||
await contains(".o-mail-Message[data-persistent]:contains('Hello MitchellOp!')");
|
||||
await contains(".o-mail-ChatWindow-header .o-mail-ActionList-button", { count: 2 });
|
||||
await contains(".o-mail-ChatWindow-header .o-mail-ActionList-button[title='Fold']");
|
||||
await contains(".o-mail-ChatWindow-header .o-mail-ActionList-button[title*='Close']");
|
||||
await contains(".o-discuss-Call", { count: 0 });
|
||||
// simulate operator starts call
|
||||
const [channelId] = pyEnv["discuss.channel"].search([
|
||||
["channel_type", "=", "livechat"],
|
||||
[
|
||||
"channel_member_ids",
|
||||
"in",
|
||||
pyEnv["discuss.channel.member"].search([["partner_id", "=", joelPid]]),
|
||||
],
|
||||
]);
|
||||
await withUser(serverState.userId, () =>
|
||||
rpc("/mail/rtc/channel/join_call", { channel_id: channelId }, { silent: true })
|
||||
);
|
||||
await contains(".o-discuss-Call button", { count: 2 });
|
||||
await contains(".o-discuss-Call button[title='Join Video Call']");
|
||||
await contains(".o-discuss-Call button[title='Join Call']");
|
||||
// still same actions in header
|
||||
await contains(".o-mail-ChatWindow-header .o-mail-ActionList-button", { count: 2 });
|
||||
await contains(".o-mail-ChatWindow-header .o-mail-ActionList-button[title='Fold']");
|
||||
await contains(".o-mail-ChatWindow-header .o-mail-ActionList-button[title*='Close']");
|
||||
});
|
||||
|
||||
test("avatar url contains access token for non-internal users", async () => {
|
||||
const pyEnv = await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
pyEnv["res.partner"].write(serverState.partnerId, { user_livechat_username: "MitchellOp" });
|
||||
const [partner] = pyEnv["res.partner"].search_read([["id", "=", serverState.partnerId]]);
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(
|
||||
`.o-mail-ChatWindow-threadAvatar img[data-src="${getOrigin()}/web/image/res.partner/${
|
||||
partner.id
|
||||
}/avatar_128?access_token=${partner.id}&unique=${
|
||||
deserializeDateTime(partner.write_date).ts
|
||||
}"]`
|
||||
);
|
||||
await contains(
|
||||
`.o-mail-Message-avatar[data-src="${getOrigin()}/web/image/res.partner/${
|
||||
partner.id
|
||||
}/avatar_128?access_token=${partner.id}&unique=${
|
||||
deserializeDateTime(partner.write_date).ts
|
||||
}"]`
|
||||
);
|
||||
await insertText(".o-mail-Composer-input", "Hello World!");
|
||||
triggerHotkey("Enter");
|
||||
const guestId = pyEnv.cookie.get("dgid");
|
||||
const [guest] = pyEnv["mail.guest"].read(guestId);
|
||||
await contains(
|
||||
`.o-mail-Message-avatar[data-src="${getOrigin()}/web/image/mail.guest/${
|
||||
guest.id
|
||||
}/avatar_128?access_token=${guest.id}&unique=${deserializeDateTime(guest.write_date).ts}"]`
|
||||
);
|
||||
});
|
||||
|
||||
test("can close confirm livechat with keyboard", async () => {
|
||||
await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
onRpcBefore((route) => {
|
||||
if (route === "/im_livechat/visitor_leave_session") {
|
||||
asyncStep(route);
|
||||
}
|
||||
});
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-ChatWindow");
|
||||
await insertText(".o-mail-Composer-input", "Hello");
|
||||
await triggerHotkey("Enter");
|
||||
await contains(".o-mail-Thread:not([data-transient])");
|
||||
await triggerHotkey("Escape");
|
||||
await contains(".o-livechat-CloseConfirmation", {
|
||||
text: "Leaving will end the live chat. Do you want to proceed?",
|
||||
});
|
||||
await triggerHotkey("Escape");
|
||||
await contains(".o-livechat-CloseConfirmation", { count: 0 });
|
||||
await triggerHotkey("Escape");
|
||||
await contains(".o-livechat-CloseConfirmation", {
|
||||
text: "Leaving will end the live chat. Do you want to proceed?",
|
||||
});
|
||||
await triggerHotkey("Enter");
|
||||
await waitForSteps(["/im_livechat/visitor_leave_session"]);
|
||||
await contains(".o-mail-ChatWindow", { text: "Did we correctly answer your question?" });
|
||||
});
|
||||
|
||||
test("Should not show IM status of agents", async () => {
|
||||
mockGetMedia();
|
||||
const pyEnv = await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
const joelUid = pyEnv["res.users"].create({
|
||||
name: "Joel",
|
||||
share: true,
|
||||
login: "joel",
|
||||
password: "joel",
|
||||
});
|
||||
pyEnv["res.partner"].create({ name: "Joel", user_ids: [joelUid] });
|
||||
pyEnv["res.partner"].write(serverState.partnerId, {
|
||||
im_status: "online",
|
||||
user_livechat_username: "MitchellOp",
|
||||
});
|
||||
await start({ authenticateAs: { login: "joel", password: "joel" } });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-ChatWindow-header:text('MitchellOp')");
|
||||
await insertText(".o-mail-Composer-input", "Hello MitchellOp!");
|
||||
await triggerHotkey("Enter");
|
||||
await contains(".o-mail-Message[data-persistent]:contains('Hello MitchellOp!')");
|
||||
await click(".o-mail-ChatWindow-header");
|
||||
await contains(".o-mail-ChatBubble");
|
||||
await assertChatBubbleAndWindowImStatus("MitchellOp", 0);
|
||||
});
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { expirableStorage } from "@im_livechat/core/common/expirable_storage";
|
||||
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { mockDate } from "@odoo/hoot-mock";
|
||||
import { asyncStep, waitForSteps } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
|
||||
test("value is removed from expirable storage after expiration", () => {
|
||||
mockDate("2023-01-01 00:00:00");
|
||||
const ONE_DAY = 60 * 60 * 24;
|
||||
expirableStorage.setItem("foo", "bar", ONE_DAY);
|
||||
expect(expirableStorage.getItem("foo")).toBe("bar");
|
||||
mockDate("2023-01-01 23:00:00");
|
||||
expect(expirableStorage.getItem("foo")).toBe("bar");
|
||||
mockDate("2023-01-02 00:00:01");
|
||||
expect(expirableStorage.getItem("foo")).toBe(null);
|
||||
});
|
||||
|
||||
test("subscribe/unsubscribe to storage changes", async () => {
|
||||
const fooCallback = (value) => asyncStep(`foo - ${value}`);
|
||||
const barCallback = (value) => asyncStep(`bar - ${value}`);
|
||||
expirableStorage.onChange("foo", fooCallback);
|
||||
expirableStorage.onChange("bar", barCallback);
|
||||
expirableStorage.setItem("foo", 1);
|
||||
await waitForSteps(["foo - 1"]);
|
||||
expirableStorage.setItem("bar", 2);
|
||||
await waitForSteps(["bar - 2"]);
|
||||
expirableStorage.removeItem("foo");
|
||||
await waitForSteps(["foo - null"]);
|
||||
expirableStorage.offChange("foo", fooCallback);
|
||||
expirableStorage.setItem("foo", 3);
|
||||
expirableStorage.removeItem("bar");
|
||||
await waitForSteps(["bar - null"]);
|
||||
});
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
import {
|
||||
defineLivechatModels,
|
||||
loadDefaultEmbedConfig,
|
||||
} from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import { RATING } from "@im_livechat/embed/common/livechat_service";
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
insertText,
|
||||
onRpcBefore,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { expect, test } from "@odoo/hoot";
|
||||
import {
|
||||
asyncStep,
|
||||
Command,
|
||||
getService,
|
||||
patchWithCleanup,
|
||||
serverState,
|
||||
waitForSteps,
|
||||
withUser,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
defineLivechatModels();
|
||||
|
||||
test("Do not ask feedback if empty", async () => {
|
||||
await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-ChatWindow");
|
||||
await click("[title*='Close Chat Window']");
|
||||
});
|
||||
|
||||
test("Close without feedback", async () => {
|
||||
await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
onRpcBefore((route) => {
|
||||
if (route === "/im_livechat/visitor_leave_session" || route === "/im_livechat/feedback") {
|
||||
asyncStep(route);
|
||||
}
|
||||
});
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-ChatWindow");
|
||||
await insertText(".o-mail-Composer-input", "Hello World!");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-Thread:not([data-transient])");
|
||||
await click("[title*='Close Chat Window']");
|
||||
await click(".o-livechat-CloseConfirmation-leave");
|
||||
await click("button", { text: "Close" });
|
||||
await contains(".o-livechat-LivechatButton");
|
||||
await waitForSteps(["/im_livechat/visitor_leave_session"]);
|
||||
});
|
||||
|
||||
test("Last operator leaving ends the livechat", async () => {
|
||||
await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
const operatorUserId = serverState.userId;
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-ChatWindow");
|
||||
await insertText(".o-mail-Composer-input", "Hello World!");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-Message-content", { text: "Hello World!" });
|
||||
// simulate operator leaving
|
||||
await withUser(operatorUserId, () =>
|
||||
getService("orm").call("discuss.channel", "action_unfollow", [
|
||||
[Object.values(getService("mail.store").Thread.records).at(-1).id],
|
||||
])
|
||||
);
|
||||
await contains("span", { text: "This livechat conversation has ended" });
|
||||
await contains(".o-mail-Composer-input", { count: 0 });
|
||||
await click("[title*='Close Chat Window']");
|
||||
await contains("p", { text: "Did we correctly answer your question?" }); // shows immediately feedback
|
||||
});
|
||||
|
||||
test("Feedback with rating and comment", async () => {
|
||||
await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
onRpcBefore((route, args) => {
|
||||
if (route === "/im_livechat/visitor_leave_session") {
|
||||
asyncStep(route);
|
||||
}
|
||||
if (route === "/im_livechat/feedback") {
|
||||
asyncStep(route);
|
||||
expect(args.reason).toInclude("Good job!");
|
||||
expect(args.rate).toBe(RATING.OK);
|
||||
}
|
||||
});
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-ChatWindow");
|
||||
await insertText(".o-mail-Composer-input", "Hello World!");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-Thread:not([data-transient])");
|
||||
await click("[title*='Close Chat Window']");
|
||||
await click(".o-livechat-CloseConfirmation-leave");
|
||||
await waitForSteps(["/im_livechat/visitor_leave_session"]);
|
||||
await click(`img[alt="${RATING.OK}"]`);
|
||||
await insertText("textarea[placeholder='Explain your note']", "Good job!");
|
||||
await click("button:contains(Send):enabled");
|
||||
await contains("p", { text: "Thank you for your feedback" });
|
||||
await waitForSteps(["/im_livechat/feedback"]);
|
||||
});
|
||||
|
||||
test("Closing folded chat window should open it with feedback", async () => {
|
||||
await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await insertText(".o-mail-Composer-input", "Hello World!");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-Thread:not([data-transient])");
|
||||
await click("[title='Fold']");
|
||||
await click(".o-mail-ChatBubble");
|
||||
await click("[title*='Close Chat Window']");
|
||||
await click(".o-livechat-CloseConfirmation-leave");
|
||||
await click(".o-mail-ChatHub-bubbleBtn");
|
||||
await contains(".o-mail-ChatWindow p", { text: "Did we correctly answer your question?" });
|
||||
});
|
||||
|
||||
test("Start new session from feedback panel", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = await loadDefaultEmbedConfig();
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-ChatWindow", { text: "Mitchell Admin" });
|
||||
await insertText(".o-mail-Composer-input", "Hello World!");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-Thread:not([data-transient])");
|
||||
await click("[title*='Close Chat Window']");
|
||||
await click(".o-livechat-CloseConfirmation-leave");
|
||||
pyEnv["im_livechat.channel"].write([channelId], {
|
||||
user_ids: [Command.clear(serverState.userId)],
|
||||
});
|
||||
pyEnv["im_livechat.channel"].write([channelId], {
|
||||
user_ids: [
|
||||
pyEnv["res.users"].create({
|
||||
partner_id: pyEnv["res.partner"].create({ name: "Bob Operator" }),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
await click("button", { text: "New Session" });
|
||||
await contains(".o-mail-ChatWindow", { count: 1 });
|
||||
await contains(".o-mail-ChatWindow", { text: "Bob Operator" });
|
||||
});
|
||||
|
||||
test("open review link on good rating", async () => {
|
||||
patchWithCleanup(window, {
|
||||
open: (...args) => {
|
||||
expect.step("window.open");
|
||||
expect(args[0]).toBe("https://www.odoo.com");
|
||||
expect(args[1]).toBe("_blank");
|
||||
},
|
||||
});
|
||||
await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await insertText(".o-mail-Composer-input", "Hello World!");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-Message-content", { text: "Hello World!" });
|
||||
await click("[title*='Close Chat Window']");
|
||||
await click(".o-livechat-CloseConfirmation-leave");
|
||||
await click(`img[alt="${RATING.GOOD}"]`);
|
||||
await insertText("textarea[placeholder='Explain your note']", "Good job!");
|
||||
await click("button:contains(Send):enabled");
|
||||
await expect.waitForSteps(["window.open"]);
|
||||
});
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import {
|
||||
defineLivechatModels,
|
||||
loadDefaultEmbedConfig,
|
||||
} from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import { click, start, startServer } from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { press, waitFor } from "@odoo/hoot-dom";
|
||||
import {
|
||||
asyncStep,
|
||||
contains,
|
||||
getService,
|
||||
onRpc,
|
||||
serverState,
|
||||
waitForSteps,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("Handle livechat history command", async () => {
|
||||
const pyEnv = await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
onRpc("/im_livechat/history", ({ url }) => {
|
||||
asyncStep(new URL(url).pathname);
|
||||
return true;
|
||||
});
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-Composer-input").edit("Hello World!", { confirm: false });
|
||||
await press("Enter");
|
||||
await waitFor(".o-mail-Message:contains(Hello World!)");
|
||||
const thread = Object.values(getService("mail.store").Thread.records).at(-1);
|
||||
const guestId = pyEnv.cookie.get("dgid");
|
||||
const [guest] = pyEnv["mail.guest"].read(guestId);
|
||||
pyEnv["bus.bus"]._sendone(guest, "im_livechat.history_command", {
|
||||
id: thread.id,
|
||||
partner_id: serverState.partnerId,
|
||||
});
|
||||
await waitForSteps(["/im_livechat/history"]);
|
||||
});
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
import {
|
||||
defineLivechatModels,
|
||||
loadDefaultEmbedConfig,
|
||||
} from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
insertText,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { mailDataHelpers } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { asyncStep, patchWithCleanup, waitForSteps } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("open/close temporary channel", async () => {
|
||||
await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-ChatWindow");
|
||||
await contains(".o-livechat-LivechatButton", { count: 0 });
|
||||
await click("[title*='Close Chat Window']");
|
||||
await contains(".o-mail-ChatWindow", { count: 0 });
|
||||
await contains(".o-livechat-LivechatButton", { count: 1 });
|
||||
});
|
||||
|
||||
test("open/close persisted channel", async () => {
|
||||
await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
const env = await start({ authenticateAs: false });
|
||||
env.services.bus_service.subscribe("discuss.channel/new_message", () =>
|
||||
asyncStep("discuss.channel/new_message")
|
||||
);
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await insertText(".o-mail-Composer-input", "How can I help?");
|
||||
await triggerHotkey("Enter");
|
||||
await contains(".o-mail-Thread:not([data-transient])");
|
||||
await contains(".o-mail-Message-content", { text: "How can I help?" });
|
||||
await waitForSteps(["discuss.channel/new_message"]);
|
||||
await click("[title*='Close Chat Window']");
|
||||
await click(".o-livechat-CloseConfirmation-leave");
|
||||
await contains(".o-mail-ChatWindow", { text: "Did we correctly answer your question?" });
|
||||
await click("[title*='Close Chat Window']");
|
||||
await contains(".o-mail-ChatWindow", { count: 0 });
|
||||
await contains(".o-livechat-LivechatButton", { count: 1 });
|
||||
});
|
||||
|
||||
test("livechat not available", async () => {
|
||||
await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
patchWithCleanup(mailDataHelpers, {
|
||||
_process_request_for_all(store) {
|
||||
super._process_request_for_all(...arguments);
|
||||
store.add({ livechat_available: false });
|
||||
},
|
||||
});
|
||||
await start({ authenticateAs: false });
|
||||
await contains(".o-mail-ChatHub");
|
||||
await contains(".o-livechat-LivechatButton", { count: 0 });
|
||||
});
|
||||
|
||||
test("clicking on notification opens the chat", async () => {
|
||||
const pyEnv = await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
const btnAndTextRuleId = pyEnv["im_livechat.channel.rule"].create({
|
||||
action: "display_button_and_text",
|
||||
});
|
||||
patchWithCleanup(mailDataHelpers, {
|
||||
_process_request_for_all(store) {
|
||||
super._process_request_for_all(...arguments);
|
||||
store.add(pyEnv["im_livechat.channel.rule"].browse(btnAndTextRuleId), {
|
||||
action: "display_button_and_text",
|
||||
});
|
||||
store.add({ livechat_rule: btnAndTextRuleId });
|
||||
},
|
||||
});
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton-notification", {
|
||||
text: "Need help? Chat with us.",
|
||||
});
|
||||
await contains(".o-mail-ChatWindow");
|
||||
});
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
import {
|
||||
defineLivechatModels,
|
||||
loadDefaultEmbedConfig,
|
||||
} from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import { expirableStorage } from "@im_livechat/core/common/expirable_storage";
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
insertText,
|
||||
listenStoreFetch,
|
||||
onRpcBefore,
|
||||
setupChatHub,
|
||||
start,
|
||||
startServer,
|
||||
STORE_FETCH_ROUTES,
|
||||
triggerHotkey,
|
||||
userContext,
|
||||
waitStoreFetch,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import {
|
||||
asyncStep,
|
||||
Command,
|
||||
onRpc,
|
||||
serverState,
|
||||
waitForSteps,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("persisted session history", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const livechatChannelId = await loadDefaultEmbedConfig();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ guest_id: guestId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_channel_id: livechatChannelId,
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
expirableStorage.setItem(
|
||||
"im_livechat.saved_state",
|
||||
JSON.stringify({
|
||||
store: { "discuss.channel": [{ id: channelId }] },
|
||||
persisted: true,
|
||||
livechatUserId: serverState.publicUserId,
|
||||
})
|
||||
);
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "Old message in history",
|
||||
res_id: channelId,
|
||||
model: "discuss.channel",
|
||||
message_type: "comment",
|
||||
});
|
||||
setupChatHub({ opened: [channelId] });
|
||||
await start({
|
||||
authenticateAs: { ...pyEnv["mail.guest"].read(guestId)[0], _name: "mail.guest" },
|
||||
});
|
||||
await contains(".o-mail-Message-content", { text: "Old message in history" });
|
||||
});
|
||||
|
||||
test("previous operator prioritized", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const livechatChannelId = await loadDefaultEmbedConfig();
|
||||
const userId = pyEnv["res.users"].create({ name: "John Doe", im_status: "online" });
|
||||
const previousOperatorId = pyEnv["res.partner"].create({
|
||||
name: "John Doe",
|
||||
user_ids: [userId],
|
||||
});
|
||||
pyEnv["im_livechat.channel"].write([livechatChannelId], { user_ids: [Command.link(userId)] });
|
||||
expirableStorage.setItem("im_livechat_previous_operator", JSON.stringify(previousOperatorId));
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-Message-author", { text: "John Doe" });
|
||||
});
|
||||
|
||||
test("Only necessary requests are made when creating a new chat", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const livechatChannelId = await loadDefaultEmbedConfig();
|
||||
const operatorPartnerId = serverState.partnerId;
|
||||
onRpcBefore((route, args) => {
|
||||
if (!route.includes("assets") && !STORE_FETCH_ROUTES.includes(route)) {
|
||||
asyncStep(`${route} - ${JSON.stringify(args)}`);
|
||||
}
|
||||
});
|
||||
listenStoreFetch(undefined, { logParams: ["init_livechat"] });
|
||||
await start({ authenticateAs: false });
|
||||
await contains(".o-livechat-LivechatButton");
|
||||
await waitStoreFetch([
|
||||
"failures", // called because mail/core/web is loaded in test bundle
|
||||
"systray_get_activities", // called because mail/core/web is loaded in test bundle
|
||||
"init_messaging",
|
||||
["init_livechat", livechatChannelId],
|
||||
]);
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-Message", { text: "Hello, how may I help you?" });
|
||||
await waitForSteps([
|
||||
`/im_livechat/get_session - ${JSON.stringify({
|
||||
channel_id: livechatChannelId,
|
||||
previous_operator_id: null,
|
||||
persisted: false,
|
||||
})}`,
|
||||
]);
|
||||
await insertText(".o-mail-Composer-input", "Hello!");
|
||||
await waitForSteps([]);
|
||||
await triggerHotkey("Enter");
|
||||
await contains(".o-mail-Message", { text: "Hello!" });
|
||||
const [threadId] = pyEnv["discuss.channel"].search([], { order: "id DESC" });
|
||||
await waitStoreFetch(
|
||||
[
|
||||
"failures", // called because mail/core/web is loaded in test bundle
|
||||
"systray_get_activities", // called because mail/core/web is loaded in test bundle
|
||||
"init_messaging",
|
||||
],
|
||||
{
|
||||
stepsBefore: [
|
||||
`/im_livechat/get_session - ${JSON.stringify({
|
||||
channel_id: livechatChannelId,
|
||||
previous_operator_id: operatorPartnerId,
|
||||
persisted: true,
|
||||
})}`,
|
||||
`/mail/message/post - ${JSON.stringify({
|
||||
post_data: {
|
||||
body: "Hello!",
|
||||
email_add_signature: true,
|
||||
message_type: "comment",
|
||||
subtype_xmlid: "mail.mt_comment",
|
||||
},
|
||||
thread_id: threadId,
|
||||
thread_model: "discuss.channel",
|
||||
context: { ...userContext(), temporary_id: 0.8200000000000001 },
|
||||
})}`,
|
||||
],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("do not create new thread when operator answers to visitor", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const livechatChannelId = await loadDefaultEmbedConfig();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
onRpc("/im_livechat/get_session", async () => asyncStep("/im_livechat/get_session"));
|
||||
onRpc("/mail/message/post", async () => asyncStep("/mail/message/post"));
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ guest_id: guestId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_channel_id: livechatChannelId,
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
create_uid: serverState.publicUserId,
|
||||
});
|
||||
setupChatHub({ opened: [channelId] });
|
||||
await start({
|
||||
authenticateAs: pyEnv["res.users"].search_read([["id", "=", serverState.userId]])[0],
|
||||
});
|
||||
await insertText(".o-mail-Composer-input", "Hello!");
|
||||
await triggerHotkey("Enter");
|
||||
await contains(".o-mail-Message", { text: "Hello!" });
|
||||
await waitForSteps(["/mail/message/post"]);
|
||||
});
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
import { waitUntilSubscribe } from "@bus/../tests/bus_test_helpers";
|
||||
import {
|
||||
defineLivechatModels,
|
||||
loadDefaultEmbedConfig,
|
||||
} from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import {
|
||||
assertChatHub,
|
||||
click,
|
||||
contains,
|
||||
focus,
|
||||
insertText,
|
||||
onRpcBefore,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { advanceTime } from "@odoo/hoot-mock";
|
||||
import { getService, serverState, withUser } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { LivechatButton } from "@im_livechat/embed/common/livechat_button";
|
||||
import { queryFirst } from "@odoo/hoot-dom";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("Session is reset after failing to persist the channel", async () => {
|
||||
await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
onRpcBefore("/im_livechat/get_session", (args) => {
|
||||
if (args.persisted) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await insertText(".o-mail-Composer-input", "Hello World!");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o_notification", {
|
||||
text: "No available collaborator, please try again later.",
|
||||
});
|
||||
await contains(".o-livechat-LivechatButton");
|
||||
await advanceTime(LivechatButton.DEBOUNCE_DELAY + 10);
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-ChatWindow");
|
||||
});
|
||||
|
||||
test("Fold state is saved", async () => {
|
||||
await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-Thread");
|
||||
await insertText(".o-mail-Composer-input", "Hello World!");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-Thread:not([data-transient])");
|
||||
assertChatHub({ opened: [1] });
|
||||
await click(".o-mail-ChatWindow-header");
|
||||
await contains(".o-mail-Thread", { count: 0 });
|
||||
assertChatHub({ folded: [1] });
|
||||
await click(".o-mail-ChatBubble");
|
||||
assertChatHub({ opened: [1] });
|
||||
});
|
||||
|
||||
test.tags("focus required");
|
||||
test("Seen message is saved on the server", async () => {
|
||||
const pyEnv = await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
const userId = serverState.userId;
|
||||
await start({ authenticateAs: false });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await contains(".o-mail-Thread");
|
||||
await insertText(".o-mail-Composer-input", "Hello, I need help!");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-Message", { text: "Hello, I need help!" });
|
||||
await waitUntilSubscribe();
|
||||
const initialSeenMessageId = Object.values(getService("mail.store").Thread.records).at(-1)
|
||||
.self_member_id.seen_message_id?.id;
|
||||
queryFirst(".o-mail-Composer-input").blur();
|
||||
await withUser(userId, () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: {
|
||||
body: "Hello World!",
|
||||
message_type: "comment",
|
||||
subtype_xmlid: "mail.mt_comment",
|
||||
},
|
||||
thread_id: Object.values(getService("mail.store").Thread.records).at(-1).id,
|
||||
thread_model: "discuss.channel",
|
||||
})
|
||||
);
|
||||
await contains(".o-mail-Thread-newMessage");
|
||||
await contains(".o-mail-ChatWindow-counter", { text: "1" });
|
||||
await focus(".o-mail-Composer-input");
|
||||
await contains(".o-mail-ChatWindow-counter", { count: 0 });
|
||||
const guestId = pyEnv.cookie.get("dgid");
|
||||
const [member] = pyEnv["discuss.channel.member"].search_read([
|
||||
["guest_id", "=", guestId],
|
||||
["channel_id", "=", Object.values(getService("mail.store").Thread.records).at(-1).id],
|
||||
]);
|
||||
expect(initialSeenMessageId).not.toBe(member.seen_message_id[0]);
|
||||
expect(
|
||||
Object.values(getService("mail.store").Thread.records).at(-1).self_member_id.seen_message_id
|
||||
.id
|
||||
).toBe(member.seen_message_id[0]);
|
||||
});
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import {
|
||||
defineLivechatModels,
|
||||
loadDefaultEmbedConfig,
|
||||
} from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import { expirableStorage } from "@im_livechat/core/common/expirable_storage";
|
||||
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
hover,
|
||||
setupChatHub,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
|
||||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
defineLivechatModels();
|
||||
describe.current.tags("desktop");
|
||||
|
||||
test("user custom live chat user name for message reactions", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const livechatChannelId = await loadDefaultEmbedConfig();
|
||||
pyEnv["res.partner"].write([serverState.partnerId], { user_livechat_username: "Michou" });
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ guest_id: guestId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_channel_id: livechatChannelId,
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
body: "Hello world",
|
||||
res_id: channelId,
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
reaction_ids: [
|
||||
pyEnv["mail.message.reaction"].create({
|
||||
content: "👍",
|
||||
partner_id: serverState.partnerId,
|
||||
}),
|
||||
],
|
||||
});
|
||||
expirableStorage.setItem(
|
||||
"im_livechat.saved_state",
|
||||
JSON.stringify({
|
||||
store: { "discuss.channel": [{ id: channelId }] },
|
||||
persisted: true,
|
||||
livechatUserId: serverState.publicUserId,
|
||||
})
|
||||
);
|
||||
setupChatHub({ opened: [channelId] });
|
||||
await start({
|
||||
authenticateAs: { ...pyEnv["mail.guest"].read(guestId)[0], _name: "mail.guest" },
|
||||
});
|
||||
await hover(".o-mail-MessageReaction");
|
||||
await click(".o-mail-MessageReactionList-preview", {
|
||||
text: "👍:+1: reacted by Michou",
|
||||
});
|
||||
await contains(".o-mail-MessageReactionMenu-persona", { text: "Michou" });
|
||||
});
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import {
|
||||
defineLivechatModels,
|
||||
loadDefaultEmbedConfig,
|
||||
} from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
insertText,
|
||||
onRpcBefore,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { onRpc } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("send", async () => {
|
||||
const pyEnv = await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
onRpcBefore("/im_livechat/email_livechat_transcript", () => expect.step(`send_transcript`));
|
||||
const partnerId = pyEnv["res.partner"].create({ email: "paul@example.com", name: "Paul" });
|
||||
pyEnv["res.users"].create({ partner_id: partnerId, login: "paul", password: "paul" });
|
||||
await start({ authenticateAs: { login: "paul", password: "paul" } });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await insertText(".o-mail-Composer-input", "Hello World!");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-Thread:not([data-transient])");
|
||||
await click(".o-mail-ChatWindow-header [title*='Close']");
|
||||
await click(".o-livechat-CloseConfirmation-leave");
|
||||
await contains("label", { text: "Receive a copy of this conversation" });
|
||||
await contains("input:enabled", { value: "paul@example.com" });
|
||||
await click("button[data-action='sendTranscript']:enabled");
|
||||
await contains(".form-text", { text: "The conversation was sent." });
|
||||
await expect.waitForSteps(["send_transcript"]);
|
||||
});
|
||||
|
||||
test("send failed", async () => {
|
||||
const pyEnv = await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
onRpc("/im_livechat/email_livechat_transcript", () => {
|
||||
throw new Error();
|
||||
});
|
||||
const partnerId = pyEnv["res.partner"].create({ email: "paul@example.com", name: "Paul" });
|
||||
pyEnv["res.users"].create({ partner_id: partnerId, login: "paul", password: "paul" });
|
||||
await start({ authenticateAs: { login: "paul", password: "paul" } });
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await insertText(".o-mail-Composer-input", "Hello World!");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-Thread:not([data-transient])");
|
||||
await click(".o-mail-ChatWindow-header [title*='Close']");
|
||||
await click(".o-livechat-CloseConfirmation-leave");
|
||||
await contains("input", { value: "paul@example.com" });
|
||||
await click("button[data-action='sendTranscript']:enabled");
|
||||
await contains(".form-text", { text: "An error occurred. Please try again." });
|
||||
});
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
import { waitUntilSubscribe } from "@bus/../tests/bus_test_helpers";
|
||||
import { expirableStorage } from "@im_livechat/core/common/expirable_storage";
|
||||
import {
|
||||
defineLivechatModels,
|
||||
loadDefaultEmbedConfig,
|
||||
} from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
focus,
|
||||
insertText,
|
||||
listenStoreFetch,
|
||||
setupChatHub,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey,
|
||||
waitStoreFetch,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { asyncStep, Command, onRpc, serverState, withUser } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { queryFirst } from "@odoo/hoot-dom";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("new message from operator displays unread counter", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const livechatChannelId = await loadDefaultEmbedConfig();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId }),
|
||||
Command.create({ guest_id: guestId }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_channel_id: livechatChannelId,
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
expirableStorage.setItem(
|
||||
"im_livechat.saved_state",
|
||||
JSON.stringify({
|
||||
store: { "discuss.channel": [{ id: channelId }] },
|
||||
persisted: true,
|
||||
livechatUserId: serverState.publicUserId,
|
||||
})
|
||||
);
|
||||
setupChatHub({ opened: [channelId] });
|
||||
onRpc("/discuss/channel/messages", () => asyncStep("/discuss/channel/message"));
|
||||
const userId = serverState.userId;
|
||||
listenStoreFetch(["init_messaging", "init_livechat", "discuss.channel"]);
|
||||
await start({
|
||||
authenticateAs: { ...pyEnv["mail.guest"].read(guestId)[0], _name: "mail.guest" },
|
||||
});
|
||||
await waitStoreFetch(["init_messaging", "init_livechat", "discuss.channel"], {
|
||||
stepsAfter: ["/discuss/channel/message"],
|
||||
});
|
||||
// send after init_messaging because bus subscription is done after init_messaging
|
||||
await withUser(userId, () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: { body: "Are you there?", message_type: "comment" },
|
||||
thread_id: channelId,
|
||||
thread_model: "discuss.channel",
|
||||
})
|
||||
);
|
||||
await contains(".o-mail-ChatWindow-counter", { text: "1" });
|
||||
});
|
||||
|
||||
test.tags("focus required");
|
||||
test("focus on unread livechat marks it as read", async () => {
|
||||
const pyEnv = await startServer();
|
||||
await loadDefaultEmbedConfig();
|
||||
const userId = serverState.userId;
|
||||
listenStoreFetch(["init_messaging", "init_livechat"]);
|
||||
await start({ authenticateAs: false });
|
||||
await waitStoreFetch(["init_messaging", "init_livechat"]);
|
||||
await click(".o-livechat-LivechatButton");
|
||||
await insertText(".o-mail-Composer-input", "Hello World!");
|
||||
await triggerHotkey("Enter");
|
||||
// Wait for bus subscription to be done after persisting the thread:
|
||||
// presence of the message is not enough (temporary message).
|
||||
await waitUntilSubscribe();
|
||||
await contains(".o-mail-Message-content", { text: "Hello World!" });
|
||||
const [channelId] = pyEnv["discuss.channel"].search([
|
||||
["channel_type", "=", "livechat"],
|
||||
[
|
||||
"channel_member_ids",
|
||||
"in",
|
||||
pyEnv["discuss.channel.member"].search([["guest_id", "=", pyEnv.cookie.get("dgid")]]),
|
||||
],
|
||||
]);
|
||||
await waitStoreFetch("init_messaging");
|
||||
queryFirst(".o-mail-Composer-input").blur();
|
||||
// send after init_messaging because bus subscription is done after init_messaging
|
||||
await withUser(userId, () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: { body: "Are you there?", message_type: "comment" },
|
||||
thread_id: channelId,
|
||||
thread_model: "discuss.channel",
|
||||
})
|
||||
);
|
||||
await contains(".o-mail-ChatWindow-counter", { text: "1" });
|
||||
await contains(".o-mail-Message", { text: "Are you there?" });
|
||||
await focus(".o-mail-Composer-input");
|
||||
await contains(".o-mail-ChatWindow-counter", { count: 0 });
|
||||
});
|
||||
|
|
@ -0,0 +1,422 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
focus,
|
||||
insertText,
|
||||
openDiscuss,
|
||||
patchUiSize,
|
||||
setupChatHub,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { withGuest } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { press, waitFor } from "@odoo/hoot-dom";
|
||||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { defineLivechatModels } from "./livechat_test_helpers";
|
||||
import { advanceTime, mockDate } from "@odoo/hoot-mock";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("tab on discuss composer goes to oldest unread livechat", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId_1 = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
const guestId_2 = pyEnv["mail.guest"].create({ name: "Visitor 12" });
|
||||
const guestId_3 = pyEnv["mail.guest"].create({ name: "Visitor 13" });
|
||||
const channelIds = pyEnv["discuss.channel"].create([
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId_1, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
name: "Livechat 1",
|
||||
},
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
message_unread_counter: 1,
|
||||
last_interest_dt: "2021-01-02 10:00:00",
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId_2, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
name: "Livechat 2",
|
||||
},
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
message_unread_counter: 1,
|
||||
last_interest_dt: "2021-01-01 10:00:00",
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId_3, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
name: "Livechat 3",
|
||||
},
|
||||
]);
|
||||
pyEnv["mail.message"].create([
|
||||
{
|
||||
author_guest_id: guestId_2,
|
||||
body: "Hello",
|
||||
model: "discuss.channel",
|
||||
res_id: channelIds[1],
|
||||
},
|
||||
{
|
||||
author_guest_id: guestId_3,
|
||||
body: "Hello",
|
||||
model: "discuss.channel",
|
||||
res_id: channelIds[2],
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openDiscuss(channelIds[0]);
|
||||
await contains(".o-mail-DiscussSidebarChannel.o-active", { text: "Visitor 11" });
|
||||
await focus(".o-mail-Composer-input");
|
||||
await contains(".o-mail-Composer-input[placeholder='Tab to next livechat']");
|
||||
await contains(".o-active .o-mail-DiscussSidebar-badge", { count: 0 });
|
||||
triggerHotkey("Tab");
|
||||
await contains(".o-mail-DiscussSidebarChannel.o-active", { text: "Visitor 13" });
|
||||
await focus(".o-mail-Composer-input");
|
||||
await contains(".o-active .o-mail-DiscussSidebar-badge", { count: 0 });
|
||||
triggerHotkey("Tab");
|
||||
await contains(".o-mail-DiscussSidebarChannel.o-active", { text: "Visitor 12" });
|
||||
});
|
||||
|
||||
test.tags("focus required");
|
||||
test("Tab livechat picks ended livechats last", async () => {
|
||||
mockDate("2021-01-02T10:05:00");
|
||||
const pyEnv = await startServer();
|
||||
const guestIds = pyEnv["mail.guest"].create([
|
||||
{ name: "Visitor 0" },
|
||||
{ name: "Visitor 1" },
|
||||
{ name: "Visitor 2" },
|
||||
{ name: "Visitor 3" },
|
||||
{ name: "Visitor 4" },
|
||||
]);
|
||||
const livechatChannelId = pyEnv["im_livechat.channel"].create({
|
||||
name: "Test",
|
||||
user_ids: [serverState.userId],
|
||||
});
|
||||
const channelIds = pyEnv["discuss.channel"].create(
|
||||
guestIds.map((guestId, idx) => ({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
last_interest_dt: `2021-01-02 10:00:0${idx}`,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
livechat_channel_id: livechatChannelId,
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
create_uid: serverState.publicUserId,
|
||||
}))
|
||||
);
|
||||
pyEnv["mail.message"].create(
|
||||
guestIds.map((guestId, idx) => ({
|
||||
author_guest_id: guestId,
|
||||
body: "Hello",
|
||||
model: "discuss.channel",
|
||||
res_id: channelIds[idx],
|
||||
}))
|
||||
);
|
||||
/**
|
||||
* channel id | last_interest_dt | livechat_end_dt | unread
|
||||
* -----------+---------------------+-----------------+--------
|
||||
* 0 | 2021-01-02 10:00:00 | false | true
|
||||
* 1 | 2021-01-02 10:00:01 | false | true
|
||||
* 2 | 2021-01-02 10:00:02 | false | true
|
||||
* 3 | 2021-01-02 10:00:03 | false | true
|
||||
* 4 | 2021-01-02 10:00:04 | false | true
|
||||
*/
|
||||
patchUiSize({ width: 1920 });
|
||||
setupChatHub({ folded: [channelIds[0], channelIds[1], channelIds[2], channelIds[3]] });
|
||||
await start();
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await click(".o-mail-NotificationItem", { text: "Visitor 4" });
|
||||
await contains(".o-mail-ChatWindow:contains('Visitor 4') .o-mail-Message:contains('Hello')");
|
||||
await contains(".o-mail-ChatWindow:contains('Visitor 4') .o-mail-Composer.o-focused");
|
||||
await contains(".o-mail-ChatWindow:contains('Visitor 4') .badge", { count: 0 });
|
||||
await advanceTime(5_000);
|
||||
await withGuest(guestIds[1], () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: {
|
||||
body: "livechat 1",
|
||||
message_type: "comment",
|
||||
subtype_xmlid: "mail.mt_comment",
|
||||
},
|
||||
thread_id: channelIds[1],
|
||||
thread_model: "discuss.channel",
|
||||
})
|
||||
);
|
||||
await advanceTime(5_000);
|
||||
await withGuest(guestIds[1], () =>
|
||||
rpc("/im_livechat/visitor_leave_session", { channel_id: channelIds[1] })
|
||||
);
|
||||
await advanceTime(5_000);
|
||||
await withGuest(guestIds[3], () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: {
|
||||
body: "livechat 3",
|
||||
message_type: "comment",
|
||||
subtype_xmlid: "mail.mt_comment",
|
||||
},
|
||||
thread_id: channelIds[3],
|
||||
thread_model: "discuss.channel",
|
||||
})
|
||||
);
|
||||
await waitFor(".o-mail-ChatBubble[name='Visitor 3'] .badge:contains('2')", { timeout: 3000 });
|
||||
/**
|
||||
* channel id | last_interest_dt | livechat_end_dt | unread
|
||||
* -----------+---------------------+-----------------+--------
|
||||
* 0 | 2021-01-02 10:00:00 | false | true
|
||||
* 1 | 2021-01-02 10:05:10 | now() | true
|
||||
* 2 | 2021-01-02 10:00:02 | false | true
|
||||
* 3 | 2021-01-02 10:05:15 | false | true
|
||||
* 4 | 2021-01-02 10:00:04 | false | false
|
||||
*/
|
||||
await press("Tab");
|
||||
await contains(".o-mail-ChatWindow", { count: 2 });
|
||||
await contains(".o-mail-ChatWindow:contains('Visitor 0') .o-mail-Message:contains('Hello')");
|
||||
await contains(".o-mail-ChatWindow:contains('Visitor 0') .o-mail-Composer.o-focused");
|
||||
await contains(".o-mail-ChatWindow:contains('Visitor 0') .badge", { count: 0 });
|
||||
await press("Tab");
|
||||
await contains(".o-mail-ChatWindow", { count: 3 });
|
||||
await contains(".o-mail-ChatWindow:contains('Visitor 2') .o-mail-Message:contains('Hello')");
|
||||
await contains(".o-mail-ChatWindow:contains('Visitor 2') .o-mail-Composer.o-focused");
|
||||
await contains(".o-mail-ChatWindow:contains('Visitor 2') .badge", { count: 0 });
|
||||
await advanceTime(5_000);
|
||||
await withGuest(guestIds[0], () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: {
|
||||
body: "livechat 0",
|
||||
message_type: "comment",
|
||||
subtype_xmlid: "mail.mt_comment",
|
||||
},
|
||||
thread_id: channelIds[0],
|
||||
thread_model: "discuss.channel",
|
||||
})
|
||||
);
|
||||
await waitFor(".o-mail-ChatWindow:contains('Visitor 0') .badge:contains('1')", {
|
||||
timeout: 3000,
|
||||
});
|
||||
/**
|
||||
* channel id | last_interest_dt | livechat_end_dt | unread
|
||||
* -----------+---------------------+-----------------+--------
|
||||
* 0 | 2021-01-02 10:05:20 | false | true
|
||||
* 1 | 2021-01-02 10:05:10 | now() | true
|
||||
* 2 | 2021-01-02 10:00:02 | false | false
|
||||
* 3 | 2021-01-02 10:05:15 | false | true
|
||||
* 4 | 2021-01-02 10:00:04 | false | false
|
||||
*/
|
||||
await press("Tab");
|
||||
await contains(".o-mail-ChatWindow:contains('Visitor 3') .o-mail-Message:contains('Hello')");
|
||||
await contains(".o-mail-ChatWindow:contains('Visitor 3') .o-mail-Composer.o-focused");
|
||||
await contains(".o-mail-ChatWindow:contains('Visitor 3') .badge", { count: 0 });
|
||||
await press("Tab");
|
||||
await contains(".o-mail-ChatWindow:contains('Visitor 0') .o-mail-Composer.o-focused");
|
||||
await contains(".o-mail-ChatWindow:contains('Visitor 0') .badge", { count: 0 });
|
||||
await press("Tab");
|
||||
await contains(".o-mail-ChatWindow:contains('Visitor 1') .o-mail-Message:contains('Hello')");
|
||||
await contains("span", { text: "This livechat conversation has ended" });
|
||||
});
|
||||
|
||||
test.tags("focus required");
|
||||
test("switching to folded chat window unfolds it", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId_1 = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
const guestId_2 = pyEnv["mail.guest"].create({ name: "Visitor 12" });
|
||||
const channelIds = pyEnv["discuss.channel"].create([
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId_1, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
name: "Livechat 1",
|
||||
},
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
last_interest_dt: "2021-01-02 10:00:00",
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId_2, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
name: "Livechat 2",
|
||||
},
|
||||
]);
|
||||
pyEnv["mail.message"].create({
|
||||
author_guest_id: guestId_2,
|
||||
body: "Hello",
|
||||
model: "discuss.channel",
|
||||
res_id: channelIds[1],
|
||||
});
|
||||
setupChatHub({ opened: [channelIds[0]], folded: [channelIds[1]] });
|
||||
await start();
|
||||
await contains(".o-mail-ChatBubble[name='Visitor 12']");
|
||||
await focus(".o-mail-Composer-input", {
|
||||
parent: [".o-mail-ChatWindow", { text: "Visitor 11" }],
|
||||
});
|
||||
triggerHotkey("Tab");
|
||||
await contains(".o-mail-ChatWindow", {
|
||||
text: "Visitor 12",
|
||||
contains: [".o-mail-Composer-input:focus"],
|
||||
});
|
||||
});
|
||||
|
||||
test.tags("focus required");
|
||||
test("switching to hidden chat window unhides it", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [guestId_1, guestId_2] = pyEnv["mail.guest"].create([
|
||||
{ name: "Visitor 11" },
|
||||
{ name: "Visitor 12" },
|
||||
]);
|
||||
const channelIds = pyEnv["discuss.channel"].create([
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId_1, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
name: "Livechat 1",
|
||||
},
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
last_interest_dt: "2021-01-02 10:00:00",
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId_2, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
name: "Livechat 2",
|
||||
},
|
||||
{ name: "general" },
|
||||
]);
|
||||
const [livechat_1] = channelIds;
|
||||
pyEnv["mail.message"].create({
|
||||
author_guest_id: guestId_2,
|
||||
body: "Hello",
|
||||
model: "discuss.channel",
|
||||
res_id: livechat_1,
|
||||
});
|
||||
setupChatHub({ opened: channelIds.reverse() });
|
||||
patchUiSize({ width: 900 }); // enough for 2 chat windows max
|
||||
await start();
|
||||
// FIXME: expected order: general, 12, 11
|
||||
await contains(".o-mail-ChatWindow", { count: 2 });
|
||||
await contains(".o-mail-ChatWindow", { count: 0, text: "Visitor 11" });
|
||||
await focus(".o-mail-Composer-input", {
|
||||
parent: [".o-mail-ChatWindow", { text: "Visitor 12" }],
|
||||
});
|
||||
triggerHotkey("Tab");
|
||||
await contains(".o-mail-ChatWindow", {
|
||||
text: "Visitor 11",
|
||||
contains: [".o-mail-Composer-input:focus"],
|
||||
});
|
||||
});
|
||||
|
||||
test("tab on composer doesn't switch thread if user is typing", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId_1 = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
const guestId_2 = pyEnv["mail.guest"].create({ name: "Visitor 12" });
|
||||
const channelIds = pyEnv["discuss.channel"].create([
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId_1, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
name: "Livechat 1",
|
||||
},
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
message_unread_counter: 1,
|
||||
last_interest_dt: "2021-01-02 10:00:00",
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId_2, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
name: "Livechat 2",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openDiscuss(channelIds[0]);
|
||||
await insertText(".o-mail-Composer-input", "Hello, ");
|
||||
triggerHotkey("Tab");
|
||||
await contains(".o-mail-DiscussSidebarChannel.o-active", { text: "Visitor 11" });
|
||||
});
|
||||
|
||||
test("tab on composer doesn't switch thread if no unread thread", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId_1 = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
const guestId_2 = pyEnv["mail.guest"].create({ name: "Visitor 12" });
|
||||
const channelIds = pyEnv["discuss.channel"].create([
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId_1, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
name: "Livechat 1",
|
||||
},
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId_2, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
name: "Livechat 2",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openDiscuss(channelIds[0]);
|
||||
await focus(".o-mail-Composer-input");
|
||||
triggerHotkey("Tab");
|
||||
await contains(".o-mail-DiscussSidebarChannel.o-active", { text: "Visitor 11" });
|
||||
});
|
||||
|
|
@ -1,198 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import '@mail/../tests/helpers/mock_server'; // ensure mail overrides are applied first
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { MockServer } from "@web/../tests/helpers/mock_server";
|
||||
|
||||
patch(MockServer.prototype, 'im_livechat', {
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async _performRPC(route, args) {
|
||||
if (route === '/im_livechat/get_session') {
|
||||
const channel_id = args.channel_id;
|
||||
const anonymous_name = args.anonymous_name;
|
||||
const previous_operator_id = args.previous_operator_id;
|
||||
const context = args.context;
|
||||
return this._mockRouteImLivechatGetSession(channel_id, anonymous_name, previous_operator_id, context);
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private Mocked Routes
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Simulates the `/im_livechat/get_session` route.
|
||||
*
|
||||
* @private
|
||||
* @param {integer} channel_id
|
||||
* @param {string} anonymous_name
|
||||
* @param {integer} [previous_operator_id]
|
||||
* @param {Object} [context={}]
|
||||
* @returns {Object}
|
||||
*/
|
||||
_mockRouteImLivechatGetSession(channel_id, anonymous_name, previous_operator_id, context = {}) {
|
||||
let user_id;
|
||||
let country_id;
|
||||
if ('mockedUserId' in context) {
|
||||
// can be falsy to simulate not being logged in
|
||||
user_id = context.mockedUserId;
|
||||
} else {
|
||||
user_id = this.currentUserId;
|
||||
}
|
||||
// don't use the anonymous name if the user is logged in
|
||||
if (user_id) {
|
||||
const user = this.getRecords('res.users', [['id', '=', user_id]])[0];
|
||||
country_id = user.country_id;
|
||||
} else {
|
||||
// simulate geoip
|
||||
const countryCode = context.mockedCountryCode;
|
||||
const country = this.getRecords('res.country', [['code', '=', countryCode]])[0];
|
||||
if (country) {
|
||||
country_id = country.id;
|
||||
anonymous_name = anonymous_name + ' (' + country.name + ')';
|
||||
}
|
||||
}
|
||||
return this._mockImLivechatChannel_openLivechatMailChannel(channel_id, anonymous_name, previous_operator_id, user_id, country_id);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private Mocked Methods
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_mockMailChannelChannelInfo(ids) {
|
||||
const channelInfos = this._super(...arguments);
|
||||
for (const channelInfo of channelInfos) {
|
||||
const channel = this.getRecords('mail.channel', [['id', '=', channelInfo.id]])[0];
|
||||
channelInfo['channel']['anonymous_name'] = channel.anonymous_name;
|
||||
// add the last message date
|
||||
if (channel.channel_type === 'livechat') {
|
||||
// add the operator id
|
||||
if (channel.livechat_operator_id) {
|
||||
const operator = this.getRecords('res.partner', [['id', '=', channel.livechat_operator_id]])[0];
|
||||
// livechat_username ignored for simplicity
|
||||
channelInfo.operator_pid = [operator.id, operator.display_name.replace(',', '')];
|
||||
}
|
||||
}
|
||||
}
|
||||
return channelInfos;
|
||||
},
|
||||
/**
|
||||
* Simulates `_get_available_users` on `im_livechat.channel`.
|
||||
*
|
||||
* @private
|
||||
* @param {integer} id
|
||||
* @returns {Object}
|
||||
*/
|
||||
_mockImLivechatChannel_getAvailableUsers(id) {
|
||||
const livechatChannel = this.getRecords('im_livechat.channel', [['id', '=', id]])[0];
|
||||
const users = this.getRecords('res.users', [['id', 'in', livechatChannel.user_ids]]);
|
||||
return users.filter(user => user.im_status === 'online');
|
||||
},
|
||||
/**
|
||||
* Simulates `_get_livechat_mail_channel_vals` on `im_livechat.channel`.
|
||||
*
|
||||
* @private
|
||||
* @param {integer} id
|
||||
* @returns {Object}
|
||||
*/
|
||||
_mockImLivechatChannel_getLivechatMailChannelVals(id, anonymous_name, operator, user_id, country_id) {
|
||||
// partner to add to the mail.channel
|
||||
const operator_partner_id = operator.partner_id;
|
||||
const membersToAdd = [[0, 0, {
|
||||
is_pinned: false,
|
||||
partner_id: operator_partner_id,
|
||||
}]];
|
||||
let visitor_user;
|
||||
if (user_id) {
|
||||
const visitor_user = this.getRecords('res.users', [['id', '=', user_id]])[0];
|
||||
if (visitor_user && visitor_user.active && visitor_user !== operator) {
|
||||
// valid session user (not public)
|
||||
membersToAdd.push([0, 0, { partner_id: visitor_user.partner_id.id }]);
|
||||
}
|
||||
} else {
|
||||
membersToAdd.push([0, 0, { partner_id: this.publicPartnerId }]);
|
||||
}
|
||||
const membersName = [
|
||||
visitor_user ? visitor_user.display_name : anonymous_name,
|
||||
operator.livechat_username ? operator.livechat_username : operator.name,
|
||||
];
|
||||
return {
|
||||
'channel_member_ids': membersToAdd,
|
||||
'livechat_active': true,
|
||||
'livechat_operator_id': operator_partner_id,
|
||||
'livechat_channel_id': id,
|
||||
'anonymous_name': user_id ? false : anonymous_name,
|
||||
'country_id': country_id,
|
||||
'channel_type': 'livechat',
|
||||
'name': membersName.join(' '),
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Simulates `_get_random_operator` on `im_livechat.channel`.
|
||||
* Simplified mock implementation: returns the first available operator.
|
||||
*
|
||||
* @private
|
||||
* @param {integer} id
|
||||
* @returns {Object}
|
||||
*/
|
||||
_mockImLivechatChannel_getRandomOperator(id) {
|
||||
const availableUsers = this._mockImLivechatChannel_getAvailableUsers(id);
|
||||
return availableUsers[0];
|
||||
},
|
||||
/**
|
||||
* Simulates `_open_livechat_mail_channel` on `im_livechat.channel`.
|
||||
*
|
||||
* @private
|
||||
* @param {integer} id
|
||||
* @param {string} anonymous_name
|
||||
* @param {integer} [previous_operator_id]
|
||||
* @param {integer} [user_id]
|
||||
* @param {integer} [country_id]
|
||||
* @returns {Object}
|
||||
*/
|
||||
_mockImLivechatChannel_openLivechatMailChannel(id, anonymous_name, previous_operator_id, user_id, country_id) {
|
||||
let operator;
|
||||
if (previous_operator_id) {
|
||||
const availableUsers = this._mockImLivechatChannel_getAvailableUsers(id);
|
||||
operator = availableUsers.find(user => user.partner_id === previous_operator_id);
|
||||
}
|
||||
if (!operator) {
|
||||
operator = this._mockImLivechatChannel_getRandomOperator(id);
|
||||
}
|
||||
if (!operator) {
|
||||
// no one available
|
||||
return false;
|
||||
}
|
||||
// create the session, and add the link with the given channel
|
||||
const mailChannelVals = this._mockImLivechatChannel_getLivechatMailChannelVals(id, anonymous_name, operator, user_id, country_id);
|
||||
const mailChannelId = this.pyEnv['mail.channel'].create(mailChannelVals);
|
||||
this._mockMailChannel_broadcast([mailChannelId], [operator.partner_id]);
|
||||
return this._mockMailChannelChannelInfo([mailChannelId])[0];
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_mockResPartner_GetChannelsAsMember(ids) {
|
||||
const partner = this.getRecords('res.partner', [['id', 'in', ids]])[0];
|
||||
const members = this.getRecords('mail.channel.member', [['partner_id', '=', partner.id], ['is_pinned', '=', true]]);
|
||||
const livechats = this.getRecords('mail.channel', [
|
||||
['channel_type', '=', 'livechat'],
|
||||
['channel_member_ids', 'in', members.map(member => member.id)],
|
||||
]);
|
||||
return [
|
||||
...this._super(ids),
|
||||
...livechats,
|
||||
];
|
||||
},
|
||||
});
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import '@mail/../tests/helpers/mock_server'; // ensure mail overrides are applied first
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { MockServer } from "@web/../tests/helpers/mock_server";
|
||||
|
||||
patch(MockServer.prototype, 'im_livechat/controllers/main', {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async _performRPC(route, args) {
|
||||
if (route === '/im_livechat/notify_typing') {
|
||||
const uuid = args.uuid;
|
||||
const is_typing = args.is_typing;
|
||||
const context = args.context;
|
||||
return this._mockRouteImLivechatNotifyTyping(uuid, is_typing, context);
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
* Simulates the `/im_livechat/notify_typing` route.
|
||||
*
|
||||
* @private
|
||||
* @param {string} uuid
|
||||
* @param {boolean} is_typing
|
||||
* @param {Object} [context={}]
|
||||
*/
|
||||
_mockRouteImLivechatNotifyTyping(uuid, is_typing, context = {}) {
|
||||
const [mailChannel] = this.getRecords('mail.channel', [['uuid', '=', uuid]]);
|
||||
const partnerId = context.mockedPartnerId || this.currentPartnerId;
|
||||
const [memberOfCurrentUser] = this.getRecords('mail.channel.member', [['channel_id', '=', mailChannel.id], ['partner_id', '=', partnerId]]);
|
||||
this._mockMailChannelMember_NotifyTyping([memberOfCurrentUser.id], is_typing);
|
||||
},
|
||||
});
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import '@mail/../tests/helpers/mock_server/models/mail_channel_member'; // ensure mail overrides are applied first
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { MockServer } from "@web/../tests/helpers/mock_server";
|
||||
|
||||
patch(MockServer.prototype, 'im_livechat/models/mail_channel_member', {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_mockMailChannelMember_GetPartnerData(ids) {
|
||||
const [member] = this.getRecords('mail.channel.member', [['id', 'in', ids]]);
|
||||
const [channel] = this.getRecords('mail.channel', [['id', '=', member.channel_id]]);
|
||||
const [partner] = this.getRecords('res.partner', [['id', '=', member.partner_id]], { active_test: false });
|
||||
if (channel.channel_type === 'livechat') {
|
||||
const data = {
|
||||
'id': partner.id,
|
||||
'is_public': partner.is_public,
|
||||
};
|
||||
if (partner.user_livechat_username) {
|
||||
data['user_livechat_username'] = partner.user_livechat_username;
|
||||
} else {
|
||||
data['name'] = partner.name;
|
||||
}
|
||||
if (!partner.is_public) {
|
||||
const [country] = this.getRecords('res.country', [['id', '=', partner.country_id]]);
|
||||
data['country'] = country
|
||||
? {
|
||||
'code': country.code,
|
||||
'id': country.id,
|
||||
'name': country.name,
|
||||
}
|
||||
: [['clear']];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
return this._super(ids);
|
||||
},
|
||||
});
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { addModelNamesToFetch, insertModelFields } from '@bus/../tests/helpers/model_definitions_helpers';
|
||||
|
||||
addModelNamesToFetch(['im_livechat.channel']);
|
||||
insertModelFields('res.users.settings', {
|
||||
is_discuss_sidebar_category_livechat_open: { default: true },
|
||||
});
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { expect } from "@odoo/hoot";
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
setupChatHub,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { Command, onRpc, serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export async function livechatLastAgentLeaveFromChatWindow() {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write([serverState.userId], {
|
||||
group_ids: pyEnv["res.groups"]
|
||||
.search_read([["id", "=", serverState.groupLivechatId]])
|
||||
.map(({ id }) => id),
|
||||
});
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor" });
|
||||
const livechatChannelId = pyEnv["im_livechat.channel"].create({
|
||||
name: "HR",
|
||||
user_ids: [serverState.userId],
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
livechat_channel_id: livechatChannelId,
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
create_uid: serverState.publicUserId,
|
||||
});
|
||||
setupChatHub({ opened: [channelId] });
|
||||
onRpc("discuss.channel", "action_unfollow", () => {
|
||||
expect.step("action_unfollow");
|
||||
});
|
||||
await start();
|
||||
await contains(".o-mail-ChatWindow");
|
||||
await click("button[title*='Close Chat Window']");
|
||||
await click("button:contains('Yes, leave conversation')");
|
||||
await expect.waitForSteps(["action_unfollow"]);
|
||||
await contains(".o-mail-ChatWindow", { count: 0 });
|
||||
}
|
||||
|
|
@ -0,0 +1,352 @@
|
|||
import { defineLivechatModels } from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
insertText,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, press, test, waitFor } from "@odoo/hoot";
|
||||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { serializeDate, today } from "@web/core/l10n/dates";
|
||||
import { getOrigin } from "@web/core/utils/urls";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("livechat note is loaded when opening the channel info list", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "James" });
|
||||
pyEnv["res.partner"].create({
|
||||
name: "James",
|
||||
user_ids: [userId],
|
||||
});
|
||||
const countryId = pyEnv["res.country"].create({ code: "be", name: "Belgium" });
|
||||
const guestId = pyEnv["mail.guest"].create({
|
||||
name: "Visitor #20",
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
country_id: countryId,
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
livechat_note: "<p>Initial note<br/>Second line</p>",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-livechat-ChannelInfoList textarea", { value: "Initial note\nSecond line" });
|
||||
});
|
||||
|
||||
test("shows country and language in channel info list", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const countryId = pyEnv["res.country"].create({ code: "BE", name: "Belgium" });
|
||||
const langId = pyEnv["res.lang"].create({ name: "English" });
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor #20" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
country_id: countryId,
|
||||
channel_type: "livechat",
|
||||
livechat_lang_id: langId,
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains("h6", { text: "Country & Language" });
|
||||
await contains("span[title='Language']", { text: "English" });
|
||||
const [country] = pyEnv["res.country"].search_read([["id", "=", countryId]]);
|
||||
await contains(`.o_country_flag[data-src*='/country_flags/${country.code.toLowerCase()}.png']`);
|
||||
});
|
||||
|
||||
test("editing livechat note is synced between tabs", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write([serverState.userId], {
|
||||
group_ids: [serverState.groupLivechatId],
|
||||
});
|
||||
const userId = pyEnv["res.users"].create({ name: "James" });
|
||||
pyEnv["res.partner"].create({
|
||||
name: "James",
|
||||
user_ids: [userId],
|
||||
});
|
||||
const countryId = pyEnv["res.country"].create({ code: "be", name: "Belgium" });
|
||||
const guestId = pyEnv["mail.guest"].create({
|
||||
name: "Visitor #20",
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
country_id: countryId,
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
livechat_note: "<p>Initial note</p>",
|
||||
});
|
||||
const tab1 = await start({ asTab: true });
|
||||
const tab2 = await start({ asTab: true });
|
||||
await openDiscuss(channelId, { target: tab1 });
|
||||
await openDiscuss(channelId, { target: tab2 });
|
||||
await contains(`${tab1.selector} .o-livechat-ChannelInfoList textarea`, {
|
||||
value: "Initial note",
|
||||
});
|
||||
await contains(`${tab2.selector} .o-livechat-ChannelInfoList textarea`, {
|
||||
value: "Initial note",
|
||||
});
|
||||
await insertText(`${tab1.selector} .o-livechat-ChannelInfoList textarea`, "Updated note", {
|
||||
replace: true,
|
||||
});
|
||||
document.querySelector(`${tab1.selector} .o-livechat-ChannelInfoList textarea`).blur(); // Trigger the blur event to save the note
|
||||
await contains(`${tab2.selector} .o-livechat-ChannelInfoList textarea`, {
|
||||
value: "Updated note",
|
||||
}); // Note should be synced with bus
|
||||
});
|
||||
|
||||
test("shows live chat status in discuss sidebar", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "James" });
|
||||
pyEnv["res.partner"].create({
|
||||
name: "James",
|
||||
user_ids: [userId],
|
||||
});
|
||||
const countryId = pyEnv["res.country"].create({ code: "be", name: "Belgium" });
|
||||
const guestId = pyEnv["mail.guest"].create({
|
||||
name: "Visitor #20",
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
country_id: countryId,
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
livechat_status: "waiting",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-livechat-ChannelInfoList button.active", { text: "Waiting for customer" });
|
||||
await contains(".o-mail-DiscussSidebar-item span[title='Waiting for customer']");
|
||||
await click(".o-livechat-ChannelInfoList button", { text: "Looking for help" });
|
||||
await contains(".o-livechat-ChannelInfoList button.active", { text: "Looking for help" });
|
||||
await contains(".o-mail-DiscussSidebar-item span[title='Looking for help']");
|
||||
// live chat status icon also in messaging menu item
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await contains(
|
||||
".o-mail-MessagingMenu .o-mail-NotificationItem:contains('Visitor #20') [title='Looking for help']"
|
||||
);
|
||||
});
|
||||
|
||||
test("editing livechat status is synced between tabs", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "James" });
|
||||
pyEnv["res.partner"].create({
|
||||
name: "James",
|
||||
user_ids: [userId],
|
||||
});
|
||||
const countryId = pyEnv["res.country"].create({ code: "be", name: "Belgium" });
|
||||
const guestId = pyEnv["mail.guest"].create({
|
||||
name: "Visitor #20",
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
country_id: countryId,
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
livechat_status: "in_progress",
|
||||
});
|
||||
const tab1 = await start({ asTab: true });
|
||||
const tab2 = await start({ asTab: true });
|
||||
await openDiscuss(channelId, { target: tab1 });
|
||||
await openDiscuss(channelId, { target: tab2 });
|
||||
await contains(`${tab1.selector} .o-livechat-ChannelInfoList button.active`, {
|
||||
text: "In progress",
|
||||
});
|
||||
await contains(`${tab2.selector} .o-livechat-ChannelInfoList button.active`, {
|
||||
text: "In progress",
|
||||
});
|
||||
await click(`${tab1.selector} .o-livechat-ChannelInfoList button`, {
|
||||
text: "Waiting for customer",
|
||||
});
|
||||
await contains(`${tab1.selector} .o-livechat-ChannelInfoList button.active`, {
|
||||
text: "Waiting for customer",
|
||||
});
|
||||
await contains(`${tab2.selector} .o-livechat-ChannelInfoList button.active`, {
|
||||
text: "Waiting for customer",
|
||||
}); // Status should be synced with bus
|
||||
});
|
||||
|
||||
test("Manage expertises from channel info list", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write([serverState.userId], {
|
||||
group_ids: [serverState.groupLivechatManagerId, serverState.groupLivechatId],
|
||||
});
|
||||
const userId = pyEnv["res.users"].create({ name: "James" });
|
||||
pyEnv["res.partner"].create({ name: "James", user_ids: [userId] });
|
||||
const countryId = pyEnv["res.country"].create({ code: "be", name: "Belgium" });
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor #20" });
|
||||
const expertiseIds = pyEnv["im_livechat.expertise"].create([{ name: "pricing" }]);
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
country_id: countryId,
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
livechat_expertise_ids: expertiseIds,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-livechat-ChannelInfoList .o_tag", { text: "pricing" });
|
||||
await insertText(".o-livechat-ExpertiseTagsAutocomplete input", "events");
|
||||
await click("a", { text: 'Create "events"' });
|
||||
await contains(".o-livechat-ChannelInfoList .o_tag", { text: "events" });
|
||||
await click(".o-livechat-ExpertiseTagsAutocomplete input");
|
||||
await press("Backspace");
|
||||
await contains(".o-livechat-ChannelInfoList .o_tag", { text: "events", count: 0 });
|
||||
await press("Backspace");
|
||||
await contains(".o-livechat-ChannelInfoList .o_tag", { text: "pricing", count: 0 });
|
||||
await contains(".o-livechat-ExpertiseTagsAutocomplete input[placeholder='Add expertise']");
|
||||
await click("a", { text: "events" });
|
||||
await contains(".o-livechat-ChannelInfoList .o_tag", { text: "events" });
|
||||
});
|
||||
|
||||
test("Can download transcript from channel info panel", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor #20" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_end_dt: serializeDate(today().plus({ days: -1 })),
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(
|
||||
`a[href='${getOrigin()}/im_livechat/download_transcript/${channelId}']:text(Download)`
|
||||
);
|
||||
});
|
||||
|
||||
test("Disable actions for non-livechat users", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor #20" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_status: "in_progress",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await waitFor(".o-livechat-LivechatStatusSelection button:text(In progress):disabled");
|
||||
await waitFor(".o-livechat-LivechatStatusSelection button:text(Waiting for customer):disabled");
|
||||
await waitFor(".o-livechat-LivechatStatusSelection button:text(Looking for help):disabled");
|
||||
await waitFor("textarea[placeholder='Add your notes here...']:disabled");
|
||||
await waitFor(".o-livechat-ExpertiseTagsAutocomplete.o-disabled");
|
||||
});
|
||||
|
||||
test("info panel toggle state persists across chats", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [guestId1, guestId2] = pyEnv["mail.guest"].create([
|
||||
{ name: "Visitor 1" },
|
||||
{ name: "Visitor 2" },
|
||||
]);
|
||||
pyEnv["discuss.channel"].create([
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId1, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
},
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId2, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await click(".o-mail-DiscussSidebarChannel:text('Visitor 1')");
|
||||
await contains(".o-livechat-ChannelInfoList");
|
||||
await click("button[name='livechat-info']");
|
||||
await contains(".o-livechat-ChannelInfoList", { count: 0 });
|
||||
await click(".o-mail-DiscussSidebarChannel:text('Visitor 2')");
|
||||
await contains(".o-mail-DiscussContent-threadName[title='Visitor 2']");
|
||||
await contains(".o-livechat-ChannelInfoList", { count: 0 });
|
||||
await click("button[name='livechat-info']");
|
||||
await contains(".o-livechat-ChannelInfoList");
|
||||
await click(".o-mail-DiscussSidebarChannel:text('Visitor 1')");
|
||||
await contains(".o-mail-DiscussContent-threadName[title='Visitor 1']");
|
||||
await contains(".o-livechat-ChannelInfoList");
|
||||
});
|
||||
|
||||
test("auto-open of livechat info & members panels should combine", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor" });
|
||||
pyEnv["discuss.channel"].create([
|
||||
{
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
},
|
||||
{
|
||||
channel_type: "channel",
|
||||
name: "General",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await click(".o-mail-DiscussSidebarChannel:text('General')");
|
||||
await contains(".o-discuss-ChannelMemberList");
|
||||
await click(".o-mail-DiscussSidebarChannel:text('Visitor')");
|
||||
await contains(".o-discuss-ChannelMemberList", { count: 0 });
|
||||
await contains(".o-livechat-ChannelInfoList");
|
||||
await click("button[name='livechat-info']");
|
||||
await contains(".o-livechat-ChannelInfoList", { count: 0 });
|
||||
await contains(".o-discuss-ChannelMemberList", { count: 0 });
|
||||
await click(".o-mail-DiscussSidebarChannel:text('General')");
|
||||
await contains(".o-discuss-ChannelMemberList");
|
||||
await contains(".o-livechat-ChannelInfoList", { count: 0 });
|
||||
await click("button[name='member-list']");
|
||||
await contains(".o-discuss-ChannelMemberList", { count: 0 });
|
||||
await contains(".o-livechat-ChannelInfoList", { count: 0 });
|
||||
await click(".o-mail-DiscussSidebarChannel:text('Visitor')");
|
||||
await click("button[name='livechat-info']");
|
||||
await contains(".o-livechat-ChannelInfoList");
|
||||
await contains(".o-discuss-ChannelMemberList", { count: 0 });
|
||||
await click("button[name='member-list']");
|
||||
await contains(".o-discuss-ChannelMemberList");
|
||||
await contains(".o-livechat-ChannelInfoList", { count: 0 });
|
||||
await click(".o-mail-DiscussSidebarChannel:text('General')");
|
||||
await contains(".o-discuss-ChannelMemberList");
|
||||
await contains(".o-livechat-ChannelInfoList", { count: 0 });
|
||||
});
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
import { IrWebSocket } from "@im_livechat/../tests/mock_server/mock_models/ir_websocket";
|
||||
|
||||
import { mailModels, startServer } from "@mail/../tests/mail_test_helpers";
|
||||
import { RatingRating } from "@rating/../tests/mock_server/models/rating_rating";
|
||||
import {
|
||||
defineModels,
|
||||
serverState,
|
||||
patchWithCleanup,
|
||||
MockServer,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
import { DiscussChannel } from "./mock_server/mock_models/discuss_channel";
|
||||
import { DiscussChannelMember } from "./mock_server/mock_models/discuss_channel_member";
|
||||
import { LivechatChannel } from "./mock_server/mock_models/im_livechat_channel";
|
||||
import { LivechatChannelRule } from "./mock_server/mock_models/livechat_channel_rule";
|
||||
import { Im_LivechatExpertise } from "./mock_server/mock_models/im_livechat_expertise";
|
||||
import { ResGroupsPrivilege } from "./mock_server/mock_models/res_groups_privilege";
|
||||
import { ResGroups } from "./mock_server/mock_models/res_groups";
|
||||
import { ResPartner } from "./mock_server/mock_models/res_partner";
|
||||
import { ResUsers } from "./mock_server/mock_models/res_users";
|
||||
import { session } from "@web/session";
|
||||
|
||||
export function defineLivechatModels() {
|
||||
return defineModels(livechatModels);
|
||||
}
|
||||
|
||||
export const livechatModels = {
|
||||
...mailModels,
|
||||
DiscussChannel,
|
||||
DiscussChannelMember,
|
||||
LivechatChannel,
|
||||
LivechatChannelRule,
|
||||
Im_LivechatExpertise,
|
||||
IrWebSocket,
|
||||
RatingRating,
|
||||
ResPartner,
|
||||
ResUsers,
|
||||
ResGroupsPrivilege,
|
||||
ResGroups,
|
||||
};
|
||||
|
||||
serverState.groupLivechatId = 42;
|
||||
serverState.groupLivechatManagerId = 43;
|
||||
|
||||
/**
|
||||
* Setup the server side of the livechat app.
|
||||
*
|
||||
* @returns {Promise<number>} the id of the livechat channel.
|
||||
*/
|
||||
export async function loadDefaultEmbedConfig() {
|
||||
const pyEnv = MockServer.env ?? (await startServer());
|
||||
const livechatChannelId = pyEnv["im_livechat.channel"].create({
|
||||
user_ids: [serverState.userId],
|
||||
});
|
||||
patchWithCleanup(session, {
|
||||
livechatData: {
|
||||
can_load_livechat: true,
|
||||
serverUrl: window.origin,
|
||||
options: {
|
||||
header_background_color: "#875A7B",
|
||||
button_background_color: "#875A7B",
|
||||
title_color: "#FFFFFF",
|
||||
button_text_color: "#FFFFFF",
|
||||
button_text: "Need help? Chat with us.",
|
||||
default_message: "Hello, how may I help you?",
|
||||
channel_name: "YourWebsite.com",
|
||||
channel_id: livechatChannelId,
|
||||
default_username: "Visitor",
|
||||
review_link: "https://www.odoo.com",
|
||||
},
|
||||
},
|
||||
});
|
||||
return livechatChannelId;
|
||||
}
|
||||
|
|
@ -0,0 +1,314 @@
|
|||
import { waitForChannels } from "@bus/../tests/bus_test_helpers";
|
||||
|
||||
import { defineLivechatModels } from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import { LFH_UNSUBSCRIBE_DELAY } from "@im_livechat/core/public_web/discuss_app_model_patch";
|
||||
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
openDiscuss,
|
||||
openFormView,
|
||||
setupChatHub,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
|
||||
import { advanceTime, describe, expect, test } from "@odoo/hoot";
|
||||
import { tick, waitFor } from "@odoo/hoot-dom";
|
||||
|
||||
import {
|
||||
Command,
|
||||
getService,
|
||||
onRpc,
|
||||
patchWithCleanup,
|
||||
serverState,
|
||||
withUser,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { Deferred } from "@web/core/utils/concurrency";
|
||||
|
||||
defineLivechatModels();
|
||||
describe.current.tags("desktop");
|
||||
|
||||
test("Show looking for help in the sidebar while active or still seeking help", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write([serverState.userId], {
|
||||
group_ids: pyEnv["res.groups"]
|
||||
.search_read([["id", "=", serverState.groupLivechatId]])
|
||||
.map(({ id }) => id),
|
||||
});
|
||||
const bobPartnerId = pyEnv["res.partner"].create({
|
||||
name: "bob",
|
||||
user_ids: [Command.create({ name: "bob" })],
|
||||
});
|
||||
const bobChannelId = pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [Command.create({ partner_id: bobPartnerId })],
|
||||
livechat_status: "need_help",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechatNeedHelp .oi-chevron-down");
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "bob" });
|
||||
await waitForChannels(["im_livechat.looking_for_help"]);
|
||||
await rpc("/im_livechat/session/update_status", {
|
||||
channel_id: bobChannelId,
|
||||
livechat_status: "in_progress",
|
||||
});
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "bob", count: 0 });
|
||||
await rpc("/im_livechat/session/update_status", {
|
||||
channel_id: bobChannelId,
|
||||
livechat_status: "need_help",
|
||||
});
|
||||
await click(".o-mail-DiscussSidebarChannel", { text: "bob" });
|
||||
await contains(".o-mail-DiscussSidebarChannel.o-active", { text: "bob" });
|
||||
await waitForChannels([`discuss.channel_${bobChannelId}`]);
|
||||
await rpc("/im_livechat/session/update_status", {
|
||||
channel_id: bobChannelId,
|
||||
livechat_status: "in_progress",
|
||||
});
|
||||
await contains(".o-livechat-LivechatStatusSelection .o-inProgress.active");
|
||||
await waitForChannels([`discuss.channel_${bobChannelId}`]);
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "bob" });
|
||||
await click(".o-mail-Mailbox[data-mailbox-id=starred");
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "bob", count: 0 });
|
||||
await waitForChannels([`discuss.channel_${bobChannelId}`], { operation: "delete" });
|
||||
});
|
||||
|
||||
test("Do not auto-open chat window on new message when locally pinned", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write([serverState.userId], {
|
||||
group_ids: pyEnv["res.groups"]
|
||||
.search_read([["id", "=", serverState.groupLivechatId]])
|
||||
.map(({ id }) => id),
|
||||
});
|
||||
setupChatHub({
|
||||
folded: [
|
||||
pyEnv["discuss.channel"].create({
|
||||
name: "General",
|
||||
channel_type: "channel",
|
||||
}),
|
||||
],
|
||||
opened: [
|
||||
pyEnv["discuss.channel"].create({
|
||||
name: "Support",
|
||||
channel_type: "channel",
|
||||
}),
|
||||
],
|
||||
});
|
||||
const bobPartnerId = pyEnv["res.partner"].create({
|
||||
name: "bob",
|
||||
user_ids: [Command.create({ name: "bob" })],
|
||||
});
|
||||
const bobChannelId = pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [Command.create({ partner_id: bobPartnerId })],
|
||||
livechat_status: "need_help",
|
||||
});
|
||||
await start();
|
||||
getService("bus_service").subscribe("discuss.channel/new_message", () =>
|
||||
expect.step("discuss.channel/new_message")
|
||||
);
|
||||
await openDiscuss();
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechatNeedHelp .oi-chevron-down");
|
||||
await click(".o-mail-DiscussSidebarChannel", { text: "bob" });
|
||||
await waitForChannels([`discuss.channel_${bobChannelId}`]);
|
||||
await withUser(serverState.userId, async () => {
|
||||
await rpc("/mail/message/post", {
|
||||
post_data: {
|
||||
body: "Hello, how can I help?",
|
||||
message_type: "comment",
|
||||
subtype_xmlid: "mail.mt_comment",
|
||||
},
|
||||
thread_id: bobChannelId,
|
||||
thread_model: "discuss.channel",
|
||||
});
|
||||
});
|
||||
await contains(".o-mail-Message", { text: "Hello, how can I help?" });
|
||||
await expect.waitForSteps(["discuss.channel/new_message"]);
|
||||
await openFormView("res.partner", bobPartnerId);
|
||||
await contains(".o-mail-ChatBubble");
|
||||
await contains(".o-mail-ChatBubble[name=General]");
|
||||
await contains(".o-mail-ChatBubble", { count: 0, text: "bob" });
|
||||
await contains(".o-mail-ChatWindow", { text: "Support" });
|
||||
await contains(".o-mail-ChatWindow", { count: 0, text: "bob" });
|
||||
});
|
||||
|
||||
test("Enable/disable looking for help when category is opened/folded", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write([serverState.userId], {
|
||||
group_ids: pyEnv["res.groups"]
|
||||
.search_read([["id", "=", serverState.groupLivechatId]])
|
||||
.map(({ id }) => id),
|
||||
});
|
||||
localStorage.setItem("discuss_sidebar_category_im_livechat.category_need_help_open", false);
|
||||
await start();
|
||||
patchWithCleanup(getService("bus_service"), {
|
||||
addChannel: (channelName) => {
|
||||
if (channelName === "im_livechat.looking_for_help") {
|
||||
expect.step(`addChannel - ${channelName}`);
|
||||
}
|
||||
},
|
||||
deleteChannel: (channelName) => {
|
||||
if (channelName === "im_livechat.looking_for_help") {
|
||||
expect.step(`deleteChannel - ${channelName}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
onRpc("/mail/data", async (req) => {
|
||||
const { params } = await req.json();
|
||||
if (params.fetch_params.includes("/im_livechat/looking_for_help")) {
|
||||
expect.step("fetch looking_for_help");
|
||||
}
|
||||
});
|
||||
await openDiscuss();
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechatNeedHelp .oi-chevron-right");
|
||||
await expect.waitForSteps([]);
|
||||
await click(".o-mail-DiscussSidebarCategory-livechatNeedHelp button");
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechatNeedHelp .oi-chevron-down");
|
||||
await expect.waitForSteps([
|
||||
"addChannel - im_livechat.looking_for_help",
|
||||
"fetch looking_for_help",
|
||||
]);
|
||||
await click(".o-mail-DiscussSidebarCategory-livechatNeedHelp button");
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechatNeedHelp .oi-chevron-right");
|
||||
await expect.waitForSteps([]);
|
||||
await advanceTime(LFH_UNSUBSCRIBE_DELAY + 1000);
|
||||
await expect.waitForSteps(["deleteChannel - im_livechat.looking_for_help"]);
|
||||
await click(".o-mail-DiscussSidebarCategory-livechatNeedHelp button");
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechatNeedHelp .oi-chevron-down");
|
||||
await expect.waitForSteps([
|
||||
"addChannel - im_livechat.looking_for_help",
|
||||
"fetch looking_for_help",
|
||||
]);
|
||||
});
|
||||
|
||||
test("Show join button when help is required and self is not a member", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write([serverState.userId], {
|
||||
group_ids: pyEnv["res.groups"]
|
||||
.search_read([["id", "=", serverState.groupLivechatId]])
|
||||
.map(({ id }) => id),
|
||||
});
|
||||
const bobPartnerId = pyEnv["res.partner"].create({
|
||||
name: "bob",
|
||||
user_ids: [Command.create({ name: "bob" })],
|
||||
});
|
||||
const channel = pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [Command.create({ partner_id: bobPartnerId })],
|
||||
livechat_status: "need_help",
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channel);
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechatNeedHelp .oi-chevron-down");
|
||||
await contains(".o-livechat-LivechatStatusSelection .active", { text: "Looking for help" });
|
||||
await click("button[name='join-livechat-needing-help']");
|
||||
await contains(".o-livechat-LivechatStatusSelection .active", { text: "In progress" });
|
||||
await contains("button[name='join-livechat-needing-help']", { count: 0 });
|
||||
await click(".o-livechat-LivechatStatusSelection button", { text: "Looking for help" });
|
||||
await contains(".o-livechat-LivechatStatusSelection .active", { text: "Looking for help" });
|
||||
// Now that we are members, the button is not shown, even if help is required.
|
||||
await contains("button[name='join-livechat-needing-help']", { count: 0 });
|
||||
});
|
||||
|
||||
test("Show notification when joining a channel that already received help", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const bobPartnerId = pyEnv["res.partner"].create({
|
||||
name: "bob",
|
||||
user_ids: [Command.create({ name: "bob" })],
|
||||
});
|
||||
// Simulate another agent attempting to join the channel to provide help at the same time,
|
||||
// but succeeding just before the current agent (server returns false when it happens).
|
||||
onRpc("discuss.channel", "livechat_join_channel_needing_help", () => false);
|
||||
const channel = pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [Command.create({ partner_id: bobPartnerId })],
|
||||
livechat_status: "need_help",
|
||||
});
|
||||
const env = await start();
|
||||
patchWithCleanup(env.services.notification, {
|
||||
add: (message, options) => expect.step(`${options.type} - ${message}`),
|
||||
});
|
||||
await openDiscuss(channel);
|
||||
await contains(".o-livechat-LivechatStatusSelection .active", { text: "Looking for help" });
|
||||
await click("button[name='join-livechat-needing-help']");
|
||||
expect.waitForSteps(["warning - Someone has already joined this conversation"]);
|
||||
});
|
||||
|
||||
test("Hide 'help already received' notification when channel is not visible", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write(serverState.userId, { notification_type: "inbox" });
|
||||
const bobPartnerId = pyEnv["res.partner"].create({
|
||||
name: "bob",
|
||||
user_ids: [Command.create({ name: "bob" })],
|
||||
});
|
||||
// Simulate another agent attempting to join the channel to provide help at the same time,
|
||||
// but succeeding just before the current agent (server returns false when it happens).
|
||||
let canRespondDeferred;
|
||||
onRpc("discuss.channel", "livechat_join_channel_needing_help", async () => {
|
||||
await canRespondDeferred;
|
||||
return false;
|
||||
});
|
||||
const channel = pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [Command.create({ partner_id: bobPartnerId })],
|
||||
livechat_status: "need_help",
|
||||
});
|
||||
const env = await start();
|
||||
patchWithCleanup(env.services.notification, {
|
||||
add: (message, options) => expect.step(`${options.type} - ${message}`),
|
||||
});
|
||||
await openDiscuss(channel);
|
||||
await contains(".o-livechat-LivechatStatusSelection .active", { text: "Looking for help" });
|
||||
await click("button[name='join-livechat-needing-help']");
|
||||
expect.waitForSteps(["warning - Someone has already joined this conversation"]);
|
||||
canRespondDeferred = new Deferred();
|
||||
await click("button[name='join-livechat-needing-help']");
|
||||
await click(".o-mail-DiscussSidebar-item", { text: "Inbox" });
|
||||
await contains(".o-mail-DiscussContent-threadName[title='Inbox']");
|
||||
canRespondDeferred.resolve();
|
||||
await tick();
|
||||
await expect.waitForSteps([]);
|
||||
});
|
||||
|
||||
test("Expertise matching hint is shown in the sidebar when chat is looking for help", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write([serverState.userId], {
|
||||
group_ids: pyEnv["res.groups"]
|
||||
.search_read([["id", "=", serverState.groupLivechatId]])
|
||||
.map(({ id }) => id),
|
||||
});
|
||||
const bobPartnerId = pyEnv["res.partner"].create({
|
||||
name: "bob",
|
||||
user_ids: [Command.create({ name: "bob" })],
|
||||
});
|
||||
const janePartnerId = pyEnv["res.partner"].create({
|
||||
name: "jane",
|
||||
user_ids: [Command.create({ name: "jane" })],
|
||||
});
|
||||
const expertiseIds = pyEnv["im_livechat.expertise"].create([{ name: "pricing" }]);
|
||||
pyEnv["res.users"].write([serverState.userId], { livechat_expertise_ids: expertiseIds });
|
||||
pyEnv["discuss.channel"].create([
|
||||
{
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [Command.create({ partner_id: bobPartnerId })],
|
||||
livechat_status: "need_help",
|
||||
livechat_expertise_ids: expertiseIds,
|
||||
},
|
||||
{
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [Command.create({ partner_id: janePartnerId })],
|
||||
livechat_status: "need_help",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await waitFor(
|
||||
".o-mail-DiscussSidebarChannel:text(bob):has([title='Relevant to your expertise'])"
|
||||
);
|
||||
await waitFor(".o-mail-DiscussSidebarChannel:text(jane)");
|
||||
await waitFor(
|
||||
".o-mail-DiscussSidebarChannel:text(jane):not(:has([title='Relevant to your expertise']))"
|
||||
);
|
||||
});
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { click, contains, patchUiSize, start, startServer } from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { defineLivechatModels } from "./livechat_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test('livechats should be in "chat" filter', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await contains(".o-mail-MessagingMenu button.fw-bold", { text: "Notifications" });
|
||||
await contains(".o-mail-NotificationItem", { text: "Visitor 11" });
|
||||
await click(".o-mail-MessagingMenu button", { text: "Chats" });
|
||||
await contains(".o-mail-MessagingMenu button.fw-bold", { text: "Chats" });
|
||||
await contains(".o-mail-NotificationItem", { text: "Visitor 11" });
|
||||
});
|
||||
|
||||
test('livechats should be in "livechat" tab in mobile', async () => {
|
||||
patchUiSize({ height: 360, width: 640 });
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await click("button", { text: "Live Chats" });
|
||||
await contains(".o-mail-NotificationItem", { text: "Visitor 11" });
|
||||
await click("button", { text: "Chats" });
|
||||
await contains(".o-mail-NotificationItem", { count: 0, text: "Visitor 11" });
|
||||
});
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import {
|
||||
contains,
|
||||
listenStoreFetch,
|
||||
start,
|
||||
startServer,
|
||||
waitStoreFetch,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { withGuest } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { Command, patchWithCleanup, serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { defineLivechatModels } from "./livechat_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("push notifications are Odoo toaster on Android", async () => {
|
||||
// Notifications without ServiceWorker in Chrome Android no longer work.
|
||||
// This simulates Android Notification behavior by throwing a
|
||||
// ServiceWorkerRegistration error as a fallback.
|
||||
patchWithCleanup(window, {
|
||||
Notification: class Notification {
|
||||
static get permission() {
|
||||
return "granted";
|
||||
}
|
||||
constructor() {
|
||||
throw new Error("ServiceWorkerRegistration error");
|
||||
}
|
||||
},
|
||||
});
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
name: "Livechat 1",
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
});
|
||||
listenStoreFetch("init_messaging");
|
||||
await start();
|
||||
await waitStoreFetch("init_messaging");
|
||||
// send after init_messaging because bus subscription is done after init_messaging
|
||||
await withGuest(guestId, () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: {
|
||||
body: "Hello world!",
|
||||
message_type: "comment",
|
||||
subtype_xmlid: "mail.mt_comment",
|
||||
},
|
||||
thread_model: "discuss.channel",
|
||||
thread_id: channelId,
|
||||
})
|
||||
);
|
||||
await contains(".o_notification:has(.o_notification_bar.bg-info)", {
|
||||
text: "Visitor. Hello world!",
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import { describe, test } from "@odoo/hoot";
|
||||
import {
|
||||
SIZES,
|
||||
click,
|
||||
contains,
|
||||
patchUiSize,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { defineLivechatModels } from "./livechat_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("Livechat button is not present when there is no livechat thread", async () => {
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
await start();
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await contains(".o-mail-MessagingMenu");
|
||||
await contains(".o-mail-MessagingMenu-navbar span", { count: 0, text: "Livechat" });
|
||||
});
|
||||
|
||||
test("Livechat button is present when there is at least one livechat thread", async () => {
|
||||
patchUiSize({ size: SIZES.SM });
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({
|
||||
partner_id: serverState.publicPartnerId,
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await click(".o_menu_systray i[aria-label='Messages']");
|
||||
await contains(".o-mail-MessagingMenu");
|
||||
await contains(".o-mail-MessagingMenu-navbar", { text: "Live Chats" });
|
||||
});
|
||||
|
|
@ -0,0 +1,265 @@
|
|||
import {
|
||||
mailDataHelpers,
|
||||
parseRequestParams,
|
||||
registerRoute,
|
||||
} from "@mail/../tests/mock_server/mail_mock_server";
|
||||
import { Command, makeKwArgs, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { loadBundle } from "@web/core/assets";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
|
||||
/**
|
||||
* @template [T={}]
|
||||
* @typedef {import("@web/../tests/web_test_helpers").RouteCallback<T>} RouteCallback
|
||||
*/
|
||||
|
||||
registerRoute("/im_livechat/get_session", get_session);
|
||||
/** @type {RouteCallback} */
|
||||
async function get_session(request) {
|
||||
/** @type {import("mock_models").DiscussChannel} */
|
||||
const DiscussChannel = this.env["discuss.channel"];
|
||||
/** @type {import("mock_models").LivechatChannel} */
|
||||
const LivechatChannel = this.env["im_livechat.channel"];
|
||||
/** @type {import("mock_models").ResCountry} */
|
||||
const ResCountry = this.env["res.country"];
|
||||
/** @type {import("mock_models").ResPartner} */
|
||||
const ResPartner = this.env["res.partner"];
|
||||
/** @type {import("mock_models").ResUsers} */
|
||||
const ResUsers = this.env["res.users"];
|
||||
|
||||
let {
|
||||
channel_id,
|
||||
previous_operator_id,
|
||||
persisted,
|
||||
context = {},
|
||||
} = await parseRequestParams(request);
|
||||
previous_operator_id = parseInt(previous_operator_id);
|
||||
const agent = LivechatChannel._get_operator(channel_id, previous_operator_id);
|
||||
if (!agent) {
|
||||
return false;
|
||||
}
|
||||
let country_id;
|
||||
if (this.env.user && !ResUsers._is_public(this.env.uid)) {
|
||||
country_id = this.env.user.country_id;
|
||||
} else if (context.mockedCountryCode) {
|
||||
// simulate geoip
|
||||
const country = ResCountry._filter([["code", "=", context.mockedCountryCode]])[0];
|
||||
if (country) {
|
||||
country_id = country.id;
|
||||
}
|
||||
}
|
||||
if (!persisted) {
|
||||
const store = new mailDataHelpers.Store();
|
||||
ResUsers._init_store_data(store);
|
||||
store.add("discuss.channel", {
|
||||
channel_type: "livechat",
|
||||
fetchChannelInfoState: "fetched",
|
||||
id: -1,
|
||||
isLoaded: true,
|
||||
livechat_operator_id: mailDataHelpers.Store.one(
|
||||
ResPartner.browse(agent.partner_id),
|
||||
makeKwArgs({ fields: ["avatar_128", "user_livechat_username"] })
|
||||
),
|
||||
scrollUnread: false,
|
||||
});
|
||||
return { store_data: store.get_result(), channel_id: -1 };
|
||||
}
|
||||
const channelVals = LivechatChannel._get_livechat_discuss_channel_vals(channel_id, {
|
||||
agent: agent,
|
||||
});
|
||||
channelVals.country_id = country_id;
|
||||
const channelId = DiscussChannel.create(channelVals);
|
||||
const store = new mailDataHelpers.Store();
|
||||
ResUsers._init_store_data(store);
|
||||
store.add(DiscussChannel.browse(channelId));
|
||||
store.add(DiscussChannel.browse(channelId), {
|
||||
isLoaded: true,
|
||||
scrollUnread: false,
|
||||
});
|
||||
return { store_data: store.get_result(), channel_id: channelId };
|
||||
}
|
||||
|
||||
registerRoute("/im_livechat/visitor_leave_session", visitor_leave_session);
|
||||
/** @type {RouteCallback} */
|
||||
async function visitor_leave_session(request) {
|
||||
/** @type {import("mock_models").DiscussChannel} */
|
||||
const DiscussChannel = this.env["discuss.channel"];
|
||||
|
||||
const { channel_id } = await parseRequestParams(request);
|
||||
const [channel] = DiscussChannel.search_read([["id", "=", channel_id]]);
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
DiscussChannel._close_livechat_session(channel_id);
|
||||
}
|
||||
registerRoute("/im_livechat/feedback", feedback);
|
||||
/** @type {RouteCallback} */
|
||||
async function feedback(request) {
|
||||
/** @type {import("mock_models").DiscussChannel} */
|
||||
const DiscussChannel = this.env["discuss.channel"];
|
||||
/** @type {import("mock_models").RatingRating} */
|
||||
const RatingRating = this.env["rating.rating"];
|
||||
|
||||
const { channel_id, rate, reason } = await parseRequestParams(request);
|
||||
let [channel] = DiscussChannel.search_read([["id", "=", channel_id]]);
|
||||
if (!channel) {
|
||||
return false;
|
||||
}
|
||||
const values = {
|
||||
rating: rate,
|
||||
consumed: true,
|
||||
feedback: reason,
|
||||
is_internal: false,
|
||||
res_id: channel.id,
|
||||
res_model: "discuss.channel",
|
||||
rated_partner_id: channel.channel_partner_ids[0],
|
||||
};
|
||||
if (channel.rating_ids.length === 0) {
|
||||
RatingRating.create(values);
|
||||
} else {
|
||||
RatingRating.write([channel.rating_ids[0]], values);
|
||||
}
|
||||
[channel] = DiscussChannel.search_read([["id", "=", channel_id]]);
|
||||
return channel.rating_ids[0];
|
||||
}
|
||||
|
||||
registerRoute("/im_livechat/init", livechat_init);
|
||||
/** @type {RouteCallback} */
|
||||
async function livechat_init(request) {
|
||||
return {
|
||||
available_for_me: true,
|
||||
rule: {},
|
||||
};
|
||||
}
|
||||
|
||||
registerRoute("/im_livechat/email_livechat_transcript", email_livechat_transcript);
|
||||
/** @type {RouteCallback} */
|
||||
async function email_livechat_transcript(request) {
|
||||
const DiscussChannel = this.env["discuss.channel"];
|
||||
const { channel_id, email } = await parseRequestParams(request);
|
||||
const [channel] = DiscussChannel.search_read([["id", "=", channel_id]]);
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
DiscussChannel._email_livechat_transcript(channel_id, email);
|
||||
}
|
||||
|
||||
registerRoute("/im_livechat/emoji_bundle", get_emoji_bundle);
|
||||
/** @type {RouteCallback} */
|
||||
async function get_emoji_bundle(request) {
|
||||
await loadBundle("web.assets_emoji");
|
||||
return new Response();
|
||||
}
|
||||
|
||||
registerRoute("/im_livechat/session/update_status", session_update_status);
|
||||
/** @type {RouteCallback} */
|
||||
async function session_update_status(request) {
|
||||
/** @type {import("mock_models").DiscussChannel} */
|
||||
const DiscussChannel = this.env["discuss.channel"];
|
||||
const { channel_id, livechat_status } = await parseRequestParams(request);
|
||||
if (this.env.user.share) {
|
||||
return false;
|
||||
}
|
||||
const [channel] = DiscussChannel.search_read([["id", "=", channel_id]]);
|
||||
if (!channel) {
|
||||
return false;
|
||||
}
|
||||
DiscussChannel.write([channel_id], {
|
||||
livechat_status: livechat_status,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
registerRoute("/im_livechat/session/update_note", session_update_note);
|
||||
/** @type {RouteCallback} */
|
||||
async function session_update_note(request) {
|
||||
/** @type {import("mock_models").DiscussChannel} */
|
||||
const DiscussChannel = this.env["discuss.channel"];
|
||||
const { channel_id, note } = await parseRequestParams(request);
|
||||
if (this.env.user.share) {
|
||||
return false;
|
||||
}
|
||||
const [channel] = DiscussChannel.search_read([["id", "=", channel_id]]);
|
||||
if (!channel) {
|
||||
return false;
|
||||
}
|
||||
DiscussChannel.write([channel_id], {
|
||||
livechat_note: note,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
registerRoute("/im_livechat/conversation/write_expertises", livechat_conversation_write_expertises);
|
||||
/** @type {RouteCallback} */
|
||||
async function livechat_conversation_write_expertises(request) {
|
||||
/** @type {import("mock_models").DiscussChannel} */
|
||||
const DiscussChannel = this.env["discuss.channel"];
|
||||
const { channel_id, orm_commands } = await parseRequestParams(request);
|
||||
const [channel] = DiscussChannel.search_read([["id", "=", channel_id]]);
|
||||
if (!channel) {
|
||||
return false;
|
||||
}
|
||||
DiscussChannel.write(channel_id, { livechat_expertise_ids: orm_commands });
|
||||
}
|
||||
|
||||
registerRoute(
|
||||
"/im_livechat/conversation/create_and_link_expertise",
|
||||
livechat_conversation_create_and_link_expertise
|
||||
);
|
||||
/** @type {RouteCallback} */
|
||||
async function livechat_conversation_create_and_link_expertise(request) {
|
||||
/** @type {import("mock_models").DiscussChannel} */
|
||||
const DiscussChannel = this.env["discuss.channel"];
|
||||
/** @type {import("mock_models").ImLivechatExpertise} */
|
||||
const ImLivechatExpertise = this.env["im_livechat.expertise"];
|
||||
const { channel_id, expertise_name } = await parseRequestParams(request);
|
||||
const [channel] = DiscussChannel.search([["id", "=", channel_id]]);
|
||||
if (!channel) {
|
||||
return false;
|
||||
}
|
||||
const [expertise] = ImLivechatExpertise.search([["name", "=", expertise_name]]);
|
||||
let expertiseId = expertise?.id;
|
||||
if (!expertise) {
|
||||
expertiseId = ImLivechatExpertise.create({ name: expertise_name });
|
||||
}
|
||||
DiscussChannel.write(channel_id, { livechat_expertise_ids: [Command.link(expertiseId)] });
|
||||
}
|
||||
|
||||
patch(mailDataHelpers, {
|
||||
_process_request_for_all(store, name, params) {
|
||||
const ResPartner = this.env["res.partner"];
|
||||
const ResUsers = this.env["res.users"];
|
||||
super._process_request_for_all(...arguments);
|
||||
store.add({ livechat_available: true });
|
||||
if (name === "init_livechat") {
|
||||
if (this.env.user && !ResUsers._is_public(this.env.uid)) {
|
||||
store.add(
|
||||
ResPartner.browse(this.env.user.partner_id),
|
||||
makeKwArgs({ fields: ["email"] })
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
_process_request_for_internal_user(store, name, params) {
|
||||
super._process_request_for_internal_user(...arguments);
|
||||
if (name === "im_livechat.channel") {
|
||||
const LivechatChannel = this.env["im_livechat.channel"];
|
||||
store.add(
|
||||
LivechatChannel.browse(LivechatChannel.search([])),
|
||||
makeKwArgs({ fields: ["are_you_inside", "name"] })
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (name === "/im_livechat/looking_for_help") {
|
||||
const DiscussChannel = this.env["discuss.channel"];
|
||||
store.add(
|
||||
DiscussChannel.browse(
|
||||
DiscussChannel.search([["livechat_status", "=", "need_help"]])
|
||||
)
|
||||
);
|
||||
}
|
||||
if (name === "/im_livechat/fetch_self_expertise") {
|
||||
const ResUsers = this.env["res.users"];
|
||||
store.add(ResUsers.browse(serverState.userId), ["livechat_expertise_ids"]);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
declare module "mock_models" {
|
||||
import { LivechatChannel as LivechatChannel2 } from "../im_livechat_channel";
|
||||
import { RatingRating as RatingRating2 } from "../rating_rating";
|
||||
|
||||
export interface LivechatChannel extends LivechatChannel2 {}
|
||||
export interface RatingRating extends RatingRating2 {}
|
||||
|
||||
export interface Models {
|
||||
"im_livechat.channel": LivechatChannel,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
import { mailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { mailDataHelpers } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
|
||||
import { fields, getKwArgs, makeKwArgs, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { serializeDate } from "@web/core/l10n/dates";
|
||||
import { ensureArray } from "@web/core/utils/arrays";
|
||||
|
||||
export class DiscussChannel extends mailModels.DiscussChannel {
|
||||
livechat_channel_id = fields.Many2one({ relation: "im_livechat.channel", string: "Channel" }); // FIXME: somehow not fetched properly
|
||||
livechat_lang_id = fields.Many2one({ relation: "res.lang", string: "Language" });
|
||||
livechat_note = fields.Html({ sanitize: true });
|
||||
livechat_status = fields.Selection({
|
||||
selection: [
|
||||
("in_progress", "In progress"),
|
||||
("waiting", "Waiting for customer"),
|
||||
("need_help", "Looking for help"),
|
||||
],
|
||||
});
|
||||
livechat_expertise_ids = fields.Many2many({
|
||||
relation: "im_livechat.expertise",
|
||||
});
|
||||
|
||||
action_unfollow(idOrIds) {
|
||||
/** @type {import("mock_models").BusBus} */
|
||||
const BusBus = this.env["bus.bus"];
|
||||
|
||||
const ids = ensureArray(idOrIds);
|
||||
for (const channel_id of ids) {
|
||||
const [channel] = this.browse(channel_id);
|
||||
if (channel.channel_type == "livechat" && channel.channel_member_ids.length <= 2) {
|
||||
this.write([channel.id], { livechat_end_dt: serializeDate(luxon.DateTime.now()) });
|
||||
BusBus._sendone(
|
||||
channel,
|
||||
"mail.record/insert",
|
||||
new mailDataHelpers.Store()
|
||||
.add(this.browse(channel_id), makeKwArgs({ fields: ["livechat_end_dt"] }))
|
||||
.get_result()
|
||||
);
|
||||
}
|
||||
}
|
||||
return super.action_unfollow(...arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @param {number[]} ids
|
||||
* @param {number[]} partner_ids
|
||||
* @param {boolean} [invite_to_rtc_call=undefined]
|
||||
*/
|
||||
add_members(ids, partner_ids, invite_to_rtc_call) {
|
||||
const kwargs = getKwArgs(arguments, "ids", "partner_ids", "invite_to_rtc_call");
|
||||
ids = kwargs.ids;
|
||||
delete kwargs.ids;
|
||||
partner_ids = kwargs.partner_ids || [];
|
||||
const channels = this.browse(
|
||||
Array.from(super.add_members(ids, partner_ids, invite_to_rtc_call)).map(
|
||||
({ channel_id }) => channel_id
|
||||
)
|
||||
);
|
||||
for (const channel of channels) {
|
||||
if (channel.livechat_status == "need_help") {
|
||||
this.write([channel.id], { livechat_status: "in_progress" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_channel_basic_info_fields() {
|
||||
return [
|
||||
...super._channel_basic_info_fields(),
|
||||
"livechat_lang_id",
|
||||
"livechat_note",
|
||||
"livechat_status",
|
||||
"livechat_expertise_ids",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @type {typeof mailModels.DiscussChannel["prototype"]["_to_store"]}
|
||||
*/
|
||||
_to_store(store) {
|
||||
/** @type {import("mock_models").ResCountry} */
|
||||
const ResCountry = this.env["res.country"];
|
||||
/** @type {import("mock_models").ResLang} */
|
||||
const ResLang = this.env["res.lang"];
|
||||
/** @type {import("mock_models").ResPartner} */
|
||||
const ResPartner = this.env["res.partner"];
|
||||
|
||||
super._to_store(...arguments);
|
||||
for (const channel of this) {
|
||||
const channelInfo = {};
|
||||
const [country] = ResCountry.browse(channel.country_id);
|
||||
channelInfo["country_id"] = country
|
||||
? {
|
||||
code: country.code,
|
||||
id: country.id,
|
||||
name: country.name,
|
||||
}
|
||||
: false;
|
||||
// add the last message date
|
||||
if (channel.channel_type === "livechat") {
|
||||
// add the operator id
|
||||
if (channel.livechat_operator_id) {
|
||||
// livechat_username ignored for simplicity
|
||||
channelInfo.livechat_operator_id = mailDataHelpers.Store.one(
|
||||
ResPartner.browse(channel.livechat_operator_id),
|
||||
makeKwArgs({ fields: ["avatar_128", "user_livechat_username"] })
|
||||
);
|
||||
} else {
|
||||
channelInfo.livechat_operator_id = false;
|
||||
}
|
||||
channelInfo.livechat_lang_id = channel.livechat_lang_id
|
||||
? mailDataHelpers.Store.one(
|
||||
ResLang.browse(channel.livechat_lang_id),
|
||||
makeKwArgs({ fields: ["name"] })
|
||||
)
|
||||
: false;
|
||||
channelInfo["livechat_end_dt"] = channel.livechat_end_dt;
|
||||
channelInfo["livechat_note"] = ["markup", channel.livechat_note];
|
||||
channelInfo["livechat_status"] = channel.livechat_status;
|
||||
channelInfo["livechat_expertise_ids"] = mailDataHelpers.Store.many(
|
||||
this.env["im_livechat.expertise"].browse(channel.livechat_expertise_ids),
|
||||
makeKwArgs({ fields: ["name"] })
|
||||
);
|
||||
channelInfo.livechat_channel_id = mailDataHelpers.Store.one(
|
||||
this.env["im_livechat.channel"].browse(channel.livechat_channel_id),
|
||||
makeKwArgs({ fields: ["name"] })
|
||||
);
|
||||
}
|
||||
store._add_record_fields(this.browse(channel.id), channelInfo);
|
||||
}
|
||||
}
|
||||
_close_livechat_session(channel_id) {
|
||||
/** @type {import("mock_models").BusBus} */
|
||||
const BusBus = this.env["bus.bus"];
|
||||
|
||||
if (this.browse(channel_id)[0].livechat_end_dt) {
|
||||
return;
|
||||
}
|
||||
this.write([channel_id], { livechat_end_dt: serializeDate(luxon.DateTime.now()) });
|
||||
const [channel] = this.browse(channel_id);
|
||||
BusBus._sendone(
|
||||
channel,
|
||||
"mail.record/insert",
|
||||
new mailDataHelpers.Store()
|
||||
.add(this.browse(channel_id), makeKwArgs({ fields: ["livechat_end_dt"] }))
|
||||
.get_result()
|
||||
);
|
||||
if (channel.message_ids.length === 0) {
|
||||
return;
|
||||
}
|
||||
this.message_post(
|
||||
channel.id,
|
||||
makeKwArgs({
|
||||
body: this._get_visitor_leave_message(),
|
||||
message_type: "comment",
|
||||
subtype_xmlid: "mail.mt_comment",
|
||||
})
|
||||
);
|
||||
}
|
||||
_get_visitor_leave_message() {
|
||||
return "Visitor left the conversation.";
|
||||
}
|
||||
|
||||
_email_livechat_transcript(channel_id, email) {
|
||||
const [channel] = this.browse(channel_id);
|
||||
this.message_post(
|
||||
channel.id,
|
||||
makeKwArgs({
|
||||
body: `<div class="o_mail_notification o_hide_author">${this.env.user.name} sent the conversation to ${email}</div>`,
|
||||
message_type: "notification",
|
||||
subtype_xmlid: "mail.mt_comment",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @type {typeof mailModels.DiscussChannel["prototype"]["_types_allowing_seen_infos"]}
|
||||
*/
|
||||
_types_allowing_seen_infos() {
|
||||
return super._types_allowing_seen_infos(...arguments).concat(["livechat"]);
|
||||
}
|
||||
|
||||
livechat_join_channel_needing_help(idOrIds) {
|
||||
const channel = this.browse(idOrIds)[0];
|
||||
if (channel.livechat_status !== "need_help") {
|
||||
return false;
|
||||
}
|
||||
this.add_members([channel.id], [this.env.user.partner_id]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @type {typeof models.Model["prototype"]["write"]} */
|
||||
write(idOrIds, values) {
|
||||
const kwargs = getKwArgs(arguments, "ids", "vals");
|
||||
({ ids: idOrIds, vals: values } = kwargs);
|
||||
const needHelpBefore = [];
|
||||
for (const channel of this._filter([["livechat_status", "=", "need_help"]])) {
|
||||
needHelpBefore.push(channel.id);
|
||||
}
|
||||
const result = super.write(...arguments);
|
||||
const needHelpAfter = [];
|
||||
for (const channel of this._filter([["livechat_status", "=", "need_help"]])) {
|
||||
needHelpAfter.push(channel.id);
|
||||
}
|
||||
const updatedChannelIds = [
|
||||
...needHelpBefore.filter((id) => !needHelpAfter.includes(id)),
|
||||
...needHelpAfter.filter((id) => !needHelpBefore.includes(id)),
|
||||
];
|
||||
if (updatedChannelIds.length) {
|
||||
this.env["bus.bus"]._sendone(
|
||||
[this.env["res.groups"].browse(serverState.groupLivechatId), "LOOKING_FOR_HELP"],
|
||||
"mail.record/insert",
|
||||
new mailDataHelpers.Store().add(this.browse(updatedChannelIds)).get_result()
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import { mailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { fields } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class DiscussChannelMember extends mailModels.DiscussChannelMember {
|
||||
livechat_member_type = fields.Selection({
|
||||
selection: [
|
||||
["agent", "Agent"],
|
||||
["visitor", "Visitor"],
|
||||
["bot", "Chatbot"],
|
||||
],
|
||||
compute: false,
|
||||
});
|
||||
/**
|
||||
* @override
|
||||
* @type {typeof mailModels.DiscussChannelMember["prototype"]["_get_store_partner_fields"]}
|
||||
*/
|
||||
_get_store_partner_fields(fields) {
|
||||
/** @type {import("mock_models").DiscussChannel} */
|
||||
const DiscussChannel = this.env["discuss.channel"];
|
||||
|
||||
const member = this[0];
|
||||
const [channel] = DiscussChannel.browse(member.channel_id);
|
||||
if (channel.channel_type === "livechat") {
|
||||
if (!fields) {
|
||||
fields = [
|
||||
"active",
|
||||
"avatar_128",
|
||||
"country_id",
|
||||
"im_status",
|
||||
"is_public",
|
||||
"user_livechat_username",
|
||||
];
|
||||
if (member.livechat_member_type == "visitor") {
|
||||
fields.push("offline_since", "email");
|
||||
}
|
||||
}
|
||||
}
|
||||
return super._get_store_partner_fields(fields);
|
||||
}
|
||||
/**
|
||||
* @override
|
||||
* @type {typeof mailModels.DiscussChannelMember["prototype"]["_to_store"]}
|
||||
*/
|
||||
_to_store(store, fields, extra_fields) {
|
||||
super._to_store(...arguments);
|
||||
for (const member of this) {
|
||||
store._add_record_fields(this.browse(member.id), {
|
||||
livechat_member_type: member.livechat_member_type,
|
||||
});
|
||||
}
|
||||
}
|
||||
get _to_store_defaults() {
|
||||
return super._to_store_defaults.concat(["livechat_member_type"]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
import { mailDataHelpers } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
|
||||
import { Command, fields, getKwArgs, makeKwArgs, models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class LivechatChannel extends models.ServerModel {
|
||||
_name = "im_livechat.channel";
|
||||
|
||||
available_operator_ids = fields.Many2many({ relation: "res.users" }); // FIXME: somehow not fetched properly
|
||||
user_ids = fields.Many2many({ relation: "res.users" }); // FIXME: somehow not fetched properly
|
||||
|
||||
/** @param {integer} id */
|
||||
action_join(id) {
|
||||
this.write([id], { user_ids: [Command.link(this.env.user.id)] });
|
||||
const [partner] = this.env["res.partner"].read(this.env.user.partner_id);
|
||||
this.env["bus.bus"]._sendone(
|
||||
partner,
|
||||
"mail.record/insert",
|
||||
new mailDataHelpers.Store(
|
||||
this.browse(id),
|
||||
makeKwArgs({ fields: ["are_you_inside", "name"] })
|
||||
).get_result()
|
||||
);
|
||||
}
|
||||
|
||||
/** @param {integer} id */
|
||||
action_quit(id) {
|
||||
this.write(id, { user_ids: [Command.unlink(this.env.user.id)] });
|
||||
const [partner] = this.env["res.partner"].read(this.env.user.partner_id);
|
||||
this.env["bus.bus"]._sendone(
|
||||
partner,
|
||||
"mail.record/insert",
|
||||
new mailDataHelpers.Store(
|
||||
this.browse(id),
|
||||
makeKwArgs({ fields: ["are_you_inside", "name"] })
|
||||
).get_result()
|
||||
);
|
||||
}
|
||||
|
||||
/** @param {integer} id */
|
||||
_compute_available_operator_ids(id) {
|
||||
/** @type {import("mock_models").ResUsers} */
|
||||
const ResUsers = this.env["res.users"];
|
||||
|
||||
const [livechatChannel] = this.browse(id);
|
||||
const users = ResUsers.browse(livechatChannel.user_ids);
|
||||
return users.filter((user) => user.im_status === "online");
|
||||
}
|
||||
/** @param {integer} id */
|
||||
_get_livechat_discuss_channel_vals(id, operator_info) {
|
||||
/** @type {import("mock_models").MailGuest} */
|
||||
const MailGuest = this.env["mail.guest"];
|
||||
/** @type {import("mock_models").ResUsers} */
|
||||
const ResUsers = this.env["res.users"];
|
||||
const agent = operator_info["agent"];
|
||||
|
||||
const membersToAdd = [
|
||||
Command.create({
|
||||
last_interest_dt: "2021-01-01 10:00:00",
|
||||
livechat_member_type: "agent",
|
||||
partner_id: agent.partner_id,
|
||||
unpin_dt: "2021-01-01 12:00:00",
|
||||
}),
|
||||
];
|
||||
const guest = ResUsers._is_public(this.env.uid) && MailGuest._get_guest_from_context();
|
||||
if (guest) {
|
||||
membersToAdd.push(
|
||||
Command.create({ guest_id: guest.id, livechat_member_type: "visitor" })
|
||||
);
|
||||
}
|
||||
let visitorUser;
|
||||
if (this.env.user && !ResUsers._is_public(this.env.uid) && this.env.user !== agent) {
|
||||
visitorUser = this.env.user;
|
||||
membersToAdd.push(
|
||||
Command.create({
|
||||
livechat_member_type: "visitor",
|
||||
partner_id: visitorUser.partner_id,
|
||||
})
|
||||
);
|
||||
}
|
||||
const membersName = [
|
||||
visitorUser ? visitorUser.display_name : guest.name,
|
||||
agent.livechat_username ? agent.livechat_username : agent.name,
|
||||
];
|
||||
return {
|
||||
channel_partner_ids: [agent.partner_id],
|
||||
channel_member_ids: membersToAdd,
|
||||
livechat_operator_id: agent.partner_id,
|
||||
livechat_channel_id: id,
|
||||
livechat_status: "in_progress",
|
||||
channel_type: "livechat",
|
||||
name: membersName.join(" "),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Simplified mock implementation: returns
|
||||
* the previous operator if he is still available
|
||||
* or the first available operator.
|
||||
*
|
||||
* @param {integer} id
|
||||
*/
|
||||
_get_operator(id, previous_operator_id) {
|
||||
const availableUsers = this._compute_available_operator_ids(id);
|
||||
return (
|
||||
availableUsers.find((operator) => operator.partner_id === previous_operator_id) ??
|
||||
availableUsers[0]
|
||||
);
|
||||
}
|
||||
|
||||
_to_store(store, fields) {
|
||||
const kwargs = getKwArgs(arguments, "store", "fields");
|
||||
fields = kwargs.fields;
|
||||
store._add_record_fields(
|
||||
this,
|
||||
fields.filter((field) => field !== "are_you_inside")
|
||||
);
|
||||
for (const livechatChannel of this) {
|
||||
if (fields.includes("are_you_inside")) {
|
||||
store._add_record_fields(this.browse(livechatChannel.id), {
|
||||
are_you_inside: livechatChannel.user_ids.includes(this.env.user.id),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class Im_LivechatExpertise extends models.ServerModel {
|
||||
_name = "im_livechat.expertise";
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { mailModels } from "@mail/../tests/mail_test_helpers";
|
||||
|
||||
import { serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class IrWebSocket extends mailModels.IrWebSocket {
|
||||
/**
|
||||
* @override
|
||||
* @type {typeof busModels.IrWebSocket["prototype"]["_build_bus_channel_list"]}
|
||||
*/
|
||||
_build_bus_channel_list(channels) {
|
||||
channels = [...super._build_bus_channel_list(channels)];
|
||||
const result = channels;
|
||||
for (const channel of channels) {
|
||||
if (channel === "im_livechat.looking_for_help") {
|
||||
result.push([
|
||||
this.env["res.groups"].browse(serverState.groupLivechatId)[0],
|
||||
"LOOKING_FOR_HELP",
|
||||
]);
|
||||
}
|
||||
}
|
||||
return result.filter((channel) => channel !== "im_livechat.looking_for_help");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { models } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class LivechatChannelRule extends models.ServerModel {
|
||||
_name = "im_livechat.channel.rule";
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import { mailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { mailDataHelpers } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
|
||||
import { makeKwArgs } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class MailMessage extends mailModels.MailMessage {
|
||||
_author_to_store(ids, store) {
|
||||
/** @type {import("mock_models").DiscussChannel} */
|
||||
const DiscussChannel = this.env["discuss.channel"];
|
||||
/** @type {import("mock_models").MailMessage} */
|
||||
const MailMessage = this.env["mail.message"];
|
||||
/** @type {import("mock_models").ResPartner} */
|
||||
const ResPartner = this.env["res.partner"];
|
||||
|
||||
const messages_w_author_livechat = MailMessage.browse(ids).filter((message) => {
|
||||
if (!message.author_id || message.model !== "discuss.channel" || !message.res_id) {
|
||||
return false;
|
||||
}
|
||||
const channel = DiscussChannel.browse(message.res_id);
|
||||
return channel.channel_type === "livechat";
|
||||
});
|
||||
super._author_to_store(
|
||||
ids.filter(
|
||||
(id) => !messages_w_author_livechat.map((message) => message.id).includes(id)
|
||||
),
|
||||
store
|
||||
);
|
||||
for (const message of messages_w_author_livechat) {
|
||||
store.add(this.browse(message.id), {
|
||||
author_id: mailDataHelpers.Store.one(
|
||||
ResPartner.browse(message.author_id),
|
||||
makeKwArgs({
|
||||
fields: ["avatar_128", "is_company", "user_livechat_username", "user"],
|
||||
})
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { serverState } from "@web/../tests/web_test_helpers";
|
||||
import { mailModels } from "@mail/../tests/mail_test_helpers";
|
||||
|
||||
export class ResGroups extends mailModels.ResGroups {
|
||||
_records = [
|
||||
...this._records,
|
||||
{
|
||||
id: serverState.groupLivechatId,
|
||||
name: "Livechat User",
|
||||
privilege_id: false,
|
||||
},
|
||||
{
|
||||
id: serverState.groupLivechatManagerId,
|
||||
name: "Livechat Manager",
|
||||
privilege_id: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import { webModels } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class ResGroupsPrivilege extends webModels.ResGroupsPrivilege {}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import { mailModels } from "@mail/../tests/mail_test_helpers";
|
||||
|
||||
import { getKwArgs, makeKwArgs, serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class ResPartner extends mailModels.ResPartner {
|
||||
/**
|
||||
* @override
|
||||
* @type {typeof mailModels.ResPartner["prototype"]["_search_for_channel_invite_to_store"]}
|
||||
*/
|
||||
_search_for_channel_invite_to_store(ids, store, channel_id) {
|
||||
/** @type {import("mock_models").DiscussChannel} */
|
||||
const DiscussChannel = this.env["discuss.channel"];
|
||||
/** @type {import("mock_models").DiscussChannelMember} */
|
||||
const DiscussChannelMember = this.env["discuss.channel.member"];
|
||||
/** @type {import("mock_models").LivechatChannel} */
|
||||
const LivechatChannel = this.env["im_livechat.channel"];
|
||||
/** @type {import("mock_models").Im_LivechatExpertise} */
|
||||
const Im_LivechatExpertise = this.env["im_livechat.expertise"];
|
||||
/** @type {import("mock_models").ResLang} */
|
||||
const ResLang = this.env["res.lang"];
|
||||
/** @type {import("mock_models").ResPartner} */
|
||||
const ResPartner = this.env["res.partner"];
|
||||
/** @type {import("mock_models").ResUsers} */
|
||||
const ResUsers = this.env["res.users"];
|
||||
|
||||
super._search_for_channel_invite_to_store(ids, store, channel_id);
|
||||
const [channel] = DiscussChannel.browse(channel_id);
|
||||
if (channel?.channel_type !== "livechat") {
|
||||
return;
|
||||
}
|
||||
const activeLivechatPartners = LivechatChannel._filter([])
|
||||
.map(({ available_operator_ids }) => available_operator_ids)
|
||||
.flat()
|
||||
.map((userId) => ResUsers.browse(userId)[0].partner_id);
|
||||
for (const partner of ResPartner.browse(ids)) {
|
||||
const data = {
|
||||
invite_by_self_count: DiscussChannelMember.search_count([
|
||||
["partner_id", "=", partner.id],
|
||||
["create_uid", "=", serverState.userId],
|
||||
]),
|
||||
is_available: activeLivechatPartners.includes(partner.id),
|
||||
};
|
||||
if (partner.lang) {
|
||||
data.lang_name = ResLang.search_read([["code", "=", partner.lang]])[0].name;
|
||||
}
|
||||
if (partner.user_ids.length) {
|
||||
const [user] = ResUsers.browse(partner.user_ids[0]);
|
||||
if (user) {
|
||||
const userLangs = user.livechat_lang_ids
|
||||
.map((langId) => ResLang.browse(langId)[0])
|
||||
.filter((lang) => lang.name !== data.lang_name);
|
||||
data.livechat_languages = userLangs.map((lang) => lang.name);
|
||||
data.livechat_expertise = user.livechat_expertise_ids.map(
|
||||
(expId) => Im_LivechatExpertise.browse(expId)[0].name
|
||||
);
|
||||
}
|
||||
}
|
||||
store.add(this.browse(partner.id), makeKwArgs({ fields: ["user_livechat_username"] }));
|
||||
store.add(this.browse(partner.id), data);
|
||||
store.add(this.browse(partner.id), makeKwArgs({ extra_fields: ["is_in_call"] }));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @override
|
||||
* @type {typeof mailModels.ResPartner["prototype"]["_to_store"]}
|
||||
*/
|
||||
_to_store(store, fields) {
|
||||
const kwargs = getKwArgs(arguments, "store", "fields");
|
||||
fields = kwargs.fields;
|
||||
|
||||
super._to_store(...arguments);
|
||||
if (fields && fields.includes("user_livechat_username")) {
|
||||
store._add_record_fields(
|
||||
this.filter((partner) => !partner.user_livechat_username),
|
||||
["name"]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { mailModels } from "@mail/../tests/mail_test_helpers";
|
||||
import { serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
export class ResUsers extends mailModels.ResUsers {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_init_store_data(store) {
|
||||
super._init_store_data(...arguments);
|
||||
store.add({
|
||||
has_access_livechat: this.env.user?.group_ids.includes(serverState.groupLivechatId),
|
||||
});
|
||||
store.add(this.browse(this.env.uid), {
|
||||
is_livechat_manager: this.env.user?.group_ids.includes(
|
||||
serverState.groupLivechatManagerId
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
afterNextRender,
|
||||
start,
|
||||
startServer,
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
QUnit.module('im_livechat', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('chat_window_manager', {}, function () {
|
||||
QUnit.module('chat_window_manager_tests.js');
|
||||
|
||||
QUnit.test('closing a chat window with no message from admin side unpins it', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const resPartnerId1 = pyEnv['res.partner'].create({ name: "Demo" });
|
||||
pyEnv['res.users'].create({ partner_id: resPartnerId1 });
|
||||
const mailChannelId1 = pyEnv['mail.channel'].create(
|
||||
{
|
||||
channel_member_ids: [
|
||||
[0, 0, {
|
||||
is_pinned: true,
|
||||
partner_id: pyEnv.currentPartnerId,
|
||||
}],
|
||||
[0, 0, { partner_id: resPartnerId1 }],
|
||||
],
|
||||
channel_type: "livechat",
|
||||
uuid: 'channel-10-uuid',
|
||||
},
|
||||
);
|
||||
const { messaging } = await start();
|
||||
|
||||
await afterNextRender(() => document.querySelector(`.o_MessagingMenu_toggler`).click());
|
||||
await afterNextRender(() => document.querySelector(`.o_NotificationList_preview`).click());
|
||||
await afterNextRender(() => document.querySelector(`.o_ChatWindowHeader_commandClose`).click());
|
||||
const channels = await messaging.rpc({
|
||||
model: 'mail.channel',
|
||||
method: 'channel_info',
|
||||
args: [mailChannelId1],
|
||||
}, { shadow: true });
|
||||
assert.strictEqual(
|
||||
channels[0].is_pinned,
|
||||
false,
|
||||
'Livechat channel should not be pinned',
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { start, startServer } from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
QUnit.module('im_livechat', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('composer_tests.js');
|
||||
|
||||
QUnit.test('livechat: no add attachment button', async function (assert) {
|
||||
// Attachments are not yet supported in livechat, especially from livechat
|
||||
// visitor PoV. This may likely change in the future with task-2029065.
|
||||
assert.expect(2);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const livechatId = pyEnv['mail.channel'].create({ channel_type: 'livechat' });
|
||||
const { openDiscuss } = await start({
|
||||
discuss: {
|
||||
context: { active_id: livechatId },
|
||||
},
|
||||
});
|
||||
await openDiscuss();
|
||||
assert.containsOnce(document.body, '.o_Composer', "should have a composer");
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_Composer_buttonAttachment',
|
||||
"composer linked to livechat should not have a 'Add attachment' button"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat: disable attachment upload via drag and drop', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const livechatId = pyEnv['mail.channel'].create({ channel_type: 'livechat' });
|
||||
const { openDiscuss } = await start({
|
||||
discuss: {
|
||||
context: { active_id: livechatId },
|
||||
},
|
||||
});
|
||||
await openDiscuss();
|
||||
assert.containsOnce(document.body, '.o_Composer', "should have a composer");
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_Composer_dropZone',
|
||||
"composer linked to livechat should not have a dropzone"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
start,
|
||||
startServer,
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
QUnit.module('im_livechat', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('discuss_sidebar_category_item_tests.js');
|
||||
|
||||
QUnit.test('livechat - avatar: should have a smiley face avatar for an anonymous livechat item', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.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();
|
||||
|
||||
const livechatItem = document.querySelector(`
|
||||
.o_DiscussSidebarCategoryItem[data-channel-id="${mailChannelId1}"]
|
||||
`);
|
||||
assert.containsOnce(
|
||||
livechatItem,
|
||||
`.o_DiscussSidebarCategoryItem_image`,
|
||||
"should have an avatar"
|
||||
);
|
||||
assert.strictEqual(
|
||||
livechatItem.querySelector(`:scope .o_DiscussSidebarCategoryItem_image`).dataset.src,
|
||||
'/mail/static/src/img/smiley/avatar.jpg',
|
||||
'should have the smiley face as the avatar for anonymous users'
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - avatar: should have a partner profile picture for a livechat item linked with a partner', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const resPartnerId1 = pyEnv['res.partner'].create({
|
||||
name: "Jean",
|
||||
});
|
||||
const mailChannelId1 = pyEnv['mail.channel'].create({
|
||||
channel_member_ids: [
|
||||
[0, 0, { partner_id: pyEnv.currentPartnerId }],
|
||||
[0, 0, { partner_id: resPartnerId1 }],
|
||||
],
|
||||
channel_type: 'livechat',
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
});
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
|
||||
const livechatItem = document.querySelector(`
|
||||
.o_DiscussSidebarCategoryItem[data-channel-id="${mailChannelId1}"]
|
||||
`);
|
||||
assert.containsOnce(
|
||||
livechatItem,
|
||||
`.o_DiscussSidebarCategoryItem_image`,
|
||||
"should have an avatar"
|
||||
);
|
||||
assert.strictEqual(
|
||||
livechatItem.querySelector(`:scope .o_DiscussSidebarCategoryItem_image`).dataset.src,
|
||||
`/web/image/res.partner/${resPartnerId1}/avatar_128`,
|
||||
'should have the partner profile picture as the avatar for partners'
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -1,449 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
afterNextRender,
|
||||
start,
|
||||
startServer,
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
QUnit.module('im_livechat', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('discuss_sidebar_category_tests.js');
|
||||
|
||||
QUnit.test('livechat - counter: should not have a counter if the category is unfolded and without unread messages', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
pyEnv['mail.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.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategory_counter`,
|
||||
"should not have a counter if the category is unfolded and without unread messages",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - counter: should not have a counter if the category is unfolded and with unread messages', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
pyEnv['mail.channel'].create({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_member_ids: [
|
||||
[0, 0, {
|
||||
message_unread_counter: 10,
|
||||
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.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategory_counter`,
|
||||
"should not have a counter if the category is unfolded and with unread messages",
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - counter: should not have a counter if category is folded and without unread messages', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
pyEnv['mail.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,
|
||||
});
|
||||
pyEnv['res.users.settings'].create({
|
||||
user_id: pyEnv.currentUserId,
|
||||
is_discuss_sidebar_category_livechat_open: false,
|
||||
});
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategory_counter`,
|
||||
"should not have a counter if the category is folded and without unread messages"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - counter: should have correct value of unread threads if category is folded and with unread messages', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
pyEnv['mail.channel'].create({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_member_ids: [
|
||||
[0, 0, {
|
||||
message_unread_counter: 10,
|
||||
partner_id: pyEnv.currentPartnerId,
|
||||
}],
|
||||
[0, 0, { partner_id: pyEnv.publicPartnerId }],
|
||||
],
|
||||
channel_type: 'livechat',
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
});
|
||||
pyEnv['res.users.settings'].create({
|
||||
user_id: pyEnv.currentUserId,
|
||||
is_discuss_sidebar_category_livechat_open: false,
|
||||
});
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
|
||||
assert.strictEqual(
|
||||
document.querySelector(`.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategory_counter`).textContent,
|
||||
"1",
|
||||
"should have correct value of unread threads if category is folded and with unread messages"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - states: close manually by clicking the title', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.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,
|
||||
});
|
||||
pyEnv['res.users.settings'].create({
|
||||
user_id: pyEnv.currentUserId,
|
||||
is_discuss_sidebar_category_livechat_open: true,
|
||||
});
|
||||
const { messaging, openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
|
||||
);
|
||||
|
||||
// fold the livechat category
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
|
||||
messaging.discuss.categoryLivechat.localId}"]
|
||||
.o_DiscussSidebarCategory_title
|
||||
`).click()
|
||||
);
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
|
||||
"Category livechat should be closed and the content should be invisible"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - states: open manually by clicking the title', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.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,
|
||||
});
|
||||
pyEnv['res.users.settings'].create({
|
||||
user_id: pyEnv.currentUserId,
|
||||
is_discuss_sidebar_category_livechat_open: false,
|
||||
});
|
||||
const { messaging, openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
|
||||
);
|
||||
|
||||
// open the livechat category
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
|
||||
messaging.discuss.categoryLivechat.localId}"]
|
||||
.o_DiscussSidebarCategory_title
|
||||
`).click()
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
|
||||
"Category livechat should be open and the content should be visible"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - states: close should update the value on the server', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
pyEnv['mail.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,
|
||||
});
|
||||
pyEnv['res.users.settings'].create({
|
||||
user_id: pyEnv.currentUserId,
|
||||
is_discuss_sidebar_category_livechat_open: true,
|
||||
});
|
||||
const currentUserId = pyEnv.currentUserId;
|
||||
const { messaging, openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
|
||||
const initalSettings = await messaging.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
initalSettings.is_discuss_sidebar_category_livechat_open,
|
||||
true,
|
||||
"the value in server side should be true"
|
||||
);
|
||||
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
|
||||
messaging.discuss.categoryLivechat.localId}"]
|
||||
.o_DiscussSidebarCategory_title
|
||||
`).click()
|
||||
);
|
||||
const newSettings = await messaging.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
newSettings.is_discuss_sidebar_category_livechat_open,
|
||||
false,
|
||||
"the value in server side should be false"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - states: open should update the value on the server', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
pyEnv['mail.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,
|
||||
});
|
||||
pyEnv['res.users.settings'].create({
|
||||
user_id: pyEnv.currentUserId,
|
||||
is_discuss_sidebar_category_livechat_open: false,
|
||||
});
|
||||
const currentUserId = pyEnv.currentUserId;
|
||||
const { messaging, openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
|
||||
const initalSettings = await messaging.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
initalSettings.is_discuss_sidebar_category_livechat_open,
|
||||
false,
|
||||
"the value in server side should be false"
|
||||
);
|
||||
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
|
||||
messaging.discuss.categoryLivechat.localId}"]
|
||||
.o_DiscussSidebarCategory_title
|
||||
`).click()
|
||||
);
|
||||
const newSettings = await messaging.rpc({
|
||||
model: 'res.users.settings',
|
||||
method: '_find_or_create_for_user',
|
||||
args: [[currentUserId]],
|
||||
});
|
||||
assert.strictEqual(
|
||||
newSettings.is_discuss_sidebar_category_livechat_open,
|
||||
true,
|
||||
"the value in server side should be true"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - states: close from the bus', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.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 resUsersSettingsId1 = pyEnv['res.users.settings'].create({
|
||||
user_id: pyEnv.currentUserId,
|
||||
is_discuss_sidebar_category_livechat_open: true,
|
||||
});
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
|
||||
);
|
||||
|
||||
await afterNextRender(() => {
|
||||
pyEnv['bus.bus']._sendone(pyEnv.currentPartner, 'res.users.settings/insert', {
|
||||
id: resUsersSettingsId1,
|
||||
'is_discuss_sidebar_category_livechat_open': false,
|
||||
});
|
||||
});
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
|
||||
"Category livechat should be closed and the content should be invisible"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - states: open from the bus', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.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 resUsersSettingsId1 = pyEnv['res.users.settings'].create({
|
||||
user_id: pyEnv.currentUserId,
|
||||
is_discuss_sidebar_category_livechat_open: false,
|
||||
});
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
|
||||
);
|
||||
|
||||
await afterNextRender(() => {
|
||||
pyEnv['bus.bus']._sendone(pyEnv.currentPartner, 'res.users.settings/insert', {
|
||||
id: resUsersSettingsId1,
|
||||
'is_discuss_sidebar_category_livechat_open': true,
|
||||
});
|
||||
});
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
|
||||
"Category livechat should be open and the content should be visible"
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
QUnit.test('livechat - states: category item should be invisible if the category is closed', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.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 { messaging, openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
|
||||
);
|
||||
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
|
||||
messaging.discuss.categoryLivechat.localId}"]
|
||||
.o_DiscussSidebarCategory_title
|
||||
`).click()
|
||||
);
|
||||
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
|
||||
"inactive item should be invisible if the category is folded"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat - states: the active category item should be visble even if the category is closed', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.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 { messaging, openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`
|
||||
);
|
||||
|
||||
const livechat = document.querySelector(`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`);
|
||||
await afterNextRender(() => {
|
||||
livechat.click();
|
||||
});
|
||||
assert.ok(livechat.classList.contains('o-active'));
|
||||
|
||||
await afterNextRender(() =>
|
||||
document.querySelector(`.o_DiscussSidebarCategory[data-category-local-id="${
|
||||
messaging.discuss.categoryLivechat.localId}"]
|
||||
.o_DiscussSidebarCategory_title
|
||||
`).click()
|
||||
);
|
||||
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_DiscussSidebarCategory_item[data-channel-id="${mailChannelId1}"]`,
|
||||
'the active livechat item should remain open even if the category is folded'
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -1,435 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
afterNextRender,
|
||||
nextAnimationFrame,
|
||||
start,
|
||||
startServer,
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
import { datetime_to_str } from 'web.time';
|
||||
|
||||
QUnit.module('im_livechat', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('discuss_tests.js');
|
||||
|
||||
QUnit.test('livechat in the sidebar: basic rendering', async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.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(document.body, '.o_Discuss_sidebar',
|
||||
"should have a sidebar section"
|
||||
);
|
||||
const groupLivechat = document.querySelector('.o_DiscussSidebar_categoryLivechat');
|
||||
assert.ok(groupLivechat,
|
||||
"should have a channel group livechat"
|
||||
);
|
||||
const titleText = groupLivechat.querySelector('.o_DiscussSidebarCategory_titleText');
|
||||
assert.strictEqual(
|
||||
titleText.textContent.trim(),
|
||||
"Livechat",
|
||||
"should have a channel group named 'Livechat'"
|
||||
);
|
||||
const livechat = groupLivechat.querySelector(`
|
||||
.o_DiscussSidebarCategoryItem[data-channel-id="${mailChannelId1}"]
|
||||
`);
|
||||
assert.ok(
|
||||
livechat,
|
||||
"should have a livechat in sidebar"
|
||||
);
|
||||
assert.strictEqual(
|
||||
livechat.textContent,
|
||||
"Visitor 11",
|
||||
"should have 'Visitor 11' as livechat name"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechat in the sidebar: existing user with country', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const resCountryId1 = pyEnv['res.country'].create({
|
||||
code: 'be',
|
||||
name: "Belgium",
|
||||
});
|
||||
const resPartnerId1 = pyEnv['res.partner'].create({
|
||||
country_id: resCountryId1,
|
||||
name: "Jean",
|
||||
});
|
||||
pyEnv['mail.channel'].create({
|
||||
channel_member_ids: [
|
||||
[0, 0, { partner_id: pyEnv.currentPartnerId }],
|
||||
[0, 0, { partner_id: resPartnerId1 }],
|
||||
],
|
||||
channel_type: 'livechat',
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
});
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_categoryLivechat',
|
||||
"should have a channel group livechat in the side bar"
|
||||
);
|
||||
const livechat = document.querySelector('.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategoryItem');
|
||||
assert.ok(
|
||||
livechat,
|
||||
"should have a livechat in sidebar"
|
||||
);
|
||||
assert.strictEqual(
|
||||
livechat.textContent,
|
||||
"Jean (Belgium)",
|
||||
"should have user name and country as livechat name"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('do not add livechat in the sidebar on visitor opening his chat', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
pyEnv['res.users'].write([pyEnv.currentUserId], { im_status: 'online' });
|
||||
const imLivechatChannelId1 = pyEnv['im_livechat.channel'].create({
|
||||
user_ids: [pyEnv.currentUserId],
|
||||
});
|
||||
const { messaging, openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_categoryLivechat',
|
||||
"should not have any livechat in the sidebar initially"
|
||||
);
|
||||
|
||||
// simulate livechat visitor opening his chat
|
||||
await messaging.rpc({
|
||||
route: '/im_livechat/get_session',
|
||||
params: {
|
||||
context: {
|
||||
mockedUserId: false,
|
||||
},
|
||||
channel_id: imLivechatChannelId1,
|
||||
},
|
||||
});
|
||||
await nextAnimationFrame();
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_categoryLivechat',
|
||||
"should still not have any livechat in the sidebar after visitor opened his chat"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('do not add livechat in the sidebar on visitor typing', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
pyEnv['res.users'].write([pyEnv.currentUserId], { im_status: 'online' });
|
||||
const imLivechatChannelId1 = pyEnv['im_livechat.channel'].create({
|
||||
user_ids: [pyEnv.currentUserId],
|
||||
});
|
||||
const mailChannelId1 = pyEnv['mail.channel'].create({
|
||||
channel_member_ids: [
|
||||
[0, 0, {
|
||||
is_pinned: false,
|
||||
partner_id: pyEnv.currentPartnerId,
|
||||
}],
|
||||
[0, 0, { partner_id: pyEnv.publicPartnerId }],
|
||||
],
|
||||
channel_type: 'livechat',
|
||||
livechat_channel_id: imLivechatChannelId1,
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
});
|
||||
const { messaging, openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_categoryLivechat',
|
||||
"should not have any livechat in the sidebar initially"
|
||||
);
|
||||
|
||||
// simulate livechat visitor typing
|
||||
const channel = pyEnv['mail.channel'].searchRead([['id', '=', mailChannelId1]])[0];
|
||||
await messaging.rpc({
|
||||
route: '/im_livechat/notify_typing',
|
||||
params: {
|
||||
context: {
|
||||
mockedPartnerId: pyEnv.publicPartnerId,
|
||||
},
|
||||
is_typing: true,
|
||||
uuid: channel.uuid,
|
||||
},
|
||||
});
|
||||
await nextAnimationFrame();
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_categoryLivechat',
|
||||
"should still not have any livechat in the sidebar after visitor started typing"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('add livechat in the sidebar on visitor sending first message', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
pyEnv['res.users'].write([pyEnv.currentUserId], { im_status: 'online' });
|
||||
const resCountryId1 = pyEnv['res.country'].create({
|
||||
code: 'be',
|
||||
name: "Belgium",
|
||||
});
|
||||
const imLivechatChannelId1 = pyEnv['im_livechat.channel'].create({
|
||||
user_ids: [pyEnv.currentUserId],
|
||||
});
|
||||
const mailChannelId1 = pyEnv['mail.channel'].create({
|
||||
anonymous_name: "Visitor (Belgium)",
|
||||
channel_member_ids: [
|
||||
[0, 0, {
|
||||
is_pinned: false,
|
||||
partner_id: pyEnv.currentPartnerId,
|
||||
}],
|
||||
[0, 0, { partner_id: pyEnv.publicPartnerId }],
|
||||
],
|
||||
channel_type: 'livechat',
|
||||
country_id: resCountryId1,
|
||||
livechat_channel_id: imLivechatChannelId1,
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
});
|
||||
const { messaging, openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_categoryLivechat',
|
||||
"should not have any livechat in the sidebar initially"
|
||||
);
|
||||
|
||||
// simulate livechat visitor sending a message
|
||||
const channel = pyEnv['mail.channel'].searchRead([['id', '=', mailChannelId1]])[0];
|
||||
await afterNextRender(async () => messaging.rpc({
|
||||
route: '/mail/chat_post',
|
||||
params: {
|
||||
context: {
|
||||
mockedUserId: false,
|
||||
},
|
||||
uuid: channel.uuid,
|
||||
message_content: "new message",
|
||||
},
|
||||
}));
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_categoryLivechat',
|
||||
"should have a channel group livechat in the side bar after receiving first message"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategoryItem',
|
||||
"should have a livechat in the sidebar after receiving first message"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategoryItem .o_DiscussSidebarCategoryItem_name').textContent,
|
||||
"Visitor (Belgium)",
|
||||
"should have visitor name and country as livechat name"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('livechats are sorted by last activity time in the sidebar: most recent at the top', async function (assert) {
|
||||
assert.expect(6);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const [mailChannelId1, mailChannelId2] = pyEnv['mail.channel'].create([
|
||||
{
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_member_ids: [
|
||||
[0, 0, {
|
||||
last_interest_dt: datetime_to_str(new Date(2021, 0, 1)),
|
||||
partner_id: pyEnv.currentPartnerId,
|
||||
}],
|
||||
[0, 0, { partner_id: pyEnv.publicPartnerId }],
|
||||
],
|
||||
channel_type: 'livechat',
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
},
|
||||
{
|
||||
anonymous_name: "Visitor 12",
|
||||
channel_member_ids: [
|
||||
[0, 0, {
|
||||
last_interest_dt: datetime_to_str(new Date(2021, 0, 2)),
|
||||
partner_id: pyEnv.currentPartnerId,
|
||||
}],
|
||||
[0, 0, { partner_id: pyEnv.publicPartnerId }],
|
||||
],
|
||||
channel_type: 'livechat',
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
},
|
||||
]);
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss();
|
||||
const initialLivechats = document.querySelectorAll('.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategory_item');
|
||||
assert.strictEqual(
|
||||
initialLivechats.length,
|
||||
2,
|
||||
"should have 2 livechat items"
|
||||
);
|
||||
assert.strictEqual(
|
||||
Number(initialLivechats[0].dataset.channelId),
|
||||
mailChannelId2,
|
||||
"first livechat should be the one with the more recent last activity time"
|
||||
);
|
||||
assert.strictEqual(
|
||||
Number(initialLivechats[1].dataset.channelId),
|
||||
mailChannelId1,
|
||||
"second livechat should be the one with the less recent last activity time"
|
||||
);
|
||||
|
||||
// post a new message on the last channel
|
||||
await afterNextRender(() => initialLivechats[1].click());
|
||||
await afterNextRender(() => document.execCommand('insertText', false, "Blabla"));
|
||||
await afterNextRender(() => document.querySelector('.o_Composer_buttonSend').click());
|
||||
|
||||
const newLivechats = document.querySelectorAll('.o_DiscussSidebar_categoryLivechat .o_DiscussSidebarCategory_item');
|
||||
assert.strictEqual(
|
||||
newLivechats.length,
|
||||
2,
|
||||
"should have 2 livechat items"
|
||||
);
|
||||
assert.strictEqual(
|
||||
Number(newLivechats[0].dataset.channelId),
|
||||
mailChannelId1,
|
||||
"first livechat should be the one with the more recent last activity time"
|
||||
);
|
||||
assert.strictEqual(
|
||||
Number(newLivechats[1].dataset.channelId),
|
||||
mailChannelId2,
|
||||
"second livechat should be the one with the less recent last activity time"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('invite button should be present on livechat', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.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({
|
||||
discuss: {
|
||||
params: {
|
||||
default_active_id: `mail.channel_${mailChannelId1}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
await openDiscuss();
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_ThreadViewTopbar_inviteButton',
|
||||
"Invite button should be visible in top bar when livechat is active thread"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('call buttons should not be present on livechat', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.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({
|
||||
discuss: {
|
||||
params: {
|
||||
default_active_id: `mail.channel_${mailChannelId1}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
await openDiscuss();
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_ThreadViewTopbar_callButton',
|
||||
"Call buttons should not be visible in top bar when livechat is active thread"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('reaction button should not be present on livechat', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.channel'].create({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
channel_partner_ids: [pyEnv.currentPartnerId, pyEnv.publicPartnerId],
|
||||
});
|
||||
const { click, insertText, openDiscuss } = await start({
|
||||
discuss: {
|
||||
params: {
|
||||
default_active_id: `mail.channel_${mailChannelId1}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
await openDiscuss();
|
||||
await insertText('.o_ComposerTextInput_textarea', "Test");
|
||||
await click('.o_Composer_buttonSend');
|
||||
await click('.o_Message');
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_MessageActionView_actionReaction',
|
||||
"should not have action to add a reaction"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('reply button should not be present on livechat', async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.channel'].create({
|
||||
anonymous_name: "Visitor 11",
|
||||
channel_type: 'livechat',
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
channel_partner_ids: [pyEnv.currentPartnerId, pyEnv.publicPartnerId],
|
||||
});
|
||||
const { click, insertText, openDiscuss } = await start({
|
||||
discuss: {
|
||||
params: {
|
||||
default_active_id: `mail.channel_${mailChannelId1}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
await openDiscuss();
|
||||
await insertText('.o_ComposerTextInput_textarea', "Test");
|
||||
await click('.o_Composer_buttonSend');
|
||||
await click('.o_Message');
|
||||
assert.containsNone(
|
||||
document.body,
|
||||
'.o_MessageActionView_actionReplyTo',
|
||||
"should not have reply action"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
afterNextRender,
|
||||
start,
|
||||
startServer,
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
QUnit.module('im_livechat', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('messaging_menu_tests.js');
|
||||
|
||||
QUnit.test('livechats should be in "chat" filter', async function (assert) {
|
||||
assert.expect(7);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.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();
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_MessagingMenu',
|
||||
"should have messaging menu"
|
||||
);
|
||||
|
||||
await afterNextRender(() => document.querySelector('.o_MessagingMenu_toggler').click());
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_MessagingMenuTab[data-tab-id="all"]',
|
||||
"should have a tab/filter 'all' in messaging menu"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_MessagingMenuTab[data-tab-id="chat"]',
|
||||
"should have a tab/filter 'chat' in messaging menu"
|
||||
);
|
||||
assert.hasClass(
|
||||
document.querySelector('.o_MessagingMenuTab[data-tab-id="all"]'),
|
||||
'o-active',
|
||||
"tab/filter 'all' of messaging menu should be active initially"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]`,
|
||||
"livechat should be listed in 'all' tab/filter of messaging menu"
|
||||
);
|
||||
|
||||
await afterNextRender(() =>
|
||||
document.querySelector('.o_MessagingMenuTab[data-tab-id="chat"]').click()
|
||||
);
|
||||
assert.hasClass(
|
||||
document.querySelector('.o_MessagingMenuTab[data-tab-id="chat"]'),
|
||||
'o-active',
|
||||
"tab/filter 'chat' of messaging menu should become active after click"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
`.o_ChannelPreviewView[data-channel-id="${mailChannelId1}"]`,
|
||||
"livechat should be listed in 'chat' tab/filter of messaging menu"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
afterNextRender,
|
||||
start,
|
||||
startServer,
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
QUnit.module('im_livechat', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('thread_icon_tests.js');
|
||||
|
||||
QUnit.test('livechat: public website visitor is typing', async function (assert) {
|
||||
assert.expect(4);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.channel'].create({
|
||||
anonymous_name: "Visitor 20",
|
||||
channel_member_ids: [
|
||||
[0, 0, { partner_id: pyEnv.currentPartnerId }],
|
||||
[0, 0, { partner_id: pyEnv.publicPartnerId }],
|
||||
],
|
||||
channel_type: 'livechat',
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
});
|
||||
const { messaging, openDiscuss } = await start({
|
||||
discuss: {
|
||||
context: { active_id: mailChannelId1 },
|
||||
},
|
||||
});
|
||||
await openDiscuss();
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_ThreadViewTopbar .o_ThreadIcon',
|
||||
"should have thread icon"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_ThreadIcon .fa.fa-comments',
|
||||
"should have default livechat icon"
|
||||
);
|
||||
|
||||
const mailChannel1 = pyEnv['mail.channel'].searchRead([['id', '=', mailChannelId1]])[0];
|
||||
// simulate receive typing notification from livechat visitor "is typing"
|
||||
await afterNextRender(() => messaging.rpc({
|
||||
route: '/im_livechat/notify_typing',
|
||||
params: {
|
||||
context: {
|
||||
mockedPartnerId: pyEnv.publicPartnerId,
|
||||
},
|
||||
is_typing: true,
|
||||
uuid: mailChannel1.uuid,
|
||||
},
|
||||
}));
|
||||
assert.containsOnce(
|
||||
document.body,
|
||||
'.o_ThreadIcon_typing',
|
||||
"should have thread icon with visitor currently typing"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_ThreadIcon_typing').title,
|
||||
"Visitor 20 is typing...",
|
||||
"title of icon should tell visitor is currently typing"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import {
|
||||
afterNextRender,
|
||||
start,
|
||||
startServer,
|
||||
} from '@mail/../tests/helpers/test_utils';
|
||||
|
||||
QUnit.module('im_livechat', {}, function () {
|
||||
QUnit.module('components', {}, function () {
|
||||
QUnit.module('thread_textual_typing_status_tests.js');
|
||||
|
||||
QUnit.test('receive visitor typing status "is typing"', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
const pyEnv = await startServer();
|
||||
const mailChannelId1 = pyEnv['mail.channel'].create({
|
||||
anonymous_name: "Visitor 20",
|
||||
channel_member_ids: [
|
||||
[0, 0, { partner_id: pyEnv.currentPartnerId }],
|
||||
[0, 0, { partner_id: pyEnv.publicPartnerId }],
|
||||
],
|
||||
channel_type: 'livechat',
|
||||
livechat_operator_id: pyEnv.currentPartnerId,
|
||||
});
|
||||
const { messaging, openDiscuss } = await start({
|
||||
discuss: {
|
||||
context: { active_id: mailChannelId1 },
|
||||
},
|
||||
});
|
||||
await openDiscuss();
|
||||
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_ThreadTextualTypingStatus').textContent,
|
||||
"",
|
||||
"Should display no one is currently typing"
|
||||
);
|
||||
|
||||
const mailChannel1 = pyEnv['mail.channel'].searchRead([['id', '=', mailChannelId1]])[0];
|
||||
// simulate receive typing notification from livechat visitor "is typing"
|
||||
await afterNextRender(() => messaging.rpc({
|
||||
route: '/im_livechat/notify_typing',
|
||||
params: {
|
||||
context: {
|
||||
mockedPartnerId: pyEnv.publicPartnerId,
|
||||
},
|
||||
is_typing: true,
|
||||
uuid: mailChannel1.uuid,
|
||||
},
|
||||
}));
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_ThreadTextualTypingStatus').textContent,
|
||||
"Visitor 20 is typing...",
|
||||
"Should display that visitor is typing"
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,469 @@
|
|||
import { waitForChannels } from "@bus/../tests/bus_test_helpers";
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
insertText,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { withGuest } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { mockDate, tick } from "@odoo/hoot-mock";
|
||||
import { asyncStep, Command, serverState, waitForSteps } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { deserializeDateTime } from "@web/core/l10n/dates";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { url } from "@web/core/utils/urls";
|
||||
import { defineLivechatModels } from "./livechat_test_helpers";
|
||||
import { press } from "@odoo/hoot-dom";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("Unknown visitor", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains(".o-mail-DiscussSidebar .o-mail-DiscussSidebarCategory-livechat");
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "Visitor 11" });
|
||||
});
|
||||
|
||||
test("Do not show channel when visitor is typing", async () => {
|
||||
mockDate("2023-01-03 12:00:00"); // so that it's after last interest (mock server is in 2019 by default!)
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write([serverState.userId], { im_status: "online" });
|
||||
const livechatChannelId = pyEnv["im_livechat.channel"].create({
|
||||
user_ids: [serverState.userId],
|
||||
});
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
unpin_dt: "2021-01-01 12:00:00",
|
||||
last_interest_dt: "2021-01-01 10:00:00",
|
||||
livechat_member_type: "agent",
|
||||
partner_id: serverState.partnerId,
|
||||
}),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_channel_id: livechatChannelId,
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains(".o-mail-DiscussSidebarCategory", { count: 2 });
|
||||
await contains(
|
||||
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel-container",
|
||||
{
|
||||
count: 0,
|
||||
}
|
||||
);
|
||||
// simulate livechat visitor typing
|
||||
const channel = pyEnv["discuss.channel"].search_read([["id", "=", channelId]])[0];
|
||||
await withGuest(guestId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
is_typing: true,
|
||||
channel_id: channel.id,
|
||||
})
|
||||
);
|
||||
// weak test, no guaranteed that we waited long enough for the livechat to potentially appear
|
||||
await tick();
|
||||
await contains(
|
||||
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel-container",
|
||||
{
|
||||
count: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("Smiley face avatar for livechat item linked to a guest", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
const guest = pyEnv["mail.guest"].search_read([["id", "=", guestId]])[0];
|
||||
await contains(
|
||||
`.o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel-container img[data-src='${url(
|
||||
`/web/image/mail.guest/${guestId}/avatar_128?unique=${
|
||||
deserializeDateTime(guest.write_date).ts
|
||||
}`
|
||||
)}']`
|
||||
);
|
||||
});
|
||||
|
||||
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({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ partner_id: partnerId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
const partner = pyEnv["res.partner"].search_read([["id", "=", partnerId]])[0];
|
||||
await contains(
|
||||
`.o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel-container img[data-src='${url(
|
||||
`/web/image/res.partner/${partnerId}/avatar_128?unique=${
|
||||
deserializeDateTime(partner.write_date).ts
|
||||
}`
|
||||
)}']`
|
||||
);
|
||||
});
|
||||
|
||||
test("No counter if the category is unfolded and with unread messages", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
message_unread_counter: 10,
|
||||
livechat_member_type: "agent",
|
||||
partner_id: serverState.partnerId,
|
||||
}),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechat");
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechat .o-mail-Discuss-category-counter", {
|
||||
count: 0,
|
||||
});
|
||||
});
|
||||
|
||||
test("No counter if category is folded and without unread messages", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechat");
|
||||
await click(".o-mail-DiscussSidebarCategory-livechat .btn");
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechat .o-discuss-badge", { count: 0 });
|
||||
});
|
||||
|
||||
test("Counter should have correct value of unread threads if category is folded and with unread messages", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
livechat_member_type: "agent",
|
||||
partner_id: serverState.partnerId,
|
||||
}),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
author_guest_id: guestId,
|
||||
message_type: "comment",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
// first, close the live chat category
|
||||
await click(".o-mail-DiscussSidebarCategory-livechat .btn");
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechat .o-discuss-badge", { text: "1" });
|
||||
});
|
||||
|
||||
test("Close manually by clicking the title", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains(
|
||||
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel-container"
|
||||
);
|
||||
// fold the livechat category
|
||||
await click(".o-mail-DiscussSidebarCategory-livechat .btn");
|
||||
await contains(".o-mail-DiscussSidebarChannel", { count: 0 });
|
||||
});
|
||||
|
||||
test("Open manually by clicking the title", async () => {
|
||||
mockDate("2023-01-03 12:00:00");
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
last_interest_dt: "2021-01-01 10:00:00",
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({
|
||||
guest_id: guestId,
|
||||
last_interest_dt: "2021-01-01 10:00:00",
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
// first, close the live chat category
|
||||
await click(".o-mail-DiscussSidebarCategory-livechat .btn");
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechat");
|
||||
await contains(
|
||||
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel-container",
|
||||
{
|
||||
count: 0,
|
||||
}
|
||||
);
|
||||
// open the livechat category
|
||||
await click(".o-mail-DiscussSidebarCategory-livechat .btn");
|
||||
await contains(
|
||||
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel-container"
|
||||
);
|
||||
});
|
||||
|
||||
test("Category item should be invisible if the category is closed", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains(
|
||||
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel-container"
|
||||
);
|
||||
await click(".o-mail-DiscussSidebarCategory-livechat .btn");
|
||||
await contains(
|
||||
".o-mail-DiscussSidebarCategory-livechat + .o-mail-DiscussSidebarChannel-container",
|
||||
{
|
||||
count: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("Active category item should be visible even if the category is closed", async () => {
|
||||
mockDate("2023-01-03 12:00:00");
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
last_interest_dt: "2021-01-01 10:00:00",
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({
|
||||
guest_id: guestId,
|
||||
last_interest_dt: "2021-01-01 10:00:00",
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await click(".o-mail-DiscussSidebarChannel", { text: "Visitor 11" });
|
||||
await contains(".o-mail-DiscussSidebarChannel.o-active", { text: "Visitor 11" });
|
||||
await click(".o-mail-DiscussSidebarCategory-livechat .btn");
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "Visitor 11" });
|
||||
});
|
||||
|
||||
test("Clicking on leave button leaves the channel", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({
|
||||
guest_id: pyEnv["mail.guest"].create({ name: "Visitor 11" }),
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
create_uid: serverState.publicUserId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await contains(".o-mail-DiscussSidebarChannel", { text: "Visitor 11" });
|
||||
await click("[title='Chat Actions']");
|
||||
await click(".o-dropdown-item:contains('Leave Channel')");
|
||||
await click("button:contains(Leave Conversation)");
|
||||
await contains(".o-mail-DiscussSidebarChannel", { count: 0, text: "Visitor 11" });
|
||||
});
|
||||
|
||||
test("Message unread counter", async () => {
|
||||
mockDate("2023-01-03 12:00:00");
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 11" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
last_interest_dt: "2021-01-03 10:00:00",
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({
|
||||
guest_id: guestId,
|
||||
last_interest_dt: "2021-01-03 10:00:00",
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
withGuest(guestId, () =>
|
||||
rpc("/mail/message/post", {
|
||||
post_data: {
|
||||
body: "hu",
|
||||
message_type: "comment",
|
||||
subtype_xmlid: "mail.mt_comment",
|
||||
},
|
||||
thread_id: channelId,
|
||||
thread_model: "discuss.channel",
|
||||
})
|
||||
);
|
||||
await contains(".o-mail-DiscussSidebarChannel .badge", { text: "1" });
|
||||
});
|
||||
|
||||
test("unknown livechat can be displayed and interacted with", async () => {
|
||||
mockDate("2023-01-03 12:00:00");
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write(serverState.userId, { notification_type: "inbox" });
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Jane" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: partnerId,
|
||||
last_interest_dt: "2021-01-01 10:00:00",
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({
|
||||
guest_id: pyEnv["mail.guest"].create({ name: "Jane" }),
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: partnerId,
|
||||
create_uid: serverState.publicUserId,
|
||||
});
|
||||
const env = await start();
|
||||
env.services.bus_service.subscribe("discuss.channel/new_message", () =>
|
||||
asyncStep("discuss.channel/new_message")
|
||||
);
|
||||
await openDiscuss("mail.box_inbox");
|
||||
await contains("button.o-active", { text: "Inbox" });
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechat", { count: 0 });
|
||||
await contains(".o-mail-DiscussSidebarChannel", { count: 0 });
|
||||
await openDiscuss(channelId);
|
||||
await waitForChannels([`discuss.channel_${channelId}`]);
|
||||
await contains(".o-mail-DiscussSidebarChannel.o-active", { text: "Jane" });
|
||||
await insertText(".o-mail-Composer-input", "Hello", { replace: true });
|
||||
await press("Enter");
|
||||
await contains(".o-mail-Message", { text: "Hello" });
|
||||
await waitForSteps(["discuss.channel/new_message"]);
|
||||
await click("button", { text: "Inbox" });
|
||||
await contains(".o-mail-DiscussSidebarChannel:not(.o-active)", { text: "Jane" });
|
||||
await click("[title='Chat Actions']");
|
||||
await click(".o-dropdown-item:contains('Leave Channel')");
|
||||
await contains(".o-mail-DiscussSidebarCategory-livechat", { count: 0 });
|
||||
await contains(".o-mail-DiscussSidebarChannel", { count: 0 });
|
||||
});
|
||||
|
||||
test("Local sidebar category state is shared between tabs", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({
|
||||
guest_id: pyEnv["mail.guest"].create({ name: "Visitor #12" }),
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
livechat_operator_id: serverState.user,
|
||||
});
|
||||
const env1 = await start({ asTab: true });
|
||||
const env2 = await start({ asTab: true });
|
||||
await openDiscuss(undefined, { target: env1 });
|
||||
await openDiscuss(undefined, { target: env2 });
|
||||
await contains(`${env1.selector} .o-mail-DiscussSidebarCategory-livechat .oi-chevron-down`);
|
||||
await contains(`${env2.selector} .o-mail-DiscussSidebarCategory-livechat .oi-chevron-down`);
|
||||
await click(`${env1.selector} .o-mail-DiscussSidebarCategory-livechat .btn`);
|
||||
await contains(`${env1.selector} .o-mail-DiscussSidebarCategory-livechat .oi-chevron-right`);
|
||||
await contains(`${env2.selector} .o-mail-DiscussSidebarCategory-livechat .oi-chevron-right`);
|
||||
});
|
||||
|
||||
test("live chat is displayed below its category", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const livechatChannelId = pyEnv["im_livechat.channel"].create({ name: "Helpdesk" });
|
||||
browser.localStorage.setItem(
|
||||
`discuss_sidebar_category_im_livechat.category_${livechatChannelId}_open`,
|
||||
false
|
||||
);
|
||||
pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
livechat_channel_id: livechatChannelId,
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({
|
||||
guest_id: pyEnv["mail.guest"].create({ name: "Visitor #12" }),
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss();
|
||||
await click(".o-mail-DiscussSidebarCategory .btn", { text: "Helpdesk" });
|
||||
await contains(
|
||||
".o-mail-DiscussSidebarCategory:contains(Helpdesk) + .o-mail-DiscussSidebarChannel-container:contains(Visitor #12)"
|
||||
);
|
||||
});
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
import { describe, test } from "@odoo/hoot";
|
||||
import {
|
||||
click,
|
||||
contains,
|
||||
insertText,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { defineLivechatModels } from "./livechat_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("Suggestions are shown after delimiter was used in text (::)", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["mail.canned.response"].create({
|
||||
source: "hello",
|
||||
substitution: "Hello dear customer, how may I help you?",
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({
|
||||
partner_id: serverState.publicPartnerId,
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await insertText(".o-mail-Composer-input", "::");
|
||||
await contains(".o-mail-Composer-suggestion strong", { text: "hello" });
|
||||
await insertText(".o-mail-Composer-input", ")");
|
||||
await contains(".o-mail-Composer-suggestion strong", { count: 0 });
|
||||
await insertText(".o-mail-Composer-input", " ::");
|
||||
await contains(".o-mail-Composer-suggestion strong", { text: "hello" });
|
||||
});
|
||||
|
||||
test("Cannot mention other channels in a livechat", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [channelId] = pyEnv["discuss.channel"].create([
|
||||
{
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
partner_id: serverState.partnerId,
|
||||
livechat_member_type: "agent",
|
||||
}),
|
||||
Command.create({
|
||||
partner_id: serverState.publicPartnerId,
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
channel_type: "channel",
|
||||
group_public_id: false,
|
||||
name: "Link and Zelda",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await insertText(".o-mail-Composer-input", "#");
|
||||
await contains(".o-mail-Composer-suggestion", { count: 0 });
|
||||
});
|
||||
|
||||
test("Internal user mention shows their live chat username", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].write([serverState.partnerId], { user_livechat_username: "Batman" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({
|
||||
partner_id: serverState.publicPartnerId,
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
});
|
||||
pyEnv["res.users"]._applyComputesAndValidate();
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await insertText(".o-mail-Composer-input", "@");
|
||||
await click('.o-mail-Composer-suggestion:contains(Mitchell Admin "Batman")');
|
||||
await contains(".o-mail-Composer-input:value(@Batman)");
|
||||
await click(".o-mail-Composer button[title='Send']:enabled");
|
||||
await contains(".o-mail-Message a.o_mail_redirect", { text: "@Batman" });
|
||||
});
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import { contains, openDiscuss, start, startServer } from "@mail/../tests/mail_test_helpers";
|
||||
import { withGuest } from "@mail/../tests/mock_server/mail_mock_server";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { defineLivechatModels } from "./livechat_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("Public website visitor is typing", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor 20" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-DiscussContent-header .o-mail-ThreadIcon .fa.fa-circle-o");
|
||||
const channel = pyEnv["discuss.channel"].search_read([["id", "=", channelId]])[0];
|
||||
// simulate receive typing notification from livechat visitor "is typing"
|
||||
withGuest(guestId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
is_typing: true,
|
||||
channel_id: channel.id,
|
||||
})
|
||||
);
|
||||
await contains(".o-mail-DiscussContent-header .o-discuss-Typing-icon");
|
||||
await contains(
|
||||
".o-mail-DiscussContent-header .o-discuss-Typing-icon[title='Visitor 20 is typing...']"
|
||||
);
|
||||
});
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
insertText,
|
||||
openDiscuss,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { Command, serverState, withUser } from "@web/../tests/web_test_helpers";
|
||||
import { defineLivechatModels } from "./livechat_test_helpers";
|
||||
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { press } from "@odoo/hoot-dom";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
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({
|
||||
name: "James",
|
||||
user_ids: [userId],
|
||||
});
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor #20" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-DiscussContent-threadName[title='Visitor #20']");
|
||||
await click("button[title='Invite People']");
|
||||
await click("input", {
|
||||
parent: [".o-discuss-ChannelInvitation-selectable", { text: "James" }],
|
||||
});
|
||||
await click("button:enabled", { text: "Invite" });
|
||||
await contains(".o-discuss-ChannelInvitation", { count: 0 });
|
||||
await click("button[title='Members']");
|
||||
await contains(".o-discuss-ChannelMember", { text: "James" });
|
||||
await contains(".o-mail-DiscussContent-threadName[title='Visitor #20']");
|
||||
});
|
||||
|
||||
test("Can set a custom name to livechat conversation", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor #20" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-DiscussSidebar-item:contains('Visitor #20')");
|
||||
await contains(".o-mail-DiscussContent-threadName[title='Visitor #20']");
|
||||
await insertText(".o-mail-DiscussContent-threadName", "New Name", { replace: true });
|
||||
await triggerHotkey("Enter");
|
||||
await contains(".o-mail-DiscussContent-threadName[title='New Name']");
|
||||
await contains(".o-mail-DiscussSidebar-item:contains('New Name')");
|
||||
});
|
||||
|
||||
test("Display livechat custom username if defined", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].write(serverState.partnerId, {
|
||||
user_livechat_username: "livechat custom username",
|
||||
});
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor #20" });
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await insertText(".o-mail-Composer-input", "hello");
|
||||
await press("Enter");
|
||||
await contains(".o-mail-Message-author", { text: "livechat custom username" });
|
||||
});
|
||||
|
||||
test("Display livechat custom name in typing status", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const userId = pyEnv["res.users"].create({ name: "James" });
|
||||
const partnerId = pyEnv["res.partner"].create({
|
||||
name: "James",
|
||||
user_ids: [userId],
|
||||
user_livechat_username: "livechat custom username",
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_operator_id: partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await withUser(userId, () =>
|
||||
rpc("/discuss/channel/notify_typing", {
|
||||
channel_id: channelId,
|
||||
is_typing: true,
|
||||
})
|
||||
);
|
||||
await contains(".o-discuss-Typing", { text: "livechat custom username is typing..." });
|
||||
});
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("change_chatbot_step_type", {
|
||||
steps: () => [
|
||||
{
|
||||
content: "Open an existing script",
|
||||
trigger: ".o_field_cell[data-tooltip='Clear Answer Test Bot']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Open first step",
|
||||
trigger: '.o_row_draggable .o_field_cell:contains("Question")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Change step type to 'text'",
|
||||
trigger: 'div[name="step_type"] input',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.dropdown-item:contains("Text")',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Verify answers cleared",
|
||||
trigger: ".btn-primary:contains('Save')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_form_button_save",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
// Ensure form is properly saved, in which case the save button is hidden.
|
||||
trigger: ".o_form_button_save:not(:visible)",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -1,32 +1,32 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import tour from "web_tour.tour";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const requestChatSteps = [
|
||||
{
|
||||
trigger: ".o_livechat_button",
|
||||
trigger: ".o-livechat-root:shadow .o-livechat-LivechatButton",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_thread_window",
|
||||
trigger: ".o-livechat-root:shadow .o-mail-ChatWindow",
|
||||
},
|
||||
];
|
||||
|
||||
tour.register("im_livechat_request_chat", { test: true }, requestChatSteps);
|
||||
registry.category("web_tour.tours").add("im_livechat_request_chat", {
|
||||
steps: () => requestChatSteps,
|
||||
});
|
||||
|
||||
tour.register("im_livechat_request_chat_and_send_message", { test: true }, [
|
||||
...requestChatSteps,
|
||||
{
|
||||
trigger: ".o_composer_text_field",
|
||||
run: "text Hello, I need help please !",
|
||||
},
|
||||
{
|
||||
trigger: '.o_composer_text_field',
|
||||
run() {
|
||||
$(".o_composer_text_field").trigger($.Event("keydown", { which: 13 }));
|
||||
registry.category("web_tour.tours").add("im_livechat_request_chat_and_send_message", {
|
||||
steps: () => [
|
||||
...requestChatSteps,
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow .o-mail-Composer-input",
|
||||
run: "edit Hello, I need help please !",
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_thread_message:contains('Hello, I need help')",
|
||||
},
|
||||
]);
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow .o-mail-Composer-input",
|
||||
run: "press Enter",
|
||||
},
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow .o-mail-Message:contains('Hello, I need help')",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,130 +1,99 @@
|
|||
/** @odoo-module */
|
||||
import { registry } from "@web/core/registry";
|
||||
import { stepUtils } from "@web_tour/tour_utils";
|
||||
|
||||
import tour from "web_tour.tour";
|
||||
|
||||
const commonSteps = [tour.stepUtils.showAppsMenuItem(), {
|
||||
trigger: '.o_app[data-menu-xmlid="im_livechat.menu_livechat_root"]',
|
||||
}, {
|
||||
trigger: 'button[data-menu-xmlid="im_livechat.livechat_config"]',
|
||||
}, {
|
||||
trigger: 'a[data-menu-xmlid="im_livechat.chatbot_config"]',
|
||||
}, {
|
||||
trigger: '.o_list_button_add',
|
||||
}, {
|
||||
trigger: 'input[id="title"]',
|
||||
run: 'text Test Chatbot Sequence'
|
||||
}, {
|
||||
trigger: 'div[name="script_step_ids"] .o_field_x2many_list_row_add a'
|
||||
}, {
|
||||
trigger: 'textarea#message',
|
||||
run: 'text Step 1'
|
||||
}, {
|
||||
trigger: 'button:contains("Save & New")'
|
||||
}, {
|
||||
trigger: 'tr:contains("Step 1")',
|
||||
in_modal: false,
|
||||
run: () => {}
|
||||
}, {
|
||||
trigger: 'textarea#message',
|
||||
run: 'text Step 2'
|
||||
}, {
|
||||
trigger: 'button:contains("Save & New")'
|
||||
}, {
|
||||
trigger: 'tr:contains("Step 2")',
|
||||
in_modal: false,
|
||||
run: () => {}
|
||||
}, {
|
||||
trigger: 'textarea#message',
|
||||
run: 'text Step 3'
|
||||
}];
|
||||
function createChatbotSteps(...stepMessages) {
|
||||
return [
|
||||
{
|
||||
trigger: "div[name='script_step_ids'] .o_field_x2many_list_row_add a",
|
||||
run: "click",
|
||||
},
|
||||
...stepMessages
|
||||
.map((message) => [
|
||||
{
|
||||
trigger: ".modal .odoo-editor-editable",
|
||||
run: `editor ${message}`,
|
||||
},
|
||||
{
|
||||
trigger: `.modal .odoo-editor-editable:contains(${message})`,
|
||||
},
|
||||
{
|
||||
trigger: ".modal button:contains(Save & New)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: `tr:contains(${message})`,
|
||||
},
|
||||
{
|
||||
trigger: ".modal .odoo-editor-editable:empty",
|
||||
},
|
||||
])
|
||||
.flat(),
|
||||
{
|
||||
trigger: ".modal-footer button:contains(Discard)",
|
||||
run: "click",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const commonSteps = [
|
||||
stepUtils.showAppsMenuItem(),
|
||||
{
|
||||
trigger: '.o_app[data-menu-xmlid="im_livechat.menu_livechat_root"]',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: 'button[data-menu-xmlid="im_livechat.livechat_config"]',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: 'a[data-menu-xmlid="im_livechat.chatbot_config"]',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_list_button_add",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: 'input[id="title_0"]',
|
||||
run: "edit Test Chatbot Sequence",
|
||||
},
|
||||
...createChatbotSteps("Step 1", "Step 2", "Step 3"),
|
||||
];
|
||||
|
||||
/**
|
||||
* Simply create a few steps in order to check the sequences.
|
||||
*/
|
||||
tour.register('im_livechat_chatbot_steps_sequence_tour', {
|
||||
test: true,
|
||||
url: '/web',
|
||||
}, [
|
||||
...commonSteps, {
|
||||
trigger: 'button:contains("Save & Close")'
|
||||
}, {
|
||||
trigger: 'body.o_web_client:not(.modal-open)',
|
||||
run() {},
|
||||
}, ...tour.stepUtils.discardForm()
|
||||
]);
|
||||
registry.category("web_tour.tours").add("im_livechat_chatbot_steps_sequence_tour", {
|
||||
url: "/odoo",
|
||||
steps: () => [
|
||||
...commonSteps,
|
||||
{
|
||||
trigger: "body.o_web_client:not(.modal-open)",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* Same as above, with an extra drag&drop at the end.
|
||||
*/
|
||||
tour.register('im_livechat_chatbot_steps_sequence_with_move_tour', {
|
||||
test: true,
|
||||
url: '/web',
|
||||
}, [
|
||||
...commonSteps, {
|
||||
trigger: 'button:contains("Save & New")'
|
||||
}, {
|
||||
trigger: 'tr:contains("Step 3")',
|
||||
in_modal: false,
|
||||
run: () => {}
|
||||
}, {
|
||||
trigger: 'textarea#message',
|
||||
run: 'text Step 4'
|
||||
}, {
|
||||
trigger: 'button:contains("Save & New")'
|
||||
}, {
|
||||
trigger: 'tr:contains("Step 4")',
|
||||
in_modal: false,
|
||||
run: () => {}
|
||||
}, {
|
||||
trigger: 'textarea#message',
|
||||
run: 'text Step 5'
|
||||
}, {
|
||||
trigger: 'button:contains("Save & Close")'
|
||||
}, {
|
||||
trigger: 'body.o_web_client:not(.modal-open)',
|
||||
run: () => {}
|
||||
}, {
|
||||
trigger: 'tr:contains("Step 5") .o_row_handle',
|
||||
run: () => {
|
||||
// move 'step 5' between 'step 1' and 'step 2'
|
||||
const from = document.querySelector('div[name="script_step_ids"] tr:nth-child(5) .o_row_handle');
|
||||
const fromPosition = from.getBoundingClientRect();
|
||||
fromPosition.x += from.offsetWidth / 2;
|
||||
fromPosition.y += from.offsetHeight / 2;
|
||||
|
||||
const to = document.querySelector('div[name="script_step_ids"] tr:nth-child(2) .o_row_handle');
|
||||
from.dispatchEvent(new Event("mouseenter", { bubbles: true }));
|
||||
from.dispatchEvent(new MouseEvent("mousedown", {
|
||||
bubbles: true,
|
||||
which: 1,
|
||||
button: 0,
|
||||
clientX: fromPosition.x,
|
||||
clientY: fromPosition.y}));
|
||||
from.dispatchEvent(new MouseEvent("mousemove", {
|
||||
bubbles: true,
|
||||
which: 1,
|
||||
button: 0,
|
||||
// dragging is only enabled when the mouse have moved from at least 10 pixels from the original position
|
||||
clientX: fromPosition.x + 20,
|
||||
clientY: fromPosition.y + 20,
|
||||
}));
|
||||
to.dispatchEvent(new Event("mouseenter", { bubbles: true }));
|
||||
from.dispatchEvent(new Event("mouseup", { bubbles: true }));
|
||||
}
|
||||
}, {
|
||||
trigger: 'div[name="script_step_ids"] .o_field_x2many_list_row_add a'
|
||||
}, {
|
||||
trigger: 'textarea#message',
|
||||
run: 'text Step 6'
|
||||
}, {
|
||||
trigger: 'button:contains("Save & Close")'
|
||||
}, {
|
||||
trigger: 'body.o_web_client:not(.modal-open)',
|
||||
run: () => {}
|
||||
}, {
|
||||
trigger: 'tr:contains("Step 6")',
|
||||
in_modal: false,
|
||||
run: () => {}
|
||||
}, ...tour.stepUtils.discardForm(),
|
||||
]);
|
||||
registry.category("web_tour.tours").add("im_livechat_chatbot_steps_sequence_with_move_tour", {
|
||||
url: "/odoo",
|
||||
steps: () => [
|
||||
...commonSteps,
|
||||
...createChatbotSteps("Step 4", "Step 5"),
|
||||
{
|
||||
trigger: "body.o_web_client:not(.modal-open)",
|
||||
},
|
||||
{
|
||||
trigger: 'div[name="script_step_ids"] tr:nth-child(5) .o_row_handle',
|
||||
run: 'drag_and_drop(div[name="script_step_ids"] tr:nth-child(2))',
|
||||
},
|
||||
...createChatbotSteps("Step 6"),
|
||||
{
|
||||
trigger: "body.o_web_client:not(.modal-open)",
|
||||
},
|
||||
{
|
||||
trigger: 'tr:contains("Step 6")',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import { delay } from "@web/core/utils/concurrency";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("im_livechat_history_back_and_forth_tour", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: "button.o_switch_view.o_list",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_data_cell:contains(Visitor)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-mail-DiscussContent-threadName[title='Visitor']",
|
||||
async run() {
|
||||
await delay(1000);
|
||||
history.back();
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_data_cell:contains(Visitor)",
|
||||
async run() {
|
||||
await delay(0);
|
||||
history.forward();
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o-mail-DiscussContent-threadName[title='Visitor']",
|
||||
async run() {
|
||||
await delay(1000);
|
||||
history.back();
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_data_cell:contains(Visitor)",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
|
||||
/**
|
||||
* @param {"list" | "kanban"} viewType
|
||||
* @returns {import("@web_tour/tour_service/tour_service").TourStep[]}
|
||||
*/
|
||||
function getSteps(viewType) {
|
||||
let bobChatId;
|
||||
return [
|
||||
{
|
||||
trigger: ".o_control_panel .active:contains(Looking for Help)",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
viewType === "list"
|
||||
? ".o_list_table:has(.o_data_row:contains(bob_looking_for_help))"
|
||||
: ".o_kanban_renderer:has(.o_kanban_record [name=livechat_agent_partner_ids] [aria-label^=bob_looking_for_help])",
|
||||
async run() {
|
||||
const { orm } = odoo.__WOWL_DEBUG__.root.env.services;
|
||||
[bobChatId] = await orm.search("discuss.channel", [
|
||||
["livechat_status", "=", "need_help"],
|
||||
["livechat_agent_partner_ids.name", "like", "bob_looking_for_help%"],
|
||||
]);
|
||||
await orm.write("discuss.channel", [bobChatId], {
|
||||
livechat_status: "in_progress",
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
viewType === "list"
|
||||
? ".o_list_table:not(:has(.o_data_row:contains(bob_looking_for_help)))"
|
||||
: ".o_kanban_renderer:not(:has(.o_kanban_record [name=livechat_agent_partner_ids] [aria-label^=bob_looking_for_help]))",
|
||||
async run() {
|
||||
const { orm } = odoo.__WOWL_DEBUG__.root.env.services;
|
||||
await orm.write("discuss.channel", [bobChatId], {
|
||||
livechat_status: "need_help",
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
viewType === "list"
|
||||
? ".o_list_table:has(.o_data_row:contains(bob_looking_for_help))"
|
||||
: ".o_kanban_renderer:has(.o_kanban_record [name=livechat_agent_partner_ids] [aria-label^=bob_looking_for_help])",
|
||||
},
|
||||
];
|
||||
}
|
||||
registry.category("web_tour.tours").add("im_livechat.looking_for_help_list_real_time_update_tour", {
|
||||
steps: () => getSteps("list"),
|
||||
});
|
||||
registry
|
||||
.category("web_tour.tours")
|
||||
.add("im_livechat.looking_for_help_kanban_real_time_update_tour", {
|
||||
steps: () => getSteps("kanban"),
|
||||
});
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
|
||||
let bobChatId;
|
||||
let tagId;
|
||||
registry.category("web_tour.tours").add("im_livechat.looking_for_help_tags_real_time_update_tour", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o_control_panel .active:contains(Looking for Help)",
|
||||
},
|
||||
{
|
||||
trigger: ".o_optional_columns_dropdown_toggle",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: '.o-dropdown-item input[name="livechat_conversation_tag_ids"]',
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_optional_columns_dropdown_toggle",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_list_table:has(.o_data_row:contains(bob_looking_for_help))",
|
||||
},
|
||||
{
|
||||
trigger: '.o_data_cell[name="livechat_conversation_tag_ids"]:not(:has(.o_tag))',
|
||||
async run() {
|
||||
const { orm } = odoo.__WOWL_DEBUG__.root.env.services;
|
||||
[bobChatId] = await orm.search("discuss.channel", [
|
||||
["livechat_status", "=", "need_help"],
|
||||
["livechat_agent_partner_ids.name", "like", "bob_looking_for_help%"],
|
||||
]);
|
||||
[tagId] = await orm.create("im_livechat.conversation.tag", [{ name: "Discuss" }]);
|
||||
// Simulate other user adding a tag
|
||||
await rpc("/im_livechat/conversation/update_tags", {
|
||||
channel_id: bobChatId,
|
||||
tag_ids: [tagId],
|
||||
method: "ADD",
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
'.o_data_cell[name="livechat_conversation_tag_ids"]:has(.o_tag:contains(Discuss))',
|
||||
async run() {
|
||||
// Simulate other user removing a tag
|
||||
await rpc("/im_livechat/conversation/update_tags", {
|
||||
channel_id: bobChatId,
|
||||
tag_ids: [tagId],
|
||||
method: "DELETE",
|
||||
});
|
||||
},
|
||||
},
|
||||
{ trigger: '.o_data_cell[name="livechat_conversation_tag_ids"]:not(:has(.o_tag))' },
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
|
||||
function makePivotRedirectTourSteps(singleRecordName, multiRecordName) {
|
||||
return [
|
||||
{
|
||||
content: "Click on a cell with a single related record",
|
||||
trigger: `.o_pivot table tbody tr:has(th:contains(${singleRecordName})) td:eq(0)`,
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-mail-Discuss",
|
||||
content: "Verify redirection to the single record view",
|
||||
},
|
||||
{
|
||||
content: "Go back to the pivot view",
|
||||
trigger: ".o_back_button",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
content: "Click on a cell with a multiple related records",
|
||||
trigger: `.o_pivot table tbody tr:has(th:contains(${multiRecordName})) td:eq(0)`,
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_list_view",
|
||||
content: "Verify redirection to the list view for multiple records",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
registry.category("web_tour.tours").add("im_livechat_agents_report_pivot_redirect_tour", {
|
||||
steps: () => makePivotRedirectTourSteps("test 1", "test 2"),
|
||||
});
|
||||
|
||||
registry.category("web_tour.tours").add("im_livechat_sessions_report_pivot_redirect_tour", {
|
||||
steps: () => makePivotRedirectTourSteps("operator_1", "operator_2"),
|
||||
});
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import { whenReady } from "@odoo/owl";
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { patchWithCleanup } from "@web/../tests/helpers/utils";
|
||||
|
||||
let firstChannelId;
|
||||
registry.category("web_tour.tours").add("im_livechat_session_history_open", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: "body",
|
||||
async run() {
|
||||
await whenReady();
|
||||
const busService = odoo.__WOWL_DEBUG__.root.env.services.bus_service;
|
||||
patchWithCleanup(busService, {
|
||||
addChannel(channel) {
|
||||
document.body.classList.add(`o-bus-channel-${channel}`);
|
||||
return super.addChannel(...arguments);
|
||||
},
|
||||
deleteChannel(channel) {
|
||||
document.body.classList.remove(`o-bus-channel-${channel}`);
|
||||
return super.deleteChannel(...arguments);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".o_switch_view[data-tooltip='List']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o_data_cell:contains('test 2')",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-mail-Message-content:contains('Test Channel 2 Msg')",
|
||||
async run({ waitFor }) {
|
||||
firstChannelId =
|
||||
odoo.__WOWL_DEBUG__.root.env.services.action.currentController.state.resId;
|
||||
await waitFor(`body.o-bus-channel-discuss\\.channel_${firstChannelId}`, {
|
||||
timeout: 3000,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
trigger: ".oi-chevron-right",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-mail-Message-content:contains('Test Channel 1 Msg')",
|
||||
async run({ waitFor }) {
|
||||
await waitFor(`body:not(.o-bus-channel-discuss\\.channel_${firstChannelId})`, {
|
||||
timeout: 3000,
|
||||
});
|
||||
const channelId =
|
||||
odoo.__WOWL_DEBUG__.root.env.services.action.currentController.state.resId;
|
||||
await waitFor(`body.o-bus-channel-discuss\\.channel_${channelId}`, {
|
||||
trimeout: 3000,
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("im_livechat.looking_for_help_discuss_category_tour", {
|
||||
steps: () => [
|
||||
{
|
||||
// Two live chats are looking for help, they are both in the "Looking for help" category.
|
||||
trigger:
|
||||
".o-mail-DiscussSidebarCategory-livechatNeedHelp + .o-mail-DiscussSidebarChannel-container:contains(Visitor Accounting) + .o-mail-DiscussSidebarChannel-container:contains(Visitor Sales)",
|
||||
},
|
||||
{
|
||||
trigger: ".o-mail-DiscussSidebarChannel:contains(Sales) .o-mail-starred",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".o-mail-DiscussSidebarChannel:contains(Accounting):not(:has(.o-mail-starred))",
|
||||
},
|
||||
{
|
||||
trigger: ".o-mail-DiscussSidebarChannel:contains(Accounting)",
|
||||
run: "hover && click [title='Chat Actions']",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".o-mail-DiscussSidebar:has(.o-mail-DiscussSidebarChannel:contains(Accounting))",
|
||||
},
|
||||
{
|
||||
trigger: "button[name='livechat-status']",
|
||||
run: "hover",
|
||||
},
|
||||
{
|
||||
trigger: ".o-livechat-LivechatStatusSelection-Label:contains(In progress)",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".o-mail-DiscussSidebar:not(:has(.o-mail-DiscussSidebarChannel:contains(Accounting)))",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("im_livechat.basic_tour", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".channel_name:contains(Support Channel)",
|
||||
},
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow .o-livechat-LivechatButton",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow .o-mail-ChatWindow",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
|
||||
registry.category("web_tour.tours").add("im_livechat.meeting_view_tour", {
|
||||
steps: () => [
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow .o-livechat-LivechatButton",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow .o-mail-Thread[data-transient]",
|
||||
},
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow .o-mail-Composer-input",
|
||||
run: "edit Hello!",
|
||||
},
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow .o-mail-Composer-input",
|
||||
run: "press Enter",
|
||||
},
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow [title='Join Call']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow .o-discuss-Call [title='Fullscreen']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow .o-mail-Meeting",
|
||||
},
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow .o-mail-MeetingSideActions [name^='more-action:'] ",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger: ".o-livechat-root:shadow [name='call-settings']",
|
||||
run: "click",
|
||||
},
|
||||
{
|
||||
trigger:
|
||||
".o-livechat-root:shadow .o-mail-DiscussContent-panelContainer .o-mail-ActionPanel-header:contains('voice settings')",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import { defineLivechatModels } from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import { contains, focus, openDiscuss, start, startServer } from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { press } from "@odoo/hoot-dom";
|
||||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { serializeDate, today } from "@web/core/l10n/dates";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("agent can send conversation after livechat ends", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const demoPartnerId = pyEnv["res.partner"].create({
|
||||
name: "Awesome partner",
|
||||
email: "awesome@example.com",
|
||||
});
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ partner_id: demoPartnerId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
channel_type: "livechat",
|
||||
livechat_end_dt: serializeDate(today()),
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await focus("input[placeholder='mail@example.com']", { value: "awesome@example.com" });
|
||||
await press("Enter");
|
||||
await contains(".form-text", { text: "The conversation was sent." });
|
||||
});
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import { describe, test } from "@odoo/hoot";
|
||||
import { click, contains, openDiscuss, start, startServer } from "@mail/../tests/mail_test_helpers";
|
||||
import { Command, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { defineLivechatModels } from "./livechat_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("message translation in livechat (agent is member)", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({
|
||||
guest_id: pyEnv["mail.guest"].create({ name: "Mario" }),
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
body: "Mai mettere l'ananas sulla pizza!",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message");
|
||||
await click("[title='Expand']");
|
||||
await contains(".o-dropdown-item:contains('Translate')");
|
||||
});
|
||||
|
||||
test("message translation in livechat (agent is not member)", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const channelId = pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({
|
||||
guest_id: pyEnv["mail.guest"].create({ name: "Mario" }),
|
||||
livechat_member_type: "visitor",
|
||||
}),
|
||||
],
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
body: "Mai mettere l'ananas sulla pizza!",
|
||||
model: "discuss.channel",
|
||||
res_id: channelId,
|
||||
});
|
||||
await start();
|
||||
await openDiscuss(channelId);
|
||||
await contains(".o-mail-Message");
|
||||
await click("[title='Expand']");
|
||||
await contains(".o-dropdown-item:contains('Translate')");
|
||||
});
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
import { Command, patchWithCleanup, serverState } from "@web/../tests/web_test_helpers";
|
||||
import { defineLivechatModels } from "@im_livechat/../tests/livechat_test_helpers";
|
||||
import {
|
||||
contains,
|
||||
setupChatHub,
|
||||
start,
|
||||
startServer,
|
||||
click,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { mockDate } from "@odoo/hoot-mock";
|
||||
import { Store } from "@mail/core/common/store_service";
|
||||
import { Thread } from "@mail/core/common/thread";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineLivechatModels();
|
||||
|
||||
test("Visitor going offline shows disconnection banner to operator", async () => {
|
||||
patchWithCleanup(Store, { IM_STATUS_DEBOUNCE_DELAY: 0 });
|
||||
patchWithCleanup(Thread.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.IM_STATUS_DELAY = 0;
|
||||
},
|
||||
});
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.users"].write([serverState.userId], {
|
||||
group_ids: pyEnv["res.groups"]
|
||||
.search_read([["id", "=", serverState.groupLivechatId]])
|
||||
.map(({ id }) => id),
|
||||
});
|
||||
const guestId = pyEnv["mail.guest"].create({ name: "Visitor", im_status: "online" });
|
||||
const livechatChannelId = pyEnv["im_livechat.channel"].create({
|
||||
name: "HR",
|
||||
user_ids: [serverState.userId],
|
||||
});
|
||||
const channel_id = pyEnv["discuss.channel"].create({
|
||||
channel_type: "livechat",
|
||||
channel_member_ids: [
|
||||
Command.create({ partner_id: serverState.partnerId, livechat_member_type: "agent" }),
|
||||
Command.create({ guest_id: guestId, livechat_member_type: "visitor" }),
|
||||
],
|
||||
livechat_channel_id: livechatChannelId,
|
||||
livechat_operator_id: serverState.partnerId,
|
||||
create_uid: serverState.publicUserId,
|
||||
});
|
||||
setupChatHub({ opened: [channel_id] });
|
||||
await start();
|
||||
await contains(".o-mail-ChatWindow");
|
||||
mockDate("2025-01-01 12:00:00", +1);
|
||||
pyEnv["mail.guest"].write(guestId, { im_status: "offline" });
|
||||
pyEnv["bus.bus"]._sendone(guestId, "bus.bus/im_status_updated", {
|
||||
partner_id: false,
|
||||
guest_id: guestId,
|
||||
im_status: "offline",
|
||||
});
|
||||
await contains(".o-livechat-VisitorDisconnected", {
|
||||
text: "Visitor is disconnected since 1:00 PM",
|
||||
});
|
||||
mockDate("2025-01-02 12:00:00", +1);
|
||||
await click("button[title*='Fold']");
|
||||
await click(".o-mail-ChatBubble");
|
||||
await contains(".o-livechat-VisitorDisconnected", {
|
||||
text: "Visitor is disconnected since yesterday at 1:00 PM",
|
||||
});
|
||||
mockDate("2025-01-05 12:00:00", +1);
|
||||
await click("button[title*='Fold']");
|
||||
await click(".o-mail-ChatBubble");
|
||||
await contains(".o-livechat-VisitorDisconnected", { text: `Visitor is disconnected` });
|
||||
pyEnv["bus.bus"]._sendone(guestId, "bus.bus/im_status_updated", {
|
||||
partner_id: false,
|
||||
guest_id: guestId,
|
||||
im_status: "online",
|
||||
});
|
||||
await contains(".o-livechat-VisitorDisconnected", { count: 0 });
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue