Products · Build a dot
Testing
Test a dot's pages with testPage, without Dash or the simulator. Fake the Dash calls they make and check what happened.
testPage runs one page in a plain test, with no Dash and no simulator. You call its mutations and actions with tap, then check what happened. It works with any test runner. These examples use Vitest:
npm install --save-dev vitestA test for the cart page of a Freetown cookshop:
import { testPage } from '@monimesl/dot-runtime/testing';
import { describe, expect, it } from 'vitest';
import cart from './controller';
describe('cart', () => {
it('adds jollof and goes to checkout', async () => {
const page = await testPage(cart, { options: { from: 'menu' } });
await page.tap('add');
await page.tap('setQuantity', { value: 3 });
await page.tap('checkout');
expect(page.state).toMatchObject({ items: 1, quantity: 3 });
expect(page.navigations).toEqual([
{ action: 'push', page: 'checkout', options: { items: '1' } },
]);
});
});Import @monimesl/dot-runtime/testing before the controller, so definePage, createEvent and env exist when it loads. env starts empty. Fill in what the page reads:
Object.assign(env, { API_URL: 'https://api.example.test' });Open, tap and check
testPage(page, settings) opens the page as Dash would, running onLoad and then onShow. Its settings are all optional:
options: the page's launch options.host: fakes for the Dash calls it makes. See Fake Dash calls.results: by page id, the value a page opened withpushForResulthands back, as in{ 'pick-genre': 'afrobeats' }. A page you leave out givesundefined, as if the person went back.
Drive the page with tap(name, detail?) for its mutations and actions, and with show(), hide(), refresh() and unload() for its hooks. Then check:
state: the page's state now.navigations: the pages it opened and closed.hostCalls: the Dash calls it made.result: what it gave back withbackWithResult.
The testPage reference has every setting and member.
When a mutation or action throws, tap rejects with that error, and so does tapping a name the page doesn't have:
await expect(page.tap('pay')).rejects.toThrow('Payment declined');Fake Dash calls
host maps each Dash call to a function, and what the function returns is the call's result. The name drops this.: this.dash.ui.alert becomes dash.ui.alert. A call without a fake fails with unsupported.
const page = await testPage(cancelOrder, {
host: {
'dash.ui.alert': () => ({ action: 'cancel' }),
'dash.network.http.request': () => ({
ok: true,
status: 200,
statusText: 'OK',
headers: { 'content-type': 'application/json' },
body: '{"cancelled":true}',
}),
},
});
await page.tap('cancel');
expect(page.hostCalls.map((call) => call.method)).toEqual([
'dash.ui.alert',
'dash.network.http.request',
]);Write a body as text, even when the page reads JSON. dash.utils and dash.supports don't need fakes.
A test draws no page, so this.captureView() gives an empty picture. It still saves that picture with Dash, so fake dash.storage.file.write and dash.storage.file.open.
Make a call fail
Throw a DashError with the code you want:
import { DashError } from '@monimesl/dot-runtime';
const page = await testPage(send, {
host: {
'dash.contacts.pickPhone': () => {
throw new DashError('denied', 'The person said no');
},
},
});
await page.tap('chooseCousin');
expect(page.state.typeNumber).toBe(true);To test a closed picker, use the code cancelled.
Test taps with data
tap sends an event without data. When a handler reads data-* attributes from event.data, send the whole event with dispatch:
await page.dispatch('addPhoto', createEvent('click', {}, { source: 'camera' }));Test a mutation on its own
A mutation is a plain function. Call it with a state and an event:
expect(cart.mutations?.add?.({ items: 1, quantity: 1 }, createEvent('tap'))).toEqual({ items: 2 });Run it in the simulator too
testPage doesn't check that state is plain data, and it doesn't draw the page. Run the dot in the simulator as well. The simulator keeps Dash's limits, and holds calls back while its tab is hidden, as Dash does while the person is in another app.