477af66232
* feat(chat): polish syntax highlighting and tool payload rendering (#94) * [verified] feat(chat): polish syntax highlighting and tool payload rendering * [verified] fix(chat): tighten large tool payload rendering * docs: update data volume path in Docker docs Align documentation with docker-compose.yml change: hermes-web-ui-data -> hermes-web-ui, /app/dist/data -> /root/.hermes-web-ui Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: bundle server build and restructure service modules - Add build-server.mjs script for standalone server compilation - Add logger service with structured output - Restructure auth, gateway-manager, hermes-cli, hermes services - Update docker-compose volume mount path - Update tsconfig and entry point for bundled server Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: separate controllers from routes and centralize route registration - Extract business logic from route handlers into controllers/ - Add centralized route registry in routes/index.ts with public/auth/protected layers - Replace global auth whitelist with sequential middleware registration - Extract shared helpers to services/config-helpers.ts - Allow custom provider name to be user-editable in ProviderFormModal - Deduplicate custom providers by poolKey instead of base_url in getAvailable Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: auth bypass via path case, SPA serving, and provider improvements - Fix auth bypass: path case-insensitive check for /api, /v1, /upload - Fix SPA returning 401: skip auth for non-API paths (static files) - Fix profile switch: use local loading state instead of shared store ref - Auto-append /v1 to base_url when fetching models (frontend + backend) - Guard .env writing to built-in providers only - Add builtin field to provider presets, enable base_url input in form - Print auth token to console on startup (pino only writes to file) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Zhicheng Han <43314240+hanzckernel@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
81 lines
2.8 KiB
TypeScript
81 lines
2.8 KiB
TypeScript
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: `<span class="mock-${language}">${content}</span>`,
|
|
})),
|
|
}))
|
|
|
|
vi.mock('highlight.js', () => ({
|
|
default: highlightJsMock,
|
|
}))
|
|
|
|
import { normalizeHighlightLanguage, renderHighlightedCodeBlock } from '@/components/hermes/chat/highlight'
|
|
|
|
describe('highlight helper', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
highlightJsMock.getLanguage.mockImplementation((lang?: string) => ['shell', 'xml', 'yaml', 'bash', 'json'].includes(lang || ''))
|
|
highlightJsMock.highlight.mockImplementation((content: string, { language }: { language: string }) => ({
|
|
value: `<span class="mock-${language}">${content}</span>`,
|
|
}))
|
|
})
|
|
|
|
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('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</span>')
|
|
})
|
|
|
|
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</span>')
|
|
})
|
|
|
|
it('falls back to escaped plaintext for unsupported fence labels', () => {
|
|
const html = renderHighlightedCodeBlock('<tag>', 'unknown', 'Copy')
|
|
|
|
expect(highlightJsMock.highlight).not.toHaveBeenCalled()
|
|
expect(html).toContain('<tag>')
|
|
expect(html).toContain('class="code-lang">unknown</span>')
|
|
})
|
|
|
|
it('falls back to escaped plaintext when direct highlighting throws', () => {
|
|
highlightJsMock.highlight.mockImplementationOnce(() => {
|
|
throw new Error('boom')
|
|
})
|
|
|
|
const html = renderHighlightedCodeBlock('<tag>', 'vue', 'Copy')
|
|
|
|
expect(html).toContain('<tag>')
|
|
expect(html).toContain('class="code-lang">vue</span>')
|
|
})
|
|
})
|