Plate 仓库覆盖率优先级分诊实战基于 bun 覆盖率数据驱动非 React 测试攻坚排序【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate本文是 Plate基于 Slate 的富文本编辑器集成 AI 与 shadcn/ui仓库内测试覆盖率治理工作的一份实战记录通过一次bun test --coverage全仓覆盖率运行结合评分信号 包价值排名的分诊方法从数百个文件中精准筛出下一批最值得补测试的源码文件并给出score 6严格批次 9 个文件与score 5宽批次 18 个文件两级可执行清单。读完本文你将掌握如何用 lcov 覆盖率输出驱动非 React 单元测试的优先级排序理解评分信号seam 类型、未覆盖行数、覆盖率、近期清扫惩罚、包信号惩罚的构成与调优思路并能直接复用文档中的命令与分诊表在你的仓库里落地同类工作流。一、背景为什么需要覆盖率优先级分诊Plate 是一个典型的大型 monorepopackages/下包含 core、table、caption、callout、cursor、excalidraw、udecode/depset 等数十个包测试面铺得很开。覆盖率治理如果只是笼统地看总体百分比很容易陷入两个误区覆盖率虚荣指标coverage vanity为了刷高数字去补那些价值低的测试或者反复清扫已经覆盖过的包React 组件测试与纯逻辑测试混为一谈/react相关文件组件渲染、hooks需要 DOM 与浏览器环境与纯函数式单元测试的 seam 形态完全不同混在一起排序会稀释真正值得投入的非 React 单元测试缝隙。本分诊文档docs/plans/2026-03-24-coverage-priority-map.md正是为此而生只对packages/**/src/**下真实存在的非 React 单元测试缝隙打分用一次确定性覆盖率运行 加权评分把下一个该补哪批测试从拍脑袋变成可复现的排序。二、分诊输入与评分信号文档明确给出了本次分诊的三个输入输入值说明Coverage sourcelcov.info输出到.coverage-repo-2026-03-24a/lcov.info由 bun 覆盖率运行生成的标准 lcov 格式报告Constraints排除/react忽略覆盖率虚荣只对packages/**/src/**下真实的非 React 单元测试缝隙打分确保排序聚焦纯逻辑 seamScoring signalsseam 类型、未覆盖行数、覆盖率比例、近期已完成批次、包信号惩罚五个信号共同决定每个文件的 score其中近期已完成批次与包信号惩罚是这套分诊方法里最有意思的两个信号如果某个包刚在 3 月 22 日24 日的批次里被清扫过它的文件会被显式扣分避免排名反复把已扫过的包顶上来而包信号惩罚则避免同一个包的多个文件因为相似特征扎堆排满整个清单。这些信号的组合让 score 同时反映文件维度还有多少未覆盖行、当前覆盖率多低与治理维度这个包最近是否值得再动。三、覆盖率运行命令、配置与结果3.1 运行命令本次分诊使用的完整命令为bun test --coverage --coverage-reporterlcov --coverage-dir.coverage-repo-2026-03-24a --reporterdots逐参数拆解--coverage启用覆盖率收集对应根package.json中的test:coverage脚本bun test --coverage--coverage-reporterlcov以 lcov 格式输出供后续解析评分--coverage-dir.coverage-repo-2026-03-24a把 lcov 报告写到仓库根目录下名为.coverage-repo-2026-03-24a的目录形成带日期的可追溯快照--reporterdots测试过程输出用紧凑的 dots 格式减少刷屏。3.2 测试环境配置该命令的运行依赖仓库根目录 bunfig.toml 中的[test]配置[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先于任何测试文件执行 DOM 全局环境初始化这也是bun test能在 Node 环境模拟 DOM 的原因tsconfig指向测试专用 tsconfig提供/registry/*等路径映射onlyFailures true让日常跑测试只回显失败而覆盖率运行时配合--reporterdots保持输出最小化。3.3 运行结果本次运行的官方记录为2643 pass0 fail493 files耗时2.45s即493 个测试文件全绿通过、零失败总耗时约 2.45 秒。这是一次干净、快速的确定性基线运行为后续评分提供了可靠的数据源。注意这是文档记录的当日历史结果实际复跑数据会随代码演进变化。四、分诊结论score 6 是诚实的下一批在拿到覆盖率数据并完成打分后文档给出了明确的行动建议强结论strong takescore 6是诚实的下一批the honest next batch——这是性价比最高的攻坚集合值得优先执行宽结论如果想在重跑覆盖率之前再多扫一轮可以取score 5作为更宽的批次结构性判断排除/react之后剩余真实价值高度集中在两个包——udecode/depset与table其后都是更小的叶子工作或近期已覆盖的遗留物。这句结论很重要分诊的价值不只是给出一批文件名而是指出价值分布——大多数值得补的缝隙集中在少数包上先啃大头再处理零碎。五、阈值统计score 分布的全局视图文档给出各阈值下的文件数量这是理解分数分布的关键阈值文件数说明score 81极端高优先即 depset 的 get-package-manager.tsscore 10score 78严格批次的主力score 69严格批次Strict Batchscore 518宽批次Wider Batchscore 432score 350score 278score 1127全部有待补缝隙的文件从分布可见分数越高文件越少呈明显的长尾形态。score 1有 127 个文件但真正值得立即动手的只有最顶部的 918 个——这正是评分排序的价值把长尾中高价值的头部挑出来而不是眉毛胡子一把抓。六、包价值排名depset 与 table 双雄按包优先级聚合本次分诊的价值排名如下排名包优先级Top 文件分数files6files51udecode/depset44410442table21071103cursor1117114caption1117115callout1117116excalidraw111711优先级数值可以理解为该包所有达标文件的加权聚合udecode/depset以 444 遥遥领先且4 个文件分数 6是当之无愧的第一攻坚目标table虽然只有一个文件达到 6 分但有10 个文件达到 5 分是宽批次里体量最大的包cursor、caption、callout、excalidraw 各有一个 7 分文件属于单点突破型。6.1 为什么 depset 是第一优先从仓库源码看packages/udecode/depset/README.md 说明这是一个同步 package.json 依赖到最新版本/指定版本/匹配模式的命令行工具支持npx depsetlatest、pnpm dlx depsetlatest、yarn dlx depsetlatest、bunx depsetlatest四种调用方式。它的核心逻辑全部是纯 Node 工具函数包管理器探测、日志、错误处理、spinner完全不依赖 DOM——这是教科书级的非 React 单元测试缝隙评分机制把它排在首位完全符合只扫真实非 React seam的约束。七、严格批次逐文件剖析score 6共 9 个文件这是文档给出的诚实下一批每个文件的源码位置、所属包、分数与覆盖现状如下文件包score覆盖率未覆盖行get-package-manager.tsudecode/depset100.0%30withCaption.tscaption70.0%67getSelectionRects.tscursor70.0%60logger.tsudecode/depset70.0%27getSelectionWidth.tstable77.4%25insertCallout.tscallout70.0%24insertExcalidraw.tsexcalidraw70.0%21handle-error.tsudecode/depset70.0%21spinner.tsudecode/depset60.0%12下面逐个说明这些文件的职责与测试切入点帮助理解为什么它们值得补测试。7.1 depset 四件套score 10 / 7 / 7 / 6get-package-manager.tsscore 10未覆盖 30 行——包管理器探测export async function getPackageManager( targetDir: string, { programmatic true, // ni specific withFallback true, }: { programmatic?: boolean; withFallback?: boolean } {} ): PromisePackageManager { const detected await detect({ cwd: targetDir, programmatic }); if (detected) { if (detected.startsWith(yarn)) return yarn; // Catches yarn and yarnberry if (detected.startsWith(pnpm)) return pnpm; // Catches pnpm and pnpmversion if (detected bun) return bun; if (detected npm) return npm; } if (withFallback) { // Fallback to user agent if not detected. const userAgent process.env.npm_config_user_agent || ; // yarn / pnpm / bun 的前缀匹配…… } return npm; // Default or if no fallback matched }从源码看它依赖antfu/ni的detect并处理 yarnberryyarn与yarn版本号前缀归一、user agent 回退、最终默认 npm 四层逻辑。这是典型的分支密集型纯函数每条分支4 种包管理器 × 探测命中/未命中 × 是否回退都值得一个用例是 score 10 的原因所在。logger.tsscore 7未覆盖 27 行——带颜色的日志工具import { cyan, green, red, yellow } from kleur/colors; export const highlighter { error: red, info: cyan, success: green, warn: yellow, }; export const logger { break() { console.log(); }, error(...args: unknown[]) { console.log(highlighter.error(args.join( ))); }, info(...args: unknown[]) { console.log(highlighter.info(args.join( ))); }, log(...args: unknown[]) { console.log(args.join( )); }, success(...args: unknown[]) { console.log(highlighter.success(args.join( ))); }, warn(...args: unknown[]) { console.log(highlighter.warn(args.join( ))); }, };六个方法 四个颜色映射是 CLI 工具的输出基础设施测试时可 mockconsole.log断言输出内容与颜色包裹。handle-error.tsscore 7未覆盖 21 行——统一错误处理export function handleError(error: unknown): void { logger.error(Something went wrong. Please check the error below for more details.); if (typeof error string) { logger.error(error); } else if (error instanceof ZodError) { logger.error(Validation failed:); for (const [key, value] of Object.entries(error.flatten().fieldErrors)) { logger.error(- ${highlighter.info(key)}: ${(value as string[]).join(, )}); } } else if (error instanceof Error) { logger.error(error.message); } logger.break(); process.exit(1); }分支覆盖字符串错误、ZodErrorschema 校验失败时逐字段打印、普通Error三种形态最后process.exit(1)。测试时需要分别注入三类错误对象断言输出与退出码。spinner.tsscore 6未覆盖 12 行——CLI 进度指示import ora, { type Options } from ora; export function spinner(text: Options[text], options?: { silent?: boolean }) { return ora({ isSilent: options?.silent, text }); }基于ora的薄封装核心是isSilent参数透传适合在无 TTY 的 CI 环境静默运行。7.2 table 的 getSelectionWidth.tsscore 7覆盖率 7.4%未覆盖 25 行export const getSelectionWidth T extends [TTableCellElement, Path]( cells: T[] ) { let max 0; let lastCellRowIndex getCellRowIndexByPath(cells[0][1]); let total 0; cells.forEach(([cell, cellPath]) { const currentCellRowIndex getCellRowIndexByPath(cellPath); const colSpan cell.colSpan ?? cell.attributes?.colspan; const colSpanNumbered colSpan ? Number(colSpan) : 1; // on the same line if (currentCellRowIndex lastCellRowIndex) { total colSpanNumbered; } else { max Math.max(total, max); total colSpanNumbered; } lastCellRowIndex currentCellRowIndex; }); return Math.max(total, max); };该函数计算合并单元格表格的选择宽度按行遍历单元格累加同行的colSpan行切换时更新最大值。它同时兼容新式cell.colSpan与旧式cell.attributes?.colspan两种属性形态注意Number(colSpan)的字符串兜底是表格合并table merge功能的核心几何计算之一覆盖现状仅 7.4%补测价值明确。7.3 caption 的 withCaption.tsscore 7未覆盖 67 行export const withCaption: OverrideEditorCaptionConfig ({ editor, getOptions, tf: { apply, moveLine }, }) { return { transforms: { apply(operation) { // 拦截 set_selection处理 ↑ 键从表格/图片聚焦到 caption 的逻辑 if (operation.type set_selection) { … } apply(operation); }, moveLine: (options) { // focus caption from image on down arrow if (!options.reverse) { … } return moveLine(options); }, }, }; };这是 caption 插件对编辑器 transform 的 overrideapply中拦截set_selection操作配合isHotkey(up)判断向上箭头在 caption 有内容时把焦点移入moveLine中处理向下箭头聚焦 caption。源码第 12 行留有一条 TODO 注释明确指向尚未完成的测试任务评分机制准确识别了这个有明确 TODO、无测试覆盖的缝隙。7.4 cursor 的 getSelectionRects.tsscore 7未覆盖 60 行export const getSelectionRects (editor: Editor, { range, xOffset, yOffset }): SelectionRect[] { const [start, end] RangeApi.edges(range); const domRange editor.api.toDOMRange(range); // 遍历 range 内文本节点用 toDOMNode document.createRange().getClientRects() // 收集每个文本节点的 client rect并减去 xOffset/yOffset 偏移 … };它把 Slate 的TRange投影成一组 DOMclient rect供光标/选区装饰定位包含两个值得注意的防御分支toDOMRange返回空时提前返回[]domNode?.parentElement不存在时the given Node has no parent 异常场景也返回[]。这些边界分支正是单测应覆盖的焦点。7.5 callout 的 insertCallout.tsscore 7未覆盖 24 行export const CALLOUT_STORAGE_KEY plate-storage-callout; export const insertCallout (editor, { icon, variant, ...options } {}) { editor.tf.insertNodesTCalloutElement( { children: [{ text: }], icon: icon ?? localStorage.getItem(CALLOUT_STORAGE_KEY) ?? , type: editor.getType(KEYS.callout), variant, }, options as any ); };插入 callout 节点的 transform默认图标从localStorage读取并回退到。测试需覆盖localStorage有值/无值、显式传入 icon、variant 透传等路径。7.6 excalidraw 的 insertExcalidraw.tsscore 7未覆盖 21 行export const insertExcalidraw (editor, props {}, options {}) { if (!editor.selection) return; const selectionParentEntry editor.api.parent(editor.selection); if (!selectionParentEntry) return; const [, path] selectionParentEntry; editor.tf.insertNodesTExcalidrawElement( { children: [{ text: }], type: editor.getType(KEYS.excalidraw), ...props }, { at: path, nextBlock: true, ...(options as any) } ); };插入 Excalidraw 画布节点的 transform两个提前返回分支无 selection、无父节点加nextBlock: true的插入行为都是低成本的断言点。八、宽批次剖析score 5共 18 个文件table 包的攻坚战宽批次在严格批次 9 个文件之外追加了 9 个文件全部来自 table 包。这意味着 score 5 的攻坚主题非常清晰补齐 table 合并/查询/变换模块的测试矩阵。追加清单均 score 5文件目录职责withTable.tslib表格插件的编辑器 override 入口deleteRow.tsmerge合并表格中删除行含 rowSpan 收缩/下移逻辑getSelectedCells.tsqueries根据 selection 收集被选中的单元格getTableGridByRange.tsmerge按 range 计算表格网格isTableRectangular.tsmerge判断表格是否为矩形考虑 colspan/rowspansplitTableCell.tsmerge拆分合并单元格moveSelectionFromCell.tstransforms从当前单元格移动 selectiongetSelectedCellsBorders.tsqueries计算选中单元格的边框集合getTableRowIndex.tsqueries获取单元格所在行索引以 deleteRow.ts 为例其核心复杂度在合并单元格场景它先收集受影响单元格集合按列优先迭代保证顺序再分为需要下移到下一行的跨行单元格moveToNextRowCells与需要收缩 rowSpan 的单元格squizeRowSpanCells两类分别处理最后删除目标行当删除首行且无下一行时直接tf.remove.table()整表删除。rowSpan的数值计算rowsDeleteNumber、endingRowIndex、rowsNumberAffected充满边界条件是表格合并删除这个高风险行为的核心覆盖价值极高。再看 isTableRectangular.ts它把每行的有效单元格数按getRowSpan展开、累加getColSpan记录到数组最后检查所有行是否相等——这是判断表格能否按矩形网格操作如合并、拆分的前提纯计算、易测试正是 score 5 定位的低垂果实。值得强调的是这些文件在仓库中大多已存在对应 spec 测试如getSelectionWidth.spec.ts、isTableRectangular.spec.ts、deleteRow.spec.tsx、getSelectedCells.spec.tsx、getTableGridByRange.spec.tsx、splitTableCell.ts、moveSelectionFromCell.spec.tsx、getSelectedCellsBorders.spec.tsx、getTableRowIndex.spec.tsx均可在 packages/table/src/lib/merge 与 packages/table/src/lib/queries 目录下找到——本分诊文档2026-03-24评估的是当时这些 spec 尚未覆盖/未达标的状态后续清扫批次如 2026-03-24 起的coverage-priority-map-post-*系列文档持续消化了这份清单读者可对照 docs/plans 目录下的后续文档追踪进展。九、方法论 Notes三个容易被忽略的治理细节文档末尾的 Notes 补充了三个保证分诊结果可信的关键决策近期批次显式惩罚3 月 22 日24 日已完成清扫的包在本次排名中被显式扣分Recent March 22-24 passes were explicitly penalized避免排名反复把已扫过的包重新顶上来——这是对清扫-重测-再清扫循环空转的防御/react是排除而非降权React 相关文件不是排名靠后而是直接不参与评分excluded, not merely ranked lower从源头保证剩余清单全部是纯逻辑 seamTSV 输出保留完整矩阵除本文的聚合视图外分诊还产出了包含完整包矩阵与完整文件矩阵的 TSV 输出供需要精确分诊exact triage的场景逐行核对。这套方法本质上是一个可迭代的治理闭环跑覆盖率 → 生成 lcov → 按信号打分 → 按分数与包价值排序 → 攻坚最高分批次 → 下一轮运行时自动惩罚已扫包。每一轮运行都会因近期清扫惩罚而把火力转移到新的缝隙上避免局部过拟合。十、在 Plate 仓库中复现与延伸如果你想在当前仓库复现这套工作流步骤如下跑覆盖率基线使用仓库根 package.json 的test:coverage或文档命令bun test --coverage --coverage-reporterlcov --coverage-dir.coverage-repo-YYYY-MM-DD --reporterdots解析 lcov以.coverage-repo-*/lcov.info为输入按文档的评分信号seam 类型、未覆盖行数、覆盖率比例、近期完成批次、包信号惩罚给packages/**/src/**下非/react文件打分输出两级批次score 6作为严格批次优先攻坚score 5作为宽批次兜底对照源码补测试严格批次优先处理 depset 四件套get-package-manager.ts、logger.ts、handle-error.ts、spinner.ts随后是 table 合并/查询/变换矩阵与 caption、cursor、callout、excalidraw 的单点文件下一轮自动换目标新批次完成后重跑覆盖率靠近期清扫惩罚自然把排名让给下一批缝隙。该方法与仓库中大量后续分诊文档如 2026-03-24-coverage-priority-map-post-batch.md、2026-03-24-full-repo-coverage-roadmap.md、2026-03-24-non-react-coverage-roadmap.md构成完整闭环读者可以在 docs/plans 目录中按时间线追踪这套治理方法的演进与落地效果。结语本文完整还原了 Plate 仓库 2026-03-24 覆盖率优先级分诊的输入、方法、数据与结论bun test --coverage一次 2643 通过、0 失败、493 文件、2.45 秒的确定性基线产出score 69 文件与score 518 文件两级攻坚清单价值高度集中在udecode/depset优先级 444与table优先级 210两个包。核心方法论——非 React seam 过滤、五信号评分、近期清扫惩罚、包信号惩罚——不仅适用于本仓库也可以直接移植到任何以测试覆盖率持续治理为目标的 monorepo 项目中。【免费下载链接】plateRich-text editor with AI and shadcn/ui项目地址: https://gitcode.com/GitHub_Trending/pl/plate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
