// Tab 键集成测试(审计 E1 定性):向组装好的编辑器派发真实 keydown 事件, // 验证表格跳格 / 代码块行首缩进到底由哪个插件处理——此前的测试都是直调函数, // 从未验证过 Milkdown 自带 indent 插件(对 Tab 无条件插两空格)是否抢先。 import { describe, it, expect } from 'vitest' import { createMarkdownEditor, getMarkdown } from '../src/editor/editor' import { editorViewCtx } from '@milkdown/kit/core' import { TextSelection } from '@milkdown/kit/prose/state' const MD = [ '普通段落文字。', '', '| A | B |', '| --- | --- |', '| 甲一 | 乙二 |', '', '```ts', 'const x = 1', '```', '', ].join('\n') function dispatchTab(host: HTMLElement, editor: ReturnType, shift = false) { const view = editor.action((ctx) => ctx.get(editorViewCtx)) const event = new KeyboardEvent('keydown', { key: 'Tab', shiftKey: shift, bubbles: true, cancelable: true }) view.dom.dispatchEvent(event) return event } function setCursorIn(editor: ReturnType, needle: string, offsetInText = 1) { editor.action((ctx) => { const view = ctx.get(editorViewCtx) let target = -1 view.state.doc.descendants((node, pos) => { if (target < 0 && node.isText && node.text?.includes(needle)) { target = pos + node.text.indexOf(needle) + offsetInText } return target < 0 }) if (target < 0) throw new Error(`找不到文本 ${needle}`) view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, target))) view.focus() }) } function cursorCellText(editor: ReturnType): string { return editor.action((ctx) => { const view = ctx.get(editorViewCtx) return view.state.selection.$from.parent.textContent }) } describe('Tab 键真实派发', () => { it('表格内 Tab 跳到下一个单元格而不是插入空格', async () => { const host = document.createElement('div') document.body.appendChild(host) const editor = createMarkdownEditor(host, MD) await editor.create() setCursorIn(editor, '甲一') const before = getMarkdown(editor) dispatchTab(host, editor) expect(cursorCellText(editor)).toBe('乙二') expect(getMarkdown(editor)).toBe(before) // 文档未被插入空格改动 await editor.destroy() host.remove() }) it('代码块内 Tab 在行首缩进(光标在行中时)', async () => { const host = document.createElement('div') document.body.appendChild(host) const editor = createMarkdownEditor(host, MD) await editor.create() setCursorIn(editor, 'const x = 1', 4) // 光标停在 "con|st" 处 dispatchTab(host, editor) const md = getMarkdown(editor) expect(md).toContain(' const x = 1') // 行首多了两空格 expect(md).not.toContain('con st') // 不是在光标处插空格 await editor.destroy() host.remove() }) })