| 1 | import { EnvironmentId, MessageId, ThreadId, type AssistantCitation } from "@t3tools/contracts"; |
| 2 | import { |
| 3 | createMemoryHistory, |
| 4 | createRootRoute, |
| 5 | createRoute, |
| 6 | createRouter, |
| 7 | } from "@tanstack/react-router"; |
| 8 | import { describe, expect, it } from "vite-plus/test"; |
| 9 | |
| 10 | import { |
| 11 | assistantCitationFromLocation, |
| 12 | assistantCitationNavigation, |
| 13 | } from "./assistantCitationNavigation"; |
| 14 | |
| 15 | const 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 | |
| 27 | function 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 | |
| 36 | describe("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 | }); |