Monaco Editor单元测试仅渲染第一行问题求助
针对Monaco Editor在Vitest环境下的单元测试方案
问题定位
你遇到的Monaco仅渲染第一行、无法交互的问题,本质是Vitest的jsdom环境对Monaco的DOM模拟不完整——Monaco依赖大量浏览器原生API(如requestAnimationFrame、完整的DOM事件系统),而jsdom默认模拟有限,vitest-canvas-mock只覆盖了canvas相关能力,没补全Monaco需要的其他核心API。
基础配置修复
先完善Vitest的环境配置,确保Monaco能正常初始化:
- 在
vitest.config.ts中添加环境模拟配置:
import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { environment: 'jsdom', setupFiles: ['./vitest.setup.ts'], testTimeout: 10000, // 给Monaco异步初始化留足时间 fakeTimers: { enableGlobally: true, timers: ['setTimeout', 'setInterval', 'requestAnimationFrame'], }, }, });
- 在
vitest.setup.ts中补全Monaco依赖的缺失API:
import 'vitest-canvas-mock'; // 模拟ResizeObserver(Monaco布局依赖) window.ResizeObserver = class ResizeObserver { constructor(callback: () => void) { callback(); } observe() {} unobserve() {} disconnect() {} }; // 模拟文本选择API(Monaco编辑交互依赖) Object.defineProperty(window, 'getSelection', { value: () => ({ anchorNode: document.body, anchorOffset: 0, focusNode: document.body, focusOffset: 0, isCollapsed: true, rangeCount: 0, getRangeAt: () => ({ setStart: () => {}, setEnd: () => {}, collapse: () => {}, getBoundingClientRect: () => ({ top: 0, left: 0, width: 0, height: 0 }), cloneRange: () => ({}) }), removeAllRanges: () => {}, addRange: () => {} }) });
编写规范的Monaco单元测试
1. 基础渲染验证(单元测试)
优先通过mock Monaco实例的方式,验证编辑器初始化逻辑:
import { render } from '@testing-library/vue'; // Vue项目示例,React可替换为react-testing-library import MyMonacoEditor from './MyMonacoEditor.vue'; import { vi } from 'vitest'; // Mock Monaco核心模块 vi.mock('monaco-editor', () => ({ editor: { create: vi.fn((el, options) => ({ getValue: () => options.value, setValue: vi.fn(), onDidChangeModelContent: vi.fn(), dispose: vi.fn(), })), }, })); test('Monaco编辑器初始化时传入完整的多行内容', () => { const testContent = `function hello() { console.log('world'); return true; }`; render(MyMonacoEditor, { props: { value: testContent } }); // 验证编辑器创建时接收了完整的初始内容 expect(vi.mocked(import('monaco-editor').editor.create)).toHaveBeenCalledWith( expect.any(HTMLElement), expect.objectContaining({ value: testContent }) ); });
2. 交互功能测试(单元测试)
验证内容变更、事件触发等核心交互逻辑:
import { render } from '@testing-library/vue'; import MyMonacoEditor from './MyMonacoEditor.vue'; import { vi } from 'vitest'; test('Monaco编辑器内容变更时能触发组件的change事件', async () => { let onChangeCallback: () => void; const mockEditor = { getValue: vi.fn(() => '初始内容'), setValue: vi.fn(), onDidChangeModelContent: vi.fn((cb) => { onChangeCallback = cb; return { dispose: vi.fn() }; }), dispose: vi.fn(), }; vi.mock('monaco-editor', () => ({ editor: { create: vi.fn(() => mockEditor) }, })); const { emitted } = render(MyMonacoEditor, { props: { value: '初始内容' } }); // 模拟编辑器内部的内容变更事件 onChangeCallback?.(); mockEditor.getValue.mockReturnValue('修改后的多行内容'); // 验证组件向外抛出了正确的变更事件 expect(emitted().change).toHaveLength(1); expect(emitted().change[0][0]).toBe('修改后的多行内容'); });
3. 真实交互验证(E2E测试)
如果需要验证真实DOM下的输入、渲染效果,可配合Playwright做E2E测试:
import { test, expect } from '@playwright/test'; test('Monaco编辑器可正常输入并渲染多行文本', async ({ page }) => { await page.goto('/editor-page'); // 打开你的编辑器页面路由 const editorInput = page.locator('.monaco-editor textarea'); // 输入多行代码 await editorInput.fill(`function calculateSum(a, b) { const total = a + b; return total; }`); // 验证编辑器内容正确 const inputValue = await editorInput.inputValue(); expect(inputValue).toContain('const total = a + b;'); expect(inputValue).toContain('return total;'); });
核心注意事项
- 单元测试优先mock Monaco实例,避免真实DOM环境的复杂度;
- 复杂交互、样式渲染类场景,用E2E测试更可靠;
- 拆分测试粒度:每个测试只验证一个功能点,比如初始化渲染、内容变更、语法校验触发等。
内容的提问来源于stack exchange,提问作者Vamshi
相关产品推荐
相关产品推荐

