# Testing

Test a primitive's interactions, accessibility attributes, server output, and reading direction.

`@ui-sdk/core/testing` provides helpers to simulate input, check focus and accessibility attributes, and inspect a primitive's state and server output.

Use them to test a primitive directly or check a rendered component. Interaction tests need a DOM, such as jsdom or a browser. Server output and part reads work without one.

## Check a Pattern

If you've rendered a widget that follows a common accessibility pattern, `checkPattern` checks its roles, names, states, references between parts, and keyboard interactions:

```ts
import { checkPattern } from "@ui-sdk/core/testing"

await checkPattern(root, "tabs")
```

`root` is the widget's element, or an element around it. The check throws one error listing the problems it found. Here, removing the tab list's name gives it something to report:

Press Check the Tabs Pattern. Then select Leave the tab list unnamed and run the check again to see the missing-name error.

The supported patterns are `listbox`, `menu`, `menubar`, `tabs`, `radio-group`, `toolbar`, `tree`, `dialog`, `alertdialog`, `disclosure`, `accordion`, `combobox`, `slider`, `switch`, and `checkbox`.

The check uses the widget's keys and restores focus and state where it can. Open a menu or dialog before checking it. A menu check activates an item, so use test actions that are safe to run.

It requires Home and End for listboxes, tabs, and toolbars, a name for every tab list, and `aria-modal="true"` for modal dialogs. Those are deliberately stricter than the optional behavior in parts of the WAI-ARIA Authoring Practices.

## Mount a Primitive

Use `mount` when you want to test the primitive itself. Pass its definition, options, and a little markup. Tags that match slots become parts; their attributes supply the part props.

This test uses the [confirm button](/docs/core/kernel/handle-changes#changes). The first press arms it. The next confirms and disarms it:

```ts
import { expect, test } from "vitest"
import { mount } from "@ui-sdk/core/testing"
import { confirmButton } from "./confirm-button"

test("confirm with a click and Enter", async () => {
  const button = mount(confirmButton, {}, `<root>Delete</root>`)
  try {
    await button.click(button.root)
    expect(button.state.armed).toBe(true)
    expect(button.changes.at(-1)).toMatchObject({
      key: "armed",
      reason: "press",
      source: "mouse",
    })

    await button.keys("Enter")
    expect(button.state.armed).toBe(false)
  } finally {
    button.unmount()
  }
})
```

Press Run the Test to click the confirm button, press Enter, and inspect the resulting state, change reasons, and input sources.

`root` is the first element in the markup. Use `part(slot, key)` to find another part, such as `view.part("item", "pear")`. `state` reads the current state, `api` exposes the primitive's methods, and `changes` records each accepted change.

Always call `unmount` when you're done. It removes the markup, unbinds the parts, and destroys the instance.

## Typing, Hovering and Announcements

Focus an input before calling `type`. Use `keys` for special keys and combinations, including `Tab`, `Shift+Tab`, and `Space`:

```ts
const search = mount(
  searchField,
  {},
  `<root><input aria-label="Search"></input></root>`
)
try {
  search.part("input")!.focus()
  await search.type("pear")
  expect(search.state.query).toBe("pear")

  await search.keys("Escape")
  expect(search.state.query).toBe("")
} finally {
  search.unmount()
}
```

For pointer interactions, `hover(element)` moves the pointer onto a part. `drag(element, { to: target })` drags to another element; `drag(element, { by: { x, y } })` moves by a distance.

Press Run the Tests to type and clear a search, hover a seat, and check the announcement from an empty sign-up form.

`announced` captures the text the primitive sends to its screen reader announcer. Wait for scheduled work with `settle` before checking a delayed announcement:

```ts
await form.click(form.root.querySelector("button")!)
await form.settle()
expect(form.announced).toContain("Enter an email address")
```

`settle` waits for microtasks, a timer turn, and two animation frames. If you need the grouped changes reported by the devtools, read `settled`; `changes` keeps every intermediate change.

## The Server's HTML

`checkServer` renders the supplied parts without a DOM and returns the HTML. It checks that creating the primitive reads no browser globals, clock, or randomness, and that the parts' attributes, public state, and presence read none of those nor options:

```ts
import { checkServer } from "@ui-sdk/core/testing"

const html = checkServer(
  readMore,
  {},
  `<root><trigger>Read more</trigger><panel>More</panel></root>`
)
```

This uses the [read-more primitive](/docs/core/kernel/define-a-primitive#parts-that-come-and-go). The check throws if it finds a dependency that could make a part render differently on the server and client:

Press Check the Server HTML to read the rendered parts. Then select Read the clock in attrs and run it again to see the dependency reported.

The markup follows the same rules as `mount`. Ordinary HTML tags stay as they are. Use `as="form"`, for example, when a slot needs to render a specific tag.

## Both Directions

For widgets with horizontal arrow navigation, `checkDirection` compares left-to-right and right-to-left behavior. It mounts a fresh instance for each check, Tabs into it, and compares focus and state after the arrows:

```ts
import { checkDirection } from "@ui-sdk/core/testing"

await checkDirection(pager, {}, `<root>Page</root>`)
```

Left in right-to-left text should match Right in left-to-right text, and vice versa. The check also fails when Right does nothing in left-to-right text, because it has no movement to compare.

Press Check Both Directions. Then select Ignore the direction to try a pager whose horizontal arrows never swap.

The [pager example](/docs/core/platform#direction) shows how it reads direction from the page.

## Reading Parts

For a focused rendering assertion, create an instance and inspect a part directly. These helpers need no DOM:

```ts
import { partAttrs, partPresent, partState } from "@ui-sdk/core/testing"

const instance = readMore({ id: "more" })
try {
  instance.api.toggle()
  const trigger = instance.part("trigger", {})
  expect(partAttrs(trigger)).toMatchObject({ "aria-expanded": "true" })
  expect(partState(trigger)).toEqual({ open: true })
  expect(partPresent(instance.part("panel", {}))).toBe(true)
} finally {
  instance.destroy()
}
```

`partAttrs` reads HTML attributes, `partState` reads the public state passed to render functions, and `partPresent` tells you whether the part should render.

Select Open it and compare the trigger's attributes and public state with the panel's presence.

## Picking the Platform

Shortcut labels use Command on Apple platforms and Control elsewhere. Set the platform explicitly when testing them, so the machine running the test doesn't change the result:

```ts
import { formatShortcut } from "@ui-sdk/core"
import { setPlatform } from "@ui-sdk/core/testing"

setPlatform("apple")
expect(formatShortcut("mod+b")).toContain("⌘B")

setPlatform("other")
expect(formatShortcut("mod+b")).toContain("Ctrl+B")
```

The override stays in effect for later tests. Set it in your test setup to give each test the platform it expects.

## Testing in a Framework

Render through the framework's testing tools when you need to check your component's markup, props, or integration with surrounding UI. The same approach applies: interact, then read the result. You can also pass its rendered element to `checkPattern`.

Unlike `mount`, framework renderers don't fill gaps in a DOM test environment. For tests that only need a popup to render, a jsdom setup can provide these small stubs:

```ts
HTMLElement.prototype.showPopover ??= function () {}
HTMLElement.prototype.hidePopover ??= function () {}
Element.prototype.getAnimations ??= () => []
document.head.append(
  Object.assign(document.createElement("style"), {
    textContent: "[popover] { display: block !important }",
  })
)
```

These stubs don't exercise native popup or animation behavior. Use a browser for those checks, and for checking the finished component with a keyboard and screen reader.
