1import { AtomRegistry } from "effect/reactivity";
2import * as AsyncResult from "effect/reactivity/AsyncResult";
3import { afterEach, describe, expect, it, vi } from "vite-plus/test";
4
5import {
6 createSourceHighlightAtomFamily,
7 type SourceHighlightTokens,
8} from "./sourceHighlightingState";
9
10const highlightedTokens: SourceHighlightTokens = [
11 [{ content: "const", color: "#0000ff", fontStyle: null }],
12];
13
14afterEach(() => {
15 vi.restoreAllMocks();
16});
17
18describe("sourceHighlightingState", () => {
19 it("reuses completed highlighting across equivalent route remounts", async () => {
20 const highlight = vi.fn(async () => highlightedTokens);
21 const sourceHighlightAtom = createSourceHighlightAtomFamily({ highlight, idleTtlMs: 1_000 });
22 const registry = AtomRegistry.make({ timeoutResolution: 1 });
23 const input = {
24 path: "src/example.ts",
25 contents: "const value = 1;",
26 theme: "light" as const,
27 };
28 const firstAtom = sourceHighlightAtom(input);
29 const firstUnmount = registry.mount(firstAtom);
30
31 await vi.waitFor(() => {
32 expect(AsyncResult.isSuccess(registry.get(firstAtom))).toBe(true);
33 });
34 firstUnmount();
35
36 const remountedAtom = sourceHighlightAtom({ ...input });
37 const secondUnmount = registry.mount(remountedAtom);
38
39 expect(remountedAtom).toBe(firstAtom);
40 expect(AsyncResult.isSuccess(registry.get(remountedAtom))).toBe(true);
41 expect(highlight).toHaveBeenCalledTimes(1);
42
43 secondUnmount();
44 registry.dispose();
45 });
46
47 it("does not reuse highlighting when the source contents change", async () => {
48 const highlight = vi.fn(async () => highlightedTokens);
49 const sourceHighlightAtom = createSourceHighlightAtomFamily({ highlight });
50 const registry = AtomRegistry.make();
51 const firstAtom = sourceHighlightAtom({
52 path: "src/example.ts",
53 contents: "const value = 1;",
54 theme: "light",
55 });
56 const secondAtom = sourceHighlightAtom({
57 path: "src/example.ts",
58 contents: "const value = 2;",
59 theme: "light",
60 });
61 const firstUnmount = registry.mount(firstAtom);
62 const secondUnmount = registry.mount(secondAtom);
63
64 await vi.waitFor(() => {
65 expect(AsyncResult.isSuccess(registry.get(firstAtom))).toBe(true);
66 expect(AsyncResult.isSuccess(registry.get(secondAtom))).toBe(true);
67 });
68 expect(secondAtom).not.toBe(firstAtom);
69 expect(highlight).toHaveBeenCalledTimes(2);
70
71 firstUnmount();
72 secondUnmount();
73 registry.dispose();
74 });
75
76 it("recomputes highlighting after the idle cache entry expires", async () => {
77 const highlight = vi.fn(async () => highlightedTokens);
78 const sourceHighlightAtom = createSourceHighlightAtomFamily({ highlight, idleTtlMs: 5 });
79 const registry = AtomRegistry.make({ timeoutResolution: 1 });
80 const atom = sourceHighlightAtom({
81 path: "src/example.ts",
82 contents: "const value = 1;",
83 theme: "light",
84 });
85 const firstUnmount = registry.mount(atom);
86
87 await vi.waitFor(() => {
88 expect(AsyncResult.isSuccess(registry.get(atom))).toBe(true);
89 });
90 firstUnmount();
91 await new Promise((resolve) => setTimeout(resolve, 100));
92
93 const secondUnmount = registry.mount(atom);
94 await vi.waitFor(() => {
95 expect(highlight).toHaveBeenCalledTimes(2);
96 expect(AsyncResult.isSuccess(registry.get(atom))).toBe(true);
97 });
98
99 secondUnmount();
100 registry.dispose();
101 });
102
103 it("exposes highlighter errors as a failed async result", async () => {
104 const highlight = vi.fn(async () => {
105 throw new Error("highlight failed");
106 });
107 const sourceHighlightAtom = createSourceHighlightAtomFamily({ highlight });
108 const registry = AtomRegistry.make();
109 const atom = sourceHighlightAtom({
110 path: "src/example.ts",
111 contents: "const value = 1;",
112 theme: "light",
113 });
114 const unmount = registry.mount(atom);
115
116 await vi.waitFor(() => {
117 expect(AsyncResult.isFailure(registry.get(atom))).toBe(true);
118 });
119
120 unmount();
121 registry.dispose();
122 });
123});