[IMP] mail: add/modify activity client tests

We add a test that checks the rendering of the activity view and the activity
popover with activities done.

We also add a test that marks as done an activity and verify that the activity
view is updated accordingly.

And we do a correction in the test "activity menu widget: activity menu with 2
models" (assert.ok(selectContaining... was always true as an array is always
returned and [] is true in javascript).

Task-3558929

Part-of: odoo/odoo#139373
This commit is contained in:
Pierre-Yves Dufays
2023-11-02 13:25:48 +00:00
committed by Thibault Delavallée
parent 949039135d
commit d2f44211b9
3 changed files with 227 additions and 12 deletions
@@ -4,7 +4,7 @@ import { Domain } from "@web/core/domain";
import { groupBy, sortBy, unique } from "@web/core/utils/arrays";
import { patch } from "@web/core/utils/patch";
import { MockServer } from "@web/../tests/helpers/mock_server";
import { deserializeDate } from "@web/core/l10n/dates";
import { deserializeDate, serializeDate, today } from "@web/core/l10n/dates";
const { DateTime} = luxon;
@@ -84,8 +84,7 @@ patch(MockServer.prototype, {
* @returns {Object}
*/
_mockMailActivityActionDone(ids) {
const activities = this.getRecords("mail.activity", [["id", "in", ids]]);
this.mockUnlink("mail.activity", [activities.map((activity) => activity.id)]);
this._mockMailActivityActionFeedback(ids);
},
/**
* Simulates `action_feedback` on `mail.activity`.
@@ -96,7 +95,24 @@ patch(MockServer.prototype, {
* @returns {Object}
*/
_mockMailActivityActionFeedback(ids, attachment_ids= null) {
this._mockMailActivityActionDone(ids);
const activities = this.getRecords("mail.activity", [["id", "in", ids]]);
const activityTypes = this.getRecords("mail.activity.type", [
["id", "in", unique(activities.map((a) => a.activity_type_id))],
]);
const activityTypeById = Object.fromEntries(
activityTypes.map((actType) => [actType.id, actType])
);
this.mockWrite("mail.activity", [
activities
.filter((act) => activityTypeById[act.activity_type_id].keep_done)
.map((act) => act.id),
{ active: false, date_done: serializeDate(today()), state: "done" },
]);
this.mockUnlink("mail.activity", [
activities
.filter((act) => !activityTypeById[act.activity_type_id].keep_done)
.map((act) => act.id),
]);
},
/**
* Simulates `action_feedback_schedule_next` on `mail.activity`.
@@ -241,6 +241,162 @@ QUnit.module("test_mail", {}, function () {
);
});
QUnit.test("activity view: Activity rendering with done activities", async function (assert) {
function containsN(domElement, selector, containings) {
return Array.from(domElement.querySelectorAll(selector)).filter((sel) =>
containings.every((containing) => sel.textContent.includes(containing))
);
}
const activityTypeUpload = pyEnv["mail.activity.type"].create({
category: "upload_file",
name: "Test Upload document",
keep_done: true,
});
pyEnv["mail.activity"].create(
Object.entries(["done", "done", "done", "done", "planned", "planned", "planned"]).map(
([idx, state]) => ({
active: state !== "done",
activity_type_id: activityTypeUpload,
attachment_ids:
state === "done"
? [
pyEnv["ir.attachment"].create({
name: `attachment ${idx}`,
create_date: serializeDate(
DateTime.now().minus({ days: idx })
),
create_uid: pyEnv.currentUserId,
}),
]
: [],
can_write: true,
date_deadline: serializeDate(DateTime.now().plus({ days: idx })),
date_done:
state === "done"
? serializeDate(DateTime.now().minus({ days: idx }))
: false,
display_name: `Upload folders ${idx}`,
state: state,
user_id: pyEnv["res.users"].create({ display_name: `user${idx}` }),
})
)
);
const [meetingRecord, officeRecord] = pyEnv["mail.test.activity"].search([]);
const uploadDoneActs = pyEnv["mail.activity"].searchRead([
["activity_type_id", "=", activityTypeUpload],
["active", "=", false],
]);
const uploadPlannedActs = pyEnv["mail.activity"].searchRead([
["activity_type_id", "=", activityTypeUpload],
]);
pyEnv["mail.test.activity"].write([meetingRecord], {
activity_ids: [
uploadPlannedActs[0].id,
uploadPlannedActs[1].id,
uploadPlannedActs[2].id,
uploadDoneActs[0].id,
],
});
pyEnv["mail.test.activity"].write([officeRecord], {
activity_ids: [uploadDoneActs[1].id, uploadDoneActs[2].id, uploadDoneActs[3].id],
});
const { openView } = await start({
serverData,
});
await openView({
res_model: "mail.test.activity",
views: [[false, "activity"]],
});
const domActivity = document.querySelector(".o_activity_view");
const domHeaderUpload = domActivity.querySelector(
"table thead tr:first-child th:nth-child(6)"
);
const selRowMeetingCellUpload = "table tbody tr:first-child td:nth-child(6)";
const domRowMeetingCellUpload = domActivity.querySelector(selRowMeetingCellUpload);
const selRowOfficeCellUpload = "table tbody tr:nth-child(2) td:nth-child(6)";
const domRowOfficeCellUpload = domActivity.querySelector(selRowOfficeCellUpload);
// Headers
await contains(".o_column_progress .progress-bar:first-child[data-tooltip='3 Planned']", {
target: domHeaderUpload,
});
await contains(".o_animated_number", {
target: domHeaderUpload,
text: "3",
});
await contains(".o_column_progress_aggregated_on", {
target: domHeaderUpload,
text: "7",
});
// Cells avatars
await contains(
`.o-mail-Avatar img[data-src='/web/image/res.users/${uploadPlannedActs[0].user_id[0]}/avatar_128'`,
{ target: domRowMeetingCellUpload }
);
await contains(
`.o-mail-Avatar img[data-src='/web/image/res.users/${uploadPlannedActs[1].user_id[0]}/avatar_128'`,
{ target: domRowMeetingCellUpload }
);
await contains(
`.o-mail-Avatar img[data-src='/web/image/res.users/${uploadPlannedActs[2].user_id[0]}/avatar_128'`,
{ target: domRowMeetingCellUpload, count: 0 }
);
await contains(`.o-mail-Avatar`, { target: domRowOfficeCellUpload, count: 0 }); // all activity are done
// Cells counters
assert.ok(
containsN(domRowMeetingCellUpload, ".o-mail-ActivityCell-counter", ["3", "/", "4"])
);
await contains(".o-mail-ActivityCell-counter", {
text: "3",
target: domRowOfficeCellUpload,
});
// Cells dates
await contains(".o-mail-ActivityCell-deadline", {
text: luxon.DateTime.fromISO(uploadPlannedActs[0].date_deadline).toLocaleString(
luxon.DateTime.DATE_SHORT
),
target: domRowMeetingCellUpload,
});
await contains(".o-mail-ActivityCell-deadline", {
text: luxon.DateTime.fromISO(uploadDoneActs[1].date_done).toLocaleString(
luxon.DateTime.DATE_SHORT
),
target: domRowOfficeCellUpload,
});
// Activity list popovers content
await click(domActivity, `${selRowMeetingCellUpload} > div`);
await contains(".o-mail-ActivityListPopover .badge.text-bg-success", { text: "3" }); // 3 planned
for (const actIdx of [0, 1, 2]) {
await contains(".o-mail-ActivityListPopoverItem", {
text: uploadPlannedActs[actIdx].user_id[1],
});
}
await contains(".o-mail-ActivityListPopoverItem", { text: "Due in 4 days" });
await contains(".o-mail-ActivityListPopoverItem", { text: "Due in 5 days" });
await contains(".o-mail-ActivityListPopoverItem", { text: "Due in 6 days" });
await contains(".o-mail-ActivityListPopover .badge.text-bg-secondary", { text: "1" }); // 1 done
await contains(".o-mail-ActivityListPopoverItem", { text: uploadDoneActs[0].user_id[1] });
await contains(".o-mail-ActivityListPopoverItem", {
text: luxon.DateTime.fromISO(uploadDoneActs[0].date_done).toLocaleString(
luxon.DateTime.DATE_SHORT
),
});
await click(domActivity, `${selRowOfficeCellUpload} > div`);
await contains(".o-mail-ActivityListPopover .badge.text-bg-secondary", { text: "3" }); // 3 done
for (const actIdx of [1, 2, 3]) {
await contains(".o-mail-ActivityListPopoverItem", {
text: luxon.DateTime.fromISO(uploadDoneActs[actIdx].date_done).toLocaleString(
luxon.DateTime.DATE_SHORT
),
});
await contains(".o-mail-ActivityListPopoverItem", {
text: uploadDoneActs[actIdx].user_id[1],
});
}
});
QUnit.test(
"activity view: a pager can be used when there are more than the limit of 100 activities to display",
async function (assert) {
@@ -538,6 +694,49 @@ QUnit.module("test_mail", {}, function () {
]);
});
QUnit.test("activity view: Mark as done with keep done enabled", async function (assert) {
const emailActType = pyEnv["mail.activity.type"].search([["name", "=", "Email"]])[0];
pyEnv["mail.activity.type"].write([emailActType], {
keep_done: true,
});
const { openView } = await start({
serverData,
});
await openView({
res_model: "mail.test.activity",
views: [[false, "activity"]],
context: { active_test: false },
});
const domActivity = document.querySelector(".o_activity_view");
const domHeaderEmail = domActivity.querySelector(
"table thead tr:first-child th:nth-child(2)"
);
const selRowOfficeCellEmail = "table tbody tr:nth-child(2) td:nth-child(2)";
await contains(".o_animated_number", {
target: domHeaderEmail,
text: "2",
});
await contains(".o_column_progress_aggregated_on", {
target: domHeaderEmail,
text: "2",
});
await click(domActivity, `${selRowOfficeCellEmail} > div`);
await click(
document,
".o-mail-ActivityListPopoverItem .o-mail-ActivityListPopoverItem-markAsDone"
);
await click(document, ".o-mail-ActivityMarkAsDone button[aria-label='Done']");
await contains(".o_animated_number", {
target: domHeaderEmail,
text: "1",
});
await contains(".o_column_progress_aggregated_on", {
target: domHeaderEmail,
text: "2",
});
});
QUnit.test("activity view: no group_by_menu and no comparison_menu", async function (assert) {
assert.expect(4);
@@ -45,10 +45,10 @@ QUnit.test("do not show empty text when at least some future activities", async
});
QUnit.test("activity menu widget: activity menu with 2 models", async (assert) => {
function selectContaining(domElement, selector, containings) {
function containsN(domElement, selector, containings) {
return Array.from(domElement.querySelectorAll(selector)).filter((sel) =>
containings.every((containing) => sel.textContent.includes(containing))
);
).length;
}
const tomorrow = today().plus({ days: 1 });
@@ -95,22 +95,22 @@ QUnit.test("activity menu widget: activity menu with 2 models", async (assert) =
await contains(".o-mail-ActivityMenu");
await contains(".o-mail-ActivityMenu .o-mail-ActivityGroup", { count: 2 });
assert.ok(
selectContaining(document, ".o-mail-ActivityMenu .o-mail-ActivityGroup", ["res.partner", "0 Late"])
containsN(document, ".o-mail-ActivityMenu .o-mail-ActivityGroup", ["res.partner", "0 Late"])
);
assert.ok(
selectContaining(document, ".o-mail-ActivityMenu .o-mail-ActivityGroup", ["res.partner", "1 Today"])
containsN(document, ".o-mail-ActivityMenu .o-mail-ActivityGroup", ["res.partner", "1 Today"])
);
assert.ok(
selectContaining(document, ".o-mail-ActivityMenu .o-mail-ActivityGroup", ["res.partner", "0 Future"])
containsN(document, ".o-mail-ActivityMenu .o-mail-ActivityGroup", ["res.partner", "0 Future"])
);
assert.ok(
selectContaining(document, ".o-mail-ActivityMenu .o-mail-ActivityGroup", ["mail.test.activity", "1 Late"])
containsN(document, ".o-mail-ActivityMenu .o-mail-ActivityGroup", ["mail.test.activity", "1 Late"])
);
assert.ok(
selectContaining(document, ".o-mail-ActivityMenu .o-mail-ActivityGroup", ["mail.test.activity", "1 Today"])
containsN(document, ".o-mail-ActivityMenu .o-mail-ActivityGroup", ["mail.test.activity", "1 Today"])
);
assert.ok(
selectContaining(document, ".o-mail-ActivityMenu .o-mail-ActivityGroup", ["mail.test.activity", "2 Future"])
containsN(document, ".o-mail-ActivityMenu .o-mail-ActivityGroup", ["mail.test.activity", "2 Future"])
);
actionChecks.res_model = "res.partner";
await click(".o-mail-ActivityMenu .o-mail-ActivityGroup", { text: "res.partner" });