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

@vttforge/testing

v0.14.2

Published

Foundry mocks for Vitest and Quench helpers, for testing VTTForge systems and modules.

Downloads

3,628

Readme

@vttforge/testing

Helpers for testing Foundry VTT packages.

Three entry points, one for each place a test can run.

@vttforge/testing/vitest

Runs in CI against mocked globals. Covers everything up to the moment a window renders: data models, settings, hook registration, migrations, document updates.

import { createMockActor, withMockFoundry } from '@vttforge/testing/vitest';

const foundry = withMockFoundry();
registerMyModule();

foundry.callHook('init');
expect(foundry.settings[0].key).toBe('cacheSize');

foundry.restore();

withMockFoundry installs foundry, game, CONFIG, Hooks, ui and CONST, and hands back a handle that records what your code registered: hooks, settings, settings menus, keybindings, sheets, enrichers and notifications. restore() puts every global back, including deleting the ones that never existed.

Registered settings also land in game.settings.settings, the registry Foundry keys by namespace.key. That map is the only way to reach a setting belonging to a package you did not write, so a module that enumerates, reports on or copies what a world holds reads it:

const foundry = withMockFoundry();
game.settings.register('some-system', 'schemaVersion', {
  scope: 'world',
  config: false,
  type: String,
  default: '1.0.0',
});

const entry = game.settings.settings.get('some-system.schemaVersion');
expect(entry?.scope).toBe('world');

Registration normalises as it stores, the way Foundry does: an unknown scope falls back to client, and a missing default becomes null. Menus land in game.settings.menus the same way.

Anything else your code reads goes in globals. Foundry puts every document class on the global scope, and the fixed set above does not include them:

const foundry = withMockFoundry({
  globals: { JournalEntry: { create: vi.fn() } },
});

restore() clears those too.

The mock documents behave like real ones where it counts: update merges rather than replacing, and dotted paths expand. A mock that replaces lets a test pass while the real thing drops every sibling key.

Naming the globals

A test that reads game.settings would otherwise get "Cannot find name 'game'". Importing from this entry declares them, so there is nothing to configure.

@vttforge/testing/quench

Runs inside a live world, for what a mock cannot answer: a sheet that really draws, a socket with two clients, a document that round-trips through the database.

import { registerBatch } from '@vttforge/testing/quench';

registerBatch('my-module.sheets', ({ describe, it, assert }) => {
  describe('character sheet', () => {
    it('renders', async () => {
      const actor = await Actor.create({ name: 'T', type: 'character' });
      await actor.sheet.render(true);
      assert.ok(actor.sheet.rendered);
      await actor.delete();
    });
  });
});

Safe to call at module scope: it waits for quenchReady. Code that assumes Quench has already loaded registers a batch that never appears. Outside Foundry it is a no-op, so a file holding both kinds of test can still be imported by the vitest run.

@vttforge/testing/container

Boots a real Foundry in Docker, installs your build into it, and leaves it on the join screen. For the questions a mock cannot answer and Quench cannot reach: whether the manifest you ship is the one Foundry reads, what a migration does to stored documents, whether the package loads at all.

import { startFoundryContainer } from '@vttforge/testing/container';

const foundry = await startFoundryContainer({
  acceptLicense: true,
  name: 'my-module-e2e',
  packages: [
    { kind: 'system', id: 'some-system', from: 'test/fixtures/system' },
    { kind: 'module', id: 'my-module', from: 'dist' },
  ],
});

try {
  // drive foundry.baseUrl with Playwright, or fetch it directly
} finally {
  foundry.stop();
}

Needs docker on the PATH, and FOUNDRY_LICENSE_KEY, FOUNDRY_USERNAME and FOUNDRY_PASSWORD in the environment. They are handed to Docker by name, so no credential is written into an argument list. The first run downloads Foundry and takes a couple of minutes; later runs reuse the data volume.

You accept the licence

Booting Foundry records an answer to its licence agreement. This code will not answer it for you: startFoundryContainer throws unless you pass acceptLicense: true or set FOUNDRY_ACCEPT_LICENSE=1. Read the agreement first.

Two things the API makes you notice

Foundry scans its packages directory once, at startup. install() copies a package in; it becomes visible after restart(). That is why packages is an option on startFoundryContainer, which installs before the world launches.

The container name, the volume, the port and the world id all default, and two runs sharing any of them collide. Name them when a project runs more than one, or runs alongside another project's. The port is the one that catches people: a distinct name does not help, because every run publishes on the same default. When the bind fails the error says so and names the option.

A second world

Foundry runs one world at a time. createWorld declares another, switchWorld launches it.

foundry.createWorld({ id: 'second', title: 'Second world' });
await foundry.switchWorld('second');

A switch restarts Foundry, so a browser session on the old world is gone and a test driving one joins again. World-scoped state does not carry over either: a module enabled in the first world starts disabled in the second. That is what the second world is for when a package moves data between them.

foundry.worldId and foundry.system follow the switch.

stopFoundryContainer(name) and foundryContainerLogs(name) do the same as the handle's stop() and logs(), for a teardown script in its own process and for a boot that failed before there was a handle.

What to test where

Anything before _renderHTML is testable in Vitest. Real rendering belongs to Quench. What only a whole Foundry can answer belongs to the container. There is no helper that mounts a sheet against a mock actor and returns its HTML, and none is planned: a copy of the Application framework inside a mock renders something like Foundry, so a test that passes against the copy says nothing about the real one.