Plate 项目 basic-styles 与 math 包非 React 测试覆盖实战:从测试计划到源码级验证
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
导读
本文基于 Plate(platejs)仓库中的测试覆盖执行计划文档 2026-03-23-basic-styles-and-math-coverage-pass.md,完整还原一次针对@platejs/basic-styles与@platejs/math两个非 React 包的定向测试覆盖行动:包括覆盖目标、被测 API 的源码实现原理、聚焦测试用例的断言逻辑,以及从bun test到turbo build/typecheck/lint的完整验证链路。读完本文,你将掌握这两个包中 9 个核心函数与插件的职责边界与调用方式,并了解 Plate 仓库"测试覆盖计划 → 聚焦 spec → 多命令验证"的标准化执行流程,可直接复用到其他包的覆盖工作中。
一、背景:为什么做这次覆盖 Pass
在 Plate 仓库中,packages/basic-styles与packages/math是两个纯逻辑(non-React)功能包,分别承载段落级排版样式与 KaTeX 数学公式渲染。随着仓库整体转向bun test测试运行器,并推行"按包、按函数粒度补齐覆盖"的测试策略,2026-03-23 的这份计划文档定义了一次小而聚焦的覆盖行动:只针对两个包的非 React 部分补写精确到单个函数/插件的 spec,并明确延后/react子目录、字体插件整体扫尾与额外的 KaTeX 行为矩阵。
其核心策略值得注意:先圈定精确的 API 名单(Scope),再逐一对齐源码实现补 spec,最后用一组标准命令验证。这与仓库内其他覆盖计划(如 2026-03-23-core-coverage-pass.md、2026-03-23-combobox-coverage-pass.md)属于同一套执行模式。
二、basic-styles 包的覆盖范围与源码原理
本次为@platejs/basic-styles圈定 4 个被测目标,均位于 packages/basic-styles/src/lib(React 版本在 packages/basic-styles/src/react,不在本次范围)。
2.1setLineHeight:注入式块级行高变换
源码位于 setLineHeight.ts,其实现体现了 Plate 的"注入(inject)"架构:
export const setLineHeight = ( editor: SlateEditor, value: number, setNodesOptions?: SetNodesOptions ): void => { const { defaultNodeValue, nodeKey } = editor.getInjectProps(BaseLineHeightPlugin); const match = getInjectMatch( editor, editor.getPlugin({ key: KEYS.lineHeight }) ); if (value === defaultNodeValue) { editor.tf.unsetNodes(nodeKey!, { match, ...setNodesOptions }); } else { editor.tf.setNodes({ [nodeKey!]: value }, { match: match as any, ...setNodesOptions, }); } };关键行为:
- 通过
editor.getInjectProps(BaseLineHeightPlugin)读取插件的defaultNodeValue与nodeKey,实现"单一数据源"; - 当传入值等于默认值(
1.5)时走unsetNodes删除lineHeight属性,否则用setNodes写入——即"回到默认即清理",避免文档中堆积冗余属性。
对应 spec setLineHeight.spec.tsx 用jsxt构造编辑器状态,覆盖三个断言分支:
- 对匹配的
p块设置lineHeight: 2后,节点类型保持p并新增属性; - 已带
lineHeight={2}的段落调用setLineHeight(editor, 1.5)后属性被移除(回到默认值即清理); - 对
h1等非注入目标调用时不产生任何变更——验证了getInjectMatch的匹配边界。
2.2toUnitLess:单位剥离工具
源码位于 toUnitLess.ts,实现极简但边界清晰:
const digitRegex = /\d+/; // return '0' if value not valid export const toUnitLess = (value: string): string => { const match = digitRegex.exec(value); if (!match) return '0'; const num = Number(match[0]); if (value.endsWith('rem')) return (num * 16).toString(); return num.toString(); };- 提取字符串中第一段连续数字;
- 无法匹配(如空串、
auto)时返回'0'; rem单位按 16px 基准换算为像素数值(2rem → '32');px或纯数字则直接剥离单位。
对应 toUnitLess.spec.ts 用三组用例锁定了"非法值归零、px/纯数字保数值、rem 乘 16"三个契约,这是后续行高 HTML 反序列化时把 CSS 字符串规范化为数值的前提。
2.3BaseLineHeightPlugin:块级注入 + HTML 反序列化
源码位于 BaseLineHeightPlugin.ts,通过createSlatePlugin声明:
key: KEYS.lineHeight,inject.isBlock: true,targetPlugins: [KEYS.p]——只注入到段落块;nodeProps声明defaultNodeValue: 1.5、nodeKey: 'lineHeight';targetPluginToInject向目标插件注入 HTML deserializer:解析<p style="line-height: ...">的element.style.lineHeight为节点属性;- 最后
extendTransforms暴露绑定编辑器实例的editor.tf.lineHeight.setNodes(value, options)。
其 spec BaseLineHeightPlugin.spec.ts 验证了三件事:插件暴露的注入契约(isBlock、targetPlugins、nodeProps)正确;注入的 deserializer 能把style.lineHeight转成{[lineHeight type]: '2'};通过editor.tf.lineHeight.setNodes(2, { at: [] })应用、再传1.5清除,与setLineHeight共用同一变换。
2.4BaseFontColorPlugin:叶子标记 + 颜色反序列化
源码位于 BaseFontColorPlugin.ts:
nodeProps声明defaultNodeValue: 'black'、nodeKey: 'color';- HTML deserializer 标记
isLeaf: true,validStyle: { color: '*' },命中任意颜色样式时把element.style.color解析为叶子 mark; extendTransforms暴露editor.tf.color.addMark(value),内部转发到editor.tf.addMarks({ [KEYS.color]: value })。
其 spec BaseFontColorPlugin.spec.ts 验证了rgb(255, 0, 0)这类颜色字符串会被原样保留为KEYS.colormark,并确认addMark正确转发到编辑器级addMarks。这保证了从 HTML 粘贴带色文本时 mark 结构一致。
三、math 包的覆盖范围与源码原理
@platejs/math在 packages/math/src/lib 下实现公式节点模型与 KaTeX 渲染入口,React 交互层(packages/math/src/react)本次明确延后。被测目标共 5 个。
3.1insertEquation与insertInlineEquation:公式节点插入
两个变换均位于 packages/math/src/lib/transforms:
// insertEquation.ts editor.tf.insertNodes<TEquationElement>({ children: [{ text: '' }], texExpression: '', type: editor.getType(KEYS.equation), }, options);// insertInlineEquation.ts editor.tf.insertNodes<TEquationElement>({ children: [{ text: '' }], texExpression: texExpression ?? editor.api.string(editor.selection), type: editor.getType(KEYS.inlineEquation), }, options);差异点:块级insertEquation插入空表达式节点;行内insertInlineEquation若未显式传入texExpression,会用editor.api.string(editor.selection)把当前选区文本作为初始 TeX 表达式——这是"选中文本一键转行内公式"的典型入口。两个 spec(insertEquation.spec.ts、insertInlineEquation.spec.ts)分别断言了空表达式插入与选区文本回填两种路径。
3.2BaseEquationPlugin与BaseInlineEquationPlugin:节点类型与 HTML 导入
两个插件(BaseEquationPlugin.ts、BaseInlineEquationPlugin.ts)结构一致,差别在节点形态:
- 块级:
node: { isElement: true, isVoid: true }; - 行内:
node: { isElement: true, isInline: true, isVoid: true }; - 两者 HTML deserializer 都从
data-slate-tex-expression属性还原texExpression(用于复制/粘贴场景); - 均通过
.overrideEditor(withEquation)挂载withEquation.internal.ts中的编辑器覆写逻辑,并通过extendEditorTransforms暴露editor.tf.insert.equation(...)与editor.tf.insert.inlineEquation(...); - 块级插件额外
import 'katex/dist/katex.min.css',确保公式渲染样式随包自带。
withEquation的底层公式归一化逻辑见 withEquation.internal.ts,输入规则($$等触发符)见 MathRules.ts 与 inputRules.spec.tsx。
3.3getEquationHtml:KaTeX 服务端渲染出口
源码位于 getEquationHtml.ts:
export const getEquationHtml = ({ element, options, }: { element: TEquationElement; options?: KatexOptions; }) => katex.renderToString(getEquationExpression(element), options);- 直接调用 KaTeX 的
renderToString生成 HTML 字符串,可用于 SSR、导出或非浏览器环境; - 表达式经 getEquationExpression.internal.ts 归一化——该工具兼容早期 HTML 导入把数值/布尔属性存为原始类型的历史数据,统一
String(...)转换,无法识别时回退空串; options可透传KatexOptions(如displayMode、throwOnError),getEquationHtml.spec.ts 覆盖了表达式归一化与渲染调用。
四、验证计划:多命令组合的完整链路
原计划文档给出的验证步骤,是本次覆盖"通过"判定的唯一依据,逐条如下:
| 命令 | 作用 |
|---|---|
bun test(针对改动文件) | 快速验证本次新加的聚焦 spec |
bun test packages/basic-styles/src/lib packages/math/src/lib | 对两个包的全部 lib 目录做完整回归 |
pnpm test:profile -- --top 20 packages/basic-styles/src packages/math/src | 输出两个包最慢的 20 个用例,检查性能回归 |
pnpm test:slowest -- --top 20 packages/basic-styles/src packages/math/src | 聚焦最慢用例清单,识别拖慢 CI 的测试 |
pnpm install | 重新解析依赖锁文件,确保新 spec 依赖可用 |
pnpm turbo build --filter=./packages/basic-styles --filter=./packages/math | 构建两个目标包(Turbo 增量构建) |
pnpm turbo typecheck --filter=./packages/basic-styles --filter=./packages/math | 包级 TypeScript 类型检查 |
pnpm lint:fix | 修复 lint 问题,保持代码风格一致 |
这套组合覆盖了"测试运行 → 性能/慢用例体检 → 依赖解析 → 构建 → 类型 → 风格"六个维度,--filter=./packages/<name>把验证精确限制在改动包内,避免全仓构建拖慢反馈速度——这也是仓库内其他覆盖 pass 计划(如 2026-03-23-code-block-coverage-pass.md)共用的验证模板。
五、执行结果与覆盖收获
计划文档最终标记为status: completed,Result 确认:
- 为
setLineHeight、toUnitLess、BaseLineHeightPlugin、BaseFontColorPlugin补写了聚焦 spec; - 为
insertEquation、insertInlineEquation、BaseEquationPlugin、BaseInlineEquationPlugin、getEquationHtml补写了聚焦 spec; - 上述全部验证命令通过,包括定向
bun test、lib 目录全量bun test、test:profile/test:slowest体检、pnpm install、turbo build、turbo typecheck 与lint:fix。
六、延后项与后续建议
原计划明确声明本次不包含的内容,可作为后续覆盖工作的候选清单:
/react子目录(如 packages/math/src/react 下的useEquationInput、EquationPlugin等,相关交互 spec 已存在于 useEquationInput.spec.tsx);- basic-styles 的宽泛字体插件整体扫尾(
BaseFontFamilyPlugin、BaseFontSizePlugin、BaseFontWeightPlugin、BaseFontBackgroundColorPlugin等仍有独立 spec,可另行批量评估); - 额外的 KaTeX 行为矩阵(如错误表达式、不同
KatexOptions组合的渲染快照)。
若沿用本计划的方法论,可将pnpm test:profile/test:slowest的输出作为后续性能优化(见 editor-performance-master-plan.md)的输入,形成"覆盖 → 体检 → 优化"闭环。
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考