1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/web_gantt/static/tests/gantt_dependency_tests.js
2024-12-10 09:04:09 +07:00

1077 lines
40 KiB
JavaScript

/** @odoo-module **/
import {
click,
dragAndDrop,
drag,
getFixture,
patchDate,
patchWithCleanup,
triggerEvent,
} from "@web/../tests/helpers/utils";
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
import { COLORS } from "@web_gantt/gantt_connector";
import { GanttRenderer } from "@web_gantt/gantt_renderer";
import { CLASSES, SELECTORS, getPill, getPillWrapper } from "./helpers";
import { registry } from "@web/core/registry";
import { browser } from "@web/core/browser/browser";
/** @typedef {import("@web_gantt/gantt_renderer").ConnectorProps} ConnectorProps */
/** @typedef {import("@web_gantt/gantt_renderer").PillId} PillId */
/**
* @typedef {`[${ResId},${ResId},${ResId},${ResId}]`} ConnectorTaskIds
* In the following order: [masterTaskId, masterTaskUserId, taskId, taskUserId]
*/
/** @typedef {number | false} ResId */
/**
* @param {Element} connector
* @param {"remove" | "reschedule-forward" | "reschedule-backward"} button
*/
async function clickConnectorButton(connector, button) {
await triggerEvent(connector, null, "pointermove");
switch (button) {
case "remove": {
return click(connector, SELECTORS.connectorRemoveButton);
}
case "reschedule-backward": {
return click(connector, `${SELECTORS.connectorRescheduleButton}:first-of-type`);
}
case "reschedule-forward": {
return click(connector, `${SELECTORS.connectorRescheduleButton}:last-of-type`);
}
}
}
/**
* @param {number | "new"} id
*/
export function getConnector(id) {
if (!/^__connector__/.test(id)) {
id = `__connector__${id}`;
}
return getFixture().querySelector([
`${SELECTORS.cellContainer} ${SELECTORS.connector}[data-connector-id='${id}']`,
]);
}
export function getConnectorMap(renderer) {
/**
* @param {PillId} pillId
*/
const getIdAndUserIdFromPill = (pillId) => {
/** @type {[ResId, ResId]} */
const result = [renderer.pills[pillId]?.record.id || false, false];
if (result[0]) {
const pills = renderer.mappingRecordToPillsByRow[result[0]]?.pills;
if (pills) {
const pillEntry = Object.entries(pills).find((e) => e[1].id === pillId);
if (pillEntry) {
const [firstGroup] = JSON.parse(pillEntry[0]);
if (firstGroup.user_ids?.length) {
result[1] = firstGroup.user_ids[0] || false;
}
}
}
}
return result;
};
/** @type {Map<ConnectorTaskIds, ConnectorProps>} */
const connectorMap = new Map();
for (const connector of Object.values(renderer.connectors)) {
const { sourcePillId, targetPillId } = renderer.mappingConnectorToPills[connector.id];
if (!sourcePillId || !targetPillId) {
continue;
}
const key = JSON.stringify([
...getIdAndUserIdFromPill(sourcePillId),
...getIdAndUserIdFromPill(targetPillId),
]);
connectorMap.set(key, connector);
}
return connectorMap;
}
const ganttViewParams = {
type: "gantt",
resModel: "project.task",
arch: /* xml */ `
<gantt
date_start="planned_date_begin"
date_stop="date_deadline"
default_scale="month"
dependency_field="depend_on_ids"
/>
`,
};
/** @type {GanttRenderer} */
let renderer;
/** @type {HTMLElement} */
let target;
QUnit.module("Views > GanttView", (hooks) => {
hooks.beforeEach(async () => {
patchDate(2021, 9, 10, 8, 0, 0);
patchWithCleanup(GanttRenderer.prototype, {
setup() {
super.setup(...arguments);
renderer = this;
},
});
setupViewRegistries();
target = getFixture();
ganttViewParams.serverData = {
models: {
"project.task": {
fields: {
id: { string: "ID", type: "integer" },
name: { string: "Name", type: "char" },
planned_date_begin: { string: "Start Date", type: "datetime" },
date_deadline: { string: "Stop Date", type: "datetime" },
user_ids: { string: "Assignees", type: "many2many", relation: "res.users" },
allow_task_dependencies: {
string: "Allow Task Dependencies",
type: "boolean",
default: true,
},
depend_on_ids: {
string: "Depends on",
type: "one2many",
relation: "project.task",
},
display_warning_dependency_in_gantt: {
string: "Display warning dependency in Gantt",
type: "boolean",
default: true,
},
},
records: [
{
id: 1,
name: "Task 1",
planned_date_begin: "2021-10-11 18:30:00",
date_deadline: "2021-10-11 19:29:59",
user_ids: [1],
depend_on_ids: [],
},
{
id: 2,
name: "Task 2",
planned_date_begin: "2021-10-12 11:30:00",
date_deadline: "2021-10-12 12:29:59",
user_ids: [1, 3],
depend_on_ids: [1],
},
{
id: 3,
name: "Task 3",
planned_date_begin: "2021-10-13 06:30:00",
date_deadline: "2021-10-13 07:29:59",
user_ids: [],
depend_on_ids: [2],
},
{
id: 4,
name: "Task 4",
planned_date_begin: "2021-10-14 22:30:00",
date_deadline: "2021-10-14 23:29:59",
user_ids: [2, 3],
depend_on_ids: [2],
},
{
id: 5,
name: "Task 5",
planned_date_begin: "2021-10-15 01:53:10",
date_deadline: "2021-10-15 02:34:34",
user_ids: [],
depend_on_ids: [],
},
{
id: 6,
name: "Task 6",
planned_date_begin: "2021-10-16 23:00:00",
date_deadline: "2021-10-16 23:21:01",
user_ids: [1, 3],
depend_on_ids: [4, 5],
},
{
id: 7,
name: "Task 7",
planned_date_begin: "2021-10-17 10:30:12",
date_deadline: "2021-10-17 11:29:59",
user_ids: [1, 2, 3],
depend_on_ids: [6],
},
{
id: 8,
name: "Task 8",
planned_date_begin: "2021-10-18 06:30:12",
date_deadline: "2021-10-18 07:29:59",
user_ids: [1, 3],
depend_on_ids: [7],
},
{
id: 9,
name: "Task 9",
planned_date_begin: "2021-10-19 06:30:12",
date_deadline: "2021-10-19 07:29:59",
user_ids: [2],
depend_on_ids: [8],
},
{
id: 10,
name: "Task 10",
planned_date_begin: "2021-10-19 06:30:12",
date_deadline: "2021-10-19 07:29:59",
user_ids: [2],
depend_on_ids: [],
},
{
id: 11,
name: "Task 11",
planned_date_begin: "2021-10-18 06:30:12",
date_deadline: "2021-10-18 07:29:59",
user_ids: [2],
depend_on_ids: [10],
},
{
id: 12,
name: "Task 12",
planned_date_begin: "2021-10-18 06:30:12",
date_deadline: "2021-10-19 07:29:59",
user_ids: [2],
depend_on_ids: [],
},
{
id: 13,
name: "Task 13",
planned_date_begin: "2021-10-18 07:29:59",
date_deadline: "2021-10-20 07:29:59",
user_ids: [2],
depend_on_ids: [12],
},
],
},
"res.users": {
fields: {
id: { string: "ID", type: "integer" },
name: { string: "Name", type: "char" },
},
records: [
{ id: 1, name: "User 1" },
{ id: 2, name: "User 2" },
{ id: 3, name: "User 3" },
{ id: 4, name: "User 4" },
],
},
},
};
ganttViewParams.groupBy = ["user_ids"];
});
QUnit.module("Dependencies");
QUnit.test("Connectors are correctly computed and rendered.", async (assert) => {
assert.expect(46);
/**
* @type {Map<ConnectorTaskIds, keyof typeof COLORS>}
* => Check that there is a connector between masterTaskId from group masterTaskUserId and taskId from group taskUserId with normal|error color.
*/
const testMap = new Map([
["[1,1,2,1]", "default"],
["[1,1,2,3]", "default"],
["[2,1,3,false]", "default"],
["[2,3,3,false]", "default"],
["[2,1,4,2]", "default"],
["[2,3,4,3]", "default"],
["[4,2,6,1]", "default"],
["[4,3,6,3]", "default"],
["[5,false,6,1]", "default"],
["[5,false,6,3]", "default"],
["[6,1,7,1]", "default"],
["[6,1,7,2]", "default"],
["[6,3,7,2]", "default"],
["[6,3,7,3]", "default"],
["[7,1,8,1]", "default"],
["[7,2,8,1]", "default"],
["[7,2,8,3]", "default"],
["[7,3,8,3]", "default"],
["[8,1,9,2]", "default"],
["[8,3,9,2]", "default"],
["[10,2,11,2]", "error"],
["[12,2,13,2]", "warning"],
]);
await makeView(ganttViewParams);
const connectorMap = getConnectorMap(renderer);
for (const [testKey, colorCode] of testMap.entries()) {
const [masterTaskId, masterTaskUserId, taskId, taskUserId] = JSON.parse(testKey);
assert.ok(
connectorMap.has(testKey),
`There should be a connector between task ${masterTaskId} from group user ${masterTaskUserId} and task ${taskId} from group user ${taskUserId}.`
);
const connector = connectorMap.get(testKey);
const connectorStroke = getConnector(connector.id).querySelector(
SELECTORS.connectorStroke
);
assert.hasAttrValue(connectorStroke, "stroke", COLORS[colorCode].color);
}
assert.strictEqual(testMap.size, connectorMap.size);
assert.strictEqual(target.querySelectorAll(SELECTORS.connector).length, testMap.size);
});
QUnit.test("Connectors are correctly rendered.", async (assert) => {
patchWithCleanup(GanttRenderer.prototype, {
shouldRenderRecordConnectors(record) {
return record.id !== 1;
},
});
ganttViewParams.serverData.models["project.task"].records = [
{
id: 1,
name: "Task 1",
planned_date_begin: "2021-10-11 18:30:00",
date_deadline: "2021-10-11 19:29:59",
user_ids: [1],
depend_on_ids: [],
},
{
id: 2,
name: "Task 2",
planned_date_begin: "2021-10-12 11:30:00",
date_deadline: "2021-10-12 12:29:59",
user_ids: [1],
depend_on_ids: [1],
},
{
id: 3,
name: "Task 3",
planned_date_begin: "2021-10-13 06:30:00",
date_deadline: "2021-10-13 07:29:59",
user_ids: [],
depend_on_ids: [1, 2],
},
];
await makeView(ganttViewParams);
const connectorMap = getConnectorMap(renderer);
assert.deepEqual(
[...connectorMap.keys()],
["[2,1,3,false]"],
"The only rendered connector should be the one from task_id 2 to task_id 3"
);
});
QUnit.test(
"Connectors are correctly computed and rendered when consolidation is active.",
async (assert) => {
ganttViewParams.serverData.models["project.task"].records = [
{
id: 1,
name: "Task 1",
planned_date_begin: "2021-10-11 18:30:00",
date_deadline: "2021-10-11 19:29:59",
user_ids: [1],
depend_on_ids: [],
},
{
id: 2,
name: "Task 2",
planned_date_begin: "2021-10-12 11:30:00",
date_deadline: "2021-10-12 12:29:59",
user_ids: [1, 3],
depend_on_ids: [1],
},
{
id: 3,
name: "Task 3",
planned_date_begin: "2021-10-13 06:30:00",
date_deadline: "2021-10-13 07:29:59",
user_ids: [],
depend_on_ids: [2],
},
{
id: 4,
name: "Task 4",
planned_date_begin: "2021-10-14 22:30:00",
date_deadline: "2021-10-14 23:29:59",
user_ids: [2, 3],
depend_on_ids: [2],
},
{
id: 5,
name: "Task 5",
planned_date_begin: "2021-10-15 01:53:10",
date_deadline: "2021-10-15 02:34:34",
user_ids: [],
depend_on_ids: [],
},
{
id: 6,
name: "Task 6",
planned_date_begin: "2021-10-16 23:00:00",
date_deadline: "2021-10-16 23:21:01",
user_ids: [1, 3],
depend_on_ids: [4, 5],
},
{
id: 7,
name: "Task 7",
planned_date_begin: "2021-10-17 10:30:12",
date_deadline: "2021-10-17 11:29:59",
user_ids: [1, 2, 3],
depend_on_ids: [6],
},
{
id: 8,
name: "Task 8",
planned_date_begin: "2021-10-18 06:30:12",
date_deadline: "2021-10-18 07:29:59",
user_ids: [1, 3],
depend_on_ids: [7],
},
{
id: 9,
name: "Task 9",
planned_date_begin: "2021-10-19 06:30:12",
date_deadline: "2021-10-19 07:29:59",
user_ids: [2],
depend_on_ids: [8],
},
{
id: 10,
name: "Task 10",
planned_date_begin: "2021-10-19 06:30:12",
date_deadline: "2021-10-19 07:29:59",
user_ids: [2],
depend_on_ids: [],
},
{
id: 11,
name: "Task 11",
planned_date_begin: "2021-10-18 06:30:12",
date_deadline: "2021-10-18 07:29:59",
user_ids: [2],
depend_on_ids: [10],
},
{
id: 12,
name: "Task 12",
planned_date_begin: "2021-10-18 06:30:12",
date_deadline: "2021-10-19 07:29:59",
user_ids: [2],
depend_on_ids: [],
},
{
id: 13,
name: "Task 13",
planned_date_begin: "2021-10-18 07:29:59",
date_deadline: "2021-10-20 07:29:59",
user_ids: [2],
depend_on_ids: [12],
},
];
await makeView({
...ganttViewParams,
arch: /* xml */ `
<gantt
date_start="planned_date_begin"
date_stop="date_deadline"
default_scale="month"
dependency_field="depend_on_ids"
consolidation_max="{'user_ids': 100 }"
/>
`,
});
// groups have been created of r
assert.strictEqual(
target.querySelectorAll(".o_gantt_row_header.o_gantt_group.o_group_open").length,
4
);
function getConnectorCounts() {
return target.querySelectorAll(SELECTORS.connector).length;
}
function getGroupRow(index) {
return target.querySelectorAll(".o_gantt_row_header.o_gantt_group")[index];
}
assert.strictEqual(getConnectorCounts(), 22);
await click(getGroupRow(1));
assert.doesNotHaveClass(getGroupRow(1), "o_group_open");
assert.strictEqual(getConnectorCounts(), 13);
await click(getGroupRow(1));
assert.strictEqual(getConnectorCounts(), 22);
await click(getGroupRow(1));
assert.strictEqual(getConnectorCounts(), 13);
await click(getGroupRow(2));
assert.strictEqual(getConnectorCounts(), 6);
await click(getGroupRow(0));
assert.strictEqual(getConnectorCounts(), 4);
await click(getGroupRow(3));
assert.strictEqual(getConnectorCounts(), 0);
}
);
QUnit.test(
"Connector hovered state is triggered and color is set accordingly.",
async (assert) => {
await makeView(ganttViewParams);
assert.doesNotHaveClass(getConnector(1), CLASSES.highlightedConnector);
assert.hasAttrValue(
getConnector(1).querySelector(SELECTORS.connectorStroke),
"stroke",
COLORS.default.color
);
await triggerEvent(getConnector(1), null, "pointermove");
assert.hasClass(getConnector(1), CLASSES.highlightedConnector);
assert.hasAttrValue(
getConnector(1).querySelector(SELECTORS.connectorStroke),
"stroke",
COLORS.default.highlightedColor
);
}
);
QUnit.test("Buttons are displayed when hovering a connector.", async (assert) => {
await makeView(ganttViewParams);
assert.containsNone(
getConnector(1),
SELECTORS.connectorStrokeButton,
"Connectors that are not hovered don't display buttons."
);
await triggerEvent(getConnector(1), null, "pointermove");
assert.containsN(
getConnector(1),
SELECTORS.connectorStrokeButton,
3,
"Connectors that are hovered display buttons."
);
});
QUnit.test(
"Buttons are displayed when hovering a connector after a pill has been hovered.",
async (assert) => {
await makeView(ganttViewParams);
assert.containsNone(
getConnector(1),
SELECTORS.connectorStrokeButton,
"Connectors that are not hovered don't display buttons."
);
const task1Pill = getPill("Task 1");
await triggerEvent(task1Pill, null, "pointermove");
const firstConnector = getConnector(1); // (start at task1Pill)
assert.containsNone(
firstConnector,
SELECTORS.connectorStrokeButton,
"Connectors that are not hovered don't display buttons."
);
assert.hasClass(firstConnector, CLASSES.highlightedConnector);
await triggerEvent(firstConnector, null, "pointermove");
assert.hasClass(firstConnector, CLASSES.highlightedConnector);
assert.containsN(
getConnector(1),
SELECTORS.connectorStrokeButton,
3,
"Connectors that are hovered display buttons."
);
}
);
QUnit.test("Connector buttons: remove a dependency", async (assert) => {
await makeView({
...ganttViewParams,
async mockRPC(_route, { method, model, args }) {
if (
model === "project.task" &&
["web_gantt_reschedule", "write"].includes(method)
) {
assert.step(JSON.stringify([method, args]));
return true;
}
},
});
await clickConnectorButton(getConnector(1), "remove");
assert.verifySteps([`["write",[[2],{"depend_on_ids":[[3,1,false]]}]]`]);
});
QUnit.test("Connector buttons: reschedule task backward date.", async (assert) => {
await makeView({
...ganttViewParams,
async mockRPC(_route, { method, model, args }) {
if (
model === "project.task" &&
["web_gantt_reschedule", "write"].includes(method)
) {
assert.step(JSON.stringify([method, args]));
return true;
}
},
});
await clickConnectorButton(getConnector(1), "reschedule-backward");
assert.verifySteps([
`["web_gantt_reschedule",["backward",1,2,"depend_on_ids",null,"planned_date_begin","date_deadline"]]`,
]);
});
QUnit.test("Connector buttons: reschedule task forward date.", async (assert) => {
await makeView({
...ganttViewParams,
async mockRPC(_route, { method, model, args }) {
if (
model === "project.task" &&
["web_gantt_reschedule", "write"].includes(method)
) {
assert.step(JSON.stringify([method, args]));
return true;
}
},
});
await clickConnectorButton(getConnector(1), "reschedule-forward");
assert.verifySteps([
`["web_gantt_reschedule",["forward",1,2,"depend_on_ids",null,"planned_date_begin","date_deadline"]]`,
]);
});
QUnit.test(
"Connector buttons: reschedule task start backward, different data.",
async (assert) => {
await makeView({
...ganttViewParams,
async mockRPC(_route, { method, model, args }) {
if (
model === "project.task" &&
["web_gantt_reschedule", "write"].includes(method)
) {
assert.step(JSON.stringify([method, args]));
return true;
}
},
});
await clickConnectorButton(getConnector(1), "reschedule-backward");
assert.verifySteps([
`["web_gantt_reschedule",["backward",1,2,"depend_on_ids",null,"planned_date_begin","date_deadline"]]`,
]);
}
);
QUnit.test("Connector buttons: reschedule task forward, different data.", async (assert) => {
await makeView({
...ganttViewParams,
async mockRPC(_route, { method, model, args }) {
if (
model === "project.task" &&
["web_gantt_reschedule", "write"].includes(method)
) {
assert.step(JSON.stringify([method, args]));
return true;
}
},
});
await clickConnectorButton(getConnector(1), "reschedule-forward");
assert.verifySteps([
`["web_gantt_reschedule",["forward",1,2,"depend_on_ids",null,"planned_date_begin","date_deadline"]]`,
]);
});
QUnit.test(
"Hovering a task pill should highlight related tasks and dependencies",
async (assert) => {
assert.expect(31);
/** @type {Map<ConnectorTaskIds, boolean>} */
const testMap = new Map([
["[1,1,2,1]", true],
["[1,1,2,3]", true],
["[2,1,3,false]", true],
["[2,3,3,false]", true],
["[2,1,4,2]", true],
["[2,3,4,3]", true],
["[10,2,11,2]", false],
]);
ganttViewParams.serverData.models["project.task"].records = [
{
id: 1,
name: "Task 1",
planned_date_begin: "2021-10-10 18:30:00",
date_deadline: "2021-10-11 19:29:59",
user_ids: [1],
depend_on_ids: [],
},
{
id: 2,
name: "Task 2",
planned_date_begin: "2021-10-12 11:30:00",
date_deadline: "2021-10-12 12:29:59",
user_ids: [1, 3],
depend_on_ids: [1],
},
{
id: 3,
name: "Task 3",
planned_date_begin: "2021-10-13 06:30:00",
date_deadline: "2021-10-13 07:29:59",
user_ids: [],
depend_on_ids: [2],
},
{
id: 4,
name: "Task 4",
planned_date_begin: "2021-10-14 22:30:00",
date_deadline: "2021-10-14 23:29:59",
user_ids: [2, 3],
depend_on_ids: [2],
},
{
id: 10,
name: "Task 10",
planned_date_begin: "2021-10-19 06:30:12",
date_deadline: "2021-10-19 07:29:59",
user_ids: [2],
depend_on_ids: [],
display_warning_dependency_in_gantt: false,
},
{
id: 11,
name: "Task 11",
planned_date_begin: "2021-10-18 06:30:12",
date_deadline: "2021-10-18 07:29:59",
user_ids: [2],
depend_on_ids: [10],
},
];
await makeView(ganttViewParams);
const connectorMap = getConnectorMap(renderer);
const pills = [];
for (const wrapper of target.querySelectorAll(SELECTORS.pillWrapper)) {
const pillId = wrapper.dataset.pillId;
pills.push({
el: wrapper.querySelector(SELECTORS.pill),
recordId: renderer.pills[pillId].record.id,
});
}
const task2Pills = pills.filter((p) => p.recordId === 2);
assert.strictEqual(task2Pills.length, 2);
assert.containsNone(target, CLASSES.highlightedPill);
// Check that all connectors are not in hover state.
for (const testKey of testMap.keys()) {
assert.doesNotHaveClass(
getConnector(connectorMap.get(testKey).id),
CLASSES.highlightedConnector
);
}
await triggerEvent(getPill("Task 2", { nth: 1 }), null, "pointermove");
// Both pills should be highlighted
assert.hasClass(getPillWrapper("Task 2", { nth: 1 }), CLASSES.highlightedPill);
assert.hasClass(getPillWrapper("Task 2", { nth: 2 }), CLASSES.highlightedPill);
// The rest of the pills should not be highlighted nor display connector creators
for (const { el, recordId } of pills) {
if (recordId !== 2) {
assert.doesNotHaveClass(el, CLASSES.highlightedPill);
}
}
// Check that all connectors are in the expected hover state.
for (const [testKey, shouldBeHighlighted] of testMap.entries()) {
const connector = getConnector(connectorMap.get(testKey).id);
if (shouldBeHighlighted) {
assert.hasClass(connector, CLASSES.highlightedConnector);
} else {
assert.doesNotHaveClass(connector, CLASSES.highlightedConnector);
}
assert.containsNone(connector, SELECTORS.connectorStrokeButton);
}
}
);
QUnit.test(
"Hovering a connector should cause the connected pills to get highlighted.",
async (assert) => {
assert.expect(4);
await makeView(ganttViewParams);
assert.containsNone(target, SELECTORS.highlightedConnector);
assert.containsNone(target, SELECTORS.highlightedPill);
await triggerEvent(getConnector(1), null, "pointermove");
assert.containsOnce(target, SELECTORS.highlightedConnector);
assert.containsN(target, SELECTORS.highlightedPill, 2);
}
);
QUnit.test("Connectors are displayed behind pills, except on hover.", async (assert) => {
assert.expect(2);
const getZIndex = (el) => Number(getComputedStyle(el).zIndex) || 0;
await makeView(ganttViewParams);
assert.ok(getZIndex(getPillWrapper("Task 2")) > getZIndex(getConnector(1)));
await triggerEvent(getConnector(1), null, "pointermove");
assert.ok(getZIndex(getPillWrapper("Task 2")) < getZIndex(getConnector(1)));
});
QUnit.test("Create a connector from the gantt view.", async (assert) => {
assert.expect(2);
await makeView({
...ganttViewParams,
async mockRPC(_route, { method, model, args }) {
if (model === "project.task" && method === "write") {
assert.step(JSON.stringify([method, args]));
}
},
});
// Explicitly shows the connector creator wrapper since its "display: none"
// disappears on native CSS hover, which cannot be programatically emulated.
const rightWrapper = target.querySelector(SELECTORS.connectorCreatorWrapper);
rightWrapper.classList.add("d-block");
await dragAndDrop(
rightWrapper.querySelector(SELECTORS.connectorCreatorBullet),
getPill("Task 2")
);
assert.verifySteps([`["write",[[2],{"depend_on_ids":[[4,3,false]]}]]`]);
});
QUnit.test("Create a connector from the gantt view: going fast", async (assert) => {
await makeView({
...ganttViewParams,
domain: [["id", "in", [1, 3]]],
});
// Explicitly shows the connector creator wrapper since its "display: none"
// disappears on native CSS hover, which cannot be programatically emulated.
const rightWrapper = getPillWrapper("Task 1").querySelector(
SELECTORS.connectorCreatorWrapper
);
rightWrapper.classList.add("d-block");
const connectorBullet = rightWrapper.querySelector(SELECTORS.connectorCreatorBullet);
const bulletRect = connectorBullet.getBoundingClientRect();
const initialPosition = {
x: Math.floor(bulletRect.left), // floor to avoid sub-pixel positioning
y: Math.floor(bulletRect.top), // floor to avoid sub-pixel positioning
};
await triggerEvent(connectorBullet, null, "pointerdown", {
clientX: initialPosition.x,
clientY: initialPosition.y,
});
// Here we simulate a fast move, using arbitrary values.
const currentPosition = {
x: Math.floor(initialPosition.x + 123), // floor to avoid sub-pixel positioning
y: Math.floor(initialPosition.y + 12), // floor to avoid sub-pixel positioning
};
await triggerEvent(target, null, "pointermove", {
clientX: currentPosition.x,
clientY: currentPosition.y,
});
// Then we check that the connector stroke is correctly positioned.
const connectorStroke = getConnector("new").querySelector(SELECTORS.connectorStroke);
const strokeRect = connectorStroke.getBoundingClientRect();
assert.strictEqual(strokeRect.left, initialPosition.x);
assert.strictEqual(strokeRect.top, initialPosition.y);
assert.strictEqual(strokeRect.left + strokeRect.width, currentPosition.x);
assert.strictEqual(strokeRect.top + strokeRect.height, currentPosition.y);
});
QUnit.test("Connectors should be rendered if connected pill is not visible", async (assert) => {
// Generate a lot of users so that the connectors are far beyond the visible
// viewport, hence generating fake extra pills to render the connectors.
for (let i = 0; i < 100; i++) {
const id = 100 + i;
ganttViewParams.serverData.models["res.users"].records.push({
id,
name: `User ${id}`,
});
ganttViewParams.serverData.models["project.task"].records.push({
id,
name: `Task ${id}`,
planned_date_begin: "2021-10-11 18:30:00",
date_deadline: "2021-10-11 19:29:59",
user_ids: [id],
depend_on_ids: [],
});
}
ganttViewParams.serverData.models["project.task"].records[12].user_ids = [199];
await makeView(ganttViewParams);
assert.containsN(target, SELECTORS.connector, 13);
});
QUnit.test("No display of resize handles when creating a connector", async (assert) => {
assert.expect(1);
await makeView(ganttViewParams);
// Explicitly shows the connector creator wrapper since its "display: none"
// disappears on native CSS hover, which cannot be programatically emulated.
const rightWrapper = target.querySelector(SELECTORS.connectorCreatorWrapper);
rightWrapper.classList.add("d-block");
// Creating a connector and hover another pill while dragging it
const { moveTo } = await drag(rightWrapper.querySelector(SELECTORS.connectorCreatorBullet));
await moveTo(getPill("Task 2"));
assert.containsNone(target, SELECTORS.resizeHandle);
});
QUnit.test("Renderer in connect mode when creating a connector", async (assert) => {
await makeView(ganttViewParams);
// Explicitly shows the connector creator wrapper since its "display: none"
// disappears on native CSS hover, which cannot be programatically emulated.
const rightWrapper = target.querySelector(SELECTORS.connectorCreatorWrapper);
rightWrapper.classList.add("d-block");
// Creating a connector and hover another pill while dragging it
const { moveTo } = await drag(rightWrapper.querySelector(SELECTORS.connectorCreatorBullet));
await moveTo(getPill("Task 2"));
assert.hasClass(target.querySelector(SELECTORS.renderer), "o_connect");
});
QUnit.test(
"Connector creators of initial pill are highlighted when creating a connector",
async (assert) => {
await makeView(ganttViewParams);
// Explicitly shows the connector creator wrapper since its "display: none"
// disappears on native CSS hover, which cannot be programatically emulated.
const sourceWrapper = target.querySelector(SELECTORS.pillWrapper);
const rightWrapper = sourceWrapper.querySelector(SELECTORS.connectorCreatorWrapper);
rightWrapper.classList.add("d-block");
// Creating a connector and hover another pill while dragging it
const { moveTo } = await drag(
rightWrapper.querySelector(SELECTORS.connectorCreatorBullet)
);
await moveTo(getPill("Task 2"));
assert.hasClass(sourceWrapper, CLASSES.lockedConnectorCreator);
}
);
QUnit.test(
"Connector creators of hovered pill are highlighted when creating a connector",
async (assert) => {
await makeView(ganttViewParams);
// Explicitly shows the connector creator wrapper since its "display: none"
// disappears on native CSS hover, which cannot be programatically emulated.
const rightWrapper = target.querySelector(SELECTORS.connectorCreatorWrapper);
rightWrapper.classList.add("d-block");
// Creating a connector and hover another pill while dragging it
const { moveTo } = await drag(
rightWrapper.querySelector(SELECTORS.connectorCreatorBullet)
);
const destinationWrapper = getPillWrapper("Task 2");
const destinationPill = destinationWrapper.querySelector(SELECTORS.pill);
await moveTo(destinationPill);
// moveTo only triggers a pointerenter event on destination pill,
// a pointermove event is still needed to highlight it
await triggerEvent(destinationPill, null, "pointermove");
assert.hasClass(destinationWrapper, CLASSES.highlightedConnectorCreator);
}
);
QUnit.test(
"Switch to full-size browser: the connections between pills should be diplayed",
async (assert) => {
const ui = { isSmall: true };
patchWithCleanup(browser, { setTimeout: (fn) => fn() });
const fakeUIService = {
start(env) {
Object.defineProperty(env, "isSmall", {
get() {
return ui.isSmall;
},
});
return ui;
},
};
registry.category("services").add("ui", fakeUIService);
await makeView(ganttViewParams);
// Mobile view
assert.containsNone(
target,
"svg.o_gantt_connector",
"Gantt connectors should not be visible in small/mobile view"
);
// Resizing browser to leave mobile view
ui.isSmall = false;
patchWithCleanup(browser, { innerWidth: 1200 });
await triggerEvent(window, null, "resize");
assert.containsN(
target,
"svg.o_gantt_connector",
22,
"Gantt connectors should be visible when switching to desktop view"
);
}
);
});