Plate 测试收割器之 Portable Text Source Routing 全解:200 个外部测试文件如何路由到 Slate v2 行为家族
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
本文以 docs/editor-test-harvester/portabletext/source-routing.md 为骨架,结合 inventory.md、test-index.md 与 2026-05-29 收割计划,完整解读 Plate 仓库中"编辑器测试收割器(editor-test-harvester)"对 Portable Text 测试资产的家族路由(source routing)机制。读者将掌握路由表 8 列字段的含义、11 个行为家族与 2 个产品策略家族的划分依据、四类动作(refactor-existing / create-new / split / plate-owned)的决策逻辑,以及
bun test与 Playwright 验证命令的正确用法。
一、这份文档在收割流程中的位置
source-routing.md 不是一篇孤立的表格,而是 Plate 仓库"编辑器测试收割"体系四件套产物之一。收割器(editor-test-harvester)的工作流由 docs/plans/templates/editor-test-harvester.md 模板约束,其完整流程在 2026-05-29-portable-text-editor-test-harvest.md 中记录为九个阶段:Intake and boundary → Inventory → Test-name extraction → Classification pressure → Behavior extraction → Slate/Plate coverage mapping → Action planning → Ecosystem synthesis → Closure review。
四件产物各司其职:
| 产物 | 路径 | 内容 |
|---|---|---|
| report | docs/editor-test-harvester/portabletext/report.md | 行为/产品家族矩阵、归属判定、Next slice 交接 |
| inventory | docs/editor-test-harvester/portabletext/inventory.md | 502 行完整清单与逐文件分类(Runnable / Category / Family / Reason) |
| test-index | docs/editor-test-harvester/portabletext/test-index.md | 200 个可运行文件的 1944 个测试名/场景名锚点 |
| source-routing | docs/editor-test-harvester/portabletext/source-routing.md | 本文主体:200 个 portable / portable-mixed 文件的家族路由表 |
关键前提:路由表里的Source列(如packages/editor/gherkin-tests/delete.test.ts)指向的是被收割的外部 Portable Text 仓库(目标../portabletext,MIT 许可),并非当前 plate 仓库内的文件。当前仓库内的packages/目录不包含editor包,因此这些 Source 路径不能直接在当前仓库解析——它们是"待迁移/待对齐"的行为来源。这正是路由表存在的意义:把这些外部测试文件的行为不变量(behavior invariant)逐条归入本仓库 Slate v2(.tmp/slate-v2工作副本)或 Plate 产品的责任范围。
二、路由表 8 列字段语义
路由表的表头固定为 8 列,每一列对应收割决策链上的一环:
| 列 | 含义 | 说明 |
|---|---|---|
# | 行号 | 1~200,与 test-index.md 的序号一致,便于交叉引用 |
Source | 外部仓库内的测试文件路径 | 相对于被收割仓库../portabletext |
Category | 类别 | 二值:portable(纯可移植行为)或portable-mixed(行为与产品策略混合) |
Family | 家族编号 | PT-H01~PT-H12行为家族、PT-P01/PT-P02产品策略家族 |
Family title | 家族标题 | 一句话描述该家族覆盖的行为面 |
Action | 建议动作 | refactor-existing/create-new/split/covered/plate-owned等 |
Target owner | 目标归属 | .tmp/slate-v2下的测试文件、Plate 序列化器包、或 backlog 项 |
Verification / backlog command | 验证命令或待办 | 可复制的bun test/ Playwright 命令,或指向 Plate 产品待办 |
表头之上的元信息(Source、Generated、行数)用于可追溯性:"Portable and portable-mixed files routed: 200" 是本次收割的硬性验收指标之一——验收门槛要求每一个可运行的 portable / portable-mixed 文件都被路由到某个行为家族(见计划文档 Completion threshold)。
三、类别:portable 与 portable-mixed 的分界线
路由表第一道分类是Category,它决定了一个测试文件的行为到底属于"原始编辑器内核(raw Slate)"还是"产品策略(Plate)"。依据 inventory.md 的统计,200 个被路由文件中:portable 94 个、portable-mixed 106 个(该清单同时给出全部 502 行的分类账:portable 94、portable-mixed 106、plate-owned 100、skip 14、harness 188、product-shell 0、uncertain 0)。
- portable:该文件承载的是可移植的编辑行为不变量——删除、插入、选择投影、void 对象边界、历史与协同等。这些不变量应当下沉到 Slate v2 内核测试中,与 Portable Text 的 schema / API 形态无关。典型如
packages/editor/gherkin-tests/delete.test.ts(PT-H05)、packages/editor/src/engine/path/compare-paths.test.ts(PT-H11)。 - portable-mixed:文件同时压测"原始行为"与"产品策略",需要拆分。inventory.md 为每类混排行给出了具体理由,例如:
paste.test.ts→ PT-H06,"raw fragment clipboard behavior mixed with serializer/matcher policy"(剪贴板原始片段行为混入了序列化器/匹配器策略);event.drag.drop.test.tsx→ PT-H07,"native drag/drop substrate mixed with product drag handles and schema objects"(原生拖放底子混入了产品拖拽手柄与 schema 对象);behavior-api.test.tsx→ PT-H12,"raw event/command pressure mixed with Plate plugin or renderer API policy"。
拆分原则(计划文档 Findings 与 Decisions 的原话转述):保持 raw Slate 无观点(不导入 Portable Text schema 或行为 API);剪贴板原始片段/选择行为归 Slate,序列化器与 matcher 归 Plate;列表、序列化、mark、annotation、渲染器、工具栏、插件类行一律路由给 Plate。
四、行为家族全景:11 + 2 个家族
路由表内实际出现的行为家族为 11 个(PT-H01~PT-H09、PT-H11、PT-H12;PT-H10 focus/keyboard 在 report 矩阵中定义,但本批次 200 行中没有路由到它的文件)。另有 PT-P01 / PT-P02 两个产品策略家族,主要承载 plate-owned 行的归属(inventory.md 中约 100 行为 plate-owned)。以下为各家族的标题、含义与代表行:
| 家族 | 标题 | 覆盖的行为面 | 路由表中的代表行 |
|---|---|---|---|
| PT-H01 | Selection boundary and projection | 选择边界与投影:range 悬挂、选中端点、根/对象边界 | gherkin-tests/selection.test.ts、engine/point/transform-point.test.ts、selectors/selector.get-selection-text.test.ts、tests/to-engine-range.test.tsx |
| PT-H02 | Content roots and containers | 内容根与容器:可编辑嵌套根、容器内规范化、渲染 | src/schema/resolve-containers.test.ts、tests/container-normalization.test.tsx、tests/container-rendering.test.tsx |
| PT-H03 | Void and object boundary editing | void/对象边界编辑:孤立图片、行内对象、spacer | gherkin-tests/block-objects.test.ts、engine/node/is-void-node.test.ts、tests/inline-objects.test.tsx |
| PT-H04 | Insert, split, and break placement | 插入/拆分/换行落点:placement 矩阵、fragment 插入 | gherkin-tests/insert.block.test.ts、tests/event.insert.block.test.tsx(38 个场景)、utils/util.block-offset.test.ts |
| PT-H05 | Delete and backspace matrix | 删除/退格矩阵:合并块、边界、词/行删除 | gherkin-tests/removing-blocks.test.ts、tests/event.delete.matrix.test.tsx、tests/event.delete.backward.test.tsx |
| PT-H06 | Clipboard and fragment serialization | 剪贴板与 fragment 序列化:粘贴、HTML 反序列化 | gherkin-tests/paste.test.ts、tests/event.paste.test.tsx、tests/upload-images-on-paste.test.tsx |
| PT-H07 | Drag/drop and drop target resolution | 拖放与落点解析:self-drop 抑制、跨容器移动 | src/editor/resolve-element-drop-position.test.ts、tests/event.drag.drop.test.tsx、tests/event.drag.drop.self-drop.test.tsx |
| PT-H08 | History and remote collaboration rebasing | 历史与远程协同 rebase:undo/redo、远端补丁、选择稳定性 | gherkin-tests/undo-redo.test.tsx、tests/collaborative-editing.test.tsx、tests/selection-after-remote-patches.test.tsx |
| PT-H09 | IME and composition transport | IME/组合输入传输:日文、韩文、格式化边界组合 | tests/composition.test.ts(日韩 IME、装饰器/注解边界的 25 组测试) |
| PT-H11 | Path, point, range, and operation core | 路径/点/区间/操作内核:comparePaths、parentPath、getNode、dirty paths、operation-to-patches | engine/path/*.test.ts、node-traversal/*.test.ts、internal-utils/operation-to-patches.test.ts |
| PT-H12 | Behavior pipeline and extension ordering | 行为管线与扩展排序:事件 raise/forward/execute、优先级、渲染默认值 | tests/behavior-api.test.tsx(27 个场景)、tests/event.patches.test.tsx(95 个锚点)、tests/positional-override-*.test.tsx |
| PT-P01 | Portable Text 产品策略 | marks/annotations/comments/decorators/list 属于 Plate 产品策略 | inventory.md 中 plate-owned 行(annotations.test.ts、lists.test.ts等) |
| PT-P02 | 序列化/插件/工具栏策略 | parser/serializer/plugin/toolbar 属于 Plate 包与文档 | inventory.md 中 plate-owned 行(html、markdown、docx、block-tools 等包) |
体量观察(逐行统计路由表):PT-H12 是最大的家族(约 68 行),主要来自packages/editor/tests/下的事件与行为管线测试;PT-H02 与 PT-H11 各约 30 行;PT-H09 仅 1 行(composition),PT-H06 与 PT-H07 各 3~4 行——后者正是计划文档反复强调的"需要真实浏览器证据、不能只用模型推断"的高风险面。
五、Action 语义:四类动作的决策逻辑
路由表第 5 列Action把家族判定翻译成具体工作指令,其取值在路由表中可归纳为:
| 动作 | 语义 | 路由表中的典型表述 | 代表行 |
|---|---|---|---|
refactor-existing | 已有 Slate v2 测试可承载该不变量,先重构归并,避免复制重复测试 | "refactor-existing into a compact placement matrix before adding duplicates"(先压成紧凑矩阵再加重复) | PT-H04 的 insert / split / break 各文件 |
create-new | 该行为在 Slate v2 侧尚无覆盖,需新建测试 | "create-new browser rows after unit resolver gap check"(先查单元解析器缺口再建浏览器行) | PT-H07 的 drag/drop 行 |
refactor-existing/create-new | 部分覆盖:重构已有并补齐根/对象边界缺口 | "refactor-existing and fill object/root boundary gaps" | PT-H01、PT-H05、PT-H08 多数行 |
split | 把混合行为拆给不同 owner:原始行为归 Slate,策略归 Plate | "split raw fragment/selection proof from Plate serializers and matchers" | PT-H06 的 paste 行 |
covered/refactor-existing | 已有契约覆盖,除非缺少 root-aware 变体才补 | "covered/refactor-existing unless a root-aware variant is missing" | PT-H11 的 path/operation 行 |
plate-owned | 不进入 Slate v2 执行,路由到 Plate 产品 backlog | "plate-owned/refactor-existing only if raw transaction effect hook is accepted" | PT-H12 的插件/渲染器行 |
两条纪律值得注意:一是"refactor-existing before duplicates"——大部分家族要求先强化 Slate v2 已有测试,而不是机械复制外部测试;二是"browser rows require later apply proof"——PT-H07、PT-H06、PT-H09 等浏览器面只承认真实的运行时证据(Playwright),模型推断不足以作为 proof(见计划文档 Open risks)。
六、验证命令解读:bun test 与 Playwright 双轨
路由表最后一列给出了可直接复制的验证命令,分为两条轨道:
轨道一:纯单元/契约测试(bun test)
cd .tmp/slate-v2 && bun test ./packages/slate/test/operations-contract.ts ./packages/slate/test/transforms-contract.ts这是 PT-H04 / PT-H05 / PT-H11 等单元面的标准命令,一个命令内可并列多个契约文件。命令要求先进入.tmp/slate-v2工作副本——这是收割流程约定俗成的 Slate v2 检出目录(当前仓库根下仅有packages/slate源码包,.tmp/slate-v2由 apply 阶段创建)。PT-H11 家族的完整命令还包含 root-aware 契约家族的四连测:
cd .tmp/slate-v2 && bun test \ ./packages/slate/test/root-location-contract.ts \ ./packages/slate/test/rooted-operation-contract.ts \ ./packages/slate/test/generic-operation-contract.ts \ ./packages/slate/test/operations-contract.ts轨道二:浏览器集成/压力测试(Playwright + chromium)
cd .tmp/slate-v2 && bun test ./packages/slate-react/test/slate-void-shell-contract.test.tsx && \ PLAYWRIGHT_RETRIES=0 bunx playwright test playwright/integration/examples/editable-voids.test.ts --project=chromium关键参数语义(结合路由表反复出现的形态归纳):
bun test负责 React 层的契约测试(如slate-void-shell-contract.test.tsx、projected-clipboard-contract.test.ts);PLAYWRIGHT_RETRIES=0关闭重试,让失败即时暴露——收割语境下重试会掩盖 flaky 行为;--project=chromium固定浏览器项目,保证跨机器可复现;- 集成示例(
playwright/integration/examples/*.test.ts)与压力测试(playwright/stress/generated-editing.test.ts)分层,压力行用于 PT-H03/PT-H05 的边界压力面。
不同家族的命令组合在路由表中高度模式化,例如 PT-H01 统一指向selection-rebase-contract.ts+projections-and-selection-contract.test.tsx+view-selection-contract.test.ts,PT-H08 统一指向collab-selection-stress-contract.ts+collab-history-runtime-contract.ts+projected-collab-substrate-contract.test.ts——这相当于为每个家族预定义了"验证处方"。
七、代表性路由案例分析
案例一:PT-H03(void/对象边界)——跨 React 与浏览器双轨验证
gherkin-tests/block-objects.test.ts(孤立图片的方向键、删除、退格场景)被路由为:refactor-existing + 为缺口创建精确的对象边界浏览器行;目标为slate-void-shell-contract.test.tsx、playwright/integration/examples/editable-voids.test.ts、playwright/stress/generated-editing.test.ts。这条路由说明:void 对象边界不变量既要有 React 层契约,也要有浏览器层真实输入证据。
案例二:PT-H06(剪贴板)——split 动作的典型
gherkin-tests/paste.test.ts与tests/event.paste.test.tsx均为 portable-mixed,动作是 split:"把原始 fragment/选择证据从 Plate 序列化器与 matcher 中拆出"。目标同时列出.tmp/slate-v2的剪贴板契约/粘贴浏览器测试与 "Plate serializer packages"。这就是第二章所述"拆分原则"在具体行上的落地。
案例三:PT-H12(行为管线)——plate-owned 的大本营
tests/behavior-api.test.tsx、tests/event.patches.test.tsx、renderer.types.test.tsx、priority.sort.test.ts等 68 行大多标为 plate-owned 或"仅当 raw transaction effect hook 被接受时才重构",验证命令指向transaction-contract.ts(可选)。理由在 inventory.md 中统一表述为 "editor behavior with framework policy mixed in"——事件 API 的 execute/raise/forward/effect 命名是产品 API 压力,不是需要照搬到 raw Slate 的 API。
八、数据来源与可复现性
路由表不是手写清单,而是由可复现流程生成:
- 清单命令(记录在 inventory.md):
rg --files ../portabletext \ | rg '(^|/)(__tests__|test|tests|spec|e2e|integration|playwright|cypress|wdio|fixtures)(/|$)|\.(test|spec)\.[cm]?[jt]sx?$' \ | rg -v '(^|/)(dist|build|coverage|node_modules|vendor|fixtures/generated|__snapshots__)(/|$)'许可证门:Portable Text 为 MIT/permissive,因此收割产物以 durable 模式落盘在
docs/editor-test-harvester/portabletext/(behavior-only 模式才会放入.tmp),并遵循"不复制源文件正文"的卫生约束——产物只记录路径、测试名、行号锚点与全新表述的不变量。交叉锚点:test-index.md 为 200 个文件提取了 1944 个
file:line name锚点(describe/test/Scenario/Feature),路由表的行号与之一一对应。例如gherkin-tests/delete.test.ts的 15 个锚点直接指向gherkin-spec/delete.feature的 Feature/Scenario 行号。验收记录(计划文档 Verification evidence):产物 2026-05-29 重新生成,502 行清单、321 个可运行行、0 新增/0 删除、harvest score 0.94(无维度低于 0.92),并通过
autogoal check-complete机械检查。
九、与仓库现状的衔接
- Slate v2 目标:路由表的目标 owner 集中在
.tmp/slate-v2(packages/slate/test、packages/slate-react/test、packages/slate-browser/test、playwright/)。当前仓库的 packages/slate 即是 Slate v2 源码本体,收割的 apply 阶段以它为执行基底。 - Plate 归属:PT-P01/PT-P02 与 PT-H12 的 plate-owned 行指向 Plate 的序列化、列表、schema、插件、工具栏包(如 packages/markdown、packages/html 等目录在 inventory 中大量出现),这些行为不进入 raw Slate 执行。
- 同类对照:该收割体系同样应用于其他编辑器生态,可参见 docs/editor-test-harvester/tiptap/portable-mixed-routing.md(TipTap 的 63 个 portable-mixed 文件路由),其列结构与判定逻辑一致,便于跨生态横向阅读。
十、给维护者的使用方式
- 查行:想知道某个外部测试文件的行为归属,按文件名在路由表
Source列检索,得到家族 + 动作 + 验证命令。 - 查面:想确认某类行为(如 void 边界、删除矩阵)在 Slate v2 侧是否有覆盖,按
Family title聚合,Target owner即覆盖位置清单。 - 执行验证:进入
.tmp/slate-v2检出后,直接复制Verification / backlog command列运行;浏览器面优先PLAYWRIGHT_RETRIES=0暴露 flaky。 - 追溯:需要更细的测试名/场景锚点时,按行号跳转 test-index.md;需要全量分类(含 plate-owned / harness / skip)时读 inventory.md。
需要重申的边界:本文所述 source-routing 是只读分析产物,收割流程明确规定 report-only 阶段不修改.tmp/slate-v2、Plate 包、docs 或构建配置(见计划文档 Constraints 与 Boundaries);所有Source路径属于外部 Portable Text 仓库,不可在当前仓库直接当作文件链接使用。
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考