54ddb0aa1e
* 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.
94 lines
3.1 KiB
TypeScript
94 lines
3.1 KiB
TypeScript
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
import type { DockviewApi } from "dockview-react";
|
|
import { useDockviewStore } from "./dockview-store";
|
|
|
|
type ActivePanelEvent = { id: string };
|
|
type CapturedHandlers = {
|
|
active: ((e?: ActivePanelEvent) => void) | null;
|
|
};
|
|
|
|
type ParamsPanel = { id: string; params: Record<string, unknown> };
|
|
|
|
function makeApi(panels: ParamsPanel[] = []): { api: DockviewApi; captured: CapturedHandlers } {
|
|
const captured: CapturedHandlers = { active: null };
|
|
const api = {
|
|
onDidActivePanelChange: (cb: (e?: ActivePanelEvent) => void) => {
|
|
captured.active = cb;
|
|
return { dispose: vi.fn() };
|
|
},
|
|
onDidAddPanel: () => ({ dispose: vi.fn() }),
|
|
onDidRemovePanel: () => ({ dispose: vi.fn() }),
|
|
getPanel: (id: string) => panels.find((p) => p.id === id),
|
|
hasMaximizedGroup: () => false,
|
|
} as unknown as DockviewApi;
|
|
return { api, captured };
|
|
}
|
|
|
|
describe("dockview-store resolveFilePath (via onDidActivePanelChange)", () => {
|
|
beforeEach(() => {
|
|
useDockviewStore.getState().setApi(null);
|
|
});
|
|
|
|
it("resolves pinned file: panel id to its path", () => {
|
|
const { api, captured } = makeApi();
|
|
useDockviewStore.getState().setApi(api);
|
|
|
|
captured.active?.({ id: "file:src/foo.ts" });
|
|
|
|
expect(useDockviewStore.getState().activeFilePath).toBe("src/foo.ts");
|
|
});
|
|
|
|
it("resolves pinned diff:file: panel id to its path", () => {
|
|
const { api, captured } = makeApi();
|
|
useDockviewStore.getState().setApi(api);
|
|
|
|
captured.active?.({ id: "diff:file:src/bar.ts" });
|
|
|
|
expect(useDockviewStore.getState().activeFilePath).toBe("src/bar.ts");
|
|
});
|
|
|
|
it("resolves preview:file-editor panel via params.path", () => {
|
|
const { api, captured } = makeApi([
|
|
{ id: "preview:file-editor", params: { path: "src/baz.ts" } },
|
|
]);
|
|
useDockviewStore.getState().setApi(api);
|
|
|
|
captured.active?.({ id: "preview:file-editor" });
|
|
|
|
expect(useDockviewStore.getState().activeFilePath).toBe("src/baz.ts");
|
|
});
|
|
|
|
it("resolves preview:file-diff panel via params.path", () => {
|
|
const { api, captured } = makeApi([
|
|
{ id: "preview:file-diff", params: { path: "src/diff.ts" } },
|
|
]);
|
|
useDockviewStore.getState().setApi(api);
|
|
|
|
captured.active?.({ id: "preview:file-diff" });
|
|
|
|
expect(useDockviewStore.getState().activeFilePath).toBe("src/diff.ts");
|
|
});
|
|
|
|
it("clears activeFilePath when a non-file panel becomes active", () => {
|
|
const { api, captured } = makeApi();
|
|
useDockviewStore.getState().setApi(api);
|
|
|
|
captured.active?.({ id: "file:src/foo.ts" });
|
|
expect(useDockviewStore.getState().activeFilePath).toBe("src/foo.ts");
|
|
|
|
captured.active?.({ id: "chat" });
|
|
expect(useDockviewStore.getState().activeFilePath).toBeNull();
|
|
});
|
|
|
|
it("clears activeFilePath when active-panel-change fires with no panel", () => {
|
|
const { api, captured } = makeApi();
|
|
useDockviewStore.getState().setApi(api);
|
|
|
|
captured.active?.({ id: "diff:file:src/bar.ts" });
|
|
expect(useDockviewStore.getState().activeFilePath).toBe("src/bar.ts");
|
|
|
|
captured.active?.(undefined);
|
|
expect(useDockviewStore.getState().activeFilePath).toBeNull();
|
|
});
|
|
});
|