9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
1384 lines
48 KiB
TypeScript
1384 lines
48 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import { waitForAnimations } from "../helpers/animations";
|
|
import { installMockBridge, TEST_IDENTITIES } from "../helpers/bridge";
|
|
|
|
const SHOTS = "test-results/project-pr-review";
|
|
const RECOVERY_SHOTS = "test-results/project-pr-conflict-recovery";
|
|
const REVIEWER_AGENT_PUBKEY = "a".repeat(64);
|
|
const DEFAULT_MOCK_PUBKEY = "deadbeef".repeat(8);
|
|
|
|
// 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 }),
|
|
);
|
|
});
|
|
}
|
|
|
|
async function openBuzzProject(page: import("@playwright/test").Page) {
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await page.getByTestId("open-projects-view").click();
|
|
await page.getByTestId("projects-section-projects").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();
|
|
}
|
|
|
|
test("same-second request changes supersedes approval", async ({ page }) => {
|
|
await enableProjectsFeature(page);
|
|
await page.addInitScript(() => {
|
|
Date.now = () => 1_900_000_000_000;
|
|
});
|
|
await installMockBridge(page);
|
|
await openBuzzProject(page);
|
|
|
|
await page.getByRole("tab", { name: "Pull Request" }).click();
|
|
const aliceRow = page
|
|
.getByTestId("project-pull-request-row")
|
|
.filter({ hasText: "alice" })
|
|
.first();
|
|
await expect(aliceRow).toBeVisible({ timeout: 10_000 });
|
|
await aliceRow.getByRole("button", { name: /^#/ }).click();
|
|
|
|
await page.getByRole("button", { name: "Approve", exact: true }).click();
|
|
const approveDialog = page.getByRole("dialog", {
|
|
name: "Approve pull request",
|
|
});
|
|
await approveDialog
|
|
.getByRole("textbox", { name: "Approval summary" })
|
|
.fill("Approved at the fixed second.");
|
|
await approveDialog
|
|
.getByRole("button", { name: "Approve", exact: true })
|
|
.click();
|
|
await expect(page.getByText("Pull request approved.")).toBeVisible();
|
|
await expect(
|
|
page.getByRole("button", { name: "Approve", exact: true }),
|
|
).toHaveCount(0);
|
|
|
|
const commentComposer = page.getByTestId(
|
|
"project-pull-request-comment-composer",
|
|
);
|
|
await commentComposer
|
|
.getByRole("button", { name: "Comment", exact: true })
|
|
.click();
|
|
await page.getByRole("menuitemradio", { name: "Request changes" }).click();
|
|
await commentComposer
|
|
.locator('[contenteditable="true"]')
|
|
.fill("Changes requested at the same fixed second.");
|
|
await commentComposer.getByRole("button", { name: "Send message" }).click();
|
|
await expect(page.getByText("Changes requested.")).toBeVisible();
|
|
|
|
const [approvalEvent, changeRequestEvent] = await page.evaluate(() => {
|
|
const decisions =
|
|
window.__BUZZ_E2E_SIGNED_EVENTS__?.filter(
|
|
(event) =>
|
|
event.kind === 1 &&
|
|
event.tags.some(
|
|
(tag) =>
|
|
tag[0] === "t" &&
|
|
(tag[1] === "approval" || tag[1] === "changes-requested"),
|
|
),
|
|
) ?? [];
|
|
return [decisions.at(-2), decisions.at(-1)];
|
|
});
|
|
expect(approvalEvent?.tags).toContainEqual(["t", "approval"]);
|
|
expect(changeRequestEvent?.tags).toContainEqual(["t", "changes-requested"]);
|
|
expect(changeRequestEvent?.createdAt).toBeGreaterThan(
|
|
approvalEvent?.createdAt ?? 0,
|
|
);
|
|
});
|
|
|
|
test("PR creator/owner can toggle draft, request reviews, and approve", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await page.addInitScript(() => {
|
|
window.__BUZZ_E2E_REJECT_PROJECT_EVENT_KINDS__ = [1631];
|
|
});
|
|
await installMockBridge(page);
|
|
await openBuzzProject(page);
|
|
|
|
await page.getByRole("tab", { name: "Pull Request" }).click();
|
|
const prRows = page.getByTestId("project-pull-request-row");
|
|
await expect(prRows.first()).toBeVisible({ timeout: 10_000 });
|
|
|
|
// Pick a PR authored by alice: the viewer is not the author, so the
|
|
// Approve button must be available alongside the owner status controls.
|
|
const aliceRow = prRows.filter({ hasText: "alice" }).first();
|
|
await expect(aliceRow).toBeVisible();
|
|
await aliceRow.getByRole("button", { name: /^#/ }).click();
|
|
|
|
const header = page.getByRole("heading", { level: 3 });
|
|
await expect(header.first()).toBeVisible();
|
|
const sourceChannelLink = page.getByRole("button", {
|
|
name: "Open author-claimed origin channel #general",
|
|
exact: true,
|
|
});
|
|
await expect(sourceChannelLink).toBeVisible();
|
|
|
|
// Owner viewing an open PR: draft toggle and both review decisions are offered.
|
|
const morePullRequestActions = page.getByRole("button", {
|
|
name: "More pull request actions",
|
|
});
|
|
const approve = page.getByRole("button", { name: "Approve", exact: true });
|
|
const commentComposer = page.getByTestId(
|
|
"project-pull-request-comment-composer",
|
|
);
|
|
const reviewMode = commentComposer.getByRole("button", {
|
|
name: "Comment",
|
|
exact: true,
|
|
});
|
|
await expect(morePullRequestActions).toBeVisible();
|
|
await expect(approve).toBeVisible();
|
|
await expect(reviewMode).toBeVisible();
|
|
|
|
// Request a review from bob via the centered reviewer dialog.
|
|
await page.getByRole("button", { name: "Add Reviewer", exact: true }).click();
|
|
await expect(
|
|
page.getByRole("dialog").getByRole("heading", { name: "Add reviewer" }),
|
|
).toBeVisible();
|
|
await waitForAnimations(page);
|
|
await page.screenshot({
|
|
fullPage: false,
|
|
path: `${SHOTS}/00-add-reviewer-dialog.png`,
|
|
});
|
|
await page.getByTestId("project-reviewer-search").fill("bob");
|
|
await page
|
|
.getByTestId(`project-reviewer-result-${TEST_IDENTITIES.bob.pubkey}`)
|
|
.evaluate((button) => {
|
|
button.click();
|
|
button.click();
|
|
});
|
|
await expect(page.getByText("Review requested.")).toBeVisible();
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(
|
|
() =>
|
|
window.__BUZZ_E2E_SIGNED_EVENTS__?.filter(
|
|
(event) =>
|
|
event.kind === 1 &&
|
|
event.tags.some(
|
|
(tag) => tag[0] === "t" && tag[1] === "review-request",
|
|
),
|
|
).length ?? 0,
|
|
),
|
|
)
|
|
.toBe(1);
|
|
const reviewHistoryToggle = page.getByTestId(
|
|
"project-pull-request-review-history-toggle",
|
|
);
|
|
await expect(reviewHistoryToggle).toHaveAttribute("aria-expanded", "true");
|
|
await expect(
|
|
page.getByTestId("project-pull-request-timeline-row"),
|
|
).toHaveCount(1);
|
|
// The requested reviewer appears in the reviewers row and default timeline.
|
|
await expect(page.getByText("Requested a review from bob")).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
|
|
await waitForAnimations(page);
|
|
await page.screenshot({
|
|
fullPage: false,
|
|
path: `${SHOTS}/01-review-requested.png`,
|
|
});
|
|
|
|
await reviewMode.click();
|
|
await page.getByRole("menuitemradio", { name: "Request changes" }).click();
|
|
await commentComposer
|
|
.locator('[contenteditable="true"]')
|
|
.fill("Please handle the empty state before merging.");
|
|
await commentComposer.getByRole("button", { name: "Send message" }).click();
|
|
await expect(page.getByText("Changes requested.")).toBeVisible();
|
|
await expect(reviewMode).toHaveText("Comment");
|
|
await expect(
|
|
page.getByText("Please handle the empty state before merging."),
|
|
).toBeVisible();
|
|
await expect(
|
|
page
|
|
.getByTestId("project-pull-request-timeline-row")
|
|
.filter({ hasText: "requested changes" }),
|
|
).toBeVisible({ timeout: 10_000 });
|
|
await expect(
|
|
page.getByText("Changes requested", { exact: true }),
|
|
).toHaveCount(0);
|
|
const changeRequestEvent = await page.evaluate(() =>
|
|
window.__BUZZ_E2E_SIGNED_EVENTS__
|
|
?.filter(
|
|
(event) =>
|
|
event.kind === 1 &&
|
|
event.tags.some(
|
|
(tag) => tag[0] === "t" && tag[1] === "changes-requested",
|
|
),
|
|
)
|
|
.at(-1),
|
|
);
|
|
expect(changeRequestEvent?.content).toBe(
|
|
"Please handle the empty state before merging.",
|
|
);
|
|
expect(changeRequestEvent?.tags).toContainEqual(["c", expect.any(String)]);
|
|
const reviewDecisionEvents = await page.evaluate(
|
|
() =>
|
|
window.__BUZZ_E2E_SIGNED_EVENTS__?.filter(
|
|
(event) =>
|
|
event.kind === 1 &&
|
|
event.tags.some(
|
|
(tag) =>
|
|
tag[0] === "t" &&
|
|
(tag[1] === "approval" || tag[1] === "changes-requested"),
|
|
),
|
|
) ?? [],
|
|
);
|
|
expect(reviewDecisionEvents).toHaveLength(1);
|
|
|
|
await waitForAnimations(page);
|
|
await page.screenshot({
|
|
fullPage: false,
|
|
path: `${SHOTS}/05-changes-requested.png`,
|
|
});
|
|
const changeRequestRow = page
|
|
.getByTestId("project-pull-request-timeline-row")
|
|
.filter({ hasText: "requested changes" })
|
|
.first();
|
|
await changeRequestRow.getByRole("button").first().hover();
|
|
await expect(page.getByTestId("user-profile-popover")).toBeVisible();
|
|
await page.mouse.move(0, 0);
|
|
await expect(page.getByTestId("user-profile-popover")).toBeHidden();
|
|
|
|
await expect(reviewHistoryToggle).toContainText("Collapse review history");
|
|
const expandedReviewRows = page.getByTestId(
|
|
"project-pull-request-timeline-row",
|
|
);
|
|
await expect(expandedReviewRows).toHaveCount(2);
|
|
await expect(expandedReviewRows.nth(0)).toContainText(
|
|
"Requested a review from bob",
|
|
);
|
|
await expect(expandedReviewRows.nth(1)).toContainText("requested changes");
|
|
await expect(approve).toBeVisible();
|
|
await reviewHistoryToggle.click();
|
|
await expect(reviewHistoryToggle).toContainText("Show 2 earlier activities");
|
|
await expect(
|
|
page.getByTestId("project-pull-request-timeline-row"),
|
|
).toHaveCount(0);
|
|
await expect(changeRequestRow).toBeHidden();
|
|
|
|
// Replace the completed change request with an approval. Both decisions
|
|
// remain tied to the current commit and their timestamps preserve order.
|
|
await approve.click();
|
|
const approveDialog = page.getByRole("dialog", {
|
|
name: "Approve pull request",
|
|
});
|
|
await approveDialog
|
|
.getByRole("textbox", { name: "Approval summary" })
|
|
.fill("Ready to merge.");
|
|
await approveDialog
|
|
.getByRole("button", { name: "Approve", exact: true })
|
|
.click();
|
|
await expect(page.getByText("Pull request approved.")).toBeVisible();
|
|
await expect(
|
|
page.getByRole("button", { name: "Approve", exact: true }),
|
|
).toHaveCount(0);
|
|
await expect(page.getByText("Approved", { exact: true })).toHaveCount(0);
|
|
const approvalEvent = await page.evaluate(() =>
|
|
window.__BUZZ_E2E_SIGNED_EVENTS__
|
|
?.filter(
|
|
(event) =>
|
|
event.kind === 1 &&
|
|
event.tags.some((tag) => tag[0] === "t" && tag[1] === "approval"),
|
|
)
|
|
.at(-1),
|
|
);
|
|
expect(approvalEvent?.content).toBe("Ready to merge.");
|
|
expect(approvalEvent?.tags).toContainEqual(["c", expect.any(String)]);
|
|
expect(approvalEvent?.createdAt).toBeGreaterThan(
|
|
changeRequestEvent?.createdAt ?? 0,
|
|
);
|
|
await reviewHistoryToggle.click();
|
|
await expect(page.getByText("Ready to merge.")).toBeVisible({
|
|
timeout: 10_000,
|
|
});
|
|
await expect(
|
|
page.getByTestId("project-pull-request-timeline-row"),
|
|
).toHaveCount(3);
|
|
|
|
await waitForAnimations(page);
|
|
await page.screenshot({
|
|
fullPage: false,
|
|
path: `${SHOTS}/02-approved.png`,
|
|
});
|
|
|
|
// Histories over three entries show only the latest three until explicitly
|
|
// expanded. Collapsing the whole timeline preserves that inner choice.
|
|
await commentComposer
|
|
.locator('[contenteditable="true"]')
|
|
.fill("Remember the expanded history state.");
|
|
await commentComposer.getByRole("button", { name: "Send message" }).click();
|
|
await expect(page.getByText("Comment posted.")).toBeVisible();
|
|
const timelineRows = page.getByTestId("project-pull-request-timeline-row");
|
|
const earlierActivities = page.getByTestId(
|
|
"project-pull-request-earlier-activities",
|
|
);
|
|
await expect(timelineRows).toHaveCount(3);
|
|
await expect(earlierActivities).toContainText("Show 1 earlier activity");
|
|
|
|
await reviewHistoryToggle.click();
|
|
await expect(timelineRows).toHaveCount(0);
|
|
await reviewHistoryToggle.click();
|
|
await expect(timelineRows).toHaveCount(3);
|
|
await expect(earlierActivities).toBeVisible();
|
|
|
|
await earlierActivities.click();
|
|
await expect(timelineRows).toHaveCount(4);
|
|
await reviewHistoryToggle.click();
|
|
await expect(timelineRows).toHaveCount(0);
|
|
await reviewHistoryToggle.click();
|
|
await expect(timelineRows).toHaveCount(4);
|
|
await expect(earlierActivities).toHaveCount(0);
|
|
|
|
// Convert to draft: badge flips to Draft and the ready button appears.
|
|
await morePullRequestActions.click();
|
|
await page.getByRole("menuitem", { name: "Convert to draft" }).click();
|
|
await expect(page.getByText("Converted to draft.")).toBeVisible();
|
|
const readyForReview = page.getByRole("button", {
|
|
name: "Ready for review",
|
|
});
|
|
await expect(readyForReview).toBeVisible({ timeout: 10_000 });
|
|
await expect(morePullRequestActions).toBeVisible();
|
|
|
|
await waitForAnimations(page);
|
|
await page.screenshot({
|
|
fullPage: false,
|
|
path: `${SHOTS}/03-draft.png`,
|
|
});
|
|
|
|
// And back: Ready for review restores the Open state.
|
|
await readyForReview.click();
|
|
await expect(page.getByText("Marked as ready for review.")).toBeVisible();
|
|
await expect(morePullRequestActions).toBeVisible({ timeout: 10_000 });
|
|
|
|
// Closing is reversible, unlike merging: a closed PR can be reopened.
|
|
await morePullRequestActions.click();
|
|
const closePullRequest = page.getByRole("menuitem", {
|
|
name: "Close pull request",
|
|
});
|
|
await closePullRequest.click();
|
|
await expect(page.getByText("Pull request closed.")).toBeVisible();
|
|
const reopenPullRequest = page.getByRole("button", {
|
|
name: "Reopen pull request",
|
|
});
|
|
await expect(reopenPullRequest).toBeVisible({ timeout: 10_000 });
|
|
await expect(closePullRequest).toHaveCount(0);
|
|
|
|
await reopenPullRequest.click();
|
|
await expect(page.getByText("Pull request reopened.")).toBeVisible();
|
|
await expect(morePullRequestActions).toBeVisible({ timeout: 10_000 });
|
|
|
|
await page.getByRole("button", { name: "Merge", exact: true }).click();
|
|
await expect(page.getByTestId("merge-pull-request-confirm")).toBeVisible();
|
|
await page.getByTestId("merge-pull-request-confirm-button").click();
|
|
await expect(page.getByText("Merged feature into main.")).toBeVisible();
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(
|
|
() =>
|
|
window.__BUZZ_E2E_SIGNED_EVENTS__?.filter(
|
|
(event) => event.kind === 1631,
|
|
).length ?? 0,
|
|
),
|
|
)
|
|
.toBe(1);
|
|
await expect(
|
|
page.getByRole("button", {
|
|
name: "Publish merged status",
|
|
exact: true,
|
|
}),
|
|
).toBeVisible();
|
|
await page
|
|
.getByRole("button", {
|
|
name: "Publish merged status",
|
|
exact: true,
|
|
})
|
|
.click();
|
|
await expect(
|
|
page.getByText("Published merged pull request status."),
|
|
).toBeVisible();
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(
|
|
() =>
|
|
window.__BUZZ_E2E_SIGNED_EVENTS__?.filter(
|
|
(event) => event.kind === 1631,
|
|
).length ?? 0,
|
|
),
|
|
)
|
|
.toBe(1);
|
|
const mergedEvent = await page.evaluate(() =>
|
|
window.__BUZZ_E2E_SIGNED_EVENTS__
|
|
?.filter((event) => event.kind === 1631)
|
|
.at(-1),
|
|
);
|
|
expect(mergedEvent?.tags).toContainEqual([
|
|
"merge-commit",
|
|
"abcdef0123456789abcdef0123456789abcdef01",
|
|
]);
|
|
expect(mergedEvent?.tags.some((tag) => tag[0] === "e")).toBe(true);
|
|
const mergeCommandCount = await page.evaluate(
|
|
() =>
|
|
window.__BUZZ_E2E_COMMANDS__?.filter(
|
|
(command) => command === "merge_project_pull_request",
|
|
).length ?? 0,
|
|
);
|
|
expect(mergeCommandCount).toBe(1);
|
|
const mergePayload = await page.evaluate(() =>
|
|
window.__BUZZ_E2E_COMMAND_PAYLOADS__?.find(
|
|
(entry) => entry.command === "merge_project_pull_request",
|
|
),
|
|
);
|
|
expect(mergePayload?.payload).toMatchObject({
|
|
input: {
|
|
expectedCommit: expect.any(String),
|
|
sourceBranch: expect.any(String),
|
|
targetBranch: "main",
|
|
targetOwner: DEFAULT_MOCK_PUBKEY,
|
|
},
|
|
});
|
|
|
|
await sourceChannelLink.click();
|
|
await expect(page.getByTestId("chat-title")).toHaveText("general");
|
|
});
|
|
|
|
test("merge conflicts offer persistent terminal recovery", async ({ page }) => {
|
|
await enableProjectsFeature(page);
|
|
await installMockBridge(page);
|
|
await openBuzzProject(page);
|
|
await page.evaluate(() => {
|
|
window.__BUZZ_E2E_PROJECT_MERGE_ERROR__ = {
|
|
code: "merge_conflict",
|
|
message: "Pull request has merge conflicts.",
|
|
recovery: {
|
|
action: "open_terminal",
|
|
sourceBranch: "feature",
|
|
targetBranch: "main",
|
|
},
|
|
};
|
|
});
|
|
|
|
await page.getByRole("tab", { name: "Pull Request" }).click();
|
|
const aliceRow = page
|
|
.getByTestId("project-pull-request-row")
|
|
.filter({ hasText: "alice" })
|
|
.first();
|
|
await aliceRow.getByRole("button", { name: /^#/ }).click();
|
|
await page.getByRole("button", { name: "Merge", exact: true }).click();
|
|
await page.getByTestId("merge-pull-request-confirm-button").click();
|
|
|
|
const recovery = page.getByTestId("merge-conflict-recovery");
|
|
await expect(recovery).toBeVisible();
|
|
await expect(
|
|
recovery.getByRole("button", { name: "Copy commands" }),
|
|
).toBeDisabled();
|
|
await waitForAnimations(page);
|
|
await recovery.screenshot({
|
|
path: `${RECOVERY_SHOTS}/01-merge-conflict.png`,
|
|
});
|
|
await recovery.getByRole("button", { name: "Resolve in Terminal" }).click();
|
|
await expect(
|
|
page.getByText("Recovery commit fetched and terminal opened."),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.getByText("Recovery commit fetched and terminal opened."),
|
|
).toBeHidden({ timeout: 10_000 });
|
|
await expect(recovery).toContainText("git switch 'main'");
|
|
await expect(recovery).toContainText("git merge 'refs/buzz/merge-recovery/");
|
|
await expect(
|
|
recovery.getByRole("button", { name: "Copy commands" }),
|
|
).toBeEnabled();
|
|
await waitForAnimations(page);
|
|
await recovery.screenshot({
|
|
path: `${RECOVERY_SHOTS}/02-merge-conflict-prepared.png`,
|
|
});
|
|
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(
|
|
() =>
|
|
window.__BUZZ_E2E_COMMAND_PAYLOADS__?.find(
|
|
(entry) => entry.command === "open_project_merge_recovery_terminal",
|
|
) ?? null,
|
|
),
|
|
)
|
|
.toMatchObject({
|
|
command: "open_project_merge_recovery_terminal",
|
|
payload: {
|
|
input: {
|
|
expectedCommit: expect.any(String),
|
|
sourceBranch: "feature",
|
|
targetBranch: "main",
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
test("reviewer can leave a commit-scoped inline diff comment", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await installMockBridge(page);
|
|
await openBuzzProject(page);
|
|
|
|
await page.getByRole("tab", { name: "Pull Request" }).click();
|
|
const aliceRow = page
|
|
.getByTestId("project-pull-request-row")
|
|
.filter({ hasText: "alice" })
|
|
.first();
|
|
await aliceRow.getByRole("button", { name: /^#/ }).click();
|
|
await page.getByRole("tab", { name: /Files changed/ }).click();
|
|
|
|
const diffLine = page
|
|
.getByTestId("project-diff-line")
|
|
.filter({ hasText: "function CommunityTabs({ selectedCommitHash })" });
|
|
await expect(diffLine).toBeVisible({ timeout: 10_000 });
|
|
await diffLine.hover();
|
|
await diffLine.getByTestId("project-diff-add-comment").click();
|
|
|
|
const composer = page.getByTestId("project-inline-comment-thread");
|
|
await composer
|
|
.locator("[contenteditable='true']")
|
|
.fill("Please add a type for this parameter.");
|
|
await composer.getByRole("button", { name: "Comment", exact: true }).click();
|
|
await page.getByRole("menuitemradio", { name: "Request changes" }).click();
|
|
await composer.getByRole("button", { name: "Send message" }).click();
|
|
await expect(page.getByText("Changes requested.")).toBeVisible();
|
|
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(() =>
|
|
window.__BUZZ_E2E_SIGNED_EVENTS__?.find(
|
|
(event) => event.content === "Please add a type for this parameter.",
|
|
),
|
|
),
|
|
)
|
|
.not.toBeUndefined();
|
|
const inlineCommentEvent = await page.evaluate(() =>
|
|
window.__BUZZ_E2E_SIGNED_EVENTS__?.find(
|
|
(event) => event.content === "Please add a type for this parameter.",
|
|
),
|
|
);
|
|
expect(inlineCommentEvent?.tags).toContainEqual(["t", "inline-comment"]);
|
|
expect(inlineCommentEvent?.tags).toContainEqual(["t", "changes-requested"]);
|
|
expect(inlineCommentEvent?.tags).toContainEqual(["c", expect.any(String)]);
|
|
expect(inlineCommentEvent?.tags).toContainEqual([
|
|
"file",
|
|
"desktop/src/features/projects/ui/ProjectDetailScreen.tsx",
|
|
]);
|
|
expect(inlineCommentEvent?.tags).toContainEqual(["side", "new"]);
|
|
expect(inlineCommentEvent?.tags).toContainEqual(["line", "3"]);
|
|
await expect(page.getByTestId("project-inline-comment")).toContainText(
|
|
"Please add a type for this parameter.",
|
|
);
|
|
|
|
await page.getByRole("tab", { name: "Conversation" }).click();
|
|
await expect(
|
|
page.getByTestId("project-pull-request-review-history-toggle"),
|
|
).toHaveAttribute("aria-expanded", "true");
|
|
await expect(
|
|
page.getByText("Please add a type for this parameter."),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.getByText("desktop/src/features/projects/ui/ProjectDetailScreen.tsx"),
|
|
).toBeVisible();
|
|
await waitForAnimations(page);
|
|
await page.screenshot({
|
|
fullPage: false,
|
|
path: `${SHOTS}/04-inline-comment.png`,
|
|
});
|
|
|
|
await page
|
|
.getByRole("button", {
|
|
name: "Open desktop/src/features/projects/ui/ProjectDetailScreen.tsx new line 3 in Files changed",
|
|
})
|
|
.click();
|
|
await expect(
|
|
page.getByRole("tab", { name: /Files changed/ }),
|
|
).toHaveAttribute("data-state", "active");
|
|
const focusedLine = page.getByTestId("project-diff-focused-line");
|
|
await expect(focusedLine).toBeVisible();
|
|
await expect(focusedLine).toHaveAttribute(
|
|
"data-path",
|
|
"desktop/src/features/projects/ui/ProjectDetailScreen.tsx",
|
|
);
|
|
await expect(focusedLine).toHaveAttribute("data-side", "new");
|
|
await expect(focusedLine).toHaveAttribute("data-line", "3");
|
|
await focusedLine.click();
|
|
await expect(page.getByTestId("project-diff-focused-line")).toHaveCount(0);
|
|
});
|
|
|
|
test("managed agent repository owner can merge", async ({ page }) => {
|
|
await enableProjectsFeature(page);
|
|
await page.addInitScript((owner) => {
|
|
window.__BUZZ_E2E_PROJECT_OWNER_OVERRIDE__ = owner;
|
|
}, TEST_IDENTITIES.alice.pubkey);
|
|
await installMockBridge(page, {
|
|
managedAgents: [
|
|
{
|
|
pubkey: TEST_IDENTITIES.alice.pubkey,
|
|
name: "Brain",
|
|
},
|
|
{
|
|
pubkey: REVIEWER_AGENT_PUBKEY,
|
|
name: "Reviewer Bot",
|
|
},
|
|
],
|
|
});
|
|
await openBuzzProject(page);
|
|
|
|
await page.getByRole("tab", { name: "Pull Request" }).click();
|
|
const agentRow = page
|
|
.getByTestId("project-pull-request-row")
|
|
.filter({ hasText: "Brain" })
|
|
.first();
|
|
await expect(agentRow).toBeVisible({ timeout: 10_000 });
|
|
await agentRow.getByRole("button", { name: /^#/ }).click();
|
|
await page.getByRole("button", { name: "Add Reviewer", exact: true }).click();
|
|
await page.getByTestId("project-reviewer-search").fill("Reviewer Bot");
|
|
await page
|
|
.getByTestId(`project-reviewer-result-${REVIEWER_AGENT_PUBKEY}`)
|
|
.click();
|
|
await expect(page.getByText("Review requested.")).toBeVisible();
|
|
const reviewRequestPayload = await page.evaluate(() =>
|
|
window.__BUZZ_E2E_COMMAND_PAYLOADS__?.find(
|
|
(entry) => entry.command === "sign_project_pull_request_review_request",
|
|
),
|
|
);
|
|
expect(reviewRequestPayload?.payload).toMatchObject({
|
|
input: {
|
|
reviewers: [REVIEWER_AGENT_PUBKEY],
|
|
targetOwner: TEST_IDENTITIES.alice.pubkey,
|
|
},
|
|
});
|
|
await page.getByRole("button", { name: "More pull request actions" }).click();
|
|
const closePullRequest = page.getByRole("menuitem", {
|
|
name: "Close pull request",
|
|
});
|
|
await expect(closePullRequest).toBeVisible();
|
|
await closePullRequest.click();
|
|
await expect(page.getByText("Pull request closed.")).toBeVisible();
|
|
await page.getByRole("button", { name: "Reopen pull request" }).click();
|
|
await expect(page.getByText("Pull request reopened.")).toBeVisible();
|
|
const statusPayloads = await page.evaluate(() =>
|
|
window.__BUZZ_E2E_COMMAND_PAYLOADS__?.filter(
|
|
(entry) => entry.command === "sign_project_pull_request_status",
|
|
),
|
|
);
|
|
expect(statusPayloads).toHaveLength(2);
|
|
expect(statusPayloads?.map((entry) => entry.payload)).toEqual([
|
|
expect.objectContaining({
|
|
input: expect.objectContaining({
|
|
status: "closed",
|
|
targetOwner: TEST_IDENTITIES.alice.pubkey,
|
|
}),
|
|
}),
|
|
expect.objectContaining({
|
|
input: expect.objectContaining({
|
|
status: "open",
|
|
targetOwner: TEST_IDENTITIES.alice.pubkey,
|
|
}),
|
|
}),
|
|
]);
|
|
await page.getByRole("button", { name: "Merge", exact: true }).click();
|
|
await page.getByTestId("merge-pull-request-confirm-button").click();
|
|
await expect(page.getByText("Merged feature into main.")).toBeVisible();
|
|
|
|
const mergePayload = await page.evaluate(() =>
|
|
window.__BUZZ_E2E_COMMAND_PAYLOADS__?.find(
|
|
(entry) => entry.command === "merge_project_pull_request",
|
|
),
|
|
);
|
|
expect(mergePayload?.payload).toMatchObject({
|
|
input: {
|
|
expectedCommit: expect.any(String),
|
|
sourceBranch: expect.any(String),
|
|
targetBranch: "main",
|
|
targetOwner: TEST_IDENTITIES.alice.pubkey,
|
|
},
|
|
});
|
|
});
|
|
|
|
test("viewer without repository ownership cannot merge", async ({ page }) => {
|
|
await enableProjectsFeature(page);
|
|
await page.addInitScript((owner) => {
|
|
window.__BUZZ_E2E_PROJECT_OWNER_OVERRIDE__ = owner;
|
|
}, TEST_IDENTITIES.alice.pubkey);
|
|
await installMockBridge(page, {
|
|
managedAgents: [
|
|
{
|
|
pubkey: REVIEWER_AGENT_PUBKEY,
|
|
name: "Reviewer Bot",
|
|
},
|
|
],
|
|
});
|
|
await openBuzzProject(page);
|
|
|
|
await page.getByRole("tab", { name: "Pull Request" }).click();
|
|
const prRow = page.getByTestId("project-pull-request-row").first();
|
|
await expect(prRow).toBeVisible({ timeout: 10_000 });
|
|
await prRow.getByRole("button", { name: /^#/ }).click();
|
|
|
|
await expect(
|
|
page.getByRole("button", { name: "Merge", exact: true }),
|
|
).toHaveCount(0);
|
|
const mergeCommandCount = await page.evaluate(
|
|
() =>
|
|
window.__BUZZ_E2E_COMMANDS__?.filter(
|
|
(command) => command === "merge_project_pull_request",
|
|
).length ?? 0,
|
|
);
|
|
expect(mergeCommandCount).toBe(0);
|
|
|
|
const authorizationError = await page.evaluate(async (targetOwner) => {
|
|
try {
|
|
await window.__BUZZ_E2E_INVOKE_MOCK_COMMAND__?.(
|
|
"merge_project_pull_request",
|
|
{
|
|
input: {
|
|
expectedCommit: "1".repeat(40),
|
|
pullRequestAuthor: "2".repeat(64),
|
|
pullRequestId: "3".repeat(64),
|
|
repoAddress: `30617:${targetOwner}:buzz`,
|
|
sourceBranch: "feature/untrusted",
|
|
statusCreatedAt: 1,
|
|
targetBranch: "main",
|
|
targetOwner,
|
|
},
|
|
},
|
|
);
|
|
return null;
|
|
} catch (error) {
|
|
return error instanceof Error ? error.message : String(error);
|
|
}
|
|
}, TEST_IDENTITIES.alice.pubkey);
|
|
expect(authorizationError).toContain(
|
|
"Only the repository owner or the owner of its managed agent",
|
|
);
|
|
});
|
|
|
|
test("project pull requests preserve partial results from batched queries", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await page.addInitScript(() => {
|
|
window.__BUZZ_E2E_REJECT_PROJECT_QUERY_KINDS__ = [1619];
|
|
});
|
|
await installMockBridge(page);
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await page.getByTestId("open-projects-view").click();
|
|
await page
|
|
.getByRole("button", { name: "Pull Requests", exact: true })
|
|
.click();
|
|
|
|
await expect(
|
|
page.getByRole("button", { name: /^View / }).first(),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.getByText(/Some pull request details could not be loaded/),
|
|
).toBeVisible();
|
|
await expect(page.getByRole("button", { name: "Retry" })).toBeVisible();
|
|
|
|
const workItemFilters = await page.evaluate(
|
|
() =>
|
|
window.__BUZZ_E2E_PROJECT_QUERY_FILTERS__?.filter(
|
|
(filter) => filter.limit === 2_000,
|
|
) ?? [],
|
|
);
|
|
expect(
|
|
workItemFilters
|
|
.map((filter) => JSON.stringify([...(filter.kinds ?? [])].sort()))
|
|
.sort(),
|
|
).toEqual(
|
|
[[1], [1618, 1621], [1619], [1630, 1631, 1632, 1633]]
|
|
.map((kinds) => JSON.stringify(kinds))
|
|
.sort(),
|
|
);
|
|
expect(
|
|
workItemFilters.every((filter) => (filter["#a"]?.length ?? 0) > 1),
|
|
).toBe(true);
|
|
const expectedRepoAddresses = [
|
|
`30617:${DEFAULT_MOCK_PUBKEY}:buzz`,
|
|
`30617:${TEST_IDENTITIES.alice.pubkey}:relay-tools`,
|
|
`30617:${TEST_IDENTITIES.bob.pubkey}:design-system`,
|
|
].sort();
|
|
for (const filter of workItemFilters) {
|
|
expect([...(filter["#a"] ?? [])].sort()).toEqual(expectedRepoAddresses);
|
|
}
|
|
|
|
await page.evaluate(() => {
|
|
window.__BUZZ_E2E_REJECT_PROJECT_QUERY_KINDS__ = [];
|
|
});
|
|
await page.getByRole("button", { name: "Retry" }).click();
|
|
await expect(
|
|
page.getByText(/Some pull request details could not be loaded/),
|
|
).toHaveCount(0);
|
|
});
|
|
|
|
test("project pull request author rollover stays identity-only", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await installMockBridge(page);
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await page.getByTestId("open-projects-view").click();
|
|
await page
|
|
.getByRole("button", { name: "Pull Requests", exact: true })
|
|
.click();
|
|
await page.getByRole("button", { name: "List layout" }).click();
|
|
|
|
const row = page.locator('[data-testid^="projects-pr-row-"]').first();
|
|
const author = row.getByTestId("projects-pr-author");
|
|
await expect(author).toBeVisible();
|
|
await expect(
|
|
author.locator(
|
|
'[data-testid="projects-pr-author-avatar-image"], [data-testid="projects-pr-author-avatar-fallback"]',
|
|
),
|
|
).toBeVisible();
|
|
|
|
const authorLabel = (
|
|
await author.getByTestId("projects-pr-author-label").innerText()
|
|
).trim();
|
|
await author.hover();
|
|
const rollover = page.getByTestId("projects-pr-author-rollover");
|
|
await expect(rollover).toBeVisible();
|
|
await expect(rollover).toContainText(authorLabel);
|
|
await expect(rollover).toContainText(/Agent|Person/);
|
|
await expect(rollover).not.toContainText("Created");
|
|
await expect(page.getByTestId("user-profile-popover")).toHaveCount(0);
|
|
});
|
|
|
|
test("project issue author rollover matches pull requests", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await installMockBridge(page);
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await page.getByTestId("open-projects-view").click();
|
|
await page.getByRole("button", { name: "Issues", exact: true }).click();
|
|
await page.getByRole("button", { name: "List layout" }).click();
|
|
|
|
const row = page.locator('[data-testid^="projects-issue-row-"]').first();
|
|
const author = row.getByTestId("projects-issue-author");
|
|
await expect(author).toBeVisible();
|
|
await expect(
|
|
author.locator(
|
|
'[data-testid="projects-issue-author-avatar-image"], [data-testid="projects-issue-author-avatar-fallback"]',
|
|
),
|
|
).toBeVisible();
|
|
|
|
const authorLabel = (
|
|
await author.getByTestId("projects-issue-author-label").innerText()
|
|
).trim();
|
|
await author.hover();
|
|
const rollover = page.getByTestId("projects-issue-author-rollover");
|
|
await expect(rollover).toBeVisible();
|
|
await expect(rollover).toContainText(authorLabel);
|
|
await expect(rollover).toContainText(/Agent|Person/);
|
|
await expect(rollover).not.toContainText("Created");
|
|
await expect(page.getByTestId("user-profile-popover")).toHaveCount(0);
|
|
});
|
|
|
|
test("project pull requests report aggregate root query failures", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await page.addInitScript(() => {
|
|
window.__BUZZ_E2E_REJECT_PROJECT_QUERY_KINDS__ = [1618];
|
|
});
|
|
await installMockBridge(page);
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await page.getByTestId("open-projects-view").click();
|
|
await page
|
|
.getByRole("button", { name: "Pull Requests", exact: true })
|
|
.click();
|
|
|
|
await expect(page.getByText("Could not load pull requests.")).toBeVisible();
|
|
await expect(page.getByRole("button", { name: "Retry" })).toBeVisible();
|
|
await expect(page.getByText("No pull requests yet.")).toHaveCount(0);
|
|
|
|
await page.evaluate(() => {
|
|
window.__BUZZ_E2E_REJECT_PROJECT_QUERY_KINDS__ = [];
|
|
});
|
|
await page.getByRole("button", { name: "Retry" }).click();
|
|
await expect(page.getByText("Could not load pull requests.")).toHaveCount(0);
|
|
await expect(
|
|
page.getByRole("button", { name: /^View / }).first(),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("project issues preserve partial results from aggregate queries", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await page.addInitScript(() => {
|
|
window.__BUZZ_E2E_REJECT_PROJECT_QUERY_KINDS__ = [1];
|
|
});
|
|
await installMockBridge(page);
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await page.getByTestId("open-projects-view").click();
|
|
await page.getByRole("button", { name: "Issues", exact: true }).click();
|
|
|
|
await expect(
|
|
page.getByRole("button", { name: /^View / }).first(),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.getByText("Some issue details could not be loaded."),
|
|
).toBeVisible();
|
|
await expect(page.getByText(/Missing comments\./)).toBeVisible();
|
|
await expect(page.getByRole("button", { name: "Retry" })).toBeVisible();
|
|
|
|
await page.evaluate(() => {
|
|
window.__BUZZ_E2E_REJECT_PROJECT_QUERY_KINDS__ = [];
|
|
});
|
|
await page.getByRole("button", { name: "Retry" }).click();
|
|
await expect(
|
|
page.getByText("Some issue details could not be loaded."),
|
|
).toHaveCount(0);
|
|
});
|
|
|
|
test("project overview reports aggregate work-item failures", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await page.addInitScript(() => {
|
|
window.__BUZZ_E2E_REJECT_PROJECT_QUERY_KINDS__ = [1618];
|
|
});
|
|
await installMockBridge(page);
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await page.getByTestId("open-projects-view").click();
|
|
|
|
await expect(
|
|
page.getByText("Could not load project activity."),
|
|
).toBeVisible();
|
|
await expect(page.getByRole("button", { name: "Retry" })).toBeVisible();
|
|
|
|
await page.evaluate(() => {
|
|
window.__BUZZ_E2E_REJECT_PROJECT_QUERY_KINDS__ = [];
|
|
});
|
|
await page.getByRole("button", { name: "Retry" }).click();
|
|
await expect(page.getByText("Could not load project activity.")).toHaveCount(
|
|
0,
|
|
);
|
|
});
|
|
|
|
test("project overview does not paint a background behind its cards", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await installMockBridge(page);
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await page.getByTestId("open-projects-view").click();
|
|
|
|
await expect(page.getByTestId("projects-overview-panel")).toHaveCSS(
|
|
"background-color",
|
|
"rgba(0, 0, 0, 0)",
|
|
);
|
|
|
|
const stats = page.getByTestId("projects-overview-stat");
|
|
await expect(stats).toHaveCount(4);
|
|
for (let index = 0; index < 4; index += 1) {
|
|
await expect(stats.nth(index)).toHaveCSS(
|
|
"background-color",
|
|
"rgba(0, 0, 0, 0)",
|
|
);
|
|
await expect(stats.nth(index)).toHaveCSS("border-style", "solid");
|
|
}
|
|
|
|
const activityCards = page.getByTestId("projects-activity-card");
|
|
await expect(activityCards.first()).toBeVisible();
|
|
const activityCardCount = await activityCards.count();
|
|
for (let index = 0; index < activityCardCount; index += 1) {
|
|
await expect(activityCards.nth(index)).toHaveCSS(
|
|
"background-color",
|
|
"rgba(0, 0, 0, 0)",
|
|
);
|
|
await expect(activityCards.nth(index)).toHaveCSS("border-style", "solid");
|
|
}
|
|
});
|
|
|
|
test("repository rows identify their git host", async ({ page }) => {
|
|
await enableProjectsFeature(page);
|
|
await installMockBridge(page);
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await page.getByTestId("open-projects-view").click();
|
|
await page.getByRole("button", { name: "Repositories", exact: true }).click();
|
|
await page.getByRole("button", { name: "List layout" }).click();
|
|
|
|
const buzzHostIcon = page
|
|
.getByTestId("repository-row-buzz")
|
|
.getByTestId("repository-host-icon");
|
|
await expect(buzzHostIcon).toHaveAttribute(
|
|
"aria-label",
|
|
"Buzz-hosted repository",
|
|
);
|
|
await expect(
|
|
page
|
|
.getByTestId("repository-row-relay-tools")
|
|
.getByTestId("repository-host-icon"),
|
|
).toHaveAttribute("aria-label", "Git data hosted on github.com");
|
|
|
|
await buzzHostIcon.hover();
|
|
await expect(
|
|
page.getByRole("tooltip", { name: "Buzz-hosted repository" }),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("project subsections do not paint backgrounds behind list or grid items", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await installMockBridge(page);
|
|
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
await page.getByTestId("open-projects-view").click();
|
|
|
|
for (const section of ["Repositories", "Pull Requests", "Issues"] as const) {
|
|
await page.getByRole("button", { name: section, exact: true }).click();
|
|
await page.getByRole("button", { name: "List layout" }).click();
|
|
|
|
const listItems = page.locator(
|
|
section === "Repositories"
|
|
? '[data-testid^="repository-row-"]'
|
|
: section === "Pull Requests"
|
|
? '[data-testid^="projects-pr-row-"]'
|
|
: '[data-testid^="projects-issue-row-"]',
|
|
);
|
|
await expect(listItems.first()).toBeVisible();
|
|
const listItemCount = await listItems.count();
|
|
for (let index = 0; index < listItemCount; index += 1) {
|
|
await expect(listItems.nth(index)).toHaveCSS(
|
|
"background-color",
|
|
"rgba(0, 0, 0, 0)",
|
|
);
|
|
await expect(listItems.nth(index)).toHaveCSS("border-style", "solid");
|
|
}
|
|
|
|
await page.getByRole("button", { name: "Grid layout" }).click();
|
|
const gridCards = page.locator(
|
|
section === "Repositories"
|
|
? '[data-testid^="repository-card-"]'
|
|
: "[data-projects-grid-card]",
|
|
);
|
|
await expect(gridCards.first()).toBeVisible();
|
|
const gridCardCount = await gridCards.count();
|
|
for (let index = 0; index < gridCardCount; index += 1) {
|
|
await expect(gridCards.nth(index)).toHaveCSS(
|
|
"background-color",
|
|
"rgba(0, 0, 0, 0)",
|
|
);
|
|
await expect(gridCards.nth(index)).toHaveCSS("border-style", "solid");
|
|
}
|
|
}
|
|
});
|
|
|
|
test("project detail content areas do not paint background fills", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await installMockBridge(page);
|
|
await openBuzzProject(page);
|
|
|
|
const expectVisiblePanelsToBeTransparent = async () => {
|
|
const panels = page.locator("[data-project-detail-panel]:visible");
|
|
await expect(panels.first()).toBeVisible();
|
|
const panelCount = await panels.count();
|
|
for (let index = 0; index < panelCount; index += 1) {
|
|
await expect(panels.nth(index)).toHaveCSS(
|
|
"background-color",
|
|
"rgba(0, 0, 0, 0)",
|
|
);
|
|
await expect(panels.nth(index)).toHaveCSS("border-style", "solid");
|
|
}
|
|
};
|
|
|
|
for (const tab of [
|
|
"Overview",
|
|
"Files",
|
|
"Commits",
|
|
"Issues",
|
|
"Pull Request",
|
|
"Contributors",
|
|
]) {
|
|
await page.getByRole("tab", { name: tab, exact: true }).click();
|
|
await expectVisiblePanelsToBeTransparent();
|
|
}
|
|
|
|
await page.getByRole("tab", { name: "Pull Request", exact: true }).click();
|
|
const pullRequest = page.getByTestId("project-pull-request-row").first();
|
|
await expect(pullRequest).toBeVisible();
|
|
await pullRequest.getByRole("button", { name: /^#/ }).click();
|
|
await expectVisiblePanelsToBeTransparent();
|
|
});
|
|
|
|
test("project without a checkout offers fetch feedback and dropdown cloning", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await installMockBridge(page);
|
|
await openBuzzProject(page);
|
|
|
|
await expect(
|
|
page.getByRole("button", { name: "Buzz", exact: true }),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.getByRole("button", { name: "Buzz", exact: true }),
|
|
).toHaveClass(/\bborder-input\/40\b/);
|
|
await expect(page.getByRole("button", { name: /main/ })).toHaveClass(
|
|
/\bborder-input\/40\b/,
|
|
);
|
|
await expect(
|
|
page.getByRole("button", { name: "Clone", exact: true }),
|
|
).toHaveCount(0);
|
|
await page.getByRole("button", { name: "Fetch", exact: true }).click();
|
|
await expect(page.getByText("Remote state refreshed.")).toBeVisible();
|
|
|
|
await page.getByRole("button", { name: "Buzz", exact: true }).click();
|
|
const cloneItem = page.getByRole("menuitem", {
|
|
name: "Local missing Clone",
|
|
});
|
|
await expect(cloneItem.getByText("Local missing")).toHaveClass(
|
|
/text-muted-foreground/,
|
|
);
|
|
await expect(cloneItem.getByText("Clone", { exact: true })).toHaveClass(
|
|
/\bborder-input\/60\b/,
|
|
);
|
|
await cloneItem.click();
|
|
await expect(page.getByText("Cloned repository.")).toBeVisible();
|
|
const commands = await page.evaluate(
|
|
() => window.__BUZZ_E2E_COMMANDS__ ?? [],
|
|
);
|
|
expect(commands).toContain("clone_project_repository");
|
|
});
|
|
|
|
test("project branches can be created from the selected remote branch", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await installMockBridge(page, {
|
|
projectHeadBranch: "master",
|
|
relaySelf: TEST_IDENTITIES.bob.pubkey,
|
|
});
|
|
await openBuzzProject(page);
|
|
|
|
await page.getByRole("button", { name: /main/ }).click();
|
|
await page.getByTestId("project-create-branch").click();
|
|
await page
|
|
.getByTestId("project-create-branch-name")
|
|
.fill("feature/branch-management");
|
|
await page.getByTestId("project-create-branch-submit").click();
|
|
|
|
await expect(
|
|
page.getByText("Created branch feature/branch-management from main.", {
|
|
exact: true,
|
|
}),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.getByRole("button", { name: /feature\/branch-management/ }),
|
|
).toBeVisible();
|
|
await page
|
|
.getByRole("button", { name: /feature\/branch-management/ })
|
|
.click();
|
|
await expect(
|
|
page.getByRole("menuitemradio", { name: "feature/branch-management" }),
|
|
).toBeVisible();
|
|
await page.getByRole("menuitemradio", { name: "main" }).click();
|
|
await page.getByRole("button", { name: /main/ }).click();
|
|
await expect(
|
|
page.getByRole("menuitemradio", { name: "feature/branch-management" }),
|
|
).toBeVisible();
|
|
const commands = await page.evaluate(
|
|
() => window.__BUZZ_E2E_COMMANDS__ ?? [],
|
|
);
|
|
expect(commands).toContain("create_project_remote_branch");
|
|
|
|
await openBuzzProject(page);
|
|
await page.getByRole("button", { name: /main/ }).click();
|
|
await expect(
|
|
page.getByRole("menuitemradio", { name: "feature/branch-management" }),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("repository tags can be browsed as immutable remote snapshots", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await installMockBridge(page);
|
|
await openBuzzProject(page);
|
|
|
|
await page.getByRole("button", { name: /main/ }).click();
|
|
await expect(page.getByText("Tags", { exact: true })).toBeVisible();
|
|
await expect(
|
|
page.getByRole("menuitemradio", { name: /v1\.0\.0.*0123456/ }),
|
|
).toBeVisible();
|
|
await page.getByRole("menuitemradio", { name: /v1\.0\.0.*0123456/ }).click();
|
|
|
|
await expect(page.getByRole("button", { name: /v1\.0\.0/ })).toBeVisible();
|
|
await expect(
|
|
page.getByRole("button", { name: "Buzz", exact: true }),
|
|
).toBeVisible();
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(() => {
|
|
const call = [...(window.__BUZZ_E2E_COMMAND_PAYLOADS__ ?? [])]
|
|
.reverse()
|
|
.find((entry) => entry.command === "get_project_repo_snapshot");
|
|
return (call?.payload as { targetRef?: string } | undefined)?.targetRef;
|
|
}),
|
|
)
|
|
.toBe("refs/tags/v1.0.0");
|
|
await page.getByRole("button", { name: /v1\.0\.0/ }).click();
|
|
await expect(page.getByTestId("project-create-branch")).toHaveCount(0);
|
|
await expect(page.getByTestId("project-delete-branch")).toHaveCount(0);
|
|
|
|
await page.getByRole("menuitemradio", { name: "main" }).click();
|
|
await page.getByRole("button", { name: /main/ }).click();
|
|
await expect(page.getByTestId("project-create-branch")).toBeVisible();
|
|
});
|
|
|
|
test("project branches can be deleted but the default branch cannot", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await installMockBridge(page);
|
|
await openBuzzProject(page);
|
|
|
|
await page.getByRole("button", { name: /main/ }).click();
|
|
await expect(page.getByTestId("project-delete-branch")).toBeDisabled();
|
|
await page.getByTestId("project-create-branch").click();
|
|
await page
|
|
.getByTestId("project-create-branch-name")
|
|
.fill("feature/delete-me");
|
|
await page.getByTestId("project-create-branch-submit").click();
|
|
await expect(
|
|
page.getByRole("button", { name: /feature\/delete-me/ }),
|
|
).toBeVisible();
|
|
await page.getByRole("button", { name: /feature\/delete-me/ }).click();
|
|
await page.getByTestId("project-delete-branch").click();
|
|
await expect(page.getByTestId("project-delete-branch-dialog")).toBeVisible();
|
|
await page.getByTestId("project-delete-branch-submit").click();
|
|
|
|
await expect(
|
|
page.getByText("Deleted branch feature/delete-me.", { exact: true }),
|
|
).toBeVisible();
|
|
await expect(page.getByRole("button", { name: /main/ })).toBeVisible();
|
|
const commands = await page.evaluate(
|
|
() => window.__BUZZ_E2E_COMMANDS__ ?? [],
|
|
);
|
|
expect(commands).toContain("delete_project_remote_branch");
|
|
});
|
|
|
|
test("pushed local branch can open a pull request", async ({ page }) => {
|
|
await enableProjectsFeature(page);
|
|
await page.addInitScript(() => {
|
|
const commit = "1234567890abcdef1234567890abcdef12345678";
|
|
window.__BUZZ_E2E_PROJECT_REPO_SYNC_STATUS__ = {
|
|
local_path: "/tmp/buzz/REPOS/buzz",
|
|
local_branch: "feature/projects-workflow",
|
|
local_branches: ["feature/projects-workflow", "space"],
|
|
local_head: commit,
|
|
local_short_head: commit.slice(0, 7),
|
|
remote_branch: "feature/projects-workflow",
|
|
remote_head: commit,
|
|
remote_short_head: commit.slice(0, 7),
|
|
merge_base: "0123456789abcdef0123456789abcdef01234567",
|
|
ahead_count: 0,
|
|
behind_count: 0,
|
|
has_uncommitted_changes: false,
|
|
has_untracked_files: false,
|
|
can_push: false,
|
|
push_block_reason: "Local branch is already pushed.",
|
|
can_pull: false,
|
|
pull_block_reason: "Local branch is up to date.",
|
|
};
|
|
});
|
|
await installMockBridge(page);
|
|
await openBuzzProject(page);
|
|
|
|
await page.getByRole("button", { name: /main/ }).click();
|
|
await expect(
|
|
page.getByRole("menuitemradio", { name: "space" }),
|
|
).toBeVisible();
|
|
await page
|
|
.getByRole("menuitemradio", { name: "feature/projects-workflow" })
|
|
.click();
|
|
await page.getByRole("tab", { name: "Pull Request", exact: true }).click();
|
|
await page.getByRole("button", { name: "Pull Request", exact: true }).click();
|
|
await expect(page.getByTestId("create-pull-request-repository")).toHaveValue(
|
|
/:buzz$/,
|
|
);
|
|
await expect(page.getByTestId("create-pull-request-base-branch")).toHaveValue(
|
|
"main",
|
|
);
|
|
await expect(
|
|
page.getByTestId("create-pull-request-compare-branch"),
|
|
).toHaveValue("feature/projects-workflow");
|
|
await page
|
|
.getByTestId("create-pull-request-title")
|
|
.fill("Complete the Projects git workflow");
|
|
await page
|
|
.getByTestId("create-pull-request-body")
|
|
.fill("Adds the missing desktop write path.");
|
|
await page.getByTestId("create-pull-request-submit").evaluate((button) => {
|
|
button.click();
|
|
button.click();
|
|
});
|
|
await expect(page.getByText("Pull request created.")).toBeVisible();
|
|
|
|
const createdEvents = await page.evaluate(
|
|
() =>
|
|
window.__BUZZ_E2E_SIGNED_EVENTS__?.filter(
|
|
(event) => event.kind === 1618,
|
|
) ?? [],
|
|
);
|
|
expect(createdEvents).toHaveLength(1);
|
|
const [createdEvent] = createdEvents;
|
|
expect(createdEvent?.tags).toContainEqual([
|
|
"branch-name",
|
|
"feature/projects-workflow",
|
|
]);
|
|
expect(createdEvent?.tags).toContainEqual(["target-branch", "main"]);
|
|
expect(createdEvent?.tags).toContainEqual([
|
|
"subject",
|
|
"Complete the Projects git workflow",
|
|
]);
|
|
});
|
|
|
|
test("project issue can be created from the issues header", async ({
|
|
page,
|
|
}) => {
|
|
await enableProjectsFeature(page);
|
|
await installMockBridge(page);
|
|
await openBuzzProject(page);
|
|
|
|
await page.getByRole("tab", { name: "Issues", exact: true }).click();
|
|
await page.getByRole("button", { name: "Issues", exact: true }).click();
|
|
await page
|
|
.getByTestId("create-issue-title")
|
|
.fill("Document the broken workflow");
|
|
await page
|
|
.getByTestId("create-issue-body")
|
|
.fill("The project workflow needs a clear repair path.");
|
|
await page.getByTestId("create-issue-submit").click();
|
|
await expect(page.getByText("Issue created.")).toBeVisible();
|
|
|
|
const createdEvent = await page.evaluate(() =>
|
|
window.__BUZZ_E2E_SIGNED_EVENTS__?.find((event) => event.kind === 1621),
|
|
);
|
|
expect(createdEvent?.tags).toContainEqual([
|
|
"subject",
|
|
"Document the broken workflow",
|
|
]);
|
|
});
|