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

moonshiner

v1.2.0

Published

High-proof testing

Downloads

275

Readme

Moonshiner

High-proof testing

InstallationWriting testsRunning testsConfiguring testsBrowser testsVisual testsPlugins

Installation

$ npm install --save-dev moonshiner

Writing tests

Write tests by importing and using familiar test methods:

// ./tests/test.js
import { describe, it } from 'moonshiner';

describe('my tests', () => {
  it('passes', () => {
    assert.ok(true)
  });

  it('fails', () => {
    assert.ok(false)
  });
});

Running tests

Run tests by executing your test script with Node:

$ node ./tests/test.js

🚀 Running tests

  my tests
    ✅ passes
    ❌ fails

❌ Failed:

  my tests
    fails
      AssertionError: The expression evaluated to a falsy value:

        assert.ok(false)

🏁 Summary

  ✅ 1 passing
  ❌ 1 failing

Tests can also be run using the moonshiner CLI:

$ npx moonshiner --require ./tests/test.js

Configuring tests

Tests can be configured by providing an options argument to tests, suites, or hooks:

// describe
function describe(name: string, options?: TestOptions, fn?: TestSuiteFn): void;
function describe(name: string, fn: TestSuiteFn, options?: TestOptions): void;

// test, it
function test(name: string, options?: TestOptions, fn?: TestFn): void;
function test(name: string, fn: TestFn, options?: TestOptions): void;

// before, beforeEach, after, afterEach
function hook(options: TestOptions, fn: TestFn): void;
function hook(fn: TestFn, options?: TestOptions): void;

// shared options
type TestOptions = {
  timeout?: number,
  skip?: boolean,
  only?: boolean
};

Tests can also be configured with specific test methods:

test.only('isolated test', () => {/* ... */});
test.skip('skipped test', () => {/* ... */});

test('test timeout', t => {
  // will reset the active timeout
  t.timeout(10_000);
  // ...
});

The test root, which all other tests decend from, can be configured by importing and using the configure() method:

import { configure } from 'moonshiner';

configure({
  timeout: 10_000,
  // automatically require these test files
  require: './tests/**/*.test.js'
});

The test root can also be configured by providing flags to the moonshiner CLI:

$ npx moonshiner --timeout 10000 --require ./tests/**/*.test.js

Moonshiner's CLI will also load the first config file found matching the following conditions:

  • is named moonshiner.config.* or test.config.*
  • is located in tests, test, or the current working directory
  • is formatted as .js, .mjs, .cjs, .json, .yml, or .yaml

A config file may also be provided to the CLI using the --config flag, or to the configure() method using the config option.

Reporters

Moonshiner comes with several built-in reporters, and uses the spec and summary reporters by default. Reporters can be specified and configured with the reporter or reporters option.

  • spec - outputs test results in a human-readable format
  • summary - outputs test results as a summary
  • dot - outputs test results in a compact format
  • tap - outputs test results in a TAP format
  • junit - outputs test results in a jUnit XML format (coming soon)

Custom reporters can be defined by extending the base reporter class, or by providing a generator function:

configure({
  reporter: function* myReporter(events) {
    for await (let { type, data } of events) {
      switch (type) {
        case 'test:pass':
          yield `pass: ${data.test.name}\n`;
          break;
        case 'test:fail':
          yield `fail: ${data.test.name}\n`;
          break;
        case 'test:end':
          yield '\n\n';
          yield `passing: ${data.total.passing}\n`;
          yield `failing: ${data.total.failing}\n`;
          break;
      }
    }
  }
});

Browser tests

Moonshiner tests are isomorphic and can run in both Node and Browser environments. Moonshiner can also launch browsers and serve files from Node environments if configured to do so:

browser: chrome # launch a chrome browser
serve: ./       # serve the current working directory

The serve option may also specify virtual files that don't actually exist locally. This can be used to create a virtual index for our browser tests:

browser: chrome
serve:
  - ./
  - /index.html: |
      <!doctype html>
      <html lang="en">
      <body>
        <script type="module" src="/test.js"></script>
      </body>
      </html>

Now when we run Moonshier, it will automatically start a server and launch a headless browser before running any tests. As tests in the browser run, they will report upstream with any Node tests.

Frameworks and bundlers

You can use typical test hooks such as before() and after() to perform setup and teardown respectively. However in most cases, configuration options are often derived during setup and need to be available before calling configure(). This can be done in async modules, either before Moonshiner runs, or after disabling autorun and calling run() directly.

import { configure, after } from 'moonshiner';
import { createServer } from 'vite';

// create a vite server and start listening
const vite = await createServer({ /* ... */ });
await vite.listen();

configure({
  browser: {
    name: 'Chrome',
    // provide the vite url to the browser
    url: vite.resolvedUrls.local[0],
  }
});

// clean up vite after tests run
after(async () => {
  await vite.close();
});
import { configure, run } from 'moonshiner';
import { rollup } from 'rollup';

// disable autorun before bundling, as it might take a while
configure({ autorun: 0 });

// generate a rollup bundle
const bundler = await rollup({ /* ... */ });
const bundle = await bundler.generate({ output: 'esm' });

configure({
  browser: 'Chrome',
  // transform bundle output into a format expected by `serve`
  serve: bundle.output.reduce((files, f) => {
    files[`/${f.fileName}`] = f.code ?? f.source;
    return files;
  }, {})
});

// manually run tests
run();

Visual tests

When running tests in supported browsers, a screenshot() method is made available to test contexts. This method can be used to capture screenshots of the current page using the test name as the screenshot name. If a screenshot already exists and it does not match the new screenshot, the new screenshot is saved beside the existing one and a test error is raised.

By default, screenshots are compared using strict equality of their base64 contents. A custom screenshot compare() option can be configured to compare screenshots using other methods. The example below uses odiff, a pixel differencing tool:

// tests/run.js
import { configure } from 'moonshiner';
import { compare } from 'odiff-bin';

configure({
  browser: 'Chrome',
  screenshots: {
    /** optional path where screenshots will be saved */
    // directory: '__screenshots__',
    /** optional custom suffixes used for new screenshot and diff filenames */
    // suffix: { new: '.new', diff: '.diff' },
    /** optional screenshot comparison function */
    async compare(baseline, comparison, diff) {
      // accepts image paths to compare, producing a diff image if not matching
      let { match } = await compare(baseline, comparison, diff);
      // should return { match: true } if no difference is found
      return { match };
    }
  },
  // ...
});

When comparing screenshots, the compare function will be called with the existing screenshot path and the new screenshot path. This function should return an object with a match property which should be true when screenshots match. The compare function is also called with a third argument, a diff path, which can be used to save a diff image with the other screenshots. Any existing diff image is removed before comparing new screenshots.

Still brewing

Planned features are still coming soon, such as additional reporters, plugins, and more!