Skip to content

Commit bb0b09d

Browse files
authored
fix(router-lite): cancelling navigation with redirection from canLoad hook from child route (#2131)
* chore: adding failing test * fix(router-lite): initial fix for canceling navigation from child routes * chore: cleanup * test: router-lite * chore: clean up and more assertions
1 parent c1af10a commit bb0b09d

File tree

4 files changed

+120
-1
lines changed

4 files changed

+120
-1
lines changed

packages/__tests__/src/router-lite/lifecycle-hooks.spec.ts

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
Registration,
2323
resolve,
2424
} from '@aurelia/kernel';
25-
import { IRouter, IRouteViewModel, IViewportInstruction, NavigationInstruction, Params, route, RouteNode, RouterConfiguration } from '@aurelia/router-lite';
25+
import { IRouter, IRouterEvents, IRouteViewModel, IViewportInstruction, NavigationInstruction, Params, route, RouteNode, RouterConfiguration } from '@aurelia/router-lite';
2626
import { Aurelia, CustomElement, customElement, IHydratedController, ILifecycleHooks, lifecycleHooks } from '@aurelia/runtime-html';
2727
import { assert, TestContext } from '@aurelia/testing';
2828
import { TestRouterConfiguration } from './_shared/configuration.js';
@@ -6393,4 +6393,109 @@ describe('router-lite/lifecycle-hooks.spec.ts', function () {
63936393

63946394
await au.stop(true);
63956395
});
6396+
6397+
it('redirecting from canLoad from a child route should work without error', async function () {
6398+
@customElement({ name: 'c-1', template: `c1` })
6399+
class C1 implements IRouteViewModel {
6400+
canLoad(_params: Params, _next: RouteNode, _current: RouteNode | null): NavigationInstruction {
6401+
return 'p-1/c-2';
6402+
}
6403+
}
6404+
6405+
@customElement({ name: 'c-2', template: `c2` })
6406+
class C2 { }
6407+
6408+
@customElement({ name: 'c-3', template: `c3` })
6409+
class C3 { }
6410+
6411+
@route({ routes: [C1, C2, C3] })
6412+
@customElement({ name: 'p-1', template: '<au-viewport></au-viewport>' })
6413+
class P1 { }
6414+
6415+
@route({ routes: [P1] })
6416+
@customElement({ name: 'ro-ot', template: '<au-viewport></au-viewport>' })
6417+
class Root { }
6418+
6419+
const { au, host, container } = await createFixture(Root, [C1, C2, C3, P1, Registration.instance(IKnownScopes, [/* Root.name, P1.name, C1.name, C2.name */])]);
6420+
const router = container.get(IRouter);
6421+
let errorCount = 0;
6422+
let cancelCount = 0;
6423+
const routerEvents = container.get(IRouterEvents);
6424+
routerEvents.subscribe('au:router:navigation-error', () => errorCount++);
6425+
routerEvents.subscribe('au:router:navigation-cancel', () => cancelCount++);
6426+
6427+
// round#1 - load c1 and redirect to c2 should take place
6428+
await router.load('p-1/c-1');
6429+
assert.html.textContent(host, 'c2');
6430+
assert.strictEqual(errorCount, 0, 'errorCount');
6431+
assert.strictEqual(cancelCount, 1, 'cancelCount');
6432+
6433+
// round#2 - load c3 and it should work a as expected
6434+
errorCount = 0;
6435+
cancelCount = 0;
6436+
await router.load('p-1/c-3');
6437+
assert.html.textContent(host, 'c3');
6438+
assert.strictEqual(errorCount, 0, 'errorCount');
6439+
assert.strictEqual(cancelCount, 0, 'cancelCount');
6440+
6441+
// round#3 - load c2 normally, and an it should be loaded
6442+
await router.load('p-1/c-2');
6443+
assert.html.textContent(host, 'c2');
6444+
assert.strictEqual(errorCount, 0, 'errorCount');
6445+
assert.strictEqual(cancelCount, 0, 'cancelCount');
6446+
6447+
// round#4 - cleanse before testing the c-1 -_ c-2 redirect scenario once again
6448+
await router.load('p-1/c-3');
6449+
assert.html.textContent(host, 'c3');
6450+
assert.strictEqual(errorCount, 0, 'errorCount');
6451+
assert.strictEqual(cancelCount, 0, 'cancelCount');
6452+
6453+
// round#5 - load c1 and redirect to c2 should take place
6454+
await router.load('p-1/c-1');
6455+
assert.html.textContent(host, 'c2');
6456+
assert.strictEqual(errorCount, 0, 'errorCount');
6457+
assert.strictEqual(cancelCount, 1, 'cancelCount');
6458+
6459+
await au.stop(true);
6460+
});
6461+
6462+
it('cancelling from canUnload from a child route should work without error', async function () {
6463+
@customElement({ name: 'c-1', template: `c1` })
6464+
class C1 implements IRouteViewModel {
6465+
canUnload(_next: RouteNode | null, _current: RouteNode): boolean | Promise<boolean> {
6466+
return false;
6467+
}
6468+
}
6469+
6470+
@customElement({ name: 'c-2', template: `c2` })
6471+
class C2 { }
6472+
6473+
@route({ routes: [C1, C2] })
6474+
@customElement({ name: 'p-1', template: '<au-viewport></au-viewport>' })
6475+
class P1 { }
6476+
6477+
@route({ routes: [P1] })
6478+
@customElement({ name: 'ro-ot', template: '<au-viewport></au-viewport>' })
6479+
class Root { }
6480+
6481+
const { au, host, container } = await createFixture(Root, [C1, C2, P1, Registration.instance(IKnownScopes, [/* Root.name, P1.name, C1.name, C2.name */])]);
6482+
const router = container.get(IRouter);
6483+
let errorCount = 0;
6484+
let cancelCount = 0;
6485+
const routerEvents = container.get(IRouterEvents);
6486+
routerEvents.subscribe('au:router:navigation-error', () => errorCount++);
6487+
routerEvents.subscribe('au:router:navigation-cancel', () => cancelCount++);
6488+
6489+
await router.load('p-1/c-1');
6490+
assert.html.textContent(host, 'c1');
6491+
assert.strictEqual(errorCount, 0, 'errorCount');
6492+
assert.strictEqual(cancelCount, 0, 'cancelCount');
6493+
6494+
await router.load('p-1/c-2');
6495+
assert.html.textContent(host, 'c1');
6496+
assert.strictEqual(errorCount, 0, 'errorCount');
6497+
assert.strictEqual(cancelCount, 1, 'cancelCount');
6498+
6499+
await au.stop(true);
6500+
});
63966501
});

packages/router-lite/src/events.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export const enum Events {
158158
vpaUnexpectedDeactivation = 3351,
159159
vpaUnexpectedState = 3352,
160160
vpaUnexpectedGuardsResult = 3353,
161+
vpaCanLoadGuardsResult = 3354,
161162
// #endregion
162163
// #region instruction
163164
instrInvalid = 3400,
@@ -331,6 +332,7 @@ const eventMessageMap: Record<Events, string> = {
331332
[Events.vpaUnexpectedDeactivation]: 'Unexpected viewport deactivation outside of a transition context at %s',
332333
[Events.vpaUnexpectedState]: 'Unexpected state at %s of %s',
333334
[Events.vpaUnexpectedGuardsResult]: 'Unexpected guardsResult %s at %s',
335+
[Events.vpaCanLoadGuardsResult]: 'canLoad returned redirect result %s by the component agent %s',
334336
// #endregion
335337

336338
// #region instruction

packages/router-lite/src/router.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,12 @@ export class Router {
646646
for (const node of all) {
647647
node.context.vpa._swap(tr, b);
648648
}
649+
})._continueWith(b => {
650+
// it is possible that some of the child routes are cancelling the navigation
651+
if (tr.guardsResult !== true) {
652+
b._push();
653+
this._cancelNavigation(tr);
654+
}
649655
})._continueWith(() => {
650656
if (__DEV__) trace(logger, Events.rtrRunFinalizing);
651657
// order doesn't matter for this operation

packages/router-lite/src/viewport-agent.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,10 @@ export class ViewportAgent {
279279
this._unexpectedState('canLoad');
280280
}
281281
})._continueWith(b1 => {
282+
if (tr.guardsResult !== true) {
283+
if (__DEV__) trace(logger, Events.vpaCanLoadGuardsResult, tr.guardsResult, this._nextCA);
284+
return;
285+
}
282286
const next = this._nextNode!;
283287
switch (this._$plan) {
284288
case 'none':
@@ -649,6 +653,7 @@ export class ViewportAgent {
649653
});
650654
}
651655
})._continueWith(b1 => {
656+
if (tr.guardsResult !== true) return;
652657
for (const node of newChildren) {
653658
tr._run(() => {
654659
b1._push();
@@ -658,6 +663,7 @@ export class ViewportAgent {
658663
});
659664
}
660665
})._continueWith(b1 => {
666+
if (tr.guardsResult !== true) return;
661667
for (const node of newChildren) {
662668
tr._run(() => {
663669
b1._push();

0 commit comments

Comments
 (0)