navigation-stack 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/.babelrc.cjs +17 -0
  2. package/.eslintignore +8 -0
  3. package/.eslintrc.cjs +10 -0
  4. package/.github/workflows/main.yml +39 -0
  5. package/.yarn/install-state.gz +0 -0
  6. package/.yarnrc.yml +1 -0
  7. package/CODE_OF_CONDUCT.md +77 -0
  8. package/LICENSE +21 -0
  9. package/README.md +249 -0
  10. package/codecov.yml +1 -0
  11. package/karma.conf.cjs +63 -0
  12. package/lib/cjs/ActionTypes.js +14 -0
  13. package/lib/cjs/Actions.js +27 -0
  14. package/lib/cjs/LocationStateStorage.js +60 -0
  15. package/lib/cjs/addNavigationBlocker.js +7 -0
  16. package/lib/cjs/basePath.js +58 -0
  17. package/lib/cjs/createMiddlewares.js +43 -0
  18. package/lib/cjs/createSearchFromQuery.js +13 -0
  19. package/lib/cjs/environment/BrowserEnvironment.js +111 -0
  20. package/lib/cjs/environment/MemoryEnvironment.js +150 -0
  21. package/lib/cjs/environment/ServerEnvironment.js +53 -0
  22. package/lib/cjs/getLocationUrl.js +20 -0
  23. package/lib/cjs/index.js +30 -0
  24. package/lib/cjs/isPromise.js +9 -0
  25. package/lib/cjs/locationReducer.js +13 -0
  26. package/lib/cjs/middleware/createBasePathMiddleware.js +24 -0
  27. package/lib/cjs/middleware/createEnvironmentMiddleware.js +58 -0
  28. package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +128 -0
  29. package/lib/cjs/middleware/createTransformLocationMiddleware.js +38 -0
  30. package/lib/cjs/middleware/navigationActionMiddleware.js +37 -0
  31. package/lib/cjs/middleware/normalizeInputLocationMiddleware.js +27 -0
  32. package/lib/cjs/navigationBlockers.js +146 -0
  33. package/lib/cjs/normalizeInputLocation.js +46 -0
  34. package/lib/cjs/onlyAllowedOnClientSide.js +10 -0
  35. package/lib/cjs/parseLocationUrl.js +39 -0
  36. package/lib/cjs/parseQueryFromSearch.js +16 -0
  37. package/lib/esm/ActionTypes.js +9 -0
  38. package/lib/esm/Actions.js +21 -0
  39. package/lib/esm/LocationStateStorage.js +53 -0
  40. package/lib/esm/addNavigationBlocker.js +2 -0
  41. package/lib/esm/basePath.js +53 -0
  42. package/lib/esm/createMiddlewares.js +37 -0
  43. package/lib/esm/createSearchFromQuery.js +8 -0
  44. package/lib/esm/environment/BrowserEnvironment.js +104 -0
  45. package/lib/esm/environment/MemoryEnvironment.js +143 -0
  46. package/lib/esm/environment/ServerEnvironment.js +46 -0
  47. package/lib/esm/getLocationUrl.js +15 -0
  48. package/lib/esm/index.js +12 -0
  49. package/lib/esm/isPromise.js +4 -0
  50. package/lib/esm/locationReducer.js +7 -0
  51. package/lib/esm/middleware/createBasePathMiddleware.js +19 -0
  52. package/lib/esm/middleware/createEnvironmentMiddleware.js +52 -0
  53. package/lib/esm/middleware/createNavigationBlockerMiddleware.js +123 -0
  54. package/lib/esm/middleware/createTransformLocationMiddleware.js +33 -0
  55. package/lib/esm/middleware/navigationActionMiddleware.js +32 -0
  56. package/lib/esm/middleware/normalizeInputLocationMiddleware.js +22 -0
  57. package/lib/esm/navigationBlockers.js +138 -0
  58. package/lib/esm/normalizeInputLocation.js +41 -0
  59. package/lib/esm/onlyAllowedOnClientSide.js +5 -0
  60. package/lib/esm/parseLocationUrl.js +33 -0
  61. package/lib/esm/parseQueryFromSearch.js +11 -0
  62. package/lib/index.d.ts +301 -0
  63. package/package.json +100 -0
  64. package/renovate.json +3 -0
  65. package/src/ActionTypes.js +9 -0
  66. package/src/Actions.js +26 -0
  67. package/src/LocationStateStorage.js +59 -0
  68. package/src/addNavigationBlocker.js +2 -0
  69. package/src/basePath.js +65 -0
  70. package/src/createMiddlewares.js +41 -0
  71. package/src/createSearchFromQuery.js +9 -0
  72. package/src/environment/BrowserEnvironment.js +109 -0
  73. package/src/environment/MemoryEnvironment.js +151 -0
  74. package/src/environment/ServerEnvironment.js +54 -0
  75. package/src/getLocationUrl.js +12 -0
  76. package/src/index.js +12 -0
  77. package/src/isPromise.js +8 -0
  78. package/src/locationReducer.js +8 -0
  79. package/src/middleware/createBasePathMiddleware.js +20 -0
  80. package/src/middleware/createEnvironmentMiddleware.js +57 -0
  81. package/src/middleware/createNavigationBlockerMiddleware.js +128 -0
  82. package/src/middleware/createTransformLocationMiddleware.js +29 -0
  83. package/src/middleware/navigationActionMiddleware.js +27 -0
  84. package/src/middleware/normalizeInputLocationMiddleware.js +21 -0
  85. package/src/navigationBlockers.js +158 -0
  86. package/src/normalizeInputLocation.js +44 -0
  87. package/src/onlyAllowedOnClientSide.js +5 -0
  88. package/src/parseLocationUrl.js +40 -0
  89. package/src/parseQueryFromSearch.js +12 -0
  90. package/test/.eslintrc.cjs +17 -0
  91. package/test/Action.test.js +72 -0
  92. package/test/ActionTypes.test.js +13 -0
  93. package/test/LocationStateStorage.test.js +75 -0
  94. package/test/basePath.test.js +158 -0
  95. package/test/createMiddlewares.test.js +62 -0
  96. package/test/environment/BrowserEnvironment.test.js +165 -0
  97. package/test/environment/MemoryEnvironment.test.js +218 -0
  98. package/test/environment/ServerEnvironment.test.js +23 -0
  99. package/test/getLocationUrl.test.js +33 -0
  100. package/test/helpers.js +34 -0
  101. package/test/index.js +44 -0
  102. package/test/index.test.js +20 -0
  103. package/test/locationReducer.test.js +42 -0
  104. package/test/middleware/createBasePathMiddleware.test.js +67 -0
  105. package/test/middleware/createNavigationBlockerMiddleware.test.js +472 -0
  106. package/test/middleware/createTransformLocationMiddleware.test.js +44 -0
  107. package/test/middleware/navigationActionMiddleware.test.js +74 -0
  108. package/test/middleware/normalizeInputLocationMiddleware.test.js +62 -0
  109. package/test/normalizeInputLocation.test.js +81 -0
  110. package/test/parseLocationUrl.test.js +30 -0
  111. package/types/.eslintrc.cjs +3 -0
  112. package/types/index.d.ts +301 -0
  113. package/types/tsconfig.json +14 -0
package/.babelrc.cjs ADDED
@@ -0,0 +1,17 @@
1
+ module.exports = (api) => ({
2
+ presets: [
3
+ [
4
+ '@4c',
5
+ {
6
+ modules: api.env() === 'esm' ? false : 'commonjs',
7
+ },
8
+ ],
9
+ ],
10
+ plugins: [api.env() !== 'esm' && 'add-module-exports'].filter(Boolean),
11
+
12
+ env: {
13
+ test: {
14
+ plugins: ['istanbul'],
15
+ },
16
+ },
17
+ });
package/.eslintignore ADDED
@@ -0,0 +1,8 @@
1
+ !.babelrc.js
2
+ !.eslintrc.js
3
+
4
+ **/*.d.ts
5
+ **/coverage/
6
+ **/lib/
7
+ **/node_modules/
8
+ README.md
package/.eslintrc.cjs ADDED
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ extends: ['4catalyzer', 'prettier'],
3
+ plugins: ['prettier'],
4
+ env: {
5
+ browser: true,
6
+ },
7
+ rules: {
8
+ 'prettier/prettier': 'error',
9
+ },
10
+ };
@@ -0,0 +1,39 @@
1
+ name: Build and test
2
+ on:
3
+ push:
4
+ branches: [master]
5
+ pull_request:
6
+ branches: [master]
7
+
8
+ env:
9
+ DISPLAY: :99.0
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ strategy:
15
+ matrix:
16
+ browser: ['ChromeCi', 'Firefox']
17
+ node-version: [16.x]
18
+
19
+ steps:
20
+ - uses: actions/checkout@v2
21
+ - name: Use Node.js ${{ matrix.node-version }}
22
+ uses: actions/setup-node@v1
23
+ with:
24
+ node-version: ${{ matrix.node-version }}
25
+
26
+ - name: Setup firefox
27
+ if: ${{ matrix.browser == 'Firefox' }}
28
+ uses: browser-actions/setup-firefox@latest
29
+ with:
30
+ firefox-version: 'latest'
31
+ - name: Setup xvfb
32
+ run: |
33
+ sudo apt-get install xvfb
34
+ Xvfb $DISPLAY -screen 0 1024x768x24 > /dev/null 2>&1 &
35
+ - run: yarn install --frozen-lockfile
36
+ - env:
37
+ BROWSER: ${{ matrix.browser }}
38
+ run: yarn test --coverage
39
+ - run: node_modules/.bin/codecov
Binary file
package/.yarnrc.yml ADDED
@@ -0,0 +1 @@
1
+ nodeLinker: node-modules
@@ -0,0 +1,77 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and free environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a censorship-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ education, socio-economic status, nationality, personal appearance, race,
10
+ religion, or sexual identity and orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating an open and free environment
15
+ include:
16
+
17
+ - Not constraining the language to be "welcoming" or "inclusive"
18
+ - Not demanding show of empathy towards other community members
19
+ - Not dictating anyone to be respectful of differing viewpoints and experiences
20
+ - Not forcing anyone to change their views or opinions regardless of those
21
+ - Not intimidating other people into accepting your own views or opinions
22
+ - Not blackmailing other people to disclose their personal views or opinions
23
+ - Not constraining other people from publishing their personal views or opinions in an unintrusive way
24
+ - Focusing on what is best for the ecosystem
25
+
26
+ Examples of acceptable behavior by participants include:
27
+
28
+ - The use of sexualized language
29
+ - Occasional trolling or insulting comments that are not completely off-topic
30
+
31
+ Examples of unacceptable behavior by participants include:
32
+
33
+ - Publishing others' private information, such as a physical or electronic address, without explicit permission
34
+ - Unwelcome sexual attention or advances
35
+ - Public harassment or personal attacks when carried out in an bold or intrusive way
36
+ - Private harassment
37
+ - Any actions that are in violation of the local laws or otherwise considered illegal
38
+ - Other conduct which could reasonably be considered inappropriate in an open and free setting
39
+
40
+ ## Our Responsibilities
41
+
42
+ Project maintainers are responsible for clarifying the standards of acceptable
43
+ behavior and are free to take appropriate and fair corrective action in
44
+ response to any instances of unacceptable behavior.
45
+
46
+ Project maintainers have the right and authority to remove, edit, or
47
+ reject comments, commits, code, wiki edits, issues, and other contributions
48
+ that are not aligned to this Code of Conduct, or to ban temporarily or
49
+ permanently any contributor for other behaviors that they deem inappropriate,
50
+ threatening, offensive, or harmful.
51
+
52
+ ## Scope
53
+
54
+ This Code of Conduct applies both within project spaces and in public spaces
55
+ when an individual is representing the project or its community. Examples of
56
+ representing a project or community include using an official project e-mail
57
+ address, posting via an official social media account, or acting as an appointed
58
+ representative at an online or offline event. Representation of a project may be
59
+ further defined and clarified by project maintainers.
60
+
61
+ ## Enforcement
62
+
63
+ Instances of unacceptable behavior may be reported by contacting the project team.
64
+ The complaints will likely be reviewed and investigated and may result in a response that
65
+ is deemed necessary and appropriate to the circumstances. The project team should maintain confidentiality with regard to the reporter of an incident.
66
+ Further details of specific enforcement policies may be posted separately.
67
+
68
+ Project maintainers who do not follow the Code of Conduct in good
69
+ faith may face temporary or permanent repercussions as determined by other
70
+ members of the project's leadership.
71
+
72
+ ## Attribution
73
+
74
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
75
+ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
76
+
77
+ [homepage]: https://www.contributor-covenant.org
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2016 4Catalyzer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,249 @@
1
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
2
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
3
+ **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
4
+
5
+ - [navigation-stack](#navigation-stack)
6
+ - [Install](#install)
7
+ - [Use](#use)
8
+ - [Current Location](#current-location)
9
+ - [Why Redux?](#why-redux)
10
+ - [Environment](#environment)
11
+ - [Base Path](#base-path)
12
+ - [Location State Storage](#location-state-storage)
13
+ - [Block Navigation](#block-navigation)
14
+ - [Utility](#utility)
15
+ - [Development](#development)
16
+
17
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
18
+
19
+ # navigation-stack
20
+
21
+ [![npm version](https://img.shields.io/npm/v/navigation-stack.svg?style=flat-square)](https://www.npmjs.com/package/navigation-stack)
22
+ [![npm downloads](https://img.shields.io/npm/dm/navigation-stack.svg?style=flat-square)](https://www.npmjs.com/package/navigation-stack)
23
+
24
+ Handles web browser navigation in a web application.
25
+
26
+ Originally forked from [`farce`](http://npmjs.com/package/farce) package to fix a [bug](https://github.com/4Catalyzer/farce/issues/483).
27
+
28
+ ## Install
29
+
30
+ ```
31
+ npm install navigation-stack
32
+ ```
33
+
34
+ ## Use
35
+
36
+ `navigation-stack` provides "middlewares", "actions" and a "reducer" that could be used with `redux` or any other `redux`-compatible package such as [`mini-redux`](https://www.npmjs.com/package/mini-redux).
37
+
38
+ ```js
39
+ import { createStore, applyMiddleware } from 'redux'
40
+
41
+ import {
42
+ createMiddlewares,
43
+ locationReducer,
44
+ Actions,
45
+ BrowserEnvironment
46
+ } from 'navigation-stack'
47
+
48
+ const store = createStore(
49
+ locationReducer, // Reducer function. For example, `locationReducer()`.
50
+ applyMiddleware(...createMiddlewares(new BrowserEnvironment()))
51
+ )
52
+
53
+ store.dispatch(Actions.init())
54
+ ```
55
+
56
+ After that, dispatch any of the `Actions` in order to navigate.
57
+
58
+ ```js
59
+ // To navigate to a new page.
60
+ store.dispatch(Actions.push('/new/location'))
61
+
62
+ // To redirect to a new page.
63
+ store.dispatch(Actions.replace('/new/location'))
64
+
65
+ // To go back.
66
+ store.dispatch(Actions.shift(-1))
67
+
68
+ // To go forward.
69
+ store.dispatch(Actions.shift(1))
70
+ ```
71
+
72
+ To view the current location:
73
+
74
+ ```js
75
+ // When `locationReducer()` is used,
76
+ // `store.getState()` is the current location.
77
+ console.log(store.getState())
78
+ ```
79
+
80
+ (optional) (advanced) Stop and clean up:
81
+
82
+ ```js
83
+ store.dispatch(Actions.dispose())
84
+ ```
85
+
86
+ ## Current Location
87
+
88
+ To track the current location, the application could listen to `ActionTypes.UPDATE` action. The `payload` of the action is the current location.
89
+
90
+ For example, below is the source code for the default `locationReducer`.
91
+
92
+ ```js
93
+ import { ActionTypes } from 'navigation-stack'
94
+
95
+ // With this reducer, `state` would always tell the current location.
96
+ function reducer(state, action) {
97
+ if (action.type === ActionTypes.UPDATE) {
98
+ // `action.payload` is the current location.
99
+ return action.payload
100
+ }
101
+ return state
102
+ }
103
+ ```
104
+
105
+ Calling `store.dispatch(Actions.init())` will trigger the initial `ActionTypes.UPDATE` action which will set the current location. From then on, the current location will always stay in sync with the web browser's URL bar.
106
+
107
+ The current location will also "magically" be updated when the user clicks "Back" or "Forward" button in the web browser.
108
+
109
+ ## Why Redux?
110
+
111
+ Why complicate things by providing "middlewares", "actions" and a "reducer" when it could be just a conventional API? That's because always knowing the "current location" means having to deal with "state management" in one way or another, and the simplest and most popular "state management" toolkit to date seems to be Redux.
112
+
113
+ If it was just about dispatching the `Actions` then of course it wouldn't require any "state management". But it's the "get current location" piece that changes the whole picture. One could say that using Redux for such a simple task is an overkill but actually reinventing a wheel is what I would consider "overkill". It's like crafting your own screwdriver just because the one from Walmart feels too bulky.
114
+
115
+ ## Environment
116
+
117
+ ```js
118
+ import {
119
+ BrowserEnvironment,
120
+ ServerEnvironment,
121
+ MemoryEnvironment
122
+ } from 'navigation-stack'
123
+
124
+ new BrowserEnvironment()
125
+ new ServerEnvironment('/location-url')
126
+ new MemoryEnvironment('/location-url')
127
+ ```
128
+
129
+ - Use `BrowserEnvironment` in a web browser.
130
+ - Use `ServerEnvironment` in server-side rendering.
131
+ - Use `MemoryEnvironment` in tests.
132
+ - `MemoryEnvironment` supports an optional second argument — an `options` object with properties:
133
+ - `save(state)` — Saves the environment state.
134
+ - `load()` — Loads a previously-saved environment state.
135
+
136
+ ## Base Path
137
+
138
+ If the web application is hosted under a certain URL prefix, it should be specified in `createMiddlewares()` call as `basePath` parameter.
139
+
140
+ ```js
141
+ createMiddlewares(environment, { basePath?: '/base/path' })
142
+ ```
143
+
144
+ ## Location State Storage
145
+
146
+ One could use an environment-specific `LocationStateStorage` in order to store location-specific state. For example, one could store scroll position of a page and then restore that scroll position when the user decides to navigate "Back" to the page.
147
+
148
+ ```js
149
+ import { BrowserEnvironment, LocationStateStorage } from 'navigation-stack'
150
+
151
+ const environment = new BrowserEnvironment()
152
+
153
+ const storage = new LocationStateStorage(environment, { namespace?: 'optional-namespace' })
154
+
155
+ const location = { pathname: '/abc' }
156
+
157
+ storage.set(location, 'key', 123)
158
+ storage.get(location, 'key') === 123
159
+ ```
160
+
161
+ `LocationStateStorage` doesn't provide any guarantees about actually storing the data: if it encounters any errors in the process, it simply ignores them. This simplifies the API in a way that the application doesn't have to wrap `.get()`/`.set()` calls in a `try/catch` block. And judging by the nature of location-specific state, that type of data is inherently non-essential and rather "nice-to-have".
162
+
163
+ ## Block Navigation
164
+
165
+ ```js
166
+ import { createStore, applyMiddleware } from 'redux'
167
+
168
+ import {
169
+ createMiddlewares,
170
+ locationReducer,
171
+ Actions,
172
+ BrowserEnvironment,
173
+ addNavigationBlocker
174
+ } from 'navigation-stack'
175
+
176
+ const environment = new BrowserEnvironment()
177
+
178
+ const store = createStore(
179
+ locationReducer, // Reducer function. For example, `locationReducer()`.
180
+ applyMiddleware(...createMiddlewares(environment))
181
+ )
182
+
183
+ store.dispatch(Actions.init())
184
+
185
+ const removeNavigationBlocker = addNavigationBlocker(
186
+ environment,
187
+ (newLocation) => {
188
+ // Returning `true` means "block this navigation".
189
+ return true
190
+ }
191
+ );
192
+
193
+ // This navigation won't be performed.
194
+ store.dispatch(Actions.push('/new/location'))
195
+
196
+ // Disable the navigation blocker.
197
+ removeNavigationBlocker()
198
+
199
+ // This navigation now will be performed.
200
+ store.dispatch(Actions.push('/new/location'))
201
+ ```
202
+
203
+ Navigation blocker should be a function that receives a `newLocation` argument and could be "synchronous" or "asynchronous" (i.e. return a `Promise`, aka `async`/`await`).
204
+
205
+ Navigation blockers fire both when navigating from one page to another and when closing the current browser tab. In the latter case, `newLocation` argument will be `null`, the function can't return a `Promise`, and returning `true` will cause the web browser to show a confirmation modal with a non-customizable browser-specific text.
206
+
207
+ ## Utility
208
+
209
+ This package exports a couple of utility functions.
210
+
211
+ ```js
212
+ import {
213
+ addBasePath,
214
+ removeBasePath,
215
+ getLocationUrl,
216
+ parseLocationUrl
217
+ } from 'navigation-stack'
218
+
219
+ // Parses a location URL to a location object.
220
+ // If there're no query parameters, `query` property will not be added.
221
+ parseLocationUrl('/abc?d=e') === {
222
+ pathname: '/abc',
223
+ search: '?d=e',
224
+ query: { d: 'e' },
225
+ hash: ''
226
+ }
227
+
228
+ // Converts a location object to a location URL.
229
+ getLocationUrl({ pathname: '/abc', search: '?d=e', hash: '' }) === '/abc?d=e'
230
+
231
+ // Adds `basePath` to a location object or a location URL.
232
+ addBasePath('/abc', '/base-path') === '/base-path/abc'
233
+ addBasePath({ pathname: '/abc' }, '/base-path') === { pathname: '/base-path/abc' }
234
+
235
+ // Removes `basePath` from a location object or a location URL.
236
+ // If `basePath` is not present in location, it won't do anything.
237
+ removeBasePath('/base-path/abc', '/base-path') === '/abc';
238
+ removeBasePath({ pathname: '/base-path/abc' }, '/base-path') === { pathname: '/abc' }
239
+ ```
240
+
241
+ ## Development
242
+
243
+ Clone the repository. Then:
244
+
245
+ ```
246
+ yarn
247
+ yarn format
248
+ yarn test
249
+ ```
package/codecov.yml ADDED
@@ -0,0 +1 @@
1
+ comment: off
package/karma.conf.cjs ADDED
@@ -0,0 +1,63 @@
1
+ // An attempted rewrite in ESM threw an error: "ReferenceError: require is not defined"
2
+ // when processing `import` in the `.js` files.
3
+ //
4
+ // import puppeteer from 'puppeteer';
5
+ // import webpack from 'webpack'; // eslint-disable-line import/no-extraneous-dependencies
6
+ //
7
+ // process.env.CHROME_BIN = puppeteer.executablePath();
8
+
9
+ const webpack = require('webpack'); // eslint-disable-line import/no-extraneous-dependencies
10
+
11
+ process.env.CHROME_BIN = require('puppeteer').executablePath();
12
+
13
+ module.exports = (config) => {
14
+ const { env } = process;
15
+
16
+ config.set({
17
+ frameworks: ['mocha', 'webpack', 'sinon-chai'],
18
+
19
+ files: ['test/index.js', { pattern: 'test/**/*.test.js', watched: false }],
20
+
21
+ preprocessors: {
22
+ 'test/**/*.js': ['webpack', 'sourcemap'],
23
+ },
24
+
25
+ webpack: {
26
+ mode: 'development',
27
+ module: {
28
+ rules: [
29
+ { test: /\.js$/, exclude: /node_modules/, use: 'babel-loader' },
30
+ ],
31
+ },
32
+ plugins: [
33
+ new webpack.DefinePlugin({
34
+ __DEV__: true,
35
+ }),
36
+ ],
37
+ },
38
+
39
+ webpackMiddleware: {
40
+ noInfo: true,
41
+ },
42
+
43
+ reporters: ['mocha', 'coverage'],
44
+
45
+ mochaReporter: {
46
+ output: 'autowatch',
47
+ },
48
+
49
+ coverageReporter: {
50
+ type: 'lcov',
51
+ dir: 'coverage',
52
+ },
53
+
54
+ customLaunchers: {
55
+ ChromeCi: {
56
+ base: 'ChromeHeadless',
57
+ flags: ['--no-sandbox'],
58
+ },
59
+ },
60
+
61
+ browsers: env.BROWSER ? env.BROWSER.split(',') : ['Chrome'],
62
+ });
63
+ };
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _default = exports.default = {
6
+ INIT: '@@navigation-stack/INIT',
7
+ PUSH: '@@navigation-stack/PUSH',
8
+ REPLACE: '@@navigation-stack/REPLACE',
9
+ NAVIGATE: '@@navigation-stack/NAVIGATE',
10
+ SHIFT: '@@navigation-stack/SHIFT',
11
+ UPDATE: '@@navigation-stack/UPDATE',
12
+ DISPOSE: '@@navigation-stack/DISPOSE'
13
+ };
14
+ module.exports = exports.default;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _ActionTypes = _interopRequireDefault(require("./ActionTypes"));
6
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ var _default = exports.default = {
8
+ init: () => ({
9
+ type: _ActionTypes.default.INIT
10
+ }),
11
+ push: location => ({
12
+ type: _ActionTypes.default.PUSH,
13
+ payload: location
14
+ }),
15
+ replace: location => ({
16
+ type: _ActionTypes.default.REPLACE,
17
+ payload: location
18
+ }),
19
+ shift: delta => ({
20
+ type: _ActionTypes.default.SHIFT,
21
+ payload: delta
22
+ }),
23
+ dispose: () => ({
24
+ type: _ActionTypes.default.DISPOSE
25
+ })
26
+ };
27
+ module.exports = exports.default;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _getLocationUrl = _interopRequireDefault(require("./getLocationUrl"));
6
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ class LocationStateStorage {
8
+ constructor(environment, {
9
+ namespace
10
+ } = {}) {
11
+ this._environment = environment;
12
+ this._getFallbackLocationKey = _getLocationUrl.default;
13
+ this._stateKeyPrefix = namespace ? `${namespace}|` : '';
14
+ }
15
+ get(location, key) {
16
+ const stateKey = this._getStateKey(location, key);
17
+ try {
18
+ const value = this._environment.getState(stateKey);
19
+ // === null is probably sufficient.
20
+ if (value === null) {
21
+ return undefined;
22
+ }
23
+
24
+ // We want to catch JSON parse errors in case someone separately threw
25
+ // junk into sessionStorage under our namespace.
26
+ return JSON.parse(value);
27
+ } catch (error) {
28
+ // Pretend that the entry doesn't exist.
29
+ return undefined;
30
+ }
31
+ }
32
+ set(location, key, value) {
33
+ const stateKey = this._getStateKey(location, key);
34
+ if (value === undefined) {
35
+ try {
36
+ this._environment.removeState(stateKey);
37
+ } catch (error) {
38
+ // No need to handle errors here.
39
+ }
40
+ return;
41
+ }
42
+
43
+ // Unlike with read, we want to fail on invalid values here, since the
44
+ // value here is provided by the caller of this method.
45
+ const valueString = JSON.stringify(value);
46
+ try {
47
+ this._environment.setState(stateKey, valueString);
48
+ } catch (error) {
49
+ // No need to handle errors here either. If it didn't work, it didn't
50
+ // work. We make no guarantees about actually saving the value.
51
+ }
52
+ }
53
+ _getStateKey(location, key) {
54
+ const locationKey = location.key || this._getFallbackLocationKey(location);
55
+ const keyPrefix = `${this._stateKeyPrefix}${locationKey}`;
56
+ return `${keyPrefix}|${key}`;
57
+ }
58
+ }
59
+ exports.default = LocationStateStorage;
60
+ module.exports = exports.default;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+ var _navigationBlockers = require("./navigationBlockers");
6
+ exports.default = _navigationBlockers.addNavigationBlocker;
7
+ module.exports = exports.default;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.addBasePath = addBasePath;
5
+ exports.removeBasePath = removeBasePath;
6
+ function normalizeBasePath(basePath) {
7
+ if (!basePath || basePath === '/') {
8
+ return undefined;
9
+ }
10
+
11
+ // Validate `basePath`.
12
+ if (basePath[0] !== '/') {
13
+ throw new Error('`basePath` must start with a slash');
14
+ }
15
+
16
+ // Remove trailing slash from `basePath`.
17
+ if (basePath.slice(-1) === '/') {
18
+ basePath = basePath.slice(0, -1);
19
+ }
20
+ return basePath;
21
+ }
22
+ function removeBasePathFromRelativeUrl(url, basePath) {
23
+ if (url.indexOf(basePath) === 0) {
24
+ // `farce` had a bug here:
25
+ // `location.pathname` is supposed to always be non-empty.
26
+ // If `basePath` is set to `/basePath` and the user navigates to `/basePath` URL,
27
+ // originally here it would simply strips the whole string from the URL
28
+ // and the result would be incorrect: `pathname: ""`.
29
+ // The fix below is adding `|| '/'` in the `return` statement.
30
+ // https://github.com/4Catalyzer/farce/issues/483
31
+ return url.slice(basePath.length) || '/';
32
+ }
33
+ return url;
34
+ }
35
+ function addBasePath(location, basePath) {
36
+ basePath = normalizeBasePath(basePath);
37
+ if (!basePath) {
38
+ return location;
39
+ }
40
+ if (typeof location === 'string') {
41
+ return `${basePath}${location}`;
42
+ }
43
+ return Object.assign({}, location, {
44
+ pathname: `${basePath}${location.pathname}`
45
+ });
46
+ }
47
+ function removeBasePath(location, basePath) {
48
+ basePath = normalizeBasePath(basePath);
49
+ if (!basePath) {
50
+ return location;
51
+ }
52
+ if (typeof location === 'string') {
53
+ return removeBasePathFromRelativeUrl(location, basePath);
54
+ }
55
+ return Object.assign({}, location, {
56
+ pathname: removeBasePathFromRelativeUrl(location.pathname, basePath)
57
+ });
58
+ }