+ const Route = () => (
+
{[...shouldIntercept, ...shouldNavigate].map((target, i) => {
const url = '/' + i + '/' + target;
if (target === null) return
target = {target + ''};
@@ -524,31 +525,32 @@ describe('Router', () => {
);
- let pushState;
-
- before(() => {
- pushState = sinon.spy(history, 'pushState');
- addEventListener('click', clickHandler);
- });
-
- after(() => {
- pushState.restore();
- removeEventListener('click', clickHandler);
- });
+ let triedToNavigate = false;
+ const handler = (e) => {
+ e.intercept();
+ if (e['preact-iso-ignored']) {
+ triedToNavigate = true;
+ }
+ }
beforeEach(async () => {
- render(
-
-
-
-
-
- ,
- scratch
- );
- Route.resetHistory();
- clickHandler.resetHistory();
- pushState.resetHistory();
+ const App = () => {
+ useEffect(() => {
+ navigation.addEventListener('navigate', handler);
+ return () => navigation.removeEventListener('navigate', handler);
+ }, []);
+
+ return (
+
+
+
+
+
+
+ );
+ }
+ render(
, scratch);
+ await sleep(10);
});
const getName = target => (target == null ? 'no target attribute' : `target="${target}"`);
@@ -563,9 +565,9 @@ describe('Router', () => {
el.click();
await sleep(1);
expect(loc).to.deep.include({ url });
- expect(Route).to.have.been.calledOnce;
- expect(pushState).to.have.been.calledWith(null, '', url);
- expect(clickHandler).to.have.been.called;
+ expect(triedToNavigate).to.be.false;
+
+ triedToNavigate = false;
});
}
@@ -577,9 +579,9 @@ describe('Router', () => {
if (!el) throw Error(`Unable to find link: ${sel}`);
el.click();
await sleep(1);
- expect(Route).not.to.have.been.called;
- expect(pushState).not.to.have.been.called;
- expect(clickHandler).to.have.been.called;
+ expect(triedToNavigate).to.be.true;
+
+ triedToNavigate = false;
});
}
});
@@ -588,8 +590,6 @@ describe('Router', () => {
const shouldIntercept = ['/app', '/app/deeper'];
const shouldNavigate = ['/site', '/site/deeper'];
- const clickHandler = sinon.fake(e => e.preventDefault());
-
const Links = () => (
<>
Internal Link
@@ -599,102 +599,81 @@ describe('Router', () => {
>
);
- let pushState;
-
- before(() => {
- pushState = sinon.spy(history, 'pushState');
- addEventListener('click', clickHandler);
- });
-
- after(() => {
- pushState.restore();
- removeEventListener('click', clickHandler);
- });
-
- beforeEach(async () => {
- clickHandler.resetHistory();
- pushState.resetHistory();
- });
+ let triedToNavigate = false;
+ const handler = (e) => {
+ e.intercept();
+ if (e['preact-iso-ignored']) {
+ triedToNavigate = true;
+ }
+ }
- it('should intercept clicks on links matching the `scope` props (string)', async () => {
- render(
-
-
-
- ,
- scratch
- );
+ it('should support the `scope` prop (string)', async () => {
+ const App = () => {
+ useEffect(() => {
+ navigation.addEventListener('navigate', handler);
+ return () => navigation.removeEventListener('navigate', handler);
+ }, []);
+
+ return (
+
+
+
+
+ );
+ }
+ render(
, scratch);
+ await sleep(10);
for (const url of shouldIntercept) {
scratch.querySelector(`a[href="${url}"]`).click();
await sleep(1);
expect(loc).to.deep.include({ url });
- expect(pushState).to.have.been.calledWith(null, '', url);
- expect(clickHandler).to.have.been.called;
+ expect(triedToNavigate).to.be.false;
- pushState.resetHistory();
- clickHandler.resetHistory();
+ triedToNavigate = false;
}
- });
-
- it('should allow default browser navigation for links not matching the `scope` props (string)', async () => {
- render(
-
-
-
- ,
- scratch
- );
for (const url of shouldNavigate) {
scratch.querySelector(`a[href="${url}"]`).click();
await sleep(1);
- expect(pushState).not.to.have.been.called;
- expect(clickHandler).to.have.been.called;
+ expect(triedToNavigate).to.be.true;
- pushState.resetHistory();
- clickHandler.resetHistory();
+ triedToNavigate = false;
}
});
- it('should intercept clicks on links matching the `scope` props (regex)', async () => {
- render(
-
-
-
- ,
- scratch
- );
+ it('should support the `scope` prop (regex)', async () => {
+ const App = () => {
+ useEffect(() => {
+ navigation.addEventListener('navigate', handler);
+ return () => navigation.removeEventListener('navigate', handler);
+ }, []);
+
+ return (
+
+
+
+
+ );
+ }
+ render(
, scratch);
+ await sleep(10);
for (const url of shouldIntercept) {
scratch.querySelector(`a[href="${url}"]`).click();
await sleep(1);
expect(loc).to.deep.include({ url });
- expect(pushState).to.have.been.calledWith(null, '', url);
- expect(clickHandler).to.have.been.called;
+ expect(triedToNavigate).to.be.false;
- pushState.resetHistory();
- clickHandler.resetHistory();
+ triedToNavigate = false;
}
- });
-
- it('should allow default browser navigation for links not matching the `scope` props (regex)', async () => {
- render(
-
-
-
- ,
- scratch
- );
for (const url of shouldNavigate) {
scratch.querySelector(`a[href="${url}"]`).click();
await sleep(1);
- expect(pushState).not.to.have.been.called;
- expect(clickHandler).to.have.been.called;
+ expect(triedToNavigate).to.be.true;
- pushState.resetHistory();
- clickHandler.resetHistory();
+ triedToNavigate = false;
}
});
});
@@ -702,7 +681,14 @@ describe('Router', () => {
it('should scroll to top when navigating forward', async () => {
const scrollTo = sinon.spy(window, 'scrollTo');
- const Route = sinon.fake(() =>
);
+ const Route = sinon.fake(
+ () => (
+
+ )
+ );
+
render(
@@ -717,7 +703,7 @@ describe('Router', () => {
expect(Route).to.have.been.calledOnce;
Route.resetHistory();
- loc.route('/programmatic');
+ navigation.navigate('/programmatic');
await sleep(1);
expect(loc).to.deep.include({ url: '/programmatic' });
@@ -740,14 +726,13 @@ describe('Router', () => {
});
it('should ignore clicks on document fragment links', async () => {
- const pushState = sinon.spy(history, 'pushState');
-
const Route = sinon.fake(
() =>
);
+
render(
@@ -760,7 +745,6 @@ describe('Router', () => {
scratch
);
- expect(Route).to.have.been.calledOnce;
Route.resetHistory();
scratch.querySelector('a[href="#foo"]').click();
@@ -769,7 +753,6 @@ describe('Router', () => {
// NOTE: we don't (currently) propagate in-page anchor navigations into context, to avoid useless renders.
expect(loc).to.deep.include({ url: '/' });
expect(Route).not.to.have.been.called;
- expect(pushState).not.to.have.been.called;
expect(location.hash).to.equal('#foo');
scratch.querySelector('a[href="/other#bar"]').click();
@@ -777,14 +760,10 @@ describe('Router', () => {
expect(Route).to.have.been.calledOnce;
expect(loc).to.deep.include({ url: '/other#bar', path: '/other' });
- expect(pushState).to.have.been.called;
expect(location.hash).to.equal('#bar');
-
- pushState.restore();
});
it('should normalize children', async () => {
- const pushState = sinon.spy(history, 'pushState');
const Route = sinon.fake(() => foo);
const routes = ['/foo', '/bar'];
@@ -807,9 +786,6 @@ describe('Router', () => {
expect(Route).to.have.been.calledOnce;
expect(loc).to.deep.include({ url: '/foo#foo', path: '/foo' });
- expect(pushState).to.have.been.called;
-
- pushState.restore();
});
it('should match nested routes', async () => {
@@ -866,12 +842,37 @@ describe('Router', () => {
});
it('should replace the current URL', async () => {
- const pushState = sinon.spy(history, 'pushState');
- const replaceState = sinon.spy(history, 'replaceState');
+ render(
+
+
+ null} />
+ null} />
+ null} />
+
+
+ ,
+ scratch
+ );
+
+ navigation.navigate('/foo');
+ navigation.navigate('/bar', { history: 'replace' });
+
+ const entries = navigation.entries();
+
+ // Top of the stack
+ const last = new URL(entries[entries.length - 1].url);
+ expect(last.pathname).to.equal('/bar');
+ // Entry before
+ const secondLast = new URL(entries[entries.length - 2].url);
+ expect(secondLast.pathname).to.equal('/');
+ });
+
+ it('should support navigating backwards and forwards', async () => {
render(
+ null} />
null} />
@@ -879,12 +880,20 @@ describe('Router', () => {
scratch
);
- loc.route("/foo", true);
- expect(pushState).not.to.have.been.called;
- expect(replaceState).to.have.been.calledWith(null, "", "/foo");
+ navigation.navigate('/foo');
+ await sleep(10);
+
+ expect(loc).to.deep.include({ url: '/foo', path: '/foo', searchParams: {} });
+
+ navigation.back();
+ await sleep(10);
+
+ expect(loc).to.deep.include({ url: '/', path: '/', searchParams: {} });
+
+ navigation.forward();
+ await sleep(10);
- pushState.restore();
- replaceState.restore();
+ expect(loc).to.deep.include({ url: '/foo', path: '/foo', searchParams: {} });
});
});