深入解析 Relay 的 Markdown 驱动端到端测试:以 Greeting Query 最小夹具为例

发布时间:2026/9/21 15:47:39
深入解析 Relay 的 Markdown 驱动端到端测试:以 Greeting Query 最小夹具为例
前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载导读Greeting Query 是 Relay 仓库中端到端测试体系relay-e2e-test的一个最小化夹具fixture它用 Grats 以 TypeScript 定义一个返回字符串的 GraphQL 根字段通过useLazyLoadQuery发起查询最终把结果渲染进 React 组件。本文以这个 Hello, Jordan! 测试为线索完整拆解 markdown 驱动的 E2E 测试格式、Grats 服务端定义、Relay 运行时组件写法、交互步骤 DSL 与快照断言机制并下沉到 测试执行器、夹具解析器 等源码让读者既会写自己的夹具也理解这套体系在编译、类型检查、渲染全链路上是如何工作的。一、Greeting Query一次贯穿全链路的 Hello World关联文档 greeting.md 开篇即说明了它的定位一个基础的端到端测试——Grats 的gqlQueryField解析器返回一个字符串Relay 通过useLazyLoadQuery获取它组件渲染出结果。wait Hello, Jordan!这个wait步骤对应的期望快照 greeting.snap.md 极其简单divHello, Jordan!/div一进一出之间实际发生了四件事Grats 从server.ts的类型推导出 GraphQL schemarelay-compiler 从App.tsx里的graphql模板生成__generated__/类型产物tsc用仓库内的.d.ts对两端代码做类型校验React Testing Library 渲染组件、执行交互、产出 HTML 快照。理解这一个夹具就等于理解了整个 relay-e2e-test 的骨架。二、夹具的四段式结构原文档完整拆解每个夹具是一个自包含的.md文件按 writing-fixtures.md 的规范greeting.md 恰好演示了最标准的四段结构## Relay Config、## Server、## App、## Steps。每个代码块必须带titlefilename属性标题决定了它最终落盘的文件名语言标签仅用于语法高亮。2.1 Relay Config编译器最小配置{ src: ./, schema: ./schema.graphql, language: typescript }三个字段的含义与作用范围如下srcRelay 编译器扫描graphql模板的源码根目录此处为./即夹具模板目录App.tsx中的graphql\... 标签会被提取编译。schemaschema 文件路径。注意这个schema.graphql并不是手写的而是由 runFixture.js 第一步执行 Grats 自动生成的grats --tsconfig tsconfig.json产出template/schema.graphqlschema.ts编译器随后读取它完成字段解析与类型推导。languagetypescript即生成__generated__/*.graphql.ts类型产物供App.tsx导入AppTestQuery使用。这与 tsconfig.template.json 中strict: true、moduleResolution: bundler、jsx: react-jsx的配置配套保证夹具代码以最严格的 TypeScript 模式被校验。2.2 Server用 Grats 声明式定义 GraphQL schema/** gqlQueryField */ export function greeting(): string { return Hello, Jordan!; }这是 Grats 的核心用法一个带gqlQueryFielddocblock 注解的 TypeScript 函数其签名即为 GraphQL 根 Query 上的字段定义——函数名greeting成为字段名返回类型string成为字段类型映射为 GraphQL 标量String。Grats 在编译期读取这些注解从代码的类型结构中直接推导出 schema省去手写 schema 与 resolver 之间的同步负担。writing-fixtures.md 还补充了更复杂的形态用gqlMutationField/gqlSubscriptionField定义变更与订阅用/** gqlType */加类型字面量定义对象类型字段级用gqlField注解只有在需要异步方法解析器如async bio(): Promisestring时才改用 class。2.3 AppRelay 数据获取与渲染的标准范式import { Suspense } from react; import { RelayEnvironmentProvider, useLazyLoadQuery } from react-relay; import { graphql, Environment } from relay-runtime; import { gratsNetwork } from ../GratsNetwork; import { AppTestQuery } from ./__generated__/AppTestQuery.graphql; const testEnvironment new Environment({ network: gratsNetwork }); function Greeting() { const data useLazyLoadQueryAppTestQuery( graphql query AppTestQuery { greeting } , {}, ); return div{data.greeting}/div; } export default function TestApp() { return ( RelayEnvironmentProvider environment{testEnvironment} Suspense fallback{divLoading.../div} Greeting / /Suspense /RelayEnvironmentProvider ); }这段代码演示了 Relay 现代 API 的完整装配EnvironmentRelay 的数据获取入口这里注入的是共享的gratsNetwork。GratsNetwork.ts 用Network.create同时注册了 fetch 与 subscribe 两条通道内部直接调用 graphql-js 的execute/subscribe用Observable.create把ExecutionResult或AsyncIterable增量交付场景适配成 Relay 的RelayObservable——这意味着夹具在纯内存中执行真正的 GraphQL无需任何网络服务器。useLazyLoadQueryAppTestQuery泛型参数指向编译器生成的类型。graphql\query AppTestQuery { greeting }是 Relay 的模板标签编译器据此生成AppTestQuery的类型与操作描述符{} 是空变量对象。RelayEnvironmentProviderSuspenseprovider 向下传递 environmentuseLazyLoadQuery在 Suspense 边界内挂起直到数据就绪。因此fallback里渲染的是Loading...而数据到达后Greeting才挂载并输出divHello, Jordan!/div。export default TestApp夹具必须默认导出组件fixtures-test.js 会动态import这个默认导出并用 React Testing Library 渲染。注意data.greeting的类型是编译期保证的生成的AppTestQuery$data上存在greeting: string字段tsc会在第三步见第五节验证这一点。2.4 Steps交互步骤 DSLwait Hello, Jordan!steps代码块是夹具的可选交互段语法为action [role] name [value]。三种动作在 runInteractions.js 中实现动作语义底层查询click Name/click role Name点击指定文本或 ARIA role 可访问名称的元素getByText/getByRoleuserEvent.clicktype Name value/type role Name value向目标元素输入文本getByText/getByRoleuserEvent.typewait Text/wait role Name等待元素出现异步/Suspense 场景findByText/findByRole名称必须加引号role可以是任意 ARIA rolebutton、link、textbox等。对 greeting 夹具来说wait Hello, Jordan!的作用正是等待 Suspense 解析、异步数据落屏之后再拍摄快照——没有它快照可能停在Loading...的 fallback 状态。一个夹具最多只能有一个steps块parseMarkdown.js 会对此抛出错误。三、快照断言greeting.snap.md 与 Jest 语义夹具的期望结果是以纯文本.snap.md文件维护的而非 jest 原生.snap格式便于代码评审时直接阅读 diff。greeting 的快照只包含## HTML段divHello, Jordan!/div实际上快照可以包含至多三个部分按 fixtures-test.js 的顺序拼接## Type Errors类型检查诊断见第四节干净时整段省略## Console夹具运行期间捕获的console.log/warn/error输出经 fixtures-test.js 的 spy 采集并做%s格式化## HTMLcontainer.innerHTML渲染结果。快照遵循 jest 标准语义见 README.md 与 writing-fixtures.md运行方式新快照快照有变更yarn test:e2e生成并通过失败yarn test:e2e -u生成并通过重写并通过yarn test:e2e --ci失败失败因此新增夹具需先在本地跑一次生成.snap.md并随夹具一起提交刻意变更行为后用-u重写。CI 环境会被 jest 自动视为--ci防止“悄悄写快照永远通过”的静默退化。四、类型检查greeting 之外的隐形防线greeting 夹具本身类型干净但它所在体系的核心价值之一是唯一一处真正使用仓库手写.d.ts的地方。流程如下relay-compiler 生成__generated__/*.graphql.ts后runFixture.js 以tsc -p tsconfig.json --noEmit --pretty false对夹具整体做类型检查App.tsx里useLazyLoadQueryAppTestQuery的泛型、data.greeting的字段读取都会对照本仓库relay-runtime 与 react-relay 的声明文件验证由 setupTempDir.js 在生成的 tsconfigpaths中指向仓库源码。值得强调的两个设计类型错误不阻断测试诊断会被写入快照顶部的## Type Errors段因此类型回归表现为快照 diff照样能让 CI 失败同时允许提交一个表征已知坏类型的夹具。配套样例见 typechecking/unselected-field.md。绝对路径脱敏诊断中出现的包绝对路径会被替换为relay-runtime、react-relay占位符runFixture.js保证快照在 GitHub 与内部仓库两种布局下可共享。五、执行链路一个夹具从 Markdown 到 HTML 快照的旅程fixtures-test.js 递归扫描fixtures/下所有非.snap.md的.md文件每个文件生成一个 jest 用例。greeting.md 在其中的完整旅程是解析parseMarkdown.js 用正则提取所有带title...的代码块fileRegex与唯一的steps块stepsRegex返回{codeBlocks, steps}。建目录setupTempDir.js 在系统临时目录创建relay-e2e-*目录把代码块按 filename 写入template/子目录拷贝共享的GratsNetwork.ts生成带paths的 tsconfig并软链node_modules隔离的 react19 / graphql16 依赖。编译runFixture.js 依次执行grats生成 schema、relay-compiler生成__generated__/产物通过环境变量FORCE_NO_WATCHMAN: 1关闭 watchman、tsc类型检查。渲染动态importtemplate/App.tsx的默认导出用 React Testing Library 的render挂载。交互若有 steps 块执行 runInteractions.js 的 DSL。断言拼接Type Errors / Console / HTML三段与.snap.md比对按 jest 快照模式写入或失败最后cleanup()并删除临时目录。其中编译器二进制按 getRelayCompilerBinary 的优先级解析RELAY_COMPILER_BINARY环境变量 → 本地 cargo 构建compiler/target/debug/relay→ git worktree 主目录的构建产物 →node_modules/.bin/relay-compilernpm 回退。CI 环境下 npm 回退是硬错误防止静默降级到旧版发布包——这意味着你可以用cargo build --manifest-pathcompiler/Cargo.toml --bin relay构建本仓库的 Rust 编译器后让整套夹具针对它运行。六、运行与调试把 Greeting Query 跑起来在仓库根目录yarn test:e2e只跑单个夹具yarn test:e2e -- --testNamePattern greeting首次准备packages/relay-e2e-test有独立的node_modules隔离 react19 与 graphql16cd packages/relay-e2e-test yarn install yarn build # 需先构建 babel-plugin-relay若用仓库自带的 Rust 编译器cargo build --manifest-pathcompiler/Cargo.toml --bin relay调试夹具时快照是你最直接的观察窗口改server.ts返回的字符串、改App.tsx的查询字段或渲染结构yarn test:e2e都会给出 HTML/类型诊断的 diff配合-u更新期望。七、总结从 Greeting Query 这个最小夹具可以看到 Relay E2E 测试体系的三重价值自包含——服务端、客户端、交互、期望全部收进一个 markdown 文件贴近真实——用真实的 Grats schema、真实的 relay-compiler 产物、真实的 React 渲染与仓库.d.ts类型检查跑完整链路可读可审——快照是纯文本 markdown行为变化以 diff 形式进入代码评审。它既是新夹具的起点模板也是理解Relay 组件如何被端到端验证的最佳入门样例。想深入了解格式规范与更多模式可继续阅读 writing-fixtures.md 与 relay-e2e-test README以及fixtures/下 mutations、mixed-interface、typechecking 等子目录中更复杂的真实用例。赞分享前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载相关推荐深入解析 Fumadocs Satteri 本地 Markdown 编译管线以 simple.md 测试夹具为样本深入解析 Fumadocs Satteri 本地 Markdown 编译管线以 simple.md 测试夹具为样本 导读 本文以 packages/satte前端文档MCP 服务Prettier 格式化 Markdown 内嵌 CSS 代码块以 mdn-transform 测试夹具为实例的深度解析Prettier 格式化 Markdown 内嵌 CSS 代码块以 mdn transform 测试夹具为实例的深度解析 本篇技术指南以 Prettier 仓开发工具格式化CLI深入解析 GitHub Desktop 的 git for-each-ref 文本解析以 repo-with-many-refs 测试夹具为例深入解析 GitHub Desktop 的 git for each ref 文本解析以 repo with many refs 测试夹具为例 GitHub桌面应用版本控制开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考