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

5104 lines
189 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** @odoo-module **/
import { registerCleanup } from "../../helpers/cleanup";
import { defaultLocalization } from "../../helpers/mock_services";
import {
click,
editInput,
getFixture,
makeDeferred,
nextTick,
patchDate,
patchTimeZone,
patchWithCleanup,
triggerEvent,
} from "../../helpers/utils";
import {
changeScale,
clickDate,
clickEvent,
findEvent,
findFilterPanelFilter,
findFilterPanelSection,
findPickedDate,
findTimeRow,
moveEventToAllDaySlot,
moveEventToDate,
moveEventToTime,
navigate,
pickDate,
resizeEventToTime,
selectAllDayRange,
selectDateRange,
selectTimeRange,
toggleFilter,
toggleSectionFilter,
} 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 { CharField } from "@web/views/fields/char/char_field";
import { actionService } from "@web/webclient/actions/action_service";
const fieldRegistry = registry.category("fields");
const preloadedDataRegistry = registry.category("preloadedData");
const serviceRegistry = registry.category("services");
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" },
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 attrs="{'invisible': [['allday', '=', True]]}">
<field name="start" />
<field name="stop" />
</group>
<group attrs="{'invisible': [['allday', '=', False]]}">
<field name="start_date" />
<field name="stop_date" />
</group>
</form>
`,
"event,1,form": `
<form>
<field name="allday" invisible="1" />
<field name="start" attrs="{'invisible': [['allday', '=', False]]}" />
<field name="stop" attrs="{'invisible': [['allday', '=', True]]}" />
</form>
`,
},
};
});
QUnit.module("CalendarView");
QUnit.test(`simple calendar rendering`, async (assert) => {
assert.expect(24);
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,
});
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" />
<field name="partner_id" filters="1" 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",
9,
"should display 9 events on the week (4 event + 5 days event)"
);
assert.containsN(
target,
".o_calendar_sidebar tr:has(.ui-state-active) td",
7,
"week scale should highlight 7 days in mini calendar"
);
await click(target, ".scale_button_selection");
await click(target, ".o_calendar_button_day"); // display only one day
assert.containsN(target, ".fc-event", 2, "should display 2 events on the day");
assert.containsOnce(
target,
".o_calendar_sidebar .o_selected_range",
"should highlight the target day in mini calendar"
);
await click(target, ".scale_button_selection");
await click(target, ".o_calendar_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",
7,
"should display 7 events on the month (5 events + 2 week event - 1 'event 6' is filtered + 1 'Undefined event')"
);
assert.containsN(
target,
".o_calendar_sidebar td a",
31,
"month scale should highlight all days in mini calendar"
);
// 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",
7,
"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", 4, "should now only display 4 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 2", "partner 1", "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()
),
["partner 2", "partner 1", "foo partner 5", "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()
),
["partner 2", "partner 1", "foo partner 5", "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(`breadcrumbs are updated with the displayed period`, async (assert) => {
await makeView({
type: "calendar",
resModel: "event",
serverData,
arch: `
<calendar date_start="start" date_stop="stop" all_day="allday" />
`,
});
// displays week mode by default
assert.strictEqual(
target.querySelector(".o_control_panel .breadcrumb-item.active").textContent,
"undefined (Dec 11 17, 2016)",
"should display the current week"
);
// switch to day mode
await changeScale(target, "day");
assert.strictEqual(
target.querySelector(".o_control_panel .breadcrumb-item.active").textContent,
"undefined (December 12, 2016)",
"should display the current day"
);
// switch to month mode
await changeScale(target, "month");
assert.strictEqual(
target.querySelector(".o_control_panel .breadcrumb-item.active").textContent,
"undefined (December 2016)",
"should display the current month"
);
// switch to year mode
await changeScale(target, "year");
assert.strictEqual(
target.querySelector(".o_control_panel .breadcrumb-item.active").textContent,
"undefined (2016)",
"should display the current year"
);
});
QUnit.test(`create and change events`, async (assert) => {
assert.expect(28);
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 === "write") {
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,
"Confirmation",
"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.containsNone(target, ".fc-event-container .fc-event", "should display 0 events");
await navigate(target, "prev");
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 = this._super(...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_add="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,
[
{
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");
assert.strictEqual(
dayHeaders[0].textContent,
"Sun 11",
"The first day of the week should be Sunday"
);
assert.strictEqual(
dayHeaders[dayHeaders.length - 1].textContent,
"Sat 17",
"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");
assert.strictEqual(
dayHeaders[0].textContent,
"Mon 12",
"The first day of the week should be Monday"
);
assert.strictEqual(
dayHeaders[dayHeaders.length - 1].textContent,
"Sun 18",
"The last day of the week should be Sunday"
);
});
QUnit.test(`week numbering`, async (assert) => {
// The week is now calculated by FullCalendar (ISO week). If it's start a sunday it
// returns the week of the monday.
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, 4);
assert.containsOnce(target, ".o_cw_popover", "should open a popover clicking on event");
assert.strictEqual(
target.querySelector(".o_cw_popover .popover-header").textContent,
"event 4",
"popover should have a title 'event 4'"
);
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.querySelector(".o_cw_popover .list-group-item b.text-capitalize").textContent,
"December 14, 2016",
"should display date 'December 14, 2016'"
);
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("strong").textContent,
"Custom Name: ",
"label should be a 'Custom Name'"
);
assert.strictEqual(
groups[0].querySelector(".o_field_char").textContent,
"event 4",
"value should be a 'event 4'"
);
assert.containsOnce(groups[1], ".o_form_uri", "should apply m20 widget");
assert.strictEqual(
groups[1].querySelector("strong").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" attrs="{'invisible': [['is_hatched', '=', False]]}" />
<field name="start" attrs="{'invisible': [['is_hatched', '=', True]]}" />
</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 with widget which has specialData attribute`, async (assert) => {
assert.expect(3);
fieldRegistry.add("specialWidget", CharField);
preloadedDataRegistry.add("specialWidget", {
loadOnTypes: ["char"],
preload: () => {
assert.step("_fetchSpecialDataForMyWidget");
},
});
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" widget="specialWidget" />
<field name="partner_id" />
</calendar>
`,
});
await clickEvent(target, 4);
assert.containsOnce(target, ".o_cw_popover", "should open a popover clicking on event");
assert.verifySteps(["_fetchSpecialDataForMyWidget"]);
});
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 === "create") {
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"] .o_datepicker .o_datepicker_input`);
await click(
document.body,
`.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]`
);
await click(
document.body,
`.bootstrap-datetimepicker-widget .timepicker .timepicker-hour`
);
await click(
document.body.querySelectorAll(
`.bootstrap-datetimepicker-widget .timepicker-hours td.hour`
)[8]
);
await click(
document.body,
`.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]`
);
// use datepicker to enter a date: 12/13/2016 10:00:00
await click(target, `.o_field_widget[name="stop"] .o_datepicker .o_datepicker_input`);
await click(
document.body,
`.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]`
);
await click(
document.body,
`.bootstrap-datetimepicker-widget .timepicker .timepicker-hour`
);
await click(
document.body.querySelectorAll(
`.bootstrap-datetimepicker-widget .timepicker-hours td.hour`
)[10]
);
await click(
document.body,
`.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]`
);
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");
assert.strictEqual(
headers[0].textContent,
"Sun 11",
"The calendar start date should be 2016-12-11"
);
assert.strictEqual(
headers[headers.length - 1].textContent,
"Sat 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 === "create") {
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"] .o_datepicker .o_datepicker_input`);
await click(
document.body,
`.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]`
);
await click(
document.body,
`.bootstrap-datetimepicker-widget .timepicker .timepicker-hour`
);
await click(
document.body.querySelectorAll(
`.bootstrap-datetimepicker-widget .timepicker-hours td.hour`
)[8]
);
await click(
document.body,
`.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]`
);
// use datepicker to enter a date: 12/13/2016 10:00:00
await click(target, `.o_field_widget[name="stop"] .o_datepicker .o_datepicker_input`);
await click(
document.body,
`.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]`
);
await click(
document.body,
`.bootstrap-datetimepicker-widget .timepicker .timepicker-hour`
);
await click(
document.body.querySelectorAll(
`.bootstrap-datetimepicker-widget .timepicker-hours td.hour`
)[10]
);
await click(
document.body,
`.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]`
);
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_add="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_add="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",
9,
"should display 9 events on the week (4 event + 5 days 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",
4,
"should display 4 events on the week (1 event + 3 days 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",
4,
"should display 4 events on the week (1 event + 3 days 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 for month mode`, 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",
"should show time in week mode"
);
// switch to day mode
await changeScale(target, "day");
assert.containsOnce(
findEvent(target, 2),
".fc-content .fc-time",
"should show time in day mode"
);
});
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"
);
});
QUnit.test(`start time should not shown in month mode 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"
);
});
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", 9, "should display 9 events on the week");
// Click on the "all" filter to reload all events
await toggleFilter(target, "partner_ids", "all");
assert.containsN(target, ".fc-event", 9, "should display 9 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()
),
["Forest", "Desert", "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.hasClass(findEvent(target, 5), "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.hasClass(findEvent(target, 5), "o_calendar_color_4");
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", 11, "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 navigate(target, "prev");
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).querySelector(".o_event_title").textContent,
"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 (All day)"
);
});
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(`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") {
assert.strictEqual(args[0].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_add="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_add="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"] .o_datepicker .o_datepicker_input`);
await click(
document.body,
`.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]`
);
await click(document.body, `.bootstrap-datetimepicker-widget .timepicker .timepicker-hour`);
await click(
document.body.querySelectorAll(
`.bootstrap-datetimepicker-widget .timepicker-hours td.hour`
)[7]
);
await click(
document.body,
`.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]`
);
// use datepicker to enter a date: 12/20/2016 19:00:00
await click(target, `.o_field_widget[name="stop"] .o_datepicker .o_datepicker_input`);
await click(
document.body,
`.bootstrap-datetimepicker-widget .picker-switch a[data-action="togglePicker"]`
);
await click(document.body, `.bootstrap-datetimepicker-widget .timepicker .timepicker-hour`);
await click(
document.body.querySelectorAll(
`.bootstrap-datetimepicker-widget .timepicker-hours td.hour`
)[19]
);
await click(
document.body,
`.bootstrap-datetimepicker-widget .picker-switch a[data-action="close"]`
);
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_add="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.textContent),
["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(1);
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 .breadcrumb-item").textContent,
"context initial date (January 30, 2016)",
"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");
assert.strictEqual(
dayHeaders[0].textContent,
"Sunday",
"The first day of the week should be Sunday"
);
assert.strictEqual(
dayHeaders[dayHeaders.length - 1].textContent,
"Saturday",
"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");
assert.strictEqual(
dayHeaders[0].textContent,
"Monday",
"The first day of the week should be Monday"
);
assert.strictEqual(
dayHeaders[dayHeaders.length - 1].textContent,
"Sunday",
"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(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 });
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"
);
}
},
});
const dayHeaders = target.querySelectorAll(".fc-day-header");
assert.strictEqual(
dayHeaders[0].textContent,
"Mon 9",
"The first day of the week should be Monday the 9th"
);
assert.strictEqual(
dayHeaders[dayHeaders.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(4);
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"
);
}
},
});
const dayHeaders = target.querySelectorAll(".fc-day-header");
assert.strictEqual(
dayHeaders[0].textContent,
"Sat 7",
"The first day of the week should be Saturday the 7th"
);
assert.strictEqual(
dayHeaders[dayHeaders.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 36",
"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 { ...this._super(), 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 { ...this._super(), 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 === "write") {
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_add="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_add="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.notOk(event.closest(".fc-day-grid"), "event should not 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,
"Jan 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:55 event 216:55 event 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_calendar_scale_buttons .scale_button_selection");
assert.containsOnce(target, ".o_calendar_scale_buttons .o_calendar_button_day");
assert.containsOnce(target, ".o_calendar_scale_buttons .o_calendar_button_week");
assert.containsNone(target, ".o_calendar_scale_buttons .o_calendar_button_month");
assert.containsNone(target, ".o_calendar_scale_buttons .o_calendar_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_add="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 owl.Component {
setup() {
owl.onWillStart(() => def);
}
}
DeferredWidget.template = owl.xml``;
fieldRegistry.add("deferred_widget", 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,
"user: name: event 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") {
assert.deepEqual(args[0], 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="name" attrs="{'readonly': [['unknown_field', '=', 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="name" attrs="{'readonly': [['unknown_field', '=', 42]]}" />
</calendar>
`,
});
await click(target, ".ui-datepicker-today");
// test would fail here if we went to week mode
assert.containsOnce(target, ".fc-dayGridMonth-view");
});
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("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"), "");
});
});