Files
paseo/packages/app/src/utils/sidebar-shortcuts.test.ts
T
Mohamed BoudraandGitHub 9d19e90323 Show worktree names in workspace hover cards (#2711)
* fix(app): show worktree names in workspace hover cards

Managed worktree paths exposed Paseo's internal directory layout instead of the useful worktree name. Carry the owned worktree root through the sidebar model while preserving the full workspace path as the copy target.

* fix(workspaces): source owned worktree names from daemon

The daemon owns worktree path semantics, so expose the managed worktree slug and let the sidebar model produce a display-ready label. This keeps path parsing out of the hover-card component while preserving the full path for copying.

* fix(app): preserve worktree labels in replica cache
2026-08-01 05:17:47 +08:00

321 lines
9.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type {
SidebarProjectEntry,
SidebarWorkspaceEntry,
} from "@/hooks/use-sidebar-workspaces-list";
import { buildStatusGroups } from "@/hooks/sidebar-status-view-model";
import {
buildSidebarShortcutModel,
buildStatusSidebarShortcutModel,
getRelativeSidebarShortcutTarget,
} from "./sidebar-shortcuts";
function workspace(input: {
serverId: string;
workspaceId: string;
workspaceDirectory: string;
name: string;
projectKey?: string;
statusBucket?: SidebarWorkspaceEntry["statusBucket"];
statusEnteredAt?: Date | null;
}): SidebarWorkspaceEntry {
return {
workspaceKey: `${input.serverId}:${input.workspaceId}`,
serverId: input.serverId,
workspaceId: input.workspaceId,
projectViewKey: input.projectKey ?? "project-default",
projectName: input.projectKey ?? "Project",
workspaceDirectory: input.workspaceDirectory,
workspaceDirectoryLabel: input.workspaceDirectory,
projectKind: "git",
workspaceKind: "checkout",
name: input.name,
title: null,
currentBranch: null,
statusBucket: input.statusBucket ?? "done",
archivingAt: null,
statusEnteredAt: input.statusEnteredAt ?? null,
diffStat: null,
prHint: null,
archiveHasUncommittedChanges: null,
archiveUnpushedCommitCount: null,
scripts: [],
hasRunningScripts: false,
};
}
function project(projectKey: string, workspaces: SidebarWorkspaceEntry[]): SidebarProjectEntry {
return {
viewKey: projectKey,
projectName: projectKey,
projectKind: "git",
iconWorkingDir: workspaces[0]?.workspaceDirectory ?? "",
hosts: [
{
serverId: workspaces[0]?.serverId ?? "s1",
projectId: projectKey,
iconWorkingDir: workspaces[0]?.workspaceDirectory ?? "",
worktreeSupport: "supported" as const,
},
],
workspaces,
};
}
describe("buildSidebarShortcutModel", () => {
it("builds shortcut targets in visual order and excludes collapsed projects", () => {
const projects = [
project("p1", [
workspace({
serverId: "s1",
workspaceId: "ws-main",
workspaceDirectory: "/repo/main",
name: "main",
}),
workspace({
serverId: "s1",
workspaceId: "ws-feat-a",
workspaceDirectory: "/repo/feat-a",
name: "feat-a",
}),
]),
project("p2", [
workspace({
serverId: "s1",
workspaceId: "ws-repo2-main",
workspaceDirectory: "/repo2/main",
name: "main",
}),
workspace({
serverId: "s1",
workspaceId: "ws-repo2-feat-a",
workspaceDirectory: "/repo2/feat-a",
name: "feat-a",
}),
]),
];
const model = buildSidebarShortcutModel({
projects,
collapsedProjectKeys: new Set<string>(["p2"]),
});
expect(model.shortcutTargets).toEqual([
{ serverId: "s1", workspaceId: "ws-main" },
{ serverId: "s1", workspaceId: "ws-feat-a" },
]);
expect(model.shortcutIndexByWorkspaceKey.get("s1:ws-main")).toBe(1);
expect(model.shortcutIndexByWorkspaceKey.get("s1:ws-feat-a")).toBe(2);
expect(model.shortcutIndexByWorkspaceKey.get("s1:ws-repo2-main")).toBeUndefined();
});
it("limits shortcuts to 9", () => {
const workspaces = Array.from({ length: 20 }, (_, index) =>
workspace({
serverId: "s",
workspaceId: `ws-${index + 1}`,
workspaceDirectory: `/repo/w${index + 1}`,
name: `w${index + 1}`,
}),
);
const projects = [project("p", workspaces)];
const model = buildSidebarShortcutModel({
projects,
collapsedProjectKeys: new Set<string>(),
});
expect(model.shortcutTargets).toHaveLength(9);
expect(model.shortcutTargets[0]).toEqual({ serverId: "s", workspaceId: "ws-1" });
expect(model.shortcutTargets[8]).toEqual({ serverId: "s", workspaceId: "ws-9" });
});
it("excludes a collapsed project's workspaces regardless of project kind", () => {
const gitProject = project("p1", [
workspace({
serverId: "s1",
workspaceId: "ws-main",
workspaceDirectory: "/repo/main",
name: "main",
}),
]);
const directoryProject = project("p2", [
workspace({
serverId: "s1",
workspaceId: "ws-script",
workspaceDirectory: "/scripts",
name: "scripts",
}),
]);
directoryProject.projectKind = "directory";
directoryProject.hosts = directoryProject.hosts.map((host) => ({
...host,
worktreeSupport: "unsupported" as const,
}));
const model = buildSidebarShortcutModel({
projects: [gitProject, directoryProject],
collapsedProjectKeys: new Set<string>(["p1", "p2"]),
});
expect(model.shortcutTargets).toEqual([]);
});
});
describe("buildStatusSidebarShortcutModel", () => {
it("builds shortcut targets in status visual order", () => {
const workspaces = [
workspace({
serverId: "s1",
workspaceId: "done-old",
workspaceDirectory: "/repo/done-old",
name: "done old",
projectKey: "p1",
statusBucket: "done",
statusEnteredAt: new Date("2026-01-01T00:00:00.000Z"),
}),
workspace({
serverId: "s1",
workspaceId: "running-new",
workspaceDirectory: "/repo/running-new",
name: "running new",
projectKey: "p2",
statusBucket: "running",
statusEnteredAt: new Date("2026-03-01T00:00:00.000Z"),
}),
workspace({
serverId: "s1",
workspaceId: "needs-input",
workspaceDirectory: "/repo/needs-input",
name: "needs input",
projectKey: "p1",
statusBucket: "needs_input",
statusEnteredAt: new Date("2026-02-01T00:00:00.000Z"),
}),
workspace({
serverId: "s1",
workspaceId: "running-old",
workspaceDirectory: "/repo/running-old",
name: "running old",
projectKey: "p2",
statusBucket: "running",
statusEnteredAt: new Date("2026-01-15T00:00:00.000Z"),
}),
];
const model = buildStatusSidebarShortcutModel({
groups: buildStatusGroups(
workspaces,
new Map([
["p1", "Project 1"],
["p2", "Project 2"],
]),
),
});
expect(model.shortcutTargets).toEqual([
{ serverId: "s1", workspaceId: "needs-input" },
{ serverId: "s1", workspaceId: "running-new" },
{ serverId: "s1", workspaceId: "running-old" },
{ serverId: "s1", workspaceId: "done-old" },
]);
expect(model.shortcutIndexByWorkspaceKey.get("s1:needs-input")).toBe(1);
expect(model.shortcutIndexByWorkspaceKey.get("s1:running-new")).toBe(2);
expect(model.shortcutIndexByWorkspaceKey.get("s1:running-old")).toBe(3);
expect(model.shortcutIndexByWorkspaceKey.get("s1:done-old")).toBe(4);
});
it("excludes collapsed status groups from shortcut targets", () => {
const workspaces = [
workspace({
serverId: "s1",
workspaceId: "needs-input",
workspaceDirectory: "/repo/needs-input",
name: "needs input",
projectKey: "p1",
statusBucket: "needs_input",
}),
workspace({
serverId: "s1",
workspaceId: "running",
workspaceDirectory: "/repo/running",
name: "running",
projectKey: "p1",
statusBucket: "running",
}),
];
const model = buildStatusSidebarShortcutModel({
groups: buildStatusGroups(workspaces, new Map([["p1", "Project 1"]])),
collapsedStatusGroupKeys: new Set(["needs_input"]),
});
expect(model.shortcutTargets).toEqual([{ serverId: "s1", workspaceId: "running" }]);
expect(model.shortcutIndexByWorkspaceKey.get("s1:needs-input")).toBeUndefined();
expect(model.shortcutIndexByWorkspaceKey.get("s1:running")).toBe(1);
});
});
describe("getRelativeSidebarShortcutTarget", () => {
const targets = [
{ serverId: "s1", workspaceId: "ws-1" },
{ serverId: "s1", workspaceId: "ws-2" },
{ serverId: "s1", workspaceId: "ws-3" },
];
it("moves backward and forward through the numbered shortcut target list", () => {
expect(
getRelativeSidebarShortcutTarget({
targets,
currentTarget: { serverId: "s1", workspaceId: "ws-2" },
delta: -1,
}),
).toEqual({ serverId: "s1", workspaceId: "ws-1" });
expect(
getRelativeSidebarShortcutTarget({
targets,
currentTarget: { serverId: "s1", workspaceId: "ws-2" },
delta: 1,
}),
).toEqual({ serverId: "s1", workspaceId: "ws-3" });
});
it("wraps around the numbered shortcut target list", () => {
expect(
getRelativeSidebarShortcutTarget({
targets,
currentTarget: { serverId: "s1", workspaceId: "ws-1" },
delta: -1,
}),
).toEqual({ serverId: "s1", workspaceId: "ws-3" });
expect(
getRelativeSidebarShortcutTarget({
targets,
currentTarget: { serverId: "s1", workspaceId: "ws-3" },
delta: 1,
}),
).toEqual({ serverId: "s1", workspaceId: "ws-1" });
});
it("falls back to the nearest edge when the current route is not in the numbered list", () => {
expect(
getRelativeSidebarShortcutTarget({
targets,
currentTarget: { serverId: "s1", workspaceId: "ws-hidden" },
delta: 1,
}),
).toEqual({ serverId: "s1", workspaceId: "ws-1" });
expect(
getRelativeSidebarShortcutTarget({
targets,
currentTarget: { serverId: "s1", workspaceId: "ws-hidden" },
delta: -1,
}),
).toEqual({ serverId: "s1", workspaceId: "ws-3" });
});
});