尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Jest Watch Plugins 实战指南:扩展 watch 模式菜单与测试生命周期钩子

Jest Watch Plugins 实战指南:扩展 watch 模式菜单与测试生命周期钩子 Jest Watch Plugins 实战指南扩展 watch 模式菜单与测试生命周期钩子【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jestJest 的 watch 插件系统允许开发者挂钩测试运行的特定生命周期事件并在 watch 模式菜单中注册自定义按键与提示从而把 Jest 的 watch 模式改造成贴合自身工作流的交互式开发工具。本文以 Jest 30.4 文档 WatchPlugins.md 为主体结合仓库源码与测试用例完整讲解插件接口、生命周期钩子、菜单集成、配置定制与按键冲突处理读完即可动手编写自己的 watch 插件。一、认识 Watch 插件系统Jest 的 watch 插件系统提供了一种把自定义逻辑挂接到 Jest 特定环节的机制一方面可以监听测试运行的生命周期事件文件变更、测试运行完成、某条测试是否该运行另一方面可以在 watch 模式菜单中注册「按键 提示文字」按下按键即执行对应代码。二者组合起来就能开发出完全贴合个人工作流的交互式体验。从源码结构看插件系统由packages/jest-watcher包支撑其中types.ts 定义了WatchPlugin接口、JestHookSubscriber订阅者类型、UsageDatakeyprompt等核心类型JestHooks.ts 实现了钩子的订阅subscriber与触发emitter机制BaseWatchPlugin.ts 提供抽象基类内置apply、getUsageInfo、onKey、run的空实现方便快速继承。Watch Plugin 接口一个最小的插件就是一个拥有三个方法的类class MyWatchPlugin { // Add hooks to Jest lifecycle events apply(jestHooks) {} // Get the prompt information for interactive plugins getUsageInfo(globalConfig) {} // Executed when the key from getUsageInfo is input run(globalConfig, updateConfigAndRun) {} }对照 types.ts 中的WatchPlugin接口可以看到实际可选的成员还包括isInternal标记内置插件与onKey处理按键原始输入。getUsageInfo允许返回null表示该插件不注册菜单按键run返回Promisevoid | boolean。把插件接入 Jest在 Jest 配置中通过watchPlugins数组声明插件路径即可接入module.exports { // ... watchPlugins: [path/to/yourWatchPlugin], };Jest 在启动 watch 模式时会加载这些模块。加载逻辑位于 packages/jest-core/src/watch.ts通过requireOrImportModule动态加载插件模块因此 CJS、ESM 均可然后以{config, stdin, stdout}为参数实例化插件类若初始化失败会抛出带有格式化错误上下文的异常。仓库的端到端测试 e2e/watch-plugins 分别用cjs、js、mjs、js-type-module四种模块形态验证了插件的加载。二、Hooking into Jest生命周期钩子自定义 watch 插件可以通过apply方法向 Jest 注册钩子。即使插件不打算注册交互式菜单按键也可以只使用钩子来观察或干预测试运行。apply(jestHooks)apply方法接收一个jestHooks参数允许插件挂接到测试运行生命周期的具体环节class MyWatchPlugin { apply(jestHooks) {} }从 JestHooks.ts 的实现可以看到JestHooks内部维护了onFileChange、onTestRunComplete、shouldRunTestSuite三组监听器数组getSubscriber()返回的订阅者把回调推入数组getEmitter()返回的发射器负责在事件发生时逐一调用监听器。启动 watch 模式时Jest 会先对内置插件调用plugin.apply(hookSubscriber)watch.ts再对配置中的第三方插件依次调用watch.ts。isUsed(hook)方法还允许外部判断某个钩子是否被注册过。jestHooks.shouldRunTestSuite(testSuiteInfo)返回布尔值或Promiseboolean以处理异步操作决定某条测试是否应该运行class MyWatchPlugin { apply(jestHooks) { jestHooks.shouldRunTestSuite(testSuiteInfo { return testSuiteInfo.testPath.includes(my-keyword); }); // or a promise jestHooks.shouldRunTestSuite(testSuiteInfo { return Promise.resolve(testSuiteInfo.testPath.includes(my-keyword)); }); } }testSuiteInfo的结构见 types.ts包含config项目配置、testPath测试文件路径与可选的duration。值得注意的实现细节该钩子是「一票否决制」。在 JestHooks.ts 中发射器会用Promise.all并行调用所有监听器然后result.every(Boolean)——即只要有一个监听器返回false该测试套件就不会运行。jestHooks.onTestRunComplete(results)在每次测试运行结束时被调用参数是测试结果class MyWatchPlugin { apply(jestHooks) { jestHooks.onTestRunComplete(results { this._hasSnapshotFailure results.snapshot.failure; }); } }results是AggregatedResult类型来自jest/test-result包含汇总的断言数、失败数以及snapshot等字段。上面的例子用它在每次运行结束后记录「是否存在快照失败」供菜单按键或后续逻辑使用。jestHooks.onFileChange({projects})只要文件系统发生变化就会被调用projects: Arrayconfig: ProjectConfig, testPaths: Arraystring包含 Jest 正在监听的所有测试路径。class MyWatchPlugin { apply(jestHooks) { jestHooks.onFileChange(({projects}) { this._projects projects; }); } }对应类型JestHookExposedFS定义在 types.ts每个项目包含config与testPaths数组。插件可以在文件变更后把项目清单缓存起来供自定义菜单使用。三、Watch Menu Integration注册交互式菜单按键除了生命周期钩子插件还能通过「getUsageInfo返回按键/提示 run执行按键逻辑」的方式为 watch 菜单增加或覆盖功能。getUsageInfo(globalConfig)实现该方法并返回一个key和prompt即可向 watch 菜单添加一个按键class MyWatchPlugin { getUsageInfo(globalConfig) { return { key: s, prompt: do something, }; } }这会在 watch 模式菜单中新增一行› Press s to do something.Watch Usage › Press p to filter by a filename regex pattern. › Press t to filter by a test name regex pattern. › Press q to quit watch mode. › Press s to do something. // -- This is our plugin › Press Enter to trigger a test run.菜单渲染逻辑在 watch.ts 的usage函数中先输出内置项c、a/f、o、p、t、q等再通过getSortedUsageRows(watchPlugins, globalConfig)把各插件的key/prompt拼成Press key to prompt.行。按键的获取见getPluginKeywatch.ts调用plugin.getUsageInfo(globalConfig)并取其返回对象的key。注意如果插件的按键与某个默认按键重复你的插件将覆盖该默认按键前提是该默认键允许被覆盖见下文「Choosing a good key」。run(globalConfig, updateConfigAndRun)处理getUsageInfo返回的按键对应的事件。该方法返回一个Promiseboolean当插件希望把控制权交还给 Jest 时 resolve布尔值表示 Jest 拿回控制权后是否应重新运行测试globalConfigJest 当前全局配置的表示updateConfigAndRun允许你在交互式插件运行期间触发一次测试运行。class MyWatchPlugin { run(globalConfig, updateConfigAndRun) { // do something. } }注意如果你调用了updateConfigAndRun那么run方法就不应 resolve 为真值否则会触发双重运行。Authorized configuration keys可授权更新的配置键出于稳定与安全考虑updateConfigAndRun只能更新全局配置中的部分键当前白名单如下bail见 Configuration.mdchangedSince见 CLI.mdcollectCoverage见 Configuration.mdcollectCoverageFrom见 Configuration.mdcoverageDirectory见 Configuration.mdcoverageReporters见 Configuration.mdnotify见 Configuration.mdnotifyMode见 Configuration.mdonlyFailures见 Configuration.mdreporters见 Configuration.mdtestNamePattern见 CLI.mdtestPathPatterns见 CLI.mdupdateSnapshot见 CLI.mdverbose见 Configuration.md白名单在源码中有两处对应物一处是 types.ts 中的AllowedConfigOptions类型额外还包含findRelatedTests、nonFlagArgs、mode与testPathPatterns数组形式另一处是 watch.ts 中updateConfigAndRun的实参解构与updateGlobalConfig调用。从该实现还能看到两个细节调用后会立即startRun(globalConfig)触发一次测试运行并且updateSnapshot不是粘性的——运行结束后会被重置为none除非之前就是all。四、Customization通过配置定制插件插件可以通过 Jest 配置进行定制。在watchPlugins数组中使用「路径 配置对象」的元组形式module.exports { // ... watchPlugins: [ [ path/to/yourWatchPlugin, { key: k, // - your custom key prompt: show a custom prompt, }, ], ], };推荐的配置项key修改插件按键prompt允许用户定制插件提示文字。如果用户提供了自定义配置它会作为参数传给插件构造函数。从 watch.ts 可以看到实例化时传入的是{config: pluginWithConfig.config, stdin, stdout}其中stdin/stdout由 Jest 注入config即你在配置里写的定制对象class MyWatchPlugin { constructor({config}) {} }WatchPluginClass类型types.ts正式声明了构造参数为{config, stdin, stdout}。若要进一步感知按键、实现类似内置p/t那样的输入式交互可以继承 BaseWatchPlugin.ts它把stdin/stdout存为受保护成员并结合 PatternPrompt.ts内置的「Pattern Mode」交互基类支持 Esc 退出、Enter 应用正则或 constants.ts 中导出的按键码常量KEYS.ENTER、KEYS.ESCAPE、方向键、CONTROL_C等Windows 与 Unix 的退格键码不同。五、Choosing a good key按键选择与冲突处理Jest 允许第三方插件覆盖部分内置功能键但并非全部。以下按键不可覆盖保留给 Jest 内部c清除过滤模式i交互式更新不匹配的快照q退出u更新所有不匹配的快照w显示 watch 模式用法 / 可用操作以下内置功能键可以被覆盖p测试文件名模式t测试名称模式任何未被内置功能使用的按键都可以声明。建议避免使用在各种键盘上难以输入的键如é、€或默认不可见的字符很多 Mac 键盘对|、\、[等没有直观的键帽提示。当冲突发生时从源码看内置插件的保留信息集中在 watch.tsINTERNAL_PLUGINS包含 6 个内置插件FailedTestsInteractivePlugin、TestPathPatternPlugin、TestNamePatternPlugin、UpdateSnapshotsPlugin、UpdateSnapshotsInteractivePlugin、QuitPlugin而RESERVED_KEY_PLUGINS为u、i、q等键登记了forbiddenOverwriteMessage。如果插件试图覆盖保留键Jest 会抛出带描述信息的错误Watch plugin YourFaultyPlugin attempted to register key q, that is reserved internally for quitting watch mode. Please change the configuration key for this plugin.第三方插件同样禁止覆盖已在插件列表watchPlugins数组中更靠前位置注册的另一个第三方插件的键。发生这种情况时也会得到帮助定位问题的错误信息Watch plugins YourFaultyPlugin and TheirFaultyPlugin both attempted to register key x. Please change the key configuration for one of the conflicting plugins to avoid overlap.冲突检测的完整逻辑在checkForConflicts函数watch.ts它维护一张「键 → 插件」映射表若新插件要注册的键已被不可覆盖的插件占用则按上述规则抛出ValidationError。注意内置保留键的判断依据是forbiddenOverwriteMessage是否存在——只有携带该消息的键即u、i、q绝对不可覆盖而p、t等插件虽然内置但可以被第三方插件覆盖。六、实战组合钩子与菜单的完整示例把以上知识点组合起来一个完整的 watch 插件通常长这样// my-watch-plugin.js class SnapshotAwareWatchPlugin { constructor({config, stdin, stdout}) { this._config config; this._hasSnapshotFailure false; this._projects []; } apply(jestHooks) { // 记录每次运行是否出现快照失败 jestHooks.onTestRunComplete(results { this._hasSnapshotFailure results.snapshot.failure; }); // 监听文件系统变化 jestHooks.onFileChange(({projects}) { this._projects projects; }); } getUsageInfo() { return { key: this._config.key || s, prompt: this._config.prompt || show snapshot failure status, }; } async run() { const status this._hasSnapshotFailure ? snapshot failures detected : all snapshots up to date; process.stdout.write(\n[my-plugin] ${status}\n); return false; // 不触发测试重跑 } } module.exports SnapshotAwareWatchPlugin;配置中使用同时定制按键与提示module.exports { watchPlugins: [ [ ./my-watch-plugin.js, { key: s, prompt: show snapshot failure status, }, ], ], };验证方式在项目根目录运行jest --watch或jest --watchAll菜单中应出现› Press s to show snapshot failure status.仓库自身的 e2e 测试 e2e/watch-plugins/js/tests/index.js 展示了插件加载后可正常执行测试的最小验证形态。此外Jest 主仓库的 jest.config.mjs 自己就在使用第三方插件jest-watch-typeahead的filename与testname两个入口——这是社区插件在真实项目中的直接落地范例可作为编写第三方插件的参考对象。七、小结Jest watch 插件系统由三块能力构成生命周期钩子applyshouldRunTestSuite/onTestRunComplete/onFileChange、菜单集成getUsageInfo注册键位、run执行逻辑、updateConfigAndRun在受控白名单内更新配置并触发重跑以及配置定制watchPlugins数组中的元组形式 构造函数config参数。编写时注意三点shouldRunTestSuite是「任一 false 即不运行」的与逻辑调用了updateConfigAndRun后run不要 resolve 为真值按键应避开c/i/q/u/w五个不可覆盖的保留键并留意与第三方插件之间的键位重叠。【免费下载链接】jestDelightful JavaScript Testing.项目地址: https://gitcode.com/gh_mirrors/je/jest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表