import { expect, test } from "@playwright/test"; import { waitForAnimations } from "../helpers/animations"; import { installMockBridge } from "../helpers/bridge"; const SHOTS = "test-results/project-commit-detail"; const ALIGNMENT_TOLERANCE_PX = 2; // The projects surface is a preview feature — opt in before the app mounts. // Must run before installMockBridge so React reads the override on mount. async function enableProjectsFeature(page: import("@playwright/test").Page) { await page.addInitScript(() => { window.localStorage.setItem( "buzz-feature-overrides-v1", JSON.stringify({ projects: true }), ); }); } test("top-level project lists align dates and overflow actions", async ({ page, }) => { await enableProjectsFeature(page); await page.addInitScript(() => { window.localStorage.setItem("buzz.projects.viewMode", "list"); }); await installMockBridge(page); await page.goto("/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); await expect( page.getByRole("heading", { level: 1, name: "Projects" }), ).toBeVisible(); async function trailingPositions(row: import("@playwright/test").Locator) { await waitForAnimations(page); const date = row.getByTestId("projects-row-date"); const menu = row.getByRole("button", { name: /More options for/ }); await expect(date).toBeVisible(); await expect(menu).toBeVisible(); const dateBox = await date.boundingBox(); const menuBox = await menu.boundingBox(); expect(dateBox).not.toBeNull(); expect(menuBox).not.toBeNull(); return { dateX: dateBox?.x ?? 0, menuX: menuBox?.x ?? 0 }; } await page.getByRole("button", { name: "Repositories", exact: true }).click(); await page.getByRole("button", { name: "Filter repositories" }).click(); await expect( page.getByRole("menuitem", { name: "My Repositories" }), ).toBeVisible(); await expect(page.getByRole("menuitem", { name: "Local" })).toBeVisible(); await page.keyboard.press("Escape"); const repositoryPositions = await trailingPositions( page.locator('[data-testid^="project-row-"]').first(), ); await page .getByRole("button", { name: "Pull Requests", exact: true }) .click(); await page.getByRole("button", { name: "Filter pull requests" }).click(); await expect( page.getByRole("menuitem", { name: "My Pull Requests" }), ).toBeVisible(); await page.keyboard.press("Escape"); await page.getByTestId("projects-create-menu").hover(); await expect( page.getByRole("menuitem", { name: "Repository" }), ).toBeVisible(); await expect(page.getByRole("menuitem", { name: "Issue" })).toBeVisible(); await page .getByRole("menuitem", { name: "Pull Request", exact: true }) .click(); await expect(page.getByTestId("create-pull-request-dialog")).toBeVisible(); await expect( page.getByTestId("create-pull-request-repository"), ).toBeVisible(); await page.keyboard.press("Escape"); await page.getByTestId("projects-create-menu").hover(); await page.getByRole("menuitem", { name: "Issue" }).click(); await expect(page.getByTestId("create-issue-repository")).toBeVisible(); await page.keyboard.press("Escape"); const pullRequestRow = page .locator('[data-testid^="projects-pr-row-"]') .first(); const pullRequestPositions = await trailingPositions(pullRequestRow); await pullRequestRow .getByRole("button", { name: /More options for/ }) .click(); await expect( page.getByRole("menuitem", { name: /Review PR|View (draft|merge|closed)/ }), ).toBeVisible(); await page.keyboard.press("Escape"); await page.getByRole("button", { name: "Issues", exact: true }).click(); await page.getByRole("button", { name: "Filter issues" }).click(); await expect(page.getByRole("menuitem", { name: "My Issues" })).toBeVisible(); await page.keyboard.press("Escape"); const issueRow = page.locator('[data-testid^="projects-issue-row-"]').first(); const issuePositions = await trailingPositions(issueRow); expect( Math.abs(pullRequestPositions.dateX - repositoryPositions.dateX), ).toBeLessThanOrEqual(ALIGNMENT_TOLERANCE_PX); expect( Math.abs(pullRequestPositions.menuX - repositoryPositions.menuX), ).toBeLessThanOrEqual(ALIGNMENT_TOLERANCE_PX); expect( Math.abs(issuePositions.dateX - repositoryPositions.dateX), ).toBeLessThanOrEqual(ALIGNMENT_TOLERANCE_PX); expect( Math.abs(issuePositions.menuX - repositoryPositions.menuX), ).toBeLessThanOrEqual(ALIGNMENT_TOLERANCE_PX); await page.setViewportSize({ height: 720, width: 900 }); await page.getByRole("button", { name: "Repositories", exact: true }).click(); const responsiveRepositoryRow = page .locator('[data-testid^="project-row-"]') .first(); await expect( responsiveRepositoryRow.getByTestId("projects-row-summary"), ).toBeHidden(); await expect( responsiveRepositoryRow.getByTestId("projects-row-people"), ).toBeHidden(); await expect( responsiveRepositoryRow.getByTestId("projects-row-date"), ).toBeVisible(); await expect( responsiveRepositoryRow.getByRole("button", { name: /More options for/ }), ).toBeVisible(); expect( await responsiveRepositoryRow.evaluate( (row) => row.scrollWidth <= row.clientWidth, ), ).toBe(true); }); test("commit detail opens from the commits feed with a diff", async ({ page, }) => { await enableProjectsFeature(page); await installMockBridge(page); // The preview server is a static file server without SPA fallback, so // enter at "/" and navigate via the sidebar. await page.goto("/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); // The overview no longer lists repository cards — switch to the // Repositories filter to reveal the project cards/rows. await page.getByRole("button", { name: "Repositories", exact: true }).click(); // Open the first mock project (dtag "buzz" from the e2e bridge fixture). const projectEntry = page .locator( '[data-testid="project-card-buzz"], [data-testid="project-row-buzz"]', ) .first(); await expect(projectEntry).toBeVisible({ timeout: 10_000 }); await projectEntry.click(); await page.getByRole("tab", { name: "Commits" }).click(); const commitRows = page.getByTestId("project-activity-feed-item"); await expect(commitRows.first()).toBeVisible({ timeout: 10_000 }); // Commits share the rounded list structure used by issues and pull requests. await expect( page.getByRole("heading", { name: "Commits", exact: true }), ).toBeVisible(); await waitForAnimations(page); await page.screenshot({ fullPage: false, path: `${SHOTS}/02-commits-feed.png`, }); // Open the newest commit via its subject button. await commitRows .first() .getByRole("button", { name: /Add Trello board workflow details/ }) .click(); // Detail header: author line, subject, and hash. await expect(page.getByText("Commit from")).toBeVisible(); await expect( page.getByRole("heading", { name: "Add Trello board workflow details" }), ).toBeVisible(); await expect( page.getByRole("button", { name: "Copy commit hash" }), ).toBeVisible(); await expect( page.getByRole("link", { name: "project guide" }), ).toHaveAttribute("href", "https://example.com/project-guide"); await expect( page.getByRole("button", { name: "Architecture" }), ).toBeVisible(); await expect(page.locator("video")).toHaveAttribute( "src", "https://example.com/project-demo.mp4", ); // Diff from the mocked get_project_repo_diff renders changed files. await expect(page.getByText("2 changed files")).toBeVisible({ timeout: 10_000, }); await expect( page.getByText("CommunityTabs({ selectedCommitHash })"), ).toBeVisible(); await waitForAnimations(page); await page.screenshot({ fullPage: false, path: `${SHOTS}/01-commit-detail.png`, }); // Breadcrumb category segment steps back to the commits feed. await page .getByRole("navigation", { name: "Project breadcrumb" }) .getByRole("button", { name: "Commits", exact: true }) .click(); await expect(commitRows.first()).toBeVisible(); // The commits feed itself gets a grayed sub-tab crumb. await expect( page.getByRole("navigation", { name: "Project breadcrumb" }), ).toContainText("Commits"); // The project-name segment goes to the project home (Overview tab). await commitRows .first() .getByRole("button", { name: /Add Trello board workflow details/ }) .click(); await expect(page.getByText("Commit from")).toBeVisible(); await page .getByRole("navigation", { name: "Project breadcrumb" }) .getByRole("button", { name: "buzz", exact: true }) .click(); await expect(page.getByRole("tab", { name: "Overview" })).toHaveAttribute( "aria-selected", "true", ); // The Projects root segment leaves the project entirely. await page .getByRole("navigation", { name: "Project breadcrumb" }) .getByRole("button", { name: "Projects", exact: true }) .click(); await expect(projectEntry).toBeVisible(); }); test("pull request and issue feeds share the commit row structure", async ({ page, }) => { await enableProjectsFeature(page); await installMockBridge(page); await page.goto("/", { waitUntil: "domcontentloaded" }); await page.getByTestId("open-projects-view").click(); // The overview no longer lists repository cards — switch to the // Repositories filter to reveal the project cards/rows. await page.getByRole("button", { name: "Repositories", exact: true }).click(); const projectEntry = page .locator( '[data-testid="project-card-buzz"], [data-testid="project-row-buzz"]', ) .first(); await expect(projectEntry).toBeVisible({ timeout: 10_000 }); await projectEntry.click(); // PR rows use the shared feed row: title button + #id cluster cell. await page.getByRole("tab", { name: "Pull Request" }).click(); const prRows = page.getByTestId("project-pull-request-row"); await expect(prRows.first()).toBeVisible({ timeout: 10_000 }); await expect( prRows.first().getByRole("button", { name: /^#/ }), ).toBeVisible(); await waitForAnimations(page); await page.screenshot({ fullPage: false, path: `${SHOTS}/03-prs-feed.png` }); // The #id cell opens the PR detail, same as clicking the title. await prRows.first().getByRole("button", { name: /^#/ }).click(); await expect( page.getByRole("navigation", { name: "Project breadcrumb" }), ).toContainText("Pull Request"); // Step back to the feed so the community tabs are available again. await page .getByRole("navigation", { name: "Project breadcrumb" }) .getByRole("button", { name: "Pull Request", exact: true }) .click(); await expect(prRows.first()).toBeVisible(); // Issue rows share the same structure. await page.getByRole("tab", { name: "Issues" }).click(); const issueRows = page.getByTestId("project-issue-row"); await expect(issueRows.first()).toBeVisible({ timeout: 10_000 }); await expect( issueRows.first().getByRole("button", { name: /^#/ }), ).toBeVisible(); await waitForAnimations(page); await page.screenshot({ fullPage: false, path: `${SHOTS}/04-issues-feed.png`, }); });