614 lines · 20.5 KB · TypeScript
RawDownload
import { EnvironmentId, ProjectId, ThreadId } from "@t3tools/contracts";import { scopeProjectRef } from "@t3tools/client-runtime";import { describe, expect, it } from "vitest"; import {  selectProjectsAcrossEnvironments,  selectSidebarThreadsAcrossEnvironments,  selectSidebarThreadsForProjectRef,  selectSidebarThreadsForProjectRefs,  type AppState,  type EnvironmentState,} from "./store";import {  deriveLogicalProjectKey,  deriveLogicalProjectKeyFromSettings,  derivePhysicalProjectKey,  resolveProjectGroupingMode,} from "./logicalProject";import type { Project, SidebarThreadSummary } from "./types";import { DEFAULT_INTERACTION_MODE } from "./types"; // ── Fixture Identifiers ────────────────────────────────────────────── const primaryEnvId = EnvironmentId.make("env-primary");const remoteEnvId = EnvironmentId.make("env-remote"); const sharedProjectPrimaryId = ProjectId.make("shared-proj-primary");const sharedProjectRemoteId = ProjectId.make("shared-proj-remote");const localOnlyProjectId = ProjectId.make("local-only-proj");const remoteOnlyProjectId = ProjectId.make("remote-only-proj"); const threadP1 = ThreadId.make("thread-shared-primary-1");const threadP2 = ThreadId.make("thread-shared-primary-2");const threadR1 = ThreadId.make("thread-shared-remote-1");const threadL1 = ThreadId.make("thread-local-only-1");const threadRO1 = ThreadId.make("thread-remote-only-1"); const SHARED_REPO_CANONICAL_KEY = "github.com/example/shared-repo";const DEFAULT_GROUPING_SETTINGS = {  sidebarProjectGroupingMode: "repository" as const,  sidebarProjectGroupingOverrides: {},}; // ── Factory Helpers ────────────────────────────────────────────────── function makeProject(  overrides: Partial<Project> & Pick<Project, "id" | "environmentId" | "name">,): Project {  return {    cwd: `/tmp/${overrides.name}`,    defaultModelSelection: { provider: "codex" as const, model: "gpt-5-codex" },    createdAt: "2026-01-01T00:00:00.000Z",    updatedAt: "2026-01-01T00:00:00.000Z",    scripts: [],    ...overrides,  };} function makeSidebarThreadSummary(  overrides: Partial<SidebarThreadSummary> &    Pick<SidebarThreadSummary, "id" | "environmentId" | "projectId" | "title">,): SidebarThreadSummary {  return {    interactionMode: DEFAULT_INTERACTION_MODE,    session: null,    createdAt: "2026-01-01T00:00:00.000Z",    archivedAt: null,    updatedAt: "2026-01-01T00:00:00.000Z",    latestTurn: null,    branch: null,    worktreePath: null,    latestUserMessageAt: null,    hasPendingApprovals: false,    hasPendingUserInput: false,    hasActionableProposedPlan: false,    ...overrides,  };} function makeEmptyEnvironmentState(): EnvironmentState {  return {    projectIds: [],    projectById: {},    threadIds: [],    threadIdsByProjectId: {},    threadShellById: {},    threadSessionById: {},    threadTurnStateById: {},    messageIdsByThreadId: {},    messageByThreadId: {},    activityIdsByThreadId: {},    activityByThreadId: {},    proposedPlanIdsByThreadId: {},    proposedPlanByThreadId: {},    turnDiffIdsByThreadId: {},    turnDiffSummaryByThreadId: {},    sidebarThreadSummaryById: {},    bootstrapComplete: true,  };} // ── Fixture: Two environments, shared + local-only + remote-only projects ── function makeFixtureState(): AppState {  // Shared project: same repo in both envs  const sharedProjectPrimary = makeProject({    id: sharedProjectPrimaryId,    environmentId: primaryEnvId,    name: "shared-repo",    repositoryIdentity: {      canonicalKey: SHARED_REPO_CANONICAL_KEY,      locator: {        source: "git-remote",        remoteName: "origin",        remoteUrl: "https://github.com/example/shared-repo.git",      },    },  });  const sharedProjectRemote = makeProject({    id: sharedProjectRemoteId,    environmentId: remoteEnvId,    name: "shared-repo",    repositoryIdentity: {      canonicalKey: SHARED_REPO_CANONICAL_KEY,      locator: {        source: "git-remote",        remoteName: "origin",        remoteUrl: "https://github.com/example/shared-repo.git",      },    },  });  // Local-only project  const localOnlyProject = makeProject({    id: localOnlyProjectId,    environmentId: primaryEnvId,    name: "local-only",  });  // Remote-only project  const remoteOnlyProject = makeProject({    id: remoteOnlyProjectId,    environmentId: remoteEnvId,    name: "remote-only",  });   // Threads  const summaryP1 = makeSidebarThreadSummary({    id: threadP1,    environmentId: primaryEnvId,    projectId: sharedProjectPrimaryId,    title: "Shared primary thread 1",  });  const summaryP2 = makeSidebarThreadSummary({    id: threadP2,    environmentId: primaryEnvId,    projectId: sharedProjectPrimaryId,    title: "Shared primary thread 2",  });  const summaryR1 = makeSidebarThreadSummary({    id: threadR1,    environmentId: remoteEnvId,    projectId: sharedProjectRemoteId,    title: "Shared remote thread 1",  });  const summaryL1 = makeSidebarThreadSummary({    id: threadL1,    environmentId: primaryEnvId,    projectId: localOnlyProjectId,    title: "Local only thread 1",  });  const summaryRO1 = makeSidebarThreadSummary({    id: threadRO1,    environmentId: remoteEnvId,    projectId: remoteOnlyProjectId,    title: "Remote only thread 1",  });   const primaryEnvState: EnvironmentState = {    ...makeEmptyEnvironmentState(),    projectIds: [sharedProjectPrimaryId, localOnlyProjectId],    projectById: {      [sharedProjectPrimaryId]: sharedProjectPrimary,      [localOnlyProjectId]: localOnlyProject,    },    threadIds: [threadP1, threadP2, threadL1],    threadIdsByProjectId: {      [sharedProjectPrimaryId]: [threadP1, threadP2],      [localOnlyProjectId]: [threadL1],    },    sidebarThreadSummaryById: {      [threadP1]: summaryP1,      [threadP2]: summaryP2,      [threadL1]: summaryL1,    },  };   const remoteEnvState: EnvironmentState = {    ...makeEmptyEnvironmentState(),    projectIds: [sharedProjectRemoteId, remoteOnlyProjectId],    projectById: {      [sharedProjectRemoteId]: sharedProjectRemote,      [remoteOnlyProjectId]: remoteOnlyProject,    },    threadIds: [threadR1, threadRO1],    threadIdsByProjectId: {      [sharedProjectRemoteId]: [threadR1],      [remoteOnlyProjectId]: [threadRO1],    },    sidebarThreadSummaryById: {      [threadR1]: summaryR1,      [threadRO1]: summaryRO1,    },  };   return {    activeEnvironmentId: primaryEnvId,    environmentStateById: {      [primaryEnvId]: primaryEnvState,      [remoteEnvId]: remoteEnvState,    },  };} // ── Tests ──────────────────────────────────────────────────────────── describe("environment grouping", () => {  describe("deriveLogicalProjectKey", () => {    it("uses repositoryIdentity.canonicalKey when present", () => {      const project = makeProject({        id: sharedProjectPrimaryId,        environmentId: primaryEnvId,        name: "shared-repo",        repositoryIdentity: {          canonicalKey: SHARED_REPO_CANONICAL_KEY,          locator: {            source: "git-remote",            remoteName: "origin",            remoteUrl: "https://github.com/example/shared-repo.git",          },        },      });      expect(deriveLogicalProjectKey(project)).toBe(SHARED_REPO_CANONICAL_KEY);    });     it("falls back to scoped project key when no repositoryIdentity", () => {      const project = makeProject({        id: localOnlyProjectId,        environmentId: primaryEnvId,        name: "local-only",      });      expect(deriveLogicalProjectKey(project)).toBe(derivePhysicalProjectKey(project));    });     it("groups projects from different environments that share the same canonical key", () => {      const primary = makeProject({        id: sharedProjectPrimaryId,        environmentId: primaryEnvId,        name: "shared-repo",        repositoryIdentity: {          canonicalKey: SHARED_REPO_CANONICAL_KEY,          locator: {            source: "git-remote",            remoteName: "origin",            remoteUrl: "https://github.com/example/shared-repo.git",          },        },      });      const remote = makeProject({        id: sharedProjectRemoteId,        environmentId: remoteEnvId,        name: "shared-repo",        repositoryIdentity: {          canonicalKey: SHARED_REPO_CANONICAL_KEY,          locator: {            source: "git-remote",            remoteName: "origin",            remoteUrl: "https://github.com/example/shared-repo.git",          },        },      });      expect(deriveLogicalProjectKey(primary)).toBe(deriveLogicalProjectKey(remote));    });     it("groups repo root and nested projects from the same repository by default", () => {      const rootProject = makeProject({        id: sharedProjectPrimaryId,        environmentId: primaryEnvId,        name: "shared-repo",        cwd: "/workspace/repo",        repositoryIdentity: {          canonicalKey: SHARED_REPO_CANONICAL_KEY,          rootPath: "/workspace/repo",          locator: {            source: "git-remote",            remoteName: "origin",            remoteUrl: "https://github.com/example/shared-repo.git",          },        },      });      const nestedProject = makeProject({        id: localOnlyProjectId,        environmentId: primaryEnvId,        name: "web",        cwd: "/workspace/repo/apps/web",        repositoryIdentity: {          canonicalKey: SHARED_REPO_CANONICAL_KEY,          rootPath: "/workspace/repo",          locator: {            source: "git-remote",            remoteName: "origin",            remoteUrl: "https://github.com/example/shared-repo.git",          },        },      });       expect(deriveLogicalProjectKey(rootProject)).toBe(SHARED_REPO_CANONICAL_KEY);      expect(deriveLogicalProjectKey(nestedProject)).toBe(SHARED_REPO_CANONICAL_KEY);    });     it("uses repository path grouping when requested", () => {      const rootProject = makeProject({        id: sharedProjectPrimaryId,        environmentId: primaryEnvId,        name: "shared-repo",        cwd: "/workspace/repo",        repositoryIdentity: {          canonicalKey: SHARED_REPO_CANONICAL_KEY,          rootPath: "/workspace/repo",          locator: {            source: "git-remote",            remoteName: "origin",            remoteUrl: "https://github.com/example/shared-repo.git",          },        },      });      const nestedProject = makeProject({        id: localOnlyProjectId,        environmentId: primaryEnvId,        name: "web",        cwd: "/workspace/repo/apps/web",        repositoryIdentity: {          canonicalKey: SHARED_REPO_CANONICAL_KEY,          rootPath: "/workspace/repo",          locator: {            source: "git-remote",            remoteName: "origin",            remoteUrl: "https://github.com/example/shared-repo.git",          },        },      });       expect(        deriveLogicalProjectKey(rootProject, {          groupingMode: "repository_path",        }),      ).toBe(SHARED_REPO_CANONICAL_KEY);      expect(        deriveLogicalProjectKey(nestedProject, {          groupingMode: "repository_path",        }),      ).toBe(`${SHARED_REPO_CANONICAL_KEY}::apps/web`);    });     it("groups matching nested project paths across environments when repo roots differ", () => {      const primary = makeProject({        id: sharedProjectPrimaryId,        environmentId: primaryEnvId,        name: "web",        cwd: "/workspace/repo/apps/web",        repositoryIdentity: {          canonicalKey: SHARED_REPO_CANONICAL_KEY,          rootPath: "/workspace/repo",          locator: {            source: "git-remote",            remoteName: "origin",            remoteUrl: "https://github.com/example/shared-repo.git",          },        },      });      const remote = makeProject({        id: sharedProjectRemoteId,        environmentId: remoteEnvId,        name: "web",        cwd: "/srv/checkout/apps/web",        repositoryIdentity: {          canonicalKey: SHARED_REPO_CANONICAL_KEY,          rootPath: "/srv/checkout",          locator: {            source: "git-remote",            remoteName: "origin",            remoteUrl: "https://github.com/example/shared-repo.git",          },        },      });       expect(        deriveLogicalProjectKey(primary, {          groupingMode: "repository_path",        }),      ).toBe(`${SHARED_REPO_CANONICAL_KEY}::apps/web`);      expect(        deriveLogicalProjectKey(primary, {          groupingMode: "repository_path",        }),      ).toBe(        deriveLogicalProjectKey(remote, {          groupingMode: "repository_path",        }),      );    });     it("does NOT group projects without shared canonical key", () => {      const local = makeProject({        id: localOnlyProjectId,        environmentId: primaryEnvId,        name: "local-only",      });      const remote = makeProject({        id: remoteOnlyProjectId,        environmentId: remoteEnvId,        name: "remote-only",      });      expect(deriveLogicalProjectKey(local)).not.toBe(deriveLogicalProjectKey(remote));    });     it("uses per-project overrides from settings", () => {      const project = makeProject({        id: sharedProjectPrimaryId,        environmentId: primaryEnvId,        name: "shared-repo",        repositoryIdentity: {          canonicalKey: SHARED_REPO_CANONICAL_KEY,          locator: {            source: "git-remote",            remoteName: "origin",            remoteUrl: "https://github.com/example/shared-repo.git",          },        },      });       expect(resolveProjectGroupingMode(project, DEFAULT_GROUPING_SETTINGS)).toBe("repository");      expect(        deriveLogicalProjectKeyFromSettings(project, {          ...DEFAULT_GROUPING_SETTINGS,          sidebarProjectGroupingOverrides: {            [derivePhysicalProjectKey(project)]: "separate",          },        }),      ).toBe(derivePhysicalProjectKey(project));    });  });   describe("selectProjectsAcrossEnvironments", () => {    it("returns all projects from all environments", () => {      const state = makeFixtureState();      const projects = selectProjectsAcrossEnvironments(state);      expect(projects).toHaveLength(4);      const names = projects.map((p) => p.name).toSorted();      expect(names).toEqual(["local-only", "remote-only", "shared-repo", "shared-repo"]);    });  });   describe("selectSidebarThreadsAcrossEnvironments", () => {    it("returns all sidebar thread summaries from all environments", () => {      const state = makeFixtureState();      const threads = selectSidebarThreadsAcrossEnvironments(state);      expect(threads).toHaveLength(5);      const ids = new Set(threads.map((t) => t.id));      expect(ids).toContain(threadP1);      expect(ids).toContain(threadP2);      expect(ids).toContain(threadR1);      expect(ids).toContain(threadL1);      expect(ids).toContain(threadRO1);    });  });   describe("selectSidebarThreadsForProjectRef", () => {    it("returns threads for a single project ref", () => {      const state = makeFixtureState();      const ref = scopeProjectRef(primaryEnvId, sharedProjectPrimaryId);      const threads = selectSidebarThreadsForProjectRef(state, ref);      expect(threads).toHaveLength(2);      expect(threads.map((t) => t.id)).toEqual([threadP1, threadP2]);    });     it("returns empty array for null ref", () => {      const state = makeFixtureState();      expect(selectSidebarThreadsForProjectRef(state, null)).toEqual([]);    });     it("returns empty array for nonexistent environment", () => {      const state = makeFixtureState();      const ref = scopeProjectRef(EnvironmentId.make("nonexistent"), sharedProjectPrimaryId);      expect(selectSidebarThreadsForProjectRef(state, ref)).toEqual([]);    });  });   describe("selectSidebarThreadsForProjectRefs", () => {    it("returns empty for empty refs", () => {      const state = makeFixtureState();      expect(selectSidebarThreadsForProjectRefs(state, [])).toEqual([]);

Lines 1–500 of 614.