19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:30:27 +01:00
parent d1963a3c3a
commit 2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions

View file

@ -0,0 +1,20 @@
import { defineMailModels, start } from "@mail/../tests/mail_test_helpers";
import { describe, expect, test } from "@odoo/hoot";
import { getService } from "@web/../tests/web_test_helpers";
describe.current.tags("desktop");
defineMailModels();
test("Attachment model properties", async () => {
await start();
const attachment = getService("mail.store")["ir.attachment"].insert({
id: 750,
mimetype: "text/plain",
name: "test.txt",
});
expect(attachment.isText).toBe(true);
expect(attachment.isViewable).toBe(true);
expect(attachment.mimetype).toBe("text/plain");
expect(attachment.name).toBe("test.txt");
expect(attachment.extension).toBe("txt");
});

View file

@ -0,0 +1,17 @@
import { Action } from "@mail/core/common/action";
import { describe, expect, test } from "@odoo/hoot";
describe.current.tags("desktop");
test("store is correctly set on actions", async () => {
const storeSym = Symbol("STORE");
const ownerSym = Symbol("COMPONENT");
const action = new Action({
owner: ownerSym,
id: "test",
definition: {},
store: storeSym,
});
expect(action.store).toBe(storeSym);
});

View file

@ -0,0 +1,91 @@
import {
click,
contains,
defineMailModels,
insertText,
openFormView,
start,
startServer,
} from "@mail/../tests/mail_test_helpers";
import { describe, test } from "@odoo/hoot";
import { animationFrame } from "@odoo/hoot-dom";
import { getOrigin } from "@web/core/utils/urls";
describe.current.tags("desktop");
defineMailModels();
test("following internal link from chatter does not open chat window", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Jeanne" });
pyEnv["mail.message"].create({
body: `Created by <a href="#" data-oe-model="res.partner" data-oe-id="${pyEnv.user.partner_id}">Admin</a>`,
model: "res.partner",
res_id: partnerId,
});
await start();
await openFormView("res.partner", partnerId);
await contains(".o_last_breadcrumb_item", { text: "Jeanne" });
await click("a", { text: "Admin" });
await contains(".o_last_breadcrumb_item", { text: "Mitchell Admin" });
// Assert 0 chat windows not sufficient because not enough time for potential chat window opening.
// Let's open another chat window to give some time and assert only manually open chat window opens.
await contains(".o-mail-ChatWindow", { count: 0 });
await click(".o_menu_systray i[aria-label='Messages']");
await click("button", { text: "New Message" });
await insertText("input[placeholder='Search a conversation']", "abc");
await click("a", { text: "Create Channel" });
await contains(".o-mail-ChatWindow-header", { text: "abc" });
await contains(".o-mail-ChatWindow", { count: 1 });
});
test("message link shows error when the message is not known", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Alice" });
const url = `${getOrigin()}/mail/message/999999`;
pyEnv["mail.message"].create({
body: `Check this out <a class="o_message_redirect" href="${url}" data-oe-model="mail.message" data-oe-id="999999">${url}</a>`,
model: "res.partner",
res_id: partnerId,
});
await start();
await openFormView("res.partner", partnerId);
await click("a.o_message_redirect");
await contains(".o_notification:contains(This conversation isnt available.)");
});
test("same-thread message link does not open the thread again but highlights the message", async () => {
const pyEnv = await startServer();
const [aliceId, lenaId] = pyEnv["res.partner"].create([{ name: "Alice" }, { name: "Lena" }]);
const helloMessageId = pyEnv["mail.message"].create({
body: "Hello",
model: "res.partner",
res_id: aliceId,
});
const heyMessageId = pyEnv["mail.message"].create({
body: "Hey",
model: "res.partner",
res_id: lenaId,
});
const helloUrl = `${getOrigin()}/mail/message/${helloMessageId}`;
pyEnv["mail.message"].create({
body: `Check this out <a class="o_message_redirect" href="${helloUrl}" data-oe-model="mail.message" data-oe-id="${helloMessageId}">${helloUrl}</a>`,
model: "res.partner",
res_id: aliceId,
});
const heyUrl = `${getOrigin()}/mail/message/${heyMessageId}`;
pyEnv["mail.message"].create({
body: `Another thread <a class="o_message_redirect" href="${heyUrl}" data-oe-model="mail.message" data-oe-id="${heyMessageId}">${heyUrl}</a>`,
model: "res.partner",
res_id: aliceId,
});
await start();
await openFormView("res.partner", aliceId);
await click("a.o_message_redirect:contains(Alice)");
await contains(".o-mail-Message.o-highlighted:contains(Hello)");
await animationFrame(); // give enough time for the potential breadcrumb item to render
await contains(".breadcrumb-item", { count: 0 });
await click("a.o_message_redirect:contains(Lena)");
await contains(".o-mail-Message.o-highlighted:contains(Hey)");
await contains(".breadcrumb-item:contains(Alice)");
});

View file

@ -0,0 +1,81 @@
import { Message } from "@mail/core/common/message_model";
import { defineMailModels, start } from "@mail/../tests/mail_test_helpers";
import { expect, test } from "@odoo/hoot";
import {
asyncStep,
getService,
patchWithCleanup,
waitForSteps,
} from "@web/../tests/web_test_helpers";
defineMailModels();
test("store.insert can delete record", async () => {
await start();
const store = getService("mail.store");
store.insert({ "mail.message": [{ id: 1 }] });
expect(store["mail.message"].get({ id: 1 })?.id).toBe(1);
store.insert({ "mail.message": [{ id: 1, _DELETE: true }] });
expect(store["mail.message"].get({ id: 1 })?.id).toBe(undefined);
});
test("store.insert deletes record without creating it", async () => {
patchWithCleanup(Message, {
new() {
const message = super.new(...arguments);
asyncStep(`new-${message.id}`);
return message;
},
});
await start();
const store = getService("mail.store");
store.insert({ "mail.message": [{ id: 1, _DELETE: true }] });
await waitForSteps([]);
expect(store["mail.message"].get({ id: 1 })?.id).toBe(undefined);
store.insert({ "mail.message": [{ id: 2 }] });
await waitForSteps(["new-2"]);
});
test("store.insert deletes record after relation created it", async () => {
patchWithCleanup(Message, {
new() {
const message = super.new(...arguments);
asyncStep(`new-${message.id}`);
return message;
},
});
await start();
const store = getService("mail.store");
store.insert({
"mail.message": [{ id: 1, _DELETE: true }],
// they key coverage of the test is to have the relation listed after the delete
"mail.link.preview": [{ id: 1 }],
"mail.message.link.preview": [{ id: 1, link_preview_id: 1, message_id: 1 }],
});
await waitForSteps(["new-1"]);
expect(store["mail.message"].get({ id: 1 })?.id).toBe(undefined);
});
test("store.insert different PY model having same JS model", async () => {
await start();
const store = getService("mail.store");
const data = {
"discuss.channel": [
{ id: 1, name: "General" },
{ id: 2, name: "Sales" },
],
"mail.thread": [
{ id: 1, model: "discuss.channel" },
{ id: 3, name: "R&D", model: "discuss.channel" },
],
};
store.insert(data);
expect(store.Thread.records).toHaveLength(6); // 3 mailboxes + 3 channels
expect(Boolean(store.Thread.get({ id: 1, model: "discuss.channel" }))).toBe(true);
expect(Boolean(store.Thread.get({ id: 2, model: "discuss.channel" }))).toBe(true);
expect(Boolean(store.Thread.get({ id: 3, model: "discuss.channel" }))).toBe(true);
});

View file

@ -0,0 +1,47 @@
import { defineMailModels, start } from "@mail/../tests/mail_test_helpers";
import { describe, expect, test } from "@odoo/hoot";
import { markup } from "@odoo/owl";
import { deserializeDateTime, serializeDateTime } from "@web/core/l10n/dates";
import { getService, serverState } from "@web/../tests/web_test_helpers";
describe.current.tags("desktop");
defineMailModels();
test("Message model properties", async () => {
await start();
const store = getService("mail.store");
store.Store.insert({
self_partner: { id: serverState.partnerId },
});
store.Thread.insert({
id: serverState.partnerId,
model: "res.partner",
name: "general",
});
store["ir.attachment"].insert({
id: 750,
mimetype: "text/plain",
name: "test.txt",
});
const message = store["mail.message"].insert({
attachment_ids: 750,
author_id: { id: 5, name: "Demo" },
body: markup`<p>Test</p>`,
date: deserializeDateTime("2019-05-05 10:00:00"),
id: 4000,
starred: true,
model: "res.partner",
thread: { id: serverState.partnerId, model: "res.partner" },
res_id: serverState.partnerId,
});
expect(message.body?.toString()).toBe("<p>Test</p>");
expect(serializeDateTime(message.date)).toBe("2019-05-05 10:00:00");
expect(message.id).toBe(4000);
expect(message.attachment_ids[0].name).toBe("test.txt");
expect(message.thread.id).toBe(serverState.partnerId);
expect(message.thread.name).toBe("general");
expect(message.author_id.id).toBe(5);
expect(message.author_id.name).toBe("Demo");
});

View file

@ -0,0 +1,381 @@
import { waitNotifications } from "@bus/../tests/bus_test_helpers";
import {
click,
contains,
defineMailModels,
insertText,
listenStoreFetch,
openDiscuss,
openFormView,
setupChatHub,
start,
startServer,
triggerHotkey,
waitStoreFetch,
} from "@mail/../tests/mail_test_helpers";
import { describe, test } from "@odoo/hoot";
import { click as hootClick, press, queryFirst } from "@odoo/hoot-dom";
import { mockDate } from "@odoo/hoot-mock";
import { Command, serverState, withUser } from "@web/../tests/web_test_helpers";
import { rpc } from "@web/core/network/rpc";
describe.current.tags("desktop");
defineMailModels();
test("keep new message separator when message is deleted", async () => {
const pyEnv = await startServer();
const generalId = pyEnv["discuss.channel"].create({ name: "General" });
pyEnv["mail.message"].create([
{
body: "message 0",
message_type: "comment",
model: "discuss.channel",
author_id: serverState.partnerId,
res_id: generalId,
},
{
body: "message 1",
message_type: "comment",
model: "discuss.channel",
author_id: serverState.partnerId,
res_id: generalId,
},
]);
await start();
await openDiscuss(generalId);
await contains(".o-mail-Message", { count: 2 });
queryFirst(".o-mail-Composer-input").blur();
await click("[title='Expand']", {
parent: [".o-mail-Message", { text: "message 0" }],
});
await click(".o-dropdown-item:contains('Mark as Unread')");
await contains(".o-mail-Thread-newMessage ~ .o-mail-Message", { text: "message 0" });
await click("[title='Expand']", {
parent: [".o-mail-Message", { text: "message 0" }],
});
await click(".o-dropdown-item:contains('Delete')");
await click(".modal button", { text: "Delete" });
await contains(".o-mail-Message", { text: "message 0", count: 0 });
await contains(".o-mail-Thread-newMessage ~ .o-mail-Message", { text: "message 1" });
});
test("new message separator is not shown if all messages are new", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const bobPartnerId = pyEnv["res.partner"].create({ name: "Bob" });
for (let i = 0; i < 5; i++) {
pyEnv["mail.message"].create({
author_id: bobPartnerId,
body: `message ${i}`,
message_type: "comment",
model: "discuss.channel",
res_id: channelId,
});
}
await start();
await openDiscuss(channelId);
await contains(".o-mail-Message", { count: 5 });
await contains(".o-mail-Thread-newMessage hr + span", { count: 0, text: "New" });
});
test("new message separator is shown after first mark as read, on receiving new message", async () => {
const pyEnv = await startServer();
const bobPartnerId = pyEnv["res.partner"].create({ name: "Bob" });
const bobUserId = pyEnv["res.users"].create({ name: "Bob", partner_id: bobPartnerId });
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ partner_id: serverState.partnerId }),
Command.create({ partner_id: bobPartnerId }),
],
channel_type: "chat",
});
pyEnv["mail.message"].create({
author_id: bobPartnerId,
body: `Message 0`,
model: "discuss.channel",
res_id: channelId,
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-Message", { text: "Message 0" });
await contains(".o-mail-Thread-newMessage", { count: 0, text: "New" });
await withUser(bobUserId, () =>
rpc("/mail/message/post", {
post_data: {
body: "Message 1",
message_type: "comment",
subtype_xmlid: "mail.mt_comment",
},
thread_id: channelId,
thread_model: "discuss.channel",
})
);
await contains(".o-mail-Thread-newMessage ~ .o-mail-Message", { text: "Message 1" });
await contains(".o-mail-Thread-newMessage", { text: "New" });
});
test("keep new message separator until user goes back to the thread", async () => {
const pyEnv = await startServer();
pyEnv["res.users"].write(serverState.userId, { notification_type: "inbox" });
const partnerId = pyEnv["res.partner"].create({ name: "Foreigner partner" });
const channelId = pyEnv["discuss.channel"].create({
name: "test",
channel_member_ids: [
Command.create({ partner_id: partnerId }),
Command.create({ partner_id: serverState.partnerId }),
],
});
const messageIds = pyEnv["mail.message"].create([
{
author_id: partnerId,
body: "Message body 1",
message_type: "comment",
model: "discuss.channel",
res_id: channelId,
},
{
author_id: partnerId,
body: "Message body 2",
message_type: "comment",
model: "discuss.channel",
res_id: channelId,
},
]);
// simulate that there is at least one read message in the channel
const [memberId] = pyEnv["discuss.channel.member"].search([
["channel_id", "=", channelId],
["partner_id", "=", serverState.partnerId],
]);
pyEnv["discuss.channel.member"].write([memberId], { new_message_separator: messageIds[0] + 1 });
await start();
await openDiscuss(channelId);
await contains(".o-mail-Thread");
await contains(".o-mail-Thread-newMessage ~ .o-mail-Message", { text: "Message body 2" });
await contains(".o-mail-Thread-newMessage:contains('New')");
await hootClick(document.body); // Force "focusin" back on the textarea
await hootClick(".o-mail-Composer-input");
await waitNotifications([
"mail.record/insert",
(n) => n["discuss.channel.member"][0].new_message_separator,
]);
await hootClick(".o-mail-DiscussSidebar-item:contains(History)");
await contains(".o-mail-DiscussContent-threadName", { value: "History" });
await hootClick(".o-mail-DiscussSidebar-item:contains(test)");
await contains(".o-mail-DiscussContent-threadName", { value: "test" });
await contains(".o-mail-Message", { text: "Message body 2" });
await contains(".o-mail-Thread-newMessage:contains('New')", { count: 0 });
});
test("show new message separator on receiving new message when out of odoo focus", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Foreigner partner" });
const userId = pyEnv["res.users"].create({
name: "Foreigner user",
partner_id: partnerId,
});
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ message_unread_counter: 0, partner_id: serverState.partnerId }),
Command.create({ partner_id: partnerId }),
],
channel_type: "channel",
name: "General",
});
const messageId = pyEnv["mail.message"].create({
body: "not empty",
model: "discuss.channel",
res_id: channelId,
});
// simulate that there is at least one read message in the channel
const [memberId] = pyEnv["discuss.channel.member"].search([
["channel_id", "=", channelId],
["partner_id", "=", serverState.partnerId],
]);
pyEnv["discuss.channel.member"].write([memberId], { new_message_separator: messageId + 1 });
await start();
await openDiscuss(channelId);
await contains(".o-mail-Thread");
await contains(".o-mail-Thread-newMessage:contains('New')", { count: 0 });
// simulate receiving a message
await withUser(userId, () =>
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-Message", { text: "hu" });
await contains(".o-mail-Thread-newMessage:contains('New')");
await contains(".o-mail-Thread-newMessage ~ .o-mail-Message", { text: "hu" });
});
test("keep new message separator until current user sends a message", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
await start();
await openDiscuss(channelId);
await insertText(".o-mail-Composer-input", "hello");
await triggerHotkey("Enter");
await contains(".o-mail-Message", { text: "hello" });
await click(".o-mail-Message [title='Expand']");
await click(".o-dropdown-item:contains('Mark as Unread')");
await contains(".o-mail-Thread-newMessage:contains('New')");
await insertText(".o-mail-Composer-input", "hey!");
await press("Enter");
await contains(".o-mail-Message", { count: 2 });
await contains(".o-mail-Thread-newMessage:contains('New')", { count: 0 });
});
test("keep new message separator when switching between chat window and discuss of same thread", async () => {
const pyEnv = await startServer();
pyEnv["discuss.channel"].create({ channel_type: "channel", name: "General" });
await start();
await click(".o_menu_systray i[aria-label='Messages']");
await click("button", { text: "General" });
await insertText(".o-mail-Composer-input", "Very important message!");
await triggerHotkey("Enter");
await click(".o-mail-Message [title='Expand']");
await click(".o-dropdown-item:contains('Mark as Unread')");
await contains(".o-mail-Thread-newMessage");
// dropdown requires an extra delay before click (because handler is registered in useEffect)
await contains("[title='Open Actions Menu']");
await click("[title='Open Actions Menu']");
await click(".o-dropdown-item", { text: "Open in Discuss" });
await contains(".o-mail-DiscussContent-threadName", { value: "General" });
await contains(".o-mail-Thread-newMessage");
await openFormView("res.partner", serverState.partnerId);
await contains(".o-mail-ChatWindow-header", { text: "General" });
await contains(".o-mail-Thread-newMessage");
});
test("show new message separator when message is received in chat window", 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 partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const userId = pyEnv["res.users"].create({ name: "Foreigner user", partner_id: partnerId });
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",
partner_id: serverState.partnerId,
}),
Command.create({ partner_id: partnerId }),
],
channel_type: "chat",
});
const messageId = pyEnv["mail.message"].create({
body: "not empty",
model: "discuss.channel",
res_id: channelId,
});
const [memberId] = pyEnv["discuss.channel.member"].search([
["channel_id", "=", channelId],
["partner_id", "=", serverState.partnerId],
]);
pyEnv["discuss.channel.member"].write([memberId], { new_message_separator: messageId + 1 });
setupChatHub({ opened: [channelId] });
await start();
// simulate receiving a message
withUser(userId, () =>
rpc("/mail/message/post", {
post_data: { body: "hu", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
);
await contains(".o-mail-ChatWindow");
await contains(".o-mail-Message", { count: 2 });
await contains(".o-mail-Thread-newMessage:contains('New'):contains('New')");
await contains(".o-mail-Thread-newMessage + .o-mail-Message", { text: "hu" });
});
test("show new message separator when message is received while chat window is closed", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
const userId = pyEnv["res.users"].create({
name: "Foreigner user",
partner_id: partnerId,
});
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ partner_id: serverState.partnerId }),
Command.create({ partner_id: partnerId }),
],
channel_type: "chat",
});
const messageId = pyEnv["mail.message"].create({
body: "not empty",
model: "discuss.channel",
res_id: channelId,
});
// simulate that there is at least one read message in the channel
const [memberId] = pyEnv["discuss.channel.member"].search([
["channel_id", "=", channelId],
["partner_id", "=", serverState.partnerId],
]);
pyEnv["discuss.channel.member"].write([memberId], { new_message_separator: messageId + 1 });
setupChatHub({ opened: [channelId] });
listenStoreFetch("init_messaging");
await start();
await waitStoreFetch("init_messaging");
await click(".o-mail-ChatWindow-header [title*='Close Chat Window']");
await contains(".o-mail-ChatWindow", { count: 0 });
// send after init_messaging because bus subscription is done after init_messaging
// simulate receiving a message
await withUser(userId, () =>
rpc("/mail/message/post", {
post_data: { body: "hu", message_type: "comment" },
thread_id: channelId,
thread_model: "discuss.channel",
})
);
await contains(".o-mail-ChatBubble");
await contains(".o-mail-ChatBubble-counter", { text: "1" });
await click(".o-mail-ChatBubble");
await contains(".o-mail-Thread-newMessage:contains('New')");
});
test("only show new message separator in its thread", async () => {
// when a message acts as the reference for displaying new message separator,
// this should applies only when vieweing the message in its thread.
const pyEnv = await startServer();
pyEnv["res.users"].write(serverState.userId, { notification_type: "inbox" });
const demoPartnerId = pyEnv["res.partner"].create({ name: "Demo" });
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const messageIds = pyEnv["mail.message"].create([
{
author_id: demoPartnerId,
body: "Hello",
message_type: "comment",
model: "discuss.channel",
res_id: channelId,
},
{
author_id: demoPartnerId,
body: "@Mitchell Admin",
attachment_ids: [],
message_type: "comment",
model: "discuss.channel",
res_id: channelId,
needaction: true,
},
]);
// simulate that there is at least one read message in the channel
const [memberId] = pyEnv["discuss.channel.member"].search([
["channel_id", "=", channelId],
["partner_id", "=", serverState.partnerId],
]);
pyEnv["discuss.channel.member"].write([memberId], { new_message_separator: messageIds[0] + 1 });
await start();
await openDiscuss(channelId);
await contains(".o-mail-Thread-newMessage ~ .o-mail-Message", { text: "@Mitchell Admin" });
await click(".o-mail-DiscussSidebar-item", { text: "Inbox" });
await contains(".o-mail-DiscussContent-threadName", { value: "Inbox" });
await contains(".o-mail-Message", { text: "@Mitchell Admin" });
await contains(".o-mail-Thread-newMessage ~ .o-mail-Message", {
count: 0,
text: "@Mitchell Admin",
});
});

View file

@ -0,0 +1,63 @@
import {
contains,
defineMailModels,
setupChatHub,
start,
startServer,
} from "@mail/../tests/mail_test_helpers";
import { describe, test } from "@odoo/hoot";
import { Command, getService, serverState } from "@web/../tests/web_test_helpers";
describe.current.tags("desktop");
defineMailModels();
test("openChat: display notification for partner without user", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
await start();
await getService("mail.store").openChat({ partnerId });
await contains(".o_notification:has(.o_notification_bar.bg-info)", {
text: "You can only chat with partners that have a dedicated user.",
});
});
test("openChat: display notification for wrong user", async () => {
const pyEnv = await startServer();
pyEnv["res.users"].create({});
await start();
// userId not in the server data
await getService("mail.store").openChat({ userId: 4242 });
await contains(".o_notification:has(.o_notification_bar.bg-warning)", {
text: "You can only chat with existing users.",
});
});
test("openChat: open new chat for user", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["res.users"].create({ partner_id: partnerId });
await start();
await contains(".o-mail-ChatHub");
await contains(".o-mail-ChatWindow", { count: 0 });
getService("mail.store").openChat({ partnerId });
await contains(".o-mail-ChatWindow");
});
test.tags("focus required");
test("openChat: open existing chat for user", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
pyEnv["res.users"].create({ partner_id: partnerId });
const channelId = pyEnv["discuss.channel"].create({
channel_member_ids: [
Command.create({ partner_id: serverState.partnerId }),
Command.create({ partner_id: partnerId }),
],
channel_type: "chat",
});
setupChatHub({ opened: [channelId] });
await start();
await contains(".o-mail-ChatWindow .o-mail-Composer-input:not(:focus)");
getService("mail.store").openChat({ partnerId });
await contains(".o-mail-ChatWindow .o-mail-Composer-input:focus");
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,214 @@
import { HIGHLIGHT_CLASS, searchHighlight } from "@mail/core/common/message_search_hook";
import {
SIZES,
click,
contains,
defineMailModels,
insertText,
openDiscuss,
openFormView,
patchUiSize,
start,
startServer,
triggerHotkey,
} from "@mail/../tests/mail_test_helpers";
import { describe, expect, test } from "@odoo/hoot";
import { markup } from "@odoo/owl";
import { serverState } from "@web/../tests/web_test_helpers";
describe.current.tags("desktop");
defineMailModels();
test("Search highlight", async () => {
const testCases = [
{
input: markup`test odoo`,
output: `test <span class="${HIGHLIGHT_CLASS}">odoo</span>`,
searchTerm: "odoo",
},
{
input: markup`<a href="https://www.odoo.com">https://www.odoo.com</a>`,
output: `<a href="https://www.odoo.com">https://www.<span class="${HIGHLIGHT_CLASS}">odoo</span>.com</a>`,
searchTerm: "odoo",
},
{
input: '<a href="https://www.odoo.com">https://www.odoo.com</a>',
output: `&lt;a href="https://www.<span class="${HIGHLIGHT_CLASS}">odoo</span>.com"&gt;https://www.<span class="${HIGHLIGHT_CLASS}">odoo</span>.com&lt;/a&gt;`,
searchTerm: "odoo",
},
{
input: markup`<a href="https://www.odoo.com">Odoo</a>`,
output: `<a href="https://www.odoo.com"><span class="${HIGHLIGHT_CLASS}">Odoo</span></a>`,
searchTerm: "odoo",
},
{
input: markup`<a href="https://www.odoo.com">Odoo</a> Odoo is a free software`,
output: `<a href="https://www.odoo.com"><span class="${HIGHLIGHT_CLASS}">Odoo</span></a> <span class="${HIGHLIGHT_CLASS}">Odoo</span> is a free software`,
searchTerm: "odoo",
},
{
input: markup`odoo is a free software`,
output: `<span class="${HIGHLIGHT_CLASS}">odoo</span> is a free software`,
searchTerm: "odoo",
},
{
input: markup`software ODOO is a free`,
output: `software <span class="${HIGHLIGHT_CLASS}">ODOO</span> is a free`,
searchTerm: "odoo",
},
{
input: markup`<ul>
<li>Odoo</li>
<li><a href="https://odoo.com">Odoo ERP</a> Best ERP</li>
</ul>`,
output: `<ul>
<li><span class="${HIGHLIGHT_CLASS}">Odoo</span></li>
<li><a href="https://odoo.com"><span class="${HIGHLIGHT_CLASS}">Odoo</span> ERP</a> Best ERP</li>
</ul>`,
searchTerm: "odoo",
},
{
input: markup`test <strong>Odoo</strong> test`,
output: `<span class="${HIGHLIGHT_CLASS}">test</span> <strong><span class="${HIGHLIGHT_CLASS}">Odoo</span></strong> <span class="${HIGHLIGHT_CLASS}">test</span>`,
searchTerm: "odoo test",
},
{
input: markup`test <br> test`,
output: `<span class="${HIGHLIGHT_CLASS}">test</span> <br> <span class="${HIGHLIGHT_CLASS}">test</span>`,
searchTerm: "odoo test",
},
{
input: markup`<strong>test</strong> test`,
output: `<strong><span class="${HIGHLIGHT_CLASS}">test</span></strong> <span class="${HIGHLIGHT_CLASS}">test</span>`,
searchTerm: "test",
},
{
input: markup`<strong>a</strong> test`,
output: `<strong><span class="${HIGHLIGHT_CLASS}">a</span></strong> <span class="${HIGHLIGHT_CLASS}">test</span>`,
searchTerm: "a test",
},
{
input: markup`&amp;amp;`,
output: `<span class="${HIGHLIGHT_CLASS}">&amp;amp;</span>`,
searchTerm: "&amp;",
},
{
input: markup`&amp;amp;`,
output: `<span class="${HIGHLIGHT_CLASS}">&amp;</span>amp;`,
searchTerm: "&",
},
{
input: markup`<strong>test</strong> hello`,
output: `<strong><span class="${HIGHLIGHT_CLASS}">test</span></strong> <span class="${HIGHLIGHT_CLASS}">hello</span>`,
searchTerm: "test hello",
},
{
input: markup`<p>&lt;strong&gt;test&lt;/strong&gt; hello</p>`,
output: `<p>&lt;strong&gt;<span class="${HIGHLIGHT_CLASS}">test</span>&lt;/strong&gt; <span class="${HIGHLIGHT_CLASS}">hello</span></p>`,
searchTerm: "test hello",
},
];
for (const { input, output, searchTerm } of testCases) {
expect(searchHighlight(searchTerm, input).toString()).toBe(output);
}
});
test("Display highlighted search in chatter", async () => {
patchUiSize({ size: SIZES.XXL });
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
pyEnv["mail.message"].create({
body: "not empty",
model: "res.partner",
res_id: partnerId,
});
await start();
await openFormView("res.partner", partnerId);
await click("[title='Search Messages']");
await insertText(".o_searchview_input", "empty");
triggerHotkey("Enter");
await contains(`.o-mail-SearchMessageResult .o-mail-Message span.${HIGHLIGHT_CLASS}`);
});
test("Display multiple highlighted search in chatter", async () => {
patchUiSize({ size: SIZES.XXL });
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
pyEnv["mail.message"].create({
body: "not test empty",
model: "res.partner",
res_id: partnerId,
});
await start();
await openFormView("res.partner", partnerId);
await click("[title='Search Messages']");
await insertText(".o_searchview_input", "not empty");
triggerHotkey("Enter");
await contains(`.o-mail-SearchMessageResult .o-mail-Message span.${HIGHLIGHT_CLASS}`, {
count: 2,
});
});
test("Display highlighted search in Discuss", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
pyEnv["mail.message"].create({
author_id: serverState.partnerId,
body: "not empty",
attachment_ids: [],
message_type: "comment",
model: "discuss.channel",
res_id: channelId,
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-Message");
await click("button[title='Search Messages']");
await insertText(".o_searchview_input", "empty");
triggerHotkey("Enter");
await contains(`.o-mail-SearchMessagesPanel .o-mail-Message span.${HIGHLIGHT_CLASS}`);
});
test("Display multiple highlighted search in Discuss", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
pyEnv["mail.message"].create({
author_id: serverState.partnerId,
body: "not prout empty",
attachment_ids: [],
message_type: "comment",
model: "discuss.channel",
res_id: channelId,
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-Message");
await click("button[title='Search Messages']");
await insertText(".o_searchview_input", "not empty");
triggerHotkey("Enter");
await contains(`.o-mail-SearchMessagesPanel .o-mail-Message span.${HIGHLIGHT_CLASS}`, {
count: 2,
});
});
test("Display highlighted with escaped character must ignore them", async () => {
patchUiSize({ size: SIZES.XXL });
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
pyEnv["mail.message"].create({
body: "<p>&lt;strong&gt;test&lt;/strong&gt; hello</p>",
model: "res.partner",
res_id: partnerId,
});
await start();
await openFormView("res.partner", partnerId);
await click("[title='Search Messages']");
await insertText(".o_searchview_input", "test hello");
triggerHotkey("Enter");
await contains(`.o-mail-SearchMessageResult .o-mail-Message span.${HIGHLIGHT_CLASS}`, {
count: 2,
});
await contains(`.o-mail-Message-body`, { text: "<strong>test</strong> hello" });
});