mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
## Summary Project issues, pull requests, reviews, and commit details no longer flatten rich content into inert or plain text. They now share the message markdown and media pipeline, preserving NIP-92 `imeta` metadata so links, images, and videos render consistently. Commit bodies are fetched only when a single-commit detail view is opened, keeping list queries lightweight while exposing full context beside the diff. ### Related issue None found. ### Testing - Pre-push suite: `desktop-check`, `desktop-test`, `desktop-tauri-test`, `rust-tests`, and `mobile-test` - Project issue and pull request regression tests cover preserving attachment metadata on roots, updates, and comments - The project commit detail smoke scenario verifies linked text, images, and video in commit bodies Signed-off-by: Thomas Petersen <thomasp@squareup.com>
306 lines
11 KiB
TypeScript
306 lines
11 KiB
TypeScript
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`,
|
|
});
|
|
});
|