// @vitest-environment jsdom import { beforeEach, describe, expect, it, vi } from 'vitest' const highlightJsMock = vi.hoisted(() => ({ getLanguage: vi.fn((lang?: string) => ['shell', 'xml', 'yaml', 'bash', 'json'].includes(lang || '')), highlight: vi.fn((content: string, { language }: { language: string }) => ({ value: `${content}`, })), registerLanguage: vi.fn(), })) const copyToClipboardMock = vi.hoisted(() => vi.fn<(text: string) => Promise>(async () => true)) vi.mock('highlight.js', () => ({ default: highlightJsMock, })) vi.mock('@/utils/clipboard', () => ({ copyToClipboard: copyToClipboardMock, })) import { extractUnifiedDiffPayload, handleCodeBlockCopyClick, inferStructuredLanguage, isUnifiedDiffContent, normalizeHighlightLanguage, renderHighlightedCodeBlock, } from '@/components/hermes/chat/highlight' const UNIFIED_DIFF_SAMPLE = `diff --git a/foo.ts b/foo.ts index 1111111..2222222 100644 --- a/foo.ts +++ b/foo.ts @@ -1,2 +1,2 @@ -const value = 1 +const value = 2 console.log(value) ` describe('highlight helper', () => { beforeEach(() => { vi.clearAllMocks() copyToClipboardMock.mockResolvedValue(true) highlightJsMock.getLanguage.mockImplementation((lang?: string) => ['shell', 'xml', 'yaml', 'bash', 'json'].includes(lang || '')) highlightJsMock.highlight.mockImplementation((content: string, { language }: { language: string }) => ({ value: `${content}`, })) }) it.each([ ['vue', 'xml'], ['yml', 'yaml'], ['sh', 'bash'], ['zsh', 'bash'], ['shellscript', 'bash'], ['shell', 'shell'], ])('normalizes %s to %s', (input, expected) => { expect(normalizeHighlightLanguage(input)).toBe(expected) }) it('uses a delegated copy attribute instead of inline javascript', () => { const html = renderHighlightedCodeBlock('x', 'json', 'Copy') expect(html).toContain('data-copy-code="true"') expect(html).not.toContain('onclick=') }) it('can render code blocks without a copy button', () => { const html = renderHighlightedCodeBlock('x', 'json', 'Copy', { showCopyButton: false, }) expect(html).not.toContain('data-copy-code="true"') expect(html).not.toContain('copy-btn') }) it('infers patch-style raw payloads as diff', () => { expect(inferStructuredLanguage([ '*** Begin Patch', '*** Update File: demo.ts', '@@', '-old', '+new', '*** End Patch', ].join('\n'))).toBe('diff') }) it('does not infer primitive JSON-looking text as json', () => { expect(inferStructuredLanguage('false')).toBeUndefined() expect(inferStructuredLanguage('0')).toBeUndefined() }) it('infers unified diff payloads as diff', () => { expect(inferStructuredLanguage([ '--- a/demo.ts', '+++ b/demo.ts', '@@ -1 +1 @@', '-old', '+new', ].join('\n'))).toBe('diff') }) it('preserves shell-session highlighting instead of remapping shell fences to bash', () => { const html = renderHighlightedCodeBlock('$ ls\nfoo.txt\n', 'shell', 'Copy') expect(highlightJsMock.highlight).toHaveBeenCalledWith('$ ls\nfoo.txt\n', { language: 'shell', ignoreIllegals: true, }) expect(html).toContain('class="code-lang">shell') }) it('skips highlighting for large known-language blocks when a render limit is set', () => { const html = renderHighlightedCodeBlock('x'.repeat(5000), 'vue', 'Copy', { maxHighlightLength: 2000, }) expect(highlightJsMock.highlight).not.toHaveBeenCalled() expect(html).toContain('class="code-lang">vue') }) it('falls back to escaped plaintext for unsupported fence labels', () => { const html = renderHighlightedCodeBlock('', 'unknown', 'Copy') expect(highlightJsMock.highlight).not.toHaveBeenCalled() expect(html).toContain('<tag>') expect(html).toContain('class="code-lang">unknown') }) it('falls back to escaped plaintext when direct highlighting throws', () => { highlightJsMock.highlight.mockImplementationOnce(() => { throw new Error('boom') }) const html = renderHighlightedCodeBlock('', 'vue', 'Copy') expect(html).toContain('<tag>') expect(html).toContain('class="code-lang">vue') }) it('detects unified diff content conservatively', () => { expect(isUnifiedDiffContent(UNIFIED_DIFF_SAMPLE)).toBe(true) expect(isUnifiedDiffContent('--- note\n+++ more\nplain text')).toBe(false) expect(isUnifiedDiffContent('@@ -1 +1 @@\n-a\n+b', 'diff')).toBe(true) }) it('extracts unified diffs from JSON payload fields shared by chat and group chat renderers', () => { const nestedDiff = { ok: true, result: { difference: UNIFIED_DIFF_SAMPLE, }, } expect(extractUnifiedDiffPayload(nestedDiff)).toBe(UNIFIED_DIFF_SAMPLE) expect(extractUnifiedDiffPayload({ difference: 'not a diff' })).toBeNull() }) it('renders unified diffs with semantic rows, line numbers, and no highlight.js execution', () => { const html = renderHighlightedCodeBlock(UNIFIED_DIFF_SAMPLE, undefined, 'Copy') expect(highlightJsMock.highlight).not.toHaveBeenCalled() expect(html).toContain('hljs-unified-diff') expect(html).toContain('class="code-lang">diff') expect(html).toContain('diff-line diff-line-file-header') expect(html).toContain('diff-line diff-line-hunk-header') expect(html).toContain('diff-line diff-line-removed') expect(html).toContain('diff-line diff-line-added') expect(html).toContain('class="diff-line-number diff-line-number-old" aria-hidden="true">1') expect(html).toContain('class="diff-line-number diff-line-number-new" aria-hidden="true">1') expect(html).toContain('class="diff-line-number diff-line-number-context" aria-hidden="true">2') expect(html).toContain('class="diff-line-content">-const value = 1') expect(html).toContain('class="diff-line-content">+const value = 2') }) it('renders unified diff rows without newline text nodes between block rows', () => { const html = renderHighlightedCodeBlock(UNIFIED_DIFF_SAMPLE, undefined, 'Copy') expect(html).not.toContain('\n