import { describe, expect, it } from "vite-plus/test"; import { ProjectId, ProviderInstanceId, ThreadId } from "@t3tools/contracts"; import type { OrchestrationShellSnapshot, OrchestrationShellStreamEvent } from "@t3tools/contracts"; import { applyShellStreamEvent } from "./shellReducer.ts"; const baseSnapshot: OrchestrationShellSnapshot = { snapshotSequence: 0, projects: [], threads: [], updatedAt: "2026-04-01T00:00:00.000Z", }; const stubProject = { id: ProjectId.make("project-1"), title: "Test Project", workspaceRoot: "/workspace/test", repositoryIdentity: null, defaultModelSelection: null, scripts: [], createdAt: "2026-04-01T00:00:00.000Z", updatedAt: "2026-04-01T00:00:00.000Z", } as const; const stubThread = { id: ThreadId.make("thread-1"), projectId: ProjectId.make("project-1"), title: "Test Thread", modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" }, runtimeMode: "full-access" as const, interactionMode: "default" as const, branch: null, worktreePath: null, latestTurn: null, createdAt: "2026-04-01T00:00:00.000Z", updatedAt: "2026-04-01T00:00:00.000Z", archivedAt: null, settledOverride: null, settledAt: null, latestUserMessageAt: null, hasPendingApprovals: false, hasPendingUserInput: false, hasActionableProposedPlan: false, session: null, } as const; describe("applyShellStreamEvent", () => { it("ignores stale project upserts without mutating the snapshot", () => { const snapshotWithProject: OrchestrationShellSnapshot = { ...baseSnapshot, snapshotSequence: 4, projects: [stubProject], }; for (const sequence of [3, 4]) { const next = applyShellStreamEvent(snapshotWithProject, { kind: "project-upserted", sequence, project: { ...stubProject, title: "Stale Title" }, }); expect(next).toBe(snapshotWithProject); expect(next.snapshotSequence).toBe(4); expect(next.projects[0]?.title).toBe("Test Project"); } }); describe("project-upserted", () => { it("adds a new project", () => { const event: OrchestrationShellStreamEvent = { kind: "project-upserted", sequence: 1, project: stubProject, }; const next = applyShellStreamEvent(baseSnapshot, event); expect(next.projects).toHaveLength(1); expect(next.projects[0]?.id).toBe("project-1"); expect(next.snapshotSequence).toBe(1); }); it("updates an existing project", () => { const snapshotWithProject: OrchestrationShellSnapshot = { ...baseSnapshot, projects: [stubProject], }; const updatedProject = { ...stubProject, title: "Updated Title" }; const event: OrchestrationShellStreamEvent = { kind: "project-upserted", sequence: 2, project: updatedProject, }; const next = applyShellStreamEvent(snapshotWithProject, event); expect(next.projects).toHaveLength(1); expect(next.projects[0]?.title).toBe("Updated Title"); expect(next.snapshotSequence).toBe(2); }); }); describe("project-removed", () => { it("removes a project by id", () => { const snapshotWithProject: OrchestrationShellSnapshot = { ...baseSnapshot, projects: [stubProject], }; const event: OrchestrationShellStreamEvent = { kind: "project-removed", sequence: 3, projectId: ProjectId.make("project-1"), }; const next = applyShellStreamEvent(snapshotWithProject, event); expect(next.projects).toHaveLength(0); expect(next.snapshotSequence).toBe(3); }); }); describe("thread-upserted", () => { it("adds a new thread", () => { const event: OrchestrationShellStreamEvent = { kind: "thread-upserted", sequence: 4, thread: stubThread, }; const next = applyShellStreamEvent(baseSnapshot, event); expect(next.threads).toHaveLength(1); expect(next.threads[0]?.id).toBe("thread-1"); expect(next.snapshotSequence).toBe(4); }); it("updates an existing thread", () => { const snapshotWithThread: OrchestrationShellSnapshot = { ...baseSnapshot, threads: [stubThread], }; const updatedThread = { ...stubThread, title: "Updated Thread" }; const event: OrchestrationShellStreamEvent = { kind: "thread-upserted", sequence: 5, thread: updatedThread, }; const next = applyShellStreamEvent(snapshotWithThread, event); expect(next.threads).toHaveLength(1); expect(next.threads[0]?.title).toBe("Updated Thread"); }); }); describe("thread-removed", () => { it("removes a thread by id", () => { const snapshotWithThread: OrchestrationShellSnapshot = { ...baseSnapshot, threads: [stubThread], }; const event: OrchestrationShellStreamEvent = { kind: "thread-removed", sequence: 6, threadId: ThreadId.make("thread-1"), }; const next = applyShellStreamEvent(snapshotWithThread, event); expect(next.threads).toHaveLength(0); expect(next.snapshotSequence).toBe(6); }); }); it("returns original snapshot for unrecognized event kinds", () => { const unknownEvent = { kind: "unknown-future-event", sequence: 99 } as any; const next = applyShellStreamEvent(baseSnapshot, unknownEvent); expect(next).toBe(baseSnapshot); }); });