forked from Mapan/odoo17e
569 lines
23 KiB
JavaScript
569 lines
23 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import { addBusServicesToRegistry } from "@bus/../tests/helpers/test_utils";
|
|
|
|
import { mountRoomBookingView } from "@room/../tests/room_booking_tests_utils";
|
|
|
|
import { click, editInput, nextTick, patchDate } from "@web/../tests/helpers/utils";
|
|
import {
|
|
makeFakeDialogService,
|
|
makeFakeNotificationService,
|
|
} from "@web/../tests/helpers/mock_services";
|
|
import { registry } from "@web/core/registry";
|
|
|
|
/**
|
|
* Assert that the given slots are correctly displayed in the view.
|
|
* The only distinction between slots is their (text-)background color.
|
|
* Slots are expected to be strings formatted as "hhmm".
|
|
* @param {QUnit.assert} assert
|
|
* @param {HTMLElement} target
|
|
* @param {Object} slots
|
|
* @param {Array} slots.bookedSlots
|
|
* @param {Array} slots.freeSlots
|
|
* @param {Array} slots.selectedSlots
|
|
* @param {Array} slots.selectableSlots
|
|
*/
|
|
const assertSlots = (assert, target, slots) => {
|
|
slots.bookedSlots?.forEach((slot) => {
|
|
assert.containsOnce(target, `#slot${slot} > .bg-secondary`);
|
|
});
|
|
slots.freeSlots?.forEach((slot) => {
|
|
assert.containsOnce(target, `#slot${slot} > .bg-success`);
|
|
});
|
|
slots.selectedSlots?.forEach((slot) => {
|
|
assert.containsOnce(target, `#slot${slot} > .text-bg-primary`);
|
|
});
|
|
slots.selectableSlots?.forEach((slot) => {
|
|
assert.containsOnce(target, `#slot${slot} > .text-bg-success`);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Click on a slot in the view and waits for the next tick.
|
|
* @param {HTMLElement} target
|
|
* @param {string} slot ("hhmm" format).
|
|
* @returns {Promise}
|
|
*/
|
|
const clickOnSlot = async (target, slot) => {
|
|
click(target, `#slot${slot}`);
|
|
await nextTick();
|
|
};
|
|
|
|
/**
|
|
* Click on the schedule button and waits for the next tick.
|
|
* @param {HTMLElement} target
|
|
* @returns {Promise}
|
|
* */
|
|
const clickSchedule = async (target) => {
|
|
click(target, ".btn.rounded-pill i.fa-calendar-plus-o");
|
|
await nextTick();
|
|
};
|
|
|
|
QUnit.module("Room Booking Form", (hooks) => {
|
|
hooks.beforeEach(async () => {
|
|
addBusServicesToRegistry();
|
|
registry.category("services").add("dialog", makeFakeDialogService());
|
|
registry.category("services").add("notification", makeFakeNotificationService());
|
|
});
|
|
|
|
// Test view flow with no existing bookings
|
|
QUnit.test("Room Booking Form - No existing booking", async (assert) => {
|
|
const mockRPC = (route, args) => {
|
|
if (route === "/room/room_test/get_existing_bookings") {
|
|
return [];
|
|
}
|
|
};
|
|
patchDate(2023, 5, 17, 10, 35, 0);
|
|
const { target } = await mountRoomBookingView(mockRPC);
|
|
await clickSchedule(target);
|
|
// First slot is now
|
|
const slotsList = target.querySelectorAll(".o_room_scheduler_slots .col");
|
|
assert.strictEqual(slotsList[0].innerText, "10:35 AM");
|
|
// Second slot is the next half hour
|
|
assert.strictEqual(slotsList[1].innerText, "11:00 AM");
|
|
// Last slot is 11:30 PM (midnight is added when a start date is selected)
|
|
assert.strictEqual(slotsList[slotsList.length - 1].innerText, "11:30 PM");
|
|
// All slots should be free
|
|
assert.containsN(target, ".col > .bg-success", slotsList.length);
|
|
|
|
// Click on a slot to select a start date (8PM)
|
|
await clickOnSlot(target, "2000");
|
|
assertSlots(assert, target, {
|
|
freeSlots: ["1035", "1930"],
|
|
selectedSlots: ["2000"],
|
|
selectableSlots: ["2030", "0000"],
|
|
});
|
|
assert.containsOnce(target, "#slot2030:contains('0:30')");
|
|
assert.containsOnce(target, "#slot0000:contains('4:00')");
|
|
|
|
// Click on another slot to select a stop date (9PM)
|
|
await clickOnSlot(target, "2100");
|
|
assertSlots(assert, target, {
|
|
freeSlots: ["1035", "1930"],
|
|
selectedSlots: ["2000", "2030", "2100"],
|
|
selectableSlots: ["2130", "0000"],
|
|
});
|
|
// Duration of selectable slots should not have changed
|
|
assert.containsOnce(target, "#slot2130:contains('1:30')");
|
|
assert.containsOnce(target, "#slot0000:contains('4:00')");
|
|
|
|
// Click on another slot to select another stop date
|
|
await clickOnSlot(target, "2130");
|
|
assertSlots(assert, target, {
|
|
freeSlots: ["1035", "1930"],
|
|
selectedSlots: ["2000", "2030", "2100", "2130"],
|
|
selectableSlots: ["2200", "0000"],
|
|
});
|
|
assert.containsOnce(target, "#slot2200:contains('2:00')");
|
|
assert.containsOnce(target, "#slot0000:contains('4:00')");
|
|
|
|
// Click on another slot to select another start date
|
|
await clickOnSlot(target, "1930");
|
|
assertSlots(assert, target, {
|
|
freeSlots: ["1035", "1900"],
|
|
selectedSlots: ["1930", "2000", "2100", "2130"],
|
|
selectableSlots: ["2200", "0000"],
|
|
});
|
|
assert.containsOnce(target, "#slot2200:contains('2:30')");
|
|
assert.containsOnce(target, "#slot0000:contains('4:30')");
|
|
|
|
// Click on selected start to deselect start and stop
|
|
await clickOnSlot(target, "1930");
|
|
// Every slot should appear as free
|
|
assert.containsN(target, ".col > .bg-success", slotsList.length);
|
|
|
|
// Click on a slot to select a start date
|
|
await clickOnSlot(target, "2000");
|
|
assertSlots(assert, target, {
|
|
freeSlots: ["1035", "1930"],
|
|
selectedSlots: ["2000"],
|
|
selectableSlots: ["2030", "2130", "0000"],
|
|
});
|
|
// Click on a slot to select a stop date
|
|
await clickOnSlot(target, "2130");
|
|
assertSlots(assert, target, {
|
|
freeSlots: ["1035", "1930"],
|
|
selectedSlots: ["2000", "2030", "2100", "2130"],
|
|
selectableSlots: ["2200", "0000"],
|
|
});
|
|
// Click on selected stop date to deselect it
|
|
await clickOnSlot(target, "2130");
|
|
assertSlots(assert, target, {
|
|
freeSlots: ["1035", "1930"],
|
|
selectedSlots: ["2000"],
|
|
selectableSlots: ["2030", "2100", "2200"],
|
|
});
|
|
// Duration of selectable slots should not have changed
|
|
assert.containsOnce(target, "#slot2200:contains('2:00')");
|
|
assert.containsOnce(target, "#slot0000:contains('4:00')");
|
|
|
|
// Click on selected start to deselect it
|
|
await clickOnSlot(target, "2000");
|
|
// Every slot should appear as free
|
|
assert.containsN(target, ".col > .bg-success", slotsList.length);
|
|
});
|
|
|
|
// Test view flow with existing bookings
|
|
QUnit.test("Room Booking Form - Existing bookings", async (assert) => {
|
|
const mockRPC = (route, args) => {
|
|
if (route === "/room/room_test/get_existing_bookings") {
|
|
return [
|
|
{
|
|
id: 1,
|
|
name: "Booking 1",
|
|
start_datetime: "2023-06-17 10:00:00",
|
|
stop_datetime: "2023-06-17 11:00:00",
|
|
},
|
|
{
|
|
id: 2,
|
|
name: "Booking 2",
|
|
start_datetime: "2023-06-17 11:00:00",
|
|
stop_datetime: "2023-06-17 11:26:00",
|
|
},
|
|
{
|
|
id: 3,
|
|
name: "Booking 3",
|
|
start_datetime: "2023-06-17 14:00:00",
|
|
stop_datetime: "2023-06-17 14:15:00",
|
|
},
|
|
{
|
|
id: 4,
|
|
name: "Booking 4",
|
|
start_datetime: "2023-06-17 14:45:00",
|
|
stop_datetime: "2023-06-17 15:00:00",
|
|
},
|
|
];
|
|
}
|
|
};
|
|
patchDate(2023, 5, 17, 10, 35, 0);
|
|
const { target } = await mountRoomBookingView(mockRPC);
|
|
await clickSchedule(target);
|
|
assertSlots(assert, target, {
|
|
bookedSlots: ["1100", "1130", "1200", "1500", "1530"],
|
|
freeSlots: ["1035", "1230"],
|
|
});
|
|
// There should be only 5 booked slots
|
|
assert.containsN(target, ".col > .bg-secondary", 5);
|
|
// Nothing happens when clicking on a booked slot
|
|
await clickOnSlot(target, "1100");
|
|
// There should still be 5 booked slots
|
|
assert.containsN(target, ".col > .bg-secondary", 5);
|
|
// No slot should have been selected
|
|
assert.containsNone(target, ".col > .text-bg-primary");
|
|
// Select a start slot between 2 bookings
|
|
await clickOnSlot(target, "1300");
|
|
assertSlots(assert, target, {
|
|
bookedSlots: ["1100", "1130", "1200", "1530"],
|
|
selectedSlots: ["1300"],
|
|
selectableSlots: ["1330", "1500"],
|
|
});
|
|
// Clicking on a slot that is booked does nothing
|
|
await clickOnSlot(target, "1530");
|
|
assertSlots(assert, target, {
|
|
bookedSlots: ["1100", "1130", "1200", "1530"],
|
|
selectedSlots: ["1300"],
|
|
selectableSlots: ["1330", "1500"],
|
|
});
|
|
// Select the end slot (first booked slot that follows the selected start that should be selectable)
|
|
await clickOnSlot(target, "1500");
|
|
assertSlots(assert, target, {
|
|
bookedSlots: ["1100", "1130", "1200", "1530"],
|
|
selectedSlots: ["1300", "1330", "1400", "1430", "1500"],
|
|
});
|
|
// Click on a free slot after the next booked slot (should select it as start)
|
|
await clickOnSlot(target, "1800");
|
|
assertSlots(assert, target, {
|
|
bookedSlots: ["1100", "1130", "1200", "1500", "1530"],
|
|
selectedSlots: ["1800"],
|
|
selectableSlots: ["1830", "0000"],
|
|
});
|
|
// Select a stop date
|
|
await clickOnSlot(target, "1900");
|
|
assertSlots(assert, target, {
|
|
bookedSlots: ["1100", "1130", "1200", "1500", "1530"],
|
|
selectedSlots: ["1800", "1830", "1900"],
|
|
selectableSlots: ["1930", "0000"],
|
|
});
|
|
// Click on a free slot before the previous booked slot (should reset end)
|
|
await clickOnSlot(target, "1300");
|
|
assertSlots(assert, target, {
|
|
bookedSlots: ["1100", "1130", "1200", "1530"],
|
|
selectedSlots: ["1300"],
|
|
selectableSlots: ["1330", "1500"],
|
|
});
|
|
});
|
|
|
|
// Test booking creation flow
|
|
QUnit.test("Room Booking Form - Create a Booking", async (assert) => {
|
|
assert.expect(2);
|
|
const mockRPC = (route, args) => {
|
|
if (route === "/room/room_test/get_existing_bookings") {
|
|
return [];
|
|
} else if (route === "/room/room_test/booking/create") {
|
|
assert.deepEqual(args, {
|
|
name: "Meeting",
|
|
start_datetime: "2023-06-17 10:00:00",
|
|
stop_datetime: "2023-06-17 11:00:00",
|
|
});
|
|
return true;
|
|
}
|
|
};
|
|
|
|
patchDate(2023, 5, 17, 10, 35, 0);
|
|
const { target } = await mountRoomBookingView(mockRPC);
|
|
await clickSchedule(target);
|
|
await editInput(target, "input[placeholder='Booking Name']", "Meeting");
|
|
await clickOnSlot(target, "1100");
|
|
await clickOnSlot(target, "1200");
|
|
// Mocked RPC will assert that the booking data is correct
|
|
click(target, ".o_room_scheduler > div:last-child .btn-primary");
|
|
await nextTick();
|
|
// Should come back to main view (no notification has been sent so the
|
|
// sidebar won't be updated)
|
|
assert.containsOnce(target, ".fa-check-circle.fa-3x");
|
|
});
|
|
|
|
// Test booking edition flow
|
|
QUnit.test("Room Booking Form - Editing booking", async (assert) => {
|
|
assert.expect(13);
|
|
const mockRPC = (route, args) => {
|
|
if (route === "/room/room_test/get_existing_bookings") {
|
|
return [
|
|
{
|
|
id: 1,
|
|
name: "Booking 1",
|
|
start_datetime: "2023-06-17 11:00:00",
|
|
stop_datetime: "2023-06-17 12:00:00",
|
|
},
|
|
{
|
|
id: 2,
|
|
name: "Booking 2",
|
|
start_datetime: "2023-06-17 12:00:00",
|
|
stop_datetime: "2023-06-17 13:00:00",
|
|
},
|
|
];
|
|
} else if (route === "/room/room_test/booking/2/update") {
|
|
assert.deepEqual(args, {
|
|
name: "Edited Meeting",
|
|
start_datetime: "2023-06-17 13:00:00",
|
|
stop_datetime: "2023-06-17 14:00:00",
|
|
});
|
|
return true;
|
|
}
|
|
};
|
|
patchDate(2023, 5, 17, 10, 35, 0);
|
|
const { target } = await mountRoomBookingView(mockRPC);
|
|
click(target, ".o_room_sidebar .list-group-item:first-child");
|
|
await nextTick();
|
|
// Check that the slots of the booking to edit are selected
|
|
assert.strictEqual(
|
|
target.querySelector("input[placeholder='Booking Name']").value,
|
|
"Booking 1",
|
|
);
|
|
assertSlots(assert, target, {
|
|
bookedSlots: ["1330"],
|
|
selectedSlots: ["1200", "1230", "1300"],
|
|
});
|
|
// Click on the other booking in the sidebar to make sure the view is updated
|
|
click(target, ".o_room_sidebar .list-group-item:last-child");
|
|
await nextTick();
|
|
// Check that the title and the slots have been updated accordingly
|
|
assert.strictEqual(
|
|
target.querySelector("input[placeholder='Booking Name']").value,
|
|
"Booking 2",
|
|
);
|
|
assertSlots(assert, target, {
|
|
bookedSlots: ["1200", "1230"],
|
|
selectedSlots: ["1300", "1330", "1400"],
|
|
});
|
|
// Change title, start and end date
|
|
await editInput(target, "input[placeholder='Booking Name']", "Edited Meeting");
|
|
await clickOnSlot(target, "1300");
|
|
await clickOnSlot(target, "1400");
|
|
await clickOnSlot(target, "1500");
|
|
// Mocked RPC will assert that the data is correct
|
|
click(target, ".o_room_scheduler > div:last-child .btn-primary");
|
|
await nextTick();
|
|
// Should come back to main view (no notification has been sent so the
|
|
// sidebar won't be updated)
|
|
assert.containsOnce(target, ".fa-check-circle.fa-3x");
|
|
});
|
|
|
|
// Test flow of the editing the current booking (first slot shown is now and should be selected,
|
|
// and extending it should not update the start date)
|
|
QUnit.test("Room Booking Form - Edit Current Booking", async (assert) => {
|
|
const mockRPC = (route, args) => {
|
|
if (route === "/room/room_test/get_existing_bookings") {
|
|
return [
|
|
{
|
|
id: 1,
|
|
name: "Current Booking",
|
|
start_datetime: "2023-06-17 13:00:00",
|
|
stop_datetime: "2023-06-17 14:00:00",
|
|
},
|
|
];
|
|
} else if (route === "/room/room_test/booking/1/update") {
|
|
assert.deepEqual(args, {
|
|
name: "Extended Booking",
|
|
start_datetime: "2023-06-17 13:00:00",
|
|
stop_datetime: "2023-06-17 15:00:00",
|
|
});
|
|
return true;
|
|
}
|
|
};
|
|
patchDate(2023, 5, 17, 14, 35, 0);
|
|
const { target } = await mountRoomBookingView(mockRPC);
|
|
click(target, ".o_room_sidebar .list-group-item:first-child");
|
|
await nextTick();
|
|
// Check that the first slot is now and is selected
|
|
assert.containsOnce(target, "#slot1435 > .text-bg-primary");
|
|
// Extend the meeting
|
|
await clickOnSlot(target, "1600");
|
|
await editInput(target, "input[placeholder='Booking Name']", "Extended Booking");
|
|
// The start is the current slot, but it should not be considered as a new start
|
|
click(target, ".o_room_scheduler > div:last-child .btn-primary");
|
|
await nextTick();
|
|
// Should come back to main view
|
|
assert.containsOnce(target, ".fa-calendar-times-o.fa-3x");
|
|
});
|
|
|
|
// Test reception of a new booking while the view is opened (slots should update accordingly)
|
|
QUnit.test("Room Booking Form - Receiving new booking", async (assert) => {
|
|
const mockRPC = (route, args) => {
|
|
if (route === "/room/room_test/get_existing_bookings") {
|
|
return [];
|
|
}
|
|
};
|
|
patchDate(2023, 5, 17, 10, 35, 0);
|
|
const { notifyView, target } = await mountRoomBookingView(mockRPC, true);
|
|
await clickSchedule(target);
|
|
assert.containsN(target, ".col > .bg-success", 27);
|
|
// Send a new booking notification
|
|
await notifyView("booking/create", [
|
|
{
|
|
id: 1,
|
|
name: "Booking 1",
|
|
start_datetime: "2023-06-17 11:00:00",
|
|
stop_datetime: "2023-06-17 12:00:00",
|
|
},
|
|
]);
|
|
await nextTick();
|
|
// Slots should appear as booked
|
|
assertSlots(assert, target, {
|
|
bookedSlots: ["1200", "1230"],
|
|
});
|
|
// Other slots should remain free
|
|
assert.containsN(target, ".col > .bg-success", 25);
|
|
});
|
|
|
|
// Test reception of a deleted booking while the view is opened (slots should update accordingly)
|
|
QUnit.test("Room Booking Form - Receiving deleted booking", async (assert) => {
|
|
const mockRPC = (route, args) => {
|
|
if (route === "/room/room_test/get_existing_bookings") {
|
|
return [
|
|
{
|
|
id: 1,
|
|
name: "Booking 1",
|
|
start_datetime: "2023-06-17 11:00:00",
|
|
stop_datetime: "2023-06-17 12:00:00",
|
|
},
|
|
];
|
|
}
|
|
};
|
|
patchDate(2023, 5, 17, 10, 35, 0);
|
|
const { notifyView, target } = await mountRoomBookingView(mockRPC, true);
|
|
await clickSchedule(target);
|
|
// Make sure slots are marked as taken
|
|
assertSlots(assert, target, {
|
|
bookedSlots: ["1200", "1230"],
|
|
});
|
|
// Send notification of booking deletion
|
|
await notifyView("booking/delete", [{ id: 1 }]);
|
|
// Check that the slots have been freed
|
|
assertSlots(assert, target, {
|
|
freeSlots: ["1200", "1230"],
|
|
});
|
|
});
|
|
|
|
// Test reception of a booking update while the view is opened (slots should update accordingly)
|
|
QUnit.test("Room Booking Form - Receiving updated booking", async (assert) => {
|
|
const mockRPC = (route, args) => {
|
|
if (route === "/room/room_test/get_existing_bookings") {
|
|
return [
|
|
{
|
|
id: 1,
|
|
name: "Booking 1",
|
|
start_datetime: "2023-06-17 11:00:00",
|
|
stop_datetime: "2023-06-17 12:00:00",
|
|
},
|
|
];
|
|
}
|
|
};
|
|
patchDate(2023, 5, 17, 10, 35, 0);
|
|
const { notifyView, target } = await mountRoomBookingView(mockRPC, true);
|
|
await clickSchedule(target);
|
|
// Make sure slots are booked
|
|
assertSlots(assert, target, {
|
|
bookedSlots: ["1200", "1230"],
|
|
});
|
|
// Send notification of booking update
|
|
await notifyView("booking/update", [
|
|
{
|
|
id: 1,
|
|
start_datetime: "2023-06-17 12:00:00",
|
|
stop_datetime: "2023-06-17 13:00:00",
|
|
name: "Booking 1",
|
|
},
|
|
]);
|
|
// Check that the booked slots have changed
|
|
assertSlots(assert, target, {
|
|
bookedSlots: ["1300", "1330"],
|
|
freeSlots: ["1200", "1230"],
|
|
});
|
|
});
|
|
|
|
// Test correct behavior of the day selector (slots should update when changing date)
|
|
QUnit.test("Room Booking Form - Day Selector", async (assert) => {
|
|
const mockRPC = (route, args) => {
|
|
if (route === "/room/room_test/get_existing_bookings") {
|
|
return [
|
|
{
|
|
id: 1,
|
|
name: "Booking 1",
|
|
start_datetime: "2023-06-17 11:00:00",
|
|
stop_datetime: "2023-06-17 12:00:00",
|
|
},
|
|
{
|
|
id: 2,
|
|
name: "Booking 2",
|
|
start_datetime: "2023-06-18 12:00:00",
|
|
stop_datetime: "2023-06-18 13:00:00",
|
|
},
|
|
];
|
|
}
|
|
};
|
|
patchDate(2023, 5, 17, 10, 35, 0);
|
|
const { target } = await mountRoomBookingView(mockRPC);
|
|
await clickSchedule(target);
|
|
const daySelector = target.querySelector(".o_room_scheduler > div > div:last-child");
|
|
// Selected day is today by default
|
|
assert.containsOnce(daySelector, "button.btn-primary:contains('17')");
|
|
assertSlots(assert, target, {
|
|
bookedSlots: ["1200", "1230"],
|
|
freeSlots: ["1300"],
|
|
});
|
|
// Next day should not appear disabled and should load booking of next day
|
|
click(daySelector, "button.btn-primary + button:enabled");
|
|
await nextTick();
|
|
assert.containsOnce(daySelector, "button.btn-primary:contains('18')");
|
|
// Check that slots updated (and that morning slots are shown)
|
|
assertSlots(assert, target, {
|
|
bookedSlots: ["1300"],
|
|
freeSlots: ["0000", "0030", "1200"],
|
|
});
|
|
// Click on next week
|
|
click(daySelector, ".oi-chevron-right");
|
|
await nextTick();
|
|
assert.containsOnce(daySelector, "button.btn-primary:contains('25')");
|
|
// Check that slots updated
|
|
assertSlots(assert, target, {
|
|
freeSlots: ["1200", "1300"],
|
|
});
|
|
// Click on the first booking in the sidebar
|
|
click(target, ".o_room_sidebar .list-group-item:first-child");
|
|
await nextTick();
|
|
// Should come back to today
|
|
assert.containsOnce(daySelector, "button.btn-primary:contains('17')");
|
|
assertSlots(assert, target, {
|
|
selectedSlots: ["1200", "1230", "1300"],
|
|
});
|
|
});
|
|
|
|
// Check that the form is closed when the booking being edited is deleted by another user
|
|
QUnit.test("Room Booking Form - Delete booking being edited", async (assert) => {
|
|
const mockRPC = (route, args) => {
|
|
if (route === "/room/room_test/get_existing_bookings") {
|
|
return [
|
|
{
|
|
id: 1,
|
|
name: "Booking 1",
|
|
start_datetime: "2023-06-17 11:00:00",
|
|
stop_datetime: "2023-06-17 12:00:00",
|
|
},
|
|
];
|
|
}
|
|
};
|
|
patchDate(2023, 5, 17, 10, 35, 0);
|
|
const { notifyView, target } = await mountRoomBookingView(mockRPC, true);
|
|
click(target, ".o_room_sidebar .list-group-item:first-child");
|
|
await nextTick();
|
|
assertSlots(assert, target, {
|
|
selectedSlots: ["1200", "1230"],
|
|
});
|
|
// Notify view that booking has been deleted
|
|
await notifyView("booking/delete", [{ id: 1 }]);
|
|
// Make sure we left the booking form
|
|
assert.containsOnce(target, ".fa-check-circle.fa-3x");
|
|
});
|
|
});
|