packages/__tests__/src/router/router.link-click-navigation.spec.ts
import { IContainer } from '@aurelia/kernel';
import { IRoute, IRouter, IRouterOptions, RouterConfiguration } from '@aurelia/router';
import { Aurelia, CustomElement, IPlatform } from '@aurelia/runtime-html';
import { MockBrowserHistoryLocation, TestContext, assert } from '@aurelia/testing';
describe('router/router.link-click-navigation.spec.ts', function () {
function getModifiedRouter(container: IContainer) {
const router = container.get(IRouter) as IRouter;
const mockBrowserHistoryLocation = new MockBrowserHistoryLocation();
mockBrowserHistoryLocation.changeCallback = async (ev) => { router.viewer.handlePopStateEvent(ev); };
router.viewer.history = mockBrowserHistoryLocation as any;
router.viewer.location = mockBrowserHistoryLocation as any;
return router;
}
type NavigationStateCallback = (type: 'push' | 'replace', data: any, title: string, path: string) => void;
function spyNavigationStates(router: IRouter, spy: NavigationStateCallback) {
let _pushState;
let _replaceState;
if (spy) {
_pushState = router.viewer.location.pushState;
router.viewer.location.pushState = function (data, title, path) {
spy('push', data, title, path);
_pushState.call(router.viewer.location, data, title, path);
};
_replaceState = router.viewer.location.replaceState;
router.viewer.location.replaceState = function (data, title, path) {
spy('replace', data, title, path);
_replaceState.call(router.viewer.location, data, title, path);
};
}
return { _pushState, _replaceState };
}
function unspyNavigationStates(router, _push, _replace) {
if (_push) {
router.viewer.location.pushState = _push;
router.viewer.location.replaceState = _replace;
}
}
async function $setup(config?: IRouterOptions, dependencies: any[] = [], routes: IRoute[] = [], stateSpy: NavigationStateCallback = void 0) {
const ctx = TestContext.create();
const { container, platform } = ctx;
const App = CustomElement.define({
name: 'app',
template: '<au-viewport name="app"></au-viewport>',
dependencies
}, class {
public static routes: IRoute[] = routes;
});
const host = ctx.doc.createElement('div');
ctx.doc.body.appendChild(host as any);
const au = new Aurelia(container)
.register(
RouterConfiguration.customize(config ?? {}),
App)
.app({ host: host, component: App });
const router = getModifiedRouter(container);
const { _pushState, _replaceState } = spyNavigationStates(router, stateSpy);
await au.start();
async function $teardown() {
unspyNavigationStates(router, _pushState, _replaceState);
RouterConfiguration.customize();
await au.stop(true);
ctx.doc.body.removeChild(host);
au.dispose();
}
return { ctx, container, platform, host, au, router, $teardown, App };
}
this.timeout(30000);
const routes = [
{ id: 'route-one', path: 'route-one', component: 'one-route' },
{ id: 'route-two', path: 'route-two/:id', component: 'two-route' },
{ id: 'zero-id', path: 'route-zero-id', component: 'zero' },
];
const Nav = CustomElement.define({
name: 'nav', template: `
<style>.active { background-color: gold; }</style>
<a load="one">one</a>
<a load="two(123)">two(123)</a>
<a load="two(456)">two(456)</a>
<a load="route-one">route-one</a>
<a load="route-two/123">route-two/123</a>
<a load="route-two/456">route-two/456</a>
<au-viewport name="nav-vp"></au-viewport>
`,
});
const NavBind = CustomElement.define({
name: 'nav-bind', template: `
<style>.active { background-color: gold; }</style>
<a load.bind="{ component: 'one' }">bind: { component: 'one' }</a>
<a load.bind="{ component: 'two', parameters: { id: 123 }}">bind: { component: 'two', parameters: { id: 123 }}</a>
<a load.bind="{ component: 'two', parameters: { id: 456 }}">bind: { component: 'two', parameters: { id: 456 }}</a>
<a load.bind="{ id: 'route-one' }">bind: { id: 'route-one' }</a>
<a load.bind="{ id: 'route-two', parameters: { id: 123 }}">bind: { id: 'route-two', parameters: { id: 123 }}</a>
<a load.bind="{ id: 'route-two', parameters: { id: 456 }}">bind: { id: 'route-two', parameters: { id: 456 }}</a>
<au-viewport name="nav-vp"></au-viewport>
`,
});
const NavAttributes = CustomElement.define({
name: 'nav-attributes', template: `
<style>.active { background-color: gold; }</style>
<a load="component: one">attributes: component: one</a>
<a load="component: two; parameters.bind: { id: 123 };">attributes: component: two; parameters.bind: { id: 123 };</a>
<a load="component: two; parameters.bind: { id: 456 };">attributes: component: two; parameters.bind: { id: 456 };</a>
<a load="id: route-one">attributes: { id: route-one }</a>
<a load="id: route-two; parameters.bind: { id: 123 };">attributes: id: route-two; parameters.bind: { id: 123 };</a>
<a load="id: route-two; parameters.bind: { id: 456 };">attributes: id: route-two; parameters.bind: { id: 456 };</a>
<au-viewport name="nav-vp"></au-viewport>
`,
});
const One = CustomElement.define({ name: 'one', template: '!one!' });
const Two = CustomElement.define({ name: 'two', template: '!two${id}!' }, class {
public static parameters = ['id'];
public id: string;
public loading(params) { if (params.id != null) { this.id = `:${params.id}`; } }
});
const OneRoute = CustomElement.define({ name: 'one-route', template: '!one-route!' });
const TwoRoute = CustomElement.define({ name: 'two-route', template: '!two-route${id}!' }, class {
public static parameters = ['id'];
public id: string;
public loading(params) { if (params.id != null) { this.id = `:${params.id}`; } }
});
const tests = [
{ load: 'one', result: '!one!', },
{ load: 'two(123)', result: '!two:123!', },
{ load: 'two(456)', result: '!two:456!', },
{ load: 'route-one', result: '!one-route!', },
{ load: 'route-two/123', result: '!two-route:123!', },
{ load: 'route-two/456', result: '!two-route:456!', },
];
for (let i = 0; i < tests.length; i++) {
const test = tests[i];
it(`for "${test.load}"`, async function () {
const { platform, host, router, $teardown } = await $setup({}, [Nav, One, Two, OneRoute, TwoRoute], routes);
await $load('/nav', router, platform);
await platform.domQueue.yield();
const links = host.getElementsByTagName('A') as unknown as HTMLElement[];
const link = links[i];
link.click();
await platform.domQueue.yield();
assert.includes(host.textContent, test.result, test.load);
for (const l of links) {
assert.strictEqual(l.classList.contains('active'), l === link, `${l.innerText}: ${l.classList.contains('active')}`);
}
await $teardown();
});
}
const bindTests = [
{ load: 'bind: one', result: '!one!', },
{ load: 'bind: two(123)', result: '!two:123!', },
{ load: 'bind: two(456)', result: '!two:456!', },
{ load: 'bind: route-one', result: '!one-route!', },
{ load: 'bind: route-two/123', result: '!two-route:123!', },
{ load: 'bind: route-two/456', result: '!two-route:456!', },
];
for (let i = 0; i < bindTests.length; i++) {
const test = bindTests[i];
it(`for "${test.load}"`, async function () {
const { platform, host, router, $teardown } = await $setup({}, [NavBind, One, Two, OneRoute, TwoRoute], routes);
await $load('/nav-bind', router, platform);
await platform.domQueue.yield();
const links = host.getElementsByTagName('A') as unknown as HTMLElement[];
const link = links[i];
link.click();
await platform.domQueue.yield();
assert.includes(host.textContent, test.result, test.load);
for (const l of links) {
assert.strictEqual(l.classList.contains('active'), l === link, `${l.innerText}: ${l.classList.contains('active')}`);
}
await $teardown();
});
}
const attributesTests = [
{ load: 'attributes: one', result: '!one!', },
{ load: 'attributes: two(123)', result: '!two:123!', },
{ load: 'attributes: two(456)', result: '!two:456!', },
{ load: 'attributes: route-one', result: '!one-route!', },
{ load: 'attributes: route-two/123', result: '!two-route:123!', },
{ load: 'attributes: route-two/456', result: '!two-route:456!', },
];
for (let i = 0; i < attributesTests.length; i++) {
const test = attributesTests[i];
it(`for "${test.load}"`, async function () {
const { platform, host, router, $teardown } = await $setup({}, [NavAttributes, One, Two, OneRoute, TwoRoute], routes);
await $load('/nav-attributes', router, platform);
await platform.domQueue.yield();
const links = host.getElementsByTagName('A') as unknown as HTMLElement[];
const link = links[i];
link.click();
await platform.domQueue.yield();
assert.includes(host.textContent, test.result, test.load);
for (const l of links) {
assert.strictEqual(l.classList.contains('active'), l === link, `${l.innerText}: ${l.classList.contains('active')}`);
}
await $teardown();
});
}
});
const $load = async (path: string, router: IRouter, platform: IPlatform) => {
await router.load(path);
platform.domQueue.flush();
};