拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Plate Toc 包覆盖率冲刺:getHeadingList / insertToc / isHeading / BaseTocPlugin 的测试与验证实战

Plate Toc 包覆盖率冲刺getHeadingList / insertToc / isHeading / BaseTocPlugin 的测试与验证实战【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate导读本文基于仓库中《Toc Coverage Pass》测试冲刺计划见 docs/plans/2026-03-23-toc-coverage-pass.md完整还原platejs/tocTable of Contents包中四个核心非 React 接缝——getHeadingList、insertToc、isHeading与BaseTocPlugin——的源码实现、测试覆盖策略与全链路验证命令。读完本文你将掌握 Plate 目录功能底层 API 的职责边界、它们的测试写法以及如何用bun testturbo build/typecheck lint 组合完成一次小而完整的覆盖率冲刺。一、冲刺目标聚焦 Toc 包内唯一的非 React 接缝该计划的目标非常明确对platejs/toc包做一次微小tiny的、围绕包内唯一真实接缝的非 React 覆盖率冲刺。所谓接缝seam指的是 Toc 功能在纯数据/逻辑层对外暴露的 API 边界——它们不依赖 React、不依赖 DOM可以在 Node 环境直接以createSlateEditor实例化验证。计划把范围严格收敛到四个入口接缝文件位置职责getHeadingListpackages/toc/src/internal/getHeadingList.ts遍历编辑器产出标题列表insertTocpackages/toc/src/lib/transforms/insertToc.ts向文档插入一个 TOC 节点isHeadingpackages/toc/src/lib/utils/isHeading.ts判断节点是否为标题节点BaseTocPluginpackages/toc/src/lib/BaseTocPlugin.ts定义 toc 元素类型、配置与默认选项明确推迟Explicit Deferrals计划同时声明了本次不做的范围保证冲刺不过度膨胀/react即 packages/toc/src/react 目录包含TocPlugin.tsx与useContentController、useContentObserver、useTocController、useTocObserver、useTocSideBar、useTocElement等 hooksscroll observer hooks滚动监听相关逻辑sidebar/controller 逻辑侧边栏与控制器 UI 逻辑。从源码结构看packages/toc/src 天然划分为internal、lib含transforms、utils、react三个层次本次冲刺只覆盖前两层react层留给后续专项。二、核心接缝源码逐行拆解2.1isHeading最底层的类型判定// packages/toc/src/lib/utils/isHeading.ts import { type TNode, KEYS } from platejs; export const isHeading (node: TNode) node.type KEYS.heading.includes(node.type as any);这是四个接缝中最薄的一个通过KEYS.headingPlate 核心导出的一组标题类型键通常为h1h6判断节点type是否属于标题。它的两个关键行为对type缺失的节点返回 falsynode.type为undefined时短路不会调用includes对非标题类型返回false如段落类型KEYS.p。getHeadingList正是以它作为节点遍历的match谓词因此它是整个标题收集管线的判定基座。2.2getHeadingList标题收集主流程// packages/toc/src/internal/getHeadingList.ts const headingDepth: Recordstring, number { h1: 1, h2: 2, h3: 3, h4: 4, h5: 5, h6: 6, }; export const getHeadingList (editor: SlateEditor) { const options editor.getOptions(BaseTocPlugin); if (options.queryHeading) { return options.queryHeading(editor); } const headingList: Heading[] []; const values editor.api.nodesTElement({ at: [], match: (n) isHeading(n), }); if (!values) return []; for (const [node, path] of values) { const { type } node; const title NodeApi.string(node); const depth headingDepth[type]; const id node.id as string; if (title) { headingList.push({ id, depth, path, title, type }); } } return headingList; };流程可分为三步读取插件配置editor.getOptions(BaseTocPlugin)取到queryHeading覆盖函数。若用户配置了queryHeading直接以编辑器为参数调用并返回其结果——这是为自定义标题来源例如只收集特定范围、或接入外部大纲预留的扩展点。遍历标题节点editor.api.nodes({ at: [], match: isHeading })从根路径[]开始遍历所有满足isHeading的节点得到[node, path]迭代对。组装Heading对象对每个标题节点通过NodeApi.string(node)提取纯文本标题依据headingDepth映射表h1→1 …h6→6计算层级深度并读取节点id。空标题节点会被if (title)过滤掉——这是 Toc 列表质量的关键保障。产出的Heading类型定义于 packages/toc/src/lib/types.tsimport type { Path } from platejs; export type Heading { id: string; depth: number; path: Path; title: string; type: string; };path直接引用 Slate 的Path类型供上层跳转/高亮逻辑精确定位标题位置。2.3insertToc向文档插入 TOC 节点// packages/toc/src/lib/transforms/insertToc.ts import type { InsertNodesOptions, SlateEditor, TElement } from platejs; import { KEYS } from platejs; export const insertToc ( editor: SlateEditor, options?: InsertNodesOptions ) { editor.tf.insertNodesTElement( { children: [{ text: }], type: editor.getType(KEYS.toc), }, options as any ); };节点形状插入的 TOC 节点包含一个空的文本子节点{ text: }满足 void 元素的最小内容约束type通过editor.getType(KEYS.toc)解析——这保证了当用户把 toc 节点类型重命名为自定义类型时插入的节点类型也能正确跟随位置可定制第二个参数InsertNodesOptions直接透传给editor.tf.insertNodes调用方可通过{ at: [1] }指定插入位置。2.4BaseTocPlugin插件配置与默认值// packages/toc/src/lib/BaseTocPlugin.ts export type TocConfig PluginConfig toc, { isScroll: boolean; topOffset: number; queryHeading?: (editor: SlateEditor) Heading[]; } ; export const BaseTocPlugin createTSlatePluginTocConfig({ key: KEYS.toc, node: { isElement: true, isVoid: true }, options: { isScroll: true, topOffset: 80, }, });配置项说明配置项类型默认值含义isScrollbooleantrue是否启用滚动联动供上层滚动观察逻辑消费topOffsetnumber80滚动定位的顶部偏移像素数queryHeading(editor) Heading[]未配置自定义标题收集函数覆盖getHeadingList默认逻辑node: { isElement: true, isVoid: true }声明 toc 为块级 void 元素——这是后续所有删除/回车/Tab 行为测试的基础假设。三、测试覆盖四个接缝的规范文件Specs计划的 Result 部分确认added focusedtocspecs forgetHeadingList,insertToc,isHeading, andBaseTocPlugin。这些 spec 均使用createSlateEditor在无 DOM 环境构建编辑器是典型的数据层测试范式。3.1getHeadingList.spec.ts见 packages/toc/src/internal/getHeadingList.spec.ts覆盖两个场景场景 A只收集有标题文本的标题节点构造编辑器包含h1Title、空文本h2、普通段落p、h3Section四种节点断言结果只包含两条expect(getHeadingList(editor)).toEqual([ { depth: 1, id: a, path: [0], title: Title, type: h1 }, { depth: 3, id: b, path: [3], title: Section, type: h3 }, ]);这同时验证了三个行为空标题被过滤空h2不在结果中、非标题被排除段落p不在结果中、path 正确反映原始索引h3位于索引 3。场景 BqueryHeading覆盖生效通过BaseTocPlugin.configure({ options: { queryHeading } })注入自定义收集函数断言getHeadingList直接返回覆盖结果即使文档中真实存在h1并验证queryHeading被以编辑器为参数调用toHaveBeenCalledWith(editor)。3.2insertToc.spec.ts见 packages/toc/src/lib/transforms/insertToc.spec.ts默认节点形状insertToc(editor, { at: [1] })后editor.children第二项应为{ children: [{ text: }], type: KEYS.toc }尊重自定义类型BaseTocPlugin.configure({ node: { type: custom-toc } })后插入节点type变为custom-toc——验证了editor.getType(KEYS.toc)的类型解析链路。3.3isHeading.spec.ts见 packages/toc/src/lib/utils/isHeading.spec.ts采用it.each(KEYS.heading)参数化用例对KEYS.heading中每个标题类型断言返回true反向用例断言段落类型与无type节点返回 falsy。3.4BaseTocPlugin.spec.ts见 packages/toc/src/lib/BaseTocPlugin.spec.ts这是四个 spec 中行为最丰富的一个围绕toc 是 void 元素这一核心属性用editor.tf.*变换验证编辑语义用例操作断言默认配置editor.getPlugin(BaseTocPlugin)key KEYS.tocnode为{ isElement: true, isVoid: true }options为{ isScroll: true, topOffset: 80 }前向删除光标在 toc 内deleteForward(character)删除整个 toc 块后续段落前移选区稳定后向删除光标在 toc 后一段deleteBackward(character)不穿墙删除 toc而是把选区移到 toc 上path: [0, 0]行移动move({ reverse: true, unit: line })选区落在 toc 上而非其空文本子节点回车toc 选中态insertBreak()不在 toc 内部创建文本文档结构不变Tabeditor.tf.tab({ reverse: false })返回false不进入 toc 文本这些用例本质上是在验证 void 元素与 Slate 变换的交互边界是目录块不能被当作普通段落编辑这一产品行为的底层保障。四、验证计划从单测到构建的完整命令链计划的 Verification Plan 列出 8 步验证命令构成一次从受影响文件到全量构建的递进验证链# 1. 只跑被改动 spec 的定向测试快速反馈 bun test packages/toc/src/internal/getHeadingList.spec.ts # 2. 跑整个 toc 包的非 React 测试 bun test packages/toc/src # 3. 测试性能画像找出最慢的 20 个用例防止新测试拖慢套件 pnpm test:profile -- --top 20 packages/toc/src # 4. 慢用例榜单同样输出 top 20交叉核对 pnpm test:slowest -- --top 20 packages/toc/src # 5. 同步依赖锁文件 pnpm install # 6. 只构建 toc 包turbo 过滤语法 pnpm turbo build --filter./packages/toc # 7. 只对 toc 包做类型检查 pnpm turbo typecheck --filter./packages/toc # 8. 自动修复 lint pnpm lint:fix要点解读--filter./packages/toc是 turbo 的路径过滤语法只对目标包执行 build/typecheck避免全仓构建test:profile与test:slowest组合用于守住性能基线覆盖率冲刺不应以牺牲测试速度为代价--top 20让最慢用例浮出水面bun test而非 jest当前仓库测试运行器为 Bunbun test packages/toc/src会递归执行src下所有*.spec.ts。计划 Result 声明以上 8 步全部通过verification passed冲刺状态为completed见文档 frontmatterstatus: completedtype: testingdate: 2026-03-23。五、可复现的实验路径如果你要在本地复现本次冲刺建议按以下顺序操作阅读四个核心源文件建立对接缝职责的认知packages/toc/src/lib/utils/isHeading.tspackages/toc/src/internal/getHeadingList.tspackages/toc/src/lib/transforms/insertToc.tspackages/toc/src/lib/BaseTocPlugin.ts对照四个 spec 文件理解每个断言的意图上一节已逐一给出路径依次执行第四节命令链观察bun test的通过情况、profile/slowest 榜单以及turbo build/typecheck的输出若需扩展注意计划明示的边界/react、scroll observer hooks、sidebar/controller 逻辑不在本次范围内改动时不要误触。六、结语一次小而完整的测试冲刺模板从本计划可以看到一种可复用的测试冲刺方法论先框定包内唯一的真实接缝 → 明示推迟范围防膨胀 → 为每个接缝写聚焦 spec → 用递进式命令链验证定向单测 → 包内全量 → 性能画像 → 构建/类型检查 → lint。platejs/toc包在此次冲刺后getHeadingList、insertToc、isHeading、BaseTocPlugin四个非 React 接缝均具备可回归的测试保障为后续react层滚动观察、侧边栏控制器的专项覆盖打下了干净的地基。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门