FEATURED · 精选文章

Plate 仓库 Bun 全包图测试套件健康修复实录:从误导性报错到测试污染根因治理

发布时间 / 2026/9/15 19:16:25
来源 / 创域科博编辑部
栏目 / 资讯中心
Plate 仓库 Bun 全包图测试套件健康修复实录:从误导性报错到测试污染根因治理 Plate 仓库 Bun 全包图测试套件健康修复实录从误导性报错到测试污染根因治理【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate本篇技术指南以 Plate 仓库Rich-text editor with AI and shadcn/ui中 Broad Bun Suite Health Fix 计划 为骨架完整复盘一次全仓 Bun 包图测试扫描broad Bun package-graph sweep从变红到转绿的完整排查过程。读者将掌握如何识别单测单独通过、组合运行时失败的测试污染test pollution问题、为什么共享包入口如platejs、platejs/react、lib/index.tsbarrel不能做局部模块替换式 mock、以及如何在 React 规格中静默掉有意的警告路径而保持运行时行为完整。背景broad Bun package-graph sweep 是什么Plate 仓库在 2026-03-25 之前完成了多轮非 React 覆盖率补齐non-React coverage passes。这些工作之后需要重新运行一次覆盖多个包的全包图扫描以确认整个依赖图是健康的。这次扫描以 Bun 作为测试运行器一次性将packages/link、packages/markdown、packages/core、packages/autoformat、packages/basic-nodes、packages/table、packages/list-classic、packages/suggestion等目录下的全部规格纳入同一个测试进程中执行bun test packages/link/src packages/markdown/src packages/core/src \ packages/autoformat/src packages/basic-nodes/src packages/table/src \ packages/list-classic/src packages/suggestion/src与逐文件运行相比包图扫描的价值在于它模拟了真实 CI 与真实消费者在同一进程中加载多个包的状态能暴露仅在模块加载顺序、共享模块状态叠加时才出现的跨文件问题。本次扫描变红的根因恰恰就在这里——问题不在产品代码而在测试自身的污染。误导性症状editor.tf在 SlateExtensionPlugin 中 undefined宽扫描的首次失败表象非常像一次真实的产品回归在 SlateExtensionPlugin.ts 内部出现editor.tf为 undefined 的报错。从该文件的实现看editor.tf在插件扩展编辑器的 transforms 时是核心依赖.extendEditorTransforms(({ editor, getOption, tf }) { const apply tf?.apply ?? editor.tf.apply; // ... })这里甚至专门做了防御性回退tf?.apply ?? editor.tf.apply说明editor.tf正常情况下必然存在。因此当它在扫描中出现 undefined 时第一反应是某个产品文件把编辑器的 transforms 弄丢了——但文档明确记载这个症状具有误导性misleading产品代码并非根因。关键转折点是下面的组合复现结论getLinkAttributes.spec.ts 单独运行完全通过只有当它与 floatingLinkTriggers.spec.ts 配对运行时才失败。单测通过、配对失败是测试污染最典型的指纹某个测试在进程生命周期内篡改了共享模块的导出导致随后按模块加载顺序执行的、看似毫不相关的规格读取到了被替换后的假模块表面fake module surface。于是排查方向从随机产品文件收敛为第一个真实共享根因first real shared root cause。第一颗毒丸floatingLinkTriggers 整模块替换platejsfloatingLinkTriggers.spec.ts 的修复前实现是为了伪造getEditorPlugin一个函数却把整个platejs入口模块替换成了部分 mock 表面。这在 Bun 的模块系统中意味着一旦该测试在本进程内执行后续任何通过platejs导入getEditorPlugin、createSlateEditor等真实导出的规格都会读到被替换后的残缺模块。修复后的版本展示了正确做法——局部 spy 而非整模块替换import * as platejs from platejs; const getEditorPluginMock mock(); // 只对完全局部的模块做 mock.module不会泄漏到无关规格 mock.module(../LinkPlugin, () ({ LinkPlugin: { key: link }, })); describe(floating link triggers, () { beforeEach(() { spyOn(platejs, getEditorPlugin).mockImplementation( getEditorPluginMock as any ); getEditorPluginMock.mockReset(); }); afterEach(() { mock.restore(); }); it(opens insert mode with selected text when the current selection is eligible, async () { const { triggerFloatingLinkInsert } await import( ./triggerFloatingLinkInsert?test${Math.random().toString(36).slice(2)} ); // ... getEditorPluginMock.mockReturnValue({ api: { floatingLink: { show } }, getOptions: () ({ mode: }), setOption, type: a, }); // 断言 setOption(text, ...) / show(insert, editor-id) 等 }); });要点拆解spyOn(platejs, getEditorPlugin)直接挂在真实模块命名空间上只替换测试需要的那一个导出platejs的其余真实导出原样保留mock.module(...)仅保留给../LinkPlugin这类完全局部的模块——替换它不会影响其他无关规格通过?test${Math.random()...}查询参数动态 import确保每个用例拿到全新模块实例避免跨用例状态残留afterEach中mock.restore()把 spy 全部还原。第二颗毒丸useTableMergeState 用部分 mock 表面替换整个../../libbarrel第二个问题出在表格包useTableMergeState.spec.tsx 曾把整个../../libbarrel即 packages/table/src/lib/index.ts替换成一个只包含极少数导出的部分 mock 表面。任何后续从该 barrel 导入其他工具函数例如单元格边界计算、选区解析等的规格都会得到 missing export 的崩溃——这正是配套解决方案文档中记录的missing exports frompackages/table/src/lib/index.ts症状的来源。修复后该规格改为对platejs/react与../../lib的真实导出分别打点 spyimport { renderHook } from testing-library/react; import * as platejsReact from platejs/react; import * as tableLib from ../../lib; const useEditorPluginMock mock(); const useEditorSelectorMock mock(); const useReadOnlyMock mock(); const getSelectedCellEntriesMock mock(); const getSelectedCellsBoundingBoxMock mock(); // 仅局部模块仍使用 mock.module mock.module(../TablePlugin, () ({ TablePlugin: { key: table }, })); describe(useTableMergeState, () { beforeEach(() { spyOn(platejsReact, useEditorPlugin).mockImplementation(useEditorPluginMock as any); spyOn(platejsReact, useEditorSelector).mockImplementation(useEditorSelectorMock as any); spyOn(platejsReact, useReadOnly).mockImplementation(useReadOnlyMock as any); spyOn(tableLib, getSelectedCellEntries).mockImplementation(getSelectedCellEntriesMock as any); spyOn(tableLib, getSelectedCellsBoundingBox).mockImplementation(getSelectedCellsBoundingBoxMock as any); // 全部 mockReset }); afterEach(() { mock.restore(); }); it(computes merge state for rectangular multi-cell selections and split state for merged cells, async () { const { useTableMergeState } await import( ./useTableMergeState?test${Math.random().toString(36).slice(2)} ); // mock 返回值useReadOnlyfalse、选区条目、包围盒、disableMerge 选项 const { result } renderHook(() useTableMergeState()); expect(result.current).toEqual({ canMerge: true, canSplit: false }); }); });这个版本的核心思想与第一处修复一致测试只需要 5 个函数就只 spy 这 5 个导出platejs/react与../../lib上其余的真实导出保持完好不会毒化同进程内的其他规格。这也是配套解决方案文档给出的硬性规则If a Bun spec touches a shared package entrypoint or barrel likeplatejs,platejs/react, or a packagelib/index.ts, default to local spies on real exports. Partialmock.module(...)replacements on shared surfaces are suite-pollution bait.见 共享包 mock 应为局部 spy 而非部分 Bun 模块替换。噪音清理useElementStore 的USE_ELEMENT_CONTEXT警告墙前两处修复解决了失败第三处修复解决的是噪音。宽扫描虽然转绿但 useElementStore.spec.tsx 会在输出中刷出一整屏USE_ELEMENT_CONTEXT警告。这些警告是有意的该规格专门渲染了位于ElementProvider之外的消费者组件来验证降级行为如返回空对象、回退到最近 provider path等用例运行时本就应该发出未在元素上下文中使用的警告。真正的噪音来源是测试脚手架harness用了默认的 debug logger把console.warn直接打到了测试输出里。先看运行时的警告来源。USE_ELEMENT_CONTEXT这个错误类型定义在 DebugPlugin.tsexport type DebugErrorType | (string {}) | DEFAULT | OPTION_UNDEFINED | OVERRIDE_MISSING | PLUGIN_DEPENDENCY_MISSING | PLUGIN_MISSING | USE_CREATE_PLUGIN | USE_ELEMENT_CONTEXT;并在 useElement.ts 与 usePath.ts 中被抛出// useElement.ts / usePath.ts 中的警告路径 USE_ELEMENT_CONTEXT而默认 logger 在 DebugPlugin 的 options 里直接绑定console.warnoptions: { logger: { // ... warn: (message, type, details) console.warn(${type ? [${type}] : }${message}, details), }, // ... }修复方式是在规格的测试编辑器中覆盖 DebugPlugin 的 logger让警告路径的覆盖保持执行、但输出保持安静const PlateWrapper ({ children }: { children: React.ReactNode }) { const editor createPlateEditor({ plugins: [ DebugPlugin.configure({ options: { logger: { warn: () {} } as any, throwErrors: false, }, }), ], }); return Plate editor{editor}{children}/Plate; };关键设计取舍只替换 logger 的warn不全局静音——throwErrors: false保证警告不抛错warn: () {}让预期的警告不刷屏运行时警告路径完整保留——useElement/usePath仍然会调用debug.warn警告分支代码照常被覆盖只是输出被截获配套规则明确不要全局静音运行时见 警告路径 React 规格应在 harness 中捕获 debug.warn。可复现验证矩阵与构建前置门禁文档为每一步修复都给出了可独立复现的命令这些命令也是回归回归时最值得保留的回归探针验证目标命令复现组合link 双规格污染bun test packages/link/src/react/utils/getLinkAttributes.spec.ts packages/link/src/react/utils/floatingLinkTriggers.spec.ts表格双规格配对bun test packages/table/src/react/components/TableCellElement/setSelectedCellsBorder.spec.tsx packages/table/src/react/hooks/useTableMergeState.spec.tsx表格包切片bun test packages/table/src宽扫描8 包全包图bun test packages/link/src packages/markdown/src packages/core/src packages/autoformat/src packages/basic-nodes/src packages/table/src packages/list-classic/src packages/suggestion/src噪音清理单测bun test packages/core/src/react/stores/element/useElementStore.spec.tsx这里有一条可复用的方法论把单独通过 配对失败的最小组合固化成一条命令。getLinkAttributes.spec.tsfloatingLinkTriggers.spec.ts的组合就是这条最小复现路径——它比整个宽扫描更快、更聚焦未来任何回归都能先用它做二分定位。在测试转绿之后计划还要求对受影响的包执行构建优先门禁build-first verification确认测试通过不代表产物构建被破坏pnpm install pnpm turbo build --filter./packages/link --filter./packages/table pnpm turbo typecheck --concurrency1 --filter./packages/link --filter./packages/table pnpm lint:fix其中typecheck --concurrency1串行执行是为了在类型检查阶段排除并发资源竞争lint:fix统一格式化新增的 spy 代码。这一整套测试 → 构建 → 类型检查 → lint的闭环保证了修复没有把问题从测试层搬到构建层。经验沉淀两条可持续的测试卫生规则规则一共享包 mock 必须是局部 spy凡是规格会触达共享包入口或 barrelplatejs、platejs/react、某个包的lib/index.ts一律默认对真实导出做局部spyOnmock.module(...)只允许用于完全局部的模块。理由在解决方案文档中写得很直白Partialmock.module(...)replacements on shared surfaces are suite-pollution bait.这是因为 Bun 的mock.module会替换整个模块的解析结果其影响范围是进程级的、与加载顺序相关的而spyOn只改动命名空间上单个属性的读取行为并且可被mock.restore()彻底还原。从仓库现状看mock.module的用法仍广泛存在于各包规格中如 ai、combobox、emoji、excalidraw、dnd 等包的 spec因此这条规则的实际意义是在共享表面上一律升级为 spy把mock.module的适用范围收窄到局部模块。规则二警告路径规格必须在 harness 里截获 debug.warn如果 React 规格有意命中警告路径例如渲染 Provider 外的消费者就在规格 harness 中通过DebugPlugin.configure({ options: { logger: { warn: () {} } } })覆盖该测试编辑器的 logger。不要全局静音运行时也不要把预期警告留在宽扫描输出里。这样既保住了警告分支的代码覆盖率又保证了全包图扫描的日志可读性。结论这次 Broad Bun Suite Health Fix 是一次典型的测试基础设施去伪存真案例宽扫描红了的表象editor.tfundefined指向产品代码但通过单测通过、配对失败的指纹确认了真正根因是测试污染两个共享表面上的整模块替换式 mockplatejs、../../lib是两颗毒丸修复为局部 spy 后跨文件污染消失一处有意的警告路径噪音通过 harness 内覆盖 DebugPlugin logger 得到静音修复以复现组合 → 包切片 → 宽扫描 → 构建/类型/lint的分层验证闭环收尾并沉淀出两条可复用的测试卫生规则详见 共享包 mock 解决方案 与 警告路径解决方案。对于任何使用 Bun 作为测试运行器的多包仓库本文的两条规则与配对复现命令方法论均可直接迁移当共享入口被某个测试部分替换随机且看似无关的失败就会在包图扫描中周期性爆发——先把测试面修诚实再让产品代码接受审判。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻