Files
kandev/apps/web/e2e/tests/changes-panel-active-tab-highlight.spec.ts
T
José Almeida 54ddb0aa1e feat(web): highlight active-tab file row in Changes panel (#954)
* feat(web): track active file path for diff panels

Extend resolveFilePath so activeFilePath updates when a file-diff panel
(preview:file-diff or diff:file:<path>) becomes the active dockview
panel. Also re-subscribe onDidParametersChange for the preview file-diff
slot so swapping the preview to a different file refreshes the path.

* feat(web): highlight active-tab file row in Changes panel

When a file's diff tab is the active dockview panel, mark the matching
row in unstaged, staged, and PR Changes lists with data-active="true"
and reuse the existing bg-accent/60 highlight (same as multi-select).
PRFileRow also gains data-changes-file so it shares the existing row
selector with FileRow.

* test(web): cover active-tab highlight unit + E2E

Unit test in FileRow guards the data-active attribute and bg-accent/60
class when isActive (with and without isSelected). New E2E spec drives
the full flow: open file-a diff, switch to file-b, close the preview,
and verify highlight follows when a file moves from unstaged to staged.

* fix(web): use distinct e2e task titles for active-tab highlight spec

clickTab("Changes") fails strict-mode when the task title also contains
the word "Changes" — Playwright matches both the session tab and the
dockview Changes tab. Renames to "Active Highlight (Staged) Test".

* test(web): close pinned diff:file panels in closeFileDiffPreview

The helper previously closed only the preview slot, leaving pinned
diff:file:<path> panels (created when a preview is promoted) active.
Sweeps every diff:file: panel so the row settles to data-active=false
regardless of how the diff was opened.

* test(web): unit-cover resolveFilePath panel-id branches

Exercises every branch of the dockview-store resolveFilePath closure:
file:, diff:file:, preview:file-editor, and preview:file-diff (the
last two via params.path), plus the null-out paths. Catches regressions
in the panel-id mapping without paying for an E2E round-trip.

* fix(web): strip "changes" from active-tab spec profile names

The agent profile name becomes the dockview session tab title, so a
profile named "changes-active-highlight" makes clickTab("Changes")
match two .dv-default-tab elements (session tab + Changes tab) and
trip strict-mode. Rename to "active-highlight" / "active-highlight-
staged" to keep the locator unambiguous.

* docs(web): note multi-repo path collision in PRFilesRepoGroup

Mirrors the FileListBody comment so future authors see the same caveat
in both file-list components: activeFilePath carries the path but not
the repo, so identical paths across repos light up both rows.

* fix(web): snapshot panels before closing in closeFileDiffPreview

dockview's api.panels is a live reference; calling panel.api.close()
removes the panel in place, so iterating the live array skipped every
other diff:file:* panel when two or more were open. Filter into a
local array before closing.
2026-05-19 15:53:48 +01:00

110 lines
4.2 KiB
TypeScript

import { test, expect } from "../fixtures/test-base";
import path from "node:path";
import {
GitHelper,
makeGitEnv,
openTaskSession,
createStandardProfile,
} from "../helpers/git-helper";
test.describe("Changes Panel — Active-Tab Highlight", () => {
test("clicking a file row highlights the row while its diff tab is active", async ({
testPage,
apiClient,
seedData,
backend,
}) => {
const repoDir = path.join(backend.tmpDir, "repos", "e2e-repo");
const git = new GitHelper(repoDir, makeGitEnv(backend.tmpDir));
const profile = await createStandardProfile(apiClient, "active-highlight");
await apiClient.createTaskWithAgent(seedData.workspaceId, "Active Highlight Test", profile.id, {
description: "/e2e:simple-message",
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
repository_ids: [seedData.repositoryId],
});
const session = await openTaskSession(testPage, "Active Highlight Test");
await session.clickTab("Changes");
git.createFile("active-a.ts", "a");
git.createFile("active-b.ts", "b");
await expect(testPage.getByTestId("unstaged-files-section")).toBeVisible({ timeout: 15_000 });
const fileA = session.changesFileRow("active-a.ts");
const fileB = session.changesFileRow("active-b.ts");
await expect(fileA).toBeVisible({ timeout: 15_000 });
await expect(fileB).toBeVisible({ timeout: 15_000 });
// Both start inactive.
await expect(fileA).toHaveAttribute("data-active", "false");
await expect(fileB).toHaveAttribute("data-active", "false");
// Open file-a → row A becomes active.
await fileA.click();
await expect(fileA).toHaveAttribute("data-active", "true", { timeout: 5_000 });
await expect(fileB).toHaveAttribute("data-active", "false");
await expect(session.changesActiveRows()).toHaveCount(1);
// Switch to file-b → highlight follows.
await fileB.click();
await expect(fileB).toHaveAttribute("data-active", "true", { timeout: 5_000 });
await expect(fileA).toHaveAttribute("data-active", "false");
await expect(session.changesActiveRows()).toHaveCount(1);
// Close the diff preview → nothing highlighted.
await session.closeFileDiffPreview();
await expect(fileA).toHaveAttribute("data-active", "false", { timeout: 5_000 });
await expect(fileB).toHaveAttribute("data-active", "false");
await expect(session.changesActiveRows()).toHaveCount(0);
});
test("highlight follows a file when it moves from unstaged to staged", async ({
testPage,
apiClient,
seedData,
backend,
}) => {
const repoDir = path.join(backend.tmpDir, "repos", "e2e-repo");
const git = new GitHelper(repoDir, makeGitEnv(backend.tmpDir));
const profile = await createStandardProfile(apiClient, "active-highlight-staged");
await apiClient.createTaskWithAgent(
seedData.workspaceId,
"Active Highlight Staged Test",
profile.id,
{
description: "/e2e:simple-message",
workflow_id: seedData.workflowId,
workflow_step_id: seedData.startStepId,
repository_ids: [seedData.repositoryId],
},
);
const session = await openTaskSession(testPage, "Active Highlight Staged Test");
await session.clickTab("Changes");
git.createFile("staged-active.ts", "a");
await expect(testPage.getByTestId("unstaged-files-section")).toBeVisible({ timeout: 15_000 });
const row = session.changesFileRow("staged-active.ts");
await expect(row).toBeVisible({ timeout: 15_000 });
await row.click();
await expect(row).toHaveAttribute("data-active", "true", { timeout: 5_000 });
// Stage the file via the existing per-file stage button; row re-renders in
// the staged section but must keep data-active="true" since the diff tab
// for the same path is still the active panel.
git.stageFile("staged-active.ts");
const stagedList = session.changes.getByTestId("staged-file-list");
await expect(stagedList.getByText("staged-active.ts")).toBeVisible({ timeout: 15_000 });
// The active row should now live inside the staged list.
const stagedRow = stagedList.locator(`[data-changes-file="staged-active.ts"]`);
await expect(stagedRow).toHaveAttribute("data-active", "true", { timeout: 5_000 });
});
});