Vitest Environment for testing AlpineJS
npm install testing-library-alpineAll the Environment, Helpers, and Utilities needed to test Alpine components! All built for Vitest!
1. Install with PNPM
``sh`
pnpm add testing-library-alpine
2. Add to vitest.config.ts
`ts
import { alpineTestingPlugin } from 'testing-library-alpine';
export default defineConfig({
plugins: [alpineTestingPlugin()],
});
`
Testing Library: Alpine automatically sets up a DOM/Browser environment (via Happy-DOM) and sets up Alpine in that environment.
It also handles registering test cleanup hooks, and spying on component methods.
`ts
it('Works', () => {
document.body.append(
,
);
Alpine.start();
expect(document.body.firstElementChild.textContent).toBe('world');
});
`Render
Also provided is a
Render class that can be used to easily and safely instantiate Alpine components within a test.`ts
it('can check Alpine data Context', async () => {
const el = await render().withData(
'testing',
{ foo: 'bar' },
);
expect(el).toHaveData({ foo: 'bar' }).toHaveTextContent('bar');
});
`$3
In place of calling the
Render constructor, render is exposed to build a Render instance. Just pass in a string of HTML to render.`ts
const el = await render('hello'); // HTMLDivElement;
`Render provides both a synchronous and asynchronous API for committing the rendered HTML to the DOM and activating Alpine.####
Render.commit (sync)`ts
() => HTMLElement;
`commit will synchronously commit the HTML to the DOM and initialize the tree with Alpine, immediately returning the root element of the provided string.`ts
const el = render('hello').commit(); // HTMLDivElement;
`####
await Render / Render.then(cb: (el: HTMLElement) => void): void (async)You can also simply
await your Render instance to render the HTML to the DOM and initialize the tree with Alpine.This internally calls the
commit method and then additionally waits for any asynchronous operations in the Alpine/component lifecycle to complete.`ts
const el = await render('hello'); // HTMLDivElement;
`$3
`ts
(plugins: PluginCallback | PluginCallback[]) => Render;
`Mirrors
Alpine.plugin.$3
`ts
>(
name: string,
component: (...args: unknown[]) => AlpineComponent,
) => Render;
`Mirrors
Alpine.data.$3
`ts
>(
name: string,
component: (...args: unknown[]) => AlpineComponent,
) => Render;
`Mirrors
Alpine.data.$3
`ts
>(name: string, store: T) => Render;
`Mirrors
Alpine.store.$3
`ts
(name: string, directive: AlpineDirective) => Render;
`Mirrors
Alpine.directive.$3
`ts
(
name: string,
cb: (
el: ElementWithXAttributes,
options: MagicUtilities,
) => unknown,
) => Render;
`Mirrors
Alpine.magic.Alpine Assertion
This also introduces a handful of useful assertion methods, to make testing components a bit easier. Some just make testing against the DOM easier (and chainable) and others are more Alpine specific.
$3
`ts
it('Works', () => {
document.body.append(
,
);
Alpine.start();
expect(document.body.firstElementChild).toHaveData({ hello: 'world' });
});
`$3
Checks if the expected object is contained within the context of the provided
HTMLElement.This is subset equality, so the objects do not need to directly match, but the expected record must be contained within the Alpine Context of the
HTMLElement. This also works with deeply nested trees and most common data structures.`ts
it('can check Alpine data Context', async () => {
const el = await render(
"",
);
expect(el)
.toHaveTextContent('bar')
.toHaveData({ foo: 'bar' })
.not.toHaveData({ foo: 'baz' });
});
`$3
Checks if an element has exactly matching text content.
`ts
it('can check textContent', async () => {
const el = await render('hello');
expect(el).toHaveTextContent('hello').not.toHaveTextContent('goodbye');
});
`> Note: this trims both the text content of the element, and the expected string.
$3
Checks if an element's text content contains the expected string.
`ts
it('can check textContent', async () => {
const el = await render('hello');
expect(el).toContainTextContent('ell').not.toContainTextContent('shello');
});
`$3
$3
Asserts that an element has an attribute (with optionally provided key).
`ts
it('can check if an element has an attribute', async () => {
const el = await render('');
expect(el)
.toHaveAttribute('disabled')
.toHaveAttribute('type', 'button')
.not.toHaveAttribute('contenteditable');
});
`> Note: Underneath the hood this uses
HTMLElement.matches with a constructed CSS Selector.$3
Asserts that an element has the expected class applied.
`ts
it('can check if an element has a class', async () => {
const el = await render('');
expect(el).toHaveClass('bar').not.toHaveClass('foobar');
});
`$3
Asserts an element has a subset of style declarations applied in the style attribute.
`ts
it('can check if an element has style', async () => {
const el = await render('');
expect(el).toHaveStyle({ color: 'red' }).not.toHaveStyle({ color: 'blue' });
});
`$3
Asserts an element has a subset of style declarations from inline css, class names, css selecters, default styles.
`ts
it('can check if an element has a computed style', async () => {
const el = await render('');
expect(el)
.toHaveComputedStyle({ display: 'block' })
.not.toHaveComputedStyle({ display: 'inline' });
});
`$3
Asserts an element does not have
display: none.`ts
it('can check if an element is visible', async () => {
const el = await render(
'',
);
expect(el.children[0]).toBeVisible().not.toBeHidden();
expect(el.children[1]).toBeHidden().not.toBeVisible();
});
`$3
Asserts an element has
display: none.`ts
it('can check if an element is visible', async () => {
const el = await render(
'',
);
expect(el.children[0]).toBeVisible().not.toBeHidden();
expect(el.children[1]).toBeHidden().not.toBeVisible();
});
`$3
Asserts an element has the expected number of children.
`ts
it('can check if an element has N children', async () => {
const el = await render('');
expect(el).toHaveNChildren(2).not.toHaveNChildren(1).toHaveNChildren(3);
});
``