forked from Mapan/odoo17e
2340 lines
94 KiB
JavaScript
2340 lines
94 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import {
|
|
click,
|
|
editInput,
|
|
getFixture,
|
|
getNodesTextContent,
|
|
nextTick,
|
|
patchDate,
|
|
patchWithCleanup,
|
|
selectDropdownItem,
|
|
triggerEvent,
|
|
triggerScroll,
|
|
} from "@web/../tests/helpers/utils";
|
|
import { toggleMenuItem, toggleSearchBarMenu } from "@web/../tests/search/helpers";
|
|
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
|
import { createWebClient, doAction } from "@web/../tests/webclient/helpers";
|
|
import { browser } from "@web/core/browser/browser";
|
|
import { Domain } from "@web/core/domain";
|
|
import { hoverGridCell } from "./helpers";
|
|
|
|
let serverData, target;
|
|
|
|
QUnit.module("Views", (hooks) => {
|
|
hooks.beforeEach(() => {
|
|
serverData = {
|
|
models: {
|
|
"analytic.line": {
|
|
fields: {
|
|
project_id: { string: "Project", type: "many2one", relation: "project" },
|
|
task_id: { string: "Task", type: "many2one", relation: "task" },
|
|
selection_field: {
|
|
string: "Selection Field",
|
|
type: "selection",
|
|
selection: [
|
|
["abc", "ABC"],
|
|
["def", "DEF"],
|
|
["ghi", "GHI"],
|
|
],
|
|
},
|
|
date: { string: "Date", type: "date" },
|
|
unit_amount: {
|
|
string: "Unit Amount",
|
|
type: "float",
|
|
group_operator: "sum",
|
|
},
|
|
},
|
|
records: [
|
|
{
|
|
id: 1,
|
|
project_id: 31,
|
|
selection_field: "abc",
|
|
date: "2017-01-24",
|
|
unit_amount: 2.5,
|
|
},
|
|
{
|
|
id: 2,
|
|
project_id: 31,
|
|
task_id: 1,
|
|
selection_field: "def",
|
|
date: "2017-01-25",
|
|
unit_amount: 2,
|
|
},
|
|
{
|
|
id: 3,
|
|
project_id: 31,
|
|
task_id: 1,
|
|
selection_field: "def",
|
|
date: "2017-01-25",
|
|
unit_amount: 5.5,
|
|
},
|
|
{
|
|
id: 4,
|
|
project_id: 31,
|
|
task_id: 1,
|
|
selection_field: "def",
|
|
date: "2017-01-30",
|
|
unit_amount: 10,
|
|
},
|
|
{
|
|
id: 5,
|
|
project_id: 142,
|
|
task_id: 12,
|
|
selection_field: "ghi",
|
|
date: "2017-01-31",
|
|
unit_amount: -3.5,
|
|
},
|
|
],
|
|
},
|
|
project: {
|
|
fields: {
|
|
name: { string: "Project Name", type: "char" },
|
|
},
|
|
records: [
|
|
{ id: 31, display_name: "P1" },
|
|
{ id: 142, display_name: "Webocalypse Now" },
|
|
],
|
|
},
|
|
task: {
|
|
fields: {
|
|
name: { string: "Task Name", type: "char" },
|
|
project_id: { string: "Project", type: "many2one", relation: "project" },
|
|
},
|
|
records: [
|
|
{ id: 1, display_name: "BS task", project_id: 31 },
|
|
{ id: 12, display_name: "Another BS task", project_id: 142 },
|
|
{ id: 54, display_name: "yet another task", project_id: 142 },
|
|
],
|
|
},
|
|
},
|
|
views: {
|
|
"analytic.line,false,form": `
|
|
<form string="Add a line">
|
|
<group>
|
|
<group>
|
|
<field name="project_id"/>
|
|
<field name="task_id"/>
|
|
<field name="date"/>
|
|
<field name="unit_amount" string="Time spent"/>
|
|
</group>
|
|
</group>
|
|
</form>`,
|
|
"analytic.line,false,list": `
|
|
<tree>
|
|
<field name="date" />
|
|
<field name="project_id" />
|
|
<field name="task_id" />
|
|
<field name="selection_field" />
|
|
<field name="unit_amount" />
|
|
</tree>`,
|
|
"analytic.line,false,grid": `
|
|
<grid>
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
"analytic.line,1,grid": `<grid>
|
|
<field name="project_id" type="row" section="1"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
"analytic.line,false,search": `
|
|
<search>
|
|
<field name="project_id"/>
|
|
<filter string="Project" name="groupby_project" domain="[]" context="{'group_by': 'project_id'}"/>
|
|
<filter string="Task" name="groupby_task" domain="[]" context="{'group_by': 'task_id'}"/>
|
|
<filter string="Selection" name="groupby_selection" domain="[]" context="{'group_by': 'selection_field'}"/>
|
|
</search>
|
|
`,
|
|
"task,false,form": `<form><field name="display_name"/></form>`,
|
|
"task,false,search": `<search/>`,
|
|
},
|
|
};
|
|
setupViewRegistries();
|
|
target = getFixture();
|
|
patchDate(2017, 0, 30, 0, 0, 0);
|
|
patchWithCleanup(browser, {
|
|
setTimeout: (fn) => fn(),
|
|
clearTimeout: () => {},
|
|
});
|
|
});
|
|
|
|
QUnit.module("GridView");
|
|
|
|
QUnit.test("basic empty grid view", async function (assert) {
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid>
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="day" string="Day" span="day" step="day"/>
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
<range name="year" string="Year" span="year" step="month"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
domain: Domain.FALSE.toList({}),
|
|
});
|
|
|
|
assert.containsOnce(target, ".o_grid_view");
|
|
assert.containsOnce(target, ".o_grid_renderer");
|
|
assert.containsOnce(target, ".o_grid_buttons:visible");
|
|
assert.containsNone(target, ".o_grid_custom_buttons");
|
|
assert.containsOnce(target, ".o_grid_navigation_buttons");
|
|
assert.strictEqual(
|
|
target.querySelector(".o_grid_navigation_buttons button:first-child").textContent,
|
|
" Today ",
|
|
"The first navigation button should be the Today one."
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_navigation_buttons button > span.oi-arrow-left",
|
|
"The previous button should be there"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_navigation_buttons button > span.oi-arrow-right",
|
|
"The next button should be there"
|
|
);
|
|
assert.containsOnce(target, ".o_view_scale_selector");
|
|
assert.strictEqual(
|
|
target.querySelector(".o_view_scale_selector button.scale_button_selection")
|
|
.textContent,
|
|
"Day",
|
|
"The default active range should be the first one define in the grid view"
|
|
);
|
|
await click(target, ".scale_button_selection");
|
|
assert.containsOnce(
|
|
target,
|
|
".o_view_scale_selector .o_scale_button_day",
|
|
"The Day scale should be in the dropdown menu"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_view_scale_selector .o_scale_button_week",
|
|
"The week scale should be in the dropdown menu"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_view_scale_selector .o_scale_button_month",
|
|
"The month scale should be in the dropdown menu"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_column_title.o_grid_highlightable.fw-bolder",
|
|
"The column title containing the date should be the current date"
|
|
);
|
|
assert.strictEqual(
|
|
target.querySelector(".o_grid_column_title.o_grid_highlightable.fw-bolder")
|
|
.textContent,
|
|
"Mon,\nJan\u00A030",
|
|
"The current date should be Monday on 30 January 2023"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_column_title.o_grid_highlightable",
|
|
1,
|
|
"It should have 1 column"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_column_title.o_grid_row_total",
|
|
1,
|
|
"It should have 1 column for the total"
|
|
);
|
|
assert.containsOnce(target, ".o_grid_column_title.o_grid_row_total");
|
|
assert.strictEqual(
|
|
target.querySelector(".o_grid_column_title.o_grid_row_total").textContent,
|
|
serverData.models["analytic.line"].fields.unit_amount.string,
|
|
"The column title of row totals should be the string of the measure field"
|
|
);
|
|
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_add_line a",
|
|
"No Add a line button should be displayed when create_inline is false (default behavior)"
|
|
);
|
|
});
|
|
|
|
QUnit.test("basic empty grid view using a specific range by default", async function (assert) {
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid>
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="day" string="Day" span="day" step="day"/>
|
|
<range name="week" string="Week" span="week" step="day" default="1"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
<range name="year" string="Year" span="year" step="month"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
domain: Domain.FALSE.toList({}),
|
|
});
|
|
|
|
assert.containsOnce(target, ".o_grid_view");
|
|
assert.containsOnce(target, ".o_grid_renderer");
|
|
assert.containsN(
|
|
target,
|
|
".o_grid_column_title.o_grid_highlightable",
|
|
7,
|
|
"It should have 7 column representing the dates on a week."
|
|
);
|
|
assert.deepEqual(
|
|
getNodesTextContent(
|
|
target.querySelectorAll(".o_grid_column_title.o_grid_highlightable")
|
|
),
|
|
[
|
|
"Sun,\nJan\u00A029",
|
|
"Mon,\nJan\u00A030",
|
|
"Tue,\nJan\u00A031",
|
|
"Wed,\nFeb\u00A01",
|
|
"Thu,\nFeb\u00A02",
|
|
"Fri,\nFeb\u00A03",
|
|
"Sat,\nFeb\u00A04",
|
|
],
|
|
"check the columns title is correctly formatted when the range is week"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_column_title.o_grid_row_total",
|
|
1,
|
|
"It should have 1 column for the total"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_column_title.o_grid_highlightable.fw-bolder",
|
|
"The column title containing the current date should not be there."
|
|
);
|
|
assert.strictEqual(
|
|
target.querySelector(".o_grid_column_title.o_grid_highlightable.fw-bolder")
|
|
.textContent,
|
|
"Mon,\nJan\u00A030",
|
|
"The current date should be Monday on 30 January"
|
|
);
|
|
});
|
|
|
|
QUnit.test("basic grid view", async function (assert) {
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid>
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="day" string="Day" span="day" step="day"/>
|
|
<range name="week" string="Week" span="week" step="day" default="1"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
<range name="year" string="Year" span="year" step="month"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.containsN(
|
|
target,
|
|
".o_grid_row.o_grid_highlightable:not(.o_grid_row_title,.o_grid_column_total,.o_grid_row_total)",
|
|
14,
|
|
"The number of cells containing numeric value and whom is not a total cell should be 14 (2 rows and 7 cells to represent the week)"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_row.o_grid_highlightable.text-danger:not(.o_grid_row_title,.o_grid_column_total,.o_grid_row_total)",
|
|
"In those 14 cells, one has a value less than 0 and so the text should be red"
|
|
);
|
|
assert.strictEqual(
|
|
target.querySelector(
|
|
".o_grid_row.o_grid_highlightable.text-danger:not(.o_grid_row_title,.o_grid_column_total,.o_grid_row_total)"
|
|
).textContent,
|
|
"-3.50",
|
|
"The cell with text color in red should contain `-3.50`"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_row.o_grid_highlightable.o_grid_column_total.text-danger",
|
|
"The cell containing the column total and in that column a cell is negative to also get a total negative should have text color in red"
|
|
);
|
|
assert.strictEqual(
|
|
target.querySelector(".o_grid_row.o_grid_highlightable.o_grid_column_total.text-danger")
|
|
.textContent,
|
|
"-3.50"
|
|
);
|
|
assert.containsOnce(target, ".o_grid_row.o_grid_highlightable.o_grid_row_total.bg-danger");
|
|
assert.strictEqual(
|
|
target.querySelector(".o_grid_row.o_grid_highlightable.o_grid_row_total.bg-danger")
|
|
.textContent,
|
|
"-3.50"
|
|
);
|
|
assert.containsN(
|
|
target,
|
|
".o_grid_row.o_grid_highlightable > div.bg-info",
|
|
3,
|
|
"The cell in the column of the current should have `bg-info` class as the header"
|
|
);
|
|
assert.containsN(target, ".o_grid_row.o_grid_row_title.o_grid_highlightable", 2);
|
|
assert.deepEqual(
|
|
getNodesTextContent(
|
|
target.querySelectorAll(".o_grid_row.o_grid_row_title.o_grid_highlightable")
|
|
),
|
|
["P1 | BS task", "Webocalypse Now | Another BS task"]
|
|
);
|
|
await click(target, ".o_grid_navigation_buttons button span.oi-arrow-right");
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_row.o_grid_highlightable:not(.o_grid_row_title,.o_grid_column_total,.o_grid_row_total)",
|
|
"No cell should be found because no records is found next week"
|
|
);
|
|
assert.containsOnce(target, ".o_view_nocontent", "No content div should be displayed");
|
|
assert.containsNone(
|
|
target,
|
|
"div.bg-info",
|
|
"No column should be the current date since we move in the following week."
|
|
);
|
|
await click(target, ".o_grid_navigation_buttons button span.oi-arrow-right");
|
|
assert.containsNone(target, "div.o_grid_row_title", "should not have any cell");
|
|
});
|
|
|
|
QUnit.test("basic grouped grid view", async function (assert) {
|
|
patchDate(2017, 0, 25, 0, 0, 0);
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid>
|
|
<field name="project_id" type="row" section="1"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_section.o_grid_section_title",
|
|
1,
|
|
"A section should be displayed (for the project P1)"
|
|
);
|
|
assert.strictEqual(
|
|
target.querySelector(".o_grid_section.o_grid_section_title").textContent,
|
|
"P1",
|
|
"The title of the section should be the project name"
|
|
);
|
|
assert.containsN(
|
|
target,
|
|
".o_grid_section:not(.o_grid_section_title,.o_grid_row_total)",
|
|
7,
|
|
"7 cells for the section should be displayed to represent the total per day of the section"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_section.o_grid_row_total",
|
|
"One cell should be displayed to display the total of the week for the whole section"
|
|
);
|
|
assert.strictEqual(
|
|
target.querySelector(".o_grid_section.o_grid_row_total").textContent,
|
|
"10:00",
|
|
"The total of the section should be equal to 10 hours."
|
|
);
|
|
assert.containsN(
|
|
target,
|
|
".o_grid_row.o_grid_row_title",
|
|
2,
|
|
"2 rows should be displayed below that section (one per task)"
|
|
);
|
|
assert.deepEqual(
|
|
getNodesTextContent(target.querySelectorAll(".o_grid_row.o_grid_row_title")),
|
|
["None", "BS task"]
|
|
);
|
|
assert.containsN(
|
|
target,
|
|
".o_grid_row:not(.o_grid_row_title,.o_grid_row_total,.o_grid_column_total,.o_grid_add_line)",
|
|
14,
|
|
"7 cells per row should be displayed to get value per day in the current week"
|
|
);
|
|
assert.deepEqual(
|
|
getNodesTextContent(
|
|
target.querySelectorAll(
|
|
".o_grid_row:not(.o_grid_row_title,.o_grid_row_total,.o_grid_column_total,.o_grid_add_line)"
|
|
)
|
|
),
|
|
[
|
|
// row 1
|
|
"0:00",
|
|
"0:00",
|
|
"2:30",
|
|
"0:00",
|
|
"0:00",
|
|
"0:00",
|
|
"0:00",
|
|
// row 2
|
|
"0:00",
|
|
"0:00",
|
|
"0:00",
|
|
"7:30",
|
|
"0:00",
|
|
"0:00",
|
|
"0:00",
|
|
]
|
|
);
|
|
assert.containsN(
|
|
target,
|
|
".o_grid_row.o_grid_row_total",
|
|
2,
|
|
"One cell per row should be displayed to display the total of the week"
|
|
);
|
|
assert.deepEqual(
|
|
getNodesTextContent(target.querySelectorAll(".o_grid_row.o_grid_row_total")),
|
|
["2:30", "7:30"]
|
|
);
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_search_btn",
|
|
"No search button should be displayed in the grid cells."
|
|
);
|
|
await hoverGridCell(target.querySelectorAll('.o_grid_section.o_grid_highlightable')[1]);
|
|
await click(target, ".o_grid_cell button.o_grid_search_btn");
|
|
|
|
// Click on next period to have no data
|
|
await click(target, ".o_grid_navigation_buttons button span.oi-arrow-left");
|
|
assert.containsNone(target, ".o_grid_section");
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_row.o_grid_highlightable:not(.o_grid_row_title,.o_grid_column_total,.o_grid_row_total)",
|
|
"No cell should be found because no records is found next week"
|
|
);
|
|
assert.containsOnce(target, ".o_view_nocontent", "No content div should be displayed");
|
|
assert.containsNone(
|
|
target,
|
|
"div.bg-info",
|
|
"No column should be the current date since we move in the following week."
|
|
);
|
|
});
|
|
|
|
QUnit.test("clicking on the info icon on a cell triggers a do_action for section rows", async function (assert) {
|
|
patchDate(2017, 0, 25, 0, 0, 0);
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid>
|
|
<field name="project_id" type="row" section="1"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_search_btn",
|
|
"No search button should be displayed in the grid cells."
|
|
);
|
|
await hoverGridCell(target.querySelectorAll('.o_grid_section.o_grid_highlightable')[1]);
|
|
await click(target, ".o_grid_cell button.o_grid_search_btn");
|
|
});
|
|
|
|
QUnit.test("Add/remove groupbys in search view", async function (assert) {
|
|
patchDate(2017, 0, 25, 0, 0, 0);
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid>
|
|
<field name="project_id" type="row" section="1"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
searchViewArch: `
|
|
<search>
|
|
<filter string="Project" name="groupby_project" domain="[]" context="{'group_by': 'project_id'}"/>
|
|
<filter string="Task" name="groupby_task" domain="[]" context="{'group_by': 'task_id'}"/>
|
|
</search>
|
|
`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
await toggleSearchBarMenu(target);
|
|
let groupBys = target.querySelectorAll("span.o_menu_item");
|
|
let groupByProject, groupByTask;
|
|
for (const gb of groupBys) {
|
|
if (gb.textContent === "Task") {
|
|
groupByTask = gb;
|
|
} else {
|
|
groupByProject = gb;
|
|
}
|
|
}
|
|
await click(groupByTask, "");
|
|
await click(groupByProject, "");
|
|
assert.containsNone(target, ".o_grid_section");
|
|
assert.containsN(target, ".o_grid_row_title", 2);
|
|
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_grid_row_title")), [
|
|
"None | P1",
|
|
"BS task | P1",
|
|
]);
|
|
await click(target, ".o_grid_navigation_buttons button span.oi-arrow-right");
|
|
assert.containsNone(target, ".o_grid_section");
|
|
assert.containsN(target, ".o_grid_row_title", 2);
|
|
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_grid_row_title")), [
|
|
"BS task | P1",
|
|
"Another BS task | Webocalypse Now",
|
|
]);
|
|
|
|
// Remove the group and check the default groupbys defined in the view are correctly used.
|
|
await toggleSearchBarMenu(target);
|
|
groupBys = target.querySelectorAll("span.o_menu_item");
|
|
for (const gb of groupBys) {
|
|
if (gb.textContent === "Task") {
|
|
groupByTask = gb;
|
|
} else {
|
|
groupByProject = gb;
|
|
}
|
|
}
|
|
await click(groupByTask, "");
|
|
await click(groupByProject, "");
|
|
assert.containsN(target, ".o_grid_section_title", 2);
|
|
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_grid_section_title")), [
|
|
"P1",
|
|
"Webocalypse Now",
|
|
]);
|
|
assert.containsN(target, ".o_grid_row_title", 2);
|
|
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_grid_row_title")), [
|
|
"BS task",
|
|
"Another BS task",
|
|
]);
|
|
});
|
|
|
|
QUnit.test("groupBy with a field", async function (assert) {
|
|
patchDate(2017, 0, 25, 0, 0, 0);
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid>
|
|
<field name="project_id" type="row" section="1"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
searchViewArch: `
|
|
<search>
|
|
<filter string="Task" name="groupby_task" domain="[]" context="{'group_by': 'task_id'}"/>
|
|
</search>
|
|
`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
await toggleSearchBarMenu(target);
|
|
await click(target, "span.o_menu_item:not(.o_add_custom_filter)");
|
|
assert.containsNone(target, ".o_grid_section");
|
|
assert.containsN(target, ".o_grid_row_title", 2);
|
|
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_grid_row_title")), [
|
|
"None",
|
|
"BS task",
|
|
]);
|
|
await click(target, ".o_grid_navigation_buttons button span.oi-arrow-right");
|
|
assert.containsNone(target, ".o_grid_section");
|
|
assert.containsN(target, ".o_grid_row_title", 2);
|
|
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_grid_row_title")), [
|
|
"BS task",
|
|
"Another BS task",
|
|
]);
|
|
});
|
|
|
|
QUnit.test("groupBy doesn't change the scale", async function (assert) {
|
|
patchDate(2017, 0, 25, 0, 0, 0);
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid>
|
|
<field name="project_id" type="row" section="1"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
searchViewArch: `
|
|
<search>
|
|
<filter string="Task" name="groupby_task" domain="[]" context="{'group_by': 'task_id'}"/>
|
|
</search>
|
|
`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
await click(target, ".scale_button_selection");
|
|
await click(target, ".o_view_scale_selector .o_scale_button_month");
|
|
assert.strictEqual(
|
|
target.querySelector(".o_view_scale_selector button.scale_button_selection")
|
|
.textContent,
|
|
"Month",
|
|
"The active range should be Month"
|
|
);
|
|
await toggleSearchBarMenu(target)
|
|
await click(target, "div.o_group_by_menu > span.o_menu_item");
|
|
assert.strictEqual(
|
|
target.querySelector(".o_view_scale_selector button.scale_button_selection")
|
|
.textContent,
|
|
"Month",
|
|
"The active range should still be Month"
|
|
);
|
|
});
|
|
|
|
QUnit.test("groupBy with column field should not be supported", async function (assert) {
|
|
patchDate(2017, 0, 25, 0, 0, 0);
|
|
const grid = await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid>
|
|
<field name="project_id" type="row" section="1"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
searchViewArch: `
|
|
<search>
|
|
<filter string="Date" name="date" domain="[]" context="{'group_by': 'date'}"/>
|
|
</search>
|
|
`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
if (args.method === "web_read_group") {
|
|
assert.deepEqual(args.kwargs.groupby, ["date:day", "task_id", "project_id"]);
|
|
}
|
|
},
|
|
});
|
|
|
|
patchWithCleanup(grid.env.services.notification, {
|
|
add: (message, options) => {
|
|
assert.strictEqual(
|
|
message,
|
|
"Grouping by the field used in the column of the grid view is not possible."
|
|
);
|
|
assert.strictEqual(options.type, "warning");
|
|
},
|
|
});
|
|
|
|
await toggleSearchBarMenu(target);
|
|
const groupByDropdown = target.querySelector(
|
|
".o_menu_item:not(.o_add_custom_filter)"
|
|
).parentNode;
|
|
await toggleMenuItem(target, "Date");
|
|
const dateOptionNodes = groupByDropdown.querySelectorAll(".o_item_option");
|
|
await click(dateOptionNodes[0], "");
|
|
await click(dateOptionNodes[1], "");
|
|
});
|
|
|
|
QUnit.test("DOM keys are unique", async function (assert) {
|
|
serverData.models["analytic.line"].records = [
|
|
{ id: 12, project_id: 142, date: "2017-01-17", unit_amount: 0 },
|
|
{ id: 4, project_id: 143, date: "2017-01-18", unit_amount: 0 },
|
|
{ id: 5, project_id: 142, date: "2017-01-18", unit_amount: 0 },
|
|
{ id: 10, project_id: 31, date: "2017-01-18", unit_amount: 0 },
|
|
{ id: 22, project_id: 33, date: "2017-01-19", unit_amount: 0 },
|
|
{ id: 21, project_id: 99, date: "2017-01-19", unit_amount: 0 },
|
|
{ id: 1, project_id: 31, date: "2017-01-24", unit_amount: 2.5 },
|
|
{ id: 3, project_id: 143, date: "2017-01-25", unit_amount: 5.5 },
|
|
{ id: 2, project_id: 33, date: "2017-01-25", unit_amount: 2 },
|
|
];
|
|
serverData.models.project.records = [
|
|
{ id: 31, display_name: "Rem" },
|
|
{ id: 33, display_name: "Rer" },
|
|
{ id: 142, display_name: "Sas" },
|
|
{ id: 143, display_name: "Sassy" },
|
|
{ id: 99, display_name: "Sar" },
|
|
];
|
|
patchDate(2017, 0, 25, 0, 0, 0);
|
|
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid>
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
<range name="year" string="Year" span="year" step="month"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_grid_row_title")), [
|
|
"Rem",
|
|
"Rer",
|
|
"Sassy",
|
|
]);
|
|
await click(target, ".o_grid_navigation_buttons button span.oi-arrow-left");
|
|
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_grid_row_title")), [
|
|
"Sas",
|
|
"Rem",
|
|
"Sassy",
|
|
"Rer",
|
|
"Sar",
|
|
]);
|
|
});
|
|
|
|
QUnit.test("Group By Selection field", async function (assert) {
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid>
|
|
<field name="selection_field" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
<range name="year" string="Year" span="year" step="month"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_grid_row_title")), [
|
|
"DEF",
|
|
"GHI",
|
|
]);
|
|
await click(target, ".o_grid_navigation_buttons button span.oi-arrow-left");
|
|
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_grid_row_title")), [
|
|
"ABC",
|
|
"DEF",
|
|
]);
|
|
});
|
|
|
|
QUnit.test("Create record with Add button in grid view", async function (assert) {
|
|
patchDate(2017, 1, 25, 0, 0, 0);
|
|
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid display_empty="1">
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
<range name="year" string="Year" span="year" step="month"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
} else if (args.method === "create") {
|
|
assert.strictEqual(
|
|
args.args[0][0].date,
|
|
"2017-02-25",
|
|
"default date should be the current day"
|
|
);
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.containsNone(target, ".o_grid_row_title");
|
|
assert.containsNone(target, ".modal");
|
|
assert.containsNone(target, ".o_view_nocontent");
|
|
await click(
|
|
$(target).find(".o_control_panel_main_buttons > :visible").get(0),
|
|
".o_grid_button_add"
|
|
);
|
|
assert.containsOnce(target, ".modal");
|
|
await selectDropdownItem(target, "project_id", "P1");
|
|
await selectDropdownItem(target, "task_id", "BS task");
|
|
|
|
// input unit_amount
|
|
await editInput(target, ".modal .o_field_widget[name=unit_amount] input", "4");
|
|
|
|
// save
|
|
await click(target, ".modal .modal-footer button.o_form_button_save");
|
|
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_row_title",
|
|
"the record should be created and a row should be added"
|
|
);
|
|
assert.strictEqual(target.querySelector(".o_grid_row_title").textContent, "P1 | BS task");
|
|
});
|
|
|
|
QUnit.test("Create record with Add button in grid view grouped", async function (assert) {
|
|
patchDate(2017, 1, 25, 0, 0, 0);
|
|
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid display_empty="1">
|
|
<field name="project_id" type="row" section="1"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
<range name="year" string="Year" span="year" step="month"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
} else if (args.method === "create") {
|
|
assert.strictEqual(
|
|
args.args[0][0].date,
|
|
"2017-02-25",
|
|
"default date should be the current day"
|
|
);
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.containsNone(target, ".o_grid_row_title");
|
|
assert.containsNone(target, ".modal");
|
|
assert.containsNone(target, ".o_view_nocontent");
|
|
await click(
|
|
$(target).find(".o_control_panel_main_buttons > :visible").get(0),
|
|
".o_grid_button_add"
|
|
);
|
|
assert.containsOnce(target, ".modal");
|
|
await selectDropdownItem(target, "project_id", "P1");
|
|
await selectDropdownItem(target, "task_id", "BS task");
|
|
|
|
// input unit_amount
|
|
await editInput(target, ".modal .o_field_widget[name=unit_amount] input", "4");
|
|
|
|
// save
|
|
await click(target, ".modal .modal-footer button.o_form_button_save");
|
|
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_section_title",
|
|
"the record should be created and a row should be added"
|
|
);
|
|
assert.strictEqual(target.querySelector(".o_grid_section_title").textContent, "P1");
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_row_title",
|
|
"the record should be created and a row should be added"
|
|
);
|
|
assert.strictEqual(target.querySelector(".o_grid_row_title").textContent, "BS task");
|
|
});
|
|
|
|
QUnit.test("switching active range", async function (assert) {
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid>
|
|
<field name="project_id" type="row" section="1"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.strictEqual(
|
|
target.querySelector(".o_view_scale_selector button.scale_button_selection")
|
|
.textContent,
|
|
"Week",
|
|
"The default active range should be the first one define in the grid view"
|
|
);
|
|
assert.containsN(
|
|
target,
|
|
".o_grid_column_title.o_grid_highlightable",
|
|
7,
|
|
"It should have 7 columns (one for each day)"
|
|
);
|
|
await click(target, ".scale_button_selection");
|
|
assert.containsOnce(
|
|
target,
|
|
".o_view_scale_selector .o_scale_button_week",
|
|
"The week scale should be in the dropdown menu"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_view_scale_selector .o_scale_button_month",
|
|
"The month scale should be in the dropdown menu"
|
|
);
|
|
await click(target, ".o_view_scale_selector .o_scale_button_month");
|
|
assert.strictEqual(
|
|
target.querySelector(".o_view_scale_selector button.scale_button_selection")
|
|
.textContent,
|
|
"Month",
|
|
"The active range should be Month"
|
|
);
|
|
assert.containsN(
|
|
target,
|
|
".o_grid_column_title.o_grid_highlightable",
|
|
31,
|
|
"It should have 31 columns (one for each day)"
|
|
);
|
|
});
|
|
|
|
QUnit.test("clicking on the info icon on a cell triggers a do_action", async function (assert) {
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid>
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
} else if (
|
|
args.method === "get_views" &&
|
|
args.kwargs.views.find((v) => v[1] === "list")
|
|
) {
|
|
const context = args.kwargs.context;
|
|
const expectedContext = {
|
|
default_project_id: 31,
|
|
default_task_id: 1,
|
|
default_date: "2017-01-30",
|
|
};
|
|
for (const [key, value] of Object.entries(expectedContext)) {
|
|
assert.strictEqual(context[key], value);
|
|
}
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_search_btn",
|
|
"No search button should be displayed in the grid cells."
|
|
);
|
|
const cells = target.querySelectorAll(".o_grid_row .o_grid_cell_readonly");
|
|
const cell = cells[1];
|
|
await hoverGridCell(cell);
|
|
await click(target, ".o_grid_cell button.o_grid_search_btn");
|
|
});
|
|
|
|
QUnit.test("editing a value [REQUIRE FOCUS]", async function (assert) {
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid editable="1">
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
} else if (args.method === "grid_update_cell") {
|
|
assert.strictEqual(
|
|
args.model,
|
|
"analytic.line",
|
|
"The update cell should be called in the current model."
|
|
);
|
|
const [domain, fieldName, value] = args.args;
|
|
const domainExpected = Domain.and([
|
|
[
|
|
["project_id", "=", 31],
|
|
["task_id", "=", 1],
|
|
],
|
|
[
|
|
["date", ">=", "2017-01-29"],
|
|
["date", "<", "2017-01-30"],
|
|
],
|
|
]).toList({});
|
|
assert.deepEqual(domain, domainExpected);
|
|
assert.strictEqual(
|
|
fieldName,
|
|
"unit_amount",
|
|
"The value updated should be the measure field"
|
|
);
|
|
assert.strictEqual(
|
|
value,
|
|
2,
|
|
"The value should be the one entered by the user, that is 2"
|
|
);
|
|
}
|
|
},
|
|
});
|
|
|
|
const cells = target.querySelectorAll(".o_grid_row .o_grid_cell_readonly");
|
|
const cell = cells[0];
|
|
const cellContainer = cell.closest(".o_grid_highlightable");
|
|
const columnTotal = target.querySelector(
|
|
`.o_grid_row.o_grid_column_total[data-grid-column="${cellContainer.dataset.gridColumn}"]`
|
|
);
|
|
const [columnTotalHours, columnTotalMinutes] = (
|
|
(columnTotal.textContent?.length && columnTotal.textContent.split(":")) || [0, 0]
|
|
).map((value) => Number(value));
|
|
const rowTotal = target.querySelector(
|
|
`.o_grid_row_total[data-grid-row="${cellContainer.dataset.gridRow}"]`
|
|
);
|
|
const [rowTotalHours, rowTotalMinutes] = (
|
|
(rowTotal.textContent?.length && rowTotal.textContent.split(":")) || [0, 0]
|
|
).map((value) => Number(value));
|
|
assert.strictEqual(cell.textContent, "0:00");
|
|
await hoverGridCell(cell);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_cell",
|
|
"The GridCell component should be mounted on the grid cell hovered."
|
|
);
|
|
const gridCellComponentEl = target.querySelector(".o_grid_cell");
|
|
const gridCell = cell.closest(".o_grid_row");
|
|
assert.strictEqual(
|
|
gridCellComponentEl.style["grid-row"],
|
|
gridCell.style["grid-row"],
|
|
"The GridCell component should be mounted in the same cell than the one hovered in the grid view."
|
|
);
|
|
assert.strictEqual(
|
|
gridCellComponentEl.style["grid-column"],
|
|
gridCell.style["grid-column"],
|
|
"The GridCell component should be mounted in the same cell than the one hovered in the grid view."
|
|
);
|
|
assert.strictEqual(
|
|
gridCellComponentEl.style["z-index"],
|
|
"1",
|
|
"The GridCell component should be mounted in the same cell than the one hovered in the grid view."
|
|
);
|
|
await click(target, ".o_grid_cell");
|
|
await nextTick();
|
|
assert.containsOnce(target, ".o_grid_cell input");
|
|
await editInput(target, ".o_grid_cell input", "2");
|
|
await nextTick();
|
|
assert.strictEqual(cell.textContent, "2:00");
|
|
assert.strictEqual(
|
|
columnTotal.textContent,
|
|
`${columnTotalHours + 2}:${String(columnTotalMinutes).padStart(2, "0")}`
|
|
);
|
|
assert.strictEqual(
|
|
rowTotal.textContent,
|
|
`${rowTotalHours + 2}:${String(rowTotalMinutes).padStart(2, "0")}`
|
|
);
|
|
});
|
|
|
|
QUnit.test("hide row total", async function (assert) {
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid hide_line_total="1">
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.containsN(target, ".o_grid_row_title", 2);
|
|
assert.containsNone(target, ".o_grid_row_total", "No row total should be displayed");
|
|
assert.containsN(target, ".o_grid_column_total", 7), "Columns total should be displayed";
|
|
});
|
|
|
|
QUnit.test("hide column total", async function (assert) {
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid hide_column_total="1">
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.containsN(target, ".o_grid_row_title", 2);
|
|
assert.containsN(target, ".o_grid_row_total", 3, "Rows total should be displayed");
|
|
assert.containsNone(target, ".o_grid_column_total", " No column total should be displayed");
|
|
});
|
|
|
|
QUnit.test("display bar chart total", async function (assert) {
|
|
serverData.models["analytic.line"].records.push({
|
|
id: 8,
|
|
project_id: 142,
|
|
task_id: 54,
|
|
date: "2017-01-25",
|
|
unit_amount: 4,
|
|
});
|
|
patchDate(2017, 0, 25, 0, 0, 0);
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid barchart_total="1" editable="1">
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.containsN(target, ".o_grid_row_title", 3);
|
|
assert.containsN(target, ".o_grid_row_total", 5, "Rows total should be displayed");
|
|
assert.containsN(
|
|
target,
|
|
".o_grid_column_total:not(.o_grid_bar_chart_container)",
|
|
8,
|
|
"8 cells should be visible to display the total per colunm"
|
|
);
|
|
assert.containsN(
|
|
target,
|
|
".o_grid_bar_chart_container",
|
|
7,
|
|
"The bar chart total container should be displayed (one per column)"
|
|
);
|
|
assert.containsN(
|
|
target,
|
|
".o_grid_bar_chart_total_pill",
|
|
2,
|
|
"2 bar charts totals should be displayed because the 5 others columns as a total equals to 0."
|
|
);
|
|
|
|
const barchartTotalNodes = target.querySelectorAll(".o_grid_bar_chart_total_pill");
|
|
const expectedBarchartTotalHeightArray = ["", "", "", "", "", "", ""];
|
|
for (let index = 0; index < barchartTotalNodes.length; index++) {
|
|
assert.strictEqual(
|
|
barchartTotalNodes[index].textContent,
|
|
expectedBarchartTotalHeightArray[index]
|
|
);
|
|
}
|
|
|
|
const cells = target.querySelectorAll(".o_grid_row .o_grid_cell_readonly");
|
|
const cell = cells[0];
|
|
assert.strictEqual(cell.textContent, "0:00");
|
|
await hoverGridCell(cell);
|
|
await click(target, ".o_grid_cell");
|
|
await nextTick();
|
|
assert.containsOnce(target, ".o_grid_cell input");
|
|
await editInput(target, ".o_grid_cell input", "2");
|
|
assert.containsN(
|
|
target,
|
|
".o_grid_bar_chart_total_pill",
|
|
3,
|
|
"3 bar chart totals should be now displayed because a new column as a total greater than 0."
|
|
);
|
|
});
|
|
|
|
QUnit.test("row and column are highlighted when hovering a cell", async function (assert) {
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid barchart_total="1" editable="1">
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_row.o_grid_highlightable.bg-700",
|
|
"No cell should be highlighted"
|
|
);
|
|
const cells = target.querySelectorAll(".o_grid_row .o_grid_cell_readonly");
|
|
const cell = cells[0];
|
|
await hoverGridCell(cell);
|
|
assert.containsN(
|
|
target,
|
|
".o_grid_row.o_grid_highlightable.o_grid_highlighted.o_grid_row_highlighted",
|
|
8,
|
|
"8 cells should be highlighted (the cells in the same rows (title row included))"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_row_total.o_grid_highlighted.o_grid_row_highlighted",
|
|
"The row total should also be highlighted"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_column_title.o_grid_highlighted",
|
|
"The column title in the same column then the cell hovered should be highlighted"
|
|
);
|
|
});
|
|
|
|
QUnit.test("grid_anchor stays when navigating", async function (assert) {
|
|
// create an action manager to test the interactions with the search view
|
|
const webClient = await createWebClient({
|
|
serverData,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
await doAction(webClient, {
|
|
res_model: "analytic.line",
|
|
type: "ir.actions.act_window",
|
|
views: [[false, "grid"]],
|
|
context: {
|
|
search_default_project_id: 31,
|
|
grid_anchor: "2017-01-31",
|
|
},
|
|
});
|
|
|
|
// check first column header
|
|
assert.ok(
|
|
getNodesTextContent(target.querySelectorAll(".o_grid_column_title")).includes(
|
|
"Tue,\nJan\u00A031"
|
|
),
|
|
"The 31st of January should be displayed in the grid view."
|
|
);
|
|
|
|
// move to previous week, and check first column header
|
|
await click(target, ".oi-arrow-left");
|
|
assert.notOk(
|
|
getNodesTextContent(target.querySelectorAll(".o_grid_column_title")).includes(
|
|
"Tue,\nJan\u00A031"
|
|
),
|
|
"The 31st of January should no longer be displayed in the grid view"
|
|
);
|
|
assert.ok(
|
|
getNodesTextContent(target.querySelectorAll(".o_grid_column_title")).includes(
|
|
"Tue,\nJan\u00A024"
|
|
),
|
|
"The 24th of January should be displayed in the grid view."
|
|
);
|
|
|
|
// remove the filter in the searchview
|
|
await click(target, ".o_facet_remove");
|
|
assert.ok(
|
|
getNodesTextContent(target.querySelectorAll(".o_grid_column_title")).includes(
|
|
"Tue,\nJan\u00A024"
|
|
),
|
|
"The 24th of January should be always displayed in the grid view even if we remove a filter in the search view"
|
|
);
|
|
});
|
|
|
|
QUnit.test(
|
|
"dialog should not close when clicking the link to many2one field",
|
|
async function (assert) {
|
|
// create an action manager to test the interactions with the search view
|
|
const webClient = await createWebClient({
|
|
serverData,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
} else if (route === "/web/dataset/call_kw/task/get_formview_id") {
|
|
return false;
|
|
}
|
|
},
|
|
});
|
|
|
|
await doAction(webClient, {
|
|
res_model: "analytic.line",
|
|
type: "ir.actions.act_window",
|
|
views: [[false, "grid"]],
|
|
});
|
|
|
|
await click(
|
|
$(target).find(".o_control_panel_main_buttons > :visible").get(0),
|
|
".o_grid_button_add"
|
|
);
|
|
await nextTick();
|
|
assert.containsOnce(target, ".modal[role='dialog']");
|
|
|
|
await selectDropdownItem(target, "task_id", "BS task");
|
|
await click(target, '.modal .o_field_widget[name="task_id"] button.o_external_button');
|
|
// Clicking somewhere on the form dialog should not close it
|
|
assert.containsN(target, ".modal[role='dialog']", 2);
|
|
await click(target.querySelector(".modal[role='dialog']"));
|
|
assert.containsN(target, ".modal[role='dialog']", 2);
|
|
}
|
|
);
|
|
|
|
QUnit.test("grid with two tasks with same name, and widget", async function (assert) {
|
|
serverData.models.task.records = [
|
|
{ id: 1, display_name: "Awesome task", project_id: 31 },
|
|
{ id: 2, display_name: "Awesome task", project_id: 31 },
|
|
];
|
|
serverData.models["analytic.line"].records = [
|
|
{ id: 1, task_id: 1, date: "2017-01-30", unit_amount: 2 },
|
|
{ id: 2, task_id: 2, date: "2017-01-31", unit_amount: 5.5 },
|
|
];
|
|
const webClient = await createWebClient({
|
|
serverData,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
await doAction(webClient, {
|
|
res_model: "analytic.line",
|
|
type: "ir.actions.act_window",
|
|
views: [[false, "grid"]],
|
|
context: { search_default_groupby_task: 1 }, // to avoid creating a new grid view to remove project_id in rows
|
|
});
|
|
|
|
assert.containsN(target, ".o_grid_row_title", 2);
|
|
assert.deepEqual(getNodesTextContent(target.querySelectorAll(".o_grid_row_title")), [
|
|
"Awesome task",
|
|
"Awesome task",
|
|
]);
|
|
});
|
|
|
|
QUnit.test("test grid cell formatting with float_time widget", async function (assert) {
|
|
patchDate(2017, 0, 24, 0, 0, 0);
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
groupBy: ["task_id", "project_id"],
|
|
arch: `<grid>
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="day" string="Day" span="day" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_row.o_grid_highlightable:not(.o_grid_row_title,.o_grid_column_total,.o_grid_row_total)"
|
|
);
|
|
assert.strictEqual(
|
|
target.querySelector(
|
|
".o_grid_row.o_grid_highlightable:not(.o_grid_row_title,.o_grid_column_total,.o_grid_row_total)"
|
|
).textContent,
|
|
"2:30",
|
|
"Check if the cell is correctly formatted as float time"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_column_total:not(.o_grid_row_title,.o_grid_row_total,.o_grid_bar_chart_container)"
|
|
);
|
|
assert.strictEqual(
|
|
target.querySelector(
|
|
".o_grid_column_total:not(.o_grid_row_title,.o_grid_row_total,.o_grid_bar_chart_container) span"
|
|
).textContent,
|
|
"2:30",
|
|
"check format time is used"
|
|
);
|
|
assert.containsOnce(target, ".o_grid_row.o_grid_highlightable.o_grid_row_total");
|
|
assert.strictEqual(
|
|
target.querySelector(".o_grid_row.o_grid_highlightable.o_grid_row_total").textContent,
|
|
"2:30",
|
|
"check format time is used"
|
|
);
|
|
});
|
|
|
|
QUnit.test(
|
|
"The help content is not displayed instead of the grid with `display_empty` is true in the grid tag",
|
|
async function (assert) {
|
|
patchDate(2022, 0, 1, 0, 0, 0); // to be sure no data is found
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid display_empty="1">
|
|
<field name="project_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="day" string="Day" span="day" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.containsNone(target, ".o_view_nocontent", "No content div should be displayed");
|
|
}
|
|
);
|
|
|
|
QUnit.test("create_inline: test add a line in the grid view", async function (assert) {
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid create_inline="1">
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_button_add:visible",
|
|
"'Add a line' control panel button should not be visible"
|
|
);
|
|
assert.deepEqual(
|
|
getNodesTextContent(target.querySelectorAll(".o_grid_renderer .o_grid_add_line a")),
|
|
["Add a line "],
|
|
"A button `Add a line` should be displayed in the grid view"
|
|
);
|
|
await click(target, ".o_grid_renderer .o_grid_add_line a");
|
|
assert.containsOnce(target, ".modal");
|
|
await click(target, ".modal .modal-footer button.o_form_button_cancel");
|
|
await click(target, ".o_grid_navigation_buttons button span.oi-arrow-right");
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_add_line a",
|
|
"No Add a line button should be displayed when no data is found"
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_button_add:visible",
|
|
"'Add a line' control panel button should be visible"
|
|
);
|
|
});
|
|
|
|
QUnit.test(
|
|
"create_inline=true and display_empty=true: test add a line in the grid view",
|
|
async function (assert) {
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid create_inline="1" display_empty="1">
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
domain: Domain.FALSE.toList({}),
|
|
});
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_add_line a",
|
|
"The Add a line button should be displayed even if there is no data"
|
|
);
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_button_add:visible",
|
|
"The 'Add a line' button in the control panel should not be visible."
|
|
);
|
|
assert.deepEqual(
|
|
getNodesTextContent(target.querySelectorAll(".o_grid_renderer .o_grid_add_line a")),
|
|
["Add a line "],
|
|
"A button `Add a line` should be displayed in the grid view"
|
|
);
|
|
await click(target, ".o_grid_add_line a");
|
|
assert.containsOnce(target, ".modal");
|
|
await click(target, ".modal .modal-footer button.o_form_button_cancel");
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_add_line a",
|
|
"No Add a line button should be displayed when no data is found"
|
|
);
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_button_add:visible",
|
|
"'Add a line' control panel button should be visible"
|
|
);
|
|
}
|
|
);
|
|
|
|
QUnit.test("create/edit disabled for readonly grid view", async function (assert) {
|
|
serverData.models["analytic.line"].fields.validated = {
|
|
string: "Validation",
|
|
type: "boolean",
|
|
group_operator: "bool_or",
|
|
};
|
|
serverData.models["analytic.line"].records.push({
|
|
id: 8,
|
|
project_id: 142,
|
|
task_id: 54,
|
|
date: "2017-01-25",
|
|
unit_amount: 4,
|
|
validated: true,
|
|
});
|
|
patchDate(2017, 0, 25, 0, 0, 0);
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid editable="1">
|
|
<field name="validated" type="readonly"/>
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="day" string="Day" span="day" step="day"/>
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
const cells = target.querySelectorAll(".o_grid_row .o_grid_cell_readonly");
|
|
let cell = cells[0];
|
|
await hoverGridCell(cell);
|
|
assert.containsOnce(target, ".o_grid_cell .o_grid_search_btn");
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_cell.o_field_cursor_disabled",
|
|
"The cell should not be in readonly"
|
|
);
|
|
await triggerEvent(target, ".o_grid_cell", "mouseout");
|
|
cell = cells[1];
|
|
await hoverGridCell(cell);
|
|
assert.containsOnce(target, ".o_grid_cell .o_grid_search_btn");
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_cell.o_field_cursor_disabled",
|
|
"The cell should be in readonly since at least one timesheet is validated in that cell"
|
|
);
|
|
await click(target, "button.o_grid_search_btn");
|
|
});
|
|
|
|
QUnit.test(
|
|
"display the empty grid without None line when there is no data",
|
|
async function (assert) {
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid>
|
|
<field name="project_id" type="row" section="1"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
domain: Domain.FALSE.toList({}),
|
|
});
|
|
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_section_title",
|
|
"No section should be displayed to display 'None'"
|
|
);
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_row_title",
|
|
"No row should be added to display 'None'"
|
|
);
|
|
}
|
|
);
|
|
|
|
QUnit.test('ensure the "None" is displayed in multi-level groupby', async function (assert) {
|
|
const webClient = await createWebClient({
|
|
serverData,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
await doAction(webClient, {
|
|
res_model: "analytic.line",
|
|
type: "ir.actions.act_window",
|
|
views: [[1, "grid"]],
|
|
context: {
|
|
search_default_project_id: 31,
|
|
search_default_groupby_task: 1,
|
|
search_default_groupby_selection: 1,
|
|
grid_anchor: "2017-01-24",
|
|
},
|
|
});
|
|
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_section",
|
|
"No section should be displayed since the section field is not first in the groupby"
|
|
);
|
|
assert.ok(
|
|
getNodesTextContent(target.querySelectorAll(".o_grid_row_title")).includes(
|
|
"None | ABC"
|
|
),
|
|
"'None' should be displayed"
|
|
);
|
|
});
|
|
|
|
QUnit.test('Group By selection field without passing selection field in data', async function (assert) {
|
|
serverData.models["analytic.line"].records.push({
|
|
id: 6,
|
|
project_id: 142,
|
|
task_id: 12,
|
|
date: "2017-01-24",
|
|
unit_amount: 7.0,
|
|
});
|
|
|
|
const webClient = await createWebClient({
|
|
serverData,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
await doAction(webClient, {
|
|
res_model: "analytic.line",
|
|
type: "ir.actions.act_window",
|
|
views: [[1, "grid"]],
|
|
context: {
|
|
search_default_groupby_selection: 1,
|
|
grid_anchor: "2017-01-24",
|
|
},
|
|
});
|
|
|
|
assert.ok(
|
|
getNodesTextContent(target.querySelectorAll(".o_grid_row_title")).includes(
|
|
"None"
|
|
),
|
|
"'None' should be displayed."
|
|
);
|
|
});
|
|
|
|
QUnit.test("stop edition when the user clicks outside", async function (assert) {
|
|
const arch = serverData.views["analytic.line,false,grid"].replace(
|
|
"<grid>",
|
|
'<grid editable="1">'
|
|
);
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
const cells = target.querySelectorAll(".o_grid_row .o_grid_cell_readonly");
|
|
const cell = cells[1];
|
|
await hoverGridCell(cell);
|
|
await click(target, ".o_grid_cell");
|
|
await nextTick();
|
|
|
|
assert.containsOnce(target, ".o_grid_cell input", "The cell should be in edit mode");
|
|
|
|
await click(target, ".o_grid_view");
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_cell input",
|
|
"The GridCell should no longer be visible and so no cell is in edit mode."
|
|
);
|
|
});
|
|
|
|
QUnit.test(
|
|
"display no content helper when no data and sample data is used (with display_empty='1')",
|
|
async function (assert) {
|
|
const arch = serverData.views["analytic.line,false,grid"].replace(
|
|
"<grid>",
|
|
`<grid create_inline="1"
|
|
form_view_id="%(timesheet_grid.my_timesheet_form_view)d"
|
|
editable="1"
|
|
display_empty="1"
|
|
sample="1"
|
|
>`
|
|
);
|
|
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
domain: Domain.FALSE.toList({}),
|
|
});
|
|
|
|
assert.containsOnce(
|
|
target,
|
|
".o_view_sample_data",
|
|
"The sample data should be displayed since no records is found."
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_view_nocontent",
|
|
"The action helper should also be displayed since the sample data is displayed even if display_empty='1'."
|
|
);
|
|
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_buttons .o_grid_button_add:visible",
|
|
"The `Add a Line` button should be displayed when no content data is displayed to be able to create a record."
|
|
);
|
|
|
|
await click(target, ".o_grid_navigation_buttons span.oi-arrow-right");
|
|
assert.containsNone(
|
|
target,
|
|
".o_view_sample_data",
|
|
"The sample data should no longer be displayed since display_empty is true in the grid view"
|
|
);
|
|
assert.containsNone(
|
|
target,
|
|
".o_view_nocontent",
|
|
"The no content helper should no longer be displayed since display_empty is true in the grid view."
|
|
);
|
|
assert.containsNone(
|
|
target,
|
|
".o_grid_buttons .o_grid_button_add:visible",
|
|
"The `Add a Line` button should no longer be displayed near the `Today` one since the no content helper is not displayed."
|
|
);
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_grid .o_grid_row.o_grid_add_line.position-md-sticky",
|
|
"The `Add a Line` button should be displayed in the grid view since create_inline='1'"
|
|
);
|
|
}
|
|
);
|
|
|
|
QUnit.test("Only relevant grid rows are rendered with larger recordsets", async (assert) => {
|
|
// Setup: generates 100 new tasks and related analytic lines distributed
|
|
// in all available projects, deterministically based on their ID.
|
|
const { fields: alFields, records: analyticLines } = serverData.models["analytic.line"];
|
|
const { records: tasks } = serverData.models["task"];
|
|
const { records: projects } = serverData.models["project"];
|
|
const selectionValues = alFields.selection_field.selection;
|
|
const today = luxon.DateTime.local().toFormat("yyyy-MM-dd");
|
|
for (let id = 100; id < 200; id++) {
|
|
const projectId = projects[id % projects.length].id;
|
|
tasks.push({
|
|
id,
|
|
display_name: `BS task #${id}`,
|
|
project_id: projectId,
|
|
});
|
|
analyticLines.push({
|
|
id,
|
|
project_id: projectId,
|
|
task_id: id,
|
|
selection_field: selectionValues[id % selectionValues.length][0],
|
|
date: today,
|
|
unit_amount: (id % 10) + 1, // 1 to 10
|
|
});
|
|
}
|
|
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: /* xml */ `
|
|
<grid>
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="day" string="Day" span="day" step="day"/>
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
<range name="year" string="Year" span="year" step="month"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure"/>
|
|
</grid>`,
|
|
async mockRPC(_route, { method }) {
|
|
if (method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Returns unique "data-grid-row" attributes to check for rows equality
|
|
* @returns {string[]}
|
|
*/
|
|
const getCurrentRows = () => [
|
|
...new Set([...grid.children].map((el) => el.dataset.gridRow)),
|
|
];
|
|
|
|
const content = target.querySelector(".o_content");
|
|
const grid = target.querySelector(".o_grid_grid");
|
|
const firstRow = grid.querySelector(".o_grid_column_title");
|
|
content.style = "height: 600px; overflow: scroll;";
|
|
|
|
// This is to ensure that the virtual rows will not be impacted by
|
|
// sub-pixel calculations.
|
|
await triggerScroll(content, { top: 0 });
|
|
|
|
const initialRows = getCurrentRows();
|
|
let currentRows = initialRows;
|
|
|
|
assert.strictEqual(content.scrollTop, 0, "content should be scrolled to the top");
|
|
assert.strictEqual(content.offsetHeight, 600, "content should have its height fixed");
|
|
// ! This next assertion is important: it ensures that the grid rows are
|
|
// ! hard-coded so that the virtual hook can work with it. Adapt this test
|
|
// ! accordingly should the row height change.
|
|
assert.strictEqual(
|
|
grid.clientHeight -
|
|
firstRow.offsetHeight /* first row is "auto" so we don't count it */,
|
|
(tasks.length - 1) /* ignore total row */ * 48 /* base grid row height */,
|
|
"grid content should be the height of its row height times the amount of records"
|
|
);
|
|
assert.ok(currentRows.length < tasks.length, "not all rows should be displayed");
|
|
|
|
// Scroll to the middle of the grid
|
|
await triggerScroll(content, { top: content.scrollHeight / 2 });
|
|
|
|
assert.notDeepEqual(currentRows, getCurrentRows(), "rows should be different");
|
|
assert.ok(getCurrentRows().length < tasks.length, "not all rows should be displayed");
|
|
currentRows = getCurrentRows();
|
|
|
|
// Scroll to the end of the grid
|
|
await triggerScroll(content, { top: content.scrollHeight });
|
|
|
|
assert.notDeepEqual(currentRows, getCurrentRows(), "rows should be different");
|
|
assert.ok(getCurrentRows().length < tasks.length, "not all rows should be displayed");
|
|
|
|
// Scroll back to top
|
|
await triggerScroll(content, { top: 0 });
|
|
|
|
assert.deepEqual(getCurrentRows(), initialRows, "rows should be the same as initially");
|
|
});
|
|
|
|
QUnit.test("Edition navigate with tab/shift+tab and enter key", async (assert) => {
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid editable="1">
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
function checkGridCellInRightPlace(expectedGridRow, expectedGridColumn) {
|
|
const gridCell = target.querySelector(".o_grid_cell");
|
|
assert.strictEqual(gridCell.dataset.gridRow, expectedGridRow);
|
|
assert.strictEqual(gridCell.dataset.gridColumn, expectedGridColumn);
|
|
}
|
|
|
|
const firstCell = target.querySelector(".o_grid_row[data-row='1'][data-column='0']");
|
|
assert.strictEqual(firstCell.dataset.gridRow, "2");
|
|
assert.strictEqual(firstCell.dataset.gridColumn, "2");
|
|
await hoverGridCell(firstCell, ".o_grid_cell_readonly");
|
|
assert.containsOnce(
|
|
target,
|
|
".o_grid_cell",
|
|
"The GridCell component should be mounted on the grid cell hovered."
|
|
);
|
|
checkGridCellInRightPlace(firstCell.dataset.gridRow, firstCell.dataset.gridColumn);
|
|
await click(target, ".o_grid_cell");
|
|
await nextTick();
|
|
|
|
// Go to the next cell
|
|
await triggerEvent(target, ".o_grid_cell input", "keydown", { key: "tab" });
|
|
checkGridCellInRightPlace("2", "3");
|
|
|
|
// Go to the previous cell
|
|
await triggerEvent(target, ".o_grid_cell input", "keydown", { key: "shift+tab" });
|
|
checkGridCellInRightPlace("2", "2");
|
|
|
|
// Go the cell below
|
|
await triggerEvent(target, ".o_grid_cell input", "keydown", { key: "enter" });
|
|
checkGridCellInRightPlace("3", "2");
|
|
|
|
// Go up since it is the cell in the row
|
|
await triggerEvent(target, ".o_grid_cell input", "keydown", { key: "enter" });
|
|
checkGridCellInRightPlace("2", "3");
|
|
|
|
await triggerEvent(target, ".o_grid_cell input", "keydown", { key: "shift+tab" });
|
|
checkGridCellInRightPlace("2", "2");
|
|
|
|
// Go to the last editable cell in the grid view since it is the first cell.
|
|
await triggerEvent(target, ".o_grid_cell input", "keydown", { key: "shift+tab" });
|
|
checkGridCellInRightPlace("3", "8");
|
|
|
|
// Go back to the first cell since it is the last cell in grid view.
|
|
await triggerEvent(target, ".o_grid_cell input", "keydown", { key: "tab" });
|
|
checkGridCellInRightPlace("2", "2");
|
|
|
|
// Go to the last editable cell in the grid view since it is the first cell.
|
|
await triggerEvent(target, ".o_grid_cell input", "keydown", { key: "shift+tab" });
|
|
checkGridCellInRightPlace("3", "8");
|
|
|
|
// Go back to the first cell since it is the last cell in grid view.
|
|
await triggerEvent(target, ".o_grid_cell input", "keydown", { key: "enter" });
|
|
checkGridCellInRightPlace("2", "2");
|
|
});
|
|
|
|
QUnit.test("Add custom buttons in grid view", async function (assert) {
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid editable="1">
|
|
<button name="action_test" string="Test" />
|
|
<button name="action_test_invisible" string="Test Invisible" invisible="not context.get('coucou', False)"/>
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
|
|
assert.containsN(
|
|
target,
|
|
"button[name='action_test']",
|
|
2, // the second one is invisible for responsive.
|
|
"The custom button should be visible",
|
|
);
|
|
assert.containsNone(target, "button[name='action_test_invisible']");
|
|
});
|
|
|
|
QUnit.test("date should be grouped by month in year range", async function (assert) {
|
|
assert.expect(1);
|
|
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `
|
|
<grid display_empty="1">
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="year" string="Year" span="year" step="month"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure"/>
|
|
</grid>
|
|
`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
} else if (args.method === "web_read_group") {
|
|
assert.deepEqual(args.kwargs.groupby, ["date:month", "project_id", "task_id"]);
|
|
}
|
|
},
|
|
});
|
|
});
|
|
|
|
QUnit.test(
|
|
"display notification when the update of the grid cell cannot be done",
|
|
async function (assert) {
|
|
const grid = await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid editable="1">
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
} else if (args.method === "grid_update_cell") {
|
|
assert.step("grid_update_cell");
|
|
return {
|
|
type: "ir.actions.client",
|
|
tag: "display_notification",
|
|
params: {
|
|
message: "test display a notification",
|
|
type: "danger",
|
|
sticky: false,
|
|
},
|
|
};
|
|
}
|
|
},
|
|
});
|
|
patchWithCleanup(grid.env.services.action, {
|
|
doAction: (data) => {
|
|
if (data.tag === "display_notification") {
|
|
assert.step(`notification_${data.params.type}`);
|
|
}
|
|
},
|
|
});
|
|
|
|
const cells = target.querySelectorAll(".o_grid_row .o_grid_cell_readonly");
|
|
const cell = cells[0];
|
|
await hoverGridCell(cell);
|
|
await click(target, ".o_grid_cell");
|
|
await nextTick();
|
|
assert.containsOnce(target, ".o_grid_cell input");
|
|
await editInput(target, ".o_grid_cell input", "2");
|
|
assert.verifySteps(["grid_update_cell", "notification_danger"]);
|
|
}
|
|
);
|
|
|
|
QUnit.test(
|
|
"grid: use the context in the action when a record will be created",
|
|
async function (assert) {
|
|
patchDate(2017, 1, 25, 0, 0, 0);
|
|
|
|
await makeView({
|
|
type: "grid",
|
|
resModel: "analytic.line",
|
|
serverData,
|
|
arch: `<grid display_empty="1">
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="row"/>
|
|
<field name="date" type="col">
|
|
<range name="week" string="Week" span="week" step="day"/>
|
|
<range name="month" string="Month" span="month" step="day"/>
|
|
<range name="year" string="Year" span="year" step="month"/>
|
|
</field>
|
|
<field name="unit_amount" type="measure"/>
|
|
</grid>`,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
} else if (args.method === "create") {
|
|
assert.strictEqual(
|
|
args.args[0][0].date,
|
|
"2017-02-25",
|
|
"default date should be the current day"
|
|
);
|
|
}
|
|
},
|
|
context: {
|
|
default_project_id: 31,
|
|
},
|
|
});
|
|
assert.containsNone(target, ".o_grid_row_title");
|
|
assert.containsNone(target, ".modal");
|
|
assert.containsNone(target, ".o_view_nocontent");
|
|
await click(
|
|
$(target).find(".o_control_panel_main_buttons > :visible").get(0),
|
|
".o_grid_button_add"
|
|
);
|
|
assert.containsOnce(target, ".modal");
|
|
assert.containsOnce(target, ".modal div[name=project_id]");
|
|
assert.strictEqual(
|
|
target.querySelector(".modal div[name=project_id] input").value,
|
|
"P1"
|
|
);
|
|
}
|
|
);
|
|
|
|
QUnit.test("restore navigationInfo from previous state", async function (assert) {
|
|
const webClient = await createWebClient({
|
|
serverData,
|
|
async mockRPC(route, args) {
|
|
if (args.method === "grid_unavailability") {
|
|
return {};
|
|
}
|
|
},
|
|
});
|
|
await doAction(webClient, {
|
|
res_model: "analytic.line",
|
|
type: "ir.actions.act_window",
|
|
views: [[false, "grid"]],
|
|
});
|
|
await click(target, ".oi-arrow-left");
|
|
assert.ok(
|
|
getNodesTextContent(target.querySelectorAll(".o_grid_column_title")).includes(
|
|
"Tue,\nJan\u00A024"
|
|
),
|
|
"The 24th of January should be displayed in the grid view"
|
|
);
|
|
await hoverGridCell((target.querySelectorAll(".o_grid_row .o_grid_cell_readonly"))[0]);
|
|
await click(target, ".o_grid_cell button.o_grid_search_btn");
|
|
await click(target.querySelector(".breadcrumb-item.o_back_button"));
|
|
assert.ok(
|
|
getNodesTextContent(target.querySelectorAll(".o_grid_column_title")).includes(
|
|
"Tue,\nJan\u00A024"
|
|
),
|
|
"The 24th of January should still be displayed in the grid view"
|
|
);
|
|
});
|
|
|
|
QUnit.test("export navigationInfo when col is not a range", async function (assert) {
|
|
serverData.views["analytic.line,false,grid"] = `<grid>
|
|
<field name="project_id" type="row"/>
|
|
<field name="task_id" type="col"/>
|
|
<field name="unit_amount" type="measure" widget="float_time"/>
|
|
</grid>`;
|
|
const webClient = await createWebClient({ serverData });
|
|
await doAction(webClient, {
|
|
res_model: "analytic.line",
|
|
type: "ir.actions.act_window",
|
|
views: [[false, "grid"]],
|
|
});
|
|
await hoverGridCell(target.querySelectorAll(".o_grid_row .o_grid_cell_readonly")[0]);
|
|
await click(target, ".o_grid_cell button.o_grid_search_btn");
|
|
assert.containsOnce(target, ".o_list_view");
|
|
});
|
|
});
|