import { afterEach, describe, expect, it, vi } from "vite-plus/test"; import type { NativeReviewDiffRow } from "./nativeReviewDiffSurface";import type { NativeReviewDiffFile } from "./nativeReviewDiffTypes";import { highlightNativeReviewDiffVisibleRows } from "./nativeReviewDiffHighlighter"; const tokenization = vi.hoisted(() => ({ calls: [] as string[], afterCall: undefined as (() => void) | undefined,})); vi.mock("@shikijs/core", async (importOriginal) => { const original = await importOriginal<typeof import("@shikijs/core")>(); return { ...original, createHighlighterCore: async (...args: Parameters<typeof original.createHighlighterCore>) => { const highlighter = await original.createHighlighterCore(...args); return { ...highlighter, codeToTokensBase: (...input: Parameters<typeof highlighter.codeToTokensBase>) => { tokenization.calls.push(input[0]); const result = highlighter.codeToTokensBase(...input); tokenization.afterCall?.(); return result; }, }; }, };}); // Exercise the native entry path without requiring an iOS or Android runtime.vi.mock("react-native-shiki-engine", async () => { const { createJavaScriptRegexEngine } = await import("@shikijs/engine-javascript"); return { isNativeEngineAvailable: () => true, createNativeEngine: createJavaScriptRegexEngine };}); afterEach(() => { tokenization.calls = []; tokenization.afterCall = undefined;}); const TYPESCRIPT_FILE: NativeReviewDiffFile = { id: "file-1", path: "example.ts", language: "typescript", additions: 0, deletions: 0,}; function makeLine( input: Pick<NativeReviewDiffRow, "id" | "content" | "change" | "oldLineNumber" | "newLineNumber">,): NativeReviewDiffRow { return { kind: "line", fileId: TYPESCRIPT_FILE.id, ...input, };} function makeHunk(id: string): NativeReviewDiffRow { return { kind: "hunk", id, fileId: TYPESCRIPT_FILE.id, text: "@@", };} function highlight( rows: ReadonlyArray<NativeReviewDiffRow>, alreadyHighlightedRowIds?: ReadonlySet<string>,) { return highlightNativeReviewDiffVisibleRows({ rows, files: [TYPESCRIPT_FILE], scheme: "dark", engine: "javascript", firstRowIndex: 0, lastRowIndex: rows.length - 1, overscanRows: 0, maxRows: 100, alreadyHighlightedRowIds, });} describe("highlightNativeReviewDiffVisibleRows", () => { it("does not carry grammar state across hunk boundaries", async () => { const exportRow = makeLine({ id: "export-row", content: "export async function run() {}", change: "add", oldLineNumber: null, newLineNumber: 100, }); const rows = [ makeHunk("hunk-1"), makeLine({ id: "import-open", content: "import {", change: "context", oldLineNumber: 1, newLineNumber: 1, }), makeLine({ id: "import-entry", content: " Model,", change: "context", oldLineNumber: 2, newLineNumber: 2, }), makeHunk("hunk-2"), exportRow, ]; const [highlighted, standalone] = await Promise.all([ highlight(rows), highlight([makeHunk("standalone-hunk"), exportRow]), ]); expect(highlighted.tokensByRowId[exportRow.id]).toEqual(standalone.tokensByRowId[exportRow.id]); }); it("keeps grammar state across inline comment rows", async () => { const openingRow = makeLine({ id: "template-open", content: "const message = `open", change: "add", oldLineNumber: null, newLineNumber: 1, }); const closingRow = makeLine({ id: "template-close", content: "closed`;", change: "add", oldLineNumber: null, newLineNumber: 2, }); const trailingRow = makeLine({ id: "trailing-row", content: "export const answer = 42;", change: "add", oldLineNumber: null, newLineNumber: 3, }); const commentRow: NativeReviewDiffRow = { kind: "comment", id: "comment-1", fileId: TYPESCRIPT_FILE.id, commentText: "Review note", }; const [withComment, contiguous] = await Promise.all([ highlight([openingRow, commentRow, closingRow, trailingRow]), highlight([openingRow, closingRow, trailingRow]), ]); expect(withComment.tokensByRowId).toEqual(contiguous.tokensByRowId); }); it("does not join unhighlighted rows across cached gaps", async () => { const trailingRow = makeLine({ id: "trailing-row", content: "export const answer = 42;", change: "add", oldLineNumber: null, newLineNumber: 3, }); const rows = [ makeLine({ id: "template-open", content: "const message = `open", change: "add", oldLineNumber: null, newLineNumber: 1, }), makeLine({ id: "template-close", content: "closed`;", change: "add", oldLineNumber: null, newLineNumber: 2, }), trailingRow, ]; const [highlighted, standalone] = await Promise.all([ highlight(rows, new Set(["template-close"])), highlight([trailingRow]), ]); expect(highlighted.tokensByRowId[trailingRow.id]).toEqual( standalone.tokensByRowId[trailingRow.id], ); }); it("keeps deletion grammar state out of addition rows", async () => { const additionRow = makeLine({ id: "addition-row", content: "export const answer = 42;", change: "add", oldLineNumber: null, newLineNumber: 1, }); const rows = [ makeLine({ id: "deletion-row", content: "const removed = `open", change: "delete", oldLineNumber: 1, newLineNumber: null, }), additionRow, ]; const [highlighted, standalone] = await Promise.all([ highlight(rows), highlight([additionRow]), ]); expect(highlighted.tokensByRowId[additionRow.id]).toEqual( standalone.tokensByRowId[additionRow.id], ); });}); describe.each(["native", "javascript"] as const)("%s highlighting budgets", (engine) => { const highlightRows = (rows: ReadonlyArray<NativeReviewDiffRow>, signal?: AbortSignal) => highlightNativeReviewDiffVisibleRows({ rows, files: [TYPESCRIPT_FILE], scheme: "dark", engine, firstRowIndex: 0, lastRowIndex: rows.length - 1, overscanRows: 0, signal, }); const line = (id: number, content: string) => makeLine({ id: `line-${id}`, content, change: "add", oldLineNumber: null, newLineNumber: id, }); it("still highlights a line at the length limit", async () => { const content = `// ${"x".repeat(997)}`; const result = await highlightRows([line(1, content)]); expect(tokenization.calls).toEqual([content]); expect(result.tokensByRowId["line-1"]?.some((token) => token.color !== null)).toBe(true); }); it("keeps only the long line plain and resumes highlighting after it", async () => { const longLine = `${"x".repeat(1_001)} /*`; const rows = [ line(1, "export const before = 1;"), line(2, longLine), { kind: "comment", id: "note", commentText: "Check this", fileId: TYPESCRIPT_FILE.id }, line(3, "export const inside = 'x';"), makeHunk("next-hunk"), line(100, "export const after = 2;"), ] satisfies ReadonlyArray<NativeReviewDiffRow>; const result = await highlightRows(rows); expect(result.engine).toBe(engine); expect(Object.keys(result.tokensByRowId)).toEqual(["line-1", "line-2", "line-3", "line-100"]); expect(result.tokensByRowId["line-2"]).toEqual([ { content: longLine, color: null, fontStyle: null }, ]); for (const id of ["line-1", "line-3", "line-100"]) { expect(result.tokensByRowId[id]?.some((token) => token.color !== null)).toBe(true); } expect(tokenization.calls.some((code) => code.includes(longLine))).toBe(false); }); it("highlights the rows after a long line the same regardless of the first window", async () => { const rows = [ line(1, "export const before = 1;"), line(2, `const data = "${"x".repeat(1_050)}";`), line(3, "export const inside = 'x';"), line(4, "export const after = 2;"), ]; const spanning = await highlightRows(rows); const afterLongLine = await highlightNativeReviewDiffVisibleRows({ rows, files: [TYPESCRIPT_FILE], scheme: "dark", engine, firstRowIndex: 2, lastRowIndex: 3, overscanRows: 0, }); for (const id of ["line-3", "line-4"]) { expect(spanning.tokensByRowId[id]?.some((token) => token.color !== null)).toBe(true); expect(afterLongLine.tokensByRowId[id]).toEqual(spanning.tokensByRowId[id]); } }); it("preserves multiline grammar and row mapping across character-limited batches", async () => { const opening = line(1, "const message = `open"); const body = Array.from({ length: 40 }, (_, index) => line(index + 2, "inside ".repeat(45))); const closing = line(42, "closed`; "); const trailing = line(43, "export const after = 2;"); const result = await highlightRows([opening, ...body, closing, trailing]); const calls = [...tokenization.calls]; const expected = await highlightRows([ opening, { ...closing, newLineNumber: 2 }, { ...trailing, newLineNumber: 3 }, ]); expect(calls.length).toBeGreaterThan(1); expect(calls.every((code) => code.length <= 8_000)).toBe(true); expect(result.rowCount).toBe(43); for (const row of [opening, ...body, closing, trailing]) { expect(result.tokensByRowId[row.id]?.map((token) => token.content).join("")).toBe( row.content, ); } expect(result.tokensByRowId[closing.id]).toEqual(expected.tokensByRowId[closing.id]); expect(result.tokensByRowId[trailing.id]).toEqual(expected.tokensByRowId[trailing.id]); }); it("stops before later batches and publishes no partial result after cancellation", async () => { const controller = new AbortController(); tokenization.afterCall = () => controller.abort(); const rows = Array.from({ length: 30 }, (_, index) => line(index + 1, `// ${"x".repeat(400)}`)); const result = await highlightRows(rows, controller.signal); expect(tokenization.calls).toHaveLength(1); expect(result.rowCount).toBe(0); expect(result.tokensByRowId).toEqual({}); });});
import { afterEach, describe, expect, it, vi } from "vite-plus/test";
import type { NativeReviewDiffRow } from "./nativeReviewDiffSurface";
import type { NativeReviewDiffFile } from "./nativeReviewDiffTypes";
import { highlightNativeReviewDiffVisibleRows } from "./nativeReviewDiffHighlighter";
const tokenization = vi.hoisted(() => ({
calls: [] as string[],
afterCall: undefined as (() => void) | undefined,
}));
vi.mock("@shikijs/core", async (importOriginal) => {
const original = await importOriginal<typeof import("@shikijs/core")>();
return {
...original,
createHighlighterCore: async (...args: Parameters<typeof original.createHighlighterCore>) => {
const highlighter = await original.createHighlighterCore(...args);
return {
...highlighter,
codeToTokensBase: (...input: Parameters<typeof highlighter.codeToTokensBase>) => {
tokenization.calls.push(input[0]);
const result = highlighter.codeToTokensBase(...input);
tokenization.afterCall?.();
return result;
},
};
},
};
});
// Exercise the native entry path without requiring an iOS or Android runtime.
vi.mock("react-native-shiki-engine", async () => {
const { createJavaScriptRegexEngine } = await import("@shikijs/engine-javascript");
return { isNativeEngineAvailable: () => true, createNativeEngine: createJavaScriptRegexEngine };
});
afterEach(() => {
tokenization.calls = [];
tokenization.afterCall = undefined;
});
const TYPESCRIPT_FILE: NativeReviewDiffFile = {
id: "file-1",
path: "example.ts",
language: "typescript",
additions: 0,
deletions: 0,
};
function makeLine(
input: Pick<NativeReviewDiffRow, "id" | "content" | "change" | "oldLineNumber" | "newLineNumber">,
): NativeReviewDiffRow {
return {
kind: "line",
fileId: TYPESCRIPT_FILE.id,
...input,
};
}
function makeHunk(id: string): NativeReviewDiffRow {
return {
kind: "hunk",
id,
fileId: TYPESCRIPT_FILE.id,
text: "@@",
};
}
function highlight(
rows: ReadonlyArray<NativeReviewDiffRow>,
alreadyHighlightedRowIds?: ReadonlySet<string>,
) {
return highlightNativeReviewDiffVisibleRows({
rows,
files: [TYPESCRIPT_FILE],
scheme: "dark",
engine: "javascript",
firstRowIndex: 0,
lastRowIndex: rows.length - 1,
overscanRows: 0,
maxRows: 100,
alreadyHighlightedRowIds,
});
}
describe("highlightNativeReviewDiffVisibleRows", () => {
it("does not carry grammar state across hunk boundaries", async () => {
const exportRow = makeLine({
id: "export-row",
content: "export async function run() {}",
change: "add",
oldLineNumber: null,
newLineNumber: 100,
});
const rows = [
makeHunk("hunk-1"),
makeLine({
id: "import-open",
content: "import {",
change: "context",
oldLineNumber: 1,
newLineNumber: 1,
}),
makeLine({
id: "import-entry",
content: " Model,",
change: "context",
oldLineNumber: 2,
newLineNumber: 2,
}),
makeHunk("hunk-2"),
exportRow,
];
const [highlighted, standalone] = await Promise.all([
highlight(rows),
highlight([makeHunk("standalone-hunk"), exportRow]),
]);
expect(highlighted.tokensByRowId[exportRow.id]).toEqual(standalone.tokensByRowId[exportRow.id]);
});
it("keeps grammar state across inline comment rows", async () => {
const openingRow = makeLine({
id: "template-open",
content: "const message = `open",
change: "add",
oldLineNumber: null,
newLineNumber: 1,
});
const closingRow = makeLine({
id: "template-close",
content: "closed`;",
change: "add",
oldLineNumber: null,
newLineNumber: 2,
});
const trailingRow = makeLine({
id: "trailing-row",
content: "export const answer = 42;",
change: "add",
oldLineNumber: null,
newLineNumber: 3,
});
const commentRow: NativeReviewDiffRow = {
kind: "comment",
id: "comment-1",
fileId: TYPESCRIPT_FILE.id,
commentText: "Review note",
};
const [withComment, contiguous] = await Promise.all([
highlight([openingRow, commentRow, closingRow, trailingRow]),
highlight([openingRow, closingRow, trailingRow]),
]);
expect(withComment.tokensByRowId).toEqual(contiguous.tokensByRowId);
});
it("does not join unhighlighted rows across cached gaps", async () => {
const trailingRow = makeLine({
id: "trailing-row",
content: "export const answer = 42;",
change: "add",
oldLineNumber: null,
newLineNumber: 3,
});
const rows = [
makeLine({
id: "template-open",
content: "const message = `open",
change: "add",
oldLineNumber: null,
newLineNumber: 1,
}),
makeLine({
id: "template-close",
content: "closed`;",
change: "add",
oldLineNumber: null,
newLineNumber: 2,
}),
trailingRow,
];
const [highlighted, standalone] = await Promise.all([
highlight(rows, new Set(["template-close"])),
highlight([trailingRow]),
]);
expect(highlighted.tokensByRowId[trailingRow.id]).toEqual(
standalone.tokensByRowId[trailingRow.id],
);
});
it("keeps deletion grammar state out of addition rows", async () => {
const additionRow = makeLine({
id: "addition-row",
content: "export const answer = 42;",
change: "add",
oldLineNumber: null,
newLineNumber: 1,
});
const rows = [
makeLine({
id: "deletion-row",
content: "const removed = `open",
change: "delete",
oldLineNumber: 1,
newLineNumber: null,
}),
additionRow,
];
const [highlighted, standalone] = await Promise.all([
highlight(rows),
highlight([additionRow]),
]);
expect(highlighted.tokensByRowId[additionRow.id]).toEqual(
standalone.tokensByRowId[additionRow.id],
);
});
});
describe.each(["native", "javascript"] as const)("%s highlighting budgets", (engine) => {
const highlightRows = (rows: ReadonlyArray<NativeReviewDiffRow>, signal?: AbortSignal) =>
highlightNativeReviewDiffVisibleRows({
rows,
files: [TYPESCRIPT_FILE],
scheme: "dark",
engine,
firstRowIndex: 0,
lastRowIndex: rows.length - 1,
overscanRows: 0,
signal,
});
const line = (id: number, content: string) =>
makeLine({
id: `line-${id}`,
content,
change: "add",
oldLineNumber: null,
newLineNumber: id,
});
it("still highlights a line at the length limit", async () => {
const content = `// ${"x".repeat(997)}`;
const result = await highlightRows([line(1, content)]);
expect(tokenization.calls).toEqual([content]);
expect(result.tokensByRowId["line-1"]?.some((token) => token.color !== null)).toBe(true);
});
it("keeps only the long line plain and resumes highlighting after it", async () => {
const longLine = `${"x".repeat(1_001)} /*`;
const rows = [
line(1, "export const before = 1;"),
line(2, longLine),
{ kind: "comment", id: "note", commentText: "Check this", fileId: TYPESCRIPT_FILE.id },
line(3, "export const inside = 'x';"),
makeHunk("next-hunk"),
line(100, "export const after = 2;"),
] satisfies ReadonlyArray<NativeReviewDiffRow>;
const result = await highlightRows(rows);
expect(result.engine).toBe(engine);
expect(Object.keys(result.tokensByRowId)).toEqual(["line-1", "line-2", "line-3", "line-100"]);
expect(result.tokensByRowId["line-2"]).toEqual([
{ content: longLine, color: null, fontStyle: null },
]);
for (const id of ["line-1", "line-3", "line-100"]) {
expect(result.tokensByRowId[id]?.some((token) => token.color !== null)).toBe(true);
}
expect(tokenization.calls.some((code) => code.includes(longLine))).toBe(false);
});
it("highlights the rows after a long line the same regardless of the first window", async () => {
const rows = [
line(1, "export const before = 1;"),
line(2, `const data = "${"x".repeat(1_050)}";`),
line(3, "export const inside = 'x';"),
line(4, "export const after = 2;"),
];
const spanning = await highlightRows(rows);
const afterLongLine = await highlightNativeReviewDiffVisibleRows({
rows,
files: [TYPESCRIPT_FILE],
scheme: "dark",
engine,
firstRowIndex: 2,
lastRowIndex: 3,
overscanRows: 0,
});
for (const id of ["line-3", "line-4"]) {
expect(spanning.tokensByRowId[id]?.some((token) => token.color !== null)).toBe(true);
expect(afterLongLine.tokensByRowId[id]).toEqual(spanning.tokensByRowId[id]);
}
});
it("preserves multiline grammar and row mapping across character-limited batches", async () => {
const opening = line(1, "const message = `open");
const body = Array.from({ length: 40 }, (_, index) => line(index + 2, "inside ".repeat(45)));
const closing = line(42, "closed`; ");
const trailing = line(43, "export const after = 2;");
const result = await highlightRows([opening, ...body, closing, trailing]);
const calls = [...tokenization.calls];
const expected = await highlightRows([
opening,
{ ...closing, newLineNumber: 2 },
{ ...trailing, newLineNumber: 3 },
]);
expect(calls.length).toBeGreaterThan(1);
expect(calls.every((code) => code.length <= 8_000)).toBe(true);
expect(result.rowCount).toBe(43);
for (const row of [opening, ...body, closing, trailing]) {
expect(result.tokensByRowId[row.id]?.map((token) => token.content).join("")).toBe(
row.content,
);
}
expect(result.tokensByRowId[closing.id]).toEqual(expected.tokensByRowId[closing.id]);
expect(result.tokensByRowId[trailing.id]).toEqual(expected.tokensByRowId[trailing.id]);
});
it("stops before later batches and publishes no partial result after cancellation", async () => {
const controller = new AbortController();
tokenization.afterCall = () => controller.abort();
const rows = Array.from({ length: 30 }, (_, index) => line(index + 1, `// ${"x".repeat(400)}`));
const result = await highlightRows(rows, controller.signal);
expect(tokenization.calls).toHaveLength(1);
expect(result.rowCount).toBe(0);
expect(result.tokensByRowId).toEqual({});
});
});