1import { describe, expect, it, vi } from "vite-plus/test";
2
3import type { ReviewRenderableLineRow } from "./reviewModel";
4import {
5 highlightCodeSnippet,
6 highlightReviewSelectedLines,
7 highlightSourceFile,
8} from "./shikiReviewHighlighter";
9
10describe("highlightSourceFile", () => {
11 it("preserves one highlighted token row per source line without trailing newlines", async () => {
12 const lines = [
13 'const items = ["a"];',
14 'expect(items).toEqual(["a"]);',
15 "const next = items.map((item) => item.toUpperCase());",
16 'expect(next).toContain("A");',
17 ];
18
19 const highlighted = await highlightSourceFile({
20 path: "apps/mobile/src/example.ts",
21 contents: lines.join("\n"),
22 theme: "light",
23 });
24
25 expect(highlighted.map((tokens) => tokens.map((token) => token.content).join(""))).toEqual(
26 lines,
27 );
28 });
29
30 it("keeps colors for a block comment that spans highlight batches", async () => {
31 // Long enough that the comment body crosses at least one batch boundary.
32 const body = Array.from({ length: 400 }, (_, index) => `const insideComment${index} = 1;`);
33 const highlighted = await highlightSourceFile({
34 path: "example.ts",
35 contents: ["/*", ...body, "*/", "const after = 2;"].join("\n"),
36 theme: "dark",
37 });
38
39 const commentColors = new Set(
40 highlighted.slice(1, -2).flatMap((line) => line.map((token) => token.color)),
41 );
42 expect(commentColors.size).toBe(1);
43 expect(highlighted.at(-1)?.map((token) => token.color)).not.toEqual(
44 highlighted[1]?.map((token) => token.color),
45 );
46 });
47
48 it("falls back to plain tokens for very long lines", async () => {
49 const longLine = `const value = "${"a".repeat(1_100)}";`;
50
51 const highlighted = await highlightSourceFile({
52 path: "apps/mobile/src/example-long-line.ts",
53 contents: longLine,
54 theme: "light",
55 });
56
57 expect(highlighted).toEqual([
58 [
59 {
60 content: longLine,
61 color: null,
62 fontStyle: null,
63 },
64 ],
65 ]);
66 });
67
68 it("initializes source and snippet highlighting without a warmup", async () => {
69 vi.resetModules();
70 const highlighter = await import("./shikiReviewHighlighter");
71 const source = "const answer: number = 42;";
72
73 const highlighted = await highlighter.highlightSourceFile({
74 path: "example.ts",
75 contents: source,
76 theme: "dark",
77 });
78
79 expect(
80 highlighted
81 .flat()
82 .map((token) => token.content)
83 .join(""),
84 ).toBe(source);
85 expect(highlighted.flat().some((token) => token.color !== null)).toBe(true);
86 expect(
87 await highlighter.highlightCodeSnippet({ code: source, language: "ts", theme: "dark" }),
88 ).toEqual(highlighted);
89 });
90});
91
92describe("highlightReviewSelectedLines", () => {
93 it("adds word-alt diff emphasis for paired deletion and addition lines", async () => {
94 const lines: ReviewRenderableLineRow[] = [
95 {
96 kind: "line",
97 id: "delete-1",
98 change: "delete",
99 oldLineNumber: 1,
100 newLineNumber: null,
101 content: "const before = 1;",
102 additionTokenIndex: null,
103 deletionTokenIndex: 0,
104 comparison: { change: "add", tokenIndex: 0 },
105 },
106 {
107 kind: "line",
108 id: "add-1",
109 change: "add",
110 oldLineNumber: null,
111 newLineNumber: 1,
112 content: "const after = 2;",
113 additionTokenIndex: 0,
114 deletionTokenIndex: null,
115 comparison: { change: "delete", tokenIndex: 0 },
116 },
117 ];
118
119 const highlighted = await highlightReviewSelectedLines({
120 filePath: "apps/mobile/src/example-inline-diff.txt",
121 lines,
122 theme: "light",
123 });
124
125 expect(highlighted["delete-1"]?.some((token) => token.diffHighlight === true)).toBe(true);
126 expect(highlighted["add-1"]?.some((token) => token.diffHighlight === true)).toBe(true);
127 });
128});
129
130describe("highlightCodeSnippet", () => {
131 it.each(["light", "dark"] as const)(
132 "preserves diff-prefixed TSX review snippets in %s mode",
133 async (theme) => {
134 const lines = [
135 "- onClick={() => submitOrder(cart)}",
136 "+ onClick={handleSubmit}",
137 "+ disabled={isSubmitting}",
138 ];
139 const highlighted = await highlightCodeSnippet({
140 code: lines.join("\n"),
141 language: "tsx",
142 theme,
143 });
144 expect(highlighted.map((line) => line.map((token) => token.content).join(""))).toEqual(lines);
145 expect(
146 new Set(
147 highlighted
148 .flat()
149 .map((token) => token.color)
150 .filter(Boolean),
151 ).size,
152 ).toBeGreaterThan(1);
153 },
154 );
155
156 it("resolves language aliases and returns syntax-colored tokens", async () => {
157 const source = "const answer: number = 42;";
158 const highlighted = await highlightCodeSnippet({
159 code: source,
160 language: "ts",
161 theme: "dark",
162 });
163
164 expect(
165 highlighted
166 .flat()
167 .map((token) => token.content)
168 .join(""),
169 ).toBe(source);
170 expect(highlighted.flat().some((token) => token.color !== null)).toBe(true);
171 });
172});