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,464 @@
import {
click,
contains,
defineMailModels,
onRpcBefore,
openDiscuss,
start,
startServer,
} from "@mail/../tests/mail_test_helpers";
import { describe, expect, test } from "@odoo/hoot";
import { mockUserAgent } from "@odoo/hoot-mock";
import { asyncStep, patchWithCleanup, waitForSteps } from "@web/../tests/web_test_helpers";
import { download } from "@web/core/network/download";
import { getOrigin } from "@web/core/utils/urls";
import { isMobileOS } from "@web/core/browser/feature_detection";
describe.current.tags("desktop");
defineMailModels();
test("simplest layout", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const attachmentId = pyEnv["ir.attachment"].create({
name: "test.txt",
mimetype: "text/plain",
});
pyEnv["mail.message"].create({
attachment_ids: [attachmentId],
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-Message .o-mail-AttachmentList");
expect(".o-mail-AttachmentContainer:first").toHaveAttribute("title", "test.txt");
await contains(".o-mail-AttachmentCard-image");
expect(".o-mail-AttachmentCard-image:first").toHaveClass("o_image"); // required for mimetype.scss style
expect(".o-mail-AttachmentCard-image:first").toHaveAttribute("data-mimetype", "text/plain"); // required for mimetype.scss style
await contains(".o-mail-AttachmentButtons button", { count: 2 });
await contains(".o-mail-Attachment-unlink");
await contains(".o-mail-AttachmentButtons button[title='Download']");
});
test("layout with card details and filename and extension", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const attachmentId = pyEnv["ir.attachment"].create({
name: "test.txt",
mimetype: "text/plain",
});
pyEnv["mail.message"].create({
attachment_ids: [attachmentId],
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-AttachmentContainer", { text: "test.txt" });
});
test("link-type attachment should have open button instead of download button", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const attachment_ids = pyEnv["ir.attachment"].create([
{
name: "url.example",
mimetype: "text/plain",
type: "url",
url: "https://www.odoo.com",
},
{
name: "test.txt",
mimetype: "text/plain",
},
]);
pyEnv["mail.message"].create({
attachment_ids,
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-AttachmentCard", { count: 2 });
await contains(".o-mail-AttachmentCard:eq(0)", { text: "url.example" });
await contains(".o-mail-AttachmentCard:eq(1)", { text: "test.txt" });
await contains(
".o-mail-AttachmentContainer:eq(0) .o-mail-AttachmentButtons a[title='Open Link']"
);
await contains(
".o-mail-AttachmentContainer:eq(0) .o-mail-AttachmentButtons button[title='Download']",
{ count: 0 }
);
await contains(
".o-mail-AttachmentContainer:eq(1) .o-mail-AttachmentButtons button[title='Download']"
);
await contains(`.o-mail-AttachmentButtons a[title='Open Link'][target='_blank']`);
});
test("clicking on the delete attachment button multiple times should do the rpc only once", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const attachmentId = pyEnv["ir.attachment"].create({
name: "test.txt",
mimetype: "text/plain",
});
pyEnv["mail.message"].create({
attachment_ids: [attachmentId],
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
onRpcBefore("/mail/attachment/delete", () => asyncStep("attachment_unlink"));
await start();
await openDiscuss(channelId);
await click(".o-mail-Attachment-unlink");
await click(".modal-footer .btn-primary");
await click(".modal-footer .btn-primary");
await click(".modal-footer .btn-primary");
await contains(".o-mail-Attachment-unlink", { count: 0 });
await waitForSteps(["attachment_unlink"]); // The unlink method must be called once
});
test("view attachment", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const attachmentId = pyEnv["ir.attachment"].create({
name: "test.png",
mimetype: "image/png",
});
pyEnv["mail.message"].create({
attachment_ids: [attachmentId],
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-AttachmentImage");
await click(".o-mail-AttachmentImage");
await contains(".o-FileViewer");
});
test("can view pdf url", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const attachmentId = pyEnv["ir.attachment"].create({
name: "url.pdf.example",
mimetype: "application/pdf",
type: "url",
url: "https://pdfobject.com/pdf/sample.pdf",
});
pyEnv["mail.message"].create({
attachment_ids: [attachmentId],
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
await start();
await openDiscuss(channelId);
await click(".o-mail-AttachmentContainer", { text: "url.pdf.example" });
await contains(".o-FileViewer");
await contains(
`iframe.o-FileViewer-view[data-src="/web/static/lib/pdfjs/web/viewer.html?file=${encodeURIComponent(
`${getOrigin()}/web/content/${attachmentId}?filename=url.pdf.example`
)}#pagemode=none"]`
);
});
test("close attachment viewer", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const attachmentId = pyEnv["ir.attachment"].create({
name: "test.png",
mimetype: "image/png",
});
pyEnv["mail.message"].create({
attachment_ids: [attachmentId],
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-AttachmentImage");
await click(".o-mail-AttachmentImage");
await contains(".o-FileViewer");
await click(".o-FileViewer div[aria-label='Close']");
await contains(".o-FileViewer", { count: 0 });
});
test("[technical] does not crash when the viewer is closed before image load", async () => {
/**
* When images are displayed using "src" attribute for the 1st time, it fetches the resource.
* In this case, images are actually displayed (fully fetched and rendered on screen) when
* "<image>" intercepts "load" event.
*
* Current code needs to be aware of load state of image, to display spinner when loading
* and actual image when loaded. This test asserts no crash from mishandling image becoming
* loaded from being viewed for 1st time, but viewer being closed while image is loading.
*/
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const attachmentId = pyEnv["ir.attachment"].create({
name: "test.png",
mimetype: "image/png",
});
pyEnv["mail.message"].create({
attachment_ids: [attachmentId],
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
await start();
await openDiscuss(channelId);
await click(".o-mail-AttachmentImage");
await contains(".o-FileViewer-viewImage");
await click(".o-FileViewer div[aria-label='Close']");
// Simulate image becoming loaded.
expect(() => {
document
.querySelector(".o-FileViewer-viewImage")
.dispatchEvent(new Event("load", { bubbles: true }));
}).not.toThrow();
});
test("plain text file is viewable", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const attachmentId = pyEnv["ir.attachment"].create({
name: "test.txt",
mimetype: "text/plain",
});
pyEnv["mail.message"].create({
attachment_ids: [attachmentId],
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-AttachmentContainer.o-viewable");
});
test("HTML file is viewable", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const attachmentId = pyEnv["ir.attachment"].create({
name: "test.html",
mimetype: "text/html",
});
pyEnv["mail.message"].create({
attachment_ids: [attachmentId],
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-AttachmentContainer.o-viewable");
});
test("ODT file is not viewable", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const attachmentId = pyEnv["ir.attachment"].create({
name: "test.odt",
mimetype: "application/vnd.oasis.opendocument.text",
});
pyEnv["mail.message"].create({
attachment_ids: [attachmentId],
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-AttachmentContainer:not(.o-viewable)");
});
test("DOCX file is not viewable", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const attachmentId = pyEnv["ir.attachment"].create({
name: "test.docx",
mimetype: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});
pyEnv["mail.message"].create({
attachment_ids: [attachmentId],
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-AttachmentContainer:not(.o-viewable)");
});
test("should not view attachment from click on non-viewable attachment in list containing a viewable attachment", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const [attachmentId_1, attachmentId_2] = pyEnv["ir.attachment"].create([
{
name: "test.png",
mimetype: "image/png",
},
{
name: "test.odt",
mimetype: "application/vnd.oasis.opendocument.text",
},
]);
pyEnv["mail.message"].create({
attachment_ids: [attachmentId_1, attachmentId_2],
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
await start();
await openDiscuss(channelId);
await contains(".o-mail-AttachmentContainer[title='test.png'].o-viewable");
await contains(".o-mail-AttachmentContainer:not(.o-viewable)", { text: "test.odt" });
await click(".o-mail-AttachmentContainer", { text: "test.odt" });
// weak test, no guarantee that we waited long enough for the potential file viewer to show
await contains(".o-FileViewer", { count: 0 });
await click(".o-mail-AttachmentContainer[title='test.png']");
await contains(".o-FileViewer");
});
test("img file has proper src in discuss.channel", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const attachmentId = pyEnv["ir.attachment"].create({
name: "test.png",
mimetype: "image/png",
res_id: channelId,
res_model: "discuss.channel",
});
pyEnv["mail.message"].create({
attachment_ids: [attachmentId],
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
await start();
await openDiscuss(channelId);
await contains(
`.o-mail-AttachmentContainer[title='test.png'] img[data-src*='${getOrigin()}/web/image/${attachmentId}?filename=test.png']`
);
});
test("download url of non-viewable binary file", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const attachmentId = pyEnv["ir.attachment"].create({
name: "test.o",
mimetype: "application/octet-stream",
type: "binary",
});
pyEnv["mail.message"].create({
attachment_ids: [attachmentId],
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
await start();
await openDiscuss(channelId);
await contains(".fa-download");
patchWithCleanup(download, {
_download: (options) => {
expect(options.url).toBe(`${getOrigin()}/web/content/${attachmentId}?filename=test.o&download=true`);
},
});
await click(".fa-download");
});
test("check actions in mobile view", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({
channel_type: "channel",
name: "channel1",
});
const attachmentId = pyEnv["ir.attachment"].create({
name: "test.txt",
mimetype: "text/plain",
});
pyEnv["mail.message"].create({
attachment_ids: [attachmentId],
body: "<p>Test</p>",
model: "discuss.channel",
res_id: channelId,
message_type: "comment",
});
await start();
await openDiscuss(channelId);
mockUserAgent("android");
expect(isMobileOS()).toBe(true);
await click(".o-mail-AttachmentContainer [title='Actions']");
await contains(".dropdown-item", { text: "Remove" });
await contains(".dropdown-item", { text: "Download" });
});

View file

@ -0,0 +1,59 @@
import {
click,
contains,
defineMailModels,
inputFiles,
openDiscuss,
openFormView,
start,
startServer,
} from "@mail/../tests/mail_test_helpers";
import { describe, test } from "@odoo/hoot";
import { Deferred } from "@odoo/hoot-mock";
import { onRpc } from "@web/../tests/web_test_helpers";
describe.current.tags("desktop");
defineMailModels();
test("no conflicts between file uploads", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({});
const channelId = pyEnv["discuss.channel"].create({});
const text = new File(["hello, world"], "text1.txt", { type: "text/plain" });
const text2 = new File(["hello, world"], "text2.txt", { type: "text/plain" });
pyEnv["mail.message"].create({
body: "not empty",
model: "discuss.channel",
res_id: channelId,
});
await start();
// Uploading file in the first thread: res.partner chatter.
await openFormView("res.partner", partnerId);
await click("button", { text: "Send message" });
await inputFiles(".o-mail-Chatter .o-mail-Composer input[type=file]", [text]);
// Uploading file in the second thread: discuss.channel in chatWindow.
await click("i[aria-label='Messages']");
await click(".o-mail-NotificationItem");
await inputFiles(".o-mail-ChatWindow .o-mail-Composer input[type=file]", [text2]);
await contains(".o-mail-Chatter .o-mail-AttachmentContainer");
await contains(".o-mail-ChatWindow .o-mail-AttachmentContainer");
await contains(
".o-mail-Chatter .o-mail-AttachmentContainer:not(.o-isUploading):contains(text1.txt)"
);
await contains(
".o-mail-ChatWindow .o-mail-AttachmentContainer:not(.o-isUploading):contains(text2.txt)"
);
});
test("Attachment shows spinner during upload", async () => {
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "channel_1" });
const text2 = new File(["hello, world"], "text2.txt", { type: "text/plain" });
onRpc("/mail/attachment/upload", () => new Deferred()); // never fulfill the attachment upload promise.
await start();
await openDiscuss(channelId);
await inputFiles(".o-mail-Composer input[type=file]", [text2]);
await contains(
".o-mail-AttachmentContainer.o-isUploading:contains(text2.txt) .fa-circle-o-notch"
);
});

View file

@ -0,0 +1,131 @@
import {
click,
contains,
defineMailModels,
isInViewportOf,
openDiscuss,
start,
startServer,
} from "@mail/../tests/mail_test_helpers";
import { Thread } from "@mail/core/common/thread";
import { describe, test } from "@odoo/hoot";
import { advanceTime, Deferred, tick, waitFor } from "@odoo/hoot-dom";
import { disableAnimations } from "@odoo/hoot-mock";
import { router, routerBus } from "@web/core/browser/router";
import { range } from "@web/core/utils/numbers";
import { mountWebClient, patchWithCleanup } from "@web/../tests/web_test_helpers";
defineMailModels();
describe.current.tags("desktop");
test("can highlight messages that are not yet loaded", async () => {
disableAnimations();
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
let middleMessageId;
for (let i = 0; i < 200; i++) {
const messageId = pyEnv["mail.message"].create({
body: `message ${i}`,
model: "discuss.channel",
res_id: channelId,
});
if (i === 100) {
middleMessageId = messageId;
}
}
await pyEnv["discuss.channel"].set_message_pin(channelId, middleMessageId, true);
await start();
await openDiscuss(channelId);
await tick(); // Wait for the scroll to first unread to complete.
await isInViewportOf(".o-mail-Message:contains(message 199)", ".o-mail-Thread");
await click("a[data-oe-type='highlight']");
await isInViewportOf(".o-mail-Message:contains(message 100)", ".o-mail-Thread");
});
test("can highlight message (slow ref registration)", async () => {
disableAnimations();
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
let middleMessageId;
for (let i = 0; i < 200; i++) {
const messageId = pyEnv["mail.message"].create({
body: `message ${i}`,
model: "discuss.channel",
res_id: channelId,
});
if (i === 100) {
middleMessageId = messageId;
}
}
await pyEnv["discuss.channel"].set_message_pin(channelId, middleMessageId, true);
let slowRegisterMessageDef;
patchWithCleanup(Thread.prototype, {
async registerMessageRef(...args) {
// Ensure scroll is made even when messages are mounted later.
await slowRegisterMessageDef;
return super.registerMessageRef(...args);
},
});
await start();
await openDiscuss(channelId);
await tick(); // Wait for the scroll to first unread to complete.
await isInViewportOf(".o-mail-Message:contains(message 199)", ".o-mail-Thread");
slowRegisterMessageDef = new Deferred();
await click("a[data-oe-type='highlight']");
await advanceTime(1000);
slowRegisterMessageDef.resolve();
await isInViewportOf(".o-mail-Message:contains(message 100)", ".o-mail-Thread");
});
test("highlight scrolls to beginning of long message", async () => {
disableAnimations();
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
const [messageId1] = pyEnv["mail.message"].create([
{
body: `long message `.repeat(500),
model: "discuss.channel",
res_id: channelId,
},
{
body: `short message`,
model: "discuss.channel",
res_id: channelId,
},
]);
await pyEnv["discuss.channel"].set_message_pin(channelId, messageId1, true);
await start();
await openDiscuss(channelId);
await waitFor(".o-mail-Message:contains('short message')");
await isInViewportOf(".o-mail-Message:contains('short message')", ".o-mail-Thread");
await click("a[data-oe-type='highlight']");
await advanceTime(1000);
await isInViewportOf(".o-mail-Message:contains('long message')", ".o-mail-Thread");
await isInViewportOf(
".o-mail-Message:contains('long message') .o-mail-Message-avatar", // avatar is at beginning of message
".o-mail-Thread"
);
});
test("Chatter jumps when navigating to a specific message link", async () => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
const messageIds = range(0, 31).map((i) =>
pyEnv["mail.message"].create({
body: `message ${i}`,
model: "res.partner",
res_id: partnerId,
})
);
await mountWebClient();
router.pushState(
router.urlToState(
new URL(
`${window.location.origin}/odoo/res.partner/${partnerId}?highlight_message_id=${messageIds[0]}`
)
),
{ sync: true }
);
routerBus.trigger("ROUTE_CHANGE");
await contains(".o-mail-Message.o-highlighted .o-mail-Message-content", { text: "message 0" });
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,249 @@
import {
click,
contains,
defineMailModels,
focus,
openDiscuss,
patchUiSize,
scroll,
SIZES,
start,
startServer,
} from "@mail/../tests/mail_test_helpers";
import { describe, test } from "@odoo/hoot";
import { mockUserAgent, tick } from "@odoo/hoot-mock";
import {
asyncStep,
Command,
onRpc,
serverState,
waitForSteps,
withUser,
} from "@web/../tests/web_test_helpers";
import { rpc } from "@web/core/network/rpc";
describe.current.tags("desktop");
defineMailModels();
test("show unread messages banner when there are unread messages", 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 < 30; ++i) {
pyEnv["mail.message"].create({
author_id: bobPartnerId,
body: `message ${i}`,
model: "discuss.channel",
res_id: channelId,
});
}
await start();
await openDiscuss(channelId);
await contains(".o-mail-Message", { count: 30 });
await contains("span", { text: "30 new messagesMark as Read" });
});
test("mark thread as read from unread messages banner", 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 < 30; ++i) {
pyEnv["mail.message"].create({
author_id: bobPartnerId,
body: `message ${i}`,
model: "discuss.channel",
res_id: channelId,
});
}
await start();
await openDiscuss(channelId);
await contains(".o-mail-Message", { count: 30 });
await click("span", {
text: "Mark as Read",
parent: ["span", { text: "30 new messagesMark as Read" }],
});
});
test("reset new message separator from unread messages banner", 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 < 30; ++i) {
pyEnv["mail.message"].create({
author_id: bobPartnerId,
body: `message ${i}`,
model: "discuss.channel",
res_id: channelId,
});
}
pyEnv["discuss.channel.member"].search([
["partner_id", "=", serverState.partnerId],
["channel_id", "=", channelId],
]);
await start();
await openDiscuss(channelId);
await contains(".o-mail-Message", { count: 30 });
await contains(".o-mail-Message", {
text: "message 0",
});
await click("span", {
text: "Mark as Read",
parent: ["span", { text: "30 new messagesMark as Read" }],
});
await contains("span", { text: "30 new messagesMark as Read", count: 0 });
});
test("remove banner when scrolling to bottom", 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 < 50; ++i) {
pyEnv["mail.message"].create({
author_id: bobPartnerId,
body: `message ${i}`,
model: "discuss.channel",
res_id: channelId,
});
}
onRpc("/discuss/channel/mark_as_read", () => asyncStep("mark_as_read"));
await start();
await openDiscuss(channelId);
await contains(".o-mail-Message", { count: 30 });
await contains(".o-mail-Composer.o-focused");
await focus(".o-mail-Thread");
await contains(".o-mail-Thread-banner", { text: "50 new messages" });
await tick(); // wait for the scroll to first unread to complete
await scroll(".o-mail-Thread", "bottom");
await contains(".o-mail-Message", { count: 50 });
// Banner is still present as there are more messages to load so we did not
// reach the actual bottom.
await contains(".o-mail-Thread-banner", { text: "50 new messages" });
await scroll(".o-mail-Thread", "bottom");
await contains(".o-mail-Thread-banner", { text: "50 new messages", count: 0 });
await waitForSteps(["mark_as_read"]);
});
test("remove banner when opening thread at the bottom", async () => {
const pyEnv = await startServer();
pyEnv["res.users"].write(serverState.userId, { notification_type: "inbox" });
const channelId = pyEnv["discuss.channel"].create({ name: "general" });
const bobPartnerId = pyEnv["res.partner"].create({ name: "Bob" });
const messageId = pyEnv["mail.message"].create({
author_id: bobPartnerId,
body: `Hello World`,
model: "discuss.channel",
res_id: channelId,
});
const [selfMemberId] = pyEnv["discuss.channel.member"].search([
["partner_id", "=", serverState.partnerId],
["channel_id", "=", channelId],
]);
pyEnv["discuss.channel.member"].write([selfMemberId], { new_message_separator: messageId + 1 });
await start();
await openDiscuss(channelId);
await click("[title='Expand']", { parent: [".o-mail-Message", { text: "Hello World" }] });
await click(".o-dropdown-item:contains('Mark as Unread')");
await contains(".o-mail-Thread-banner", { text: "1 new message" });
await click(".o-mail-DiscussSidebar-item", { text: "Inbox" });
await contains(".o-mail-DiscussContent-threadName[title='Inbox']");
await click(".o-mail-DiscussSidebarChannel", { text: "general" });
await contains(".o-mail-DiscussContent-threadName[title='general']");
await contains(".o-mail-Thread-banner", { text: "1 new message", count: 0 });
});
test("keep banner after mark as unread when scrolling to bottom", 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 < 30; ++i) {
pyEnv["mail.message"].create({
author_id: bobPartnerId,
body: `message ${i}`,
model: "discuss.channel",
res_id: channelId,
});
}
await start();
await openDiscuss(channelId);
await click("[title='Expand']", { parent: [".o-mail-Message", { text: "message 29" }] });
await click(".o-dropdown-item:contains('Mark as Unread')");
await scroll(".o-mail-Thread", "bottom");
await contains(".o-mail-Thread-banner", { text: "30 new messages" });
});
test("sidebar and banner counters display same value", 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_type: "chat",
channel_member_ids: [
Command.create({ partner_id: serverState.partnerId }),
Command.create({ partner_id: bobPartnerId }),
],
});
for (let i = 0; i < 30; ++i) {
pyEnv["mail.message"].create({
author_id: bobPartnerId,
body: `message ${i}`,
model: "discuss.channel",
res_id: channelId,
});
}
await start();
await openDiscuss();
await contains(".o-mail-DiscussSidebar-badge", {
text: "30",
parent: [".o-mail-DiscussSidebarChannel", { text: "Bob" }],
});
await click(".o-mail-DiscussSidebarChannel", { text: "Bob" });
await contains(".o-mail-Thread-banner", { text: "30 new messages" });
await contains(".o-mail-DiscussSidebar-badge", { text: "30" });
await withUser(bobUserId, () =>
rpc("/mail/message/post", {
post_data: {
body: "Hello!",
message_type: "comment",
subtype_xmlid: "mail.mt_comment",
},
thread_id: channelId,
thread_model: "discuss.channel",
})
);
await contains(".o-mail-Thread-banner", { text: "31 new messages" });
await contains(".o-mail-DiscussSidebar-badge", {
text: "31",
parent: [".o-mail-DiscussSidebarChannel", { text: "Bob" }],
});
});
test("mobile: mark as read when opening chat", async () => {
mockUserAgent("android");
const pyEnv = await startServer();
const bobPartnerId = pyEnv["res.partner"].create({ name: "bob" });
const channelId = pyEnv["discuss.channel"].create({
channel_type: "chat",
channel_member_ids: [
Command.create({ partner_id: serverState.partnerId }),
Command.create({ partner_id: bobPartnerId }),
],
});
pyEnv["mail.message"].create({
body: "Hello!",
model: "discuss.channel",
author_id: bobPartnerId,
res_id: channelId,
});
patchUiSize({ size: SIZES.SM });
await start();
await openDiscuss();
await contains("button.active", { text: "Notifications" });
await click("button:has(.badge:contains('1'))", { text: "Chats" });
await contains(".o-mail-NotificationItem:has(.badge:contains(1))", { text: "bob" });
await click(".o-mail-NotificationItem", { text: "bob" });
await contains(".o-mail-Message");
await contains(".o-mail-Thread.o-focused");
await contains(".o-mail-Composer:not(.o-focused)");
await click(".o-mail-ChatWindow-header [title*='Close Chat Window']");
await contains(".o-mail-NotificationItem:has(.badge:contains(1))", { text: "bob", count: 0 });
});