npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ariakit/test

v0.8.0

Published

Ariakit test utils

Downloads

141,464

Readme

Ariakit Test

Important: This package is experimental and does not follow semantic versioning, meaning breaking changes may occur in patch and minor versions.

Utilities for simulating user interactions in Ariakit's unit and end-to-end tests.

Contents

Installation

npm i @ariakit/test

Usage

Import helpers from the package root to simulate user interactions:

import { click, press, type } from "@ariakit/test";

The @ariakit/test/react entry point renders React components for testing, and the @ariakit/test/playwright entry point provides query helpers for Playwright tests.

Test environment

The helpers expect a DOM that implements PointerEvent, which means happy-dom, jsdom v27 or later, or a real browser.

They still run on jsdom 26, which jest-environment-jsdom 30 depends on. Every event the helpers fire there carries the same mouse, modifier, and pointer members it carries anywhere else, so a listener reads the same clientX, button, pointerId, and modifier state it would in a browser. click, auxclick, and contextmenu are built from MouseEvent there rather than PointerEvent, which also keeps the members a browser computes.

The pointer* events are the ones that environment cannot build from an interface of their own, so they come from Event and are not instanceof MouseEvent. The dispatch layer still derives pageX, pageY, and which, but leaves the layout-dependent offsetX and offsetY undefined. The PointerEvent global is absent altogether, so both new PointerEvent() and event instanceof PointerEvent throw a ReferenceError.

API reference

blur

function blur(element?: DirtiableElement | null): Promise<void>;

Removes focus from an element, simulating a real user moving focus away from it. When no element is passed, the currently focused element (document.activeElement) is used. If typing changed the element's value since it gained focus, a change event is dispatched before it's blurred.

Example:

await type("hello", q.textbox());
// Dispatches the pending `change` event, then blurs the textbox.
await blur();

click

function click(
  element: Element | null,
  options?: PointerEventInit,
  tap = false,
): Promise<void>;

Clicks on an element, simulating the sequence of events a real mouse click produces — hovering the target, then pointerdown, mousedown, focus, pointerup, mouseup, and click.

Hidden and disabled elements are handled the same way a browser would, and clicks on labels, option elements, and form controls behave like native interactions. Pass options to set event properties such as modifier keys (e.g. { shiftKey: true }).

Pass button to click with another mouse button. Activation behavior runs on click, so a non-primary button fires auxclick instead and doesn't activate labels or option elements, and the secondary button also fires contextmenu while it's held down. Each step derives buttons from that button, so an explicit buttons is ignored here; mouseDown and mouseUp accept one to describe a chorded gesture.

Example:

await click(q.button("Submit"));
// With a modifier key held down:
await click(q.option("Item"), { shiftKey: true });
// With the middle mouse button, firing `auxclick`:
await click(q.link("Ariakit"), { button: 1 });

dispatch

type Target = Document | Window | Node | Element | null;

type EventFunction = (element: Target, options?: object) => Promise<boolean>;

type DispatchEventType = Exclude<EventType, "doubleClick"> | "auxClick";

type EventsObject = {
  [K in DispatchEventType]: EventFunction;
};

const dispatch: typeof baseDispatch & EventsObject;

Creates and fires a DOM event on an element, then waits for the resulting microtasks to flush. Call dispatch.<eventName>(element, options) to build and fire a specific event (e.g. dispatch.keyDown, dispatch.click, dispatch.input), or call dispatch(element, event) directly with an Event instance.

Unlike higher-level helpers such as click and type, this fires a single event without simulating the surrounding interaction sequence. Pointer and mouse events fired on an element with pointer-events: none are re-dispatched on the nearest ancestor that has pointer events enabled, matching how browsers route those events.

A pointer event built by name reports the contact size and transducer angle browsers report for a device with neither, so width and height are 1 and altitudeAngle is a right angle. Supplying only the tilt or spherical angle pair derives the other pair. The members describing a gesture, such as pressure and isPrimary, keep their defaults here; the higher-level helpers fill those in. An event you construct yourself keeps whatever its constructor gave it.

Mouse and pointer events built by name derive pageX and pageY from the client coordinates and target window scroll, and derive which from button. The layout-dependent offsetX and offsetY keep the environment's values.

click, auxclick, and contextmenu are built as PointerEvent, the way browsers dispatch them, so they accept and report pointer properties such as pointerType. An environment with no PointerEvent builds them as MouseEvent instead, and they report the same properties there.

Returns: A promise that resolves to false when the event's default action was prevented with event.preventDefault(), and true otherwise.

Example:

await dispatch.keyDown(q.textbox(), { key: "Enter" });
await dispatch.click(q.button());
await dispatch.auxClick(q.link("Ariakit"), { button: 1 });
// Fire a custom event instance directly:
await dispatch(q.textbox(), new Event("selectstart", { bubbles: true }));

focus

function focus(element: Element | null): Promise<void>;

Moves focus to an element, simulating a real user focusing it. Elements that aren't focusable are ignored, and focusing the already focused element is a no-op. If typing changed another element's value since it gained focus, its pending change event is dispatched before focus moves.

Example:

await focus(q.textbox());
expect(q.textbox()).toHaveFocus();

hover

function hover(
  element: Element | null,
  options?: PointerEventInit,
): Promise<void>;

Moves the pointer over an element, simulating a real user hovering it. Fires the relevant pointer/mouse enter, over, and move events, and dispatches the matching leave events on the previously hovered element.

Hidden elements and elements with pointer-events: none are handled the way a browser would. Pass options to set event properties such as modifier keys. The pointer events report pressure: 0, or 0.5 when you pass buttons to describe a move with a button held down, as during a drag.

Example:

await hover(q.button("More options"));
expect(q.menu()).toBeVisible();

mouseDown

function mouseDown(
  element: Element | null,
  options?: PointerEventInit,
): Promise<void>;

Presses a pointer button down on an element, firing pointerdown and mousedown and moving focus the way a browser would. Disabled elements still receive the pointer event but not mousedown, and focus falls back to the closest focusable ancestor when the target itself isn't focusable.

This is one step of a full click; use it directly to test press-and-hold behavior. Pass options to set event properties such as modifier keys, or button to press another mouse button. The events report the pressed button in buttons, like a browser does, unless you pass buttons yourself to describe a chorded gesture. When that value shows another button was already held down, the press fires pointermove instead of pointerdown, the way Pointer Events routes a chorded press, and the compatibility mousedown still fires. The pointer event reports pressure: 0.5, the value Pointer Events defines while a device with no pressure sensor holds a button down.

Example:

await mouseDown(q.button("Resize"));
// ...assert the press state, then release:
await mouseUp(q.button("Resize"));

mouseUp

function mouseUp(
  element: Element | null,
  options?: PointerEventInit,
): Promise<void>;

Releases a pointer button on an element, firing pointerup and mouseup. Disabled elements still receive the pointer event but not mouseup.

This is the counterpart to mouseDown and one step of a full click. Pass options to set event properties such as modifier keys, or button to release another mouse button. The events report no button still held down in buttons, like a browser does, unless you pass buttons yourself to describe the buttons a chorded gesture keeps held. When that value shows another button stays held down, the release fires pointermove instead of pointerup, the way Pointer Events routes a chorded release, and the compatibility mouseup still fires. The pointer event reports pressure: 0, or 0.5 while a chorded gesture keeps a button held.

Example:

await mouseDown(q.button("Resize"));
await mouseUp(q.button("Resize"));

press

function press(
  key: string,
  element?: Element | null,
  options: KeyboardEventInit = {},
): Promise<void>;

Presses a key on an element, simulating a real user keyboard interaction. Fires keydown and keyup and applies the browser's default behavior for that key — moving focus with Tab, activating buttons and submitting forms with Enter, clicking buttons, checkboxes, and radios with Space, moving the caret with the arrow and Home/End keys, and typing printable characters into text fields.

When no element is passed, the currently focused element is used. Shortcuts such as press.Enter() and press.Tab() are provided for common keys, and press.ShiftTab() moves focus backwards.

Use press.down and press.up to fire only the keydown or keyup half of a press. Each defaults to the currently focused element, so a key released after focus moved away — for example, an element that disables itself on keydown — lands where a real browser would deliver it.

Example:

await press.Tab();
await press.Enter();
// `press.Enter(element)` is shorthand for `press("Enter", element)`:
await press.Enter(q.button("Submit"));
// Split a press so the keyup lands on whatever is focused at release time:
await press.down.Space();
await press.up.Space();

query

type Query = ReturnType<typeof createRoleQuery>;

type TextQuery = ReturnType<typeof createTextQuery>;

type RoleQueries = Record<AriaRole, Query>;

interface QueryObject extends RoleQueries {
  text: TextQuery;
  labeled: TextQuery;
  within: (element?: HTMLElement | null) => QueryObject;
}

const query: QueryObject;

Queries the DOM by ARIA role, accessible name, text, or label, built on top of Testing Library. Call a role method such as query.button(name) or query.dialog() to get the matching element, passing a string or RegExp to match its accessible name. Queries throw when no matching element is found. Use query.text() and query.labeled() to query by text content or associated label, and query.within(element) to scope queries to a subtree.

Every query also exposes .lazy (return a reusable function that runs the query when called), .all (return all matches, including an empty array), .wait (resolve once the element appears), and .maybe (return null when it's missing) variants. Role queries additionally expose .hidden to include otherwise-hidden elements.

Example:

const dialog = query.dialog.maybe.lazy("Settings");
expect(dialog()).not.toBeInTheDocument();
await click(query.button("Open settings"));
expect(dialog()).toBeVisible();
// Wait for an element to appear, or scope a query to a subtree:
await query.alert.wait();
query.within(dialog()).button("Close");

q

type Query = ReturnType<typeof createRoleQuery>;

type TextQuery = ReturnType<typeof createTextQuery>;

type RoleQueries = Record<AriaRole, Query>;

interface QueryObject extends RoleQueries {
  text: TextQuery;
  labeled: TextQuery;
  within: (element?: HTMLElement | null) => QueryObject;
}

const q: QueryObject;

Short alias for query. Queries the DOM by ARIA role, accessible name, text, or label.

Example:

const dialog = q.dialog.maybe.lazy("Settings");
expect(dialog()).not.toBeInTheDocument();
await click(q.button("Open settings"));
expect(dialog()).toBeVisible();

rightClick

function rightClick(
  element: Element | null,
  options?: PointerEventInit,
): Promise<void>;

Right-clicks on an element, simulating the sequence of events a real secondary mouse click produces — hovering the target, then right-button pointerdown, mousedown, focus, contextmenu, pointerup, mouseup, and auxclick.

Hidden elements are handled the same way a browser would, and no synthetic click event is fired. Pass options to set event properties such as modifier keys.

Example:

await rightClick(q.text("Open menu"));

select

function select(
  text: string,
  element: Element | null = document.body,
  options?: PointerEventInit,
): Promise<void>;

Selects a range of text within an element, simulating a real user dragging across it. Hovers and presses on the element, finds the given text in its descendant text nodes, sets the document selection to cover it, then releases.

When no element is passed, document.body is used. Pass options to set event properties such as modifier keys. Each step derives buttons from the button it presses, so an explicit buttons is ignored.

Example:

await select("hello world");
expect(document.getSelection()?.toString()).toBe("hello world");

sleep

function sleep(ms = defaultMs): Promise<void>;

Waits for the DOM to settle between simulated interactions by yielding across two animation frames and a short timeout.

The other helpers in this package call it internally, but you can await it directly to let pending updates, transitions, or effects flush before asserting. The default delay is small and environment-dependent; pass ms to override it. Outside a real browser it also drains the host scheduler so concurrent React work that the delay raced past settles before the call resolves.

Example:

await click(q.button("Open"));
await sleep();
expect(q.dialog()).toBeVisible();

tap

function tap(
  element: Element | null,
  options?: PointerEventInit,
): Promise<void>;

Clicks on an element without the brief delay that click waits between pressing and releasing, reproducing the timing of a quick tap. It fires the same pointer, mouse, and click events as click. Pass options to set event properties such as modifier keys.

Example:

await tap(q.button("Submit"));

type

function type(
  text: string,
  element?: (DirtiableElement & HTMLElement) | null,
  options: InputEventInit | KeyboardEventInit = {},
): Promise<void>;

Types text into an element, simulating a real user pressing each key. Focuses the element, then for each character fires keydown, updates the value and caret position of text fields through an input event (preceded by keypress when inserting a printable character), and fires keyup.

Special characters map to their keys: "\b" is Backspace, "\x7f" is Delete, "\n" is Enter, and "\t" is Tab. When no element is passed, the currently focused element is used. Pass options to set event properties such as modifier keys or composition state.

Example:

await type("Hello", q.textbox());
// Delete the last character with a Backspace:
await type("\b");

waitFor

function waitFor<T>(
  callback: () => T,
  options?: DOMTestingLibrary.waitForOptions,
): Promise<T>;

Re-runs a callback until it stops throwing or the timeout is reached, re-exporting Testing Library's waitFor with this package's async batching applied. Use it to wait for an assertion to pass after an asynchronous update. Pass options to configure the timeout, interval, and other behavior.

Example:

await click(q.button("Close"));
await waitFor(() => expect(q.dialog.maybe()).not.toBeInTheDocument());

React API reference

RenderOptions

interface RenderOptions extends Omit<
  ReactTestingLibrary.RenderOptions,
  "queries"
> {
  strictMode?: boolean;
}

Options for the render function. Accepts every option from Testing Library's render (except queries), plus strictMode to wrap the rendered UI in React's StrictMode.

Example:

const options: RenderOptions = { strictMode: true };
await render(<App />, options);

render

function render(
  ui: ReactNode,
  options?: RenderOptions,
): Promise<{
  unmount: () => void;
  rerender: (newUi: ReactNode) => Promise<void>;
}>;

Renders a React element into the document for testing, waiting for effects and the next frame to flush before resolving.

Built on Testing Library's render, it returns unmount to remove the tree and an async rerender to update it with new UI. Pass strictMode: true to wrap the element in React's StrictMode, or any other Testing Library render option.

Example:

const { rerender, unmount } = await render(<Button>Submit</Button>);
await click(q.button("Submit"));
await rerender(<Button>Sent</Button>);
unmount();

Playwright API reference

query

type RoleQuery = (
  name?: string | RegExp,
  options?: Parameters<Page["getByRole"]>[1],
) => Locator;

type TextQuery = (
  name: Parameters<Page["getByText"]>[0],
  options?: Parameters<Page["getByText"]>[1],
) => Locator;

type RoleQueries = Record<AriaRole, RoleQuery>;

type Queries = RoleQueries & { text: TextQuery };

function query(locator: Page | Locator | FrameLocator): Queries;

Creates role- and text-based query helpers for a Playwright Page, Locator, or FrameLocator. Call a role method such as query(page).button(name) to get a Locator from getByRole, or query(page).text(name) to match by text content.

Names are matched exactly by default. This mirrors the role-based query from the package root for end-to-end Playwright tests.

Example:

const { button, dialog } = query(page);
await button("Open").click();
await expect(dialog()).toBeVisible();

Core Team

Contributing

Follow the instructions on the contributing guide.