# Testing (/docs/testing)



Every component in `@42-central/ui-react` is built on [Ark UI](https://ark-ui.com), so interactive
elements already carry correct ARIA roles, accessible names, and keyboard behavior — the
strongest, most resilient thing an end-to-end test can assert, since it doubles as
accessibility coverage. Reach for that first:

```ts
await page.getByRole('button', { name: 'Save changes' }).click();
await page.getByRole('combobox', { name: 'Project' }).click();
```

Role/name queries fall short in a few real cases though: a list of rows or menu items whose
content repeats or changes, a picker where the display label isn't a safe selector, or
anything you'd otherwise have to find by combining text content with DOM traversal
(`.closest('tr')`) or position (`nth-child&#x60;) — both break the moment copy or layout order
changes. For exactly those cases, every component exposes a **`testId` prop** that renders as
a stable `data-testid` — see the cheatsheet below for where it lands on each component shape.

## The `testId` convention [#the-testid-convention]

`testId` always renders as `data-testid` on the component's real interactive DOM node — never
on a decorative wrapper. Where that lands depends on the component's shape:

| Component shape        | Example                                                                          | Where `testId` lands                                                                  |
| ---------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| Single-root component  | `Button`, `Badge`, a compound part like `Tabs.Trigger`                           | That element itself                                                                   |
| Shell-wrapped control  | `Input`, `NumberInput`, `Select`'s trigger                                       | The real inner control (e.g. the `<input>`), not the styled wrapper `<div>` around it |
| Data-driven array item | `Select`/`MultiSelect`/`Autocomplete`'s `data`, `Menu`'s `data`, `Tabs`'s `data` | An optional `testId` field on each item, defaulted from the item's own `value`        |
| `DataTable` rows/cells | —                                                                                | Auto-derived from the resolved row id and column id — no config needed                |

```tsx
<Button testId="save-button">Save</Button>
```

```tsx
// Lands on the real <input>, not Input's styled wrapper div.
<Input label="Email" testId="email-input" />
```

```tsx
// Each option gets its own data-testid; omit it and one is derived from value.
<Select
  label="Project"
  data={[
    { value: 'libft', label: 'Libft' },
    { value: 'printf', label: 'ft_printf', testId: 'printf-option' },
  ]}
/>
// → data-testid="item-libft" and data-testid="printf-option"
```

Compound components carry the same prop on every part that renders a DOM node — set it on
whichever part your test actually needs to find (`Tabs.Trigger`, `Menu.Item`,
`Popover.Content`, …).

## Ark's own selectors — free, always on [#arks-own-selectors--free-always-on]

Underneath every component, Ark UI's machines already stamp `data-scope` / `data-part` on
every rendered part, and `data-value` on selectable items (options, tabs, menu items) — with
zero code in this kit. These work as a selector today even without setting `testId`:

```ts
// Ark-machine-backed compounds (Select, Tabs, Menu, Combobox, …)
page.locator('[data-part="item"][data-value="printf"]');
page.locator('[data-part="trigger"][data-value="overview"]'); // a Tabs trigger
```

`testId` exists for the cases this doesn't cover well: when `value` isn't a safe/stable
selector on its own, or for components (`DataTable`) that aren't Ark-machine-backed at all and
so don't get this for free.

## `DataTable`: zero-config row/cell/header ids [#datatable-zero-config-rowcellheader-ids]

`DataTable` derives a `data-testid` for every row, cell, and header automatically — from the
row's resolved id (`getRowId`, or react-table's own index-based fallback) and each column's
id. No configuration is required to get a usable, stable selector:

```tsx
<DataTable columns={columns} data={users} />
// → data-testid="row-0", "row-0-cell-email", "header-email", …
```

```tsx
<DataTable columns={columns} data={users} getRowId={(u) => u.id} />
// → data-testid="row-<u.id>", "row-<u.id>-cell-email", …
```

Override the row scheme with `getRowTestId` when you need a different naming convention —
every cell still derives from whatever it returns:

```tsx
<DataTable
  columns={columns}
  data={users}
  getRowId={(u) => u.id}
  getRowTestId={(user, id) => `user-${user.email}-${id}`}
/>
// → data-testid="user-ada@lovelace.dev-1", "user-ada@lovelace.dev-1-cell-email", …
```

## Playwright quickstart [#playwright-quickstart]

Nothing in this repo is Playwright-specific — `testId` is just a `data-testid` attribute, so
any tool that queries the DOM works. These snippets assume `@playwright/test` configured in
your own app, pointed at pages that render 42UI components.

**Picking a `Select` option:**

```ts
import { test, expect } from '@playwright/test';

test('picks a project from the Select', async ({ page }) => {
  await page.goto('/settings');
  await page.getByRole('combobox', { name: 'Project' }).click();
  await page.getByTestId('printf-option').click();
  await expect(page.getByRole('combobox', { name: 'Project' })).toHaveText('ft_printf');
});
```

**Switching a `Tabs` panel:**

```ts
test('switches to the Activity tab', async ({ page }) => {
  await page.goto('/dashboard');
  await page.getByTestId('tab-activity').click();
  await expect(page.getByTestId('tab-activity-panel')).toBeVisible();
});
```

**Clicking a `DataTable` row:**

```ts
test('opens a user from the table', async ({ page }) => {
  await page.goto('/users');
  await page.getByTestId('row-1-cell-email').click();
  await expect(page).toHaveURL(/\/users\/1$/);
});
```

## Internal test suite [#internal-test-suite]

This is also how `@42-central/ui-react`'s own component tests are written — see
[`packages/react/TESTING.md`](https://github.com/42staff/kit/blob/main/packages/react/TESTING.md)
in the repo: role/accessible-name queries by default, `getByTestId` reserved for the same
repeated/data-driven/positional cases described above. Every component's own `## Accessibility`
section (on its individual docs page) documents the exact roles and accessible names it exposes,
if you'd rather assert against those directly instead of `testId`.
