1import { describe, expect, it } from "vite-plus/test";
2import { scopeThreadRef } from "@t3tools/client-runtime/environment";
3import { ThreadId } from "@t3tools/contracts";
4import { DraftId } from "./composerDraftStore";
5
6import {
7 buildDraftThreadRouteParams,
8 buildThreadRouteParams,
9 isThreadRouteSnapshotAuthoritative,
10 resolveActiveThreadRouteRef,
11 resolveThreadRouteRenderState,
12 resolveThreadRouteRef,
13 resolveThreadRouteTarget,
14} from "./threadRoutes";
15
16describe("threadRoutes", () => {
17 it("does not treat cached thread lists as authoritative for deep links", () => {
18 expect(isThreadRouteSnapshotAuthoritative("empty")).toBe(false);
19 expect(isThreadRouteSnapshotAuthoritative("cached")).toBe(false);
20 expect(isThreadRouteSnapshotAuthoritative("synchronizing")).toBe(false);
21 expect(isThreadRouteSnapshotAuthoritative("live")).toBe(true);
22 });
23
24 it("builds canonical thread route params from a scoped ref", () => {
25 const ref = scopeThreadRef("env-1" as never, ThreadId.make("thread-1"));
26
27 expect(buildThreadRouteParams(ref)).toEqual({
28 environmentId: "env-1",
29 threadId: "thread-1",
30 });
31 });
32
33 it("resolves a scoped ref only when both params are present", () => {
34 expect(
35 resolveThreadRouteRef({
36 environmentId: "env-1",
37 threadId: "thread-1",
38 }),
39 ).toEqual({
40 environmentId: "env-1",
41 threadId: "thread-1",
42 });
43
44 expect(resolveThreadRouteRef({ environmentId: "env-1" })).toBeNull();
45 expect(resolveThreadRouteRef({ threadId: "thread-1" })).toBeNull();
46 });
47
48 it("builds canonical draft route params from a draft id", () => {
49 expect(buildDraftThreadRouteParams(DraftId.make("draft-1"))).toEqual({
50 draftId: "draft-1",
51 });
52 });
53
54 it("resolves draft and server route targets", () => {
55 expect(
56 resolveThreadRouteTarget({
57 environmentId: "env-1",
58 threadId: "thread-1",
59 }),
60 ).toEqual({
61 kind: "server",
62 threadRef: {
63 environmentId: "env-1",
64 threadId: "thread-1",
65 },
66 });
67
68 expect(
69 resolveThreadRouteTarget({
70 draftId: "draft-1",
71 }),
72 ).toEqual({
73 kind: "draft",
74 draftId: "draft-1",
75 });
76 });
77
78 it("resolves the backing thread while a draft route is being promoted", () => {
79 const target = resolveThreadRouteTarget({ draftId: "draft-1" });
80
81 expect(
82 resolveActiveThreadRouteRef(target, {
83 environmentId: "env-1" as never,
84 threadId: ThreadId.make("draft-thread"),
85 promotedTo: scopeThreadRef("env-2" as never, ThreadId.make("server-thread")),
86 }),
87 ).toEqual({
88 environmentId: "env-2",
89 threadId: "server-thread",
90 });
91 });
92
93 it("does not treat a draft's reserved thread ref as an active sidebar thread", () => {
94 const target = resolveThreadRouteTarget({ draftId: "draft-1" });
95
96 expect(
97 resolveActiveThreadRouteRef(target, {
98 environmentId: "env-1" as never,
99 threadId: ThreadId.make("draft-thread"),
100 promotedTo: null,
101 }),
102 ).toBeNull();
103 });
104
105 it("renders authoritative server-thread shells when bootstrap is complete", () => {
106 expect(
107 resolveThreadRouteRenderState({
108 bootstrapComplete: true,
109 serverThreadExists: true,
110 serverThreadDeleted: false,
111 draftThreadExists: false,
112 }),
113 ).toBe("ready");
114 });
115
116 it("renders server threads and local drafts when they are ready", () => {
117 expect(
118 resolveThreadRouteRenderState({
119 bootstrapComplete: true,
120 serverThreadExists: true,
121 serverThreadDeleted: false,
122 draftThreadExists: false,
123 }),
124 ).toBe("ready");
125 expect(
126 resolveThreadRouteRenderState({
127 bootstrapComplete: true,
128 serverThreadExists: false,
129 serverThreadDeleted: false,
130 draftThreadExists: true,
131 }),
132 ).toBe("ready");
133 });
134
135 it.each([
136 { status: "cached", source: "server thread" },
137 { status: "synchronizing", source: "server thread" },
138 { status: "cached", source: "draft" },
139 { status: "synchronizing", source: "draft" },
140 ] as const)(
141 "keeps available $source visible while the shell is $status",
142 ({ status, source }) => {
143 expect(
144 resolveThreadRouteRenderState({
145 bootstrapComplete: isThreadRouteSnapshotAuthoritative(status),
146 serverThreadExists: source === "server thread",
147 serverThreadDeleted: false,
148 draftThreadExists: source === "draft",
149 }),
150 ).toBe("ready");
151 },
152 );
153
154 it("distinguishes bootstrap loading from a missing thread", () => {
155 expect(
156 resolveThreadRouteRenderState({
157 bootstrapComplete: false,
158 serverThreadExists: false,
159 serverThreadDeleted: false,
160 draftThreadExists: false,
161 }),
162 ).toBe("loading");
163 expect(
164 resolveThreadRouteRenderState({
165 bootstrapComplete: true,
166 serverThreadExists: false,
167 serverThreadDeleted: false,
168 draftThreadExists: false,
169 }),
170 ).toBe("missing");
171 });
172
173 it("redirects deleted server threads", () => {
174 expect(
175 resolveThreadRouteRenderState({
176 bootstrapComplete: true,
177 serverThreadExists: true,
178 serverThreadDeleted: true,
179 draftThreadExists: false,
180 }),
181 ).toBe("missing");
182 });
183
184 it("waits for the live shell before redirecting a deleted thread", () => {
185 expect(
186 resolveThreadRouteRenderState({
187 bootstrapComplete: isThreadRouteSnapshotAuthoritative("cached"),
188 serverThreadExists: true,
189 serverThreadDeleted: true,
190 draftThreadExists: false,
191 }),
192 ).toBe("loading");
193 });
194});