// 阶段 0 编辑行为验收:撤销/重做、程序化插入后的选区落点、输入规则即时渲染。 // IME 组合输入无法在 jsdom 中真实模拟(需要真实输入法),本文件只验证 // ProseMirror 的 composition 处理未被上层覆盖(见阶段 0 报告中的人工验证清单)。 import { describe, it, expect, afterEach } from 'vitest' import type { Editor } from '@milkdown/kit/core' import { editorViewCtx } from '@milkdown/kit/core' import { createMarkdownEditor, getMarkdown, callUndo, callRedo } from '../src/editor' let editors: Editor[] = [] afterEach(async () => { for (const e of editors) { try { await e.destroy() } catch { /* ignore */ } } editors = [] document.body.innerHTML = '' }) async function load(markdown: string): Promise { const root = document.createElement('div') document.body.appendChild(root) const editor = createMarkdownEditor(root, markdown) editors.push(editor) await editor.create() return editor } describe('编辑行为', () => { it('撤销恢复到原文,重做再次应用修改', async () => { const editor = await load('原始内容\n') editor.action((ctx) => { const view = ctx.get(editorViewCtx) view.dispatch(view.state.tr.insertText('追加文字', view.state.doc.content.size)) }) expect(getMarkdown(editor)).toContain('追加文字') callUndo(editor) expect(getMarkdown(editor)).not.toContain('追加文字') expect(getMarkdown(editor)).toContain('原始内容') callRedo(editor) expect(getMarkdown(editor)).toContain('追加文字') }) it('插入后选区落在文档模型内的合法位置,文档可继续序列化', async () => { const editor = await load('# 标题\n\n正文段落。\n') editor.action((ctx) => { const view = ctx.get(editorViewCtx) const pos = view.state.doc.content.size view.dispatch(view.state.tr.insertText('尾巴')) const sel = view.state.selection expect(sel.from).toBeGreaterThan(0) expect(sel.from).toBeLessThanOrEqual(view.state.doc.content.size + 1) expect(pos).toBeGreaterThan(0) }) expect(getMarkdown(editor)).toContain('尾巴') }) it('编辑器 DOM 为 contenteditable 且启用了 ProseMirror 历史栈', async () => { const editor = await load('检查 DOM\n') const ok = editor.action((ctx) => { const view = ctx.get(editorViewCtx) return view.dom.getAttribute('contenteditable') === 'true' }) expect(ok).toBe(true) }) })