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,35 @@
import {
click,
contains,
defineMailModels,
openFormView,
start,
startServer,
} from "@mail/../tests/mail_test_helpers";
import { describe, expect, test } from "@odoo/hoot";
import { asyncStep, onRpc, serverState, waitForSteps } from "@web/../tests/web_test_helpers";
defineMailModels();
describe.current.tags("desktop");
test("Manage messages", async () => {
serverState.debug = "1";
const pyEnv = await startServer();
onRpc("mail.message", "web_search_read", (params) => {
expect(params.kwargs.context.default_res_id).toBe(partnerId);
expect(params.kwargs.context.default_res_model).toBe("res.partner");
expect(params.kwargs.domain).toEqual([
"&",
["res_id", "=", partnerId],
["model", "=", "res.partner"],
]);
asyncStep("message_read");
});
await start();
const partnerId = pyEnv["res.partner"].create({ name: "Bob" });
await openFormView("res.partner", partnerId);
await click(".o_debug_manager .dropdown-toggle");
await click(".dropdown-item", { text: "Messages" });
await waitForSteps(["message_read"]);
await contains(".o_breadcrumb .active > span", { text: "Messages" });
});

View file

@ -0,0 +1,27 @@
import { click, contains, defineMailModels, start } from "@mail/../tests/mail_test_helpers";
import { describe, test } from "@odoo/hoot";
import { mountWithCleanup, serverState } from "@web/../tests/web_test_helpers";
import { Avatar } from "@mail/views/web/fields/avatar/avatar";
describe.current.tags("desktop");
defineMailModels();
test("basic rendering", async () => {
await start();
await mountWithCleanup(Avatar, {
props: {
resId: serverState.userId,
resModel: "res.users",
displayName: "User display name",
},
});
await contains(".o-mail-Avatar");
await contains(".o-mail-Avatar img");
await contains(".o-mail-Avatar img[data-src='/web/image/res.users/7/avatar_128']");
await contains(".o-mail-Avatar span");
await contains(".o-mail-Avatar span", { text: "User display name" });
await contains(".o_avatar_card", { count: 0 });
await click(".o-mail-Avatar img");
await contains(".o_avatar_card");
});

View file

@ -0,0 +1,35 @@
import {
click,
contains,
defineMailModels,
insertText,
openFormView,
start,
} from "@mail/../tests/mail_test_helpers";
import { describe, test } from "@odoo/hoot";
import { serverState } from "@web/../tests/web_test_helpers";
defineMailModels();
describe.current.tags("desktop");
test("insert emoji at end of word", async () => {
await start();
await openFormView("res.partner", serverState.partnerId, {
arch: `<form><field name="name" widget="text_emojis"/></form>`,
});
await insertText("textarea#name_0", "Hello", { replace: true });
await click(".o_field_text_emojis button");
await click('.o-Emoji[data-codepoints="😀"]');
await contains("textarea#name_0", { value: "Hello😀" });
});
test("insert emoji as new word", async () => {
await start();
await openFormView("res.partner", serverState.partnerId, {
arch: `<form><field name="name" widget="text_emojis"/></form>`,
});
await insertText("textarea#name_0", "Hello ", { replace: true });
await click(".o_field_text_emojis button");
await click('.o-Emoji[data-codepoints="😀"]');
await contains("textarea#name_0", { value: "Hello 😀" });
});

View file

@ -0,0 +1,35 @@
import {
click,
contains,
defineMailModels,
insertText,
openFormView,
start,
} from "@mail/../tests/mail_test_helpers";
import { describe, test } from "@odoo/hoot";
import { serverState } from "@web/../tests/web_test_helpers";
defineMailModels();
describe.current.tags("desktop");
test("insert emoji at end of word", async () => {
await start();
await openFormView("res.partner", serverState.partnerId, {
arch: `<form><field name="name" widget="char_emojis"/></form>`,
});
await insertText("input#name_0", "Hello", { replace: true });
await click(".o_field_char_emojis button");
await click('.o-Emoji[data-codepoints="😀"]');
await contains("input#name_0", { value: "Hello😀" });
});
test("insert emoji as new word", async () => {
await start();
await openFormView("res.partner", serverState.partnerId, {
arch: `<form><field name="name" widget="char_emojis"/></form>`,
});
await insertText("input#name_0", "Hello ", { replace: true });
await click(".o_field_char_emojis button");
await click('.o-Emoji[data-codepoints="😀"]');
await contains("input#name_0", { value: "Hello 😀" });
});

View file

@ -0,0 +1,431 @@
import {
click,
contains,
defineMailModels,
openFormView,
openKanbanView,
openListView,
start,
startServer,
triggerHotkey,
} from "@mail/../tests/mail_test_helpers";
import { describe, expect, test } from "@odoo/hoot";
import { registry } from "@web/core/registry";
import { getOrigin } from "@web/core/utils/urls";
defineMailModels();
describe.current.tags("desktop");
test("many2many_avatar_user in kanban view", async () => {
const pyEnv = await startServer();
const userIds = pyEnv["res.users"].create([
{ partner_id: pyEnv["res.partner"].create({ name: "Mario" }) },
{ partner_id: pyEnv["res.partner"].create({ name: "Yoshi" }) },
{ partner_id: pyEnv["res.partner"].create({ name: "Luigi" }) },
{ partner_id: pyEnv["res.partner"].create({ name: "Tapu" }) },
]);
pyEnv["m2x.avatar.user"].create({ user_ids: userIds });
await start();
await openKanbanView("m2x.avatar.user", {
arch: `
<kanban>
<templates>
<t t-name="card">
<field name="user_id"/>
<field name="user_ids" widget="many2many_avatar_user"/>
</t>
</templates>
</kanban>
`,
});
expect(".o_kanban_record .o_field_many2many_avatar_user .o_m2m_avatar_empty").toHaveText("+2");
await click(".o_kanban_record .o_field_many2many_avatar_user .o_quick_assign");
await contains(".o_popover > .o_field_tags > .o_tag", { count: 4 });
await contains(".o_popover > .o_field_tags > :nth-child(1 of .o_tag)", { text: "Tapu" });
await contains(".o_popover > .o_field_tags > :nth-child(2 of .o_tag)", { text: "Luigi" });
await contains(".o_popover > .o_field_tags > :nth-child(3 of .o_tag)", { text: "Yoshi" });
await contains(".o_popover > .o_field_tags > :nth-child(4 of .o_tag)", { text: "Mario" });
});
test('many2one_avatar_user widget edited by the smart action "Assign to..."', async () => {
const pyEnv = await startServer();
const [userId_1] = pyEnv["res.users"].create([
{ partner_id: pyEnv["res.partner"].create({ name: "Mario" }) },
{ partner_id: pyEnv["res.partner"].create({ name: "Luigi" }) },
{ partner_id: pyEnv["res.partner"].create({ name: "Yoshi" }) },
]);
const avatarUserId_1 = pyEnv["m2x.avatar.user"].create({ user_id: userId_1 });
await start();
await openFormView("m2x.avatar.user", avatarUserId_1, {
arch: "<form><field name='user_id' widget='many2one_avatar_user'/></form>",
});
await contains(".o_field_many2one_avatar_user .o_external_button");
await contains(".o_field_many2one_avatar_user input", { value: "Mario" });
triggerHotkey("control+k");
await click(".o_command", { text: "Assign to ...ALT + I" });
await contains(".o_command", { count: 6 });
await contains(":nth-child(1 of .o_command)", { text: "Mitchell Admin" });
await contains(":nth-child(2 of .o_command)", { text: "Public user" });
await contains(":nth-child(3 of .o_command)", { text: "OdooBot" });
await contains(":nth-child(4 of .o_command)", { text: "Mario" });
await contains(":nth-child(5 of .o_command)", { text: "Luigi" });
await contains(":nth-child(6 of .o_command)", { text: "Yoshi" });
await click(".o_command", { text: "Luigi" });
await contains(".o_field_many2one_avatar_user input", { value: "Luigi" });
});
test('many2many_avatar_user widget edited by the smart action "Assign to..."', async () => {
const pyEnv = await startServer();
const [userId_1, userId_2] = pyEnv["res.users"].create([
{ partner_id: pyEnv["res.partner"].create({ name: "Mario" }) },
{ partner_id: pyEnv["res.partner"].create({ name: "Yoshi" }) },
{ partner_id: pyEnv["res.partner"].create({ name: "Luigi" }) },
]);
const m2xAvatarUserId1 = pyEnv["m2x.avatar.user"].create({ user_ids: [userId_1, userId_2] });
await start();
await openFormView("m2x.avatar.user", m2xAvatarUserId1, {
arch: "<form><field name='user_ids' widget='many2many_avatar_user'/></form>",
});
await contains(".o_tag_badge_text", { count: 2 });
await contains(":nth-child(1 of .o_tag) .o_tag_badge_text", { text: "Mario" });
await contains(":nth-child(2 of .o_tag) .o_tag_badge_text", { text: "Yoshi" });
triggerHotkey("control+k");
await contains(".o_command", { text: "Assign to ...ALT + I" });
// Assign Luigi
triggerHotkey("alt+i");
await contains(".o_command", { count: 4 });
await contains(":nth-child(1 of .o_command)", { text: "Mitchell Admin" });
await contains(":nth-child(2 of .o_command)", { text: "Public user" });
await contains(":nth-child(3 of .o_command)", { text: "OdooBot" });
await contains(":nth-child(4 of .o_command)", { text: "Luigi" });
await click(".o_command", { text: "Luigi" });
await contains(".o_tag_badge_text", { count: 3 });
await contains(":nth-child(1 of .o_tag) .o_tag_badge_text", { text: "Mario" });
await contains(":nth-child(2 of .o_tag) .o_tag_badge_text", { text: "Yoshi" });
await contains(":nth-child(3 of .o_tag) .o_tag_badge_text", { text: "Luigi" });
});
test('many2one_avatar_user widget edited by the smart action "Assign to me" in form view', async () => {
const pyEnv = await startServer();
const userId = pyEnv["res.users"].create({
partner_id: pyEnv["res.partner"].create({ name: "Mario" }),
});
const avatarUserId_1 = pyEnv["m2x.avatar.user"].create({ user_id: userId });
await start();
await openFormView("m2x.avatar.user", avatarUserId_1, {
arch: "<form><field name='user_id' widget='many2one_avatar_user'/></form>",
});
await contains(".o_field_many2one_avatar_user input", { value: "Mario" });
await triggerHotkey("control+k");
await contains(".o_command", { text: "Assign to meALT + SHIFT + I" });
await triggerHotkey("alt+shift+i");
await contains(".o_field_many2one_avatar_user input", { value: "Mitchell Admin" });
// Unassign me
await triggerHotkey("control+k");
await click(".o_command", { text: "Unassign from meALT + SHIFT + I" });
await contains(".o_field_many2one_avatar_user input", { value: "" });
});
test('many2one_avatar_user widget edited by the smart action "Assign to me"', async () => {
const pyEnv = await startServer();
const userId_1 = pyEnv["res.users"].create({
partner_id: pyEnv["res.partner"].create({ name: "Mario" }),
});
const avatarUserId_1 = pyEnv["m2x.avatar.user"].create({ user_id: userId_1 });
await start();
await openFormView("m2x.avatar.user", avatarUserId_1, {
arch: "<form><field name='user_id' widget='many2one_avatar_user'/></form>",
});
await contains(".o_field_many2one_avatar_user input", { value: "Mario" });
triggerHotkey("control+k");
await contains(".o_command", { text: "Assign to meALT + SHIFT + I" });
// Assign to me
triggerHotkey("alt+shift+i");
await contains(".o_field_many2one_avatar_user input", { value: "Mitchell Admin" });
// Unassign from me
triggerHotkey("control+k");
await click(".o_command", { text: "Unassign from meALT + SHIFT + I" });
await contains(".o_field_many2one_avatar_user input", { value: "" });
});
test('many2one_avatar_user widget edited by the smart action "Assign to me" in list view', async () => {
const pyEnv = await startServer();
const [userId_1, userId_2] = pyEnv["res.users"].create([
{ partner_id: pyEnv["res.partner"].create({ name: "Mario" }) },
{ partner_id: pyEnv["res.partner"].create({ name: "Luigi" }) },
]);
pyEnv["m2x.avatar.user"].create([{ user_id: userId_2 }, { user_id: userId_1 }]);
await start();
await openListView("m2x.avatar.user", {
arch: "<list multi_edit='1'><field name='user_id' widget='many2one_avatar_user'/></list>",
});
await contains(":nth-child(1 of .o_data_row) .o_field_many2one_avatar_user .o_many2one", {
text: "Luigi",
});
await contains(":nth-child(2 of .o_data_row) .o_field_many2one_avatar_user .o_many2one", {
text: "Mario",
});
// Select all
await click(".o_list_table > thead .o_list_controller input");
await triggerHotkey("control+k");
await contains(".o_command", { text: "Assign to meALT + SHIFT + I" });
// Assign me
await triggerHotkey("alt+shift+i");
// Multi-edit confirmation dialog
await contains(".o_dialog");
// Cancel
await click(".o_dialog .modal-footer button:nth-child(2)");
await contains(":nth-child(1 of .o_data_row) .o_field_many2one_avatar_user .o_many2one", {
text: "Luigi",
});
await contains(":nth-child(2 of .o_data_row) .o_field_many2one_avatar_user .o_many2one", {
text: "Mario",
});
// Assign me
await triggerHotkey("alt+shift+i");
// Multi-edit confirmation dialog
await contains(".o_dialog");
// Confirm
await click(".o_dialog .modal-footer button:nth-child(1)");
await contains(".o_dialog", { count: 0 });
await contains(":nth-child(1 of .o_data_row) .o_field_many2one_avatar_user .o_many2one", {
text: "Mitchell Admin",
});
await contains(":nth-child(2 of .o_data_row) .o_field_many2one_avatar_user .o_many2one", {
text: "Mitchell Admin",
});
// Unassign me
await triggerHotkey("alt+shift+u");
// Multi-edit confirmation dialog
await contains(".o_dialog");
// Confirm
await click(".o_dialog .modal-footer button:nth-child(1)");
await contains(".o_field_many2one_avatar_user .o_form_uri", { count: 0 });
});
test('many2many_avatar_user widget edited by the smart action "Assign to me"', async () => {
const pyEnv = await startServer();
const [userId_1, userId_2] = pyEnv["res.users"].create([
{ partner_id: pyEnv["res.partner"].create({ name: "Mario" }) },
{ partner_id: pyEnv["res.partner"].create({ name: "Yoshi" }) },
]);
const m2xAvatarUserId1 = pyEnv["m2x.avatar.user"].create({
user_ids: [userId_1, userId_2],
});
await start();
await openFormView("m2x.avatar.user", m2xAvatarUserId1, {
arch: "<form><field name='user_ids' widget='many2many_avatar_user'/></form>",
});
await contains(".o_tag_badge_text", { count: 2 });
await contains(":nth-child(1 of .o_tag) .o_tag_badge_text", { text: "Mario" });
await contains(":nth-child(2 of .o_tag) .o_tag_badge_text", { text: "Yoshi" });
triggerHotkey("control+k");
await contains(".o_command", { text: "Assign to meALT + SHIFT + I" });
// Assign me
triggerHotkey("alt+shift+i");
await contains(".o_tag_badge_text", { count: 3 });
await contains(":nth-child(1 of .o_tag) .o_tag_badge_text", { text: "Mario" });
await contains(":nth-child(2 of .o_tag) .o_tag_badge_text", { text: "Yoshi" });
await contains(":nth-child(3 of .o_tag) .o_tag_badge_text", { text: "Mitchell Admin" });
// Unassign me
triggerHotkey("control+k");
await contains(".o_command", { text: "Unassign from meALT + SHIFT + I" });
triggerHotkey("alt+shift+i");
await contains(".o_tag_badge_text", { count: 2 });
await contains(":nth-child(1 of .o_tag) .o_tag_badge_text", { text: "Mario" });
await contains(":nth-child(2 of .o_tag) .o_tag_badge_text", { text: "Yoshi" });
});
test("avatar_user widget displays the appropriate user image in list view", async () => {
const pyEnv = await startServer();
const userId = pyEnv["res.users"].create({
partner_id: pyEnv["res.partner"].create({ name: "Mario" }),
});
const avatarUserId = pyEnv["m2x.avatar.user"].create({ user_id: userId });
await start();
await openListView("m2x.avatar.user", {
res_id: avatarUserId,
arch: "<list><field name='user_id' widget='many2one_avatar_user'/></list>",
});
await contains(`.o_m2o_avatar > img[data-src="/web/image/res.users/${userId}/avatar_128"]`);
});
test("avatar_user widget displays the appropriate user image in kanban view", async () => {
const pyEnv = await startServer();
const userId = pyEnv["res.users"].create({ name: "Mario" });
const avatarUserId = pyEnv["m2x.avatar.user"].create({ user_id: userId });
await start();
await openKanbanView("m2x.avatar.user", {
res_id: avatarUserId,
arch: `
<kanban>
<templates>
<t t-name="card">
<field name="user_id" widget="many2one_avatar_user"/>
</t>
</templates>
</kanban>
`,
});
await start();
await contains(`.o_m2o_avatar > img[data-src="/web/image/res.users/${userId}/avatar_128"]`);
});
test("avatar card preview", async () => {
registry.category("services").add(
"im_status",
{
start() {
return {
registerToImStatus() {},
unregisterFromImStatus() {},
updateBusPresence() {},
};
},
},
{ force: true }
);
const pyEnv = await startServer();
const userId = pyEnv["res.users"].create({
partner_id: pyEnv["res.partner"].create({
email: "Mario@odoo.test",
name: "Mario",
phone: "+78786987",
}),
im_status: "online",
});
const avatarUserId = pyEnv["m2x.avatar.user"].create({ user_id: userId });
await start();
await openKanbanView("m2x.avatar.user", {
res_id: avatarUserId,
arch: `
<kanban>
<templates>
<t t-name="card">
<field name="user_id" widget="many2one_avatar_user"/>
</t>
</templates>
</kanban>
`,
});
// Open card
await click(".o_m2o_avatar > img");
await contains(".o_avatar_card");
await contains(".o_card_user_infos > span", { text: "Mario" });
await contains(".o_card_user_infos > a", { text: "Mario@odoo.test" });
await contains(".o_card_user_infos > a", { text: "+78786987" });
// Close card
await click(".o_action_manager");
await contains(".o_avatar_card", { count: 0 });
});
test("avatar card preview (partner_id field)", async () => {
registry.category("services").add(
"im_status",
{
start() {
return {
registerToImStatus() {},
unregisterFromImStatus() {},
updateBusPresence() {},
};
},
},
{ force: true }
);
const pyEnv = await startServer();
const userId = pyEnv["res.users"].create({
im_status: "online",
});
const partnerId = pyEnv["res.partner"].create({
email: "Mario@odoo.test",
name: "Mario",
phone: "+78786987",
user_ids: [userId],
});
const avatarUserId = pyEnv["m2x.avatar.user"].create({ partner_id: partnerId });
await start();
await openKanbanView("m2x.avatar.user", {
res_id: avatarUserId,
arch: `
<kanban>
<templates>
<t t-name="card">
<field name="partner_id" widget="many2one_avatar_user"/>
</t>
</templates>
</kanban>
`,
});
// Open card
await click(".o_m2o_avatar > img");
await contains(".o_avatar_card");
await contains(".o_card_user_infos > span", { text: "Mario" });
await contains(".o_card_user_infos > a", { text: "Mario@odoo.test" });
await contains(".o_card_user_infos > a", { text: "+78786987" });
// Close card
await click(".o_action_manager");
await contains(".o_avatar_card", { count: 0 });
});
test("avatar_user widget displays the appropriate user image in form view", async () => {
const pyEnv = await startServer();
const userId = pyEnv["res.users"].create({
partner_id: pyEnv["res.partner"].create({ name: "Mario" }),
});
const avatarUserId = pyEnv["m2x.avatar.user"].create({ user_ids: [userId] });
await start();
await openFormView("m2x.avatar.user", avatarUserId, {
arch: "<form><field name='user_ids' widget='many2many_avatar_user'/></form>",
});
await contains(
`.o_field_many2many_avatar_user.o_field_widget .o_avatar img[data-src="${getOrigin()}/web/image/res.users/${userId}/avatar_128"]`
);
});
test("many2one_avatar_user widget in list view", async () => {
const pyEnv = await startServer();
const userId = pyEnv["res.users"].create({
partner_id: pyEnv["res.partner"].create({
email: "Mario@partner.com",
name: "Mario",
phone: "+45687468",
}),
});
pyEnv["m2x.avatar.user"].create({ user_id: userId });
await start();
await openListView("m2x.avatar.user", {
arch: "<list><field name='user_id' widget='many2one_avatar_user'/></list>",
});
await contains(".o_data_cell .o_many2one span");
await contains(".o_data_cell .o_many2one a", { count: 0 });
await click(".o_data_cell .o_m2o_avatar > img");
await contains(".o_avatar_card");
await contains(".o_card_user_infos > span", { text: "Mario" });
await contains(".o_card_user_infos > a", { text: "Mario@partner.com" });
await contains(".o_card_user_infos > a", { text: "+45687468" });
});
test("many2many_avatar_user widget in form view", async () => {
const pyEnv = await startServer();
const userId = pyEnv["res.users"].create({
name: "Mario",
partner_id: pyEnv["res.partner"].create({
email: "Mario@partner.com",
name: "Mario",
phone: "+45687468",
}),
});
const avatarUserId = pyEnv["m2x.avatar.user"].create({ user_ids: [userId] });
await start();
await openFormView("m2x.avatar.user", avatarUserId, {
arch: "<form><field name='user_ids' widget='many2many_avatar_user'/></form>",
});
await click(".o_field_many2many_avatar_user .o_avatar img");
await contains(".o_avatar_card");
await contains(".o_card_user_infos > span", { text: "Mario" });
await contains(".o_card_user_infos > a", { text: "Mario@partner.com" });
await contains(".o_card_user_infos > a", { text: "+45687468" });
});

View file

@ -0,0 +1,128 @@
import { beforeEach, describe, expect, test } from "@odoo/hoot";
import {
click,
contains,
defineMailModels,
insertText,
openFormView,
start,
startServer,
} from "@mail/../tests/mail_test_helpers";
import {
asyncStep,
clickFieldDropdown,
clickFieldDropdownItem,
onRpc,
waitForSteps,
} from "@web/../tests/web_test_helpers";
import { queryAll } from "@odoo/hoot-dom";
import { ResPartner } from "../../mock_server/mock_models/res_partner";
defineMailModels();
describe.current.tags("desktop");
beforeEach(() => {
ResPartner._views.form = /* xml */ `
<form>
<field name="name"/>
<field name="email"/>
</form>
`;
});
test("fieldmany2many tags email (edition)", async () => {
const pyEnv = await startServer();
const [partnerId_1, partnerId_2] = pyEnv["res.partner"].create([
{ name: "gold", email: "coucou@petite.perruche" },
{ name: "", email: "", type: "invoice" },
]);
pyEnv["res.partner"].write([partnerId_2], { parent_id: partnerId_1 });
const messageId = pyEnv["mail.message"].create({ partner_ids: [partnerId_1] });
onRpc("res.partner", "web_read", (params) => {
expect(params.kwargs.specification).toInclude("email");
asyncStep(`web_read ${JSON.stringify(params.args[0])}`);
});
onRpc("res.partner", "web_save", (params) => {
expect(params.kwargs.specification).toInclude("email");
asyncStep(`web_save ${JSON.stringify(params.args[0])}`);
});
onRpc("res.partner", "get_formview_id", () => false);
await start();
await openFormView("mail.message", messageId, {
arch: `
<form>
<field name="body"/>
<field name="partner_ids" widget="many2many_tags_email"/>
</form>
`,
});
await waitForSteps([]);
await contains('.o_field_many2many_tags_email[name="partner_ids"] .badge.o_tag_color_0');
await clickFieldDropdown("partner_ids");
await clickFieldDropdownItem("partner_ids", "gold, Invoice");
const tags = queryAll('.o_field_many2many_tags_email[name="partner_ids"] .badge.o_tag_color_0');
expect(tags[1].innerText).toBe("gold, Invoice");
await contains(".o-mail-RecipientsInputTagsListPopover");
// set the email
await insertText(".o-mail-RecipientsInputTagsListPopover input", "coucou@petite.perruche");
await click(".o-mail-RecipientsInputTagsListPopover .btn-primary");
await contains('.o_field_many2many_tags_email[name="partner_ids"] .badge.o_tag_color_0', {
count: 2,
});
expect(tags[0].innerText).toBe("gold");
expect(tags[0].querySelector(".o_badge_text")).toHaveAttribute(
"title",
"coucou@petite.perruche"
);
// should have read Partner_2 2 times: when opening the dropdown and when saving the new email.
await waitForSteps([`web_read [${partnerId_2}]`, `web_save [${partnerId_2}]`]);
});
test("fieldmany2many tags email popup close without filling", async () => {
const pyEnv = await startServer();
pyEnv["res.partner"].create([
{ name: "Valid Valeria", email: "normal_valid_email@test.com" },
{ name: "Deficient Denise", email: "" },
]);
onRpc("res.partner", "get_formview_id", () => false);
await start();
await openFormView("mail.message", undefined, {
arch: `
<form>
<field name="body"/>
<field name="partner_ids" widget="many2many_tags_email"/>
</form>
`,
});
// add an other existing tag
await clickFieldDropdown("partner_ids");
await clickFieldDropdownItem("partner_ids", "Deficient Denise");
await contains(".o-mail-RecipientsInputTagsListPopover");
// set the email
await insertText(".o-mail-RecipientsInputTagsListPopover input", "coucou@petite.perruche");
// Close the modal dialog without saving (should remove partner from invalid records)
await click(".o-mail-RecipientsInputTagsListPopover .btn-secondary");
// Selecting a partner with a valid email shouldn't open the modal dialog for the previous partner
await contains(".o_field_widget[name='partner_ids'] .badge", { count: 0 });
await clickFieldDropdown("partner_ids");
await clickFieldDropdownItem("partner_ids", "Valid Valeria");
await contains(".o-mail-RecipientsInputTagsListPopover", { count: 0 });
});
test("many2many_tags_email widget can load more than 40 records", async () => {
const pyEnv = await startServer();
const partnerIds = [];
for (let i = 100; i < 200; i++) {
partnerIds.push(pyEnv["res.partner"].create({ display_name: `partner${i}` }));
}
const messageId = pyEnv["mail.message"].create({ partner_ids: partnerIds });
await start();
await openFormView("mail.message", messageId, {
arch: "<form><field name='partner_ids' widget='many2many_tags'/></form>",
});
await contains('.o_field_widget[name="partner_ids"] .badge', { count: 100 });
await contains(".o_form_editable");
await clickFieldDropdown("partner_ids");
await clickFieldDropdownItem("partner_ids", "Public user");
await contains('.o_field_widget[name="partner_ids"] .badge', { count: 101 });
});

View file

@ -0,0 +1,54 @@
import {
click,
contains,
defineMailModels,
insertText,
openFormView,
start,
startServer,
} from "@mail/../tests/mail_test_helpers";
import { DiscussChannel } from "@mail/../tests/mock_server/mock_models/discuss_channel";
import { describe, expect, test } from "@odoo/hoot";
import { keyDown, runAllTimers } from "@odoo/hoot-dom";
import { asyncStep, onRpc, waitForSteps } from "@web/../tests/web_test_helpers";
defineMailModels();
describe.current.tags("desktop");
test("onchange_on_keydown option triggers onchange properly", async () => {
DiscussChannel._onChanges.description = () => {};
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
await start();
onRpc("discuss.channel", "onchange", (params) => {
expect(params.args[1].description).toBe("testing the keydown event");
asyncStep("onchange");
});
await openFormView("discuss.channel", channelId, {
arch: "<form><field name='description' onchange_on_keydown='True'/></form>",
});
await insertText("textarea#description_0", "testing the keydown event");
await waitForSteps(["onchange"]);
});
test("editing a text field with the onchange_on_keydown option disappearing shouldn't trigger a crash", async () => {
DiscussChannel._onChanges.description = () => {};
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
onRpc("discuss.channel", "onchange", () => asyncStep("onchange"));
await start();
await openFormView("discuss.channel", channelId, {
arch: `
<form>
<field name="description" onchange_on_keydown="True" invisible="name == 'yop'"/>
<field name="name"/>
</form>
`,
});
await click("textarea#description_0");
await keyDown("a");
await insertText("[name=name] input", "yop", { replace: true });
await contains("textarea#description_0", { count: 0 });
await runAllTimers();
await waitForSteps([]);
});

View file

@ -0,0 +1,93 @@
import {
click,
contains,
defineMailModels,
openView,
registerArchs,
start,
startServer,
} from "@mail/../tests/mail_test_helpers";
import { beforeEach, test } from "@odoo/hoot";
import { mockDate } from "@odoo/hoot-mock";
defineMailModels();
beforeEach(() => mockDate("2024-10-20 10:00:00", +1));
test("Text scheduled date field", async () => {
const pyEnv = await startServer();
registerArchs({
"mail.compose.message,false,form": `<form><field name="scheduled_date" widget="text_scheduled_date"/></form>`,
});
const composerId = pyEnv["mail.compose.message"].create({
subject: "Greetings",
body: "<p>Hello There</p>",
model: "res.partner",
});
await start();
await openView({
res_model: "mail.compose.message",
res_id: composerId,
views: [["mail.compose.message,false,form", "form"]],
});
// should not contain text as scheduled date is empty
await contains(".o_field_text_scheduled_date button", { text: "" });
await click(".o_field_text_scheduled_date button");
// should open the dialog to select the schedule date
await contains(".modal");
// clear button should not be shown as no selected date is set on the record
await contains(".modal-footer button", { text: "Clear Time", count: 0 });
await click(".modal input[value='afternoon']");
await contains(".modal input[value='afternoon']:checked");
await click(".modal-footer .btn-primary");
// button should show the scheduled date
await contains(".o_field_text_scheduled_date button", {
text: "Sending Oct 21, 1:00 PM",
});
await click(".o_field_text_scheduled_date button");
// previously selected datetime should be selected in the dialog
await contains(".modal input[value='afternoon']:checked");
// should be able to clear the selected datetime
await click(".modal-footer button:contains('Clear Time')");
// button should be empty again
await contains(".o_field_text_scheduled_date button", { text: "" });
});
test("Datetime scheduled date field", async () => {
const pyEnv = await startServer();
registerArchs({
"mail.scheduled.message,false,form": `<form><field name="scheduled_date" widget="datetime_scheduled_date"/></form>`,
});
const composerId = pyEnv["mail.scheduled.message"].create({
subject: "Greetings",
body: "<p>Hello There</p>",
model: "res.partner",
scheduled_date: "2024-10-21 12:00:00",
});
await start();
await openView({
res_model: "mail.scheduled.message",
res_id: composerId,
views: [["mail.scheduled.message,false,form", "form"]],
});
// button should show the scheduled date
await contains(".o_field_datetime_scheduled_date button", {
text: "Sending Oct 21, 1:00 PM",
});
await click(".o_field_datetime_scheduled_date button");
// should open the dialog to select the schedule date
await contains(".modal");
// current scheduled datetime should be selected in the dialog
await contains(".modal input[value='afternoon']:checked");
// clear button should not be shown (can't clear scheduled date of scheduled message)
await contains(".modal-footer button", { text: "Clear Time", count: 0 });
await click(".modal input[value='morning']");
await contains(".modal input[value='morning']:checked");
await click(".modal-footer .btn-primary");
// button should show the new scheduled date
await contains(".o_field_datetime_scheduled_date button", {
text: "Sending Oct 21, 8:00 AM",
});
});

View file

@ -0,0 +1,43 @@
import {
click,
contains,
defineMailModels,
start,
startServer,
} from "@mail/../tests/mail_test_helpers";
import { describe, test } from "@odoo/hoot";
import { getService, switchView } from "@web/../tests/web_test_helpers";
defineMailModels();
describe.current.tags("desktop");
test('shortcut widget displays the appropriate "::" icon across views', async () => {
const pyEnv = await startServer();
pyEnv["mail.canned.response"].create([{ source: "hello" }]);
await start();
await getService("action").doAction({
res_model: "mail.canned.response",
type: "ir.actions.act_window",
views: [
[false, "list"],
[false, "form"],
[false, "kanban"],
],
});
const selector = `div[name='source']`;
await contains(`.o_control_panel_navigation .o_cp_switch_buttons`);
await contains(`.o_switch_view`, { count: 2 });
await contains(".o_list_view .o_content");
await contains(selector, { text: "::hello" });
await switchView("kanban");
await contains(".o_kanban_view .o_content");
await contains(selector, { text: "::hello" });
await click(".o_control_panel_main_buttons .o-kanban-button-new");
await contains(`.o_form_view .o_content`);
await contains(`${selector} input[type='text']`);
await contains(selector, { text: "::" });
});