Skip to content

Patternfly interaction / behavioral unit testing agent #5

@dlabaj

Description

@dlabaj

Agent Name

pf-rtl-interact-test

Problem It Solves

This slash command - agent is used to help write unit / behavior tests.

Agent Markdown

`---
description: Generate React Testing Library tests focused on user interactions and snapshots
argument-hint: [component-file-path]

Generate comprehensive React Testing Library tests focused on user interactions and snapshots for the component at: $ARGUMENTS

Your Task

  1. Read and analyze the component file to identify:

    • Interactive elements (buttons, inputs, forms, toggles, etc.)
    • Event handlers (onClick, onKeyDown, onChange, onSubmit, etc.)
    • Props that control behavior
    • State changes triggered by user actions
    • Conditional rendering based on interactions
  2. Generate test file in a __tests__/ directory next to the component

    • File naming: If component is Button.tsx, create __tests__/Button.test.tsx
    • If __tests__/ doesn't exist, create it
  3. Write interaction-focused tests following these patterns:

Test Structure

import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ComponentName } from '../ComponentName';

describe('ComponentName', () => {
  test('descriptive test name', async () => {
    const user = userEvent.setup();
    const mockCallback = jest.fn();

    render(<ComponentName onSomeEvent={mockCallback} />);

    await user.click(screen.getByRole('button', { name: 'Label' }));

    expect(mockCallback).toHaveBeenCalledWith(expectedArgs);
  });
});

Test Categories to Generate

1. Click Interactions

  • Test all clickable elements (buttons, links, toggles)
  • Verify callbacks are invoked with correct arguments
  • Test state changes after clicks
  • Test multiple click sequences

2. Keyboard Interactions

  • Test Enter key on interactive elements
  • Test Escape key for closing/canceling
  • Test Tab navigation
  • Test Arrow keys for navigation/selection
  • Test Space for toggles/checkboxes
  • Verify keyboard callbacks

3. Form Interactions

  • Test typing in input fields
  • Test form submissions
  • Test field validation
  • Test clearing fields
  • Test select/dropdown changes

4. State-Dependent Interactions

  • Test interactions when component is in different states
  • Test disabled state (interactions should not work)
  • Test expanded/collapsed states
  • Test selected/unselected states

5. Accessibility in Interactions

  • Verify ARIA attributes change correctly (aria-expanded, aria-pressed, etc.)
  • Test accessible names remain correct
  • Verify focus management
  • Test screen reader announcements (aria-live)

6. Snapshot Tests

  • Always place snapshot tests at the end of the test file (after all interaction tests)
  • Snapshots serve as regression detection, not primary tests
  • Use const { asFragment } = render(...) pattern
  • Use toMatchSnapshot() with external snapshot files
  • For simple components: Single snapshot with representative props
  • For complex components: Multiple snapshots for different states (default, expanded, disabled, etc.)
  • Use descriptive test names: "matches snapshot" or "renders [state] snapshot"

Code Quality Standards

Always:

  • Use userEvent.setup() at the start of each test
  • Use await with all user events (user.click(), user.type(), etc.)
  • Use screen queries (not destructured queries)
  • Use semantic queries: getByRole, getByLabelText, getByPlaceholderText
  • Create mock functions with jest.fn() for callbacks
  • Use descriptive test names that explain the behavior being tested
  • Group related tests in describe blocks

Avoid:

  • Direct DOM manipulation
  • Testing implementation details
  • Testing props directly (test behavior instead)
  • Synchronous user interactions (always use await)

Example Patterns

Testing a click callback:

test('calls onClick handler when button is clicked', async () => {
  const user = userEvent.setup();
  const onClick = jest.fn();

  render(<Button onClick={onClick}>Click me</Button>);

  await user.click(screen.getByRole('button', { name: 'Click me' }));

  expect(onClick).toHaveBeenCalledTimes(1);
});

Testing keyboard interaction:

test('closes modal when Escape key is pressed', async () => {
  const user = userEvent.setup();
  const onClose = jest.fn();

  render(<Modal isOpen onClose={onClose} />);

  await user.keyboard('{Escape}');

  expect(onClose).toHaveBeenCalled();
});

Testing form input:

test('calls onChange when user types in input', async () => {
  const user = userEvent.setup();
  const onChange = jest.fn();

  render(<Input onChange={onChange} />);

  const input = screen.getByRole('textbox');
  await user.type(input, 'Hello');

  expect(onChange).toHaveBeenCalledTimes(5); // once per character
});

Testing state changes:

test('toggles expanded state when toggle button is clicked', async () => {
  const user = userEvent.setup();

  render(<Accordion />);

  const toggle = screen.getByRole('button', { name: 'Toggle section' });

  expect(toggle).toHaveAttribute('aria-expanded', 'false');

  await user.click(toggle);

  expect(toggle).toHaveAttribute('aria-expanded', 'true');
});

Testing with snapshots (simple component - single snapshot):

// All interaction tests above...

// Snapshot test at the very end
test('matches snapshot', () => {
  const { asFragment } = render(
    <Button variant="primary" onClick={jest.fn()}>
      Click me
    </Button>
  );
  expect(asFragment()).toMatchSnapshot();
});

Testing with snapshots (complex component - multiple states):

// All interaction tests above...

// Multiple snapshots for different states
test('renders default state snapshot', () => {
  const { asFragment } = render(<Accordion>Content</Accordion>);
  expect(asFragment()).toMatchSnapshot();
});

test('renders expanded state snapshot', () => {
  const { asFragment } = render(<Accordion isExpanded>Content</Accordion>);
  expect(asFragment()).toMatchSnapshot();
});

test('renders disabled state snapshot', () => {
  const { asFragment } = render(<Accordion isDisabled>Content</Accordion>);
  expect(asFragment()).toMatchSnapshot();
});

Output Requirements

  1. Create the test file in the appropriate location
  2. Include all necessary imports
  3. Generate 5-15 interaction tests covering the main interactive behaviors
  4. Add 1-3 snapshot tests at the end (simple components: 1 snapshot, complex components: 2-3 snapshots for different states)
  5. Include helpful comments for complex test setups
  6. Ensure tests are independent and can run in any order
  7. Follow existing code style in the project

After generating the tests, provide a summary of:

  • How many tests were created (interaction tests + snapshot tests)
  • What interactions are covered
  • What states are captured in snapshots
  • Any additional tests that might be beneficial to add manually
    `

Testing Notes (Optional)

Use /pf-rtl-interact-test path/to/Component.tsx in any project on your system

Tested on: user feedback repo (patternfly/react-user-feedback#97)

What worked: Creation of unit tests for user feedback for a single component.

What needs improvement: Could add ability to work on multiple components.

Edge cases or limitations:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions