Plate 仓库非 React 测试覆盖收口实战:Phase 3 之后的审查、评分与停止决策
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
本文基于 Plate 仓库(Rich-text editor with AI and shadcn/ui 的 monorepo)在 2026-03-25 完成的非 React 测试覆盖审查(Testing Review Non React Post Phase 3)展开。文章完整还原了这次审查的目标、可复现的验证命令、文件级评分规则、阈值分布、Phase 4 冻结路线图及其执行记录,并结合仓库内真实源码(docx 列表清理器、表格边框查询、Slate 删除变换等)与测试基建脚本,讲解"为什么这样做""命令如何工作""哪些文件被选中及为何被选中"。读者读完后,可以掌握在大型 monorepo 中对"非 React 临时切分"做覆盖收口的一整套可操作流程:重新生成作用域清单、跑 scoped 覆盖率、做套件健康检查、按文件价值评分定批、以及判断何时应该"诚实地停止"。
一、背景:什么是"非 React 临时切分"覆盖审查
Plate 是一个以 React 为核心的富文本编辑器 monorepo,但仓库中大量核心逻辑(docx/markdown 序列化、Slate 变换、表格查询、插件契约)本身并不依赖 React 渲染层。在 2026-03-25 之前,项目已经经历了多个覆盖阶段:
2026-03-24系列完成了全仓库(full-repo)与非 React 路径的多轮testing-review;2026-03-25又先做了 broad Bun suite-health 清理(见 2026-03-25-broad-bun-suite-health-fix.md)。
本关联文档 2026-03-25-testing-review-non-react-post-phase-3.md 记录的,正是在Phase 3 覆盖率烧尽之后、以及 broad 套件健康清理之后,对临时非 React 切分做的一次"全新复审"(fresh testing-review pass)。它的核心目标很明确:
重新跑一遍限定在非 React 作用域内的覆盖率,重新生成文件清单与评分,检查套件健康,更新或锁定下一份非 React 路线图,最后给出"继续还是停止"的结论。
它不是一个功能开发文档,而是一份可复现的测试工程方法论文档——这类文档在开源仓库中价值极高,因为它沉淀了覆盖率治理的纪律:文件优先、诚实评分、及时止损。
二、Goal 与 Status:一次审查的验收清单
原文档的 Goal 只有一句话:"Run a fresh testing-review pass for the temporary non-React cut after Phase 3 and the broad Bun suite-health cleanup."(在 Phase 3 与 broad Bun 套件健康清理之后,对临时非 React 切分做一次全新测试审查。)
与之配套的 Status 清单定义了这次审查必须交付的五个动作,这也是任何一次覆盖收口审查的通用验收清单:
- 运行一次全新的、限定作用域的非 React 覆盖率(Run fresh scoped non-React coverage);
- 运行套件健康检查(Run suite-health checks);
- 重新生成包级与文件级评分(Regenerate package and file scoring);
- 更新或锁定下一份非 React 路线图(Update or lock the next non-React roadmap);
- 总结"继续还是停止"(Summarize whether to continue or stop)。
五个动作在本轮全部完成(文档中均标记为[x])。这种"先定义验收项再执行"的结构,正是文档中反复强调的"file-first ranking, not package theater"(文件优先排名,而非包剧场)纪律在流程层面的体现。
三、Verification:可复现的验证命令与工具链
原文档在 Verification 一节给出了五类可复现命令。它们各自解决一个具体的审查问题,下面结合仓库脚本逐一展开。
3.1 重新生成非 React 测试文件清单
# 产物:.claude/tmp/non_react_test_files.txt(615 个文件)这是整个流程的起点。原文档 Notes 中特别强调了一个关键教训:
The previous scoped non-React file list was stale. This pass regenerated it before coverage. The earlier list would have lied about remaining gaps.(上一份非 React 文件清单已经过时。本次在跑覆盖率之前重新生成它。旧清单会"说谎",掩盖真正的缺口。)
也就是说,如果沿用旧清单跑覆盖率,结果会虚假地好看或虚假地难看;重新生成清单保证覆盖率快照与当前源码结构一致。从仓库测试基建看,这种"按清单圈定测试范围"的模式与 tooling/scripts/test-fast.mjs 的路径过滤机制一脉相承:该脚本会把非-开头的裸参数视为 path filter,通过globSync做动态匹配、对静态路径做前缀/包含匹配,从而只跑被圈定的测试文件。
3.2 限定作用域的覆盖率运行
bun test --coverage --coverage-reporter=lcov --coverage-dir=.coverage-repo-2026-03-25b --reporter=dots针对的是重新生成的非 React 文件清单。这里三个关键参数的含义:
--coverage:启用 Bun 内建覆盖率采集;--coverage-reporter=lcov:输出 LCOV 格式,产物为.coverage-repo-2026-03-25b/lcov.info,后续评分正是以这份 lcov 为输入源(详见下文"输入与约束");--reporter=dots:点状进度输出,适合长跑时观察。
本次运行结果(记录于配套的 覆盖率优先级映射文档):
Fresh non-React coverage:2784 pass, 0 fail, 566 files, 2.32s。
同一时刻 fast suite(全量快速套件)的结果为:3022 pass, 0 fail, 614 files, 3.83s,平均单测耗时 0.508ms、中位数 0.212ms。也就是说,非 React 切分覆盖了 fast suite 566 个文件中的绝大部分。
3.3 套件健康检查:profile 与 slowest
pnpm test:profile -- --top 25 pnpm test:slowest -- --top 25这两个命令都路由到 tooling/scripts/test-slowest.mjs,区别在于--profile只输出性能画像、不因超阈值而失败(profileOnly 分支),而pnpm test:slowest在存在超限用例时会以退出码 1 失败。脚本内部会:
- 以 junit reporter 跑一遍 fast suite,输出到
/tmp/plate-test-slowest/junit-fast.xml; - 解析
<testcase>的 time 属性,按"每个用例耗时"和"每个文件总耗时"两个维度排序; - 打印 Top N 慢用例与 Top 20 慢文件,并用
!(超慢桶)、~(警告区)标注; - 依据 tooling/config/test-suites.mjs 中的阈值判定健康状态:
export const FAST_TEST_SLOW_CASE_THRESHOLD_MS = isCI ? 90 : 75; export const FAST_TEST_SLOW_FILE_THRESHOLD_MS = isCI ? 180 : 150;即单用例慢桶阈值本地 75ms / CI 90ms,单文件总耗时慢桶阈值本地 150ms / CI 180ms。一旦出现慢桶违规,脚本会提示"Move the offending spec to*.slow.ts[x]so it runs viapnpm test:slow",即把超标用例迁移到慢速车道。
本次审查的慢桶检查结论:threshold breaches: none(无任何阈值越界),且警告区(warning zone)也没有需要处理的常客。这与 2026-03-25-main-push-test-slowest-and-use-event-plate-id.md 中把test:slowest纳入 main push 检查的做法互相印证:套件健康是持续门禁,不是一次性审查。
3.4 三类"测试债务"扫描
原文档给出了三条 ripgrep 扫描命令,分别对应三种常见的测试债务形态:
# 1. 被 skip 的用例(describe.skip / it.skip / test.skip / xit / xdescribe) rg -n 'describe\.skip|it\.skip|test\.skip|xit\(|xdescribe\(' packages apps \ -g '*.spec.ts' -g '*.spec.tsx' -g '*.slow.ts' -g '*.slow.tsx' # 2. 被整块注释掉的用例(^\s*//\s*(describe|it|test)() rg -n '^\s*//\s*(describe|it|test)\(' packages apps \ -g '*.spec.ts' -g '*.spec.tsx' -g '*.slow.ts' -g '*.slow.tsx' # 3. 跨 spec 导入(from '...spec',通常意味着测试之间的隐式耦合) rg -n "from '.*\.spec'|from \".*\.spec\"" packages apps \ -g '*.spec.ts' -g '*.spec.tsx' -g '*.slow.ts' -g '*.slow.tsx'本次扫描结论:
- skipped-test debt scan:none worth fixing(没有值得修的跳过用例);
- commented-out spec scan:none worth fixing;
- cross-spec import scan:none(零命中)。
注意:这三条命令是"债务检测"而非"必须清零"。结论用词是 "none worth fixing"——即使有零星命中,只要不构成实际问题,就不值得投入。这与后文"覆盖率虚荣"(coverage vanity)的反对立场完全一致:审查的目标是产出诚实的数据与决策,不是数字洁癖。
四、覆盖率的输入与约束:lcov 与四条红线
配套的 覆盖率优先级映射文档 给出了本次评分的输入与约束,这是理解"为什么要这样评"的关键:
- Coverage source:
.coverage-repo-2026-03-25b/lcov.info(即 3.2 节命令的产物); - 约束一:临时排除
/react(temporary exclude/react); - 约束二:不做浏览器或 e2e 覆盖工作(no browser or e2e coverage work);
- 约束三:不做覆盖率虚荣(no coverage vanity);
- 约束四:文件优先排名,不做包剧场(file-first ranking, not package theater)。
这四条红线定义了整个收口的边界:只对纯逻辑层负责,不越界到渲染层与 e2e,且反对用"包覆盖率"这种容易被平均掩盖真相的指标做决策。
五、Scoring Rules:文件级评分规则全文
评分规则是这套方法论的核心资产,原文共六条,逐条继承如下:
- 作用域:
packages/**/src/**。 - 排除项:测试文件、barrel(桶文件/重导出索引)、声明文件、明显的纯类型文件、
/react、以及packages/playwright下的浏览器包工作。 - 扣分项:包装器(wrappers)、微小的碎屑(tiny crumbs)、DOM 重残留(DOM-heavy leftovers)、schema 尘埃(schema dust)、以及巨大的序列化器污泥(giant serializer sludge)。
- 加分项:确定性变换(deterministic transforms)、查询(queries)、parser 或 serializer 接缝(seams)、插件契约(plugin contracts)、插件解析(plugin resolution)、以及有界运行时辅助(bounded runtime helpers)。
- 诚实原则:不在 lcov 中出现的运行时文件一律视为未覆盖(Missing-from-lcov runtime files are treated as uncovered)。原文档的原话是:"Pretending they are covered is clown math."(假装它们被覆盖了是荒谬数学。)
- 包评分定义:
package_score是该包内剩余文件评分最高的前 5 个之和(the sum of the top 5 remaining file scores in that package),而不是把所有残余碎屑加起来——这样包分不会被"碎屑数量"污染。
这套规则的哲学很清晰:评分奖励的是"有契约、可确定性验证"的代码,惩罚的是"低信噪比、高维护成本"的代码。它直接决定了下一节的批次排序。
六、阈值分布与批次排序:从 score 5 到 score 1
本次复审后的文件评分阈值分布为:
| 阈值 | 剩余文件数 |
|---|---|
score >= 5 | 5 |
score >= 4 | 25 |
score >= 3 | 70 |
score >= 2 | 108 |
score >= 1 | 210 |
关键结论(文档 Strong Take 原意):
There are no
score >= 6files left. The entire honestscore >= 5set is just five files, and four of them are one tinybasic-nodesparser-plugin lane.(已无score >= 6的文件;整个诚实的score >= 5集合只剩 5 个文件,其中 4 个属于basic-nodes同一条极小的 parser-mark 插件车道。)
6.1 Strict Next Batch:必须收口的 5 个文件
docx— docxListToList.ts — score5basic-nodes— BaseCodePlugin.ts — score5basic-nodes— BaseStrikethroughPlugin.ts — score5basic-nodes— BaseItalicPlugin.ts — score5basic-nodes— BaseUnderlinePlugin.ts — score5
6.2 Wider Optional Batch:可选的 7 个 score-4 文件
table— getSelectedCellsBorders.ts — score4autoformat— AutoformatPlugin.ts — score4core— DebugPlugin.ts — score4udecode/depset— get-package-manager.ts — score4table— getCellIndices.ts — score4basic-nodes— BaseBoldPlugin.ts — score4slate— deleteText.ts — score4
6.3 按价值排序的包与最佳文件
Packages By Value(前 12 名):
basic-nodes— 24slate— 20docx— 18table— 17core— 16docx-io— 16list-classic— 16dnd— 15markdown— 12resizable— 12suggestion— 10cursor— 9
Best Files By Value(Top 15,含覆盖率与未覆盖行数):
| # | 包 | 文件 | score | 覆盖率 | 未覆盖行 |
|---|---|---|---|---|---|
| 1 | docx | docxListToList.ts | 5 | 78.9% | 8 |
| 2 | basic-nodes | BaseCodePlugin.ts | 5 | 76.9% | 6 |
| 3 | basic-nodes | BaseStrikethroughPlugin.ts | 5 | 83.3% | 4 |
| 4 | basic-nodes | BaseItalicPlugin.ts | 5 | 82.6% | 4 |
| 5 | basic-nodes | BaseUnderlinePlugin.ts | 5 | 82.6% | 4 |
| 6 | table | getSelectedCellsBorders.ts | 4 | 95.1% | 12 |
| 7 | docx-io | settings.ts | 4 | 0.0% | 11 |
| 8 | slate | hasDOMNode.ts | 4 | 18.2% | 9 |
| 9 | autoformat | AutoformatPlugin.ts | 4 | 88.9% | 8 |
| 10 | slate | hasEditableTarget.ts | 4 | 20.0% | 8 |
| 11 | slate | hasSelectableTarget.ts | 4 | 20.0% | 8 |
| 12 | slate | hasTarget.ts | 4 | 20.0% | 8 |
| 13 | core | DebugPlugin.ts | 4 | 90.6% | 5 |
| 14 | udecode/depset | get-package-manager.ts | 4 | 81.5% | 5 |
| 15 | table | getCellIndices.ts | 4 | 81.8% | 4 |
这张表信息量很大,值得专门解读两点:
- 高覆盖率 ≠ 高优先级:
getSelectedCellsBorders.ts覆盖率高达 95.1% 却只排 score 4,说明它的剩余缺口多位于跨单元格边界(left-adjacent、right-edge)这类低价值分支;而hasDOMNode.ts覆盖率仅 18.2% 却也只有 score 4,因为它是 DOM-only 接缝,"真实代码,但错误的阶段"。 - score 与覆盖率是正交的两个维度:score 衡量"剩余缺口值不值得补",覆盖率衡量"已经覆盖了多少"。评分 = 剩余价值,而不是覆盖率缺口大小。
七、从源码看被选中文件的真实结构
评分不是凭空给分。下面以 4 个代表性文件为例,说明它们为什么被选中、测试要补什么。这些文件路径均可直接在仓库中打开验证。
7.1 docx 列表转换器:docxListToList.ts
export const docxListToList = (element: Element): Result => { const listLevel = getDocxListIndent(element); let listHtml = ''; let nextSibling: Element | null = element; while (nextSibling) { if (isDocxBookmark(nextSibling)) { nextSibling = nextSibling.nextElementSibling; continue; } if (!isDocxList(nextSibling)) break; const nextListLevel = getDocxListIndent(nextSibling); if (nextListLevel < listLevel) break; // 更低层级,当前列表结束 if (nextListLevel > listLevel) { const nestedList = docxListToList(nextSibling); // 递归处理嵌套 if (nestedList.list) listHtml += nestedList.list.outerHTML; nextSibling = nestedList.nextSibling; continue; } listHtml += `<li>${getDocxListContentHtml(nextSibling)}</li>`; const currentElement = nextSibling; nextSibling = currentElement.nextElementSibling; currentElement.remove(); } const listTagName = isDocxOl(element) ? 'ol' : 'ul'; const list = parseHtmlElement(`<${listTagName}>${listHtml}</${listTagName}>`); return { list, nextSibling }; };这是一个典型的确定性 parser 接缝:输入 DOM 元素、输出 HTML 字符串与下一个兄弟节点。它同时包含两个"扣分项/加分项"的判别特征——递归嵌套与兄弟游标推进是有边界的确定性变换(加分),而 bookmark 跳过则是容易被漏测的分支。因此 Phase 4 执行记录中对该文件的补测就是两件事:bookmark 跳过与嵌套列表递归(见 docxListToList.spec.ts)。
7.2 表格选中边框查询:getSelectedCellsBorders.ts
该文件导出getSelectedCellsBorders及三个辅助谓词isSelectedCellBordersNone/isSelectedCellBordersOuter/isSelectedCellBorder,核心逻辑是:对选中的单元格集合计算包围盒(getSelectedCellsBoundingBox),然后单次遍历所有单元格,按top/bottom/left/right四个方向判断边框可见性,并综合出none(全无边框)与outer(外框完整)两个聚合状态。
它被评为 score 4 的原因在源码里写得很清楚:存在大量边界条件——首行单元格的 top 边框要看自身、非首行要看上方单元格的 bottom 边框(getTopTableCell)、首列同理要看左侧单元格的 right 边框(getLeftTableCell),再加上colSpan/rowSpan展开循环。Phase 4 对它的补测正是补齐"left-adjacent 边框检查"和"right-edge 边界 fallthrough"(见 getSelectedCellsBorders.spec.tsx)。
7.3 单元格索引查询:getCellIndices.ts
export const getCellIndices = ( editor: SlateEditor, element: TTableCellElement ): CellIndices => { const { getOption } = getEditorPlugin<TableConfig>(editor, { key: KEYS.table }); let indices = getOption('cellIndices', element.id!); if (!indices) { indices = computeCellIndices(editor, { cellNode: element })!; if (!indices) { editor.api.debug.warn( 'No cell indices found for element. Make sure all table cells have an id.', 'TABLE_CELL_INDICES' ); } } return indices ?? { col: 0, row: 0 }; };这是一个"缓存优先、计算兜底、降级默认"的三段式查询:先查插件 option 缓存(cellIndices按element.id索引),未命中则实时计算,计算失败则告警并返回{ col: 0, row: 0 }。对应测试 getCellIndices.spec.ts 补的是三个分支:cache-hit(命中缓存)、warn(计算失败告警)与fallback(默认降级)——正好覆盖这三段式中的每一段。
7.4 Slate 删除变换:deleteText.ts
这是slate包内部的删除核心变换,包含一整套复杂分支:void 节点的定位与剔除、跨块合并(mergeNodes)、range unhang、point ref 保护,以及一个非常特殊的泰文脚本处理——当删除方向为reverse且删除的是泰文字符串时,按"码点"而非"字形簇"删除(THAI_SCRIPT_REGEX = /[\u0E00-\u0E7F]+/,删 N 个字符后把剩余码点插回)。
Phase 4 对它的补测聚焦两处:point-inside-void 删除(光标在 void 内部时把范围挤到 void 外)与文档末尾的前向删除 no-op(editor.api.after(at, opts) || editor.api.end([])兜底后的空操作),见 deleteText.spec.tsx。这类"变换 + 边界"组合正是评分规则中"deterministic transforms"的加分对象。
7.5 basic-nodes 的 parser-mark 车道:BaseCodePlugin.ts
export const BaseCodePlugin = createSlatePlugin({ key: KEYS.code, node: { isLeaf: true }, parsers: { html: { deserializer: { rules: [ { validNodeName: ['CODE'] }, { validStyle: { fontFamily: 'Consolas' } }, ], query({ element }) { const blockAbove = findHtmlParentElement(element, 'P'); if (blockAbove?.style.fontFamily === 'Consolas') return false; return !findHtmlParentElement(element, 'PRE'); }, }, }, }, render: { as: 'code' }, rules: { selection: { affinity: 'hard' } }, }).extendTransforms(({ editor, type }) => ({ toggle: () => { editor.tf.toggleMark(type); }, }));它是"插件契约 + 解析器接缝"的典型:createSlatePlugin声明 key、isLeaf 节点、HTML 反序列化规则(CODE标签或 Consolas 字体系列)以及query否决逻辑(P 标签内 Consolas 不算 code、PRE 内部不算)。它与其他三个兄弟插件(BaseStrikethrough/BaseItalic/BaseUnderline)共享同一条 parser-mark 车道,因此 Phase 4 用一个合并的 BaseMarkPlugins.spec.ts 统一覆盖"parser 否决 + toggle 接线",一次收口四个文件,这也解释了为什么"5 个 score-5 文件中有 4 个是一条车道"。
八、Caveats:数据噪声与诚实解读
配套文档专门列出四条 Caveats,防止读者被包级数据误导:
- 包总分比文件分噪声更大:信任文件排名(Trust the file ranking more);
basic-nodes看起来分高,只是因为一条小的 parser-mark 车道还部分未覆盖,不代表整个包需要清扫;slate被 DOM-editor 残留物抬高:它们是真实文件,但对最后一轮非 React 收口而言"错误的阶段"(wrong phase);docx-io自我高估:schema 尘埃与html-to-docx.ts未覆盖,但都不是高 ROI 目标。
这些 Caveats 是方法论的重要组成部分:数据要标注使用边界。包分数只能用来"寻找值得看的文件",不能用来"判断包质量"。
九、Stop Condition:诚实的停止条件
文档给出的停止条件是一段值得反复读的工程判断:
Stop non-React coverage after the phase-4 roadmap below. The remaining misses after that are mostly DOM-only Slate crumbs, schema or type dust, wrapper/plugin residue, and giant low-ROI serializer sludge. That is where more non-React coverage turns into percentage cosplay. Switch to React or architecture-safety work instead.
(完成下面的 Phase 4 路线图后停止非 React 覆盖。此后的剩余缺口大多是 Slate 的 DOM-only 碎屑、schema/类型尘埃、包装器/插件残渣,以及低 ROI 的巨大序列化器污泥。继续补下去,非 React 覆盖就会变成"百分比角色扮演"。应当转向 React 或架构安全性工作。)
"percentage cosplay"(百分比角色扮演)这个词精准定义了"覆盖率虚荣"的形态:当剩余缺口全是低价值代码时,覆盖率数字再涨也没有工程意义。停止条件 = 剩余价值的边际收益归零,而不是数字到 100。
十、Phase 4 路线图:冻结规则、执行与收尾
配套的 2026-03-25-non-react-coverage-roadmap-phase-4.md 是本次审查的决策产物,它定义了非 React 覆盖的"最后一班车"。
10.1 Lock Rules(冻结规则)
- 仅限临时非 React 切分(Phase: temporary non-React cut only);
- 以文件级 TSV 作为唯一真相来源(Frozen threshold: use the file TSV as source of truth);
- Tier 1= 所有剩余
score >= 5文件;Tier 2= 可选的score = 4最佳清理项; - 保持队列文件优先(Keep the queue file-first),不得回退成包清扫(Do not collapse this back into package sweeps);
- 后续 pass 只能把条目标记为
done/removed/deferred,除非候选集合发生实质性变化,否则不得重排整个队列(Do not reshuffle the whole thing)。
10.2 Execution Policy(执行策略)
- Tier 1:只有当你还想要最后一轮非 React 冲刺时才执行;
- Tier 2:可选打磨,不是强制;
- 完成 Tier 1 或 Tier 2 后,停止非 React 覆盖并转向(After Tier 1 or Tier 2, stop non-React coverage and move on)。
10.3 执行记录(已全部完成)
2026-03-25-non-react-coverage-roadmap-phase-4-execution.md 记录了 Phase 4 的实际工作量,Tier 1 与 Tier 2 共 12 个文件全部标记[done]:
- Tier 1(5 个):
docxListToList(bookmark 跳过 + 嵌套递归)、BaseCode/Strikethrough/Italic/Underline 四个插件(合并进新增的BaseMarkPlugins.spec.ts,覆盖 parser 否决 + toggle 接线); - Tier 2(7 个):
getSelectedCellsBorders(left-adjacent 与 right-edge)、AutoformatPlugin(混合规则 undo-on-delete 与无insertTrigger的成功自动格式化)、DebugPlugin(默认 console logger 表面)、get-package-manager(yarn fallback 与空 user-agent 默认)、getCellIndices(cache-hit 与 warn/fallback)、deleteText(void 内删除与文档末尾前向删除 no-op)。
10.4 Deferred By Design(刻意延期清单)
hasDOMNode.ts等 score-4 Slate DOM-editor 助手——理由:DOM-only 接缝,"真实代码,错误阶段";settings.ts(docx-io schema 尘埃)——理由:低信号数据/schema 常量,ROI 差;html-to-docx.ts——理由:巨大序列化器污泥,不适合作为最后的非 React 投入;BasePlugin.ts及类似 core 基础 slab——理由:是广泛重构目标,不适合做晚期覆盖切片;isTouchEvent.ts等工具尘埃——理由:未覆盖,但不值得动。
10.5 Update Rule(队列维护规则)
- 文件获得直接测试 → 翻转为
[done]; - 文件被证明是虚假 ROI → 翻转为
[deferred]并附理由; - 文件消失 → 翻转为
[removed]; - 禁止因为"新一轮 pass 有新感觉"就重排队列(Do not reshuffle the queue because a fresh pass had a new vibe)。
这套"冻结 + 状态翻转"机制保证路线图是可追溯的执行台账,而不是反复无常的待办清单。
十一、最终结论:为什么这一轮是"最后的诚实路线图"
原文档 Notes 的最后两行是整份文档的题眼:
There are no remaining
score >= 6non-React files. Only fivescore >= 5files remain, so this is the last honest non-React roadmap.
(已无score >= 6的非 React 文件;只剩 5 个score >= 5文件,所以这是最后一份诚实的非 React 路线图。)
结合 Phase 4 执行记录末尾的 Outcome:
Phase 4 is complete. Non-React coverage work is spent enough that more passes would mostly be crumbs, wrappers, DOM-only Slate dust, or sludge.
(Phase 4 完成。非 React 覆盖工作已经投入得足够充分,更多轮次的收益将主要是碎屑、包装器、Slate 的 DOM-only 尘埃或污泥。)
以及执行时的完整验证链:
# 定向跑新增/扩展的 8 个 spec 文件 bun test packages/docx/src/lib/docx-cleaner/utils/docxListToList.spec.ts \ packages/basic-nodes/src/lib/BaseMarkPlugins.spec.ts \ packages/table/src/lib/queries/getSelectedCellsBorders.spec.tsx \ packages/table/src/lib/utils/getCellIndices.spec.ts \ packages/autoformat/src/lib/AutoformatPlugin.spec.tsx \ packages/core/src/lib/plugins/debug/DebugPlugin.spec.ts \ packages/udecode/depset/src/utils/get-package-manager.spec.ts \ packages/slate/src/internal/transforms/deleteText.spec.tsx # 按目录批量回归 bun test packages/docx/src/lib/docx-cleaner/utils packages/basic-nodes/src/lib \ packages/table/src/lib packages/autoformat/src/lib packages/core/src/lib \ packages/udecode/depset/src/utils packages/slate/src/internal/transforms # 构建 + 类型检查 + lint 全链路 pnpm install pnpm turbo build --filter=./packages/docx --filter=./packages/basic-nodes \ --filter=./packages/table --filter=./packages/autoformat \ --filter=./packages/core --filter=./packages/udecode/depset --filter=./packages/slate pnpm turbo typecheck --concurrency=1 --filter=./packages/docx --filter=./packages/basic-nodes \ --filter=./packages/table --filter=./packages/autoformat \ --filter=./packages/core --filter=./packages/udecode/depset --filter=./packages/slate pnpm lint:fix至此,非 React 覆盖工作以一个明确的停止点收尾:剩余缺口已被定性为低价值代码,继续投入将违背"no coverage vanity"的红线。这个停止点本身,就是这套方法论最有价值的产品。
十二、给测试工程实践的四个可迁移要点
- 先刷新清单,再跑覆盖率:作用域清单一旦过时,覆盖率快照就会失真。任何"切分式"覆盖率审查的第一步都应该是重新生成测试文件清单。
- 文件评分优先于包评分:包分数噪声大(会被残留物抬高),真正可操作的是文件级排名;用"剩余价值"而非"覆盖率缺口"排序,能自然避开低 ROI 的污泥与尘埃。
- 为"停止"建立显式条件:当剩余缺口主要是 DOM-only 接缝、schema 尘埃、包装器残渣与序列化器污泥时,继续补覆盖就是"百分比角色扮演"。把停止条件写进路线图,防止覆盖工作无限延伸。
- 用状态机维护收口队列:
done / removed / deferred三态 + 禁止随意重排,让路线图成为可追溯的台账;deferred必须附理由,理由本身就是在沉淀工程判断。
延伸阅读
- 本次审查的原始记录:2026-03-25-testing-review-non-react-post-phase-3.md
- 覆盖率评分与数据详情:2026-03-25-coverage-priority-map-testing-review-non-react-post-phase-3.md
- 冻结路线图:2026-03-25-non-react-coverage-roadmap-phase-4.md
- 执行记录:2026-03-25-non-react-coverage-roadmap-phase-4-execution.md
- 测试基建:快速套件脚本 tooling/scripts/test-fast.mjs、慢速/性能分析脚本 tooling/scripts/test-slowest.mjs、阈值常量 tooling/config/test-suites.mjs、根脚本入口 package.json
- 关联源码:docxListToList.ts、getSelectedCellsBorders.ts、getCellIndices.ts、deleteText.ts、BaseCodePlugin.ts
【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考