Skip to content
This repository was archived by the owner on Sep 28, 2023. It is now read-only.

Commit 6233b91

Browse files
author
James Fox
authored
require initialization and only navigate to catchAll if defined (#17)
## Summary This PR requires consumers to explicitly call `initialize` before consuming the instance. This allows finer control over when the event listeners are added. We also enable true cleanup by updating `reset` to remove its `onpopstate` event listener _correctly_. Finally, only navigate to the catchall route if one is defined (prevents runaway navigation if a URL isn't found). ## Test Plan Added unit tests here for all new behavior and verified it works by `yarn link`ing in the monolith
1 parent 1673dd5 commit 6233b91

File tree

4 files changed

+104
-19
lines changed

4 files changed

+104
-19
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6+
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
### Breaking Changes
11+
12+
- Require explicit initialization via `initialize` before using. (See #17)
13+
14+
### Fixes
15+
16+
- Ensure that `reset` cleans up the `onpopstate` listener. (See #17)
17+
- Only navigate to the `catchAll` path if one has been provided. (See #17)
18+
19+
20+
## [1.4.7] - April 5th, 2019
21+

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@optimizely/nuclear-router",
3-
"version": "1.4.7",
3+
"version": "2.0.0",
44
"description": "NuclearJS Router",
55
"main": "dist/main.js",
66
"scripts": {

src/Router.js

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,19 @@ export default class Router {
3333
base: '',
3434
}, opts)
3535

36-
this.setInitialState();
37-
3836
this.onRouteStart = this.opts.onRouteStart;
39-
this.onRouteComplete = this.opts.onRouteComplete
37+
this.onRouteComplete = this.opts.onRouteComplete;
38+
this.__onpopstate = this.__onpopstate.bind(this);
39+
}
4040

41-
WindowEnv.addEventListener('popstate', this.__onpopstate.bind(this))
41+
initialize() {
42+
this.setInitialState();
43+
WindowEnv.addEventListener('popstate', this.__onpopstate);
44+
}
45+
46+
reset() {
47+
this.setInitialState();
48+
WindowEnv.removeEventListener('popstate', this.__onpopstate);
4249
}
4350

4451
setInitialState() {
@@ -100,13 +107,10 @@ export default class Router {
100107
this.__dispatch(canonicalPath, 'replace')
101108
}
102109

103-
reset() {
104-
this.setInitialState();
105-
WindowEnv.removeEventListener('popstate', this.__onpopstate)
106-
}
107-
108110
catchall() {
109-
WindowEnv.navigate(this.__catchallPath)
111+
if (typeof this.__catchallPath === 'string') {
112+
WindowEnv.navigate(this.__catchallPath);
113+
}
110114
}
111115

112116
/**

test/Router.spec.js

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,29 +25,78 @@ describe('Router', () => {
2525
sinon.stub(HistoryEnv, 'pushState')
2626
sinon.stub(HistoryEnv, 'replaceState')
2727
sinon.stub(DocumentEnv, 'getTitle').returns(pageTitle)
28+
sinon.stub(WindowEnv, 'addEventListener')
29+
sinon.stub(WindowEnv, 'removeEventListener')
2830
})
2931

3032
afterEach(() => {
3133
HistoryEnv.pushState.restore()
3234
HistoryEnv.replaceState.restore()
3335
DocumentEnv.getTitle.restore()
36+
WindowEnv.addEventListener.restore()
37+
WindowEnv.removeEventListener.restore()
3438
})
3539

3640
describe('construction', () => {
37-
beforeEach(() => {
38-
sinon.stub(WindowEnv, 'addEventListener')
39-
})
40-
41-
afterEach(() => {
42-
WindowEnv.addEventListener.restore()
43-
})
44-
4541
it('should create a Router instance', () => {
4642
let router = new Router
4743
expect(router instanceof Router).toBe(true)
4844
})
4945
})
5046

47+
describe('initialization and reset', function() {
48+
let emptyRouter;
49+
beforeEach(function() {
50+
emptyRouter = new Router({});
51+
});
52+
53+
context('initialize', function() {
54+
it('should not define state before initialization', function() {
55+
expect(emptyRouter.__fromPath).toBe(undefined);
56+
expect(emptyRouter.__routes).toBe(undefined);
57+
expect(emptyRouter.__currentCanonicalPath).toBe(undefined);
58+
sinon.assert.notCalled(WindowEnv.addEventListener);
59+
});
60+
61+
it('should set initial state and add popstate listener upon initialization', function() {
62+
emptyRouter.initialize();
63+
expect(emptyRouter.__fromPath).toBe(null);
64+
expect(emptyRouter.__routes.length).toBe(0);
65+
expect(emptyRouter.__currentCanonicalPath).toBe(null);
66+
sinon.assert.calledOnce(WindowEnv.addEventListener);
67+
sinon.assert.calledWith(WindowEnv.addEventListener, 'popstate');
68+
});
69+
})
70+
71+
context('reset', function() {
72+
beforeEach(function() {
73+
emptyRouter.initialize();
74+
emptyRouter.registerRoutes([
75+
{
76+
match: '/the_route',
77+
handle: [
78+
(ctx, next) => {
79+
next()
80+
},
81+
],
82+
},
83+
]);
84+
emptyRouter.go('/the_route');
85+
return getMacroTaskResolvedPromise();
86+
})
87+
it('should reset initial state and remove popstate listener', function() {
88+
expect(emptyRouter.__routes.length).toBe(1);
89+
sinon.assert.notCalled(WindowEnv.removeEventListener);
90+
emptyRouter.reset();
91+
expect(emptyRouter.__fromPath).toBe(null);
92+
expect(emptyRouter.__routes.length).toBe(0);
93+
expect(emptyRouter.__currentCanonicalPath).toBe(null);
94+
sinon.assert.calledOnce(WindowEnv.removeEventListener);
95+
sinon.assert.calledWith(WindowEnv.removeEventListener, 'popstate');
96+
});
97+
})
98+
});
99+
51100
describe('navigation', () => {
52101
let router
53102
let spy1, spy2, spy3
@@ -65,6 +114,7 @@ describe('Router', () => {
65114
},
66115
onRouteStart: onRouteStartSpy,
67116
})
117+
router.initialize();
68118

69119
spy1 = sinon.spy()
70120
spy2 = sinon.spy()
@@ -197,6 +247,16 @@ describe('Router', () => {
197247
sinon.assert.calledWithExactly(WindowEnv.navigate, '/404');
198248
})
199249
});
250+
251+
it('should not window navigate if the catchall route is not set', () => {
252+
router.__catchallPath = null;
253+
router.go('/abcdefg');
254+
255+
return getMacroTaskResolvedPromise()
256+
.then(() => {
257+
sinon.assert.notCalled(WindowEnv.navigate);
258+
})
259+
});
200260
});
201261

202262
describe('when a single route matches', function() {

0 commit comments

Comments
 (0)