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/runtime';
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/runtime';
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/runtime';
AvenxMock.trigger(buttonElement, 'click');

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/runtime';
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/runtime';
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/runtime';
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 provides built-in component mounting and event dispatching helpers to simplify isolated unit testing in Vitest, Jest, or Playwright.

mountTestComponent(ComponentClass, options)

Section titled “mountTestComponent(ComponentClass, options)”

Mounts an Avenx Single File Component in an isolated DOM container with custom initial props, state overrides, and slot content.

Parameters

  • ComponentClass (typeof AvenxComponent): Component class or definition object to instantiate.
  • options (object, optional):
    • props (object): Initial component prop values.
    • state (object): State property overrides.
    • slots (object): HTML string or node content for default and named slots.
    • target (HTMLElement): DOM target container (defaults to dynamically created div).

Return Value

Returns a TestWrapper object with the following properties and methods:

  • component: The mounted AvenxComponent instance.
  • element: Root DOM element of the mounted component.
  • update(): Method to flush pending asynchronous updates.
  • unmount(): Cleans up DOM nodes and invokes lifecycle teardown.

Dispatches synthetic browser events (e.g. click, input, change, submit) on rendered DOM elements and triggers component update cycles.

Parameters

  • element (HTMLElement): Target DOM node to receive the event.
  • eventName (string): Event type (e.g. 'click', 'input', 'submit').
  • detail (object, optional): Custom event detail payload.

Return Value

  • Promise<void> (resolves after event dispatch and component re-render).
import { describe, it, expect } from 'vitest';
import { mountTestComponent, fireEvent } from 'avenx-js/testing';
import CounterComponent from '../src/components/Counter.component.js';
describe('CounterComponent', () => {
it('increments count when button is clicked', async () => {
const wrapper = await mountTestComponent(CounterComponent, {
props: { initialCount: 5 },
});
expect(wrapper.element.querySelector('.count').textContent).toBe('Count: 5');
const button = wrapper.element.querySelector('button.increment');
await fireEvent(button, 'click');
expect(wrapper.element.querySelector('.count').textContent).toBe('Count: 6');
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, AvenxMock } from 'avenx-core/runtime';
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, AvenxMock } from 'avenx-core/runtime';
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, AvenxMock } from 'avenx-core/runtime';
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, AvenxMock } from 'avenx-core/runtime';
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',
]);