New Rule Proposal: no-unsettled-absence-query
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 35/100
- Loại issue
- Tính năng
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Ít trao đổi
- Công nghệ
- eslint, javascript, typescript
- Lĩnh vực
- testing-qa, tooling
Hướng nghiên cứu
Không có tệp repository, bài kiểm thử hoặc entry point nào được nêu. Hãy bắt đầu bằng cách xem xét các quy tắc Testing Library ESLint hiện có và các bài kiểm thử của chúng, sau đó so sánh các ví dụ lỗi và thành công được đề xuất. Công việc được xem là hoàn tất khi cảnh báo mới nhất quán phát hiện các assertion về sự vắng mặt chưa ổn định, đồng thời cho phép các biểu thức ổn định đã được ghi chép.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Name for new rule
no-unsettled-absence-query
Description of the new rule
Asserting absence with queryBy* + .not.toBeInTheDocument() immediately after render(), before the component has settled, can produce a false positive. The element isn't there yet, not because it won't be there. The community calls this Testing Ghosts.
Testing Library's render() does flush the initial synchronous render (it's wrapped in act() internally), so for a plain component with no async behavior, checking absence right after render() is technically correct. The problem shows up when the component has async side effects like data fetching, lazy loading, state updates from useEffect, etc. In those cases the UI hasn't finished updating, and the absence check passes vacuously. Since the rule can't know whether a component has async behavior, it flags all unsettled absence assertions and lets the developer confirm intent by adding a settling expression.
This rule would enforce that an absence assertion (expect(screen.queryBy*(...)).not.toBeInTheDocument() / .not.toBeVisible()) must be preceded by a "settling" expression within the test body.
Why existing rules don't cover this:
| Existing Rule | Why it doesn't solve this |
|---|---|
prefer-presence-queries |
Enforces correct query type, but doesn't track timing relative to render |
no-await-sync-queries |
Only flags await directly on sync queries, not timing of assertions |
await-async-queries / await-async-utils |
About promise handling, not assertion ordering |
prefer-find-by |
Simplifies waitFor + getBy -> findBy, doesn't detect premature absence checks |
Related issues: #269, #411, #518 - all adjacent but none address this specific pattern.
Testing Library feature
This rule relates to the absence/presence assertion pattern described in the Appearance and Disappearance guide. Specifically, it enforces correct ordering of queryBy* absence assertions relative to async settling utilities (findBy*, waitFor, act) and sync settling queries (getBy*).
Testing Library framework(s)
All frameworks: React, Angular, Vue, Svelte, etc. The pattern applies anywhere screen.queryBy* + .not.toBeInTheDocument() is used.
What category of rule is this?
Warns about a potential error. Less valuable as an error given the call-out above of vanilla components.
Code examples
Fail:
// Absence assertion before component has settled, "Testing a Ghost"
test('shows no error', () => {
render(<AsyncComponent />);
expect(screen.queryByText('error')).not.toBeInTheDocument();
});
// Absence assertion BEFORE the awai, order matters
test('shows no error', async () => {
render(<AsyncComponent />);
expect(screen.queryByText('error')).not.toBeInTheDocument();
await screen.findByText('loaded');
});
// queryAllBy variant
test('shows no alerts', () => {
render(<AsyncComponent />);
expect(screen.queryAllByRole('alert')).not.toBeInTheDocument();
});
// Absence assertion inside waitFor - passes on first retry, still a ghost
test('shows no error', async () => {
render(<AsyncComponent />);
await waitFor(() => {
expect(screen.queryByText('error')).not.toBeInTheDocument();
});
});
Pass:
// findBy* settles the component first
test('shows no error', async () => {
render(<AsyncComponent />);
await screen.findByText('loaded');
expect(screen.queryByText('error')).not.toBeInTheDocument();
});
// waitFor settles the component first
test('shows no error', async () => {
render(<AsyncComponent />);
await waitFor(() => expect(something).toBe(true));
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
});
// act settles the component first
test('shows no error', async () => {
await act(() => render(<AsyncComponent />));
expect(screen.queryByText('error')).not.toBeInTheDocument();
});
// getBy* proves sync render completed
test('shows no error', () => {
render(<Component />);
screen.getByText('visible heading');
expect(screen.queryByText('error')).not.toBeInTheDocument();
});
What counts as "settled":
- Any
awaitexpression on a preceding statement — coversfindBy*,waitFor,act, custom async helpers - A
screen.getBy*/screen.getAllBy*call — proves the sync render produced expected output
Matchers checked: .not.toBeInTheDocument(), .not.toBeVisible()
Anything else?
References:
- Testing Library: Appearance and Disappearance guide
- Kent C. Dodds: Common mistakes with React Testing Library
- Gerardo Perrucci: Stop Testing Ghosts
- react-testing-library #865 (vague similarity; in the same realm with component mounting/unmounting states)
Do you want to submit a pull request to make the new rule?
I have a working implementation with full test coverage that I'm happy to submit as a PR if this proposal is accepted.
- Ngôn ngữ chính
- TypeScript
- Star
- 1.1k
- Fork
- 167
- Merge trung bình
- 19 giờ 58 phút
- Pull request đã merge (30 ngày)
- 2
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của testing-library/eslint-plugin-testing-library
-
bug triage
Độ khó 3/5 Nửa ngày Mức phù hợp với người mới 58/100
-
`no-wait-for-side-effects`: side effects not detected via `userEvent.setup()` instances and when wrapped in `void`Có thể đã có người làm @AmineBenzaazaa đã nhận 11 ngày trước. Đang mởbug triage
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 76/100
testing-library/eslint-plugin-testing-library#1370 · 1 bình luận ·
-
Rules don’t recognize @testing-library/angular/zoneless importsCó thể đã có người làm @ThiloAschebrock đã nhận 93 ngày trước. Đang mởbug
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 70/100
-
prefer-presence-queries autofix leaves queryBy* in destructure while rewriting call site to getBy*, producing a ReferenceErrorCó thể đã có người làm @j1ang3 đã nhận 98 ngày trước. Đang mở
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 72/100
-
Dependency DashboardĐang mởpinned
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 10/100
Tất cả issue của testing-library/eslint-plugin-testing-library
Issue tương tự
-
[Bug]: Payment providers removed from config are never disabled in payment_providerCó thể đã có người làm @medusa-os-bot đã nhận hôm nay. Đang mởtype: bug
Độ khó 2/5 Nửa ngày Mức phù hợp với người mới 82/100
medusajs/medusa#17544 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
software-mansion/argent#1651 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
Maintainer thường phản hồi trong vòng 1 ngày
-
lane:reliability
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 1 ngày
-
needs-triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
Maintainer thường phản hồi trong vòng 1 ngày