Skip to content

Testing API

Avenx-JS ships with built-in testing utilities for mounting and testing components and pages in isolation, without a full app instance.

A static utility class providing mocking helpers for bridges, sandboxes, and event triggering.

AvenxMock.createMockBridge(bridgeClassOrObject, initialData)

Section titled “AvenxMock.createMockBridge(bridgeClassOrObject, initialData)”

Creates a deep proxy around a bridge class or object, tracking method calls and state changes.

Parameters

  • bridgeClassOrObject (function | object): A bridge class (constructor) or an existing bridge instance to wrap.
  • initialData (object, optional): Initial state to assign onto the mock instance.

Returns

  • object: A proxied mock bridge with special introspection properties:
    • $calls (MockBridgeCall[]) — Array containing every intercepted method call as { method, args }.
  • $stateChanges (MockBridgeStateChange[]) — Array containing every intercepted property mutation as { prop, value }.
  • $onStateChange(callback: (prop: string, value: any) => void): () => void — Subscribes to state changes and returns an unsubscribe function.
  • $onCall(callback: (method: string, args: any[]) => void): () => void — Subscribes to intercepted method calls and returns an unsubscribe function.
  • $reset(): void — Clears the recorded $calls and $stateChanges history.
  • $isMock (true) — Read-only flag indicating the object is a mock bridge. Example
import { AvenxMock } from 'avenx-core/testing';
import AuthBridge from '../src/global/auth.bridge.js';
const mockAuth = AvenxMock.createMockBridge(AuthBridge, { isLoggedIn: false });
mockAuth.login('user@example.com');
console.log(mockAuth.$calls);
// [{ method: 'login', args: ['user@example.com'] }]
mockAuth.isLoggedIn = true;
console.log(mockAuth.$stateChanges);
// [{ prop: 'isLoggedIn', value: true }]

Creates and returns a new AvenxSandbox instance for mounting components in isolation.

Returns

  • AvenxSandbox: A new sandbox instance.
import { AvenxMock } from 'avenx-core/testing';
const sandbox = AvenxMock.createSandbox();

AvenxMock.trigger(element, eventName, eventData)

Section titled “AvenxMock.trigger(element, eventName, eventData)”

Dispatches an event on a DOM element (or a mock element), for simulating user interaction in tests.

Parameters

  • element (Element): The target element to dispatch the event on.
  • eventName (string): The event type to trigger (e.g., 'click', 'input').
  • eventData (object, optional): Additional properties merged onto the dispatched event.

Behavior

  • If a real Event/CustomEvent and dispatchEvent are available, a standard CustomEvent is dispatched with eventData set as detail.
  • If the element exposes a custom trigger() method, that is called instead.
  • Otherwise, falls back to manually walking up parentNode and invoking matching listeners[eventName] handlers, respecting stopPropagation().
import { AvenxMock } from 'avenx-core/testing';
AvenxMock.trigger(buttonElement, 'click');

AvenxMock.triggerEvent(target, eventName, detailOrOptions)

Section titled “AvenxMock.triggerEvent(target, eventName, detailOrOptions)”

Dispatches native or synthetic DOM events with full support for input values and keyboard/mouse options.

import { AvenxMock } from 'avenx-core/testing';
AvenxMock.triggerEvent(inputElement, 'input', { value: 'test@example.com' });

Creates and attaches an isolated in-memory router to window.__avenx_routers for testing components that access route parameters or perform navigation.

import { AvenxMock } from 'avenx-core/testing';
const router = AvenxMock.createMockRouter({
hash: '#/users/42?tab=settings',
page: 'UserProfile',
params: { id: '42' },
queryParams: { tab: 'settings' }
});

A container for registering components and bridges, then mounting them in isolation for testing.

Registers a component class under a given name in the sandbox.

Parameters

  • name (string): The name to register the component under.
  • compClass (typeof AvenxComponent): The component class.

Returns

  • AvenxSandbox: The sandbox instance (chainable).

Registers a bridge instance under a given name in the sandbox.

Parameters

  • name (string): The name to register the bridge under.
  • bridgeInstance (object): The bridge instance (often created via AvenxMock.createMockBridge).

Returns

  • AvenxSandbox: The sandbox instance (chainable).

Mocks the current active router state, allowing components and pages that depend on route parameters, query strings, or the active page path to be tested in isolation without instantiating a full AvenxRouter.

Parameters

  • route (object): The mocked route object. Properties include:
    • hash (string, optional): The mocked URL route hash (e.g. '#/users/42').
    • page (string, optional): The name of the active page component (e.g. 'UserProfile').
    • params (object, optional): Key/value map of dynamic path parameters (e.g. { id: '42' }).
    • params.query (object, optional): Key/value map of parsed URL query parameters (e.g. { tab: 'settings', filter: 'active' }).

Returns

  • AvenxSandbox: The sandbox instance (chainable).

Example: Testing a Route-Dependent Component

import { AvenxMock } from 'avenx-core/testing';
import UserProfilePage from '../src/pages/user-profile.page.js';
const sandbox = AvenxMock.createSandbox();
// Mock active route with path parameter 'id' and query parameter 'tab'
sandbox.setRoute({
hash: '#/users/42?tab=settings',
page: 'UserProfile',
params: {
id: '42',
query: {
tab: 'settings',
filter: 'active',
},
},
});
const wrapper = sandbox.mount(UserProfilePage);
console.log(wrapper.html);
// Component accesses route params and renders using mocked id '42' and tab 'settings'

Waits for any pending scheduled component updates to flush, before making assertions.

Returns

  • Promise<void>
await sandbox.waitForUpdate();

Mounts a component (or page) class in isolation using the sandbox’s registered bridges and components.

Parameters

  • compClass (typeof AvenxComponent): The component or page class to mount.
  • props (object, optional): Props to pass into the component.
  • container (Element, optional): A DOM element to mount into. If omitted, a <div> is created automatically (using document.createElement when available, or an internal mock element otherwise).

Returns

  • object: A mount helper with:
    • instance — the mounted component instance.
    • container — the DOM element the component was mounted into.
    • html — getter returning the current serialized inner HTML.
    • update() — manually triggers instance.update().
    • trigger(selectorOrElement, eventName, eventData) — finds an element by CSS selector (or accepts an element directly) within the container and calls AvenxMock.trigger() on it.

Example

import { AvenxMock } from 'avenx-core/testing';
import Counter from '../src/components/counter/counter.component.js';
const sandbox = AvenxMock.createSandbox();
const wrapper = sandbox.mount(Counter, { initialCount: 5 });
console.log(wrapper.html);
// <div class="content">...</div>
wrapper.trigger('button', 'click');
await sandbox.waitForUpdate();
console.log(wrapper.html);
// Reflects updated state after the click

Full Example: Testing a Component with a Mocked Bridge

Section titled “Full Example: Testing a Component with a Mocked Bridge”
import { AvenxMock } from 'avenx-core/testing';
import ProfileCard from '../src/components/profile-card/profile-card.component.js';
import UserBridge from '../src/global/user.bridge.js';
const sandbox = AvenxMock.createSandbox();
const mockUserBridge = AvenxMock.createMockBridge(UserBridge, { name: 'Ada' });
sandbox.registerBridge('user', mockUserBridge);
const wrapper = sandbox.mount(ProfileCard);
console.log(wrapper.html);
// Renders using the mocked 'Ada' user state
mockUserBridge.name = 'Grace';
await sandbox.waitForUpdate();
console.log(wrapper.html);
// Re-renders reflecting the updated mock state
console.log(mockUserBridge.$stateChanges);
// [{ prop: 'name', value: 'Grace' }]

Avenx-JS exports its testing helpers (AvenxMock, AvenxSandbox, mountTestComponent, fireEvent, flushPromises) from avenx-core/testing to simplify isolated unit testing with test runners like Vitest, Jest, Node Test Runner, or Playwright.

These live behind their own entry point so they can never reach a production bundle. Importing them from avenx-core/runtime does not work — the runtime entry contains only what a browser needs at runtime.

mountTestComponent(ComponentClass, options)

Section titled “mountTestComponent(ComponentClass, options)”

Instantiates and mounts an Avenx component or page into a test container, pre-configuring props, reactive state overrides, transcluded slots, and mock bridges.

function mountTestComponent<C extends AvenxComponent>(
ComponentClass: new (...args: any[]) => C,
options?: MountTestComponentOptions
): Promise<MountTestComponentResult<C>>
Option Type Description
props object Initial property values passed to the component constructor.
state / initialState object Reactive state overrides assigned before mounting.
slots string | object | Element Slot transclusion content (HTML string or slot map e.g. { default: '<p>...</p>', header: '...' }).
container / target Element Target DOM element to mount into. Defaults to an auto-created <div>.
bridges object Map of mock or real bridges injected into the component.
components object Map of child components registered with the instance.
route object Mock route configuration passed to AvenxMock.createMockRouter().
Property / Method Type Description
instance / component AvenxComponent The mounted component instance.
element Element The root DOM element rendered by the component.
container Element The parent container hosting the mounted component.
html string Getter returning the current serialized inner HTML markup of the container.
update() () => void Manually triggers a synchronous component update cycle.
unmount() () => void Invokes component unmounting, cleanup watchers, and lifecycle hooks (onBeforeUnmount, onUnmounted).

Dispatches synthetic DOM events (e.g. click, input, change, submit, keydown) on rendered DOM elements, applies form control values, and automatically awaits microtask scheduler completion via nextTick().

function fireEvent(
element: Element,
eventType: string,
detail?: { value?: any; checked?: boolean; [key: string]: any }
): Promise<void>

Parameters:

  • element (Element): The target DOM node to receive the event.
  • eventType (string): The event type name (e.g. 'click', 'input', 'submit').
  • detail (object, optional): Payload or options. If detail.value is provided, element.value is updated before firing 'input'/'change'. If detail.checked is provided, element.checked is set.

Returns: Promise<void> — Resolves after the event has dispatched and all queued microtask DOM updates have flushed.


import { describe, it, expect } from 'vitest';
import { mountTestComponent, fireEvent } from 'avenx-core/testing';
import SearchBoxComponent from '../src/components/SearchBox.component.js';
describe('SearchBoxComponent Unit Test', () => {
it('updates query and triggers search on submit', async () => {
// 1. Mount component with props and initial state overrides
const wrapper = await mountTestComponent(SearchBoxComponent, {
props: { placeholderText: 'Search articles...' },
state: { query: 'Initial query' }
});
// 2. Assert initial rendered DOM
expect(wrapper.element.querySelector('input').value).toBe('Initial query');
expect(wrapper.element.querySelector('input').placeholder).toBe('Search articles...');
// 3. Simulate user typing into input field with fireEvent
const input = wrapper.element.querySelector('input');
await fireEvent(input, 'input', { value: 'Avenx Reactivity' });
// 4. Assert reactive state updated
expect(wrapper.instance.state.query).toBe('Avenx Reactivity');
// 5. Simulate form submission
const form = wrapper.element.querySelector('form');
await fireEvent(form, 'submit');
// 6. Assert rendered output via wrapper.html
expect(wrapper.html).toContain('Results for: Avenx Reactivity');
// 7. Clean up component instance
wrapper.unmount();
});
});

Headless Router Testing & SSR (MemoryNavigationDelegate)

Section titled “Headless Router Testing & SSR (MemoryNavigationDelegate)”

To test router transitions, guards, resolvers, and page title updates in Jest, Vitest, or Node.js without a browser DOM environment, use MemoryNavigationDelegate (lib/core/runtime/navigation/MemoryNavigationDelegate.js).

Unit Testing Router Transitions and Guards

Section titled “Unit Testing Router Transitions and Guards”
import { AvenxApp } from 'avenx-core/runtime';
import { MemoryNavigationDelegate } from 'avenx-core/runtime/navigation';
import AuthGuard from '../src/guards/auth.guard.js';
describe('Router Headless Tests', () => {
let delegate;
let router;
beforeEach(() => {
// 1. Create an in-memory navigation delegate starting at '#/'
delegate = new MemoryNavigationDelegate('#/');
// 2. Initialize router with memory delegate
router = AvenxApp.initRouter(
{
'#/': { page: 'Home', title: 'Home Page' },
'#/dashboard': { page: 'Dashboard', title: 'Dashboard', guards: [AuthGuard] },
'#/login': { page: 'Login', title: 'Login Page' },
},
{
navigationDelegate: delegate,
titlePrefix: 'App | ',
}
);
});
afterEach(() => {
// 3. Clean up router and delegate listeners
if (router && typeof router.destroy === 'function') {
router.destroy();
}
if (delegate) {
delegate.destroy();
}
});
test('navigates in memory and updates title', async () => {
expect(delegate.getHash()).toBe('#/');
expect(delegate.title).toBe('App | Home Page');
// Programmatically navigate
await router.navigate('#/dashboard');
// Unauthenticated user redirected to #/login by AuthGuard
expect(delegate.getHash()).toBe('#/login');
expect(delegate.title).toBe('App | Login Page');
});
});

The recipes below build on AvenxMock.createSandbox() for common real-world scenarios: slots, $emit, async updates, and lifecycle ordering.

Mount a host that projects markup into a child <slot>, then assert the projected content appears in the rendered HTML.

import { AvenxComponent } from 'avenx-core/runtime';
import { AvenxMock } from 'avenx-core/testing';
class Card extends AvenxComponent {
static template = `
<div class="card">
<header class="card-header"><slot name="header"></slot></header>
<div class="card-body"><slot></slot></div>
</div>
`;
}
class CardHost extends AvenxComponent {
static template = `
<ax-card>
<template name="header"><h2>Profile</h2></template>
<p class="bio">Ada Lovelace</p>
</ax-card>
`;
}
const sandbox = AvenxMock.createSandbox();
sandbox.register('ax-card', Card);
const wrapper = sandbox.mount(CardHost);
expect(wrapper.html).toContain('Profile');
expect(wrapper.html).toContain('Ada Lovelace');
expect(wrapper.html).toContain('class="card-body"');

$emit(eventName, detail) dispatches a CustomEvent on the component root. Listen on wrapper.container (or the instance root) before triggering the action that emits.

import { AvenxComponent } from 'avenx-core/runtime';
import { AvenxMock } from 'avenx-core/testing';
class CounterButton extends AvenxComponent {
constructor() {
super();
this.state = { count: 0 };
}
static template = `
<button class="inc" @click="increment()">+</button>
`;
increment() {
this.state.count += 1;
this.$emit('change', { count: this.state.count });
}
}
const sandbox = AvenxMock.createSandbox();
const wrapper = sandbox.mount(CounterButton);
const emissions = [];
wrapper.container.addEventListener('change', (event) => {
emissions.push(event.detail);
});
wrapper.trigger('.inc', 'click');
await sandbox.waitForUpdate();
expect(emissions).toEqual([{ count: 1 }]);
expect(wrapper.instance.state.count).toBe(1);

Asynchronous State Updates & Microtask Batching

Section titled “Asynchronous State Updates & Microtask Batching”

Reactive mutations are scheduled asynchronously. Always await sandbox.waitForUpdate() (or chain mutations then wait once) before asserting DOM output so the microtask flush completes.

import { AvenxComponent } from 'avenx-core/runtime';
import { AvenxMock } from 'avenx-core/testing';
class StatusBadge extends AvenxComponent {
constructor() {
super();
this.state = { label: 'idle' };
}
static template = `<span class="badge">{{ state.label }}</span>`;
}
const sandbox = AvenxMock.createSandbox();
const wrapper = sandbox.mount(StatusBadge);
// Multiple mutations in the same turn batch into one render pass
wrapper.instance.state.label = 'loading';
wrapper.instance.state.label = 'ready';
await sandbox.waitForUpdate();
expect(wrapper.html).toContain('ready');
expect(wrapper.html).not.toContain('loading');

When driving updates from a mocked bridge, mutate the bridge then wait once:

mockAuth.isLoggedIn = true;
mockAuth.user.name = 'Ada';
await sandbox.waitForUpdate();

Record hook invocations on the instance to verify mount → update → unmount ordering during a test.

import { AvenxComponent } from 'avenx-core/runtime';
import { AvenxMock } from 'avenx-core/testing';
class LifecycleProbe extends AvenxComponent {
constructor() {
super();
this.state = { ticks: 0 };
this.hookLog = [];
}
onMount() {
this.hookLog.push('onMount');
}
onBeforeUpdate() {
this.hookLog.push('onBeforeUpdate');
}
onUpdate() {
this.hookLog.push('onUpdate');
}
onUnmount() {
this.hookLog.push('onUnmount');
}
static template = `<div>{{ state.ticks }}</div>`;
}
const sandbox = AvenxMock.createSandbox();
const wrapper = sandbox.mount(LifecycleProbe);
expect(wrapper.instance.hookLog).toEqual(['onMount']);
wrapper.instance.state.ticks = 1;
await sandbox.waitForUpdate();
expect(wrapper.instance.hookLog).toEqual(['onMount', 'onBeforeUpdate', 'onUpdate']);
wrapper.instance.unmount();
expect(wrapper.instance.hookLog).toEqual([
'onMount',
'onBeforeUpdate',
'onUpdate',
'onUnmount',
]);

Avenx includes a zero-dependency DOM serializer and snapshot engine designed for component contract testing without external runners.

Use .toMatchSnapshot() directly on the wrapper returned by mountTestComponent:

import { mountTestComponent } from 'avenx-core/testing';
import { MyComponent } from './MyComponent.js';
const wrapper = await mountTestComponent(MyComponent, {
props: { title: 'Welcome' }
});
// Asserts against __snapshots__/<testfile>.snap
wrapper.toMatchSnapshot('initial render');
## Snapshot Testing
Avenx includes a zero-dependency DOM serializer and snapshot engine designed for component contract testing without external runners.
### Usage in Mounted Components
Use `.toMatchSnapshot()` directly on the wrapper returned by `mountTestComponent`:
`/*
avenx-core/testing exports matchers */
import { mountTestComponent } from 'avenx-core/testing';
import { MyComponent } from './MyComponent.js';
const wrapper = await mountTestComponent(MyComponent, {
props: { title: 'Welcome' }
});
// Asserts against __snapshots__/<testfile>.snap
wrapper.toMatchSnapshot('initial render');
``
### Standalone Assertion
Use `assertSnapshot` to test raw markup or detached DOM nodes:

import { assertSnapshot } from ‘avenx-core/testing’;

assertSnapshot(myElement, ‘custom element state’, { masks: [ { match: /id-[0-9]+/g, replace: ‘id-[mask]’ } ] });

### Dynamic Content & Masking
Volatile attributes such as internal scoped CSS hashes (`ax-[hash]`) and generated IDs
are automatically masked to prevent brittle snapshots. Custom masking rules can be
provided via the `masks` option.
### Updating Snapshots & CI Behavior
- **Update flag**: Pass `-u` or `--update-snapshots` to rewrite stale snapshots.
- **CI Mode**: When `process.env.CI` becomes active, missing or mismatched snapshots immediately fail tests to prevent accidental baseline drift.