* fix(app): preserve project selection across hosts Rendered project identity and persisted equivalence were conflated after duplicate-project handling. Keep them separate so host changes resolve selectable equivalents deterministically without resetting legacy sidebar identity. * fix(app): restore selected clone after host round trip Keep manual origin and current destination placements separately so switching back restores the chosen checkout while archive gaps preserve the active host selection. * fix(app): key status icons by project view * fix(app): preserve origin during archive gaps
117 lines
3.7 KiB
TypeScript
117 lines
3.7 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type {
|
|
SidebarProjectEntry,
|
|
SidebarWorkspaceEntry,
|
|
SidebarWorkspacePlacement,
|
|
} from "@/hooks/use-sidebar-workspaces-list";
|
|
import { buildSidebarProjection } from "./sidebar-projection";
|
|
|
|
function makeWorkspace(id: string, statusBucket: SidebarWorkspaceEntry["statusBucket"] = "done") {
|
|
const placement: SidebarWorkspacePlacement = {
|
|
workspaceKey: `srv:${id}`,
|
|
serverId: "srv",
|
|
workspaceId: id,
|
|
projectViewKey: "project",
|
|
projectName: "Project",
|
|
projectKind: "git",
|
|
workspaceKind: "worktree",
|
|
name: id,
|
|
};
|
|
const entry: SidebarWorkspaceEntry = {
|
|
...placement,
|
|
title: null,
|
|
currentBranch: null,
|
|
statusBucket,
|
|
statusEnteredAt: null,
|
|
archivingAt: null,
|
|
diffStat: null,
|
|
prHint: null,
|
|
archiveHasUncommittedChanges: null,
|
|
archiveUnpushedCommitCount: null,
|
|
scripts: [],
|
|
hasRunningScripts: false,
|
|
};
|
|
return { placement, entry };
|
|
}
|
|
|
|
function makeProject(workspaces: SidebarWorkspacePlacement[]): SidebarProjectEntry {
|
|
return {
|
|
viewKey: "project",
|
|
projectName: "Project",
|
|
projectKind: "git",
|
|
iconWorkingDir: "/repo",
|
|
hosts: [
|
|
{
|
|
serverId: "srv",
|
|
projectId: "project",
|
|
iconWorkingDir: "/repo",
|
|
canCreateWorktree: true,
|
|
},
|
|
],
|
|
workspaces,
|
|
};
|
|
}
|
|
|
|
function projectionInput(options?: {
|
|
groupMode?: "project" | "status";
|
|
pinnedCollapsed?: boolean;
|
|
}) {
|
|
const pinned = makeWorkspace("pinned", "running");
|
|
const unpinned = makeWorkspace("unpinned", "needs_input");
|
|
return {
|
|
projects: [makeProject([pinned.placement, unpinned.placement])],
|
|
pinnedKeys: {
|
|
pinnedWorkspaceKeys: [pinned.placement.workspaceKey],
|
|
pinnedAtByKey: { [pinned.placement.workspaceKey]: "2026-07-12T12:00:00.000Z" },
|
|
},
|
|
workspaceEntriesByKey: new Map([
|
|
[pinned.entry.workspaceKey, pinned.entry],
|
|
[unpinned.entry.workspaceKey, unpinned.entry],
|
|
]),
|
|
projectNamesByViewKey: new Map([["project", "Project"]]),
|
|
groupMode: options?.groupMode ?? ("project" as const),
|
|
pinnedCollapsed: options?.pinnedCollapsed ?? false,
|
|
collapsedProjectKeys: new Set<string>(),
|
|
collapsedStatusGroupKeys: new Set<string>(),
|
|
};
|
|
}
|
|
|
|
describe("buildSidebarProjection", () => {
|
|
it("uses one pin-aware projection for project rows and shortcut order", () => {
|
|
const projection = buildSidebarProjection(projectionInput());
|
|
|
|
expect(projection.pinnedGroups.pinnedChats.map((entry) => entry.workspaceId)).toEqual([
|
|
"pinned",
|
|
]);
|
|
const remainingProject = projection.pinnedGroups.unpinnedProjects[0];
|
|
expect(remainingProject?.workspaces.map((entry) => entry.workspaceId)).toEqual(["unpinned"]);
|
|
expect(projection.shortcutModel.shortcutTargets).toEqual([
|
|
{ serverId: "srv", workspaceId: "pinned" },
|
|
{ serverId: "srv", workspaceId: "unpinned" },
|
|
]);
|
|
});
|
|
|
|
it("keeps pinned chats above status groups and removes them from those groups", () => {
|
|
const projection = buildSidebarProjection(projectionInput({ groupMode: "status" }));
|
|
|
|
expect(projection.statusGroups.map((group) => group.bucket)).toEqual(["needs_input"]);
|
|
expect(projection.statusGroups[0]?.rows.map((entry) => entry.workspaceId)).toEqual([
|
|
"unpinned",
|
|
]);
|
|
expect(projection.shortcutModel.shortcutTargets).toEqual([
|
|
{ serverId: "srv", workspaceId: "pinned" },
|
|
{ serverId: "srv", workspaceId: "unpinned" },
|
|
]);
|
|
});
|
|
|
|
it("does not number pinned chats while the pinned section is collapsed", () => {
|
|
const projection = buildSidebarProjection(
|
|
projectionInput({ groupMode: "status", pinnedCollapsed: true }),
|
|
);
|
|
|
|
expect(projection.shortcutModel.shortcutTargets).toEqual([
|
|
{ serverId: "srv", workspaceId: "unpinned" },
|
|
]);
|
|
});
|
|
});
|