Files
enterprise-addons/timesheet_grid/static/tests/timesheet_grid.test.js
T

486 lines
22 KiB
JavaScript

import { beforeEach, expect, test } from "@odoo/hoot";
import { click, queryAll, queryAllAttributes, queryAllTexts, queryFirst } from "@odoo/hoot-dom";
import { animationFrame, mockDate } from "@odoo/hoot-mock";
import {
getService,
mountView,
mountWithCleanup,
patchWithCleanup,
onRpc,
removeFacet,
} from "@web/../tests/web_test_helpers";
import { user } from "@web/core/user";
import { WebClient } from "@web/webclient/webclient";
import { patchSession } from "@hr_timesheet/../tests/hr_timesheet_models";
import { defineTimesheetModels, HRTimesheet } from "./hr_timesheet_models";
defineTimesheetModels();
beforeEach(patchSession);
function checkSectionsColsOverAndDownTime() {
expect(queryAllTexts(".o_grid_section.text-warning")).toEqual(["25:00", "10:00"], {
message: "Mario has overtime (25h00 > 8h00) and (10h00 > 8h00)",
});
expect(".o_grid_section.o_grid_row_total.text-bg-warning").toHaveCount(1, {
message: "Mario has overtime (35h00 > 16h00)",
});
expect(queryAllTexts(".o_grid_section.text-danger")).toEqual(["2:30", "0:00"], {
message: "Luigi has downtime (2h30 < 8h00) and (0h00 < 8h00)",
});
expect(".o_grid_section.o_grid_row_total.text-bg-danger").toHaveCount(1, {
message: "Luigi has downtime (2h30 < 12h00)",
});
expect(".o_grid_section.o_grid_row_total.text-bg-success").toHaveCount(2, {
message: "Yoshi (5h30 = 5h30) and Toad (0h00 = 0h00) are in time.",
});
}
test("hr.timesheet (grid): no groupby", async () => {
await mountView({
type: "grid",
resModel: "account.analytic.line",
});
expect(".o_grid_component_timesheet_many2one_avatar_employee").toHaveCount(6, {
message: "There should be 6 employee avatars",
});
expect(".o_grid_component_timesheet_many2one").toHaveCount(11, {
message: "There should be 11 many2one widgets",
});
expect(".o_grid_row_title").toHaveCount(6, {
message: "There should be 6 rows",
});
expect(queryAllTexts(".o_grid_row.text-danger")).toEqual(["-3:30", "25:00"]);
});
test("hr.timesheet (grid): groupby employee", async () => {
await mountView({
type: "grid",
resModel: "account.analytic.line",
groupBy: ["employee_id"],
});
expect(".o_grid_component_timesheet_many2one_avatar_employee").toHaveCount(4, {
message: "There should be 4 employee avatars",
});
expect(".o_grid_row_title").toHaveCount(4, {
message: "There should be 4 rows",
});
expect(queryAllTexts(".o_grid_row.text-danger")).toEqual(["-3:30", "25:00"]);
});
test("hr.timesheet (grid): groupby employee > task", async () => {
await mountView({
type: "grid",
resModel: "account.analytic.line",
groupBy: ["employee_id", "task_id"],
});
expect(".o_grid_component_timesheet_many2one_avatar_employee").toHaveCount(6, {
message: "There should be 6 employee avatars",
});
expect(".o_grid_row_title").toHaveCount(6, {
message: "There should be 6 rows",
});
expect(".o_grid_component_timesheet_many2one").toHaveCount(5, {
message: "There should be 5 many2one widgets",
});
expect(".o_grid_component").toHaveCount(11, {
message: "There should be 11 widgets",
});
expect(queryAllTexts(".o_grid_row.text-danger")).toEqual(["-3:30", "25:00"]);
});
test("hr.timesheet (grid): groupby task > employee", async () => {
await mountView({
type: "grid",
resModel: "account.analytic.line",
groupBy: ["task_id", "employee_id"],
});
expect(".o_grid_component_timesheet_many2one_avatar_employee").toHaveCount(6, {
message: "There should be 6 employee avatars",
});
expect(".o_grid_row_title").toHaveCount(6, {
message: "There should be 4 rows",
});
expect(".o_grid_component_timesheet_many2one").toHaveCount(6, {
message: "There should be 6 many2one widgets",
});
expect(".o_grid_component_timesheet_many2one .o_grid_no_data").toHaveCount(1, {
message: "There should be one many2one widget with no data",
});
expect(".o_grid_component").toHaveCount(12, {
message: "There should be 12 widgets",
});
expect(".o_grid_row.text-danger").toHaveText("-3:30");
});
test("hr.timesheet (grid): employee section - no groupby", async () => {
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "account.analytic.line",
type: "ir.actions.act_window",
views: [[1, "grid"]],
context: { group_by: [] },
});
expect(
".o_grid_section_title .o_grid_component_timesheet_many2one_avatar_employee"
).toHaveCount(4, {
message: "There should be 4 sections with employee avatar",
});
expect(".o_grid_component_timesheet_many2one").toHaveCount(11, {
message: "There should be 11 many2one widgets in total",
});
expect(
".o_grid_row_title .o_grid_component_timesheet_many2one_avatar_employee"
).not.toHaveCount(null, {
message: "No employee avatar should be displayed in the rows",
});
expect(".o_grid_row_title .o_grid_component_timesheet_many2one").toHaveCount(11, {
message: "The 11 many2one widgets should be displayed in the rows",
});
expect(".o_grid_section_title .o_grid_component_timesheet_many2one").not.toHaveCount(null, {
message: "No many2one widgets should be displayed in the sections",
});
expect(".o_grid_section_title").toHaveCount(4, {
message: "4 sections should be rendered in the grid view",
});
expect(".o_grid_row_title").toHaveCount(6, {
message: "There should be 6 rows displayed in the grid",
});
expect(".o_grid_add_line .btn-link").toHaveCount(2, {
message: "There should be 2 Add a line button",
});
checkSectionsColsOverAndDownTime();
expect(queryAllTexts(".o_grid_row.text-danger")).toEqual(["-3:30", "25:00"]);
});
test("hr.timesheet (grid): employee section - groupby employee", async () => {
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "account.analytic.line",
type: "ir.actions.act_window",
views: [[1, "grid"]],
context: { group_by: ["employee_id"] },
});
expect(
".o_grid_section_title .o_grid_component_timesheet_many2one_avatar_employee"
).not.toHaveCount(null, {
message: "No employee avatar should be displayed in the sections",
});
expect(".o_grid_row_title .o_grid_component_timesheet_many2one_avatar_employee").toHaveCount(
4,
{ message: "There should be 4 rows with employee avatar" }
);
expect(".o_grid_component_timesheet_many2one").not.toHaveCount(null, {
message: "No many2one widgets should be rendered",
});
expect(".o_grid_section_title").not.toHaveCount(null, {
message: "No sections should be displayed in the grid",
});
expect(".o_grid_row_title").toHaveCount(4, {
message: "4 rows should be rendered in the grid view",
});
expect(queryAllTexts(".o_grid_row.text-danger")).toEqual(["-3:30", "25:00"]);
});
test("hr.timesheet (grid): employee section - groupby employee > task", async () => {
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "account.analytic.line",
type: "ir.actions.act_window",
views: [[1, "grid"]],
context: { group_by: ["employee_id", "task_id"] },
});
expect(
".o_grid_section_title .o_grid_component_timesheet_many2one_avatar_employee"
).toHaveCount(4, {
message: "There should be 4 sections with employee avatar",
});
expect(".o_grid_component_timesheet_many2one").toHaveCount(6, {
message: "There should be 6 many2one widgets in total",
});
expect(".o_grid_component_timesheet_many2one .o_grid_no_data").toHaveCount(1, {
message: "There should be one many2one widget with no data",
});
expect(".o_grid_row_title .o_grid_component_timesheet_many2one_avatar_employee").toHaveCount(
0,
{
message: "No employee avatar should be displayed in the rows",
}
);
expect(".o_grid_row_title .o_grid_component_timesheet_many2one").toHaveCount(6, {
message: "The 6 many2one widgets should be displayed in the rows",
});
expect(".o_grid_row_title .o_grid_component_timesheet_many2one .o_grid_no_data").toHaveCount(
1,
{
message: "There should be one many2one widget with no data",
}
);
expect(".o_grid_section_title .o_grid_component_timesheet_many2one").toHaveCount(0, {
message: "No many2one widgets should be displayed in the sections",
});
expect(".o_grid_section_title").toHaveCount(4, {
message: "4 sections should be rendered in the grid view",
});
expect(".o_grid_row_title").toHaveCount(6, {
message: "There should be 6 rows displayed in the grid",
});
checkSectionsColsOverAndDownTime();
expect(queryAllTexts(".o_grid_row.text-danger")).toEqual(["-3:30", "25:00"]);
});
test("hr.timesheet (grid): employee section - groupby task > employee", async () => {
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "account.analytic.line",
type: "ir.actions.act_window",
views: [[1, "grid"]],
context: { group_by: ["task_id", "employee_id"] },
});
expect(
".o_grid_section_title .o_grid_component_timesheet_many2one_avatar_employee"
).toHaveCount(0, {
message: "No employee avatar should be displayed in the sections",
});
expect(".o_grid_row_title .o_grid_component_timesheet_many2one_avatar_employee").toHaveCount(
6,
{
message: "There should be 4 rows with employee avatar",
}
);
expect(".o_grid_component_timesheet_many2one").toHaveCount(6, {
message: "6 many2one widgets should be rendered",
});
expect(".o_grid_component_timesheet_many2one .o_grid_no_data").toHaveCount(1, {
message: "There should be one many2one widget with no data",
});
expect(".o_grid_section_title").toHaveCount(0, {
message: "No sections should be displayed in the grid",
});
expect(".o_grid_row_title").toHaveCount(6, {
message: "6 rows should be rendered in the grid view",
});
expect(".o_grid_row.text-danger").toHaveText("-3:30");
});
test("hr.timesheet (grid): avatar widget shouldn't display overtime if period includes today", async () => {
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "account.analytic.line",
type: "ir.actions.act_window",
views: [[1, "grid"]],
context: { group_by: [] },
});
expect(
".o_grid_section_title .o_grid_component_timesheet_many2one_avatar_employee"
).toHaveCount(4, {
message: "There should be 4 sections with employee avatar",
});
expect(".o_grid_section_title .o_timesheet_overtime_indication").toHaveCount(0, {
message: "No overtime indication should be displayed",
});
});
test("hr.timesheet (grid): avatar widget should display hours in gray if all the hours were performed", async () => {
mockDate("2017-01-31 00:00:00", 0);
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "account.analytic.line",
type: "ir.actions.act_window",
views: [[1, "grid"]],
context: { group_by: [], grid_anchor: "2017-01-25" },
});
expect(
".o_grid_section_title .o_grid_component_timesheet_many2one_avatar_employee"
).toHaveCount(4, {
message: "There should be 4 sections with employee avatar",
});
expect(".o_grid_section_title .o_timesheet_overtime_indication").toHaveCount(3, {
message: "All the avatar should have a timesheet overtime indication displayed except one",
});
const sectionsTitleNodes = queryAll(".o_grid_section_title");
const sectionWithDangerOvertimeTextContents = [];
const sectionWithSuccessOvertimeTextContents = [];
const sectionWithoutOvertimeTextContents = [];
for (const node of sectionsTitleNodes) {
const overtimeNode = queryFirst(".o_timesheet_overtime_indication", { root: node });
if (overtimeNode) {
if (overtimeNode.classList.contains("text-danger")) {
sectionWithDangerOvertimeTextContents.push(node.textContent);
} else {
sectionWithSuccessOvertimeTextContents.push(node.textContent);
}
} else {
sectionWithoutOvertimeTextContents.push(node.textContent);
}
}
expect(sectionWithDangerOvertimeTextContents).toEqual(["Mario-198:00", "Toad-1.00"], {
message:
"Mario and Toad have not done all his working hours (the overtime indication for Toad is formatted in float since uom is Days and not hours)",
});
expect(sectionWithSuccessOvertimeTextContents).toEqual(["Yoshi+04:00"], {
message: "Yoshi should have done his working hours and even more",
});
expect(sectionWithoutOvertimeTextContents).toEqual(["Luigi"], {
message: "Luigi should have done his working hours without doing extra hours",
});
});
test("hr.timesheet (grid): when in Next week date should be first working day", async () => {
await mountView({
type: "grid",
resModel: "account.analytic.line",
groupBy: [],
});
await click(".o_grid_navigation_buttons > div > button > span.oi-arrow-right");
await animationFrame();
await click(".o_control_panel_main_buttons .o_grid_button_add");
await animationFrame();
expect(".modal").toHaveCount(1);
expect(".modal .o_field_widget[name=date] button").toHaveValue("01/30/2017")
});
test("hr.timesheet (grid): when in Previous week date should be first working day", async () => {
await mountView({
type: "grid",
resModel: "account.analytic.line",
groupBy: [],
});
await click(".o_grid_navigation_buttons > div > button > span.oi-arrow-left");
await animationFrame();
await click(".o_control_panel_main_buttons .o_grid_button_add");
await animationFrame();
expect(".modal").toHaveCount(1);
expect(".modal .o_field_widget[name=date] button").toHaveValue("01/16/2017");
});
test("hr.timesheet (grid): display sample data and then data + fetch last validate timesheet date", async () => {
HRTimesheet._views["grid,1"] = HRTimesheet._views["grid,1"].replace(
"<grid",
"<grid sample='1'"
);
onRpc(({ method }) => {
if (method === "get_last_validated_timesheet_date") {
expect.step("get_last_validated_timesheet_date");
}
});
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "account.analytic.line",
type: "ir.actions.act_window",
views: [[1, "grid"]],
context: { search_default_nothing: 1 },
});
expect(".o_view_sample_data").toHaveCount(1);
await removeFacet("Nothing");
expect(".o_grid_sample_data").toHaveCount(0);
expect(".o_grid_section_title").toHaveCount(4);
expect.verifySteps(["get_last_validated_timesheet_date"]); // the rpc should be called only once
});
test("test timesheet grid when grouped by employees shows color code on timesheets", async () => {
await mountWithCleanup(WebClient);
await getService("action").doAction({
res_model: "account.analytic.line",
type: "ir.actions.act_window",
views: [[ false, "grid" ]],
context: { group_by: [ "employee_id"] },
});
/**
* Working periods of all employees in the test
*
* Mario - 6 hours on 25 and 27
* Luigi - 8 hours on 24 and 25
* Yoshi - 5.5 hours on 25
* Toad - No working periods
*
* The timesheet here would be as (denoted as timesheet amount / that day work period length (corresponding color) )
* ╔═══════╦════════════════╦═════════════════════════╦══════════════════╦══════════════════════╦═════════════════════╗
* ║ ║ Jan 25 ║ Jan 26 ║ Jan 27 ║ Jan 28 ║ Unit amount ║
* ╠═══════╬════════════════╬═════════════════════════╬══════════════════╬══════════════════════╬═════════════════════╣
* ║ Luigi ║ 2:30 / 8 (Red) ║ 0 ║ 0 ║ -3:30 / 0 (No color) ║ -1:00 / 8 (Red) ║
* ╠═══════╬════════════════╬═════════════════════════╬══════════════════╬══════════════════════╬═════════════════════╣
* ║ Mario ║ 0 ║ 25 / 8 (Orange) ║ 0 ║ 10 / 8 (Orange) ║ 35 / 16 (Orange) ║
* ╠═══════╬════════════════╬═════════════════════════╬══════════════════╬══════════════════════╬═════════════════════╣
* ║ Toad ║ 0 ║ 0 ║ 4 / 0 (No Color) ║ 0 ║ 4 / 0 (Green) ║
* ╠═══════╬════════════════╬═════════════════════════╬══════════════════╬══════════════════════╬═════════════════════╣
* ║ Yoshi ║ 0 ║ 5:30 / 5.30 (No color) ║ 0 ║ 0 ║ 5:30 / 5:30 (Green) ║
* ╚═══════╩════════════════╩═════════════════════════╩══════════════════╩══════════════════════╩═════════════════════╝
*
**/
expect(".o_grid_row .o_grid_cell_readonly span:contains(2:30)").toHaveClass("text-danger", {
message: "The cell text should be red as that employee has working period of 8 hours but has timesheet of 2.5 hours",
});
expect(".o_grid_row .o_grid_cell_readonly span:contains(-3:30)").toHaveClass("text-900", {
message: "The cell text should be normal as that employee has no working period of 8 hours but has timesheet of 2.5 hours",
});
expect(".o_grid_row .o_grid_cell_readonly span:contains(25:00)").toHaveClass("text-warning", {
message: "The cell text should be orange as that employee has working period of 8 hours but has timesheet of 25 hours",
});
expect(".o_grid_row .o_grid_cell_readonly span:contains(10:00)").toHaveClass("text-warning", {
message: "The cell text should be orange as that employee has working period of 8 hours but has timesheet of 10 hours",
});
expect(".o_grid_row .o_grid_cell_readonly span:contains(4:00)").toHaveClass("text-900", {
message: "The cell text should be normal as that employee has no working period but has timesheet of 4 hours",
});
expect(".o_grid_row .o_grid_cell_readonly span:contains(5:30)").toHaveClass("text-success", {
message: "The cell text should be normal as that employee has working period of 5.5 and has timesheet of 5.5 hours",
});
expect(queryFirst(".o_grid_row.o_grid_row_total span:contains(-1:00)").closest(".o_grid_row")).toHaveClass("text-bg-danger", {
message: "The total cell should be red as that employee has working period of 8 and has timesheet of -1 hours",
});
expect(queryFirst(".o_grid_row.o_grid_row_total span:contains(35:00)").closest(".o_grid_row")).toHaveClass("text-bg-warning", {
message: "The total cell should be orange as that employee has working period of 16 and has timesheet of 35 hours",
});
expect(queryFirst(".o_grid_row.o_grid_row_total span:contains(4:00)").closest(".o_grid_row")).toHaveClass("text-bg-success", {
message: "The total cell should be green as that employee has no working period and has timesheet of 4 hours",
});
expect(queryFirst(".o_grid_row.o_grid_row_total span:contains(5:30)").closest(".o_grid_row")).toHaveClass("text-bg-success", {
message: "The total cell should be green as that employee has working period of 5.5 and has timesheet of 5.5 hours",
});
});
test("hr.timesheet (grid): should display public employee avatars", async () => {
patchWithCleanup(user, {
hasGroup: () => false,
});
const publicEmployeeIds = [...new Set(HRTimesheet._records.map((timesheet) => timesheet.employee_id))];
await mountView({
type: "grid",
resModel: "account.analytic.line",
groupBy: ["employee_id"],
});
expect(
queryAllAttributes(".o_m2o_avatar > img", "data-src").sort()
).toEqual(
publicEmployeeIds
.map((employeeId) => `/web/image/hr.employee.public/${employeeId}/avatar_128`)
.sort()
);
});