Skip to content
This repository has been archived by the owner on Jan 5, 2023. It is now read-only.

HugoDF/alpine-test-utils

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

test npm version

Alpine.js Test Utils

Utilities for testing Alpine.js components.

This library allows you to quickly and easily write tests for Alpine.js applications via Node.js using any testing library.

That means you can use AVA, Tape, Mocha, Jest or whatever other testing library you enjoy using.

This project is not officially affiliated with Alpine.js, it's maintained by community members. For any feedback, questions or issues, please create issues and pull requests or merely upvote or comment on existing issues or pull requests.

Table of Contents

Installation

Prerequisites

  • Node.js version 10, 12 or 14

Install Package

The following recommended installation requires npm. If you are unfamiliar with npm, see the npm docs. Npm comes installed with Node.js since node version 0.8.x, therefore, you likely already have it.

npm install --save-dev alpine-test-utils

You may also use yarn to install.

yarn add --dev alpine-test-utils

Peer Dependencies

IMPORTANT: If you're loading Alpine.js from CDN (using a script tag) you'll need to install alpinejs in order to use alpine-test-utils. It should be the same version as the version of Alpine.js you are loading from CDN. using.

npm install --save-dev alpinejs
# or for Yarn users
yarn add --dev alpinejs

Quick Start, Write your first test

Here's an example to render a simple Alpine.js component using Jest/Jasmine syntax:

import {render} from 'alpine-test-utils';

test('test foo component', () => {
  const componentHtml = `<div x-data="{foo: 'bar'}">
    <span x-text="foo"></span>
  </div>`
  const component = render(componentHtml);
  expect(component.querySelector('span').textContent).toEqual('bar');
});

For more complex use cases, please see USE_CASES.md or for the full API, see the following section.

API

Method Description
render Render & run Alpine.js component markup
load Extract Alpine.js component markup from files
loadSync Synchronous variant of load
waitFor Wait for an assertion to pass
$nextTick Wait for a re-render or async work to happen
setGlobal Override globals using an object
setMutationObserver Set a custom MutationObserver implementation

render

Render Alpine.js Component Markup to JSDOM & initialise Alpine.js.

Parameters:

  • markup - string - the Alpine.js markup to render
  • data - (Optional) object or string - data to use to override contents of x-data

Returns:

  • an AlpineElement - an Element with added Alpine.js $data and $el properties and Alpine Test Utils $nextTick function.

Usage Example: render a component and check text is displayed as per x-data.

test('component renders content of "foo" in span', () => {
  const component = render(`<div x-data="{ foo: 'bar' }">
    <span x-text="foo"></span>
  </div>`);
  expect(component.querySelector('span').textContent).toEqual('bar');
});

For a more advanced example see Clicking a button to toggle visibility.

load/loadSync

Load markup from a file asynchronously using a path.

Note: when a single x-data Alpine.js component is found in the file, it is returned. If multiple components are found, all are returned in an Array.

Parameters:

  • filePath - Path to the HTML/template file to load components from

Returns:

  • in the async case, a Promise<string[]|string> (a Promise that resolves to a string or an array of strings)
  • in the sync case, a string[]|string.

Usage Example: load a PHP template, see the full use-case.

test('my test', async () => {
  const markupAsync = await load(path.join(__dirname, '../fixtures/template.php'));
  const markupSync = loadSync(path.join(__dirname, '../fixtures/template.php'));
});

waitFor

Wait until assertions pass, wrapper for wait-for-expect.

Parameters:

  • callback containing the assertions. "predicate" that has to complete without throwing
  • timeout - Optional, Number - Maximum wait interval, 4500ms by default
  • interval - Optional, Number - Wait interval, 50ms by default

Returns: Promise that resolves/rejects based on whether the assertions eventually pass.

Usage example: for more advanced use-cases see Clicking a button to toggle visibility and Intercepting fetch calls & waiting for re-renders

test('clicking a button to toggle visibility', async () => {
  const component = render(`<div x-data="{ isOpen: false }">
    <button @click="isOpen = !isOpen"></button>
    <span x-show="isOpen"></span>
  </div>`);

  expect(component.querySelector('span').style.display).toEqual('none');
  component.querySelector('button').click();
  await waitFor(() => {
    expect(component.querySelector('span').style.display).toEqual('');
  });
});

$nextTick

Note: prefer waitFor it's more flexible and accurate.

Function to wait until a render/async operation happens.

Parameters: none.

Returns:

  • a Promise that resolves after the next async operation has completed (ie. on the next tick of the event loop)

Note this exported as a global from the Alpine Test Utils module and is attached to components during render, see render.

test('clicking a button to toggle visibility', async () => {
  const component = render(`<div x-data="{ isOpen: false }">
    <button @click="isOpen = !isOpen"></button>
    <span x-show="isOpen"></span>
  </div>`);

  expect(component.querySelector('span').style.display).toEqual('none');
  component.querySelector('button').click();
  await component.$nextTick();
  expect(component.querySelector('span').style.display).toEqual('');
});

setGlobal

Override Node.js global using passed override object.

The implementation is literally Object.assign(global, override).

Parameters:

  • an object with keys to override on the global object

Returns: none.

Usage example: overring global.fetch, see the full use case Intercepting fetch calls & waiting for re-renders.

test('intercepting fetch calls', async () => {
  setGlobal({
    fetch: () =>
      Promise.resolve({
        json: () => Promise.resolve(['data-1', 'data-2'])
      })
  });
});

Roadmap

If you are interested in the future direction of this project, please take a look at the open issues and pull requests. I would love to hear your feedback!

Contributing

Requirements

  • Node 10
  • Yarn 1.x or npm

Setup

  1. Clone the repository
  2. Run yarn or npm install installs all required dependencies.
  3. Run yarn test to run all tests :D.

npm scripts

Equivalent npm run <script> should also work

  • yarn test run tests with ava.
  • yarn build will run JSDoc -> TypeScript typing conversion with jsdoc and tsd-jsdoc, changes to ./types.d.ts shoud be committed.
  • yarn lint will lint all of the files with xo
  • yarn format will run lint with --fix option on all the examples files (and tests).
  • yarn release, run clean, production build and release with np.

About

This package is maintained by Hugo from Code with Hugo and Alpine.js Weekly.

Acknowledgments

Special thanks to:

LICENSE

Code is licensed under the MIT License.