> ## Documentation Index
> Fetch the complete documentation index at: https://dhanurgo.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing Guide

> Unit testing with Vitest, E2E testing with Playwright, and mock patterns

# Testing Guide

HostMetrics uses **Vitest** for unit and integration tests and **Playwright** for end-to-end browser tests.

## Test Stack

| Tool       | Purpose                    | Config File            |
| ---------- | -------------------------- | ---------------------- |
| Vitest     | Unit and integration tests | `vitest.config.ts`     |
| Playwright | End-to-end browser tests   | `playwright.config.ts` |
| jsdom      | DOM environment for Vitest | Set in Vitest config   |

## Directory Structure

```
src/__tests__/
├── unit/                    # Unit tests for hooks, utils, pure functions
│   ├── useKPIs.test.ts
│   ├── calculations.test.ts
│   └── ...
├── integration/             # Integration tests (DB + component)
│   └── ...
└── mocks/
    └── supabase.ts          # Shared Supabase mock

e2e/
├── specs/                   # Playwright test specs
│   ├── dashboard.spec.ts
│   ├── trips.spec.ts
│   └── ...
├── auth.setup.ts            # Authentication setup
└── test-data.ts             # Test fixture data
```

## Running Tests

### Unit Tests

```bash theme={null}
# Run all unit tests in watch mode
npm run test

# Run all unit tests once (CI mode)
npm run test -- --run

# Run only unit tests
npm run test -- --run "src/__tests__/unit/"

# Run a specific test file
npm run test -- --run "src/__tests__/unit/useKPIs.test.ts"

# Run with coverage report
npm run test -- --run --coverage
```

### End-to-End Tests

```bash theme={null}
# Run all E2E tests
npx playwright test

# Run a specific spec file
npx playwright test e2e/specs/dashboard.spec.ts

# Run in headed mode (visible browser)
npx playwright test --headed

# Run in a specific browser
npx playwright test --project=chromium

# Open the Playwright UI
npx playwright test --ui
```

## Vitest Configuration

Key settings from `vitest.config.ts`:

* **Environment:** `jsdom` (simulates browser DOM)
* **Coverage thresholds:** 80% for branches, functions, lines, and statements
* **Path aliases:** `@/` maps to `src/` (mirrors the app's tsconfig)
* **Setup files:** Global test setup for mocks and environment

## Test File Naming

| What you're testing | File name pattern        | Location              |
| ------------------- | ------------------------ | --------------------- |
| Component           | `ComponentName.test.tsx` | `src/__tests__/unit/` |
| Hook                | `hookName.test.ts`       | `src/__tests__/unit/` |
| Utility function    | `utilName.test.ts`       | `src/__tests__/unit/` |
| E2E user flow       | `feature.spec.ts`        | `e2e/specs/`          |

## Mock Patterns

### Mocking the Supabase Client

The shared Supabase mock lives at `src/__tests__/mocks/supabase.ts`. Use it in unit tests:

```typescript theme={null}
import { vi, describe, it, expect, beforeEach } from "vitest";

// Mock the Supabase client module
vi.mock("@/lib/db/_client", () => ({
  getClient: () => mockSupabaseClient,
  getCurrentUserId: () => "test-user-id",
}));

const mockSupabaseClient = {
  from: vi.fn().mockReturnThis(),
  select: vi.fn().mockReturnThis(),
  eq: vi.fn().mockReturnThis(),
  order: vi.fn().mockReturnThis(),
  single: vi.fn().mockResolvedValue({
    data: { id: "trip-1", reservation_id: "RES-001" },
    error: null,
  }),
};
```

### Mocking Hooks in Component Tests

When testing components, mock the hooks they depend on:

```typescript theme={null}
import { vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { DashboardStats } from "@/components/dashboard/DashboardStats";

vi.mock("@/hooks/useKPIs", () => ({
  useKPIs: () => ({
    kpis: {
      totalEarnings: 15000,
      totalExpenses: 5000,
      netProfit: 10000,
      profitMargin: 66.7,
    },
    isLoading: false,
    error: null,
  }),
}));

describe("DashboardStats", () => {
  it("renders KPI values", () => {
    render(<DashboardStats />);
    expect(screen.getByText("$15,000.00")).toBeInTheDocument();
  });
});
```

### Mocking Next.js Router

```typescript theme={null}
vi.mock("next/navigation", () => ({
  useRouter: () => ({
    push: vi.fn(),
    replace: vi.fn(),
    back: vi.fn(),
  }),
  usePathname: () => "/dashboard",
  useSearchParams: () => new URLSearchParams(),
}));
```

## Writing Unit Tests

Follow this structure for hook tests:

```typescript theme={null}
import { describe, it, expect, beforeEach, vi } from "vitest";
import { renderHook, waitFor } from "@testing-library/react";
import { useBookings } from "@/hooks/useBookings";

vi.mock("@/lib/db/_client", () => ({
  getClient: () => mockClient,
  getCurrentUserId: vi.fn().mockResolvedValue("test-user-id"),
}));

describe("useBookings", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("fetches bookings on mount", async () => {
    const { result } = renderHook(() => useBookings());

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    expect(result.current.bookings).toHaveLength(3);
  });

  it("filters by status", async () => {
    const { result } = renderHook(() =>
      useBookings({ status: "completed" })
    );

    await waitFor(() => {
      expect(result.current.bookings).toHaveLength(2);
    });
  });
});
```

## Playwright E2E Tests

### Browser Projects

Playwright is configured with 5 browser projects:

| Project         | Browser       | Viewport |
| --------------- | ------------- | -------- |
| `chromium`      | Chromium      | Desktop  |
| `firefox`       | Firefox       | Desktop  |
| `webkit`        | WebKit/Safari | Desktop  |
| `mobile-chrome` | Mobile Chrome | 390x844  |
| `mobile-safari` | Mobile Safari | 390x844  |

### Auth Setup

E2E tests authenticate before running specs using `auth.setup.ts`:

```typescript theme={null}
// e2e/auth.setup.ts
import { test as setup } from "@playwright/test";

setup("authenticate", async ({ page }) => {
  await page.goto("/login");
  await page.fill('[name="email"]', process.env.TEST_EMAIL!);
  await page.fill('[name="password"]', process.env.TEST_PASSWORD!);
  await page.click('button[type="submit"]');
  await page.waitForURL("/dashboard");

  // Save auth state for reuse
  await page.context().storageState({ path: "e2e/.auth/state.json" });
});
```

### Writing E2E Specs

```typescript theme={null}
import { test, expect } from "@playwright/test";

test.describe("Dashboard", () => {
  test("displays KPI cards", async ({ page }) => {
    await page.goto("/dashboard");
    await expect(page.getByText("Total Earnings")).toBeVisible();
    await expect(page.getByText("Net Profit")).toBeVisible();
  });

  test("navigates to trip details", async ({ page }) => {
    await page.goto("/bookings");
    await page.click("text=RES-001");
    await expect(page).toHaveURL(/\/bookings\//);
  });
});
```

## Pre-Commit Test Commands

Before creating a PR, always run:

```bash theme={null}
# Unit tests (must pass)
npm run test -- --run

# Or specifically unit tests only
npm run test -- --run "src/__tests__/unit/"
```

See the [PR Checklist](/development/pr-checklist) for the full verification workflow.
