Also fix CandidateListItem to not use Reorder.Item when isActive=false, which caused a framer-motion crash on resolved thread detail pages. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
114 lines
4.4 KiB
TypeScript
114 lines
4.4 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
test.describe("Thread detail page", () => {
|
|
test("loads with candidates visible", async ({ page }) => {
|
|
await page.goto("/collection?tab=planning");
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// Click the "New Backpack" thread card
|
|
await page.getByText("New Backpack").click();
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// Thread detail page should show the thread name
|
|
await expect(
|
|
page.getByRole("heading", { name: "New Backpack" }),
|
|
).toBeVisible({ timeout: 5000 });
|
|
|
|
// Candidates should be visible
|
|
await expect(page.getByText("ULA Circuit")).toBeVisible({ timeout: 5000 });
|
|
await expect(page.getByText("Gossamer Gear Mariposa")).toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
await expect(page.getByText("Granite Gear Crown2 38")).toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
});
|
|
|
|
test("rank badges are visible for top 3 candidates", async ({ page }) => {
|
|
await page.goto("/collection?tab=planning");
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
await page.getByText("New Backpack").click();
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// Rank badges are medal icons — the component renders LucideIcon with name="medal"
|
|
// for ranks 1, 2, 3. We can verify via SVG elements or by checking the page has
|
|
// the expected number of medal icons (one per top candidate).
|
|
// The list view is default, which renders CandidateListItem with RankBadge.
|
|
// With 3 candidates all in top 3, all 3 get medal icons.
|
|
await expect(page.locator("text=ULA Circuit")).toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
await expect(page.locator("text=Gossamer Gear Mariposa")).toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
await expect(page.locator("text=Granite Gear Crown2 38")).toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
|
|
// Verify candidates are rendered (rank badges are SVG medals, not text)
|
|
// Check that at least the 3 candidates are present in the list
|
|
const candidateRows = page.locator(".bg-white.rounded-xl.border");
|
|
await expect(candidateRows).toHaveCount(3, { timeout: 5000 });
|
|
});
|
|
|
|
test("comparison view toggle works", async ({ page }) => {
|
|
await page.goto("/collection?tab=planning");
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
await page.getByText("New Backpack").click();
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
await expect(page.getByText("ULA Circuit")).toBeVisible({ timeout: 5000 });
|
|
|
|
// The compare button is a button with title="Compare view" (icon button with columns-3 icon)
|
|
const compareButton = page.getByRole("button", { name: "Compare view" });
|
|
await expect(compareButton).toBeVisible({ timeout: 5000 });
|
|
await compareButton.click();
|
|
|
|
// After clicking, a table should appear with Weight and Price row labels
|
|
await expect(page.locator("table")).toBeVisible({ timeout: 5000 });
|
|
// The comparison table renders row labels in sticky <td> cells (exact match to avoid
|
|
// matching candidate notes that contain the word "weight" or "price")
|
|
await expect(
|
|
page.getByRole("cell", { name: "Weight", exact: true }),
|
|
).toBeVisible({ timeout: 5000 });
|
|
await expect(
|
|
page.getByRole("cell", { name: "Price", exact: true }),
|
|
).toBeVisible({ timeout: 5000 });
|
|
|
|
// All 3 candidates should appear as table column headers (in <thead>)
|
|
await expect(page.locator("thead").getByText("ULA Circuit")).toBeVisible({
|
|
timeout: 5000,
|
|
});
|
|
await expect(
|
|
page.locator("thead").getByText("Gossamer Gear Mariposa"),
|
|
).toBeVisible({ timeout: 5000 });
|
|
});
|
|
|
|
test("resolved thread shows winner banner", async ({ page }) => {
|
|
await page.goto("/collection?tab=planning");
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// Click the "Resolved" tab pill button in the planning view
|
|
await page.getByRole("button", { name: "Resolved" }).click();
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// Camp Stove resolved thread should appear
|
|
await expect(page.getByText("Camp Stove")).toBeVisible({ timeout: 5000 });
|
|
|
|
// Click the Camp Stove thread card and wait for URL to change to thread detail
|
|
await page.getByText("Camp Stove").click();
|
|
await page.waitForURL(/\/threads\/\d+/, { timeout: 5000 });
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// Should show the resolved thread heading
|
|
await expect(page.getByRole("heading", { name: "Camp Stove" })).toBeVisible(
|
|
{ timeout: 5000 },
|
|
);
|
|
|
|
// The winner candidate (BRS-3000T) should be visible in the candidate list
|
|
await expect(page.getByText("BRS-3000T")).toBeVisible({ timeout: 5000 });
|
|
});
|
|
});
|