
Plate 测试基建迁移实战slate-history 全量迁入 Bun移除仓库最后一条 Mocha 测试通道【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate本文以 Plate 仓库中 2026-04-17-slate-v2-slate-history-full-bun-cutover.md 这一份已完成的迁移计划文档为主线完整拆解「slate-history 测试全量迁入 Bun」的目标、发现、实施方案与验证矩阵并结合当前仓库中 slate-history 的实际源码HistoryApi、withHistory插件与根级 Bun 测试基建bunfig.toml、bunTestSetup.ts、test-fast.mjs进行源码级佐证。读完本文你将掌握如何把一套依赖 Mocha 动态夹具的测试通道平滑迁移到 Bun 单测图test graph、如何处理「夹具入口同时承担测试引导」的双重职责问题、如何保留被有意跳过的夹具skip fixture语义以及如何用一套可复现的命令完成迁移后的全量验证。一、背景为什么 slate-history 是最后一条 Mocha 通道在 slate-v2 的测试基础设施演进中仓库测试经历了一个从 Jest/Mocha 混合向 Bun 单测统一收敛的过程。2026-04-16-slate-v2-bun-test-migration-plan.md 明确了总目标在可行范围内把测试全面迁离 Jest 与 Mocha默认目标运行器是bun test仅在 Bun 无法诚实承载的通道上回退到 Vitest例如 DOM 选择/焦点保真度要求高的slate-react通道而 Playwright 浏览器集成测试保持不变。在此之前slate、slate-hyperscript、slate-history三个夹具型fixture包都挂在同一条 Mocha 通道上它们通过support/fixtures.js这个共享的 Mocha 夹具加载器对成百上千个 hyperscript 夹具文件做同步发现、嵌套describe分组、require/动态导入和执行。随着slate-hyperscript先完成迁移见 2026-04-16-slate-v2-slate-hyperscript-bun-migration.mdslate也于次日完成全量切换见 2026-04-17-slate-v2-slate-full-bun-cutover.mdslate-history成为仓库中仅存的最后一条 Mocha 测试通道。本文档status: completed即已落地完成的目标非常明确把packages/slate-history/test全部迁移到 Bun并从仓库中彻底移除最后一条 Mocha 通道。说明本文档面向的是上游 slate-v2 仓库原始路径形如/Users/zbeyens/git/slate-v2/packages/slate-history。在当前 Plate 单体仓库中slate-history 的能力已被并入platejs/slate包位于 packages/slate/src/slate-history 目录测试也以.spec.tsx形式直接挂入bun test图见下文第四节与第五节与本文档描述的终态一致。二、核心发现迁移的三个关键判断文档的 Findings 部分给出了三个决定迁移方案形态的发现这也是理解整个迁移思路的钥匙1.test/index.js存在「双重职责」反模式packages/slate-history/test/index.js与旧版slate入口有完全相同的问题职责 A工具导出所有夹具文件通过它导入共享的 hyperscript 符号jsx职责 B套件引导它同时负责 bootstrap 整条 Mocha 测试套件。这种「既是被依赖的 helper又是套件入口」的结构导致任何想脱离 Mocha 直接运行单个夹具的尝试都会重新进入 Mocha 引导逻辑夹具无法在 Bun 下被安全、独立地加载。这一点在slate包的迁移中已被反复验证2026-04-17-slate-v2-slate-editor-above-bun-migration.md 明确记录“a Bun spec cannot safely import those fixtures untiljsxis split out”。迁移解法把test/index.js改造成纯工具导出模块pure helper export将套件引导职责彻底剥离。2. 该包只需要两组夹具与slate包庞大的夹具语料不同slate-history的夹具只有两类undo验证 undo/redo 行为包括插入文本、批量合并、选区恢复等isHistory验证HistoryApi.isHistory对历史对象结构的判别。夹具规模小意味着不需要复杂的按目录分桶策略一个包级 Bun 入口即可覆盖全部夹具。3. 一个包级 Bun 入口足以替代 Mocha 装载器由于夹具分组只有两组文档的结论是one package-local Bun entry is enough to replace the Mocha harness——无需保留动态夹具发现/加载器用一份一等公民first-class的 Bun spec 文件即可承担全部夹具执行。三、迁移方案四步完成切换文档给出的 Plan 是清晰的四步走把packages/slate-history/test/index.js转为纯工具导出模块剥离套件引导逻辑使其只承担夹具所需的 hyperscript 符号导出让 Bun spec 可以安全 import新增一个 Bun 套件入口用一份 package-local 的 Bun spec 覆盖整个slate-history夹具语料undoisHistory两组切换包级与根级脚本彻底移除 Mocha包括更新packages/slate-history/package.json的test脚本为bun test并清理根级test:mocha通道验证包构建、类型检查、lint、Bun 与根测试流确保迁移后整条链路全绿。这套顺序遵循了迁移计划文档中的通用纪律先让夹具可被 Bun 独立加载再切换脚本最后做全量验证绝不先把脚本切过去再回头修夹具。这与 2026-04-16-slate-v2-bun-test-migration-plan.md 中“证明 Bun 能先跑通代表性夹具smoke harness再逐包切脚本”的阶段划分一脉相承。四、仓库中的 slate-history 实现夹具到底在测什么要理解迁移的价值需要先看清slate-history模块本身。在当前仓库中它位于 packages/slate/src/slate-history由三部分构成。4.1HistoryApi历史对象的判别与批次控制history.ts 定义了History类型与HistoryApiHistory由redos与undos两个Batch[]组成每个Batch记录operations、selectionBefore、可选的selectionAfterHistoryApi.isHistory(value)通过isPlainObjectArray.isArray(value.redos/undos)OperationApi.isOperationList三重校验判别对象是否为合法的历史结构——这正是isHistory夹具组要覆盖的行为批次控制通过三个WeakMap实现SAVING、MERGING、SPLITTING_ONCE并暴露withMerging、withNewBatch、withoutMerging、withoutSaving四个上下文辅助方法控制“一批操作是否合并进上一条历史记录”“是否强制开启新批次”“是否完全不写入历史”。4.2withHistory插件undo/redo 的核心逻辑with-history.ts 是迁移后夹具真正压测的对象其关键路径包括初始化为编辑器挂载e.history { redos: [], undos: [] }apply拦截每个操作进来后先用isSaving()判断是否值得保存set_selection类操作被shouldSave直接排除不写历史再通过shouldMerge判断是否与上一条操作合并——连续insert_textop.offset prev.offset prev.text.length且路径相同与连续remove_text满足条件时合并为同一批次批次上限while (undos.length 100) undos.shift()历史栈最多保留 100 条批次undo取undos栈顶批次将操作经OperationApi.inverse求逆并reverse()后按序apply恢复selectionBefore再把整批移入redosredo取redos栈顶批次在withoutSavingwithoutNormalizing下重放全部操作恢复selectionAfter ?? selectionBefore写新操作时清空redoshistory.redos []保证撤销后新编辑不会产生“孤儿重做”额外处理setNodesBatch批量节点更新并在末尾调用syncLegacyMethods(e)同步旧版方法名e.undo/e.redo/e.tf.*。4.3 迁移后的测试形态一等公民 Bun spec当前仓库中 slate-history 的测试已是一等公民的 Bun specwith-history.spec.tsx 与 history.spec.tsx。它们使用platejs/test-utils提供的jsxthyperscript 工厂见 jsx.ts通过/** jsx jsxt */编译指令和editor、hp、cursor等标签声明初始文档树例如/** jsx jsxt */ import { jsxt } from platejs/test-utils; import { createEditor } from ../create-editor; import { withHistory } from ./with-history; const createHistoryEditor (value: any): any withHistory(createEditor(value)); it(batches contiguous insertText operations into one undo step, () { const editor createHistoryEditor( ( editor hp one cursor / /hp /editor ) as any ); editor.insertText(t); editor.insertText(w); editor.insertText(o); expect(editor.history.undos).toHaveLength(1); expect(editor.history.undos[0].operations).toHaveLength(3); editor.undo(); // 文档树恢复到 one选区回到 offset 3 });这个用例恰好对应undo夹具组中最核心的合并语义三次连续insertText只生成一条历史批次一次 undo 全部回退。测试文件不 import 任何bun:testAPI直接使用全局describe/it/expect——这正是迁移计划文档反复强调的纪律Bun 的 runner API 只允许出现在 setup 里见第五节。五、支撑迁移的根级 Bun 测试基建slate-history 能“一个 Bun 入口替代 Mocha 装载器”依赖的是仓库已经成型的一套根级 Bun 测试基建。5.1bunfig.toml单一根配置不做包级分叉根目录 bunfig.toml 是唯一的 Bun 测试配置源[test] # Preload scripts execute BEFORE any test file # Order matters: setup must come first for DOM globals preload [./tooling/config/bunTestSetup.ts] # Use test-specific tsconfig for path mappings (e.g., /registry/*) tsconfig ./tooling/config/tsconfig.test.json # Keep the inner loop quiet. Full pass spam is slower and useless. onlyFailures true三个关键点单一根preload所有测试文件执行前先加载 bunTestSetup.ts统一注入 DOM 与 runner 兼容层测试专用 tsconfigtsconfig.test.json 负责/registry/*、/components/*等路径映射保证 Bun 运行时解析与类型检查一致onlyFailures true默认只输出失败用例保持开发内循环安静——这对跑上千个夹具的包尤其重要。迁移计划文档中的约束是“只保留根级bunfig.toml不为每个包 fork 测试配置”这一约束在slate-hyperscript、slate、slate-history三个包的迁移中被一致遵守见 2026-04-16-slate-v2-slate-hyperscript-bun-migration.md 与 2026-04-17-slate-v2-slate-full-bun-cutover.md。5.2bunTestSetup.tsBun 测试的兼容层职责tooling/config/bunTestSetup.ts 承担了让“不 importbun:test的测试文件”能直接运行的全部脏活注册 Happy DOM 全局GlobalRegistrator.register(...)必须在任何用到document/window的代码之前执行并配置disableIframePageLoading、disableJavaScriptFileLoading等策略补齐 DOM 差异手动处理isContentEditable只读属性slate-test-utils需要写入该属性、显式注入DOMParser、创建document.body、设置compatMode扩展断言expect.extend(matchers)把testing-library/jest-dom的匹配器并入 Bun 的expect每用例清理afterEach(() cleanup())卸载已渲染的 React 组件防止用例间泄漏全局 mock 兼容把mock、spyOn挂到globalThis测试文件无需import { mock } from bun:testNode 兼容补丁注入TextEncoder、MessageChannel等运行时缺失的全局噪音过滤屏蔽 Vimeo 401、Happy DOM iframe/脚本加载失败等已知无害的 console 噪音。5.3test-fast.mjs根级 Bun 测试图的编排器根test脚本指向bun tooling/scripts/test-fast.mjs见根 package.json。该编排器从 test-suites.mjs 读取TEST_FILE_PATTERNSpackages/**/*.spec.{ts,tsx}等发现全部快速套件文件并具备两个关键能力mock.module隔离静态分析文件及其本地依赖链是否使用mock.module(...)若使用则单独隔离进程运行避免跨文件模块 mock 污染fileUsesMockModule递归解析本地 importJUnit 报告合并多批运行后把临时 XML 合并为单一testsuites报告。slate-history迁移后其上游布局中的test/*.spec.ts入口即自然落入该快速图与slate、slate-hyperscript的 Bun 通道汇合当前仓库中packages/slate/src/slate-history/*.spec.tsx同样由该图承载。5.4 迁移专用机制scoped preload transform 与跳过夹具保留迁移过程中有两个易踩坑的点在关联计划文档中有明确设计1legacy TSX 夹具的 JSX pragma 问题。旧夹具文件/** jsx jsx */ 本地jsx值在纯 Bun TSX 编译下不会走 slate-hyperscript 工厂路径夹具输出会变成 React 风格对象直接破坏编辑器变更与withHistory(...)。解法是包级作用域的 preload transform在 setup 中针对特定测试目录注入 stockslate-hyperscript工厂 import迁移后的夹具保持裸 TSX无 per-file pragma。slate包的above/edges切片就是通过共享的run-editor-fixtures.js与一条覆盖Editor/above/*.tsx、Editor/edges/*.tsx的 scoped preload 规则完成的见 2026-04-17-slate-v2-slate-editor-above-bun-migration.md。2跳过夹具语义必须保留。旧 Mocha 通道用this.skip()表达“已知失败/有意跳过”迁到 Bun 后不得把跳过夹具伪装成通过用例。设计是预读夹具源码若包含显式export const skip true则注册it.skip(...)否则注册普通异步it(...)。迁移计划文档还特别记录slate-history存在“一个有意跳过的夹具”one intentional skipped fixture迁移后它应作为 skipped 而非 passed 保留。六、验证矩阵迁移完成的标准动作文档的 Verification 部分给出了一组可复现的验证命令这是迁移「完成」的判定标准# 包级slate-history 测试跑 Bun上游布局中该包存在时的等价命令 pnpm --filter slate-history test # 包级构建与类型检查经 Turbo 过滤到单个包 pnpm turbo build --filter./packages/slate-history pnpm turbo typecheck --filter./packages/slate-history # 仓库级lint 自动修复 pnpm lint:fix # 仓库级全量类型检查与全量测试 pnpm typecheck pnpm test对应到当前 Plate 单体仓库slate-history 已并入platejs/slate等价命令为pnpm --filter slate test # 包级 bun testpackage.json 中 test 脚本经 plate-pkg p:test 落到 bun test pnpm turbo build --filter./packages/slate pnpm turbo typecheck --filter./packages/slate pnpm lint:fix pnpm typecheck pnpm test验证纪律有三条不许静默减少用例数No test count may be silently reduced不许抑制或删除测试Do not suppress or delete tests有意跳过的夹具保持 skipped而不是伪造通过。七、更大图景从 Mocha 全量退场到 Bun 硬切换把本次迁移放回时间线可以看到一条清晰的收敛路径阶段对象结果2026-04-16slate-hyperscript迁入 BunMocha 夹具库与动态装载器移除2026-04-16proof slice moved slice证明 Bun 可跑通非 DOM 逻辑测试与代表性夹具2026-04-17slate含Editor/above、Editor/edges切片全量迁入 Bun2026-04-17slate-history本文档最后一条 Mocha 通道关闭Mocha 从仓库移除2026-04-18根工作流pnpm硬切换为 Bun见 2026-04-18-bun-hard-cut.md其中 2026-04-18-bun-hard-cut.md 进一步规定删除pnpm表面无兼容别名、保留唯一根bunfig.toml、不留任何回弹进pnpm的僵尸脚本。当前仓库根 package.json 的脚本表中已无test:mocha/test:jest字样test直接走bun tooling/scripts/test-fast.mjstest:all、test:slow、test:coverage全部由 Bun 承载与“Mocha 完全退场”的终态一致。八、经验总结这类夹具型测试迁移的可复用清单从本文档及其关联计划中可以提炼出一套可复用的迁移方法论先分离双重职责入口任何“既被夹具 import、又引导测试套件”的入口文件必须先拆成纯 helper 导出否则 Bun spec 无法安全加载夹具用第一份 smoke spec 验证假设先跑通一个普通.ts用例、一个.tsxhyperscript 夹具、一个 skipped 夹具再动包级脚本夹具发现要同步、分组要保留、跳过要显式用 Bun 全局it注册skip true映射为it.skip绝不伪造通过JSX pragma 问题用 scoped preload 解决一次不为每个夹具改文件而是注入 stockslate-hyperscript工厂 importrunner API 不进测试文件bun:test的 import 只允许出现在 setup测试文件只依赖全局describe/it/expect/mock脚本切换晚于夹具可运行先让夹具在 Bun 下绿再改package.json的test脚本最后清理根级旧通道验证命令标准化包级 test turbo build/typecheck 根级 lint/typecheck/test 全量跑绿才算完成且用例数不得减少。这套方法论不仅适用于 slate 系夹具包也适用于任何“Mocha 动态夹具装载器 大量 hyperscript/JSX 夹具”的存量测试通道迁移。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考