Recipes for common tests with @blotch/angular-lynx-testing-library: signal changes, events, host data, native APIs, error fallbacks and snapshots. Set up the library first, as in Testing.
Your handler runs right away.fireEvent.tap() calls increment() before it returns.
The screen updates afterwards. As on a device, Angular re-renders after the handler, so await waitForUpdate() before you check the tree.
Pass event data as the second argument. Its properties are copied onto the event your handler receives. Lynx's <input> puts its text in event.detail.value, for example:
No host app runs in a test, so InitData and GlobalData hold nothing. Provide a stand-in for LynxInitData or LynxGlobalData with the values you need:
src/app/welcome.test.ts
import { Component, computed, inject, signal } from '@angular/core';import { LYNX_ELEMENTS, LynxInitData } from '@blotch/angular-lynx';import { render } from '@blotch/angular-lynx-testing-library';@Component({ selector: 'app-welcome', imports: [LYNX_ELEMENTS], template: `<text>Hi {{ userId() }}</text>`,})class Welcome { readonly #initData = inject(LynxInitData); readonly userId = computed( () => this.#initData.initData()?.['userId'] ?? 'guest', );}it('greets the user named in InitData', async () => { const { getByText } = await render(Welcome, { providers: [ { provide: LynxInitData, useValue: { initData: signal({ userId: 'user_42' }) }, }, ], }); expect(getByText('Hi user_42')).toBeTruthy();});
LynxTheme and LynxLocale read GlobalData, so the same trick sets them. For example, { provide: LynxGlobalData, useValue: { globalData: signal({ theme: 'Dark' }) } } makes LynxTheme.isDarkMode() return true.
Services that call the phone return nothing or throw in jsdom: LynxNativeModule.call() returns undefined, and LynxTextMeasure.measure() throws. Provide fakes:
@boundary works in tests. Give the test an error handler that records caught errors. The default LynxErrorHandler reports them to Lynx, and the test environment's stand-in for Lynx throws them again.
src/app/price.test.ts
import { Component, ErrorHandler, Injectable, signal } from '@angular/core';import { LYNX_ELEMENTS, LynxErrorHandler } from '@blotch/angular-lynx';import { render, waitForUpdate } from '@blotch/angular-lynx-testing-library';@Injectable()class RecordingErrorHandler extends LynxErrorHandler { readonly caught: Error[] = []; override onViewError(error: Error): void { this.caught.push(error); }}@Component({ selector: 'app-price', imports: [LYNX_ELEMENTS], template: ` @boundary { <text>{{ format(price()) }}</text> } @error { <text>Price unavailable</text> } `,})class Price { readonly price = signal(10); format(value: number): string { if (Number.isNaN(value)) throw new Error('Bad price'); return `$${value}`; }}it('shows the fallback when the price is bad', async () => { const handler = new RecordingErrorHandler(); const { getByText, componentRef } = await render(Price, { providers: [{ provide: ErrorHandler, useValue: handler }], }); (componentRef.instance as Price).price.set(Number.NaN); await waitForUpdate(); expect(getByText('Price unavailable')).toBeTruthy(); expect(handler.caught).toHaveLength(1);});
componentRef is the rendered component's ComponentRef:
const { componentRef } = await render(Label);// Read a service from the app's injector.const store = componentRef.injector.get(CartStore);// Change state on the instance, then wait for the update.(componentRef.instance as Label).label.set('updated');await waitForUpdate();
componentRef.setInput() works under ng test, which compiles components ahead of time. Under plain Vitest, which compiles them at runtime, it doesn't. Change writable signals on the instance there.
The library destroys the rendered app after each test. To turn that off, set the environment variable ATL_SKIP_AUTO_CLEANUP=true and call cleanup() or unmount() yourself.