SuperAPI AI 工具集

debug-menu-and-test-hooks

测试与质量

查看项目

更新于 10-18 18:03

技能介绍

用途

该技能解决的是 React Native(Expo)应用中“仅测试用代码”的隔离问题:调试菜单、调试深链、模拟时钟与模拟离线网络只在开发与预览构建中存在,生产构建中完全不含这些代码。所有入口都收敛到一个字面量开关 TEST_ONLY,并由检查脚本在应用自身代码上验证开关、入口、接线、注册表与模拟实现。

主要能力

  • 单一开关:src/test-only.ts 是进入测试专用代码的唯一途径,其表达式为 export const TEST_ONLY: TestOnlyApi | null = process.env.EXPO_PUBLIC_APP_VARIANT === 'production' ? null : (require('./test-only-entry') as TestOnlyApi);。资料说明 Metro 只会在收集 require() 调用之前折叠这一字面量比较,因此使用导入常量、辅助函数、DEV 或 NODE_ENV 都会导致代码被发布到商店或从预览构建中被剥离。
  • 派生类型:TestOnlyApi 取自入口的 typeof,而非手写,避免类型与导出不一致,也避免 knip 把入口导出全部报为未使用。
  • 入口清单与哨兵字符串:入口只包含 export { ... } from 行以及 TEST_BUILD_SENTINEL = '<APP>_TEST_BUILD_ONLY',该字符串不出现在其他应用文件中,用于在打包与构建检查中证明剥离生效。
  • 统一读取:每个导出都通过 TEST_ONLY?.member 被启动流程、根栈或调试路由读取,且每个被入口再导出的文件夹都登记在 contracts/architecture.json 的 testOnlyFolders 中。
  • 启动时包装端口:在应用注册表的 prepareRootKits(src/services/create-root-kits.ts)中调用 TEST_ONLY?.createTestHooks({ services: device, ... }),由 AppRoot 的 startApp 最先调用,在数据层打开之前替换时钟,并在服务持有连接性时替换连接性;所有读取方拿到 testHooks?.services ?? device。
  • 模拟时钟:只改变 today(),每次调用都读取模拟日期;nowMs() 与 msUntilNextLocalDay() 始终调用真实时钟,useToday() 仍在真实午夜翻转。
  • 模拟离线:在可达性中枢上实现完整的 ConnectivityPort,模拟时为 none,否则为真实可达性,isOnline 由其派生,报告被丢弃,stop() 释放真实适配器。
  • 调试深链:<scheme>://debug/setup?... 对应路由 src/app/(debug)/debug/setup.tsx,渲染 TEST_ONLY?.DebugSetupRoot 并使用 useLocalSearchParams(),生产环境重定向回首页;DEBUG_LINK_PARAMS 与 contracts/debug-links.json 列出相同参数与取值。
  • 严格解析:未知、重复或非法参数返回类型化的 debug-link-* 失败,不应用任何变更,记录日志并显示 debug.setup-error。
  • 执行顺序:先开关,再保存的设置(通过应用的 DebugSettingsTarget,没有则拒绝),最后 router.replace 到目标屏幕;方向重启会结束本次运行;每一步都设置值,因此同一链接应用两次不会产生额外变化。
  • 开关存储:src/stores/debug-overrides/(reducer、cases、先持久化后发布)保存在 expo-sqlite/kv-store 的 debug.overrides 下,链接与菜单通过相同 action 写入。
  • 动效与探针:调试套件(TEST_ONLY?.createDebugKit,注册在应用根套件注册表)填充根的 motion 槽位,TestHooksRoot 取代 MotionConfig 并在 motion=still 时传入 preference="on";canvas 探针仅在 probe=1 时渲染。
  • 剥离证明:修改开关、入口或变体后,连续执行 development 与 production 导出(不加 --clear),并对两者运行 app-variants-and-environment 的 check-variant-bundle。

使用方式

  1. 阅读 references/gate-and-entry.md(始终),再按任务阅读 references/debug-deep-link.md、references/simulated-clock-and-connectivity.md、references/wiring-and-menu.md、references/checker.md。
  2. 检查前置条件:组合根与时钟(architecture-and-boundaries)、ConnectivityPort 及其中枢(networking-and-server-state)、src/core/result.ts、date-key.ts、languages.ts、设置存储(若链接会改设置),以及 app-variants-and-environment 的变体与 Metro 缓存键。
  3. 将模板复制到其首行指定的路径:templates/src/test-only.ts 与 test-only-entry.ts(重命名哨兵中的 TASKS)、templates/src/services/test-hooks/、templates/src/services/debug-storage/、templates/src/stores/debug-overrides/、templates/src/features/debug/(含调试套件 debug-root-kit.tsx)、templates/src/app/(debug)/、templates/src/i18n/catalogs/ 的 debug.* 键,以及 templates/contracts/debug-links.json 及其 schema。测试钩子套件在库中为规范版本,此处的副本是同步的,不在此处编辑。
  4. 声明文件夹:把 src/features/debug/、src/services/test-hooks/、src/services/debug-storage/、src/stores/debug-overrides/ 加入 contracts/architecture.json 的 testOnlyFolders;若有第二个持久化存储,加入 state-management-zustand 的 contracts/stores.json。
  5. 通过根槽位接线:对应用根套件注册表做两处编辑(不改规范根文件),prepareRootKits 创建钩子并返回其服务,createRootKits 注册 TEST_ONLY?.createDebugKit(hooks)(motion 槽位与 (debug) 分组);应用保存语言与主题后配置 settings target。
  6. 修改参数或开关时测试先行:注册表测试、计划测试、reducer cases,然后代码,最后 contracts/debug-links.json。
  7. 运行测试:npx jest src/features/debug src/services/test-hooks src/services/debug-storage src/stores/debug-overrides --ci --maxWorkers=2,以及 npm run typecheck、npx eslint . --max-warnings 0、npx knip。
  8. 证明剥离:两次导出,再分别运行 check-variant-bundle;之后 rm -rf dist/variant-proof。
  9. 运行检查器:node ${CLAUDE_SKILL_DIR}/scripts/check-test-hooks.mjs .,修复每条 FAIL 行并重跑直到 RESULT: PASS。

适用场景

适用于使用 Expo Router 的 React Native 应用,需要在开发与预览构建中提供调试菜单、调试深链、模拟时钟与模拟离线网络,同时确保生产构建完全剥离这些代码,并希望用检查脚本在应用自身代码上验证开关、入口、接线、注册表与模拟实现。

限制

  • 资料标注验证环境为 Expo SDK 57(expo 57.0.26)、React Native 0.86.3、expo-router 57.0.24、knip 6.38.0,使用 Jest 与 iOS 连续 expo export 打包;2026-10-17 在库的参考应用中通过根槽位重新验证,iOS 与 Android 的开发与生产导出均通过 check-variant-bundle;尚未在模拟器或仿真器上实际驱动。
  • 检查器允许的例外:SKIP contracts/architecture.json [test-only-folders] not yet created(由 architecture-and-boundaries 创建)、NOT APPLICABLE: the app has no test-only code: ...(在采用本技能之前),以及 React Navigation 上的 link-route 行说明。
  • 资料未提供收费、价格、平台支持范围等商业信息。

反模式(资料列举)

在配置文件中使用 const IS_TEST_BUILD = ... 再 IS_TEST_BUILD ? require(...) : null;使用 if (__DEV__) showDebugMenu();调试路由直接导入调试屏幕;手写 TestOnlyApi 并用 @public 标签或 knip 忽略来消除告警;用 Linking.addEventListener('url') 处理调试链接;解析器跳过未知参数或接受 offline=true;用 setDate() 替换 nowMs(),或只给单个屏幕打补丁的“模拟离线”;把调试开关放在 React state 或应用偏好中;为其他技能的钩子另开第二个开关;把模拟连接性端口与真实服务并列分发;用 xcrun simctl openurl 或 adb shell am start -d 打开调试链接;在 (debug) 分组布局中使用 <Redirect>。