高危逻辑 A1-A10:saved 写盘快照、重载卸载回写闸门与期望值校验、 ?file 无条件打开、偏好读后才写+防抖、depth=0 守卫、查找命中随编辑 作废、自动保存避让冲突标签、搜索防链接环、重 IO 命令异步化+上限。 样式:--panel 更正为 --bg-panel、新增 .input 基础样式、文档主题 纸面/字色成对、打印隐藏清单补全、paperTone 驱动源码明暗。 性能:状态栏/大纲按正文 memo、大纲延后提取、Sidebar memo、专注 装饰映射复用、查找防抖、图片 base64 过 IPC。 中危一批:关窗落盘后放行、回收站去向明示、GBK 搜索、create_new 落盘、Mermaid 渲染代次守卫、多行粘贴分段等。 回归:新增 tabkey/audit-fix 集成测试(E1 Tab 抢先定性排除), vitest 71 通过 + 2 跳过。
81 lines
2.9 KiB
TypeScript
81 lines
2.9 KiB
TypeScript
// 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<typeof createMarkdownEditor>, 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<typeof createMarkdownEditor>, 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<typeof createMarkdownEditor>): 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()
|
|
})
|
|
})
|