Skip to main content
Version: v4.43

API Reference

Complete reference for the @stencil/vitest testing utilities.

Render Function

render(VNode, options?)

Renders a Stencil component for testing.

import { render, h } from '@stencil/vitest';

const result = await render(<my-component name="World" />);

Options

OptionTypeDefaultDescription
waitForReadybooleantrueWait for component hydration before returning
spyOnSpyOnOptions-Configure spies for methods, props, and lifecycle hooks

waitForReady

By default, render() waits for components to be fully hydrated before returning. It detects when Stencil applies the hydrated flag (class or attribute) to your component, respecting your stencil.config settings.

// Default behavior - waits for hydration
const { root } = await render(<my-component />);

// Skip hydration wait (useful for pre-ready states)
const { root } = await render(<my-component />, { waitForReady: false });

spyOn

Configure spies for component methods, props, and lifecycle hooks. See the Mocking & Spying guide for comprehensive documentation.

const { root, spies } = await render(<my-component />, {
spyOn: {
methods: ['handleClick'],
props: ['variant'],
lifecycle: ['componentDidLoad'],
},
});

expect(spies?.methods.handleClick).toHaveBeenCalled();

Return Values

PropertyTypeDescription
rootHTMLElementThe rendered component element
waitForChanges() => Promise<void>Wait for component re-renders
setProps(props: object) => Promise<void>Update props and wait for changes
unmount() => voidRemove the component from the DOM
spyOnEvent(eventName: string) => EventSpyCreate a spy for custom events
spiesComponentSpies | undefinedSpy objects when using spyOn option

Example

import { render, h } from '@stencil/vitest';

const { root, waitForChanges, setProps, unmount, spyOnEvent } = await render(
<my-component name="World" />
);

// Access the element
expect(root.textContent).toContain('World');

// Update props directly
root.name = 'Stencil';
await waitForChanges();

// Or update props via setProps
await setProps({ name: 'Vitest' });

// Spy on events
const eventSpy = spyOnEvent('myEvent');
root.click();
expect(eventSpy).toHaveReceivedEvent();

// Unmount component
unmount();

Custom Matchers

Class Matchers

toHaveClass(className)

Checks if an element has a specific CSS class:

expect(element).toHaveClass('active');
expect(element).toHaveClass('btn-primary');
expect(element).not.toHaveClass('disabled');

toHaveClasses(classNames)

Checks if an element has all specified CSS classes (partial match):

expect(element).toHaveClasses(['btn', 'btn-primary']);
expect(element).toHaveClasses(['active', 'visible']);

toMatchClasses(classNames)

Checks if an element has exactly the specified CSS classes (exact match):

expect(element).toMatchClasses(['btn', 'btn-primary']);
// Fails if element has additional classes

Attribute Matchers

toHaveAttribute(name, value?)

Checks if an element has a specific attribute, optionally with a specific value:

expect(element).toHaveAttribute('aria-label', 'Close');
expect(element).toHaveAttribute('disabled');
expect(element).not.toHaveAttribute('hidden');

toEqualAttribute(name, value)

Checks if an element has an attribute with an exact value:

expect(element).toEqualAttribute('type', 'button');
expect(element).toEqualAttribute('aria-label', 'Submit form');

toEqualAttributes(attributes)

Checks multiple attributes and their values at once:

expect(element).toEqualAttributes({
type: 'button',
disabled: true,
'aria-label': 'Submit',
});

Property Matchers

toHaveProperty(name, value)

Checks if an element has a specific property value:

expect(element).toHaveProperty('value', 'test');
expect(element).toHaveProperty('checked', true);

Text Content Matchers

toHaveTextContent(text)

Checks if an element contains specific text (includes shadow DOM):

expect(element).toHaveTextContent('Hello World');

toHaveLightTextContent(text)

Checks if an element contains specific text (light DOM only):

expect(element).toHaveLightTextContent('Slotted content');

toEqualText(text)

Checks if an element's text content exactly matches (includes shadow DOM):

expect(element).toEqualText('Exact text match');

toEqualLightText(text)

Checks if an element's text content exactly matches (light DOM only):

expect(element).toEqualLightText('Exact slotted text');

Shadow DOM Matchers

toHaveShadowRoot()

Checks if an element has a shadow root:

expect(element).toHaveShadowRoot();

toEqualHtml(html)

Compares the element's HTML including shadow DOM:

await expect(element).toEqualHtml(`
<my-component>
<mock:shadow-root>
<div class="container">
<slot></slot>
</div>
</mock:shadow-root>
Slotted content
</my-component>
`);

toEqualLightHtml(html)

Compares only the light DOM HTML:

await expect(element).toEqualLightHtml(`
<my-component first="John" last="Doe">
<span>Child content</span>
</my-component>
`);

Event Matchers

These matchers work with EventSpy objects returned by spyOnEvent:

toHaveReceivedEvent()

Checks if an event has been received at least once:

expect(eventSpy).toHaveReceivedEvent();

toHaveReceivedEventTimes(count)

Checks if an event has been received a specific number of times:

expect(eventSpy).toHaveReceivedEventTimes(3);
expect(eventSpy).toHaveReceivedEventTimes(0); // No events

toHaveReceivedEventDetail(detail)

Checks if the last received event has the expected detail:

expect(eventSpy).toHaveReceivedEventDetail({
value: 'test',
timestamp: expect.any(Number),
});

Screenshot Matchers

toMatchScreenshot()

Compares the element's visual appearance (browser tests only):

await expect(element).toMatchScreenshot();

Utility Functions

serializeHtml(element, options?)

Serializes an HTML element to a string, including shadow DOM content.

import { serializeHtml } from '@stencil/vitest';

const html = serializeHtml(element, {
serializeShadowRoot: true, // Include shadow DOM (default: true)
pretty: true, // Prettify output (default: true)
excludeStyles: true, // Exclude <style> tags (default: true)
});

Options:

OptionTypeDefaultDescription
serializeShadowRootbooleantrueInclude shadow DOM content
prettybooleantrueFormat with indentation
excludeStylesbooleantrueRemove <style> tags

prettifyHtml(html)

Formats an HTML string with indentation for readability.

import { prettifyHtml } from '@stencil/vitest';

const formatted = prettifyHtml('<div><span>Hello</span></div>');
// Returns:
// <div>
// <span>
// Hello
// </span>
// </div>

waitForStable(elementOrSelector, timeout?)

Waits for an element to be rendered and visible in the DOM. Browser tests only.

import { waitForStable } from '@stencil/vitest';

// Wait for an element
await waitForStable(element);

// Wait using a selector
await waitForStable('my-component .inner-element');

// Custom timeout (default: 5000ms)
await waitForStable('my-component', 10000);
note

In non-browser environments, waitForStable logs a warning and returns immediately.

waitForExist(selector, timeout?)

Waits for an element matching the selector to exist in the DOM. Works in all environments.

import { waitForExist } from '@stencil/vitest';

// Wait for an element to appear
const element = await waitForExist('my-component .lazy-loaded');

// Custom timeout (default: 5000ms)
const element = await waitForExist('#dynamic-content', 10000);

Returns: The element if found, or null if timeout is reached.

EventSpy API

The spyOnEvent function returns an EventSpy object:

const spy = spyOnEvent('myEvent');

Properties:

PropertyTypeDescription
eventsCustomEvent[]Array of all received events
firstEventCustomEvent | undefinedFirst received event
lastEventCustomEvent | undefinedMost recent event

Re-exports

@stencil/vitest re-exports common Vitest utilities for convenience:

import {
describe,
it,
expect,
beforeEach,
afterEach,
beforeAll,
afterAll,
vi,
h, // Stencil's h function for JSX
} from '@stencil/vitest';