580 lines
17 KiB
JavaScript
580 lines
17 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import { uiService } from "@web/core/ui/ui_service";
|
|
import { createElement } from "@web/core/utils/xml";
|
|
import { registry } from "@web/core/registry";
|
|
import { Field } from "@web/views/fields/field";
|
|
import { clearRegistryWithCleanup, makeTestEnv } from "../../helpers/mock_env";
|
|
import { click, getFixture, mount, nextTick, triggerEvent } from "../../helpers/utils";
|
|
import { setupViewRegistries } from "@web/../tests/views/helpers";
|
|
|
|
export function makeEnv(services = {}) {
|
|
clearRegistryWithCleanup(registry.category("main_components"));
|
|
setupViewRegistries();
|
|
services = Object.assign(
|
|
{
|
|
ui: uiService,
|
|
},
|
|
services
|
|
);
|
|
|
|
for (const [key, service] of Object.entries(services)) {
|
|
registry.category("services").add(key, service, { force: true });
|
|
}
|
|
|
|
return makeTestEnv({
|
|
config: {
|
|
setDisplayName: () => {},
|
|
},
|
|
});
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
export async function mountComponent(C, env, props) {
|
|
const target = getFixture();
|
|
return await mount(C, target, { env, props });
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
export function makeFakeDate() {
|
|
return luxon.DateTime.local(2021, 7, 16, 8, 0, 0, 0);
|
|
}
|
|
|
|
export function makeFakeRecords() {
|
|
return {
|
|
1: {
|
|
id: 1,
|
|
title: "1 day, all day in July",
|
|
start: makeFakeDate(),
|
|
isAllDay: true,
|
|
end: makeFakeDate(),
|
|
},
|
|
2: {
|
|
id: 2,
|
|
title: "3 days, all day in July",
|
|
start: makeFakeDate().plus({ days: 2 }),
|
|
isAllDay: true,
|
|
end: makeFakeDate().plus({ days: 4 }),
|
|
},
|
|
3: {
|
|
id: 3,
|
|
title: "1 day, all day in June",
|
|
start: makeFakeDate().plus({ months: -1 }),
|
|
isAllDay: true,
|
|
end: makeFakeDate().plus({ months: -1 }),
|
|
},
|
|
4: {
|
|
id: 4,
|
|
title: "3 days, all day in June",
|
|
start: makeFakeDate().plus({ months: -1, days: 2 }),
|
|
isAllDay: true,
|
|
end: makeFakeDate().plus({ months: -1, days: 4 }),
|
|
},
|
|
5: {
|
|
id: 5,
|
|
title: "Over June and July",
|
|
start: makeFakeDate().startOf("month").plus({ days: -2 }),
|
|
isAllDay: true,
|
|
end: makeFakeDate().startOf("month").plus({ days: 2 }),
|
|
},
|
|
};
|
|
}
|
|
|
|
export const FAKE_FILTER_SECTIONS = [
|
|
{
|
|
label: "Attendees",
|
|
fieldName: "partner_ids",
|
|
avatar: {
|
|
model: "res.partner",
|
|
field: "avatar_128",
|
|
},
|
|
hasAvatar: true,
|
|
write: {
|
|
model: "filter_partner",
|
|
field: "partner_id",
|
|
},
|
|
canCollapse: true,
|
|
canAddFilter: true,
|
|
filters: [
|
|
{
|
|
type: "user",
|
|
label: "Mitchell Admin",
|
|
active: true,
|
|
value: 3,
|
|
colorIndex: 3,
|
|
recordId: null,
|
|
canRemove: false,
|
|
hasAvatar: true,
|
|
},
|
|
{
|
|
type: "all",
|
|
label: "Everybody's calendar",
|
|
active: false,
|
|
value: "all",
|
|
colorIndex: null,
|
|
recordId: null,
|
|
canRemove: false,
|
|
hasAvatar: false,
|
|
},
|
|
{
|
|
type: "record",
|
|
label: "Brandon Freeman",
|
|
active: true,
|
|
value: 4,
|
|
colorIndex: 4,
|
|
recordId: 1,
|
|
canRemove: true,
|
|
hasAvatar: true,
|
|
},
|
|
{
|
|
type: "record",
|
|
label: "Marc Demo",
|
|
active: false,
|
|
value: 6,
|
|
colorIndex: 6,
|
|
recordId: 2,
|
|
canRemove: true,
|
|
hasAvatar: true,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: "Users",
|
|
fieldName: "user_id",
|
|
avatar: {
|
|
model: null,
|
|
field: null,
|
|
},
|
|
hasAvatar: false,
|
|
write: {
|
|
model: null,
|
|
field: null,
|
|
},
|
|
canCollapse: false,
|
|
canAddFilter: false,
|
|
filters: [
|
|
{
|
|
type: "record",
|
|
label: "Brandon Freeman",
|
|
active: false,
|
|
value: 1,
|
|
colorIndex: false,
|
|
recordId: null,
|
|
canRemove: true,
|
|
hasAvatar: true,
|
|
},
|
|
{
|
|
type: "record",
|
|
label: "Marc Demo",
|
|
active: false,
|
|
value: 2,
|
|
colorIndex: false,
|
|
recordId: null,
|
|
canRemove: true,
|
|
hasAvatar: true,
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
export const FAKE_FIELDS = {
|
|
id: { string: "Id", type: "integer" },
|
|
user_id: { string: "User", type: "many2one", relation: "user", default: -1 },
|
|
partner_id: {
|
|
string: "Partner",
|
|
type: "many2one",
|
|
relation: "partner",
|
|
related: "user_id.partner_id",
|
|
default: 1,
|
|
},
|
|
name: { string: "Name", type: "char" },
|
|
start_date: { string: "Start Date", type: "date" },
|
|
stop_date: { string: "Stop Date", type: "date" },
|
|
start: { string: "Start Datetime", type: "datetime" },
|
|
stop: { string: "Stop Datetime", type: "datetime" },
|
|
delay: { string: "Delay", type: "float" },
|
|
allday: { string: "Is All Day", type: "boolean" },
|
|
partner_ids: {
|
|
string: "Attendees",
|
|
type: "one2many",
|
|
relation: "partner",
|
|
default: [[6, 0, [1]]],
|
|
},
|
|
type: { string: "Type", type: "integer" },
|
|
event_type_id: { string: "Event Type", type: "many2one", relation: "event_type" },
|
|
color: { string: "Color", type: "integer", related: "event_type_id.color" },
|
|
};
|
|
|
|
function makeFakeModelState() {
|
|
const fakeFieldNode = createElement("field", { name: "name" });
|
|
const fakeModels = { event: FAKE_FIELDS };
|
|
return {
|
|
canCreate: true,
|
|
canDelete: true,
|
|
canEdit: true,
|
|
date: makeFakeDate(),
|
|
fieldMapping: {
|
|
date_start: "start_date",
|
|
date_stop: "stop_date",
|
|
date_delay: "delay",
|
|
all_day: "allday",
|
|
color: "color",
|
|
},
|
|
fieldNames: ["start_date", "stop_date", "color", "delay", "allday", "user_id"],
|
|
fields: FAKE_FIELDS,
|
|
filterSections: FAKE_FILTER_SECTIONS,
|
|
firstDayOfWeek: 0,
|
|
isDateHidden: false,
|
|
isTimeHidden: false,
|
|
hasAllDaySlot: true,
|
|
hasEditDialog: false,
|
|
quickCreate: false,
|
|
popoverFieldNodes: {
|
|
name: Field.parseFieldNode(fakeFieldNode, fakeModels, "event", "calendar"),
|
|
},
|
|
activeFields: {
|
|
name: {
|
|
context: "{}",
|
|
invisible: false,
|
|
readonly: false,
|
|
required: false,
|
|
onChange: false,
|
|
},
|
|
},
|
|
rangeEnd: makeFakeDate().endOf("month"),
|
|
rangeStart: makeFakeDate().startOf("month"),
|
|
records: makeFakeRecords(),
|
|
resModel: "event",
|
|
scale: "month",
|
|
scales: ["day", "week", "month", "year"],
|
|
unusualDays: [],
|
|
};
|
|
}
|
|
|
|
export function makeFakeModel(state = {}) {
|
|
return {
|
|
...makeFakeModelState(),
|
|
load() {},
|
|
createFilter() {},
|
|
createRecord() {},
|
|
unlinkFilter() {},
|
|
unlinkRecord() {},
|
|
updateFilter() {},
|
|
updateRecord() {},
|
|
...state,
|
|
};
|
|
}
|
|
|
|
// DOM Utils
|
|
//------------------------------------------------------------------------------
|
|
|
|
async function scrollTo(el, scrollParam) {
|
|
el.scrollIntoView(scrollParam);
|
|
await new Promise(window.requestAnimationFrame);
|
|
}
|
|
|
|
export function findPickedDate(target) {
|
|
return target.querySelector(".o_datetime_picker .o_selected");
|
|
}
|
|
|
|
export async function pickDate(target, date) {
|
|
const day = date.split("-")[2];
|
|
const iDay = parseInt(day, 10) - 1;
|
|
const el = target.querySelectorAll(`.o_datetime_picker .o_date_item_cell:not(.o_out_of_range)`)[
|
|
iDay
|
|
];
|
|
el.scrollIntoView();
|
|
await click(el);
|
|
}
|
|
|
|
function findAllDaySlot(target, date) {
|
|
return target.querySelector(`.fc-day-grid .fc-day[data-date="${date}"]`);
|
|
}
|
|
|
|
export function findDateCell(target, date) {
|
|
return target.querySelector(`.fc-day-top[data-date="${date}"]`);
|
|
}
|
|
|
|
export function findEvent(target, eventId) {
|
|
return target.querySelector(`.o_event[data-event-id="${eventId}"]`);
|
|
}
|
|
|
|
function findDateCol(target, date) {
|
|
return target.querySelector(`.fc-day-header[data-date="${date}"]`);
|
|
}
|
|
|
|
export function findTimeRow(target, time) {
|
|
return target.querySelector(`.fc-slats [data-time="${time}"] .fc-widget-content`);
|
|
}
|
|
|
|
async function triggerEventForCalendar(el, type, position = {}) {
|
|
const rect = el.getBoundingClientRect();
|
|
const x = position.x || rect.x + rect.width / 2;
|
|
const y = position.y || rect.y + rect.height / 2;
|
|
const attrs = {
|
|
which: 1,
|
|
clientX: x,
|
|
clientY: y,
|
|
};
|
|
await triggerEvent(el, null, type, attrs);
|
|
}
|
|
|
|
export async function clickAllDaySlot(target, date) {
|
|
const el = findAllDaySlot(target, date);
|
|
await scrollTo(el);
|
|
await triggerEventForCalendar(el, "mousedown");
|
|
await triggerEventForCalendar(el, "mouseup");
|
|
await nextTick();
|
|
}
|
|
|
|
export async function clickDate(target, date) {
|
|
const el = findDateCell(target, date);
|
|
await scrollTo(el);
|
|
await triggerEventForCalendar(el, "mousedown");
|
|
await triggerEventForCalendar(el, "mouseup");
|
|
await nextTick();
|
|
}
|
|
|
|
export async function clickEvent(target, eventId) {
|
|
const el = findEvent(target, eventId);
|
|
await scrollTo(el);
|
|
await click(el);
|
|
await nextTick();
|
|
}
|
|
|
|
export async function selectTimeRange(target, startDateTime, endDateTime) {
|
|
const [startDate, startTime] = startDateTime.split(" ");
|
|
const [endDate, endTime] = endDateTime.split(" ");
|
|
|
|
const startCol = findDateCol(target, startDate);
|
|
const endCol = findDateCol(target, endDate);
|
|
const startRow = findTimeRow(target, startTime);
|
|
const endRow = findTimeRow(target, endTime);
|
|
|
|
await scrollTo(startRow);
|
|
const startColRect = startCol.getBoundingClientRect();
|
|
const startRowRect = startRow.getBoundingClientRect();
|
|
|
|
await triggerEventForCalendar(startRow, "mousedown", {
|
|
x: startColRect.x + startColRect.width / 2,
|
|
y: startRowRect.y + 1,
|
|
});
|
|
|
|
await scrollTo(endRow, false);
|
|
const endColRect = endCol.getBoundingClientRect();
|
|
const endRowRect = endRow.getBoundingClientRect();
|
|
|
|
await triggerEventForCalendar(endRow, "mousemove", {
|
|
x: endColRect.x + endColRect.width / 2,
|
|
y: endRowRect.y - 1,
|
|
});
|
|
await triggerEventForCalendar(endRow, "mouseup", {
|
|
x: endColRect.x + endColRect.width / 2,
|
|
y: endRowRect.y - 1,
|
|
});
|
|
await nextTick();
|
|
}
|
|
|
|
export async function selectDateRange(target, startDate, endDate) {
|
|
const start = findDateCell(target, startDate);
|
|
const end = findDateCell(target, endDate);
|
|
await scrollTo(start);
|
|
await triggerEventForCalendar(start, "mousedown");
|
|
await scrollTo(end);
|
|
await triggerEventForCalendar(end, "mousemove");
|
|
await triggerEventForCalendar(end, "mouseup");
|
|
await nextTick();
|
|
}
|
|
|
|
export async function selectAllDayRange(target, startDate, endDate) {
|
|
const start = findAllDaySlot(target, startDate);
|
|
const end = findAllDaySlot(target, endDate);
|
|
await scrollTo(start);
|
|
await triggerEventForCalendar(start, "mousedown");
|
|
await scrollTo(end);
|
|
await triggerEventForCalendar(end, "mousemove");
|
|
await triggerEventForCalendar(end, "mouseup");
|
|
await nextTick();
|
|
}
|
|
|
|
export async function moveEventToDate(target, eventId, date, options = {}) {
|
|
const event = findEvent(target, eventId);
|
|
const cell = findDateCell(target, date);
|
|
|
|
await scrollTo(event);
|
|
await triggerEventForCalendar(event, "mousedown");
|
|
|
|
await scrollTo(cell);
|
|
await triggerEventForCalendar(cell, "mousemove");
|
|
|
|
if (!options.disableDrop) {
|
|
await triggerEventForCalendar(cell, "mouseup");
|
|
}
|
|
await nextTick();
|
|
}
|
|
|
|
export async function moveEventToTime(target, eventId, dateTime) {
|
|
const event = findEvent(target, eventId);
|
|
const [date, time] = dateTime.split(" ");
|
|
|
|
const col = findDateCol(target, date);
|
|
const row = findTimeRow(target, time);
|
|
|
|
// Find event position
|
|
await scrollTo(event);
|
|
const eventRect = event.getBoundingClientRect();
|
|
const eventPos = {
|
|
x: eventRect.x + eventRect.width / 2,
|
|
y: eventRect.y,
|
|
};
|
|
|
|
await triggerEventForCalendar(event, "mousedown", eventPos);
|
|
|
|
// Find target position
|
|
await scrollTo(row, false);
|
|
const colRect = col.getBoundingClientRect();
|
|
const rowRect = row.getBoundingClientRect();
|
|
const toPos = {
|
|
x: colRect.x + colRect.width / 2,
|
|
y: rowRect.y - 1,
|
|
};
|
|
|
|
await triggerEventForCalendar(row, "mousemove", toPos);
|
|
await triggerEventForCalendar(row, "mouseup", toPos);
|
|
await nextTick();
|
|
}
|
|
|
|
export async function moveEventToAllDaySlot(target, eventId, date) {
|
|
const event = findEvent(target, eventId);
|
|
const slot = findAllDaySlot(target, date);
|
|
|
|
// Find event position
|
|
await scrollTo(event);
|
|
const eventRect = event.getBoundingClientRect();
|
|
const eventPos = {
|
|
x: eventRect.x + eventRect.width / 2,
|
|
y: eventRect.y,
|
|
};
|
|
await triggerEventForCalendar(event, "mousedown", eventPos);
|
|
|
|
// Find target position
|
|
await scrollTo(slot);
|
|
const slotRect = slot.getBoundingClientRect();
|
|
const toPos = {
|
|
x: slotRect.x + slotRect.width / 2,
|
|
y: slotRect.y - 1,
|
|
};
|
|
await triggerEventForCalendar(slot, "mousemove", toPos);
|
|
await triggerEventForCalendar(slot, "mouseup", toPos);
|
|
await nextTick();
|
|
}
|
|
|
|
export async function resizeEventToTime(target, eventId, dateTime) {
|
|
const event = findEvent(target, eventId);
|
|
const [date, time] = dateTime.split(" ");
|
|
|
|
const col = findDateCol(target, date);
|
|
const row = findTimeRow(target, time);
|
|
|
|
// Find event position
|
|
await scrollTo(event);
|
|
await triggerEventForCalendar(event, "mouseenter");
|
|
|
|
// Find event resizer
|
|
const resizer = event.querySelector(".fc-end-resizer");
|
|
resizer.style.display = "block";
|
|
resizer.style.width = "100%";
|
|
resizer.style.height = "1em";
|
|
resizer.style.bottom = "0";
|
|
const resizerRect = resizer.getBoundingClientRect();
|
|
const resizerPos = {
|
|
x: resizerRect.x + resizerRect.width / 2,
|
|
y: resizerRect.y + resizerRect.height / 2,
|
|
};
|
|
await triggerEventForCalendar(resizer, "mousedown", resizerPos);
|
|
|
|
// Find target position
|
|
await scrollTo(row, false);
|
|
const colRect = col.getBoundingClientRect();
|
|
const rowRect = row.getBoundingClientRect();
|
|
const toPos = {
|
|
x: colRect.x + colRect.width / 2,
|
|
y: rowRect.y - 1,
|
|
};
|
|
|
|
await triggerEventForCalendar(row, "mousemove", toPos);
|
|
await triggerEventForCalendar(row, "mouseup", toPos);
|
|
await nextTick();
|
|
}
|
|
|
|
export async function resizeEventToDate(target, eventId, date) {
|
|
const event = findEvent(target, eventId);
|
|
const slot = findAllDaySlot(target, date);
|
|
|
|
await scrollTo(event);
|
|
await triggerEventForCalendar(event, "mouseenter");
|
|
|
|
// Find event resizer
|
|
const resizer = event.querySelector(".fc-end-resizer");
|
|
resizer.style.display = "block";
|
|
resizer.style.width = "100%";
|
|
resizer.style.height = "1em";
|
|
resizer.style.bottom = "0";
|
|
const resizerRect = resizer.getBoundingClientRect();
|
|
const resizerPos = {
|
|
x: resizerRect.x + resizerRect.width,
|
|
y: resizerRect.y + resizerRect.height / 2,
|
|
};
|
|
await triggerEventForCalendar(resizer, "mousedown", resizerPos);
|
|
// Find slot position
|
|
await scrollTo(slot, false);
|
|
const slotRect = slot.getBoundingClientRect();
|
|
const toPos = {
|
|
x: slotRect.x + slotRect.width / 2,
|
|
y: slotRect.y + slotRect.height / 2,
|
|
};
|
|
await triggerEventForCalendar(slot, "mousemove", toPos);
|
|
await triggerEventForCalendar(slot, "mouseup", toPos);
|
|
await nextTick();
|
|
}
|
|
|
|
export async function changeScale(target, scale) {
|
|
await click(target, `.o_view_scale_selector .scale_button_selection`);
|
|
await click(target, `.o_view_scale_selector .o_scale_button_${scale}`);
|
|
await nextTick();
|
|
}
|
|
|
|
export async function navigate(target, direction) {
|
|
await click(target, `.o_calendar_navigation_buttons .o_calendar_button_${direction}`);
|
|
}
|
|
|
|
export function findFilterPanelSection(target, sectionName) {
|
|
return target.querySelector(`.o_calendar_filter[data-name="${sectionName}"]`);
|
|
}
|
|
|
|
export function findFilterPanelFilter(target, sectionName, filterValue) {
|
|
return findFilterPanelSection(target, sectionName).querySelector(
|
|
`.o_calendar_filter_item[data-value="${filterValue}"]`
|
|
);
|
|
}
|
|
|
|
export function findFilterPanelSectionFilter(target, sectionName) {
|
|
return findFilterPanelSection(target, sectionName).querySelector(
|
|
`.o_calendar_filter_items_checkall`
|
|
);
|
|
}
|
|
|
|
export async function toggleFilter(target, sectionName, filterValue) {
|
|
const el = findFilterPanelFilter(target, sectionName, filterValue).querySelector(`input`);
|
|
await scrollTo(el);
|
|
await click(el);
|
|
}
|
|
|
|
export async function toggleSectionFilter(target, sectionName) {
|
|
const el = findFilterPanelSectionFilter(target, sectionName).querySelector(`input`);
|
|
await scrollTo(el);
|
|
await click(el);
|
|
}
|