Testing
This router mocks nothing: it uses the browser’s Navigation API and URLPattern as they are. This page covers what can be checked without a browser, and what to watch for when checking navigation inside one.
On this page
Check the route table without a browser
The route table defineRoutes returns matches a path with match. It is a plain function, so which page a path lands on, the params it takes and the order patterns match in can all be checked without a browser.
src/routes.test.tsimport { expect, it } from 'vitest';
import { routes } from './routes';
it('sends /products/new to its own page', () => {
const found = routes.match('/products/new');
expect(found?.pattern).toBe('/products/new');
});
it('reads the id of a product page', () => {
const found = routes.match('/products/42');
expect(found?.params).toStrictEqual({ id: '42' });
});matchPath is likewise a function you can call without a browser. Code that marks a link by where you are can take the path as an argument, and be checked along with matchPath.
Change pages inside a browser
A test that renders <Router> and changes pages runs inside a browser with the Navigation API. This package’s own tests use Vitest’s browser mode on Chromium, Firefox and WebKit.
src/app.browser.test.tsximport { navigateTo, Router } from '@k8ordo/router';
import { expect, it } from 'vitest';
import { render } from 'vitest-browser-react';
import { routes } from './routes';
it('shows the product page once finished resolves', async () => {
await render(<Router routes={routes} />);
await navigateTo('/products/:id', { id: '1' }).finished;
const heading = document.querySelector('h1');
expect(heading?.textContent).toBe('Product 1');
});finished resolves once the new page is on screen, so after awaiting it the screen can be checked straight away, with no retrying.
A page’s useEffect runs after it is on screen, though. To check what an effect did, use a form that retries until it passes, such as expect.element or vi.waitFor.
Intercept navigations outside the table yourself
A navigation <Router> does not take becomes an ordinary page load. Inside a test, that moves the very page the tests run in somewhere else.
Moving to a URL outside the table before a test, or back to the original URL afterwards, is exactly that. For those navigations only, the test intercepts the navigate event itself.
src/app.browser.test.tsxconst interceptEverything = (event: NavigateEvent) => {
if (event.canIntercept) event.intercept();
};
const navigateOutside = async (url: string) => {
navigation.addEventListener('navigate', interceptEverything);
try {
await navigation.navigate(url, { history: 'replace' }).finished;
} finally {
navigation.removeEventListener('navigate', interceptEverything);
}
};
let home: string;
beforeEach(() => {
home = location.href;
});
afterEach(async () => {
await navigateOutside(home);
});Note
Once <Router> is rendered, it takes navigations to paths in the table itself; the test has nothing to intercept there.
Check back and forward in a top-level page
Vitest’s browser mode runs a test inside an iframe. There, Firefox and WebKit do not restore the scroll position on back and forward, and Firefox runs a traversal’s handler twice.
This package therefore checks going back and forward only in a top-level page it opens with Playwright. To check the scroll position after going back, do the same and leave the iframe.