diff --git a/apps/web/src/components/review/use-syntax-highlight.fallback.test.ts b/apps/web/src/components/review/use-syntax-highlight.fallback.test.ts index 1cd7bb7..d4bc869 100644 --- a/apps/web/src/components/review/use-syntax-highlight.fallback.test.ts +++ b/apps/web/src/components/review/use-syntax-highlight.fallback.test.ts @@ -67,4 +67,65 @@ describe('useHighlightedFile — fallback path (Worker unavailable)', () => { expect(result.current?.get(1)).toEqual([MOCK_TOKEN_A]) expect(result.current?.get(2)).toEqual([MOCK_TOKEN_B]) }) + + it('returns a complete token map with no lines missing for ≤200-line input (single-chunk equivalence)', async () => { + const { useHighlightedFile } = await import('./use-syntax-highlight') + + // 5 lines — well within the 200-line chunk size, so a single codeToTokens call handles all + const MOCK_TOKENS = [ + [{ content: 'line1', color: '#fff', offset: 0 }], + [{ content: 'line2', color: '#fff', offset: 0 }], + [{ content: 'line3', color: '#fff', offset: 0 }], + [{ content: 'line4', color: '#fff', offset: 0 }], + [{ content: 'line5', color: '#fff', offset: 0 }], + ] + mockCodeToTokens.mockReturnValueOnce({ tokens: MOCK_TOKENS }) + + const lines = [1, 2, 3, 4, 5].map((n) => ({ + content: `line${n}`, + newLineNumber: n, + type: 'context' as const, + })) + + const { result } = renderHook(() => useHighlightedFile('src/bar.ts', lines)) + + await waitFor( + () => { + expect(result.current).not.toBeNull() + }, + { timeout: 5000 }, + ) + + // All 5 line numbers must be present — no lines missing + expect(result.current!.size).toBe(5) + for (let n = 1; n <= 5; n++) { + expect(result.current!.get(n)).toEqual(MOCK_TOKENS[n - 1]) + } + }) + + it('calls AbortController.abort() when component unmounts during chunked fallback', async () => { + const { useHighlightedFile } = await import('./use-syntax-highlight') + + const abortSpy = vi.spyOn(AbortController.prototype, 'abort') + + // Delay the mock so the hook is still in-flight when we unmount + mockCodeToTokens.mockImplementationOnce( + () => + new Promise((resolve) => + setTimeout(() => resolve({ tokens: [[MOCK_TOKEN_A]] }), 500), + ), + ) + + const lines = [{ content: 'const x = 1', newLineNumber: 1, type: 'added' as const }] + + const { unmount } = renderHook(() => useHighlightedFile('unmount.ts', lines)) + + // Unmount while the async chunked highlight is still pending + unmount() + + // The cleanup function calls abortController.abort() + expect(abortSpy).toHaveBeenCalled() + + abortSpy.mockRestore() + }) })