From caba7b8cbb9bed38c27e1fde4c668936957a3cd0 Mon Sep 17 00:00:00 2001 From: mango <1711456624@qq.com> Date: Wed, 29 Jul 2026 18:46:04 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=85=20test(ai):=20=E6=94=B9=E7=94=A8?= =?UTF-8?q?=E8=A1=8C=E4=B8=BA=E6=96=AD=E8=A8=80=E9=AA=8C=E8=AF=81=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=9D=97=E4=BE=9D=E8=B5=96=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...essageCodeBlock.dependencyBoundary.test.ts | 140 +++++++++++++++--- 1 file changed, 123 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/ai/messageBubble/AIMessageCodeBlock.dependencyBoundary.test.ts b/frontend/src/components/ai/messageBubble/AIMessageCodeBlock.dependencyBoundary.test.ts index 576ea03b..fa523886 100644 --- a/frontend/src/components/ai/messageBubble/AIMessageCodeBlock.dependencyBoundary.test.ts +++ b/frontend/src/components/ai/messageBubble/AIMessageCodeBlock.dependencyBoundary.test.ts @@ -1,27 +1,133 @@ -import { readFileSync } from 'node:fs'; -import { describe, expect, it } from 'vitest'; +import React from 'react'; +import { act, create, type ReactTestRenderer } from 'react-test-renderer'; +import { describe, expect, it, vi } from 'vitest'; -const source = readFileSync(new URL('./AIMessageCodeBlock.tsx', import.meta.url), 'utf8'); -const viteConfigSource = readFileSync(new URL('../../../../vite.config.ts', import.meta.url), 'utf8'); +import viteConfig from '../../../../vite.config'; +import { buildOverlayWorkbenchTheme } from '../../../utils/overlayWorkbenchTheme'; +import { AIMessageCodeBlock } from './AIMessageCodeBlock'; + +const dependencyMocks = vi.hoisted(() => ({ + completeRegistryLoad: vi.fn(), + mermaidInitialize: vi.fn(), + mermaidLoad: vi.fn(), + mermaidRender: vi.fn(async () => ({ svg: 'diagram' })), + prismLightLoad: vi.fn(), + registerLanguage: vi.fn(), +})); + +vi.mock('react-syntax-highlighter', () => { + dependencyMocks.completeRegistryLoad(); + return { default: () => null }; +}); + +vi.mock('react-syntax-highlighter/dist/esm/prism-light', () => { + dependencyMocks.prismLightLoad(); + return { + default: Object.assign(() => null, { + registerLanguage: dependencyMocks.registerLanguage, + }), + }; +}); + +vi.mock('mermaid', () => { + dependencyMocks.mermaidLoad(); + return { + default: { + initialize: dependencyMocks.mermaidInitialize, + render: dependencyMocks.mermaidRender, + }, + }; +}); + +vi.mock('antd', () => ({ + Tooltip: ({ children }: { children: React.ReactNode }) => children, + message: { error: vi.fn() }, +})); + +vi.mock('@ant-design/icons', () => ({ + CheckOutlined: () => null, + CopyOutlined: () => null, + PlayCircleOutlined: () => null, +})); + +vi.mock('../../common/ResizableDraggableModal', () => ({ + default: Object.assign(() => null, { confirm: vi.fn() }), +})); + +const codeHighlightDependencies = [ + 'react-syntax-highlighter/dist/esm/prism-light', + 'react-syntax-highlighter/dist/esm/languages/prism/bash', + 'react-syntax-highlighter/dist/esm/languages/prism/css', + 'react-syntax-highlighter/dist/esm/languages/prism/diff', + 'react-syntax-highlighter/dist/esm/languages/prism/go', + 'react-syntax-highlighter/dist/esm/languages/prism/ini', + 'react-syntax-highlighter/dist/esm/languages/prism/java', + 'react-syntax-highlighter/dist/esm/languages/prism/javascript', + 'react-syntax-highlighter/dist/esm/languages/prism/json', + 'react-syntax-highlighter/dist/esm/languages/prism/jsx', + 'react-syntax-highlighter/dist/esm/languages/prism/markdown', + 'react-syntax-highlighter/dist/esm/languages/prism/markup', + 'react-syntax-highlighter/dist/esm/languages/prism/php', + 'react-syntax-highlighter/dist/esm/languages/prism/python', + 'react-syntax-highlighter/dist/esm/languages/prism/ruby', + 'react-syntax-highlighter/dist/esm/languages/prism/rust', + 'react-syntax-highlighter/dist/esm/languages/prism/sql', + 'react-syntax-highlighter/dist/esm/languages/prism/toml', + 'react-syntax-highlighter/dist/esm/languages/prism/tsx', + 'react-syntax-highlighter/dist/esm/languages/prism/typescript', + 'react-syntax-highlighter/dist/esm/languages/prism/yaml', + 'react-syntax-highlighter/dist/esm/styles/prism/vsc-dark-plus', + 'react-syntax-highlighter/dist/esm/styles/prism/vs', +]; + +const renderCodeBlock = (className: string, children: string) => React.createElement(AIMessageCodeBlock, { + className, + children, + darkMode: false, + overlayTheme: buildOverlayWorkbenchTheme(false), +}); describe('AIMessageCodeBlock dependency boundary', () => { - it('does not pull the complete syntax-highlighter language registry into the AI panel chunk', () => { - expect(source).not.toMatch(/from\s+['"]react-syntax-highlighter['"]/); - expect(source).toMatch(/react-syntax-highlighter\/dist\/esm\/prism-light/); + it('loads the lightweight syntax highlighter without the complete language registry', () => { + expect(dependencyMocks.completeRegistryLoad).not.toHaveBeenCalled(); + expect(dependencyMocks.prismLightLoad).toHaveBeenCalledOnce(); + expect(dependencyMocks.registerLanguage).toHaveBeenCalledTimes(20); }); - it('loads Mermaid only when a Mermaid fenced block is rendered', () => { - expect(source).not.toMatch(/^import\s+mermaid\s+from\s+['"]mermaid['"];?$/m); - expect(source).toMatch(/await\s+import\(['"]mermaid['"]\)/); - }); + it('loads Mermaid only when a Mermaid fenced block is rendered', async () => { + let renderer: ReactTestRenderer | undefined; + let mermaidContainer: { innerHTML: string } | undefined; - it('pre-bundles every static syntax-highlighter entry before Wails opens the panel', () => { - const syntaxHighlighterImports = Array.from(source.matchAll( - /from\s+['"](react-syntax-highlighter\/dist\/esm\/[^'"]+)['"]/g, - )).map((match) => match[1]); + try { + act(() => { + renderer = create(renderCodeBlock('language-sql', 'SELECT 1;'), { + createNodeMock: (element) => { + if (element.type === 'div' && element.props.className === 'ai-mermaid-container') { + mermaidContainer = { innerHTML: '' }; + return mermaidContainer; + } + return {}; + }, + }); + }); + expect(dependencyMocks.mermaidLoad).not.toHaveBeenCalled(); - for (const dependency of syntaxHighlighterImports) { - expect(viteConfigSource).toContain(`'${dependency}'`); + await act(async () => { + renderer?.update(renderCodeBlock('language-mermaid', 'graph TD; A-->B;')); + }); + await vi.waitFor(() => { + expect(dependencyMocks.mermaidLoad).toHaveBeenCalledOnce(); + expect(dependencyMocks.mermaidInitialize).toHaveBeenCalledWith({ startOnLoad: false, theme: 'default' }); + expect(dependencyMocks.mermaidRender).toHaveBeenCalledWith(expect.stringMatching(/^mermaid-/), 'graph TD; A-->B;'); + expect(mermaidContainer?.innerHTML).toBe('diagram'); + }); + } finally { + act(() => renderer?.unmount()); } }); + + it('pre-bundles every static syntax-highlighter dependency', () => { + const includedDependencies = viteConfig.optimizeDeps?.include || []; + expect(includedDependencies).toEqual(expect.arrayContaining(codeHighlightDependencies)); + }); });