* Reapply "fix: replace filter implementations on bookings (#19445)" (#19506)
This reverts commit f6af6e34bd.
* force custom date range selector for bookings except for Past tab
* fix e2e test
* add e2e tests
* fix e2e test
431 lines
18 KiB
TypeScript
431 lines
18 KiB
TypeScript
import { expect } from "@playwright/test";
|
|
|
|
import prisma from "@calcom/prisma";
|
|
import { BookingStatus } from "@calcom/prisma/client";
|
|
import { MembershipRole } from "@calcom/prisma/enums";
|
|
|
|
import { createTeamEventType } from "./fixtures/users";
|
|
import type { Fixtures } from "./lib/fixtures";
|
|
import { test } from "./lib/fixtures";
|
|
import { setupManagedEvent } from "./lib/testUtils";
|
|
|
|
test.afterEach(({ users }) => users.deleteAll());
|
|
|
|
test.describe("Bookings", () => {
|
|
test.describe("Upcoming bookings", () => {
|
|
test("show attendee bookings and organizer bookings in asc order by startDate", async ({
|
|
page,
|
|
users,
|
|
bookings,
|
|
}) => {
|
|
const firstUser = await users.create();
|
|
const secondUser = await users.create();
|
|
|
|
const bookingWhereFirstUserIsOrganizerFixture = await createBooking({
|
|
title: "Booking as organizer",
|
|
bookingsFixture: bookings,
|
|
// Create a booking 3 days from today
|
|
relativeDate: 3,
|
|
organizer: firstUser,
|
|
organizerEventType: firstUser.eventTypes[0],
|
|
attendees: [
|
|
{ name: "First", email: "first@cal.com", timeZone: "Europe/Berlin" },
|
|
{ name: "Second", email: "second@cal.com", timeZone: "Europe/Berlin" },
|
|
{ name: "Third", email: "third@cal.com", timeZone: "Europe/Berlin" },
|
|
],
|
|
});
|
|
const bookingWhereFirstUserIsOrganizer = await bookingWhereFirstUserIsOrganizerFixture.self();
|
|
|
|
const bookingWhereFirstUserIsAttendeeFixture = await createBooking({
|
|
title: "Booking as attendee",
|
|
bookingsFixture: bookings,
|
|
organizer: secondUser,
|
|
// Booking created 2 days from today
|
|
relativeDate: 2,
|
|
organizerEventType: secondUser.eventTypes[0],
|
|
attendees: [
|
|
{ name: "OrganizerAsBooker", email: firstUser.email, timeZone: "Europe/Berlin" },
|
|
{ name: "Second", email: "second@cal.com", timeZone: "Europe/Berlin" },
|
|
{ name: "Third", email: "third@cal.com", timeZone: "Europe/Berlin" },
|
|
],
|
|
});
|
|
const bookingWhereFirstUserIsAttendee = await bookingWhereFirstUserIsAttendeeFixture.self();
|
|
|
|
await firstUser.apiLogin();
|
|
await page.goto(`/bookings/upcoming`);
|
|
const upcomingBookings = page.locator('[data-testid="upcoming-bookings"]');
|
|
const firstUpcomingBooking = upcomingBookings.locator('[data-testid="booking-item"]').nth(0);
|
|
const secondUpcomingBooking = upcomingBookings.locator('[data-testid="booking-item"]').nth(1);
|
|
await expect(
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
firstUpcomingBooking.locator(`text=${bookingWhereFirstUserIsAttendee!.title}`)
|
|
).toBeVisible();
|
|
await expect(
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
secondUpcomingBooking.locator(`text=${bookingWhereFirstUserIsOrganizer!.title}`)
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("Cannot choose date range presets", async ({ page, users, bookings, webhooks }) => {
|
|
const firstUser = await users.create();
|
|
await firstUser.apiLogin();
|
|
await page.goto(`/bookings/upcoming`);
|
|
|
|
await page.locator('[data-testid="add-filter-button"]').click();
|
|
await page.locator('[data-testid="add-filter-item-dateRange"]').click();
|
|
await page.locator('[data-testid="filter-popover-trigger-dateRange"]').click();
|
|
|
|
await expect(page.locator('[data-testid="date-range-options-c"]')).toBeHidden();
|
|
await expect(page.locator('[data-testid="date-range-options-w"]')).toBeHidden();
|
|
await expect(page.locator('[data-testid="date-range-options-m"]')).toBeHidden();
|
|
await expect(page.locator('[data-testid="date-range-options-y"]')).toBeHidden();
|
|
await expect(page.locator('[data-testid="date-range-options-t"]')).toBeHidden();
|
|
await expect(page.locator('[data-testid="date-range-options-tdy"]')).toBeHidden();
|
|
|
|
await expect(page.locator('[data-testid="date-range-calendar"]')).toBeVisible();
|
|
});
|
|
});
|
|
test.describe("Past bookings", () => {
|
|
test("Mark first guest as no-show", async ({ page, users, bookings, webhooks }) => {
|
|
const firstUser = await users.create();
|
|
const secondUser = await users.create();
|
|
|
|
const bookingWhereFirstUserIsOrganizerFixture = await createBooking({
|
|
title: "Booking as organizer",
|
|
bookingsFixture: bookings,
|
|
// Create a booking 3 days ago
|
|
relativeDate: -3,
|
|
organizer: firstUser,
|
|
organizerEventType: firstUser.eventTypes[0],
|
|
attendees: [
|
|
{ name: "First", email: "first@cal.com", timeZone: "Europe/Berlin" },
|
|
{ name: "Second", email: "second@cal.com", timeZone: "Europe/Berlin" },
|
|
{ name: "Third", email: "third@cal.com", timeZone: "Europe/Berlin" },
|
|
],
|
|
});
|
|
const bookingWhereFirstUserIsOrganizer = await bookingWhereFirstUserIsOrganizerFixture.self();
|
|
await firstUser.apiLogin();
|
|
const webhookReceiver = await webhooks.createReceiver();
|
|
await page.goto(`/bookings/past`);
|
|
const pastBookings = page.locator('[data-testid="past-bookings"]');
|
|
const firstPastBooking = pastBookings.locator('[data-testid="booking-item"]').nth(0);
|
|
const titleAndAttendees = firstPastBooking.locator('[data-testid="title-and-attendees"]');
|
|
const firstGuest = firstPastBooking.locator('[data-testid="guest"]').nth(0);
|
|
await firstGuest.click();
|
|
await expect(page.locator('[data-testid="unmark-no-show"]')).toBeHidden();
|
|
await expect(page.locator('[data-testid="mark-no-show"]')).toBeVisible();
|
|
await page.locator('[data-testid="mark-no-show"]').click();
|
|
await firstGuest.click();
|
|
await expect(page.locator('[data-testid="unmark-no-show"]')).toBeVisible();
|
|
await expect(page.locator('[data-testid="mark-no-show"]')).toBeHidden();
|
|
await webhookReceiver.waitForRequestCount(1);
|
|
const [request] = webhookReceiver.requestList;
|
|
const body = request.body;
|
|
// remove dynamic properties that differs depending on where you run the tests
|
|
const dynamic = "[redacted/dynamic]";
|
|
// @ts-expect-error we are modifying the object
|
|
body.createdAt = dynamic;
|
|
expect(body).toMatchObject({
|
|
triggerEvent: "BOOKING_NO_SHOW_UPDATED",
|
|
createdAt: "[redacted/dynamic]",
|
|
payload: {
|
|
message: "first@cal.com marked as no-show",
|
|
attendees: [{ email: "first@cal.com", noShow: true }],
|
|
bookingUid: bookingWhereFirstUserIsOrganizer?.uid,
|
|
bookingId: bookingWhereFirstUserIsOrganizer?.id,
|
|
},
|
|
});
|
|
webhookReceiver.close();
|
|
});
|
|
test("Mark 3rd attendee as no-show", async ({ page, users, bookings }) => {
|
|
const firstUser = await users.create();
|
|
const secondUser = await users.create();
|
|
|
|
const bookingWhereFirstUserIsOrganizerFixture = await createBooking({
|
|
title: "Booking as organizer",
|
|
bookingsFixture: bookings,
|
|
// Create a booking 4 days ago
|
|
relativeDate: -4,
|
|
organizer: firstUser,
|
|
organizerEventType: firstUser.eventTypes[0],
|
|
attendees: [
|
|
{ name: "First", email: "first@cal.com", timeZone: "Europe/Berlin" },
|
|
{ name: "Second", email: "second@cal.com", timeZone: "Europe/Berlin" },
|
|
{ name: "Third", email: "third@cal.com", timeZone: "Europe/Berlin" },
|
|
{ name: "Fourth", email: "fourth@cal.com", timeZone: "Europe/Berlin" },
|
|
],
|
|
});
|
|
const bookingWhereFirstUserIsOrganizer = await bookingWhereFirstUserIsOrganizerFixture.self();
|
|
|
|
await firstUser.apiLogin();
|
|
await page.goto(`/bookings/past`);
|
|
const pastBookings = page.locator('[data-testid="past-bookings"]');
|
|
const firstPastBooking = pastBookings.locator('[data-testid="booking-item"]').nth(0);
|
|
const titleAndAttendees = firstPastBooking.locator('[data-testid="title-and-attendees"]');
|
|
const moreGuests = firstPastBooking.locator('[data-testid="more-guests"]');
|
|
await moreGuests.click();
|
|
const firstGuestInMore = page.getByRole("menuitemcheckbox").nth(0);
|
|
await expect(firstGuestInMore).toBeChecked({ checked: false });
|
|
await firstGuestInMore.click();
|
|
await expect(firstGuestInMore).toBeChecked({ checked: true });
|
|
const updateNoShow = firstPastBooking.locator('[data-testid="update-no-show"]');
|
|
await updateNoShow.click();
|
|
await moreGuests.click();
|
|
await expect(firstGuestInMore).toBeChecked({ checked: true });
|
|
});
|
|
test("Team admin/owner can mark first attendee as no-show", async ({
|
|
page,
|
|
users,
|
|
bookings,
|
|
webhooks,
|
|
}) => {
|
|
const { adminUser, memberUser, managedEvent } = await setupManagedEvent({ users });
|
|
|
|
const bookingFixture = await createBooking({
|
|
title: "Managed Event Booking",
|
|
bookingsFixture: bookings,
|
|
// Create a booking 3 days ago
|
|
relativeDate: -3,
|
|
organizer: memberUser,
|
|
organizerEventType: managedEvent,
|
|
attendees: [
|
|
{ name: "First Guest", email: "first@cal.com", timeZone: "Europe/Berlin" },
|
|
{ name: "Second Guest", email: "second@cal.com", timeZone: "Europe/Berlin" },
|
|
{ name: "Third Guest", email: "third@cal.com", timeZone: "Europe/Berlin" },
|
|
],
|
|
});
|
|
const booking = await bookingFixture.self();
|
|
await adminUser.apiLogin();
|
|
const { webhookReceiver, teamId } = await webhooks.createTeamReceiver();
|
|
await page.goto(`/bookings/past`);
|
|
const pastBookings = page.locator('[data-testid="past-bookings"]');
|
|
const firstPastBooking = pastBookings.locator('[data-testid="booking-item"]').nth(0);
|
|
const titleAndAttendees = firstPastBooking.locator('[data-testid="title-and-attendees"]');
|
|
const firstGuest = firstPastBooking.locator('[data-testid="guest"]').nth(0);
|
|
await firstGuest.click();
|
|
await expect(page.locator('[data-testid="mark-no-show"]')).toBeVisible();
|
|
await page.locator('[data-testid="mark-no-show"]').click();
|
|
await firstGuest.click();
|
|
await expect(page.locator('[data-testid="unmark-no-show"]')).toBeVisible();
|
|
await expect(page.locator('[data-testid="mark-no-show"]')).toBeHidden();
|
|
await webhookReceiver.waitForRequestCount(1);
|
|
const [request] = webhookReceiver.requestList;
|
|
const body = request.body;
|
|
const dynamic = "[redacted/dynamic]";
|
|
// @ts-expect-error we are modifying the object
|
|
body.createdAt = dynamic;
|
|
expect(body).toMatchObject({
|
|
triggerEvent: "BOOKING_NO_SHOW_UPDATED",
|
|
createdAt: "[redacted/dynamic]",
|
|
payload: {
|
|
message: "first@cal.com marked as no-show",
|
|
attendees: [{ email: "first@cal.com", noShow: true }],
|
|
bookingUid: booking?.uid,
|
|
bookingId: booking?.id,
|
|
},
|
|
});
|
|
|
|
// Close webhook receiver
|
|
webhookReceiver.close();
|
|
});
|
|
|
|
test("Can choose date range presets", async ({ page, users, bookings, webhooks }) => {
|
|
const firstUser = await users.create();
|
|
await firstUser.apiLogin();
|
|
await page.goto(`/bookings/past`);
|
|
|
|
await page.locator('[data-testid="add-filter-button"]').click();
|
|
await page.locator('[data-testid="add-filter-item-dateRange"]').click();
|
|
await page.locator('[data-testid="filter-popover-trigger-dateRange"]').click();
|
|
|
|
await expect(page.locator('[data-testid="date-range-options-c"]')).toBeVisible();
|
|
await expect(page.locator('[data-testid="date-range-options-w"]')).toBeVisible();
|
|
await expect(page.locator('[data-testid="date-range-options-m"]')).toBeVisible();
|
|
await expect(page.locator('[data-testid="date-range-options-y"]')).toBeVisible();
|
|
await expect(page.locator('[data-testid="date-range-options-t"]')).toBeVisible();
|
|
await expect(page.locator('[data-testid="date-range-options-tdy"]')).toBeVisible();
|
|
|
|
await expect(page.locator('[data-testid="date-range-calendar"]')).toBeHidden();
|
|
});
|
|
});
|
|
|
|
test("People filter includes bookings where filtered person is attendee", async ({
|
|
page,
|
|
users,
|
|
bookings,
|
|
}) => {
|
|
const firstUser = await users.create(
|
|
{ name: "First" },
|
|
{
|
|
hasTeam: true,
|
|
teamRole: MembershipRole.ADMIN,
|
|
}
|
|
);
|
|
const teamId = (await firstUser.getFirstTeamMembership()).teamId;
|
|
const secondUser = await users.create({ name: "Second" });
|
|
const thirdUser = await users.create({ name: "Third" });
|
|
// Add teammates to the team
|
|
await prisma.membership.createMany({
|
|
data: [
|
|
{
|
|
teamId: teamId,
|
|
userId: secondUser.id,
|
|
role: MembershipRole.MEMBER,
|
|
accepted: true,
|
|
},
|
|
{
|
|
teamId: teamId,
|
|
userId: thirdUser.id,
|
|
role: MembershipRole.MEMBER,
|
|
accepted: true,
|
|
},
|
|
],
|
|
});
|
|
const teamEvent = await createTeamEventType(
|
|
{ id: firstUser.id },
|
|
{ id: teamId },
|
|
{ teamEventSlug: "team-event-slug" }
|
|
);
|
|
|
|
//Create a TeamEventType booking where ThirdUser is attendee
|
|
const thirdUserAttendeeTeamEventBookingFixture = await createBooking({
|
|
title: "ThirdUser is Attendee for TeamEvent",
|
|
bookingsFixture: bookings,
|
|
relativeDate: 6,
|
|
organizer: firstUser,
|
|
organizerEventType: teamEvent,
|
|
attendees: [{ name: "Third", email: thirdUser.email, timeZone: "Europe/Berlin" }],
|
|
});
|
|
const thirdUserAttendeeTeamEvent = await thirdUserAttendeeTeamEventBookingFixture.self();
|
|
|
|
//Create a IndividualEventType booking where ThirdUser,SecondUser are attendees and FirstUser is organizer
|
|
const thirdUserAttendeeIndividualBookingFixture = await createBooking({
|
|
title: "ThirdUser is Attendee and FirstUser is Organizer",
|
|
bookingsFixture: bookings,
|
|
relativeDate: 3,
|
|
organizer: firstUser,
|
|
organizerEventType: firstUser.eventTypes[0],
|
|
attendees: [
|
|
{ name: "Third", email: thirdUser.email, timeZone: "Europe/Berlin" },
|
|
{ name: "Second", email: secondUser.email, timeZone: "Europe/Berlin" },
|
|
],
|
|
});
|
|
const thirdUserAttendeeIndividualBooking = await thirdUserAttendeeIndividualBookingFixture.self();
|
|
|
|
//Create a IndividualEventType booking where ThirdUser is organizer and FirstUser,SecondUser are attendees
|
|
const thirdUserOrganizerBookingFixture = await createBooking({
|
|
title: "ThirdUser is Organizer and FirstUser is Attendee",
|
|
bookingsFixture: bookings,
|
|
organizer: thirdUser,
|
|
relativeDate: 2,
|
|
organizerEventType: thirdUser.eventTypes[0],
|
|
attendees: [
|
|
{ name: "First", email: firstUser.email, timeZone: "Europe/Berlin" },
|
|
{ name: "Second", email: secondUser.email, timeZone: "Europe/Berlin" },
|
|
],
|
|
});
|
|
const thirdUserOrganizerBooking = await thirdUserOrganizerBookingFixture.self();
|
|
|
|
//Create a booking where FirstUser is organizer and SecondUser is attendee
|
|
await createBooking({
|
|
title: "FirstUser is Organizer and SecondUser is Attendee",
|
|
bookingsFixture: bookings,
|
|
organizer: firstUser,
|
|
relativeDate: 4,
|
|
organizerEventType: firstUser.eventTypes[0],
|
|
attendees: [{ name: "Second", email: secondUser.email, timeZone: "Europe/Berlin" }],
|
|
});
|
|
|
|
//admin login
|
|
//Select 'ThirdUser' in people filter
|
|
await firstUser.apiLogin();
|
|
await page.goto(`/bookings/upcoming`);
|
|
|
|
await page.locator('[data-testid="add-filter-button"]').click();
|
|
await page.locator('[data-testid="add-filter-item-userId"]').click();
|
|
await page.locator('[data-testid="filter-popover-trigger-userId"]').click();
|
|
|
|
await page
|
|
.locator(`[data-testid="multi-select-options-userId"] [role="option"]:has-text("${thirdUser.name}")`)
|
|
.click();
|
|
|
|
await page.waitForResponse((response) => /\/api\/trpc\/bookings\/get.*/.test(response.url()));
|
|
|
|
//expect only 3 bookings (out of 4 total) to be shown in list.
|
|
//where ThirdUser is either organizer or attendee
|
|
const upcomingBookingsTable = page.locator('[data-testid="upcoming-bookings"]');
|
|
const bookingListItems = upcomingBookingsTable.locator('[data-testid="booking-item"]');
|
|
const bookingListCount = await bookingListItems.count();
|
|
|
|
expect(bookingListCount).toBe(3);
|
|
|
|
//verify with the booking titles
|
|
const firstUpcomingBooking = bookingListItems.nth(0);
|
|
await expect(
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
firstUpcomingBooking.locator(`text=${thirdUserOrganizerBooking!.title}`)
|
|
).toBeVisible();
|
|
|
|
const secondUpcomingBooking = bookingListItems.nth(1);
|
|
await expect(
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
secondUpcomingBooking.locator(`text=${thirdUserAttendeeIndividualBooking!.title}`)
|
|
).toBeVisible();
|
|
|
|
const thirdUpcomingBooking = bookingListItems.nth(2);
|
|
await expect(
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
thirdUpcomingBooking.locator(`text=${thirdUserAttendeeTeamEvent!.title}`)
|
|
).toBeVisible();
|
|
});
|
|
});
|
|
|
|
async function createBooking({
|
|
bookingsFixture,
|
|
organizer,
|
|
organizerEventType,
|
|
attendees,
|
|
/**
|
|
* Relative date from today
|
|
* -1 means yesterday
|
|
* 0 means today
|
|
* 1 means tomorrow
|
|
*/
|
|
relativeDate = 0,
|
|
durationMins = 30,
|
|
title,
|
|
}: {
|
|
bookingsFixture: Fixtures["bookings"];
|
|
organizer: {
|
|
id: number;
|
|
username: string | null;
|
|
};
|
|
organizerEventType: {
|
|
id: number;
|
|
};
|
|
attendees: {
|
|
name: string;
|
|
email: string;
|
|
timeZone: string;
|
|
}[];
|
|
relativeDate?: number;
|
|
durationMins?: number;
|
|
title: string;
|
|
}) {
|
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
const bookingDurationMs = durationMins * 60 * 1000;
|
|
const startTime = new Date(Date.now() + relativeDate * DAY_MS);
|
|
const endTime = new Date(Date.now() + relativeDate * DAY_MS + bookingDurationMs);
|
|
return await bookingsFixture.create(organizer.id, organizer.username, organizerEventType.id, {
|
|
title,
|
|
status: BookingStatus.ACCEPTED,
|
|
startTime,
|
|
endTime,
|
|
attendees: {
|
|
createMany: {
|
|
data: [...attendees],
|
|
},
|
|
},
|
|
});
|
|
}
|