8ecbfbeee4
Direct navigation to /admin/clients/{id} now:
- Fetches GET /api/clients/{id} on mount (unconditional)
- Fetches GET /api/pets?clientId= on mount
- Shows loading state while fetching
- Shows error state on failure (401/404/5xx)
- Preserves existing link-based navigation from ClientsPage
Added ClientDetailPage.tsx as a standalone route component.
Added 3 E2E tests covering direct nav, loading state, and error state.
Co-authored-by: Test User <test@example.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
115 lines
3.6 KiB
TypeScript
115 lines
3.6 KiB
TypeScript
import { test, expect } from "./fixtures.js";
|
|
|
|
/**
|
|
* Client management E2E tests.
|
|
*
|
|
* API calls are mocked so tests run without a live backend.
|
|
*/
|
|
|
|
const MOCK_CLIENTS = [
|
|
{
|
|
id: "client-1",
|
|
name: "Alice Johnson",
|
|
email: "alice@example.com",
|
|
phone: "555-0101",
|
|
address: null,
|
|
notes: null,
|
|
emailOptOut: false,
|
|
status: "active",
|
|
disabledAt: null,
|
|
createdAt: "2026-01-01T00:00:00.000Z",
|
|
updatedAt: "2026-01-01T00:00:00.000Z",
|
|
},
|
|
{
|
|
id: "client-2",
|
|
name: "Bob Williams",
|
|
email: "bob@example.com",
|
|
phone: null,
|
|
address: null,
|
|
notes: null,
|
|
emailOptOut: false,
|
|
status: "active",
|
|
disabledAt: null,
|
|
createdAt: "2026-01-02T00:00:00.000Z",
|
|
updatedAt: "2026-01-02T00:00:00.000Z",
|
|
},
|
|
];
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.route("/api/clients**", (route) =>
|
|
route.fulfill({ json: MOCK_CLIENTS })
|
|
);
|
|
// Pets loaded when a client is selected
|
|
await page.route("/api/pets**", (route) =>
|
|
route.fulfill({ json: [] })
|
|
);
|
|
});
|
|
|
|
test("clients page shows client list", async ({ page }) => {
|
|
await page.goto("/admin/clients");
|
|
await expect(page.getByText("Alice Johnson")).toBeVisible();
|
|
await expect(page.getByText("Bob Williams")).toBeVisible();
|
|
});
|
|
|
|
test("clients page shows search input", async ({ page }) => {
|
|
await page.goto("/admin/clients");
|
|
await expect(page.getByPlaceholder(/search/i).first()).toBeVisible();
|
|
});
|
|
|
|
test("clicking a client shows their details", async ({ page }) => {
|
|
await page.goto("/admin/clients");
|
|
await expect(page.getByText("Alice Johnson")).toBeVisible();
|
|
await page.getByText("Alice Johnson").click();
|
|
// Email appears in both the list row and the detail panel once selected
|
|
await expect(page.getByText("alice@example.com")).toHaveCount(2);
|
|
});
|
|
|
|
test("direct URL navigation to client detail fetches data and renders client name", async ({ page }) => {
|
|
// Mock individual client fetch for direct navigation
|
|
await page.route("/api/clients/client-1", (route) =>
|
|
route.fulfill({ json: MOCK_CLIENTS[0] })
|
|
);
|
|
// Mock pets for this client
|
|
await page.route("/api/pets**", (route) =>
|
|
route.fulfill({ json: [] })
|
|
);
|
|
|
|
await page.goto("/admin/clients/client-1");
|
|
// Client name must be visible without any clicking
|
|
await expect(page.getByText("Alice Johnson")).toBeVisible();
|
|
// Should show back to list link
|
|
await expect(page.getByText("← Back to list")).toBeVisible();
|
|
});
|
|
|
|
test("direct URL navigation shows loading then client", async ({ page }) => {
|
|
let resolvePets: (value: unknown) => void;
|
|
const petsPromise = new Promise((resolve) => { resolvePets = resolve; });
|
|
|
|
await page.route("/api/clients/client-1", (route) =>
|
|
route.fulfill({ json: MOCK_CLIENTS[0] })
|
|
);
|
|
await page.route("/api/pets**", async (route) => {
|
|
await petsPromise;
|
|
await route.fulfill({ json: [] });
|
|
});
|
|
|
|
const navigationPromise = page.goto("/admin/clients/client-1");
|
|
// Should show loading state briefly
|
|
await expect(page.getByText("Loading client…")).toBeVisible();
|
|
// Resolve pets and wait for navigation
|
|
resolvePets!();
|
|
await navigationPromise;
|
|
// After data loads, client name is shown
|
|
await expect(page.getByText("Alice Johnson")).toBeVisible();
|
|
});
|
|
|
|
test("direct URL navigation shows error state on failure", async ({ page }) => {
|
|
await page.route("/api/clients/nonexistent", (route) =>
|
|
route.fulfill({ status: 404, json: { error: "Client not found" } })
|
|
);
|
|
|
|
await page.goto("/admin/clients/nonexistent");
|
|
await expect(page.getByText(/client not found/i)).toBeVisible();
|
|
await expect(page.getByText("← Back to clients")).toBeVisible();
|
|
});
|