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

@stencil/vitest

v1.8.0

Published

First-class testing utilities for Stencil design systems with Vitest

Downloads

3,346

Readme

@stencil/vitest

First-class testing utilities for Stencil components, powered by Vitest.

Quick Start

1. Install

npm i --save-dev @stencil/vitest vitest

For browser testing, also install:

npm i -D @vitest/browser-playwright
# or
npm i -D @vitest/browser-webdriverio

2. Create vitest.config.ts

import { defineVitestConfig } from '@stencil/vitest/config';
import { playwright } from '@vitest/browser-playwright';

export default defineVitestConfig({
  stencilConfig: './stencil.config.ts',
  test: {
    projects: [
      // Unit tests - node environment for functions / logic
      {
        test: {
          name: 'unit',
          include: ['src/**/*.unit.{ts,tsx}'],
          environment: 'node',
        },
      },
      // Spec tests - via a node DOM of your choice
      {
        test: {
          name: 'spec',
          include: ['src/**/*.spec.{ts,tsx}'],
          environment: 'stencil',
          setupFiles: ['./vitest-setup.ts'],

          // Optional environment options

          // environmentOptions: {
          //   stencil: {
          //     domEnvironment: 'happy-dom' | 'jsdom' | 'mock-doc' (default)
          //                      ^^ Make sure to install relevant packages
          //   },
          // },
        },
      },
      // Browser tests
      {
        test: {
          name: 'browser',
          include: ['src/**/*.test.{ts,tsx}'],
          setupFiles: ['./vitest-setup.ts'],
          browser: {
            enabled: true,
            provider: playwright(),
            headless: true,
            instances: [{ browser: 'chromium' }],
          },
        },
      },
    ],
  },
});

refer to Vitest's documentation for all configuration options

3. Load your components

// vitest-setup.ts

// Load Stencil components.
// Adjust according to your build output of choice *
await import('./dist/test-components/test-components.esm.js');

export {};
// * Bear in mind, you may need `buildDist: true` (in your stencil.config)
// or `--prod` to use an output other than the browser lazy-loader

4. Write Tests

// src/components/my-button/my-button.spec.tsx

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

describe('my-button', () => {
  it('renders with text', async () => {
    const { root, waitForChanges } = await render(<my-button label="Click me" />);
    root.click();
    await waitForChanges();
    await expect(root).toEqualHtml(`
      <my-button class="hydrated">
        <mock:shadow-root>
          <button class="button button--secondary button--small" type="button">
            <slot></slot>
          </button>
        </mock:shadow-root>
        Small
      </my-button>
    `);
  });
});

5. Run tests

// package.json
{
  "scripts": {
    "test": "stencil-test",
    "test:watch": "stencil-test --watch",
    "test:e2e": "stencil-test --project browser",
    "test:spec": "stencil-test --project spec"
  }
}

API

Rendering

render(VNode)

Render a component for testing.

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

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

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

// Update props
root.name = 'Stencil';
await waitForChanges();
// or
await setProps({ name: 'Stencil' });

// Unmount component
unmount();

Available matchers:

// DOM assertions
expect(element).toHaveClass('active');
expect(element).toHaveClasses(['active', 'primary']); // Contains all / partial match
expect(element).toMatchClasses(['active']); // Exact match
expect(element).toHaveAttribute('aria-label', 'Close');
expect(element).toEqualAttribute('type', 'button');
expect(element).toEqualAttributes({ type: 'button', disabled: true });
expect(element).toHaveProperty('value', 'test');
expect(element).toHaveTextContent('Hello World'); // includes shadow DOM text
expect(element).toHaveLightTextContent('Hello World'); // light DOM only
expect(element).toEqualText('Exact text match'); // includes shadow DOM text
expect(element).toEqualLightText('Exact text match'); // light DOM only

// Shadow DOM
expect(element).toHaveShadowRoot();
await expect(element).toEqualHtml('<div>Expected HTML</div>');
await expect(element).toEqualLightHtml('<div>Light DOM only</div>');

Event Testing

Test custom events emitted by your components:

const { root, spyOnEvent, waitForChanges } = await render(<my-button />);

// Spy on events
const clickSpy = spyOnEvent('buttonClick');
const changeSpy = spyOnEvent('valueChange');

// Trigger events
root.click();
await waitForChanges();

// Assert events were emitted
expect(clickSpy).toHaveReceivedEvent();
expect(clickSpy).toHaveReceivedEventTimes(1);
expect(clickSpy).toHaveReceivedEventDetail({ buttonId: 'my-button' });

// Access event data
expect(clickSpy.events).toHaveLength(1);
expect(clickSpy.firstEvent?.detail).toEqual({ buttonId: 'my-button' });
expect(clickSpy.lastEvent?.detail).toEqual({ buttonId: 'my-button' });

Snapshots

The package includes a custom snapshot serializer for Stencil components that properly handles shadow DOM:

import { render, h } from '@stencil/vitest';
...
const { root } = await render(<my-component />);
expect(root).toMatchSnapshot();

Snapshot output example:

<my-component>
  <mock:shadow-root>
    <button class="primary">
      <slot />
    </button>
  </mock:shadow-root>
  Click me
</my-component>

Screenshot Testing

Browser tests can include screenshot comparisons using Vitest's screenshot capabilities:

import { render, h } from '@stencil/vitest';
...
const { root } = await render(<my-button variant="primary">Primary Button</my-button>);
await expect(root).toMatchScreenshot();

Refer to Vitest's screenshot testing documentation for more details.

Utils

serializeHtml(element, options?)

Serializes an HTML element to a string, including shadow DOM content. Useful for debugging or creating custom assertions.

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)
});

prettifyHtml(html)

Formats 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. Only works in real browser environments (not jsdom/happy-dom).

Accepts either an Element or a CSS selector string. When a selector is provided, it polls until the element appears in the DOM.

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

// Wait for a rendered element to be stable / visible
const { root } = await render(<my-component />);
await waitForStable(root);

// Wait for an element using a selector (useful when element isn't in DOM yet)
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. Unlike waitForStable, this works in both real browsers and mock DOM environments (jsdom/happy-dom).

Returns the element if found, or null if timeout is reached.

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

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

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

CLI

The stencil-test CLI wraps both Stencil builds with Vitest testing.

Add to package.json

{
  "scripts": {
    "test": "stencil-test",
    "test:watch": "stencil-test --watch"
  }
}

Usage

# Build once, test once
stencil-test

# Watch mode (rebuilds on component changes, interactive Vitest)
stencil-test --watch

# Watch mode with dev server
stencil-test --watch --serve

# Production build before testing
stencil-test --prod

# Pass arguments to Vitest
stencil-test --watch --coverage

# Test specific files
stencil-test button.spec.ts

# Test specific project
stencil-test --project browser

CLI Options

The stencil-test CLI supports most of Stencil's CLI options and all of Vitest CLI options

Global Variables

The stencil-test CLI exposes global variables that can be accessed in your tests to check which CLI flags were used:

| Global | Type | Description | | ------------------- | --------- | -------------------------------------- | | __STENCIL_PROD__ | boolean | true when --prod flag is passed | | __STENCIL_SERVE__ | boolean | true when --serve flag is passed | | __STENCIL_PORT__ | string | Port number when --port is specified |

if (__STENCIL_PROD__) {
  console.log('Running tests against production build');
}

if (__STENCIL_SERVE__) {
  const baseUrl = `http://localhost:${__STENCIL_PORT__ || '3333'}`;
}

TypeScript Support

Add to your tsconfig.json for type definitions:

{
  "compilerOptions": {
    "types": ["@stencil/vitest/globals"]
  }
}

License

MIT

Contributing

See CONTRIBUTING.md for development setup and guidelines.