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 🙏

© 2024 – Pkg Stats / Ryan Hefner

testing-library-alpine

v0.0.1-alpha.2

Published

Vitest Environment for testing AlpineJS

Downloads

15

Readme

Testing Library: Alpine

All the Environment, Helpers, and Utilities needed to test Alpine components! All built for Vitest!

Installation

  1. Install with PNPM
pnpm add testing-library-alpine
  1. Add to vitest.config.ts
import { alpineTestingPlugin } from 'testing-library-alpine';

export default defineConfig({
  plugins: [alpineTestingPlugin()],
});

Runtime Environment

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.

Example Test

it('Works', () => {
  document.body.append(
    `<div x-data="{ hello: 'world' }" x-text="hello"></div>`,
  );
  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.

it('can check Alpine data Context', async () => {
  const el = await render(`<div x-data="testing" x-text=foo></div>`).withData(
    'testing',
    { foo: 'bar' },
  );
  expect(el).toHaveData({ foo: 'bar' }).toHaveTextContent('bar');
});

render: (template: string) => Render

In place of calling the Render constructor, render is exposed to build a Render instance. Just pass in a string of HTML to render.

const el = await render('<div>hello</div>'); // HTMLDivElement;

Render provides both a synchronous and asynchronous API for committing the rendered HTML to the DOM and activating Alpine.

Render.commit (sync)

() => 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.

const el = render('<div>hello</div>').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.

const el = await render('<div>hello</div>'); // HTMLDivElement;

Render.withPlugin

(plugins: PluginCallback | PluginCallback[]) => Render;

Mirrors Alpine.plugin.

Render.withData

<T extends Record<string | symbol, unknown>>(
  name: string,
  component: (...args: unknown[]) => AlpineComponent<T>,
) => Render;

Mirrors Alpine.data.

Render.withComponent

<T extends Record<string | symbol, unknown>>(
  name: string,
  component: (...args: unknown[]) => AlpineComponent<T>,
) => Render;

Mirrors Alpine.data.

Render.withStore

<T extends Record<string | symbol, unknown>>(name: string, store: T) => Render;

Mirrors Alpine.store.

Render.withDirective

(name: string, directive: AlpineDirective) => Render;

Mirrors Alpine.directive.

Render.withMagic

(
  name: string,
  cb: (
    el: ElementWithXAttributes<HTMLElement>,
    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.

Example

it('Works', () => {
  document.body.append(
    `<div x-data="{ hello: 'world' }" x-text="hello"></div>`,
  );
  Alpine.start();
  expect(document.body.firstElementChild).toHaveData({ hello: 'world' });
});

Assertion<T extends HTMLElement>.toHaveData: (expected: Record<string, unknown>) => Assertion<T>

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.

it('can check Alpine data Context', async () => {
  const el = await render(
    "<div x-data=\"{ foo: 'bar', fizz: 'buzz' }\" x-text=foo></div>",
  );
  expect(el)
    .toHaveTextContent('bar')
    .toHaveData({ foo: 'bar' })
    .not.toHaveData({ foo: 'baz' });
});

Assertion<T extends HTMLElement>.toHaveTextContent: (expected: string) => Assertion<T>

Checks if an element has exactly matching text content.

it('can check textContent', async () => {
  const el = await render('<div>hello</div>');
  expect(el).toHaveTextContent('hello').not.toHaveTextContent('goodbye');
});

Note: this trims both the text content of the element, and the expected string.

Assertion<T extends HTMLElement>.toContainTextContent: (expected: string) => Assertion<T>

Checks if an element's text content contains the expected string.

it('can check textContent', async () => {
  const el = await render('<div>hello</div>');
  expect(el).toContainTextContent('ell').not.toContainTextContent('shello');
});

Assertion<T extends HTMLElement>.toMatch: (selector: string) => Assertion<T>

Assertion<T extends HTMLElement>.toHaveAttribute: (expectedKey: string, value?: string) => Assertion<T>

Asserts that an element has an attribute (with optionally provided key).

it('can check if an element has an attribute', async () => {
  const el = await render('<button type="button" disabled ></button>');
  expect(el)
    .toHaveAttribute('disabled')
    .toHaveAttribute('type', 'button')
    .not.toHaveAttribute('contenteditable');
});

Note: Underneath the hood this uses HTMLElement.matches with a constructed CSS Selector.

Assertion<T extends HTMLElement>.toHaveClass: (expected: string) => Assertion<T>

Asserts that an element has the expected class applied.

it('can check if an element has a class', async () => {
  const el = await render('<div class="foo bar"></div>');
  expect(el).toHaveClass('bar').not.toHaveClass('foobar');
});

Assertion<T extends HTMLElement>.toHaveStyle: (expected: Partial<CSSStyleDeclaration>) => Assertion<T>

Asserts an element has a subset of style declarations applied in the style attribute.

it('can check if an element has style', async () => {
  const el = await render('<div style="color: red;"></div>');
  expect(el).toHaveStyle({ color: 'red' }).not.toHaveStyle({ color: 'blue' });
});

Assertion<T extends HTMLElement>.toHaveComputedStyle: (expected: Partial<CSSStyleDeclaration>) => Assertion<T>

Asserts an element has a subset of style declarations from inline css, class names, css selecters, default styles.

it('can check if an element has a computed style', async () => {
  const el = await render('<div style="color: red;"></div>');
  expect(el)
    .toHaveComputedStyle({ display: 'block' })
    .not.toHaveComputedStyle({ display: 'inline' });
});

Assertion<T extends HTMLElement>.toBeVisible: () => Assertion<T>

Asserts an element does not have display: none.

it('can check if an element is visible', async () => {
  const el = await render(
    '<div x-data="{ show: true }"><span x-show="show"></span><span x-show="!show"></span></div>',
  );
  expect(el.children[0]).toBeVisible().not.toBeHidden();
  expect(el.children[1]).toBeHidden().not.toBeVisible();
});

Assertion<T extends HTMLElement>.toBeHidden: () => Assertion<T>

Asserts an element has display: none.

it('can check if an element is visible', async () => {
  const el = await render(
    '<div x-data="{ show: true }"><span x-show="show"></span><span x-show="!show"></span></div>',
  );
  expect(el.children[0]).toBeVisible().not.toBeHidden();
  expect(el.children[1]).toBeHidden().not.toBeVisible();
});

Assertion<T extends HTMLElement>.toHaveNChildren: (expected: number) => Assertion<T>

Asserts an element has the expected number of children.

it('can check if an element has N children', async () => {
  const el = await render('<div><span></span><span></span></div>');
  expect(el).toHaveNChildren(2).not.toHaveNChildren(1).toHaveNChildren(3);
});