import { beforeEach, describe, expect, test } from "@odoo/hoot";
import {
click,
hover,
keyDown,
keyUp,
pointerDown,
press,
queryAllTexts,
queryOne,
scroll,
} from "@odoo/hoot-dom";
import { Deferred, advanceTime, animationFrame, mockDate, runAllTimers } from "@odoo/hoot-mock";
import {
contains,
defineParams,
fields,
mockService,
onRpc,
patchWithCleanup,
validateSearch,
} from "@web/../tests/web_test_helpers";
import { ResUsers, Tasks, defineGanttModels } from "./gantt_mock_models";
import {
CLASSES,
SELECTORS,
clickCell,
dragPill,
editPill,
focusToday,
ganttControlsChanges,
getActiveScale,
getCell,
getGridContent,
getPill,
getPillWrapper,
hoverGridCell,
mountGanttView,
resizePill,
selectGanttRange,
selectRange,
setScale,
} from "./web_gantt_test_helpers";
import { omit, pick } from "@web/core/utils/objects";
import { deserializeDate } from "@web/core/l10n/dates";
// Hard-coded daylight saving dates from 2019
const DST_DATES = {
winterToSummer: {
before: "2019-03-30",
after: "2019-03-31",
},
summerToWinter: {
before: "2019-10-26",
after: "2019-10-27",
},
};
describe.current.tags("desktop");
defineGanttModels();
beforeEach(() => {
mockDate("2018-12-20T08:00:00", +1);
defineParams({
lang_parameters: {
time_format: "%I:%M:%S",
},
});
});
test("date navigation with timezone (1h)", async () => {
onRpc("get_gantt_data", ({ kwargs }) => {
expect.step(kwargs.domain.toString());
});
await mountGanttView({
resModel: "tasks",
arch: '',
});
expect.verifySteps(["&,start,<,2019-02-28 23:00:00,stop,>,2018-11-30 23:00:00"]);
expect(getGridContent().range).toBe("From: 12/01/2018 to: 02/28/2019");
// switch to day view and check day navigation
await setScale(5);
await ganttControlsChanges();
expect.verifySteps(["&,start,<,2019-02-28 23:00:00,stop,>,2018-11-30 23:00:00"]);
expect(getGridContent().range).toBe("From: 12/01/2018 to: 02/28/2019");
// switch to week view and check week navigation
await setScale(1);
await ganttControlsChanges();
expect.verifySteps(["&,start,<,2019-02-28 23:00:00,stop,>,2018-11-30 23:00:00"]);
expect(getGridContent().range).toBe("From: 12/01/2018 to: 02/28/2019");
// switch to year view and check year navigation
await setScale(5);
await ganttControlsChanges();
expect.verifySteps(["&,start,<,2019-02-28 23:00:00,stop,>,2018-11-30 23:00:00"]);
expect(getGridContent().range).toBe("From: 12/01/2018 to: 02/28/2019");
});
test("if a on_create is specified, execute the action rather than opening a dialog. And reloads after the action", async () => {
mockService("action", {
doAction(action, options) {
expect.step(`[action] ${action}`);
expect(options.additionalContext).toEqual({
default_start: "2018-11-30 23:00:00",
default_stop: "2018-12-31 23:00:00",
lang: "en",
allowed_company_ids: [1],
start: "2018-11-30 23:00:00",
stop: "2018-12-31 23:00:00",
tz: "taht",
uid: 7,
});
options.onClose();
},
});
onRpc("get_gantt_data", () => {
expect.step("get_gantt_data");
});
await mountGanttView({
resModel: "tasks",
arch: '',
});
expect.verifySteps(["get_gantt_data"]);
await contains(SELECTORS.addButton + ":visible").click();
expect.verifySteps(["[action] this_is_create_action", "get_gantt_data"]);
});
test("select cells to plan a task", async () => {
mockService("dialog", {
add(_, props) {
expect.step(`[dialog] ${props.title}`);
expect(props.context).toEqual({
default_start: "2018-12-18 23:00:00",
default_stop: "2018-12-21 23:00:00",
lang: "en",
allowed_company_ids: [1],
start: "2018-12-18 23:00:00",
stop: "2018-12-21 23:00:00",
tz: "taht",
uid: 7,
});
},
});
await mountGanttView({
resModel: "tasks",
arch: '',
});
await contains(getCell("19 December 2018")).dragAndDrop(getCell("21 December 2018"));
expect.verifySteps(["[dialog] Plan"]);
});
test("drag and drop on the same cell to plan a task", async () => {
mockService("dialog", {
add(_, props) {
expect.step(`[dialog] ${props.title}`);
expect(props.context).toEqual({
default_start: "2018-12-14 23:00:00",
default_stop: "2018-12-15 23:00:00",
lang: "en",
allowed_company_ids: [1],
start: "2018-12-14 23:00:00",
stop: "2018-12-15 23:00:00",
tz: "taht",
uid: 7,
});
},
});
await mountGanttView({
resModel: "tasks",
arch: '',
});
await contains(getCell("15 December 2018")).dragAndDrop(getCell("15 December 2018"));
expect.verifySteps(["[dialog] Plan"]);
});
test("row id is properly escaped to avoid name issues in selection", async () => {
mockService("dialog", {
add() {
expect.step("[dialog]");
},
});
ResUsers._records[0].name = "O'Reilly";
await mountGanttView({
resModel: "tasks",
arch: '',
});
await hoverGridCell("11 December 2018");
await clickCell("11 December 2018");
expect.verifySteps(["[dialog]"]);
});
test("select cells to plan a task: 1-level grouped", async () => {
mockService("dialog", {
add(_, props) {
expect.step(`[dialog] ${props.title}`);
expect(props.context).toEqual({
default_start: "2018-12-10 23:00:00",
default_stop: "2018-12-12 23:00:00",
default_user_id: 1,
lang: "en",
allowed_company_ids: [1],
start: "2018-12-10 23:00:00",
stop: "2018-12-12 23:00:00",
tz: "taht",
uid: 7,
user_id: 1,
});
},
});
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: ["user_id"],
});
await hoverGridCell("11 December 2018");
const { moveTo, drop } = await contains(getCell("11 December 2018")).drag();
moveTo(getCell("12 December 2018"));
await runAllTimers(); // Pointer move is subjected to throttleForAnimation in gantt
drop();
expect.verifySteps(["[dialog] Plan"]);
});
test("select cells to plan a task: 2-level grouped", async () => {
mockService("dialog", {
add(_, props) {
expect.step(`[dialog] ${props.title}`);
expect(props.context).toEqual({
default_project_id: 1,
default_start: "2018-12-10 23:00:00",
default_stop: "2018-12-12 23:00:00",
default_user_id: 1,
allowed_company_ids: [1],
lang: "en",
project_id: 1,
start: "2018-12-10 23:00:00",
stop: "2018-12-12 23:00:00",
tz: "taht",
uid: 7,
user_id: 1,
});
},
});
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: ["user_id", "project_id"],
});
await hoverGridCell("11 December 2018");
const dragAndDrop1 = await contains(getCell("11 December 2018")).drag();
dragAndDrop1.moveTo(getCell("12 December 2018"));
await advanceTime(20); // Pointer move is subjected to throttleForAnimation in gantt
dragAndDrop1.drop();
// nothing happens
await hoverGridCell("11 December 2018", "Project 1");
await advanceTime(20);
const dragAndDrop2 = await contains(getCell("11 December 2018", "Project 1")).drag();
dragAndDrop2.moveTo(getCell("12 December 2018", "Project 1"));
await advanceTime(20);
dragAndDrop2.drop();
expect.verifySteps(["[dialog] Plan"]);
});
test("hovering a cell with special character", async () => {
expect.assertions(1);
// add special character to data
ResUsers._records[0].name = "User' 1";
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: ["user_id", "project_id"],
});
// hover on first header "User' 1" with data-row-id equal to [{"user_id":[1,"User' 1"]}]
// the "'" must be escaped with "\\'" in findSiblings to prevent the selector to crash
await contains(".o_gantt_row_header").hover();
expect(".o_gantt_row_header:first").toHaveClass("o_gantt_group_hovered", {
message: "hover style is applied to the element",
});
});
test("open a dialog to add a new task", async () => {
defineParams({
lang_parameters: {
time_format: "%H:%M:%S",
},
});
Tasks._views = {
form: `
`,
};
await mountGanttView({
resModel: "tasks",
arch: '',
});
expect(".modal").toHaveCount(0);
await contains(SELECTORS.addButton + ":visible").click();
// check that the dialog is opened with prefilled fields
expect(".modal").toHaveCount(1);
expect(".o_field_widget[name=start] input").toHaveValue("12/01/2018 00:00:00");
expect(".o_field_widget[name=stop] input").toHaveValue("01/01/2019 00:00:00");
});
test("open a dialog to create/edit a task", async () => {
defineParams({
lang_parameters: {
time_format: "%H:%M:%S",
},
});
Tasks._views = {
form: `
`,
};
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: ["user_id", "project_id", "stage"],
});
// open dialog to create a task
expect(".modal").toHaveCount(0);
await hoverGridCell("10 December 2018", "In Progress");
await clickCell("10 December 2018", "In Progress");
// check that the dialog is opened with prefilled fields
expect(".modal").toHaveCount(1);
expect(".modal-title").toHaveText("Create");
await contains(".o_field_widget[name=name] input").edit("Task 8");
expect(".o_field_widget[name=start] input").toHaveValue("12/10/2018 00:00:00");
expect(".o_field_widget[name=stop] input").toHaveValue("12/11/2018 00:00:00");
expect(".o_field_widget[name=project_id] input").toHaveValue("Project 1");
expect(".o_field_widget[name=user_id] input").toHaveValue("User 1");
expect(".o_field_widget[name=stage] select").toHaveValue('"in_progress"');
// create the task
await contains(".o_form_button_save").click();
expect(".modal").toHaveCount(0);
// open dialog to view a task
await editPill("Task 8");
expect(".modal").toHaveCount(1);
expect(".modal-title").toHaveText("Open");
expect(".o_field_widget[name=name] input").toHaveValue("Task 8");
expect(".o_field_widget[name=start] input").toHaveValue("12/10/2018 00:00:00");
expect(".o_field_widget[name=stop] input").toHaveValue("12/11/2018 00:00:00");
expect(".o_field_widget[name=project_id] input").toHaveValue("Project 1");
expect(".o_field_widget[name=user_id] input").toHaveValue("User 1");
expect(".o_field_widget[name=stage] select").toHaveValue('"in_progress"');
});
test("open a dialog to create a task when grouped by many2many field", async () => {
Tasks._fields.user_ids = fields.Many2many({
string: "Assignees",
relation: "res.users",
});
Tasks._records[0].user_ids = [1, 2];
Tasks._views = {
form: `
`,
};
await mountGanttView({
resModel: "tasks",
arch: ``,
groupBy: ["user_ids", "project_id"],
});
// Check grouped rows
expect(getGridContent().rows).toEqual([
{
title: "Undefined Assignees",
isGroup: true,
pills: [
{ title: "1", colSpan: "17 (1/2) December 2018 -> 19 December 2018" },
{ title: "2", colSpan: "20 December 2018 -> 20 (1/2) December 2018" },
{ title: "2", colSpan: "20 (1/2) December 2018 -> 20 December 2018" },
{ title: "1", colSpan: "21 December 2018 -> 22 (1/2) December 2018" },
{ title: "1", colSpan: "27 December 2018 -> 03 (1/2) January 2019" },
],
},
{
title: "Project 1",
pills: [
{
level: 0,
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
title: "Task 2",
},
{
level: 1,
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
title: "Task 4",
},
{
level: 0,
colSpan: "27 December 2018 -> 03 (1/2) January 2019",
title: "Task 3",
},
],
},
{
title: "Project 2",
pills: [
{
level: 0,
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
title: "Task 7",
},
],
},
{
title: "User 1",
isGroup: true,
pills: [{ title: "1", colSpan: "Out of bounds (1) -> 31 December 2018" }],
},
{
title: "Project 1",
pills: [
{ level: 0, colSpan: "Out of bounds (1) -> 31 December 2018", title: "Task 1" },
],
},
{
title: "User 2",
isGroup: true,
pills: [{ title: "1", colSpan: "Out of bounds (1) -> 31 December 2018" }],
},
{
title: "Project 1",
pills: [
{ level: 0, colSpan: "Out of bounds (1) -> 31 December 2018", title: "Task 1" },
],
},
]);
// open dialog to create a task with two many2many values
await hoverGridCell("10 December 2018", "Project 1", { num: 2 });
await clickCell("10 December 2018", "Project 1", { num: 2 });
await contains(".o_field_widget[name=name] input").edit("NEW TASK 0");
await contains(".o_field_widget[name=user_ids] input").fill("User 2", { confirm: false });
await runAllTimers();
await contains(".o-autocomplete--dropdown-menu li:first-child a").click();
await contains(".o_form_button_save").click();
expect(".modal").toHaveCount(0);
const [, , , , fifthRow, , seventhRow] = getGridContent().rows;
expect(fifthRow).toEqual({
title: "Project 1",
pills: [
{ level: 0, colSpan: "Out of bounds (1) -> 31 December 2018", title: "Task 1" },
{ level: 1, colSpan: "10 December 2018 -> 10 December 2018", title: "NEW TASK 0" },
],
});
expect(seventhRow).toEqual({
title: "Project 1",
pills: [
{ level: 0, colSpan: "Out of bounds (1) -> 31 December 2018", title: "Task 1" },
{ level: 1, colSpan: "10 December 2018 -> 10 December 2018", title: "NEW TASK 0" },
],
});
// open dialog to create a task with no many2many values
await hoverGridCell("24 December 2018", "Project 2");
await clickCell("24 December 2018", "Project 2");
await contains(".o_field_widget[name=name] input").edit("NEW TASK 1");
await contains(".o_form_button_save").click();
expect(".modal").toHaveCount(0);
const [, , thirdRow] = getGridContent().rows;
expect(thirdRow).toEqual({
title: "Project 2",
pills: [
{
level: 0,
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
title: "Task 7",
},
{ level: 0, colSpan: "24 December 2018 -> 24 December 2018", title: "NEW TASK 1" },
],
});
});
test("open a dialog to create a task, does not have a delete button", async () => {
Tasks._views = {
form: ``,
};
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: [],
});
await hoverGridCell("10 December 2018");
await clickCell("10 December 2018");
expect(".modal").toHaveCount(1);
expect(".modal .o_btn_remove").toHaveCount(0);
});
test("open a dialog to edit a task, has a delete buttton", async () => {
Tasks._views = {
form: ``,
};
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: [],
});
await editPill("Task 1");
expect(".modal").toHaveCount(1);
expect(".modal .o_form_button_remove").toHaveCount(1);
});
test("clicking on delete button in edit dialog triggers a confirmation dialog, clicking discard does not call unlink on the model", async () => {
Tasks._views = {
form: ``,
};
onRpc(({ method }) => {
if (method === "unlink") {
expect.step(method);
}
});
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: [],
});
expect(".o_dialog").toHaveCount(0);
await editPill("Task 1");
expect(".o_dialog").toHaveCount(1);
// trigger the delete button
await contains(".o_dialog .o_form_button_remove").click();
expect(".o_dialog").toHaveCount(2);
const button = queryOne(".o_dialog:not(.o_inactive_modal) footer .btn-secondary");
expect(button).toHaveText("Cancel");
await contains(button).click();
expect(".o_dialog").toHaveCount(1);
expect.verifySteps([]);
});
test("clicking on delete button in edit dialog triggers a confirmation dialog, clicking ok calls unlink on the model", async () => {
Tasks._views = {
form: ``,
};
onRpc("unlink", () => {
expect.step("unlink");
});
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: [],
});
expect(".o_dialog").toHaveCount(0);
await editPill("Task 1");
expect(".o_dialog").toHaveCount(1);
// trigger the delete button
await contains(".o_dialog .o_form_button_remove").click();
expect(".o_dialog").toHaveCount(2);
const button = queryOne(".o_dialog:not(.o_inactive_modal) footer .btn-primary");
expect(button).toHaveText("Ok");
await contains(button).click();
expect(".o_dialog").toHaveCount(0);
expect.verifySteps(["unlink"]);
// Check that the pill has disappeared
await expect(editPill("Task 1")).rejects.toThrow();
});
test("create dialog with timezone", async () => {
defineParams({
lang_parameters: {
time_format: "%H:%M:%S",
},
});
expect.assertions(3);
Tasks._views = {
form: ``,
};
onRpc(({ method, args }) => {
if (method === "web_save") {
expect(args[1]).toEqual({
start: "2018-12-09 23:00:00",
stop: "2018-12-10 23:00:00",
});
}
});
await mountGanttView({
resModel: "tasks",
arch: '',
});
await hoverGridCell("10 December 2018");
await clickCell("10 December 2018");
expect(".o_field_widget[name=start] input").toHaveValue("12/10/2018 00:00:00");
expect(".o_field_widget[name=stop] input").toHaveValue("12/11/2018 00:00:00");
await contains(".o_form_button_save").click();
});
test("open a dialog to plan a task", async () => {
Tasks._views = {
list: '
',
search: '',
};
Tasks._records.push(
{ id: 41, name: "Task 41" },
{ id: 42, name: "Task 42", stop: "2018-12-31 18:29:59" },
{ id: 43, name: "Task 43", start: "2018-11-30 18:30:00" }
);
onRpc(({ method, args, model }) => {
if (method === "write") {
expect.step(model);
expect(args[0]).toEqual([41, 42], { message: "should write on the selected ids" });
expect(args[1]).toEqual({
start: "2018-12-09 23:00:00",
stop: "2018-12-10 23:00:00",
});
}
});
onRpc("has_group", () => true);
await mountGanttView({
resModel: "tasks",
arch: '',
});
// click on the plan button
await hoverGridCell("10 December 2018");
await clickCell("10 December 2018");
expect(".modal .o_list_view").toHaveCount(1);
expect(queryAllTexts(".modal .o_list_view .o_data_cell")).toEqual([
"Task 41",
"Task 42",
"Task 43",
]);
// Select the first two tasks
await contains(".modal .o_list_view tbody tr:nth-child(1) input").click();
await contains(".modal .o_list_view tbody tr:nth-child(2) input").click();
await contains(".modal footer .o_select_button").click();
expect.verifySteps(["tasks"]);
});
test("open a dialog to plan a task (multi-level)", async () => {
Tasks._views = {
list: '
',
search: '',
};
Tasks._records.push({ id: 41, name: "Task 41" });
onRpc(({ args, method, model }) => {
if (method === "write") {
expect.step(model);
expect(args[0]).toEqual([41], { message: "should write on the selected id" });
expect(args[1]).toEqual(
{
project_id: 1,
stage: "todo",
start: "2018-12-09 23:00:00",
stop: "2018-12-10 23:00:00",
user_id: 1,
},
{ message: "should write on all the correct fields" }
);
}
});
onRpc("has_group", () => true);
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: ["user_id", "project_id", "stage"],
});
// click on the plan button
await hoverGridCell("10 December 2018", "To Do");
await clickCell("10 December 2018", "To Do");
expect(".modal .o_list_view").toHaveCount(1);
expect(".modal .o_list_view .o_data_cell").toHaveText("Task 41");
// Select the first task
await contains(".modal .o_list_view tbody tr:nth-child(1) input").click();
await animationFrame();
await contains(".modal-footer .o_select_button").click();
expect.verifySteps(["tasks"]);
});
test("expand/collapse rows", async () => {
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: ["user_id", "project_id", "stage"],
});
expect(getGridContent().rows.map((r) => omit(r, "pills"))).toEqual([
{ title: "User 1", isGroup: true },
{ title: "Project 1", isGroup: true },
{ title: "To Do" },
{ title: "In Progress" },
{ title: "Project 2", isGroup: true },
{ title: "Done" },
{ title: "User 2", isGroup: true },
{ title: "Project 1", isGroup: true },
{ title: "Done" },
{ title: "Cancelled" },
{ title: "Project 2", isGroup: true },
{ title: "Cancelled" },
]);
// collapse all groups
await contains(SELECTORS.collapseButton).click();
expect(getGridContent().rows.map((r) => omit(r, "pills"))).toEqual([
{ title: "User 1", isGroup: true },
{ title: "User 2", isGroup: true },
]);
// expand all groups
await contains(SELECTORS.expandButton).click();
expect(getGridContent().rows.map((r) => omit(r, "pills"))).toEqual([
{ title: "User 1", isGroup: true },
{ title: "Project 1", isGroup: true },
{ title: "To Do" },
{ title: "In Progress" },
{ title: "Project 2", isGroup: true },
{ title: "Done" },
{ title: "User 2", isGroup: true },
{ title: "Project 1", isGroup: true },
{ title: "Done" },
{ title: "Cancelled" },
{ title: "Project 2", isGroup: true },
{ title: "Cancelled" },
]);
// collapse the first group
await contains(`${SELECTORS.rowHeader}${SELECTORS.group}:nth-child(1)`).click();
expect(getGridContent().rows.map((r) => omit(r, "pills"))).toEqual([
{ title: "User 1", isGroup: true },
{ title: "User 2", isGroup: true },
{ title: "Project 1", isGroup: true },
{ title: "Done" },
{ title: "Cancelled" },
{ title: "Project 2", isGroup: true },
{ title: "Cancelled" },
]);
});
test("collapsed rows remain collapsed at reload", async () => {
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: ["user_id", "project_id", "stage"],
});
expect(getGridContent().rows.map((r) => omit(r, "pills"))).toEqual([
{ title: "User 1", isGroup: true },
{ title: "Project 1", isGroup: true },
{ title: "To Do" },
{ title: "In Progress" },
{ title: "Project 2", isGroup: true },
{ title: "Done" },
{ title: "User 2", isGroup: true },
{ title: "Project 1", isGroup: true },
{ title: "Done" },
{ title: "Cancelled" },
{ title: "Project 2", isGroup: true },
{ title: "Cancelled" },
]);
// collapse the first group
await contains(`${SELECTORS.rowHeader}${SELECTORS.group}:nth-child(1)`).click();
expect(getGridContent().rows.map((r) => omit(r, "pills"))).toEqual([
{ title: "User 1", isGroup: true },
{ title: "User 2", isGroup: true },
{ title: "Project 1", isGroup: true },
{ title: "Done" },
{ title: "Cancelled" },
{ title: "Project 2", isGroup: true },
{ title: "Cancelled" },
]);
// reload
await validateSearch();
expect(getGridContent().rows.map((r) => omit(r, "pills"))).toEqual([
{ title: "User 1", isGroup: true },
{ title: "User 2", isGroup: true },
{ title: "Project 1", isGroup: true },
{ title: "Done" },
{ title: "Cancelled" },
{ title: "Project 2", isGroup: true },
{ title: "Cancelled" },
]);
});
test("resize a pill", async () => {
expect.assertions(10);
onRpc("write", ({ args }) => {
// initial dates -- start: '2018-11-30 18:30:00', stop: '2018-12-31 18:29:59'
expect.step(args);
});
await mountGanttView({
resModel: "tasks",
arch: '',
domain: [["id", "=", 1]],
context: { initialDate: "2018-12-25" },
});
expect(SELECTORS.pill).toHaveCount(1, { message: "there should be one pill (Task 1)" });
expect(SELECTORS.resizable).toHaveCount(1);
expect(SELECTORS.resizeHandle).toHaveCount(0);
await contains(getPillWrapper("Task 1")).hover();
// No start resizer because the start date overflows
expect(SELECTORS.resizeStartHandle).toHaveCount(0);
expect(SELECTORS.resizeEndHandle).toHaveCount(1);
// resize to one cell smaller at end (-1 day)
await resizePill(getPillWrapper("Task 1"), "end", -1);
await selectGanttRange({ startDate: "2018-11-10", stopDate: "2018-11-30" });
expect(".o_gantt_pill").toHaveCount(1, { message: "there should still be one pill (Task 1)" });
expect(SELECTORS.resizable).toHaveCount(1);
await contains(getPillWrapper("Task 1")).hover();
// No end resizer because the end date overflows
expect(SELECTORS.resizeStartHandle).toHaveCount(1);
expect(SELECTORS.resizeEndHandle).toHaveCount(0);
// resize to one cell smaller at start (-1 day)
await resizePill(getPillWrapper("Task 1"), "start", -1);
expect.verifySteps([
[[1], { stop: "2018-12-30 18:29:59" }],
[[1], { start: "2018-11-29 18:30:00" }],
]);
});
test("resize pill in year mode", async () => {
expect.assertions(2);
onRpc(({ method }) => {
if (method === "write") {
throw new Error("Should not call write");
}
});
await mountGanttView({
resModel: "tasks",
arch: '',
});
const initialPillWidth = getPillWrapper("Task 5").getBoundingClientRect().width;
expect(getPillWrapper("Task 5")).toHaveClass(CLASSES.resizable);
// Resize way over the limit
await resizePill(getPillWrapper("Task 5"), "end", 0, { x: 200 });
expect(initialPillWidth).toBe(getPillWrapper("Task 5").getBoundingClientRect().width, {
message: "the pill should have the same width as before the resize",
});
});
test("resize a pill (2)", async () => {
expect.assertions(5);
onRpc("write", ({ args }) => expect.step(args));
await mountGanttView({
resModel: "tasks",
arch: '',
domain: [["id", "=", 2]],
});
expect(SELECTORS.pill).toHaveCount(1);
await contains(getPillWrapper("Task 2")).hover();
expect(getPillWrapper("Task 2")).toHaveClass(CLASSES.resizable);
expect(SELECTORS.resizeHandle).toHaveCount(2);
// resize to one cell larger
await resizePill(getPillWrapper("Task 2"), "end", +1);
expect(".modal").toHaveCount(0);
expect.verifySteps([[[2], { stop: "2018-12-23 06:29:59" }]]);
});
test("resize a pill: invalid result", async () => {
Tasks._records[1].start = "2018-12-17 10:30:00";
Tasks._records[1].stop = "2018-12-17 15:30:00";
onRpc("write", () => {
throw new Error("Pill should not be resized");
});
await mountGanttView({
resModel: "tasks",
arch: '',
domain: [["id", "=", 2]],
});
expect(SELECTORS.pill).toHaveCount(1);
await contains(getPillWrapper("Task 2")).hover();
expect(getPillWrapper("Task 2")).toHaveClass(CLASSES.resizable);
expect(SELECTORS.resizeHandle).toHaveCount(2);
expect(getGridContent().rows).toEqual([
{
pills: [{ title: "Task 2", level: 0, colSpan: "17 December 2018 -> 17 December 2018" }],
},
]);
// shift end date towards start date
await resizePill(getPillWrapper("Task 2"), "end", -1);
expect(".modal").toHaveCount(0);
expect(getGridContent().rows).toEqual([
{
pills: [{ title: "Task 2", level: 0, colSpan: "17 December 2018 -> 17 December 2018" }],
},
]);
expect(".o_notification").toHaveCount(1);
expect(".o_notification .o_notification_body").toHaveText(
"Ending date cannot be before the starting date"
);
await contains(".o_notification_close").click();
// shift start date towards end date
await resizePill(getPillWrapper("Task 2"), "start", +1);
expect(".modal").toHaveCount(0);
expect(getGridContent().rows).toEqual([
{
pills: [{ title: "Task 2", level: 0, colSpan: "17 December 2018 -> 17 December 2018" }],
},
]);
expect(".o_notification").toHaveCount(1);
expect(".o_notification .o_notification_body").toHaveText(
"Starting date cannot be after the ending date"
);
});
test.tags("desktop");
test("resize a pill: quickly enter the neighbour pill when resize start", async () => {
await mountGanttView({
resModel: "tasks",
arch: '',
domain: [["id", "in", [4, 7]]],
});
expect(SELECTORS.pill).toHaveCount(2);
await contains(getPillWrapper("Task 4")).hover();
expect(getPillWrapper("Task 4")).toHaveClass(CLASSES.resizable);
expect(SELECTORS.resizeHandle).toHaveCount(2);
// Here we simulate a resize start on Task 4 and quickly enter Task 7
// The resize handle should not be added to Task 7
await pointerDown(SELECTORS.resizeEndHandle);
await hover(getPillWrapper("Task 7"));
expect(getPillWrapper("Task 4").querySelectorAll(SELECTORS.resizeHandle)).toHaveCount(2);
expect(getPillWrapper("Task 7").querySelectorAll(SELECTORS.resizeHandle)).toHaveCount(0);
});
test("create a task maintains the domain", async () => {
Tasks._views = { form: '' };
await mountGanttView({
resModel: "tasks",
arch: '',
domain: [["user_id", "=", 2]], // I am an important line
});
expect(SELECTORS.pill).toHaveCount(3);
await hoverGridCell("06 December 2018");
await clickCell("06 December 2018");
await contains(".modal [name=name] input").edit("new task");
await contains(".modal .o_form_button_save").click();
expect(SELECTORS.pill).toHaveCount(3);
});
test("pill is updated after failed resized", async () => {
onRpc("get_gantt_data", () => {
expect.step("get_gantt_data");
});
onRpc("write", () => {
expect.step("write");
return true;
});
await mountGanttView({
resModel: "tasks",
arch: '',
domain: [["id", "=", 7]],
});
const initialPillWidth = getPillWrapper("Task 7").getBoundingClientRect().width;
// resize to one cell larger (1 day)
await resizePill(getPillWrapper("Task 7"), "end", +1);
expect(initialPillWidth).toBe(getPillWrapper("Task 7").getBoundingClientRect().width);
expect.verifySteps(["get_gantt_data", "write", "get_gantt_data"]);
});
test("move a pill in the same row", async () => {
expect.assertions(5);
onRpc("write", ({ args }) => {
expect(args[0]).toEqual([7], { message: "should write on the correct record" });
expect(args[1]).toEqual(
{
start: "2018-12-21 12:30:12",
stop: "2018-12-21 18:29:59",
},
{ message: "both start and stop date should be correctly set (+1 day)" }
);
});
await mountGanttView({
resModel: "tasks",
arch: '',
domain: [["id", "=", 7]],
});
expect(getPillWrapper("Task 7")).toHaveClass(CLASSES.draggable);
expect(getGridContent().rows).toEqual([
{
pills: [
{
title: "Task 7",
level: 0,
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
},
],
},
]);
// move a pill in the next cell (+1 day)
const { drop } = await dragPill("Task 7");
await drop({ column: "21 December 2018", part: 2 });
expect(getGridContent().rows).toEqual([
{
pills: [
{
title: "Task 7",
level: 0,
colSpan: "21 (1/2) December 2018 -> 21 December 2018",
},
],
},
]);
});
test("move a pill in the same row (with different timezone)", async () => {
expect.assertions(4);
patchWithCleanup(luxon.Settings, {
defaultZone: luxon.IANAZone.create("Europe/Brussels"),
});
Tasks._records[7].start = `${DST_DATES.winterToSummer.before} 05:00:00`;
Tasks._records[7].stop = `${DST_DATES.winterToSummer.before} 06:30:00`;
onRpc(({ args, method }) => {
if (method === "write") {
expect.step("write");
expect(args).toEqual([
[8],
{
start: `${DST_DATES.winterToSummer.after} 04:00:00`,
stop: `${DST_DATES.winterToSummer.after} 05:30:00`,
},
]);
}
});
await mountGanttView({
resModel: "tasks",
arch: '',
domain: [["id", "=", 8]],
context: {
initialDate: `${DST_DATES.winterToSummer.before} 08:00:00`,
},
});
await contains(".o_content").scroll({ x: 300 });
expect(getGridContent().rows).toEqual([
{
pills: [{ title: "Task 8", level: 0, colSpan: "30 March 2019 -> 30 (1/2) March 2019" }],
},
]);
// +1 day -> move beyond the DST switch
const { drop } = await dragPill("Task 8");
await drop({ column: "31 March 2019", part: 1 });
expect(getGridContent().rows).toEqual([
{
pills: [{ title: "Task 8", level: 0, colSpan: "31 March 2019 -> 31 (1/2) March 2019" }],
},
]);
expect.verifySteps(["write"]);
});
test("move a pill in another row", async () => {
expect.assertions(4);
onRpc("write", ({ args }) => {
expect(args[0]).toEqual([7], { message: "should write on the correct record" });
expect(args[1]).toEqual(
{
project_id: 1,
start: "2018-12-21 12:30:12",
stop: "2018-12-21 18:29:59",
},
{ message: "all modified fields should be correctly set" }
);
});
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: ["project_id"],
domain: [["id", "in", [1, 7]]],
});
expect(getGridContent().rows).toEqual([
{
title: "Project 1",
pills: [
{ title: "Task 1", level: 0, colSpan: "Out of bounds (1) -> 31 December 2018" },
],
},
{
title: "Project 2",
pills: [
{
title: "Task 7",
level: 0,
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
},
],
},
]);
// move a pill (task 7) in the other row and in the the next cell (+1 day)
const { drop } = await dragPill("Task 7");
await drop({ column: "21 December 2018", part: 2 });
expect(getGridContent().rows).toEqual([
{
title: "Project 1",
pills: [
{ title: "Task 1", level: 0, colSpan: "Out of bounds (1) -> 31 December 2018" },
{
title: "Task 7",
level: 1,
colSpan: "21 (1/2) December 2018 -> 21 December 2018",
},
],
},
]);
});
test("copy a pill in another row", async () => {
expect.assertions(6);
onRpc("copy", ({ args, kwargs }) => {
expect(args[0]).toEqual([7], { message: "should copy the correct record" });
expect(kwargs.default).toEqual(
{
start: "2018-12-21 12:30:12",
stop: "2018-12-21 18:29:59",
project_id: 1,
},
{ message: "should use the correct default values when copying" }
);
});
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: ["project_id"],
domain: [["id", "in", [1, 7, 9]]], // 9 will be the newly created record
});
expect(getGridContent().rows).toEqual([
{
title: "Project 1",
pills: [
{ title: "Task 1", level: 0, colSpan: "Out of bounds (1) -> 31 December 2018" },
],
},
{
title: "Project 2",
pills: [
{
title: "Task 7",
level: 0,
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
},
],
},
]);
await keyDown("Control");
// move a pill (task 7) in the other row and in the the next cell (+1 day)
const { drop, moveTo } = await dragPill("Task 7");
await moveTo({ column: "21 December 2018", part: 2 });
expect(SELECTORS.renderer).toHaveClass("o_copying");
await keyUp("Control");
expect(SELECTORS.renderer).toHaveClass("o_grabbing");
await keyDown("Control");
await drop({ column: "21 December 2018", part: 2 });
expect(getGridContent().rows).toEqual([
{
title: "Project 1",
pills: [
{ title: "Task 1", level: 0, colSpan: "Out of bounds (1) -> 31 December 2018" },
{
title: "Task 7 (copy)",
level: 1,
colSpan: "21 (1/2) December 2018 -> 21 December 2018",
},
],
},
{
title: "Project 2",
pills: [
{
title: "Task 7",
level: 0,
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
},
],
},
]);
});
test("move a pill in another row in multi-level grouped", async () => {
onRpc("write", ({ args }) => {
expect(args).toEqual([[7], { project_id: 1 }], {
message: "should only write on user_id on the correct record",
});
});
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: ["user_id", "project_id", "stage"],
domain: [["id", "in", [3, 7]]],
});
expect(`${SELECTORS.pillWrapper}${SELECTORS.draggable}`).toHaveCount(2);
expect(getGridContent().rows).toEqual([
{
title: "User 2",
isGroup: true,
pills: [
{ title: "1", colSpan: "20 (1/2) December 2018 -> 20 December 2018" },
{ title: "1", colSpan: "27 December 2018 -> 03 (1/2) January 2019" },
],
},
{
title: "Project 1",
isGroup: true,
pills: [{ title: "1", colSpan: "27 December 2018 -> 03 (1/2) January 2019" }],
},
{
title: "Cancelled",
pills: [
{ title: "Task 3", level: 0, colSpan: "27 December 2018 -> 03 (1/2) January 2019" },
],
},
{
title: "Project 2",
isGroup: true,
pills: [{ title: "1", colSpan: "20 (1/2) December 2018 -> 20 December 2018" }],
},
{
title: "Cancelled",
pills: [
{
title: "Task 7",
level: 0,
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
},
],
},
]);
// move a pill (task 7) in the top-level group (User 2)
const { drop } = await dragPill("Task 7");
await drop({ row: "Cancelled", column: "20 December 2018", part: 2 });
expect(getGridContent().rows).toEqual([
{
title: "User 2",
isGroup: true,
pills: [
{ title: "1", colSpan: "20 (1/2) December 2018 -> 20 December 2018" },
{ title: "1", colSpan: "27 December 2018 -> 03 (1/2) January 2019" },
],
},
{
title: "Project 1",
isGroup: true,
pills: [
{ title: "1", colSpan: "20 (1/2) December 2018 -> 20 December 2018" },
{ title: "1", colSpan: "27 December 2018 -> 03 (1/2) January 2019" },
],
},
{
title: "Cancelled",
pills: [
{
title: "Task 7",
level: 0,
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
},
{ title: "Task 3", level: 0, colSpan: "27 December 2018 -> 03 (1/2) January 2019" },
],
},
]);
});
test("move a pill in another row in multi-level grouped (many2many case)", async () => {
expect.assertions(5);
Tasks._fields.user_ids = fields.Many2many({ string: "Assignees", relation: "res.users" });
Tasks._records[1].user_ids = [1, 2];
onRpc("write", ({ args }) => {
expect(args[0]).toEqual([2], { message: "should write on the correct record" });
expect(args[1]).toEqual({ user_ids: false }, { message: "should write these changes" });
});
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: ["user_id", "project_id", "user_ids"],
domain: [
["user_id", "=", 2],
["project_id", "=", 1],
],
});
// sanity check
expect(queryAllTexts(`${SELECTORS.pillWrapper}${SELECTORS.draggable}`)).toEqual([
"Task 3",
"Task 2",
"Task 2",
]);
expect(getGridContent().rows).toEqual([
{
title: "User 2",
isGroup: true,
pills: [
{ title: "1", colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018" },
{ title: "1", colSpan: "27 December 2018 -> 03 (1/2) January 2019" },
],
},
{
title: "Project 1",
isGroup: true,
pills: [
{ title: "1", colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018" },
{ title: "1", colSpan: "27 December 2018 -> 03 (1/2) January 2019" },
],
},
{
title: "Undefined Assignees",
pills: [
{ title: "Task 3", level: 0, colSpan: "27 December 2018 -> 03 (1/2) January 2019" },
],
},
{
title: "User 1",
pills: [
{
title: "Task 2",
level: 0,
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
},
],
},
{
title: "User 2",
pills: [
{
title: "Task 2",
level: 0,
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
},
],
},
]);
// move a pill (first task 2) in "Undefined Assignees"
const { drop } = await dragPill("Task 2", { nth: 1 });
await drop({ row: "Undefined Assignees", column: "17 December 2018", part: 2 });
expect(getGridContent().rows).toEqual([
{
title: "User 2",
isGroup: true,
pills: [
{ title: "1", colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018" },
{ title: "1", colSpan: "27 December 2018 -> 03 (1/2) January 2019" },
],
},
{
title: "Project 1",
isGroup: true,
pills: [
{ title: "1", colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018" },
{ title: "1", colSpan: "27 December 2018 -> 03 (1/2) January 2019" },
],
},
{
title: "Undefined Assignees",
pills: [
{
title: "Task 2",
level: 0,
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
},
{ title: "Task 3", level: 0, colSpan: "27 December 2018 -> 03 (1/2) January 2019" },
],
},
]);
});
test("grey pills should not be resizable nor draggable", async () => {
expect.assertions(4);
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: ["user_id", "project_id"],
domain: [["id", "=", 7]],
});
const groupPill = queryOne(`${SELECTORS.pillWrapper}.o_gantt_group_pill`);
expect(groupPill).not.toHaveClass(CLASSES.resizable);
expect(groupPill).not.toHaveClass(CLASSES.draggable);
const rowPill = queryOne(`${SELECTORS.pillWrapper}:not(.o_gantt_group_pill)`);
expect(rowPill).toHaveClass(CLASSES.resizable);
expect(rowPill).toHaveClass(CLASSES.draggable);
});
test("should not be draggable when disable_drag_drop is set", async () => {
expect.assertions(1);
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: ["user_id", "project_id"],
domain: [["id", "=", 7]],
});
expect(SELECTORS.draggable).toHaveCount(0);
});
test("view reload when scale changes", async () => {
let reloadCount = 0;
onRpc("get_gantt_data", () => {
reloadCount++;
});
await mountGanttView({
resModel: "tasks",
arch: '',
});
expect(reloadCount).toBe(1, { message: "view should have loaded" });
await setScale(4);
await ganttControlsChanges();
expect(reloadCount).toBe(2, {
message: "view should have reloaded when switching scale to week",
});
await setScale(2);
await ganttControlsChanges();
expect(reloadCount).toBe(3, {
message: "view should have reloaded when switching scale to month",
});
await setScale(0);
await ganttControlsChanges();
expect(reloadCount).toBe(4, {
message: "view should have reloaded when switching scale to year",
});
});
test("view reload when period changes", async () => {
let reloadCount = 0;
onRpc("get_gantt_data", () => {
reloadCount++;
});
await mountGanttView({
resModel: "tasks",
arch: '',
});
expect(reloadCount).toBe(1, { message: "view should have loaded" });
await selectGanttRange({ startDate: "2019-01-01", stopDate: "2019-02-28" });
expect(reloadCount).toBe(2);
await selectGanttRange({ startDate: "2019-01-01", stopDate: "2019-01-31" });
expect(reloadCount).toBe(3);
});
test("unavailabilities should not be reloaded when period changes if display_unavailability is not set", async () => {
onRpc("get_gantt_data", ({ kwargs }) => {
expect.step("get_gantt_data");
expect(kwargs.unavailability_fields).toEqual([]);
});
await mountGanttView({
resModel: "tasks",
arch: '',
});
expect.verifySteps(["get_gantt_data"]);
await selectGanttRange({ startDate: "2019-01-01", stopDate: "2019-02-28" });
expect.verifySteps(["get_gantt_data"]);
await selectGanttRange({ startDate: "2019-01-01", stopDate: "2019-01-31" });
expect.verifySteps(["get_gantt_data"]);
});
test("close tooltip when drag pill", async () => {
Tasks._records[1].start = "2018-12-16 03:00:00";
Tasks._views = { form: "" };
await mountGanttView({
resModel: "tasks",
arch: '',
});
expect(getGridContent().rows).toEqual([
{
pills: [
{
title: "Task 1",
colSpan: "16 W51 2018 -> Out of bounds (33) ",
level: 0,
},
{
title: "Task 2",
colSpan: "16 W51 2018 -> 22 (1/2) W51 2018",
level: 1,
},
{
title: "Task 4",
colSpan: "20 W51 2018 -> 20 (1/2) W51 2018",
level: 2,
},
{
title: "Task 7",
colSpan: "20 (1/2) W51 2018 -> 20 W51 2018",
level: 2,
},
],
},
]);
// open popover
await contains(getPill("Task 4")).click();
expect(".o_popover").toHaveCount(1);
// enable the drag feature and move the pill
const { moveTo } = await dragPill("Task 4");
expect(".o_popover").toHaveCount(1, {
message: "popover should is still opened as the pill did not move yet",
});
await moveTo({ pill: "Task 2" });
// check popover
expect(".o_popover").toHaveCount(0, {
message: "popover should have been closed",
});
});
test("drag&drop on other pill in grouped view", async () => {
Tasks._records[0].start = "2018-12-16 05:00:00";
Tasks._records[0].stop = "2018-12-16 07:00:00";
Tasks._records[1].stop = "2018-12-17 13:00:00";
Tasks._views = { form: `` };
const def = new Deferred();
onRpc("write", () => def);
await mountGanttView({
resModel: "tasks",
arch: '',
groupBy: ["project_id"],
});
expect(getGridContent().rows).toEqual([
{
title: "Project 1",
pills: [
{ title: "Task 1", level: 0, colSpan: "16 W51 2018 -> 16 (1/2) W51 2018" },
{ title: "Task 2", level: 0, colSpan: "17 (1/2) W51 2018 -> 17 W51 2018" },
{ title: "Task 4", level: 0, colSpan: "20 W51 2018 -> 20 (1/2) W51 2018" },
],
},
{
title: "Project 2",
pills: [{ title: "Task 7", level: 0, colSpan: "20 (1/2) W51 2018 -> 20 W51 2018" }],
},
]);
await contains(getPill("Task 2")).click();
expect(".o_popover").toHaveCount(1);
const { drop } = await dragPill("Task 2");
await drop({ pill: "Task 1" });
await contains(document.body).click(); // To simulate the full 'pointerup' sequence
def.resolve();
await animationFrame();
expect(".popover").toHaveCount(0);
expect(getGridContent().rows).toEqual([
{
title: "Project 1",
pills: [
{ title: "Task 2", level: 0, colSpan: "16 W51 2018 -> 16 (1/2) W51 2018" },
{ title: "Task 1", level: 1, colSpan: "16 W51 2018 -> 16 (1/2) W51 2018" },
{ title: "Task 4", level: 0, colSpan: "20 W51 2018 -> 20 (1/2) W51 2018" },
],
},
{
title: "Project 2",
pills: [{ title: "Task 7", level: 0, colSpan: "20 (1/2) W51 2018 -> 20 W51 2018" }],
},
]);
});
test("display mode button", async () => {
onRpc("get_gantt_data", () => {
expect.step("get_gantt_data");
});
await mountGanttView({
resModel: "tasks",
arch: ``,
});
expect.verifySteps(["get_gantt_data"]);
expect(SELECTORS.dense).toHaveCount(1);
expect(SELECTORS.sparse).toHaveCount(0);
const rowsInSparseMode = [
{
title: "Task 5",
},
{
title: "Task 1",
pills: [
{ title: "Task 1", level: 0, colSpan: "Out of bounds (1) -> 31 December 2018" },
],
},
{
title: "Task 2",
pills: [
{
title: "Task 2",
level: 0,
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
},
],
},
{
title: "Task 4",
pills: [
{
title: "Task 4",
level: 0,
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
},
],
},
{
title: "Task 7",
pills: [
{
title: "Task 7",
level: 0,
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
},
],
},
{
title: "Task 3",
pills: [
{ title: "Task 3", level: 0, colSpan: "27 December 2018 -> 03 (1/2) January 2019" },
],
},
];
expect(getGridContent().rows).toEqual(rowsInSparseMode);
await click(SELECTORS.dense);
await animationFrame();
expect(SELECTORS.dense).toHaveCount(0);
expect(SELECTORS.sparse).toHaveCount(1);
expect(getGridContent().rows).toEqual([
{
pills: [
{
title: "Task 1",
colSpan: "Out of bounds (1) -> 31 December 2018",
level: 1,
},
{
title: "Task 2",
colSpan: "17 (1/2) December 2018 -> 22 (1/2) December 2018",
level: 0,
},
{
title: "Task 4",
colSpan: "20 December 2018 -> 20 (1/2) December 2018",
level: 2,
},
{
title: "Task 7",
colSpan: "20 (1/2) December 2018 -> 20 December 2018",
level: 2,
},
{
title: "Task 3",
colSpan: "27 December 2018 -> 03 (1/2) January 2019",
level: 0,
},
],
},
]);
await click(SELECTORS.sparse);
await animationFrame();
expect(SELECTORS.dense).toHaveCount(1);
expect(SELECTORS.sparse).toHaveCount(0);
expect(getGridContent().rows).toEqual(rowsInSparseMode);
expect.verifySteps([]);
});
test("unavailabilities fetched with right parameters", async () => {
onRpc("get_gantt_data", ({ kwargs }) => {
expect.step(Object.values(pick(kwargs, "start_date", "stop_date", "scale")));
});
await mountGanttView({
resModel: "tasks",
arch: ``,
});
expect.verifySteps([["2018-12-19 23:00:00", "2018-12-22 23:00:00", "day"]]);
await setScale(4);
await ganttControlsChanges();
expect.verifySteps([["2018-12-19 23:00:00", "2018-12-22 23:00:00", "week"]]);
await setScale(2);
await ganttControlsChanges();
expect.verifySteps([["2018-12-19 23:00:00", "2018-12-22 23:00:00", "month"]]);
await setScale(0);
await ganttControlsChanges();
expect.verifySteps([["2018-11-30 23:00:00", "2018-12-31 23:00:00", "year"]]);
await selectGanttRange({ startDate: "2018-12-31", stopDate: "2019-06-15" });
expect.verifySteps([["2018-11-30 23:00:00", "2019-06-30 23:00:00", "year"]]);
});
test("progress bars fetched with the right start/stop dates", async () => {
onRpc("get_gantt_data", async ({ kwargs, parent }) => {
const result = await parent();
expect.step([kwargs.start_date, kwargs.stop_date]);
result.progress_bars.user_id = {
1: { value: 50, max_value: 100 },
2: { value: 25, max_value: 200 },
};
return result;
});
await mountGanttView({
resModel: "tasks",
arch: `
`,
});
expect.verifySteps([["2018-12-19 23:00:00", "2018-12-22 23:00:00"]]);
await setScale(4);
await ganttControlsChanges();
expect.verifySteps([["2018-12-19 23:00:00", "2018-12-22 23:00:00"]]);
await setScale(2);
await ganttControlsChanges();
expect.verifySteps([["2018-12-19 23:00:00", "2018-12-22 23:00:00"]]);
await setScale(0);
await ganttControlsChanges();
expect.verifySteps([["2018-11-30 23:00:00", "2018-12-31 23:00:00"]]);
await selectGanttRange({ startDate: "2018-12-31", stopDate: "2019-06-15" });
expect.verifySteps([["2018-11-30 23:00:00", "2019-06-30 23:00:00"]]);
});
test("focus today with scroll (in range & outside)", async () => {
onRpc("get_gantt_data", () => {
expect.step("get_gantt_data");
});
await mountGanttView({
resModel: "tasks",
arch: '',
});
expect.verifySteps(["get_gantt_data"]);
expect(".o_gantt_cell.o_gantt_today").toBeVisible();
expect(queryOne(".o_gantt_cell.o_gantt_today")).toBe(getCell("20 December 2018"));
let { columnHeaders } = getGridContent();
expect(columnHeaders).toHaveLength(34);
expect(columnHeaders[0].title).toBe("03"); // December
expect(columnHeaders.at(-1).title).toBe("05"); // January
await scroll(".o_content", { left: 800 });
await animationFrame();
expect(".o_gantt_cell.o_gantt_today").toBeVisible();
columnHeaders = getGridContent().columnHeaders;
expect(columnHeaders).toHaveLength(34);
expect(columnHeaders[0].title).toBe("14"); // December
expect(columnHeaders.at(-1).title).toBe("16"); // January
await focusToday();
await ganttControlsChanges();
expect(".o_gantt_cell.o_gantt_today").toBeVisible();
columnHeaders = getGridContent().columnHeaders;
expect(columnHeaders).toHaveLength(34);
expect(columnHeaders[0].title).toBe("03"); // December
expect(columnHeaders.at(-1).title).toBe("05"); // January
await scroll(".o_content", { left: 2000 });
await animationFrame();
expect(".o_gantt_cell.o_gantt_today").not.toBeVisible();
columnHeaders = getGridContent().columnHeaders;
expect(columnHeaders).toHaveLength(34);
expect(columnHeaders[0].title).toBe("07"); // January
expect(columnHeaders.at(-1).title).toBe("09"); // February
await focusToday();
await ganttControlsChanges();
expect(".o_gantt_cell.o_gantt_today").toBeVisible();
columnHeaders = getGridContent().columnHeaders;
expect(columnHeaders).toHaveLength(34);
expect(columnHeaders[0].title).toBe("03"); // December
expect(columnHeaders.at(-1).title).toBe("05"); // January
});
test("focus today with range change (in range & outside)", async () => {
onRpc("get_gantt_data", () => {
expect.step("get_gantt_data");
});
await mountGanttView({
resModel: "tasks",
arch: '',
});
expect.verifySteps(["get_gantt_data"]);
expect(".o_gantt_cell.o_gantt_today").toBeVisible();
expect(queryOne(".o_gantt_cell.o_gantt_today")).toBe(getCell("20 December 2018"));
let gridContent = getGridContent();
expect(gridContent.range).toBe("From: 12/01/2018 to: 02/28/2019");
expect(gridContent.columnHeaders).toHaveLength(34);
expect(gridContent.columnHeaders[0].title).toBe("03"); // December
expect(gridContent.columnHeaders.at(-1).title).toBe("05"); // January
await selectGanttRange({ startDate: "2018-11-15", stopDate: "2019-02-15" });
expect.verifySteps(["get_gantt_data"]);
expect(".o_gantt_cell.o_gantt_today").toBeVisible();
expect(queryOne(".o_gantt_cell.o_gantt_today")).toBe(getCell("20 December 2018"));
gridContent = getGridContent();
expect(gridContent.range).toBe("From: 11/15/2018 to: 02/15/2019");
expect(gridContent.columnHeaders).toHaveLength(34);
expect(gridContent.columnHeaders[0].title).toBe("03"); // December
expect(gridContent.columnHeaders.at(-1).title).toBe("05"); // January
await focusToday();
await ganttControlsChanges();
// nothing happens
await selectGanttRange({ startDate: "2019-01-01", stopDate: "2019-02-28" });
expect(getGridContent().range).toBe("From: 01/01/2019 to: 02/28/2019");
expect.verifySteps(["get_gantt_data"]);
expect(".o_gantt_cell.o_gantt_today").not.toBeVisible();
await focusToday();
await ganttControlsChanges();
expect.verifySteps(["get_gantt_data"]);
expect(".o_gantt_cell.o_gantt_today").toBeVisible();
expect(getGridContent().range).toBe("From: 11/21/2018 to: 01/17/2019");
});
test("set scale: should keep focused date", async () => {
await mountGanttView({
resModel: "tasks",
arch: '',
});
// set focus around 23 January 2019
await scroll(".o_content", { left: 2000 });
await animationFrame();
expect(getCell("23 January 2019")).toBeVisible();
// day view
await setScale(5);
await ganttControlsChanges();
expect(getCell("12pm 23 January 2019")).toBeVisible();
// week view
await setScale(4);
await ganttControlsChanges();
expect(getCell("23 W4 2019")).toBeVisible();
// year view
await setScale(0);
await ganttControlsChanges();
expect(getCell("January 2019")).toBeVisible();
});
test("set start/stop date: should keep focused date", async () => {
await mountGanttView({
resModel: "tasks",
arch: '',
});
// set focus around 23 January 2019
await scroll(".o_content", { left: 2000 });
await animationFrame();
await selectGanttRange({ startDate: "2018-12-01", stopDate: "2019-05-28" });
expect(getCell("23 January 2019")).toBeVisible();
await selectGanttRange({ startDate: "2019-01-22", stopDate: "2019-05-28" });
expect(getCell("23 January 2019")).toBeVisible();
await selectGanttRange({ startDate: "2018-12-01", stopDate: "2019-01-22" });
expect(getCell("22 January 2019")).toBeVisible();
});
test("focus first pill on row header click", async () => {
Tasks._records = [
{
id: 1,
name: "Task 1",
start: "2018-11-30 23:00:00",
stop: "2018-12-01 23:00:00",
user_id: 1,
},
{
id: 2,
name: "Task 2",
start: "2019-02-27 23:00:00",
stop: "2019-02-28 23:00:00",
user_id: 1,
},
];
await mountGanttView({
resModel: "tasks",
arch: '',
});
// set focus around 23 January 2019
await scroll(".o_content", { left: 2000 });
await animationFrame();
expect(SELECTORS.pill).toHaveCount(0);
await click(SELECTORS.rowHeader);
await animationFrame();
expect(SELECTORS.pill).toHaveCount(1);
expect(SELECTORS.pill).toHaveText("Task 1");
});
test("Select a range via the range menu", async () => {
await mountGanttView({
resModel: "tasks",
arch: '',
});
let content = getGridContent();
expect(content.range).toBe("From: 12/01/2018 to: 02/28/2019");
await selectRange("Today");
content = getGridContent();
expect(content.range).toBe("12/20/2018");
await selectRange("This week");
content = getGridContent();
expect(content.range).toBe("W51 2018");
await selectRange("This month");
content = getGridContent();
expect(content.range).toBe("December 2018");
await selectRange("This quarter");
content = getGridContent();
expect(content.range).toBe("Q4 2018");
await selectRange("This year");
content = getGridContent();
expect(content.range).toBe("2018");
});
test("Select range with left/rigth arrows", async () => {
onRpc("get_gantt_data", ({ kwargs }) => {
expect.step(kwargs.domain);
});
await mountGanttView({
resModel: "tasks",
arch: '',
});
expect.verifySteps([
["&", ["start", "<", "2018-12-31 23:00:00"], ["stop", ">", "2018-11-30 23:00:00"]],
]);
let content = getGridContent();
expect(content.range).toBe("December 2018");
for (let i = 0; i < 3; i++) {
await click(SELECTORS.nextButton);
}
await click(SELECTORS.previousButton);
await ganttControlsChanges();
expect.verifySteps([
["&", ["start", "<", "2019-02-28 23:00:00"], ["stop", ">", "2019-01-31 23:00:00"]],
]);
content = getGridContent();
expect(content.range).toBe("February 2019");
await press("alt+n");
await ganttControlsChanges();
expect.verifySteps([
["&", ["start", "<", "2019-03-31 23:00:00"], ["stop", ">", "2019-02-28 23:00:00"]],
]);
content = getGridContent();
expect(content.range).toBe("March 2019");
});
test("Select scale with +/- buttons", async () => {
onRpc("get_gantt_data", () => {
expect.step("get_gantt_data");
});
await mountGanttView({
resModel: "tasks",
arch: '',
});
expect(getActiveScale()).toBe(5);
expect(SELECTORS.minusButton).toBeEnabled();
expect(SELECTORS.plusButton).not.toBeEnabled();
expect.verifySteps(["get_gantt_data"]);
for (let i = 0; i < 9; i++) {
await click(SELECTORS.minusButton);
}
await ganttControlsChanges();
expect(getActiveScale()).toBe(0);
expect(SELECTORS.minusButton).not.toBeEnabled();
expect(SELECTORS.plusButton).toBeEnabled();
expect.verifySteps(["get_gantt_data"]);
await click(SELECTORS.plusButton);
await click(SELECTORS.plusButton);
await ganttControlsChanges();
expect(getActiveScale()).toBe(2);
expect(SELECTORS.minusButton).toBeEnabled();
expect(SELECTORS.plusButton).toBeEnabled();
expect.verifySteps(["get_gantt_data"]);
await press("alt+i");
await ganttControlsChanges();
expect(getActiveScale()).toBe(3);
expect(SELECTORS.minusButton).toBeEnabled();
expect(SELECTORS.plusButton).toBeEnabled();
expect.verifySteps(["get_gantt_data"]);
});
test("make tooltip visible for a long pill", async () => {
mockDate("2024-03-01 00:00:00");
Tasks._records.length = 1;
Tasks._records[0].start = "2024-01-16 00:00:00";
Tasks._records[0].stop = "2024-11-16 00:00:00";
await mountGanttView({
resModel: "tasks",
arch: '',
context: {
default_start_date: "2024-01-01",
default_stop_date: "2024-12-31",
},
});
const { left: pillLeft, right: pillRight } = getPill("Task 1").getBoundingClientRect();
expect(pillLeft).toBeLessThan(0);
expect(pillRight).toBeGreaterThan(window.innerWidth);
expect(".o_popover").toHaveCount(0);
await contains(getPill("Task 1")).click();
expect(".o_popover").toHaveCount(1);
const popover = queryOne(".o_popover");
const { left: popoverLeft, right: popoverRight } = popover.getBoundingClientRect();
expect(popoverLeft).toBeWithin(0, window.innerWidth);
expect(popoverRight).toBeWithin(0, window.innerWidth);
});
test("date fields: domain", async () => {
expect.assertions(4);
Tasks._fields.start = fields.Date();
Tasks._fields.stop = fields.Date();
const domains = [
["&", ["start", "<", "2018-12-21"], ["stop", ">=", "2018-12-20"]],
["&", ["start", "<", "2018-12-23"], ["stop", ">=", "2018-12-16"]],
["&", ["start", "<", "2019-01-01"], ["stop", ">=", "2018-01-01"]],
["&", ["start", "<", "2019-01-01"], ["stop", ">=", "2018-12-01"]],
];
onRpc("get_gantt_data", ({ kwargs }) => {
expect(kwargs.domain).toEqual(domains.pop());
});
await mountGanttView({
type: "gantt",
resModel: "tasks",
arch: ``,
});
await selectRange("This year");
await selectRange("This week");
await selectRange("Today");
});
test("date fields: pill columns", async () => {
Tasks._fields.start = fields.Date();
Tasks._fields.stop = fields.Date();
Tasks._records = Tasks._records.slice(0, 1);
Tasks._records[0].start = "2018-12-20";
Tasks._records[0].stop = "2018-12-22";
await mountGanttView({
type: "gantt",
resModel: "tasks",
arch: ``,
});
expect(getGridContent().rows).toEqual([
{
pills: [
{
colSpan: "20 December 2018 -> 22 December 2018",
level: 0,
title: "Task 1",
},
],
},
]);
});
test.tags("desktop");
test("date fields: resize a pill", async () => {
expect.assertions(4);
Tasks._fields.start = fields.Date();
Tasks._fields.stop = fields.Date();
Tasks._records = Tasks._records.slice(0, 1);
Tasks._records[0].start = "2018-12-20";
Tasks._records[0].stop = "2018-12-22";
onRpc("write", ({ args }) => {
expect(args[0]).toEqual([1]);
// initial dates -- start: '"2018-12-20"', stop: '"2018-12-22"'
expect(args[1]).toEqual({ stop: "2018-12-21" });
});
await mountGanttView({
type: "gantt",
resModel: "tasks",
arch: ``,
});
expect(getGridContent().rows).toEqual([
{
pills: [
{
colSpan: "20 December 2018 -> 22 December 2018",
level: 0,
title: "Task 1",
},
],
},
]);
await resizePill(getPillWrapper("Task 1"), "end", -1);
expect(getGridContent().rows).toEqual([
{
pills: [
{
colSpan: "20 December 2018 -> 21 December 2018",
level: 0,
title: "Task 1",
},
],
},
]);
});
test("date fields: drag a pill", async () => {
expect.assertions(4);
Tasks._fields.start = fields.Date();
Tasks._fields.stop = fields.Date();
Tasks._records = Tasks._records.slice(0, 1);
Tasks._records[0].start = "2018-12-20";
Tasks._records[0].stop = "2018-12-22";
onRpc("write", ({ args }) => {
expect(args[0]).toEqual([1]);
// initial dates -- start: '"2018-12-20"', stop: '"2018-12-22"'
expect(args[1]).toEqual({ start: "2018-12-19", stop: "2018-12-21" });
});
await mountGanttView({
type: "gantt",
resModel: "tasks",
arch: ``,
});
expect(getGridContent().rows).toEqual([
{
pills: [
{
colSpan: "20 December 2018 -> 22 December 2018",
level: 0,
title: "Task 1",
},
],
},
]);
const { drop } = await dragPill("Task 1");
await drop({ column: "19 December 2018", part: 1 });
expect(getGridContent().rows).toEqual([
{
pills: [
{
colSpan: "19 December 2018 -> 21 December 2018",
level: 0,
title: "Task 1",
},
],
},
]);
});
test("date fields: popover", async () => {
expect.assertions(5);
Tasks._fields.start = fields.Date();
Tasks._fields.stop = fields.Date();
Tasks._records = Tasks._records.slice(0, 1);
Tasks._records[0].start = "2018-12-20";
Tasks._records[0].stop = "2018-12-22";
const task1 = Tasks._records[0];
const startDateLocalString = deserializeDate(task1.start).toFormat("f");
const stopDateLocalString = deserializeDate(task1.stop).toFormat("f");
await mountGanttView({
type: "gantt",
resModel: "tasks",
arch: ``,
});
expect(getGridContent().rows).toEqual([
{
pills: [
{
colSpan: "20 December 2018 -> 22 December 2018",
level: 0,
title: "Task 1",
},
],
},
]);
expect(".o_popover").toHaveCount(0);
await contains(SELECTORS.pill).click();
expect(".o_popover").toHaveCount(1);
expect(queryAllTexts(".o_popover .popover-body span")).toEqual([
"Task 1",
startDateLocalString,
stopDateLocalString,
]);
await contains(".o_popover .popover-header i.fa.fa-close").click();
expect(".o_popover").toHaveCount(0);
});
test("date fields: dialog", async () => {
Tasks._fields.start = fields.Date();
Tasks._fields.stop = fields.Date();
Tasks._records = Tasks._records.slice(0, 1);
Tasks._records[0].start = "2018-12-20";
Tasks._records[0].stop = "2018-12-22";
Tasks._views = {
form: `
`,
};
await mountGanttView({
type: "gantt",
resModel: "tasks",
arch: ``,
});
expect(".modal").toHaveCount(0);
await editPill("Task 1");
// check that the dialog is opened with prefilled fields
expect(".modal").toHaveCount(1);
const modal = queryOne(".modal");
expect(modal.querySelector(".o_field_widget[name=start] input")).toHaveValue("12/20/2018");
expect(modal.querySelector(".o_field_widget[name=stop] input")).toHaveValue("12/22/2018");
});
test("markup html server values", async function () {
Tasks._fields.description = fields.Html();
Tasks._records = Tasks._records.slice(0, 1);
Tasks._records[0].description = `Hello`;
await mountGanttView({
type: "gantt",
resModel: "tasks",
arch: `
`,
});
expect(".o_popover").toHaveCount(0);
await contains(SELECTORS.pill).click();
expect(".o_popover").toHaveCount(1);
expect(queryAllTexts(".o_popover .popover-body")).toEqual(["Hello"]);
await contains(".o_popover .popover-header i.fa.fa-close").click();
expect(".o_popover").toHaveCount(0);
});