How to Test Astryx Components with Vitest and @testing-library/react
Astryx provides a complete testing stack using Vitest as the test runner and @testing-library/react for component testing, with automatic jsdom environment setup, Jest-DOM matchers, and path aliases configured out of the box.
Testing React components in the Astryx design system follows a streamlined workflow defined by the repository's centralized configuration. The facebook/astryx repository orchestrates multiple UI packages through a single Vitest configuration that handles DOM simulation, StyleX compilation, and testing utilities automatically. This guide walks through the configuration structure, global setup, and practical patterns for writing robust component tests.
Vitest Configuration in Astryx
Astryx uses a project-based Vitest configuration to separate UI and Node.js testing concerns. The vitest.config.ts file in the repository root defines two distinct projects:
- ui – Runs all DOM-dependent tests for
packages/core,packages/lab, andpackages/chartsin ajsdomenvironment with StyleX Babel transformation - node – Runs CLI scripts and build tools without DOM simulation
The UI project matches files with the pattern **/*.test.{ts,tsx,mjs} and applies global setup files automatically. Path aliases ensure imports resolve to source files rather than built distributions, eliminating the need to rebuild packages during test development.
See the complete configuration: [vitest.config.ts](https://github.com/facebook/astryx/blob/main/vitest.config.ts)
Global Test Setup and Matchers
Astryx extends Vitest's capabilities through internal/test-utils/src/setup.ts, which loads automatically via the setupFiles configuration option. This setup file performs three critical functions:
- Jest-DOM matchers – Extends
expectwith DOM-specific assertions liketoBeInTheDocument(),toHaveAttribute(), andtoHaveClass() - Testing Library configuration – Configures
@testing-library/reactto ignore script, style, and live-region nodes that cause flaky text queries - Browser API polyfills – Provides
matchMediaand Popover API implementations for jsdom compatibility
// Simplified excerpt from the global setup
import '@testing-library/jest-dom';
import { configure } from '@testing-library/react';
configure({
// Ignore style/script/live-region nodes for text queries
testIdAttribute: 'data-testid',
});
// Polyfills for jsdom
window.matchMedia = window.matchMedia || function() {
return { matches: false, addEventListener: () => {}, removeEventListener: () => {} };
};
Full source: [internal/test-utils/src/setup.ts](https://github.com/facebook/astryx/blob/main/internal/test-utils/src/setup.ts)
Writing Astryx Component Tests
A typical Astryx component test imports from three sources: Vitest's test primitives, Testing Library's rendering utilities, and the component under test.
Basic Import Pattern
import { describe, it, expect, vi } from 'vitest';
import { render, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Button } from './Button';
Rendering and Accessibility Checks
Test visible output and ARIA semantics using screen queries:
describe('Button – basic rendering', () => {
it('shows the label as visible text', () => {
render(<Button label="Click me" />);
expect(screen.getByRole('button', { name: 'Click me' }))
.toBeInTheDocument();
});
});
Source: packages/core/src/Button/Button.test.tsx#L19-L23
User Interactions with user-event
Asynchronous interactions require userEvent.setup() and proper awaiting:
it('fires onClick when the button is pressed', async () => {
const handleClick = vi.fn();
const user = userEvent.setup();
render(<Button label="Press" onClick={handleClick} />);
await user.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
Source: packages/core/src/Button/Button.test.tsx#L21-L28
Testing Async States and ARIA Attributes
Loading states and disabled states require careful handling of promise resolution:
it('sets aria-busy while async clickAction is pending', async () => {
const user = userEvent.setup();
let resolve: (() => void) | undefined;
const clickAction = vi.fn(
async () => new Promise<void>(r => { resolve = r; })
);
render(<Button label="Save" clickAction={clickAction} />);
await user.click(screen.getByRole('button'));
const btn = screen.getByRole('button');
expect(btn).toHaveAttribute('aria-busy', 'true');
expect(btn).toBeDisabled();
await act(async () => {
resolve?.();
await Promise.resolve();
});
expect(btn).not.toHaveAttribute('aria-busy');
expect(btn).not.toBeDisabled();
});
Source: packages/core/src/Button/Button.test.tsx#L75-L99
Testing Slots and Child Content
The endContent prop and other slots render as children—test their presence and order:
it('renders endContent after label', () => {
render(
<Button
label="Click me"
endContent={<span data-testid="end">3</span>}
/>,
);
const btn = screen.getByRole('button');
expect(btn).toHaveTextContent('Click me');
expect(screen.getByTestId('end')).toBeInTheDocument();
expect(screen.getByTestId('end')).toHaveTextContent('3');
});
Source: packages/core/src/Button/Button.test.tsx#L54-L66
Running Tests in Astryx
The package.json scripts provide three execution modes:
| Command | Behavior |
|---|---|
pnpm test |
Single run with vitest run |
pnpm test:watch |
Interactive watch mode |
pnpm test:coverage |
V8 coverage report generation |
All UI tests execute under the ui project by default, applying the jsdom environment and StyleX transformation automatically.
Scripts source: package.json#L5-L14
Key Configuration Files
vitest.config.ts– Central configuration with project definitions, jsdom environment, and path aliasesinternal/test-utils/src/setup.ts– Global setup with Jest-DOM matchers and Testing Library configurationpackages/core/src/Button/Button.test.tsx– Reference implementation covering rendering, interactions, loading states, and slotsinternal/test-utils/src/README.md– Documentation for the shared test utilities package
Summary
- Astryx uses project-based Vitest configuration to separate UI (
jsdom) and Node.js test environments - Global setup automatically loads Jest-DOM matchers and configures
@testing-library/reactfor reliable queries userEvent.setup()with async/await handles all user interactions properly- Path aliases in Vitest config let you import from source without rebuilding packages
- npm/pnpm scripts provide run, watch, and coverage modes via
vitest.config.tsproject selection
Frequently Asked Questions
What test runner does Astryx use for component testing?
Astryx uses Vitest as the primary test runner, configured with a jsdom environment for DOM-dependent component tests. The setup is defined in vitest.config.ts at the repository root, which declares separate "ui" and "node" projects to handle different runtime requirements.
How does Astryx add Jest-DOM matchers to Vitest?
The internal/test-utils/src/setup.ts file imports @testing-library/jest-dom and extends Vitest's expect automatically. This file loads via the setupFiles array in vitest.config.ts, making matchers like toBeInTheDocument() and toHaveAttribute() available in every test without explicit imports.
Why does Astryx use userEvent.setup() instead of fireEvent?
userEvent.setup() from @testing-library/user-event simulates complete user interactions—including pointer events, keyboard navigation, and focus management—rather than firing isolated DOM events. This produces more realistic behavior and automatically handles the asynchronous timing required by modern React's concurrent features.
How do I test async loading states in Astryx components?
Capture the promise resolver in your mock function, trigger the async action with userEvent, assert the loading state immediately, then use act() from @testing-library/react to flush the resolved promise before asserting the final state. The Button loading test in Button.test.tsx demonstrates this pattern with aria-busy and disabled state verification.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →