/** @odoo-module **/ import { click, nextTick, getFixture, patchDate, patchWithCleanup } from "@web/../tests/helpers/utils"; import { clickAllDaySlot } from "@web/../tests/views/calendar/helpers"; import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; import { registry } from "@web/core/registry"; import { userService } from "@web/core/user_service"; import testUtils from '@web/../tests/legacy/helpers/test_utils'; import { getServerModels } from "./appointment_tests_common"; const { DateTime } = luxon; const serviceRegistry = registry.category("services"); const mockRegistry = registry.category("mock_server"); let target; let serverData; const uid = 1; let appointmentMock; QUnit.module('appointment.appointment_link', { before: function () { appointmentMock = mockRegistry.get("/appointment/appointment_type/get_staff_user_appointment_types"); mockRegistry.add("/appointment/appointment_type/get_staff_user_appointment_types", function (route, args) { if (route === "/appointment/appointment_type/get_staff_user_appointment_types") { const domain = [ ['staff_user_ids', 'in', [1]], ['category', '!=', 'custom'], ['website_published', '=', true], ]; const appointment_types_info = this.mockSearchRead('appointment.type', [domain, ['category', 'name']], {}); return Promise.resolve({ appointment_types_info: appointment_types_info }); } }, { force: true }); }, beforeEach: function () { serverData = { models: { ...getServerModels(DateTime.now().plus({ year: 1 }).year), 'appointment.slot': { fields: { appointment_type_id: {type: 'many2one', relation: 'appointment.type'}, start_datetime: {string: 'Start', type: 'datetime'}, end_datetime: {string: 'End', type: 'datetime'}, duration: {string: 'Duration', type: 'float'}, slot_type: { string: 'Slot Type', type: 'selection', selection: [['recurring', 'Recurring'], ['unique', 'One Shot']], }, }, }, 'filter_partner': { fields: { id: {string: "ID", type: "integer"}, user_id: {string: "user", type: "many2one", relation: 'res.users'}, partner_id: {string: "partner", type: "many2one", relation: 'res.partner'}, partner_checked: {string: "checked", type: "boolean"}, }, records: [ { id: 4, user_id: uid, partner_id: uid, partner_checked: true }, { id: 5, user_id: 214, partner_id: 214, partner_checked: true, } ] }, }, views: {}, }; patchDate(DateTime.now().plus({years:1}).year, 0, 5, 0, 0, 0); target = getFixture(); setupViewRegistries(); serviceRegistry.add( "user", { ...userService, start() { const fakeUserService = userService.start(...arguments); Object.defineProperty(fakeUserService, "userId", { get: () => uid, }); return fakeUserService; }, }, { force: true } ); }, after: function () { mockRegistry.add("/appointment/appointment_type/get_staff_user_appointment_types", appointmentMock, { force: true }); } }, function () { QUnit.test('verify appointment links button are displayed', async function (assert) { assert.expect(3); await makeView({ type: "calendar", resModel: 'calendar.event', serverData, arch: ` `, mockRPC: async function (route, args) { if (route === '/web/dataset/call_kw/res.partner/get_attendee_detail') { return Promise.resolve([]); } else if (route === '/web/dataset/call_kw/res.users/has_group') { return Promise.resolve(true); } else if (route === '/calendar/check_credentials') { return Promise.resolve({}); } }, }); assert.containsOnce(target, 'button:contains("Share Availabilities")'); await click(target, '.dropdownAppointmentLink'); assert.containsOnce(target, 'button:contains("Test Appointment")'); assert.containsOnce(target, 'button:contains("Any Time")'); }); QUnit.test('create/search anytime appointment type', async function (assert) { assert.expect(9); patchWithCleanup(navigator, { clipboard: { writeText: (value) => { assert.strictEqual( value, `http://amazing.odoo.com/appointment/3?filter_staff_user_ids=%5B${uid}%5D` ); } }, }); await makeView({ type: "calendar", resModel: 'calendar.event', serverData, arch: ` `, mockRPC: function (route, args) { if (route === "/appointment/appointment_type/search_create_anytime") { assert.step(route); } else if (route === '/web/dataset/call_kw/res.partner/get_attendee_detail') { return Promise.resolve([]); } else if (route === '/web/dataset/call_kw/res.users/has_group') { return Promise.resolve(true); } else if (route === '/calendar/check_credentials') { return Promise.resolve({}); } }, session: { 'web.base.url': 'http://amazing.odoo.com', }, }); assert.strictEqual(2, serverData.models['appointment.type'].records.length) await click(target.querySelector('.dropdownAppointmentLink')); await click(target.querySelector('.o_appointment_search_create_anytime_appointment')); await nextTick(); assert.verifySteps(['/appointment/appointment_type/search_create_anytime']); assert.strictEqual(3, serverData.models['appointment.type'].records.length, "Create a new appointment type") await click(target.querySelector('.o_appointment_discard_slots')); await click(target.querySelector('.dropdownAppointmentLink')); await click(target.querySelector('.o_appointment_search_create_anytime_appointment')); await nextTick(); assert.verifySteps(['/appointment/appointment_type/search_create_anytime']); assert.strictEqual(3, serverData.models['appointment.type'].records.length, "Does not create a new appointment type"); }); QUnit.test('discard slot in calendar', async function (assert) { assert.expect(11); const calendar = await makeView({ type: "calendar", resModel: 'calendar.event', serverData, arch: ` `, mockRPC: async function (route, args) { if (route === '/web/dataset/call_kw/res.partner/get_attendee_detail') { return Promise.resolve([]); } else if (route === '/web/dataset/call_kw/res.users/has_group') { return Promise.resolve(true); } else if (route === '/calendar/check_credentials') { return Promise.resolve({}); } }, }); await click(target.querySelector('.o_calendar_filter_item[data-value=all] input')); await click(target.querySelector('.o_appointment_select_slots')); await nextTick(); assert.strictEqual(calendar.env.calendarState.mode, 'slots-creation', "The calendar is now in a mode to create custom appointment time slots"); assert.containsN(target, '.fc-event', 2); assert.containsNone(target, '.o_calendar_slot'); // Same behavior as previous next button (+7 days) const currentDayPickerElement = target.querySelector('.o_datetime_picker .o_today.o_selected'); const allPickerElement = [...currentDayPickerElement.parentElement.children] await click(allPickerElement[allPickerElement.indexOf(currentDayPickerElement) + 7]); await nextTick(); assert.containsOnce(target, '.fc-event', 'There is one calendar event'); assert.containsNone(target, '.o_calendar_slot', 'There is no slot yet'); await clickAllDaySlot(target, DateTime.now().toFormat("yyyy'-01-12'")); await nextTick(); assert.containsN(target, '.fc-event', 2, 'There is 2 events in the calendar'); assert.containsOnce(target, '.o_calendar_slot', 'One of them is a slot'); await click(target.querySelector('button.o_appointment_discard_slots')); await nextTick(); assert.containsOnce(target, '.fc-event', 'The calendar event is still here'); assert.containsNone(target, '.o_calendar_slot', 'The slot has been discarded'); await click(target.querySelector('.o_calendar_button_today')); await nextTick(); assert.containsN(target, '.fc-event', 2); assert.containsNone(target, '.o_calendar_slot'); }); QUnit.test("cannot move real event in slots-creation mode", async function (assert) { assert.expect(4); const calendar = await makeView({ type: "calendar", resModel: 'calendar.event', serverData, arch: ` `, mockRPC: function (route, args) { if (args.method === "write") { assert.step('write event'); } else if (route === '/web/dataset/call_kw/res.partner/get_attendee_detail') { return Promise.resolve([]); } else if (route === '/web/dataset/call_kw/res.users/has_group') { return Promise.resolve(true); } else if (route === '/calendar/check_credentials') { return Promise.resolve({}); } }, }); await click(target.querySelector('.o_calendar_filter_item[data-value=all] input')); await click(target.querySelector('.o_appointment_select_slots')); assert.strictEqual(calendar.env.calendarState.mode, 'slots-creation', "The calendar is now in a mode to create custom appointment time slots"); assert.containsN(target, '.fc-event', 2); assert.containsNone(target, '.o_calendar_slot'); await testUtils.dom.dragAndDrop($(target.querySelector('.fc-event')), $(target.querySelector('.fc-day'))); await nextTick(); assert.verifySteps([]); }); QUnit.test("create slots for custom appointment type", async function (assert) { assert.expect(13); patchWithCleanup(navigator, { clipboard: { writeText: (value) => { assert.strictEqual( value, `http://amazing.odoo.com/appointment/3?filter_staff_user_ids=%5B${uid}%5D` ); } } }); const calendar = await makeView({ type: "calendar", resModel: 'calendar.event', serverData, arch: ` `, mockRPC: function (route, args) { if (route === "/appointment/appointment_type/create_custom") { assert.step(route); } else if (route === '/web/dataset/call_kw/res.partner/get_attendee_detail') { return Promise.resolve([]); } else if (route === '/web/dataset/call_kw/res.users/has_group') { return Promise.resolve(true); } else if (route === '/calendar/check_credentials') { return Promise.resolve({}); } }, }); await click(target.querySelector('.o_calendar_filter_item[data-value=all] input')); await click(target.querySelector('.o_appointment_select_slots')); assert.strictEqual(calendar.env.calendarState.mode, 'slots-creation', "The calendar is now in a mode to create custom appointment time slots"); assert.containsN(target, '.fc-event', 2); assert.containsNone(target, '.o_calendar_slot'); // Same behavior as previous next button (+7 days) const currentDayPickerElement = target.querySelector('.o_datetime_picker .o_today.o_selected'); const allPickerElement = [...currentDayPickerElement.parentElement.children] await click(allPickerElement[allPickerElement.indexOf(currentDayPickerElement) + 7]); assert.containsOnce(target, '.fc-event', 'There is one calendar event'); assert.containsNone(target, '.o_calendar_slot', 'There is no slot yet'); await clickAllDaySlot(target, DateTime.now().toFormat("yyyy'-01-12'")); await nextTick(); assert.containsN(target, '.fc-event', 2, 'There is 2 events in the calendar'); assert.containsOnce(target, '.o_calendar_slot', 'One of them is a slot'); await click(target.querySelector('button.o_appointment_get_link')); assert.verifySteps(['/appointment/appointment_type/create_custom']); assert.containsOnce(target, '.fc-event', 'The calendar event is still here'); assert.containsNone(target, '.o_calendar_slot', 'The slot has been cleared after the creation'); assert.strictEqual(serverData.models['appointment.slot'].records.length, 1); }); QUnit.test('filter works in slots-creation mode', async function (assert) { assert.expect(11); const calendar = await makeView({ type: "calendar", resModel: 'calendar.event', serverData, arch: ` `, mockRPC: function (route, args) { if (route === '/web/dataset/call_kw/res.partner/get_attendee_detail') { return Promise.resolve([]); } else if (route === '/web/dataset/call_kw/res.users/has_group') { return Promise.resolve(true); } else if (route === '/calendar/check_credentials') { return Promise.resolve({}); } }, }); await click(target.querySelector('.o_calendar_filter_item[data-value=all] input')); // Two events are displayed assert.containsN(target, '.fc-event', 2); assert.containsNone(target, '.o_calendar_slot'); // Switch to slot-creation mode and create a slot for a custom appointment type await click(target.querySelector('.o_appointment_select_slots')); assert.strictEqual(calendar.env.calendarState.mode, 'slots-creation', "The calendar is now in a mode to create custom appointment time slots"); // Same behavior as previous next button (+7 days) const currentDayPickerElement = target.querySelector('.o_datetime_picker .o_today.o_selected'); const allPickerElement = [...currentDayPickerElement.parentElement.children] await click(allPickerElement[allPickerElement.indexOf(currentDayPickerElement) + 7]); assert.containsOnce(target, '.fc-event'); assert.containsNone(target, '.o_calendar_slot'); await clickAllDaySlot(target, DateTime.now().toFormat("yyyy'-01-12'")); await nextTick(); assert.containsN(target, '.fc-event', 2, 'There is 2 events in the calendar'); assert.containsOnce(target, '.o_calendar_slot', 'One of them is a slot'); // Modify filters of the calendar to display less calendar event await click(target.querySelector('.o_calendar_filter_item:last-of-type > input')); assert.containsOnce(target, '.fc-event', 'There is now only 1 events displayed'); assert.containsOnce(target, '.o_calendar_slot', 'The slot created is still displayed'); await click(target.querySelector('.o_calendar_filter_item:last-of-type > input')); await click(target.querySelector('button.o_appointment_discard_slots')); assert.containsOnce(target, '.fc-event', 'There is now only 1 calendar event displayed'); assert.containsNone(target, '.o_calendar_slot', 'There is no more slots in the calendar view'); }); QUnit.test('click & copy appointment type url', async function (assert) { assert.expect(3); patchWithCleanup(navigator, { clipboard: { writeText: (value) => { assert.strictEqual( value, `http://amazing.odoo.com/appointment/2?filter_staff_user_ids=%5B${uid}%5D` ); } } }); await makeView({ type: "calendar", resModel: 'calendar.event', serverData, arch: ` `, mockRPC: function (route, args) { if (route === '/appointment/appointment_type/get_book_url') { assert.step(route) } else if (route === '/web/dataset/call_kw/res.partner/get_attendee_detail') { return Promise.resolve([]); } else if (route === '/web/dataset/call_kw/res.users/has_group') { return Promise.resolve(true); } else if (route === '/calendar/check_credentials') { return Promise.resolve({}); } }, }); await click(target.querySelector('.dropdownAppointmentLink')); await click(target.querySelector('.o_appointment_appointment_link_clipboard')); assert.verifySteps(['/appointment/appointment_type/get_book_url']); }); });