fix(tests): fix flakey tests with PropertiesModal.test.tsx, FiltersConfigModal.test.tsx and ChartList.listview.test.tsx#36037
Conversation
|
Based on your review schedule, I'll hold off on reviewing this PR until it's marked as ready for review. If you'd like me to take a look now, comment
|
There was a problem hiding this comment.
Pull Request Overview
This PR modernizes test patterns in two test files by adopting async/await conventions for userEvent interactions and refactoring asynchronous query patterns to follow Testing Library best practices.
Key changes:
- Convert all
userEventcalls to async/await pattern for proper test sequencing - Replace
waitFor+getByRole/getByTextpatterns withfindByRole/findByTextfor cleaner async queries - Add try-finally blocks around fake timer usage to ensure cleanup
- Fix test assertion to match actual API response type (cache_timeout as string)
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| superset-frontend/src/explore/components/PropertiesModal/PropertiesModal.test.tsx | Removed unused afterEach cleanup hook, converted userEvent to async/await, refactored waitFor blocks to use findBy queries, and corrected cache_timeout type expectation from number to string |
| superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx | Removed global test timeout, converted userEvent to async/await, refactored waitFor blocks, added proper try-finally blocks for fake timers, and improved test code organization |
| await waitFor(async () => { | ||
| expect(screen.queryByLabelText('Loading')).not.toBeInTheDocument(); | ||
| userEvent.click(screen.getByText('birth_names')); | ||
| await userEvent.click(screen.getByText('birth_names')); | ||
| }); |
There was a problem hiding this comment.
Using an async callback function inside waitFor is an anti-pattern. The waitFor utility expects synchronous checks that can be repeatedly executed until they pass. An async function inside waitFor can lead to unpredictable behavior.
Instead, await the user interaction before waitFor, and only include the synchronous expectation inside:
await userEvent.click(screen.getByText(DATASET_REGEX));
await userEvent.click(screen.getByText('birth_names'));
await waitFor(() => {
expect(screen.queryByLabelText('Loading')).not.toBeInTheDocument();
});There was a problem hiding this comment.
This test is skipped so we can omit for now
- PropertiesModal.test.tsx: add await to 18+ userEvent calls - FiltersConfigModal.test.tsx: add await to 26 userEvent calls - Fix anti-pattern: remove userEvent from waitFor blocks - Fix cache_timeout test expectation to match API string type - Resolves race condition causing PropertiesModal test failures Root cause: Tests written in 2021 with synchronous userEvent pattern became problematic as components gained more async complexity. Recent changes (PR #33392) made race conditions consistently reproducible. Follows pattern from PR #35717, #35918, and DatasourceControl fixes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
PropertiesModal.test.tsx: - Remove double-await pattern with findBy* queries (lines 477-486) - Move userEvent outside waitFor callbacks (lines 357-364, 414-421, 443-452) - Remove unnecessary afterEach sleep (lines 156-160) FiltersConfigModal.test.tsx: - Remove global 60s timeout, apply only to slow test (line 188) - Fix timer handling in 'validates the pre-filter value' test (lines 347-363) - Add timer cleanup to 'modifies the name of a filter' test (lines 592-639) - Handle multiple validation error messages using getAllByText Performance improvements: - PropertiesModal runtime: Previously had issues, now stable at ~53s - FiltersConfigModal runtime: ~314s (consistent) - Eliminated timer leaks causing subsequent test timeouts Quality improvements: - Eliminates potential for masked bugs from repeated userEvent calls - Follows React Testing Library best practices - Proper timer cleanup prevents test interference - More explicit handling of multiple DOM elements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
28db3ca to
42b2ac9
Compare
|
Looks like #33791 fixed some of the flakiness. This is just additional cleanup. |
## Test Fixes ### ChartList.listview.test.tsx - Fixed flaky timeout in 'can bulk select and deselect all charts' test - Root cause: forEach with expect inside waitFor blocks - Solution: Moved checkbox assertions outside waitFor, only wait for summary text - Pattern: Same anti-pattern fixed in PropertiesModal/FiltersConfigModal (commits a97a8c3, 42b2ac9) ### PropertiesModal.test.tsx - Fixed type mismatch in cache_timeout test expectation - Changed from string '1000' to number 1000 to match implementation - Implementation converts to number before API call (line 219) ### FiltersConfigModal.test.tsx - Fixed 'validates the pre-filter value' test failure - Changed getByText to queryAllByText to handle multiple error messages - Follows existing pattern in same test file (line 363) ## Test Structure Modernization ### ChartList.listview.test.tsx - Removed describe block wrapper (line 67-68 and closing brace) - Updated all 21 test names to include "ChartList list view" prefix for context - Follows Kent C. Dodds "avoid nesting when testing" principle - Matches pattern used in PropertiesModal and FiltersConfigModal tests ### Analysis - PropertiesModal.test.tsx: Already flat ✓ - FiltersConfigModal.test.tsx: Already flat ✓ ## Test Results - ChartList.listview.test.tsx: 21 tests passing (158s) - PropertiesModal.test.tsx: 26 tests passing (40s) - FiltersConfigModal.test.tsx: 16 tests passing, 2 skipped (293s) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…erEvent Addresses feedback on commit 449bce9 by fixing three critical test issues: **Timer Cleanup Bug (FiltersConfigModal):** - Remove redundant timer calls after try/finally block - Fixes Jest error from calling runOnlyPendingTimers() after useRealTimers() - Lines 370-371 removed (called timer helpers outside fake-timer mode) **Mock Pollution (ChartList):** - Add mockIsFeatureEnabled.mockReset() to afterEach block - Prevents test pollution from TAGGING_SYSTEM mock persisting across tests - Ensures each test starts with clean mock state **User Interaction Pattern (ChartList):** - Migrate 24 fireEvent.click() calls to await userEvent.click() - Remove unused fireEvent import - Follows best practices for simulating user interactions - Prevents potential race conditions and missed async updates - Lines affected: 184, 243, 256, 269, 376, 402, 426, 645, 656, 673, 704, 713, 726, 751, 760, 773, 804, 816, 826, 848, 859, 871, 892, 903, 911 **Test Results:** - ChartList: 21 tests passed (165s) - FiltersConfigModal: 16 tests passed, 2 skipped (341s) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
| // All checkboxes should be checked | ||
| let checkboxes = screen.getAllByRole('checkbox'); | ||
| checkboxes.forEach(checkbox => { | ||
| expect(checkbox).toBeChecked(); | ||
| }); |
There was a problem hiding this comment.
The checkbox validation after selection has been moved outside of waitFor(), which could cause race conditions. The checkboxes should be queried and validated inside a waitFor() to ensure the UI has updated after the click event. This validation was previously at line ~656-661 inside a waitFor block.
| checkboxes = screen.getAllByRole('checkbox'); | ||
| checkboxes.forEach(checkbox => { | ||
| expect(checkbox).not.toBeChecked(); |
There was a problem hiding this comment.
The checkbox validation after deselection has been moved outside of waitFor(), which could cause race conditions. The checkboxes should be queried and validated inside a waitFor() to ensure the UI has updated after the deselect action. This validation was previously at line ~622-628 inside a waitFor block.
| checkboxes = screen.getAllByRole('checkbox'); | |
| checkboxes.forEach(checkbox => { | |
| expect(checkbox).not.toBeChecked(); | |
| await waitFor(() => { | |
| const checkboxes = screen.getAllByRole('checkbox'); | |
| checkboxes.forEach(checkbox => { | |
| expect(checkbox).not.toBeChecked(); | |
| }); |
| }); | ||
| await userEvent.clear(cacheTimeout); | ||
| await userEvent.type(cacheTimeout, '1000'); | ||
| expect(cacheTimeout).toHaveValue('1000'); |
There was a problem hiding this comment.
The expectation for the input value should be wrapped in waitFor() to ensure the user input has been processed. The userEvent.type() is async but the DOM update might not be immediate. Consider: await waitFor(() => expect(cacheTimeout).toHaveValue('1000'));
| expect(cacheTimeout).toHaveValue('1000'); | |
| await waitFor(() => expect(cacheTimeout).toHaveValue('1000')); |
There was a problem hiding this comment.
There are two awaits before this, so this should be fine.
| }); | ||
| await userEvent.clear(certifiedBy); | ||
| await userEvent.type(certifiedBy, 'Test certified by'); | ||
| expect(certifiedBy).toHaveValue('Test certified by'); |
There was a problem hiding this comment.
The expectation for the input value should be wrapped in waitFor() to ensure the user input has been processed. The userEvent.type() is async but the DOM update might not be immediate. Consider: await waitFor(() => expect(certifiedBy).toHaveValue('Test certified by'));
| expect(certifiedBy).toHaveValue('Test certified by'); | |
| await waitFor(() => expect(certifiedBy).toHaveValue('Test certified by')); |
There was a problem hiding this comment.
this should be fine as we have await for the two steps above this.
There was a problem hiding this comment.
the steps before are already awaiting, so this should be fine.
| }); | ||
| await userEvent.clear(certificationDetails); | ||
| await userEvent.type(certificationDetails, 'Test certification details'); | ||
| expect(certificationDetails).toHaveValue('Test certification details'); |
There was a problem hiding this comment.
The expectation for the input value should be wrapped in waitFor() to ensure the user input has been processed. The userEvent.type() is async but the DOM update might not be immediate. Consider: await waitFor(() => expect(certificationDetails).toHaveValue('Test certification details'));
| expect(certificationDetails).toHaveValue('Test certification details'); | |
| await waitFor(() => expect(certificationDetails).toHaveValue('Test certification details')); |
There was a problem hiding this comment.
this should be fine as we have await for the two steps above this.
...ontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
Outdated
Show resolved
Hide resolved
...ontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
Outdated
Show resolved
Hide resolved
...ontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
Outdated
Show resolved
Hide resolved
...ontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx
Outdated
Show resolved
Hide resolved
Fixes valid issues identified by GitHub Copilot automated review: **waitFor Anti-pattern (FiltersConfigModal):** - Replace 4 instances of `await waitFor(() => screen.getByText(...))` - Use `await screen.findByText(...)` instead (lines 228, 253, 269, 285) - `findBy` queries are designed for waiting, `waitFor` is for assertions - Improves code clarity and follows React Testing Library best practices **Test Name Mismatch (PropertiesModal):** - Line 259: Rename test to accurately reflect behavior - Was: "Save button should call only onSave" - Now: "Save button should call onSave and onHide" - Test expects both callbacks to be called, name now matches **Investigated but Not Fixed:** - ChartList checkbox assertions (lines 661-664, 678-681) - Tests passing consistently (21/21), no flakiness observed - Previous `waitFor` blocks provide sufficient stability - Decision: Leave as-is, false positive from Copilot **Test Results:** - FiltersConfigModal: 16 tests passed in 313s ✅ - ChartList: 21 tests passed in 155s ✅ - PropertiesModal: Test name now accurate ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigModal.test.tsx:1
- Corrected test description from '"Save" button should call only "onSave"' to '"Save" button should call "onSave" and "onHide"' to accurately reflect what the test validates.
/**
…nfigModal.test.tsx and ChartList.listview.test.tsx (apache#36037) Co-authored-by: Claude <noreply@anthropic.com>
…nfigModal.test.tsx and ChartList.listview.test.tsx (apache#36037) Co-authored-by: Claude <noreply@anthropic.com>
…nfigModal.test.tsx and ChartList.listview.test.tsx (apache#36037) Co-authored-by: Claude <noreply@anthropic.com> (cherry picked from commit 0a5144f)
…nfigModal.test.tsx and ChartList.listview.test.tsx (apache#36037) Co-authored-by: Claude <noreply@anthropic.com> (cherry picked from commit 0a5144f)
SUMMARY
This PR fixes several React Testing Library anti-patterns in test files that were causing race conditions, test failures, and timer leaks. The changes follow React Testing Library best practices and eliminate potential bug-masking issues.
Changes made:
PropertiesModal.test.tsx:
FiltersConfigModal.test.tsx:
Why these changes matter:
hiding real timing bugs
test(dashboard): eliminate race condition in FiltersConfigModal test #35717, test(useThemeMenuItems): fix race conditions by awaiting all userEvent calls #35918)
BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
TESTING INSTRUCTIONS
ADDITIONAL INFORMATION