news 2026/9/10 21:07:39

React 19 Test Guardian 实战指南:从 act 导入修复到零失败测试套件

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
React 19 Test Guardian 实战指南:从 act 导入修复到零失败测试套件

React 19 Test Guardian 实战指南:从 act 导入修复到零失败测试套件

【免费下载链接】awesome-copilotCommunity-contributed instructions, agents, skills, and configurations to help you make the most of GitHub Copilot.项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-copilot

导读

react19-test-guardian是当前仓库中 React 19 迁移套件(react19-upgrade 插件)里负责测试文件迁移与验证的专项子代理。它的使命只有一条:把所有测试文件迁移到 React 19 兼容形态,并持续运行测试套件直到npm test报出0 失败。本文以 agents/react19-test-guardian.agent.md 为核心骨架,结合仓库中同套件的 react19-commander、react19-auditor、react19-migrator 以及 react19-test-patterns 技能 等源码证据,系统讲解测试迁移的 8 个标准改造点(T1–T8)、执行循环、错误分类表与完成门槛。读完本文,你将掌握一套可直接照做的"测试套件修复 + 验证"方法论,以及每条修复背后的 React 19 行为变更原理。


一、Agent 定位:迁移管线中的"守门员"

1.1 在 React 19 迁移管线中的角色

在 react19-commander.agent.md 定义的迁移管线中,整个 React 18 → React 19 升级被拆成四个串行阶段:

  1. Audit(审计)— 由react19-auditor全库扫描破坏性变更与弃用模式,产出.github/react19-audit.md报告;
  2. Deps(依赖手术)— 由react19-dep-surgeon升级 react@19、@testing-library/react@16+ 并清零 peer 依赖冲突;
  3. Migrate(源码迁移)— 由react19-migrator重写所有非测试源码中的废弃 API;
  4. Tests(测试修复与验证)— 由react19-test-guardian修复全部测试文件,并把测试套件跑到全绿。

react19-test-guardian的元数据(frontmatter)写得很清楚:

name: react19-test-guardian description: 'Test suite fixer and verification specialist. Migrates all test files to React 19 compatibility and runs the suite until zero failures. Uses memory to track per-file fix progress and failure history. Does not stop until npm test reports 0 failures. Invoked as a subagent by react19-commander.' user-invocable: false

关键信息:user-invocable: false表明它不面向用户直接调用,而是由react19-commander通过#tool:agent机制以子代理方式激活(见 react19-commander.agent.md 中 PHASE 4 的调用指令)。这保证了迁移管线的"串行 + 门禁"纪律——只有测试全绿,commander 才会把迁移状态推进到done

1.2 与 React 18 Test Guardian 的对照

仓库中还存在 react18-test-guardian.agent.md,两者同为"测试守护者",但关注点不同:

维度React 18 Test GuardianReact 19 Test Guardian
核心痛点Enzyme 无 React 18 支持、RTL v14userEvent异步化、自动批处理回归react-dom/test-utils被移除、StrictMode 不再双调用 effect、错误日志行为变化
关键 APIact()异步语义、waitForactreact导入、fireEvent替换Simulate
相同点都是"跑到 0 失败才停"、都用记忆协议断点续传同左

这种"先升到 18.3.1,再升到 19"的两步走策略,在 plugins/react19-upgrade/README.md 的 Prerequisite 一节有明确说明。


二、记忆协议(Memory Protocol):断点续传的关键

测试修复往往跨多个会话。react19-test-guardian使用vscode/memory工具把进度持久化,保证会话中断后可以从上次位置继续:

1. 会话开始时读取既往状态:

#tool:memory read repository "react19-test-state"

2. 每修复一个文件后写入检查点:

#tool:memory write repository "react19-test-state" "fixed:[filename]"

3. 每完成一轮完整测试运行后记录失败数:

#tool:memory write repository "react19-test-state" "run-[N]:failures:[count]"

该协议与同套件其他代理一脉相承:react19-commanderreact19-migration-state记录phase/auditComplete/depsComplete/migrateComplete/testsComplete/reactVersion/failedTests/lastRun构成的 JSON 状态(见 react19-commander.agent.md);react19-auditorreact19-audit-progress记录各扫描阶段进度。整个套件因此具备"中断可恢复"能力——这是大规模迁移工程里极其实用的设计。


三、启动序列(Boot Sequence):先摸底,再动手

代理开工的第一步是两条命令:

# 1. 列出全部测试文件,掌握改造范围 find src/ \( -name "*.test.js" -o -name "*.test.jsx" -o -name "*.spec.js" -o -name "*.spec.jsx" \) | sort # 2. 基线运行——先跑一遍,捕获当前失败数量 npm test -- --watchAll=false --passWithNoTests --forceExit 2>&1 | tail -30

然后把基线失败数写入记忆:baseline: [N] failures

这里的命令参数值得逐一理解:

  • --watchAll=false:关闭 Jest 监听模式,跑完即退出,适配 CI 与脚本场景;
  • --passWithNoTests:仓库中暂时没有测试文件时也不报错,避免干扰迁移前期流程;
  • --forceExit:强制进程退出,防止有异步句柄未关闭导致脚本挂起(迁移期测试本身不稳定时很实用)。

与 react19-auditor.agent.md 的 PHASE 4 相呼应,auditor 会预先用以下 grep 扫描出测试侧的雷区,test-guardian 启动后可直接对照react19-audit.md里"Test Files Requiring Changes"清单逐文件开工:

# act import 位置错误 grep -rn "from 'react-dom/test-utils'" src/ --include="*.test.*" --include="*.spec.*" # Simulate 使用(已移除) grep -rn "Simulate\." src/ --include="*.test.*" --include="*.spec.*" # react-test-renderer(已弃用) grep -rn "react-test-renderer" src/ --include="*.test.*" --include="*.spec.*" # 间谍调用次数断言(可能受 StrictMode 变化影响) grep -rn "toHaveBeenCalledTimes" src/ --include="*.test.*" --include="*.spec.*" | head -20

四、测试迁移参考:8 个标准改造点(T1–T8)

T1:act() 导入修复 —— 最高优先级

变更事实:act不再从react-dom/test-utils导出,必须改为从react导入。

扫描:

grep -rn "from 'react-dom/test-utils'" src/ --include="*.test.*"

改前 / 改后:

// Before import { act } from 'react-dom/test-utils' // After import { act } from 'react'

在 skills/react19-test-patterns/SKILL.md 中,act导入被列为第一优先级,原因很直接:"fix first, it unblocks everything else"——act是 React 测试同步渲染的基石,导入位置不对,后面几乎所有交互类断言都会连锁失败。若原导入混有其他 test-utils 导出,需要拆分导入:

// Before(混合导入) import { act, Simulate, renderIntoDocument } from 'react-dom/test-utils'; // After(拆分导入) import { act } from 'react'; import { fireEvent, render } from '@testing-library/react';

T2:Simulate → fireEvent

变更事实:Simulate已从react-dom/test-utils移除,交互触发统一改用 Testing Library 的fireEvent

扫描:

grep -rn "Simulate\." src/ --include="*.test.*"

改前 / 改后:

// Before import { Simulate } from 'react-dom/test-utils'; Simulate.click(element); Simulate.change(input, { target: { value: 'hello' } }); // After import { fireEvent } from '@testing-library/react'; fireEvent.click(element); fireEvent.change(input, { target: { value: 'hello' } });

react19-test-patterns 补充了更多常见映射:Simulate.submit(form)fireEvent.submit(form)Simulate.keyDown(element, { key: 'Enter', keyCode: 13 })fireEvent.keyDown(element, { key: 'Enter', keyCode: 13 })

T3:react-dom/test-utils 全量导出清理

React 19 移除了react-dom/test-utils的大部分导出,官方映射如下:

旧 API(react-dom/test-utils)新方案
actimport { act } from 'react'
SimulatefireEventfrom@testing-library/react
renderIntoDocumentrenderfrom@testing-library/react
findRenderedDOMComponentWithTagRTL 查询(getByRolegetByTestId等)
scryRenderedDOMComponentsWithTagRTL 查询(getAllByRole等)
isElementisCompositeComponent直接删除——RTL 下不再需要

react19-test-patterns 中还有两条补充映射:findRenderedDOMComponentWithClassgetByRolecontainer.querySelectorisDOMComponent→ 直接删除。

这里体现的是 RTL 的哲学:测试"行为与输出"而非"实现细节",旧的手工 DOM 断言大多可以被语义化查询替代。

T4:StrictMode 间谍调用次数更新

行为变更事实:React 19 的 StrictMode 在开发环境下不再双调用 effect

  • React 18:StrictMode dev 下 effect 执行两次 → 相关 spy 被调用 ×2 / ×4;
  • React 19:effect 只执行一次 → spy 调用 ×1 / ×2。

核心策略:跑测试、读实际次数、按实际值更新断言,绝不靠猜。文档明确要求用如下命令从失败信息中提取真实计数:

# 只跑失败的那个测试,抓取实际计数 npm test -- --watchAll=false --testPathPattern="ComponentName" --forceExit 2>&1 | grep -E "Expected|Received|toHaveBeenCalled"

react19-test-patterns 给出了精细化的判断口径,值得注意:

// effect 类调用:React 18 双调用 ×2 → React 19 单次 ×1 expect(mockFn).toHaveBeenCalledTimes(2); // 改为 1 // 渲染阶段调用(组件函数体)在 React 19 StrictMode 下仍然双调用: expect(renderSpy).toHaveBeenCalledTimes(2); // 保持 2 不变

也就是说:并不是所有 spy 计数都减半,只有 effect 相关的调用才受"不再双调用"影响,渲染函数体依旧双调用。这正是文档强调"measure, don't guess"的原因。

T5:测试中的 useRef 形态

React 19 中未初始化useRef()current初值从undefined变为null。凡是在测试里直接构造 ref 或断言 ref 形态的代码都需要同步:

// Before const ref = { current: undefined }; // After const ref = { current: null };

T6:自定义 render 助手校验

很多项目会封装test-utils.js/renderWithProviders/customRender之类的渲染助手。React 19 下必须确认这些助手基于 RTL 的render实现(其内部使用createRoot),而不是过时的ReactDOM.render

定位:

find src/ -name "test-utils.js" -o -name "renderWithProviders*" -o -name "custom-render*" 2>/dev/null grep -rn "customRender\|renderWith" src/ --include="*.js" | head -10

若发现旧实现,用 RTL 的render+wrapper改写。这一步"每个代码库只需校验一次,而不是每个测试文件各查一遍",性价比极高(该要点同样出现在 react19-test-patterns 的优先级顺序第 6 条)。

T7:错误边界(Error Boundary)测试更新

行为变更事实:React 19 改变了错误日志行为——错误不再被重复上报。

// Before(React 18):console.error 被调用两次(React 记录 + 重新抛出) expect(console.error).toHaveBeenCalledTimes(2); // After(React 19):只调用一次 expect(console.error).toHaveBeenCalledTimes(1);

扫描:

grep -rn "ErrorBoundary\|console\.error" src/ --include="*.test.*"

T8:异步 act() 包裹

若运行测试时看到Warning: An update to X inside a test was not wrapped in act(...),说明存在异步状态更新未包裹进act。React 19 对act纪律的要求与 React 18 一脉相承,交互触发后应立即包裹:

// Before fireEvent.click(button); expect(screen.getByText('loaded')).toBeInTheDocument(); // After await act(async () => { fireEvent.click(button); }); expect(screen.getByText('loaded')).toBeInTheDocument();

五、执行循环(Execution Loop):批次修复 + 单文件确认

5.1 Round 1:按审计报告逐文件修复

第一轮严格依据.github/react19-audit.md中"Test Files Requiring Changes"清单,对每个文件套用 T1–T8 对应改造,每完成一个文件写入记忆检查点(fixed:[filename])。

5.2 批次后整体回归

每完成一批,跑一次完整套件并只抓汇总行,快速判断进展:

npm test -- --watchAll=false --passWithNoTests --forceExit 2>&1 | grep -E "Tests:|Test Suites:|FAIL" | tail -15

5.3 Round 2+:逐个击破剩余失败

对每个 FAIL 文件执行标准化五步:

  1. 打开失败测试文件;

  2. 读取确切错误信息;

  3. 应用对应修复;

  4. 只重跑该文件确认修复生效:

    npm test -- --watchAll=false --testPathPattern="FailingFile" --forceExit 2>&1 | tail -20
  5. 写入记忆检查点。

循环往复,直到FAIL行数为零。

这种"整批跑 + 单文件验"的双循环节奏,与 react19-commander.agent.md 中"子代理说完成不算数,必须用命令验证门禁"的规则完全一致。


六、错误分类表(Error Triage Table):一线排障速查

文档给出了 7 种高频错误的即查即用对照表:

错误原因修复
act is not a function导入位置错误import { act } from 'react'
Simulate is not defined导出被移除替换为fireEvent
Expected N received M(调用次数)StrictMode 行为差异跑测试,用实际次数更新
Cannot find module react-dom/test-utils包已被掏空切换全部相关导入
cannot read .current of undefineduseRef()形态变化初始值设为null
not wrapped in act(...)异步状态更新包裹进await act(async () => {...})
Warning: ReactDOM.render is no longer supported设置/助手中残留旧渲染 API改为createRoot

最后一条虽然报错出现在测试运行期,根源往往是源码或测试助手中残留ReactDOM.render——这在 react19-migrator.agent.md 的 M1 中已有标准改写(import { createRoot } from 'react-dom/client'+root.render(<App />))。若测试侧命中此错误,应回头检查第 T6 步的渲染助手是否已更新。


七、完成门槛(Completion Gate):什么才算"真正完成"

7.1 最终验证命令

echo "=== FINAL TEST SUITE RUN ===" npm test -- --watchAll=false --passWithNoTests --forceExit --verbose 2>&1 | tail -30 # 抽取结果行 npm test -- --watchAll=false --passWithNoTests --forceExit 2>&1 | grep -E "^Tests:"

并向记忆写入最终状态:

#tool:memory write repository "react19-test-state" "complete:0-failures:all-tests-green"

7.2 返回 commander 的硬性条件

只有同时满足以下全部条件才允许向上汇报:

  • Tests: X passed, X total,失败数为 0;
  • 没有删除任何测试(删除 = 隐藏问题,不是修复);
  • 没有新增任何.skip测试
  • 既有的.skip测试必须按名字逐一记录在案。

7.3 三次修复失败的处理

若某个测试连续尝试 3 次仍无法修复,规则是:不静默跳过,而是把该测试写入.github/react19-audit.md的 "Blocked Tests" 章节,注明导致问题的具体 React 19 行为变更,并把这份清单交还 commander 决策。这与 react18-test-guardian.agent.md 末尾"失败必须上报数量与组件名,不得悄悄略过"的纪律一脉相承——整个套件对"假装完成"是零容忍的。


八、从源码证据看:为什么这 8 个改造点缺一不可

把 test-guardian 的修复项与套件内其他文档对照,可以看到每条修复背后都有 React 19 真实变更支撑,绝非凭空设计:

  • 移除类变更(T1/T2/T3):react-dom/test-utils的导出在 React 19 中被大量移除,auditor 的 PHASE 2 明确把from 'react-dom/test-utils'列为"破坏性、必须修复"(见 react19-auditor.agent.md);
  • 行为类变更(T4/T7):StrictMode 不再双调用 effect、错误日志只记录一次,都会直接冲击toHaveBeenCalledTimes断言,而 plugins/react19-upgrade/README.md 的 "Behavioral Changes" 章节对这两条有明文确认;
  • 形态类变更(T5):useRef初值变化与源码迁移的 M9(useRef()useRef(null))互为表里(见 react19-migrator.agent.md);
  • 环境类变更(T6/T8):测试助手的渲染底座必须切换到基于createRoot的 RTL v16+,异步更新必须遵守act纪律。

换言之,test-guardian 的 T1–T8 覆盖了 React 19 对测试代码的全部已知冲击面:导入路径、交互 API、调用计数、ref 形态、渲染底座、错误日志、异步纪律。按此清单执行,配合记忆协议断点续传与"零失败才放行"的门禁,即可把 React 19 升级的最后一道关卡——测试全绿——稳妥拿下。


九、快速上手路径

若你正面临 React 18 → React 19 升级,可以直接以本仓库的 react19-upgrade 插件为参考模板(详见 plugins/react19-upgrade/README.md):

  1. 按顺序激活管线react19-commander会依次调度 auditor → dep-surgeon → migrator → test-guardian,任何阶段不达门禁不前进;
  2. 让 test-guardian 接管测试侧:把 T1–T3 的导入清理放在最前(它们是其他一切修复的前提),T4 务必"实测计数而非猜测",T6 只需全库校验一次;
  3. 用记忆协议保底:任何一步中断,下个会话都能从检查点续跑,不会重复劳动。

整套方法不仅适用于 React 19,其"批次修复 + 单文件验证 + 错误分类速查 + 零失败门禁"的工程范式,对任何大型框架升级中的测试迁移都同样有效。

【免费下载链接】awesome-copilotCommunity-contributed instructions, agents, skills, and configurations to help you make the most of GitHub Copilot.项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-copilot

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 21:05:45

昇腾CANN/GE矩阵乘API

aclblasCreateHandleForHgemm 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorc…

作者头像 李华
网站建设 2026/9/10 21:01:35

Python股票量化分析工作流:从数据获取到回测验证

简介&#xff1a;本资源是一套高完成度的毕业设计项目&#xff0c;面向计算机及相关专业本科生、研究生及机器学习初学者&#xff0c;聚焦股票价格预测与量化分析实战场景&#xff0c;提供从数据获取、特征工程、模型训练到结果可视化的完整技术链路。压缩包共2000个文件&#…

作者头像 李华
网站建设 2026/9/10 20:59:45

CANN/ge废弃配置参数说明

后续版本废弃配置 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、TensorFl…

作者头像 李华
网站建设 2026/9/10 20:57:47

Firefox 149版本多任务处理与硬件加速全面升级

1. Firefox 149版本核心升级解析Mozilla基金会于本月正式推送了Firefox 149稳定版更新&#xff0c;这次版本迭代聚焦于多任务处理能力的全面提升。作为一名长期跟踪浏览器技术演进的从业者&#xff0c;我在实际测试中发现本次更新包含多项底层架构改进&#xff0c;特别是对硬件…

作者头像 李华