/** @odoo-module **/ import { click, getFixture, patchDate } from "@web/../tests/helpers/utils"; import { removeFacet } from "@web/../tests/search/helpers"; import { setupViewRegistries } from "@web/../tests/views/helpers"; import { start } from "@mail/../tests/helpers/test_utils"; import { addModelNamesToFetch } from "@bus/../tests/helpers/model_definitions_helpers"; import { TimesheetGridSetupHelper } from "./helpers"; let serverData, target, timesheetGridSetup; addModelNamesToFetch(["project.project", "project.task"]); QUnit.module("Views", (hooks) => { hooks.beforeEach(async () => { timesheetGridSetup = new TimesheetGridSetupHelper(); const result = await timesheetGridSetup.setupTimesheetGrid(); serverData = result.serverData; target = getFixture(); setupViewRegistries(); }); QUnit.module("TimesheetGridView"); QUnit.test("basic timesheet - no groupby", async function (assert) { const { openView } = await start({ serverData, async mockRPC(route, args) { return await timesheetGridSetup.mockTimesheetGridRPC(route, args); }, }); await openView({ res_model: "analytic.line", views: [[false, "grid"]], context: { group_by: [] }, }); assert.containsN( target, ".o_grid_component_timesheet_many2one_avatar_employee", 6, "should have 6 employee avatars" ); assert.containsN( target, ".o_grid_component_timesheet_many2one", 11, "should have 11 many2one widgets in total" ); assert.containsN( target, ".o_grid_row_title", 6, "should have 6 rows displayed in the grid" ); }); QUnit.test("basic timesheet - groupby employees", async function (assert) { const { openView } = await start({ serverData, async mockRPC(route, args) { return await timesheetGridSetup.mockTimesheetGridRPC(route, args); }, }); await openView({ res_model: "analytic.line", views: [[false, "grid"]], context: { group_by: ["employee_id"] }, }); assert.containsN( target, ".o_grid_component_timesheet_many2one_avatar_employee", 4, "should have 4 employee avatars" ); assert.containsN( target, ".o_grid_row_title", 4, "should have 4 rows displayed in the grid" ); }); QUnit.test("basic timesheet - groupby employees>task", async function (assert) { const { openView } = await start({ serverData, async mockRPC(route, args) { return await timesheetGridSetup.mockTimesheetGridRPC(route, args); }, }); await openView({ res_model: "analytic.line", views: [[false, "grid"]], context: { group_by: ["employee_id", "task_id"] }, }); assert.containsN( target, ".o_grid_component_timesheet_many2one_avatar_employee", 6, "should have 6 employee avatars" ); assert.containsN( target, ".o_grid_row_title", 6, "should have 6 rows displayed in the grid" ); assert.containsN( target, ".o_grid_component_timesheet_many2one", 5, "should have 5 many2one widgets in total" ); assert.containsN(target, ".o_grid_component", 11, "should have 11 widgets in total"); }); QUnit.test("basic timesheet - groupby task>employees", async function (assert) { const { openView } = await start({ serverData, async mockRPC(route, args) { return await timesheetGridSetup.mockTimesheetGridRPC(route, args); }, }); await openView({ res_model: "analytic.line", views: [[false, "grid"]], context: { group_by: ["task_id", "employee_id"] }, }); assert.containsN( target, ".o_grid_component_timesheet_many2one_avatar_employee", 6, "should have 6 employee avatars" ); assert.containsN( target, ".o_grid_row_title", 6, "should have 4 rows displayed in the grid" ); assert.containsN( target, ".o_grid_component_timesheet_many2one", 6, "should have 6 many2one widgets in total" ); assert.containsOnce( target, ".o_grid_component_timesheet_many2one .o_grid_no_data", "We should have one many2one widget with no data" ); assert.containsN(target, ".o_grid_component", 12, "should have 12 widgets in total"); assert.containsOnce( target, ".o_grid_component_timesheet_many2one .o_grid_no_data", "should have 1 widget with no data" ); }); QUnit.test("timesheet with employee section - no groupby", async function (assert) { const { openView } = await start({ serverData, async mockRPC(route, args) { return await timesheetGridSetup.mockTimesheetGridRPC(route, args); }, }); await openView({ res_model: "analytic.line", views: [[1, "grid"]], context: { group_by: [] }, }); assert.containsN( target, ".o_grid_section_title .o_grid_component_timesheet_many2one_avatar_employee", 4, "should have 4 sections with employee avatar" ); assert.containsN( target, ".o_grid_component_timesheet_many2one", 11, "should have 11 many2one widgets in total" ); assert.containsNone( target, ".o_grid_row_title .o_grid_component_timesheet_many2one_avatar_employee", "No employee avatar should be displayed in the rows" ); assert.containsN( target, ".o_grid_row_title .o_grid_component_timesheet_many2one", 11, "The 11 many2one widgets should be displayed in the rows" ); assert.containsNone( target, ".o_grid_section_title .o_grid_component_timesheet_many2one", "No many2one widgets should be displayed in the sections" ); assert.containsN( target, ".o_grid_section_title", 4, "4 sections should be rendered in the grid view" ); assert.containsN( target, ".o_grid_row_title", 6, "should have 6 rows displayed in the grid" ); assert.containsN( target, ".o_grid_add_line .btn-link", 2, "should have 2 Add a line button" ); }); QUnit.test("timesheet with employee section - groupby employees", async function (assert) { const { openView } = await start({ serverData, async mockRPC(route, args) { return await timesheetGridSetup.mockTimesheetGridRPC(route, args); }, }); await openView({ res_model: "analytic.line", views: [[1, "grid"]], context: { group_by: ["employee_id"] }, }); assert.containsNone( target, ".o_grid_section_title .o_grid_component_timesheet_many2one_avatar_employee", "No employee avatar should be displayed in the sections" ); assert.containsN( target, ".o_grid_row_title .o_grid_component_timesheet_many2one_avatar_employee", 4, "should have 4 rows with employee avatar" ); assert.containsNone( target, ".o_grid_component_timesheet_many2one", "No many2one widgets should be rendered" ); assert.containsNone( target, ".o_grid_section_title", "No sections should be displayed in the grid" ); assert.containsN( target, ".o_grid_row_title", 4, "4 rows should be rendered in the grid view" ); }); QUnit.test("timesheet with employee section - groupby employee>task", async function (assert) { const { openView } = await start({ serverData, async mockRPC(route, args) { return await timesheetGridSetup.mockTimesheetGridRPC(route, args); }, }); await openView({ res_model: "analytic.line", views: [[1, "grid"]], context: { group_by: ["employee_id", "task_id"] }, }); assert.containsN( target, ".o_grid_section_title .o_grid_component_timesheet_many2one_avatar_employee", 4, "should have 4 sections with employee avatar" ); assert.containsN( target, ".o_grid_component_timesheet_many2one", 6, "should have 6 many2one widgets in total" ); assert.containsOnce( target, ".o_grid_component_timesheet_many2one .o_grid_no_data", "We should have one many2one widget with no data" ); assert.containsNone( target, ".o_grid_row_title .o_grid_component_timesheet_many2one_avatar_employee", "No employee avatar should be displayed in the rows" ); assert.containsN( target, ".o_grid_row_title .o_grid_component_timesheet_many2one", 6, "The 6 many2one widgets should be displayed in the rows" ); assert.containsOnce( target, ".o_grid_row_title .o_grid_component_timesheet_many2one .o_grid_no_data", "We should have one many2one widget with no data" ); assert.containsNone( target, ".o_grid_section_title .o_grid_component_timesheet_many2one", "No many2one widgets should be displayed in the sections" ); assert.containsN( target, ".o_grid_section_title", 4, "4 sections should be rendered in the grid view" ); assert.containsN( target, ".o_grid_row_title", 6, "should have 6 rows displayed in the grid" ); }); QUnit.test("timesheet with employee section - groupby task>employees", async function (assert) { const { openView } = await start({ serverData, async mockRPC(route, args) { return await timesheetGridSetup.mockTimesheetGridRPC(route, args); }, }); await openView({ res_model: "analytic.line", views: [[1, "grid"]], context: { group_by: ["task_id", "employee_id"] }, }); assert.containsNone( target, ".o_grid_section_title .o_grid_component_timesheet_many2one_avatar_employee", "No employee avatar should be displayed in the sections" ); assert.containsN( target, ".o_grid_row_title .o_grid_component_timesheet_many2one_avatar_employee", 6, "should have 4 rows with employee avatar" ); assert.containsN( target, ".o_grid_component_timesheet_many2one", 6, "5 many2one widgets should be rendered" ); assert.containsOnce( target, ".o_grid_component_timesheet_many2one .o_grid_no_data", "We should have one many2one widget with no data" ); assert.containsNone( target, ".o_grid_section_title", "No sections should be displayed in the grid" ); assert.containsN( target, ".o_grid_row_title", 6, "6 rows should be rendered in the grid view" ); }); QUnit.test( "timesheet avatar widget should not display overtime if in the view show the current period (today is displayed in the period)", async function (assert) { const { openView } = await start({ serverData, async mockRPC(route, args) { return await timesheetGridSetup.mockTimesheetGridRPC(route, args); }, }); await openView({ res_model: "analytic.line", views: [[1, "grid"]], context: { group_by: [] }, }); assert.containsN( target, ".o_grid_section_title .o_grid_component_timesheet_many2one_avatar_employee", 4, "should have 4 sections with employee avatar" ); assert.containsNone( target, ".o_grid_section_title .o_timesheet_overtime_indication", "No overtime indication should be displayed" ); } ); QUnit.test( "timesheet avatar widget should display hours in gray if all the hours were performed", async function (assert) { patchDate(2017, 0, 31, 0, 0, 0); const { openView } = await start({ serverData, async mockRPC(route, args) { return await timesheetGridSetup.mockTimesheetGridRPC(route, args); }, }); await openView({ res_model: "analytic.line", views: [[1, "grid"]], context: { group_by: [], grid_anchor: "2017-01-25" }, }); assert.containsN( target, ".o_grid_section_title .o_grid_component_timesheet_many2one_avatar_employee", 4, "should have 4 sections with employee avatar" ); assert.containsN( target, ".o_grid_section_title .o_timesheet_overtime_indication", 3, "All the avatar should have a timesheet overtime indication displayed except one since he did his working hours without any overtime in the grid" ); const sectionsTitleNodes = target.querySelectorAll(".o_grid_section_title"); const sectionWithDangerOvertimeTextContents = []; const sectionWithSuccessOvertimeTextContents = []; const sectionWithoutOvertimeTextContents = []; for (const node of sectionsTitleNodes) { const overtimeNode = node.querySelector(".o_timesheet_overtime_indication"); if (overtimeNode) { if (overtimeNode.classList.contains("text-danger")) { sectionWithDangerOvertimeTextContents.push(node.textContent); } else { sectionWithSuccessOvertimeTextContents.push(node.textContent); } } else { sectionWithoutOvertimeTextContents.push(node.textContent); } } assert.deepEqual( sectionWithDangerOvertimeTextContents, ["Mario-198:00", "Toad-1.00"], "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)" ); assert.deepEqual( sectionWithSuccessOvertimeTextContents, ["Yoshi+04:00"], "Yoshi should have done his working hours and even more." ); assert.deepEqual( sectionWithoutOvertimeTextContents, ["Luigi"], "Luigi should have done his working hours without doing extra hours" ); } ); QUnit.test( "employee overtime indication should be displayed in non-working days if timesheet recorded", async function (assert) { patchDate(2017, 0, 31, 0, 0, 0); const { openView } = await start({ serverData: { ...serverData, views: { ...serverData.views, "analytic.line,2,grid": ` `, }, }, async mockRPC(route, args) { if (args.method === "get_timesheet_and_working_hours_for_employees") { return { 1: { units_to_work: 0, uom: "hours", worked_hours: 2 }, 3: { units_to_work: 8, uom: "hours", worked_hours: 5.5 }, }; } return await timesheetGridSetup.mockTimesheetGridRPC(route, args); }, }); await openView({ res_model: "analytic.line", views: [[2, "grid"]], context: { group_by: [], grid_anchor: "2017-01-25" }, }); assert.containsN( target, ".o_grid_section_title .o_grid_component_timesheet_many2one_avatar_employee", 2, "should have 4 sections with employee avatar" ); assert.containsN( target, ".o_grid_section_title .o_timesheet_overtime_indication", 2, "All the avatar should have a timesheet overtime indication displayed except one since he did his working hours without any overtime in the grid" ); const sectionsTitleNodes = target.querySelectorAll(".o_grid_section_title"); const sectionWithDangerOvertimeTextContents = []; const sectionWithSuccessOvertimeTextContents = []; const sectionWithoutOvertimeTextContents = []; for (const node of sectionsTitleNodes) { const overtimeNode = node.querySelector(".o_timesheet_overtime_indication"); if (overtimeNode) { if (overtimeNode.classList.contains("text-danger")) { sectionWithDangerOvertimeTextContents.push(node.textContent); } else { sectionWithSuccessOvertimeTextContents.push(node.textContent); } } else { sectionWithoutOvertimeTextContents.push(node.textContent); } } assert.deepEqual( sectionWithDangerOvertimeTextContents, ["Yoshi-02:30"], "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)" ); assert.deepEqual( sectionWithSuccessOvertimeTextContents, ["Mario+02:00"], "Yoshi should have done his working hours and even more." ); } ); QUnit.test("when in Next week date should be first working day", async function (assert) { const { openView } = await start({ serverData, async mockRPC(route, args) { return await timesheetGridSetup.mockTimesheetGridRPC(route, args); }, }); await openView({ res_model: "analytic.line", views: [[false, "grid"]], context: { group_by: [] }, }); await click(target, ".o_grid_navigation_buttons > div > button > span.oi-arrow-right"); await click(target, ".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_grid_button_add"); assert.containsOnce(target, ".modal"); assert.strictEqual( target.querySelector(".modal .o_field_widget[name=date] input").value, "01/30/2017" ); }); QUnit.test("when in Previous week date should be first working day", async function (assert) { const { openView } = await start({ serverData, async mockRPC(route, args) { return await timesheetGridSetup.mockTimesheetGridRPC(route, args); }, }); await openView({ res_model: "analytic.line", views: [[false, "grid"]], context: { group_by: [] }, }); await click(target, ".o_grid_navigation_buttons > div > button > span.oi-arrow-left"); await click(target, ".o_control_panel_main_buttons .d-none.d-xl-inline-flex .o_grid_button_add"); assert.containsOnce(target, ".modal"); assert.strictEqual( target.querySelector(".modal .o_field_widget[name=date] input").value, "01/16/2017" ); }); QUnit.test("display sample data and then data + fetch last validate timesheet date", async (assert) => { serverData.views["analytic.line,1,grid"] = serverData.views["analytic.line,1,grid"].replace(" { const { openView } = await start({ serverData, async mockRPC(route, args) { if (args.method === "grid_unavailability") { return { 1: ["2017-01-22", "2017-01-24", "2017-01-28"], 2: ["2017-01-22", "2017-01-28"], 3: ["2017-01-22", "2017-01-28"], 4: ["2017-01-22", "2017-01-28"], false: ["2017-01-22", "2017-01-28"], }; } return await timesheetGridSetup.mockTimesheetGridRPC(route, args); }, }); await openView({ res_model: "analytic.line", views: [[1, "grid"]], context: { group_by: [] }, }); assert.containsN(target, ".o_grid_section_title", 4); assert.containsN(target, ".o_grid_row_title", 6); assert.containsN(target, ".o_grid_add_line_first_cell", 2); assert.containsN( target, ".o_grid_unavailable", (1 + 4 + 6 + 2 + 1) * 2 + 3, "2 whole columns and 3 cells for employee Mario in 2017-01-24 column." ); }); });