Files
paseo/packages/app/src/utils/sidebar-project-row-model.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

281 lines
8.5 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type {
SidebarProjectEntry,
SidebarWorkspaceEntry,
} from "@/hooks/use-sidebar-workspaces-list";
import {
buildSidebarProjectRowModel,
resolveSidebarProjectIconTarget,
resolveSidebarProjectIconTargets,
resolveSidebarProjectLocalPath,
} from "./sidebar-project-row-model";
function workspace(overrides: Partial<SidebarWorkspaceEntry> = {}): SidebarWorkspaceEntry {
return {
workspaceKey: "srv:ws-root",
serverId: "srv",
workspaceId: "ws-root",
projectViewKey: "project-1",
projectName: "paseo",
workspaceDirectory: "/repo",
workspaceDirectoryLabel: "/repo",
projectKind: "git",
workspaceKind: "checkout",
name: "paseo",
title: null,
currentBranch: null,
statusBucket: "done",
diffStat: null,
prHint: null,
archiveHasUncommittedChanges: null,
archiveUnpushedCommitCount: null,
scripts: [],
hasRunningScripts: false,
statusEnteredAt: null,
...overrides,
archivingAt: overrides.archivingAt ?? null,
};
}
type ProjectOverrides = Omit<Partial<SidebarProjectEntry>, "hosts"> & {
hosts?: Array<Omit<SidebarProjectEntry["hosts"][number], "projectId"> & { projectId?: string }>;
};
function project(overrides: ProjectOverrides = {}): SidebarProjectEntry {
const projectKind = overrides.projectKind ?? "git";
const hosts = Array.from(
overrides.hosts ?? [
{
serverId: "srv",
iconWorkingDir: "/repo",
worktreeSupport: projectKind === "git" ? "supported" : "unsupported",
},
],
(host) => Object.assign({}, host, { projectId: host.projectId ?? `project-${host.serverId}` }),
);
return {
viewKey: "project-1",
projectName: "paseo",
projectKind,
iconWorkingDir: "/repo",
workspaces: [workspace()],
...overrides,
hosts,
};
}
describe("buildSidebarProjectRowModel", () => {
it("renders a non-git single-workspace project as an expandable section", () => {
const result = buildSidebarProjectRowModel({
project: project({
projectKind: "directory",
workspaces: [workspace({ workspaceId: "ws-non-git", workspaceKind: "checkout" })],
}),
collapsed: false,
});
expect(result).toEqual({
kind: "project_section",
chevron: "collapse",
trailingAction: { kind: "none" },
});
});
it("renders a single-workspace git project as an expandable section with the new workspace action", () => {
const result = buildSidebarProjectRowModel({
project: project({
projectKind: "git",
workspaces: [workspace({ workspaceId: "ws-main", workspaceKind: "checkout" })],
}),
collapsed: true,
});
expect(result).toEqual({
kind: "project_section",
chevron: "expand",
trailingAction: {
kind: "new_workspace",
target: { serverId: "srv", projectId: "project-srv", iconWorkingDir: "/repo" },
},
});
});
it("shows the new workspace action for a non-git project when the host supports workspace multiplicity", () => {
const result = buildSidebarProjectRowModel({
project: project({ projectKind: "directory", workspaces: [] }),
collapsed: false,
supportsMultiplicityByServerId: new Map([["srv", true]]),
});
expect(result.trailingAction).toEqual({
kind: "new_workspace",
target: { serverId: "srv", projectId: "project-srv", iconWorkingDir: "/repo" },
});
});
it("hides the new workspace action for a non-git project when the host lacks workspace multiplicity", () => {
const result = buildSidebarProjectRowModel({
project: project({ projectKind: "directory", workspaces: [] }),
collapsed: false,
supportsMultiplicityByServerId: new Map([["srv", false]]),
});
expect(result.trailingAction).toEqual({ kind: "none" });
});
it("still shows the new workspace action for a git project regardless of multiplicity", () => {
const result = buildSidebarProjectRowModel({
project: project({ projectKind: "git" }),
collapsed: false,
supportsMultiplicityByServerId: new Map([["srv", false]]),
});
expect(result.trailingAction).toEqual({
kind: "new_workspace",
target: { serverId: "srv", projectId: "project-srv", iconWorkingDir: "/repo" },
});
});
it("targets the project host, not route state, for new workspace actions", () => {
const result = buildSidebarProjectRowModel({
project: project({
hosts: [
{
serverId: "host-a",
iconWorkingDir: "/repo/a",
worktreeSupport: "unsupported" as const,
},
{ serverId: "host-b", iconWorkingDir: "/repo/b", worktreeSupport: "supported" as const },
],
}),
collapsed: false,
});
expect(result).toMatchObject({
trailingAction: {
kind: "new_workspace",
target: { serverId: "host-b", iconWorkingDir: "/repo/b" },
},
});
});
it("targets the first multiplicity-capable host for a non-git project", () => {
const result = buildSidebarProjectRowModel({
project: project({
projectKind: "directory",
hosts: [
{
serverId: "host-a",
iconWorkingDir: "/repo/a",
worktreeSupport: "unsupported" as const,
},
{
serverId: "host-b",
iconWorkingDir: "/repo/b",
worktreeSupport: "unsupported" as const,
},
],
}),
collapsed: false,
supportsMultiplicityByServerId: new Map([["host-b", true]]),
});
expect(result).toMatchObject({
trailingAction: {
kind: "new_workspace",
target: { serverId: "host-b", iconWorkingDir: "/repo/b" },
},
});
});
it("renders a multi-workspace git project as an expandable section with a new workspace action", () => {
const result = buildSidebarProjectRowModel({
project: project({
projectKind: "git",
workspaces: [
workspace({ workspaceId: "ws-main", workspaceKind: "checkout" }),
workspace({ workspaceId: "ws-feature", workspaceKind: "worktree" }),
],
}),
collapsed: true,
});
expect(result).toEqual({
kind: "project_section",
chevron: "expand",
trailingAction: {
kind: "new_workspace",
target: { serverId: "srv", projectId: "project-srv", iconWorkingDir: "/repo" },
},
});
});
it("resolves project icons from the project host, not the focused host", () => {
const iconTarget = resolveSidebarProjectIconTarget(
project({
hosts: [
{ serverId: "host-b", iconWorkingDir: "/repo/b", worktreeSupport: "supported" as const },
{ serverId: "host-a", iconWorkingDir: "/repo/a", worktreeSupport: "supported" as const },
],
}),
);
expect(iconTarget).toEqual({
serverId: "host-b",
projectId: "project-host-b",
iconWorkingDir: "/repo/b",
});
});
it("keys project icon results by the rendered project view", () => {
const [iconTarget] = resolveSidebarProjectIconTargets([
project({
viewKey: '["placement","host-b","project-b"]',
hosts: [
{ serverId: "host-b", iconWorkingDir: "/repo/b", worktreeSupport: "supported" as const },
],
}),
]);
expect(iconTarget).toEqual({
projectViewKey: '["placement","host-b","project-b"]',
serverId: "host-b",
projectId: "project-host-b",
iconWorkingDir: "/repo/b",
});
});
it("resolves desktop file actions from the local project placement", () => {
const groupedProject = project({
iconWorkingDir: "/remote/repo",
hosts: [
{
serverId: "remote",
iconWorkingDir: "/remote/repo",
worktreeSupport: "supported" as const,
},
{ serverId: "local", iconWorkingDir: "/local/repo", worktreeSupport: "supported" as const },
],
});
expect(resolveSidebarProjectLocalPath(groupedProject, "local")).toBe("/local/repo");
expect(resolveSidebarProjectLocalPath(groupedProject, "missing")).toBe("");
});
it("renders an empty project as an expandable section", () => {
const result = buildSidebarProjectRowModel({
project: project({ projectKind: "git", workspaces: [] }),
collapsed: false,
});
expect(result).toEqual({
kind: "project_section",
chevron: "collapse",
trailingAction: {
kind: "new_workspace",
target: { serverId: "srv", projectId: "project-srv", iconWorkingDir: "/repo" },
},
});
});
});