You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -59,7 +59,7 @@ RTL emphasizes testing components as users interact with them. Users find button
59
59
60
60
4.**DRY Helpers** - Use reusable function in frontend/packages/console-shared/src/test-utils directoty and sub-directory if exists else extract repetitive setup into reusable functions
61
61
62
-
5.**Async-Aware** - Handle asynchronous updates with `findBy*` and `waitFor`
62
+
5.**Async-Aware** - Prefer `findByText` / `findByRole` / `findAllByRole` when waiting for content to **appear**; use `waitFor` when the assertion is **not** expressible as a query (e.g. button **disabled** after validation, **absence**, class names, mocks). See **Rule 11**.
63
63
64
64
6.**TypeScript Safety** - Use proper types for props, state, and mock data
-**`getBy*`** - Element expected to be present synchronously (throws if not found)
362
362
-**`queryBy*`** - Only for asserting element is NOT present
363
-
-**`findBy*`** - Element will appear asynchronously (returns Promise)
363
+
-**`findBy*`** - Element will appear asynchronously (returns Promise). Prefer **`findBy*`** over **`waitFor(() => … getBy* …)`** when the goal is to wait for that node to **appear** (see **Rule 11**).
364
364
365
365
**Anti-pattern:** Avoid `container.querySelector` - it tests implementation details.
366
366
@@ -456,7 +456,8 @@ it('should render the Name field', async () => {
@@ -498,18 +500,54 @@ it('should show content when expanded', () => {
498
500
499
501
### Rule 11: Handle Asynchronous Behavior
500
502
503
+
#### Prefer `findBy*` over `waitFor` + `getBy*` when waiting for content to appear
504
+
505
+
`findByText`, `findByRole`, `findAllByRole`, etc. are implemented as **`waitFor` + `getBy*`** (they return a Promise and retry until the element is found or the timeout is reached). When the intent is **“wait until this text/role appears in the DOM,”** use **`await screen.findBy…`** instead of wrapping **`getBy…`** in **`waitFor`**.
506
+
501
507
```typescript
502
-
// Use findBy* to wait for an element to appear
503
-
const element =awaitscreen.findByText('Loaded content');
504
-
expect(element).toBeVisible();
508
+
// ✅ GOOD: findBy* expresses “eventually this appears”
**Avoid Explicit act():** Rarely needed. `render`, `fireEvent`, `findBy*`, and `waitFor` already wrap operations in `act()`.
519
+
#### Keep `waitFor` when `findBy*` cannot express the assertion
520
+
521
+
**`findBy*` queries wait for presence** of a matching node. They do **not** replace every `waitFor`. Still use **`waitFor`** when you need to wait for something that is **not** “find this text/role in the tree,” for example:
522
+
523
+
| Situation | Why `findBy*` is not enough | Pattern |
|**Control state after async validation** (e.g. Save **disabled** after Formik/yup runs) | Disabled is a **property**, not a new queryable label; there is no `findBy…` for “button became disabled.” |`await waitFor(() => expect(save).toBeDisabled());` — you can still use **`findByText`** / **`findByRole`** in the **same** test for **error messages** that appear as text. |
526
+
|**Eventually absent** (list/link/control **not** in the document) |**`findBy*` waits for presence**, not absence. |`await waitFor(() => expect(screen.queryByRole('list')).not.toBeInTheDocument());` or `waitForElementToBeRemoved` when something was visible first. |
527
+
|**CSS class** or **non-query** DOM state | Not a text/role query. |`await waitFor(() => expect(input).toHaveClass('invalid-tag'));`|
528
+
|**Mock** or **callback** assertions | Not the DOM. |`await waitFor(() => expect(mockFn).toHaveBeenCalledWith(…));`|
Use `@testing-library/user-event` for clicks, typing, and other interactions. It simulates full event sequences closer to real browser behavior than low-level `fireEvent`.
**Note:**`userEvent` from `@testing-library/user-event` is not supported due to incompatible Jest version (will be updated after Jest upgrade).
615
+
**Conventions:**
616
+
- Call `const user = userEvent.setup()` per test and **await**`user.click`, `user.type`, `user.keyboard`, etc.
617
+
- For text inputs in forms, prefer **`verifyInputField`** (Rule 9) when it applies; use `userEvent` for other controls (selects, checkboxes, buttons, complex widgets).
618
+
- Reserve **`fireEvent`** only for rare cases (e.g., triggering a specific low-level DOM event that `userEvent` does not model).
574
619
575
620
### Rule 15: Test "Unhappy Paths" and Error States
576
621
@@ -648,12 +693,14 @@ Remove any imports that are not used in the test file:
648
693
649
694
```typescript
650
695
// ❌ BAD - Unused imports
651
-
import { render, screen, fireEvent, waitFor, within } from'@testing-library/react';
696
+
import { render, screen, waitFor, within } from'@testing-library/react';
When waiting for content to appear after user interactions, prefer `findBy*` queries which automatically wait for elements. See **Rule 11** for comprehensive guidance on `findBy*` vs `waitFor`, including when to use each and a detailed comparison table.
974
1021
975
-
**Strategy 3: Wrap test in act() when needed**
1022
+
**Strategy 3: Prefer userEvent + findBy* for dropdown/select-style interactions**
5. ✅ Cast mocked imports to `jest.Mock` when calling `.mockResolvedValue()` etc.
1457
1508
6. ✅ **Import `verifyInputField` for form components** - Rule 9 strictly enforced
1509
+
7. ✅ **Use `userEvent` from `@testing-library/user-event` for clicks, typing, and similar interactions** - Rule 14 (`userEvent.setup()`, then `await user.click` / `await user.type`, etc.)
Copy file name to clipboardExpand all lines: frontend/packages/console-app/src/components/modals/resource-limits/__tests__/ResourceLimitsModal.spec.tsx
0 commit comments