391c5b70d9
- console-health: add 502/Failed to load resource filter to admin page test (portal page already had it) - admin-services: mock /api/book/services endpoint used by booking wizard Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
104 lines
3.7 KiB
TypeScript
104 lines
3.7 KiB
TypeScript
import { test, expect } from "./fixtures.js";
|
|
|
|
/**
|
|
* E2E tests for services deduplication.
|
|
* Verifies that service names are unique in both admin services list
|
|
* and in the booking service picker.
|
|
*/
|
|
|
|
const MOCK_SERVICES = [
|
|
{ id: "svc-1", name: "Full Groom", description: "Bath and haircut", basePriceCents: 7500, durationMinutes: 90, isActive: true },
|
|
{ id: "svc-2", name: "Bath Only", description: "Just the bath", basePriceCents: 3500, durationMinutes: 45, isActive: true },
|
|
{ id: "svc-3", name: "Nail Trim", description: "Just nails", basePriceCents: 1500, durationMinutes: 15, isActive: true },
|
|
];
|
|
|
|
test.describe("Services Deduplication", () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Mock services endpoint FIRST before navigation
|
|
// Also mock /api/book/services used by the booking wizard
|
|
await page.route("**/api/services**", (route) =>
|
|
route.fulfill({ json: MOCK_SERVICES })
|
|
);
|
|
await page.route("**/api/book/services**", (route) =>
|
|
route.fulfill({ json: MOCK_SERVICES })
|
|
);
|
|
|
|
// Login as staff
|
|
await page.goto("/login");
|
|
await page.getByText("Alice Groomer").click();
|
|
await expect(page).toHaveURL("/admin");
|
|
});
|
|
|
|
test("admin services page shows no duplicate service names", async ({ page }) => {
|
|
await page.goto("/admin/services");
|
|
|
|
// Wait for services to load
|
|
await page.waitForTimeout(1_000);
|
|
|
|
// Collect all service names from the table
|
|
const serviceNameCells = page.locator("table tbody tr td:first-child");
|
|
const count = await serviceNameCells.count();
|
|
|
|
const names: string[] = [];
|
|
for (let i = 0; i < count; i++) {
|
|
const text = await serviceNameCells.nth(i).textContent();
|
|
if (text) names.push(text.trim());
|
|
}
|
|
|
|
// Check for duplicates
|
|
const duplicates = names.filter((name, index) => names.indexOf(name) !== index);
|
|
|
|
// Assert no duplicate names
|
|
expect(duplicates, `Found duplicate service names: ${duplicates.join(", ")}`).toHaveLength(0);
|
|
|
|
// Verify all names are unique using Set comparison
|
|
const uniqueNames = new Set(names);
|
|
expect(uniqueNames.size).toBe(names.length);
|
|
});
|
|
|
|
test("admin services page renders all services", async ({ page }) => {
|
|
await page.goto("/admin/services");
|
|
|
|
// Wait for table to render
|
|
await expect(page.locator("table")).toBeVisible({ timeout: 10_000 });
|
|
|
|
// Should show the heading
|
|
await expect(page.locator("h1")).toContainText("Services");
|
|
|
|
// Should show all unique services
|
|
const rowCount = await page.locator("table tbody tr").count();
|
|
expect(rowCount).toBeGreaterThan(0);
|
|
});
|
|
|
|
test("booking service picker shows no duplicates", async ({ page }) => {
|
|
await page.goto("/admin/book");
|
|
|
|
// Wait for services to load in the booking wizard
|
|
await page.waitForTimeout(1_000);
|
|
|
|
// Collect service names from the picker
|
|
const serviceCards = page.locator("text=/Full Groom|Bath Only|Nail Trim/");
|
|
const serviceNames: string[] = [];
|
|
|
|
// Get all text content that looks like service names
|
|
const allText = await page.locator("body").textContent();
|
|
if (allText) {
|
|
const matches = allText.match(/(?:Full Groom|Bath Only|Nail Trim)/g);
|
|
if (matches) {
|
|
serviceNames.push(...matches);
|
|
}
|
|
}
|
|
|
|
// Check for duplicates in the booking picker
|
|
const duplicates = serviceNames.filter((name, index) => serviceNames.indexOf(name) !== index);
|
|
|
|
expect(duplicates, `Found duplicate service names in booking picker: ${duplicates.join(", ")}`).toHaveLength(0);
|
|
});
|
|
|
|
test("booking wizard step 1 shows services", async ({ page }) => {
|
|
await page.goto("/admin/book");
|
|
|
|
// Should show at least one service
|
|
await expect(page.getByText("Full Groom")).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
}); |