Odoo18-Base/addons/web/static/tests/views/calendar/calendar_view_tests.js
2025-03-10 10:52:11 +07:00

5808 lines
214 KiB
JavaScript

/** @odoo-module **/
import { registerCleanup } from "../../helpers/cleanup";
import { defaultLocalization, makeFakeDialogService } from "../../helpers/mock_services";
import {
click,
editInput,
editSelect,
getFixture,
makeDeferred,
mockTimeout,
nextTick,
patchDate,
patchTimeZone,
patchWithCleanup,
triggerEvent,
} from "../../helpers/utils";
import {
changeScale,
clickDate,
clickEvent,
findEvent,
findFilterPanelFilter,
findFilterPanelSection,
findPickedDate,
findTimeRow,
moveEventToAllDaySlot,
moveEventToDate,
moveEventToTime,
navigate,
pickDate,
resizeEventToTime,
resizeEventToDate,
selectAllDayRange,
selectDateRange,
selectTimeRange,
toggleFilter,
toggleSectionFilter,
clickAllDaySlot,
} from "../../views/calendar/helpers";
import { makeView, setupViewRegistries } from "../../views/helpers";
import { createWebClient, doAction } from "../../webclient/helpers";
import { browser } from "@web/core/browser/browser";
import { dialogService } from "@web/core/dialog/dialog_service";
import { localization } from "@web/core/l10n/localization";
import { registry } from "@web/core/registry";
import { userService } from "@web/core/user_service";
import { CalendarYearRenderer } from "@web/views/calendar/calendar_year/calendar_year_renderer";
import { actionService } from "@web/webclient/actions/action_service";
import { getTimePickers } from "../../core/datetime/datetime_test_helpers";
import { CalendarController } from "@web/views/calendar/calendar_controller";
import { calendarView } from "@web/views/calendar/calendar_view";
import { Component, onWillRender, onWillStart, xml } from "@odoo/owl";
const fieldRegistry = registry.category("fields");
const serviceRegistry = registry.category("services");
const viewRegistry = registry.category("views");
let target;
let serverData;
const uid = -1;
QUnit.module("Views", ({ beforeEach }) => {
beforeEach(() => {
// 2016-12-12 08:00:00
patchDate(2016, 11, 12, 8, 0, 0);
patchWithCleanup(browser, {
setTimeout: (fn) => fn(),
clearTimeout: () => {},
});
target = getFixture();
setupViewRegistries();
serviceRegistry.add(
"user",
{
...userService,
start() {
const fakeUserService = userService.start(...arguments);
Object.defineProperty(fakeUserService, "userId", {
get: () => uid,
});
return fakeUserService;
},
},
{ force: true }
);
serverData = {
models: {
event: {
fields: {
id: { string: "ID", type: "integer" },
user_id: {
string: "user",
type: "many2one",
relation: "user",
default: uid,
},
partner_id: {
string: "user",
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" },
duration: { string: "Duration", type: "float", default: 1 },
allday: { string: "allday", 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_event: {
string: "Color",
type: "integer",
related: "event_type_id.color_event_type",
},
is_hatched: { string: "Hatched", type: "boolean" },
is_striked: { string: "Striked", type: "boolean" },
},
records: [
{
id: 1,
user_id: uid,
partner_id: 1,
name: "event 1",
start: "2016-12-11 00:00:00",
stop: "2016-12-11 00:00:00",
allday: false,
partner_ids: [1, 2, 3],
type: 1,
is_hatched: false,
},
{
id: 2,
user_id: uid,
partner_id: 1,
name: "event 2",
start: "2016-12-12 10:55:05",
stop: "2016-12-12 14:55:05",
allday: false,
partner_ids: [1, 2],
type: 3,
is_hatched: false,
},
{
id: 3,
user_id: 4,
partner_id: 4,
name: "event 3",
start: "2016-12-12 15:55:05",
stop: "2016-12-12 16:55:05",
allday: false,
partner_ids: [1],
type: 2,
is_hatched: true,
},
{
id: 4,
user_id: uid,
partner_id: 1,
name: "event 4",
start: "2016-12-14 15:55:05",
stop: "2016-12-14 18:55:05",
allday: true,
partner_ids: [1],
type: 2,
is_hatched: false,
is_striked: true,
},
{
id: 5,
user_id: 4,
partner_id: 4,
name: "event 5",
start: "2016-12-13 15:55:05",
stop: "2016-12-20 18:55:05",
allday: false,
partner_ids: [2, 3],
type: 2,
is_hatched: true,
},
{
id: 6,
user_id: uid,
partner_id: 1,
name: "event 6",
start: "2016-12-18 08:00:00",
stop: "2016-12-18 09:00:00",
allday: false,
partner_ids: [3],
type: 3,
is_hatched: true,
},
{
id: 7,
user_id: uid,
partner_id: 1,
name: "event 7",
start: "2016-11-14 08:00:00",
stop: "2016-11-16 17:00:00",
allday: false,
partner_ids: [2],
type: 1,
is_hatched: false,
},
],
methods: {
check_access_rights() {
return Promise.resolve(true);
},
},
},
user: {
fields: {
id: { string: "ID", type: "integer" },
display_name: { string: "Displayed name", type: "char" },
partner_id: {
string: "partner",
type: "many2one",
relation: "partner",
},
image: { string: "image", type: "integer" },
},
records: [
{ id: uid, display_name: "user 1", partner_id: 1 },
{ id: 4, display_name: "user 4", partner_id: 4 },
],
},
partner: {
fields: {
id: { string: "ID", type: "integer" },
display_name: { string: "Displayed name", type: "char" },
image: { string: "image", type: "integer" },
},
records: [
{ id: 1, display_name: "partner 1", image: "AAA" },
{ id: 2, display_name: "partner 2", image: "BBB" },
{ id: 3, display_name: "partner 3", image: "CCC" },
{ id: 4, display_name: "partner 4", image: "DDD" },
],
},
event_type: {
fields: {
id: { string: "ID", type: "integer" },
display_name: { string: "Displayed name", type: "char" },
color_event_type: { string: "Color", type: "integer" },
},
records: [
{ id: 1, display_name: "Event Type 1", color_event_type: 1 },
{ id: 2, display_name: "Event Type 2", color_event_type: 2 },
{ id: 3, display_name: "Event Type 3 (color 4)", color_event_type: 4 },
],
},
filter_partner: {
fields: {
id: { string: "ID", type: "integer" },
user_id: { string: "user", type: "many2one", relation: "user" },
partner_id: {
string: "partner",
type: "many2one",
relation: "partner",
},
partner_checked: { string: "checked", type: "boolean" },
},
records: [
{ id: 1, user_id: uid, partner_id: 1, partner_checked: true },
{ id: 2, user_id: uid, partner_id: 2, partner_checked: true },
{ id: 3, user_id: 4, partner_id: 3, partner_checked: true },
],
},
},
views: {
"event,false,form": `
<form>
<field name="name" />
<field name="allday" />
<group invisible="allday">
<field name="start" />
<field name="stop" />
</group>
<group invisible="not allday">
<field name="start_date" />
<field name="stop_date" />
</group>
</form>
`,
"event,1,form": `
<form>
<field name="allday" invisible="1" />
<field name="start" invisible="not allday" />
<field name="stop" invisible="allday" />
</form>
`,
},
};
});
QUnit.module("CalendarView");
QUnit.test(`simple calendar rendering`, async (assert) => {
assert.expect(23);
serverData.models.event.records.push(
{
id: 8,
user_id: uid,
partner_id: false,
name: "event 7",
start: "2016-12-18 09:00:00",
stop: "2016-12-18 10:00:00",
allday: false,
partner_ids: [2],
type: 1,
},
{
id: 9,
user_id: uid,
partner_id: false,
name: "event 8",
start: "2016-12-11 05:15:00",
stop: "2016-12-11 05:30:00",
allday: false,
partner_ids: [1, 2, 3],
duration: 0.25,
type: 1,
}
);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar event_open_popup="1" date_start="start" date_stop="stop" all_day="allday" mode="week" attendee="partner_ids" color="partner_id" date_delay="duration">
<filter name="user_id" avatar_field="image" />
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
<field name="partner_id" filters="1" invisible="1" />
<field name="duration" invisible="1"/>
</calendar>
`,
});
assert.containsOnce(
target,
".o_calendar_renderer .fc-view-container",
"should instance of fullcalendar"
);
// test view scales
assert.containsNone(
target,
".fc-event",
"By default, only the events of the current user are displayed (0 in this case)"
);
// display all events
await click(target, ".o_calendar_filter_item[data-value='all'] input");
assert.containsN(
target,
".fc-event",
6,
"should display 6 events on the week (4 event + 1 allday + 1 >24h allday)"
);
assert.containsOnce(
target,
".o_event_oneliner",
"should contain 1 oneliner event (the one we add)"
);
await click(target, ".scale_button_selection");
await click(target, ".o_scale_button_day"); // display only one day
assert.containsN(target, ".fc-event", 2, "should display 2 events on the day");
assert.containsOnce(
target.querySelector(".o_calendar_sidebar .o_datetime_picker"),
".o_highlight_start, .o_highlight_end",
"should highlight the target day in mini calendar"
);
await click(target, ".scale_button_selection");
await click(target, ".o_scale_button_month"); // display all the month
// We display the events or partner 1 2 and 4. Partner 2 has nothing and Event 6 is for partner 6 (not displayed)
await click(target, ".o_calendar_filter_item[data-value='all'] input");
await click(
target.querySelector(".o_calendar_filter .o_calendar_filter_item[data-value='1'] input")
);
await click(target, ".o_calendar_filter_item[data-value='2'] input");
assert.containsN(
target,
".fc-event",
8,
"should display 7 events on the month (6 events + 2 week event - 1 'event 6' is filtered + 1 'Undefined event')"
);
// test filters
assert.containsN(
target,
".o_calendar_sidebar .o_calendar_filter",
2,
"should display 2 filters"
);
const typeFilter = target.querySelectorAll(".o_calendar_filter")[1];
assert.ok(!!typeFilter, "should display 'user' filter");
assert.containsN(
typeFilter,
".o_calendar_filter_item",
3,
"should display 3 filter items for 'user'"
);
// filters which has no value should show with string "Undefined", should not have any user image and should show at the last
let lastFilter;
{
const filters = typeFilter.querySelectorAll(".o_calendar_filter_item");
lastFilter = filters[filters.length - 1];
}
assert.strictEqual(
lastFilter.hasAttribute("data-value"),
false,
"filters having false value should be displayed at last in filter items"
);
assert.strictEqual(
lastFilter.querySelector(".o_cw_filter_title").textContent,
"Undefined",
"filters having false value should display 'Undefined' string"
);
assert.containsNone(
lastFilter,
"label img",
"filters having false value should not have any user image"
);
const attendeesFilter = target.querySelectorAll(".o_calendar_filter")[0];
assert.ok(!!attendeesFilter, "should display 'attendees' filter");
assert.containsN(
attendeesFilter,
".o_calendar_filter_item",
3,
"should display 3 filter items for 'attendees' who use write_model (checkall + 2 saved + Everything)"
);
assert.containsOnce(
attendeesFilter,
".o-autocomplete",
"should display one2many search bar for 'attendees' filter"
);
assert.containsN(
target,
".fc-event",
8,
"should display 7 events ('event 5' counts for 2 because it spans two weeks and thus generate two fc-event elements)"
);
await click(target.querySelectorAll(".o_calendar_filter input[type=checkbox]")[1]); // click on partner 2
assert.containsN(target, ".fc-event", 6, "should now only display 6 event");
await click(target.querySelectorAll(".o_calendar_filter input[type=checkbox]")[2]);
assert.containsNone(target, ".fc-event", "should not display any event anymore");
// test search bar in filter
await click(target, ".o_calendar_sidebar input[type=text]");
let autoCompleteItems = document.body.querySelectorAll("ul.ui-autocomplete li");
assert.strictEqual(
autoCompleteItems.length,
2,
"should display 2 choices in one2many autocomplete"
);
await click(autoCompleteItems[0]);
assert.containsN(
attendeesFilter,
".o_calendar_filter_item",
4,
"should display 4 filter items for 'attendees'"
);
await click(target, ".o_calendar_sidebar input[type=text]");
autoCompleteItems = document.body.querySelectorAll("ul.ui-autocomplete li");
assert.strictEqual(
autoCompleteItems[0].textContent,
"partner 4",
"should display the last choice in one2many autocomplete"
);
await click(target.querySelectorAll(".o_calendar_filter_item .o_remove")[1]);
assert.containsN(
attendeesFilter,
".o_calendar_filter_item",
3,
"click on remove then should display 3 filter items for 'attendees'"
);
});
QUnit.test("filter panel autocomplete: updates when typing", async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
</calendar>
`,
});
const section = findFilterPanelSection(target, "partner_ids");
assert.containsNone(section, ".o-autocomplete--dropdown-menu");
assert.containsNone(section, ".o-autocomplete--dropdown-item");
await click(section, ".o-autocomplete--input");
assert.containsOnce(section, ".o-autocomplete--dropdown-menu");
assert.containsN(section, ".o-autocomplete--dropdown-item", 2);
assert.deepEqual(
[...section.querySelectorAll(".o-autocomplete--dropdown-item")].map((el) =>
el.textContent.trim()
),
["partner 3", "partner 4"]
);
const input = section.querySelector(".o-autocomplete--input");
input.value = "partner 3";
await triggerEvent(section, ".o-autocomplete--input", "input");
assert.containsOnce(section, ".o-autocomplete--dropdown-menu");
assert.containsOnce(section, ".o-autocomplete--dropdown-item");
assert.strictEqual(
section.querySelector(".o-autocomplete--dropdown-item").textContent.trim(),
"partner 3"
);
input.value = "a string that would yield to no result as it is too very much convoluted";
await triggerEvent(section, ".o-autocomplete--input", "input");
assert.containsOnce(section, ".o-autocomplete--dropdown-menu");
assert.containsOnce(section, ".o-autocomplete--dropdown-item");
assert.strictEqual(
section.querySelector(".o-autocomplete--dropdown-item").textContent.trim(),
"No records"
);
});
QUnit.test("add a filter with the search more dialog", async (assert) => {
serverData.views["partner,false,search"] = `<search></search>`;
serverData.views["partner,false,list"] = `
<tree>
<field name="display_name" />
</tree>
`;
serverData.models.partner.records.push(
{ id: 5, display_name: "foo partner 5" },
{ id: 6, display_name: "foo partner 6" },
{ id: 7, display_name: "foo partner 7" },
{ id: 8, display_name: "foo partner 8" },
{ id: 9, display_name: "foo partner 9" },
{ id: 10, display_name: "foo partner 10" },
{ id: 11, display_name: "foo partner 11" },
{ id: 12, display_name: "foo partner 12" },
{ id: 13, display_name: "foo partner 13" }
);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
</calendar>
`,
mockRPC(route) {
if (route.endsWith("/has_group")) {
return true;
}
},
});
const section = findFilterPanelSection(target, "partner_ids");
assert.containsN(section, ".o_calendar_filter_item", 3);
assert.deepEqual(
[...section.querySelectorAll(".o_calendar_filter_item")].map((el) =>
el.textContent.trim()
),
["partner 1", "partner 2", "Everything"]
);
// Open the autocomplete dropdown
assert.containsNone(section, ".o-autocomplete--dropdown-menu");
assert.containsNone(section, ".o-autocomplete--dropdown-item");
await click(section, ".o-autocomplete--input");
assert.containsOnce(section, ".o-autocomplete--dropdown-menu");
assert.containsN(section, ".o-autocomplete--dropdown-item", 9);
assert.deepEqual(
[...section.querySelectorAll(".o-autocomplete--dropdown-item")].map((el) =>
el.textContent.trim()
),
[
"partner 3",
"partner 4",
"foo partner 5",
"foo partner 6",
"foo partner 7",
"foo partner 8",
"foo partner 9",
"foo partner 10",
"Search More...",
]
);
// Change the search term
const input = section.querySelector(".o-autocomplete--input");
input.value = "foo";
await triggerEvent(section, ".o-autocomplete--input", "input");
assert.containsOnce(section, ".o-autocomplete--dropdown-menu");
assert.containsN(section, ".o-autocomplete--dropdown-item", 9);
assert.deepEqual(
[...section.querySelectorAll(".o-autocomplete--dropdown-item")].map((el) =>
el.textContent.trim()
),
[
"foo partner 5",
"foo partner 6",
"foo partner 7",
"foo partner 8",
"foo partner 9",
"foo partner 10",
"foo partner 11",
"foo partner 12",
"Search More...",
]
);
// Open the search more dialog
assert.containsNone(target, ".modal");
await click(section, ".o-autocomplete--dropdown-item:last-child");
assert.containsOnce(target, ".modal");
assert.containsN(target, ".modal .o_data_row", 9);
assert.deepEqual(
[...target.querySelectorAll(".modal .o_data_row")].map((el) => el.textContent.trim()),
[
"foo partner 5",
"foo partner 6",
"foo partner 7",
"foo partner 8",
"foo partner 9",
"foo partner 10",
"foo partner 11",
"foo partner 12",
"foo partner 13",
]
);
assert.containsOnce(target, ".modal .o_searchview_facet");
assert.strictEqual(
target.querySelector(".modal .o_searchview_facet").textContent.trim(),
"Quick search: foo"
);
// Choose a record
await click(target, ".modal .o_data_row:first-child > td:first-child");
assert.containsNone(target, ".modal");
assert.containsN(section, ".o_calendar_filter_item", 4);
assert.deepEqual(
[...section.querySelectorAll(".o_calendar_filter_item")].map((el) =>
el.textContent.trim()
),
["foo partner 5", "partner 1", "partner 2", "Everything"]
);
assert.strictEqual(input.value, "");
// Open the autocomplete dropdown
assert.containsNone(section, ".o-autocomplete--dropdown-menu");
assert.containsNone(section, ".o-autocomplete--dropdown-item");
await click(section, ".o-autocomplete--input");
assert.containsOnce(section, ".o-autocomplete--dropdown-menu");
assert.containsN(section, ".o-autocomplete--dropdown-item", 9);
assert.deepEqual(
[...section.querySelectorAll(".o-autocomplete--dropdown-item")].map((el) =>
el.textContent.trim()
),
[
"partner 3",
"partner 4",
"foo partner 6",
"foo partner 7",
"foo partner 8",
"foo partner 9",
"foo partner 10",
"foo partner 11",
"Search More...",
]
);
// Change the search term
input.value = "foo";
await triggerEvent(section, ".o-autocomplete--input", "input");
assert.containsOnce(section, ".o-autocomplete--dropdown-menu");
assert.containsN(section, ".o-autocomplete--dropdown-item", 9);
assert.deepEqual(
[...section.querySelectorAll(".o-autocomplete--dropdown-item")].map((el) =>
el.textContent.trim()
),
[
"foo partner 6",
"foo partner 7",
"foo partner 8",
"foo partner 9",
"foo partner 10",
"foo partner 11",
"foo partner 12",
"foo partner 13",
"Search More...",
]
);
// Open the search more dialog
assert.containsNone(target, ".modal");
await click(section, ".o-autocomplete--dropdown-item:last-child");
assert.containsOnce(target, ".modal");
assert.containsN(target, ".modal .o_data_row", 8);
assert.deepEqual(
[...target.querySelectorAll(".modal .o_data_row")].map((el) => el.textContent.trim()),
[
"foo partner 6",
"foo partner 7",
"foo partner 8",
"foo partner 9",
"foo partner 10",
"foo partner 11",
"foo partner 12",
"foo partner 13",
]
);
assert.containsOnce(target, ".modal .o_searchview_facet");
assert.strictEqual(
target.querySelector(".modal .o_searchview_facet").textContent.trim(),
"Quick search: foo"
);
// Close the search more dialog without choosing a record
await click(target, ".modal .o_form_button_cancel");
assert.containsNone(target, ".modal");
assert.containsN(section, ".o_calendar_filter_item", 4);
assert.deepEqual(
[...section.querySelectorAll(".o_calendar_filter_item")].map((el) =>
el.textContent.trim()
),
["foo partner 5", "partner 1", "partner 2", "Everything"]
);
assert.strictEqual(input.value, "");
});
QUnit.test(
`delete attribute on calendar doesn't show delete button in popover`,
async (assert) => {
assert.expect(2);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar event_open_popup="1" date_start="start" date_stop="stop" all_day="allday" delete="0" mode="month" />
`,
});
await clickEvent(target, 4);
assert.containsOnce(target, ".o_cw_popover", "should open a popover clicking on event");
assert.containsNone(
target,
".o_cw_popover .o_cw_popover_delete",
"should not have the 'Delete' Button"
);
}
);
QUnit.test(`create and change events`, async (assert) => {
assert.expect(29);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar event_open_popup="1" date_start="start" date_stop="stop" all_day="allday" mode="month" />
`,
mockRPC(route, { args, method }) {
if (method === "web_save" && args[0].length !== 0) {
assert.deepEqual(
args[1],
{ name: "event 4 modified" },
"should update the record"
);
}
},
});
assert.containsOnce(target, ".fc-dayGridMonth-view", "should display in month mode");
// click on an existing event to open the formViewDialog
await clickEvent(target, 4);
assert.containsOnce(target, ".o_cw_popover", "should open a popover clicking on event");
assert.containsOnce(
target,
".o_cw_popover .o_cw_popover_edit",
"popover should have an edit button"
);
assert.containsOnce(
target,
".o_cw_popover .o_cw_popover_delete",
"popover should have a delete button"
);
assert.containsOnce(
target,
".o_cw_popover .o_cw_popover_close",
"popover should have a close button"
);
await click(target, ".o_cw_popover .o_cw_popover_edit");
assert.containsOnce(
target,
".modal-body",
"should open the form view in dialog when click on event"
);
await editInput(target.querySelector(".modal-body input"), null, "event 4 modified");
await click(target, ".modal-footer .o_form_button_save");
assert.containsNone(target, ".modal-body", "save button should close the modal");
// create a new event, quick create only
await clickDate(target, "2016-12-13");
assert.containsOnce(
target,
".o-calendar-quick-create",
"should open the quick create dialog"
);
await editInput(target, ".o-calendar-quick-create--input", "new event in quick create");
await click(target, ".o-calendar-quick-create--create-btn");
assert.strictEqual(
findEvent(target, 8).textContent,
"new event in quick create",
"should display the new record after quick create"
);
assert.containsN(
target,
"td.fc-event-container[colspan]",
2,
"should the new record have only one day"
);
// create a new event, quick create only (validated by pressing enter key)
await clickDate(target, "2016-12-13");
assert.containsOnce(
target,
".o-calendar-quick-create",
"should open the quick create dialog"
);
await editInput(
target,
".o-calendar-quick-create--input",
"new event in quick create validated by pressing enter key."
);
await triggerEvent(target, ".o-calendar-quick-create--input", "keyup", { key: "Enter" });
assert.strictEqual(
findEvent(target, 9).textContent,
"new event in quick create validated by pressing enter key.",
"should display the new record by pressing enter key"
);
// create a new event and edit it
await clickDate(target, "2016-12-27");
assert.containsOnce(
target,
".o-calendar-quick-create",
"should open the quick create dialog"
);
await editInput(target, ".o-calendar-quick-create--input", "coucou");
await click(target, ".o-calendar-quick-create--edit-btn");
assert.containsOnce(target, ".modal", "should open the slow create dialog");
assert.strictEqual(
target.querySelector(".modal .modal-title").textContent,
"New Event",
"should use the string attribute as modal title"
);
assert.strictEqual(
target.querySelector(`.modal [name="name"] input`).value,
"coucou",
"should have set the name from the quick create dialog"
);
await click(target, ".modal-footer .o_form_button_save");
assert.strictEqual(
findEvent(target, 10).textContent,
"coucou",
"should display the new record with string attribute"
);
// create a new event with 2 days
await selectDateRange(target, "2016-12-20", "2016-12-21");
await editInput(target, ".o-calendar-quick-create--input", "new event in quick create 2");
await click(target, ".o-calendar-quick-create--edit-btn");
assert.strictEqual(
target.querySelector(`.modal .o_form_view [name="name"] input`).value,
"new event in quick create 2",
"should open the formViewDialog with default values"
);
await click(target, ".modal-footer .o_form_button_save");
assert.containsNone(target, ".modal", "should close dialogs");
const newEvent = findEvent(target, 11);
assert.strictEqual(
newEvent.textContent,
"new event in quick create 2",
"should display the 2 days new record"
);
assert.hasAttrValue(
newEvent.closest(".fc-event-container"),
"colspan",
"2",
"the new record should have 2 days"
);
await clickEvent(target, 11);
const popoverDescription = target.querySelector(".o_cw_popover .list-group-item");
assert.strictEqual(
popoverDescription.children[1].textContent,
"December 20-21, 2016",
"The popover description should indicate the correct range"
);
assert.strictEqual(
popoverDescription.children[2].textContent,
"2 days",
"The popover description should indicate 2 days"
);
await click(target, ".o_cw_popover_close");
// delete the a record
await clickEvent(target, 4);
await click(target, ".o_cw_popover_delete");
assert.strictEqual(
target.querySelector(".modal-title").textContent,
"Bye-bye, record!",
"should display the confirm message"
);
await click(target, ".modal-footer button.btn-primary");
assert.notOk(findEvent(target, 4), "the record should be deleted");
assert.containsN(target, ".fc-event-container .fc-event", 10, "should display 10 events");
// move to next month
await navigate(target, "next");
assert.containsN(target, ".fc-event-container .fc-event", 0, "should display 0 events");
await pickDate(target, "2017-01-01");
await changeScale(target, "month");
assert.containsNone(target, ".fc-event-container .fc-event", "should display 0 events");
await navigate(target, "prev");
await pickDate(target, "2016-12-27");
await changeScale(target, "month");
await selectDateRange(target, "2016-12-20", "2016-12-21");
await editInput(target, ".o-calendar-quick-create--input", "test");
await click(target, ".o-calendar-quick-create--create-btn");
});
QUnit.test(`quickcreate with custom create_name_field`, async (assert) => {
assert.expect(4);
serverData.models.custom_event = {
fields: {
id: { string: "ID", type: "integer" },
x_name: { string: "name", type: "char" },
x_start_date: { string: "start date", type: "date" },
},
records: [{ id: 1, x_name: "some event", x_start_date: "2016-12-06" }],
methods: {
async check_access_rights() {
return true;
},
},
};
await makeView({
type: "calendar",
resModel: "custom_event",
serverData,
arch: `
<calendar date_start="x_start_date" create_name_field="x_name" mode="month" />
`,
mockRPC(route, { args, method }) {
if (method === "create") {
assert.deepEqual(
args[0],
[
{
x_name: "custom event in quick create",
x_start_date: "2016-12-13",
},
],
"the custom create_name_field should be used instead of `name`"
);
}
},
});
// create a new event
await clickDate(target, "2016-12-13");
assert.containsOnce(
target,
".o-calendar-quick-create",
"should open the quick create dialog"
);
await editInput(target, ".o-calendar-quick-create--input", "custom event in quick create");
await click(target, ".o-calendar-quick-create--create-btn");
assert.containsOnce(
target,
`.fc-event[data-event-id="2"]`,
"should display the new custom event record"
);
assert.strictEqual(findEvent(target, 2).textContent, "custom event in quick create");
});
QUnit.test(`quickcreate switching to actual create for required fields`, async (assert) => {
assert.expect(4);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="month" event_open_popup="1" />
`,
mockRPC(route, { method }) {
if (method === "create") {
return Promise.reject({
message: {
code: 200,
data: {},
message: "Odoo server error",
},
event: new Event("server_error"),
});
}
},
});
// create a new event
await clickDate(target, "2016-12-13");
assert.strictEqual(
target.querySelector(".modal-title").textContent,
"New Event",
"should open the quick create dialog"
);
await editInput(target, ".o-calendar-quick-create--input", "custom event in quick create");
await click(target, ".o-calendar-quick-create--create-btn");
assert.containsNone(target, ".o-calendar-quick-create");
assert.strictEqual(
target.querySelector(".modal-title").textContent,
"New Event",
"should have switched to a bigger modal for an actual create rather than quickcreate"
);
assert.containsOnce(
target,
".modal .o_form_view .o_form_editable",
"should open the full event form view in a dialog"
);
});
QUnit.test(`open multiple event form at the same time`, async (assert) => {
let counter = 0;
patchWithCleanup(dialogService, {
start() {
const result = super.start(...arguments);
return {
...result,
add() {
counter++;
return result.add(...arguments);
},
};
},
});
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="month" event_open_popup="1" quick_create="0">
<field name="name" />
</calendar>
`,
});
for (let i = 0; i < 5; i++) {
await clickDate(target, "2016-12-13");
}
await nextTick();
assert.strictEqual(counter, 5, "there should had been 5 attemps to open a modal");
assert.containsOnce(target, ".modal", "there should be only one open modal");
});
QUnit.test(`create event with timezone in week mode European locale`, async (assert) => {
assert.expect(4);
serverData.models.event.records = [];
patchTimeZone(120);
patchWithCleanup(localization, defaultLocalization);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="week" event_open_popup="1">
<field name="name" />
<field name="start" />
<field name="allday" />
</calendar>
`,
mockRPC(route, { method, args }) {
if (method === "create") {
assert.deepEqual(
args[0],
[
{
allday: false,
name: "new event",
start: "2016-12-13 06:00:00",
stop: "2016-12-13 08:00:00",
},
],
"should create this event"
);
}
},
});
await selectTimeRange(target, "2016-12-13 08:00:00", "2016-12-13 10:00:00");
assert.strictEqual(
target.querySelector(".fc-content .fc-time").textContent,
"8:00 - 10:00",
"should display the time in the calendar sticker"
);
await editInput(target, ".o-calendar-quick-create--input", "new event");
await click(target, ".o-calendar-quick-create--create-btn");
assert.strictEqual(
target.querySelector(".fc-event .o_event_title").textContent,
"new event",
"should display the new event with title"
);
// delete record
await clickEvent(target, 1);
await click(target, ".o_cw_popover .o_cw_popover_delete");
await click(target, ".modal button.btn-primary");
assert.containsNone(target, ".fc-content", "should delete the record");
});
QUnit.test(`default week start (US)`, async (assert) => {
// if not given any option, default week start is on Sunday
assert.expect(3);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="week" />
`,
mockRPC(route, { method, model, kwargs }) {
if (model === "event" && method === "search_read") {
assert.deepEqual(
kwargs.domain,
[
["start", "<=", "2016-12-17 22:59:59"],
["stop", ">=", "2016-12-10 23:00:00"],
],
"The domain to search events in should be correct"
);
}
},
});
const dayHeaders = target.querySelectorAll(".fc-day-header .o_cw_day_name");
assert.strictEqual(
dayHeaders[0].textContent,
"Sun",
"The first day of the week should be Sunday"
);
assert.strictEqual(
dayHeaders[dayHeaders.length - 1].textContent,
"Sat",
"The last day of the week should be Saturday"
);
});
QUnit.test(`European week start`, async (assert) => {
assert.expect(3);
// the week start depends on the locale
patchWithCleanup(localization, { weekStart: 1 });
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="week" />
`,
mockRPC(route, { method, model, kwargs }) {
if (model === "event" && method === "search_read") {
// called twice (once for records and once for filters)
assert.deepEqual(
kwargs.domain,
[
["start", "<=", "2016-12-18 22:59:59"],
["stop", ">=", "2016-12-11 23:00:00"],
],
"The domain to search events in should be correct"
);
}
},
});
const dayHeaders = target.querySelectorAll(".fc-day-header .o_cw_day_name");
assert.strictEqual(
dayHeaders[0].textContent,
"Mon",
"The first day of the week should be Monday"
);
assert.strictEqual(
dayHeaders[dayHeaders.length - 1].textContent,
"Sun",
"The last day of the week should be Sunday"
);
});
QUnit.test(`week numbering`, async (assert) => {
// Using ISO week calculation, get the ISO week number of
// the Monday nearest to the start of the week.
patchWithCleanup(localization, { weekStart: 7 });
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="week" />
`,
});
assert.strictEqual(target.querySelector(".fc-week-number").textContent, "Week 50");
});
QUnit.test(`render popover`, async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="week">
<field name="name" string="Custom Name" />
<field name="partner_id" />
</calendar>
`,
});
await clickEvent(target, 2);
assert.containsOnce(target, ".o_cw_popover", "should open a popover clicking on event");
assert.strictEqual(
target.querySelector(".o_cw_popover .popover-header").textContent,
"event 2",
"popover should have a title 'event 2'"
);
assert.containsOnce(
target,
".o_cw_popover .o_cw_popover_edit",
"popover should have an edit button"
);
assert.containsOnce(
target,
".o_cw_popover .o_cw_popover_delete",
"popover should have a delete button"
);
assert.containsOnce(
target,
".o_cw_popover .o_cw_popover_close",
"popover should have a close button"
);
assert.strictEqual(
target.querySelectorAll(".o_cw_popover .list-group-item")[0].textContent.trim(),
"December 12, 2016",
"should display date 'December 14, 2016'"
);
assert.strictEqual(
target.querySelectorAll(".o_cw_popover .list-group-item")[1].textContent.trim(),
"11:55 - 15:55 (4 hours)"
);
assert.containsN(
target,
".o_cw_popover .o_cw_popover_fields_secondary .list-group-item",
2,
"popover should have a two fields"
);
const groups = target.querySelectorAll(
".o_cw_popover .o_cw_popover_fields_secondary .list-group-item"
);
assert.containsOnce(groups[0], ".o_field_char", "should apply char widget");
assert.strictEqual(
groups[0].querySelector("span.fw-bold").textContent,
"Custom Name",
"label should be a 'Custom Name'"
);
assert.strictEqual(
groups[0].querySelector(".o_field_char").textContent,
"event 2",
"value should be a 'event 2'"
);
assert.containsOnce(groups[1], ".o_form_uri", "should apply m20 widget");
assert.strictEqual(
groups[1].querySelector("span.fw-bold").textContent,
"user",
"label should be a 'user'"
);
assert.strictEqual(
groups[1].querySelector(".o_form_uri").textContent,
"partner 1",
"value should be a 'partner 1'"
);
await click(target, ".o_cw_popover .o_cw_popover_close");
assert.containsNone(target, ".o_cw_popover", "should close a popover");
});
QUnit.test(`render popover with modifiers`, async (assert) => {
serverData.models.event.fields.priority = {
string: "Priority",
type: "selection",
selection: [
["0", "Normal"],
["1", "Important"],
],
};
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="week">
<field name="priority" widget="priority" readonly="1" />
<field name="is_hatched" invisible="1" />
<field name="partner_id" invisible="not is_hatched" />
<field name="start" invisible="is_hatched" />
</calendar>
`,
});
await clickEvent(target, 4);
assert.containsOnce(target, ".o_cw_popover", "should open a popover clicking on event");
assert.containsOnce(
target,
".o_cw_popover .o_priority span.o_priority_star",
"priority field should not be editable"
);
assert.containsNone(
target,
".o_cw_popover li.o_invisible_modifier",
"partner_id field should be invisible"
);
assert.containsOnce(
target,
".o_cw_popover .o_field_datetime",
"The start date and time should be visible"
);
await click(target, ".o_cw_popover .o_cw_popover_close");
assert.containsNone(target, ".o_cw_popover", "should close a popover");
});
QUnit.test("render popover: inside fullcalendar popover", async (assert) => {
assert.expect(13);
// add 10 records the same day
serverData.models.event.records = Array.from({ length: 10 }).map((_, i) => ({
id: i + 1,
name: `event ${i + 1}`,
start: "2016-12-14 10:00:00",
stop: "2016-12-14 15:00:00",
user_id: uid,
}));
let expectedRequest;
serviceRegistry.add(
"action",
{
...actionService,
start() {
const result = actionService.start(...arguments);
const doAction = result.doAction;
result.doAction = (request) => {
assert.deepEqual(request, expectedRequest);
return doAction(request);
};
return result;
},
},
{ force: true }
);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="month">
<field name="name" string="Custom Name" />
<field name="partner_id" />
</calendar>
`,
mockRPC(route, { method }) {
if (method === "get_formview_id") {
return Promise.resolve(false);
}
},
});
const visibleEventsSelector = ":not(.fc-limited) > :not(.fc-limited) > .fc-event";
assert.containsN(target, visibleEventsSelector, 4);
assert.containsOnce(target, ".fc-more");
assert.strictEqual(target.querySelector(".fc-more").textContent, "+6 more");
assert.containsNone(target, ".fc-popover");
await click(target, ".fc-more");
assert.containsOnce(target, ".fc-popover");
assert.containsN(target, `.fc-popover ${visibleEventsSelector}`, 10);
assert.containsNone(target, ".o_cw_popover");
await click(target, ".fc-popover .fc-event:nth-child(1)");
assert.containsOnce(target, ".o_cw_popover");
await triggerEvent(target, ".o_cw_popover .o_cw_popover_edit", "mousedown");
assert.containsOnce(target, ".o_cw_popover");
assert.containsOnce(target, ".fc-popover");
expectedRequest = {
type: "ir.actions.act_window",
res_model: "event",
res_id: 1,
views: [[false, "form"]],
target: "current",
context: {},
};
await click(target, ".o_cw_popover .o_cw_popover_edit");
assert.containsNone(target, ".o_cw_popover");
assert.containsOnce(target, ".fc-popover");
});
QUnit.test(`attributes hide_date and hide_time`, async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" hide_date="1" hide_time="1" mode="month" />
`,
});
await clickEvent(target, 4);
assert.containsNone(
target,
".o_cw_popover .list-group-item",
"popover should not contain date/time"
);
});
QUnit.test(
`create event with timezone in week mode with formViewDialog European locale`,
async (assert) => {
assert.expect(7);
patchWithCleanup(localization, defaultLocalization);
patchTimeZone(120);
serverData.models.event.records = [];
serverData.models.event.onchanges = {
allday(obj) {
if (obj.allday) {
obj.start_date = (obj.start && obj.start.split(" ")[0]) || obj.start_date;
obj.stop_date =
(obj.stop && obj.stop.split(" ")[0]) || obj.stop_date || obj.start_date;
} else {
obj.start = (obj.start_date && obj.start_date + " 00:00:00") || obj.start;
obj.stop =
(obj.stop_date && obj.stop_date + " 00:00:00") || obj.stop || obj.start;
}
},
};
let expectedEvent;
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="week" event_open_popup="1">
<field name="name" />
</calendar>
`,
mockRPC(route, { args, kwargs, method }) {
if (method === "web_save") {
assert.deepEqual(
kwargs.context,
{
default_name: "new event",
default_start: "2016-12-13 06:00:00",
default_stop: "2016-12-13 08:00:00",
default_allday: false,
lang: "en",
tz: "taht",
uid: 7,
},
"should send the context to create events"
);
} else if (method === "write") {
assert.deepEqual(args[1], expectedEvent, "should move the event");
}
},
});
await selectTimeRange(target, "2016-12-13 08:00:00", "2016-12-13 10:00:00");
await editInput(target, ".o-calendar-quick-create--input", "new event");
await click(target, ".o-calendar-quick-create--edit-btn");
let input = target.querySelector(".o_field_widget[name='start'] input");
assert.strictEqual(input.value, "12/13/2016 08:00:00", "should display the datetime");
// Set allday to true in formViewDialog
await click(target, ".modal .o_field_boolean[name='allday'] input");
input = target.querySelector(".o_field_widget[name='start_date'] input");
assert.strictEqual(input.value, "12/13/2016", "should display the date");
await click(target, ".modal .o_field_boolean[name='allday'] input");
input = target.querySelector(".o_field_widget[name='start'] input");
assert.strictEqual(
input.value,
"12/13/2016 02:00:00",
"should display the datetime from the date with the timezone"
);
// use datepicker to enter a date: 12/13/2016 08:00:00
await click(target, `.o_field_widget[name="start"] input`);
await editSelect(getTimePickers().at(0).at(0), null, "8");
// use datepicker to enter a date: 12/13/2016 10:00:00
await click(target, `.o_field_widget[name="stop"] input`);
await editSelect(getTimePickers().at(0).at(0), null, "10");
await click(target, ".modal-footer .o_form_button_save");
assert.strictEqual(
findEvent(target, 1).querySelector(".o_event_title").textContent,
"new event",
"should display the new event with title"
);
// Move this event to another day
expectedEvent = {
allday: false,
start: "2016-12-12 06:00:00",
stop: "2016-12-12 08:00:00",
};
await moveEventToTime(target, 1, "2016-12-12 08:00:00");
// Move to "All day"
expectedEvent = {
allday: true,
start: "2016-12-12",
stop: "2016-12-12",
};
await moveEventToAllDaySlot(target, 1, "2016-12-12");
}
);
QUnit.test(`create event with timezone in week mode American locale`, async (assert) => {
assert.expect(3);
patchWithCleanup(localization, defaultLocalization);
patchTimeZone(120);
serverData.models.event.records = [];
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" event_open_popup="1">
<field name="name" />
<field name="start" />
<field name="allday" />
</calendar>
`,
mockRPC(route, { kwargs, method }) {
if (method === "create") {
assert.deepEqual(
kwargs.context,
{
default_name: "new event",
default_start: "2016-12-13 04:00:00",
default_stop: "2016-12-13 06:00:00",
default_allday: false,
lang: "en",
tz: "taht",
uid: 7,
},
"should send the context to create events"
);
}
},
});
await selectTimeRange(target, "2016-12-13 06:00:00", "2016-12-13 08:00:00");
await editInput(target, ".o-calendar-quick-create--input", "new event");
await click(target, ".o-calendar-quick-create--create-btn");
assert.strictEqual(
findEvent(target, 1).querySelector(".o_event_title").textContent,
"new event",
"should display the new event with title"
);
// delete record
await clickEvent(target, 1);
await click(target, ".o_cw_popover .o_cw_popover_delete");
await click(target, ".modal button.btn-primary");
assert.containsNone(target, ".fc-content", "should delete the record");
});
QUnit.test(`fetch event when being in timezone`, async (assert) => {
assert.expect(3);
patchTimeZone(660);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="week">
<field name="name" />
<field name="start" />
<field name="allday" />
</calendar>
`,
mockRPC(route, { kwargs, method, model }) {
if (method === "search_read" && model === "event") {
assert.deepEqual(
kwargs.domain,
[
["start", "<=", "2016-12-17 12:59:59"],
["stop", ">=", "2016-12-10 13:00:00"],
],
"The domain should contain the right range"
);
}
},
});
const headers = target.querySelectorAll(".fc-day-header .o_cw_day_number");
assert.strictEqual(
headers[0].textContent,
"11",
"The calendar start date should be 2016-12-11"
);
assert.strictEqual(
headers[headers.length - 1].textContent,
"17",
"The calendar start date should be 2016-12-17"
);
});
QUnit.test(
`create event with timezone in week mode with formViewDialog American locale`,
async (assert) => {
assert.expect(7);
patchWithCleanup(localization, defaultLocalization);
patchTimeZone(120);
serverData.models.event.records = [];
serverData.models.event.onchanges = {
allday(obj) {
if (obj.allday) {
obj.start_date = (obj.start && obj.start.split(" ")[0]) || obj.start_date;
obj.stop_date =
(obj.stop && obj.stop.split(" ")[0]) || obj.stop_date || obj.start_date;
} else {
obj.start = (obj.start_date && obj.start_date + " 00:00:00") || obj.start;
obj.stop =
(obj.stop_date && obj.stop_date + " 00:00:00") || obj.stop || obj.start;
}
},
};
let expectedEvent = null;
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="week" event_open_popup="1">
<field name="name" />
</calendar>
`,
mockRPC(route, { args, kwargs, method }) {
if (method === "web_save") {
assert.deepEqual(
kwargs.context,
{
default_name: "new event",
default_start: "2016-12-13 06:00:00",
default_stop: "2016-12-13 08:00:00",
default_allday: false,
lang: "en",
tz: "taht",
uid: 7,
},
"should send the context to create events"
);
} else if (method === "write") {
assert.deepEqual(args[1], expectedEvent, "should move the event");
}
},
});
await selectTimeRange(target, "2016-12-13 08:00:00", "2016-12-13 10:00:00");
await editInput(target, ".o-calendar-quick-create--input", "new event");
await click(target, ".o-calendar-quick-create--edit-btn");
assert.strictEqual(
target.querySelector(`.o_field_widget[name="start"] input`).value,
"12/13/2016 08:00:00",
"should display the datetime"
);
await click(target, `.modal .o_field_boolean[name="allday"] input`);
assert.strictEqual(
target.querySelector(`.o_field_widget[name="start_date"] input`).value,
"12/13/2016",
"should display the date"
);
await click(target, `.modal .o_field_boolean[name="allday"] input`);
assert.strictEqual(
target.querySelector(`.o_field_widget[name="start"] input`).value,
"12/13/2016 02:00:00",
"should display the datetime from the date with the timezone"
);
// use datepicker to enter a date: 12/13/2016 08:00:00
await click(target, `.o_field_widget[name="start"] input`);
await editSelect(getTimePickers().at(0).at(0), null, "8");
// use datepicker to enter a date: 12/13/2016 10:00:00
await click(target, `.o_field_widget[name="stop"] input`);
await editSelect(getTimePickers().at(0).at(0), null, "10");
await click(target, ".modal-footer button.btn-primary:not(.d-none)");
assert.strictEqual(
findEvent(target, 1).querySelector(".o_event_title").textContent,
"new event",
"should display the new event with title"
);
// Move this event to another day
expectedEvent = {
allday: false,
start: "2016-12-12 06:00:00",
stop: "2016-12-12 08:00:00",
};
await moveEventToTime(target, 1, "2016-12-12 08:00:00");
// Move to "All day"
expectedEvent = {
allday: true,
start: "2016-12-12",
stop: "2016-12-12",
};
await moveEventToAllDaySlot(target, 1, "2016-12-12");
}
);
QUnit.test(`check calendar week column timeformat`, async (assert) => {
patchWithCleanup(localization, { timeFormat: "hh:mm:ss" });
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" />
`,
});
assert.strictEqual(
findTimeRow(target, "08:00:00").textContent,
"8am",
"calendar should show according to timeformat"
);
assert.strictEqual(
findTimeRow(target, "23:00:00").textContent,
"11pm",
"event time format should 12 hour"
);
});
QUnit.test(`create all day event in week mode`, async (assert) => {
assert.expect(3);
patchTimeZone(120);
serverData.models.event.records = [];
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="week" event_open_popup="1">
<field name="name" />
</calendar>
`,
mockRPC(route, { args, method }) {
if (method === "create") {
assert.deepEqual(args[0], [
{
name: "new event",
start: "2016-12-14",
stop: "2016-12-15",
allday: true,
},
]);
}
},
});
await selectAllDayRange(target, "2016-12-14", "2016-12-15");
await editInput(target, ".o-calendar-quick-create--input", "new event");
await click(target, ".o-calendar-quick-create--create-btn");
const event = findEvent(target, 1);
assert.strictEqual(
event.textContent.replace(/[\s\n\r]+/g, ""),
"newevent",
"should display the new event with time and title"
);
assert.hasAttrValue(event.parentElement, "colspan", "2", "should appear over two days.");
});
QUnit.test("create all day event in month mode: utc-11", async (assert) => {
assert.expect(3);
patchTimeZone(-660);
serverData.models.event.records = [];
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="month" event_open_popup="1">
<field name="name" />
</calendar>
`,
mockRPC(route, { args, method }) {
if (method === "create") {
assert.deepEqual(args[0], [
{
name: "new event",
start: "2016-12-14",
stop: "2016-12-14",
allday: true,
},
]);
}
},
});
await clickDate(target, "2016-12-14");
await editInput(target, ".o-calendar-quick-create--input", "new event");
await click(target, ".o-calendar-quick-create--create-btn");
const event = findEvent(target, 1);
assert.strictEqual(
event.textContent.replace(/[\s\n\r]+/g, ""),
"newevent",
"should display the new event with time and title"
);
const evBox = event.getBoundingClientRect();
const dateCell = target.querySelector(`[data-date="2016-12-14"]`);
const dtBox = dateCell.getBoundingClientRect();
assert.ok(
evBox.left >= dtBox.left &&
evBox.right <= dtBox.right &&
evBox.top >= dtBox.top &&
evBox.bottom <= dtBox.bottom,
"event should be inside the proper date cell"
);
});
QUnit.test("create all day event in year mode: utc-11", async (assert) => {
assert.expect(2);
patchTimeZone(-660);
serverData.models.event.records = [];
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="year" event_open_popup="1">
<field name="name" />
</calendar>
`,
mockRPC(route, { args, method }) {
if (method === "create") {
assert.deepEqual(args[0], [
{
name: "new event",
start: "2016-12-14",
stop: "2016-12-14",
allday: true,
},
]);
}
},
});
await clickDate(target, "2016-12-14");
await editInput(target, ".o-calendar-quick-create--input", "new event");
await click(target, ".o-calendar-quick-create--create-btn");
const event = findEvent(target, 1);
const evBox = event.getBoundingClientRect();
const dateCell = target.querySelector(`[data-date="2016-12-14"]`);
const dtBox = dateCell.getBoundingClientRect();
assert.ok(
evBox.left >= dtBox.left &&
evBox.right <= dtBox.right &&
evBox.top >= dtBox.top &&
evBox.bottom <= dtBox.bottom,
"event should be inside the proper date cell"
);
});
QUnit.test(`create event with default context (no quickCreate)`, async (assert) => {
assert.expect(3);
patchTimeZone(120);
serverData.models.event.records = [];
serviceRegistry.add(
"action",
{
...actionService,
start() {
const result = actionService.start(...arguments);
const doAction = result.doAction;
result.doAction = (request) => {
assert.step("doAction");
assert.deepEqual(
request.context,
{
default_name: "New",
default_start: "2016-12-14",
default_stop: "2016-12-15",
default_allday: true,
lang: "en",
tz: "taht",
uid: 7,
},
"should send the correct data to create events"
);
return doAction(request);
};
return result;
},
},
{ force: true }
);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="week" all_day="allday" quick_create="0" />
`,
context: {
default_name: "New",
},
});
await selectAllDayRange(target, "2016-12-14", "2016-12-15");
assert.verifySteps(["doAction"]);
});
QUnit.test(`create event with default title in context (with quickCreate)`, async (assert) => {
assert.expect(1);
serverData.models.event.records = [];
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="week" all_day="allday" />
`,
context: {
default_name: "Example Title",
},
});
await selectAllDayRange(target, "2016-12-14", "2016-12-15");
const input = target.querySelector(".o-calendar-quick-create--input");
assert.strictEqual(input.value, "Example Title");
});
QUnit.test(`create all day event in week mode (no quickCreate)`, async (assert) => {
assert.expect(1);
patchTimeZone(120);
serverData.models.event.records = [];
serviceRegistry.add(
"action",
{
...actionService,
start() {
const result = actionService.start(...arguments);
const doAction = result.doAction;
result.doAction = (request) => {
assert.deepEqual(
request.context,
{
default_start: "2016-12-14",
default_stop: "2016-12-15",
default_allday: true,
lang: "en",
tz: "taht",
uid: 7,
},
"should send the correct data to create events"
);
return doAction(request);
};
return result;
},
},
{ force: true }
);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="week" quick_create="0" />
`,
});
await selectAllDayRange(target, "2016-12-14", "2016-12-15");
});
QUnit.test(`create event in month mode`, async (assert) => {
assert.expect(3);
patchTimeZone(120);
serverData.models.event.records = [];
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="month" event_open_popup="1">
<field name="name" />
</calendar>
`,
mockRPC(route, { args, method }) {
if (method === "create") {
assert.deepEqual(
args[0],
[
{
name: "new event",
start: "2016-12-14 05:00:00",
stop: "2016-12-15 17:00:00",
},
],
"should send the correct data to create events"
);
}
},
});
await selectDateRange(target, "2016-12-14", "2016-12-15");
await editInput(target, ".o-calendar-quick-create--input", "new event");
await click(target, ".o-calendar-quick-create--create-btn");
const event = findEvent(target, 1);
assert.strictEqual(
event.textContent.replace(/[\s\n\r]+/g, ""),
"newevent",
"should display the new event with time and title"
);
assert.hasAttrValue(event.parentElement, "colspan", "2", "should appear over two days.");
});
QUnit.test(`use mini calendar`, async (assert) => {
patchTimeZone(120);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="week" event_open_popup="1" />
`,
});
assert.containsOnce(target, ".fc-timeGridWeek-view", "should be in week mode");
assert.containsN(
target,
".fc-event",
5,
"should display 5 events on the week (4 event + 1 >24h event)"
);
await pickDate(target, "2016-12-19");
// Clicking on a day in another week should switch to the other week view
assert.containsOnce(target, ".fc-timeGridWeek-view", "should be in week mode");
assert.containsN(
target,
".fc-event",
2,
"should display 4 events on the week (1 event + 1 >24h event)"
);
// Clicking on a day in the same week should switch to that particular day view
await pickDate(target, "2016-12-18");
assert.containsOnce(target, ".fc-timeGridDay-view", "should be in day mode");
assert.containsN(target, ".fc-event", 2, "should display 2 events on the day");
// Clicking on the same day should toggle between day, month and week views
await pickDate(target, "2016-12-18");
assert.containsOnce(target, ".fc-dayGridMonth-view", "should be in month mode");
assert.containsN(
target,
".fc-event",
7,
"should display 7 events on the month (event 5 is on multiple weeks and generates to .fc-event)"
);
await pickDate(target, "2016-12-18");
assert.containsOnce(target, ".fc-timeGridWeek-view", "should be in week mode");
assert.containsN(
target,
".fc-event",
2,
"should display 4 events on the week (1 event + 1 >24h event)"
);
await pickDate(target, "2016-12-18");
assert.containsOnce(target, ".fc-timeGridDay-view", "should be in day mode");
assert.containsN(target, ".fc-event", 2, "should display 2 events on the day");
});
QUnit.test(`rendering, with many2many`, async (assert) => {
serverData.models.event.fields.partner_ids.type = "many2many";
serverData.models.event.records[0].partner_ids = [1, 2, 3, 4, 5];
serverData.models.partner.records.push({ id: 5, display_name: "partner 5", image: "EEE" });
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" event_open_popup="1">
<field name="partner_ids" widget="many2many_tags_avatar" avatar_field="image" write_model="filter_partner" write_field="partner_id" />
</calendar>
`,
});
assert.containsN(
target,
".o_calendar_filter_item .o_cw_filter_avatar",
3,
"should have 3 avatars in the side bar"
);
await toggleFilter(target, "partner_ids", "all");
// Event 1
await clickEvent(target, 4);
assert.containsOnce(target, ".o_cw_popover", "should open a popover clicking on event");
assert.containsOnce(target, ".o_cw_popover img", "should have 1 avatar");
// Event 2
await clickEvent(target, 1);
assert.containsOnce(target, ".o_cw_popover", "should open a popover clicking on event");
assert.containsN(target, ".o_cw_popover img", 5, "should have 5 avatar");
});
QUnit.test(`open form view`, async (assert) => {
assert.expect(2);
let expectedRequest;
serviceRegistry.add(
"action",
{
...actionService,
start() {
const result = actionService.start(...arguments);
const doAction = result.doAction;
result.doAction = (request) => {
assert.deepEqual(request, expectedRequest);
return doAction(request);
};
return result;
},
},
{ force: true }
);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="month" />
`,
mockRPC(route, { method }) {
if (method === "get_formview_id") {
return Promise.resolve("A view");
}
},
});
// click on an existing event to open the form view
expectedRequest = {
type: "ir.actions.act_window",
res_id: 4,
res_model: "event",
views: [["A view", "form"]],
target: "current",
context: {},
};
await clickEvent(target, 4);
await click(target, ".o_cw_popover .o_cw_popover_edit");
// create a new event and edit it
await clickDate(target, "2016-12-27");
await editInput(target, ".o-calendar-quick-create--input", "coucou");
expectedRequest = {
type: "ir.actions.act_window",
res_model: "event",
views: [[false, "form"]],
target: "current",
context: {
default_name: "coucou",
default_start: "2016-12-27",
default_stop: "2016-12-27",
default_allday: true,
lang: "en",
tz: "taht",
uid: 7,
},
};
await click(target, ".o-calendar-quick-create--edit-btn");
});
QUnit.test(`create and edit event in month mode (all_day: false)`, async (assert) => {
assert.expect(1);
patchTimeZone(-240);
serviceRegistry.add(
"action",
{
...actionService,
start() {
const result = actionService.start(...arguments);
const doAction = result.doAction;
result.doAction = (request) => {
assert.deepEqual(
request,
{
type: "ir.actions.act_window",
res_model: "event",
views: [[false, "form"]],
target: "current",
context: {
default_name: "coucou",
default_start: "2016-12-27 11:00:00", // 7:00 + 4h
default_stop: "2016-12-27 23:00:00", // 19:00 + 4h
default_allday: true,
lang: "en",
tz: "taht",
uid: 7,
},
},
"should open the form view with the context default values"
);
return doAction(request);
};
return result;
},
},
{ force: true }
);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="month" />
`,
});
// create a new event and edit it
await clickDate(target, "2016-12-27");
await editInput(target, ".o-calendar-quick-create--input", "coucou");
await click(target, ".o-calendar-quick-create--edit-btn");
});
QUnit.test(`show start time of single day event`, async (assert) => {
patchTimeZone(-240);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="month" />
`,
});
assert.strictEqual(
findEvent(target, 2).querySelector(".fc-content .fc-time").textContent,
"06:55",
"should have a correct time 06:55 AM in month mode"
);
assert.containsNone(
findEvent(target, 4),
".fc-content .fc-time",
"should not display a time for all day event"
);
assert.containsNone(
findEvent(target, 5),
".fc-content .fc-time",
"should not display a time for multiple days event"
);
// switch to week mode
await changeScale(target, "week");
assert.containsOnce(findEvent(target, 2), ".fc-content .fc-time");
});
QUnit.test(`start time should not shown for date type field`, async (assert) => {
patchTimeZone(-240);
serverData.models.event.fields.start.type = "date";
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="month" />
`,
});
assert.containsNone(
findEvent(target, 2),
".fc-content .fc-time",
"should not show time for date type field"
);
await changeScale(target, "week");
assert.containsNone(findEvent(target, 2), ".fc-content .fc-time");
await changeScale(target, "day");
assert.containsNone(findEvent(target, 2), ".fc-content .fc-time");
});
QUnit.test(`start time should not shown if hide_time is true`, async (assert) => {
patchTimeZone(-240);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="month" hide_time="1" />
`,
});
assert.containsNone(
findEvent(target, 2),
".fc-content .fc-time",
"should not show time for hide_time attribute"
);
await changeScale(target, "week");
assert.containsNone(findEvent(target, 2), ".fc-content .fc-time");
await changeScale(target, "day");
assert.containsNone(findEvent(target, 2), ".fc-content .fc-time");
});
QUnit.test(`readonly date_start field`, async (assert) => {
assert.expect(3);
serverData.models.event.fields.start.readonly = true;
let expectedRequest;
serviceRegistry.add(
"action",
{
...actionService,
start() {
const result = actionService.start(...arguments);
const doAction = result.doAction;
result.doAction = (request) => {
assert.deepEqual(request, expectedRequest);
return doAction(request);
};
return result;
},
},
{ force: true }
);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="month" />
`,
mockRPC(route, { method }) {
if (method === "get_formview_id") {
return Promise.resolve(false);
}
},
});
assert.containsNone(target, ".fc-resizer", "should not have resize button");
expectedRequest = {
type: "ir.actions.act_window",
res_id: 4,
res_model: "event",
views: [[false, "form"]],
target: "current",
context: {},
};
await clickEvent(target, 4);
await click(target, ".o_cw_popover .o_cw_popover_edit");
// create a new event and edit it
await clickDate(target, "2016-12-27");
await editInput(target, ".o-calendar-quick-create--input", "coucou");
expectedRequest = {
type: "ir.actions.act_window",
res_model: "event",
views: [[false, "form"]],
target: "current",
context: {
default_name: "coucou",
default_start: "2016-12-27",
default_stop: "2016-12-27",
default_allday: true,
lang: "en",
tz: "taht",
uid: 7,
},
};
await click(target, ".o-calendar-quick-create--edit-btn");
});
QUnit.test(`check filters with filter_field specified`, async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="week">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" filter_field="partner_checked" />
</calendar>
`,
});
assert.containsOnce(
findFilterPanelFilter(target, "partner_ids", 2),
"input:checked",
"checkbox should be checked"
);
await toggleFilter(target, "partner_ids", 2);
assert.containsNone(
findFilterPanelFilter(target, "partner_ids", 2),
"input:checked",
"checkbox should not be checked"
);
assert.strictEqual(
serverData.models.filter_partner.records.find((r) => r.id === 2).partner_checked,
false,
"the status of this filter should now be false"
);
await changeScale(target, "week"); // trick to reload the entire view
assert.containsNone(
findFilterPanelFilter(target, "partner_ids", 2),
"input:checked",
"checkbox should not be checked after the reload"
);
assert.strictEqual(
serverData.models.filter_partner.records.find((r) => r.id === 2).partner_checked,
false,
"the status of this filter should still be false after the reload"
);
});
QUnit.test('"all" filter', async (assert) => {
assert.expect(8);
let requestCount = 0;
const interval = [
["start", "<=", "2016-12-17 22:59:59"],
["stop", ">=", "2016-12-10 23:00:00"],
];
const expectedDomains = [
interval.concat([["partner_ids", "in", []]]),
interval.concat([["partner_ids", "in", [1]]]),
interval.concat([["partner_ids", "in", [1, 2]]]),
interval,
];
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar event_open_popup="1" date_start="start" date_stop="stop" all_day="allday" mode="week" attendee="partner_ids" color="partner_id">
<filter name="user_id" avatar_field="image" />
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
</calendar>
`,
mockRPC(route, { kwargs, method, model }) {
if (method === "search_read" && model === "event") {
assert.deepEqual(kwargs.domain, expectedDomains[requestCount]);
requestCount++;
}
},
});
// By default, no user is selected
assert.containsNone(target, ".fc-event", "should not display any event on the week");
await toggleFilter(target, "partner_ids", 1);
assert.containsN(target, ".fc-event", 4, "should display 4 events on the week");
await toggleFilter(target, "partner_ids", 2);
assert.containsN(target, ".fc-event", 5, "should display 5 events on the week");
// Click on the "all" filter to reload all events
await toggleFilter(target, "partner_ids", "all");
assert.containsN(target, ".fc-event", 5, "should display 5 events on the week");
});
QUnit.test("dynamic filters with selection fields", async (assert) => {
serverData.models.event.fields.selection = {
name: "selection",
string: "Ambiance",
type: "selection",
selection: [
["desert", "Desert"],
["forest", "Forest"],
],
};
serverData.models.event.records[0].selection = "forest";
serverData.models.event.records[1].selection = "desert";
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: /* xml */ `
<calendar date_start="start" date_stop="stop">
<field name="selection" filters="1" />
</calendar>
`,
});
const section = findFilterPanelSection(target, "selection");
assert.deepEqual(section.querySelector(".o_cw_filter_label").textContent, "Ambiance");
assert.deepEqual(
[...section.querySelectorAll(".o_calendar_filter_item")].map((el) =>
el.textContent.trim()
),
["Desert", "Forest", "Undefined"]
);
});
QUnit.test("Colors: cycling through available colors", async (assert) => {
serverData.models.filter_partner.records = Array.from({ length: 56 }, (_, i) => ({
id: i + 1,
user_id: uid,
partner_id: i + 1,
partner_checked: true,
}));
serverData.models.partner.records = Array.from({ length: 56 }, (_, i) => ({
id: i + 1,
display_name: `partner ${i + 1}`,
}));
serverData.models.event.records = Array.from({ length: 56 }, (_, i) => ({
id: i + 1,
user_id: uid,
partner_id: i + 1,
name: `event ${i + 1}`,
start: `2016-12-12 0${i % 10}:00:00`,
stop: `2016-12-12 0${i % 10}:00:00`,
partner_ids: [i + 1],
}));
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="day" color="partner_ids">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" filter_field="partner_checked" />
</calendar>
`,
});
assert.containsN(target, ".fc-event", 56);
assert.hasClass(findEvent(target, 1), "o_calendar_color_1");
assert.hasClass(findEvent(target, 55), "o_calendar_color_55");
assert.hasClass(findEvent(target, 56), "o_calendar_color_1");
const partnerSection = findFilterPanelSection(target, "partner_ids");
assert.containsOnce(partnerSection, ".o_calendar_filter_item[data-value='all']");
assert.containsN(partnerSection, ".o_calendar_filter_item:not([data-value='all'])", 56);
assert.hasClass(
partnerSection.querySelector(".o_calendar_filter_item[data-value='1']"),
"o_cw_filter_color_1"
);
assert.hasClass(
partnerSection.querySelector(".o_calendar_filter_item[data-value='55']"),
"o_cw_filter_color_55"
);
assert.hasClass(
partnerSection.querySelector(".o_calendar_filter_item[data-value='56']"),
"o_cw_filter_color_1"
);
});
QUnit.test("Colors: use available colors when attr is not number", async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" color="name">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" filter_field="partner_checked" />
</calendar>
`,
});
const colorClass = Array.from(findEvent(target, 1).classList).find((className) =>
className.startsWith("o_calendar_color_")
);
assert.notOk(isNaN(Number(colorClass.split("_").at(-1))));
await clickEvent(target, 1);
assert.hasClass(target.querySelector(".o_cw_popover"), colorClass);
});
QUnit.test(`Add filters and specific color`, async (assert) => {
serverData.models.event_type.records.push({
id: 4,
display_name: "Event Type no color",
color_event_type: 0,
});
serverData.models.event.records.push(
{
id: 8,
user_id: 4,
partner_id: 1,
name: "event 8",
start: "2016-12-11 09:00:00",
stop: "2016-12-11 10:00:00",
allday: false,
partner_ids: [1, 2, 3],
event_type_id: 3,
color_event: 4, // related is not managed by the mock server
},
{
id: 9,
user_id: 4,
partner_id: 1,
name: "event 9",
start: "2016-12-11 19:00:00",
stop: "2016-12-11 20:00:00",
allday: false,
partner_ids: [1, 2, 3],
event_type_id: 1,
color_event: 1, // related is not managed by the mock server
},
{
id: 10,
user_id: 4,
partner_id: 1,
name: "event 10",
start: "2016-12-11 12:00:00",
stop: "2016-12-11 13:00:00",
allday: false,
partner_ids: [1, 2, 3],
event_type_id: 4,
color_event: 0, // related is not managed by the mock server
}
);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="week" color="color_event" event_open_popup="1">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
<field name="event_type_id" filters="1" color="color_event_type" />
</calendar>
`,
mockRPC(route, { method, model, kwargs }) {
if (route.startsWith("/web/static/lib/fullcalendar")) {
return;
}
if (kwargs.fields) {
assert.step(`${method} (${model}) [${(kwargs.fields || []).join(", ")}]`);
} else {
assert.step(`${method} (${model})`);
}
},
});
assert.verifySteps([
"get_views (event)",
"check_access_rights (event)",
"search_read (filter_partner) [partner_id]",
"search_read (event) [display_name, start, stop, allday, color_event, partner_ids, event_type_id]",
]);
// By default no filter is selected. We check before continuing.
await toggleFilter(target, "partner_ids", 1);
assert.verifySteps([
"search_read (filter_partner) [partner_id]",
"search_read (event) [display_name, start, stop, allday, color_event, partner_ids, event_type_id]",
]);
await toggleFilter(target, "partner_ids", 2);
assert.verifySteps([
"search_read (filter_partner) [partner_id]",
"search_read (event) [display_name, start, stop, allday, color_event, partner_ids, event_type_id]",
]);
assert.containsN(target, ".o_calendar_filter", 2, "should display 2 sections");
const typeSection = findFilterPanelSection(target, "event_type_id");
assert.strictEqual(
typeSection.querySelector(".o_cw_filter_label").textContent,
"Event Type",
"should display 'Event Type' filter"
);
assert.hasClass(findEvent(target, 8), "o_calendar_color_4");
assert.hasClass(findEvent(target, 9), "o_calendar_color_1");
assert.hasClass(findEvent(target, 10), "o_calendar_color_0");
assert.containsN(
typeSection,
".o_calendar_filter_item",
4,
"should display 4 filter items for 'Event Type'"
);
assert.containsOnce(
typeSection,
`.o_calendar_filter_item[data-value="3"].o_cw_filter_color_4`,
"Filter for event type 3 must have the color 4"
);
assert.containsOnce(
target,
`.fc-event[data-event-id="8"].o_calendar_color_4`,
"Event of event type 3 must have the color 4"
);
assert.containsOnce(
target,
`.fc-event[data-event-id="10"].o_calendar_color_0`,
"The first color is used when none is provided (default int field value being 0)"
);
});
QUnit.test(`Colors: dynamic filters without any color attr`, async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop">
<field name="user_id" filters="1" invisible="1"/>
</calendar>
`,
});
assert.hasClass(findEvent(target, 1), "o_calendar_color_0");
assert.hasClass(findEvent(target, 2), "o_calendar_color_0");
assert.hasClass(findEvent(target, 3), "o_calendar_color_0");
assert.hasClass(findEvent(target, 4), "o_calendar_color_0");
assert.containsOnce(target, ".o_calendar_filter[data-name=user_id]");
assert.containsNone(
findFilterPanelSection(target, "user_id"),
"[class*='o_cw_filter_color_']"
);
});
QUnit.test(`Colors: dynamic filters without color attr (related)`, async (assert) => {
serverData.models.event.records = [
{
id: 8,
user_id: 4,
partner_id: 1,
name: "event 8",
start: "2016-12-11 09:00:00",
stop: "2016-12-11 10:00:00",
allday: false,
partner_ids: [1, 2, 3],
event_type_id: 3,
color_event: 4, // related is not managed by the mock server
},
{
id: 9,
user_id: 4,
partner_id: 1,
name: "event 9",
start: "2016-12-11 19:00:00",
stop: "2016-12-11 20:00:00",
allday: false,
partner_ids: [1, 2, 3],
event_type_id: 1,
color_event: 1, // related is not managed by the mock server
},
{
id: 10,
user_id: 4,
partner_id: 1,
name: "event 10",
start: "2016-12-11 12:00:00",
stop: "2016-12-11 13:00:00",
allday: false,
partner_ids: [1, 2, 3],
event_type_id: 2,
color_event: 2, // related is not managed by the mock server
},
];
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" color="color_event">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
<field name="event_type_id" filters="1" />
</calendar>
`,
mockRPC(route, { method, model }) {
if (method === "search_read" && model === "event_type") {
throw new Error("should not fetch event_type filter colors");
}
},
});
await toggleSectionFilter(target, "partner_ids");
assert.hasClass(findEvent(target, 8), "o_calendar_color_4");
assert.hasClass(findEvent(target, 9), "o_calendar_color_1");
assert.hasClass(findEvent(target, 10), "o_calendar_color_2");
assert.containsNone(
findFilterPanelSection(target, "partner_ids"),
"[class*='o_cw_filter_color_']"
);
assert.containsN(
findFilterPanelSection(target, "event_type_id"),
"[class*='o_cw_filter_color_']",
3
);
});
QUnit.test(`Colors: dynamic filters without color attr (direct)`, async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" color="user_id">
<field name="partner_id" avatar_field="image"/>
<field name="user_id" filters="1" invisible="1"/>
</calendar>
`,
mockRPC(route, { method, model }) {
if (method === "search_read" && model === "event_type") {
throw new Error("should not fetch event_type filter colors");
}
},
});
assert.hasClass(findEvent(target, 1), "o_calendar_color_-1"); // uid = -1 ...
assert.hasClass(findEvent(target, 2), "o_calendar_color_-1"); // uid = -1 ...
assert.hasClass(findEvent(target, 3), "o_calendar_color_4");
assert.hasClass(findEvent(target, 4), "o_calendar_color_-1"); // uid = -1 ...
assert.containsNone(
findFilterPanelSection(target, "partner_id"),
"[class*='o_cw_filter_color_']"
);
assert.containsN(
findFilterPanelSection(target, "user_id"),
"[class*='o_cw_filter_color_']",
2
);
});
QUnit.test(`makeFilterUser: color for current user`, async (assert) => {
serverData.models["res.partner"] = {
fields: {
id: { string: "ID", type: "integer" },
display_name: { string: "Displayed name", type: "char" },
image: { string: "image", type: "integer" },
},
records: [
{ id: 1, display_name: "partner 1", image: "AAA" },
{ id: 2, display_name: "partner 2", image: "BBB" },
{ id: 3, display_name: "partner 3", image: "CCC" },
{ id: 4, display_name: "partner 4", image: "DDD" },
],
};
serverData.models.event.fields.partner_id.relation = "res.partner";
serverData.models.event.fields.partner_ids.relation = "res.partner";
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" color="partner_ids">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
</calendar>
`,
});
const partnerSection = findFilterPanelSection(target, "partner_ids");
assert.containsN(partnerSection, "[class*='o_cw_filter_color_']", 3);
assert.strictEqual(
partnerSection.querySelector(".o_cw_filter_label").textContent,
"attendees"
);
assert.containsN(partnerSection, ".o_calendar_filter_item", 4);
assert.strictEqual(
partnerSection.querySelector(".o_calendar_filter_item[data-value='7']").textContent,
"Mitchell"
);
assert.containsOnce(
partnerSection,
`.o_calendar_filter_item[data-value="7"].o_cw_filter_color_7`
);
assert.containsOnce(
partnerSection,
`.o_calendar_filter_item[data-value="2"].o_cw_filter_color_2`
);
assert.containsOnce(
partnerSection,
`.o_calendar_filter_item[data-value="1"].o_cw_filter_color_1`
);
assert.strictEqual(
partnerSection.querySelector(".o_calendar_filter_item[data-value='all']").textContent,
"Everybody's calendars"
);
});
QUnit.test(`Colors: dynamic filters with same color as events`, async (assert) => {
serverData.models.event.records = [
{
id: 8,
user_id: 4,
partner_id: 1,
name: "event 8",
start: "2016-12-11 09:00:00",
stop: "2016-12-11 10:00:00",
allday: false,
partner_ids: [1, 2, 3],
event_type_id: 3,
color_event: 4, // related is not managed by the mock server
},
{
id: 9,
user_id: 4,
partner_id: 1,
name: "event 9",
start: "2016-12-11 19:00:00",
stop: "2016-12-11 20:00:00",
allday: false,
partner_ids: [1, 2, 3],
event_type_id: 1,
color_event: 1, // related is not managed by the mock server
},
{
id: 10,
user_id: 4,
partner_id: 1,
name: "event 10",
start: "2016-12-11 12:00:00",
stop: "2016-12-11 13:00:00",
allday: false,
partner_ids: [1, 2, 3],
event_type_id: 2,
color_event: 2, // related is not managed by the mock server
},
];
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" color="color_event">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
<field name="event_type_id" filters="1" color="color_event_type" />
</calendar>
`,
mockRPC(route, { method, model }) {
if (method === "search_read" && model === "event_type") {
throw new Error("should not fetch event_type filter colors");
}
},
});
await toggleSectionFilter(target, "partner_ids");
assert.hasClass(findEvent(target, 8), "o_calendar_color_4");
assert.hasClass(findEvent(target, 9), "o_calendar_color_1");
assert.hasClass(findEvent(target, 10), "o_calendar_color_2");
assert.containsN(
findFilterPanelSection(target, "event_type_id"),
"[class*='o_cw_filter_color_']",
3
);
assert.hasClass(findFilterPanelFilter(target, "event_type_id", 1), "o_cw_filter_color_1");
assert.hasClass(findFilterPanelFilter(target, "event_type_id", 2), "o_cw_filter_color_2");
assert.hasClass(findFilterPanelFilter(target, "event_type_id", 3), "o_cw_filter_color_4");
});
QUnit.test(`Colors: dynamic filters with another color source`, async (assert) => {
serverData.models.event.records = [
{
id: 8,
user_id: 4,
partner_id: 3,
name: "event 8",
start: "2016-12-11 09:00:00",
stop: "2016-12-11 10:00:00",
allday: false,
partner_ids: [1, 2, 3],
event_type_id: 3,
color_event: 4, // related is not managed by the mock server
},
{
id: 9,
user_id: 4,
partner_id: 3,
name: "event 9",
start: "2016-12-11 19:00:00",
stop: "2016-12-11 20:00:00",
allday: false,
partner_ids: [1, 2, 3],
event_type_id: 1,
color_event: 1, // related is not managed by the mock server
},
{
id: 10,
user_id: 4,
partner_id: 3,
name: "event 10",
start: "2016-12-11 12:00:00",
stop: "2016-12-11 13:00:00",
allday: false,
partner_ids: [1, 2, 3],
event_type_id: 2,
color_event: 2, // related is not managed by the mock server
},
];
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" color="partner_id">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
<field name="event_type_id" filters="1" color="color_event_type" />
</calendar>
`,
mockRPC(route, { method, model }) {
if (method === "search_read" && model === "event_type") {
assert.step("fetching event_type filter colors");
}
},
});
assert.verifySteps([]);
await toggleSectionFilter(target, "partner_ids");
assert.verifySteps(["fetching event_type filter colors"]);
assert.hasClass(findEvent(target, 8), "o_calendar_color_3");
assert.hasClass(findEvent(target, 9), "o_calendar_color_3");
assert.hasClass(findEvent(target, 10), "o_calendar_color_3");
assert.hasClass(findFilterPanelFilter(target, "event_type_id", 1), "o_cw_filter_color_1");
assert.hasClass(findFilterPanelFilter(target, "event_type_id", 2), "o_cw_filter_color_2");
assert.hasClass(findFilterPanelFilter(target, "event_type_id", 3), "o_cw_filter_color_4");
});
QUnit.test(`Colors: dynamic filters with no color source`, async (assert) => {
serverData.models.event.records = [
{
id: 8,
user_id: 4,
partner_id: 3,
name: "event 8",
start: "2016-12-11 09:00:00",
stop: "2016-12-11 10:00:00",
allday: false,
partner_ids: [1, 2, 3],
event_type_id: 3,
color_event: 4, // related is not managed by the mock server
},
{
id: 9,
user_id: 4,
partner_id: 3,
name: "event 9",
start: "2016-12-11 19:00:00",
stop: "2016-12-11 20:00:00",
allday: false,
partner_ids: [1, 2, 3],
event_type_id: 1,
color_event: 1, // related is not managed by the mock server
},
{
id: 10,
user_id: 4,
partner_id: 3,
name: "event 10",
start: "2016-12-11 12:00:00",
stop: "2016-12-11 13:00:00",
allday: false,
partner_ids: [1, 2, 3],
event_type_id: 2,
color_event: 2, // related is not managed by the mock server
},
];
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
<field name="event_type_id" filters="1" color="color_event_type" />
</calendar>
`,
mockRPC(route, { method, model }) {
if (method === "search_read" && model === "event_type") {
assert.step("fetching event_type filter colors");
}
},
});
assert.verifySteps([]);
await toggleSectionFilter(target, "partner_ids");
assert.verifySteps(["fetching event_type filter colors"]);
assert.hasClass(findFilterPanelFilter(target, "event_type_id", 1), "o_cw_filter_color_1");
assert.hasClass(findFilterPanelFilter(target, "event_type_id", 2), "o_cw_filter_color_2");
assert.hasClass(findFilterPanelFilter(target, "event_type_id", 3), "o_cw_filter_color_4");
});
QUnit.test(`create event with filters`, async (assert) => {
serverData.models.event.fields.user_id.default = 5;
serverData.models.event.fields.partner_id.default = 3;
serverData.models.user.records.push({ id: 5, display_name: "user 5", partner_id: 3 });
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="week" event_open_popup="1" attendee="partner_ids" color="partner_id">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
<field name="partner_id" filters="1" invisible="1" />
</calendar>
`,
});
// By default only
await toggleFilter(target, "partner_ids", 1);
assert.containsN(target, ".o_calendar_filter_item", 5, "should display 5 filter items");
assert.containsN(target, ".fc-event", 4, "should display 4 events");
// quick create a record
await selectTimeRange(target, "2016-12-15 06:00:00", "2016-12-15 08:00:00");
await editInput(target, ".o-calendar-quick-create--input", "coucou");
await click(target, ".o-calendar-quick-create--create-btn");
assert.containsN(
target,
".o_calendar_filter_item",
6,
"should add the missing filter (active)"
);
assert.containsN(target, ".fc-event", 5, "should display the created item");
// change default value for quick create an hide record
serverData.models.event.fields.user_id.default = 4;
serverData.models.event.fields.partner_id.default = 4;
// Disable our filter to create a record without displaying it
await toggleFilter(target, "partner_id", 4);
// quick create and other record
await selectTimeRange(target, "2016-12-13 06:00:00", "2016-12-13 08:00:00");
await editInput(target, ".o-calendar-quick-create--input", "coucou 2");
await click(target, ".o-calendar-quick-create--create-btn");
assert.containsN(target, ".o_calendar_filter_item", 6, "should have the same filters");
assert.containsN(target, ".fc-event", 4, "should not display the created item");
await toggleFilter(target, "partner_id", 4);
await toggleFilter(target, "partner_ids", 2);
assert.containsN(target, ".fc-event", 7, "should display all records");
});
QUnit.test(`create event with filters (no quickCreate)`, async (assert) => {
serverData.views["event,false,form"] = `
<form>
<group>
<field name="name" />
<field name="start" />
<field name="stop" />
<field name="user_id" />
<field name="partner_id" invisible="1" />
</group>
</form>
`;
serverData.models.event.fields.user_id.default = 5;
serverData.models.event.fields.partner_id.default = 3;
serverData.models.user.records.push({ id: 5, display_name: "user 5", partner_id: 3 });
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="week" event_open_popup="1" attendee="partner_ids" color="partner_id">
<filter name="user_id" avatar_field="image" />
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
<field name="partner_id" filters="1" invisible="1" />
</calendar>
`,
});
// dislay all attendee calendars
await toggleSectionFilter(target, "partner_ids");
await toggleFilter(target, "partner_id", 4);
assert.containsN(target, ".o_calendar_filter_item", 5, "should display 5 filter items");
assert.containsN(target, ".fc-event", 3, "should display 3 events");
// quick create a record
await selectTimeRange(target, "2016-12-15 06:00:00", "2016-12-15 08:00:00");
await editInput(target, ".o-calendar-quick-create--input", "coucou");
await click(target, ".o-calendar-quick-create--edit-btn");
await click(target, ".modal-footer .o_form_button_save");
assert.containsN(
target,
".o_calendar_filter_item",
6,
"should add the missing filter (active)"
);
assert.containsN(target, ".fc-event", 4, "should display the created item");
});
QUnit.test(`Update event with filters`, async (assert) => {
const records = serverData.models.user.records;
records.push({ id: 5, display_name: "user 5", partner_id: 3 });
serverData.models.event.onchanges = {
user_id(obj) {
obj.partner_id = records.find((r) => r.id === obj.user_id).partner_id;
},
};
serverData.views["event,false,form"] = `
<form>
<group>
<field name="name" />
<field name="start" />
<field name="stop" />
<field name="user_id" />
<field name="partner_ids" widget="many2many_tags" />
<field name="partner_id" invisible="1" />
</group>
</form>
`;
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="week" event_open_popup="1" attendee="partner_ids" color="partner_id">
<filter name="user_id" avatar_field="image" />
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
<field name="partner_id" filters="1" invisible="1" />
</calendar>
`,
});
// select needed partner filters
await toggleFilter(target, "partner_ids", 1);
await toggleFilter(target, "partner_id", 4);
assert.containsN(target, ".o_calendar_filter_item", 5, "should display 5 filter items");
assert.containsN(target, ".fc-event", 3, "should display 3 events");
await clickEvent(target, 2);
assert.containsOnce(target, ".o_cw_popover", "should open a popover clicking on event");
await click(target, ".o_cw_popover .o_cw_popover_edit");
assert.strictEqual(
target.querySelector(".modal .modal-title").textContent,
"Open: event 2",
"dialog should have a valid title"
);
await click(target, `.modal .o_field_widget[name="user_id"] input`);
await click(
document.body.querySelectorAll(".ui-autocomplete.dropdown-menu .ui-menu-item")[2]
);
await click(target, ".modal .o_form_button_save");
assert.containsN(
target,
".o_calendar_filter_item",
6,
"should add the missing filter (active)"
);
assert.containsN(target, ".fc-event", 3, "should display the updated item");
// test the behavior of the 'select all' input checkbox
assert.containsN(
target,
".o_calendar_filter_item input:checked",
3,
"should display 3 active checkbox"
);
assert.containsN(
target,
".o_calendar_filter_item input:not(:checked)",
3,
"should display 3 inactive checkbox"
);
// Click to select all users
await toggleSectionFilter(target, "partner_id");
// should contains 4 events
assert.containsN(target, ".fc-event", 4, "should display the updated events");
// Should have 4 checked boxes
assert.containsN(
target,
".o_calendar_filter_item input:checked",
4, // 3
"should display 4 active checkbox"
);
// unselect all user
await toggleSectionFilter(target, "partner_id");
assert.containsN(target, ".fc-event", 0, "should not display any event");
assert.containsN(
target,
".o_calendar_filter_item input:checked",
1,
"should display 1 active checkbox"
);
});
QUnit.test(`change pager with filters`, async (assert) => {
serverData.models.user.records.push({ id: 5, display_name: "user 5", partner_id: 3 });
serverData.models.event.records.push(
{
id: 8,
user_id: 5,
partner_id: 3,
name: "event 8",
start: "2016-12-06 04:00:00",
stop: "2016-12-06 08:00:00",
allday: false,
partner_ids: [1, 2, 3],
type: 1,
},
{
id: 9,
user_id: uid,
partner_id: 1,
name: "event 9",
start: "2016-12-07 04:00:00",
stop: "2016-12-07 08:00:00",
allday: false,
partner_ids: [1, 2, 3],
type: 1,
},
{
id: 10,
user_id: 4,
partner_id: 4,
name: "event 10",
start: "2016-12-08 04:00:00",
stop: "2016-12-08 08:00:00",
allday: false,
partner_ids: [1, 2, 3],
type: 1,
}
);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="week" event_open_popup="1" attendee="partner_ids" color="partner_id">
<filter name="user_id" avatar_field="image" />
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
<field name="partner_id" filters="1" invisible="1" />
</calendar>
`,
});
// select filter for partner 1, 2 and 4
await toggleSectionFilter(target, "partner_ids");
await toggleFilter(target, "partner_id", 4);
await pickDate(target, "2016-12-05");
await changeScale(target, "week");
assert.containsN(target, ".o_calendar_filter_item", 6, "should display 6 filter items");
assert.containsN(target, ".fc-event", 2, "should display 2 events");
const events = target.querySelectorAll(".fc-event .o_event_title");
assert.strictEqual(
Array.from(events)
.map((e) => e.textContent)
.join("")
.replace(/\s/g, ""),
"event8event9",
"should display 2 events"
);
});
QUnit.test(`ensure events are still shown if filters give an empty domain`, async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="week">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
</calendar>
`,
});
await toggleSectionFilter(target, "partner_ids");
assert.containsN(target, ".fc-event", 5, "should display 5 events");
await toggleFilter(target, "partner_ids", "all");
assert.containsN(target, ".fc-event", 5, "should display 5 events");
});
QUnit.test(`events starting at midnight`, async (assert) => {
patchWithCleanup(localization, defaultLocalization);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="week" />
`,
});
// Click on Tuesday 12am
await selectTimeRange(target, "2016-12-13 00:00:00", "2016-12-13 00:30:00");
assert.containsOnce(
target,
".o-calendar-quick-create",
"should open the quick create dialog"
);
// Creating the event
await editInput(
target.querySelector(".modal-body input"),
null,
"new event in quick create"
);
await click(target, ".o-calendar-quick-create--create-btn");
assert.strictEqual(
findEvent(target, 8).textContent,
"00:00 new event in quick create",
"should display the new record after quick create dialog"
);
});
QUnit.test(`set event as all day when field is date`, async (assert) => {
assert.expect(2);
patchTimeZone(-480); // UTC-8
serverData.models.event.records[0].start_date = "2016-12-14";
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start_date" all_day="allday" mode="week" event_open_popup="1" attendee="partner_ids" color="partner_id">
<filter name="user_id" avatar_field="image" />
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
</calendar>
`,
});
await toggleFilter(target, "partner_ids", 1);
assert.containsOnce(
target,
".fc-day-grid .fc-event-container",
"should be one event in the all day row"
);
await clickEvent(target, 1);
assert.strictEqual(
target.querySelector(".o_cw_popover .list-group-item").textContent,
"December 14, 2016 "
);
});
QUnit.test(
`set event as all day when field is date (without all_day mapping)`,
async (assert) => {
assert.expect(1);
serverData.models.event.records[0].start_date = "2016-12-14";
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start_date" mode="week" />
`,
});
assert.containsOnce(
target,
".fc-day-grid .fc-event-container",
"should be one event in the all day row"
);
}
);
QUnit.test(
`set event as all day when field is datetime (without all_day mapping)`,
async (assert) => {
await makeView({
serverData,
resModel: "event",
type: "calendar",
arch: `<calendar date_start="start" date_stop="stop" mode="week"/>`,
});
assert.containsOnce(
target,
".fc-day-grid .fc-event-container",
"should be one event in the all day row"
);
}
);
QUnit.test(`quickcreate avoid double event creation`, async (assert) => {
assert.expect(1);
let createCount = 0;
const def = makeDeferred();
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="month" event_open_popup="1" />
`,
async mockRPC(route, { method }, performRPC) {
if (method === "create") {
createCount++;
await def;
return performRPC(...arguments);
}
},
});
// create a new event
await clickDate(target, "2016-12-13");
await editInput(
target.querySelector(".modal-body input"),
null,
"new event in quick create"
);
// Simulate ENTER pressed on Create button (after a TAB)
await triggerEvent(target.querySelector(".modal-body input"), null, "keyup", {
key: "Enter",
});
await click(target, ".o-calendar-quick-create--create-btn");
def.resolve();
await nextTick();
assert.strictEqual(createCount, 1, "should create only one event");
});
QUnit.test(`calendar is configured to have no groupBy menu`, async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" />
`,
});
assert.containsNone(
target,
".o_control_panel .o_group_by_menu",
"the control panel has no groupBy menu"
);
});
QUnit.test(`timezone does not affect current day`, async (assert) => {
assert.expect(2);
patchTimeZone(2400); // 40 hours timezone
patchDate(2016, 11, 12, 8, 0, 0); // 2016-12-12 08:00:00
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" />
`,
});
assert.strictEqual(
findPickedDate(target).textContent,
"12",
"should highlight the target day"
);
await pickDate(target, "2016-12-11");
assert.strictEqual(
findPickedDate(target).textContent,
"11",
"should highlight the selected day"
);
});
QUnit.test(`timezone does not affect drag and drop`, async (assert) => {
assert.expect(10);
patchTimeZone(-2400); // UTC-40
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="month">
<field name="name" />
<field name="start" />
</calendar>
`,
mockRPC(route, { method, args }) {
if (method === "write") {
assert.deepEqual(args[0], [6], "event 6 is moved");
assert.strictEqual(
args[1].start,
"2016-11-29 08:00:00",
"event moved to 27th nov 16h00 +40 hours timezone"
);
}
},
});
assert.strictEqual(findEvent(target, 1).textContent.replace(/\s/g, ""), "08:00event1");
await clickEvent(target, 1);
assert.strictEqual(
target.querySelector(`.o_field_widget[name="start"]`).textContent,
"12/09/2016 08:00:00"
);
assert.strictEqual(findEvent(target, 6).textContent.replace(/\s/g, ""), "16:00event6");
await clickEvent(target, 6);
assert.strictEqual(
target.querySelector(`.o_field_widget[name="start"]`).textContent,
"12/16/2016 16:00:00"
);
// Move event 6 as on first day of month view (27th november 2016)
await moveEventToDate(target, 6, "2016-11-27");
assert.strictEqual(findEvent(target, 6).textContent.replace(/\s/g, ""), "16:00event6");
await clickEvent(target, 6);
assert.strictEqual(
target.querySelector(`.o_field_widget[name="start"]`).textContent,
"11/27/2016 16:00:00"
);
assert.strictEqual(findEvent(target, 1).textContent.replace(/\s/g, ""), "08:00event1");
await clickEvent(target, 1);
assert.strictEqual(
target.querySelector(`.o_field_widget[name="start"]`).textContent,
"12/09/2016 08:00:00"
);
});
QUnit.test(`timzeone does not affect calendar with date field`, async (assert) => {
assert.expect(11);
patchTimeZone(120);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start_date" mode="month">
<field name="name" />
<field name="start_date" />
</calendar>
`,
mockRPC(route, { method, args }) {
if (method === "create") {
const [values] = args[0];
assert.strictEqual(values.start_date, "2016-12-20");
}
if (method === "write") {
assert.step(args[1].start_date);
}
},
});
// Create event (on 20 december)
await clickDate(target, "2016-12-20");
await editInput(target, ".o-calendar-quick-create--input", "An event");
await click(target, ".o-calendar-quick-create--create-btn");
await clickEvent(target, 8);
assert.containsOnce(target, ".o_cw_popover", "should open a popover clicking on event");
assert.strictEqual(
target.querySelector(
".o_cw_popover .o_cw_popover_fields_secondary .list-group-item .o_field_date"
).textContent,
"12/20/2016",
"should have correct start date"
);
// Move event to another day (on 27 november)
await moveEventToDate(target, 8, "2016-11-27");
assert.verifySteps(["2016-11-27"]);
await clickEvent(target, 8);
assert.containsOnce(target, ".o_cw_popover", "should open a popover clicking on event");
assert.strictEqual(
target.querySelector(
".o_cw_popover .o_cw_popover_fields_secondary .list-group-item .o_field_date"
).textContent,
"11/27/2016",
"should have correct start date"
);
// Move event to last day (on 7 january)
await moveEventToDate(target, 8, "2017-01-07");
assert.verifySteps(["2017-01-07"]);
await clickEvent(target, 8);
assert.containsOnce(target, ".o_cw_popover", "should open a popover clicking on event");
assert.strictEqual(
target.querySelector(
".o_cw_popover .o_cw_popover_fields_secondary .list-group-item .o_field_date"
).textContent,
"01/07/2017",
"should have correct start date"
);
});
QUnit.test(`drag and drop on month mode`, async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="month" event_open_popup="1" quick_create="0">
<field name="name" />
<field name="partner_id" />
</calendar>
`,
});
// Create event (on 20 december)
await clickDate(target, "2016-12-20");
await editInput(target, ".modal-body .o_field_widget[name=name] input", "An event");
await click(target, ".modal .o_form_button_save");
await moveEventToDate(target, 1, "2016-12-19", { disableDrop: true });
assert.hasClass(findEvent(target, 1), "dayGridMonth");
// Move event to another day (on 19 december)
await moveEventToDate(target, 8, "2016-12-19");
await clickEvent(target, 8);
const row = target.querySelectorAll(".o_cw_body .list-group-item")[1];
assert.strictEqual(
row.textContent.trim(),
"07:00 - 19:00 (12 hours)",
"start and end hours shouldn't have been changed"
);
});
QUnit.test(`drag and drop on month mode with all_day mapping`, async (assert) => {
// Same test as before but in calendarEventToRecord (calendar_model.js) there is
// different condition branching with all_day mapping or not
assert.expect(1);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="month" event_open_popup="1" quick_create="0" all_day="allday">
<field name="name" />
<field name="partner_id" />
</calendar>
`,
});
// Create event (on 20 december)
await clickDate(target, "2016-12-20");
await editInput(target.querySelector(".modal-body input"), null, "An event");
await click(target.querySelector(`.o_field_widget[name="allday"] input`));
// use datepicker to enter a date: 12/20/2016 07:00:00
await click(target, `.o_field_widget[name="start"] input`);
await editSelect(getTimePickers().at(0).at(0), null, "7");
// use datepicker to enter a date: 12/20/2016 19:00:00
await click(target, `.o_field_widget[name="stop"] input`);
await editSelect(getTimePickers().at(0).at(0), null, "19");
await click(target.querySelector(".modal .o_form_button_save"));
// Move event to another day (on 19 december)
await moveEventToDate(target, 8, "2016-12-19");
await clickEvent(target, 8);
const row = target.querySelectorAll(".o_cw_body .list-group-item")[1];
assert.strictEqual(
row.textContent.trim(),
"07:00 - 19:00 (12 hours)",
"start and end hours shouldn't have been changed"
);
});
QUnit.test(`drag and drop on month mode with date_start and date_delay`, async (assert) => {
assert.expect(1);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_delay="delay" mode="month">
<field name="name" />
<field name="start" />
<field name="delay" />
</calendar>
`,
mockRPC(route, { args, method }) {
if (method === "write") {
// delay should not be written at drag and drop
assert.strictEqual(args[1].delay, undefined);
}
},
});
// Create event (on 20 december)
await clickDate(target, "2016-12-20");
await editInput(target.querySelector(".modal-body input"), null, "An event");
await click(target, ".o-calendar-quick-create--create-btn");
// Move event to another day (on 27 november)
await moveEventToDate(target, 8, "2016-11-27");
});
QUnit.test(`form_view_id attribute works (for creating events)`, async (assert) => {
assert.expect(1);
serviceRegistry.add(
"action",
{
...actionService,
start() {
const result = actionService.start(...arguments);
const doAction = result.doAction;
result.doAction = (request) => {
assert.strictEqual(
request.views[0][0],
42,
"should do a do_action with view id 42"
);
return doAction(request);
};
return result;
},
},
{ force: true }
);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="month" form_view_id="42" />
`,
mockRPC(route, { method }) {
if (method === "create") {
// we simulate here the case where a create call with just
// the field name fails. This is a normal flow, the server
// reject the create rpc (quick create), then the web client
// fall back to a form view. This happens typically when a
// model has required fields
return Promise.reject("None shall pass!");
}
},
});
await clickDate(target, "2016-12-13");
await editInput(target.querySelector(".modal-body input"), null, "It's just a fleshwound");
await click(target, ".o-calendar-quick-create--create-btn");
});
QUnit.test(`form_view_id attribute works with popup (for creating events)`, async (assert) => {
assert.expect(1);
serviceRegistry.add(
"action",
{
...actionService,
start() {
const result = actionService.start(...arguments);
const doAction = result.doAction;
result.doAction = (request) => {
assert.strictEqual(request.views[0][0], 1, "should load view with id 1");
return doAction(request);
};
return result;
},
},
{ force: true }
);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="month" open_event_popup="1" quick_create="0" form_view_id="1">
<field name="name" />
</calendar>
`,
});
await clickDate(target, "2016-12-13");
});
QUnit.test(`calendar fallback to form view id in action if necessary`, async (assert) => {
assert.expect(1);
serviceRegistry.add(
"action",
{
...actionService,
start() {
const result = actionService.start(...arguments);
const doAction = result.doAction;
result.doAction = (request) => {
assert.deepEqual(request, {
type: "ir.actions.act_window",
res_model: "event",
views: [[43, "form"]], // should use the view id from the config
target: "current",
context: {
lang: "en",
uid: 7,
tz: "taht",
default_name: "It's just a fleshwound",
default_start: "2016-12-13 06:00:00",
default_stop: "2016-12-13 18:00:00",
default_allday: true,
},
});
return doAction(request);
};
return result;
},
},
{ force: true }
);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `<calendar date_start="start" date_stop="stop" mode="month" />`,
config: { views: [[43, "form"]] },
mockRPC(route, { method }) {
if (method === "create") {
// we simulate here the case where a create call with just
// the field name fails. This is a normal flow, the server
// reject the create rpc (quick create), then the web client
// fall back to a form view. This happens typically when a
// model has required fields
return Promise.reject("None shall pass!");
}
},
});
await clickDate(target, "2016-12-13");
await editInput(target.querySelector(".modal-body input"), null, "It's just a fleshwound");
await click(target, ".o-calendar-quick-create--create-btn");
});
QUnit.test(`fullcalendar initializes with right locale`, async (assert) => {
// The machine that runs this test must have this locale available
patchWithCleanup(luxon.Settings, { defaultLocale: "fr" });
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `<calendar date_start="start" date_stop="stop" mode="week"/>`,
});
assert.deepEqual(
[...target.querySelectorAll(".fc-day-header")].map((el) =>
[
el.querySelector(".o_cw_day_name").textContent,
el.querySelector(".o_cw_day_number").textContent,
].join(" ")
),
["dim. 11", "lun. 12", "mar. 13", "mer. 14", "jeu. 15", "ven. 16", "sam. 17"]
);
});
QUnit.test(`initial_date given in the context`, async (assert) => {
assert.expect(3);
serverData.views = {
"event,1,calendar": `<calendar date_start="start" date_stop="stop" mode="day"/>`,
"event,false,search": `<search />`,
};
serverData.actions = {
1: {
id: 1,
name: "context initial date",
res_model: "event",
type: "ir.actions.act_window",
views: [[1, "calendar"]],
context: { initial_date: "2016-01-30 08:00:00" }, // 30th of january
},
};
const webClient = await createWebClient({ serverData });
await doAction(webClient, 1);
await nextTick();
assert.strictEqual(
target.querySelector(".o_control_panel .o_breadcrumb").textContent,
"context initial date",
"should display name passed in the context"
);
assert.strictEqual(
target.querySelector(".o_calendar_renderer .fc-day-header .o_cw_day_name").textContent,
"Saturday",
"should display day passed in the context"
);
assert.strictEqual(
target.querySelector(".o_calendar_renderer .fc-day-header .o_cw_day_number")
.textContent,
"30",
"should display day passed in the context"
);
});
QUnit.test(`default week start (US) month mode`, async (assert) => {
// if not given any option, default week start is on Sunday
assert.expect(8);
// 2019-09-12 08:00:00
patchDate(2019, 8, 12, 8, 0, 0);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="month" />
`,
mockRPC(route, { method, model, kwargs }) {
if (model === "event" && method === "search_read") {
assert.deepEqual(
kwargs.domain,
[
["start", "<=", "2019-10-12 22:59:59"],
["stop", ">=", "2019-08-31 23:00:00"],
],
"The domain to search events in should be correct"
);
}
},
});
const dayHeaders = target.querySelectorAll(".fc-day-header .o_cw_day_name");
assert.strictEqual(
dayHeaders[0].textContent,
"Sun",
"The first day of the week should be Sunday"
);
assert.strictEqual(
dayHeaders[dayHeaders.length - 1].textContent,
"Sat",
"The last day of the week should be Saturday"
);
const dayTops = target.querySelectorAll(".fc-day-top");
assert.strictEqual(
dayTops[0].querySelector(".fc-week-number").textContent,
"36",
"The number of the week should be correct"
);
assert.strictEqual(dayTops[0].querySelector(".fc-day-number").textContent, "1");
assert.strictEqual(dayTops[0].dataset.date, "2019-09-01");
assert.strictEqual(
dayTops[dayTops.length - 1].querySelector(".fc-day-number").textContent,
"12"
);
assert.strictEqual(dayTops[dayTops.length - 1].dataset.date, "2019-10-12");
});
QUnit.test(`European week start month mode`, async (assert) => {
assert.expect(8);
patchDate(2019, 8, 15, 8, 0, 0); // 2019-09-15 08:00:00
// the week start depends on the locale
patchWithCleanup(localization, { weekStart: 1 });
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="month" />
`,
mockRPC(route, { method, model, kwargs }) {
if (model === "event" && method === "search_read") {
assert.deepEqual(
kwargs.domain,
[
["start", "<=", "2019-10-06 22:59:59"],
["stop", ">=", "2019-08-25 23:00:00"],
],
"The domain to search events in should be correct"
);
}
},
});
const dayHeaders = target.querySelectorAll(".fc-day-header .o_cw_day_name");
assert.strictEqual(
dayHeaders[0].textContent,
"Mon",
"The first day of the week should be Monday"
);
assert.strictEqual(
dayHeaders[dayHeaders.length - 1].textContent,
"Sun",
"The last day of the week should be Sunday"
);
const dayTops = target.querySelectorAll(".fc-day-top");
assert.strictEqual(
dayTops[0].querySelector(".fc-week-number").textContent,
"35",
"The number of the week should be correct"
);
assert.strictEqual(dayTops[0].querySelector(".fc-day-number").textContent, "26");
assert.strictEqual(dayTops[0].dataset.date, "2019-08-26");
assert.strictEqual(
dayTops[dayTops.length - 1].querySelector(".fc-day-number").textContent,
"6"
);
assert.strictEqual(dayTops[dayTops.length - 1].dataset.date, "2019-10-06");
});
QUnit.test(`Monday week start week mode`, async (assert) => {
assert.expect(5);
patchDate(2019, 8, 15, 8, 0, 0); // 2019-09-15 08:00:00
// the week start depends on the locale
patchWithCleanup(localization, { weekStart: 1 });
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="week" />
`,
mockRPC(route, { method, model, kwargs }) {
if (model === "event" && method === "search_read") {
assert.deepEqual(
kwargs.domain,
[
["start", "<=", "2019-09-15 22:59:59"],
["stop", ">=", "2019-09-08 23:00:00"],
],
"The domain to search events in should be correct"
);
}
},
});
assert.containsOnce(target, ".fc-timeGridWeek-view .fc-day-grid");
const dayNameHeaders = target.querySelectorAll(".fc-day-header .o_cw_day_name");
const dayNumberHeaders = target.querySelectorAll(".fc-day-header .o_cw_day_number");
assert.strictEqual(
`${dayNameHeaders[0].textContent} ${dayNumberHeaders[0].textContent}`,
"Mon 9",
"The first day of the week should be Monday the 9th"
);
assert.strictEqual(
`${dayNameHeaders[dayNameHeaders.length - 1].textContent} ${
dayNumberHeaders[dayNumberHeaders.length - 1].textContent
}`,
"Sun 15",
"The last day of the week should be Sunday the 15th"
);
assert.strictEqual(
target.querySelector(".fc-head .fc-week-number").textContent,
"Week 37",
"The number of the week should be correct"
);
});
QUnit.test(`Saturday week start week mode`, async (assert) => {
assert.expect(5);
patchDate(2019, 8, 12, 8, 0, 0); // 2019-09-12 08:00:00
// the week start depends on the locale
patchWithCleanup(localization, { weekStart: 6 });
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="week" />
`,
mockRPC(route, { method, model, kwargs }) {
if (model === "event" && method === "search_read") {
assert.deepEqual(
kwargs.domain,
[
["start", "<=", "2019-09-13 22:59:59"],
["stop", ">=", "2019-09-06 23:00:00"],
],
"The domain to search events in should be correct"
);
}
},
});
assert.containsOnce(target, ".fc-timeGridWeek-view .fc-day-grid");
const dayNameHeaders = target.querySelectorAll(".fc-day-header .o_cw_day_name");
const dayNumberHeaders = target.querySelectorAll(".fc-day-header .o_cw_day_number");
assert.strictEqual(
`${dayNameHeaders[0].textContent} ${dayNumberHeaders[0].textContent}`,
"Sat 7",
"The first day of the week should be Saturday the 7th"
);
assert.strictEqual(
`${dayNameHeaders[dayNameHeaders.length - 1].textContent} ${
dayNumberHeaders[dayNumberHeaders.length - 1].textContent
}`,
"Fri 13",
"The last day of the week should be Friday the 13th"
);
assert.strictEqual(
target.querySelector(".fc-head .fc-week-number").textContent,
"Week 37",
"The number of the week should be correct"
);
});
QUnit.test(`Monday week start year mode`, async (assert) => {
assert.expect(4);
patchDate(2019, 8, 15, 8, 0, 0); // 2019-09-15 08:00:00
// the week start depends on the locale
patchWithCleanup(localization, { weekStart: 1 });
patchWithCleanup(CalendarYearRenderer.prototype, {
get options() {
return { ...super.options, weekNumbers: true };
},
});
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="year" />
`,
mockRPC(route, { method, model, kwargs }) {
if (model === "event" && method === "search_read") {
assert.deepEqual(
kwargs.domain,
[
["start", "<=", "2019-12-31 22:59:59"],
["stop", ">=", "2018-12-31 23:00:00"],
],
"The domain to search events in should be correct"
);
}
},
});
const weekRow = target.querySelector(".fc-day-top.fc-today").closest("tr");
const weekDays = weekRow.querySelectorAll(".fc-day-top");
assert.strictEqual(
weekDays[0].textContent,
"9",
"The first day of the week should be Monday the 9th"
);
assert.strictEqual(
weekDays[weekDays.length - 1].textContent,
"15",
"The last day of the week should be Sunday the 15th"
);
assert.strictEqual(
weekRow.querySelector(".fc-week-number").textContent,
"37",
"The number of the week should be correct"
);
});
QUnit.test(`Sunday week start year mode`, async (assert) => {
assert.expect(4);
patchDate(2019, 8, 15, 8, 0, 0); // 2019-09-15 08:00:00
// the week start depends on the locale
// the localization presents a python-like 1 to 7 weekStart value
patchWithCleanup(localization, { weekStart: 7 });
patchWithCleanup(CalendarYearRenderer.prototype, {
get options() {
return { ...super.options, weekNumbers: true };
},
});
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="year" />
`,
mockRPC(route, { method, model, kwargs }) {
if (model === "event" && method === "search_read") {
assert.deepEqual(
kwargs.domain,
[
["start", "<=", "2019-12-31 22:59:59"],
["stop", ">=", "2018-12-31 23:00:00"],
],
"The domain to search events in should be correct"
);
}
},
});
const weekRow = target.querySelector(".fc-day-top.fc-today").closest("tr");
const weekDays = weekRow.querySelectorAll(".fc-day-top");
assert.strictEqual(
weekDays[0].textContent,
"15",
"The first day of the week should be Sunday the 15th"
);
assert.strictEqual(
weekDays[weekDays.length - 1].textContent,
"21",
"The last day of the week should be Saturday the 21st"
);
assert.strictEqual(
weekRow.querySelector(".fc-week-number").textContent,
"38",
"The number of the week should be correct"
);
});
QUnit.test(
`edit record and attempt to create a record with "create" attribute set to false`,
async (assert) => {
assert.expect(8);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar event_open_popup="1" create="0" date_start="start" date_stop="stop" mode="month" />
`,
mockRPC(route, { args, method }) {
if (method === "web_save") {
assert.deepEqual(
args[1],
{ name: "event 4 modified" },
"should update the record"
);
}
},
});
// editing existing events should still be possible
// click on an existing event to open the formViewDialog
await clickEvent(target, 4);
assert.containsOnce(target, ".o_cw_popover", "should open a popover clicking on event");
assert.containsOnce(
target,
".o_cw_popover .o_cw_popover_edit",
"popover should have an edit button"
);
assert.containsOnce(
target,
".o_cw_popover .o_cw_popover_delete",
"popover should have a delete button"
);
assert.containsOnce(
target,
".o_cw_popover .o_cw_popover_close",
"popover should have a close button"
);
await click(target, ".o_cw_popover .o_cw_popover_edit");
assert.containsOnce(
target,
".modal-body",
"should open the form view in dialog when click on edit"
);
await editInput(target.querySelector(".modal-body input"), null, "event 4 modified");
await click(target.querySelector(".modal-footer .o_form_button_save"));
assert.containsNone(target, ".modal", "save button should close the modal");
// creating an event should not be possible
// attempt to create a new event with create set to false
await clickDate(target, "2016-12-13");
assert.containsNone(
target,
".modal",
"shouldn't open a quick create dialog for creating a new event with create attribute set to false"
);
}
);
QUnit.test(
`attempt to create record with "create" and "quick_add" attributes set to false`,
async (assert) => {
assert.expect(1);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar create="0" event_open_popup="1" quick_create="0" date_start="start" date_stop="stop" mode="month" />
`,
});
// attempt to create a new event with create set to false
await clickDate(target, "2016-12-13");
assert.containsNone(
target,
".modal",
"shouldn't open a form view for creating a new event with create attribute set to false"
);
}
);
QUnit.test(
`attempt to create multiples events and the same day and check the ordering on month view`,
async (assert) => {
// This test aims to verify that the order of the event in month view is coherent with their start date.
patchDate(2020, 2, 12, 8, 0, 0); // 2020-03-12 08:00:00
serverData.models.event.records = [
{
id: 1,
name: "Second event",
start: "2020-03-12 05:00:00",
stop: "2020-03-12 07:00:00",
allday: false,
},
{
id: 2,
name: "First event",
start: "2020-03-12 02:00:00",
stop: "2020-03-12 03:00:00",
allday: false,
},
{
id: 3,
name: "Third event",
start: "2020-03-12 08:00:00",
stop: "2020-03-12 09:00:00",
allday: false,
},
];
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="month" />
`,
});
assert.containsOnce(
target,
".o_calendar_renderer .fc-view-container",
"should display in the calendar"
);
// Testing the order of the events: by start date
assert.containsN(target, ".o_event_title", 3, "3 events should be available");
assert.strictEqual(
target.querySelector(".o_event_title").textContent,
"First event",
"First event should be on top"
);
}
);
QUnit.test(`create event and resize to next day (24h) on week mode`, async (assert) => {
// WOWL FYI Legacy test name: "drag and drop 24h event on week mode"
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar event_open_popup="1" quick_create="0" date_start="start" date_stop="stop" all_day="allday" mode="week" />
`,
mockRPC(route, { args, method }) {
if (method === "create") {
assert.deepEqual(args[0], [
{
allday: false,
name: "foobar",
start: "2016-12-13 07:00:00",
start_date: false,
stop: "2016-12-13 15:00:00",
stop_date: false,
},
]);
}
if (method === "write") {
assert.deepEqual(args[1], {
allday: false,
start: "2016-12-13 07:00:00",
stop: "2016-12-14 07:00:00",
});
}
},
});
await selectTimeRange(target, "2016-12-13 08:00:00", "2016-12-13 16:00:00");
await editInput(target, ".modal [name=name] input", "foobar");
await click(target, ".modal .o_form_button_save");
await resizeEventToTime(target, 8, "2016-12-14 08:00:00");
const event = findEvent(target, 8);
assert.strictEqual(event.textContent, "foobar");
assert.ok(event.closest(".fc-day-grid"), "event should be in the all day slots");
});
QUnit.test(`correctly display year view`, async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar create="0" event_open_popup="1" date_start="start" date_stop="stop" all_day="allday" mode="year" attendee="partner_ids" color="partner_id">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
<field name="partner_id" filters="1" invisible="1" />
<field name="is_hatched" invisible="1" />
<field name="is_striked" invisible="1" />
</calendar>
`,
});
await toggleFilter(target, "partner_ids", 1);
await toggleFilter(target, "partner_ids", 2);
// Check view
assert.containsN(target, ".fc-month", 12);
assert.strictEqual(
target.querySelector(".fc-month .fc-header-toolbar").textContent,
"January 2016"
);
assert.containsN(
target,
".fc-bgevent",
7,
"There should be 6 events displayed but there is 1 split on 2 weeks"
);
assert.containsN(target, ".o_event_hatched", 3);
assert.containsOnce(target, ".o_event_striked");
await clickDate(target, "2016-11-17");
assert.containsNone(target, ".o_popover");
await clickDate(target, "2016-11-16");
assert.containsOnce(target, ".o_popover");
let popoverText = target
.querySelector(".o_popover")
.textContent.replace(/\s{2,}/g, " ")
.trim();
assert.strictEqual(popoverText, "November 14-16, 2016event 7");
await click(target, ".o_cw_popover_close");
assert.containsNone(target, ".o_popover");
await clickDate(target, "2016-11-14");
assert.containsOnce(target, ".o_popover");
popoverText = target
.querySelector(".o_popover")
.textContent.replace(/\s{2,}/g, " ")
.trim();
assert.strictEqual(popoverText, "November 14-16, 2016event 7");
await click(target, ".o_cw_popover_close");
assert.containsNone(target, ".o_popover");
await clickDate(target, "2016-11-13");
assert.containsNone(target, ".o_popover");
await clickDate(target, "2016-12-10");
assert.containsNone(target, ".o_popover");
await clickDate(target, "2016-12-12");
assert.containsOnce(target, ".o_popover");
popoverText = target
.querySelector(".o_popover")
.textContent.replace(/\s{2,}/g, " ")
.trim();
assert.strictEqual(popoverText, "December 12, 201611:55event 216:55event 3");
await click(target, ".o_cw_popover_close");
assert.containsNone(target, ".o_popover");
await clickDate(target, "2016-12-14");
assert.containsOnce(target, ".o_popover");
popoverText = target
.querySelector(".o_popover")
.textContent.replace(/\s{2,}/g, " ")
.trim();
assert.strictEqual(popoverText, "December 14, 2016event 4December 13-20, 2016event 5");
await click(target, ".o_cw_popover_close");
assert.containsNone(target, ".o_popover");
await clickDate(target, "2016-12-21");
assert.containsNone(target, ".o_popover");
});
QUnit.test(`toggle filters in year view`, async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar event_open_popup="1" date_start="start" date_stop="stop" all_day="allday" mode="year" attendee="partner_ids" color="partner_id">
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
<field name="partner_id" filters="1" invisible="1" />
</calendar>
`,
});
function checkEvents(countMap) {
for (const [id, count] of Object.entries(countMap)) {
assert.containsN(target, `.fc-bgevent[data-event-id="${id}"]`, count);
}
}
// activate partner filter
await toggleFilter(target, "partner_ids", 1);
await toggleFilter(target, "partner_ids", 2);
checkEvents({ 1: 1, 2: 1, 3: 1, 4: 1, 5: 2, 7: 1 });
await toggleFilter(target, "partner_ids", 2);
checkEvents({ 1: 1, 2: 1, 3: 1, 4: 1, 5: 0, 7: 0 });
await toggleFilter(target, "partner_id", 1);
checkEvents({ 1: 0, 2: 0, 3: 1, 4: 0, 5: 0, 7: 0 });
await toggleFilter(target, "partner_id", 4);
checkEvents({ 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 7: 0 });
await toggleFilter(target, "partner_ids", 1);
checkEvents({ 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 7: 0 });
await toggleFilter(target, "partner_ids", 2);
checkEvents({ 1: 1, 2: 1, 3: 0, 4: 0, 5: 2, 7: 1 });
await toggleFilter(target, "partner_id", 4);
checkEvents({ 1: 1, 2: 1, 3: 0, 4: 0, 5: 0, 7: 1 });
});
QUnit.test(`allowed scales`, async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" scales="day,week" />
`,
});
await click(target, ".o_view_scale_selector .scale_button_selection");
assert.containsOnce(target, ".o_view_scale_selector .o_scale_button_day");
assert.containsOnce(target, ".o_view_scale_selector .o_scale_button_week");
assert.containsNone(target, ".o_view_scale_selector .o_scale_button_month");
assert.containsNone(target, ".o_view_scale_selector .o_scale_button_year");
});
QUnit.test(`click outside the popup should close it`, async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar create="0" event_open_popup="1" quick_create="0" date_start="start" date_stop="stop" all_day="allday" mode="month" />
`,
});
assert.containsNone(target, ".o_cw_popover");
await clickEvent(target, 1);
assert.containsOnce(target, ".o_cw_popover", "open popup when click on event");
await click(target, ".o_cw_popover .o_cw_body");
assert.containsOnce(target, ".o_cw_popover", "keep popup openned when click inside popup");
await click(target);
assert.containsNone(target, ".o_cw_popover", "close popup when click outside popup");
});
QUnit.test(`fields are added in the right order in popover`, async (assert) => {
const def = makeDeferred();
class DeferredWidget extends Component {
setup() {
onWillStart(() => def);
}
}
DeferredWidget.template = xml``;
fieldRegistry.add("deferred_widget", { component: DeferredWidget });
registerCleanup(() => fieldRegistry.remove("deferred_widget"));
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="month">
<field name="user_id" widget="deferred_widget" />
<field name="name" />
</calendar>
`,
});
await clickEvent(target, 4);
assert.containsNone(target, ".o_cw_popover");
def.resolve();
await nextTick();
assert.containsOnce(target, ".o_cw_popover");
assert.strictEqual(
target.querySelector(".o_cw_popover .o_cw_popover_fields_secondary").textContent,
"usernameevent 4"
);
});
QUnit.test(`select events and discard create`, async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar event_open_popup="1" date_start="start" date_stop="stop" all_day="allday" mode="year" />
`,
});
assert.containsN(target, ".fc-dayGridMonth-view", 12, "should display in year mode");
await selectDateRange(target, "2016-11-13", "2016-11-19");
assert.containsOnce(
target,
".o-calendar-quick-create",
"should open the form view in dialog when select multiple days"
);
assert.hasAttrValue(
target.querySelector(".fc-highlight"),
"colspan",
"7",
"should highlight 7 days"
);
await click(target, ".o-calendar-quick-create--cancel-btn");
assert.containsNone(target, ".fc-highlight", "should not highlight days");
});
QUnit.test(`create event in year view`, async (assert) => {
assert.expect(6);
let expectedEvent;
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar event_open_popup="1" date_start="start" date_stop="stop" all_day="allday" mode="year" />
`,
mockRPC(route, { method, args }) {
if (method === "create") {
const [values] = args[0];
assert.deepEqual(values, expectedEvent);
}
},
});
// Select the whole month of July
expectedEvent = {
allday: true,
start: "2016-07-01",
stop: "2016-07-31",
name: "Whole July",
};
await selectDateRange(target, "2016-07-01", "2016-07-31");
await editInput(target, ".o-calendar-quick-create--input[name=title]", "Whole July");
await click(target, ".o-calendar-quick-create--create-btn");
// get all rows for event 8
assert.containsN(target, ".o_event[data-event-id='8']", 6);
assert.deepEqual(
[...target.querySelectorAll(".o_event[data-event-id='8']")].map((cell) => cell.colSpan),
[2, 7, 7, 7, 7, 1],
"rows should highlight multiple days"
);
// Select the whole month of November
expectedEvent = {
allday: true,
start: "2016-11-01",
stop: "2016-11-30",
name: "Whole November",
};
await selectDateRange(target, "2016-11-01", "2016-11-30");
await editInput(target, ".o-calendar-quick-create--input[name=title]", "Whole November");
await click(target, ".o-calendar-quick-create--create-btn");
// get all rows for event 9
assert.containsN(target, ".o_event[data-event-id='9']", 5);
assert.deepEqual(
[...target.querySelectorAll(".o_event[data-event-id='9']")].map((cell) => cell.colSpan),
[5, 7, 7, 7, 4],
"rows should highlight multiple days"
);
});
QUnit.test(`popover ignores readonly field modifier`, async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="month">
<field name="delay" invisible="True" />
<field name="name" readonly="delay == 42" />
</calendar>
`,
});
await clickEvent(target, 4);
// test would fail here if we don't ignore readonly modifier
assert.containsOnce(target, ".o_cw_popover");
});
QUnit.test("can not select invalid scale from datepicker", async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="month" scales="month,year">
<field name="delay" invisible="True" />
<field name="name" readonly="delay == 42" />
</calendar>
`,
});
await click(target, ".o_datetime_picker .o_today");
// test would fail here if we went to week mode
assert.containsOnce(target, ".fc-dayGridMonth-view");
});
QUnit.test("calendar with custom quick create view", async (assert) => {
serviceRegistry.add(
"dialog",
makeFakeDialogService((className, props) => {
assert.equal(props.viewId, 2);
return () => {};
}),
{ force: true }
);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" mode="month" quick_create="1" quick_create_view_id="2">
<field name="name"/>
</calendar>
`,
});
const date = target.querySelector(".fc-day-grid td");
await clickAllDaySlot(target, date.dataset.date);
});
QUnit.test("check onWillStartModel is exectuted", async (assert) => {
assert.expect(3);
class TestCalendarController extends CalendarController {
setup() {
super.setup();
onWillRender(() => {
assert.step("render");
});
}
onWillStartModel() {
assert.step("onWillStartModel");
}
}
viewRegistry.add("test_calendar_view", {
...calendarView,
Controller: TestCalendarController,
});
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar js_class="test_calendar_view" date_start="start" date_stop="stop" all_day="allday" mode="month"/>
`,
limit: 3,
});
assert.verifySteps(["onWillStartModel", "render"]);
});
QUnit.test("check apply default record label", async (assert) => {
assert.expect(1);
class TestCalendarController extends CalendarController {
get editRecordDefaultDisplayText() {
return "Test Display";
}
}
viewRegistry.add("test_calendar_view", {
...calendarView,
Controller: TestCalendarController,
});
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar js_class="test_calendar_view" date_start="start" date_stop="stop" all_day="allday" mode="month" quick_create="0" event_open_popup="1" />
`,
});
await clickDate(target, "2016-12-13");
assert.strictEqual(
target.querySelector(".modal-title").textContent,
"Test Display",
"The text in the title should be Test Display"
);
});
QUnit.test(`calendar render properties in popover`, async (assert) => {
serverData.models.event.fields.properties = {
string: "Properties",
type: "properties",
definition_record: "event_type_id",
definition_record_field: "definitions",
};
serverData.models.event_type.fields.definitions = {
string: "Definitions",
type: "properties_definition",
};
serverData.models.event_type.records[0].definitions = [
{ name: "event_prop_1", string: "My Char", type: "char" },
{ name: "event_prop_2", string: "My Selection", type: "selection" },
];
serverData.models.event.records[0].event_type_id = 1;
serverData.models.event.records[0].properties = [
{
name: "property_1",
string: "My Char",
type: "char",
value: "hello",
view_in_cards: true,
},
{
name: "property_2",
string: "My Selection",
type: "selection",
selection: [
["a", "A"],
["b", "B"],
["c", "C"],
],
value: "b",
default: "c",
view_in_cards: true,
},
{
name: "property_3",
string: "Hidden Char",
type: "char",
value: "hidden",
view_in_cards: false,
},
];
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar quick_create="0" event_open_popup="1" date_start="start">
<field name="event_type_id" />
<field name="properties" />
</calendar>
`,
async mockRPC(route, args) {
if (args.method === "check_access_rights") {
return true;
}
},
});
await clickEvent(target, 1);
assert.deepEqual(
[
...target.querySelectorAll(".o_popover [name='properties'] .o_card_property_field"),
].map((el) => el.textContent),
["hello", "B"]
);
});
QUnit.test(`calendar create record with default properties`, async (assert) => {
serverData.models.event.fields.properties = {
string: "Properties",
type: "properties",
definition_record: "event_type_id",
definition_record_field: "definitions",
default: [{ name: "event_prop", string: "Hello", type: "char" }],
};
serverData.models.event_type.fields.definitions = {
string: "Definitions",
type: "properties_definition",
};
serverData.views["event,false,form"] = `
<form>
<group>
<field name="name" />
<field name="event_type_id" />
<field name="properties" />
</group>
</form>
`;
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar quick_create="0" event_open_popup="1" date_start="start">
<field name="event_type_id" />
<field name="properties" />
</calendar>
`,
async mockRPC(route, args) {
if (args.method === "check_access_rights") {
return true;
}
},
});
await selectTimeRange(target, "2016-12-15 06:00:00", "2016-12-15 08:00:00");
assert.containsOnce(target, ".modal");
assert.strictEqual(target.querySelector(".modal [name='properties']").textContent, "Hello");
});
QUnit.test("calendar show past events with background blur", async (assert) => {
assert.expect(2);
patchDate(2016, 11, 14, 9, 0, 0);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="week"/>
`,
});
assert.containsN(target, ".fc-event", 5, "should show 5 events");
assert.containsN(target, ".fc-event.o_past_event", 4, "should show 4 past events");
});
QUnit.test("calendar sidebar state is saved on session storage", async (assert) => {
patchWithCleanup(browser, {
sessionStorage: {
setItem(key, value) {
if (key === "calendar.showSideBar") {
assert.step(`${key}-${value}`);
}
},
getItem(key) {
if (key === "calendar.showSideBar") {
assert.step(`${key}-read`);
return false;
}
},
},
});
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="week"/>
`,
});
assert.containsNone(target, ".o_calendar_sidebar");
await click(target, ".o_calendar_header .oi-panel-right");
assert.containsOnce(target, ".o_calendar_sidebar");
assert.verifySteps(["calendar.showSideBar-read", "calendar.showSideBar-true"]);
});
QUnit.test("calendar should show date information on header", async (assert) => {
assert.expect(6);
patchDate(2015, 11, 26, 9, 0, 0);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="week"/>
`,
});
const header = target.querySelector(".o_calendar_header h5.d-inline-flex");
assert.equal(header.textContent, "December 2015 Week 52");
await changeScale(target, "day");
assert.equal(header.textContent, "26 December 2015");
await changeScale(target, "month");
assert.equal(header.textContent, "December 2015");
await changeScale(target, "year");
assert.equal(header.textContent, "2015");
await changeScale(target, "week");
await navigate(target, "next");
assert.equal(header.textContent, "December 2015 - January 2016 Week 53");
await navigate(target, "prev");
await navigate(target, "prev");
await navigate(target, "prev");
await navigate(target, "prev");
assert.equal(header.textContent, "November - December 2015 Week 49");
});
QUnit.module("CalendarView - DatePicker", ({ beforeEach }) => {
beforeEach(() => {
target = getFixture();
patchDate(2021, 7, 14, 8, 0, 0);
});
QUnit.test("Mount a CalendarDatePicker", async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="day"/>
`,
});
assert.containsOnce(target, ".o_datetime_picker");
assert.containsOnce(target.querySelector(".o_datetime_picker"), ".o_selected");
assert.strictEqual(
target.querySelector(".o_datetime_picker .o_selected").textContent,
"14"
);
assert.strictEqual(
target.querySelector(".o_datetime_picker_header .o_datetime_button").textContent,
"August 2021"
);
assert.deepEqual(
Array.from(target.querySelectorAll(".o_datetime_picker .o_day_of_week_cell")).map(
(c) => c.textContent
),
["S", "M", "T", "W", "T", "F", "S"]
);
});
QUnit.test("Scale: init with day", async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="day"/>
`,
});
assert.containsOnce(target.querySelector(".o_datetime_picker"), ".o_highlighted");
assert.containsOnce(
target.querySelector(".o_datetime_picker"),
".o_highlight_start, .o_highlight_end"
);
assert.strictEqual(
target.querySelector(".o_datetime_picker .o_highlighted").textContent,
"14"
);
});
QUnit.test("Scale: init with week", async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="week"/>
`,
});
assert.containsOnce(target.querySelector(".o_datetime_picker"), ".o_highlighted");
assert.containsOnce(
target.querySelector(".o_datetime_picker"),
".o_highlight_start, .o_highlight_end"
);
assert.strictEqual(
target.querySelector(".o_datetime_picker .o_highlighted").textContent,
"14"
);
});
QUnit.test("Scale: init with month", async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="month"/>
`,
});
assert.containsOnce(target.querySelector(".o_datetime_picker"), ".o_highlighted");
assert.containsOnce(
target.querySelector(".o_datetime_picker"),
".o_highlight_start, .o_highlight_end"
);
assert.strictEqual(
target.querySelector(".o_datetime_picker .o_highlighted").textContent,
"14"
);
});
QUnit.test("Scale: init with year", async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="year"/>
`,
});
assert.containsOnce(target.querySelector(".o_datetime_picker"), ".o_highlighted");
assert.containsOnce(
target.querySelector(".o_datetime_picker"),
".o_highlight_start, .o_highlight_end"
);
assert.strictEqual(
target.querySelector(".o_datetime_picker .o_highlighted").textContent,
"14"
);
});
QUnit.test("First day: 0 = Sunday", async (assert) => {
// the week start depends on the locale
patchWithCleanup(localization, { weekStart: 7 });
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="day"/>
`,
});
assert.deepEqual(
Array.from(target.querySelectorAll(".o_datetime_picker .o_day_of_week_cell")).map(
(c) => c.textContent
),
["S", "M", "T", "W", "T", "F", "S"]
);
});
QUnit.test("First day: 1 = Monday", async (assert) => {
// the week start depends on the locale
patchWithCleanup(localization, { weekStart: 1 });
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="day"/>
`,
});
assert.deepEqual(
Array.from(target.querySelectorAll(".o_datetime_picker .o_day_of_week_cell")).map(
(c) => c.textContent
),
["M", "T", "W", "T", "F", "S", "S"]
);
});
QUnit.test("Click on active day should change scale : day -> month", async (assert) => {
assert.expect(2);
const calendar = await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="day"/>
`,
});
await click(target.querySelector(".o_datetime_picker"), ".o_selected");
assert.strictEqual(calendar.model.scale, "month");
assert.ok(calendar.model.date.equals(luxon.DateTime.local(2021, 8, 14)));
});
QUnit.test("Click on active day should change scale : month -> week", async (assert) => {
assert.expect(2);
const calendar = await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="month"/>
`,
});
await click(target.querySelector(".o_datetime_picker"), ".o_selected");
assert.strictEqual(calendar.model.scale, "week");
assert.ok(calendar.model.date.equals(luxon.DateTime.local(2021, 8, 14)));
});
QUnit.test("Click on active day should change scale : week -> day", async (assert) => {
assert.expect(2);
const calendar = await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="week"/>
`,
});
await click(target.querySelector(".o_datetime_picker"), ".o_selected");
assert.strictEqual(calendar.model.scale, "day");
assert.ok(calendar.model.date.equals(luxon.DateTime.local(2021, 8, 14)));
});
QUnit.test("Scale: today is correctly highlighted", async (assert) => {
patchDate(2021, 6, 4, 8, 0, 0);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="month"/>
`,
});
assert.containsOnce(
target.querySelector(".o_datetime_picker"),
".o_highlighted.o_today"
);
assert.strictEqual(
target.querySelector(".o_datetime_picker .o_highlighted.o_today").textContent,
"4"
);
});
QUnit.test("Scale: scale default is fetched from sessionStorage", async (assert) => {
assert.expect(4);
patchWithCleanup(browser, {
sessionStorage: {
setItem(key, value) {
if (key === "calendar-scale") {
assert.step(`scale_${value}`);
}
},
getItem(key) {
if (key === "calendar-scale") {
return "month";
}
},
},
});
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar event_open_popup="1" date_start="start" date_stop="stop" attendee="partner_ids">
<field name="partner_ids" write_field="partner_id" />
</calendar>
`,
});
assert.equal(target.querySelector(".scale_button_selection").textContent, "Month");
await changeScale(target, "year");
assert.equal(target.querySelector(".scale_button_selection").textContent, "Year");
assert.verifySteps(["scale_year"]);
});
});
QUnit.test("calendar sidebar filters are ASC sorted (not valued @end)", async (assert) => {
patchDate(2023, 11, 14, 9, 0, 0);
serverData.models.event.records = [];
for (let i = 1; i <= 18; i++) {
serverData.models.event.records.push({
user_id: i,
name: `event ${i}`,
start: "2023-12-11 00:00:00",
stop: "2023-12-11 00:00:00",
});
}
serverData.models.event.records.push({
name: `event X`,
start: "2023-12-11 00:00:00",
stop: "2023-12-11 00:00:00",
});
serverData.models.user.records = [
{ id: 1, display_name: "Zoooro" },
{ id: 2, display_name: "Jean-Paul 1" },
{ id: 3, display_name: "Jean-Paul 2" },
{ id: 4, display_name: "Jeremy" },
{ id: 5, display_name: "Kévin" },
{ id: 6, display_name: "Romelü" },
{ id: 7, display_name: "Edên" },
{ id: 8, display_name: "Thibaùlt" },
{ id: 9, display_name: "1 - brol" },
{ id: 10, display_name: "10 - machin" },
{ id: 11, display_name: "11 - chose" },
{ id: 12, display_name: "101" },
{ id: 13, display_name: "100 - bidule" },
{ id: 14, display_name: "1000 - truc" },
{ id: 15, display_name: "00 - bazar" },
{ id: 16, display_name: "0 - chouette" },
{ id: 17, display_name: "@Hello" },
{ id: 18, display_name: "#Hello" },
];
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" mode="month">
<field name="user_id" filters="1"/>
</calendar>
`,
});
assert.strictEqual(
target.querySelector(".o_calendar_filter_items").textContent,
"00 - bazar0 - chouette1 - brol10 - machin11 - chose100 - bidule1011000 - trucEdên@Hello#HelloJean-Paul 1Jean-Paul 2JeremyKévinRomelüThibaùltZoooroUndefined"
);
});
QUnit.test("save selected date during view switching", async function (assert) {
serverData.models.event.records = [];
serverData.actions = {
1: {
id: 1,
name: "Partners",
res_model: "event",
type: "ir.actions.act_window",
views: [
[false, "list"],
[false, "calendar"],
],
},
};
serverData.views = {
"event,false,calendar": `<calendar date_start="start" date_stop="stop" mode="week"/>`,
"event,false,list": `<tree sample="1">
<field name="start"/>
<field name="stop"/>
</tree>`,
"event,false,search": `<search />`,
};
const webClient = await createWebClient({
serverData,
async mockRPC(route) {
if (route.endsWith("/has_group")) {
return true;
}
},
});
await doAction(webClient, 1);
await click(target, ".o_cp_switch_buttons .o_calendar");
await click(target, ".o_calendar_button_next");
const weekNumber = target.querySelector(".fc-week-number").textContent;
await click(target, ".o_cp_switch_buttons .o_list");
await click(target, ".o_cp_switch_buttons .o_calendar");
assert.equal(weekNumber, target.querySelector(".fc-week-number").textContent);
});
QUnit.test(
"sample data are not removed when switching back from calendar view",
async function (assert) {
serverData.models.event.records = [];
serverData.actions = {
1: {
id: 1,
name: "Partners",
res_model: "event",
type: "ir.actions.act_window",
views: [
[false, "list"],
[false, "calendar"],
],
},
};
serverData.views = {
"event,false,calendar": `<calendar date_start="start" date_stop="stop" mode="day"/>`,
"event,false,list": `<tree sample="1">
<field name="start"/>
<field name="stop"/>
</tree>`,
"event,false,search": `<search />`,
};
const webClient = await createWebClient({
serverData,
async mockRPC(route, args) {
if (args.method === "check_access_rights") {
return true;
}
if (route.endsWith("/has_group")) {
return true;
}
},
});
await doAction(webClient, 1);
assert.containsOnce(target, ".o_list_view", "should have rendered a list view");
assert.containsOnce(target, ".o_view_sample_data", "should have sample data");
await click(target, ".o_cp_switch_buttons .o_calendar");
assert.containsOnce(
target,
".o_calendar_container",
"should have rendered a calendar view"
);
await click(target, ".o_cp_switch_buttons .o_list");
assert.containsOnce(target, ".o_list_view", "should have rendered a list view");
assert.containsOnce(target, ".o_view_sample_data", "should have sample data");
}
);
QUnit.test("Retaining the 'all' filter value on re-rendering", async (assert) => {
serverData.actions = {
1: {
id: 1,
name: "Partners",
res_model: "event",
type: "ir.actions.act_window",
views: [
[false, "calendar"],
[false, "list"],
],
},
};
serverData.views = {
"event,false,calendar": `<calendar date_start="start" date_stop="stop" all_day="allday" mode="week" event_open_popup="1" attendee="partner_ids" color="partner_id">
<filter name="user_id" avatar_field="image" />
<field name="partner_ids" write_model="filter_partner" write_field="partner_id" />
<field name="partner_id" filters="1" invisible="1" />
</calendar>`,
"event,false,list": `<tree sample="1">
<field name="start"/>
<field name="stop"/>
</tree>`,
"event,false,search": `<search />`,
};
const webClient = await createWebClient({
serverData,
async mockRPC(route, args) {
if (args.method === "check_access_rights") {
return true;
}
if (route.endsWith("/has_group")) {
return true;
}
},
});
await doAction(webClient, 1);
await click(target, ".o_calendar_filter_item[data-value='all'] input");
assert.ok(
document.querySelector(".o_calendar_filter_item[data-value='all'] input").checked,
"Check if the value of the 'all' filter is set to true"
);
await click(target, ".o_cp_switch_buttons .o_list");
await click(target, ".o_cp_switch_buttons .o_calendar");
assert.ok(
document.querySelector(".o_calendar_filter_item[data-value='all'] input").checked,
"The value of the 'all' filter should remain the same as it was before re-rendering"
);
});
QUnit.test(`Resizing Pill of Multiple Days(Allday)`, async (assert) => {
const { advanceTime } = mockTimeout();
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar event_open_popup="1" date_start="start" date_stop="stop" all_day="allday" delete="0" mode="month" >
<field name="stop"/>
</calendar>`,
mockRPC(route, { args, method }) {
if (method === "create") {
assert.deepEqual(
args[0],
[
{
allday: true,
name: "new event",
start: "2016-12-25",
stop: "2016-12-28",
},
],
"should send the correct data to create events"
);
} else if (method === "write") {
assert.deepEqual(args[1], {
allday: true,
start: "2016-12-25",
stop: "2016-12-31",
});
}
},
});
await selectAllDayRange(target, "2016-12-25", "2016-12-28");
await editInput(target, ".o-calendar-quick-create--input", "new event");
await click(target, ".o-calendar-quick-create--create-btn");
await resizeEventToDate(target, 8, "2016-12-31");
await clickEvent(target, 8);
await advanceTime(300);
assert.strictEqual(
target
.querySelector(
".o_cw_popover .o_cw_popover_fields_secondary .list-group-item .o_field_datetime"
)
.textContent.split(" ")[0],
"12/31/2016",
"should have correct stop date"
);
});
QUnit.test(`update time while drag and drop on month mode`, async (assert) => {
assert.expect(2);
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" mode="month" event_open_popup="1" quick_create="0">
<field name="name" />
</calendar>
`,
});
// Create event (on 20 december)
await clickDate(target, "2016-12-20");
await editInput(target, ".modal-body .o_field_widget[name=name] input", "An event");
await click(target, ".form-check-input");
await editInput(
target,
".modal-body .o_field_widget[name=start] input",
"2016-12-20 08:00:00"
);
await editInput(
target,
".modal-body .o_field_widget[name=stop] input",
"2016-12-22 10:00:00"
);
await click(target, ".modal .o_form_button_save");
await moveEventToDate(target, 8, "2016-12-29");
await clickEvent(target, 8);
await click(target, ".o_cw_popover .o_cw_popover_edit");
const input_start = target.querySelector(".o_field_widget[name='start'] input");
assert.strictEqual(input_start.value, "12/28/2016 08:00:00", "should display the datetime");
const input_stop = target.querySelector(".o_field_widget[name='stop'] input");
assert.strictEqual(input_stop.value, "12/30/2016 10:00:00", "should display the datetime");
});
QUnit.test("html field on calendar shouldn't have a tooltip", async (assert) => {
serverData.models.event.fields.description = { string: "Description", type: "html" };
serverData.models.event.records.push({
id: 8,
user_id: uid,
partner_id: 1,
name: "event with html",
start: "2016-12-12 12:00:00",
stop: "2016-12-12 12:00:00",
allday: false,
partner_ids: [1, 2, 3],
type: 1,
is_hatched: false,
description: "<h1>description</h1>",
});
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start">
<field name="description"/>
</calendar>
`,
});
await clickEvent(target, 8);
const descriptionField = target.querySelector(
'.o_cw_popover_field .o_field_widget[name="description"]'
);
const parentLi = descriptionField.closest("li");
assert.strictEqual(parentLi.getAttribute("data-tooltip"), "");
});
});