react-spectrum test-utils RC API 迁移指南:test-utils-rc-update Codemod 深度解析
2026/9/14 2:07:28 网站建设 项目流程

react-spectrum test-utils RC API 迁移指南:test-utils-rc-update Codemod 深度解析

【免费下载链接】react-spectrumA collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.项目地址: https://gitcode.com/GitHub_Trending/re/react-spectrum

在 react-spectrum 仓库中,@react-aria/test-utils@react-spectrum/test-utils的 tester API 从 beta 升级到 RC 时发生了大规模重命名:属性式访问器变成了带get前缀的方法,部分参数键也随之统一。这篇指南以仓库中官方提供的迁移工具test-utils-rc-updatecodemod 为主体,结合其完整源码实现,讲解它的重命名规则全集、命令行用法与选项,以及它如何安全地识别 tester 变量并完成多轮 AST 改写——读完后你可以直接用它批量升级测试文件,并理解每一步改写的底层原理。

一、这个 codemod 解决什么问题

@react-spectrum/codemods是 react-spectrum 仓库自带的迁移工具包(package.json),它基于 jscodeshift 构建,随包发布一个可执行 CLI(bin: dist/index.js),内置多个 codemod,其中test-utils-rc-update专用于把使用旧版 beta tester API 的测试文件改写为 RC 版本 API(见 README)。

官方 README 给出的核心变更示例如下:

- tester.listbox; + tester.getListbox(); - tester.options(); + tester.getOptions(); - await tester.selectOption({option: 'Save'}); + await tester.toggleOptionSelection({option: 'Save'}); - tester.findOption({optionIndexOrText: 'Save'}); + tester.findOption({indexOrText: 'Save'});

可以归纳为三类变更:

  1. 属性访问改为方法调用tester.listbox这类属性(getter)统一改为tester.getListbox()形式的方法调用;
  2. 方法重命名:如options()getOptions()selectOption()toggleOptionSelection()
  3. 查找方法的参数键统一findOption({optionIndexOrText: ...})findRow({rowIndexOrText: ...})等按组件名区分的参数键,统一改为indexOrText

该 codemod 覆盖全部 tester 类型:ListBoxComboBoxSelectMenuTableGridListTreeTabsRadioGroupCheckboxGroupDialog

二、完整重命名映射表

README 只列了部分示例,而 codemod.ts 中定义的RENAME_MAP(L5-L37)才是规则全集。下表即 beta → RC 的完整 getter/方法映射:

组件beta 写法RC 写法
CheckboxGroupcheckboxgroup/checkboxes/selectedCheckboxesgetCheckboxGroup()/getCheckboxes()/getSelectedCheckboxes()
ComboBoxcombobox/trigger/listbox/sections/options()/focusedOptiongetCombobox()/getTrigger()/getListbox()/getSections()/getOptions()/getFocusedOption()
Dialogtrigger/dialoggetTrigger()/getDialog()
GridListgridlist/rows/selectedRows/cells()getGridlist()/getRows()/getSelectedRows()/getCells()
ListBoxlistbox/selectedOptions/sections/options()getListbox()/getSelectedOptions()/getSections()/getOptions()
Menutrigger/menu/submenuTriggersgetTrigger()/getMenu()/getSubmenuTriggers()
RadioGroupradiogroup/radios/selectedRadiogetRadioGroup()/getRadios()/getSelectedRadio()
Selecttrigger/listbox/sections/options()getTrigger()/getListbox()/getSections()/getOptions()
Tabletable/rowGroups/columns/rows/selectedRows/rowHeaders/cells()getTable()/getRowGroups()/getColumns()/getRows()/getSelectedRows()/getRowHeaders()/getCells()
Tabstablist/tabs/tabpanels/selectedTab/activeTabpanelgetTablist()/getTabs()/getTabpanels()/getSelectedTab()/getActiveTabpanel()
Treetree/rows/selectedRows/cells()getTree()/getRows()/getSelectedRows()/getCells()
通用selectOption()toggleOptionSelection()

find*系列的参数键映射则由FIND_PARAM_KEY_MAP(codemod.ts L39-L45)定义,将各组件专属键统一为indexOrText

find 方法旧参数键新参数键
findCheckboxcheckboxIndexOrTextindexOrText
findOptionoptionIndexOrTextindexOrText
findRowrowIndexOrTextindexOrText
findRadioradioIndexOrTextindexOrText
findTabtabIndexOrTextindexOrText

这些参数名可以直接在@react-aria/test-utils的 RC 源码中验证:例如 listbox.ts 的findOption(opts: {indexOrText: number | string})、combobox.ts、gridlist.ts、checkboxgroup.ts,签名均为indexOrText: number | string,支持传索引(数字)或文本(字符串)定位元素。

三、使用方法与命令行选项

在你的项目目录(即需要更新测试文件的目录)中运行:

npx @react-spectrum/codemods test-utils-rc-update

README 文档列出的选项如下:

  • --ignore-pattern:要忽略文件的 glob 模式,默认值为**/node_modules/**
  • --dry:试运行模式,只报告变更、不实际修改文件;
  • --path:要运行 codemod 的目录路径,默认为当前目录。

从 CLI 入口 src/index.ts 可以看到更完整的实际行为:

  • 选项定义中还注册了--dry的短选项-d,以及--parser--components--agent(后两者主要服务于 s1-to-s2 codemod);
  • 所有 codemod 强制注入默认值:parser: 'tsx'ignorePattern: '**/node_modules/**'path: '.',以及扫描扩展名extensions: 'js,jsx,mjs,cjs,ts,tsx'——也就是说 JavaScript 与 TypeScript 测试文件都会被处理,命令行传入的值会覆盖这些默认值;
  • CLI 通过node:utilparseArgs解析参数,第一个位置参数为 codemod 名称(s1-to-s2use-monopackagesuse-subpathstest-utils-rc-update),传入未知名称会报错并打印可用列表(index.ts L107-L124);
  • 运行环境要求 Node.js >= 22.14.0(见 package.json 的engines字段)。

推荐的稳妥流程是先跑一次 dry run 检查命中范围,确认无误后再正式执行:

npx @react-spectrum/codemods test-utils-rc-update --path ./src --dry npx @react-spectrum/codemods test-utils-rc-update --path ./src

四、源码级原理:tester 变量识别与多轮 AST 改写

理解这个 codemod 的实现(codemod.ts)有助于判断它的适用边界。整体流程分为“识别 tester 变量”和“四轮改写”两个阶段。

4.1 解析器配置:保留格式并容忍语法

transformer 入口(L50-L83)通过api.jscodeshift.withParser自定义了解析器:用 recast 包裹@babel/parser,开启jsxtypescripttopLevelAwaitoptionalChainingnullishCoalescingOperator等 14 个插件,并设置tokens: trueerrorRecovery: true。这意味着它能完整解析 TypeScript 测试文件中常见的await、可选链、非空断言等语法,且借助 recast 尽量保留未改动代码的原始格式。

4.2 初始识别:只有 createTester 产物才会被改写

第一遍扫描(L102-L119)找出所有形如const tester = createTester(...)const tester = await xxx.createTester(...)(含user.createTester('Menu', {root: el})这类成员调用写法)的变量声明,并把变量名收集进testerVarNames集合。unwrapAwait辅助函数(L87-L92)会先剥掉AwaitExpressionTSNonNullExpression包装层再判断调用。

关键安全边界:如果文件中一个 tester 变量都没找到,直接原样返回源文件(L121-L123)。因此someOtherObject.rowsconfig.options()这类与 tester 无关的同名属性不会被误伤——这一保证由 codemod.test.ts 中 “does not rename accessors on non-tester variables” 用例专门验证。

4.3 传播跟踪:openSubmenu 返回的新 tester

Menu tester 的openSubmenu()会返回一个新的子菜单 tester,代码中常见这样的写法:

const menuTester = user.createTester('Menu', {root: el}); let submenuUtil = (await menuTester.openSubmenu({submenuTrigger: 'Share…'}))!; expect(submenuUtil.options()).toHaveLength(2);

为了让submenuUtil也被纳入改写范围,codemod 用TESTER_RETURNING_METHODS(L48,目前只含openSubmenu)标记“返回 tester 的方法”,然后做定点迭代传播:每轮扫描变量声明,若某变量被赋值为已知 tester 调用openSubmenu的结果,就加入集合并置位继续下一轮,直到没有新增(L125-L149)。这能正确处理嵌套子菜单的多级传播(见测试用例 “handles variables assigned from openSubmenu and optional chaining on them”,codemod.test.ts L311-L333,其中nestedSubmenusubmenuTester?.openSubmenu(...)得来,其上的?.menu?.trigger?.options()全部被正确改写)。

4.4 四轮改写:覆盖普通调用、属性访问与可选链

改写阶段由四个 pass 组成,renamePropInCallee(L153-L168)是共享的核心逻辑——校验调用者对象是否为已识别的 tester 变量,再查RENAME_MAP替换方法名:

  1. Pass 1(L170-L178):改写方法调用tester.options()tester.getOptions()Pass 1B(L180-L185):同理处理可选调用tester?.options()tester?.getOptions()
  2. Pass 2(L187-L209):把纯属性访问改写为方法调用,tester.rowstester.getRows()。此处有一个重要的防重逻辑(L201-L204):若该属性访问正是某个调用的 callee(即tester.options后面紧跟()),则跳过,避免与 Pass 1 重复改写;Pass 2B(L211-L237):处理tester?.rows这类可选成员访问,生成tester?.getRows()可选调用形式,并同样跳过作为 callee 的场景;
  3. Pass 3(L239-L271):改写find*调用的第一个对象参数中的键名,{optionIndexOrText: x}{indexOrText: x}。仅当第一个参数是对象字面量(ObjectExpression)时才处理,键通过Identifier精确匹配,值不做改动。

最后(L273)仅在有实际变更(didChange)时用单引号风格输出新源码,否则原样返回——对未命中的文件零 diff 副作用。

4.5 测试用例矩阵

codemod.test.ts 基于 jscodeshift 的defineInlineTest建立了完整的“输入/输出”快照测试,覆盖了全部 11 种 tester 类型以及若干边界场景:

  • 每个 tester 类型一个用例(checkboxgroup、combobox、dialog、gridlist、listbox、menu、radiogroup、select、table、tabs、tree),同时覆盖 getter 重命名、selectOptiontoggleOptionSelectionfind*参数键改名;
  • 内联使用(L261-L277):expect(tester.tree)expect(tester.rows.length)这类不用中间变量的写法也能改写;
  • 可选链(L279-L293):tester?.menutester?.submenuTriggers[0]正确生成tester?.getMenu()tester?.getSubmenuTriggers()[0]
  • 非 tester 变量不误伤(L243-L259):与第 4.2 节的安全边界对应;
  • openSubmenu 传播 + 非空断言 + 可选链组合(L295-L333)。

五、升级后的测试代码长什么样

以 Menu 为例,codemod 之前的测试与之后(与 codemod.test.ts L109-L129 用例一致):

// 之前(beta API) const tester = user.createTester('Menu', {root: el}); tester.trigger; tester.menu; tester.sections; tester.options(); tester.submenuTriggers; await tester.selectOption({option: 'Bar', menuSelectionMode: 'single'}); // 之后(RC API,codemod 自动生成) const tester = user.createTester('Menu', {root: el}); tester.getTrigger(); tester.getMenu(); tester.getSections(); tester.getOptions(); tester.getSubmenuTriggers(); await tester.toggleOptionSelection({option: 'Bar', menuSelectionMode: 'single'});

注意toggleOptionSelection的其余参数(如menuSelectionMode)完全不受影响,codemod 只改名不动参数。

六、适用前提与注意事项

  • 适用前提:测试文件必须通过createTester(...)(含user.createTester(...)成员形式)创建 tester 变量。codemod 从源码结构看依赖这一模式做变量识别,若你封装了自己的 helper 间接返回 tester 且封装点不在被扫描范围内,链路上的新变量可能不会被传播跟踪(传播仅针对openSubmenu)。
  • 扫描范围:默认扫描js,jsx,mjs,cjs,ts,tsx五种扩展名,忽略**/node_modules/**,可用--ignore-pattern--path调整;
  • 试运行优先:大仓库升级建议先--dry(或-d)观察报告,diff 确认后落盘;
  • 环境要求:Node.js >= 22.14.0(见 codemods package.json);
  • 运行方式npx @react-spectrum/codemods test-utils-rc-update在你的业务项目中执行,它修改的是你的项目文件而非本仓库;本仓库内该 codemod 的源码位于 packages/dev/codemods/src/test-utils-rc-update/,可直接阅读实现与测试以核对行为。

完成 codemod 运行后,建议再全局搜索一遍旧键名(如optionIndexOrTextselectOption)确认没有遗漏在动态构造或字符串拼接里的写法,然后运行测试套件验证改写结果。

【免费下载链接】react-spectrumA collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.项目地址: https://gitcode.com/GitHub_Trending/re/react-spectrum

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询