Files
kandev/apps/web/e2e/tests/github/pr-list-task-indicator.spec.ts
T

139 lines
5.0 KiB
TypeScript

import { test, expect } from "../../fixtures/test-base";
test.describe("GitHub PR list task indicator", () => {
test("renders indicator states, tooltip content, and navigates on click", async ({
testPage,
apiClient,
seedData,
}) => {
test.setTimeout(60_000);
await apiClient.mockGitHubReset();
await apiClient.mockGitHubSetUser("test-user");
await apiClient.mockGitHubAddPRs([
{
number: 100,
title: "PR with linked task",
state: "open",
head_branch: "feat/linked",
base_branch: "main",
author_login: "test-user",
repo_owner: "testorg",
repo_name: "testrepo",
},
{
number: 101,
title: "PR without task",
state: "open",
head_branch: "feat/orphan",
base_branch: "main",
author_login: "test-user",
repo_owner: "testorg",
repo_name: "testrepo",
},
{
number: 102,
title: "PR with multiple tasks",
state: "open",
head_branch: "feat/multi",
base_branch: "main",
author_login: "test-user",
repo_owner: "testorg",
repo_name: "testrepo",
},
]);
const task = await apiClient.createTask(seedData.workspaceId, "Linked task title", {
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
agent_profile_id: seedData.agentProfileId,
repository_ids: [seedData.repositoryId],
});
await apiClient.mockGitHubAssociateTaskPR({
task_id: task.id,
owner: "testorg",
repo: "testrepo",
pr_number: 100,
pr_url: "https://github.com/testorg/testrepo/pull/100",
pr_title: "PR with linked task",
head_branch: "feat/linked",
base_branch: "main",
author_login: "test-user",
});
// Multi-task case: two distinct tasks linked to the same PR (PR #102).
// Regression guard for the bug where review tasks created via "+ Task"
// on the GitHub page never got their github_task_prs row written
// (synthetic worktree branches never matched the PR head on GitHub).
const multiA = await apiClient.createTask(seedData.workspaceId, "Multi task A", {
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
agent_profile_id: seedData.agentProfileId,
repository_ids: [seedData.repositoryId],
});
const multiB = await apiClient.createTask(seedData.workspaceId, "Multi task B", {
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
agent_profile_id: seedData.agentProfileId,
repository_ids: [seedData.repositoryId],
});
for (const t of [multiA, multiB]) {
await apiClient.mockGitHubAssociateTaskPR({
task_id: t.id,
owner: "testorg",
repo: "testrepo",
pr_number: 102,
pr_url: "https://github.com/testorg/testrepo/pull/102",
pr_title: "PR with multiple tasks",
head_branch: "feat/multi",
base_branch: "main",
author_login: "test-user",
});
}
await testPage.goto("/github");
// Gate on the PR list finishing its first-load chain (client GitHub-status
// fetch -> AuthenticatedLayout mount -> PR search) before asserting on the
// per-row indicators. /github doesn't SSR-hydrate github status, so the list
// only renders after that serial chain resolves. Waiting for the three rows
// here lets every indicator assertion below run against an already-rendered
// list with the default timeout instead of racing the load.
await expect(testPage.getByTestId("pr-row")).toHaveCount(3, { timeout: 15_000 });
const singleIndicator = testPage.getByTestId("pr-row-task-indicator-single");
await expect(singleIndicator).toBeVisible();
await expect(singleIndicator).toContainText("Linked task title");
const emptyIndicator = testPage.getByTestId("pr-row-task-indicator-empty");
await expect(emptyIndicator).toBeVisible();
await expect(emptyIndicator).toHaveText("No task created yet");
const multiIndicator = testPage.getByTestId("pr-row-task-indicator-multi");
await expect(multiIndicator).toBeVisible();
await expect(multiIndicator).toContainText("2");
await singleIndicator.hover();
const startStep = seedData.steps.find((s) => s.id === seedData.startStepId);
const tooltip = testPage.getByRole("tooltip");
await expect(tooltip).toContainText("Task: Linked task title", { timeout: 5_000 });
if (startStep?.title) {
await expect(tooltip).toContainText(`Step: ${startStep.title}`);
}
// Click the indicator and confirm a real route change to /t/<task.id>.
// Regression guard: `replaceTaskUrl` previously only mutated history.state
// without triggering Next.js navigation, leaving the PR list rendered.
await singleIndicator.click();
await expect(testPage).toHaveURL(new RegExp(`/t/${task.id}(?:\\?|$)`), {
timeout: 15_000,
});
await expect(testPage.getByTestId("pr-row-task-indicator-single")).toHaveCount(0, {
timeout: 10_000,
});
});
});