import { BookingStatus } from "@calcom/prisma/enums"; import type { Page } from "@playwright/test"; import { expect } from "@playwright/test"; import type { Fixtures } from "./lib/fixtures"; import { test } from "./lib/fixtures"; test.afterEach(({ users }) => users.deleteAll()); async function enableBookingsV3(prisma: Fixtures["prisma"]) { const existing = await prisma.feature.findUnique({ where: { slug: "bookings-v3" } }); await prisma.feature.upsert({ where: { slug: "bookings-v3" }, update: { enabled: true }, create: { slug: "bookings-v3", enabled: true, type: "OPERATIONAL" }, }); return existing; } async function restoreBookingsV3(prisma: Fixtures["prisma"], existing: { enabled: boolean } | null) { if (existing) { await prisma.feature.update({ where: { slug: "bookings-v3" }, data: { enabled: existing.enabled }, }); } else { await prisma.feature.deleteMany({ where: { slug: "bookings-v3" } }); } } async function createBooking({ bookingsFixture, organizer, organizerEventType, attendees, 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], }, }, }); } async function setupBookingsAndOpenSheet({ page, users, bookings, prisma, bookingCount = 3, }: { page: Page; users: Fixtures["users"]; bookings: Fixtures["bookings"]; prisma: Fixtures["prisma"]; bookingCount?: number; }) { const existingFlag = await enableBookingsV3(prisma); const user = await users.create(); const fixtures = []; for (let i = 0; i < bookingCount; i++) { const fixture = await createBooking({ title: `Booking ${i + 1}`, bookingsFixture: bookings, relativeDate: i + 1, organizer: user, organizerEventType: user.eventTypes[0], attendees: [{ name: `Attendee ${i + 1}`, email: `attendee${i + 1}@example.com`, timeZone: "Europe/Berlin" }], }); fixtures.push(fixture); } await user.apiLogin(); const bookingsGetResponse = page.waitForResponse((response) => /\/api\/trpc\/bookings\/get.*/.test(response.url()) ); await page.goto("/bookings/upcoming", { waitUntil: "domcontentloaded" }); await bookingsGetResponse; const firstBookingItem = page.locator(`[data-booking-uid="${fixtures[0].uid}"]`); await expect(firstBookingItem).toBeVisible(); const firstButton = firstBookingItem.locator('[role="button"]').first(); await firstButton.waitFor({ state: "visible" }); await firstButton.click(); const sheet = page.locator('[role="dialog"]'); await expect(sheet).toBeVisible(); return { user, fixtures, existingFlag, sheet }; } test.describe("Booking sheet keyboard shortcuts", () => { test("ArrowDown navigates to the next booking", async ({ page, users, bookings, prisma }) => { const { fixtures, existingFlag, sheet } = await setupBookingsAndOpenSheet({ page, users, bookings, prisma, }); try { await expect(sheet.getByTestId("booking-sheet-title")).toHaveText("Booking 1"); await page.keyboard.press("ArrowDown"); await expect(sheet.getByTestId("booking-sheet-title")).toHaveText("Booking 2"); } finally { await restoreBookingsV3(prisma, existingFlag); } }); test("ArrowUp navigates to the previous booking", async ({ page, users, bookings, prisma }) => { const { fixtures, existingFlag, sheet } = await setupBookingsAndOpenSheet({ page, users, bookings, prisma, }); try { await page.keyboard.press("ArrowDown"); await expect(sheet.getByTestId("booking-sheet-title")).toHaveText("Booking 2"); await page.keyboard.press("ArrowUp"); await expect(sheet.getByTestId("booking-sheet-title")).toHaveText("Booking 1"); } finally { await restoreBookingsV3(prisma, existingFlag); } }); test("ArrowDown at last booking does NOT open the actions dropdown", async ({ page, users, bookings, prisma, }) => { const { fixtures, existingFlag, sheet } = await setupBookingsAndOpenSheet({ page, users, bookings, prisma, bookingCount: 2, }); try { await page.keyboard.press("ArrowDown"); await expect(sheet.getByTestId("booking-sheet-title")).toHaveText("Booking 2"); await page.keyboard.press("ArrowDown"); await page.waitForTimeout(500); const dropdownContent = page.locator('[data-radix-popper-content-wrapper]'); await expect(dropdownContent).toBeHidden(); await expect(sheet.getByTestId("booking-sheet-title")).toHaveText("Booking 2"); } finally { await restoreBookingsV3(prisma, existingFlag); } }); test("ArrowUp at first booking does NOT open the actions dropdown", async ({ page, users, bookings, prisma, }) => { const { existingFlag, sheet } = await setupBookingsAndOpenSheet({ page, users, bookings, prisma, }); try { await expect(sheet.getByTestId("booking-sheet-title")).toHaveText("Booking 1"); await page.keyboard.press("ArrowUp"); await page.waitForTimeout(500); const dropdownContent = page.locator('[data-radix-popper-content-wrapper]'); await expect(dropdownContent).toBeHidden(); await expect(sheet.getByTestId("booking-sheet-title")).toHaveText("Booking 1"); } finally { await restoreBookingsV3(prisma, existingFlag); } }); test("Escape closes the sheet", async ({ page, users, bookings, prisma }) => { const { existingFlag, sheet } = await setupBookingsAndOpenSheet({ page, users, bookings, prisma, }); try { await expect(sheet).toBeVisible(); await page.keyboard.press("Escape"); await expect(sheet).toBeHidden(); } finally { await restoreBookingsV3(prisma, existingFlag); } }); test("Rapid arrow key presses navigate correctly without triggering dropdown", async ({ page, users, bookings, prisma, }) => { const { fixtures, existingFlag, sheet } = await setupBookingsAndOpenSheet({ page, users, bookings, prisma, }); try { await expect(sheet.getByTestId("booking-sheet-title")).toHaveText("Booking 1"); await page.keyboard.press("ArrowDown"); await page.keyboard.press("ArrowDown"); await expect(sheet.getByTestId("booking-sheet-title")).toHaveText("Booking 3"); const dropdownContent = page.locator('[data-radix-popper-content-wrapper]'); await expect(dropdownContent).toBeHidden(); } finally { await restoreBookingsV3(prisma, existingFlag); } }); });