import { expect, test, type Page } from "@playwright/test"; import { expectModalOpen, installRoutes } from "./helpers"; import { duplicatesReport } from "./fixtures/data"; // Duplicates page e2e. The site-filtered /duplicates.json is route-mocked (it // is produced at build time by compose-site.ts); the page renders cluster // cards, fetches member titles/URLs via the mocked transcript routes, and // applies the platform / match / relationship / channel / text filters. async function installDuplicatesRoute( page: Page, report: unknown | null, ): Promise { await page.route("**/duplicates.json", async (route) => { if (report === null) { await route.fulfill({ status: 404, contentType: "application/json", body: "{}" }); return; } await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(report), }); }); } test.describe("duplicates", () => { test("scopes to YouTube by default and renders cluster evidence", async ({ page, }) => { await installRoutes(page); await installDuplicatesRoute(page, duplicatesReport()); await page.goto("/duplicates"); // YouTube is on by default; other platforms start off. await expect(page.getByRole("checkbox", { name: "YouTube" })).toBeChecked(); await expect(page.getByRole("checkbox", { name: "Rumble" })).not.toBeChecked(); // Header counts reflect the YouTube-scoped view: both clusters survive // (each has ≥2 youtube members), one of which is a same-channel re-upload. await expect(page.getByText("shorts ≤180s")).toBeVisible(); await expect(page.getByText("2 clusters")).toBeVisible(); await expect(page.getByText("1 with same-channel duplicates")).toBeVisible(); const sameChannelCard = page.getByLabel(/^duplicate cluster dup-cluster/); // Real titles are fetched from transcript data for the resolvable members. await expect( sameChannelCard.getByRole("button", { name: "Transcript only — no chat", exact: true, }), ).toBeVisible(); await expect( sameChannelCard.getByRole("button", { name: "Small live chat", exact: true }), ).toBeVisible(); // The rumble member is filtered out by the default YouTube scope, so the // cluster is no longer cross-platform. await expect( sameChannelCard.getByText("Large live chat"), ).toHaveCount(0); await expect(sameChannelCard.getByText("same-channel")).toBeVisible(); await expect(sameChannelCard.getByText("cross-platform")).toHaveCount(0); // Channel name + formatted date + original-source URL on its own row. await expect( sameChannelCard.getByText("Test Channel · youtube · 2024-01-01"), ).toBeVisible(); await expect( sameChannelCard.getByRole("link", { name: 'Open "Transcript only — no chat" on youtube', }), ).toHaveAttribute("href", "https://example.com/vid-transcript-only"); // Clicking a member's title opens the transcript modal for that video. await page .getByRole("button", { name: "Small live chat", exact: true }) .click(); await expectModalOpen(page); }); test("toggling a platform reveals its members and the cross-platform flag", async ({ page, }) => { await installRoutes(page); await installDuplicatesRoute(page, duplicatesReport()); await page.goto("/duplicates"); const card = page.getByLabel(/^duplicate cluster dup-cluster/); await expect(card.getByText("Large live chat")).toHaveCount(0); await page.getByRole("checkbox", { name: "Rumble" }).check(); await expect(card.getByText("Large live chat")).toBeVisible(); await expect(card.getByText("cross-platform")).toBeVisible(); }); test("text search and match-type filters narrow the list", async ({ page }) => { await installRoutes(page); await installDuplicatesRoute(page, duplicatesReport()); await page.goto("/duplicates"); await expect(page.getByText("2 clusters")).toBeVisible(); // Text search keeps only clusters with a matching member title/channel. await page.getByLabel("filter duplicates").fill("garden"); await expect(page.getByText("1 cluster")).toBeVisible(); await expect(page.getByText("Gardening basics", { exact: true })).toBeVisible(); await expect( page.getByRole("button", { name: "Small live chat", exact: true }), ).toHaveCount(0); // Clearing search restores both, then a match-type toggle narrows by tier. await page.getByLabel("filter duplicates").fill(""); await expect(page.getByText("2 clusters")).toBeVisible(); await page.getByRole("checkbox", { name: "near transcript" }).uncheck(); // Only the exact-transcript (gardening) cluster remains. await expect(page.getByText("1 cluster")).toBeVisible(); await expect(page.getByText("Gardening basics", { exact: true })).toBeVisible(); }); test("shows the empty state when no report has been built", async ({ page, }) => { await installRoutes(page); await installDuplicatesRoute(page, null); await page.goto("/duplicates"); await expect( page.getByText("No duplicate shorts have been detected for this site yet."), ).toBeVisible(); }); });