1import type { ArchivedSnapshotEntry } from "@t3tools/client-runtime/state/threads";
2import type { OrchestrationProjectShell, OrchestrationV2ThreadShell } from "@t3tools/contracts";
3import { EnvironmentId, ProjectId, ThreadId } from "@t3tools/contracts";
4import { describe, expect, it } from "vite-plus/test";
5import * as DateTime from "effect/DateTime";
6
7import { buildArchivedThreadGroups } from "./archivedThreadList";
8import { makeRawThreadShell } from "../../test-fixtures";
9
10const environmentId = EnvironmentId.make("environment-1");
11
12function makeProject(
13 input: Partial<OrchestrationProjectShell> & Pick<OrchestrationProjectShell, "id" | "title">,
14): OrchestrationProjectShell {
15 return {
16 workspaceRoot: `/workspaces/${input.id}`,
17 repositoryIdentity: null,
18 defaultModelSelection: null,
19 scripts: [],
20 createdAt: "2026-06-01T00:00:00.000Z",
21 updatedAt: "2026-06-01T00:00:00.000Z",
22 ...input,
23 };
24}
25
26function makeThread(
27 input: Pick<OrchestrationV2ThreadShell, "id" | "projectId" | "title"> & {
28 readonly branch?: string | null;
29 readonly archivedAt?: string | null;
30 },
31): OrchestrationV2ThreadShell {
32 const archivedAt = input.archivedAt === undefined ? "2026-06-02T00:00:00.000Z" : input.archivedAt;
33 return makeRawThreadShell({
34 ...input,
35 archivedAt: archivedAt === null ? null : DateTime.makeUnsafe(archivedAt),
36 });
37}
38
39function makeSnapshot(
40 projects: ReadonlyArray<OrchestrationProjectShell>,
41 threads: ReadonlyArray<OrchestrationV2ThreadShell>,
42 targetEnvironmentId = environmentId,
43): ArchivedSnapshotEntry {
44 return {
45 environmentId: targetEnvironmentId,
46 snapshot: {
47 schemaVersion: 1,
48 snapshotSequence: 1,
49 projects,
50 threads,
51 },
52 };
53}
54
55describe("buildArchivedThreadGroups", () => {
56 it("groups archived threads by project and sorts newest first", () => {
57 const project = makeProject({ id: ProjectId.make("project-1"), title: "T3 Code" });
58 const older = makeThread({
59 id: ThreadId.make("thread-older"),
60 projectId: project.id,
61 title: "Older",
62 });
63 const newer = makeThread({
64 archivedAt: "2026-06-03T00:00:00.000Z",
65 id: ThreadId.make("thread-newer"),
66 projectId: project.id,
67 title: "Newer",
68 });
69
70 const result = buildArchivedThreadGroups({
71 snapshots: [makeSnapshot([project], [older, newer])],
72 environmentLabels: { [environmentId]: "Julius's MacBook Pro" },
73 environmentId: null,
74 searchQuery: "",
75 sortOrder: "newest",
76 });
77
78 expect(result[0]?.threads.map((thread) => thread.id)).toEqual(["thread-newer", "thread-older"]);
79 });
80
81 it("filters by environment and matches project, thread, and branch text", () => {
82 const secondEnvironmentId = EnvironmentId.make("environment-2");
83 const firstProject = makeProject({ id: ProjectId.make("project-1"), title: "T3 Code" });
84 const secondProject = makeProject({ id: ProjectId.make("project-2"), title: "Website" });
85 const firstThread = makeThread({
86 branch: "fix/archive-screen",
87 id: ThreadId.make("thread-1"),
88 projectId: firstProject.id,
89 title: "Build settings route",
90 });
91 const secondThread = makeThread({
92 id: ThreadId.make("thread-2"),
93 projectId: secondProject.id,
94 title: "Unrelated",
95 });
96 const snapshots = [
97 makeSnapshot([firstProject], [firstThread]),
98 makeSnapshot([secondProject], [secondThread], secondEnvironmentId),
99 ];
100
101 const result = buildArchivedThreadGroups({
102 snapshots,
103 environmentLabels: {
104 [environmentId]: "Local",
105 [secondEnvironmentId]: "Remote",
106 },
107 environmentId,
108 searchQuery: "archive-screen",
109 sortOrder: "oldest",
110 });
111
112 expect(result).toHaveLength(1);
113 expect(result[0]?.project.environmentId).toBe(environmentId);
114 expect(result[0]?.threads.map((thread) => thread.id)).toEqual(["thread-1"]);
115 });
116
117 it("ignores non-archived entries returned in a snapshot", () => {
118 const project = makeProject({ id: ProjectId.make("project-1"), title: "T3 Code" });
119 const active = makeThread({
120 archivedAt: null,
121 id: ThreadId.make("thread-active"),
122 projectId: project.id,
123 title: "Active",
124 });
125
126 const result = buildArchivedThreadGroups({
127 snapshots: [makeSnapshot([project], [active])],
128 environmentLabels: {},
129 environmentId: null,
130 searchQuery: "",
131 sortOrder: "newest",
132 });
133
134 expect(result).toEqual([]);
135 });
136});