news 2026/9/15 13:59:12

plate 仓库 Autoformat 非规则接缝覆盖补全:AutoformatPlugin、autoformatBlock 与 isPreviousCharacterEmpty 测试实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
plate 仓库 Autoformat 非规则接缝覆盖补全:AutoformatPlugin、autoformatBlock 与 isPreviousCharacterEmpty 测试实战

plate 仓库 Autoformat 非规则接缝覆盖补全:AutoformatPlugin、autoformatBlock 与 isPreviousCharacterEmpty 测试实战

【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate

本文以 plate 仓库(Rich-text editor with AI and shadcn/ui)的测试覆盖计划 2026-03-23-autoformat-coverage-pass.md 为骨架,深入剖析@platejs/autoformat在"规则表(rule table)之外的最后一公里"——三个非规则接缝(non-rule seam)的测试策略:AutoformatPlugin的插件覆盖层、autoformatBlock的纯转换分支,以及isPreviousCharacterEmpty的前置字符判空工具。读完本文,你将理解为何已"弃用"的 autoformat 仍需外科手术式覆盖,掌握 query veto、insertTrigger、undo-on-delete 恢复、triggerAtBlockStartallowSameTypeAbove、单字符匹配安全与空字符串范围等测试要点的具体写法,并能在 plate 仓库当前的 input-rules 架构中找到它们对应的源码落点。

背景:为什么已"弃用"的 Autoformat 仍值得一次覆盖

在开始之前,必须先厘清一个关键前提:在@platejs/autoformat的 53.0.0 版本中,该包已被标记为弃用(deprecated)。根据 CHANGELOG.md,Markdown 快捷键与文本替换(text substitution)已改为在各个特性插件(feature plugin)上以inputRules形式声明,AutoformatPlugin仅作为惰性兼容导出(inert compatibility export)保留,其源码实现为:

export const AutoformatPlugin = createSlatePlugin({ key: KEYS.autoformat, });

(见 packages/autoformat/src/plugin.ts)

但这并不意味着测试工作可以就此停止。本覆盖计划文档明确指出:

  • 现有的withAutoformat覆盖已经覆盖了大部分端到端的 mark 与 text 流程;
  • 剩余的价值在于plugin override 接缝(插件覆写层)和少数纯autoformatBlock分支

也就是说,这次覆盖不是从零开始的补测,而是"快车道(fast-lane)"上的一次外科手术:只针对最后值得覆盖的非规则接缝下手,目标是查漏补缺、防止回归,而不是重建规则表测试。

目标与范围界定:三个非规则接缝

计划文档把覆盖目标收敛为三个具体文件:

目标文件性质覆盖重点
AutoformatPlugin.ts插件契约层(override seam)query veto、insertTrigger、undo-on-delete 的数组匹配恢复
autoformatBlock.ts纯转换工具(transform helper)triggerAtBlockStart: falseallowSameTypeAbove、单字符匹配安全
isPreviousCharacterEmpty.ts纯判空工具空字符串范围的边缘行为

之所以称为"非规则接缝",是因为它们不直接参与"输入什么字符匹配什么规则"的规则表逻辑,而是规则表之外的基础设施:插件如何被装配与拦截、块级转换在何种条件下触发、以及匹配点前字符是否为空的判定。这些接缝如果出现回归,规则表测试往往无法直接暴露,需要专门的定向测试。

从当前仓库的源码结构看,三个目标在最新架构中的实际落点并不相同:

  • AutoformatPlugin依然是独立导出,但已是惰性兼容层(packages/autoformat/src/plugin.ts);
  • isPreviousCharacterEmpty不再作为独立文件存在,其逻辑内联在核心的 input-rules 实现中(packages/core/src/lib/plugins/input-rules/createInputRules.ts);
  • autoformatBlock的块级转换职责,由当前架构中的createBlockStartInputRule/matchBlockStart承担(packages/core/src/lib/plugins/input-rules/createInputRules.ts)。

约束条件解读:四条测试红线

计划文档为这次覆盖划定了四条硬性约束,每一条都直接影响测试的写法:

  1. No/react:不引入 React 渲染层测试,测试停留在纯编辑器与纯函数层面,避免因 React 版本或 DOM 环境引入的噪声;
  2. No rule-table coverage:不重复覆盖规则表(match/mode/type 组合矩阵),因为这部分价值已由withAutoformat端到端测试提供;
  3. Keep the slice in normal*.spec.ts[x]:测试文件保持常规的*.spec.ts[x]形态,与仓库既有测试体系一致,不引入特殊 runner 或专用测试目录;
  4. Prefer real editor behavior for plugin contracts and tiny stubs for pure transform helpers:对插件契约层优先使用真实编辑器行为驱动测试;对纯转换工具函数则使用微型 stub,以降低搭建成本、聚焦被测分支。

第四条约束实际上是对测试性价比的权衡:插件契约(如 veto 是否生效、触发字符是否正确)依赖编辑器的完整输入路径,用真实编辑器才能得到可信结论;而纯函数如isPreviousCharacterEmpty只需构造最小化的编辑器/范围对象即可验证,不必每次都拉起完整编辑上下文。

工作切片详解:四步执行计划

1. 为 AutoformatPlugin 添加直接 spec:query veto、insertTrigger 与 undo-on-delete

计划文档要求为AutoformatPlugin写一个直接测试,覆盖三个插件层面的行为契约:

query veto(查询否决):当某条规则/输入规则在特定上下文下被禁用时,触发字符必须原样落盘,不做任何格式化。在旧的AutoformatRule时代,这一职责由rules[].query承担;迁移后的 input-rules 架构中,对应机制是每个规则上的enabled回调。见 types.ts:

export type BaseInputRule< TContext extends SelectionInputRuleContext = SelectionInputRuleContext, > = { enabled?: BivariantCallback<[context: TContext], boolean>; priority?: number; };

测试要点:构造一个enabled返回false的规则,断言输入 trigger 字符后文本保持原样(veto 生效);再构造enabled返回true的对照规则,断言格式化正常发生。

insertTrigger(触发字符注入):input-rules 系统以trigger字段声明哪些字符可以触发该规则,运行时按 trigger 建立索引,命中后才调用resolve/apply。见 types.ts 中InsertTextInputRuletrigger字段,以及 defineInputRule.ts 对规则的统一包装。测试要点:验证多字符 trigger(如->=>)与数组形式 trigger 都能正确路由,且非 trigger 字符不会进入 resolve 流程。

undo-on-delete 的数组匹配恢复:当用户按下退格删除由规则插入的内容时,编辑器应恢复为匹配前的源文本。CHANGELOG 明确指出enableUndoOnDelete选项已被移除——"undo-on-delete is the built-in behavior",即退格恢复源文本已成为内置默认行为(见 CHANGELOG.md 的 "Option removals" 小节)。当一条规则使用数组形式的match(如["(tm)", "(TM)"])时,无论命中数组中的哪一项,删除后都应恢复对应的那一个原始字符串。测试要点:分别命中数组各项,执行删除操作,断言恢复文本与原始匹配文本逐项一致。

2. 加深 autoformatBlock.spec.ts:三个关键分支

autoformatBlock是块级自动格式化的核心转换函数,计划文档要求重点覆盖三个分支:

triggerAtBlockStart: false:默认情况下块级规则要求 trigger 出现在块起始位置(即#必须在行首才生效);当该选项为false时,允许 trigger 出现在块内更靠后的位置,匹配窗口从块起始文本中寻找。测试要点:验证开关关闭前后,同一文本在不同位置命中/不命中的行为差异。

allowSameTypeAbove:控制当光标上方的块已经是目标类型时,是否允许再次触发转换(例如在上方已存在H1时继续输入#是否重复转换)。测试要点:验证truefalse两种取值下连续块级转换的结果。

单字符匹配安全:当匹配模式是单个字符(如-*_)时,必须确保不会在普通文本的任意位置误触发转换——例如输入"a-b"不应把中间的-当作列表 trigger。这类安全保证在现架构中体现在两处:matchBlockStart要求选区折叠(isCollapsed)且块起始文本与模式严格相等(createInputRules.ts),以及createBlockFenceInputRuleconfig.fence.at(-1)取最后一个字符作为 trigger、并用context.text !== trigger先行拦截(createInputRules.ts)。测试要点:构造"单词中包含单字符 trigger"的输入序列,断言不发生意外转换。

3. 为 isPreviousCharacterEmpty 添加空字符串范围边缘用例

isPreviousCharacterEmpty的职责是判断匹配起始点之前的字符区域是否"为空"(即不存在非空白字符),用于阻止在紧贴非空白文本处触发格式匹配。它在当前仓库中的实现位于 createInputRules.ts:

const noWhiteSpaceRegex = /\S+/; const isPreviousCharacterEmpty = (editor: SlateEditor, at: Point) => { const range = editor.api.range('before', at); if (!range) return true; const text = editor.api.string(range); return text ? !noWhiteSpaceRegex.exec(text) : true; };

逐行拆解其语义:

  • editor.api.range('before', at)取得at之前的范围;at位于文档最开头、前方不存在任何内容时,rangenull,函数直接返回true——这是计划文档要求专门覆盖的"空字符串范围"边缘情况;
  • 当范围存在但文本为空字符串时,同样返回true
  • 当文本全部由空白字符组成(空格、制表符、换行)时,\S+无法匹配,返回true
  • 只要存在任意一个非空白字符,立即返回false

这个函数在 mark 规则(getMarkMatch,见 createInputRules.ts)与文本替换规则(getTextSubstitutionMatchPoints,见 createInputRules.ts)中都被调用,是防止"在单词中间或紧贴文字处误触发格式化"的关键闸门。测试要点:构造文档开头、空字符串、纯空白字符、含非空白字符四类场景,断言返回值分别为truetruetruefalse

4. 停止条件:除非直接测试暴露真实 bug

计划文档的最后一步给出了明确的止损信号:"Stop unless a direct test exposes a real bug."这意味着本次覆盖是防御性补测而非无限追责——如果上述定向测试全部通过且未暴露缺陷,则覆盖工作到此为止,不再向规则表或其他模块扩散。这一约束与"fast-lane-only"的定位一脉相承:在有限的时间预算内,把测试资源精确投放到最后的高价值接缝上。

源码落点核对:计划文件与当前架构的映射

将计划文档的三个目标文件与当前仓库源码逐一对账,可以得到一份清晰的映射表:

计划中的目标当前仓库实际落点说明
AutoformatPlugin.tspackages/autoformat/src/plugin.ts惰性兼容导出,仅含key: KEYS.autoformat
autoformatBlock.ts块级职责迁移至createBlockStartInputRule/matchBlockStart(createInputRules.ts)旧独立函数在新架构中成为规则工厂的一部分
isPreviousCharacterEmpty.ts内联于 createInputRules.ts由独立文件演化为内部工具函数

这条映射关系本身就是一个有价值的测试洞察:当包被重构后,旧文件的测试意图应当被迁移到新落点,而不是被一并丢弃。计划文档所列的测试场景(veto、触发、恢复、块起始、同类型上方、单字符安全、空范围)在迁移后的inputRules架构中依然成立,只是测试目标文件从packages/autoformat平移到了packages/core/src/lib/plugins/input-rules

运行与验证:在仓库中执行这些定向测试

本次覆盖的测试文件按约束保持普通*.spec.ts[x]形态,放置在对应包/模块的源码目录旁。仓库的测试基础设施基于 bun(根目录存在 bun.lock 与 bunfig.toml,且仓库在 2026-04 期间完成了向 bun test 的迁移,见 2026-04-16-slate-v2-bun-test-migration-plan.md),因此可在packages/core等目标目录下直接筛选运行对应的 spec 文件,例如按文件路径或测试名过滤 input-rules 相关用例。运行前请确保已按仓库根 README.md 的指引安装依赖。

验证通过的标准:三个目标接缝(插件契约层、块级转换分支、判空工具边缘)各有至少一个定向用例,且用例之间互不依赖规则表;若全部通过则按停止条件收尾,不向其他模块扩散。

结语

Autoformat 的这次覆盖补全是一个典型的"接缝测试"案例:当功能主体已被端到端测试覆盖后,真正值得投入的是那些跨层、跨模块的边界接缝。AutoformatPlugin的 veto/trigger/恢复契约、autoformatBlock的块起始与同类型分支、isPreviousCharacterEmpty的空范围语义,共同构成了规则表之外的防御面。理解这份计划的执行逻辑,既有助于在 plate 仓库中继续维护这些模块,也能为其他编辑器项目设计"fast-lane 覆盖"提供可复用的方法论。

【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 13:55:29

Cinema4D R20 合法安装与现代系统兼容方案

简介&#xff1a;本资源为Cinema 4D R20完整版安装包及配套破解方案&#xff0c;面向三维建模、动画制作与渲染初学者及中小型设计团队&#xff0c;解决正版授权门槛高、学习成本大的实际问题。压缩包共2000个文件&#xff0c;总计460.34MB&#xff0c;包含大量核心可执行文件&…

作者头像 李华
网站建设 2026/9/15 13:55:25

OpenCLI:把GUI应用封装成命令行工具的工程实践

1. 为什么写OpenCLI&#xff1a;受够了一个个点鼠标大概从第三年开始做主前端和自动化工具&#xff0c;我就一直有个执念&#xff1a;能用命令行解决的事情&#xff0c;绝不去碰图形界面。但现实很骨感&#xff0c;日常工作中总有那么几个工具&#xff0c;明明就是个网页或者一…

作者头像 李华
网站建设 2026/9/15 13:52:33

随机断网不用慌:DHCP地址池冲突排查实战

最近被朋友拉去处理一个挺典型的网络故障&#xff1a;公司里“随机终端断网”&#xff0c;断一下又自己恢复&#xff0c;客户自己查了好几天没头绪。我过去看了不到半天就定位到了根因&#xff0c;说穿了其实特别简单——不是硬件坏了&#xff0c;也不是被攻击&#xff0c;就是…

作者头像 李华