accelint-react-testing by gohypergiant/agent-skills
npx skills add https://github.com/gohypergiant/agent-skills --skill accelint-react-testing使用 Testing Library 编写可维护、以用户为中心的 React 组件测试的专家指南。专注于查询选择、无障碍优先测试和避免实现细节。
data-testid="submit" 但没有无障碍名称的按钮在测试中可以通过,但对于屏幕阅读器用户来说会失败。当测试使用测试 ID 通过时,你交付的是不可访问的 UI。查询层次结构:getByRole > getByLabelText > getByText > getByTestId。沿着这个列表每下降一步,意味着你对 UI 可用的信心就越低。userEvent 可用时,绝对不要使用 fireEvent 进行用户交互 - fireEvent 只分发单个 DOM 事件,缺少真实用户触发的事件序列: 只触发一个点击事件,但真实用户会触发 focus → mousedown → mouseup → click。使用 fireEvent 能工作的组件,在用户正常交互的生产环境中会崩溃。 模拟完整的交互序列,能捕获 fireEvent 遗漏的错误。广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
fireEvent.click()userEvent.click()container 查询或在初始渲染后使用解构查询 - const { getByText } = render(<Component />) 会创建陈旧的查询,从而错过更新:状态更改后,解构查询会搜索初始的 DOM 快照,错过新渲染的元素。这会导致对于实际存在的元素出现“未找到元素”的错误。始终使用 screen.getByText(),它会自动查询当前的 DOM 状态。一致地使用 screen 也使测试更易于维护——添加新查询不需要更新解构。aria-label="submit-button" 或 role="button" 只是为了测试能找到元素,那你就是在倒着工作。测试应该验证组件已经是可访问的,而不是为了让测试通过而使其可访问。添加仅用于测试的 ARIA 会污染生产代码并掩盖真正的无障碍问题。首先修复组件的语义化 HTML 和现有的 ARIA。waitFor - waitFor(() => expect(element).toBeInTheDocument()) 会反复轮询直到超时,而基于 Promise 的 findBy 查询可以一次性解决:await screen.findByText('loaded') 等待元素出现,无需轮询。将 waitFor 保留给不能使用 findBy 的断言(检查元素消失、等待属性更改)。waitFor(() => { fireEvent.click(button); expect(text).toBeInTheDocument(); }) 会在 waitFor 重试时多次运行点击,导致不可预测的行为。waitFor 用于等待断言,而不是触发操作。在 waitFor 外部执行所有操作,然后仅将 waitFor 用于断言:fireEvent.click(button); await waitFor(() => expect(text).toBeInTheDocument()); 或者更好的是,await userEvent.click(button); expect(await screen.findByText(text)).toBeInTheDocument();。renderWithRedux 函数会让每个开发者都崩溃:他们调用 render(<Component />) 而不是 renderWithRedux(),测试会因神秘的“无法读取未定义的属性”而失败,浪费 15 分钟调试时间。在测试工具中集中 provider 设置,并使用强制正确用法的 TypeScript 类型,或者显著地记录所需的包装器。@testing-library/react 的 render 和 @testing-library/dom 的查询会造成混淆:来自 react 包的 screen 与来自 dom 包的 getByRole 不兼容,导致“screen.getByRole 不是函数”的错误。对于 React 组件,从 @testing-library/react 导入所有查询——它重新导出了 dom 中的所有内容,并带有 React 特定的增强功能。在实现 React 组件测试之前,应用这些思维模式:
rerender() 或访问组件内部,你就是在测试实现。重构为通过用户操作进行测试。findBy*。如果元素缺失,getBy* 会立即抛出错误;findBy* 会等待它出现。对异步内容使用 getBy 会产生仅在 CI 中失败的竞态条件。findBy* 查询。消失 = waitForElementToBeRemoved。状态更改 = 带有断言的 waitFor。每个都有不同的语义;使用错误的语义会导致不稳定的测试或更长的超时。此技能使用渐进式披露来最小化上下文使用:
阅读 AGENTS.md 以获取所有规则的简明概述和一行摘要。
使用这些明确的触发器来了解何时加载每个参考文件:
强制加载(加载整个文件):
当看到这些模式时加载:
除非特别需要,否则不要加载:
每个参考文件包含:
使用提供的脚本来审计现有的测试套件:
# 检查查询优先级(testId 使用情况,container.querySelector)
./scripts/check-query-priority.sh
# 查找应使用 userEvent 的 fireEvent
./scripts/find-fire-event.sh
# 检测已弃用的 wrapper/container 模式
./scripts/detect-wrapper-queries.sh
当此技能被调用进行测试代码审查时,使用标准化的报告格式:
报告格式提供:
何时使用报告模板:
/accelint-react-testing <路径> 直接调用技能何时不使用报告模板:
关于 React Testing Library 模式的专家指导:
选择查询时使用此层次结构——从上到下尝试选项:
1. getByRole ← 首选:无障碍,反映用户和辅助技术如何交互
↓ 找不到角色?
2. getByLabelText ← 用于表单字段:匹配用户如何阅读表单
↓ 没有标签?
3. getByPlaceholderText ← 用于输入框:比标签的无障碍性差
↓ 没有占位符?
4. getByText ← 用于非交互式内容:标题、段落
↓ 文本不唯一?
5. getByDisplayValue ← 用于表单输入:当前值
↓ 没有显示值?
6. getByAltText ← 用于图像:alt 属性
↓ 没有 alt 文本?
7. getByTitle ← 用于 title 属性:无障碍性较差
↓ 没有 title?
8. getByTestId ← 最后的手段:无无障碍验证
关键原则:
screen 导出并非魔法 - 它只是 getQueriesForElement(document.body)。使用 screen.getByRole() 与从 render 解构的 getByRole() 相同,但 screen 在重新渲染后永远不会过时。screen.debug() 查看当前 DOM,或运行 screen.logTestingPlaygroundURL() 获取显示哪些查询有效的交互式工具。不要猜测选择器——让 Testing Library 向你展示可用的内容。getBy* 会抛出带有有用建议的错误,包括相似元素和可用角色。queryBy* 返回 null,需要你添加自己的断言,但错误输出帮助较小。仅在断言不存在时使用 queryBy 配合 .not.toBeInTheDocument()。await 引起。不是由正确使用 findBy 或 waitFor 引起的。await userEvent.click() 会导致“act”警告和不稳定的测试,因为状态更新发生在断言运行之后。每周安装次数
79
代码仓库
GitHub 星标数
7
首次出现
2026年2月10日
安全审计
安装于
codex77
claude-code73
cursor66
opencode65
github-copilot63
kimi-cli62
Expert guidance for writing maintainable, user-centric React component tests with Testing Library. Focused on query selection, accessibility-first testing, and avoiding implementation details.
data-testid="submit" but no accessible name works in tests but fails for screen reader users. When tests pass with test IDs, you ship inaccessible UIs. Query hierarchy: getByRole > getByLabelText > getByText > getByTestId. Each step down this list means less confidence your UI is usable.fireEvent for user interactions when userEvent is available - fireEvent dispatches single DOM events, missing the event sequence real users trigger: fireEvent.click() fires one click event, but real users trigger focus → mousedown → mouseup → click. Components that work with fireEvent break in production when users interact normally. userEvent.click() simulates the full interaction sequence, catching bugs fireEvent misses.container or use destructured queries after initial render - const { getByText } = render(<Component />) creates stale queries that miss updates: after state changes, destructured queries search the initial DOM snapshot, missing newly rendered elements. This causes "element not found" errors for elements that are actually present. Always use screen.getByText() which automatically queries the current DOM state. Using screen consistently also makes tests more maintainable - adding a new query doesn't require updating the destructuring.aria-label="submit-button" or role="button" just so tests can find elements, you're working backwards. Tests should verify the component is already accessible, not make it accessible for tests. Adding test-only ARIA pollutes production code and masks real accessibility problems. Fix the component's semantic HTML and existing ARIA first.waitFor for actions that return promises - waitFor(() => expect(element).toBeInTheDocument()) polls repeatedly until timeout when a promise-based findBy query solves it in one shot: await screen.findByText('loaded') waits for the element to appear without polling. Reserve waitFor for assertions that can't use findBy (checking element disappears, waiting for attribute changes).waitFor(() => { fireEvent.click(button); expect(text).toBeInTheDocument(); }) runs the click multiple times as waitFor retries, causing unpredictable behavior. waitFor is for waiting on assertions, not triggering actions. Perform all actions outside waitFor, then use waitFor only for the assertion: fireEvent.click(button); await waitFor(() => expect(text).toBeInTheDocument()); or better yet, await userEvent.click(button); expect(await screen.findByText(text)).toBeInTheDocument();.renderWithRedux function with undocumented required store shape breaks for every developer: they call render(<Component />) instead of renderWithRedux(), tests fail with cryptic "Cannot read property of undefined", wasting 15 minutes debugging. Centralize provider setup in test utils with TypeScript types that enforce correct usage, or document required wrappers prominently.@testing-library/react render and @testing-library/dom queries creates confusion: screen from react package doesn't work with getByRole from dom package, causing "screen.getByRole is not a function" errors. Import all queries from @testing-library/react for React components - it re-exports everything from dom with React-specific enhancements.Apply these thinking patterns before implementing React component tests:
rerender() or accesses component internals, you're testing implementation. Refactor to test through user actions.findBy* for anything loaded via useEffect, API calls, or setTimeout. getBy* throws immediately if element is missing; findBy* waits for it to appear. Using getBy for async content creates race conditions that only fail in CI.findBy* query. Disappearance = waitForElementToBeRemoved. State changes = waitFor with assertion. Each has different semantics; using the wrong one causes flaky tests or longer timeouts.This skill uses progressive disclosure to minimize context usage:
Read AGENTS.md for a concise overview of all rules with one-line summaries.
Use these explicit triggers to know when to load each reference file:
MANDATORY Loading (load entire file):
Load When You See These Patterns:
Do NOT Load Unless Specifically Needed:
Each reference file contains:
Use the provided scripts to audit existing test suites:
# Check query priority (testId usage, container.querySelector)
./scripts/check-query-priority.sh
# Find fireEvent that should be userEvent
./scripts/find-fire-event.sh
# Detect deprecated wrapper/container patterns
./scripts/detect-wrapper-queries.sh
When this skill is invoked for test code review, use the standardized report format:
Template: assets/output-report-template.md
The report format provides:
When to use the report template:
/accelint-react-testing <path>When NOT to use the report template:
Expert guidance on React Testing Library patterns:
Use this hierarchy when selecting queries - try options from top to bottom:
1. getByRole ← Preferred: Accessible, reflects how users & ATs interact
↓ Can't find role?
2. getByLabelText ← For form fields: matches how users read forms
↓ No label?
3. getByPlaceholderText ← For inputs: less accessible than labels
↓ No placeholder?
4. getByText ← For non-interactive content: headings, paragraphs
↓ Text not unique?
5. getByDisplayValue ← For form inputs: current value
↓ No display value?
6. getByAltText ← For images: alt attribute
↓ No alt text?
7. getByTitle ← For title attribute: less accessible
↓ No title?
8. getByTestId ← Last resort: no accessibility verification
Key principles:
screen export is not magic - It's just getQueriesForElement(document.body). Using screen.getByRole() is identical to destructured getByRole() from render, but screen never goes stale after re-renders.screen.debug() to see the current DOM or screen.logTestingPlaygroundURL() to get an interactive tool showing what queries work. Don't guess at selectors - let Testing Library show you what's available.getBy* throws with helpful suggestions about similar elements and available roles. returns null, requiring you to add your own assertion with less helpful error output. Use queryBy only when asserting absence with .not.toBeInTheDocument().Weekly Installs
79
Repository
GitHub Stars
7
First Seen
Feb 10, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex77
claude-code73
cursor66
opencode65
github-copilot63
kimi-cli62
Vue 3 调试指南:解决响应式、计算属性与监听器常见错误
11,900 周安装
queryBy*await on async queries. Not caused by correct use of findBy or waitFor.await userEvent.click() causes "act" warnings and flaky tests as state updates happen after assertions run.