1import { EnvironmentId, MessageId, ThreadId, type AssistantCitation } from "@t3tools/contracts";
2import {
3 createMemoryHistory,
4 createRootRoute,
5 createRoute,
6 createRouter,
7} from "@tanstack/react-router";
8import { describe, expect, it } from "vite-plus/test";
9
10import {
11 assistantCitationFromLocation,
12 assistantCitationNavigation,
13} from "./assistantCitationNavigation";
14
15const citation: AssistantCitation = {
16 version: 1,
17 environmentId: EnvironmentId.make("environment-one"),
18 threadId: ThreadId.make("thread-one"),
19 messageId: MessageId.make("assistant-one"),
20 text: "first line\n\tsecond line (with a #hash & spaces) 🚀",
21 start: 12,
22 end: 59,
23 prefix: "Before:\n",
24 suffix: "\nAfter.",
25};
26
27function createCitationRouter(initialEntry = "/environment-one/thread-one") {
28 const root = createRootRoute();
29 const thread = createRoute({ getParentRoute: () => root, path: "/$environmentId/$threadId" });
30 return createRouter({
31 routeTree: root.addChildren([thread]),
32 history: createMemoryHistory({ initialEntries: [initialEntry] }),
33 });
34}
35
36describe("assistant citation navigation", () => {
37 it("preserves encoded quote whitespace through router navigation and reload", async () => {
38 const router = createCitationRouter();
39 await router.navigate(assistantCitationNavigation(citation));
40
41 expect(assistantCitationFromLocation(router.state.location.href)).toEqual(citation);
42 let href = router.history.location.href;
43 for (let reload = 0; reload < 3; reload++) {
44 const reloaded = createCitationRouter(href);
45 await reloaded.load();
46 // Route normalization rebuilds the current location from its decoded hash.
47 await reloaded.navigate({
48 to: "/$environmentId/$threadId",
49 params: { environmentId: citation.environmentId, threadId: citation.threadId },
50 hash: true,
51 replace: true,
52 });
53 href = reloaded.history.location.href;
54 expect(assistantCitationFromLocation(href)).toEqual(citation);
55 }
56 });
57
58 it("reactivates an identical citation instead of deduplicating its navigation", async () => {
59 const router = createCitationRouter();
60 await router.navigate(assistantCitationNavigation(citation));
61 const previous = router.state.location;
62 await router.navigate(assistantCitationNavigation(citation));
63
64 expect(router.state.location.href).toBe(previous.href);
65 expect(router.state.location.state.assistantCitationActivation).not.toBe(
66 previous.state.assistantCitationActivation,
67 );
68 expect(router.history.length).toBe(3);
69 expect(assistantCitationFromLocation(router.state.location.href)).toEqual(citation);
70 });
71
72 it("ignores non-citation fragments and missing fragments", () => {
73 expect(assistantCitationFromLocation("/environment-one/thread-one")).toBeNull();
74 expect(
75 assistantCitationFromLocation("/environment-one/thread-one#ordinary-heading"),
76 ).toBeNull();
77 expect(assistantCitationFromLocation("/a/b#assistant-citation=%invalid")).toBeNull();
78 expect(
79 assistantCitationFromLocation(`/a/b#assistant-citation=${"a".repeat(140_000)}`),
80 ).toBeNull();
81 });
82});