diff --git a/addons/mail/static/tests/helpers/mock_server/models/mail_activity.js b/addons/mail/static/tests/helpers/mock_server/models/mail_activity.js index d74a24ceace..7486e468a85 100644 --- a/addons/mail/static/tests/helpers/mock_server/models/mail_activity.js +++ b/addons/mail/static/tests/helpers/mock_server/models/mail_activity.js @@ -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`. diff --git a/addons/test_mail/static/tests/activity_tests.js b/addons/test_mail/static/tests/activity_tests.js index cde4946c552..2f8ffb59401 100644 --- a/addons/test_mail/static/tests/activity_tests.js +++ b/addons/test_mail/static/tests/activity_tests.js @@ -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); diff --git a/addons/test_mail/static/tests/systray_activity_menu_tests.js b/addons/test_mail/static/tests/systray_activity_menu_tests.js index 16a2638ae34..add4c736091 100644 --- a/addons/test_mail/static/tests/systray_activity_menu_tests.js +++ b/addons/test_mail/static/tests/systray_activity_menu_tests.js @@ -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" });