mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 08:52:08 +02:00
19.0 vanilla
This commit is contained in:
parent
d1963a3c3a
commit
2d3ee4855a
7430 changed files with 2687981 additions and 2965473 deletions
|
|
@ -0,0 +1,209 @@
|
|||
import {
|
||||
SIZES,
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
openFormView,
|
||||
patchUiSize,
|
||||
scroll,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("base non-empty rendering", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
pyEnv["ir.attachment"].create([
|
||||
{
|
||||
mimetype: "text/plain",
|
||||
name: "Blah.txt",
|
||||
res_id: partnerId,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
{
|
||||
mimetype: "text/plain",
|
||||
name: "Blu.txt",
|
||||
res_id: partnerId,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId, {
|
||||
arch: `
|
||||
<form>
|
||||
<sheet></sheet>
|
||||
<chatter open_attachments="True"/>
|
||||
</form>`,
|
||||
});
|
||||
await contains(".o-mail-AttachmentBox");
|
||||
await contains("button", { text: "Attach files" });
|
||||
await contains(".o-mail-Chatter input[type='file']");
|
||||
await contains(".o-mail-AttachmentList");
|
||||
});
|
||||
|
||||
test("remove attachment should ask for confirmation", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
pyEnv["ir.attachment"].create({
|
||||
mimetype: "text/plain",
|
||||
name: "Blah.txt",
|
||||
res_id: partnerId,
|
||||
res_model: "res.partner",
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId, {
|
||||
arch: `
|
||||
<form>
|
||||
<sheet></sheet>
|
||||
<chatter open_attachments="True"/>
|
||||
</form>`,
|
||||
});
|
||||
await contains(".o-mail-AttachmentCard");
|
||||
await contains("button[title='Remove']");
|
||||
await click("button[title='Remove']");
|
||||
await contains(".modal-body", { text: 'Do you really want to delete "Blah.txt"?' });
|
||||
// Confirm the deletion
|
||||
await click(".modal-footer .btn-primary");
|
||||
await contains(".o-mail-AttachmentImage", { count: 0 });
|
||||
});
|
||||
|
||||
test("view attachments", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
pyEnv["ir.attachment"].create([
|
||||
{
|
||||
mimetype: "text/plain",
|
||||
name: "Blah.txt",
|
||||
res_id: partnerId,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
{
|
||||
mimetype: "text/plain",
|
||||
name: "Blu.txt",
|
||||
res_id: partnerId,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId, {
|
||||
arch: `
|
||||
<form>
|
||||
<sheet></sheet>
|
||||
<chatter open_attachments="True"/>
|
||||
</form>`,
|
||||
});
|
||||
await click('.o-mail-AttachmentContainer[aria-label="Blah.txt"] .o-mail-AttachmentCard-image');
|
||||
await contains(".o-FileViewer");
|
||||
await contains(".o-FileViewer-header", { text: "Blah.txt" });
|
||||
await contains(".o-FileViewer div[aria-label='Next']");
|
||||
await click(".o-FileViewer div[aria-label='Next']");
|
||||
await contains(".o-FileViewer-header", { text: "Blu.txt" });
|
||||
await contains(".o-FileViewer div[aria-label='Next']");
|
||||
await click(".o-FileViewer div[aria-label='Next']");
|
||||
await contains(".o-FileViewer-header", { text: "Blah.txt" });
|
||||
});
|
||||
|
||||
test("scroll to attachment box when toggling on", async () => {
|
||||
patchUiSize({ size: SIZES.XXL });
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
for (let i = 0; i < 30; i++) {
|
||||
pyEnv["mail.message"].create({
|
||||
body: "not empty".repeat(50),
|
||||
model: "res.partner",
|
||||
res_id: partnerId,
|
||||
});
|
||||
}
|
||||
pyEnv["ir.attachment"].create({
|
||||
mimetype: "text/plain",
|
||||
name: "Blah.txt",
|
||||
res_id: partnerId,
|
||||
res_model: "res.partner",
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains(".o-mail-Message", { count: 30 });
|
||||
await scroll(".o-mail-Chatter", "bottom");
|
||||
await click("button[aria-label='Attach files']");
|
||||
await contains(".o-mail-AttachmentBox");
|
||||
await contains(".o-mail-Chatter", { scroll: 0 });
|
||||
await contains(".o-mail-AttachmentBox", { visible: true });
|
||||
});
|
||||
|
||||
test("do not auto-scroll to attachment box when initially open", async () => {
|
||||
patchUiSize({ size: SIZES.LG });
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
pyEnv["mail.message"].create({
|
||||
body: "not empty",
|
||||
model: "res.partner",
|
||||
res_id: partnerId,
|
||||
});
|
||||
pyEnv["ir.attachment"].create({
|
||||
mimetype: "text/plain",
|
||||
name: "Blah.txt",
|
||||
res_id: partnerId,
|
||||
res_model: "res.partner",
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId, {
|
||||
arch: `
|
||||
<form>
|
||||
${`<sheet><field name="name"/></sheet>`.repeat(100)}
|
||||
<chatter open_attachments="True"/>
|
||||
</form>`,
|
||||
});
|
||||
await contains(".o-mail-Message");
|
||||
// weak test, no guarantee that we waited long enough for the potential scroll to happen
|
||||
await contains(".o_content", { scroll: 0 });
|
||||
});
|
||||
|
||||
test("attachment box should order attachments from newest to oldest", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
const resData = { res_id: partnerId, res_model: "res.partner" };
|
||||
pyEnv["ir.attachment"].create([
|
||||
{ name: "A.txt", mimetype: "text/plain", ...resData },
|
||||
{ name: "B.txt", mimetype: "text/plain", ...resData },
|
||||
{ name: "C.txt", mimetype: "text/plain", ...resData },
|
||||
]);
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains(".o-mail-Chatter [aria-label='Attach files']", { text: "3" });
|
||||
await click(".o-mail-Chatter [aria-label='Attach files']"); // open attachment box
|
||||
await contains(":nth-child(1 of .o-mail-AttachmentContainer)", { text: "C.txt" });
|
||||
await contains(":nth-child(2 of .o-mail-AttachmentContainer)", { text: "B.txt" });
|
||||
await contains(":nth-child(3 of .o-mail-AttachmentContainer)", { text: "A.txt" });
|
||||
});
|
||||
|
||||
test("attachment box auto-closed on switch to record wih no attachments", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
|
||||
{ display_name: "first partner" },
|
||||
{ display_name: "second partner" },
|
||||
]);
|
||||
pyEnv["ir.attachment"].create([
|
||||
{
|
||||
mimetype: "text/plain",
|
||||
name: "Blah.txt",
|
||||
res_id: partnerId_1,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId_1, {
|
||||
arch: `
|
||||
<form>
|
||||
<sheet></sheet>
|
||||
<chatter open_attachments="True"/>
|
||||
</form>`,
|
||||
resIds: [partnerId_1, partnerId_2],
|
||||
});
|
||||
await contains(".o-mail-AttachmentBox");
|
||||
await click(".o_pager_next");
|
||||
await contains(".o-mail-AttachmentBox", { count: 0 });
|
||||
});
|
||||
|
|
@ -0,0 +1,752 @@
|
|||
import {
|
||||
SIZES,
|
||||
STORE_FETCH_ROUTES,
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
dragenterFiles,
|
||||
dropFiles,
|
||||
insertText,
|
||||
listenStoreFetch,
|
||||
onRpcBefore,
|
||||
openFormView,
|
||||
patchUiSize,
|
||||
scroll,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey,
|
||||
waitStoreFetch,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { Deferred, advanceTime } from "@odoo/hoot-mock";
|
||||
import {
|
||||
asyncStep,
|
||||
defineActions,
|
||||
getService,
|
||||
mockService,
|
||||
serverState,
|
||||
waitForSteps,
|
||||
} from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { DELAY_FOR_SPINNER } from "@mail/chatter/web_portal/chatter";
|
||||
import { queryFirst } from "@odoo/hoot-dom";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("simple chatter on a record", async () => {
|
||||
const pyEnv = await startServer();
|
||||
onRpcBefore((route, args) => {
|
||||
if (
|
||||
(route.startsWith("/mail") || route.startsWith("/discuss")) &&
|
||||
!STORE_FETCH_ROUTES.includes(route)
|
||||
) {
|
||||
asyncStep(`${route} - ${JSON.stringify(args)}`);
|
||||
}
|
||||
});
|
||||
listenStoreFetch(undefined, { logParams: ["mail.thread"] });
|
||||
await start();
|
||||
await waitStoreFetch(["failures", "systray_get_activities", "init_messaging"]);
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains(".o-mail-Chatter-topbar");
|
||||
await contains(".o-mail-Thread");
|
||||
await waitStoreFetch(
|
||||
[
|
||||
[
|
||||
"mail.thread",
|
||||
{
|
||||
access_params: {},
|
||||
request_list: [
|
||||
"activities",
|
||||
"attachments",
|
||||
"contact_fields",
|
||||
"followers",
|
||||
"scheduledMessages",
|
||||
"suggestedRecipients",
|
||||
],
|
||||
thread_id: partnerId,
|
||||
thread_model: "res.partner",
|
||||
},
|
||||
],
|
||||
],
|
||||
{
|
||||
ignoreOrder: true,
|
||||
stepsAfter: [
|
||||
`/mail/thread/messages - {"thread_id":${partnerId},"thread_model":"res.partner","fetch_params":{"limit":30}}`,
|
||||
],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test("can post a message on a record thread", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
|
||||
onRpcBefore("/mail/message/post", (args) => {
|
||||
asyncStep("/mail/message/post");
|
||||
const expected = {
|
||||
context: args.context,
|
||||
post_data: {
|
||||
body: "hey",
|
||||
email_add_signature: true,
|
||||
message_type: "comment",
|
||||
subtype_xmlid: "mail.mt_comment",
|
||||
},
|
||||
thread_id: partnerId,
|
||||
thread_model: "res.partner",
|
||||
};
|
||||
expect(args).toEqual(expected);
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains("button", { text: "Send message" });
|
||||
await contains(".o-mail-Composer", { count: 0 });
|
||||
await click("button", { text: "Send message" });
|
||||
await contains(".o-mail-Composer");
|
||||
await insertText(".o-mail-Composer-input", "hey");
|
||||
await contains(".o-mail-Message", { count: 0 });
|
||||
await click(".o-mail-Composer button[aria-label='Send']:enabled");
|
||||
await contains(".o-mail-Message");
|
||||
await waitForSteps(["/mail/message/post"]);
|
||||
});
|
||||
|
||||
test("can post a note on a record thread", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
|
||||
onRpcBefore("/mail/message/post", (args) => {
|
||||
asyncStep("/mail/message/post");
|
||||
const expected = {
|
||||
context: args.context,
|
||||
post_data: {
|
||||
body: "hey",
|
||||
email_add_signature: true,
|
||||
message_type: "comment",
|
||||
subtype_xmlid: "mail.mt_note",
|
||||
},
|
||||
thread_id: partnerId,
|
||||
thread_model: "res.partner",
|
||||
};
|
||||
expect(args).toEqual(expected);
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains("button", { text: "Log note" });
|
||||
await contains(".o-mail-Composer", { count: 0 });
|
||||
await click("button", { text: "Log note" });
|
||||
await contains(".o-mail-Composer");
|
||||
await insertText(".o-mail-Composer-input", "hey");
|
||||
await contains(".o-mail-Message", { count: 0 });
|
||||
await click(".o-mail-Composer button:enabled", { text: "Log" });
|
||||
await contains(".o-mail-Message");
|
||||
await waitForSteps(["/mail/message/post"]);
|
||||
});
|
||||
|
||||
test("No attachment loading spinner when creating records", async () => {
|
||||
await start();
|
||||
await openFormView("res.partner");
|
||||
await contains("button[aria-label='Attach files']");
|
||||
await contains("button[aria-label='Attach files'] .fa-spin", { count: 0 });
|
||||
});
|
||||
|
||||
test("No attachment loading spinner when switching from loading record to creation of record", async () => {
|
||||
const def = new Deferred();
|
||||
const pyEnv = await startServer();
|
||||
listenStoreFetch("mail.thread", {
|
||||
async onRpc() {
|
||||
asyncStep("before mail.thread");
|
||||
await def;
|
||||
},
|
||||
});
|
||||
await start();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "John" });
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains("button[aria-label='Attach files']");
|
||||
await advanceTime(DELAY_FOR_SPINNER);
|
||||
await contains("button[aria-label='Attach files'] .fa-spin");
|
||||
await click(".o_control_panel_main_buttons .o_form_button_create");
|
||||
await contains("button[aria-label='Attach files'] .fa-spin", { count: 0 });
|
||||
await waitForSteps(["before mail.thread"]);
|
||||
def.resolve();
|
||||
await waitStoreFetch("mail.thread");
|
||||
});
|
||||
|
||||
test("Composer toggle state is kept when switching from aside to bottom", async () => {
|
||||
await patchUiSize({ size: SIZES.XXL });
|
||||
const pyEnv = await startServer();
|
||||
await start();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
|
||||
await openFormView("res.partner", partnerId);
|
||||
await click("button", { text: "Send message" });
|
||||
await contains(".o-mail-Form-chatter.o-aside .o-mail-Composer-input");
|
||||
await patchUiSize({ size: SIZES.LG });
|
||||
await contains(".o-mail-Form-chatter:not(.o-aside) .o-mail-Composer-input");
|
||||
});
|
||||
|
||||
test("Textarea content is kept when switching from aside to bottom", async () => {
|
||||
await patchUiSize({ size: SIZES.XXL });
|
||||
const pyEnv = await startServer();
|
||||
await start();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
|
||||
await openFormView("res.partner", partnerId);
|
||||
await click("button", { text: "Send message" });
|
||||
await contains(".o-mail-Form-chatter.o-aside .o-mail-Composer-input");
|
||||
await insertText(".o-mail-Composer-input", "Hello world !");
|
||||
await patchUiSize({ size: SIZES.LG });
|
||||
await contains(".o-mail-Form-chatter:not(.o-aside) .o-mail-Composer-input");
|
||||
await contains(".o-mail-Composer-input", { value: "Hello world !" });
|
||||
});
|
||||
|
||||
test("Composer type is kept when switching from aside to bottom", async () => {
|
||||
await patchUiSize({ size: SIZES.XXL });
|
||||
const pyEnv = await startServer();
|
||||
await start();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
|
||||
await openFormView("res.partner", partnerId);
|
||||
await click("button", { text: "Log note" });
|
||||
await patchUiSize({ size: SIZES.LG });
|
||||
await contains(".o-mail-Form-chatter:not(.o-aside) .o-mail-Composer-input");
|
||||
await contains("button.btn-primary", { text: "Log note" });
|
||||
await contains("button:not(.btn-primary)", { text: "Send message" });
|
||||
});
|
||||
|
||||
test("chatter: drop attachments", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
const text = new File(["hello, world"], "text.txt", { type: "text/plain" });
|
||||
const text2 = new File(["hello, worldub"], "text2.txt", { type: "text/plain" });
|
||||
const text3 = new File(["hello, world"], "text3.txt", { type: "text/plain" });
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
const files = [text, text2];
|
||||
await dragenterFiles(".o-mail-Chatter", files);
|
||||
await contains(".o-Dropzone");
|
||||
await contains(".o-mail-AttachmentContainer", { count: 0 });
|
||||
await dropFiles(".o-Dropzone", files);
|
||||
await contains(".o-mail-AttachmentContainer:not(.o-isUploading)", { count: 2 });
|
||||
const extraFiles = [text3];
|
||||
await dragenterFiles(".o-mail-Chatter", extraFiles);
|
||||
await dropFiles(".o-Dropzone", extraFiles);
|
||||
await contains(".o-mail-AttachmentContainer:not(.o-isUploading)", { count: 3 });
|
||||
});
|
||||
|
||||
test("chatter: drop attachment should refresh thread data with hasParentReloadOnAttachmentsChange prop", async () => {
|
||||
await patchUiSize({ size: SIZES.XXL });
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
const textPdf = new File([new Uint8Array(1)], "text.pdf", { type: "application/pdf" });
|
||||
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId, {
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<div class="o_attachment_preview" />
|
||||
<chatter reload_on_post="True" reload_on_attachment="True"/>
|
||||
</form>`,
|
||||
});
|
||||
await dragenterFiles(".o-mail-Chatter", [textPdf]);
|
||||
await dropFiles(".o-Dropzone", [textPdf]);
|
||||
await contains(".o-mail-Attachment iframe", { count: 1 });
|
||||
});
|
||||
|
||||
test("should display subject when subject isn't infered from the record", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "Demo" });
|
||||
pyEnv["mail.message"].create({
|
||||
body: "not empty",
|
||||
model: "res.partner",
|
||||
res_id: partnerId,
|
||||
subject: "Salutations, voyageur",
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains(".o-mail-Message", { text: "Subject: Salutations, voyageurnot empty" });
|
||||
});
|
||||
|
||||
test("should not display user notification messages in chatter", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
pyEnv["mail.message"].create({
|
||||
message_type: "user_notification",
|
||||
model: "res.partner",
|
||||
res_id: partnerId,
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains(".o-mail-Thread", { text: "The conversation is empty." });
|
||||
await contains(".o-mail-Message", { count: 0 });
|
||||
});
|
||||
|
||||
test('post message with "CTRL-Enter" keyboard shortcut in chatter', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await click("button", { text: "Send message" });
|
||||
await contains(".o-mail-Message", { count: 0 });
|
||||
await insertText(".o-mail-Composer-input", "Test");
|
||||
triggerHotkey("control+Enter");
|
||||
await contains(".o-mail-Message");
|
||||
});
|
||||
|
||||
test("base rendering when chatter has no attachment", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
for (let i = 0; i < 60; i++) {
|
||||
pyEnv["mail.message"].create({
|
||||
body: "not empty",
|
||||
model: "res.partner",
|
||||
res_id: partnerId,
|
||||
});
|
||||
}
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains(".o-mail-Chatter");
|
||||
await contains(".o-mail-Chatter-topbar");
|
||||
await contains(".o-mail-AttachmentBox", { count: 0 });
|
||||
await contains(".o-mail-Thread");
|
||||
await contains(".o-mail-Message", { count: 30 });
|
||||
});
|
||||
|
||||
test("base rendering when chatter has no record", async () => {
|
||||
await start();
|
||||
await openFormView("res.partner");
|
||||
await contains(".o-mail-Chatter");
|
||||
await contains(".o-mail-Chatter-topbar");
|
||||
await contains(".o-mail-AttachmentBox", { count: 0 });
|
||||
await contains(".o-mail-Chatter .o-mail-Thread");
|
||||
await contains(".o-mail-Message");
|
||||
await contains(".o-mail-Message-author", { text: "Mitchell Admin" });
|
||||
await contains(".o-mail-Message-body", { text: "Creating a new record..." });
|
||||
await contains("button", { count: 0, text: "Load More" });
|
||||
});
|
||||
|
||||
test("base rendering when chatter has attachments", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
pyEnv["ir.attachment"].create([
|
||||
{
|
||||
mimetype: "text/plain",
|
||||
name: "Blah.txt",
|
||||
res_id: partnerId,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
{
|
||||
mimetype: "text/plain",
|
||||
name: "Blu.txt",
|
||||
res_id: partnerId,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains(".o-mail-Chatter");
|
||||
await contains(".o-mail-Chatter-topbar");
|
||||
await contains(".o-mail-AttachmentBox", { count: 0 });
|
||||
});
|
||||
|
||||
test("show attachment box", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
pyEnv["ir.attachment"].create([
|
||||
{
|
||||
mimetype: "text/plain",
|
||||
name: "Blah.txt",
|
||||
res_id: partnerId,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
{
|
||||
mimetype: "text/plain",
|
||||
name: "Blu.txt",
|
||||
res_id: partnerId,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains(".o-mail-Chatter");
|
||||
await contains(".o-mail-Chatter-topbar");
|
||||
await contains("button[aria-label='Attach files']");
|
||||
await contains("button[aria-label='Attach files']", { text: "2" });
|
||||
await contains(".o-mail-AttachmentBox", { count: 0 });
|
||||
await click("button[aria-label='Attach files']");
|
||||
await contains(".o-mail-AttachmentBox");
|
||||
});
|
||||
|
||||
test("composer show/hide on log note/send message", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains("button", { text: "Send message" });
|
||||
await contains("button", { text: "Log note" });
|
||||
await contains(".o-mail-Composer", { count: 0 });
|
||||
await click("button", { text: "Send message" });
|
||||
await contains(".o-mail-Composer");
|
||||
expect(".o-mail-Composer-input").toBeFocused();
|
||||
await click("button", { text: "Log note" });
|
||||
await contains(".o-mail-Composer");
|
||||
expect(".o-mail-Composer-input").toBeFocused();
|
||||
await click("button", { text: "Log note" });
|
||||
await contains(".o-mail-Composer", { count: 0 });
|
||||
await click("button", { text: "Send message" });
|
||||
await contains(".o-mail-Composer");
|
||||
await click("button", { text: "Send message" });
|
||||
await contains(".o-mail-Composer", { count: 0 });
|
||||
});
|
||||
|
||||
test('do not post message with "Enter" keyboard shortcut', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await click("button", { text: "Send message" });
|
||||
await contains(".o-mail-Message", { count: 0 });
|
||||
await insertText(".o-mail-Composer-input", "Test");
|
||||
triggerHotkey("Enter");
|
||||
// weak test, no guarantee that we waited long enough for the potential message to be posted
|
||||
await contains(".o-mail-Message", { count: 0 });
|
||||
});
|
||||
|
||||
test("should not display subject when subject is the same as the thread name", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({
|
||||
name: "Salutations, voyageur",
|
||||
});
|
||||
pyEnv["mail.message"].create({
|
||||
body: "not empty",
|
||||
model: "res.partner",
|
||||
res_id: partnerId,
|
||||
subject: "Salutations, voyageur",
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains(".o-mail-Message", { text: "not empty" });
|
||||
await contains(".o-mail-Message", {
|
||||
count: 0,
|
||||
text: "Subject: Salutations, voyageurnot empty",
|
||||
});
|
||||
});
|
||||
|
||||
test("scroll position is kept when navigating from one record to another", async () => {
|
||||
await patchUiSize({ size: SIZES.XXL });
|
||||
const pyEnv = await startServer();
|
||||
const partnerId_1 = pyEnv["res.partner"].create({ name: "Harry Potter" });
|
||||
const partnerId_2 = pyEnv["res.partner"].create({ name: "Ron Weasley" });
|
||||
// Fill both channels with random messages in order for the scrollbar to
|
||||
// appear.
|
||||
pyEnv["mail.message"].create(
|
||||
Array(50)
|
||||
.fill(0)
|
||||
.map((_, index) => ({
|
||||
body: "Non Empty Body ".repeat(25),
|
||||
model: "res.partner",
|
||||
res_id: index < 20 ? partnerId_1 : partnerId_2,
|
||||
}))
|
||||
);
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId_1);
|
||||
await contains(".o-mail-Message", { count: 20 });
|
||||
const clientHeight1 = queryFirst(".o-mail-Chatter:first").clientHeight; // client height might change (cause: breadcrumb)
|
||||
const scrollValue1 = queryFirst(".o-mail-Chatter:first").scrollHeight / 2;
|
||||
await contains(".o-mail-Chatter", { scroll: 0 });
|
||||
await scroll(".o-mail-Chatter", scrollValue1);
|
||||
await openFormView("res.partner", partnerId_2);
|
||||
await contains(".o-mail-Message", { count: 30 });
|
||||
const clientHeight2 = queryFirst(".o-mail-Chatter:first").clientHeight;
|
||||
const scrollValue2 = queryFirst(".o-mail-Chatter:first").scrollHeight / 3;
|
||||
await scroll(".o-mail-Chatter", scrollValue2);
|
||||
await openFormView("res.partner", partnerId_1);
|
||||
await contains(".o-mail-Message", { count: 20 });
|
||||
const clientHeight3 = queryFirst(".o-mail-Chatter:first").clientHeight;
|
||||
await contains(".o-mail-Chatter", { scroll: scrollValue1 - (clientHeight3 - clientHeight1) });
|
||||
await openFormView("res.partner", partnerId_2);
|
||||
await contains(".o-mail-Message", { count: 30 });
|
||||
const clientHeight4 = queryFirst(".o-mail-Chatter:first").clientHeight;
|
||||
await contains(".o-mail-Chatter", { scroll: scrollValue2 - (clientHeight4 - clientHeight2) });
|
||||
});
|
||||
|
||||
test("basic chatter rendering", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ display_name: "second partner" });
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId, {
|
||||
arch: `
|
||||
<form string="Partners">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>`,
|
||||
});
|
||||
await contains(".o-mail-Chatter");
|
||||
});
|
||||
|
||||
test('chatter just contains "creating a new record" message during the creation of a new record after having displayed a chatter for an existing record', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
const views = {
|
||||
"res.partner,false,form": `
|
||||
<form string="Partners">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>`,
|
||||
};
|
||||
await start({ serverData: { views } });
|
||||
await openFormView("res.partner", partnerId);
|
||||
await click(".o_control_panel_main_buttons .o_form_button_create");
|
||||
await contains(".o-mail-Message");
|
||||
await contains(".o-mail-Message-body", { text: "Creating a new record..." });
|
||||
});
|
||||
|
||||
test("should display subject when subject is not the same as the default subject", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const fakeId = pyEnv["res.fake"].create({ name: "Salutations, voyageur" });
|
||||
pyEnv["mail.message"].create({
|
||||
body: "not empty",
|
||||
model: "res.fake",
|
||||
res_id: fakeId,
|
||||
subject: "Another Subject",
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.fake", fakeId);
|
||||
await contains(".o-mail-Message", { text: "Subject: Another Subjectnot empty" });
|
||||
});
|
||||
|
||||
test("should not display subject when subject is the same as the default subject", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const fakeId = pyEnv["res.fake"].create({ name: "Salutations, voyageur" });
|
||||
pyEnv["mail.message"].create({
|
||||
body: "not empty",
|
||||
model: "res.fake",
|
||||
res_id: fakeId,
|
||||
subject: "Custom Default Subject",
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.fake", fakeId);
|
||||
await contains(".o-mail-Message", { text: "not empty" });
|
||||
await contains(".o-mail-Message", {
|
||||
count: 0,
|
||||
text: "Subject: Custom Default Subjectnot empty",
|
||||
});
|
||||
});
|
||||
|
||||
test("should not display subject when subject is the same as the thread name with custom default subject", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const fakeId = pyEnv["res.fake"].create({ name: "Salutations, voyageur" });
|
||||
pyEnv["mail.message"].create({
|
||||
body: "not empty",
|
||||
model: "res.fake",
|
||||
res_id: fakeId,
|
||||
subject: "Salutations, voyageur",
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.fake", fakeId);
|
||||
await contains(".o-mail-Message", { text: "not empty" });
|
||||
await contains(".o-mail-Message", {
|
||||
count: 0,
|
||||
text: "Subject: Custom Default Subjectnot empty",
|
||||
});
|
||||
});
|
||||
|
||||
test("chatter updating", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
|
||||
{ display_name: "first partner" },
|
||||
{ display_name: "second partner" },
|
||||
]);
|
||||
pyEnv["mail.message"].create({
|
||||
body: "not empty",
|
||||
model: "res.partner",
|
||||
res_id: partnerId_2,
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId_1, {
|
||||
arch: `
|
||||
<form string="Partners">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>`,
|
||||
resIds: [partnerId_1, partnerId_2],
|
||||
});
|
||||
await click(".o_pager_next");
|
||||
await contains(".o-mail-Message");
|
||||
});
|
||||
|
||||
test("chatter message actions appear only after saving the form", async () => {
|
||||
await start();
|
||||
await openFormView("res.partner");
|
||||
await contains(".o-mail-Message");
|
||||
await contains(".o-mail-Message-actions", { count: 0 });
|
||||
await click(".o_form_button_save");
|
||||
await click("button", { text: "Send message" });
|
||||
await insertText(".o-mail-Composer-input", "hey");
|
||||
await click(".o-mail-Composer-send:enabled");
|
||||
await contains(".o-mail-Message-actions");
|
||||
});
|
||||
|
||||
test("post message on draft record", async () => {
|
||||
await start();
|
||||
await openFormView("res.partner", undefined, {
|
||||
arch: `
|
||||
<form string="Partners">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>`,
|
||||
});
|
||||
await click("button", { text: "Send message" });
|
||||
await insertText(".o-mail-Composer-input", "Test");
|
||||
await click(".o-mail-Composer button[aria-label='Send']:enabled");
|
||||
await contains(".o-mail-Message");
|
||||
await contains(".o-mail-Message-content", { text: "Test" });
|
||||
});
|
||||
|
||||
test("schedule activities on draft record should prompt with scheduling an activity (proceed with action)", async () => {
|
||||
const wizardOpened = new Deferred();
|
||||
mockService("action", {
|
||||
doAction(action, options) {
|
||||
if (action.res_model === "res.partner") {
|
||||
return super.doAction(...arguments);
|
||||
} else if (action.res_model === "mail.activity.schedule") {
|
||||
asyncStep("mail.activity.schedule");
|
||||
expect(action.context.active_model).toBe("res.partner");
|
||||
expect(Number(action.context.active_id)).toBeGreaterThan(0);
|
||||
options.onClose();
|
||||
wizardOpened.resolve();
|
||||
} else {
|
||||
asyncStep("Unexpected action" + action.res_model);
|
||||
}
|
||||
},
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", undefined, {
|
||||
arch: `
|
||||
<form string="Partners">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>`,
|
||||
});
|
||||
await click("button", { text: "Activity" });
|
||||
await wizardOpened;
|
||||
await waitForSteps(["mail.activity.schedule"]);
|
||||
});
|
||||
|
||||
test("upload attachment on draft record", async () => {
|
||||
const text = new File(["hello, world"], "text.text", { type: "text/plain" });
|
||||
await start();
|
||||
await openFormView("res.partner", undefined, {
|
||||
arch: `
|
||||
<form string="Partners">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>`,
|
||||
});
|
||||
await contains("button[aria-label='Attach files']");
|
||||
await contains("button[aria-label='Attach files']", { count: 0, text: "1" });
|
||||
await dragenterFiles(".o-mail-Chatter", [text]);
|
||||
await dropFiles(".o-Dropzone", [text]);
|
||||
await contains("button[aria-label='Attach files']", { text: "1" });
|
||||
});
|
||||
|
||||
test("Follower count of draft record is set to 0", async () => {
|
||||
await start();
|
||||
await openFormView("res.partner");
|
||||
await contains(".o-mail-Followers", { text: "0" });
|
||||
});
|
||||
|
||||
test("Mentions in composer should still work when using pager", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
|
||||
{ display_name: "Partner 1" },
|
||||
{ display_name: "Partner 2" },
|
||||
]);
|
||||
await patchUiSize({ size: SIZES.LG });
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId_1, { resIds: [partnerId_1, partnerId_2] });
|
||||
await click("button", { text: "Log note" });
|
||||
await click(".o_pager_next");
|
||||
await insertText(".o-mail-Composer-input", "@");
|
||||
// all records in DB: Mitchell Admin | Hermit | Public user except OdooBot
|
||||
await contains(".o-mail-Composer-suggestion", { count: 3 });
|
||||
});
|
||||
|
||||
test("form views in dialogs do not have chatter", async () => {
|
||||
defineActions([
|
||||
{
|
||||
id: 1,
|
||||
name: "Partner",
|
||||
res_model: "res.partner",
|
||||
views: [[false, "form"]],
|
||||
target: "new",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await getService("action").doAction(1);
|
||||
await contains(".o_dialog .o_form_view");
|
||||
await contains(".o-mail-Form-Chatter", { count: 0 });
|
||||
});
|
||||
|
||||
test("should display the subject even if the record name is false", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const fakeId = pyEnv["res.fake"].create({ name: false });
|
||||
pyEnv["mail.message"].create({
|
||||
body: "not empty",
|
||||
model: "res.fake",
|
||||
res_id: fakeId,
|
||||
subject: "Salutations, voyageur",
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.fake", fakeId);
|
||||
await contains(".o-mail-Message", { text: "Subject: Salutations, voyageurnot empty" });
|
||||
});
|
||||
|
||||
test("Update message recipients without saving", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].write([serverState.partnerId], { email: "mitchell@odoo.com" });
|
||||
const partnerId = pyEnv["res.partner"].create({
|
||||
name: "John Doe",
|
||||
email: "john@doe.be",
|
||||
});
|
||||
const fakeId = pyEnv["res.fake"].create({
|
||||
name: "John Doe",
|
||||
partner_id: partnerId,
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.fake", fakeId);
|
||||
await click("button", { text: "Send message" });
|
||||
await contains(".o-mail-RecipientsInput .o_tag_badge_text", { text: "John Doe" });
|
||||
await click(".o_field_many2one_selection input");
|
||||
await click(".o-autocomplete--dropdown-item", { text: "Mitchell Admin" });
|
||||
await contains(".o-mail-RecipientsInput .o_tag_badge_text", { text: "Mitchell Admin" });
|
||||
});
|
||||
|
||||
test("Update primary email in recipient without saving", async () => {
|
||||
const pyEnv = await startServer();
|
||||
pyEnv["res.partner"].write([serverState.partnerId], { email: "mitchell@odoo.com" });
|
||||
const partnerId = pyEnv["res.partner"].create({
|
||||
name: "John Doe",
|
||||
email: "john@doe.be",
|
||||
});
|
||||
const fakeId = pyEnv["res.fake"].create({
|
||||
name: "Fake record",
|
||||
partner_id: partnerId,
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.fake", fakeId);
|
||||
await click("button", { text: "Send message" });
|
||||
await insertText("div[name='email_cc'] input", "test@test.be");
|
||||
document.querySelector("div[name='email_cc'] input").blur();
|
||||
await contains(".o-mail-RecipientsInput .o_tag_badge_text", { text: "test@test.be" });
|
||||
});
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
import {
|
||||
SIZES,
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
insertText,
|
||||
openFormView,
|
||||
patchUiSize,
|
||||
scroll,
|
||||
start,
|
||||
startServer,
|
||||
triggerHotkey,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { HIGHLIGHT_CLASS } from "@mail/core/common/message_search_hook";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("Chatter should display search icon", async () => {
|
||||
const pyEnv = await startServer();
|
||||
patchUiSize({ size: SIZES.XXL });
|
||||
await start();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains("[title='Search Messages']");
|
||||
});
|
||||
|
||||
test("Click on the search icon should open the search form", async () => {
|
||||
const pyEnv = await startServer();
|
||||
patchUiSize({ size: SIZES.XXL });
|
||||
await start();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
|
||||
await openFormView("res.partner", partnerId);
|
||||
await click("[title='Search Messages']");
|
||||
await contains(".o_searchview");
|
||||
await contains(".o_searchview_input");
|
||||
});
|
||||
|
||||
test("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");
|
||||
await click(".o-mail-MessageCard-jump");
|
||||
await contains(".o-mail-Message.o-highlighted .o-mail-Message-content", { text: "not empty" });
|
||||
});
|
||||
|
||||
test("Close button should close the search panel", 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(".o-mail-Chatter-topbar [title='Search Messages']");
|
||||
await insertText(".o_searchview_input", "empty");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-SearchMessageResult .o-mail-Message");
|
||||
await click(".o-mail-SearchMessageInput [title='Close']");
|
||||
await contains(".o-mail-SearchMessageInput", { count: 0 });
|
||||
});
|
||||
|
||||
test("Search in chatter should be hightligted", 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 .${HIGHLIGHT_CLASS}`);
|
||||
});
|
||||
|
||||
test("Scrolling bottom in non-aside chatter should load more searched message", async () => {
|
||||
patchUiSize({ size: SIZES.LG }); // non-aside
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ name: "John Doe" });
|
||||
for (let i = 0; i < 60; i++) {
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: serverState.partnerId,
|
||||
body: "This is a message",
|
||||
attachment_ids: [],
|
||||
message_type: "comment",
|
||||
model: "res.partner",
|
||||
res_id: partnerId,
|
||||
});
|
||||
}
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await click("[title='Search Messages']");
|
||||
await insertText(".o_searchview_input", "message");
|
||||
triggerHotkey("Enter");
|
||||
await contains(".o-mail-SearchMessageResult .o-mail-Message", { count: 30 });
|
||||
await scroll(".o_content", "bottom");
|
||||
await contains(".o-mail-SearchMessageResult .o-mail-Message", { count: 60 });
|
||||
});
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
listenStoreFetch,
|
||||
openFormView,
|
||||
start,
|
||||
startServer,
|
||||
waitStoreFetch,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { Deferred, advanceTime } from "@odoo/hoot-mock";
|
||||
import { asyncStep, waitForSteps } from "@web/../tests/web_test_helpers";
|
||||
|
||||
import { DELAY_FOR_SPINNER } from "@mail/chatter/web_portal/chatter";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("base rendering", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains(".o-mail-Chatter-topbar");
|
||||
await contains("button", { text: "Send message" });
|
||||
await contains("button", { text: "Log note" });
|
||||
await contains("button", { text: "Activity" });
|
||||
await contains("button[aria-label='Attach files']");
|
||||
await contains(".o-mail-Followers");
|
||||
});
|
||||
|
||||
test("rendering with multiple partner followers", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [partnerId_1, partnerId_2, partnerId_3] = pyEnv["res.partner"].create([
|
||||
{ name: "Eden Hazard" },
|
||||
{ name: "Jean Michang" },
|
||||
{},
|
||||
]);
|
||||
pyEnv["mail.followers"].create([
|
||||
{
|
||||
partner_id: partnerId_2,
|
||||
res_id: partnerId_3,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
{
|
||||
partner_id: partnerId_1,
|
||||
res_id: partnerId_3,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId_3);
|
||||
await contains(".o-mail-Followers");
|
||||
await contains(".o-mail-Followers-button");
|
||||
await click(".o-mail-Followers-button");
|
||||
await contains(".o-mail-Followers-dropdown");
|
||||
await contains(".o-mail-Follower", { count: 2 });
|
||||
await contains(":nth-child(1 of .o-mail-Follower)", { text: "Jean Michang" });
|
||||
await contains(":nth-child(2 of .o-mail-Follower)", { text: "Eden Hazard" });
|
||||
});
|
||||
|
||||
test("log note toggling", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains("button:not(.active)", { text: "Log note" });
|
||||
await contains(".o-mail-Composer", { count: 0 });
|
||||
await click("button", { text: "Log note" });
|
||||
await contains("button.active", { text: "Log note" });
|
||||
await contains(".o-mail-Composer .o-mail-Composer-input[placeholder='Log an internal note…']");
|
||||
await click("button", { text: "Log note" });
|
||||
await contains("button:not(.active)", { text: "Log note" });
|
||||
await contains(".o-mail-Composer", { count: 0 });
|
||||
});
|
||||
|
||||
test("send message toggling", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains("button:not(.active)", { text: "Send message" });
|
||||
await contains(".o-mail-Composer", { count: 0 });
|
||||
await click("button", { text: "Send message" });
|
||||
await contains("button.active", { text: "Send message" });
|
||||
await contains(".o-mail-Composer-input[placeholder='Send a message to followers…']");
|
||||
await click("button", { text: "Send message" });
|
||||
await contains("button:not(.active)", { text: "Send message" });
|
||||
await contains(".o-mail-Composer", { count: 0 });
|
||||
});
|
||||
|
||||
test("log note/send message switching", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains("button:not(.active)", { text: "Send message" });
|
||||
await contains("button:not(.active)", { text: "Log note" });
|
||||
await contains(".o-mail-Composer", { count: 0 });
|
||||
await click("button", { text: "Send message" });
|
||||
await contains("button.active", { text: "Send message" });
|
||||
await contains("button:not(.active)", { text: "Log note" });
|
||||
await contains(".o-mail-Composer-input[placeholder='Send a message to followers…']");
|
||||
await click("button", { text: "Log note" });
|
||||
await contains("button:not(.active)", { text: "Send message" });
|
||||
await contains("button.active", { text: "Log note" });
|
||||
await contains(".o-mail-Composer-input[placeholder='Log an internal note…']");
|
||||
});
|
||||
|
||||
test("attachment counter without attachments", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains("button[aria-label='Attach files']");
|
||||
await contains("button[aria-label='Attach files']", { count: 0, text: "0" });
|
||||
});
|
||||
|
||||
test("attachment counter with attachments", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
pyEnv["ir.attachment"].create([
|
||||
{
|
||||
mimetype: "text/plain",
|
||||
name: "Blah.txt",
|
||||
res_id: partnerId,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
{
|
||||
mimetype: "text/plain",
|
||||
name: "Blu.txt",
|
||||
res_id: partnerId,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains("button[aria-label='Attach files']", { text: "2" });
|
||||
});
|
||||
|
||||
test("attachment counter while loading attachments", async () => {
|
||||
const def = new Deferred();
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
listenStoreFetch("mail.thread", {
|
||||
async onRpc() {
|
||||
asyncStep("before mail.thread");
|
||||
await def;
|
||||
},
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains("button[aria-label='Attach files']");
|
||||
await advanceTime(DELAY_FOR_SPINNER);
|
||||
await contains("button[aria-label='Attach files'] .fa-spin");
|
||||
await contains("button[aria-label='Attach files']", { count: 0, text: "0" });
|
||||
await waitForSteps(["before mail.thread"]);
|
||||
def.resolve();
|
||||
await waitStoreFetch("mail.thread");
|
||||
});
|
||||
|
||||
test("attachment counter transition when attachments become loaded", async () => {
|
||||
const def = new Deferred();
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
listenStoreFetch("mail.thread", {
|
||||
async onRpc() {
|
||||
asyncStep("before mail.thread");
|
||||
await def;
|
||||
},
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains("button[aria-label='Attach files']");
|
||||
await advanceTime(DELAY_FOR_SPINNER);
|
||||
await contains("button[aria-label='Attach files'] .fa-spin");
|
||||
await waitForSteps(["before mail.thread"]);
|
||||
def.resolve();
|
||||
await waitStoreFetch("mail.thread");
|
||||
await contains("button[aria-label='Attach files'] .fa-spin", { count: 0 });
|
||||
});
|
||||
|
||||
test("attachment icon open directly the file uploader if there is no attachment yet", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains(".o-mail-Chatter-fileUploader");
|
||||
await contains(".o-mail-AttachmentBox", { count: 0 });
|
||||
});
|
||||
|
||||
test("attachment icon open the attachment box when there is at least 1 attachment", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
pyEnv["ir.attachment"].create([
|
||||
{
|
||||
mimetype: "text/plain",
|
||||
name: "Blah.txt",
|
||||
res_id: partnerId,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains("button[aria-label='Attach files']");
|
||||
await contains(".o-mail-AttachmentBox", { count: 0 });
|
||||
await contains(".o-mail-Chatter-fileUploader", { count: 0 });
|
||||
await click("button[aria-label='Attach files']");
|
||||
await contains(".o-mail-AttachmentBox");
|
||||
await contains(".o-mail-Chatter-fileUploader");
|
||||
});
|
||||
|
||||
test("composer state conserved when clicking on another topbar button", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains(".o-mail-Chatter-topbar");
|
||||
await contains("button", { text: "Send message" });
|
||||
await contains("button", { text: "Log note" });
|
||||
await contains("button[aria-label='Attach files']");
|
||||
await click("button", { text: "Log note" });
|
||||
await contains("button.active", { text: "Log note" });
|
||||
await contains("button:not(.active)", { text: "Send message" });
|
||||
await click(".o-mail-Chatter-topbar button[aria-label='Attach files']");
|
||||
await contains("button.active", { text: "Log note" });
|
||||
await contains("button:not(.active)", { text: "Send message" });
|
||||
});
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
openFormView,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("base rendering follow, edit subscription and unfollow button", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const threadId = pyEnv["res.partner"].create({});
|
||||
await start();
|
||||
await openFormView("res.partner", threadId);
|
||||
await contains(".o-mail-Followers-counter", { text: "0" });
|
||||
await contains("[title='Show Followers'] .fa-user-o");
|
||||
await click("[title='Show Followers']");
|
||||
await click(".o-dropdown-item", { text: "Follow" });
|
||||
await contains(".o-mail-Followers-counter", { text: "1" });
|
||||
await contains("[title='Show Followers'] .fa-user");
|
||||
await click("[title='Show Followers']");
|
||||
await contains(".o-mail-Followers-dropdown");
|
||||
await click("[title='Edit subscription']");
|
||||
await contains(".o-mail-Followers-dropdown", { count: 0 });
|
||||
await click("[title='Show Followers']");
|
||||
await click(".o-dropdown-item", { text: "Unfollow" });
|
||||
await contains(".o-mail-Followers-counter", { text: "0" });
|
||||
await contains("[title='Show Followers'] .fa-user-o");
|
||||
});
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
editInput,
|
||||
onRpcBefore,
|
||||
openFormView,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { Deferred } from "@odoo/hoot-mock";
|
||||
import { asyncStep, mockService, onRpc, waitForSteps } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("base rendering not editable", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [threadId, partnerId] = pyEnv["res.partner"].create([
|
||||
{ hasWriteAccess: false },
|
||||
{ hasWriteAccess: false },
|
||||
]);
|
||||
pyEnv["mail.followers"].create({
|
||||
is_active: true,
|
||||
partner_id: partnerId,
|
||||
res_id: threadId,
|
||||
res_model: "res.partner",
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", threadId);
|
||||
await click(".o-mail-Followers-button");
|
||||
await contains(".o-mail-Follower");
|
||||
await contains(".o-mail-Follower-details");
|
||||
await contains(".o-mail-Follower-avatar");
|
||||
await contains(".o-mail-Follower-action", { count: 0 });
|
||||
});
|
||||
|
||||
test("base rendering editable", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [threadId, partnerId] = pyEnv["res.partner"].create([{}, {}]);
|
||||
pyEnv["mail.followers"].create({
|
||||
is_active: true,
|
||||
partner_id: partnerId,
|
||||
res_id: threadId,
|
||||
res_model: "res.partner",
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", threadId);
|
||||
await click(".o-mail-Followers-button");
|
||||
await contains(".o-mail-Follower");
|
||||
await contains(".o-mail-Follower-details");
|
||||
await contains(".o-mail-Follower-avatar");
|
||||
await contains(".o-mail-Follower");
|
||||
await contains("[title='Edit subscription']");
|
||||
await contains("[title='Remove this follower']");
|
||||
});
|
||||
|
||||
test("click on partner follower details", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [threadId, partnerId] = pyEnv["res.partner"].create([{}, {}]);
|
||||
pyEnv["mail.followers"].create({
|
||||
is_active: true,
|
||||
partner_id: partnerId,
|
||||
res_id: threadId,
|
||||
res_model: "res.partner",
|
||||
});
|
||||
const openFormDef = new Deferred();
|
||||
mockService("action", {
|
||||
doAction(action) {
|
||||
if (action?.res_id !== partnerId) {
|
||||
return super.doAction(...arguments);
|
||||
}
|
||||
asyncStep("do_action");
|
||||
expect(action.res_id).toBe(partnerId);
|
||||
expect(action.res_model).toBe("res.partner");
|
||||
expect(action.type).toBe("ir.actions.act_window");
|
||||
openFormDef.resolve();
|
||||
},
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", threadId);
|
||||
await click(".o-mail-Followers-button");
|
||||
await contains(".o-mail-Follower");
|
||||
await contains(".o-mail-Follower-details");
|
||||
await click(".o-mail-Follower-details:first");
|
||||
await openFormDef;
|
||||
await waitForSteps(["do_action"]); // redirect to partner profile
|
||||
});
|
||||
|
||||
test("click on edit follower", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [threadId, partnerId] = pyEnv["res.partner"].create([{}, {}]);
|
||||
pyEnv["mail.followers"].create({
|
||||
is_active: true,
|
||||
partner_id: partnerId,
|
||||
res_id: threadId,
|
||||
res_model: "res.partner",
|
||||
});
|
||||
onRpcBefore("/mail/read_subscription_data", () => asyncStep("fetch_subtypes"));
|
||||
await start();
|
||||
await openFormView("res.partner", threadId);
|
||||
await click(".o-mail-Followers-button");
|
||||
await contains(".o-mail-Follower");
|
||||
await contains("[title='Edit subscription']");
|
||||
await click("[title='Edit subscription']");
|
||||
await contains(".o-mail-Follower", { count: 0 });
|
||||
await waitForSteps(["fetch_subtypes"]);
|
||||
await contains(".o-mail-FollowerSubtypeDialog");
|
||||
});
|
||||
|
||||
test("edit follower and close subtype dialog", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [threadId, partnerId] = pyEnv["res.partner"].create([{}, {}]);
|
||||
pyEnv["mail.followers"].create({
|
||||
is_active: true,
|
||||
partner_id: partnerId,
|
||||
res_id: threadId,
|
||||
res_model: "res.partner",
|
||||
});
|
||||
onRpcBefore("/mail/read_subscription_data", () => asyncStep("fetch_subtypes"));
|
||||
await start();
|
||||
await openFormView("res.partner", threadId);
|
||||
await click(".o-mail-Followers-button");
|
||||
await contains(".o-mail-Follower");
|
||||
await contains("[title='Edit subscription']");
|
||||
await click("[title='Edit subscription']");
|
||||
await contains(".o-mail-FollowerSubtypeDialog");
|
||||
await waitForSteps(["fetch_subtypes"]);
|
||||
await click(".o-mail-FollowerSubtypeDialog button", { text: "Cancel" });
|
||||
await contains(".o-mail-FollowerSubtypeDialog", { count: 0 });
|
||||
});
|
||||
|
||||
test("remove a follower in a dirty form view", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [threadId, partnerId] = pyEnv["res.partner"].create([{}, {}]);
|
||||
pyEnv["discuss.channel"].create({ name: "General", display_name: "General" });
|
||||
pyEnv["mail.followers"].create({
|
||||
is_active: true,
|
||||
partner_id: partnerId,
|
||||
res_id: threadId,
|
||||
res_model: "res.partner",
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", threadId, {
|
||||
arch: `
|
||||
<form>
|
||||
<field name="name"/>
|
||||
<field name="channel_ids" widget="many2many_tags"/>
|
||||
<chatter/>
|
||||
</form>`,
|
||||
});
|
||||
await click(".o_field_many2many_tags[name='channel_ids'] input");
|
||||
await click(".dropdown-item", { text: "General" });
|
||||
await contains(".o_tag", { text: "General" });
|
||||
await contains(".o-mail-Followers-counter", { text: "1" });
|
||||
await editInput(document.body, ".o_field_char[name=name] input", "some value");
|
||||
await click(".o-mail-Followers-button");
|
||||
await click("[title='Remove this follower']");
|
||||
await contains(".o-mail-Followers-counter", { text: "0" });
|
||||
await contains(".o_field_char[name=name] input", { value: "some value" });
|
||||
await contains(".o_tag", { text: "General" });
|
||||
});
|
||||
|
||||
test("removing a follower should reload form view", async function () {
|
||||
const pyEnv = await startServer();
|
||||
const [threadId, partnerId] = pyEnv["res.partner"].create([{}, {}]);
|
||||
pyEnv["mail.followers"].create({
|
||||
is_active: true,
|
||||
partner_id: partnerId,
|
||||
res_id: threadId,
|
||||
res_model: "res.partner",
|
||||
});
|
||||
onRpc("res.partner", "web_read", ({ args }) => asyncStep(`read ${args[0][0]}`));
|
||||
await start();
|
||||
await openFormView("res.partner", threadId);
|
||||
await contains(".o-mail-Followers-button");
|
||||
await waitForSteps([`read ${threadId}`]);
|
||||
await click(".o-mail-Followers-button");
|
||||
await click("[title='Remove this follower']");
|
||||
await contains(".o-mail-Followers-counter", { text: "0" });
|
||||
await waitForSteps([`read ${threadId}`]);
|
||||
});
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
openFormView,
|
||||
scroll,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { tick } from "@odoo/hoot-dom";
|
||||
import { asyncStep, mockService, serverState, waitForSteps } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("base rendering not editable", async () => {
|
||||
await start();
|
||||
await openFormView("res.partner", undefined, {});
|
||||
await contains(".o-mail-Followers");
|
||||
await contains(".o-mail-Followers-button:disabled");
|
||||
await contains(".o-mail-Followers-dropdown", { count: 0 });
|
||||
await click(".o-mail-Followers-button");
|
||||
await contains(".o-mail-Followers-dropdown", { count: 0 });
|
||||
});
|
||||
|
||||
test("base rendering editable", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains(".o-mail-Followers");
|
||||
await contains(".o-mail-Followers-button");
|
||||
await contains(".o-mail-Followers-button:first:enabled");
|
||||
await contains(".o-mail-Followers-dropdown", { count: 0 });
|
||||
await click(".o-mail-Followers-button");
|
||||
await contains(".o-mail-Followers-dropdown");
|
||||
});
|
||||
|
||||
test('click on "add followers" button', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [partnerId_1, partnerId_2, partnerId_3] = pyEnv["res.partner"].create([
|
||||
{ name: "Partner1" },
|
||||
{ name: "François Perusse" },
|
||||
{ name: "Partner3" },
|
||||
]);
|
||||
pyEnv["mail.followers"].create({
|
||||
partner_id: partnerId_2,
|
||||
email: "bla@bla.bla",
|
||||
is_active: true,
|
||||
res_id: partnerId_1,
|
||||
res_model: "res.partner",
|
||||
});
|
||||
mockService("action", {
|
||||
doAction(action, options) {
|
||||
if (action?.res_model !== "mail.followers.edit") {
|
||||
return super.doAction(...arguments);
|
||||
}
|
||||
asyncStep("action:open_view");
|
||||
expect(action.context.default_res_model).toBe("res.partner");
|
||||
expect(action.context.default_res_ids).toEqual([partnerId_1]);
|
||||
expect(action.res_model).toBe("mail.followers.edit");
|
||||
expect(action.type).toBe("ir.actions.act_window");
|
||||
pyEnv["mail.followers"].create({
|
||||
partner_id: partnerId_3,
|
||||
email: "bla@bla.bla",
|
||||
is_active: true,
|
||||
name: "Wololo",
|
||||
res_id: partnerId_1,
|
||||
res_model: "res.partner",
|
||||
});
|
||||
options.onClose();
|
||||
},
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId_1);
|
||||
await contains(".o-mail-Followers");
|
||||
await contains(".o-mail-Followers-counter", { text: "1" });
|
||||
await click(".o-mail-Followers-button");
|
||||
await contains(".o-mail-Followers-dropdown");
|
||||
await click("a", { text: "Add Followers" });
|
||||
await contains(".o-mail-Followers-dropdown", { count: 0 });
|
||||
await waitForSteps(["action:open_view"]);
|
||||
await contains(".o-mail-Followers-counter", { text: "2" });
|
||||
await click(".o-mail-Followers-button");
|
||||
await contains(".o-mail-Follower", { count: 2 });
|
||||
await contains(":nth-child(1 of .o-mail-Follower)", { text: "François Perusse" });
|
||||
await contains(":nth-child(2 of .o-mail-Follower)", { text: "Partner3" });
|
||||
});
|
||||
|
||||
test("click on remove follower", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
|
||||
{ name: "Partner1" },
|
||||
{ name: "Partner2" },
|
||||
]);
|
||||
pyEnv["mail.followers"].create({
|
||||
partner_id: partnerId_2,
|
||||
email: "bla@bla.bla",
|
||||
is_active: true,
|
||||
name: "Wololo",
|
||||
res_id: partnerId_1,
|
||||
res_model: "res.partner",
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId_1);
|
||||
await click(".o-mail-Followers-button");
|
||||
await contains(".o-mail-Follower");
|
||||
await click("[title='Remove this follower']");
|
||||
await contains(".o-mail-Follower", { count: 0 });
|
||||
await contains(".o-mail-Followers-dropdown");
|
||||
});
|
||||
|
||||
test("Load 100 followers at once", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerIds = pyEnv["res.partner"].create(
|
||||
[...Array(210).keys()].map((i) => ({ display_name: `Partner${i}`, name: `Partner${i}` }))
|
||||
);
|
||||
pyEnv["mail.followers"].create(
|
||||
[...Array(210).keys()].map((i) => ({
|
||||
is_active: true,
|
||||
partner_id: i === 0 ? serverState.partnerId : partnerIds[i],
|
||||
res_id: partnerIds[0],
|
||||
res_model: "res.partner",
|
||||
}))
|
||||
);
|
||||
await start();
|
||||
await openFormView("res.partner", partnerIds[0]);
|
||||
await contains("button[title='Show Followers']", { text: "210" });
|
||||
await click("[title='Show Followers']");
|
||||
await contains(".o-mail-Follower", { count: 100 });
|
||||
await contains(".o-mail-Followers-dropdown", { text: "Load more" });
|
||||
await scroll(".o-mail-Followers-dropdown", "bottom");
|
||||
await contains(".o-mail-Follower", { count: 200 });
|
||||
await tick(); // give enough time for the useVisible hook to register load more as hidden
|
||||
await scroll(".o-mail-Followers-dropdown", "bottom");
|
||||
await contains(".o-mail-Follower", { count: 209 });
|
||||
await contains(".o-mail-Followers-dropdown span", { count: 0, text: "Load more" });
|
||||
});
|
||||
|
||||
test("Load 100 recipients at once", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerIds = pyEnv["res.partner"].create(
|
||||
[...Array(210).keys()].map((i) => ({
|
||||
display_name: `Partner${i}`,
|
||||
name: `Partner${i}`,
|
||||
email: `partner${i}@example.com`,
|
||||
}))
|
||||
);
|
||||
pyEnv["mail.followers"].create(
|
||||
[...Array(210).keys()].map((i) => ({
|
||||
is_active: true,
|
||||
partner_id: i === 0 ? serverState.partnerId : partnerIds[i],
|
||||
res_id: partnerIds[0],
|
||||
res_model: "res.partner",
|
||||
}))
|
||||
);
|
||||
await start();
|
||||
await openFormView("res.partner", partnerIds[0]);
|
||||
await contains("button[title='Show Followers']", { text: "210" });
|
||||
});
|
||||
|
||||
test('Show "Add follower" and subtypes edition/removal buttons on all followers if user has write access', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
|
||||
{ name: "Partner1" },
|
||||
{ name: "Partner2" },
|
||||
]);
|
||||
pyEnv["mail.followers"].create([
|
||||
{
|
||||
is_active: true,
|
||||
partner_id: serverState.partnerId,
|
||||
res_id: partnerId_1,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
{
|
||||
is_active: true,
|
||||
partner_id: partnerId_2,
|
||||
res_id: partnerId_1,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId_1);
|
||||
await click(".o-mail-Followers-button");
|
||||
await contains("a", { text: "Add Followers" });
|
||||
await contains(":nth-child(1 of .o-mail-Follower)", {
|
||||
contains: [["[title='Edit subscription']"], ["[title='Remove this follower']"]],
|
||||
});
|
||||
});
|
||||
|
||||
test('Show "No Followers" dropdown-item if there are no followers and user does not have write access', async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ hasWriteAccess: false });
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await click(".o-mail-Followers-button");
|
||||
await contains("div.disabled", { text: "No Followers" });
|
||||
});
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
import {
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
openFormView,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, test } from "@odoo/hoot";
|
||||
import { serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test("simplest layout of a followed subtype", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const subtypeId = pyEnv["mail.message.subtype"].create({
|
||||
default: true,
|
||||
name: "TestSubtype",
|
||||
});
|
||||
pyEnv["mail.followers"].create({
|
||||
display_name: "François Perusse",
|
||||
partner_id: serverState.partnerId,
|
||||
res_model: "res.partner",
|
||||
res_id: serverState.partnerId,
|
||||
subtype_ids: [subtypeId],
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", serverState.partnerId);
|
||||
await click(".o-mail-Followers-button");
|
||||
await click("[title='Edit subscription']");
|
||||
await contains(
|
||||
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id='${subtypeId}'] label`,
|
||||
{ text: "TestSubtype" }
|
||||
);
|
||||
await contains(
|
||||
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id='${subtypeId}'] input[type='checkbox']:checked`
|
||||
);
|
||||
});
|
||||
|
||||
test("simplest layout of a not followed subtype", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const subtypeId = pyEnv["mail.message.subtype"].create({
|
||||
default: true,
|
||||
name: "TestSubtype",
|
||||
});
|
||||
pyEnv["mail.followers"].create({
|
||||
display_name: "François Perusse",
|
||||
partner_id: serverState.partnerId,
|
||||
res_model: "res.partner",
|
||||
res_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", serverState.partnerId);
|
||||
await click(".o-mail-Followers-button");
|
||||
await click("[title='Edit subscription']");
|
||||
await contains(
|
||||
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id='${subtypeId}'] input[type='checkbox']:not(:checked)`
|
||||
);
|
||||
});
|
||||
|
||||
test("toggle follower subtype checkbox", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const subtypeId = pyEnv["mail.message.subtype"].create({
|
||||
default: true,
|
||||
name: "TestSubtype",
|
||||
});
|
||||
pyEnv["mail.followers"].create({
|
||||
display_name: "François Perusse",
|
||||
partner_id: serverState.partnerId,
|
||||
res_model: "res.partner",
|
||||
res_id: serverState.partnerId,
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", serverState.partnerId);
|
||||
await click(".o-mail-Followers-button");
|
||||
await click("[title='Edit subscription']");
|
||||
await contains(
|
||||
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id='${subtypeId}'] input[type='checkbox']:not(:checked)`
|
||||
);
|
||||
await click(
|
||||
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id='${subtypeId}'] input[type='checkbox']`
|
||||
);
|
||||
await contains(
|
||||
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id='${subtypeId}'] input[type='checkbox']:checked`
|
||||
);
|
||||
await click(
|
||||
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id='${subtypeId}'] input[type='checkbox']`
|
||||
);
|
||||
await contains(
|
||||
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id='${subtypeId}'] input[type='checkbox']:not(:checked)`
|
||||
);
|
||||
});
|
||||
|
||||
test("follower subtype apply", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const subtypeId1 = pyEnv["mail.message.subtype"].create({
|
||||
default: true,
|
||||
name: "TestSubtype1",
|
||||
});
|
||||
const subtypeId2 = pyEnv["mail.message.subtype"].create({
|
||||
default: true,
|
||||
name: "TestSubtype2",
|
||||
});
|
||||
pyEnv["mail.followers"].create({
|
||||
display_name: "François Perusse",
|
||||
partner_id: serverState.partnerId,
|
||||
res_model: "res.partner",
|
||||
res_id: serverState.partnerId,
|
||||
subtype_ids: [subtypeId1],
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", serverState.partnerId);
|
||||
await click(".o-mail-Followers-button");
|
||||
await click("[title='Edit subscription']");
|
||||
await contains(
|
||||
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id='${subtypeId1}'] input[type='checkbox']:checked`
|
||||
);
|
||||
await contains(
|
||||
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id='${subtypeId2}'] input[type='checkbox']:not(:checked)`
|
||||
);
|
||||
await click(
|
||||
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id='${subtypeId1}'] input[type='checkbox']`
|
||||
);
|
||||
await contains(
|
||||
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id='${subtypeId1}'] input[type='checkbox']:not(:checked)`
|
||||
);
|
||||
await click(
|
||||
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id='${subtypeId2}'] input[type='checkbox']`
|
||||
);
|
||||
await contains(
|
||||
`.o-mail-FollowerSubtypeDialog-subtype[data-follower-subtype-id='${subtypeId2}'] input[type='checkbox']:checked`
|
||||
);
|
||||
await click(".modal-footer button", { text: "Apply" });
|
||||
await contains(".o_notification", {
|
||||
text: "The subscription preferences were successfully applied.",
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
import {
|
||||
SIZES,
|
||||
click,
|
||||
contains,
|
||||
defineMailModels,
|
||||
insertText,
|
||||
openFormView,
|
||||
patchUiSize,
|
||||
scroll,
|
||||
start,
|
||||
startServer,
|
||||
} from "@mail/../tests/mail_test_helpers";
|
||||
import { describe, expect, test } from "@odoo/hoot";
|
||||
import { mockService, serverState } from "@web/../tests/web_test_helpers";
|
||||
|
||||
describe.current.tags("desktop");
|
||||
defineMailModels();
|
||||
|
||||
test.skip("Form view not scrolled when switching record", async () => {
|
||||
// FIXME: test passed in test environment but in practice scroll are reset to 0
|
||||
// HOOT matches behaviour in prod and shows tests not passing as expected
|
||||
const pyEnv = await startServer();
|
||||
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
|
||||
{
|
||||
description: [...Array(60).keys()].join("\n"),
|
||||
display_name: "Partner 1",
|
||||
},
|
||||
{
|
||||
description: [...Array(60).keys()].join("\n"),
|
||||
display_name: "Partner 2",
|
||||
},
|
||||
]);
|
||||
const messages = [...Array(60).keys()].map((id) => {
|
||||
return {
|
||||
body: "not empty",
|
||||
model: "res.partner",
|
||||
res_id: id < 29 ? partnerId_1 : partnerId_2,
|
||||
};
|
||||
});
|
||||
pyEnv["mail.message"].create(messages);
|
||||
patchUiSize({ size: SIZES.LG });
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId_1, {
|
||||
arch: `
|
||||
<form string="Partners">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
<field name="description"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>`,
|
||||
resIds: [partnerId_1, partnerId_2],
|
||||
});
|
||||
await contains(".o-mail-Message", { count: 29 });
|
||||
await contains(".o_content", { scroll: 0 });
|
||||
await scroll(".o_content", 150);
|
||||
await click(".o_pager_next");
|
||||
await contains(".o-mail-Message", { count: 30 });
|
||||
await contains(".o_content", { scroll: 150 });
|
||||
await scroll(".o_content", 0);
|
||||
await click(".o_pager_previous");
|
||||
await contains(".o-mail-Message", { count: 29 });
|
||||
await contains(".o_content", { scroll: 0 });
|
||||
});
|
||||
|
||||
test("Attachments that have been unlinked from server should be visually unlinked from record", async () => {
|
||||
// Attachments that have been fetched from a record at certain time and then
|
||||
// removed from the server should be reflected on the UI when the current
|
||||
// partner accesses this record again.
|
||||
const pyEnv = await startServer();
|
||||
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
|
||||
{ display_name: "Partner1" },
|
||||
{ display_name: "Partner2" },
|
||||
]);
|
||||
const [attachmentId_1] = pyEnv["ir.attachment"].create([
|
||||
{
|
||||
mimetype: "text.txt",
|
||||
res_id: partnerId_1,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
{
|
||||
mimetype: "text.txt",
|
||||
res_id: partnerId_1,
|
||||
res_model: "res.partner",
|
||||
},
|
||||
]);
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId_1, {
|
||||
arch: `
|
||||
<form string="Partners">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>`,
|
||||
resId: partnerId_1,
|
||||
resIds: [partnerId_1, partnerId_2],
|
||||
});
|
||||
await contains("button[aria-label='Attach files']", { text: "2" });
|
||||
// The attachment links are updated on (re)load,
|
||||
// so using pager is a way to reload the record "Partner1".
|
||||
await click(".o_pager_next");
|
||||
await contains("button[aria-label='Attach files']:not(:has(sup))");
|
||||
// Simulate unlinking attachment 1 from Partner 1.
|
||||
pyEnv["ir.attachment"].write([attachmentId_1], { res_id: 0 });
|
||||
await click(".o_pager_previous");
|
||||
await contains("button[aria-label='Attach files']", { text: "1" });
|
||||
});
|
||||
|
||||
test("ellipsis button is not duplicated when switching from read to edit mode", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: partnerId,
|
||||
// "data-o-mail-quote" added by server is intended to be compacted in ellipsis block
|
||||
body: `
|
||||
<div>
|
||||
Dear Joel Willis,<br>
|
||||
Thank you for your enquiry.<br>
|
||||
If you have any questions, please let us know.
|
||||
<br><br>
|
||||
Thank you,<br>
|
||||
<div data-o-mail-quote="1">-- <br data-o-mail-quote="1">
|
||||
System
|
||||
</div>
|
||||
</div>`,
|
||||
model: "res.partner",
|
||||
res_id: partnerId,
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId, {
|
||||
arch: `
|
||||
<form string="Partners">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>`,
|
||||
});
|
||||
await contains(".o-mail-Chatter");
|
||||
await contains(".o-mail-Message");
|
||||
await contains(".o-mail-ellipsis");
|
||||
});
|
||||
|
||||
test("[TECHNICAL] unfolded ellipsis button should not fold on message click besides that button", async () => {
|
||||
// message click triggers a re-render. Before writing of this test, the
|
||||
// insertion of ellipsis button were done during render. This meant
|
||||
// any re-render would re-insert the ellipsis button. If some buttons
|
||||
// were unfolded, any re-render would fold them again.
|
||||
//
|
||||
// This previous behavior is undesirable, and results to bothersome UX
|
||||
// such as inability to copy/paste unfolded message content due to click
|
||||
// from text selection automatically folding all ellipsis buttons.
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({ display_name: "Someone" });
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: partnerId,
|
||||
// "data-o-mail-quote" added by server is intended to be compacted in ellipsis block
|
||||
body: `
|
||||
<div>
|
||||
Dear Joel Willis,<br>
|
||||
Thank you for your enquiry.<br>
|
||||
If you have any questions, please let us know.
|
||||
<br><br>
|
||||
Thank you,<br>
|
||||
<span data-o-mail-quote="1">-- <br data-o-mail-quote="1">
|
||||
System
|
||||
</span>
|
||||
</div>`,
|
||||
model: "res.partner",
|
||||
res_id: partnerId,
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId, {
|
||||
arch: `
|
||||
<form string="Partners">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>`,
|
||||
});
|
||||
expect(".o-mail-Message-body span").toHaveCount(0);
|
||||
await click(".o-mail-ellipsis");
|
||||
expect(".o-mail-Message-body span").toHaveText('--\nSystem')
|
||||
await click(".o-mail-Message");
|
||||
expect(".o-mail-Message-body span").toHaveCount(1);
|
||||
});
|
||||
|
||||
test("ellipsis button on message of type notification", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
pyEnv["mail.message"].create({
|
||||
author_id: partnerId,
|
||||
// "data-o-mail-quote" enables ellipsis block
|
||||
body: `
|
||||
<div>
|
||||
Dear Joel Willis,<br>
|
||||
Thank you for your enquiry.<br>
|
||||
If you have any questions, please let us know.
|
||||
<br><br>
|
||||
Thank you,<br>
|
||||
<span data-o-mail-quote="1">-- <br data-o-mail-quote="1">
|
||||
System
|
||||
</span>
|
||||
</div>`,
|
||||
model: "res.partner",
|
||||
res_id: partnerId,
|
||||
message_type: "notification",
|
||||
});
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId, {
|
||||
arch: `
|
||||
<form string="Partners">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
</sheet>
|
||||
<chatter/>
|
||||
</form>`,
|
||||
});
|
||||
await contains(".o-mail-ellipsis");
|
||||
});
|
||||
|
||||
test("read more/less should appear only once for the signature", async () => {
|
||||
const pyEnv = await startServer();
|
||||
const partnerId = pyEnv["res.partner"].create({});
|
||||
|
||||
mockService("action", {
|
||||
doAction(action, { onClose }) {
|
||||
if (action.name === "Compose Email") {
|
||||
// Simulate message post of full composer
|
||||
pyEnv["mail.message"].create({
|
||||
body: action.context.default_body.toString(),
|
||||
model: action.context.default_model,
|
||||
res_id: action.context.default_res_ids[0],
|
||||
});
|
||||
return onClose(undefined);
|
||||
}
|
||||
return super.doAction(...arguments);
|
||||
},
|
||||
});
|
||||
|
||||
// Yes you can get this kind of signature by playing with the html editor
|
||||
pyEnv["res.users"].write(serverState.userId, {
|
||||
signature: `
|
||||
<div>
|
||||
<span data-o-mail-quote="1">
|
||||
--
|
||||
</span>
|
||||
</div>
|
||||
<div data-o-mail-quote="1">
|
||||
Signature !
|
||||
</div>
|
||||
<div>
|
||||
<br data-o-mail-quote="1">
|
||||
</div>
|
||||
`,
|
||||
});
|
||||
|
||||
await start();
|
||||
await openFormView("res.partner", partnerId);
|
||||
await contains(".o-mail-Chatter");
|
||||
await click(".o-mail-Chatter-sendMessage");
|
||||
await insertText(".o-mail-Composer-input", "Example Body");
|
||||
await click("[name='open-full-composer']");
|
||||
await contains(".o-mail-Message-body", { text: "Example Body", count: 1 });
|
||||
expect(".o-mail-Message .o-signature-container button.o-mail-ellipsis").toHaveCount(1);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue