Testing API
Avenx-JS ships with built-in testing utilities for mounting and testing components and pages in isolation, without a full app instance.
AvenxMock
Section titled “AvenxMock”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$callsand$stateChangeshistory.$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 }]AvenxMock.createSandbox()
Section titled “AvenxMock.createSandbox()”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/CustomEventanddispatchEventare available, a standardCustomEventis dispatched witheventDataset asdetail. - If the element exposes a custom
trigger()method, that is called instead. - Otherwise, falls back to manually walking up
parentNodeand invoking matchinglisteners[eventName]handlers, respectingstopPropagation().
import { AvenxMock } from 'avenx-core/runtime';
AvenxMock.trigger(buttonElement, 'click');AvenxSandbox
Section titled “AvenxSandbox”A container for registering components and bridges, then mounting them in isolation for testing.
register(name, compClass)
Section titled “register(name, compClass)”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).
registerBridge(name, bridgeInstance)
Section titled “registerBridge(name, bridgeInstance)”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 viaAvenxMock.createMockBridge).
Returns
AvenxSandbox: The sandbox instance (chainable).
setRoute(route)
Section titled “setRoute(route)”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'waitForUpdate()
Section titled “waitForUpdate()”Waits for any pending scheduled component updates to flush, before making assertions.
Returns
Promise<void>
await sandbox.waitForUpdate();mount(compClass, props, container)
Section titled “mount(compClass, props, container)”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 (usingdocument.createElementwhen 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 triggersinstance.update().trigger(selectorOrElement, eventName, eventData)— finds an element by CSS selector (or accepts an element directly) within the container and callsAvenxMock.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 clickFull 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 stateconsole.log(mockUserBridge.$stateChanges);// [{ prop: 'name', value: 'Grace' }]Component Unit Testing Helpers
Section titled “Component Unit Testing Helpers”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 mountedAvenxComponentinstance.element: Root DOM element of the mounted component.update(): Method to flush pending asynchronous updates.unmount(): Cleans up DOM nodes and invokes lifecycle teardown.
fireEvent(element, eventName, detail)
Section titled “fireEvent(element, eventName, detail)”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).
Full Unit Test Example
Section titled “Full Unit Test Example”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'); });});Advanced Component Testing Patterns
Section titled “Advanced Component Testing Patterns”The recipes below build on AvenxMock.createSandbox() for common real-world scenarios: slots, $emit, async updates, and lifecycle ordering.
Testing Slot Transclusion
Section titled “Testing Slot Transclusion”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"');Asserting $emit Custom Events
Section titled “Asserting $emit Custom Events”$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 passwrapper.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();Lifecycle Hook Execution Order
Section titled “Lifecycle Hook Execution Order”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',]);