Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't change anchor links to use the history API if the router doesn't have a match #18

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
Cleanup AnchorHandler and add tests
  • Loading branch information
jbuckner committed Jan 16, 2021
commit 9f5b4678c3770df9a3ce62ccedb93471571d9b1a
16 changes: 4 additions & 12 deletions src/lib/router-slot.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { GLOBAL_ROUTER_EVENTS_TARGET, ROUTER_SLOT_TAG_NAME } from "./config";
import { Cancel, EventListenerSubscription, GlobalRouterEvent, IPathFragments, IRoute, IRouteMatch, IRouterSlot, IRoutingInfo, Params, PathFragment, RouterSlotEvent } from "./model";
import { addListener, AnchorHandler, constructAbsolutePath, dispatchGlobalRouterEvent, dispatchRouteChangeEvent, ensureHistoryEvents, handleRedirect, isRedirectRoute, isResolverRoute, matchRoutes, pathWithoutBasePath, queryParentRouterSlot, removeListeners, resolvePageComponent, shouldNavigate } from "./util";
import { addListener, AnchorHandler, constructAbsolutePath, dispatchGlobalRouterEvent, dispatchRouteChangeEvent, ensureHistoryEvents, handleRedirect, IAnchorHandler, isRedirectRoute, isResolverRoute, matchRoutes, pathWithoutBasePath, queryParentRouterSlot, removeListeners, resolvePageComponent, shouldNavigate } from "./util";

const template = document.createElement("template");
template.innerHTML = `<slot></slot>`;
Expand Down Expand Up @@ -89,7 +89,7 @@ export class RouterSlot<D = any, P = any> extends HTMLElement implements IRouter
/**
* The anchor link handler for the router slot
*/
private anchorHandler?: AnchorHandler;
private anchorHandler?: IAnchorHandler;

/**
* Hooks up the element.
Expand Down Expand Up @@ -188,19 +188,11 @@ export class RouterSlot<D = any, P = any> extends HTMLElement implements IRouter
*/
protected setupAnchorListener(): void {
this.anchorHandler = new AnchorHandler(this);
window.addEventListener(
'click',
this.anchorHandler?.handleEvent.bind(this)
);
this.anchorHandler?.setup();
}

protected detachAnchorListener(): void {
if (this.anchorHandler) {
window.removeEventListener(
'click',
this.anchorHandler.handleEvent
);
}
this.anchorHandler?.teardown();
}

/**
Expand Down
27 changes: 23 additions & 4 deletions src/lib/util/anchor.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
import { IRouterSlot } from "../model";
import type { IRouterSlot } from "../model";

export interface IAnchorHandler {
setup(): void;
teardown(): void;
}

/**
* The AnchorHandler allows the RouterSlot to observe all anchor clicks
* and either handle the click or let the browser handle it.
*/
export class AnchorHandler {
export class AnchorHandler implements IAnchorHandler {
routerSlot?: IRouterSlot;

constructor(routerSlot?: IRouterSlot) {
this.routerSlot = routerSlot;
}

handleEvent(e: MouseEvent) {
setup(): void {
window.addEventListener(
'click',
(e) => this.handleEvent(e)
);
}

teardown(): void {
window.removeEventListener(
'click',
(e) => this.handleEvent(e)
);
}

private handleEvent(e: MouseEvent) {
// Find the target by using the composed path to get the element through the shadow boundaries.
const $anchor = ("composedPath" in e as any) ? e.composedPath().find($elem => $elem instanceof HTMLAnchorElement) : e.target;

Expand All @@ -38,7 +57,7 @@ export class AnchorHandler {
}

// Remove the origin from the start of the HREF to get the path
const path = $anchor.pathname;
const path = `${$anchor.pathname}${$anchor.search}`;

// Prevent the default behavior
e.preventDefault();
Expand Down
48 changes: 41 additions & 7 deletions src/test/anchor.test.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,49 @@
import { ensureAnchorHistory } from "../lib/util/anchor";
import { AnchorHandler, RouterSlot } from "../lib";
import { ensureHistoryEvents } from "../lib/util/history";
import { path } from "../lib/util/url";
import { addBaseTag, clearHistory } from "./test-helpers";

const testPath = `/about`;

describe("anchor", () => {
describe("AnchorHandler", () => {
const {expect} = chai;
let $anchor!: HTMLAnchorElement;
let $slot = new RouterSlot();
let $anchorHandler = new AnchorHandler($slot);

const addTestRoute = () => {
$slot.add([
{
path: testPath,
pathMatch: "suffix",
component: () => document.createElement("div")
}
])
}

before(() => {
ensureHistoryEvents();
ensureAnchorHistory();
addBaseTag();
document.body.appendChild($slot);
$anchorHandler.setup();
});
beforeEach(() => {
document.body.innerHTML = `
<a id="anchor" href="${testPath}">Anchor</a>
`;

$anchor = document.body.querySelector<HTMLAnchorElement>("#anchor")!;
});
afterEach(() => {
$slot.clear();
});
after(() => {
clearHistory();
$anchorHandler.teardown();
});

it("[ensureAnchorHistory] should change anchors to use history API", done => {
it("[AnchorHandler] should change anchors to use history API", done => {
addTestRoute();

window.addEventListener("pushstate", () => {
expect(path({end: false})).to.equal(testPath);
done();
Expand All @@ -34,7 +52,9 @@ describe("anchor", () => {
$anchor.click();
});

it("[ensureAnchorHistory] should not change anchors with target _blank", done => {
it("[AnchorHandler] should not change anchors with target _blank", done => {
addTestRoute();

window.addEventListener("pushstate", () => {
expect(true).to.equal(false);
});
Expand All @@ -44,7 +64,9 @@ describe("anchor", () => {
done();
});

it("[ensureAnchorHistory] should not change anchors with [data-router-slot]='disabled'", done => {
it("[AnchorHandler] should not change anchors with [data-router-slot]='disabled'", done => {
addTestRoute();

window.addEventListener("pushstate", () => {
expect(true).to.equal(false);
});
Expand All @@ -53,4 +75,16 @@ describe("anchor", () => {
$anchor.click();
done();
});

it("[AnchorHandler] should not change anchors that are not supported by the router", done => {
// there are no routes added to the $slot in this test
// so the router will not attempt to handle it

window.addEventListener("pushstate", () => {
expect(true).to.equal(false);
});

$anchor.click();
done();
});
});