# Gen Rtl Test

> Generate React Testing Library tests following OCP Console best practices

- Skill: `openshift/gen-rtl-test` (Agent Skill)
- Install (CLI): `npx skillmds@latest add openshift/gen-rtl-test`
- Raw SKILL.md: https://api.skillmd.com/api/skills/openshift/gen-rtl-test/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: openshift (https://skillmd.com/u/openshift)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/openshift/gen-rtl-test

---


# OCP Console React Component Unit Testing Best Practices

**Usage:**
- `/gen-rtl-test` - **Default**: Automatically checks `git diff` for component changes and generates tests
- `/gen-rtl-test path/to/Component.tsx` - Generate tests for a specific component
- `/gen-rtl-test @Component.tsx` - Use `@` for file autocomplete, then select the file

## Smart Component Detection Workflow

When invoked without arguments, the slash command follows this intelligent workflow:

1. **Check git diff**: Automatically run `git diff --name-only` to find modified files
2. **Filter for components**: Identify `.tsx` and `.jsx` component files (exclude test files, type files, utils)
3. **Validate components**: Ensure files contain React components (not just types or utilities)
4. **Present options**: Show user the detected components and ask which to generate tests for
5. **Fallback**: If no valid components found, prompt user for component path

This workflow ensures you automatically generate tests for components you're actively working on.

You are helping generate comprehensive React Testing Library (RTL) test cases following the established OCP Console unit testing standards.

**Before writing imports:** Inspect the component under test (and its hooks). Use **`renderWithProviders`** only if it depends on the **Redux store** and/or **React Router**. Otherwise use **`render`** from `@testing-library/react`. (See **Rule 0** for the full table, including **PluginStore** when relevant.)

## Introduction & Objectives

This guide establishes a consistent, project-wide standard for all React component tests in the OCP Console.

**Core Philosophy:** Test component behavior from a user's perspective, not internal implementation details.

### Objectives
- Establish consistent project-wide testing standards
- Promote user-centric testing that focuses on behavior over implementation
- Provide practical, rules-based guidance for common scenarios
- Improve test quality, resilience, and maintainability

---

### Rule 0: Use `renderWithProviders` Only When the Component Needs Redux and/or Router

Pick the render helper from what the **component under test** actually uses:

| Use | When the component (or non-mocked hooks it calls) … |
|-----|------------------------------------------------------|
| **`renderWithProviders`** from `@console/shared/src/test-utils/unit-test-utils` | Needs the **Redux store** (e.g. `useSelector`, `useDispatch`, k8s/resource hooks backed by the console store) **and/or** **React Router** (e.g. `useNavigate`, `useParams`, `useLocation`, `Link`, `NavLink`). The helper also wraps **PluginStore** — use it when the tree touches **dynamic plugin** APIs that expect that context. |
| **`render`** from `@testing-library/react` | Has **no** Redux or Router dependency (pure presentational UI, local `useState` only, or all store/router hooks are mocked so the real provider is unnecessary). |

```typescript
// Standalone / presentational — no Redux or Router in the component under test
import { render, screen } from '@testing-library/react';
import { BadgeLabel } from './BadgeLabel';

it('renders the label', () => {
  render(<BadgeLabel text="Ready" />);
  expect(screen.getByText('Ready')).toBeVisible();
});

// Connected to store and/or routes — use the console test wrapper
import { screen } from '@testing-library/react';
import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils';
import { DeploymentListRow } from './DeploymentListRow';

it('shows the deployment name', () => {
  renderWithProviders(<DeploymentListRow deployment={mockDeployment} />);
  expect(screen.getByRole('cell', { name: /nginx/i })).toBeVisible();
});
```

**Why `renderWithProviders` exists:** it supplies **Redux `Provider`**, **`MemoryRouter`**, and **PluginStore** so typical console components do not throw when mounting.

**Optional clarity for reviewers:** If the file uses `render` (not `renderWithProviders`), a one-line comment at the top of the file or above the first test can help reviewers, e.g. `// Unit tests: component has no Redux or Router dependencies.`

**Do not** use `renderWithProviders` “by default” for every console file — that hides missing providers in tests that should be asserting integration with real store/router behavior, and it adds cost where `render` is enough.

### Rule 0.1: Use userEvent (Not fireEvent)

**ALWAYS** use `userEvent` from `@testing-library/user-event` for user interactions.

```typescript
// FORBIDDEN — ESLint / project rules (e.g. testing-library) when enabled
import { fireEvent } from '@testing-library/react';
fireEvent.click(button);
fireEvent.change(input, { target: { value: 'test' } });

// REQUIRED
import userEvent from '@testing-library/user-event';
const user = userEvent.setup();
await user.click(button);
await user.type(input, 'test');
```

**Why:**
- `userEvent` simulates real user behavior (focus, blur, keyboard events)
- `fireEvent` dispatches raw DOM events (not realistic)
- `userEvent` catches more bugs related to event handling
- Better async handling with `await`

### Rule 0.2: Use screen Queries (Not Destructured)

**ALWAYS** use `screen` from RTL instead of destructuring queries from `render`.

```typescript
// FORBIDDEN — ESLint (e.g. testing-library/prefer-screen-queries) when enabled
const { getByRole, getByText } = render(<MyComponent />);
const button = getByRole('button');

// REQUIRED — use whichever render helper matches Rule 0, then always query via screen
import { render, screen } from '@testing-library/react';
// or: import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils';
render(<MyComponent />); // or renderWithProviders(<MyComponent />) when Redux/Router are needed
const button = screen.getByRole('button');
```

**Why:**
- Consistent query access across all tests
- Better debugging with `screen.debug()`
- Cleaner test code
- ESLint rule `testing-library/prefer-screen-queries` enforces this

---

## Section 1: React Testing Library Overview

### The RTL Approach
RTL emphasizes testing components as users interact with them. Users find buttons by visible text (e.g., "Submit"), not by CSS classes, IDs, or test IDs. Therefore, test selectors should prioritize what users see and interact with.

### Core Principles

1. **User-Centric Testing** - Test what users see and interact with. **DO NOT test:**
   - Internal component state
   - Private component methods
   - Props passed to child components
   - CSS class names or styles
   - Component structure (e.g., `expect(container.firstChild).toBe...`)

2. **Accessibility-First** - Queries match how screen readers and users interact with the UI

3. **Semantic Over Generic** - Always prefer role-based queries (e.g., `getByRole`) over generic selectors

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

5. **Async-Aware** - Handle asynchronous updates with `findBy*` and `waitFor`

6. **TypeScript Safety** - Use proper types for props, state, and mock data

7. **Arrange-Act-Assert (AAA) Pattern** - Structure tests logically:
   - **Arrange:** Render component with mocks
   - **Act:** Perform user actions
   - **Assert:** Verify expected state

---

## Section 2: Console RTL Rules

## ⚠️ CRITICAL RULE - READ FIRST

### **ALWAYS Use ES6 Imports - NEVER Use require()**

**This is the #1 most critical rule for test generation.**

## 🚫 ZERO TOLERANCE: NO require() ANYWHERE

**NEVER use `require()` in test files. NO EXCEPTIONS.**

❌ **FORBIDDEN - In test bodies:**
```typescript
it('should work', () => {
  const { k8sCreate } = require('@console/internal/module/k8s'); // ❌ NEVER
});
```

❌ **FORBIDDEN - In mock factories:**
```typescript
jest.mock('../Component', () => {
  const React = require('react'); // ❌ NEVER - even here!
  return () => React.createElement('div', null, 'Mock');
});
```

✅ **REQUIRED - ES6 imports only:**
```typescript
// Import at file top
import { k8sCreate } from '@console/internal/module/k8s';

// Simple mocks - no React.createElement needed
jest.mock('../Component', () => () => null); // ✅ Return null
jest.mock('../LoadingSpinner', () => () => 'Loading...'); // ✅ Return string

// Use in tests
it('should work', () => {
  (k8sCreate as jest.Mock).mockResolvedValue({});
});
```

**Why ZERO tolerance:**
- `require()` breaks Jest's mock hoisting mechanism
- Causes test isolation failures and flaky tests
- Violates OCP Console testing standards
- **NO exceptions - even in mock factories**

---

### Rule 1: Test File Co-location and Naming Convention

**File Structure:**
```
MyComponentDirectory/
├── __tests__/
│   └── MyComponent.spec.tsx
└── MyComponent.tsx
```

- Test file must be in `__tests__/` directory within component directory
- Test file must have same name as implementation file
- Use `.spec.tsx` extension

### Rule 2: Mocking Strategies

#### Check for Global Mocks First
Before manually mocking, check `__mocks__/` directory for existing global mocks (e.g., `react-i18next`, `localStorage`, `k8sResourcesMocks`). These are applied automatically.

#### Keep Component Mocks Simple (No JSX)
Mock functions must NOT return JSX to avoid Jest hoisting errors:

```typescript
// ✅ CORRECT:
jest.mock('../MyComponent', () => () => null);

// ✅ CORRECT:
jest.mock('../LoadingSpinner', () => () => 'Loading...');

// ✅ CORRECT: Return children directly
jest.mock('../utils/firehose', () => ({
  Firehose: (props) => props.children,
}));

// ✅ CORRECT: Use jest.fn for tracking calls
jest.mock('../utils/firehose', () => ({
  Firehose: jest.fn((props) => props.children),
}));

// ❌ INCORRECT (causes hoisting errors):
jest.mock('../MyComponent', () => () => <div>My Mock</div>);
```

#### Mock Custom Hooks with jest.fn()
```typescript
jest.mock('../useCustomHook', () => ({
  useCustomHook: jest.fn(() => [/* mock data */]),
}));
```

#### Use Static Partial Mocking for Module-Wide Control
```typescript
jest.mock('@console/internal/module/k8s', () => ({
  ...jest.requireActual('@console/internal/module/k8s'),
  k8sCreate: jest.fn(),
  k8sPatch: jest.fn(),
}));
```

#### Use jest.spyOn for Granular, Test-Level Control (Preferred)
```typescript
import * as k8sModule from '@console/internal/module/k8s';

it('should do something when k8sGet succeeds', () => {
  jest.spyOn(k8sModule, 'k8sGet').mockResolvedValue(data);
  // ... rest of the test ...
});
```

#### Controlling Redux State
**DO NOT** mock the `useReduxStore` hook. Instead, pass `initialState` to `renderWithProviders`:

```typescript
import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils';

it('should render with mock Redux data', () => {
  const mockK8sState = { /* ... */ };

  renderWithProviders(
    <MyComponent />,
    {
      initialState: {
        k8s: mockK8sState
      }
    }
  );

  expect(screen.getByText('My Mock Data')).toBeVisible();
});
```

#### ⚠️ CRITICAL: Always Use ES6 Import (Never require())
**STRICTLY ENFORCED - ZERO EXCEPTIONS**

## 🚫 NO require() ANYWHERE IN TEST FILES

Always use ES6 `import/export` syntax in test files. **NEVER** use `require()` - not in test bodies, not in mock factories, **NOWHERE**.

**✅ CORRECT - ES6 Imports:**
```typescript
// Import at the top of the file
import { k8sCreate } from '@console/internal/module/k8s';
import { history } from '@console/internal/components/utils';
import * as pdbModels from '../pdb-models';

// Simple mocks - return null or strings, NO React.createElement
jest.mock('../Component', () => () => null);
jest.mock('../ButtonBar', () => ({ children }) => children);

// Use in test
it('should create resource', async () => {
  (k8sCreate as jest.Mock).mockResolvedValue({});
  jest.spyOn(history, 'push');
  jest.spyOn(pdbModels, 'patchPDB').mockResolvedValue({});
  // ... rest of test
});
```

**❌ INCORRECT - require() ANYWHERE:**
```typescript
// ❌ NEVER in test bodies
it('should create resource', async () => {
  const { k8sCreate } = require('@console/internal/module/k8s'); // ❌ FORBIDDEN
});

// ❌ NEVER in mock factories
jest.mock('../Component', () => {
  const React = require('react'); // ❌ FORBIDDEN - even here!
  return () => React.createElement('div', null, 'Mock');

});

// ❌ NEVER in beforeEach
beforeEach(() => {
  const utils = require('../utils'); // ❌ FORBIDDEN
});
```

**How to avoid require() in mocks:**
```typescript
// ✅ Return null instead of JSX
jest.mock('../Component', () => () => null);

// ✅ Return string instead of JSX
jest.mock('../LoadingSpinner', () => () => 'Loading...');

// ✅ Return children directly
jest.mock('../Wrapper', () => ({ children }) => children);

// ✅ Use jest.fn for tracking
jest.mock('../ButtonBar', () => jest.fn(({ children }) => children));
```

**Enforcement Checklist:**
- [ ] All module imports use ES6 `import` statements at file top
- [ ] ZERO `require()` calls anywhere in the file
- [ ] Mocked modules imported at top and cast to `jest.Mock` when needed
- [ ] Mock factories return simple values (null, strings, children) - NO React.createElement

### Rule 3: Use a Clear and Focused Test Structure

```typescript
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';

// Top-level describe for the component
describe('MyComponent', () => {
  // Nested describe for specific features
  describe('when loading', () => {
    it('should show the loading spinner', () => {
      jest.spyOn(myHooksModule, 'useCustomHook').mockReturnValue({ isLoading: true });
      render(<MyComponent />);
      expect(screen.getByRole('progressbar')).toBeVisible();
    });

    it('should not show the data grid', () => {
      jest.spyOn(myHooksModule, 'useCustomHook').mockReturnValue({ isLoading: true });
      render(<MyComponent />);
      expect(screen.queryByRole('grid')).not.toBeInTheDocument();
    });
  });

  describe('when data is loaded', () => {
    it('should show the data grid', () => {
      jest.spyOn(myHooksModule, 'useCustomHook').mockReturnValue({ isLoading: false, data: [...] });
      render(<MyComponent />);
      expect(screen.getByRole('grid')).toBeVisible();
    });
  });
});
```

**Requirements:**
- All tests wrapped in top-level `describe` block named after component
- Use nested `describe` blocks for related tests
- Use `it()` method (not `test()`)
- Each `it` block tests only a single state or interaction

### Rule 4: Use the Correct Render Function

Same decision as **Rule 0**:

- **`render`** from `'@testing-library/react'` — when the component under test has **no** Redux or React Router dependency (see Rule 0 table).
- **`renderWithProviders`** from `'@console/shared/src/test-utils/unit-test-utils'` — when it needs **Redux** and/or **React Router** (and use it when plugin context is required; see Rule 0).

### Rule 5: Always Use screen for Queries

```typescript
import { render, screen } from '@testing-library/react';

// ✅ DO: Use the global 'screen' object
it('should find the heading', () => {
  render(<MyComponent />);
  const heading = screen.getByRole('heading', { name: /welcome/i });
  expect(heading).toBeVisible();
});

// ❌ AVOID: Destructuring queries from 'render'
it('should find the heading', () => {
  const { getByRole } = render(<MyComponent />);
  const heading = getByRole('heading', { name: /welcome/i });
  expect(heading).toBeVisible();
});
```

**Exception:** Use `within()` for scoped queries or when you need `container` for specific assertions.

### Rule 6: Prioritize Accessible Queries

**Query Priority (most to least preferred):**
1. `getByRole`
2. `getByLabelText`
3. `getByPlaceholderText`
4. `getByText`
5. `getByDisplayValue`
6. `getByAltText`
7. `getByTitle`
8. `getByTestId` (last resort only).  This might involve adding a `data-test` attribute to the implementation component element.

**Query Variants:**
- **`getBy*`** - Element expected to be present synchronously (throws if not found)
- **`queryBy*`** - Only for asserting element is NOT present
- **`findBy*`** - Element will appear asynchronously (returns Promise)

**Anti-pattern:** Avoid `container.querySelector` - it tests implementation details.

**Helpful Tip:** For iframe or markdown content, use `screen.getByRole('document')`.

### Rule 7: Text Matching Strategy

- **Exact text match** - Preferred when text is in a single node
- **Regex without `i` flag** - When text spans multiple wrapper nodes (avoid case-insensitive matching)

**Note:** Avoid case-insensitive matching based on Console UX text casing convention.

### Rule 8: Assertion Guidelines

```typescript
// ✅ GOOD: Tests accessible name + existence
expect(screen.getByRole('button', { name: 'Submit' })).toBeVisible();

// ❌ AVOID: Separate queries for same element
const button = screen.getByRole('button');
expect(button).toBeInTheDocument();
expect(screen.getByText('Submit')).toBeInTheDocument();
```

**When to use:**
- **`toBeVisible()`** - For elements users are expected to see or interact with
- **`toBeInTheDocument()`** - For structural elements or conditional rendering verification

**Anti-pattern:** Avoid weak assertions like `.toBeTruthy()` or `.toBeInTheDocument()` for visible elements.

### Rule 9: Use Shared verifyInputField Utility - MANDATORY for Form Fields

**CRITICAL:** When testing form input fields, **ALWAYS** use `verifyInputField` utility. This is strictly enforced.

#### When to Use verifyInputField

Use `verifyInputField` when your test needs to verify:
- ✅ Input field label exists and is associated with the input
- ✅ Input element renders correctly
- ✅ Initial/default value of the input
- ✅ Input can accept user input (onChange behavior)
- ✅ Help text appears below the field
- ✅ Required field indicator (`*`) is shown
- ✅ Field ID and accessibility attributes

**DO NOT** manually write separate assertions for each of these - use the utility instead.

#### Usage Examples

```typescript
import { verifyInputField } from '@console/shared/src/test-utils/unit-test-utils';

// ✅ GOOD: Use verifyInputField for comprehensive field testing
it('should render the Name field with label, input, and help text', async () => {
  render(<MyFormComponent />);

  await verifyInputField({
    inputLabel: 'Name',
    containerId: 'test-name-form',
    initialValue: 'test',
    testValue: 'test',
    helpText: 'Unique name for the resource',
    isRequired: true,
  });
});

// ✅ GOOD: Test multiple fields with verifyInputField
it('should render all form fields correctly', async () => {
  render(<MyFormComponent />);

  await verifyInputField({
    inputLabel: 'Name',
    containerId: 'test-name-form',
    initialValue: '',
    testValue: 'my-resource',
    isRequired: true,
  });

  await verifyInputField({
    inputLabel: 'Description',
    containerId: 'test-description-form',
    initialValue: '',
    testValue: 'A description',
    helpText: 'Optional description for this resource',
    isRequired: false,
  });
});

// ❌ BAD: Manual assertions for form fields
it('should render the Name field', async () => {
  render(<MyFormComponent />);

  // Don't do this - use verifyInputField instead!
  expect(screen.getByLabelText('Name')).toBeInTheDocument();
  expect(screen.getByLabelText('Name')).toHaveValue('');
  fireEvent.change(screen.getByLabelText('Name'), { target: { value: 'test' } });
  expect(screen.getByLabelText('Name')).toHaveValue('test');
  expect(screen.getByText('Unique name for the resource')).toBeInTheDocument();
});
```

#### When NOT to Use verifyInputField

- ❌ Non-input form controls (Select, Dropdown, Checkbox, Radio)
- ❌ Buttons or action elements
- ❌ Read-only text displays
- ❌ Custom form components that aren't text inputs

For these cases, use standard RTL queries.

#### Enforcement Checklist

When testing form components:
- [ ] Identify all text input fields in the component
- [ ] Use `verifyInputField` for each text input field test
- [ ] Avoid manual label/input/helpText assertions
- [ ] Import `verifyInputField` from `'@console/shared/src/test-utils/unit-test-utils'`

### Rule 10: Test Conditional Rendering by Asserting Both States

```typescript
import userEvent from '@testing-library/user-event';

it('should show content when expanded', async () => {
  render(<Collapsible />);
  const user = userEvent.setup();

  // 1. Assert initial hidden state
  expect(screen.queryByText('Hidden content')).not.toBeInTheDocument();

  // 2. Simulate user action
  await user.click(screen.getByRole('button', { name: 'Expand' }));

  // 3. Assert final visible state
  expect(screen.getByText('Hidden content')).toBeVisible();
});
```

### Rule 11: Handle Asynchronous Behavior

```typescript
// Use findBy* to wait for an element to appear
const element = await screen.findByText('Loaded content');
expect(element).toBeVisible();

// Use waitFor for complex assertions
await waitFor(() => {
  expect(screen.getByText('Updated')).toBeInTheDocument();
});
```

**Avoid Explicit act():** Rarely needed. `render`, `userEvent`, `findBy*`, and `waitFor` already wrap operations in `act()`.

### Rule 12: Use Lifecycle Hooks for Setup and Cleanup

```typescript
describe('MyComponent', () => {
  beforeEach(() => {
    jest.spyOn(myHooksModule, 'useCustomHook').mockReturnValue({ isLoading: false });
  });

  afterEach(() => {
    jest.restoreAllMocks();
  });

  it('should render the default state', () => {
    render(<MyComponent />);
    // ...
  });

  it('should render a different state', () => {
    jest.spyOn(myHooksModule, 'useCustomHook').mockReturnValue({ isLoading: true });
    render(<MyComponent />);
    // ...
  });
});
```

### Rule 13: Scope Queries with within()

```typescript
import { render, screen, within } from '@testing-library/react';

render(<MyDashboard />);

const userProfileCard = screen.getByTestId('profile-card');

// Scope queries to only that card
const userName = within(userProfileCard).getByText(/john doe/i);
const editButton = within(userProfileCard).getByRole('button', { name: /edit/i });

expect(userName).toBeVisible();
expect(editButton).toBeVisible();
```

### Rule 14: Simulate User Events with userEvent

```typescript
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { renderWithProviders } from '@console/shared/src/test-utils/unit-test-utils';

// Example: form reads Redux or routes — use renderWithProviders (Rule 0).
// For a form with only local state and mocked submit handlers, `render` is enough.
renderWithProviders(<MyForm />);
const user = userEvent.setup();

const input = screen.getByLabelText(/name/i);
const button = screen.getByRole('button', { name: /submit/i });

// Simulate typing
await user.type(input, 'John Doe');

// Simulate clicking
await user.click(button);
```

**Why userEvent over fireEvent:**
- `userEvent` simulates real user behavior (focus, blur, keyboard events)
- `fireEvent` dispatches raw DOM events (not realistic)
- `userEvent` catches more bugs related to event handling
- Better async handling with `await`

### Rule 15: Test "Unhappy Paths" and Error States

```typescript
it('should display an error message when the API call fails', async () => {
  jest.spyOn(k8sModule, 'k8sGet').mockRejectedValue(new Error('API Error'));

  render(<MyComponent />);

  const errorMessage = await screen.findByText(/Could not load data/i);
  expect(errorMessage).toBeVisible();

  expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
});
```

### Rule 16: Use screen.debug() for Help

```typescript
it('should find the element', () => {
  render(<MyComponent />);

  // If a query fails, use debug() to see the DOM
  // screen.debug();

  // You can also debug a specific element
  // const form = screen.getByRole('form');
  // screen.debug(form);

  const button = screen.getByRole('button', { name: /submit/i });
  expect(button).toBeVisible();
});
```

### Rule 17: Write Descriptive Test Titles

**Format:** `it('should [expected result] when [condition]')`

```typescript
// ✅ GOOD
it('should display an error when the API call fails')

// ❌ AVOID
it('works')
it('renders')
```

### Rule 18: Avoid Snapshot Tests

**DO NOT** use `toMatchSnapshot()`, `toMatchInlineSnapshot()`, or error snapshot matchers. Snapshot tests are brittle, give false security, and test implementation details. Prefer **`toStrictEqual`**, **`toMatchObject`**, or RTL queries on user-visible output.

**Enforcement:** `jest/no-restricted-matchers` from `eslint-plugin-console` **errors** on these matchers for paths matched by `plugin:console/testing-library-tests` (the same `**/*spec*` / `**/__tests__**` globs used for RTL lint).

### Rule 19: Render in Each Test by Default

**Default:** Call `render()` inside each `it` block for test isolation.

**May use `beforeEach` only if ALL tests in the block:**
- Are simple, synchronous tests
- Use the exact same props and initial state
- Only test different aspects of a single, unchanged render

### Rule 20: Use Centralized Test Data

Store mock data in centralized files (e.g., `__mocks__/k8sResourcesMocks.ts`). This:
- Mirrors production data structures
- Makes tests more representative
- Easier to maintain
- Catches type-related errors early

### Rule 21: Clean Up Unused Imports, Code, and Redundant Mocks

**MANDATORY:** After generating tests, perform cleanup to ensure code quality and maintainability.

#### Clean Up Unused Imports
Remove any imports that are not used in the test file:

```typescript
// ❌ BAD - Unused imports
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { k8sCreate, k8sPatch, k8sUpdate } from '@console/internal/module/k8s';
// ... but only using render, screen, userEvent

// ✅ GOOD - Only what's needed
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { k8sCreate } from '@console/internal/module/k8s';
```

#### Remove Redundant Mocks
Only mock what's actually used in tests:

```typescript
// ❌ BAD - Mocking unused components
jest.mock('../ComponentA', () => () => null);
jest.mock('../ComponentB', () => () => null);
jest.mock('../ComponentC', () => () => null);
// ... but ComponentB and ComponentC are never rendered

// ✅ GOOD - Only mock what's used
jest.mock('../ComponentA', () => () => null);
```

#### Remove Duplicate or Redundant Tests
Avoid testing the same behavior multiple times:

```typescript
// ❌ BAD - Redundant tests
it('should render the button', () => {
  render(<MyComponent />);
  expect(screen.getByRole('button')).toBeInTheDocument();
});

it('should display the button', () => {
  render(<MyComponent />);
  expect(screen.getByRole('button')).toBeVisible();
});

// ✅ GOOD - Single comprehensive test
it('should render the button', () => {
  render(<MyComponent />);
  expect(screen.getByRole('button', { name: 'Submit' })).toBeVisible();
});
```

#### Remove Commented Code
Delete commented-out code, debugging statements, and console.logs:

```typescript
// ❌ BAD - Commented code left in
it('should work', () => {
  render(<MyComponent />);
  // screen.debug(); // TODO: remove
  // const oldTest = screen.getByTestId('old-id');
  expect(screen.getByRole('button')).toBeVisible();
});

// ✅ GOOD - Clean, production-ready
it('should work', () => {
  render(<MyComponent />);
  expect(screen.getByRole('button')).toBeVisible();
});
```

#### Remove Unused Variables and Constants
Clean up any variables that are declared but never used:

```typescript
// Imports omitted for brevity - see Rule 14 for full import pattern
import userEvent from '@testing-library/user-event';

// ❌ BAD - Unused variables
it('should submit form', async () => {
  const mockData = { foo: 'bar' };
  const unusedSpy = jest.spyOn(console, 'log');
  const onSubmit = jest.fn();
  const user = userEvent.setup();

  render(<Form onSubmit={onSubmit} />);
  await user.click(screen.getByRole('button'));

  expect(onSubmit).toHaveBeenCalled();
});

// ✅ GOOD - Only necessary variables
it('should submit form', async () => {
  const onSubmit = jest.fn();
  const user = userEvent.setup();

  render(<Form onSubmit={onSubmit} />);
  await user.click(screen.getByRole('button'));

  expect(onSubmit).toHaveBeenCalled();
});
```

#### Remove Unnecessary Mock Static Methods
Only add static methods to mocks if they're actually called:

```typescript
// ❌ BAD - Mock has methods that are never called
jest.mock('../SelectorInput', () => Object.assign(
  jest.fn(() => null),
  {
    objectify: jest.fn(),
    arrayify: jest.fn(),
    someMethodNeverUsed: jest.fn(), // ← Never called
    anotherUnusedMethod: jest.fn(), // ← Never called
  }
));

// ✅ GOOD - Only methods that are used
jest.mock('../SelectorInput', () => Object.assign(
  jest.fn(() => null),
  {
    objectify: jest.fn(),
    arrayify: jest.fn(),
  }
));
```

**Cleanup Checklist:**
- [ ] All imports are used in the test file
- [ ] All mocked modules are referenced in tests
- [ ] No duplicate test cases
- [ ] No commented-out code (unless needed for documentation)
- [ ] No unused variables, constants, or spies
- [ ] Mock static methods are only those actually called
- [ ] No console.log, console.debug, or screen.debug() in final tests

### Rule 22: Generate Between 5-10 Tests Per Component

**IMPORTANT:** Generate between **5 and 10 focused, high-value tests** per component.

#### Why 5-10 Tests?

- **Minimum 5:** Ensures adequate coverage of critical functionality
- **Maximum 10:** Prevents over-testing and maintains quality focus
- **Quality over Quantity** - Forces focus on most important behaviors
- **Maintainability** - Easier to read, understand, and maintain
- **Faster Test Runs** - Reduced execution time
- **Better Code Reviews** - Reviewers can thoroughly examine each test
- **Reduced Redundancy** - Prevents testing the same thing multiple ways

#### How to Choose 5-10 Tests

**Priority Order:**

1. **Critical User Flows** (2-3 tests)
   - Primary user actions (e.g., form submission, data creation)
   - Most important happy path scenarios

2. **Error States** (2-3 tests)
   - API failures, validation errors
   - Edge cases that break functionality
   - "Unhappy paths" users might encounter

3. **Conditional Rendering** (2-3 tests)
   - Different states/modes of the component
   - Loading states, empty states
   - Permission-based rendering

4. **User Interactions** (1-2 tests)
   - Click handlers, input changes
   - Form validation
   - Navigation/routing

5. **Accessibility** (1 test)
   - Key accessible queries work
   - ARIA attributes present
   - Keyboard navigation (if complex)

**What NOT to Test (when limiting to 5-10):**

❌ Multiple variations of the same behavior
❌ Testing every prop combination
❌ Minor UI variations (button text, colors)
❌ Component existence tests
❌ Trivial rendering checks

#### Examples

**❌ BAD - Too Few Tests (3 tests):**
```typescript
describe('MyForm', () => {
  it('should render the form');
  it('should submit when valid');
  it('should show error when invalid');
});
```

**❌ BAD - Too Many Tests (15 tests):**
```typescript
describe('MyForm', () => {
  it('should render the form');
  it('should render the name input');
  it('should render the email input');
  it('should render the phone input');
  it('should render the submit button');
  it('should render the cancel button');
  it('should enable submit when name is filled');
  it('should enable submit when email is filled');
  it('should enable submit when all fields filled');
  it('should disable submit when name is empty');
  it('should disable submit when email is empty');
  it('should show error for invalid email');
  it('should show error for invalid phone');
  it('should submit when form is valid');
  it('should call onCancel when cancel clicked');
});
```

**✅ GOOD - Focused 8 Tests (within 5-10 range):**
```typescript
describe('MyForm', () => {
  // Critical Flow (2)
  it('should render all form fields and buttons');
  it('should submit form with valid data');

  // Error States (3)
  it('should show validation errors for invalid email');
  it('should display error message when submission fails');
  it('should disable submit button when required fields empty');

  // Conditional Rendering (2)
  it('should show loading state during submission');
  it('should populate form fields when editing existing data');

  // Accessibility (1)
  it('should have accessible form labels and buttons');
});
```

**✅ ALSO GOOD - Minimal 5 Tests (for simple components):**
```typescript
describe('SimpleButton', () => {
  // Critical Flow (2)
  it('should render button with correct label');
  it('should call onClick when clicked');

  // Error States (1)
  it('should be disabled when disabled prop is true');

  // Conditional Rendering (1)
  it('should show loading spinner when loading');

  // Accessibility (1)
  it('should have accessible button role and label');
});
```

#### When a Component Needs More Than 10 Tests

If a component is complex enough to need more than 10 tests, **it's a sign the component should be split**:

```typescript
// Instead of 20 tests for one large component:
describe('ComplexDashboard', () => {
  // 20 tests...
});

// Split into smaller components with focused tests:
describe('DashboardHeader', () => {
  // 5 tests
});

describe('DashboardFilters', () => {
  // 5 tests
});

describe('DashboardDataGrid', () => {
  // 7 tests
});

describe('DashboardActions', () => {
  // 3 tests
});
```

**5-10 Tests Rule Enforcement:**
- [ ] Total test count is between 5-10 per component
- [ ] Minimum 5 tests for adequate coverage
- [ ] Maximum 10 tests to maintain quality focus
- [ ] Each test covers unique, valuable behavior
- [ ] Tests prioritize critical user flows and error states
- [ ] No redundant or trivial tests included

### Rule 23: Zero act() Warnings - Strictly Enforced

**CRITICAL:** All tests **MUST** have **ZERO** act() warnings. This rule is strictly enforced.

#### What is an act() Warning?

```
Warning: An update to ComponentName inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
  /* fire events that update state */
});
```

#### How to Fix act() Warnings

**Strategy 1: Use userEvent with async/await**
```typescript
// ❌ BAD: Not awaiting user interactions
const user = userEvent.setup();
user.click(button); // Missing await
expect(screen.getByText('Updated')).toBeInTheDocument();

// ✅ GOOD: Await userEvent interactions
const user = userEvent.setup();
await user.click(button);
await waitFor(() => {
  expect(screen.getByText('Updated')).toBeInTheDocument();
});
```

**Strategy 2: Use findBy* queries (preferred for new elements)**
```typescript
// ❌ BAD: Using getBy for async content
const user = userEvent.setup();
await user.click(button);
expect(screen.getByText('Loaded')).toBeInTheDocument(); // May fail if async

// ✅ GOOD: Use findBy* which waits automatically
const user = userEvent.setup();
await user.click(button);
expect(await screen.findByText('Loaded')).toBeInTheDocument();
```

**Strategy 3: Use waitFor for complex interactions (e.g., dropdowns)**
```typescript
// ❌ BAD: Not waiting for dropdown to open
const user = userEvent.setup();
const dropdown = screen.getByText('Select Option');
await user.click(dropdown);
// Dropdown may not be open yet

// ✅ GOOD: Wait for dropdown content to appear
const user = userEvent.setup();
const dropdown = screen.getByText('Select Option');
await user.click(dropdown);
const option = await screen.findByText('Option 1');
await user.click(option);
```

**Note:** Do NOT wrap `userEvent` calls in `act()`. Since userEvent v14+, all interactions are already wrapped in `act()` internally. If you see act() warnings, the cause is typically a missing `await` or async state update that needs `waitFor`/`findBy*`.

**Strategy 4: Mock timers or async operations**
```typescript
// ❌ BAD: Causes act() warning from useEffect
render(<ComponentWithEffect />);

// ✅ GOOD: Wait for effects to complete
render(<ComponentWithEffect />);
await waitFor(() => {
  expect(screen.getByText('Effect completed')).toBeInTheDocument();
});
```

#### Common Causes of act() Warnings

1. **Dropdown/Select interactions** - PatternFly Select/Dropdown components
   - Solution: Use `findBy*` to wait for dropdown options to appear after click

2. **Async state updates** - useEffect, setTimeout, promises
   - Solution: Use `findBy*` or `waitFor`

3. **Form submissions** - Forms that trigger async actions
   - Solution: Use `waitFor` to check for expected outcome

4. **Component cleanup** - Effects running after test completes
   - Solution: Ensure proper cleanup with `waitFor` or mock timers

5. **Missing `await` on userEvent calls**
   - Solution: Always `await` userEvent interactions (e.g., `await user.click()`)

#### Validation Commands

**Check for act() warnings:**
```bash
yarn test -- ComponentName.spec.tsx --no-coverage 2>&1 | grep -i "act()"
```

**Expected result:** No output (zero matches)

#### Enforcement Checklist

Before completing test generation:
- [ ] Run tests and capture full output
- [ ] Check for "not wrapped in act" warnings
- [ ] Fix ALL act() warnings using strategies above
- [ ] Re-run tests to verify zero warnings
- [ ] Tests must pass with ZERO act() warnings

**If ANY act() warnings exist → IMMEDIATELY FIX before completing**

### Rule 24: Never Use expect.anything() - Strictly Enforced

**CRITICAL:** Using `expect.anything()` defeats the purpose of testing. Always use specific, meaningful assertions.

#### Why expect.anything() is Forbidden

- ❌ Provides no value - test passes regardless of actual value
- ❌ Masks bugs - incorrect values will pass
- ❌ Reduces confidence - doesn't validate behavior
- ❌ Makes tests meaningless

#### Examples

```typescript
// ❌ BAD: expect.anything() provides no value
expect(StorageClassDropdown).toHaveBeenCalledWith(
  expect.objectContaining({
    id: 'storageclass-dropdown',
    name: 'storageClass',
  }),
  expect.anything(), // ❌ FORBIDDEN
);

// ✅ GOOD: Specific assertion or omit parameter
expect(StorageClassDropdown).toHaveBeenCalledWith(
  expect.objectContaining({
    id: 'storageclass-dropdown',
    name: 'storageClass',
  }),
  {}, // Specific value
);

// ❌ BAD: expect.anything() in object matching
expect(mockFn).toHaveBeenCalledWith({
  foo: 'bar',
  baz: expect.anything(), // ❌ FORBIDDEN
});

// ✅ GOOD: Specific value or use objectContaining without it
expect(mockFn).toHaveBeenCalledWith(
  expect.objectContaining({
    foo: 'bar',
    // Only test what matters
  }),
);

// ❌ BAD: expect.anything() for return values
const result = someFunction();
expect(result).toBe(expect.anything()); // ❌ FORBIDDEN

// ✅ GOOD: Specific assertion
const result = someFunction();
expect(result).toBe('expected-value');
expect(result).toBeDefined();
expect(result).toHaveProperty('key', 'value');
```

#### When You Think You Need expect.anything()

If you're tempted to use `expect.anything()`, consider these alternatives:

1. **Use `expect.objectContaining()` without the field**
   ```typescript
   // Only test fields that matter
   expect(mockFn).toHaveBeenCalledWith(
     expect.objectContaining({
       importantField: 'value',
       // Omit unimportant fields
     }),
   );
   ```

2. **Use specific type matchers**
   ```typescript
   expect(mockFn).toHaveBeenCalledWith(expect.any(String));
   expect(mockFn).toHaveBeenCalledWith(expect.any(Function));
   expect(mockFn).toHaveBeenCalledWith(expect.any(Object));
   ```

3. **Use custom matchers**
   ```typescript
   expect(mockFn).toHaveBeenCalledWith(
     expect.stringContaining('partial'),
   );
   expect(mockFn).toHaveBeenCalledWith(
     expect.arrayContaining(['item']),
   );
   ```

4. **Don't assert on it at all**
   ```typescript
   // If a parameter doesn't matter, don't test

…(truncated)
