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 🙏

© 2025 – Pkg Stats / Ryan Hefner

fast-check-frontend

v0.0.3

Published

**Note: This library is currently a work in progress and not yet ready for production use.**

Readme

fast-check-frontend

Note: This library is currently a work in progress and not yet ready for production use.

Property-based testing for UI components. Generate random user interaction sequences and verify your components maintain their invariants.

Overview

Traditional UI tests specify exact user flows: "click button A, type text B, verify outcome C". This approach misses edge cases created by unexpected interaction sequences.

fast-check-frontend takes a different approach inspired by property-based testing:

  1. Define what should always be true about your component (invariants)
  2. Generate hundreds of random interaction sequences (clicks, typing, keyboard shortcuts, etc.)
  3. Execute each sequence and verify all invariants hold
  4. When an invariant breaks, get the exact interaction sequence that caused the failure

The library combines fast-check for property-based testing with Testing Library for realistic user interactions.

Getting Started

Installation

pnpm add -D fast-check-frontend fast-check @testing-library/user-event @testing-library/dom

Basic Example

Here's a test for a signup form that verifies two invariants under random interactions:

import fc from "fast-check";
import { render } from "@testing-library/react";
import { createInteractionProperty } from "fast-check-frontend";
import { SignupForm } from "./SignupForm";

it("maintains invariants under random interactions", async () => {
  await fc.assert(
    createInteractionProperty({
      // Setup: render a fresh component for each test
      setup: () => render(<SignupForm />).container,

      // Invariants: conditions that must always hold
      invariants: [
        // 1. Component should never crash
        (container) => !container.querySelector(".error-boundary"),

        // 2. Only one modal open at a time
        (container) =>
          container.querySelectorAll('[role="dialog"]').length <= 1,
      ],
    })
  );
});

By default, this test will:

  • Generate 100 random interaction sequences (fast-check default)
  • Each sequence contains 1-10 interactions
  • Interactions are weighted: clicks (30%), typing (25%), keyboard actions (10%), etc.
  • Target common elements: buttons, inputs, textareas, select dropdowns
  • Include edge case inputs: empty strings, Unicode, XSS attempts, SQL injection patterns

Running Tests

pnpm test

The library works with any test runner that supports async tests (Vitest, Jest, etc.).

Advanced Use Cases

Random Props Generation

Test components with randomly generated props alongside interaction sequences:

createInteractionProperty({
  // Generate random props for each test run
  propsArbitrary: fc.record({
    maxLength: fc.integer({ min: 10, max: 100 }),
    initialValue: fc.string({ maxLength: 50 }),
    disabled: fc.boolean(),
  }),

  // Setup receives the generated props
  setup: (props) => render(<MyInput {...props} />).container,

  // Invariants can access both container, interactions, and props
  invariants: [
    (container, interactions, props) => {
      const input = container.querySelector('input');
      // Verify component respects the maxLength prop
      return !input || input.value.length <= props.maxLength;
    },
    (container, interactions, props) => {
      // Verify disabled prop prevents edits
      if (props.disabled) {
        const input = container.querySelector('input');
        return input?.hasAttribute('disabled') ?? false;
      }

      return true;
    },
  ],
});

Custom Interaction Weights

Focus testing on specific interactions:

import { clickArbitrary, typeArbitrary } from "fast-check-frontend";

createInteractionProperty({
  setup: () => render(<MyComponent />).container,
  invariants: [/* ... */],
  options: {
    userInteractionArbitrary: () =>
      fc.oneof(
        { weight: 70, arbitrary: clickArbitrary() },  // 70% clicks
        { weight: 30, arbitrary: typeArbitrary() }    // 30% typing
      ),
  },
});

Custom Selectors

Target specific elements in your component:

createInteractionProperty({
  setup: () => render(<MyForm />).container,
  invariants: [/* ... */],
  options: {
    userInteractionArbitrary: () =>
      fc.oneof(
        clickArbitrary({
          selector: fc.constantFrom(
            'button[type="submit"]',
            'button.cancel',
            'input[type="checkbox"]'
          )
        }),
        typeArbitrary({
          selector: fc.constantFrom(
            'input[name="email"]',
            'input[name="password"]'
          )
        })
      ),
  },
});

Constrained Text Input

Control what text gets typed into fields:

typeArbitrary({
  selector: fc.constant('input[name="email"]'),
  text: fc.emailAddress(),  // Only valid email addresses
})

Sequence Length Configuration

Adjust how many interactions to generate per test:

createInteractionProperty({
  setup: () => render(<MyComponent />).container,
  invariants: [/* ... */],
  options: {
    sequenceMinLength: 5,   // At least 5 interactions
    sequenceMaxLength: 20,  // At most 20 interactions
  },
});

Accessing Interaction History

Debug failing tests by examining the exact sequence that broke an invariant:

createInteractionProperty({
  setup: () => render(<MyComponent />).container,
  invariants: [
    (container, interactions) => {
      const isValid = container.querySelector(".success") !== null;

      if (!isValid) {
        // Log the failing sequence for debugging
        console.log("Failed after interactions:", interactions);
      }

      return isValid;
    },
  ],
});

Each interactions object will contain a selectedElement key that will contain a copy of the element targeted by the interaction just before the interaction was executed on it.

Individual Interaction Arbitraries

Use individual interaction types for fine-grained control:

import {
  clickArbitrary,
  typeArbitrary,
  keyboardArbitrary,
  hoverArbitrary,
  selectArbitrary,
  uploadArbitrary,
  clearArbitrary,
  tabArbitrary
} from "fast-check-frontend";

// Each arbitrary can be customized
const customClick = clickArbitrary({
  selector: fc.constant("button"),
  options: fc.constant({ ctrlKey: true }),  // Always Ctrl+Click
});

const customKeyboard = keyboardArbitrary({
  keys: fc.constantFrom("{Enter}", "{Escape}", "{Tab}"),
});

How It Works

  1. Arbitraries: fast-check arbitraries generate random interaction objects (clicks, typing, keyboard actions, etc.)
  2. Execution: The library translates these objects into Testing Library user-event calls
  3. Graceful Handling: If a selector doesn't match or an element doesn't exist, the interaction is skipped (logged but doesn't fail)
  4. Invariant Checking: After each sequence, all invariants are checked. If any return false or throw, the test fails with the full interaction sequence

When to Use This Library

Good fits:

  • Complex components with many interaction paths
  • Forms with intricate validation logic
  • Components with state machines (modals, accordions, tabs)
  • Accessibility compliance (verify ARIA attributes never break)
  • Components where user errors should be handled gracefully

Not ideal for:

  • Simple presentational components with no interactions
  • Testing specific user flows (use regular Testing Library tests)
  • Visual regression testing (use screenshot testing tools)

License

MIT