Files
paseo/packages/app/src/projects/workspace-structure.test.ts
T
Mohamed BoudraandGitHub 99eca19a76 Keep selected projects when switching hosts (#2700)
* 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
2026-07-31 15:42:08 +02:00

177 lines
5.5 KiB
TypeScript

import { describe, expect, test } from "vitest";
import type { ProjectDescriptor, WorkspaceDescriptor } from "@/stores/session-store";
import { buildWorkspaceStructureProjects, createProjectViewKey } from "./workspace-structure";
function project(input: {
id: string;
key: string | null;
root: string;
name?: string;
}): ProjectDescriptor {
return {
projectId: input.id,
projectKey: input.key,
projectDisplayName: input.name ?? "acme/app",
projectCustomName: null,
projectRootPath: input.root,
projectKind: "git",
};
}
function workspace(id: string, projectId: string, root: string): WorkspaceDescriptor {
return {
id,
projectId,
projectDisplayName: "acme/app",
projectCustomName: null,
projectRootPath: root,
workspaceDirectory: root,
projectKind: "git",
workspaceKind: "local_checkout",
name: "main",
status: "done",
statusEnteredAt: null,
archivingAt: null,
diffStat: null,
scripts: [],
};
}
describe("buildWorkspaceStructureProjects", () => {
test("groups the same project key across hosts and keeps host-local ids", () => {
const key = "remote:github.com/acme/app";
const result = buildWorkspaceStructureProjects({
sessions: [
{
serverId: "host-a",
projects: [project({ id: "prj_a", key, root: "/a/app" })],
workspaces: [workspace("ws-a", "prj_a", "/a/app")],
},
{
serverId: "host-b",
projects: [project({ id: "prj_b", key, root: "/b/app" })],
workspaces: [workspace("ws-b", "prj_b", "/b/app")],
},
],
});
expect(result).toHaveLength(1);
expect(result[0]).toMatchObject({
viewKey: key,
projectKey: key,
hosts: [
{ serverId: "host-a", projectId: "prj_a" },
{ serverId: "host-b", projectId: "prj_b" },
],
workspaceKeys: ["host-a:ws-a", "host-b:ws-b"],
});
});
test("keeps two clones with the same key on one host separate", () => {
const key = "remote:github.com/acme/app";
const result = buildWorkspaceStructureProjects({
sessions: [
{
serverId: "host-a",
projects: [
project({ id: "prj_one", key, root: "/repos/one" }),
project({ id: "prj_two", key, root: "/repos/two" }),
],
workspaces: [
workspace("ws-one", "prj_one", "/repos/one"),
workspace("ws-two", "prj_two", "/repos/two"),
],
},
],
});
expect(result).toHaveLength(2);
expect(result.map((item) => item.projectKey)).toEqual([key, key]);
expect(result.map((item) => item.hosts[0]?.projectId).sort()).toEqual(["prj_one", "prj_two"]);
expect(result.map((item) => item.workspaceKeys[0]).sort()).toEqual([
"host-a:ws-one",
"host-a:ws-two",
]);
});
test("does not let project order choose which same-host clone groups with another host", () => {
const key = "remote:github.com/acme/app";
const hostAProjects = [
project({ id: "prj_one", key, root: "/repos/one" }),
project({ id: "prj_two", key, root: "/repos/two" }),
];
const build = (projects: ProjectDescriptor[]) =>
buildWorkspaceStructureProjects({
sessions: [
{ serverId: "host-a", projects, workspaces: [] },
{
serverId: "host-b",
projects: [project({ id: "prj_remote", key, root: "/repos/remote" })],
workspaces: [],
},
],
});
const identitiesByProjectId = (projects: ReturnType<typeof build>) => {
const identities: Record<string, string> = {};
for (const group of projects) {
for (const host of group.hosts) identities[host.projectId] = group.viewKey;
}
return identities;
};
const forward = build(hostAProjects);
const reversed = build(hostAProjects.toReversed());
expect(forward).toHaveLength(3);
expect(identitiesByProjectId(forward)).toEqual(identitiesByProjectId(reversed));
expect(new Set(Object.values(identitiesByProjectId(forward))).size).toBe(3);
});
test("keeps projects without persisted keys scoped to their host", () => {
const result = buildWorkspaceStructureProjects({
sessions: [
{
serverId: "host-a",
projects: [project({ id: "/workspace/app", key: null, root: "/workspace/app" })],
workspaces: [],
},
{
serverId: "host-b",
projects: [project({ id: "/workspace/app", key: null, root: "/workspace/app" })],
workspaces: [],
},
],
});
expect(result).toHaveLength(2);
expect(result.map((item) => item.projectKey)).toEqual([null, null]);
expect(new Set(result.map((item) => item.viewKey)).size).toBe(2);
});
test("keeps opaque project keys separate from placement view keys", () => {
const placementShapedKey = createProjectViewKey({
kind: "placement",
serverId: "host-a",
projectId: "prj_b",
});
const result = buildWorkspaceStructureProjects({
sessions: [
{
serverId: "host-a",
projects: [
project({ id: "prj_a", key: placementShapedKey, root: "/repos/a" }),
project({ id: "prj_b", key: null, root: "/repos/b" }),
],
workspaces: [],
},
],
});
expect(result).toHaveLength(2);
expect(new Set(result.map((item) => item.viewKey)).size).toBe(2);
expect(result.find((item) => item.projectKey === placementShapedKey)?.viewKey).toBe(
placementShapedKey,
);
});
});