Skip to content

Testing

The player core ships a testing subpath with utilities for writing unit tests against plugins without a real browser.

Install

The testing utilities are included with the core package. No additional install is needed:

Code
npm install @nomercy-entertainment/nomercy-player-core@beta

Import from the testing subpath:

TypeScript
import {
describePlugin,
assertNoListenerLeak,
} from '@nomercy-entertainment/nomercy-player-core/testing';

describePlugin

Wraps a test suite with a fresh player and registered plugin per test. Auto-disposes in afterEach.

TypeScript
import {
describePlugin,
assertNoListenerLeak,
} from '@nomercy-entertainment/nomercy-player-core/testing';
import { MyPlugin } from '../src/plugins/my-plugin';

describePlugin(MyPlugin, ({ player, plugin }) => {
test('emits dataFetched after time threshold', async () => {
const events: unknown[] = [];
player.on('plugin:my-plugin:dataFetched', (event) => events.push(event));

// Simulate a player event the plugin listens to
player.emit('time', { time: 30 });

await vi.waitFor(() => events.length > 0);
expect(events[0]).toMatchObject({ count: expect.any(Number) });
});
});

describePlugin arguments:

TypeScript
describePlugin(
PluginClass: PluginConstructor,
fn: (ctx: { player: StubPlayer; plugin: Plugin }) => void,
opts?: {
opts?: object; // options passed to plugin.initialize()
skipLeakAssertion?: boolean;
createPlayer?: () => StubPlayer;
}
): void

assertNoListenerLeak

Verifies that a plugin’s dispose() cleans up all event listeners. This is a required test for all plugins.

TypeScript
import {
assertNoListenerLeak,
createStubPlayer,
} from '@nomercy-entertainment/nomercy-player-core/testing';
import { LifecycleRegistry } from '@nomercy-entertainment/nomercy-player-core';

test('MyPlugin releases all listeners on dispose', async () => {
const player = createStubPlayer();
const plugin = new MyPlugin();
const lifecycle = new LifecycleRegistry();

await assertNoListenerLeak({
subjectId: 'my-plugin',
player,
setup: async () => {
plugin.initialize(player, {}, lifecycle);
await plugin.use();
},
teardown: () => {
plugin.dispose();
lifecycle.dispose();
},
});
});

assertNoListenerLeak snapshots the listener count before and after a setup/teardown cycle. If the count after teardown exceeds the count before setup, the test fails with details of the leak.

describePlugin already runs a leak assertion automatically after each test. Use assertNoListenerLeak directly only when you need to test a non-plugin subject or run cycles across multiple setup/teardown pairs via assertNoListenerLeakOverCycles.

Stub player

The stub player is a minimal IPlayer implementation with no browser dependencies. It:

  • Supports the full event bus (on, off, once, emit)
  • Supports phase(), metrics(), queue(), time(), volume(), and all transport stubs
  • Does not start actual media playback
  • Works in Node.js without a DOM (Vitest, Jest)

You can use the stub player directly for lower-level testing:

TypeScript
import { createStubPlayer } from '@nomercy-entertainment/nomercy-player-core/testing';

const player = createStubPlayer();
player.addPlugin(MyPlugin, { endpoint: 'http://test.local' });
await player.ready();

player.emit('ended', undefined);

Mock fetch

mockFetch() returns a self-contained fetch stub. Assign mock.fetch to the plugin’s fetch property to intercept outgoing requests:

TypeScript
import { createStubPlayer, mockFetch, describePlugin } from '@nomercy-entertainment/nomercy-player-core/testing';

describePlugin(MyPlugin, ({ plugin }) => {
test('fetches the manifest', async () => {
const mock = mockFetch();
plugin['fetch'] = mock.fetch;

mock.respondWith({ count: 5 });
await plugin.loadManifest('http://test.local/api');

expect(mock.calls[0].url).toBe('http://test.local/api');
});
});

Integration with Vitest

The core’s test suite uses Vitest. If you are using a different runner (Jest), the testing utilities still work, as the subpath has no Vitest imports.

TypeScript
// vitest.config.ts
export default {
test: {
environment: 'node', // or 'jsdom' for DOM-dependent plugins
},
};

For plugins that mount DOM elements (use this.mount(...)), use jsdom or happy-dom as the test environment.