diff --git a/tests/facility/appointments/appointmentsList.spec.ts b/tests/facility/appointments/appointmentsList.spec.ts new file mode 100644 index 00000000000..4e4ea3070fa --- /dev/null +++ b/tests/facility/appointments/appointmentsList.spec.ts @@ -0,0 +1,211 @@ +import { expect, test } from "@playwright/test"; +import { format, addDays } from "date-fns"; +import { getFacilityId } from "tests/support/facilityId"; + +test.use({ storageState: "tests/.auth/user.json" }); + +test.describe("Appointments List Page", () => { + let facilityId: string; + + test.beforeEach(async ({ page }) => { + facilityId = getFacilityId(); + await page.goto(`/facility/${facilityId}/appointments`); + }); + + /** + * Verifies the appointments page loads successfully and displays the main UI elements + */ + test("should load appointments page and display key elements", async ({ + page, + }) => { + // Wait for page to load completely + await page.waitForLoadState("networkidle"); + + // Verify page heading is visible + await expect( + page.getByRole("heading", { name: /appointments/i }), + ).toBeVisible(); + + // Verify view tabs are present (board view and list view) + await expect(page.getByRole("tab", { name: /board/i })).toBeVisible(); + await expect(page.getByRole("tab", { name: /list/i })).toBeVisible(); + + // Verify filter button is visible + await expect( + page.getByRole("button", { name: /filter/i }), + ).toBeVisible(); + }); + + /** + * Tests switching between board and list views + */ + test("should toggle between board and list views", async ({ page }) => { + await page.waitForLoadState("networkidle"); + + // Start with board view (default) + const boardTab = page.getByRole("tab", { name: /board/i }); + const listTab = page.getByRole("tab", { name: /list/i }); + + // Click list view + await listTab.click(); + await expect(listTab).toHaveAttribute("data-state", "active"); + + // Verify table structure is visible in list view + await expect(page.getByRole("table")).toBeVisible(); + + // Switch back to board view + await boardTab.click(); + await expect(boardTab).toHaveAttribute("data-state", "active"); + }); + + /** + * Tests the date filter functionality for appointments + */ + test("should filter appointments by date range", async ({ page }) => { + await page.waitForLoadState("networkidle"); + + // Open filter menu + const filterButton = page.getByRole("button", { name: /filter/i }); + await filterButton.click(); + + // Wait for filter popover to be visible + const dateFilterSection = page.getByText(/date/i).first(); + await expect(dateFilterSection).toBeVisible(); + + // Select a date range option (e.g., "Today") + const todayOption = page.getByRole("button", { name: /^today$/i }); + if (await todayOption.isVisible()) { + await todayOption.click(); + } + + // Verify filter is applied by checking URL parameters + await page.waitForTimeout(500); + const url = page.url(); + expect(url).toContain("date_from"); + }); + + /** + * Tests the practitioner filter functionality + */ + test("should show practitioner filter options", async ({ page }) => { + await page.waitForLoadState("networkidle"); + + // Look for practitioner filter/selector + // This might be a dropdown or multi-select component + const practitionerFilter = page.getByText(/practitioner/i).first(); + + // If the filter exists, verify it's visible + if (await practitionerFilter.isVisible()) { + await expect(practitionerFilter).toBeVisible(); + } + }); + + /** + * Tests appointment status filtering + */ + test("should display appointment status filters", async ({ page }) => { + await page.waitForLoadState("networkidle"); + + // Look for status filter buttons/badges + // Common appointment statuses: Pending, Confirmed, Cancelled, Completed + const statusFilters = [ + /pending/i, + /booked/i, + /confirmed/i, + /checked.?in/i, + ]; + + // Check if any status filters are visible + let statusFilterFound = false; + for (const statusPattern of statusFilters) { + const statusElement = page.getByText(statusPattern).first(); + if (await statusElement.isVisible({ timeout: 2000 }).catch(() => false)) { + statusFilterFound = true; + break; + } + } + + // If status filters are not immediately visible, they might be in a dropdown or filter menu + if (!statusFilterFound) { + const filterButton = page.getByRole("button", { name: /filter/i }); + if (await filterButton.isVisible()) { + await filterButton.click(); + // Status filters might be in the filter menu + } + } + + // At minimum, the page should have loaded without errors + await expect(page.getByRole("heading")).toBeVisible(); + }); + + /** + * Tests that appointments are displayed in the list/board + */ + test("should display appointments or empty state", async ({ page }) => { + await page.waitForLoadState("networkidle"); + + // Wait a bit for data to load + await page.waitForTimeout(1000); + + // Check for either appointments or empty state + const hasAppointments = await page + .getByRole("table") + .isVisible({ timeout: 2000 }) + .catch(() => false); + + const hasCards = await page + .locator('[data-testid*="appointment"], [class*="appointment"]') + .first() + .isVisible({ timeout: 2000 }) + .catch(() => false); + + const hasEmptyState = await page + .getByText(/no appointments/i) + .isVisible({ timeout: 2000 }) + .catch(() => false); + + // At least one should be true + const pageIsWorking = hasAppointments || hasCards || hasEmptyState; + expect(pageIsWorking).toBe(true); + }); + + /** + * Tests navigation to appointment detail when an appointment is clicked + */ + test("should allow navigation to appointment details", async ({ page }) => { + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(1000); + + // Switch to list view for easier row selection + const listTab = page.getByRole("tab", { name: /list/i }); + await listTab.click(); + + // Check if there are any appointment rows + const rows = page.getByRole("row"); + const rowCount = await rows.count(); + + if (rowCount > 1) { + // Skip header row (index 0), click first data row + const firstDataRow = rows.nth(1); + + // Look for a clickable element in the row (link or button) + const clickableElement = + firstDataRow.getByRole("link").first() || + firstDataRow.getByRole("button").first(); + + if (await clickableElement.isVisible({ timeout: 2000 })) { + await clickableElement.click(); + + // Wait for navigation + await page.waitForURL(/appointments\/.*/, { timeout: 5000 }); + + // Verify we're on an appointment detail page + const url = page.url(); + expect(url).toMatch(/appointments\/[^/]+$/); + } + } else { + // No appointments to click - test passes as the page structure is correct + console.log("No appointments available for navigation test"); + } + }); +});