# Querying Elements

Find elements using accessible patterns. Inherited from @testing-library/dom.

## Query Types

Three variants for each query method:

- **getBy\***: Throws if not found → Use when element should exist
- **queryBy\***: Returns null if not found → Use for negative assertions
- **findBy\***: Returns Promise → Use for async elements

Plural forms (**getAll\***, **queryAll\***, **findAll\***) return arrays.

## Query Priority (Best to Worst)

1. **getByRole** - Most accessible, encourages proper ARIA
2. **getByLabelText** - For form inputs
3. **getByPlaceholderText** - If no label exists
4. **getByText** - For non-interactive content
5. **getByDisplayValue** - For filled inputs
6. **getByAltText** - For images
7. **getByTitle** - When other options unavailable
8. **getByTestId** - Last resort, not user-visible

## Common Queries

### getByRole (Primary)

**Primary query method.** Finds elements by their ARIA role. Encourages accessible markup and tests behavior users can actually see.

```typescript { .api }
/**
 * Find element by ARIA role
 * @param role - ARIA role (button, heading, textbox, etc.)
 * @param options - Additional constraints
 * @returns Matching HTMLElement
 * @throws When no element or multiple elements found
 */
getByRole(role: string, options?: ByRoleOptions): HTMLElement;

/**
 * Find all elements by ARIA role
 * @returns Array of matching HTMLElements
 * @throws When no elements found
 */
getAllByRole(role: string, options?: ByRoleOptions): HTMLElement[];

interface ByRoleOptions {
  /**
   * Filter by accessible name (label, aria-label, text content)
   */
  name?: string | RegExp;

  /**
   * Filter by accessible description (aria-describedby, title)
   */
  description?: string | RegExp;

  /**
   * Include hidden elements (default: false)
   */
  hidden?: boolean;

  /**
   * Filter by selected state (for options, tabs, etc.)
   */
  selected?: boolean;

  /**
   * Filter by checked state (for checkboxes, radio buttons)
   */
  checked?: boolean;

  /**
   * Filter by pressed state (for toggle buttons)
   */
  pressed?: boolean;

  /**
   * Filter by current state (for navigation items)
   */
  current?: boolean | string;

  /**
   * Filter by expanded state (for accordions, dropdowns)
   */
  expanded?: boolean;

  /**
   * Filter by heading level (for heading role)
   */
  level?: number;

  /**
   * Enable exact matching (default: true)
   */
  exact?: boolean;

  /**
   * Custom text normalizer function
   */
  normalizer?: (text: string) => string;

  /**
   * Enable query fallbacks
   */
  queryFallbacks?: boolean;
}
```

**Common Roles:**
```typescript
screen.getByRole('button');              // <button>, role="button"
screen.getByRole('link');                // <a href>
screen.getByRole('heading', { level: 1 });  // <h1>
screen.getByRole('textbox');             // <input type="text">, <textarea>
screen.getByRole('checkbox');            // <input type="checkbox">
screen.getByRole('radio');               // <input type="radio">
screen.getByRole('combobox');            // <select>
screen.getByRole('img');                 // <img>
screen.getByRole('dialog');              // Modal/dialog
screen.getByRole('alert');               // Alert message
screen.getByRole('navigation');          // <nav>
screen.getByRole('list');                // <ul>, <ol>
screen.getByRole('listitem');            // <li>
```

**Examples:**
```typescript
// By role and name
screen.getByRole('button', { name: /submit/i });

// By state
screen.getByRole('checkbox', { checked: true });
screen.getByRole('button', { pressed: true });

// Heading level
screen.getByRole('heading', { level: 2 });
```

### getByLabelText

Finds form elements by their associated label text. Best for form inputs.

```typescript { .api }
/**
 * Find form element by label text
 * @param text - Label text (string or regex)
 * @param options - Matching options
 * @returns Matching HTMLElement
 */
getByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
getAllByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];

interface SelectorMatcherOptions {
  /**
   * Custom CSS selector to filter results
   */
  selector?: string;

  /**
   * Enable exact matching (default: true)
   */
  exact?: boolean;

  /**
   * Custom text normalizer function
   */
  normalizer?: (text: string) => string;
}
```

**Examples:**
```typescript
// <label htmlFor="email">Email</label><input id="email" />
screen.getByLabelText('Email');

// <input aria-label="Search" />
screen.getByLabelText('Search');

// <label><input /> Remember me</label>
screen.getByLabelText('Remember me');
```

### getByText

Finds elements by their text content. Good for non-interactive text elements.

```typescript { .api }
/**
 * Find element by text content
 * @param text - Text content (string or regex)
 * @param options - Matching options
 * @returns Matching HTMLElement
 */
getByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
getAllByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
```

**Examples:**
```typescript
screen.getByText('Hello World');
screen.getByText(/hello/i);  // Case insensitive
screen.getByText('Hello', { exact: false });  // Partial match
screen.getByText('Submit', { selector: 'button' });  // Filter by tag
```

### getByPlaceholderText

Finds elements by placeholder text. Use as a last resort as placeholders are not accessible replacements for labels.

```typescript { .api }
/**
 * Find element by placeholder text
 * @param text - Placeholder text (string or regex)
 * @param options - Matching options
 * @returns Matching HTMLElement
 */
getByPlaceholderText(text: string | RegExp, options?: MatcherOptions): HTMLElement;
getAllByPlaceholderText(text: string | RegExp, options?: MatcherOptions): HTMLElement[];

interface MatcherOptions {
  /**
   * Enable exact matching (default: true)
   */
  exact?: boolean;

  /**
   * Custom text normalizer function
   */
  normalizer?: (text: string) => string;
}
```

### getByDisplayValue

Finds form elements by their current value. Useful for testing filled forms.

```typescript { .api }
/**
 * Find form element by current value
 * @param value - Current display value (string or regex)
 * @param options - Matching options
 * @returns Matching HTMLElement
 */
getByDisplayValue(value: string | RegExp, options?: MatcherOptions): HTMLElement;
getAllByDisplayValue(value: string | RegExp, options?: MatcherOptions): HTMLElement[];
```

### getByAltText

Finds images and other elements by their alt text.

```typescript { .api }
/**
 * Find element by alt text
 * @param text - Alt text (string or regex)
 * @param options - Matching options
 * @returns Matching HTMLElement
 */
getByAltText(text: string | RegExp, options?: MatcherOptions): HTMLElement;
getAllByAltText(text: string | RegExp, options?: MatcherOptions): HTMLElement[];
```

### getByTitle

Finds elements by their title attribute.

```typescript { .api }
/**
 * Find element by title attribute
 * @param title - Title text (string or regex)
 * @param options - Matching options
 * @returns Matching HTMLElement
 */
getByTitle(title: string | RegExp, options?: MatcherOptions): HTMLElement;
getAllByTitle(title: string | RegExp, options?: MatcherOptions): HTMLElement[];
```

### getByTestId

Finds elements by data-testid attribute. Use as a last resort when other queries are not applicable.

```typescript { .api }
/**
 * Find element by test ID
 * @param testId - Test ID value (string or regex)
 * @param options - Matching options
 * @returns Matching HTMLElement
 */
getByTestId(testId: string | RegExp, options?: MatcherOptions): HTMLElement;
getAllByTestId(testId: string | RegExp, options?: MatcherOptions): HTMLElement[];
```

## Query Variants

### queryBy* Queries

Non-throwing variants that return `null` when element is not found. Use for asserting elements don't exist.

```typescript { .api }
queryByRole(role: string, options?: ByRoleOptions): HTMLElement | null;
queryAllByRole(role: string, options?: ByRoleOptions): HTMLElement[];

queryByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
queryAllByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];

queryByPlaceholderText(text: string | RegExp, options?: MatcherOptions): HTMLElement | null;
queryAllByPlaceholderText(text: string | RegExp, options?: MatcherOptions): HTMLElement[];

queryByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
queryAllByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];

queryByDisplayValue(value: string | RegExp, options?: MatcherOptions): HTMLElement | null;
queryAllByDisplayValue(value: string | RegExp, options?: MatcherOptions): HTMLElement[];

queryByAltText(text: string | RegExp, options?: MatcherOptions): HTMLElement | null;
queryAllByAltText(text: string | RegExp, options?: MatcherOptions): HTMLElement[];

queryByTitle(title: string | RegExp, options?: MatcherOptions): HTMLElement | null;
queryAllByTitle(title: string | RegExp, options?: MatcherOptions): HTMLElement[];

queryByTestId(testId: string | RegExp, options?: MatcherOptions): HTMLElement | null;
queryAllByTestId(testId: string | RegExp, options?: MatcherOptions): HTMLElement[];
```

### findBy* Queries

Async queries that return a promise resolving when element is found. Use for elements that appear asynchronously.

```typescript { .api }
findByRole(role: string, options?: ByRoleOptions): Promise<HTMLElement>;
findAllByRole(role: string, options?: ByRoleOptions): Promise<HTMLElement[]>;

findByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): Promise<HTMLElement>;
findAllByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): Promise<HTMLElement[]>;

findByPlaceholderText(text: string | RegExp, options?: MatcherOptions): Promise<HTMLElement>;
findAllByPlaceholderText(text: string | RegExp, options?: MatcherOptions): Promise<HTMLElement[]>;

findByText(text: string | RegExp, options?: SelectorMatcherOptions): Promise<HTMLElement>;
findAllByText(text: string | RegExp, options?: SelectorMatcherOptions): Promise<HTMLElement[]>;

findByDisplayValue(value: string | RegExp, options?: MatcherOptions): Promise<HTMLElement>;
findAllByDisplayValue(value: string | RegExp, options?: MatcherOptions): Promise<HTMLElement[]>;

findByAltText(text: string | RegExp, options?: MatcherOptions): Promise<HTMLElement>;
findAllByAltText(text: string | RegExp, options?: MatcherOptions): Promise<HTMLElement[]>;

findByTitle(title: string | RegExp, options?: MatcherOptions): Promise<HTMLElement>;
findAllByTitle(title: string | RegExp, options?: MatcherOptions): Promise<HTMLElement[]>;

findByTestId(testId: string | RegExp, options?: MatcherOptions): Promise<HTMLElement>;
findAllByTestId(testId: string | RegExp, options?: MatcherOptions): Promise<HTMLElement[]>;
```

## Utility Functions

### screen Object

Pre-bound queries to `document.body` for convenient access.

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

const screen: {
  // All query functions
  getByRole: (role: string, options?: ByRoleOptions) => HTMLElement;
  // ... all other queries

  // Debug utility
  debug: (element?: HTMLElement, maxLength?: number) => void;

  // Log roles utility
  logTestingPlaygroundURL: (element?: HTMLElement) => void;
};
```

### within Function

Get queries bound to a specific element for scoped searches.

```typescript { .api }
/**
 * Get queries scoped to a specific element
 * @param element - Element to scope queries to
 * @returns Object with all query functions bound to element
 */
function within(element: HTMLElement): {
  getByRole: (role: string, options?: ByRoleOptions) => HTMLElement;
  // ... all other query functions
};
```

## Query Patterns

### Synchronous Queries
```typescript
// Element exists
const button = screen.getByRole('button');

// Element may not exist
const dialog = screen.queryByRole('dialog');
if (dialog) {
  // Handle dialog
}

// Negative assertion
expect(screen.queryByText('Error')).not.toBeInTheDocument();

// Multiple elements
const items = screen.getAllByRole('listitem');
expect(items).toHaveLength(5);
```

### Async Queries
```typescript
// Wait for element to appear
const message = await screen.findByText('Loaded');

// Wait for multiple
const buttons = await screen.findAllByRole('button');

// With custom timeout (default: 1000ms)
const slow = await screen.findByText('Slow', {}, { timeout: 3000 });
```

### Scoped Queries
```typescript
// Query within specific element
const modal = screen.getByRole('dialog');
const closeButton = within(modal).getByRole('button', { name: /close/i });

// Multiple scopes
const nav = screen.getByRole('navigation');
const homeLink = within(nav).getByRole('link', { name: /home/i });
```

## Query Access

```typescript
// 1. screen object (recommended)
import { screen } from '@testing-library/react';
screen.getByRole('button');

// 2. render result
const { getByRole } = render(<Component />);
getByRole('button');

// 3. within for scoped queries
import { within } from '@testing-library/react';
within(container).getByRole('button');
```

## Testing Patterns

### Form Testing
```typescript
test('form submission', () => {
  render(<LoginForm />);

  const emailInput = screen.getByLabelText(/email/i);
  const passwordInput = screen.getByLabelText(/password/i);
  const submitButton = screen.getByRole('button', { name: /log in/i });

  fireEvent.change(emailInput, { target: { value: 'user@example.com' } });
  fireEvent.change(passwordInput, { target: { value: 'password123' } });
  fireEvent.click(submitButton);
});
```

### List Testing
```typescript
test('displays items', () => {
  render(<TodoList items={['Task 1', 'Task 2']} />);

  const items = screen.getAllByRole('listitem');
  expect(items).toHaveLength(2);
  expect(items[0]).toHaveTextContent('Task 1');
});
```

### Modal Testing
```typescript
test('modal interactions', async () => {
  render(<App />);

  // Open modal
  fireEvent.click(screen.getByRole('button', { name: /open/i }));

  // Wait for modal
  const modal = await screen.findByRole('dialog');
  expect(modal).toBeInTheDocument();

  // Query within modal
  const title = within(modal).getByRole('heading');
  expect(title).toHaveTextContent('Modal Title');
});
```

### Conditional Rendering
```typescript
test('shows error on failure', async () => {
  render(<Component />);

  // Initially no error
  expect(screen.queryByRole('alert')).not.toBeInTheDocument();

  // Trigger error
  fireEvent.click(screen.getByRole('button'));

  // Error appears
  const alert = await screen.findByRole('alert');
  expect(alert).toHaveTextContent('Error occurred');
});
```

## Debug Helpers

```typescript
// Print DOM
screen.debug();
screen.debug(screen.getByRole('button'));

// Print available roles
import { logRoles } from '@testing-library/react';
logRoles(container);

// Get suggested queries
// Error messages suggest better queries when getBy* fails
```

## Best Practices

1. **Prefer getByRole** - Most accessible and resilient to changes
2. **Use regex for flexibility** - `/submit/i` instead of "Submit Form"
3. **Avoid testid** - Use semantic queries unless absolutely necessary
4. **Use within()** - Scope queries to avoid ambiguity
5. **Async with findBy** - Don't use getBy in setTimeout, use findBy
6. **queryBy for absence** - Only use queryBy for negative assertions
