From 4b8a81681d1e6f06190623e4e5865f3c1b1bd412 Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Mon, 4 Nov 2019 12:42:47 -0800
Subject: [PATCH 001/133] Remove deprecated files from v4
---
DOMUtils.js | 3 ---
ExecutionEnvironment.js | 3 ---
LocationUtils.js | 3 ---
PathUtils.js | 3 ---
createBrowserHistory.js | 3 ---
createHashHistory.js | 3 ---
createMemoryHistory.js | 3 ---
createTransitionManager.js | 3 ---
es/DOMUtils.js | 7 ------
es/ExecutionEnvironment.js | 7 ------
es/LocationUtils.js | 7 ------
es/PathUtils.js | 7 ------
es/createBrowserHistory.js | 7 ------
es/createHashHistory.js | 7 ------
es/createMemoryHistory.js | 7 ------
es/createTransitionManager.js | 7 ------
es/warnAboutDeprecatedESMImport.js | 35 ------------------------------
package.json | 12 +---------
warnAboutDeprecatedCJSRequire.js | 35 ------------------------------
19 files changed, 1 insertion(+), 161 deletions(-)
delete mode 100644 DOMUtils.js
delete mode 100644 ExecutionEnvironment.js
delete mode 100644 LocationUtils.js
delete mode 100644 PathUtils.js
delete mode 100644 createBrowserHistory.js
delete mode 100644 createHashHistory.js
delete mode 100644 createMemoryHistory.js
delete mode 100644 createTransitionManager.js
delete mode 100644 es/DOMUtils.js
delete mode 100644 es/ExecutionEnvironment.js
delete mode 100644 es/LocationUtils.js
delete mode 100644 es/PathUtils.js
delete mode 100644 es/createBrowserHistory.js
delete mode 100644 es/createHashHistory.js
delete mode 100644 es/createMemoryHistory.js
delete mode 100644 es/createTransitionManager.js
delete mode 100644 es/warnAboutDeprecatedESMImport.js
delete mode 100644 warnAboutDeprecatedCJSRequire.js
diff --git a/DOMUtils.js b/DOMUtils.js
deleted file mode 100644
index c4f93d31a..000000000
--- a/DOMUtils.js
+++ /dev/null
@@ -1,3 +0,0 @@
-'use strict';
-require('./warnAboutDeprecatedCJSRequire.js')('DOMUtils');
-module.exports = require('./index.js').DOMUtils;
diff --git a/ExecutionEnvironment.js b/ExecutionEnvironment.js
deleted file mode 100644
index 44de3b2f0..000000000
--- a/ExecutionEnvironment.js
+++ /dev/null
@@ -1,3 +0,0 @@
-'use strict';
-require('./warnAboutDeprecatedCJSRequire.js')('ExecutionEnvironment');
-module.exports = require('./index.js').ExecutionEnvironment;
diff --git a/LocationUtils.js b/LocationUtils.js
deleted file mode 100644
index a86120560..000000000
--- a/LocationUtils.js
+++ /dev/null
@@ -1,3 +0,0 @@
-'use strict';
-require('./warnAboutDeprecatedCJSRequire.js')('LocationUtils');
-module.exports = require('./index.js').LocationUtils;
diff --git a/PathUtils.js b/PathUtils.js
deleted file mode 100644
index 7dc98c205..000000000
--- a/PathUtils.js
+++ /dev/null
@@ -1,3 +0,0 @@
-'use strict';
-require('./warnAboutDeprecatedCJSRequire.js')('PathUtils');
-module.exports = require('./index.js').PathUtils;
diff --git a/createBrowserHistory.js b/createBrowserHistory.js
deleted file mode 100644
index 34f12c399..000000000
--- a/createBrowserHistory.js
+++ /dev/null
@@ -1,3 +0,0 @@
-'use strict';
-require('./warnAboutDeprecatedCJSRequire.js')('createBrowserHistory');
-module.exports = require('./index.js').createBrowserHistory;
diff --git a/createHashHistory.js b/createHashHistory.js
deleted file mode 100644
index 7686422de..000000000
--- a/createHashHistory.js
+++ /dev/null
@@ -1,3 +0,0 @@
-'use strict';
-require('./warnAboutDeprecatedCJSRequire.js')('createHashHistory');
-module.exports = require('./index.js').createHashHistory;
diff --git a/createMemoryHistory.js b/createMemoryHistory.js
deleted file mode 100644
index a0ce3ff40..000000000
--- a/createMemoryHistory.js
+++ /dev/null
@@ -1,3 +0,0 @@
-'use strict';
-require('./warnAboutDeprecatedCJSRequire.js')('createMemoryHistory');
-module.exports = require('./index.js').createMemoryHistory;
diff --git a/createTransitionManager.js b/createTransitionManager.js
deleted file mode 100644
index f8cf2f6b7..000000000
--- a/createTransitionManager.js
+++ /dev/null
@@ -1,3 +0,0 @@
-'use strict';
-require('./warnAboutDeprecatedCJSRequire.js')('createTransitionManager');
-module.exports = require('./index.js').createTransitionManager;
diff --git a/es/DOMUtils.js b/es/DOMUtils.js
deleted file mode 100644
index 265a991c4..000000000
--- a/es/DOMUtils.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-import warnAboutDeprecatedESMImport from './warnAboutDeprecatedESMImport.js';
-warnAboutDeprecatedESMImport('DOMUtils');
-
-import { DOMUtils } from '../esm/history.js';
-export default DOMUtils;
diff --git a/es/ExecutionEnvironment.js b/es/ExecutionEnvironment.js
deleted file mode 100644
index 9043609bc..000000000
--- a/es/ExecutionEnvironment.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-import warnAboutDeprecatedESMImport from './warnAboutDeprecatedESMImport.js';
-warnAboutDeprecatedESMImport('ExecutionEnvironment');
-
-import { ExecutionEnvironment } from '../esm/history.js';
-export default ExecutionEnvironment;
diff --git a/es/LocationUtils.js b/es/LocationUtils.js
deleted file mode 100644
index 3dc1816b6..000000000
--- a/es/LocationUtils.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-import warnAboutDeprecatedESMImport from './warnAboutDeprecatedESMImport.js';
-warnAboutDeprecatedESMImport('LocationUtils');
-
-import { LocationUtils } from '../esm/history.js';
-export default LocationUtils;
diff --git a/es/PathUtils.js b/es/PathUtils.js
deleted file mode 100644
index 7414d2662..000000000
--- a/es/PathUtils.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-import warnAboutDeprecatedESMImport from './warnAboutDeprecatedESMImport.js';
-warnAboutDeprecatedESMImport('PathUtils');
-
-import { PathUtils } from '../esm/history.js';
-export default PathUtils;
diff --git a/es/createBrowserHistory.js b/es/createBrowserHistory.js
deleted file mode 100644
index c05b10528..000000000
--- a/es/createBrowserHistory.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-import warnAboutDeprecatedESMImport from './warnAboutDeprecatedESMImport.js';
-warnAboutDeprecatedESMImport('createBrowserHistory');
-
-import { createBrowserHistory } from '../esm/history.js';
-export default createBrowserHistory;
diff --git a/es/createHashHistory.js b/es/createHashHistory.js
deleted file mode 100644
index ff40c25b8..000000000
--- a/es/createHashHistory.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-import warnAboutDeprecatedESMImport from './warnAboutDeprecatedESMImport.js';
-warnAboutDeprecatedESMImport('createHashHistory');
-
-import { createHashHistory } from '../esm/history.js';
-export default createHashHistory;
diff --git a/es/createMemoryHistory.js b/es/createMemoryHistory.js
deleted file mode 100644
index 1cc4caab4..000000000
--- a/es/createMemoryHistory.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-import warnAboutDeprecatedESMImport from './warnAboutDeprecatedESMImport.js';
-warnAboutDeprecatedESMImport('createMemoryHistory');
-
-import { createMemoryHistory } from '../esm/history.js';
-export default createMemoryHistory;
diff --git a/es/createTransitionManager.js b/es/createTransitionManager.js
deleted file mode 100644
index 37d454068..000000000
--- a/es/createTransitionManager.js
+++ /dev/null
@@ -1,7 +0,0 @@
-'use strict';
-
-import warnAboutDeprecatedESMImport from './warnAboutDeprecatedESMImport.js';
-warnAboutDeprecatedESMImport('createTransitionManager');
-
-import { createTransitionManager } from '../esm/history.js';
-export default createTransitionManager;
diff --git a/es/warnAboutDeprecatedESMImport.js b/es/warnAboutDeprecatedESMImport.js
deleted file mode 100644
index 8215fb286..000000000
--- a/es/warnAboutDeprecatedESMImport.js
+++ /dev/null
@@ -1,35 +0,0 @@
-'use strict';
-
-var printWarning = function() {};
-
-if (process.env.NODE_ENV !== 'production') {
- printWarning = function(format, subs) {
- var index = 0;
- var message =
- 'Warning: ' +
- (subs.length > 0
- ? format.replace(/%s/g, function() {
- return subs[index++];
- })
- : format);
-
- if (typeof console !== 'undefined') {
- console.error(message);
- }
-
- try {
- // --- Welcome to debugging history ---
- // This error was thrown as a convenience so that you can use the
- // stack trace to find the callsite that triggered this warning.
- throw new Error(message);
- } catch (e) {}
- };
-}
-
-export default function(member) {
- printWarning(
- 'Please use `import { %s } from "history"` instead of `import %s from "history/es/%s"`. ' +
- 'Support for the latter will be removed in the next major release.',
- [member, member]
- );
-}
diff --git a/package.json b/package.json
index e48a21a7e..442d7f1a7 100644
--- a/package.json
+++ b/package.json
@@ -9,19 +9,9 @@
"module": "esm/history.js",
"unpkg": "umd/history.js",
"files": [
- "DOMUtils.js",
- "ExecutionEnvironment.js",
- "LocationUtils.js",
- "PathUtils.js",
"cjs",
- "createBrowserHistory.js",
- "createHashHistory.js",
- "createMemoryHistory.js",
- "createTransitionManager.js",
- "es",
"esm",
- "umd",
- "warnAboutDeprecatedCJSRequire.js"
+ "umd"
],
"sideEffects": false,
"scripts": {
diff --git a/warnAboutDeprecatedCJSRequire.js b/warnAboutDeprecatedCJSRequire.js
deleted file mode 100644
index 39578596b..000000000
--- a/warnAboutDeprecatedCJSRequire.js
+++ /dev/null
@@ -1,35 +0,0 @@
-'use strict';
-
-var printWarning = function() {};
-
-if (process.env.NODE_ENV !== 'production') {
- printWarning = function(format, subs) {
- var index = 0;
- var message =
- 'Warning: ' +
- (subs.length > 0
- ? format.replace(/%s/g, function() {
- return subs[index++];
- })
- : format);
-
- if (typeof console !== 'undefined') {
- console.error(message);
- }
-
- try {
- // --- Welcome to debugging history ---
- // This error was thrown as a convenience so that you can use the
- // stack trace to find the callsite that triggered this warning.
- throw new Error(message);
- } catch (e) {}
- };
-}
-
-module.exports = function(member) {
- printWarning(
- 'Please use `require("history").%s` instead of `require("history/%s")`. ' +
- 'Support for the latter will be removed in the next major release.',
- [member, member]
- );
-};
From c013f2f7b939a4470480cba2cd99b316283b259d Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Mon, 4 Nov 2019 12:44:37 -0800
Subject: [PATCH 002/133] Update ESLint config
---
.eslintignore | 3 +++
.eslintrc | 23 +++++++++++++++++++++++
modules/.eslintrc | 14 ++++----------
package.json | 2 +-
4 files changed, 31 insertions(+), 11 deletions(-)
create mode 100644 .eslintignore
create mode 100644 .eslintrc
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 000000000..a632b8fc8
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,3 @@
+/cjs
+/esm
+/umd
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 000000000..79afbd3b3
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,23 @@
+{
+ "parser": "babel-eslint",
+ "parserOptions": {
+ "ecmaVersion": 6,
+ "sourceType": "module"
+ },
+ "plugins": ["import"],
+ "env": {
+ "node": true
+ },
+ "extends": ["eslint:recommended", "plugin:import/errors"],
+ "rules": {
+ "prefer-arrow-callback": "error",
+ "no-unused-vars": [
+ "warn",
+ {
+ "args": "after-used",
+ "ignoreRestSiblings": true,
+ "argsIgnorePattern": "event"
+ }
+ ]
+ }
+}
diff --git a/modules/.eslintrc b/modules/.eslintrc
index dc32b8404..340546cde 100644
--- a/modules/.eslintrc
+++ b/modules/.eslintrc
@@ -1,15 +1,9 @@
{
- "parser": "babel-eslint",
- "plugins": ["import"],
"env": {
- "browser": true
+ "browser": true,
+ "node": false
},
- "extends": ["eslint:recommended", "plugin:import/errors"],
- "rules": {
- "prefer-arrow-callback": 2
- },
- "parserOptions": {
- "ecmaVersion": 6,
- "sourceType": "module"
+ "globals": {
+ "__DEV__": true
}
}
diff --git a/package.json b/package.json
index 442d7f1a7..4d2df9107 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,7 @@
"scripts": {
"build": "rollup -c",
"clean": "git clean -fdX .",
- "lint": "eslint modules",
+ "lint": "eslint .",
"prepublishOnly": "yarn build",
"test": "karma start --single-run"
},
From 5416474abea4d36ba00066fad5b35be0f1bde11d Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Mon, 4 Nov 2019 23:15:43 -0800
Subject: [PATCH 003/133] Main v5 rewrite
New features:
- Remove legacy browser support (pre pushState)
- Add state to hash history
- Use custom window when creating history objects
- Better history.block API (wip)
- Fix location.pathname encoding issues
- About 50% smaller
- No dependencies
Removed features:
- Removes basename support
- Removes getUserConfirmation
- Removes keyLength
- Removes hashType
- Removes relative pathname support in hash + memory histories
Still TBD:
- Missing pathname support in push/replace
Fixes #624
Fixes #704
Fixes #723
Fixes #726
---
.size-snapshot.json | 24 +-
modules/DOMUtils.js | 54 ---
modules/LocationUtils.js | 80 ----
modules/PathUtils.js | 59 ---
.../__tests__/BrowserHistory-basename-test.js | 88 -----
modules/__tests__/BrowserHistory-test.js | 214 ----------
.../__tests__/HashHistory-basename-test.js | 92 -----
modules/__tests__/HashHistory-coding-test.js | 76 ----
modules/__tests__/HashHistory-test.js | 215 ----------
modules/__tests__/MemoryHistory-test.js | 203 ----------
.../TestSequences/BackButtonTransitionHook.js | 13 +-
.../TestSequences/BlockEverything.js | 6 +-
.../TestSequences/BlockPopWithoutListening.js | 4 +-
modules/__tests__/TestSequences/DenyGoBack.js | 42 --
.../__tests__/TestSequences/DenyGoForward.js | 50 ---
modules/__tests__/TestSequences/DenyPush.js | 31 --
.../EncodedReservedCharacters.js | 14 +-
modules/__tests__/TestSequences/GoBack.js | 10 +-
modules/__tests__/TestSequences/GoForward.js | 14 +-
.../TestSequences/HashChangeTransitionHook.js | 33 --
.../TestSequences/HashbangHashPathCoding.js | 48 ---
...nNoKey.js => InitialLocationDefaultKey.js} | 6 +-
.../TestSequences/InitialLocationHasKey.js | 4 +-
modules/__tests__/TestSequences/Listen.js | 4 +-
.../LocationPathnameAlwaysSame.js | 26 +-
.../TestSequences/NoslashHashPathCoding.js | 50 ---
.../TestSequences/PushEncodedLocation.js | 15 +-
.../TestSequences/PushInvalidPathname.js | 17 -
.../TestSequences/PushMissingPathname.js | 8 +-
.../TestSequences/PushNewLocation.js | 6 +-
.../TestSequences/PushRelativePathname.js | 8 +-
.../PushRelativePathnameError.js | 31 ++
.../__tests__/TestSequences/PushSamePath.js | 12 +-
.../TestSequences/PushSamePathWarning.js | 55 ---
modules/__tests__/TestSequences/PushState.js | 6 +-
.../TestSequences/PushStateWarning.js | 40 --
.../TestSequences/PushUnicodeLocation.js | 21 +-
.../TestSequences/ReplaceInvalidPathname.js | 17 -
.../TestSequences/ReplaceNewLocation.js | 6 +-
.../TestSequences/ReplaceSamePath.js | 8 +-
.../__tests__/TestSequences/ReplaceState.js | 6 +-
.../TestSequences/ReplaceStateWarning.js | 40 --
.../ReturnFalseTransitionHook.js | 32 --
.../TestSequences/SlashHashPathCoding.js | 48 ---
.../TestSequences/TransitionHookArgs.js | 32 --
modules/__tests__/TestSequences/execSteps.js | 12 +-
modules/__tests__/TestSequences/index.js | 46 ---
.../__tests__/createBrowserHistory-test.js | 163 ++++++++
...test.js => createHashHistory-base-test.js} | 4 +
modules/__tests__/createHashHistory-test.js | 167 ++++++++
modules/__tests__/createLocation-test.js | 144 -------
modules/__tests__/createMemoryHistory-test.js | 139 +++++++
modules/createBrowserHistory.js | 329 ----------------
modules/createHashHistory.js | 361 -----------------
modules/createMemoryHistory.js | 186 ---------
modules/createTransitionManager.js | 78 ----
modules/index.js | 368 +++++++++++++++++-
modules/invariant.js | 1 -
modules/warning.js | 1 -
package.json | 6 +-
yarn.lock | 20 -
61 files changed, 989 insertions(+), 2904 deletions(-)
delete mode 100644 modules/DOMUtils.js
delete mode 100644 modules/LocationUtils.js
delete mode 100644 modules/PathUtils.js
delete mode 100644 modules/__tests__/BrowserHistory-basename-test.js
delete mode 100644 modules/__tests__/BrowserHistory-test.js
delete mode 100644 modules/__tests__/HashHistory-basename-test.js
delete mode 100644 modules/__tests__/HashHistory-coding-test.js
delete mode 100644 modules/__tests__/HashHistory-test.js
delete mode 100644 modules/__tests__/MemoryHistory-test.js
delete mode 100644 modules/__tests__/TestSequences/DenyGoBack.js
delete mode 100644 modules/__tests__/TestSequences/DenyGoForward.js
delete mode 100644 modules/__tests__/TestSequences/DenyPush.js
delete mode 100644 modules/__tests__/TestSequences/HashChangeTransitionHook.js
delete mode 100644 modules/__tests__/TestSequences/HashbangHashPathCoding.js
rename modules/__tests__/TestSequences/{InitialLocationNoKey.js => InitialLocationDefaultKey.js} (65%)
delete mode 100644 modules/__tests__/TestSequences/NoslashHashPathCoding.js
delete mode 100644 modules/__tests__/TestSequences/PushInvalidPathname.js
create mode 100644 modules/__tests__/TestSequences/PushRelativePathnameError.js
delete mode 100644 modules/__tests__/TestSequences/PushSamePathWarning.js
delete mode 100644 modules/__tests__/TestSequences/PushStateWarning.js
delete mode 100644 modules/__tests__/TestSequences/ReplaceInvalidPathname.js
delete mode 100644 modules/__tests__/TestSequences/ReplaceStateWarning.js
delete mode 100644 modules/__tests__/TestSequences/ReturnFalseTransitionHook.js
delete mode 100644 modules/__tests__/TestSequences/SlashHashPathCoding.js
delete mode 100644 modules/__tests__/TestSequences/TransitionHookArgs.js
delete mode 100644 modules/__tests__/TestSequences/index.js
create mode 100644 modules/__tests__/createBrowserHistory-test.js
rename modules/__tests__/{HashHistory-base-test.js => createHashHistory-base-test.js} (91%)
create mode 100644 modules/__tests__/createHashHistory-test.js
delete mode 100644 modules/__tests__/createLocation-test.js
create mode 100644 modules/__tests__/createMemoryHistory-test.js
delete mode 100644 modules/createBrowserHistory.js
delete mode 100644 modules/createHashHistory.js
delete mode 100644 modules/createMemoryHistory.js
delete mode 100644 modules/createTransitionManager.js
delete mode 100644 modules/invariant.js
delete mode 100644 modules/warning.js
diff --git a/.size-snapshot.json b/.size-snapshot.json
index c9b0bf5e6..e02c3164f 100644
--- a/.size-snapshot.json
+++ b/.size-snapshot.json
@@ -1,26 +1,26 @@
{
"esm/history.js": {
- "bundled": 28076,
- "minified": 12353,
- "gzipped": 3575,
+ "bundled": 13433,
+ "minified": 5829,
+ "gzipped": 1706,
"treeshaked": {
"rollup": {
- "code": 208,
- "import_statements": 132
+ "code": 43,
+ "import_statements": 43
},
"webpack": {
- "code": 1324
+ "code": 1027
}
}
},
"umd/history.js": {
- "bundled": 33021,
- "minified": 11943,
- "gzipped": 3917
+ "bundled": 14957,
+ "minified": 5450,
+ "gzipped": 1760
},
"umd/history.min.js": {
- "bundled": 30384,
- "minified": 9993,
- "gzipped": 3501
+ "bundled": 14343,
+ "minified": 5137,
+ "gzipped": 1619
}
}
diff --git a/modules/DOMUtils.js b/modules/DOMUtils.js
deleted file mode 100644
index 95c3a6ea6..000000000
--- a/modules/DOMUtils.js
+++ /dev/null
@@ -1,54 +0,0 @@
-export const canUseDOM = !!(
- typeof window !== 'undefined' &&
- window.document &&
- window.document.createElement
-);
-
-export function getConfirmation(message, callback) {
- callback(window.confirm(message)); // eslint-disable-line no-alert
-}
-
-/**
- * Returns true if the HTML5 history API is supported. Taken from Modernizr.
- *
- * https://github.com/Modernizr/Modernizr/blob/master/LICENSE
- * https://github.com/Modernizr/Modernizr/blob/master/feature-detects/history.js
- * changed to avoid false negatives for Windows Phones: https://github.com/reactjs/react-router/issues/586
- */
-export function supportsHistory() {
- const ua = window.navigator.userAgent;
-
- if (
- (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
- ua.indexOf('Mobile Safari') !== -1 &&
- ua.indexOf('Chrome') === -1 &&
- ua.indexOf('Windows Phone') === -1
- )
- return false;
-
- return window.history && 'pushState' in window.history;
-}
-
-/**
- * Returns true if browser fires popstate on hash change.
- * IE10 and IE11 do not.
- */
-export function supportsPopStateOnHashChange() {
- return window.navigator.userAgent.indexOf('Trident') === -1;
-}
-
-/**
- * Returns false if using go(n) with hash history causes a full page reload.
- */
-export function supportsGoWithoutReloadUsingHash() {
- return window.navigator.userAgent.indexOf('Firefox') === -1;
-}
-
-/**
- * Returns true if a given popstate event is an extraneous WebKit event.
- * Accounts for the fact that Chrome on iOS fires real popstate events
- * containing undefined state when pressing the back button.
- */
-export function isExtraneousPopstateEvent(event) {
- return event.state === undefined && navigator.userAgent.indexOf('CriOS') === -1;
-}
diff --git a/modules/LocationUtils.js b/modules/LocationUtils.js
deleted file mode 100644
index 27cb0f88d..000000000
--- a/modules/LocationUtils.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import resolvePathname from 'resolve-pathname';
-import valueEqual from 'value-equal';
-
-import { parsePath } from './PathUtils.js';
-
-export function createLocation(path, state, key, currentLocation) {
- let location;
- if (typeof path === 'string') {
- // Two-arg form: push(path, state)
- location = parsePath(path);
- location.state = state;
- } else {
- // One-arg form: push(location)
- location = { ...path };
-
- if (location.pathname === undefined) location.pathname = '';
-
- if (location.search) {
- if (location.search.charAt(0) !== '?')
- location.search = '?' + location.search;
- } else {
- location.search = '';
- }
-
- if (location.hash) {
- if (location.hash.charAt(0) !== '#') location.hash = '#' + location.hash;
- } else {
- location.hash = '';
- }
-
- if (state !== undefined && location.state === undefined)
- location.state = state;
- }
-
- try {
- location.pathname = decodeURI(location.pathname);
- } catch (e) {
- if (e instanceof URIError) {
- throw new URIError(
- 'Pathname "' +
- location.pathname +
- '" could not be decoded. ' +
- 'This is likely caused by an invalid percent-encoding.'
- );
- } else {
- throw e;
- }
- }
-
- if (key) location.key = key;
-
- if (currentLocation) {
- // Resolve incomplete/relative pathname relative to current location.
- if (!location.pathname) {
- location.pathname = currentLocation.pathname;
- } else if (location.pathname.charAt(0) !== '/') {
- location.pathname = resolvePathname(
- location.pathname,
- currentLocation.pathname
- );
- }
- } else {
- // When there is no prior location and pathname is empty, set it to /
- if (!location.pathname) {
- location.pathname = '/';
- }
- }
-
- return location;
-}
-
-export function locationsAreEqual(a, b) {
- return (
- a.pathname === b.pathname &&
- a.search === b.search &&
- a.hash === b.hash &&
- a.key === b.key &&
- valueEqual(a.state, b.state)
- );
-}
diff --git a/modules/PathUtils.js b/modules/PathUtils.js
deleted file mode 100644
index e5abec7d2..000000000
--- a/modules/PathUtils.js
+++ /dev/null
@@ -1,59 +0,0 @@
-export function addLeadingSlash(path) {
- return path.charAt(0) === '/' ? path : '/' + path;
-}
-
-export function stripLeadingSlash(path) {
- return path.charAt(0) === '/' ? path.substr(1) : path;
-}
-
-export function hasBasename(path, prefix) {
- return (
- path.toLowerCase().indexOf(prefix.toLowerCase()) === 0 &&
- '/?#'.indexOf(path.charAt(prefix.length)) !== -1
- );
-}
-
-export function stripBasename(path, prefix) {
- return hasBasename(path, prefix) ? path.substr(prefix.length) : path;
-}
-
-export function stripTrailingSlash(path) {
- return path.charAt(path.length - 1) === '/' ? path.slice(0, -1) : path;
-}
-
-export function parsePath(path) {
- let pathname = path || '/';
- let search = '';
- let hash = '';
-
- const hashIndex = pathname.indexOf('#');
- if (hashIndex !== -1) {
- hash = pathname.substr(hashIndex);
- pathname = pathname.substr(0, hashIndex);
- }
-
- const searchIndex = pathname.indexOf('?');
- if (searchIndex !== -1) {
- search = pathname.substr(searchIndex);
- pathname = pathname.substr(0, searchIndex);
- }
-
- return {
- pathname,
- search: search === '?' ? '' : search,
- hash: hash === '#' ? '' : hash
- };
-}
-
-export function createPath(location) {
- const { pathname, search, hash } = location;
-
- let path = pathname || '/';
-
- if (search && search !== '?')
- path += search.charAt(0) === '?' ? search : `?${search}`;
-
- if (hash && hash !== '#') path += hash.charAt(0) === '#' ? hash : `#${hash}`;
-
- return path;
-}
diff --git a/modules/__tests__/BrowserHistory-basename-test.js b/modules/__tests__/BrowserHistory-basename-test.js
deleted file mode 100644
index 97a67fa90..000000000
--- a/modules/__tests__/BrowserHistory-basename-test.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import expect from 'expect';
-import mock from 'jest-mock';
-import { createBrowserHistory } from 'history';
-
-describe('a browser history with a basename', () => {
- it('knows how to create hrefs', () => {
- window.history.replaceState(null, null, '/the/base');
- const history = createBrowserHistory({ basename: '/the/base' });
- const href = history.createHref({
- pathname: '/the/path',
- search: '?the=query',
- hash: '#the-hash'
- });
-
- expect(href).toEqual('/the/base/the/path?the=query#the-hash');
- });
-
- describe('with a bad basename', () => {
- it('knows how to create hrefs', () => {
- window.history.replaceState(null, null, '/the/bad/base');
- const history = createBrowserHistory({ basename: '/the/bad/base/' });
- const href = history.createHref({
- pathname: '/the/path',
- search: '?the=query',
- hash: '#the-hash'
- });
-
- expect(href).toEqual('/the/bad/base/the/path?the=query#the-hash');
- });
- });
-
- describe('with a slash basename', () => {
- it('knows how to create hrefs', () => {
- window.history.replaceState(null, null, '/');
- const history = createBrowserHistory({ basename: '/' });
- const href = history.createHref({
- pathname: '/the/path',
- search: '?the=query',
- hash: '#the-hash'
- });
-
- expect(href).toEqual('/the/path?the=query#the-hash');
- });
- });
-
- it('strips the basename from the pathname', () => {
- window.history.replaceState(null, null, '/prefix/pathname');
- const history = createBrowserHistory({ basename: '/prefix' });
- expect(history.location.pathname).toEqual('/pathname');
- });
-
- it('is not case-sensitive', () => {
- window.history.replaceState(null, null, '/PREFIX/pathname');
- const history = createBrowserHistory({ basename: '/prefix' });
- expect(history.location.pathname).toEqual('/pathname');
- });
-
- it('does not strip partial prefix matches', () => {
- const spy = mock.spyOn(console, 'warn').mockImplementation(() => {});
-
- window.history.replaceState(null, null, '/prefixed/pathname');
- const history = createBrowserHistory({ basename: '/prefix' });
- expect(history.location.pathname).toEqual('/prefixed/pathname');
-
- expect(spy).toHaveBeenCalledTimes(1);
- spy.mockRestore();
- });
-
- describe('when the pathname is only the prefix', () => {
- it('strips the prefix', () => {
- window.history.replaceState(null, null, '/prefix');
- const history = createBrowserHistory({ basename: '/prefix' });
- expect(history.location.pathname).toEqual('/');
- });
-
- it('strips the prefix when there is a search string', () => {
- window.history.replaceState(null, null, '/prefix?a=b');
- const history = createBrowserHistory({ basename: '/prefix' });
- expect(history.location.pathname).toEqual('/');
- });
-
- it('strips the prefix when there is a hash', () => {
- window.history.replaceState(null, null, '/prefix#rest');
- const history = createBrowserHistory({ basename: '/prefix' });
- expect(history.location.pathname).toEqual('/');
- });
- });
-});
diff --git a/modules/__tests__/BrowserHistory-test.js b/modules/__tests__/BrowserHistory-test.js
deleted file mode 100644
index a9ac32d66..000000000
--- a/modules/__tests__/BrowserHistory-test.js
+++ /dev/null
@@ -1,214 +0,0 @@
-import expect from 'expect';
-import { createBrowserHistory } from 'history';
-
-import * as TestSequences from './TestSequences/index.js';
-
-describe('a browser history', () => {
- let history;
- beforeEach(() => {
- if (window.location.pathname !== '/') {
- window.history.replaceState(null, null, '/');
- }
- history = createBrowserHistory();
- });
-
- it('knows how to create hrefs', () => {
- const href = history.createHref({
- pathname: '/the/path',
- search: '?the=query',
- hash: '#the-hash'
- });
-
- expect(href).toEqual('/the/path?the=query#the-hash');
- });
-
- it('does not encode the generated path', () => {
- // encoded
- const encodedHref = history.createHref({
- pathname: '/%23abc'
- });
- // unencoded
- const unencodedHref = history.createHref({
- pathname: '/#abc'
- });
-
- expect(encodedHref).toEqual('/%23abc');
- expect(unencodedHref).toEqual('/#abc');
- });
-
- describe('listen', () => {
- it('does not immediately call listeners', done => {
- TestSequences.Listen(history, done);
- });
- });
-
- describe('the initial location', () => {
- it('does not have a key', done => {
- TestSequences.InitialLocationNoKey(history, done);
- });
- });
-
- describe('push a new path', () => {
- it('calls change listeners with the new location', done => {
- TestSequences.PushNewLocation(history, done);
- });
- });
-
- describe('push the same path', () => {
- it('calls change listeners with the new location', done => {
- TestSequences.PushSamePath(history, done);
- });
- });
-
- describe('push state', () => {
- it('calls change listeners with the new location', done => {
- TestSequences.PushState(history, done);
- });
- });
-
- describe('push with no pathname', () => {
- it('calls change listeners with the normalized location', done => {
- TestSequences.PushMissingPathname(history, done);
- });
- });
-
- describe('push with a relative pathname', () => {
- it('calls change listeners with the normalized location', done => {
- TestSequences.PushRelativePathname(history, done);
- });
- });
-
- describe('push with an invalid pathname (bad percent-encoding)', () => {
- it('throws an error', done => {
- TestSequences.PushInvalidPathname(history, done);
- });
- });
-
- describe('push with a unicode path string', () => {
- it('creates a location with decoded properties', done => {
- TestSequences.PushUnicodeLocation(history, done);
- });
- });
-
- describe('push with an encoded path string', () => {
- it('creates a location object with encoded pathname', done => {
- TestSequences.PushEncodedLocation(history, done);
- });
- });
-
- describe('replace a new path', () => {
- it('calls change listeners with the new location', done => {
- TestSequences.ReplaceNewLocation(history, done);
- });
- });
-
- describe('replace the same path', () => {
- it('calls change listeners with the new location', done => {
- TestSequences.ReplaceSamePath(history, done);
- });
- });
-
- describe('replace with an invalid pathname (bad percent-encoding)', () => {
- it('throws an error', done => {
- TestSequences.ReplaceInvalidPathname(history, done);
- });
- });
-
- describe('replace state', () => {
- it('calls change listeners with the new location', done => {
- TestSequences.ReplaceState(history, done);
- });
- });
-
- describe('location created by encoded and unencoded pathname', () => {
- it('produces the same location.pathname', done => {
- TestSequences.LocationPathnameAlwaysSame(history, done);
- });
- });
-
- describe('location created with encoded/unencoded reserved characters', () => {
- it('produces different location objects', done => {
- TestSequences.EncodedReservedCharacters(history, done);
- });
- });
-
- describe('goBack', () => {
- it('calls change listeners with the previous location', done => {
- TestSequences.GoBack(history, done);
- });
- });
-
- describe('goForward', () => {
- it('calls change listeners with the next location', done => {
- TestSequences.GoForward(history, done);
- });
- });
-
- describe('block', () => {
- it('blocks all transitions', done => {
- TestSequences.BlockEverything(history, done);
- });
- });
-
- describe('block a POP without listening', () => {
- it('receives the next location and action as arguments', done => {
- TestSequences.BlockPopWithoutListening(history, done);
- });
- });
-
- describe('that accepts all transitions', () => {
- let history;
- beforeEach(() => {
- history = createBrowserHistory({
- getUserConfirmation(_, callback) {
- callback(true);
- }
- });
- });
-
- it('receives the next location and action as arguments', done => {
- TestSequences.TransitionHookArgs(history, done);
- });
-
- it('cancels the transition when it returns false', done => {
- TestSequences.ReturnFalseTransitionHook(history, done);
- });
-
- it('is called when the back button is clicked', done => {
- TestSequences.BackButtonTransitionHook(history, done);
- });
-
- it('is called on the hashchange event', done => {
- TestSequences.HashChangeTransitionHook(history, done);
- });
- });
-
- describe('that denies all transitions', () => {
- let history;
- beforeEach(() => {
- history = createBrowserHistory({
- getUserConfirmation(_, callback) {
- callback(false);
- }
- });
- });
-
- describe('clicking on a link (push)', () => {
- it('does not update the location', done => {
- TestSequences.DenyPush(history, done);
- });
- });
-
- describe('clicking the back button (goBack)', () => {
- it('does not update the location', done => {
- TestSequences.DenyGoBack(history, done);
- });
- });
-
- describe('clicking the forward button (goForward)', () => {
- it('does not update the location', done => {
- TestSequences.DenyGoForward(history, done);
- });
- });
- });
-});
diff --git a/modules/__tests__/HashHistory-basename-test.js b/modules/__tests__/HashHistory-basename-test.js
deleted file mode 100644
index 1f5e5306b..000000000
--- a/modules/__tests__/HashHistory-basename-test.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import expect from 'expect';
-import mock from 'jest-mock';
-import { createHashHistory } from 'history';
-
-describe('a hash history with a basename', () => {
- it('knows how to create hrefs', () => {
- window.location.hash = '#/the/base';
- const history = createHashHistory({ basename: '/the/base' });
- const href = history.createHref({
- pathname: '/the/path',
- search: '?the=query'
- });
-
- expect(href).toEqual('#/the/base/the/path?the=query');
- });
-
- describe('with a bad basename', () => {
- it('knows how to create hrefs', () => {
- window.location.hash = '#/the/bad/base/';
- const history = createHashHistory({ basename: '/the/bad/base/' });
- const href = history.createHref({
- pathname: '/the/path',
- search: '?the=query'
- });
-
- expect(href).toEqual('#/the/bad/base/the/path?the=query');
- });
- });
-
- describe('with a slash basename', () => {
- it('knows how to create hrefs', () => {
- const history = createHashHistory({ basename: '/' });
- const href = history.createHref({
- pathname: '/the/path',
- search: '?the=query'
- });
-
- expect(href).toEqual('#/the/path?the=query');
- });
- });
-
- it('strips the basename from the pathname', () => {
- window.location.hash = '/prefix/hello';
- const history = createHashHistory({ basename: '/prefix' });
- expect(history.location.pathname).toEqual('/hello');
- });
-
- it('is not case-sensitive', () => {
- window.location.hash = '/PREFIX/hello';
- const history = createHashHistory({ basename: '/prefix' });
- expect(history.location.pathname).toEqual('/hello');
- });
-
- it('allows special regex characters', () => {
- window.location.hash = '/prefix$special/hello';
- const history = createHashHistory({ basename: '/prefix$special' });
- expect(history.location.pathname).toEqual('/hello');
- });
-
- it('does not strip partial prefix matches', () => {
- window.location.hash = '/no-match/hello';
-
- // A warning is issued when the prefix is not present.
- const spy = mock.spyOn(console, 'warn').mockImplementation(() => {});
-
- const history = createHashHistory({ basename: '/prefix' });
- expect(history.location.pathname).toEqual('/no-match/hello');
-
- expect(spy).toHaveBeenCalledTimes(1);
- spy.mockRestore();
- });
-
- describe('when the pathname is only the prefix', () => {
- it('strips the prefix', () => {
- window.location.hash = '/prefix';
- const history = createHashHistory({ basename: '/prefix' });
- expect(history.location.pathname).toEqual('/');
- });
-
- it('strips the prefix when there is a search string', () => {
- window.location.hash = '/prefix?a=b';
- const history = createHashHistory({ basename: '/prefix' });
- expect(history.location.pathname).toEqual('/');
- });
-
- it('strips the prefix when there is a hash', () => {
- window.location.hash = '/prefix#hash';
- const history = createHashHistory({ basename: '/prefix' });
- expect(history.location.pathname).toEqual('/');
- });
- });
-});
diff --git a/modules/__tests__/HashHistory-coding-test.js b/modules/__tests__/HashHistory-coding-test.js
deleted file mode 100644
index 8faae2040..000000000
--- a/modules/__tests__/HashHistory-coding-test.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import expect from 'expect';
-import { createHashHistory } from 'history';
-
-import * as TestSequences from './TestSequences/index.js';
-
-describe('a hash history with "slash" path coding', () => {
- beforeEach(() => {
- if (window.location.hash !== '#/') {
- window.location.hash = '/';
- }
- });
-
- it('knows how to create hrefs', () => {
- const history = createHashHistory({ hashType: 'slash' });
- const href = history.createHref({
- pathname: '/the/path',
- search: '?the=query',
- hash: '#the-hash'
- });
-
- expect(href).toEqual('#/the/path?the=query#the-hash');
- });
-
- it('properly encodes and decodes window.location.hash', done => {
- const history = createHashHistory({ hashType: 'slash' });
- TestSequences.SlashHashPathCoding(history, done);
- });
-});
-
-describe('a hash history with "hashbang" path coding', () => {
- beforeEach(() => {
- if (window.location.hash !== '#!/') {
- window.location.hash = '!/';
- }
- });
-
- it('knows how to create hrefs', () => {
- const history = createHashHistory({ hashType: 'hashbang' });
- const href = history.createHref({
- pathname: '/the/path',
- search: '?the=query',
- hash: '#the-hash'
- });
-
- expect(href).toEqual('#!/the/path?the=query#the-hash');
- });
-
- it('properly encodes and decodes window.location.hash', done => {
- const history = createHashHistory({ hashType: 'hashbang' });
- TestSequences.HashbangHashPathCoding(history, done);
- });
-});
-
-describe('a hash history with "noslash" path coding', () => {
- beforeEach(() => {
- if (window.location.hash !== '') {
- window.location.hash = '';
- }
- });
-
- it('knows how to create hrefs', () => {
- const history = createHashHistory({ hashType: 'noslash' });
- const href = history.createHref({
- pathname: '/the/path',
- search: '?the=query',
- hash: '#the-hash'
- });
-
- expect(href).toEqual('#the/path?the=query#the-hash');
- });
-
- it('properly encodes and decodes window.location.hash', done => {
- const history = createHashHistory({ hashType: 'noslash' });
- TestSequences.NoslashHashPathCoding(history, done);
- });
-});
diff --git a/modules/__tests__/HashHistory-test.js b/modules/__tests__/HashHistory-test.js
deleted file mode 100644
index 63c4432f0..000000000
--- a/modules/__tests__/HashHistory-test.js
+++ /dev/null
@@ -1,215 +0,0 @@
-import expect from 'expect';
-import { createHashHistory } from 'history';
-
-import * as TestSequences from './TestSequences/index.js';
-
-const canGoWithoutReload = window.navigator.userAgent.indexOf('Firefox') === -1;
-const describeGo = canGoWithoutReload ? describe : describe.skip;
-
-describe('a hash history', () => {
- let history;
- beforeEach(() => {
- if (window.location.hash !== '#/') {
- window.location.hash = '/';
- }
- history = createHashHistory();
- });
-
- it('knows how to create hrefs', () => {
- const href = history.createHref({
- pathname: '/the/path',
- search: '?the=query',
- hash: '#the-hash'
- });
-
- expect(href).toEqual('#/the/path?the=query#the-hash');
- });
-
- it('does not encode the generated path', () => {
- // encoded
- const encodedHref = history.createHref({
- pathname: '/%23abc'
- });
- // unencoded
- const unencodedHref = history.createHref({
- pathname: '/#abc'
- });
-
- expect(encodedHref).toEqual('#/%23abc');
- expect(unencodedHref).toEqual('#/#abc');
- });
-
- describe('listen', () => {
- it('does not immediately call listeners', done => {
- TestSequences.Listen(history, done);
- });
- });
-
- describe('the initial location', () => {
- it('does not have a key', done => {
- TestSequences.InitialLocationNoKey(history, done);
- });
- });
-
- describe('push a new path', () => {
- it('calls change listeners with the new location', done => {
- TestSequences.PushNewLocation(history, done);
- });
- });
-
- describe('push the same path', () => {
- it('calls change listeners with the same location and emits a warning', done => {
- TestSequences.PushSamePathWarning(history, done);
- });
- });
-
- describe('push state', () => {
- it('calls change listeners with the new location and emits a warning', done => {
- TestSequences.PushStateWarning(history, done);
- });
- });
-
- describe('push with no pathname', () => {
- it('calls change listeners with the normalized location', done => {
- TestSequences.PushMissingPathname(history, done);
- });
- });
-
- describe('push with a relative pathname', () => {
- it('calls change listeners with the normalized location', done => {
- TestSequences.PushRelativePathname(history, done);
- });
- });
-
- describe('push with an invalid pathname (bad percent-encoding)', () => {
- it('throws an error', done => {
- TestSequences.PushInvalidPathname(history, done);
- });
- });
-
- describe('push with a unicode path string', () => {
- it('creates a location with decoded properties', done => {
- TestSequences.PushUnicodeLocation(history, done);
- });
- });
-
- describe('push with an encoded path string', () => {
- it('creates a location object with encoded pathname', done => {
- TestSequences.PushEncodedLocation(history, done);
- });
- });
-
- describe('replace a new path', () => {
- it('calls change listeners with the new location', done => {
- TestSequences.ReplaceNewLocation(history, done);
- });
- });
-
- describe('replace the same path', () => {
- it('calls change listeners with the new location', done => {
- TestSequences.ReplaceSamePath(history, done);
- });
- });
-
- describe('replace with an invalid pathname (bad percent-encoding)', () => {
- it('throws an error', done => {
- TestSequences.ReplaceInvalidPathname(history, done);
- });
- });
-
- describe('replace state', () => {
- it('calls change listeners with the new location and emits a warning', done => {
- TestSequences.ReplaceStateWarning(history, done);
- });
- });
-
- describe('location created by encoded and unencoded pathname', () => {
- it('produces the same location.pathname', done => {
- TestSequences.LocationPathnameAlwaysSame(history, done);
- });
- });
-
- describe('location created with encoded/unencoded reserved characters', () => {
- it('produces different location objects', done => {
- TestSequences.EncodedReservedCharacters(history, done);
- });
- });
-
- describeGo('goBack', () => {
- it('calls change listeners with the previous location', done => {
- TestSequences.GoBack(history, done);
- });
- });
-
- describeGo('goForward', () => {
- it('calls change listeners with the next location', done => {
- TestSequences.GoForward(history, done);
- });
- });
-
- describe('block', () => {
- it('blocks all transitions', done => {
- TestSequences.BlockEverything(history, done);
- });
- });
-
- describeGo('block a POP without listening', () => {
- it('receives the next location and action as arguments', done => {
- TestSequences.BlockPopWithoutListening(history, done);
- });
- });
-
- describe('that accepts all transitions', () => {
- let history;
- beforeEach(() => {
- history = createHashHistory({
- getUserConfirmation(_, callback) {
- callback(true);
- }
- });
- });
-
- it('receives the next location and action as arguments', done => {
- TestSequences.TransitionHookArgs(history, done);
- });
-
- const itBackButton = canGoWithoutReload ? it : it.skip;
-
- itBackButton('is called when the back button is clicked', done => {
- TestSequences.BackButtonTransitionHook(history, done);
- });
-
- it('cancels the transition when it returns false', done => {
- TestSequences.ReturnFalseTransitionHook(history, done);
- });
- });
-
- describe('that denies all transitions', () => {
- let history;
- beforeEach(() => {
- history = createHashHistory({
- getUserConfirmation(_, callback) {
- callback(false);
- }
- });
- });
-
- describe('clicking on a link (push)', () => {
- it('does not update the location', done => {
- TestSequences.DenyPush(history, done);
- });
- });
-
- describeGo('clicking the back button (goBack)', () => {
- it('does not update the location', done => {
- TestSequences.DenyGoBack(history, done);
- });
- });
-
- describeGo('clicking the forward button (goForward)', () => {
- it('does not update the location', done => {
- TestSequences.DenyGoForward(history, done);
- });
- });
- });
-});
diff --git a/modules/__tests__/MemoryHistory-test.js b/modules/__tests__/MemoryHistory-test.js
deleted file mode 100644
index 176f874fb..000000000
--- a/modules/__tests__/MemoryHistory-test.js
+++ /dev/null
@@ -1,203 +0,0 @@
-import expect from 'expect';
-import { createMemoryHistory } from 'history';
-
-import * as TestSequences from './TestSequences/index.js';
-
-describe('a memory history', () => {
- let history;
- beforeEach(() => {
- history = createMemoryHistory();
- });
-
- it('knows how to create hrefs', () => {
- const href = history.createHref({
- pathname: '/the/path',
- search: '?the=query',
- hash: '#the-hash'
- });
-
- expect(href).toEqual('/the/path?the=query#the-hash');
- });
-
- it('does not encode the generated path', () => {
- // encoded
- const encodedHref = history.createHref({
- pathname: '/%23abc'
- });
- // unencoded
- const unencodedHref = history.createHref({
- pathname: '/#abc'
- });
-
- expect(encodedHref).toEqual('/%23abc');
- expect(unencodedHref).toEqual('/#abc');
- });
-
- describe('listen', () => {
- it('does not immediately call listeners', done => {
- TestSequences.Listen(history, done);
- });
- });
-
- describe('the initial location', () => {
- it('has a key', done => {
- TestSequences.InitialLocationHasKey(history, done);
- });
- });
-
- describe('push a new path', () => {
- it('calls change listeners with the new location', done => {
- TestSequences.PushNewLocation(history, done);
- });
- });
-
- describe('push the same path', () => {
- it('calls change listeners with the new location', done => {
- TestSequences.PushSamePath(history, done);
- });
- });
-
- describe('push state', () => {
- it('calls change listeners with the new location', done => {
- TestSequences.PushState(history, done);
- });
- });
-
- describe('push with no pathname', () => {
- it('calls change listeners with the normalized location', done => {
- TestSequences.PushMissingPathname(history, done);
- });
- });
-
- describe('push with a relative pathname', () => {
- it('calls change listeners with the normalized location', done => {
- TestSequences.PushRelativePathname(history, done);
- });
- });
-
- describe('push with an invalid pathname (bad percent-encoding)', () => {
- it('throws an error', done => {
- TestSequences.PushInvalidPathname(history, done);
- });
- });
-
- describe('push with a unicode path string', () => {
- it('creates a location with decoded properties', done => {
- TestSequences.PushUnicodeLocation(history, done);
- });
- });
-
- describe('push with an encoded path string', () => {
- it('creates a location object with encoded pathname', done => {
- TestSequences.PushEncodedLocation(history, done);
- });
- });
-
- describe('replace a new path', () => {
- it('calls change listeners with the new location', done => {
- TestSequences.ReplaceNewLocation(history, done);
- });
- });
-
- describe('replace the same path', () => {
- it('calls change listeners with the new location', done => {
- TestSequences.ReplaceSamePath(history, done);
- });
- });
-
- describe('replace with an invalid pathname (bad percent-encoding)', () => {
- it('throws an error', done => {
- TestSequences.ReplaceInvalidPathname(history, done);
- });
- });
-
- describe('replace state', () => {
- it('calls change listeners with the new location', done => {
- TestSequences.ReplaceState(history, done);
- });
- });
-
- describe('location created by encoded and unencoded pathname', () => {
- it('produces the same location.pathname', done => {
- TestSequences.LocationPathnameAlwaysSame(history, done);
- });
- });
-
- describe('location created with encoded/unencoded reserved characters', () => {
- it('produces different location objects', done => {
- TestSequences.EncodedReservedCharacters(history, done);
- });
- });
-
- describe('goBack', () => {
- it('calls change listeners with the previous location', done => {
- TestSequences.GoBack(history, done);
- });
- });
-
- describe('goForward', () => {
- it('calls change listeners with the next location', done => {
- TestSequences.GoForward(history, done);
- });
- });
-
- describe('block', () => {
- it('blocks all transitions', done => {
- TestSequences.BlockEverything(history, done);
- });
- });
-
- describe('block a POP without listening', () => {
- it('receives the next location and action as arguments', done => {
- TestSequences.BlockPopWithoutListening(history, done);
- });
- });
-
- describe('that accepts all transitions', () => {
- let history;
- beforeEach(() => {
- history = createMemoryHistory({
- getUserConfirmation(_, callback) {
- callback(true);
- }
- });
- });
-
- it('receives the next location and action as arguments', done => {
- TestSequences.TransitionHookArgs(history, done);
- });
-
- it('cancels the transition when it returns false', done => {
- TestSequences.ReturnFalseTransitionHook(history, done);
- });
- });
-
- describe('that denies all transitions', () => {
- let history;
- beforeEach(() => {
- history = createMemoryHistory({
- getUserConfirmation(_, callback) {
- callback(false);
- }
- });
- });
-
- describe('push', () => {
- it('does not update the location', done => {
- TestSequences.DenyPush(history, done);
- });
- });
-
- describe('goBack', () => {
- it('does not update the location', done => {
- TestSequences.DenyGoBack(history, done);
- });
- });
-
- describe('goForward', () => {
- it('does not update the location', done => {
- TestSequences.DenyGoForward(history, done);
- });
- });
- });
-});
diff --git a/modules/__tests__/TestSequences/BackButtonTransitionHook.js b/modules/__tests__/TestSequences/BackButtonTransitionHook.js
index 9c6783c08..d0544e21c 100644
--- a/modules/__tests__/TestSequences/BackButtonTransitionHook.js
+++ b/modules/__tests__/TestSequences/BackButtonTransitionHook.js
@@ -3,17 +3,18 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- let unblock,
- hookWasCalled = false;
- const steps = [
- location => {
+ let hookWasCalled = false;
+ let unblock;
+
+ let steps = [
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/'
});
history.push('/home');
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('PUSH');
expect(location).toMatchObject({
pathname: '/home'
@@ -25,7 +26,7 @@ export default function(history, done) {
window.history.go(-1);
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('POP');
expect(location).toMatchObject({
pathname: '/'
diff --git a/modules/__tests__/TestSequences/BlockEverything.js b/modules/__tests__/TestSequences/BlockEverything.js
index b5860c312..ab8db6324 100644
--- a/modules/__tests__/TestSequences/BlockEverything.js
+++ b/modules/__tests__/TestSequences/BlockEverything.js
@@ -3,13 +3,13 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
- location => {
+ let steps = [
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/'
});
- const unblock = history.block();
+ let unblock = history.block();
history.push('/home');
diff --git a/modules/__tests__/TestSequences/BlockPopWithoutListening.js b/modules/__tests__/TestSequences/BlockPopWithoutListening.js
index 883fce364..f98887edc 100644
--- a/modules/__tests__/TestSequences/BlockPopWithoutListening.js
+++ b/modules/__tests__/TestSequences/BlockPopWithoutListening.js
@@ -8,7 +8,7 @@ export default function(history, done) {
history.push('/home');
let transitionHookWasCalled = false;
- const unblock = history.block(() => {
+ let unblock = history.block(() => {
transitionHookWasCalled = true;
});
@@ -20,7 +20,7 @@ export default function(history, done) {
// Allow some time for history to detect the PUSH.
setTimeout(() => {
- history.goBack();
+ history.back();
// Allow some time for history to detect the POP.
setTimeout(() => {
diff --git a/modules/__tests__/TestSequences/DenyGoBack.js b/modules/__tests__/TestSequences/DenyGoBack.js
deleted file mode 100644
index 122d33675..000000000
--- a/modules/__tests__/TestSequences/DenyGoBack.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps.js';
-
-export default function(history, done) {
- let unblock;
- const steps = [
- location => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- history.push('/home');
- },
- (location, action) => {
- expect(action).toBe('PUSH');
- expect(location).toMatchObject({
- pathname: '/home'
- });
-
- unblock = history.block(nextLocation => {
- expect(nextLocation).toMatchObject({
- pathname: '/'
- });
-
- return 'Are you sure?';
- });
-
- history.goBack();
- },
- (location, action) => {
- expect(action).toBe('PUSH');
- expect(location).toMatchObject({
- pathname: '/home'
- });
-
- unblock();
- }
- ];
-
- execSteps(steps, history, done);
-}
diff --git a/modules/__tests__/TestSequences/DenyGoForward.js b/modules/__tests__/TestSequences/DenyGoForward.js
deleted file mode 100644
index fa27a933d..000000000
--- a/modules/__tests__/TestSequences/DenyGoForward.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps.js';
-
-export default function(history, done) {
- let unblock;
- const steps = [
- location => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- history.push('/home');
- },
- (location, action) => {
- expect(action).toBe('PUSH');
- expect(location).toMatchObject({
- pathname: '/home'
- });
-
- history.goBack();
- },
- (location, action) => {
- expect(action).toBe('POP');
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- unblock = history.block(nextLocation => {
- expect(nextLocation).toMatchObject({
- pathname: '/home'
- });
-
- return 'Are you sure?';
- });
-
- history.goForward();
- },
- (location, action) => {
- expect(action).toBe('POP');
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- unblock();
- }
- ];
-
- execSteps(steps, history, done);
-}
diff --git a/modules/__tests__/TestSequences/DenyPush.js b/modules/__tests__/TestSequences/DenyPush.js
deleted file mode 100644
index 1fd82aea3..000000000
--- a/modules/__tests__/TestSequences/DenyPush.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps.js';
-
-export default function(history, done) {
- const steps = [
- location => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- const unblock = history.block(nextLocation => {
- expect(nextLocation).toMatchObject({
- pathname: '/home'
- });
-
- return 'Are you sure?';
- });
-
- history.push('/home');
-
- expect(history.location).toMatchObject({
- pathname: '/'
- });
-
- unblock();
- }
- ];
-
- execSteps(steps, history, done);
-}
diff --git a/modules/__tests__/TestSequences/EncodedReservedCharacters.js b/modules/__tests__/TestSequences/EncodedReservedCharacters.js
index 355a2ff00..b0addd0df 100644
--- a/modules/__tests__/TestSequences/EncodedReservedCharacters.js
+++ b/modules/__tests__/TestSequences/EncodedReservedCharacters.js
@@ -3,30 +3,30 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
+ let steps = [
() => {
// encoded string
- const pathname = '/view/%23abc';
+ let pathname = '/view/%23abc';
history.replace(pathname);
},
- location => {
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/view/%23abc'
});
// encoded object
- const pathname = '/view/%23abc';
+ let pathname = '/view/%23abc';
history.replace({ pathname });
},
- location => {
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/view/%23abc'
});
// unencoded string
- const pathname = '/view/#abc';
+ let pathname = '/view/#abc';
history.replace(pathname);
},
- location => {
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/view/',
hash: '#abc'
diff --git a/modules/__tests__/TestSequences/GoBack.js b/modules/__tests__/TestSequences/GoBack.js
index b5a5752d2..2f582e06f 100644
--- a/modules/__tests__/TestSequences/GoBack.js
+++ b/modules/__tests__/TestSequences/GoBack.js
@@ -3,23 +3,23 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
- location => {
+ let steps = [
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/'
});
history.push('/home');
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toEqual('PUSH');
expect(location).toMatchObject({
pathname: '/home'
});
- history.goBack();
+ history.back();
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toEqual('POP');
expect(location).toMatchObject({
pathname: '/'
diff --git a/modules/__tests__/TestSequences/GoForward.js b/modules/__tests__/TestSequences/GoForward.js
index 06d7f61af..7a5a6c416 100644
--- a/modules/__tests__/TestSequences/GoForward.js
+++ b/modules/__tests__/TestSequences/GoForward.js
@@ -3,31 +3,31 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
- location => {
+ let steps = [
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/'
});
history.push('/home');
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toEqual('PUSH');
expect(location).toMatchObject({
pathname: '/home'
});
- history.goBack();
+ history.back();
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toEqual('POP');
expect(location).toMatchObject({
pathname: '/'
});
- history.goForward();
+ history.forward();
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toEqual('POP');
expect(location).toMatchObject({
pathname: '/home'
diff --git a/modules/__tests__/TestSequences/HashChangeTransitionHook.js b/modules/__tests__/TestSequences/HashChangeTransitionHook.js
deleted file mode 100644
index 73fdeb917..000000000
--- a/modules/__tests__/TestSequences/HashChangeTransitionHook.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps.js';
-
-export default function(history, done) {
- let unblock,
- hookWasCalled = false;
- const steps = [
- location => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- unblock = history.block(() => {
- hookWasCalled = true;
- });
-
- window.location.hash = 'something-new';
- },
- location => {
- expect(location).toMatchObject({
- pathname: '/',
- hash: '#something-new'
- });
-
- expect(hookWasCalled).toBe(true);
-
- unblock();
- }
- ];
-
- execSteps(steps, history, done);
-}
diff --git a/modules/__tests__/TestSequences/HashbangHashPathCoding.js b/modules/__tests__/TestSequences/HashbangHashPathCoding.js
deleted file mode 100644
index 3380797d0..000000000
--- a/modules/__tests__/TestSequences/HashbangHashPathCoding.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps.js';
-
-export default function(history, done) {
- const steps = [
- location => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- expect(window.location.hash).toBe('#!/');
-
- history.push('/home?the=query#the-hash');
- },
- location => {
- expect(location).toMatchObject({
- pathname: '/home',
- search: '?the=query',
- hash: '#the-hash'
- });
-
- expect(window.location.hash).toBe('#!/home?the=query#the-hash');
-
- history.goBack();
- },
- location => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- expect(window.location.hash).toBe('#!/');
-
- history.goForward();
- },
- location => {
- expect(location).toMatchObject({
- pathname: '/home',
- search: '?the=query',
- hash: '#the-hash'
- });
-
- expect(window.location.hash).toBe('#!/home?the=query#the-hash');
- }
- ];
-
- execSteps(steps, history, done);
-}
diff --git a/modules/__tests__/TestSequences/InitialLocationNoKey.js b/modules/__tests__/TestSequences/InitialLocationDefaultKey.js
similarity index 65%
rename from modules/__tests__/TestSequences/InitialLocationNoKey.js
rename to modules/__tests__/TestSequences/InitialLocationDefaultKey.js
index 8c79d34b2..eb13fee25 100644
--- a/modules/__tests__/TestSequences/InitialLocationNoKey.js
+++ b/modules/__tests__/TestSequences/InitialLocationDefaultKey.js
@@ -3,9 +3,9 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
- location => {
- expect(location.key).toBeFalsy();
+ let steps = [
+ ({ location }) => {
+ expect(location.key).toBe('default');
}
];
diff --git a/modules/__tests__/TestSequences/InitialLocationHasKey.js b/modules/__tests__/TestSequences/InitialLocationHasKey.js
index e9a22b62a..f506fb5b2 100644
--- a/modules/__tests__/TestSequences/InitialLocationHasKey.js
+++ b/modules/__tests__/TestSequences/InitialLocationHasKey.js
@@ -3,8 +3,8 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
- location => {
+ let steps = [
+ ({ location }) => {
expect(location.key).toBeTruthy();
}
];
diff --git a/modules/__tests__/TestSequences/Listen.js b/modules/__tests__/TestSequences/Listen.js
index 84393eb68..ddc6c4998 100644
--- a/modules/__tests__/TestSequences/Listen.js
+++ b/modules/__tests__/TestSequences/Listen.js
@@ -2,8 +2,8 @@ import expect from 'expect';
import mock from 'jest-mock';
export default function(history, done) {
- const spy = mock.fn();
- const unlisten = history.listen(spy);
+ let spy = mock.fn();
+ let unlisten = history.listen(spy);
expect(spy).not.toHaveBeenCalled();
diff --git a/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js b/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js
index 48facbc6d..5a6158fef 100644
--- a/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js
+++ b/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js
@@ -3,39 +3,39 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
+ let steps = [
() => {
// encoded string
- const pathname = '/%E6%AD%B4%E5%8F%B2';
+ let pathname = '/%E6%AD%B4%E5%8F%B2';
history.replace(pathname);
},
- location => {
+ ({ location }) => {
expect(location).toMatchObject({
- pathname: '/歴史'
+ pathname: '/%E6%AD%B4%E5%8F%B2'
});
// encoded object
- const pathname = '/%E6%AD%B4%E5%8F%B2';
+ let pathname = '/%E6%AD%B4%E5%8F%B2';
history.replace({ pathname });
},
- location => {
+ ({ location }) => {
expect(location).toMatchObject({
- pathname: '/歴史'
+ pathname: '/%E6%AD%B4%E5%8F%B2'
});
// unencoded string
- const pathname = '/歴史';
+ let pathname = '/歴史';
history.replace(pathname);
},
- location => {
+ ({ location }) => {
expect(location).toMatchObject({
- pathname: '/歴史'
+ pathname: '/%E6%AD%B4%E5%8F%B2'
});
// unencoded object
- const pathname = '/歴史';
+ let pathname = '/歴史';
history.replace({ pathname });
},
- location => {
+ ({ location }) => {
expect(location).toMatchObject({
- pathname: '/歴史'
+ pathname: '/%E6%AD%B4%E5%8F%B2'
});
}
];
diff --git a/modules/__tests__/TestSequences/NoslashHashPathCoding.js b/modules/__tests__/TestSequences/NoslashHashPathCoding.js
deleted file mode 100644
index e6df14f3c..000000000
--- a/modules/__tests__/TestSequences/NoslashHashPathCoding.js
+++ /dev/null
@@ -1,50 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps.js';
-
-export default function(history, done) {
- const steps = [
- location => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- // IE 10+ gives us "#", everyone else gives us ""
- expect(window.location.hash).toMatch(/^#?$/);
-
- history.push('/home?the=query#the-hash');
- },
- location => {
- expect(location).toMatchObject({
- pathname: '/home',
- search: '?the=query',
- hash: '#the-hash'
- });
-
- expect(window.location.hash).toBe('#home?the=query#the-hash');
-
- history.goBack();
- },
- location => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- // IE 10+ gives us "#", everyone else gives us ""
- expect(window.location.hash).toMatch(/^#?$/);
-
- history.goForward();
- },
- location => {
- expect(location).toMatchObject({
- pathname: '/home',
- search: '?the=query',
- hash: '#the-hash'
- });
-
- expect(window.location.hash).toBe('#home?the=query#the-hash');
- }
- ];
-
- execSteps(steps, history, done);
-}
diff --git a/modules/__tests__/TestSequences/PushEncodedLocation.js b/modules/__tests__/TestSequences/PushEncodedLocation.js
index f71d67109..60a369303 100644
--- a/modules/__tests__/TestSequences/PushEncodedLocation.js
+++ b/modules/__tests__/TestSequences/PushEncodedLocation.js
@@ -3,21 +3,22 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
- location => {
+ let steps = [
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/'
});
- const pathname = '/歴史';
- const search = '?%E3%82%AD%E3%83%BC=%E5%80%A4';
- const hash = '#%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5';
+ let pathname = '/歴史';
+ let search = '?%E3%82%AD%E3%83%BC=%E5%80%A4';
+ let hash = '#%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5';
history.push(pathname + search + hash);
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('PUSH');
expect(location).toMatchObject({
- pathname: '/歴史',
+ pathname: '/%E6%AD%B4%E5%8F%B2',
+ // pathname: '/歴史',
search: '?%E3%82%AD%E3%83%BC=%E5%80%A4',
hash: '#%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5'
});
diff --git a/modules/__tests__/TestSequences/PushInvalidPathname.js b/modules/__tests__/TestSequences/PushInvalidPathname.js
deleted file mode 100644
index 5024eebbe..000000000
--- a/modules/__tests__/TestSequences/PushInvalidPathname.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps';
-
-export default function(history, done) {
- const steps = [
- () => {
- expect(() => {
- history.push('/hello%');
- }).toThrow(
- 'Pathname "/hello%" could not be decoded. This is likely caused by an invalid percent-encoding.'
- );
- }
- ];
-
- execSteps(steps, history, done);
-}
diff --git a/modules/__tests__/TestSequences/PushMissingPathname.js b/modules/__tests__/TestSequences/PushMissingPathname.js
index 28e7da79e..1f3499afa 100644
--- a/modules/__tests__/TestSequences/PushMissingPathname.js
+++ b/modules/__tests__/TestSequences/PushMissingPathname.js
@@ -3,15 +3,15 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
- location => {
+ let steps = [
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/'
});
history.push('/home?the=query#the-hash');
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('PUSH');
expect(location).toMatchObject({
pathname: '/home',
@@ -21,7 +21,7 @@ export default function(history, done) {
history.push('?another=query#another-hash');
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('PUSH');
expect(location).toMatchObject({
pathname: '/home',
diff --git a/modules/__tests__/TestSequences/PushNewLocation.js b/modules/__tests__/TestSequences/PushNewLocation.js
index 073f6308e..156bef476 100644
--- a/modules/__tests__/TestSequences/PushNewLocation.js
+++ b/modules/__tests__/TestSequences/PushNewLocation.js
@@ -3,15 +3,15 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
- location => {
+ let steps = [
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/'
});
history.push('/home?the=query#the-hash');
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('PUSH');
expect(location).toMatchObject({
pathname: '/home',
diff --git a/modules/__tests__/TestSequences/PushRelativePathname.js b/modules/__tests__/TestSequences/PushRelativePathname.js
index 418bf374c..75850e724 100644
--- a/modules/__tests__/TestSequences/PushRelativePathname.js
+++ b/modules/__tests__/TestSequences/PushRelativePathname.js
@@ -3,15 +3,15 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
- location => {
+ let steps = [
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/'
});
history.push('/the/path?the=query#the-hash');
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('PUSH');
expect(location).toMatchObject({
pathname: '/the/path',
@@ -21,7 +21,7 @@ export default function(history, done) {
history.push('../other/path?another=query#another-hash');
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('PUSH');
expect(location).toMatchObject({
pathname: '/other/path',
diff --git a/modules/__tests__/TestSequences/PushRelativePathnameError.js b/modules/__tests__/TestSequences/PushRelativePathnameError.js
new file mode 100644
index 000000000..7c8a0759d
--- /dev/null
+++ b/modules/__tests__/TestSequences/PushRelativePathnameError.js
@@ -0,0 +1,31 @@
+import expect from 'expect';
+
+import execSteps from './execSteps.js';
+
+export default function(history, done) {
+ let steps = [
+ ({ location }) => {
+ expect(location).toMatchObject({
+ pathname: '/'
+ });
+
+ history.push('/the/path?the=query#the-hash');
+ },
+ ({ action, location }) => {
+ expect(action).toBe('PUSH');
+ expect(location).toMatchObject({
+ pathname: '/the/path',
+ search: '?the=query',
+ hash: '#the-hash'
+ });
+
+ try {
+ history.push('../other/path?another=query#another-hash');
+ } catch (error) {
+ expect(error.message).toMatch(/relative pathnames are not supported/i);
+ }
+ }
+ ];
+
+ execSteps(steps, history, done);
+}
diff --git a/modules/__tests__/TestSequences/PushSamePath.js b/modules/__tests__/TestSequences/PushSamePath.js
index 07cdc7ea1..765ae661b 100644
--- a/modules/__tests__/TestSequences/PushSamePath.js
+++ b/modules/__tests__/TestSequences/PushSamePath.js
@@ -3,15 +3,15 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
- location => {
+ let steps = [
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/'
});
history.push('/home');
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('PUSH');
expect(location).toMatchObject({
pathname: '/home'
@@ -19,15 +19,15 @@ export default function(history, done) {
history.push('/home');
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('PUSH');
expect(location).toMatchObject({
pathname: '/home'
});
- history.goBack();
+ history.back();
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('POP');
expect(location).toMatchObject({
pathname: '/home'
diff --git a/modules/__tests__/TestSequences/PushSamePathWarning.js b/modules/__tests__/TestSequences/PushSamePathWarning.js
deleted file mode 100644
index df144ef00..000000000
--- a/modules/__tests__/TestSequences/PushSamePathWarning.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps.js';
-
-export default function(history, done) {
- let prevLocation;
-
- const steps = [
- location => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- history.push('/home');
- },
- (location, action) => {
- expect(action).toBe('PUSH');
- expect(location).toMatchObject({
- pathname: '/home'
- });
-
- prevLocation = location;
-
- history.push('/home');
- },
- (location, action) => {
- expect(action).toBe('PUSH');
- expect(location).toMatchObject({
- pathname: '/home'
- });
-
- // We should get the SAME location object. Nothing
- // new was added to the history stack.
- expect(location).toBe(prevLocation);
-
- // We should see a warning message.
- expect(warningMessage).toMatch(
- 'Hash history cannot PUSH the same path; a new entry will not be added to the history stack'
- );
- }
- ];
-
- let consoleWarn = console.error; // eslint-disable-line no-console
- let warningMessage;
-
- // eslint-disable-next-line no-console
- console.warn = message => {
- warningMessage = message;
- };
-
- execSteps(steps, history, (...args) => {
- console.warn = consoleWarn; // eslint-disable-line no-console
- done(...args);
- });
-}
diff --git a/modules/__tests__/TestSequences/PushState.js b/modules/__tests__/TestSequences/PushState.js
index b22813b74..0d111e61b 100644
--- a/modules/__tests__/TestSequences/PushState.js
+++ b/modules/__tests__/TestSequences/PushState.js
@@ -3,15 +3,15 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
- location => {
+ let steps = [
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/'
});
history.push('/home?the=query#the-hash', { the: 'state' });
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('PUSH');
expect(location).toMatchObject({
pathname: '/home',
diff --git a/modules/__tests__/TestSequences/PushStateWarning.js b/modules/__tests__/TestSequences/PushStateWarning.js
deleted file mode 100644
index d19fdab08..000000000
--- a/modules/__tests__/TestSequences/PushStateWarning.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps.js';
-
-export default function(history, done) {
- const steps = [
- location => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- history.push('/home', { the: 'state' });
- },
- (location, action) => {
- expect(action).toBe('PUSH');
- expect(location).toMatchObject({
- pathname: '/home',
- state: undefined
- });
-
- // We should see a warning message.
- expect(warningMessage).toMatch(
- 'Hash history cannot push state; it is ignored'
- );
- }
- ];
-
- let consoleWarn = console.warn; // eslint-disable-line no-console
- let warningMessage;
-
- // eslint-disable-next-line no-console
- console.warn = message => {
- warningMessage = message;
- };
-
- execSteps(steps, history, (...args) => {
- console.warn = consoleWarn; // eslint-disable-line no-console
- done(...args);
- });
-}
diff --git a/modules/__tests__/TestSequences/PushUnicodeLocation.js b/modules/__tests__/TestSequences/PushUnicodeLocation.js
index 2bad4645c..8401c2c87 100644
--- a/modules/__tests__/TestSequences/PushUnicodeLocation.js
+++ b/modules/__tests__/TestSequences/PushUnicodeLocation.js
@@ -3,23 +3,26 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
- location => {
+ let steps = [
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/'
});
- const pathname = '/歴史';
- const search = '?キー=値';
- const hash = '#ハッシュ';
+ let pathname = '/歴史';
+ let search = '?キー=値';
+ let hash = '#ハッシュ';
history.push(pathname + search + hash);
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('PUSH');
expect(location).toMatchObject({
- pathname: '/歴史',
- search: '?キー=値',
- hash: '#ハッシュ'
+ pathname: '/%E6%AD%B4%E5%8F%B2',
+ search: '?%E3%82%AD%E3%83%BC=%E5%80%A4',
+ hash: '#%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5'
+ // pathname: '/歴史',
+ // search: '?キー=値',
+ // hash: '#ハッシュ'
});
}
];
diff --git a/modules/__tests__/TestSequences/ReplaceInvalidPathname.js b/modules/__tests__/TestSequences/ReplaceInvalidPathname.js
deleted file mode 100644
index 5b85431f7..000000000
--- a/modules/__tests__/TestSequences/ReplaceInvalidPathname.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps';
-
-export default function(history, done) {
- const steps = [
- () => {
- expect(() => {
- history.replace('/hello%');
- }).toThrow(
- 'Pathname "/hello%" could not be decoded. This is likely caused by an invalid percent-encoding.'
- );
- }
- ];
-
- execSteps(steps, history, done);
-}
diff --git a/modules/__tests__/TestSequences/ReplaceNewLocation.js b/modules/__tests__/TestSequences/ReplaceNewLocation.js
index 7fb49c80e..fcfb3c421 100644
--- a/modules/__tests__/TestSequences/ReplaceNewLocation.js
+++ b/modules/__tests__/TestSequences/ReplaceNewLocation.js
@@ -3,15 +3,15 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
- location => {
+ let steps = [
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/'
});
history.replace('/home?the=query#the-hash');
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('REPLACE');
expect(location).toMatchObject({
pathname: '/home',
diff --git a/modules/__tests__/TestSequences/ReplaceSamePath.js b/modules/__tests__/TestSequences/ReplaceSamePath.js
index 2dd7ce39c..ffd292f09 100644
--- a/modules/__tests__/TestSequences/ReplaceSamePath.js
+++ b/modules/__tests__/TestSequences/ReplaceSamePath.js
@@ -5,15 +5,15 @@ import execSteps from './execSteps.js';
export default function(history, done) {
let prevLocation;
- const steps = [
- location => {
+ let steps = [
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/'
});
history.replace('/home');
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('REPLACE');
expect(location).toMatchObject({
pathname: '/home'
@@ -23,7 +23,7 @@ export default function(history, done) {
history.replace('/home');
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('REPLACE');
expect(location).toMatchObject({
pathname: '/home'
diff --git a/modules/__tests__/TestSequences/ReplaceState.js b/modules/__tests__/TestSequences/ReplaceState.js
index 7f425d79f..7e3e50a61 100644
--- a/modules/__tests__/TestSequences/ReplaceState.js
+++ b/modules/__tests__/TestSequences/ReplaceState.js
@@ -3,15 +3,15 @@ import expect from 'expect';
import execSteps from './execSteps.js';
export default function(history, done) {
- const steps = [
- location => {
+ let steps = [
+ ({ location }) => {
expect(location).toMatchObject({
pathname: '/'
});
history.replace('/home?the=query#the-hash', { the: 'state' });
},
- (location, action) => {
+ ({ action, location }) => {
expect(action).toBe('REPLACE');
expect(location).toMatchObject({
pathname: '/home',
diff --git a/modules/__tests__/TestSequences/ReplaceStateWarning.js b/modules/__tests__/TestSequences/ReplaceStateWarning.js
deleted file mode 100644
index a2804abfd..000000000
--- a/modules/__tests__/TestSequences/ReplaceStateWarning.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps.js';
-
-export default function(history, done) {
- const steps = [
- location => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- history.replace('/home', { the: 'state' });
- },
- (location, action) => {
- expect(action).toBe('REPLACE');
- expect(location).toMatchObject({
- pathname: '/home',
- state: undefined
- });
-
- // We should see a warning message.
- expect(warningMessage).toMatch(
- 'Hash history cannot replace state; it is ignored'
- );
- }
- ];
-
- let consoleWarn = console.warn; // eslint-disable-line no-console
- let warningMessage;
-
- // eslint-disable-next-line no-console
- console.warn = message => {
- warningMessage = message;
- };
-
- execSteps(steps, history, (...args) => {
- console.warn = consoleWarn; // eslint-disable-line no-console
- done(...args);
- });
-}
diff --git a/modules/__tests__/TestSequences/ReturnFalseTransitionHook.js b/modules/__tests__/TestSequences/ReturnFalseTransitionHook.js
deleted file mode 100644
index d3894fb8a..000000000
--- a/modules/__tests__/TestSequences/ReturnFalseTransitionHook.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps.js';
-
-export default function(history, done) {
- const steps = [
- location => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- const unblock = history.block(nextLocation => {
- expect(nextLocation).toMatchObject({
- pathname: '/home'
- });
-
- // Cancel the transition.
- return false;
- });
-
- history.push('/home');
-
- expect(history.location).toMatchObject({
- pathname: '/'
- });
-
- unblock();
- }
- ];
-
- execSteps(steps, history, done);
-}
diff --git a/modules/__tests__/TestSequences/SlashHashPathCoding.js b/modules/__tests__/TestSequences/SlashHashPathCoding.js
deleted file mode 100644
index 6911d217c..000000000
--- a/modules/__tests__/TestSequences/SlashHashPathCoding.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps.js';
-
-export default function(history, done) {
- const steps = [
- location => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- expect(window.location.hash).toBe('#/');
-
- history.push('/home?the=query#the-hash');
- },
- location => {
- expect(location).toMatchObject({
- pathname: '/home',
- search: '?the=query',
- hash: '#the-hash'
- });
-
- expect(window.location.hash).toBe('#/home?the=query#the-hash');
-
- history.goBack();
- },
- location => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- expect(window.location.hash).toBe('#/');
-
- history.goForward();
- },
- location => {
- expect(location).toMatchObject({
- pathname: '/home',
- search: '?the=query',
- hash: '#the-hash'
- });
-
- expect(window.location.hash).toBe('#/home?the=query#the-hash');
- }
- ];
-
- execSteps(steps, history, done);
-}
diff --git a/modules/__tests__/TestSequences/TransitionHookArgs.js b/modules/__tests__/TestSequences/TransitionHookArgs.js
deleted file mode 100644
index bc79f020c..000000000
--- a/modules/__tests__/TestSequences/TransitionHookArgs.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps.js';
-
-export default function(history, done) {
- let hookLocation, hookAction;
- const steps = [
- location => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- history.push('/home');
- },
- (location, action) => {
- expect(hookAction).toBe(action);
- expect(hookLocation).toBe(location);
- }
- ];
-
- const unblock = history.block((location, action) => {
- hookLocation = location;
- hookAction = action;
-
- return 'Are you sure?';
- });
-
- execSteps(steps, history, (...args) => {
- unblock();
- done(...args);
- });
-}
diff --git a/modules/__tests__/TestSequences/execSteps.js b/modules/__tests__/TestSequences/execSteps.js
index 264560a34..0e3c836a7 100644
--- a/modules/__tests__/TestSequences/execSteps.js
+++ b/modules/__tests__/TestSequences/execSteps.js
@@ -3,7 +3,7 @@ export default function execSteps(steps, history, done) {
unlisten,
cleanedUp = false;
- const cleanup = (...args) => {
+ let cleanup = (...args) => {
if (!cleanedUp) {
cleanedUp = true;
unlisten();
@@ -11,9 +11,9 @@ export default function execSteps(steps, history, done) {
}
};
- const execNextStep = (...args) => {
+ let execNextStep = (...args) => {
try {
- const nextStep = steps[index++];
+ let nextStep = steps[index++];
if (!nextStep) throw new Error('Test is missing step ' + index);
@@ -27,7 +27,11 @@ export default function execSteps(steps, history, done) {
if (steps.length) {
unlisten = history.listen(execNextStep);
- execNextStep(history.location);
+
+ execNextStep({
+ action: history.action,
+ location: history.location
+ });
} else {
done();
}
diff --git a/modules/__tests__/TestSequences/index.js b/modules/__tests__/TestSequences/index.js
deleted file mode 100644
index e48e6e738..000000000
--- a/modules/__tests__/TestSequences/index.js
+++ /dev/null
@@ -1,46 +0,0 @@
-export {
- default as BackButtonTransitionHook
-} from './BackButtonTransitionHook.js';
-export { default as BlockEverything } from './BlockEverything.js';
-export {
- default as BlockPopWithoutListening
-} from './BlockPopWithoutListening.js';
-export { default as DenyPush } from './DenyPush.js';
-export { default as DenyGoBack } from './DenyGoBack.js';
-export { default as DenyGoForward } from './DenyGoForward.js';
-export {
- default as EncodedReservedCharacters
-} from './EncodedReservedCharacters.js';
-export { default as GoBack } from './GoBack.js';
-export { default as GoForward } from './GoForward.js';
-export { default as HashbangHashPathCoding } from './HashbangHashPathCoding.js';
-export {
- default as HashChangeTransitionHook
-} from './HashChangeTransitionHook.js';
-export { default as InitialLocationNoKey } from './InitialLocationNoKey.js';
-export { default as InitialLocationHasKey } from './InitialLocationHasKey.js';
-export { default as Listen } from './Listen.js';
-export {
- default as LocationPathnameAlwaysSame
-} from './LocationPathnameAlwaysSame.js';
-export { default as NoslashHashPathCoding } from './NoslashHashPathCoding.js';
-export { default as PushEncodedLocation } from './PushEncodedLocation.js';
-export { default as PushInvalidPathname } from './PushInvalidPathname.js';
-export { default as PushNewLocation } from './PushNewLocation.js';
-export { default as PushMissingPathname } from './PushMissingPathname.js';
-export { default as PushSamePath } from './PushSamePath.js';
-export { default as PushSamePathWarning } from './PushSamePathWarning.js';
-export { default as PushState } from './PushState.js';
-export { default as PushStateWarning } from './PushStateWarning.js';
-export { default as PushRelativePathname } from './PushRelativePathname.js';
-export { default as PushUnicodeLocation } from './PushUnicodeLocation.js';
-export { default as ReplaceInvalidPathname } from './ReplaceInvalidPathname.js';
-export { default as ReplaceNewLocation } from './ReplaceNewLocation.js';
-export { default as ReplaceSamePath } from './ReplaceSamePath.js';
-export { default as ReplaceState } from './ReplaceState.js';
-export { default as ReplaceStateWarning } from './ReplaceStateWarning.js';
-export {
- default as ReturnFalseTransitionHook
-} from './ReturnFalseTransitionHook.js';
-export { default as SlashHashPathCoding } from './SlashHashPathCoding.js';
-export { default as TransitionHookArgs } from './TransitionHookArgs.js';
diff --git a/modules/__tests__/createBrowserHistory-test.js b/modules/__tests__/createBrowserHistory-test.js
new file mode 100644
index 000000000..3d9bf02b3
--- /dev/null
+++ b/modules/__tests__/createBrowserHistory-test.js
@@ -0,0 +1,163 @@
+import expect from 'expect';
+import { createBrowserHistory } from 'history';
+
+import InitialLocationDefaultKey from './TestSequences/InitialLocationDefaultKey.js';
+import Listen from './TestSequences/Listen.js';
+import PushNewLocation from './TestSequences/PushNewLocation.js';
+import PushSamePath from './TestSequences/PushSamePath.js';
+import PushState from './TestSequences/PushState.js';
+import PushMissingPathname from './TestSequences/PushMissingPathname.js';
+import PushRelativePathname from './TestSequences/PushRelativePathname.js';
+import PushUnicodeLocation from './TestSequences/PushUnicodeLocation.js';
+import PushEncodedLocation from './TestSequences/PushEncodedLocation.js';
+import ReplaceNewLocation from './TestSequences/ReplaceNewLocation.js';
+import ReplaceSamePath from './TestSequences/ReplaceSamePath.js';
+import ReplaceState from './TestSequences/ReplaceState.js';
+import LocationPathnameAlwaysSame from './TestSequences/LocationPathnameAlwaysSame.js';
+import EncodedReservedCharacters from './TestSequences/EncodedReservedCharacters.js';
+import GoBack from './TestSequences/GoBack.js';
+import GoForward from './TestSequences/GoForward.js';
+import BlockEverything from './TestSequences/BlockEverything.js';
+import BlockPopWithoutListening from './TestSequences/BlockPopWithoutListening.js';
+
+describe('a browser history', () => {
+ let history;
+ beforeEach(() => {
+ if (window.location.pathname !== '/') {
+ window.history.replaceState(null, null, '/');
+ }
+ history = createBrowserHistory();
+ });
+
+ it('knows how to create hrefs', () => {
+ const href = history.createHref({
+ pathname: '/the/path',
+ search: '?the=query',
+ hash: '#the-hash'
+ });
+
+ expect(href).toEqual('/the/path?the=query#the-hash');
+ });
+
+ it('does not encode the generated path', () => {
+ // encoded
+ const encodedHref = history.createHref({
+ pathname: '/%23abc'
+ });
+ // unencoded
+ const unencodedHref = history.createHref({
+ pathname: '/#abc'
+ });
+
+ expect(encodedHref).toEqual('/%23abc');
+ expect(unencodedHref).toEqual('/#abc');
+ });
+
+ describe('listen', () => {
+ it('does not immediately call listeners', done => {
+ Listen(history, done);
+ });
+ });
+
+ describe('the initial location', () => {
+ it('has the "default" key', done => {
+ InitialLocationDefaultKey(history, done);
+ });
+ });
+
+ describe('push a new path', () => {
+ it('calls change listeners with the new location', done => {
+ PushNewLocation(history, done);
+ });
+ });
+
+ describe('push the same path', () => {
+ it('calls change listeners with the new location', done => {
+ PushSamePath(history, done);
+ });
+ });
+
+ describe('push state', () => {
+ it('calls change listeners with the new location', done => {
+ PushState(history, done);
+ });
+ });
+
+ describe.skip('push with no pathname', () => {
+ it('calls change listeners with the normalized location', done => {
+ PushMissingPathname(history, done);
+ });
+ });
+
+ describe('push with a relative pathname', () => {
+ it('calls change listeners with the normalized location', done => {
+ PushRelativePathname(history, done);
+ });
+ });
+
+ describe('push with a unicode path string', () => {
+ it('creates a location with encoded properties', done => {
+ PushUnicodeLocation(history, done);
+ });
+ });
+
+ describe('push with an encoded path string', () => {
+ it('creates a location with encoded pathname', done => {
+ PushEncodedLocation(history, done);
+ });
+ });
+
+ describe('replace a new path', () => {
+ it('calls change listeners with the new location', done => {
+ ReplaceNewLocation(history, done);
+ });
+ });
+
+ describe('replace the same path', () => {
+ it('calls change listeners with the new location', done => {
+ ReplaceSamePath(history, done);
+ });
+ });
+
+ describe('replace state', () => {
+ it('calls change listeners with the new location', done => {
+ ReplaceState(history, done);
+ });
+ });
+
+ describe('location created by encoded and unencoded pathname', () => {
+ it('produces the same location.pathname', done => {
+ LocationPathnameAlwaysSame(history, done);
+ });
+ });
+
+ describe('location created with encoded/unencoded reserved characters', () => {
+ it('produces different location objects', done => {
+ EncodedReservedCharacters(history, done);
+ });
+ });
+
+ describe('back', () => {
+ it('calls change listeners with the previous location', done => {
+ GoBack(history, done);
+ });
+ });
+
+ describe('forward', () => {
+ it('calls change listeners with the next location', done => {
+ GoForward(history, done);
+ });
+ });
+
+ describe('block', () => {
+ it('blocks all transitions', done => {
+ BlockEverything(history, done);
+ });
+ });
+
+ describe('block a POP without listening', () => {
+ it('receives the next ({ action, location })', done => {
+ BlockPopWithoutListening(history, done);
+ });
+ });
+});
diff --git a/modules/__tests__/HashHistory-base-test.js b/modules/__tests__/createHashHistory-base-test.js
similarity index 91%
rename from modules/__tests__/HashHistory-base-test.js
rename to modules/__tests__/createHashHistory-base-test.js
index b3bd8b924..dbbfa3ac0 100644
--- a/modules/__tests__/HashHistory-base-test.js
+++ b/modules/__tests__/createHashHistory-base-test.js
@@ -4,6 +4,10 @@ import { createHashHistory } from 'history';
describe('a hash history on a page with a tag', () => {
let history, base;
beforeEach(() => {
+ if (window.location.hash !== '#/') {
+ window.location.hash = '/';
+ }
+
base = document.createElement('base');
base.setAttribute('href', '/prefix');
diff --git a/modules/__tests__/createHashHistory-test.js b/modules/__tests__/createHashHistory-test.js
new file mode 100644
index 000000000..cd71f98c9
--- /dev/null
+++ b/modules/__tests__/createHashHistory-test.js
@@ -0,0 +1,167 @@
+import expect from 'expect';
+import { createHashHistory } from 'history';
+
+import Listen from './TestSequences/Listen.js';
+import InitialLocationDefaultKey from './TestSequences/InitialLocationDefaultKey.js';
+import PushNewLocation from './TestSequences/PushNewLocation.js';
+import PushSamePath from './TestSequences/PushSamePath.js';
+import PushState from './TestSequences/PushState.js';
+import PushMissingPathname from './TestSequences/PushMissingPathname.js';
+import PushRelativePathnameError from './TestSequences/PushRelativePathnameError.js';
+import PushUnicodeLocation from './TestSequences/PushUnicodeLocation.js';
+import PushEncodedLocation from './TestSequences/PushEncodedLocation.js';
+import ReplaceNewLocation from './TestSequences/ReplaceNewLocation.js';
+import ReplaceSamePath from './TestSequences/ReplaceSamePath.js';
+import ReplaceState from './TestSequences/ReplaceState.js';
+import LocationPathnameAlwaysSame from './TestSequences/LocationPathnameAlwaysSame.js';
+import EncodedReservedCharacters from './TestSequences/EncodedReservedCharacters.js';
+import GoBack from './TestSequences/GoBack.js';
+import GoForward from './TestSequences/GoForward.js';
+import BlockEverything from './TestSequences/BlockEverything.js';
+import BlockPopWithoutListening from './TestSequences/BlockPopWithoutListening.js';
+
+// TODO: Do we still need this?
+// const canGoWithoutReload = window.navigator.userAgent.indexOf('Firefox') === -1;
+// const describeGo = canGoWithoutReload ? describe : describe.skip;
+
+describe('a hash history', () => {
+ let history;
+ beforeEach(() => {
+ if (window.location.hash !== '#/') {
+ window.location.hash = '/';
+ }
+ history = createHashHistory();
+ });
+
+ it('knows how to create hrefs', () => {
+ const href = history.createHref({
+ pathname: '/the/path',
+ search: '?the=query',
+ hash: '#the-hash'
+ });
+
+ expect(href).toEqual('#/the/path?the=query#the-hash');
+ });
+
+ it('does not encode the generated path', () => {
+ // encoded
+ const encodedHref = history.createHref({
+ pathname: '/%23abc'
+ });
+ // unencoded
+ const unencodedHref = history.createHref({
+ pathname: '/#abc'
+ });
+
+ expect(encodedHref).toEqual('#/%23abc');
+ expect(unencodedHref).toEqual('#/#abc');
+ });
+
+ describe('listen', () => {
+ it('does not immediately call listeners', done => {
+ Listen(history, done);
+ });
+ });
+
+ describe('the initial location', () => {
+ it('has the "default" key', done => {
+ InitialLocationDefaultKey(history, done);
+ });
+ });
+
+ describe('push a new path', () => {
+ it('calls change listeners with the new location', done => {
+ PushNewLocation(history, done);
+ });
+ });
+
+ describe('push the same path', () => {
+ it('calls change listeners with the new location', done => {
+ PushSamePath(history, done);
+ });
+ });
+
+ describe('push state', () => {
+ it('calls change listeners with the new location', done => {
+ PushState(history, done);
+ });
+ });
+
+ describe.skip('push with no pathname', () => {
+ it('calls change listeners with the normalized location', done => {
+ PushMissingPathname(history, done);
+ });
+ });
+
+ describe('push with a relative pathname', () => {
+ it('throws an error', done => {
+ PushRelativePathnameError(history, done);
+ });
+ });
+
+ describe('push with a unicode path string', () => {
+ it('creates a location with decoded properties', done => {
+ PushUnicodeLocation(history, done);
+ });
+ });
+
+ describe('push with an encoded path string', () => {
+ it('creates a location object with encoded pathname', done => {
+ PushEncodedLocation(history, done);
+ });
+ });
+
+ describe('replace a new path', () => {
+ it('calls change listeners with the new location', done => {
+ ReplaceNewLocation(history, done);
+ });
+ });
+
+ describe('replace the same path', () => {
+ it('calls change listeners with the new location', done => {
+ ReplaceSamePath(history, done);
+ });
+ });
+
+ describe('replace state', () => {
+ it('calls change listeners with the new location', done => {
+ ReplaceState(history, done);
+ });
+ });
+
+ describe('location created by encoded and unencoded pathname', () => {
+ it('produces the same location.pathname', done => {
+ LocationPathnameAlwaysSame(history, done);
+ });
+ });
+
+ describe('location created with encoded/unencoded reserved characters', () => {
+ it('produces different location objects', done => {
+ EncodedReservedCharacters(history, done);
+ });
+ });
+
+ describe('back', () => {
+ it('calls change listeners with the previous location', done => {
+ GoBack(history, done);
+ });
+ });
+
+ describe('forward', () => {
+ it('calls change listeners with the next location', done => {
+ GoForward(history, done);
+ });
+ });
+
+ describe('block', () => {
+ it('blocks all transitions', done => {
+ BlockEverything(history, done);
+ });
+ });
+
+ describe('block a POP without listening', () => {
+ it('receives the next location and action as arguments', done => {
+ BlockPopWithoutListening(history, done);
+ });
+ });
+});
diff --git a/modules/__tests__/createLocation-test.js b/modules/__tests__/createLocation-test.js
deleted file mode 100644
index 27e084265..000000000
--- a/modules/__tests__/createLocation-test.js
+++ /dev/null
@@ -1,144 +0,0 @@
-import expect from 'expect';
-import { createLocation } from 'history';
-
-describe('createLocation', () => {
- describe('with a full path', () => {
- describe('given as a string', () => {
- it('has the correct properties', () => {
- expect(createLocation('/the/path?the=query#the-hash')).toMatchObject({
- pathname: '/the/path',
- search: '?the=query',
- hash: '#the-hash'
- });
- });
- });
-
- describe('given as an object', () => {
- it('has the correct properties', () => {
- expect(
- createLocation({
- pathname: '/the/path',
- search: '?the=query',
- hash: '#the-hash'
- })
- ).toMatchObject({
- pathname: '/the/path',
- search: '?the=query',
- hash: '#the-hash'
- });
- });
- });
- });
-
- describe('with a relative path', () => {
- describe('given as a string', () => {
- it('has the correct properties', () => {
- expect(createLocation('the/path?the=query#the-hash')).toMatchObject({
- pathname: 'the/path',
- search: '?the=query',
- hash: '#the-hash'
- });
- });
- });
-
- describe('given as an object', () => {
- it('has the correct properties', () => {
- expect(
- createLocation({
- pathname: 'the/path',
- search: '?the=query',
- hash: '#the-hash'
- })
- ).toMatchObject({
- pathname: 'the/path',
- search: '?the=query',
- hash: '#the-hash'
- });
- });
- });
- });
-
- describe('with a path with no pathname', () => {
- describe('given as a string', () => {
- it('has the correct properties', () => {
- expect(createLocation('?the=query#the-hash')).toMatchObject({
- pathname: '/',
- search: '?the=query',
- hash: '#the-hash'
- });
- });
- });
-
- describe('given as an object', () => {
- it('has the correct properties', () => {
- expect(
- createLocation({ search: '?the=query', hash: '#the-hash' })
- ).toMatchObject({
- pathname: '/',
- search: '?the=query',
- hash: '#the-hash'
- });
- });
- });
- });
-
- describe('with a path with no search', () => {
- describe('given as a string', () => {
- it('has the correct properties', () => {
- expect(createLocation('/the/path#the-hash')).toMatchObject({
- pathname: '/the/path',
- search: '',
- hash: '#the-hash'
- });
- });
- });
-
- describe('given as an object', () => {
- it('has the correct properties', () => {
- expect(
- createLocation({ pathname: '/the/path', hash: '#the-hash' })
- ).toMatchObject({
- pathname: '/the/path',
- search: '',
- hash: '#the-hash'
- });
- });
- });
- });
-
- describe('with a path with no hash', () => {
- describe('given as a string', () => {
- it('has the correct properties', () => {
- expect(createLocation('/the/path?the=query')).toMatchObject({
- pathname: '/the/path',
- search: '?the=query',
- hash: ''
- });
- });
- });
-
- describe('given as an object', () => {
- it('has the correct properties', () => {
- expect(
- createLocation({ pathname: '/the/path', search: '?the=query' })
- ).toMatchObject({
- pathname: '/the/path',
- search: '?the=query',
- hash: ''
- });
- });
- });
- });
-
- describe('key', () => {
- it('has a key property if a key is provided', () => {
- const location = createLocation('/the/path', undefined, 'key');
- expect(Object.keys(location)).toContain('key');
- });
-
- it('has no key property if no key is provided', () => {
- const location = createLocation('/the/path');
- expect(Object.keys(location)).not.toContain('key');
- });
- });
-});
diff --git a/modules/__tests__/createMemoryHistory-test.js b/modules/__tests__/createMemoryHistory-test.js
new file mode 100644
index 000000000..25d69e313
--- /dev/null
+++ b/modules/__tests__/createMemoryHistory-test.js
@@ -0,0 +1,139 @@
+import expect from 'expect';
+import { createMemoryHistory } from 'history';
+
+import Listen from './TestSequences/Listen.js';
+import InitialLocationHasKey from './TestSequences/InitialLocationHasKey.js';
+import PushNewLocation from './TestSequences/PushNewLocation.js';
+import PushSamePath from './TestSequences/PushSamePath.js';
+import PushState from './TestSequences/PushState.js';
+import PushMissingPathname from './TestSequences/PushMissingPathname.js';
+import PushRelativePathnameError from './TestSequences/PushRelativePathnameError.js';
+import ReplaceNewLocation from './TestSequences/ReplaceNewLocation.js';
+import ReplaceSamePath from './TestSequences/ReplaceSamePath.js';
+import ReplaceState from './TestSequences/ReplaceState.js';
+import EncodedReservedCharacters from './TestSequences/EncodedReservedCharacters.js';
+import GoBack from './TestSequences/GoBack.js';
+import GoForward from './TestSequences/GoForward.js';
+import BlockEverything from './TestSequences/BlockEverything.js';
+import BlockPopWithoutListening from './TestSequences/BlockPopWithoutListening.js';
+
+describe('a memory history', () => {
+ let history;
+ beforeEach(() => {
+ history = createMemoryHistory();
+ });
+
+ it('knows how to create hrefs', () => {
+ const href = history.createHref({
+ pathname: '/the/path',
+ search: '?the=query',
+ hash: '#the-hash'
+ });
+
+ expect(href).toEqual('/the/path?the=query#the-hash');
+ });
+
+ it('does not encode the generated path', () => {
+ // encoded
+ const encodedHref = history.createHref({
+ pathname: '/%23abc'
+ });
+ // unencoded
+ const unencodedHref = history.createHref({
+ pathname: '/#abc'
+ });
+
+ expect(encodedHref).toEqual('/%23abc');
+ expect(unencodedHref).toEqual('/#abc');
+ });
+
+ describe('listen', () => {
+ it('does not immediately call listeners', done => {
+ Listen(history, done);
+ });
+ });
+
+ describe('the initial location', () => {
+ it('has a key', done => {
+ InitialLocationHasKey(history, done);
+ });
+ });
+
+ describe('push a new path', () => {
+ it('calls change listeners with the new location', done => {
+ PushNewLocation(history, done);
+ });
+ });
+
+ describe('push the same path', () => {
+ it('calls change listeners with the new location', done => {
+ PushSamePath(history, done);
+ });
+ });
+
+ describe('push state', () => {
+ it('calls change listeners with the new location', done => {
+ PushState(history, done);
+ });
+ });
+
+ describe.skip('push with no pathname', () => {
+ it('calls change listeners with the normalized location', done => {
+ PushMissingPathname(history, done);
+ });
+ });
+
+ describe('push with a relative pathname', () => {
+ it('throws an error', done => {
+ PushRelativePathnameError(history, done);
+ });
+ });
+
+ describe('replace a new path', () => {
+ it('calls change listeners with the new location', done => {
+ ReplaceNewLocation(history, done);
+ });
+ });
+
+ describe('replace the same path', () => {
+ it('calls change listeners with the new location', done => {
+ ReplaceSamePath(history, done);
+ });
+ });
+
+ describe('replace state', () => {
+ it('calls change listeners with the new location', done => {
+ ReplaceState(history, done);
+ });
+ });
+
+ describe('location created with encoded/unencoded reserved characters', () => {
+ it('produces different location objects', done => {
+ EncodedReservedCharacters(history, done);
+ });
+ });
+
+ describe('back', () => {
+ it('calls change listeners with the previous location', done => {
+ GoBack(history, done);
+ });
+ });
+
+ describe('forward', () => {
+ it('calls change listeners with the next location', done => {
+ GoForward(history, done);
+ });
+ });
+
+ describe('block', () => {
+ it('blocks all transitions', done => {
+ BlockEverything(history, done);
+ });
+ });
+
+ describe('block a POP without listening', () => {
+ it('receives the next location and action as arguments', done => {
+ BlockPopWithoutListening(history, done);
+ });
+ });
+});
diff --git a/modules/createBrowserHistory.js b/modules/createBrowserHistory.js
deleted file mode 100644
index dd1983dc0..000000000
--- a/modules/createBrowserHistory.js
+++ /dev/null
@@ -1,329 +0,0 @@
-import { createLocation } from './LocationUtils.js';
-import {
- addLeadingSlash,
- stripTrailingSlash,
- hasBasename,
- stripBasename,
- createPath
-} from './PathUtils.js';
-import createTransitionManager from './createTransitionManager.js';
-import {
- canUseDOM,
- getConfirmation,
- supportsHistory,
- supportsPopStateOnHashChange,
- isExtraneousPopstateEvent
-} from './DOMUtils.js';
-import invariant from './invariant.js';
-import warning from './warning.js';
-
-const PopStateEvent = 'popstate';
-const HashChangeEvent = 'hashchange';
-
-function getHistoryState() {
- try {
- return window.history.state || {};
- } catch (e) {
- // IE 11 sometimes throws when accessing window.history.state
- // See https://github.com/ReactTraining/history/pull/289
- return {};
- }
-}
-
-/**
- * Creates a history object that uses the HTML5 history API including
- * pushState, replaceState, and the popstate event.
- */
-function createBrowserHistory(props = {}) {
- invariant(canUseDOM, 'Browser history needs a DOM');
-
- const globalHistory = window.history;
- const canUseHistory = supportsHistory();
- const needsHashChangeListener = !supportsPopStateOnHashChange();
-
- const {
- forceRefresh = false,
- getUserConfirmation = getConfirmation,
- keyLength = 6
- } = props;
- const basename = props.basename
- ? stripTrailingSlash(addLeadingSlash(props.basename))
- : '';
-
- function getDOMLocation(historyState) {
- const { key, state } = historyState || {};
- const { pathname, search, hash } = window.location;
-
- let path = pathname + search + hash;
-
- warning(
- !basename || hasBasename(path, basename),
- 'You are attempting to use a basename on a page whose URL path does not begin ' +
- 'with the basename. Expected path "' +
- path +
- '" to begin with "' +
- basename +
- '".'
- );
-
- if (basename) path = stripBasename(path, basename);
-
- return createLocation(path, state, key);
- }
-
- function createKey() {
- return Math.random()
- .toString(36)
- .substr(2, keyLength);
- }
-
- const transitionManager = createTransitionManager();
-
- function setState(nextState) {
- Object.assign(history, nextState);
- history.length = globalHistory.length;
- transitionManager.notifyListeners(history.location, history.action);
- }
-
- function handlePopState(event) {
- // Ignore extraneous popstate events in WebKit.
- if (isExtraneousPopstateEvent(event)) return;
- handlePop(getDOMLocation(event.state));
- }
-
- function handleHashChange() {
- handlePop(getDOMLocation(getHistoryState()));
- }
-
- let forceNextPop = false;
-
- function handlePop(location) {
- if (forceNextPop) {
- forceNextPop = false;
- setState();
- } else {
- const action = 'POP';
-
- transitionManager.confirmTransitionTo(
- location,
- action,
- getUserConfirmation,
- ok => {
- if (ok) {
- setState({ action, location });
- } else {
- revertPop(location);
- }
- }
- );
- }
- }
-
- function revertPop(fromLocation) {
- const toLocation = history.location;
-
- // TODO: We could probably make this more reliable by
- // keeping a list of keys we've seen in sessionStorage.
- // Instead, we just default to 0 for keys we don't know.
-
- let toIndex = allKeys.indexOf(toLocation.key);
-
- if (toIndex === -1) toIndex = 0;
-
- let fromIndex = allKeys.indexOf(fromLocation.key);
-
- if (fromIndex === -1) fromIndex = 0;
-
- const delta = toIndex - fromIndex;
-
- if (delta) {
- forceNextPop = true;
- go(delta);
- }
- }
-
- const initialLocation = getDOMLocation(getHistoryState());
- let allKeys = [initialLocation.key];
-
- // Public interface
-
- function createHref(location) {
- return basename + createPath(location);
- }
-
- function push(path, state) {
- warning(
- !(
- typeof path === 'object' &&
- path.state !== undefined &&
- state !== undefined
- ),
- 'You should avoid providing a 2nd state argument to push when the 1st ' +
- 'argument is a location-like object that already has state; it is ignored'
- );
-
- const action = 'PUSH';
- const location = createLocation(path, state, createKey(), history.location);
-
- transitionManager.confirmTransitionTo(
- location,
- action,
- getUserConfirmation,
- ok => {
- if (!ok) return;
-
- const href = createHref(location);
- const { key, state } = location;
-
- if (canUseHistory) {
- globalHistory.pushState({ key, state }, null, href);
-
- if (forceRefresh) {
- window.location.href = href;
- } else {
- const prevIndex = allKeys.indexOf(history.location.key);
- const nextKeys = allKeys.slice(0, prevIndex + 1);
-
- nextKeys.push(location.key);
- allKeys = nextKeys;
-
- setState({ action, location });
- }
- } else {
- warning(
- state === undefined,
- 'Browser history cannot push state in browsers that do not support HTML5 history'
- );
-
- window.location.href = href;
- }
- }
- );
- }
-
- function replace(path, state) {
- warning(
- !(
- typeof path === 'object' &&
- path.state !== undefined &&
- state !== undefined
- ),
- 'You should avoid providing a 2nd state argument to replace when the 1st ' +
- 'argument is a location-like object that already has state; it is ignored'
- );
-
- const action = 'REPLACE';
- const location = createLocation(path, state, createKey(), history.location);
-
- transitionManager.confirmTransitionTo(
- location,
- action,
- getUserConfirmation,
- ok => {
- if (!ok) return;
-
- const href = createHref(location);
- const { key, state } = location;
-
- if (canUseHistory) {
- globalHistory.replaceState({ key, state }, null, href);
-
- if (forceRefresh) {
- window.location.replace(href);
- } else {
- const prevIndex = allKeys.indexOf(history.location.key);
-
- if (prevIndex !== -1) allKeys[prevIndex] = location.key;
-
- setState({ action, location });
- }
- } else {
- warning(
- state === undefined,
- 'Browser history cannot replace state in browsers that do not support HTML5 history'
- );
-
- window.location.replace(href);
- }
- }
- );
- }
-
- function go(n) {
- globalHistory.go(n);
- }
-
- function goBack() {
- go(-1);
- }
-
- function goForward() {
- go(1);
- }
-
- let listenerCount = 0;
-
- function checkDOMListeners(delta) {
- listenerCount += delta;
-
- if (listenerCount === 1 && delta === 1) {
- window.addEventListener(PopStateEvent, handlePopState);
-
- if (needsHashChangeListener)
- window.addEventListener(HashChangeEvent, handleHashChange);
- } else if (listenerCount === 0) {
- window.removeEventListener(PopStateEvent, handlePopState);
-
- if (needsHashChangeListener)
- window.removeEventListener(HashChangeEvent, handleHashChange);
- }
- }
-
- let isBlocked = false;
-
- function block(prompt = false) {
- const unblock = transitionManager.setPrompt(prompt);
-
- if (!isBlocked) {
- checkDOMListeners(1);
- isBlocked = true;
- }
-
- return () => {
- if (isBlocked) {
- isBlocked = false;
- checkDOMListeners(-1);
- }
-
- return unblock();
- };
- }
-
- function listen(listener) {
- const unlisten = transitionManager.appendListener(listener);
- checkDOMListeners(1);
-
- return () => {
- checkDOMListeners(-1);
- unlisten();
- };
- }
-
- const history = {
- length: globalHistory.length,
- action: 'POP',
- location: initialLocation,
- createHref,
- push,
- replace,
- go,
- goBack,
- goForward,
- block,
- listen
- };
-
- return history;
-}
-
-export default createBrowserHistory;
diff --git a/modules/createHashHistory.js b/modules/createHashHistory.js
deleted file mode 100644
index 6c29725b4..000000000
--- a/modules/createHashHistory.js
+++ /dev/null
@@ -1,361 +0,0 @@
-import { createLocation } from './LocationUtils.js';
-import {
- addLeadingSlash,
- stripLeadingSlash,
- stripTrailingSlash,
- hasBasename,
- stripBasename,
- createPath
-} from './PathUtils.js';
-import createTransitionManager from './createTransitionManager.js';
-import {
- canUseDOM,
- getConfirmation,
- supportsGoWithoutReloadUsingHash
-} from './DOMUtils.js';
-import invariant from './invariant.js';
-import warning from './warning.js';
-
-const HashChangeEvent = 'hashchange';
-
-const HashPathCoders = {
- hashbang: {
- encodePath: path =>
- path.charAt(0) === '!' ? path : '!/' + stripLeadingSlash(path),
- decodePath: path => (path.charAt(0) === '!' ? path.substr(1) : path)
- },
- noslash: {
- encodePath: stripLeadingSlash,
- decodePath: addLeadingSlash
- },
- slash: {
- encodePath: addLeadingSlash,
- decodePath: addLeadingSlash
- }
-};
-
-function stripHash(url) {
- const hashIndex = url.indexOf('#');
- return hashIndex === -1 ? url : url.slice(0, hashIndex);
-}
-
-function getHashPath() {
- // We can't use window.location.hash here because it's not
- // consistent across browsers - Firefox will pre-decode it!
- const href = window.location.href;
- const hashIndex = href.indexOf('#');
- return hashIndex === -1 ? '' : href.substring(hashIndex + 1);
-}
-
-function pushHashPath(path) {
- window.location.hash = path;
-}
-
-function replaceHashPath(path) {
- window.location.replace(stripHash(window.location.href) + '#' + path);
-}
-
-function createHashHistory(props = {}) {
- invariant(canUseDOM, 'Hash history needs a DOM');
-
- const globalHistory = window.history;
- const canGoWithoutReload = supportsGoWithoutReloadUsingHash();
-
- const { getUserConfirmation = getConfirmation, hashType = 'slash' } = props;
- const basename = props.basename
- ? stripTrailingSlash(addLeadingSlash(props.basename))
- : '';
-
- const { encodePath, decodePath } = HashPathCoders[hashType];
-
- function getDOMLocation() {
- let path = decodePath(getHashPath());
-
- warning(
- !basename || hasBasename(path, basename),
- 'You are attempting to use a basename on a page whose URL path does not begin ' +
- 'with the basename. Expected path "' +
- path +
- '" to begin with "' +
- basename +
- '".'
- );
-
- if (basename) path = stripBasename(path, basename);
-
- return createLocation(path);
- }
-
- const transitionManager = createTransitionManager();
-
- function setState(nextState) {
- Object.assign(history, nextState);
- history.length = globalHistory.length;
- transitionManager.notifyListeners(history.location, history.action);
- }
-
- let forceNextPop = false;
- let ignorePath = null;
-
- function locationsAreEqual(a, b) {
- return (
- a.pathname === b.pathname && a.search === b.search && a.hash === b.hash
- );
- }
-
- function handleHashChange() {
- const path = getHashPath();
- const encodedPath = encodePath(path);
-
- if (path !== encodedPath) {
- // Ensure we always have a properly-encoded hash.
- replaceHashPath(encodedPath);
- } else {
- const location = getDOMLocation();
- const prevLocation = history.location;
-
- if (!forceNextPop && locationsAreEqual(prevLocation, location)) return; // A hashchange doesn't always == location change.
-
- if (ignorePath === createPath(location)) return; // Ignore this change; we already setState in push/replace.
-
- ignorePath = null;
-
- handlePop(location);
- }
- }
-
- function handlePop(location) {
- if (forceNextPop) {
- forceNextPop = false;
- setState();
- } else {
- const action = 'POP';
-
- transitionManager.confirmTransitionTo(
- location,
- action,
- getUserConfirmation,
- ok => {
- if (ok) {
- setState({ action, location });
- } else {
- revertPop(location);
- }
- }
- );
- }
- }
-
- function revertPop(fromLocation) {
- const toLocation = history.location;
-
- // TODO: We could probably make this more reliable by
- // keeping a list of paths we've seen in sessionStorage.
- // Instead, we just default to 0 for paths we don't know.
-
- let toIndex = allPaths.lastIndexOf(createPath(toLocation));
-
- if (toIndex === -1) toIndex = 0;
-
- let fromIndex = allPaths.lastIndexOf(createPath(fromLocation));
-
- if (fromIndex === -1) fromIndex = 0;
-
- const delta = toIndex - fromIndex;
-
- if (delta) {
- forceNextPop = true;
- go(delta);
- }
- }
-
- // Ensure the hash is encoded properly before doing anything else.
- const path = getHashPath();
- const encodedPath = encodePath(path);
-
- if (path !== encodedPath) replaceHashPath(encodedPath);
-
- const initialLocation = getDOMLocation();
- let allPaths = [createPath(initialLocation)];
-
- // Public interface
-
- function createHref(location) {
- const baseTag = document.querySelector('base');
- let href = '';
- if (baseTag && baseTag.getAttribute('href')) {
- href = stripHash(window.location.href);
- }
- return href + '#' + encodePath(basename + createPath(location));
- }
-
- function push(path, state) {
- warning(
- state === undefined,
- 'Hash history cannot push state; it is ignored'
- );
-
- const action = 'PUSH';
- const location = createLocation(
- path,
- undefined,
- undefined,
- history.location
- );
-
- transitionManager.confirmTransitionTo(
- location,
- action,
- getUserConfirmation,
- ok => {
- if (!ok) return;
-
- const path = createPath(location);
- const encodedPath = encodePath(basename + path);
- const hashChanged = getHashPath() !== encodedPath;
-
- if (hashChanged) {
- // We cannot tell if a hashchange was caused by a PUSH, so we'd
- // rather setState here and ignore the hashchange. The caveat here
- // is that other hash histories in the page will consider it a POP.
- ignorePath = path;
- pushHashPath(encodedPath);
-
- const prevIndex = allPaths.lastIndexOf(createPath(history.location));
- const nextPaths = allPaths.slice(0, prevIndex + 1);
-
- nextPaths.push(path);
- allPaths = nextPaths;
-
- setState({ action, location });
- } else {
- warning(
- false,
- 'Hash history cannot PUSH the same path; a new entry will not be added to the history stack'
- );
-
- setState();
- }
- }
- );
- }
-
- function replace(path, state) {
- warning(
- state === undefined,
- 'Hash history cannot replace state; it is ignored'
- );
-
- const action = 'REPLACE';
- const location = createLocation(
- path,
- undefined,
- undefined,
- history.location
- );
-
- transitionManager.confirmTransitionTo(
- location,
- action,
- getUserConfirmation,
- ok => {
- if (!ok) return;
-
- const path = createPath(location);
- const encodedPath = encodePath(basename + path);
- const hashChanged = getHashPath() !== encodedPath;
-
- if (hashChanged) {
- // We cannot tell if a hashchange was caused by a REPLACE, so we'd
- // rather setState here and ignore the hashchange. The caveat here
- // is that other hash histories in the page will consider it a POP.
- ignorePath = path;
- replaceHashPath(encodedPath);
- }
-
- const prevIndex = allPaths.indexOf(createPath(history.location));
-
- if (prevIndex !== -1) allPaths[prevIndex] = path;
-
- setState({ action, location });
- }
- );
- }
-
- function go(n) {
- warning(
- canGoWithoutReload,
- 'Hash history go(n) causes a full page reload in this browser'
- );
-
- globalHistory.go(n);
- }
-
- function goBack() {
- go(-1);
- }
-
- function goForward() {
- go(1);
- }
-
- let listenerCount = 0;
-
- function checkDOMListeners(delta) {
- listenerCount += delta;
-
- if (listenerCount === 1 && delta === 1) {
- window.addEventListener(HashChangeEvent, handleHashChange);
- } else if (listenerCount === 0) {
- window.removeEventListener(HashChangeEvent, handleHashChange);
- }
- }
-
- let isBlocked = false;
-
- function block(prompt = false) {
- const unblock = transitionManager.setPrompt(prompt);
-
- if (!isBlocked) {
- checkDOMListeners(1);
- isBlocked = true;
- }
-
- return () => {
- if (isBlocked) {
- isBlocked = false;
- checkDOMListeners(-1);
- }
-
- return unblock();
- };
- }
-
- function listen(listener) {
- const unlisten = transitionManager.appendListener(listener);
- checkDOMListeners(1);
-
- return () => {
- checkDOMListeners(-1);
- unlisten();
- };
- }
-
- const history = {
- length: globalHistory.length,
- action: 'POP',
- location: initialLocation,
- createHref,
- push,
- replace,
- go,
- goBack,
- goForward,
- block,
- listen
- };
-
- return history;
-}
-
-export default createHashHistory;
diff --git a/modules/createMemoryHistory.js b/modules/createMemoryHistory.js
deleted file mode 100644
index 8b541ea1c..000000000
--- a/modules/createMemoryHistory.js
+++ /dev/null
@@ -1,186 +0,0 @@
-import { createPath } from './PathUtils.js';
-import { createLocation } from './LocationUtils.js';
-import createTransitionManager from './createTransitionManager.js';
-import warning from './warning.js';
-
-function clamp(n, lowerBound, upperBound) {
- return Math.min(Math.max(n, lowerBound), upperBound);
-}
-
-/**
- * Creates a history object that stores locations in memory.
- */
-function createMemoryHistory(props = {}) {
- const {
- getUserConfirmation,
- initialEntries = ['/'],
- initialIndex = 0,
- keyLength = 6
- } = props;
-
- const transitionManager = createTransitionManager();
-
- function setState(nextState) {
- Object.assign(history, nextState);
- history.length = history.entries.length;
- transitionManager.notifyListeners(history.location, history.action);
- }
-
- function createKey() {
- return Math.random()
- .toString(36)
- .substr(2, keyLength);
- }
-
- const index = clamp(initialIndex, 0, initialEntries.length - 1);
- const entries = initialEntries.map(entry =>
- typeof entry === 'string'
- ? createLocation(entry, undefined, createKey())
- : createLocation(entry, undefined, entry.key || createKey())
- );
-
- // Public interface
-
- const createHref = createPath;
-
- function push(path, state) {
- warning(
- !(
- typeof path === 'object' &&
- path.state !== undefined &&
- state !== undefined
- ),
- 'You should avoid providing a 2nd state argument to push when the 1st ' +
- 'argument is a location-like object that already has state; it is ignored'
- );
-
- const action = 'PUSH';
- const location = createLocation(path, state, createKey(), history.location);
-
- transitionManager.confirmTransitionTo(
- location,
- action,
- getUserConfirmation,
- ok => {
- if (!ok) return;
-
- const prevIndex = history.index;
- const nextIndex = prevIndex + 1;
-
- const nextEntries = history.entries.slice(0);
- if (nextEntries.length > nextIndex) {
- nextEntries.splice(
- nextIndex,
- nextEntries.length - nextIndex,
- location
- );
- } else {
- nextEntries.push(location);
- }
-
- setState({
- action,
- location,
- index: nextIndex,
- entries: nextEntries
- });
- }
- );
- }
-
- function replace(path, state) {
- warning(
- !(
- typeof path === 'object' &&
- path.state !== undefined &&
- state !== undefined
- ),
- 'You should avoid providing a 2nd state argument to replace when the 1st ' +
- 'argument is a location-like object that already has state; it is ignored'
- );
-
- const action = 'REPLACE';
- const location = createLocation(path, state, createKey(), history.location);
-
- transitionManager.confirmTransitionTo(
- location,
- action,
- getUserConfirmation,
- ok => {
- if (!ok) return;
-
- history.entries[history.index] = location;
-
- setState({ action, location });
- }
- );
- }
-
- function go(n) {
- const nextIndex = clamp(history.index + n, 0, history.entries.length - 1);
-
- const action = 'POP';
- const location = history.entries[nextIndex];
-
- transitionManager.confirmTransitionTo(
- location,
- action,
- getUserConfirmation,
- ok => {
- if (ok) {
- setState({
- action,
- location,
- index: nextIndex
- });
- } else {
- // Mimic the behavior of DOM histories by
- // causing a render after a cancelled POP.
- setState();
- }
- }
- );
- }
-
- function goBack() {
- go(-1);
- }
-
- function goForward() {
- go(1);
- }
-
- function canGo(n) {
- const nextIndex = history.index + n;
- return nextIndex >= 0 && nextIndex < history.entries.length;
- }
-
- function block(prompt = false) {
- return transitionManager.setPrompt(prompt);
- }
-
- function listen(listener) {
- return transitionManager.appendListener(listener);
- }
-
- const history = {
- length: entries.length,
- action: 'POP',
- location: entries[index],
- index,
- entries,
- createHref,
- push,
- replace,
- go,
- goBack,
- goForward,
- canGo,
- block,
- listen
- };
-
- return history;
-}
-
-export default createMemoryHistory;
diff --git a/modules/createTransitionManager.js b/modules/createTransitionManager.js
deleted file mode 100644
index 43732508c..000000000
--- a/modules/createTransitionManager.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import warning from './warning.js';
-
-function createTransitionManager() {
- let prompt = null;
-
- function setPrompt(nextPrompt) {
- warning(prompt == null, 'A history supports only one prompt at a time');
-
- prompt = nextPrompt;
-
- return () => {
- if (prompt === nextPrompt) prompt = null;
- };
- }
-
- function confirmTransitionTo(
- location,
- action,
- getUserConfirmation,
- callback
- ) {
- // TODO: If another transition starts while we're still confirming
- // the previous one, we may end up in a weird state. Figure out the
- // best way to handle this.
- if (prompt != null) {
- const result =
- typeof prompt === 'function' ? prompt(location, action) : prompt;
-
- if (typeof result === 'string') {
- if (typeof getUserConfirmation === 'function') {
- getUserConfirmation(result, callback);
- } else {
- warning(
- false,
- 'A history needs a getUserConfirmation function in order to use a prompt message'
- );
-
- callback(true);
- }
- } else {
- // Return false from a transition hook to cancel the transition.
- callback(result !== false);
- }
- } else {
- callback(true);
- }
- }
-
- let listeners = [];
-
- function appendListener(fn) {
- let isActive = true;
-
- function listener(...args) {
- if (isActive) fn(...args);
- }
-
- listeners.push(listener);
-
- return () => {
- isActive = false;
- listeners = listeners.filter(item => item !== listener);
- };
- }
-
- function notifyListeners(...args) {
- listeners.forEach(listener => listener(...args));
- }
-
- return {
- setPrompt,
- confirmTransitionTo,
- appendListener,
- notifyListeners
- };
-}
-
-export default createTransitionManager;
diff --git a/modules/index.js b/modules/index.js
index b02317ddb..9422193e7 100644
--- a/modules/index.js
+++ b/modules/index.js
@@ -1,5 +1,363 @@
-export { default as createBrowserHistory } from './createBrowserHistory';
-export { default as createHashHistory } from './createHashHistory';
-export { default as createMemoryHistory } from './createMemoryHistory';
-export { createLocation, locationsAreEqual } from './LocationUtils';
-export { parsePath, createPath } from './PathUtils';
+const PopAction = 'POP';
+const PushAction = 'PUSH';
+const ReplaceAction = 'REPLACE';
+
+const PopStateEvent = 'popstate';
+const HashChangeEvent = 'hashchange';
+
+// There's some duplication in this code, but only one create* method
+// should ever be used in a given web page, so it's best to just inline
+// everything for minification.
+
+export const createMemoryHistory = ({
+ initialEntries = ['/'],
+ initialIndex = 0
+} = {}) => {
+ let createLocation = ({
+ pathname = '/',
+ search = '',
+ hash = '',
+ state = null,
+ // Auto-assign keys to entries that don't already have them.
+ key = createKey()
+ }) => createReadOnlyObject({ pathname, search, hash, state, key });
+
+ let createNextLocation = (to, state) =>
+ createLocation({
+ ...(typeof to === 'string' ? parsePath(to) : to),
+ state,
+ key: createKey()
+ });
+
+ let handleAction = (nextIndex, nextAction, nextLocation) => {
+ if (__DEV__) {
+ if (nextLocation && nextLocation.pathname.charAt(0) !== '/') {
+ let arg = createPath(nextLocation);
+ let fnCall = `${nextAction.toLowerCase()}("${arg}")`;
+ throw new Error(
+ `Relative pathnames are not supported in createMemoryHistory().${fnCall}`
+ );
+ }
+ }
+
+ if (blockers.length) {
+ blockers.call({ action: nextAction, location: nextLocation });
+ } else {
+ index = nextIndex;
+ if (nextAction === PushAction) {
+ entries.splice(index, entries.length, nextLocation);
+ } else if (nextAction === ReplaceAction) {
+ entries[index] = nextLocation;
+ }
+ action = nextAction;
+ location = nextLocation || entries[index];
+ listeners.call({ action, location });
+ }
+ };
+
+ let entries = initialEntries.map(entry =>
+ createLocation(typeof entry === 'string' ? parsePath(entry) : entry)
+ );
+ let index = clamp(initialIndex, 0, entries.length - 1);
+
+ let action = PopAction;
+ let location = entries[index];
+ let blockers = createEvents();
+ let listeners = createEvents();
+
+ let history = {
+ createHref: createPath,
+ block: fn => blockers.push(fn),
+ listen: fn => listeners.push(fn),
+ push: (to, state) =>
+ handleAction(index + 1, PushAction, createNextLocation(to, state)),
+ replace: (to, state) =>
+ handleAction(index, ReplaceAction, createNextLocation(to, state)),
+ go: n => handleAction(clamp(index + n, 0, entries.length - 1), PopAction),
+ back: () => history.go(-1),
+ forward: () => history.go(1)
+ };
+
+ Object.defineProperty(history, 'action', {
+ enumerable: true,
+ get: () => action
+ });
+
+ Object.defineProperty(history, 'location', {
+ enumerable: true,
+ get: () => location
+ });
+
+ return history;
+};
+
+export const createBrowserHistory = ({
+ window = document.defaultView
+} = {}) => {
+ let getLocation = () => {
+ let { pathname, search, hash } = window.location;
+ let { state } = window.history;
+ return createReadOnlyObject({
+ pathname,
+ search,
+ hash,
+ state: (state && state.user) || null,
+ key: (state && state.key) || 'default'
+ });
+ };
+
+ let createNextLocation = (to, state) =>
+ createReadOnlyObject({
+ ...(typeof to === 'string'
+ ? parsePath(to)
+ : { pathname: '/', search: '', hash: '', ...to }),
+ state,
+ key: createKey()
+ });
+
+ // TODO: Support forceRefresh and do NOT notify listeners when used.
+ let handleAction = (nextAction, nextLocation) => {
+ if (blockers.length) {
+ blockers.call({ action: nextAction, location: nextLocation });
+
+ if (nextAction === PopAction) {
+ // TODO: revert the POP
+ }
+ } else {
+ let state = { user: nextLocation.state, key: nextLocation.key };
+ let url = createPath(nextLocation);
+
+ if (nextAction === PushAction) {
+ // try...catch because iOS limits us to 100 pushState calls :/
+ try {
+ window.history.pushState(state, null, url);
+ } catch (error) {
+ // They are going to lose state here, but there is no real
+ // way to warn them about it since the page will refresh...
+ window.location.assign(url);
+ }
+ } else if (nextAction === ReplaceAction) {
+ window.history.replaceState(state, null, url);
+ }
+
+ action = nextAction;
+ location = getLocation();
+ listeners.call({ action, location });
+ }
+ };
+
+ let action = PopAction;
+ let location = getLocation();
+ let blockers = createEvents();
+ let listeners = createEvents();
+
+ window.addEventListener(PopStateEvent, event => {
+ handleAction(PopAction, getLocation());
+ });
+
+ let history = {
+ createHref: createPath,
+ block: fn => blockers.push(fn),
+ listen: fn => listeners.push(fn),
+ push: (to, state) =>
+ handleAction(PushAction, createNextLocation(to, state)),
+ replace: (to, state) =>
+ handleAction(ReplaceAction, createNextLocation(to, state)),
+ go: n => window.history.go(n),
+ back: () => history.go(-1),
+ forward: () => history.go(1)
+ };
+
+ Object.defineProperty(history, 'action', {
+ enumerable: true,
+ get: () => action
+ });
+
+ Object.defineProperty(history, 'location', {
+ enumerable: true,
+ get: () => location
+ });
+
+ return history;
+};
+
+export const createHashHistory = ({ window = document.defaultView } = {}) => {
+ let getLocation = () => {
+ let { pathname, search, hash } = parsePath(window.location.hash.substr(1));
+ let { state } = window.history;
+ return createReadOnlyObject({
+ pathname,
+ search,
+ hash,
+ state: (state && state.user) || null,
+ key: (state && state.key) || 'default'
+ });
+ };
+
+ let createNextLocation = (to, state) =>
+ createReadOnlyObject({
+ ...(typeof to === 'string'
+ ? parsePath(to)
+ : { pathname: '/', search: '', hash: '', ...to }),
+ state,
+ key: createKey()
+ });
+
+ let action = PopAction;
+ let location = getLocation();
+ let blockers = createEvents();
+ let listeners = createEvents();
+
+ // TODO: Support forceRefresh and do NOT notify listeners when used.
+ let handleAction = (nextAction, nextLocation) => {
+ if (__DEV__) {
+ if (nextLocation.pathname.charAt(0) !== '/') {
+ let arg = createPath(nextLocation);
+ let fnCall = `${nextAction.toLowerCase()}("${arg}")`;
+ throw new Error(
+ `Relative pathnames are not supported in createHashHistory().${fnCall}`
+ );
+ }
+ }
+
+ if (blockers.length) {
+ blockers.call({ action: nextAction, location: nextLocation });
+
+ if (nextAction === PopAction) {
+ // TODO: revert the POP
+ }
+ } else {
+ let state = { user: nextLocation.state, key: nextLocation.key };
+ // TODO: Handle relative paths? Or just warn about them?
+ // TODO: Support different "hash types"?
+ let url = '#' + createPath(nextLocation);
+
+ if (nextAction === PushAction) {
+ // try...catch because iOS limits us to 100 pushState calls :/
+ try {
+ window.history.pushState(state, null, url);
+ } catch (error) {
+ // They are going to lose state here, but there is no real
+ // way to warn them about it since the page will refresh...
+ window.location.assign(url);
+ }
+ } else if (nextAction === ReplaceAction) {
+ window.history.replaceState(state, null, url);
+ }
+
+ action = nextAction;
+ location = getLocation();
+ listeners.call({ action, location });
+ }
+ };
+
+ window.addEventListener(PopStateEvent, event => {
+ handleAction(PopAction, getLocation());
+ });
+
+ window.addEventListener(HashChangeEvent, event => {
+ let nextLocation = getLocation();
+
+ // Ignore extraneous hashchange events.
+ if (createPath(nextLocation) !== createPath(location)) {
+ handleAction(PopAction, getLocation());
+ }
+ });
+
+ let history = {
+ createHref: location => {
+ let base = document.querySelector('base');
+ let href = '';
+ if (base && base.getAttribute('href')) {
+ href = stripHash(window.location.href);
+ }
+ return href + '#' + createPath(location);
+ },
+ block: fn => blockers.push(fn),
+ listen: fn => listeners.push(fn),
+ push: (to, state) =>
+ handleAction(PushAction, createNextLocation(to, state)),
+ replace: (to, state) =>
+ handleAction(ReplaceAction, createNextLocation(to, state)),
+ go: n => window.history.go(n),
+ back: () => history.go(-1),
+ forward: () => history.go(1)
+ };
+
+ Object.defineProperty(history, 'action', {
+ enumerable: true,
+ get: () => action
+ });
+
+ Object.defineProperty(history, 'location', {
+ enumerable: true,
+ get: () => location
+ });
+
+ return history;
+};
+
+// Utils
+
+const createKey = () =>
+ Math.random()
+ .toString(36)
+ .substr(2, 8);
+
+// TODO: Probably only do this in dev?
+const createReadOnlyObject = props =>
+ Object.keys(props).reduce(
+ (obj, key) =>
+ Object.defineProperty(obj, key, { enumerable: true, value: props[key] }),
+ Object.create(null)
+ );
+
+const stripHash = url => {
+ let hashIndex = url.indexOf('#');
+ return hashIndex === -1 ? url : url.slice(0, hashIndex);
+};
+
+const createPath = ({ pathname = '/', search = '', hash = '' }) =>
+ pathname + search + hash;
+
+const parsePath = path => {
+ let pathname = path || '/';
+ let search = '';
+ let hash = '';
+
+ let hashIndex = pathname.indexOf('#');
+ if (hashIndex >= 0) {
+ hash = pathname.substr(hashIndex);
+ pathname = pathname.substr(0, hashIndex);
+ }
+
+ let searchIndex = pathname.indexOf('?');
+ if (searchIndex >= 0) {
+ search = pathname.substr(searchIndex);
+ pathname = pathname.substr(0, searchIndex);
+ }
+
+ return { pathname, search, hash };
+};
+
+const createEvents = () => {
+ let handlers = [];
+
+ return {
+ get length() {
+ return handlers.length;
+ },
+ push(fn) {
+ handlers.push(fn);
+ return () => {
+ handlers = handlers.filter(handler => handler !== fn);
+ };
+ },
+ call(...args) {
+ handlers.forEach(fn => fn && fn(...args));
+ }
+ };
+};
+
+const clamp = (n, lowerBound, upperBound) =>
+ Math.min(Math.max(n, lowerBound), upperBound);
diff --git a/modules/invariant.js b/modules/invariant.js
deleted file mode 100644
index 98b4860e9..000000000
--- a/modules/invariant.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from 'tiny-invariant';
diff --git a/modules/warning.js b/modules/warning.js
deleted file mode 100644
index 510d489cd..000000000
--- a/modules/warning.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from 'tiny-warning';
diff --git a/package.json b/package.json
index 4d2df9107..92c9935ce 100644
--- a/package.json
+++ b/package.json
@@ -23,11 +23,7 @@
},
"dependencies": {
"@babel/runtime": "^7.1.2",
- "loose-envify": "^1.2.0",
- "resolve-pathname": "^3.0.0",
- "tiny-invariant": "^1.0.2",
- "tiny-warning": "^1.0.0",
- "value-equal": "^1.0.1"
+ "loose-envify": "^1.2.0"
},
"devDependencies": {
"@babel/core": "^7.1.2",
diff --git a/yarn.lock b/yarn.lock
index b8ab3882c..ab0f75a6e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4990,11 +4990,6 @@ resolve-from@^1.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
integrity sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=
-resolve-pathname@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
- integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
-
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@@ -5718,16 +5713,6 @@ timers-browserify@^2.0.4:
dependencies:
setimmediate "^1.0.4"
-tiny-invariant@^1.0.2:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.6.tgz#b3f9b38835e36a41c843a3b0907a5a7b3755de73"
- integrity sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==
-
-tiny-warning@^1.0.0:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
- integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
-
tmp@0.0.33, tmp@0.0.x:
version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@@ -6009,11 +5994,6 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
-value-equal@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
- integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
-
vm-browserify@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019"
From d51e9b7eb09188d14f3b96b221fb56ef0441f1ab Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Tue, 5 Nov 2019 15:23:19 -0800
Subject: [PATCH 004/133] Use ES6 ESLint env
---
modules/.eslintrc | 1 +
1 file changed, 1 insertion(+)
diff --git a/modules/.eslintrc b/modules/.eslintrc
index 340546cde..146cf5cae 100644
--- a/modules/.eslintrc
+++ b/modules/.eslintrc
@@ -1,6 +1,7 @@
{
"env": {
"browser": true,
+ "es6": true,
"node": false
},
"globals": {
From 749680710d53663a288b835a1cf1f0ad92968e57 Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Tue, 5 Nov 2019 15:25:21 -0800
Subject: [PATCH 005/133] Tweak test function format
---
modules/__tests__/TestSequences/BackButtonTransitionHook.js | 4 ++--
modules/__tests__/TestSequences/BlockEverything.js | 4 ++--
modules/__tests__/TestSequences/BlockPopWithoutListening.js | 4 ++--
.../__tests__/TestSequences/EncodedReservedCharacters.js | 4 ++--
modules/__tests__/TestSequences/GoBack.js | 4 ++--
modules/__tests__/TestSequences/GoForward.js | 4 ++--
.../__tests__/TestSequences/InitialLocationDefaultKey.js | 4 ++--
modules/__tests__/TestSequences/InitialLocationHasKey.js | 4 ++--
modules/__tests__/TestSequences/Listen.js | 4 ++--
.../__tests__/TestSequences/LocationPathnameAlwaysSame.js | 4 ++--
modules/__tests__/TestSequences/PushEncodedLocation.js | 4 ++--
modules/__tests__/TestSequences/PushMissingPathname.js | 4 ++--
modules/__tests__/TestSequences/PushNewLocation.js | 4 ++--
modules/__tests__/TestSequences/PushRelativePathname.js | 4 ++--
.../__tests__/TestSequences/PushRelativePathnameError.js | 4 ++--
modules/__tests__/TestSequences/PushSamePath.js | 4 ++--
modules/__tests__/TestSequences/PushState.js | 4 ++--
modules/__tests__/TestSequences/PushUnicodeLocation.js | 4 ++--
modules/__tests__/TestSequences/ReplaceNewLocation.js | 4 ++--
modules/__tests__/TestSequences/ReplaceSamePath.js | 4 ++--
modules/__tests__/TestSequences/ReplaceState.js | 4 ++--
modules/__tests__/TestSequences/execSteps.js | 6 ++++--
22 files changed, 46 insertions(+), 44 deletions(-)
diff --git a/modules/__tests__/TestSequences/BackButtonTransitionHook.js b/modules/__tests__/TestSequences/BackButtonTransitionHook.js
index d0544e21c..59343e17f 100644
--- a/modules/__tests__/TestSequences/BackButtonTransitionHook.js
+++ b/modules/__tests__/TestSequences/BackButtonTransitionHook.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let hookWasCalled = false;
let unblock;
@@ -39,4 +39,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/BlockEverything.js b/modules/__tests__/TestSequences/BlockEverything.js
index ab8db6324..bc1bd45de 100644
--- a/modules/__tests__/TestSequences/BlockEverything.js
+++ b/modules/__tests__/TestSequences/BlockEverything.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
({ location }) => {
expect(location).toMatchObject({
@@ -22,4 +22,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/BlockPopWithoutListening.js b/modules/__tests__/TestSequences/BlockPopWithoutListening.js
index f98887edc..3925870e8 100644
--- a/modules/__tests__/TestSequences/BlockPopWithoutListening.js
+++ b/modules/__tests__/TestSequences/BlockPopWithoutListening.js
@@ -1,6 +1,6 @@
import expect from 'expect';
-export default function(history, done) {
+export default (history, done) => {
expect(history.location).toMatchObject({
pathname: '/'
});
@@ -30,4 +30,4 @@ export default function(history, done) {
done();
}, 100);
}, 10);
-}
+};
diff --git a/modules/__tests__/TestSequences/EncodedReservedCharacters.js b/modules/__tests__/TestSequences/EncodedReservedCharacters.js
index b0addd0df..bc4191810 100644
--- a/modules/__tests__/TestSequences/EncodedReservedCharacters.js
+++ b/modules/__tests__/TestSequences/EncodedReservedCharacters.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
() => {
// encoded string
@@ -35,4 +35,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/GoBack.js b/modules/__tests__/TestSequences/GoBack.js
index 2f582e06f..9ff55501f 100644
--- a/modules/__tests__/TestSequences/GoBack.js
+++ b/modules/__tests__/TestSequences/GoBack.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
({ location }) => {
expect(location).toMatchObject({
@@ -28,4 +28,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/GoForward.js b/modules/__tests__/TestSequences/GoForward.js
index 7a5a6c416..94bdf42c4 100644
--- a/modules/__tests__/TestSequences/GoForward.js
+++ b/modules/__tests__/TestSequences/GoForward.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
({ location }) => {
expect(location).toMatchObject({
@@ -36,4 +36,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/InitialLocationDefaultKey.js b/modules/__tests__/TestSequences/InitialLocationDefaultKey.js
index eb13fee25..aefa11f54 100644
--- a/modules/__tests__/TestSequences/InitialLocationDefaultKey.js
+++ b/modules/__tests__/TestSequences/InitialLocationDefaultKey.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
({ location }) => {
expect(location.key).toBe('default');
@@ -10,4 +10,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/InitialLocationHasKey.js b/modules/__tests__/TestSequences/InitialLocationHasKey.js
index f506fb5b2..c06502a7a 100644
--- a/modules/__tests__/TestSequences/InitialLocationHasKey.js
+++ b/modules/__tests__/TestSequences/InitialLocationHasKey.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
({ location }) => {
expect(location.key).toBeTruthy();
@@ -10,4 +10,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/Listen.js b/modules/__tests__/TestSequences/Listen.js
index ddc6c4998..b27502fbc 100644
--- a/modules/__tests__/TestSequences/Listen.js
+++ b/modules/__tests__/TestSequences/Listen.js
@@ -1,7 +1,7 @@
import expect from 'expect';
import mock from 'jest-mock';
-export default function(history, done) {
+export default (history, done) => {
let spy = mock.fn();
let unlisten = history.listen(spy);
@@ -9,4 +9,4 @@ export default function(history, done) {
unlisten();
done();
-}
+};
diff --git a/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js b/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js
index 5a6158fef..24aef37ad 100644
--- a/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js
+++ b/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
() => {
// encoded string
@@ -41,4 +41,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/PushEncodedLocation.js b/modules/__tests__/TestSequences/PushEncodedLocation.js
index 60a369303..b55137e34 100644
--- a/modules/__tests__/TestSequences/PushEncodedLocation.js
+++ b/modules/__tests__/TestSequences/PushEncodedLocation.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
({ location }) => {
expect(location).toMatchObject({
@@ -26,4 +26,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/PushMissingPathname.js b/modules/__tests__/TestSequences/PushMissingPathname.js
index 1f3499afa..dd7f8a8e1 100644
--- a/modules/__tests__/TestSequences/PushMissingPathname.js
+++ b/modules/__tests__/TestSequences/PushMissingPathname.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
({ location }) => {
expect(location).toMatchObject({
@@ -32,4 +32,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/PushNewLocation.js b/modules/__tests__/TestSequences/PushNewLocation.js
index 156bef476..21e8e86f1 100644
--- a/modules/__tests__/TestSequences/PushNewLocation.js
+++ b/modules/__tests__/TestSequences/PushNewLocation.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
({ location }) => {
expect(location).toMatchObject({
@@ -22,4 +22,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/PushRelativePathname.js b/modules/__tests__/TestSequences/PushRelativePathname.js
index 75850e724..8e14581d5 100644
--- a/modules/__tests__/TestSequences/PushRelativePathname.js
+++ b/modules/__tests__/TestSequences/PushRelativePathname.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
({ location }) => {
expect(location).toMatchObject({
@@ -32,4 +32,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/PushRelativePathnameError.js b/modules/__tests__/TestSequences/PushRelativePathnameError.js
index 7c8a0759d..3f50a2a18 100644
--- a/modules/__tests__/TestSequences/PushRelativePathnameError.js
+++ b/modules/__tests__/TestSequences/PushRelativePathnameError.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
({ location }) => {
expect(location).toMatchObject({
@@ -28,4 +28,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/PushSamePath.js b/modules/__tests__/TestSequences/PushSamePath.js
index 765ae661b..b3a12628e 100644
--- a/modules/__tests__/TestSequences/PushSamePath.js
+++ b/modules/__tests__/TestSequences/PushSamePath.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
({ location }) => {
expect(location).toMatchObject({
@@ -36,4 +36,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/PushState.js b/modules/__tests__/TestSequences/PushState.js
index 0d111e61b..70f02bb5b 100644
--- a/modules/__tests__/TestSequences/PushState.js
+++ b/modules/__tests__/TestSequences/PushState.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
({ location }) => {
expect(location).toMatchObject({
@@ -23,4 +23,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/PushUnicodeLocation.js b/modules/__tests__/TestSequences/PushUnicodeLocation.js
index 8401c2c87..b9b3c0913 100644
--- a/modules/__tests__/TestSequences/PushUnicodeLocation.js
+++ b/modules/__tests__/TestSequences/PushUnicodeLocation.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
({ location }) => {
expect(location).toMatchObject({
@@ -28,4 +28,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/ReplaceNewLocation.js b/modules/__tests__/TestSequences/ReplaceNewLocation.js
index fcfb3c421..0c4f93fbd 100644
--- a/modules/__tests__/TestSequences/ReplaceNewLocation.js
+++ b/modules/__tests__/TestSequences/ReplaceNewLocation.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
({ location }) => {
expect(location).toMatchObject({
@@ -22,4 +22,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/ReplaceSamePath.js b/modules/__tests__/TestSequences/ReplaceSamePath.js
index ffd292f09..df06a3400 100644
--- a/modules/__tests__/TestSequences/ReplaceSamePath.js
+++ b/modules/__tests__/TestSequences/ReplaceSamePath.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let prevLocation;
let steps = [
@@ -34,4 +34,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/ReplaceState.js b/modules/__tests__/TestSequences/ReplaceState.js
index 7e3e50a61..56f7ad178 100644
--- a/modules/__tests__/TestSequences/ReplaceState.js
+++ b/modules/__tests__/TestSequences/ReplaceState.js
@@ -2,7 +2,7 @@ import expect from 'expect';
import execSteps from './execSteps.js';
-export default function(history, done) {
+export default (history, done) => {
let steps = [
({ location }) => {
expect(location).toMatchObject({
@@ -23,4 +23,4 @@ export default function(history, done) {
];
execSteps(steps, history, done);
-}
+};
diff --git a/modules/__tests__/TestSequences/execSteps.js b/modules/__tests__/TestSequences/execSteps.js
index 0e3c836a7..fb3241af7 100644
--- a/modules/__tests__/TestSequences/execSteps.js
+++ b/modules/__tests__/TestSequences/execSteps.js
@@ -1,4 +1,4 @@
-export default function execSteps(steps, history, done) {
+const execSteps = (steps, history, done) => {
let index = 0,
unlisten,
cleanedUp = false;
@@ -35,4 +35,6 @@ export default function execSteps(steps, history, done) {
} else {
done();
}
-}
+};
+
+export default execSteps;
From 824ed2978f1c25e04673b015a3f77debeb8ac492 Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Tue, 5 Nov 2019 17:30:07 -0800
Subject: [PATCH 006/133] Consolidate push/replace => navigate
---
.size-snapshot.json | 18 ++--
.../TestSequences/BackButtonTransitionHook.js | 2 +-
.../TestSequences/BlockEverything.js | 2 +-
.../TestSequences/BlockPopWithoutListening.js | 2 +-
.../EncodedReservedCharacters.js | 6 +-
modules/__tests__/TestSequences/GoBack.js | 2 +-
modules/__tests__/TestSequences/GoForward.js | 2 +-
.../LocationPathnameAlwaysSame.js | 8 +-
.../TestSequences/PushEncodedLocation.js | 2 +-
.../TestSequences/PushMissingPathname.js | 4 +-
.../TestSequences/PushNewLocation.js | 2 +-
.../TestSequences/PushRelativePathname.js | 4 +-
.../PushRelativePathnameError.js | 4 +-
.../__tests__/TestSequences/PushSamePath.js | 4 +-
modules/__tests__/TestSequences/PushState.js | 2 +-
.../TestSequences/PushUnicodeLocation.js | 2 +-
.../TestSequences/ReplaceNewLocation.js | 2 +-
.../TestSequences/ReplaceSamePath.js | 4 +-
.../__tests__/TestSequences/ReplaceState.js | 5 +-
modules/index.js | 97 +++++++++----------
20 files changed, 85 insertions(+), 89 deletions(-)
diff --git a/.size-snapshot.json b/.size-snapshot.json
index e02c3164f..ef1d954ff 100644
--- a/.size-snapshot.json
+++ b/.size-snapshot.json
@@ -1,8 +1,8 @@
{
"esm/history.js": {
- "bundled": 13433,
- "minified": 5829,
- "gzipped": 1706,
+ "bundled": 13550,
+ "minified": 5796,
+ "gzipped": 1690,
"treeshaked": {
"rollup": {
"code": 43,
@@ -14,13 +14,13 @@
}
},
"umd/history.js": {
- "bundled": 14957,
- "minified": 5450,
- "gzipped": 1760
+ "bundled": 15060,
+ "minified": 5417,
+ "gzipped": 1750
},
"umd/history.min.js": {
- "bundled": 14343,
- "minified": 5137,
- "gzipped": 1619
+ "bundled": 14446,
+ "minified": 5104,
+ "gzipped": 1611
}
}
diff --git a/modules/__tests__/TestSequences/BackButtonTransitionHook.js b/modules/__tests__/TestSequences/BackButtonTransitionHook.js
index 59343e17f..8f197bde1 100644
--- a/modules/__tests__/TestSequences/BackButtonTransitionHook.js
+++ b/modules/__tests__/TestSequences/BackButtonTransitionHook.js
@@ -12,7 +12,7 @@ export default (history, done) => {
pathname: '/'
});
- history.push('/home');
+ history.navigate('/home');
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/BlockEverything.js b/modules/__tests__/TestSequences/BlockEverything.js
index bc1bd45de..083d30c86 100644
--- a/modules/__tests__/TestSequences/BlockEverything.js
+++ b/modules/__tests__/TestSequences/BlockEverything.js
@@ -11,7 +11,7 @@ export default (history, done) => {
let unblock = history.block();
- history.push('/home');
+ history.navigate('/home');
expect(history.location).toMatchObject({
pathname: '/'
diff --git a/modules/__tests__/TestSequences/BlockPopWithoutListening.js b/modules/__tests__/TestSequences/BlockPopWithoutListening.js
index 3925870e8..8b8203d93 100644
--- a/modules/__tests__/TestSequences/BlockPopWithoutListening.js
+++ b/modules/__tests__/TestSequences/BlockPopWithoutListening.js
@@ -5,7 +5,7 @@ export default (history, done) => {
pathname: '/'
});
- history.push('/home');
+ history.navigate('/home');
let transitionHookWasCalled = false;
let unblock = history.block(() => {
diff --git a/modules/__tests__/TestSequences/EncodedReservedCharacters.js b/modules/__tests__/TestSequences/EncodedReservedCharacters.js
index bc4191810..70cea3e52 100644
--- a/modules/__tests__/TestSequences/EncodedReservedCharacters.js
+++ b/modules/__tests__/TestSequences/EncodedReservedCharacters.js
@@ -7,7 +7,7 @@ export default (history, done) => {
() => {
// encoded string
let pathname = '/view/%23abc';
- history.replace(pathname);
+ history.navigate(pathname, { replace: true });
},
({ location }) => {
expect(location).toMatchObject({
@@ -16,7 +16,7 @@ export default (history, done) => {
// encoded object
let pathname = '/view/%23abc';
- history.replace({ pathname });
+ history.navigate({ pathname }, { replace: true });
},
({ location }) => {
expect(location).toMatchObject({
@@ -24,7 +24,7 @@ export default (history, done) => {
});
// unencoded string
let pathname = '/view/#abc';
- history.replace(pathname);
+ history.navigate(pathname, { replace: true });
},
({ location }) => {
expect(location).toMatchObject({
diff --git a/modules/__tests__/TestSequences/GoBack.js b/modules/__tests__/TestSequences/GoBack.js
index 9ff55501f..11b8c02e4 100644
--- a/modules/__tests__/TestSequences/GoBack.js
+++ b/modules/__tests__/TestSequences/GoBack.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.push('/home');
+ history.navigate('/home');
},
({ action, location }) => {
expect(action).toEqual('PUSH');
diff --git a/modules/__tests__/TestSequences/GoForward.js b/modules/__tests__/TestSequences/GoForward.js
index 94bdf42c4..17e4f2375 100644
--- a/modules/__tests__/TestSequences/GoForward.js
+++ b/modules/__tests__/TestSequences/GoForward.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.push('/home');
+ history.navigate('/home');
},
({ action, location }) => {
expect(action).toEqual('PUSH');
diff --git a/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js b/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js
index 24aef37ad..adb9d5548 100644
--- a/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js
+++ b/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js
@@ -7,7 +7,7 @@ export default (history, done) => {
() => {
// encoded string
let pathname = '/%E6%AD%B4%E5%8F%B2';
- history.replace(pathname);
+ history.navigate(pathname, { replace: true });
},
({ location }) => {
expect(location).toMatchObject({
@@ -15,7 +15,7 @@ export default (history, done) => {
});
// encoded object
let pathname = '/%E6%AD%B4%E5%8F%B2';
- history.replace({ pathname });
+ history.navigate({ pathname }, { replace: true });
},
({ location }) => {
expect(location).toMatchObject({
@@ -23,7 +23,7 @@ export default (history, done) => {
});
// unencoded string
let pathname = '/歴史';
- history.replace(pathname);
+ history.navigate(pathname, { replace: true });
},
({ location }) => {
expect(location).toMatchObject({
@@ -31,7 +31,7 @@ export default (history, done) => {
});
// unencoded object
let pathname = '/歴史';
- history.replace({ pathname });
+ history.navigate({ pathname }, { replace: true });
},
({ location }) => {
expect(location).toMatchObject({
diff --git a/modules/__tests__/TestSequences/PushEncodedLocation.js b/modules/__tests__/TestSequences/PushEncodedLocation.js
index b55137e34..782d2b551 100644
--- a/modules/__tests__/TestSequences/PushEncodedLocation.js
+++ b/modules/__tests__/TestSequences/PushEncodedLocation.js
@@ -12,7 +12,7 @@ export default (history, done) => {
let pathname = '/歴史';
let search = '?%E3%82%AD%E3%83%BC=%E5%80%A4';
let hash = '#%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5';
- history.push(pathname + search + hash);
+ history.navigate(pathname + search + hash);
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/PushMissingPathname.js b/modules/__tests__/TestSequences/PushMissingPathname.js
index dd7f8a8e1..863f18896 100644
--- a/modules/__tests__/TestSequences/PushMissingPathname.js
+++ b/modules/__tests__/TestSequences/PushMissingPathname.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.push('/home?the=query#the-hash');
+ history.navigate('/home?the=query#the-hash');
},
({ action, location }) => {
expect(action).toBe('PUSH');
@@ -19,7 +19,7 @@ export default (history, done) => {
hash: '#the-hash'
});
- history.push('?another=query#another-hash');
+ history.navigate('?another=query#another-hash');
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/PushNewLocation.js b/modules/__tests__/TestSequences/PushNewLocation.js
index 21e8e86f1..abe0697a5 100644
--- a/modules/__tests__/TestSequences/PushNewLocation.js
+++ b/modules/__tests__/TestSequences/PushNewLocation.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.push('/home?the=query#the-hash');
+ history.navigate('/home?the=query#the-hash');
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/PushRelativePathname.js b/modules/__tests__/TestSequences/PushRelativePathname.js
index 8e14581d5..cd0880571 100644
--- a/modules/__tests__/TestSequences/PushRelativePathname.js
+++ b/modules/__tests__/TestSequences/PushRelativePathname.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.push('/the/path?the=query#the-hash');
+ history.navigate('/the/path?the=query#the-hash');
},
({ action, location }) => {
expect(action).toBe('PUSH');
@@ -19,7 +19,7 @@ export default (history, done) => {
hash: '#the-hash'
});
- history.push('../other/path?another=query#another-hash');
+ history.navigate('../other/path?another=query#another-hash');
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/PushRelativePathnameError.js b/modules/__tests__/TestSequences/PushRelativePathnameError.js
index 3f50a2a18..a5155ef86 100644
--- a/modules/__tests__/TestSequences/PushRelativePathnameError.js
+++ b/modules/__tests__/TestSequences/PushRelativePathnameError.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.push('/the/path?the=query#the-hash');
+ history.navigate('/the/path?the=query#the-hash');
},
({ action, location }) => {
expect(action).toBe('PUSH');
@@ -20,7 +20,7 @@ export default (history, done) => {
});
try {
- history.push('../other/path?another=query#another-hash');
+ history.navigate('../other/path?another=query#another-hash');
} catch (error) {
expect(error.message).toMatch(/relative pathnames are not supported/i);
}
diff --git a/modules/__tests__/TestSequences/PushSamePath.js b/modules/__tests__/TestSequences/PushSamePath.js
index b3a12628e..cf1780faa 100644
--- a/modules/__tests__/TestSequences/PushSamePath.js
+++ b/modules/__tests__/TestSequences/PushSamePath.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.push('/home');
+ history.navigate('/home');
},
({ action, location }) => {
expect(action).toBe('PUSH');
@@ -17,7 +17,7 @@ export default (history, done) => {
pathname: '/home'
});
- history.push('/home');
+ history.navigate('/home');
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/PushState.js b/modules/__tests__/TestSequences/PushState.js
index 70f02bb5b..1d1e720c8 100644
--- a/modules/__tests__/TestSequences/PushState.js
+++ b/modules/__tests__/TestSequences/PushState.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.push('/home?the=query#the-hash', { the: 'state' });
+ history.navigate('/home?the=query#the-hash', { state: { the: 'state' } });
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/PushUnicodeLocation.js b/modules/__tests__/TestSequences/PushUnicodeLocation.js
index b9b3c0913..2e92003c4 100644
--- a/modules/__tests__/TestSequences/PushUnicodeLocation.js
+++ b/modules/__tests__/TestSequences/PushUnicodeLocation.js
@@ -12,7 +12,7 @@ export default (history, done) => {
let pathname = '/歴史';
let search = '?キー=値';
let hash = '#ハッシュ';
- history.push(pathname + search + hash);
+ history.navigate(pathname + search + hash);
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/ReplaceNewLocation.js b/modules/__tests__/TestSequences/ReplaceNewLocation.js
index 0c4f93fbd..5a5d9d15f 100644
--- a/modules/__tests__/TestSequences/ReplaceNewLocation.js
+++ b/modules/__tests__/TestSequences/ReplaceNewLocation.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.replace('/home?the=query#the-hash');
+ history.navigate('/home?the=query#the-hash', { replace: true });
},
({ action, location }) => {
expect(action).toBe('REPLACE');
diff --git a/modules/__tests__/TestSequences/ReplaceSamePath.js b/modules/__tests__/TestSequences/ReplaceSamePath.js
index df06a3400..06d4a4c42 100644
--- a/modules/__tests__/TestSequences/ReplaceSamePath.js
+++ b/modules/__tests__/TestSequences/ReplaceSamePath.js
@@ -11,7 +11,7 @@ export default (history, done) => {
pathname: '/'
});
- history.replace('/home');
+ history.navigate('/home', { replace: true });
},
({ action, location }) => {
expect(action).toBe('REPLACE');
@@ -21,7 +21,7 @@ export default (history, done) => {
prevLocation = location;
- history.replace('/home');
+ history.navigate('/home', { replace: true });
},
({ action, location }) => {
expect(action).toBe('REPLACE');
diff --git a/modules/__tests__/TestSequences/ReplaceState.js b/modules/__tests__/TestSequences/ReplaceState.js
index 56f7ad178..2622e89e6 100644
--- a/modules/__tests__/TestSequences/ReplaceState.js
+++ b/modules/__tests__/TestSequences/ReplaceState.js
@@ -9,7 +9,10 @@ export default (history, done) => {
pathname: '/'
});
- history.replace('/home?the=query#the-hash', { the: 'state' });
+ history.navigate('/home?the=query#the-hash', {
+ replace: true,
+ state: { the: 'state' }
+ });
},
({ action, location }) => {
expect(action).toBe('REPLACE');
diff --git a/modules/index.js b/modules/index.js
index 9422193e7..13525777e 100644
--- a/modules/index.js
+++ b/modules/index.js
@@ -22,14 +22,7 @@ export const createMemoryHistory = ({
key = createKey()
}) => createReadOnlyObject({ pathname, search, hash, state, key });
- let createNextLocation = (to, state) =>
- createLocation({
- ...(typeof to === 'string' ? parsePath(to) : to),
- state,
- key: createKey()
- });
-
- let handleAction = (nextIndex, nextAction, nextLocation) => {
+ let handleNavigation = (nextIndex, nextAction, nextLocation) => {
if (__DEV__) {
if (nextLocation && nextLocation.pathname.charAt(0) !== '/') {
let arg = createPath(nextLocation);
@@ -69,11 +62,18 @@ export const createMemoryHistory = ({
createHref: createPath,
block: fn => blockers.push(fn),
listen: fn => listeners.push(fn),
- push: (to, state) =>
- handleAction(index + 1, PushAction, createNextLocation(to, state)),
- replace: (to, state) =>
- handleAction(index, ReplaceAction, createNextLocation(to, state)),
- go: n => handleAction(clamp(index + n, 0, entries.length - 1), PopAction),
+ navigate: (to, { replace = false, state = null } = {}) =>
+ handleNavigation(
+ replace ? index : index + 1,
+ replace ? ReplaceAction : PushAction,
+ createLocation({
+ ...(typeof to === 'string' ? parsePath(to) : to),
+ state,
+ key: createKey()
+ })
+ ),
+ go: n =>
+ handleNavigation(clamp(index + n, 0, entries.length - 1), PopAction),
back: () => history.go(-1),
forward: () => history.go(1)
};
@@ -106,17 +106,8 @@ export const createBrowserHistory = ({
});
};
- let createNextLocation = (to, state) =>
- createReadOnlyObject({
- ...(typeof to === 'string'
- ? parsePath(to)
- : { pathname: '/', search: '', hash: '', ...to }),
- state,
- key: createKey()
- });
-
// TODO: Support forceRefresh and do NOT notify listeners when used.
- let handleAction = (nextAction, nextLocation) => {
+ let handleNavigation = (nextAction, nextLocation) => {
if (blockers.length) {
blockers.call({ action: nextAction, location: nextLocation });
@@ -152,17 +143,24 @@ export const createBrowserHistory = ({
let listeners = createEvents();
window.addEventListener(PopStateEvent, event => {
- handleAction(PopAction, getLocation());
+ handleNavigation(PopAction, getLocation());
});
let history = {
createHref: createPath,
block: fn => blockers.push(fn),
listen: fn => listeners.push(fn),
- push: (to, state) =>
- handleAction(PushAction, createNextLocation(to, state)),
- replace: (to, state) =>
- handleAction(ReplaceAction, createNextLocation(to, state)),
+ navigate: (to, { replace = false, state = null } = {}) =>
+ handleNavigation(
+ replace ? ReplaceAction : PushAction,
+ createReadOnlyObject({
+ ...(typeof to === 'string'
+ ? parsePath(to)
+ : { pathname: '/', search: '', hash: '', ...to }),
+ state,
+ key: createKey()
+ })
+ ),
go: n => window.history.go(n),
back: () => history.go(-1),
forward: () => history.go(1)
@@ -194,22 +192,13 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
});
};
- let createNextLocation = (to, state) =>
- createReadOnlyObject({
- ...(typeof to === 'string'
- ? parsePath(to)
- : { pathname: '/', search: '', hash: '', ...to }),
- state,
- key: createKey()
- });
-
let action = PopAction;
let location = getLocation();
let blockers = createEvents();
let listeners = createEvents();
// TODO: Support forceRefresh and do NOT notify listeners when used.
- let handleAction = (nextAction, nextLocation) => {
+ let handleNavigation = (nextAction, nextLocation) => {
if (__DEV__) {
if (nextLocation.pathname.charAt(0) !== '/') {
let arg = createPath(nextLocation);
@@ -252,7 +241,7 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
};
window.addEventListener(PopStateEvent, event => {
- handleAction(PopAction, getLocation());
+ handleNavigation(PopAction, getLocation());
});
window.addEventListener(HashChangeEvent, event => {
@@ -260,7 +249,7 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
// Ignore extraneous hashchange events.
if (createPath(nextLocation) !== createPath(location)) {
- handleAction(PopAction, getLocation());
+ handleNavigation(PopAction, getLocation());
}
});
@@ -275,10 +264,17 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
},
block: fn => blockers.push(fn),
listen: fn => listeners.push(fn),
- push: (to, state) =>
- handleAction(PushAction, createNextLocation(to, state)),
- replace: (to, state) =>
- handleAction(ReplaceAction, createNextLocation(to, state)),
+ navigate: (to, { replace = false, state = null } = {}) =>
+ handleNavigation(
+ replace ? ReplaceAction : PushAction,
+ createReadOnlyObject({
+ ...(typeof to === 'string'
+ ? parsePath(to)
+ : { pathname: '/', search: '', hash: '', ...to }),
+ state,
+ key: createKey()
+ })
+ ),
go: n => window.history.go(n),
back: () => history.go(-1),
forward: () => history.go(1)
@@ -347,15 +343,12 @@ const createEvents = () => {
get length() {
return handlers.length;
},
- push(fn) {
- handlers.push(fn);
- return () => {
+ push: fn =>
+ handlers.push(fn) &&
+ (() => {
handlers = handlers.filter(handler => handler !== fn);
- };
- },
- call(...args) {
- handlers.forEach(fn => fn && fn(...args));
- }
+ }),
+ call: arg => handlers.map(fn => fn && fn(arg))
};
};
From 3cc181f5be66c99b417f686c7541abdc9ed1ea90 Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Tue, 5 Nov 2019 17:31:21 -0800
Subject: [PATCH 007/133] Add beforeunload fixture
---
fixtures/unload.html | 129 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 129 insertions(+)
create mode 100644 fixtures/unload.html
diff --git a/fixtures/unload.html b/fixtures/unload.html
new file mode 100644
index 000000000..ebf60dbef
--- /dev/null
+++ b/fixtures/unload.html
@@ -0,0 +1,129 @@
+
+
+
+ Controls
+
+
+
+
+
+ back & forward:
+
+
+
+
+ regular links:
+ home
+ one
+ two
+
+
+ pushState & replaceState:
+ pushState(home)
+ pushState(one)
+ pushState(two)
+ replaceState(three)
+
+
+ Test Scenarios
+
+
+
+ - Click block - Click back - You should be prompted to stay
+ - Click block - Click home - You should be prompted to stay
+ - Click block - Click one - You should be prompted to stay
+ - Click block - Close the tab - You should be prompted to stay
+ - Click pushState(one) - Click block - Click back - You should still be at /unload-one
+ - Click pushState(one) - Click block - Click pushState(two) - You should still be at /unload-one
+ - Click pushState(one) - Click pushState(two) - Click back - Click block - Click back - You should still be at /unload-one
+ - Click pushState(one) - Click pushState(two) - Click back - Click block - Click forward - You should still be at /unload-one
+ - Click pushState(one) - Click pushState(two) - Click replaceState(three) - Click block - Click back - You should still be at /unload-three
+
+
+
+
+
+
From 4bf15b1929d263a654036dd723b8a9fd7277cd21 Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Tue, 5 Nov 2019 22:49:32 -0800
Subject: [PATCH 008/133] Add location.index + fix blocking POP actions
---
.gitignore | 4 +-
.size-snapshot.json | 18 +-
fixtures/unload-history.html | 97 ++++++++
fixtures/{unload.html => unload-plain.html} | 24 +-
modules/index.js | 249 ++++++++++++++------
5 files changed, 292 insertions(+), 100 deletions(-)
create mode 100644 fixtures/unload-history.html
rename fixtures/{unload.html => unload-plain.html} (84%)
diff --git a/.gitignore b/.gitignore
index 5ea170866..270814e64 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
-/node_modules/
/esm/
+/fixtures/history.js
/cjs/
+/node_modules/
/umd/
-/local.log
diff --git a/.size-snapshot.json b/.size-snapshot.json
index ef1d954ff..ffc5b5dbd 100644
--- a/.size-snapshot.json
+++ b/.size-snapshot.json
@@ -1,8 +1,8 @@
{
"esm/history.js": {
- "bundled": 13550,
- "minified": 5796,
- "gzipped": 1690,
+ "bundled": 16328,
+ "minified": 6304,
+ "gzipped": 1786,
"treeshaked": {
"rollup": {
"code": 43,
@@ -14,13 +14,13 @@
}
},
"umd/history.js": {
- "bundled": 15060,
- "minified": 5417,
- "gzipped": 1750
+ "bundled": 17970,
+ "minified": 5813,
+ "gzipped": 1944
},
"umd/history.min.js": {
- "bundled": 14446,
- "minified": 5104,
- "gzipped": 1611
+ "bundled": 17356,
+ "minified": 5500,
+ "gzipped": 1813
}
}
diff --git a/fixtures/unload-history.html b/fixtures/unload-history.html
new file mode 100644
index 000000000..11348a447
--- /dev/null
+++ b/fixtures/unload-history.html
@@ -0,0 +1,97 @@
+
+
+
+ Controls
+
+
+
+
+
+ back & forward:
+
+
+
+
+ regular links:
+ home
+ one
+ two
+
+
+ pushState & replaceState:
+ pushState(home)
+ pushState(one)
+ pushState(two)
+ replaceState(three)
+
+
+ Test Scenarios
+
+
+
+ - Click block - Click back - You should be prompted to stay
+ - Click block - Click home - You should be prompted to stay
+ - Click block - Click one - You should be prompted to stay
+ - Click block - Close the tab - You should be prompted to stay
+ - Click pushState(one) - Click block - Click back - You should still be at /unload-history-one
+ - Click pushState(one) - Click block - Click pushState(two) - You should still be at /unload-history-one
+ - Click pushState(one) - Click pushState(two) - Click back - Click block - Click back - You should still be at /unload-history-one
+ - Click pushState(one) - Click pushState(two) - Click back - Click block - Click forward - You should still be at /unload-history-one
+ - Click pushState(one) - Click pushState(two) - Click replaceState(three) - Click block - Click back - You should still be at /unload-history-three
+
+
+
+
+
+
+
+
diff --git a/fixtures/unload.html b/fixtures/unload-plain.html
similarity index 84%
rename from fixtures/unload.html
rename to fixtures/unload-plain.html
index ebf60dbef..0314cc391 100644
--- a/fixtures/unload.html
+++ b/fixtures/unload-plain.html
@@ -13,16 +13,16 @@ Controls
regular links:
- home
- one
- two
+ home
+ one
+ two
pushState & replaceState:
- pushState(home)
- pushState(one)
- pushState(two)
- replaceState(three)
+ pushState(home)
+ pushState(one)
+ pushState(two)
+ replaceState(three)
Test Scenarios
@@ -33,11 +33,11 @@ Test Scenarios
Click block - Click home - You should be prompted to stay
Click block - Click one - You should be prompted to stay
Click block - Close the tab - You should be prompted to stay
- Click pushState(one) - Click block - Click back - You should still be at /unload-one
- Click pushState(one) - Click block - Click pushState(two) - You should still be at /unload-one
- Click pushState(one) - Click pushState(two) - Click back - Click block - Click back - You should still be at /unload-one
- Click pushState(one) - Click pushState(two) - Click back - Click block - Click forward - You should still be at /unload-one
- Click pushState(one) - Click pushState(two) - Click replaceState(three) - Click block - Click back - You should still be at /unload-three
+ Click pushState(one) - Click block - Click back - You should still be at /unload-plain-one
+ Click pushState(one) - Click block - Click pushState(two) - You should still be at /unload-plain-one
+ Click pushState(one) - Click pushState(two) - Click back - Click block - Click back - You should still be at /unload-plain-one
+ Click pushState(one) - Click pushState(two) - Click back - Click block - Click forward - You should still be at /unload-plain-one
+ Click pushState(one) - Click pushState(two) - Click replaceState(three) - Click block - Click back - You should still be at /unload-plain-three
diff --git a/modules/index.js b/modules/index.js
index 13525777e..15ea9007d 100644
--- a/modules/index.js
+++ b/modules/index.js
@@ -2,6 +2,7 @@ const PopAction = 'POP';
const PushAction = 'PUSH';
const ReplaceAction = 'REPLACE';
+const BeforeUnloadEvent = 'beforeunload';
const PopStateEvent = 'popstate';
const HashChangeEvent = 'hashchange';
@@ -9,6 +10,11 @@ const HashChangeEvent = 'hashchange';
// should ever be used in a given web page, so it's best to just inline
// everything for minification.
+/**
+ * Memory history stores the current location in memory. It is designed
+ * for use in stateful non-browser environments like headless tests (in
+ * node.js) and React Native.
+ */
export const createMemoryHistory = ({
initialEntries = ['/'],
initialIndex = 0
@@ -19,10 +25,11 @@ export const createMemoryHistory = ({
hash = '',
state = null,
// Auto-assign keys to entries that don't already have them.
- key = createKey()
- }) => createReadOnlyObject({ pathname, search, hash, state, key });
+ key = createKey(),
+ index
+ }) => createReadOnlyObject({ pathname, search, hash, state, key, index });
- let handleNavigation = (nextIndex, nextAction, nextLocation) => {
+ let handleNavigation = (nextAction, nextLocation) => {
if (__DEV__) {
if (nextLocation && nextLocation.pathname.charAt(0) !== '/') {
let arg = createPath(nextLocation);
@@ -36,119 +43,169 @@ export const createMemoryHistory = ({
if (blockers.length) {
blockers.call({ action: nextAction, location: nextLocation });
} else {
- index = nextIndex;
if (nextAction === PushAction) {
- entries.splice(index, entries.length, nextLocation);
+ entries.splice(nextLocation.index, entries.length, nextLocation);
} else if (nextAction === ReplaceAction) {
- entries[index] = nextLocation;
+ entries[nextLocation.index] = nextLocation;
}
action = nextAction;
- location = nextLocation || entries[index];
+ location = nextLocation;
listeners.call({ action, location });
}
};
- let entries = initialEntries.map(entry =>
- createLocation(typeof entry === 'string' ? parsePath(entry) : entry)
+ let entries = initialEntries.map((entry, index) =>
+ createLocation({
+ ...(typeof entry === 'string' ? parsePath(entry) : entry),
+ index
+ })
);
- let index = clamp(initialIndex, 0, entries.length - 1);
let action = PopAction;
- let location = entries[index];
+ let location = entries[clamp(initialIndex, 0, entries.length - 1)];
let blockers = createEvents();
let listeners = createEvents();
let history = {
+ get action() {
+ return action;
+ },
+ get location() {
+ return location;
+ },
createHref: createPath,
block: fn => blockers.push(fn),
listen: fn => listeners.push(fn),
navigate: (to, { replace = false, state = null } = {}) =>
handleNavigation(
- replace ? index : index + 1,
replace ? ReplaceAction : PushAction,
createLocation({
...(typeof to === 'string' ? parsePath(to) : to),
state,
- key: createKey()
+ key: createKey(),
+ index: location.index + (replace ? 0 : 1)
})
),
go: n =>
- handleNavigation(clamp(index + n, 0, entries.length - 1), PopAction),
+ handleNavigation(
+ PopAction,
+ entries[clamp(location.index + n, 0, entries.length - 1)]
+ ),
back: () => history.go(-1),
forward: () => history.go(1)
};
- Object.defineProperty(history, 'action', {
- enumerable: true,
- get: () => action
- });
-
- Object.defineProperty(history, 'location', {
- enumerable: true,
- get: () => location
- });
-
return history;
};
+/**
+ * Browser history stores the location in regular URLs. This is the
+ * standard for most web apps, but it requires some configuration on
+ * the server to ensure you serve the same app at multiple URLs.
+ */
export const createBrowserHistory = ({
window = document.defaultView
} = {}) => {
+ let globalHistory = window.history;
+
let getLocation = () => {
let { pathname, search, hash } = window.location;
- let { state } = window.history;
+ let state = globalHistory.state || {};
return createReadOnlyObject({
pathname,
search,
hash,
- state: (state && state.user) || null,
- key: (state && state.key) || 'default'
+ state: state.usr || null,
+ key: state.key || 'default',
+ index: state.idx
});
};
- // TODO: Support forceRefresh and do NOT notify listeners when used.
+ let ignoreNextPop = false;
+
+ // TODO: Add reload arg
let handleNavigation = (nextAction, nextLocation) => {
+ if (nextAction === PopAction && ignoreNextPop) {
+ ignoreNextPop = false;
+ return;
+ }
+
if (blockers.length) {
blockers.call({ action: nextAction, location: nextLocation });
- if (nextAction === PopAction) {
- // TODO: revert the POP
+ if (nextAction === PopAction && nextLocation.index != null) {
+ // Revert the POP
+ let n = location.index - nextLocation.index;
+ if (n) {
+ ignoreNextPop = true;
+ globalHistory.go(n);
+ }
}
} else {
- let state = { user: nextLocation.state, key: nextLocation.key };
+ let state = {
+ usr: nextLocation.state,
+ key: nextLocation.key,
+ idx: nextLocation.index
+ };
let url = createPath(nextLocation);
if (nextAction === PushAction) {
// try...catch because iOS limits us to 100 pushState calls :/
try {
- window.history.pushState(state, null, url);
+ globalHistory.pushState(state, null, url);
} catch (error) {
// They are going to lose state here, but there is no real
// way to warn them about it since the page will refresh...
window.location.assign(url);
}
} else if (nextAction === ReplaceAction) {
- window.history.replaceState(state, null, url);
+ globalHistory.replaceState(state, null, url);
}
action = nextAction;
+ // Get the location fresh so we can support relative paths.
location = getLocation();
listeners.call({ action, location });
}
};
- let action = PopAction;
- let location = getLocation();
- let blockers = createEvents();
- let listeners = createEvents();
+ let toggleBeforeUnloadBlocker = on =>
+ window[`${on ? 'add' : 'remove'}EventListener`](
+ BeforeUnloadEvent,
+ preventUnload
+ );
+
+ // Initialize the index for this document.
+ globalHistory.replaceState({ ...globalHistory.state, idx: 0 }, null);
window.addEventListener(PopStateEvent, event => {
handleNavigation(PopAction, getLocation());
});
+ let action = PopAction;
+ let location = getLocation();
+ let blockers = createEvents();
+ let listeners = createEvents();
+
let history = {
+ get action() {
+ return action;
+ },
+ get location() {
+ return location;
+ },
createHref: createPath,
- block: fn => blockers.push(fn),
+ block: fn => {
+ let unblock = blockers.push(fn);
+ if (blockers.length === 1) toggleBeforeUnloadBlocker(1);
+ return () => {
+ unblock();
+ // Remove the beforeunload listener so the document may be
+ // salvageable in the pagehide event.
+ // See https://html.spec.whatwg.org/#unloading-documents
+ if (!blockers.length) toggleBeforeUnloadBlocker(0);
+ };
+ },
listen: fn => listeners.push(fn),
navigate: (to, { replace = false, state = null } = {}) =>
handleNavigation(
@@ -158,46 +215,43 @@ export const createBrowserHistory = ({
? parsePath(to)
: { pathname: '/', search: '', hash: '', ...to }),
state,
- key: createKey()
+ key: createKey(),
+ index: location.index + (replace ? 0 : 1)
})
),
- go: n => window.history.go(n),
+ go: n => globalHistory.go(n),
back: () => history.go(-1),
forward: () => history.go(1)
};
- Object.defineProperty(history, 'action', {
- enumerable: true,
- get: () => action
- });
-
- Object.defineProperty(history, 'location', {
- enumerable: true,
- get: () => location
- });
-
return history;
};
+/**
+ * Hash history stores the location in window.location.hash. This makes
+ * it ideal for situations where you don't want to send the location to
+ * the server for some reason, either because you do cannot configure it
+ * or the URL space is reserved for something else.
+ */
export const createHashHistory = ({ window = document.defaultView } = {}) => {
+ let globalHistory = window.history;
+
let getLocation = () => {
let { pathname, search, hash } = parsePath(window.location.hash.substr(1));
- let { state } = window.history;
+ let state = globalHistory.state || {};
return createReadOnlyObject({
pathname,
search,
hash,
- state: (state && state.user) || null,
- key: (state && state.key) || 'default'
+ state: state.usr || null,
+ key: state.key || 'default',
+ index: state.idx
});
};
- let action = PopAction;
- let location = getLocation();
- let blockers = createEvents();
- let listeners = createEvents();
+ let ignoreNextPop = false;
- // TODO: Support forceRefresh and do NOT notify listeners when used.
+ // TODO: Add reload arg
let handleNavigation = (nextAction, nextLocation) => {
if (__DEV__) {
if (nextLocation.pathname.charAt(0) !== '/') {
@@ -209,29 +263,42 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
}
}
+ if (nextAction === PopAction && ignoreNextPop) {
+ ignoreNextPop = false;
+ return;
+ }
+
if (blockers.length) {
blockers.call({ action: nextAction, location: nextLocation });
- if (nextAction === PopAction) {
- // TODO: revert the POP
+ if (nextAction === PopAction && nextLocation.index != null) {
+ // Revert the POP
+ let n = location.index - nextLocation.index;
+ if (n) {
+ ignoreNextPop = true;
+ globalHistory.go(n);
+ }
}
} else {
- let state = { user: nextLocation.state, key: nextLocation.key };
- // TODO: Handle relative paths? Or just warn about them?
+ let state = {
+ usr: nextLocation.state,
+ key: nextLocation.key,
+ idx: nextLocation.index
+ };
// TODO: Support different "hash types"?
let url = '#' + createPath(nextLocation);
if (nextAction === PushAction) {
// try...catch because iOS limits us to 100 pushState calls :/
try {
- window.history.pushState(state, null, url);
+ globalHistory.pushState(state, null, url);
} catch (error) {
// They are going to lose state here, but there is no real
// way to warn them about it since the page will refresh...
window.location.assign(url);
}
} else if (nextAction === ReplaceAction) {
- window.history.replaceState(state, null, url);
+ globalHistory.replaceState(state, null, url);
}
action = nextAction;
@@ -240,6 +307,15 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
}
};
+ let toggleBeforeUnloadBlocker = on =>
+ window[`${on ? 'add' : 'remove'}EventListener`](
+ BeforeUnloadEvent,
+ preventUnload
+ );
+
+ // Initialize the index for this document.
+ globalHistory.replaceState({ ...globalHistory.state, idx: 0 }, null);
+
window.addEventListener(PopStateEvent, event => {
handleNavigation(PopAction, getLocation());
});
@@ -253,7 +329,18 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
}
});
+ let action = PopAction;
+ let location = getLocation();
+ let blockers = createEvents();
+ let listeners = createEvents();
+
let history = {
+ get action() {
+ return action;
+ },
+ get location() {
+ return location;
+ },
createHref: location => {
let base = document.querySelector('base');
let href = '';
@@ -262,7 +349,17 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
}
return href + '#' + createPath(location);
},
- block: fn => blockers.push(fn),
+ block: fn => {
+ let unblock = blockers.push(fn);
+ if (blockers.length === 1) toggleBeforeUnloadBlocker(1);
+ return () => {
+ unblock();
+ // Remove the beforeunload listener so the document may be
+ // salvageable in the pagehide event.
+ // See https://html.spec.whatwg.org/#unloading-documents
+ if (!blockers.length) toggleBeforeUnloadBlocker(0);
+ };
+ },
listen: fn => listeners.push(fn),
navigate: (to, { replace = false, state = null } = {}) =>
handleNavigation(
@@ -272,24 +369,15 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
? parsePath(to)
: { pathname: '/', search: '', hash: '', ...to }),
state,
- key: createKey()
+ key: createKey(),
+ index: location.index + (replace ? 0 : 1)
})
),
- go: n => window.history.go(n),
+ go: n => globalHistory.go(n),
back: () => history.go(-1),
forward: () => history.go(1)
};
- Object.defineProperty(history, 'action', {
- enumerable: true,
- get: () => action
- });
-
- Object.defineProperty(history, 'location', {
- enumerable: true,
- get: () => location
- });
-
return history;
};
@@ -354,3 +442,10 @@ const createEvents = () => {
const clamp = (n, lowerBound, upperBound) =>
Math.min(Math.max(n, lowerBound), upperBound);
+
+const preventUnload = event => {
+ // Cancel the event.
+ event.preventDefault();
+ // Chrome (and legacy IE) requires returnValue to be set.
+ event.returnValue = '';
+};
From 0831aae742c8146f7aaecebd2183078a899221bf Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Wed, 6 Nov 2019 11:59:11 -0800
Subject: [PATCH 009/133] Add history.retry for retrying transitions
Also, wait to call blockers on POP transitions until we have already
returned to the URL we just left.
---
.size-snapshot.json | 18 ++---
fixtures/unload-history.html | 34 ++++-----
modules/index.js | 135 +++++++++++++++++++++++++----------
3 files changed, 119 insertions(+), 68 deletions(-)
diff --git a/.size-snapshot.json b/.size-snapshot.json
index ffc5b5dbd..a0cf97e3e 100644
--- a/.size-snapshot.json
+++ b/.size-snapshot.json
@@ -1,8 +1,8 @@
{
"esm/history.js": {
- "bundled": 16328,
- "minified": 6304,
- "gzipped": 1786,
+ "bundled": 18963,
+ "minified": 7493,
+ "gzipped": 2040,
"treeshaked": {
"rollup": {
"code": 43,
@@ -14,13 +14,13 @@
}
},
"umd/history.js": {
- "bundled": 17970,
- "minified": 5813,
- "gzipped": 1944
+ "bundled": 20613,
+ "minified": 6873,
+ "gzipped": 2192
},
"umd/history.min.js": {
- "bundled": 17356,
- "minified": 5500,
- "gzipped": 1813
+ "bundled": 18495,
+ "minified": 5944,
+ "gzipped": 1893
}
}
diff --git a/fixtures/unload-history.html b/fixtures/unload-history.html
index 11348a447..e263f95c8 100644
--- a/fixtures/unload-history.html
+++ b/fixtures/unload-history.html
@@ -11,12 +11,6 @@ Controls
-
- regular links:
- home
- one
- two
-
pushState & replaceState:
pushState(home)
@@ -24,6 +18,10 @@
Controls
pushState(two)
replaceState(three)
+
+ regular link:
+ home
+
Test Scenarios
@@ -31,7 +29,6 @@ Test Scenarios
- Click block - Click back - You should be prompted to stay
- Click block - Click home - You should be prompted to stay
- - Click block - Click one - You should be prompted to stay
- Click block - Close the tab - You should be prompted to stay
- Click pushState(one) - Click block - Click back - You should still be at /unload-history-one
- Click pushState(one) - Click block - Click pushState(two) - You should still be at /unload-history-one
@@ -49,22 +46,17 @@ Test Scenarios
var blocker = document.getElementById('blocker');
var unblock;
+
blocker.addEventListener('change', () => {
if (blocker.checked) {
- unblock = h.block(({ action, location }) => {
- console.log(`blocked ${action} to ${location.pathname}`)
-
- // Use a setTimeout here to give the router time to reset
- // the URL back to what it was before we blocked the tx.
- setTimeout(() => {
- if (window.confirm(`Are you sure you want to go to ${location.pathname}?`)) {
- // Reset the blocker so the next tx is allowed.
- blocker.checked = false;
- unblock();
- // Replay the previous tx.
- h.navigate(location, { replace: action === 'REPLACE' });
- }
- }, 100);
+ unblock = h.block(tx => {
+ if (window.confirm(`Are you sure you want to go to ${tx.location.pathname}?`)) {
+ // Reset the blocker so the next transition is allowed.
+ blocker.checked = false;
+ unblock();
+ // Retry the transition.
+ h.retry(tx);
+ }
});
} else if (unblock) {
unblock();
diff --git a/modules/index.js b/modules/index.js
index 15ea9007d..484623799 100644
--- a/modules/index.js
+++ b/modules/index.js
@@ -7,8 +7,8 @@ const PopStateEvent = 'popstate';
const HashChangeEvent = 'hashchange';
// There's some duplication in this code, but only one create* method
-// should ever be used in a given web page, so it's best to just inline
-// everything for minification.
+// should ever be used in a given web page, so it's best for minifying
+// to just inline everything.
/**
* Memory history stores the current location in memory. It is designed
@@ -86,6 +86,11 @@ export const createMemoryHistory = ({
index: location.index + (replace ? 0 : 1)
})
),
+ retry: tx =>
+ history.navigate(createPath(tx.location), {
+ replace: tx.action === ReplaceAction,
+ state: tx.location.state
+ }),
go: n =>
handleNavigation(
PopAction,
@@ -121,25 +126,43 @@ export const createBrowserHistory = ({
});
};
- let ignoreNextPop = false;
+ let blockedPopTx = null;
- // TODO: Add reload arg
+ // TODO: Add reload arg to force page refresh
let handleNavigation = (nextAction, nextLocation) => {
- if (nextAction === PopAction && ignoreNextPop) {
- ignoreNextPop = false;
- return;
- }
-
- if (blockers.length) {
- blockers.call({ action: nextAction, location: nextLocation });
-
- if (nextAction === PopAction && nextLocation.index != null) {
- // Revert the POP
- let n = location.index - nextLocation.index;
- if (n) {
- ignoreNextPop = true;
- globalHistory.go(n);
+ if (nextAction === PopAction && blockedPopTx) {
+ blockers.call(blockedPopTx);
+ blockedPopTx = null;
+ } else if (blockers.length) {
+ let tx = { action: nextAction, location: nextLocation };
+
+ if (nextAction === PopAction) {
+ if (nextLocation.index != null) {
+ let n = location.index - nextLocation.index;
+ if (n) {
+ // Revert the POP
+ tx.delta = n * -1;
+ blockedPopTx = tx;
+ history.go(n);
+ }
+ } else {
+ // Trying to POP to a location with no index. We did not create
+ // this location, so we can't effectively block the navigation.
+ if (__DEV__) {
+ // TODO: Write up a doc that explains our blocking strategy in
+ // detail and link to it here so people can understand better
+ // what is going on and how to avoid it.
+ throw new Error(
+ `You are trying to block a POP navigation to a location that was not ` +
+ `created by the history library. The block will fail silently in ` +
+ `production, but in general you should do all navigation with the ` +
+ `history library (instead of using window.history.pushState directly) ` +
+ `to avoid this situation.`
+ );
+ }
}
+ } else {
+ blockers.call(tx);
}
} else {
let state = {
@@ -200,8 +223,8 @@ export const createBrowserHistory = ({
if (blockers.length === 1) toggleBeforeUnloadBlocker(1);
return () => {
unblock();
- // Remove the beforeunload listener so the document may be
- // salvageable in the pagehide event.
+ // Remove the beforeunload listener so the document may
+ // still be salvageable in the pagehide event.
// See https://html.spec.whatwg.org/#unloading-documents
if (!blockers.length) toggleBeforeUnloadBlocker(0);
};
@@ -219,6 +242,13 @@ export const createBrowserHistory = ({
index: location.index + (replace ? 0 : 1)
})
),
+ retry: tx =>
+ tx.delta
+ ? history.go(tx.delta)
+ : history.navigate(createPath(tx.location), {
+ replace: tx.action === ReplaceAction,
+ state: tx.location.state
+ }),
go: n => globalHistory.go(n),
back: () => history.go(-1),
forward: () => history.go(1)
@@ -249,7 +279,7 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
});
};
- let ignoreNextPop = false;
+ let blockedPopTx = null;
// TODO: Add reload arg
let handleNavigation = (nextAction, nextLocation) => {
@@ -263,21 +293,41 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
}
}
- if (nextAction === PopAction && ignoreNextPop) {
- ignoreNextPop = false;
- return;
- }
-
- if (blockers.length) {
- blockers.call({ action: nextAction, location: nextLocation });
-
- if (nextAction === PopAction && nextLocation.index != null) {
- // Revert the POP
- let n = location.index - nextLocation.index;
- if (n) {
- ignoreNextPop = true;
- globalHistory.go(n);
+ if (nextAction === PopAction && blockedPopTx) {
+ // Now that we are back at the original URL,
+ // call blockers with the transition we blocked.
+ blockers.call(blockedPopTx);
+ blockedPopTx = null;
+ } else if (blockers.length) {
+ let tx = { action: nextAction, location: nextLocation };
+
+ if (nextAction === PopAction) {
+ if (nextLocation.index != null) {
+ let n = location.index - nextLocation.index;
+ if (n) {
+ // Revert the POP
+ tx.delta = n * -1;
+ blockedPopTx = tx;
+ history.go(n);
+ }
+ } else {
+ // Trying to POP to a location with no index. We did not create
+ // this location, so we can't effectively block the navigation.
+ if (__DEV__) {
+ // TODO: Write up a doc that explains our blocking strategy in
+ // detail and link to it here so people can understand better
+ // what is going on and how to avoid it.
+ throw new Error(
+ `You are trying to block a POP navigation to a location that was not ` +
+ `created by the history library. The block will fail silently in ` +
+ `production, but in general you should do all navigation with the ` +
+ `history library (instead of using window.history.pushState directly) ` +
+ `to avoid this situation.`
+ );
+ }
}
+ } else {
+ blockers.call(tx);
}
} else {
let state = {
@@ -320,12 +370,14 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
handleNavigation(PopAction, getLocation());
});
+ // TODO: Is this still necessary? Which browsers do
+ // not trigger popstate when the hash changes?
window.addEventListener(HashChangeEvent, event => {
let nextLocation = getLocation();
// Ignore extraneous hashchange events.
if (createPath(nextLocation) !== createPath(location)) {
- handleNavigation(PopAction, getLocation());
+ handleNavigation(PopAction, nextLocation);
}
});
@@ -354,8 +406,8 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
if (blockers.length === 1) toggleBeforeUnloadBlocker(1);
return () => {
unblock();
- // Remove the beforeunload listener so the document may be
- // salvageable in the pagehide event.
+ // Remove the beforeunload listener so the document may
+ // still be salvageable in the pagehide event.
// See https://html.spec.whatwg.org/#unloading-documents
if (!blockers.length) toggleBeforeUnloadBlocker(0);
};
@@ -373,6 +425,13 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
index: location.index + (replace ? 0 : 1)
})
),
+ retry: tx =>
+ tx.delta
+ ? history.go(tx.delta)
+ : history.navigate(createPath(tx.location), {
+ replace: tx.action === ReplaceAction,
+ state: tx.location.state
+ }),
go: n => globalHistory.go(n),
back: () => history.go(-1),
forward: () => history.go(1)
From c1702abd72cbd1433489060622dbc4b054839743 Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Wed, 6 Nov 2019 12:12:36 -0800
Subject: [PATCH 010/133] Run all tests in parallel
---
.travis.yml | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 093b896a4..fd9f261e5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,17 +1,17 @@
language: node_js
node_js: node
cache: yarn
-env:
-- TEST_ENV=cjs BUILD_ENV=cjs
-- TEST_ENV=umd BUILD_ENV=umd
-- TEST_ENV=source
-before_script:
-- ([[ -z "$BUILD_ENV" ]] || yarn build)
-script:
-- yarn lint
-- yarn test
jobs:
include:
+ - stage: Test
+ env: TEST_ENV=cjs BUILD_ENV=cjs
+ before_script: yarn build
+ script: yarn test
+ - env: TEST_ENV=umd BUILD_ENV=umd
+ before_script: yarn build
+ script: yarn test
+ - env: TEST_ENV=source
+ script: yarn test
- stage: Release
if: tag =~ ^v[0-9]
env: NPM_TAG=$([[ "$TRAVIS_TAG" == *-* ]] && echo "next" || echo "latest")
From 67f6aea10d8aabaead2f7ff33c30dcb7d7511d64 Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Thu, 7 Nov 2019 10:22:07 -0800
Subject: [PATCH 011/133] Remove location.index, use history.push/replace
Removed the history.navigate API in favor of push + replace.
---
.size-snapshot.json | 18 +-
.../TestSequences/BackButtonTransitionHook.js | 2 +-
.../TestSequences/BlockEverything.js | 2 +-
.../TestSequences/BlockPopWithoutListening.js | 2 +-
.../EncodedReservedCharacters.js | 7 +-
modules/__tests__/TestSequences/GoBack.js | 2 +-
modules/__tests__/TestSequences/GoForward.js | 2 +-
.../LocationPathnameAlwaysSame.js | 8 +-
.../TestSequences/PushEncodedLocation.js | 2 +-
.../TestSequences/PushMissingPathname.js | 4 +-
.../TestSequences/PushNewLocation.js | 2 +-
.../TestSequences/PushRelativePathname.js | 4 +-
.../PushRelativePathnameError.js | 4 +-
.../__tests__/TestSequences/PushSamePath.js | 4 +-
modules/__tests__/TestSequences/PushState.js | 2 +-
.../TestSequences/PushUnicodeLocation.js | 2 +-
.../TestSequences/ReplaceNewLocation.js | 2 +-
.../TestSequences/ReplaceSamePath.js | 4 +-
.../__tests__/TestSequences/ReplaceState.js | 5 +-
modules/index.js | 700 +++++++++++-------
20 files changed, 461 insertions(+), 317 deletions(-)
diff --git a/.size-snapshot.json b/.size-snapshot.json
index a0cf97e3e..bd7e3f948 100644
--- a/.size-snapshot.json
+++ b/.size-snapshot.json
@@ -1,8 +1,8 @@
{
"esm/history.js": {
- "bundled": 18963,
- "minified": 7493,
- "gzipped": 2040,
+ "bundled": 21263,
+ "minified": 7754,
+ "gzipped": 2212,
"treeshaked": {
"rollup": {
"code": 43,
@@ -14,13 +14,13 @@
}
},
"umd/history.js": {
- "bundled": 20613,
- "minified": 6873,
- "gzipped": 2192
+ "bundled": 23045,
+ "minified": 7150,
+ "gzipped": 2182
},
"umd/history.min.js": {
- "bundled": 18495,
- "minified": 5944,
- "gzipped": 1893
+ "bundled": 20565,
+ "minified": 5940,
+ "gzipped": 1883
}
}
diff --git a/modules/__tests__/TestSequences/BackButtonTransitionHook.js b/modules/__tests__/TestSequences/BackButtonTransitionHook.js
index 8f197bde1..59343e17f 100644
--- a/modules/__tests__/TestSequences/BackButtonTransitionHook.js
+++ b/modules/__tests__/TestSequences/BackButtonTransitionHook.js
@@ -12,7 +12,7 @@ export default (history, done) => {
pathname: '/'
});
- history.navigate('/home');
+ history.push('/home');
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/BlockEverything.js b/modules/__tests__/TestSequences/BlockEverything.js
index 083d30c86..bc1bd45de 100644
--- a/modules/__tests__/TestSequences/BlockEverything.js
+++ b/modules/__tests__/TestSequences/BlockEverything.js
@@ -11,7 +11,7 @@ export default (history, done) => {
let unblock = history.block();
- history.navigate('/home');
+ history.push('/home');
expect(history.location).toMatchObject({
pathname: '/'
diff --git a/modules/__tests__/TestSequences/BlockPopWithoutListening.js b/modules/__tests__/TestSequences/BlockPopWithoutListening.js
index 8b8203d93..3925870e8 100644
--- a/modules/__tests__/TestSequences/BlockPopWithoutListening.js
+++ b/modules/__tests__/TestSequences/BlockPopWithoutListening.js
@@ -5,7 +5,7 @@ export default (history, done) => {
pathname: '/'
});
- history.navigate('/home');
+ history.push('/home');
let transitionHookWasCalled = false;
let unblock = history.block(() => {
diff --git a/modules/__tests__/TestSequences/EncodedReservedCharacters.js b/modules/__tests__/TestSequences/EncodedReservedCharacters.js
index 70cea3e52..97c2a1363 100644
--- a/modules/__tests__/TestSequences/EncodedReservedCharacters.js
+++ b/modules/__tests__/TestSequences/EncodedReservedCharacters.js
@@ -7,16 +7,15 @@ export default (history, done) => {
() => {
// encoded string
let pathname = '/view/%23abc';
- history.navigate(pathname, { replace: true });
+ history.replace(pathname);
},
({ location }) => {
expect(location).toMatchObject({
pathname: '/view/%23abc'
});
-
// encoded object
let pathname = '/view/%23abc';
- history.navigate({ pathname }, { replace: true });
+ history.replace({ pathname });
},
({ location }) => {
expect(location).toMatchObject({
@@ -24,7 +23,7 @@ export default (history, done) => {
});
// unencoded string
let pathname = '/view/#abc';
- history.navigate(pathname, { replace: true });
+ history.replace(pathname);
},
({ location }) => {
expect(location).toMatchObject({
diff --git a/modules/__tests__/TestSequences/GoBack.js b/modules/__tests__/TestSequences/GoBack.js
index 11b8c02e4..9ff55501f 100644
--- a/modules/__tests__/TestSequences/GoBack.js
+++ b/modules/__tests__/TestSequences/GoBack.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.navigate('/home');
+ history.push('/home');
},
({ action, location }) => {
expect(action).toEqual('PUSH');
diff --git a/modules/__tests__/TestSequences/GoForward.js b/modules/__tests__/TestSequences/GoForward.js
index 17e4f2375..94bdf42c4 100644
--- a/modules/__tests__/TestSequences/GoForward.js
+++ b/modules/__tests__/TestSequences/GoForward.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.navigate('/home');
+ history.push('/home');
},
({ action, location }) => {
expect(action).toEqual('PUSH');
diff --git a/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js b/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js
index adb9d5548..24aef37ad 100644
--- a/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js
+++ b/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js
@@ -7,7 +7,7 @@ export default (history, done) => {
() => {
// encoded string
let pathname = '/%E6%AD%B4%E5%8F%B2';
- history.navigate(pathname, { replace: true });
+ history.replace(pathname);
},
({ location }) => {
expect(location).toMatchObject({
@@ -15,7 +15,7 @@ export default (history, done) => {
});
// encoded object
let pathname = '/%E6%AD%B4%E5%8F%B2';
- history.navigate({ pathname }, { replace: true });
+ history.replace({ pathname });
},
({ location }) => {
expect(location).toMatchObject({
@@ -23,7 +23,7 @@ export default (history, done) => {
});
// unencoded string
let pathname = '/歴史';
- history.navigate(pathname, { replace: true });
+ history.replace(pathname);
},
({ location }) => {
expect(location).toMatchObject({
@@ -31,7 +31,7 @@ export default (history, done) => {
});
// unencoded object
let pathname = '/歴史';
- history.navigate({ pathname }, { replace: true });
+ history.replace({ pathname });
},
({ location }) => {
expect(location).toMatchObject({
diff --git a/modules/__tests__/TestSequences/PushEncodedLocation.js b/modules/__tests__/TestSequences/PushEncodedLocation.js
index 782d2b551..b55137e34 100644
--- a/modules/__tests__/TestSequences/PushEncodedLocation.js
+++ b/modules/__tests__/TestSequences/PushEncodedLocation.js
@@ -12,7 +12,7 @@ export default (history, done) => {
let pathname = '/歴史';
let search = '?%E3%82%AD%E3%83%BC=%E5%80%A4';
let hash = '#%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5';
- history.navigate(pathname + search + hash);
+ history.push(pathname + search + hash);
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/PushMissingPathname.js b/modules/__tests__/TestSequences/PushMissingPathname.js
index 863f18896..dd7f8a8e1 100644
--- a/modules/__tests__/TestSequences/PushMissingPathname.js
+++ b/modules/__tests__/TestSequences/PushMissingPathname.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.navigate('/home?the=query#the-hash');
+ history.push('/home?the=query#the-hash');
},
({ action, location }) => {
expect(action).toBe('PUSH');
@@ -19,7 +19,7 @@ export default (history, done) => {
hash: '#the-hash'
});
- history.navigate('?another=query#another-hash');
+ history.push('?another=query#another-hash');
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/PushNewLocation.js b/modules/__tests__/TestSequences/PushNewLocation.js
index abe0697a5..21e8e86f1 100644
--- a/modules/__tests__/TestSequences/PushNewLocation.js
+++ b/modules/__tests__/TestSequences/PushNewLocation.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.navigate('/home?the=query#the-hash');
+ history.push('/home?the=query#the-hash');
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/PushRelativePathname.js b/modules/__tests__/TestSequences/PushRelativePathname.js
index cd0880571..8e14581d5 100644
--- a/modules/__tests__/TestSequences/PushRelativePathname.js
+++ b/modules/__tests__/TestSequences/PushRelativePathname.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.navigate('/the/path?the=query#the-hash');
+ history.push('/the/path?the=query#the-hash');
},
({ action, location }) => {
expect(action).toBe('PUSH');
@@ -19,7 +19,7 @@ export default (history, done) => {
hash: '#the-hash'
});
- history.navigate('../other/path?another=query#another-hash');
+ history.push('../other/path?another=query#another-hash');
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/PushRelativePathnameError.js b/modules/__tests__/TestSequences/PushRelativePathnameError.js
index a5155ef86..3f50a2a18 100644
--- a/modules/__tests__/TestSequences/PushRelativePathnameError.js
+++ b/modules/__tests__/TestSequences/PushRelativePathnameError.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.navigate('/the/path?the=query#the-hash');
+ history.push('/the/path?the=query#the-hash');
},
({ action, location }) => {
expect(action).toBe('PUSH');
@@ -20,7 +20,7 @@ export default (history, done) => {
});
try {
- history.navigate('../other/path?another=query#another-hash');
+ history.push('../other/path?another=query#another-hash');
} catch (error) {
expect(error.message).toMatch(/relative pathnames are not supported/i);
}
diff --git a/modules/__tests__/TestSequences/PushSamePath.js b/modules/__tests__/TestSequences/PushSamePath.js
index cf1780faa..b3a12628e 100644
--- a/modules/__tests__/TestSequences/PushSamePath.js
+++ b/modules/__tests__/TestSequences/PushSamePath.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.navigate('/home');
+ history.push('/home');
},
({ action, location }) => {
expect(action).toBe('PUSH');
@@ -17,7 +17,7 @@ export default (history, done) => {
pathname: '/home'
});
- history.navigate('/home');
+ history.push('/home');
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/PushState.js b/modules/__tests__/TestSequences/PushState.js
index 1d1e720c8..70f02bb5b 100644
--- a/modules/__tests__/TestSequences/PushState.js
+++ b/modules/__tests__/TestSequences/PushState.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.navigate('/home?the=query#the-hash', { state: { the: 'state' } });
+ history.push('/home?the=query#the-hash', { the: 'state' });
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/PushUnicodeLocation.js b/modules/__tests__/TestSequences/PushUnicodeLocation.js
index 2e92003c4..b9b3c0913 100644
--- a/modules/__tests__/TestSequences/PushUnicodeLocation.js
+++ b/modules/__tests__/TestSequences/PushUnicodeLocation.js
@@ -12,7 +12,7 @@ export default (history, done) => {
let pathname = '/歴史';
let search = '?キー=値';
let hash = '#ハッシュ';
- history.navigate(pathname + search + hash);
+ history.push(pathname + search + hash);
},
({ action, location }) => {
expect(action).toBe('PUSH');
diff --git a/modules/__tests__/TestSequences/ReplaceNewLocation.js b/modules/__tests__/TestSequences/ReplaceNewLocation.js
index 5a5d9d15f..0c4f93fbd 100644
--- a/modules/__tests__/TestSequences/ReplaceNewLocation.js
+++ b/modules/__tests__/TestSequences/ReplaceNewLocation.js
@@ -9,7 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.navigate('/home?the=query#the-hash', { replace: true });
+ history.replace('/home?the=query#the-hash');
},
({ action, location }) => {
expect(action).toBe('REPLACE');
diff --git a/modules/__tests__/TestSequences/ReplaceSamePath.js b/modules/__tests__/TestSequences/ReplaceSamePath.js
index 06d4a4c42..df06a3400 100644
--- a/modules/__tests__/TestSequences/ReplaceSamePath.js
+++ b/modules/__tests__/TestSequences/ReplaceSamePath.js
@@ -11,7 +11,7 @@ export default (history, done) => {
pathname: '/'
});
- history.navigate('/home', { replace: true });
+ history.replace('/home');
},
({ action, location }) => {
expect(action).toBe('REPLACE');
@@ -21,7 +21,7 @@ export default (history, done) => {
prevLocation = location;
- history.navigate('/home', { replace: true });
+ history.replace('/home');
},
({ action, location }) => {
expect(action).toBe('REPLACE');
diff --git a/modules/__tests__/TestSequences/ReplaceState.js b/modules/__tests__/TestSequences/ReplaceState.js
index 2622e89e6..56f7ad178 100644
--- a/modules/__tests__/TestSequences/ReplaceState.js
+++ b/modules/__tests__/TestSequences/ReplaceState.js
@@ -9,10 +9,7 @@ export default (history, done) => {
pathname: '/'
});
- history.navigate('/home?the=query#the-hash', {
- replace: true,
- state: { the: 'state' }
- });
+ history.replace('/home?the=query#the-hash', { the: 'state' });
},
({ action, location }) => {
expect(action).toBe('REPLACE');
diff --git a/modules/index.js b/modules/index.js
index 484623799..4122329ad 100644
--- a/modules/index.js
+++ b/modules/index.js
@@ -25,46 +25,108 @@ export const createMemoryHistory = ({
hash = '',
state = null,
// Auto-assign keys to entries that don't already have them.
- key = createKey(),
- index
- }) => createReadOnlyObject({ pathname, search, hash, state, key, index });
+ key = createKey()
+ }) => createReadOnlyObject({ pathname, search, hash, state, key });
+
+ let entries = initialEntries.map((entry, index) =>
+ createLocation({
+ ...(typeof entry === 'string' ? parsePath(entry) : entry),
+ index
+ })
+ );
+ let index = clamp(initialIndex, 0, entries.length - 1);
+
+ let action = PopAction;
+ let location = entries[index];
+ let blockers = createEvents();
+ let listeners = createEvents();
+
+ let createHref = createPath;
+
+ let getNextLocation = (to, state) =>
+ createLocation({
+ ...(typeof to === 'string'
+ ? parsePath(to)
+ : { pathname: '/', search: '', hash: '', ...to }),
+ state,
+ key: createKey()
+ });
+
+ let allowTx = (action, location, retry) =>
+ !blockers.length || (blockers.call({ action, location, retry }), false);
+
+ let applyTx = (nextAction, nextLocation) => {
+ action = nextAction;
+ location = nextLocation;
+ listeners.call({ action, location });
+ };
+
+ let push = (to, state) => {
+ let nextAction = PushAction;
+ let nextLocation = getNextLocation(to, state);
+ let retry = () => push(to, state);
- let handleNavigation = (nextAction, nextLocation) => {
if (__DEV__) {
- if (nextLocation && nextLocation.pathname.charAt(0) !== '/') {
- let arg = createPath(nextLocation);
- let fnCall = `${nextAction.toLowerCase()}("${arg}")`;
+ if (nextLocation.pathname.charAt(0) !== '/') {
+ let arg = JSON.stringify(to);
throw new Error(
- `Relative pathnames are not supported in createMemoryHistory().${fnCall}`
+ `Relative pathnames are not supported in createMemoryHistory().push(${arg})`
);
}
}
- if (blockers.length) {
- blockers.call({ action: nextAction, location: nextLocation });
- } else {
- if (nextAction === PushAction) {
- entries.splice(nextLocation.index, entries.length, nextLocation);
- } else if (nextAction === ReplaceAction) {
- entries[nextLocation.index] = nextLocation;
+ if (allowTx(nextAction, nextLocation, retry)) {
+ index += 1;
+ entries.splice(index, entries.length, nextLocation);
+ applyTx(nextAction, nextLocation);
+ }
+ };
+
+ let replace = (to, state) => {
+ let nextAction = ReplaceAction;
+ let nextLocation = getNextLocation(to, state);
+ let retry = () => replace(to, state);
+
+ if (__DEV__) {
+ if (nextLocation.pathname.charAt(0) !== '/') {
+ let arg = JSON.stringify(to);
+ throw new Error(
+ `Relative pathnames are not supported in createMemoryHistory().replace(${arg})`
+ );
}
- action = nextAction;
- location = nextLocation;
- listeners.call({ action, location });
+ }
+
+ if (allowTx(nextAction, nextLocation, retry)) {
+ entries[index] = nextLocation;
+ applyTx(nextAction, nextLocation);
}
};
- let entries = initialEntries.map((entry, index) =>
- createLocation({
- ...(typeof entry === 'string' ? parsePath(entry) : entry),
- index
- })
- );
+ let go = n => {
+ let nextIndex = clamp(index + n, 0, entries.length - 1);
+ let nextAction = PopAction;
+ let nextLocation = entries[nextIndex];
+ let retry = () => {
+ go(n);
+ };
+
+ if (allowTx(nextAction, nextLocation, retry)) {
+ index = nextIndex;
+ applyTx(nextAction, nextLocation);
+ }
+ };
- let action = PopAction;
- let location = entries[clamp(initialIndex, 0, entries.length - 1)];
- let blockers = createEvents();
- let listeners = createEvents();
+ let back = () => {
+ go(-1);
+ };
+
+ let forward = () => {
+ go(1);
+ };
+
+ let listen = fn => listeners.push(fn);
+
+ let block = fn => blockers.push(fn);
let history = {
get action() {
@@ -73,31 +135,14 @@ export const createMemoryHistory = ({
get location() {
return location;
},
- createHref: createPath,
- block: fn => blockers.push(fn),
- listen: fn => listeners.push(fn),
- navigate: (to, { replace = false, state = null } = {}) =>
- handleNavigation(
- replace ? ReplaceAction : PushAction,
- createLocation({
- ...(typeof to === 'string' ? parsePath(to) : to),
- state,
- key: createKey(),
- index: location.index + (replace ? 0 : 1)
- })
- ),
- retry: tx =>
- history.navigate(createPath(tx.location), {
- replace: tx.action === ReplaceAction,
- state: tx.location.state
- }),
- go: n =>
- handleNavigation(
- PopAction,
- entries[clamp(location.index + n, 0, entries.length - 1)]
- ),
- back: () => history.go(-1),
- forward: () => history.go(1)
+ createHref,
+ push,
+ replace,
+ go,
+ back,
+ forward,
+ listen,
+ block
};
return history;
@@ -113,37 +158,44 @@ export const createBrowserHistory = ({
} = {}) => {
let globalHistory = window.history;
- let getLocation = () => {
+ let getIndexAndLocation = () => {
let { pathname, search, hash } = window.location;
let state = globalHistory.state || {};
- return createReadOnlyObject({
- pathname,
- search,
- hash,
- state: state.usr || null,
- key: state.key || 'default',
- index: state.idx
- });
+ return [
+ state.idx,
+ createReadOnlyObject({
+ pathname,
+ search,
+ hash,
+ state: state.usr || null,
+ key: state.key || 'default'
+ })
+ ];
};
let blockedPopTx = null;
-
- // TODO: Add reload arg to force page refresh
- let handleNavigation = (nextAction, nextLocation) => {
- if (nextAction === PopAction && blockedPopTx) {
+ let handlePop = () => {
+ if (blockedPopTx) {
blockers.call(blockedPopTx);
blockedPopTx = null;
- } else if (blockers.length) {
- let tx = { action: nextAction, location: nextLocation };
+ } else {
+ let nextAction = PopAction;
+ let [nextIndex, nextLocation] = getIndexAndLocation();
- if (nextAction === PopAction) {
- if (nextLocation.index != null) {
- let n = location.index - nextLocation.index;
+ if (blockers.length) {
+ if (nextIndex != null) {
+ let n = index - nextIndex;
if (n) {
// Revert the POP
- tx.delta = n * -1;
- blockedPopTx = tx;
- history.go(n);
+ blockedPopTx = {
+ action: nextAction,
+ location: nextLocation,
+ retry: () => {
+ go(n * -1);
+ }
+ };
+
+ go(n);
}
} else {
// Trying to POP to a location with no index. We did not create
@@ -162,53 +214,121 @@ export const createBrowserHistory = ({
}
}
} else {
- blockers.call(tx);
+ applyTx(nextAction);
}
- } else {
- let state = {
- usr: nextLocation.state,
- key: nextLocation.key,
- idx: nextLocation.index
- };
- let url = createPath(nextLocation);
-
- if (nextAction === PushAction) {
- // try...catch because iOS limits us to 100 pushState calls :/
- try {
- globalHistory.pushState(state, null, url);
- } catch (error) {
- // They are going to lose state here, but there is no real
- // way to warn them about it since the page will refresh...
- window.location.assign(url);
- }
- } else if (nextAction === ReplaceAction) {
- globalHistory.replaceState(state, null, url);
+ }
+ };
+
+ window.addEventListener(PopStateEvent, handlePop);
+
+ let action = PopAction;
+ let [index, location] = getIndexAndLocation();
+ let blockers = createEvents();
+ let listeners = createEvents();
+
+ if (index == null) {
+ index = 0;
+ globalHistory.replaceState({ ...globalHistory.state, idx: index }, null);
+ }
+
+ let createHref = createPath;
+
+ let getNextLocation = (to, state) =>
+ createReadOnlyObject({
+ ...(typeof to === 'string'
+ ? parsePath(to)
+ : { pathname: '/', search: '', hash: '', ...to }),
+ state,
+ key: createKey()
+ });
+
+ let getHistoryStateAndUrl = (nextLocation, index) => [
+ {
+ usr: nextLocation.state,
+ key: nextLocation.key,
+ idx: index
+ },
+ createHref(nextLocation)
+ ];
+
+ let allowTx = (action, location, retry) =>
+ !blockers.length || (blockers.call({ action, location, retry }), false);
+
+ let applyTx = nextAction => {
+ action = nextAction;
+ [index, location] = getIndexAndLocation();
+ listeners.call({ action, location });
+ };
+
+ let push = (to, state) => {
+ let nextAction = PushAction;
+ let nextLocation = getNextLocation(to, state);
+ let retry = () => push(to, state);
+
+ if (allowTx(nextAction, nextLocation, retry)) {
+ let [historyState, url] = getHistoryStateAndUrl(nextLocation, index + 1);
+
+ // TODO: Support forced reloading
+ // try...catch because iOS limits us to 100 pushState calls :/
+ try {
+ globalHistory.pushState(historyState, null, url);
+ } catch (error) {
+ // They are going to lose state here, but there is no real
+ // way to warn them about it since the page will refresh...
+ window.location.assign(url);
}
- action = nextAction;
- // Get the location fresh so we can support relative paths.
- location = getLocation();
- listeners.call({ action, location });
+ applyTx(nextAction);
}
};
- let toggleBeforeUnloadBlocker = on =>
- window[`${on ? 'add' : 'remove'}EventListener`](
- BeforeUnloadEvent,
- preventUnload
- );
+ let replace = (to, state) => {
+ let nextAction = ReplaceAction;
+ let nextLocation = getNextLocation(to, state);
+ let retry = () => replace(to, state);
- // Initialize the index for this document.
- globalHistory.replaceState({ ...globalHistory.state, idx: 0 }, null);
+ if (allowTx(nextAction, nextLocation, retry)) {
+ let [historyState, url] = getHistoryStateAndUrl(nextLocation, index);
- window.addEventListener(PopStateEvent, event => {
- handleNavigation(PopAction, getLocation());
- });
+ // TODO: Support forced reloading
+ globalHistory.replaceState(historyState, null, url);
- let action = PopAction;
- let location = getLocation();
- let blockers = createEvents();
- let listeners = createEvents();
+ applyTx(nextAction);
+ }
+ };
+
+ let go = n => {
+ globalHistory.go(n);
+ };
+
+ let back = () => {
+ go(-1);
+ };
+
+ let forward = () => {
+ go(1);
+ };
+
+ let listen = fn => listeners.push(fn);
+
+ let block = fn => {
+ let unblock = blockers.push(fn);
+
+ if (blockers.length === 1) {
+ window.addEventListener(BeforeUnloadEvent, promptBeforeUnload);
+ }
+
+ return () => {
+ unblock();
+
+ // Remove the beforeunload listener so the document may
+ // still be salvageable in the pagehide event.
+ // See https://html.spec.whatwg.org/#unloading-documents
+ if (!blockers.length) {
+ window.removeEventListener(BeforeUnloadEvent, promptBeforeUnload);
+ }
+ };
+ };
let history = {
get action() {
@@ -217,41 +337,14 @@ export const createBrowserHistory = ({
get location() {
return location;
},
- createHref: createPath,
- block: fn => {
- let unblock = blockers.push(fn);
- if (blockers.length === 1) toggleBeforeUnloadBlocker(1);
- return () => {
- unblock();
- // Remove the beforeunload listener so the document may
- // still be salvageable in the pagehide event.
- // See https://html.spec.whatwg.org/#unloading-documents
- if (!blockers.length) toggleBeforeUnloadBlocker(0);
- };
- },
- listen: fn => listeners.push(fn),
- navigate: (to, { replace = false, state = null } = {}) =>
- handleNavigation(
- replace ? ReplaceAction : PushAction,
- createReadOnlyObject({
- ...(typeof to === 'string'
- ? parsePath(to)
- : { pathname: '/', search: '', hash: '', ...to }),
- state,
- key: createKey(),
- index: location.index + (replace ? 0 : 1)
- })
- ),
- retry: tx =>
- tx.delta
- ? history.go(tx.delta)
- : history.navigate(createPath(tx.location), {
- replace: tx.action === ReplaceAction,
- state: tx.location.state
- }),
- go: n => globalHistory.go(n),
- back: () => history.go(-1),
- forward: () => history.go(1)
+ createHref,
+ push,
+ replace,
+ go,
+ back,
+ forward,
+ listen,
+ block
};
return history;
@@ -266,49 +359,44 @@ export const createBrowserHistory = ({
export const createHashHistory = ({ window = document.defaultView } = {}) => {
let globalHistory = window.history;
- let getLocation = () => {
+ let getIndexAndLocation = () => {
let { pathname, search, hash } = parsePath(window.location.hash.substr(1));
let state = globalHistory.state || {};
- return createReadOnlyObject({
- pathname,
- search,
- hash,
- state: state.usr || null,
- key: state.key || 'default',
- index: state.idx
- });
+ return [
+ state.idx,
+ createReadOnlyObject({
+ pathname,
+ search,
+ hash,
+ state: state.usr || null,
+ key: state.key || 'default'
+ })
+ ];
};
let blockedPopTx = null;
-
- // TODO: Add reload arg
- let handleNavigation = (nextAction, nextLocation) => {
- if (__DEV__) {
- if (nextLocation.pathname.charAt(0) !== '/') {
- let arg = createPath(nextLocation);
- let fnCall = `${nextAction.toLowerCase()}("${arg}")`;
- throw new Error(
- `Relative pathnames are not supported in createHashHistory().${fnCall}`
- );
- }
- }
-
- if (nextAction === PopAction && blockedPopTx) {
- // Now that we are back at the original URL,
- // call blockers with the transition we blocked.
+ let handlePop = () => {
+ if (blockedPopTx) {
blockers.call(blockedPopTx);
blockedPopTx = null;
- } else if (blockers.length) {
- let tx = { action: nextAction, location: nextLocation };
+ } else {
+ let nextAction = PopAction;
+ let [nextIndex, nextLocation] = getIndexAndLocation();
- if (nextAction === PopAction) {
- if (nextLocation.index != null) {
- let n = location.index - nextLocation.index;
+ if (blockers.length) {
+ if (nextIndex != null) {
+ let n = index - nextIndex;
if (n) {
// Revert the POP
- tx.delta = n * -1;
- blockedPopTx = tx;
- history.go(n);
+ blockedPopTx = {
+ action: nextAction,
+ location: nextLocation,
+ retry: () => {
+ go(n * -1);
+ }
+ };
+
+ go(n);
}
} else {
// Trying to POP to a location with no index. We did not create
@@ -327,65 +415,162 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
}
}
} else {
- blockers.call(tx);
- }
- } else {
- let state = {
- usr: nextLocation.state,
- key: nextLocation.key,
- idx: nextLocation.index
- };
- // TODO: Support different "hash types"?
- let url = '#' + createPath(nextLocation);
-
- if (nextAction === PushAction) {
- // try...catch because iOS limits us to 100 pushState calls :/
- try {
- globalHistory.pushState(state, null, url);
- } catch (error) {
- // They are going to lose state here, but there is no real
- // way to warn them about it since the page will refresh...
- window.location.assign(url);
- }
- } else if (nextAction === ReplaceAction) {
- globalHistory.replaceState(state, null, url);
+ applyTx(nextAction);
}
-
- action = nextAction;
- location = getLocation();
- listeners.call({ action, location });
}
};
- let toggleBeforeUnloadBlocker = on =>
- window[`${on ? 'add' : 'remove'}EventListener`](
- BeforeUnloadEvent,
- preventUnload
- );
-
- // Initialize the index for this document.
- globalHistory.replaceState({ ...globalHistory.state, idx: 0 }, null);
-
- window.addEventListener(PopStateEvent, event => {
- handleNavigation(PopAction, getLocation());
- });
+ window.addEventListener(PopStateEvent, handlePop);
// TODO: Is this still necessary? Which browsers do
// not trigger popstate when the hash changes?
window.addEventListener(HashChangeEvent, event => {
- let nextLocation = getLocation();
+ let [, nextLocation] = getIndexAndLocation();
// Ignore extraneous hashchange events.
if (createPath(nextLocation) !== createPath(location)) {
- handleNavigation(PopAction, nextLocation);
+ handlePop();
}
});
let action = PopAction;
- let location = getLocation();
+ let [index, location] = getIndexAndLocation();
let blockers = createEvents();
let listeners = createEvents();
+ if (index == null) {
+ index = 0;
+ globalHistory.replaceState({ ...globalHistory.state, idx: index }, null);
+ }
+
+ let createHref = location => {
+ let base = document.querySelector('base');
+ let href = '';
+
+ if (base && base.getAttribute('href')) {
+ let url = window.location.href;
+ let hashIndex = url.indexOf('#');
+ href = hashIndex === -1 ? url : url.slice(0, hashIndex);
+ }
+
+ return href + '#' + createPath(location);
+ };
+
+ let getNextLocation = (to, state) =>
+ createReadOnlyObject({
+ ...(typeof to === 'string'
+ ? parsePath(to)
+ : { pathname: '/', search: '', hash: '', ...to }),
+ state,
+ key: createKey()
+ });
+
+ let getHistoryStateAndUrl = (nextLocation, index) => [
+ {
+ usr: nextLocation.state,
+ key: nextLocation.key,
+ idx: index
+ },
+ createHref(nextLocation)
+ ];
+
+ let allowTx = (action, location, retry) =>
+ !blockers.length || (blockers.call({ action, location, retry }), false);
+
+ let applyTx = nextAction => {
+ action = nextAction;
+ [index, location] = getIndexAndLocation();
+ listeners.call({ action, location });
+ };
+
+ let push = (to, state) => {
+ let nextAction = PushAction;
+ let nextLocation = getNextLocation(to, state);
+ let retry = () => push(to, state);
+
+ if (allowTx(nextAction, nextLocation, retry)) {
+ let [historyState, url] = getHistoryStateAndUrl(nextLocation, index + 1);
+
+ if (__DEV__) {
+ if (nextLocation.pathname.charAt(0) !== '/') {
+ let arg = JSON.stringify(to);
+ throw new Error(
+ `Relative pathnames are not supported in createHashHistory().push(${arg})`
+ );
+ }
+ }
+
+ // TODO: Support forced reloading
+ // try...catch because iOS limits us to 100 pushState calls :/
+ try {
+ globalHistory.pushState(historyState, null, url);
+ } catch (error) {
+ // They are going to lose state here, but there is no real
+ // way to warn them about it since the page will refresh...
+ window.location.assign(url);
+ }
+
+ applyTx(nextAction);
+ }
+ };
+
+ let replace = (to, state) => {
+ let nextAction = ReplaceAction;
+ let nextLocation = getNextLocation(to, state);
+ let retry = () => replace(to, state);
+
+ if (__DEV__) {
+ if (nextLocation.pathname.charAt(0) !== '/') {
+ let arg = JSON.stringify(to);
+ throw new Error(
+ `Relative pathnames are not supported in createHashHistory().replace(${arg})`
+ );
+ }
+ }
+
+ if (allowTx(nextAction, nextLocation, retry)) {
+ let [historyState, url] = getHistoryStateAndUrl(nextLocation, index);
+
+ // TODO: Support forced reloading
+ globalHistory.replaceState(historyState, null, url);
+
+ applyTx(nextAction);
+ }
+ };
+
+ let go = n => {
+ globalHistory.go(n);
+ };
+
+ let back = () => {
+ go(-1);
+ };
+
+ let forward = () => {
+ go(1);
+ };
+
+ let listen = fn => listeners.push(fn);
+
+ let block = fn => {
+ let unblock = blockers.push(fn);
+
+ if (blockers.length === 1) {
+ window.addEventListener(BeforeUnloadEvent, promptBeforeUnload);
+ }
+
+ return () => {
+ unblock();
+
+ // Remove the beforeunload listener so the document may
+ // still be salvageable in the pagehide event.
+ // See https://html.spec.whatwg.org/#unloading-documents
+ if (!blockers.length) {
+ window.removeEventListener(BeforeUnloadEvent, promptBeforeUnload);
+ }
+ };
+ };
+
let history = {
get action() {
return action;
@@ -393,48 +578,14 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
get location() {
return location;
},
- createHref: location => {
- let base = document.querySelector('base');
- let href = '';
- if (base && base.getAttribute('href')) {
- href = stripHash(window.location.href);
- }
- return href + '#' + createPath(location);
- },
- block: fn => {
- let unblock = blockers.push(fn);
- if (blockers.length === 1) toggleBeforeUnloadBlocker(1);
- return () => {
- unblock();
- // Remove the beforeunload listener so the document may
- // still be salvageable in the pagehide event.
- // See https://html.spec.whatwg.org/#unloading-documents
- if (!blockers.length) toggleBeforeUnloadBlocker(0);
- };
- },
- listen: fn => listeners.push(fn),
- navigate: (to, { replace = false, state = null } = {}) =>
- handleNavigation(
- replace ? ReplaceAction : PushAction,
- createReadOnlyObject({
- ...(typeof to === 'string'
- ? parsePath(to)
- : { pathname: '/', search: '', hash: '', ...to }),
- state,
- key: createKey(),
- index: location.index + (replace ? 0 : 1)
- })
- ),
- retry: tx =>
- tx.delta
- ? history.go(tx.delta)
- : history.navigate(createPath(tx.location), {
- replace: tx.action === ReplaceAction,
- state: tx.location.state
- }),
- go: n => globalHistory.go(n),
- back: () => history.go(-1),
- forward: () => history.go(1)
+ createHref,
+ push,
+ replace,
+ go,
+ back,
+ forward,
+ listen,
+ block
};
return history;
@@ -442,6 +593,13 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
// Utils
+const promptBeforeUnload = event => {
+ // Cancel the event.
+ event.preventDefault();
+ // Chrome (and legacy IE) requires returnValue to be set.
+ event.returnValue = '';
+};
+
const createKey = () =>
Math.random()
.toString(36)
@@ -455,11 +613,6 @@ const createReadOnlyObject = props =>
Object.create(null)
);
-const stripHash = url => {
- let hashIndex = url.indexOf('#');
- return hashIndex === -1 ? url : url.slice(0, hashIndex);
-};
-
const createPath = ({ pathname = '/', search = '', hash = '' }) =>
pathname + search + hash;
@@ -495,16 +648,11 @@ const createEvents = () => {
(() => {
handlers = handlers.filter(handler => handler !== fn);
}),
- call: arg => handlers.map(fn => fn && fn(arg))
+ call: arg => {
+ handlers.forEach(fn => fn && fn(arg));
+ }
};
};
const clamp = (n, lowerBound, upperBound) =>
Math.min(Math.max(n, lowerBound), upperBound);
-
-const preventUnload = event => {
- // Cancel the event.
- event.preventDefault();
- // Chrome (and legacy IE) requires returnValue to be set.
- event.returnValue = '';
-};
From 87a042e1a245942db8e08fedb602ec50eca7040d Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Thu, 7 Nov 2019 10:23:16 -0800
Subject: [PATCH 012/133] Update block fixtures
---
.gitignore | 2 +-
fixtures/block-library/index.html | 81 +++++++++++++
fixtures/block-library/index.js | 15 +++
fixtures/block-vanilla/index.html | 143 +++++++++++++++++++++++
fixtures/block-vanilla/index.js | 15 +++
fixtures/unload-history.html | 89 ---------------
fixtures/unload-plain.html | 129 ---------------------
package.json | 1 +
yarn.lock | 183 ++++++++++++++++++++++++++++--
9 files changed, 428 insertions(+), 230 deletions(-)
create mode 100644 fixtures/block-library/index.html
create mode 100644 fixtures/block-library/index.js
create mode 100644 fixtures/block-vanilla/index.html
create mode 100644 fixtures/block-vanilla/index.js
delete mode 100644 fixtures/unload-history.html
delete mode 100644 fixtures/unload-plain.html
diff --git a/.gitignore b/.gitignore
index 270814e64..c8de53aa8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
/esm/
-/fixtures/history.js
+/fixtures/*/history.js
/cjs/
/node_modules/
/umd/
diff --git a/fixtures/block-library/index.html b/fixtures/block-library/index.html
new file mode 100644
index 000000000..85e5133f8
--- /dev/null
+++ b/fixtures/block-library/index.html
@@ -0,0 +1,81 @@
+
+
+
+ Navigation Blocking (library version)
+
+
+
+
+
+ back & forward:
+
+
+
+
+ pushState & replaceState:
+ push /
+ push /one
+ push /two
+ replace /three
+
+
+ regular links:
+ /
+ /one
+
+
+
+
+
+
diff --git a/fixtures/block-library/index.js b/fixtures/block-library/index.js
new file mode 100644
index 000000000..980cac169
--- /dev/null
+++ b/fixtures/block-library/index.js
@@ -0,0 +1,15 @@
+/* eslint-disable no-console */
+const express = require('express');
+
+let port = process.env.PORT || 5000;
+let app = express();
+
+app.use(express.static(__dirname));
+
+app.get('*', (req, res) => {
+ res.sendFile(__dirname + '/index.html');
+});
+
+app.listen(port, () => {
+ console.log(`Server listening on http://localhost:${port}`);
+});
diff --git a/fixtures/block-vanilla/index.html b/fixtures/block-vanilla/index.html
new file mode 100644
index 000000000..db1b7282d
--- /dev/null
+++ b/fixtures/block-vanilla/index.html
@@ -0,0 +1,143 @@
+
+
+
+ Navigation Blocking (vanilla JS version)
+
+
+
+
+
+ back & forward:
+
+
+
+
+ pushState & replaceState:
+ push /
+ push /one
+ push /two
+ replace /three
+
+
+ regular links:
+ /
+ /one
+
+
+
+
+
diff --git a/fixtures/block-vanilla/index.js b/fixtures/block-vanilla/index.js
new file mode 100644
index 000000000..980cac169
--- /dev/null
+++ b/fixtures/block-vanilla/index.js
@@ -0,0 +1,15 @@
+/* eslint-disable no-console */
+const express = require('express');
+
+let port = process.env.PORT || 5000;
+let app = express();
+
+app.use(express.static(__dirname));
+
+app.get('*', (req, res) => {
+ res.sendFile(__dirname + '/index.html');
+});
+
+app.listen(port, () => {
+ console.log(`Server listening on http://localhost:${port}`);
+});
diff --git a/fixtures/unload-history.html b/fixtures/unload-history.html
deleted file mode 100644
index e263f95c8..000000000
--- a/fixtures/unload-history.html
+++ /dev/null
@@ -1,89 +0,0 @@
-
-
-
- Controls
-
-
-
-
-
- back & forward:
-
-
-
-
- pushState & replaceState:
- pushState(home)
- pushState(one)
- pushState(two)
- replaceState(three)
-
-
- regular link:
- home
-
-
- Test Scenarios
-
-
-
- - Click block - Click back - You should be prompted to stay
- - Click block - Click home - You should be prompted to stay
- - Click block - Close the tab - You should be prompted to stay
- - Click pushState(one) - Click block - Click back - You should still be at /unload-history-one
- - Click pushState(one) - Click block - Click pushState(two) - You should still be at /unload-history-one
- - Click pushState(one) - Click pushState(two) - Click back - Click block - Click back - You should still be at /unload-history-one
- - Click pushState(one) - Click pushState(two) - Click back - Click block - Click forward - You should still be at /unload-history-one
- - Click pushState(one) - Click pushState(two) - Click replaceState(three) - Click block - Click back - You should still be at /unload-history-three
-
-
-
-
-
-
-
-
diff --git a/fixtures/unload-plain.html b/fixtures/unload-plain.html
deleted file mode 100644
index 0314cc391..000000000
--- a/fixtures/unload-plain.html
+++ /dev/null
@@ -1,129 +0,0 @@
-
-
-
- Controls
-
-
-
-
-
- back & forward:
-
-
-
-
- regular links:
- home
- one
- two
-
-
- pushState & replaceState:
- pushState(home)
- pushState(one)
- pushState(two)
- replaceState(three)
-
-
- Test Scenarios
-
-
-
- - Click block - Click back - You should be prompted to stay
- - Click block - Click home - You should be prompted to stay
- - Click block - Click one - You should be prompted to stay
- - Click block - Close the tab - You should be prompted to stay
- - Click pushState(one) - Click block - Click back - You should still be at /unload-plain-one
- - Click pushState(one) - Click block - Click pushState(two) - You should still be at /unload-plain-one
- - Click pushState(one) - Click pushState(two) - Click back - Click block - Click back - You should still be at /unload-plain-one
- - Click pushState(one) - Click pushState(two) - Click back - Click block - Click forward - You should still be at /unload-plain-one
- - Click pushState(one) - Click pushState(two) - Click replaceState(three) - Click block - Click back - You should still be at /unload-plain-three
-
-
-
-
-
-
diff --git a/package.json b/package.json
index 92c9935ce..1f481cd4b 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"eslint": "^3.3.0",
"eslint-plugin-import": "^2.0.0",
"expect": "^21.0.0",
+ "express": "^4.17.1",
"jest-mock": "^21.0.0",
"karma": "^3.1.3",
"karma-browserstack-launcher": "^1.3.0",
diff --git a/yarn.lock b/yarn.lock
index ab0f75a6e..2bdb837c0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -833,7 +833,7 @@ abbrev@1:
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
-accepts@~1.3.4:
+accepts@~1.3.4, accepts@~1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
@@ -1011,6 +1011,11 @@ array-find-index@^1.0.1:
resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=
+array-flatten@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+ integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
+
array-includes@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d"
@@ -1244,7 +1249,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==
-body-parser@^1.16.1:
+body-parser@1.19.0, body-parser@^1.16.1:
version "1.19.0"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
@@ -1756,6 +1761,13 @@ contains-path@^0.1.0:
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=
+content-disposition@0.5.3:
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
+ integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
+ dependencies:
+ safe-buffer "5.1.2"
+
content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
@@ -1768,11 +1780,21 @@ convert-source-map@^1.1.0:
dependencies:
safe-buffer "~5.1.1"
+cookie-signature@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
+ integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
+
cookie@0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
+cookie@0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
+ integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
+
copy-concurrently@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
@@ -1995,6 +2017,11 @@ des.js@^1.0.0:
inherits "^2.0.1"
minimalistic-assert "^1.0.0"
+destroy@~1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
+ integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
+
detect-libc@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
@@ -2430,6 +2457,11 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+etag@~1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
+ integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
+
event-emitter@~0.3.5:
version "0.3.5"
resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
@@ -2543,6 +2575,42 @@ expect@^21.0.0:
jest-message-util "^21.2.1"
jest-regex-util "^21.2.0"
+express@^4.17.1:
+ version "4.17.1"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
+ integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
+ dependencies:
+ accepts "~1.3.7"
+ array-flatten "1.1.1"
+ body-parser "1.19.0"
+ content-disposition "0.5.3"
+ content-type "~1.0.4"
+ cookie "0.4.0"
+ cookie-signature "1.0.6"
+ debug "2.6.9"
+ depd "~1.1.2"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ finalhandler "~1.1.2"
+ fresh "0.5.2"
+ merge-descriptors "1.0.1"
+ methods "~1.1.2"
+ on-finished "~2.3.0"
+ parseurl "~1.3.3"
+ path-to-regexp "0.1.7"
+ proxy-addr "~2.0.5"
+ qs "6.7.0"
+ range-parser "~1.2.1"
+ safe-buffer "5.1.2"
+ send "0.17.1"
+ serve-static "1.14.1"
+ setprototypeof "1.1.1"
+ statuses "~1.5.0"
+ type-is "~1.6.18"
+ utils-merge "1.0.1"
+ vary "~1.1.2"
+
extend-shallow@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
@@ -2646,7 +2714,7 @@ fill-range@^4.0.0:
repeat-string "^1.6.1"
to-regex-range "^2.1.0"
-finalhandler@1.1.2:
+finalhandler@1.1.2, finalhandler@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
@@ -2724,6 +2792,11 @@ for-own@^0.1.4:
dependencies:
for-in "^1.0.1"
+forwarded@~0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
+ integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
+
fragment-cache@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
@@ -2731,6 +2804,11 @@ fragment-cache@^0.2.1:
dependencies:
map-cache "^0.2.2"
+fresh@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+ integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
+
from2@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
@@ -3027,6 +3105,17 @@ http-errors@1.7.2:
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
+http-errors@~1.7.2:
+ version "1.7.3"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
+ integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
+ dependencies:
+ depd "~1.1.2"
+ inherits "2.0.4"
+ setprototypeof "1.1.1"
+ statuses ">= 1.5.0 < 2"
+ toidentifier "1.0.0"
+
http-proxy@^1.13.0:
version "1.17.0"
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a"
@@ -3101,7 +3190,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -3157,6 +3246,11 @@ invert-kv@^1.0.0:
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
+ipaddr.js@1.9.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
+ integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
+
is-accessor-descriptor@^0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"
@@ -3906,11 +4000,21 @@ memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1:
errno "^0.1.3"
readable-stream "^2.0.1"
+merge-descriptors@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+ integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
+
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+methods@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+ integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
+
micromatch@^2.3.11:
version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
@@ -3969,6 +4073,11 @@ mime-types@~2.1.24:
dependencies:
mime-db "1.40.0"
+mime@1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+ integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
+
mime@^2.1.0, mime@^2.3.1:
version "2.4.4"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5"
@@ -4091,6 +4200,11 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+ms@2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
+ integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
+
ms@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
@@ -4561,6 +4675,11 @@ path-parse@^1.0.6:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
+path-to-regexp@0.1.7:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
+ integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
+
path-type@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
@@ -4671,6 +4790,14 @@ promise-inflight@^1.0.1:
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
+proxy-addr@~2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34"
+ integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==
+ dependencies:
+ forwarded "~0.1.2"
+ ipaddr.js "1.9.0"
+
prr@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
@@ -4789,7 +4916,7 @@ randomfill@^1.0.3:
randombytes "^2.0.5"
safe-buffer "^5.1.0"
-range-parser@^1.0.3, range-parser@^1.2.0:
+range-parser@^1.0.3, range-parser@^1.2.0, range-parser@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
@@ -5150,16 +5277,16 @@ rx-lite@^3.1.2:
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=
+safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
-safe-buffer@~5.1.0, safe-buffer@~5.1.1:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
- integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
@@ -5196,11 +5323,40 @@ semver@^6.3.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+send@0.17.1:
+ version "0.17.1"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
+ integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
+ dependencies:
+ debug "2.6.9"
+ depd "~1.1.2"
+ destroy "~1.0.4"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ fresh "0.5.2"
+ http-errors "~1.7.2"
+ mime "1.6.0"
+ ms "2.1.1"
+ on-finished "~2.3.0"
+ range-parser "~1.2.1"
+ statuses "~1.5.0"
+
serialize-javascript@^1.7.0, serialize-javascript@^1.9.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb"
integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==
+serve-static@1.14.1:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
+ integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
+ dependencies:
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ parseurl "~1.3.3"
+ send "0.17.1"
+
set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@@ -5792,7 +5948,7 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
-type-is@~1.6.17:
+type-is@~1.6.17, type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
@@ -5994,6 +6150,11 @@ validate-npm-package-license@^3.0.1:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
+vary@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+ integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
+
vm-browserify@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019"
From bb91b28ff94f057e45504c6833fbf25ab1613704 Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Thu, 7 Nov 2019 10:28:36 -0800
Subject: [PATCH 013/133] Skip failing tests
---
modules/__tests__/createBrowserHistory-test.js | 9 ++++++---
modules/__tests__/createHashHistory-test.js | 9 ++++++---
2 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/modules/__tests__/createBrowserHistory-test.js b/modules/__tests__/createBrowserHistory-test.js
index 3d9bf02b3..39fec6cd3 100644
--- a/modules/__tests__/createBrowserHistory-test.js
+++ b/modules/__tests__/createBrowserHistory-test.js
@@ -95,13 +95,15 @@ describe('a browser history', () => {
});
});
- describe('push with a unicode path string', () => {
+ // Currently broken in IE 11 and Edge
+ describe.skip('push with a unicode path string', () => {
it('creates a location with encoded properties', done => {
PushUnicodeLocation(history, done);
});
});
- describe('push with an encoded path string', () => {
+ // Currently broken in IE 11 and Edge
+ describe.skip('push with an encoded path string', () => {
it('creates a location with encoded pathname', done => {
PushEncodedLocation(history, done);
});
@@ -125,7 +127,8 @@ describe('a browser history', () => {
});
});
- describe('location created by encoded and unencoded pathname', () => {
+ // Currently broken in IE 11 and Edge
+ describe.skip('location created by encoded and unencoded pathname', () => {
it('produces the same location.pathname', done => {
LocationPathnameAlwaysSame(history, done);
});
diff --git a/modules/__tests__/createHashHistory-test.js b/modules/__tests__/createHashHistory-test.js
index cd71f98c9..dcd3a837d 100644
--- a/modules/__tests__/createHashHistory-test.js
+++ b/modules/__tests__/createHashHistory-test.js
@@ -99,13 +99,15 @@ describe('a hash history', () => {
});
});
- describe('push with a unicode path string', () => {
+ // Currently broken in IE 11 and Edge
+ describe.skip('push with a unicode path string', () => {
it('creates a location with decoded properties', done => {
PushUnicodeLocation(history, done);
});
});
- describe('push with an encoded path string', () => {
+ // Currently broken in IE 11 and Edge
+ describe.skip('push with an encoded path string', () => {
it('creates a location object with encoded pathname', done => {
PushEncodedLocation(history, done);
});
@@ -129,7 +131,8 @@ describe('a hash history', () => {
});
});
- describe('location created by encoded and unencoded pathname', () => {
+ // Currently broken in IE 11 and Edge
+ describe.skip('location created by encoded and unencoded pathname', () => {
it('produces the same location.pathname', done => {
LocationPathnameAlwaysSame(history, done);
});
From d9013e47c7f822d4b24af1be814557fe3d1ad492 Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Thu, 7 Nov 2019 10:34:02 -0800
Subject: [PATCH 014/133] Fix hash history test reset
---
modules/__tests__/createBrowserHistory-test.js | 4 +---
modules/__tests__/createHashHistory-test.js | 4 +---
modules/index.js | 18 +++++++++---------
3 files changed, 11 insertions(+), 15 deletions(-)
diff --git a/modules/__tests__/createBrowserHistory-test.js b/modules/__tests__/createBrowserHistory-test.js
index 39fec6cd3..b74e64f1f 100644
--- a/modules/__tests__/createBrowserHistory-test.js
+++ b/modules/__tests__/createBrowserHistory-test.js
@@ -23,9 +23,7 @@ import BlockPopWithoutListening from './TestSequences/BlockPopWithoutListening.j
describe('a browser history', () => {
let history;
beforeEach(() => {
- if (window.location.pathname !== '/') {
- window.history.replaceState(null, null, '/');
- }
+ window.history.replaceState(null, null, '/');
history = createBrowserHistory();
});
diff --git a/modules/__tests__/createHashHistory-test.js b/modules/__tests__/createHashHistory-test.js
index dcd3a837d..7fd6d6983 100644
--- a/modules/__tests__/createHashHistory-test.js
+++ b/modules/__tests__/createHashHistory-test.js
@@ -27,9 +27,7 @@ import BlockPopWithoutListening from './TestSequences/BlockPopWithoutListening.j
describe('a hash history', () => {
let history;
beforeEach(() => {
- if (window.location.hash !== '#/') {
- window.location.hash = '/';
- }
+ window.history.replaceState(null, null, '#/');
history = createHashHistory();
});
diff --git a/modules/index.js b/modules/index.js
index 4122329ad..cc2468fc5 100644
--- a/modules/index.js
+++ b/modules/index.js
@@ -488,18 +488,18 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
let nextLocation = getNextLocation(to, state);
let retry = () => push(to, state);
+ if (__DEV__) {
+ if (nextLocation.pathname.charAt(0) !== '/') {
+ let arg = JSON.stringify(to);
+ throw new Error(
+ `Relative pathnames are not supported in createHashHistory().push(${arg})`
+ );
+ }
+ }
+
if (allowTx(nextAction, nextLocation, retry)) {
let [historyState, url] = getHistoryStateAndUrl(nextLocation, index + 1);
- if (__DEV__) {
- if (nextLocation.pathname.charAt(0) !== '/') {
- let arg = JSON.stringify(to);
- throw new Error(
- `Relative pathnames are not supported in createHashHistory().push(${arg})`
- );
- }
- }
-
// TODO: Support forced reloading
// try...catch because iOS limits us to 100 pushState calls :/
try {
From dc947ecaf6cc939d62da4e73363457d3d6c7239f Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Thu, 7 Nov 2019 11:34:11 -0800
Subject: [PATCH 015/133] Remove encoding tests
---
.../LocationPathnameAlwaysSame.js | 44 -------------------
.../TestSequences/PushEncodedLocation.js | 29 ------------
.../TestSequences/PushUnicodeLocation.js | 31 -------------
.../__tests__/createBrowserHistory-test.js | 24 ----------
modules/__tests__/createHashHistory-test.js | 24 ----------
5 files changed, 152 deletions(-)
delete mode 100644 modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js
delete mode 100644 modules/__tests__/TestSequences/PushEncodedLocation.js
delete mode 100644 modules/__tests__/TestSequences/PushUnicodeLocation.js
diff --git a/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js b/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js
deleted file mode 100644
index 24aef37ad..000000000
--- a/modules/__tests__/TestSequences/LocationPathnameAlwaysSame.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps.js';
-
-export default (history, done) => {
- let steps = [
- () => {
- // encoded string
- let pathname = '/%E6%AD%B4%E5%8F%B2';
- history.replace(pathname);
- },
- ({ location }) => {
- expect(location).toMatchObject({
- pathname: '/%E6%AD%B4%E5%8F%B2'
- });
- // encoded object
- let pathname = '/%E6%AD%B4%E5%8F%B2';
- history.replace({ pathname });
- },
- ({ location }) => {
- expect(location).toMatchObject({
- pathname: '/%E6%AD%B4%E5%8F%B2'
- });
- // unencoded string
- let pathname = '/歴史';
- history.replace(pathname);
- },
- ({ location }) => {
- expect(location).toMatchObject({
- pathname: '/%E6%AD%B4%E5%8F%B2'
- });
- // unencoded object
- let pathname = '/歴史';
- history.replace({ pathname });
- },
- ({ location }) => {
- expect(location).toMatchObject({
- pathname: '/%E6%AD%B4%E5%8F%B2'
- });
- }
- ];
-
- execSteps(steps, history, done);
-};
diff --git a/modules/__tests__/TestSequences/PushEncodedLocation.js b/modules/__tests__/TestSequences/PushEncodedLocation.js
deleted file mode 100644
index b55137e34..000000000
--- a/modules/__tests__/TestSequences/PushEncodedLocation.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps.js';
-
-export default (history, done) => {
- let steps = [
- ({ location }) => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- let pathname = '/歴史';
- let search = '?%E3%82%AD%E3%83%BC=%E5%80%A4';
- let hash = '#%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5';
- history.push(pathname + search + hash);
- },
- ({ action, location }) => {
- expect(action).toBe('PUSH');
- expect(location).toMatchObject({
- pathname: '/%E6%AD%B4%E5%8F%B2',
- // pathname: '/歴史',
- search: '?%E3%82%AD%E3%83%BC=%E5%80%A4',
- hash: '#%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5'
- });
- }
- ];
-
- execSteps(steps, history, done);
-};
diff --git a/modules/__tests__/TestSequences/PushUnicodeLocation.js b/modules/__tests__/TestSequences/PushUnicodeLocation.js
deleted file mode 100644
index b9b3c0913..000000000
--- a/modules/__tests__/TestSequences/PushUnicodeLocation.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import expect from 'expect';
-
-import execSteps from './execSteps.js';
-
-export default (history, done) => {
- let steps = [
- ({ location }) => {
- expect(location).toMatchObject({
- pathname: '/'
- });
-
- let pathname = '/歴史';
- let search = '?キー=値';
- let hash = '#ハッシュ';
- history.push(pathname + search + hash);
- },
- ({ action, location }) => {
- expect(action).toBe('PUSH');
- expect(location).toMatchObject({
- pathname: '/%E6%AD%B4%E5%8F%B2',
- search: '?%E3%82%AD%E3%83%BC=%E5%80%A4',
- hash: '#%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5'
- // pathname: '/歴史',
- // search: '?キー=値',
- // hash: '#ハッシュ'
- });
- }
- ];
-
- execSteps(steps, history, done);
-};
diff --git a/modules/__tests__/createBrowserHistory-test.js b/modules/__tests__/createBrowserHistory-test.js
index b74e64f1f..0aaa882b9 100644
--- a/modules/__tests__/createBrowserHistory-test.js
+++ b/modules/__tests__/createBrowserHistory-test.js
@@ -8,12 +8,9 @@ import PushSamePath from './TestSequences/PushSamePath.js';
import PushState from './TestSequences/PushState.js';
import PushMissingPathname from './TestSequences/PushMissingPathname.js';
import PushRelativePathname from './TestSequences/PushRelativePathname.js';
-import PushUnicodeLocation from './TestSequences/PushUnicodeLocation.js';
-import PushEncodedLocation from './TestSequences/PushEncodedLocation.js';
import ReplaceNewLocation from './TestSequences/ReplaceNewLocation.js';
import ReplaceSamePath from './TestSequences/ReplaceSamePath.js';
import ReplaceState from './TestSequences/ReplaceState.js';
-import LocationPathnameAlwaysSame from './TestSequences/LocationPathnameAlwaysSame.js';
import EncodedReservedCharacters from './TestSequences/EncodedReservedCharacters.js';
import GoBack from './TestSequences/GoBack.js';
import GoForward from './TestSequences/GoForward.js';
@@ -93,20 +90,6 @@ describe('a browser history', () => {
});
});
- // Currently broken in IE 11 and Edge
- describe.skip('push with a unicode path string', () => {
- it('creates a location with encoded properties', done => {
- PushUnicodeLocation(history, done);
- });
- });
-
- // Currently broken in IE 11 and Edge
- describe.skip('push with an encoded path string', () => {
- it('creates a location with encoded pathname', done => {
- PushEncodedLocation(history, done);
- });
- });
-
describe('replace a new path', () => {
it('calls change listeners with the new location', done => {
ReplaceNewLocation(history, done);
@@ -125,13 +108,6 @@ describe('a browser history', () => {
});
});
- // Currently broken in IE 11 and Edge
- describe.skip('location created by encoded and unencoded pathname', () => {
- it('produces the same location.pathname', done => {
- LocationPathnameAlwaysSame(history, done);
- });
- });
-
describe('location created with encoded/unencoded reserved characters', () => {
it('produces different location objects', done => {
EncodedReservedCharacters(history, done);
diff --git a/modules/__tests__/createHashHistory-test.js b/modules/__tests__/createHashHistory-test.js
index 7fd6d6983..26a0f16a5 100644
--- a/modules/__tests__/createHashHistory-test.js
+++ b/modules/__tests__/createHashHistory-test.js
@@ -8,12 +8,9 @@ import PushSamePath from './TestSequences/PushSamePath.js';
import PushState from './TestSequences/PushState.js';
import PushMissingPathname from './TestSequences/PushMissingPathname.js';
import PushRelativePathnameError from './TestSequences/PushRelativePathnameError.js';
-import PushUnicodeLocation from './TestSequences/PushUnicodeLocation.js';
-import PushEncodedLocation from './TestSequences/PushEncodedLocation.js';
import ReplaceNewLocation from './TestSequences/ReplaceNewLocation.js';
import ReplaceSamePath from './TestSequences/ReplaceSamePath.js';
import ReplaceState from './TestSequences/ReplaceState.js';
-import LocationPathnameAlwaysSame from './TestSequences/LocationPathnameAlwaysSame.js';
import EncodedReservedCharacters from './TestSequences/EncodedReservedCharacters.js';
import GoBack from './TestSequences/GoBack.js';
import GoForward from './TestSequences/GoForward.js';
@@ -97,20 +94,6 @@ describe('a hash history', () => {
});
});
- // Currently broken in IE 11 and Edge
- describe.skip('push with a unicode path string', () => {
- it('creates a location with decoded properties', done => {
- PushUnicodeLocation(history, done);
- });
- });
-
- // Currently broken in IE 11 and Edge
- describe.skip('push with an encoded path string', () => {
- it('creates a location object with encoded pathname', done => {
- PushEncodedLocation(history, done);
- });
- });
-
describe('replace a new path', () => {
it('calls change listeners with the new location', done => {
ReplaceNewLocation(history, done);
@@ -129,13 +112,6 @@ describe('a hash history', () => {
});
});
- // Currently broken in IE 11 and Edge
- describe.skip('location created by encoded and unencoded pathname', () => {
- it('produces the same location.pathname', done => {
- LocationPathnameAlwaysSame(history, done);
- });
- });
-
describe('location created with encoded/unencoded reserved characters', () => {
it('produces different location objects', done => {
EncodedReservedCharacters(history, done);
From 8b3be6f14ebe53f6fdf03349f087241885a98dec Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Thu, 7 Nov 2019 12:09:28 -0800
Subject: [PATCH 016/133] Add back missing pathname support
---
.size-snapshot.json | 18 ++--
.../__tests__/createBrowserHistory-test.js | 6 +-
modules/__tests__/createHashHistory-test.js | 4 +-
modules/__tests__/createMemoryHistory-test.js | 4 +-
modules/index.js | 95 ++++++++++---------
5 files changed, 68 insertions(+), 59 deletions(-)
diff --git a/.size-snapshot.json b/.size-snapshot.json
index bd7e3f948..a0f2fef90 100644
--- a/.size-snapshot.json
+++ b/.size-snapshot.json
@@ -1,8 +1,8 @@
{
"esm/history.js": {
- "bundled": 21263,
- "minified": 7754,
- "gzipped": 2212,
+ "bundled": 21157,
+ "minified": 7812,
+ "gzipped": 2248,
"treeshaked": {
"rollup": {
"code": 43,
@@ -14,13 +14,13 @@
}
},
"umd/history.js": {
- "bundled": 23045,
- "minified": 7150,
- "gzipped": 2182
+ "bundled": 22878,
+ "minified": 7173,
+ "gzipped": 2201
},
"umd/history.min.js": {
- "bundled": 20565,
- "minified": 5940,
- "gzipped": 1883
+ "bundled": 20184,
+ "minified": 5778,
+ "gzipped": 1867
}
}
diff --git a/modules/__tests__/createBrowserHistory-test.js b/modules/__tests__/createBrowserHistory-test.js
index 0aaa882b9..2e781ef24 100644
--- a/modules/__tests__/createBrowserHistory-test.js
+++ b/modules/__tests__/createBrowserHistory-test.js
@@ -78,14 +78,14 @@ describe('a browser history', () => {
});
});
- describe.skip('push with no pathname', () => {
- it('calls change listeners with the normalized location', done => {
+ describe('push with no pathname', () => {
+ it('reuses the current location pathname', done => {
PushMissingPathname(history, done);
});
});
describe('push with a relative pathname', () => {
- it('calls change listeners with the normalized location', done => {
+ it('normalizes the pathname relative to the current location', done => {
PushRelativePathname(history, done);
});
});
diff --git a/modules/__tests__/createHashHistory-test.js b/modules/__tests__/createHashHistory-test.js
index 26a0f16a5..312b95b18 100644
--- a/modules/__tests__/createHashHistory-test.js
+++ b/modules/__tests__/createHashHistory-test.js
@@ -82,8 +82,8 @@ describe('a hash history', () => {
});
});
- describe.skip('push with no pathname', () => {
- it('calls change listeners with the normalized location', done => {
+ describe('push with no pathname', () => {
+ it('reuses the current location pathname', done => {
PushMissingPathname(history, done);
});
});
diff --git a/modules/__tests__/createMemoryHistory-test.js b/modules/__tests__/createMemoryHistory-test.js
index 25d69e313..5b0f041f4 100644
--- a/modules/__tests__/createMemoryHistory-test.js
+++ b/modules/__tests__/createMemoryHistory-test.js
@@ -77,8 +77,8 @@ describe('a memory history', () => {
});
});
- describe.skip('push with no pathname', () => {
- it('calls change listeners with the normalized location', done => {
+ describe('push with no pathname', () => {
+ it('reuses the current location pathname', done => {
PushMissingPathname(history, done);
});
});
diff --git a/modules/index.js b/modules/index.js
index cc2468fc5..00cd424f4 100644
--- a/modules/index.js
+++ b/modules/index.js
@@ -19,21 +19,27 @@ export const createMemoryHistory = ({
initialEntries = ['/'],
initialIndex = 0
} = {}) => {
- let createLocation = ({
- pathname = '/',
- search = '',
- hash = '',
- state = null,
- // Auto-assign keys to entries that don't already have them.
- key = createKey()
- }) => createReadOnlyObject({ pathname, search, hash, state, key });
-
- let entries = initialEntries.map((entry, index) =>
- createLocation({
- ...(typeof entry === 'string' ? parsePath(entry) : entry),
- index
- })
- );
+ let entries = initialEntries.map(entry => {
+ let location = createReadOnlyObject({
+ pathname: '/',
+ search: '',
+ hash: '',
+ state: null,
+ key: createKey(),
+ ...(typeof entry === 'string' ? parsePath(entry) : entry)
+ });
+
+ if (__DEV__) {
+ if (location.pathname.charAt(0) !== '/') {
+ let arg = JSON.stringify(entry);
+ throw new Error(
+ `Relative pathnames are not supported in createMemoryHistory({ initialEntries }) (invalid entry: ${arg})`
+ );
+ }
+ }
+
+ return location;
+ });
let index = clamp(initialIndex, 0, entries.length - 1);
let action = PopAction;
@@ -43,11 +49,10 @@ export const createMemoryHistory = ({
let createHref = createPath;
- let getNextLocation = (to, state) =>
- createLocation({
- ...(typeof to === 'string'
- ? parsePath(to)
- : { pathname: '/', search: '', hash: '', ...to }),
+ let getNextLocation = (to, state = null) =>
+ createReadOnlyObject({
+ ...location,
+ ...(typeof to === 'string' ? parsePath(to) : to),
state,
key: createKey()
});
@@ -233,11 +238,10 @@ export const createBrowserHistory = ({
let createHref = createPath;
- let getNextLocation = (to, state) =>
+ let getNextLocation = (to, state = null) =>
createReadOnlyObject({
- ...(typeof to === 'string'
- ? parsePath(to)
- : { pathname: '/', search: '', hash: '', ...to }),
+ ...location,
+ ...(typeof to === 'string' ? parsePath(to) : to),
state,
key: createKey()
});
@@ -360,7 +364,9 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
let globalHistory = window.history;
let getIndexAndLocation = () => {
- let { pathname, search, hash } = parsePath(window.location.hash.substr(1));
+ let { pathname = '/', search = '', hash = '' } = parsePath(
+ window.location.hash.substr(1)
+ );
let state = globalHistory.state || {};
return [
state.idx,
@@ -456,11 +462,10 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
return href + '#' + createPath(location);
};
- let getNextLocation = (to, state) =>
+ let getNextLocation = (to, state = null) =>
createReadOnlyObject({
- ...(typeof to === 'string'
- ? parsePath(to)
- : { pathname: '/', search: '', hash: '', ...to }),
+ ...location,
+ ...(typeof to === 'string' ? parsePath(to) : to),
state,
key: createKey()
});
@@ -616,24 +621,28 @@ const createReadOnlyObject = props =>
const createPath = ({ pathname = '/', search = '', hash = '' }) =>
pathname + search + hash;
-const parsePath = path => {
- let pathname = path || '/';
- let search = '';
- let hash = '';
+let parsePath = path => {
+ let pieces = {};
- let hashIndex = pathname.indexOf('#');
- if (hashIndex >= 0) {
- hash = pathname.substr(hashIndex);
- pathname = pathname.substr(0, hashIndex);
- }
+ if (path) {
+ let hashIndex = path.indexOf('#');
+ if (hashIndex >= 0) {
+ pieces.hash = path.substr(hashIndex);
+ path = path.substr(0, hashIndex);
+ }
+
+ let searchIndex = path.indexOf('?');
+ if (searchIndex >= 0) {
+ pieces.search = path.substr(searchIndex);
+ path = path.substr(0, searchIndex);
+ }
- let searchIndex = pathname.indexOf('?');
- if (searchIndex >= 0) {
- search = pathname.substr(searchIndex);
- pathname = pathname.substr(0, searchIndex);
+ if (path) {
+ pieces.pathname = path;
+ }
}
- return { pathname, search, hash };
+ return pieces;
};
const createEvents = () => {
From 2acce397b210b5699915a3a1ad35c53d11c26d77 Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Thu, 7 Nov 2019 12:20:17 -0800
Subject: [PATCH 017/133] Remove Safari from testing, hopefully temporary
---
karma.conf.js | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/karma.conf.js b/karma.conf.js
index 192885e95..210ac1565 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -37,14 +37,17 @@ module.exports = function(config) {
os_version: '10',
browser: 'IE',
browser_version: '11.0'
- },
- BS_Safari: {
- base: 'BrowserStack',
- os: 'OS X',
- os_version: 'Mojave',
- browser: 'Safari',
- browser_version: '12.1'
}
+ // Safari throws an error if you use replaceState more
+ // than 100 times in 30 seconds :/
+ // See https://travis-ci.com/ReactTraining/history/jobs/254197476
+ // BS_Safari: {
+ // base: 'BrowserStack',
+ // os: 'OS X',
+ // os_version: 'Mojave',
+ // browser: 'Safari',
+ // browser_version: '12.1'
+ // }
// BS_iPhoneX: {
// base: 'BrowserStack',
// device: 'iPhone X',
From 50997b33bebc80dcf8e8f4fbb67ee93b4d85f942 Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Thu, 7 Nov 2019 12:32:15 -0800
Subject: [PATCH 018/133] Small tweaks
---
modules/__tests__/createBrowserHistory-test.js | 6 ++----
modules/__tests__/createHashHistory-test.js | 6 ++----
modules/__tests__/createMemoryHistory-test.js | 6 ++----
3 files changed, 6 insertions(+), 12 deletions(-)
diff --git a/modules/__tests__/createBrowserHistory-test.js b/modules/__tests__/createBrowserHistory-test.js
index 2e781ef24..f2d7ec037 100644
--- a/modules/__tests__/createBrowserHistory-test.js
+++ b/modules/__tests__/createBrowserHistory-test.js
@@ -35,16 +35,14 @@ describe('a browser history', () => {
});
it('does not encode the generated path', () => {
- // encoded
const encodedHref = history.createHref({
pathname: '/%23abc'
});
- // unencoded
+ expect(encodedHref).toEqual('/%23abc');
+
const unencodedHref = history.createHref({
pathname: '/#abc'
});
-
- expect(encodedHref).toEqual('/%23abc');
expect(unencodedHref).toEqual('/#abc');
});
diff --git a/modules/__tests__/createHashHistory-test.js b/modules/__tests__/createHashHistory-test.js
index 312b95b18..958970e5d 100644
--- a/modules/__tests__/createHashHistory-test.js
+++ b/modules/__tests__/createHashHistory-test.js
@@ -39,16 +39,14 @@ describe('a hash history', () => {
});
it('does not encode the generated path', () => {
- // encoded
const encodedHref = history.createHref({
pathname: '/%23abc'
});
- // unencoded
+ expect(encodedHref).toEqual('#/%23abc');
+
const unencodedHref = history.createHref({
pathname: '/#abc'
});
-
- expect(encodedHref).toEqual('#/%23abc');
expect(unencodedHref).toEqual('#/#abc');
});
diff --git a/modules/__tests__/createMemoryHistory-test.js b/modules/__tests__/createMemoryHistory-test.js
index 5b0f041f4..b5edb965e 100644
--- a/modules/__tests__/createMemoryHistory-test.js
+++ b/modules/__tests__/createMemoryHistory-test.js
@@ -34,16 +34,14 @@ describe('a memory history', () => {
});
it('does not encode the generated path', () => {
- // encoded
const encodedHref = history.createHref({
pathname: '/%23abc'
});
- // unencoded
+ expect(encodedHref).toEqual('/%23abc');
+
const unencodedHref = history.createHref({
pathname: '/#abc'
});
-
- expect(encodedHref).toEqual('/%23abc');
expect(unencodedHref).toEqual('/#abc');
});
From c647e9c3a3eb544eb019666d8368742f412e303e Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Thu, 7 Nov 2019 16:35:18 -0800
Subject: [PATCH 019/133] Rename to avoid collision with browser globals
---
modules/index.js | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/modules/index.js b/modules/index.js
index 00cd424f4..60a833ec9 100644
--- a/modules/index.js
+++ b/modules/index.js
@@ -2,9 +2,9 @@ const PopAction = 'POP';
const PushAction = 'PUSH';
const ReplaceAction = 'REPLACE';
-const BeforeUnloadEvent = 'beforeunload';
-const PopStateEvent = 'popstate';
-const HashChangeEvent = 'hashchange';
+const BeforeUnloadEventType = 'beforeunload';
+const PopStateEventType = 'popstate';
+const HashChangeEventType = 'hashchange';
// There's some duplication in this code, but only one create* method
// should ever be used in a given web page, so it's best for minifying
@@ -224,7 +224,7 @@ export const createBrowserHistory = ({
}
};
- window.addEventListener(PopStateEvent, handlePop);
+ window.addEventListener(PopStateEventType, handlePop);
let action = PopAction;
let [index, location] = getIndexAndLocation();
@@ -319,7 +319,7 @@ export const createBrowserHistory = ({
let unblock = blockers.push(fn);
if (blockers.length === 1) {
- window.addEventListener(BeforeUnloadEvent, promptBeforeUnload);
+ window.addEventListener(BeforeUnloadEventType, promptBeforeUnload);
}
return () => {
@@ -329,7 +329,7 @@ export const createBrowserHistory = ({
// still be salvageable in the pagehide event.
// See https://html.spec.whatwg.org/#unloading-documents
if (!blockers.length) {
- window.removeEventListener(BeforeUnloadEvent, promptBeforeUnload);
+ window.removeEventListener(BeforeUnloadEventType, promptBeforeUnload);
}
};
};
@@ -426,11 +426,11 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
}
};
- window.addEventListener(PopStateEvent, handlePop);
+ window.addEventListener(PopStateEventType, handlePop);
// TODO: Is this still necessary? Which browsers do
// not trigger popstate when the hash changes?
- window.addEventListener(HashChangeEvent, event => {
+ window.addEventListener(HashChangeEventType, event => {
let [, nextLocation] = getIndexAndLocation();
// Ignore extraneous hashchange events.
@@ -561,7 +561,7 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
let unblock = blockers.push(fn);
if (blockers.length === 1) {
- window.addEventListener(BeforeUnloadEvent, promptBeforeUnload);
+ window.addEventListener(BeforeUnloadEventType, promptBeforeUnload);
}
return () => {
@@ -571,7 +571,7 @@ export const createHashHistory = ({ window = document.defaultView } = {}) => {
// still be salvageable in the pagehide event.
// See https://html.spec.whatwg.org/#unloading-documents
if (!blockers.length) {
- window.removeEventListener(BeforeUnloadEvent, promptBeforeUnload);
+ window.removeEventListener(BeforeUnloadEventType, promptBeforeUnload);
}
};
};
From 02b43fd9dc43953c1131a06b432a7220524b94b8 Mon Sep 17 00:00:00 2001
From: Michael Jackson
Date: Thu, 7 Nov 2019 20:04:23 -0800
Subject: [PATCH 020/133] Update docs
---
.size-snapshot.json | 10 ++++----
docs/Blocking.md | 49 +++++++++++++++++----------------------
docs/GettingStarted.md | 23 +++++++++++--------
docs/Misc.md | 51 -----------------------------------------
docs/Navigation.md | 9 +++-----
docs/README.md | 10 +++-----
docs/images/block.png | Bin 0 -> 158010 bytes
7 files changed, 46 insertions(+), 106 deletions(-)
delete mode 100644 docs/Misc.md
create mode 100644 docs/images/block.png
diff --git a/.size-snapshot.json b/.size-snapshot.json
index a0f2fef90..44b07fbf1 100644
--- a/.size-snapshot.json
+++ b/.size-snapshot.json
@@ -1,8 +1,8 @@
{
"esm/history.js": {
- "bundled": 21157,
- "minified": 7812,
- "gzipped": 2248,
+ "bundled": 21197,
+ "minified": 7852,
+ "gzipped": 2251,
"treeshaked": {
"rollup": {
"code": 43,
@@ -14,12 +14,12 @@
}
},
"umd/history.js": {
- "bundled": 22878,
+ "bundled": 22918,
"minified": 7173,
"gzipped": 2201
},
"umd/history.min.js": {
- "bundled": 20184,
+ "bundled": 20224,
"minified": 5778,
"gzipped": 1867
}
diff --git a/docs/Blocking.md b/docs/Blocking.md
index b3c1aabaa..d1db8c260 100644
--- a/docs/Blocking.md
+++ b/docs/Blocking.md
@@ -1,38 +1,31 @@
# Blocking Transitions
-`history` lets you register a prompt message that will be shown to the user before location listeners are notified. This allows you to make sure the user wants to leave the current page before they navigate away.
+`history` lets you block navigation away from the current page so you can make sure e.g. the user wants to leave before they go to another page and possibly lose some changes they've made in the current page.
```js
-// Register a simple prompt message that will be shown the
-// user before they navigate away from the current page.
-const unblock = history.block('Are you sure you want to leave this page?');
-
-// Or use a function that returns the message when it's needed.
-history.block((location, action) => {
- // The location and action arguments indicate the location
- // we're transitioning to and how we're getting there.
-
- // A common use case is to prevent the user from leaving the
- // page if there's a form they haven't submitted yet.
- if (input.value !== '') return 'Are you sure you want to leave this page?';
+// Block navigation and register a callback that
+// fires when a navigation attempt is blocked.
+let unblock = history.block(tx => {
+ // Navigation was blocked! Let's show a confirmation dialog
+ // so the user can decide if they actually want to navigate
+ // away and discard changes they've made in the current page.
+ let url = tx.location.pathnanme;
+ if (window.confirm(`Are you sure you want to go to ${url}?`)) {
+ // Unblock the navigation.
+ unblock();
+
+ // Retry the transition.
+ tx.retry();
+ }
});
-
-// To stop blocking transitions, call the function returned from block().
-unblock();
```
-**Note:** You'll need to provide a `getUserConfirmation` function to use this feature with `createMemoryHistory` (see below).
+This example uses `window.confirm`, but you could also use your own custom confirm dialog if you'd rather.
-## Customizing the Confirm Dialog
+## Caveats
-By default, [`window.confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm) is used to show prompt messages to the user. If you need to override this behavior (or if you're using `createMemoryHistory`, which doesn't assume a DOM environment), provide a `getUserConfirmation` function when you create your history object.
+`history.block` will call your callback for all in-page navigation attempts, but for navigation that reloads the page (e.g. the refresh button or a link that doesn't use `history.push`) it registers [a `beforeunload` handler](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event) to prevent the navigation. In modern browsers you are not able to customize this dialog. Instead, you'll see something like this (Chrome):
-```js
-const history = createHistory({
- getUserConfirmation(message, callback) {
- // Show some custom dialog to the user and call
- // callback(true) to continue the transiton, or
- // callback(false) to abort it.
- }
-});
-```
+
+
+One subtle side effect of registering a `beforeunload` handler is that the page will not be [salvageable](https://html.spec.whatwg.org/#unloading-documents) in [the `pagehide` event](https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event).
diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md
index f7e965685..c0e78d4ce 100644
--- a/docs/GettingStarted.md
+++ b/docs/GettingStarted.md
@@ -5,7 +5,7 @@ The history library is a lightweight layer over browsers' built-in [History](htt
We provide 3 different methods for creating a `history` object, depending on the needs of your environment:
- `createBrowserHistory` is for use in modern web browsers that support the [HTML5 history API](http://diveintohtml5.info/history.html) (see [cross-browser compatibility](http://caniuse.com/#feat=history))
-- `createHashHistory` is for use in situations where you want to store the location in the [hash](https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/hash) of the current URL to avoid sending it to the server when the page reloads
+- `createHashHistory` is for use in situations where you want to store the location in the [hash](https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/hash) portion of the current URL to avoid sending it to the server when the page reloads
- `createMemoryHistory` is used as a reference implementation and may also be used in non-DOM environments, like [React Native](https://facebook.github.io/react-native/) or tests
Depending on the method you want to use to keep track of history, you'll `import` (or `require`, if you're using CommonJS) only one of these methods.
@@ -17,13 +17,13 @@ Basic usage looks like this:
```js
import { createBrowserHistory } from 'history';
-const history = createBrowserHistory();
+let history = createBrowserHistory();
// Get the current location.
-const location = history.location;
+let location = history.location;
// Listen for changes to the current location.
-const unlisten = history.listen((location, action) => {
+let unlisten = history.listen((location, action) => {
// location is an object like window.location
console.log(action, location.pathname, location.state);
});
@@ -78,7 +78,7 @@ Additionally, `createMemoryHistory` provides `history.index` and `history.entrie
You can listen for changes to the current location using `history.listen`:
```js
-history.listen((location, action) => {
+history.listen(({ action, location }) => {
console.log(
`The current URL is ${location.pathname}${location.search}${location.hash}`
);
@@ -94,17 +94,22 @@ The `location` object implements a subset of [the `window.location` interface](h
Locations may also have the following properties:
-- `location.state` - Some extra state for this location that does not reside in the URL (supported in `createBrowserHistory` and `createMemoryHistory`)
-- `location.key` - A unique string representing this location (supported in `createBrowserHistory` and `createMemoryHistory`)
+- `location.state` - Some extra state for this location that does not reside in the URL
+- `location.key` - A unique string representing this location
The `action` is one of `PUSH`, `REPLACE`, or `POP` depending on how the user got to the current URL.
+- A `PUSH` means one more entry was added to the history stack
+- A `REPLACE` means the current entry in the stack was replaced
+- A `POP` means we went to some other location already in the stack
+
## Cleaning up
When you attach a listener using `history.listen`, it returns a function that can be used to remove the listener, which can then be invoked in cleanup logic:
```js
-const unlisten = history.listen(myListener);
-// ...
+let unlisten = history.listen(myListener);
+
+// Later, when you're done...
unlisten();
```
diff --git a/docs/Misc.md b/docs/Misc.md
deleted file mode 100644
index cff65e471..000000000
--- a/docs/Misc.md
+++ /dev/null
@@ -1,51 +0,0 @@
-## Using a Base URL
-
-If all the URLs in your app are relative to some other "base" URL, use the `basename` option. This option transparently adds the given string to the front of all URLs you use.
-
-```js
-const history = createHistory({
- basename: '/the/base'
-});
-
-history.listen(location => {
- console.log(location.pathname); // /home
-});
-
-history.push('/home'); // URL is now /the/base/home
-```
-
-**Note:** `basename` is not supported in `createMemoryHistory`.
-
-## Forcing Full Page Refreshes in createBrowserHistory
-
-By default `createBrowserHistory` uses HTML5 `pushState` and `replaceState` to prevent reloading the entire page from the server while navigating around. If instead you would like to reload as the URL changes, use the `forceRefresh` option.
-
-```js
-const history = createBrowserHistory({
- forceRefresh: true
-});
-```
-
-## Modifying the Hash Type in createHashHistory
-
-By default `createHashHistory` uses a leading slash in hash-based URLs. You can use the `hashType` option to use a different hash formatting.
-
-```js
-const history = createHashHistory({
- hashType: 'slash' // the default
-});
-
-history.push('/home'); // window.location.hash is #/home
-
-const history = createHashHistory({
- hashType: 'noslash' // Omit the leading slash
-});
-
-history.push('/home'); // window.location.hash is #home
-
-const history = createHashHistory({
- hashType: 'hashbang' // Google's legacy AJAX URL format
-});
-
-history.push('/home'); // window.location.hash is #!/home
-```
diff --git a/docs/Navigation.md b/docs/Navigation.md
index bc3804eda..f8fbcf62e 100644
--- a/docs/Navigation.md
+++ b/docs/Navigation.md
@@ -5,9 +5,8 @@
- `history.push(path, [state])`
- `history.replace(path, [state])`
- `history.go(n)`
-- `history.goBack()`
-- `history.goForward()`
-- `history.canGo(n)` (only in `createMemoryHistory`)
+- `history.back()`
+- `history.forward()`
When using `push` or `replace` you can either specify both the URL path and state as separate arguments or include everything in a single location-like object as the first argument.
@@ -33,7 +32,5 @@ history.push({
// Go back to the previous history entry. The following
// two lines are synonymous.
history.go(-1);
-history.goBack();
+history.back();
```
-
-**Note:** Location state is only supported in `createBrowserHistory` and `createMemoryHistory`.
diff --git a/docs/README.md b/docs/README.md
index e9c6f955e..6169ea83e 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,11 +1,7 @@
-Welcome to the history docs! The library isn't huge, so there are really just a few files here for you to browse.
+Welcome to the history docs! The library is very small, so there are really just a few files here for you to browse.
-If this is your first time here, I'd recommend you first [install](Installation.md) the library and then read both:
+If this is your first time here, we'd recommend you first [install](Installation.md) the library and then read:
- [Getting Started](GettingStarted.md)
- [Navigation](Navigation.md)
-
-For more advanced usage, check out:
-
-- [Blocking](Blocking.md) - Details about how to block navigation attempts
-- [Misc](Misc.md) - Miscellaneous topics about several API details
+- [Blocking Navigation](Blocking.md)
diff --git a/docs/images/block.png b/docs/images/block.png
new file mode 100644
index 0000000000000000000000000000000000000000..6dd81e8b7c2ca3b1a3176a26bc76d7b74a0106c5
GIT binary patch
literal 158010
zcmeEthgVZuw=Xu(L-Xj-&|^WdAV}|tiU>$kQ0YWz0TGZ68ijtg?)}Di_l@!XfJ+7>?CiDnUUSXzo4+~tn_I@Z{C|l2!NtYJ
zudjFAl#6S>BNx}+%Llo^Bfl%FCAhfw^jx*IZ|Q4mpSb1i>FoN*iHl3`O@cX(g;~q-
zbZb+6m*)rd4pqPD$%#K~wSUjI7m+dhkKBwnbhV+g@W%@`$e*{&eeX8t4DM~VDoD~P
z*v6c?&{|>fGh^fIfdN0<_QbXTag0f7R|C$sR|jx&rCFulNyaSgjgdVcbLM(j;n^>r
zi}Y-{o}(*Ha0OE6-sd%7FzzeD6@cx89p=}qys~rU95n!;V{#&%y64LD$3Q1Z%iEk{ct*34vm4#>?aQ%~9F@jTS$F(8g-ULv
z9^oxeZZHh;urD6rE;@hQ%O3N{@$G&mj`Cq9@0r+)harz1CQ6^W>LA;eB-kH*>ByV%
z0udYA6F1-XkXL4@cNn5q$42Yqt(=
zVKF63IlRrL9ml_Gde=INxFdIPy(
z`4xEunPsN8S`Xel*f?cv;x6T$|KPZj32rS$eXxD1#ybpI6G>ov@7)_1%^5gi{c^R@
z*a82uXz1z>;h#TGTk!ht(a373&sm6lc#L1i;MQCmZxFZ`d~d@Yf77**$&r~IeJ%47
z(xx@gJ;(~*M8nie7w#8Kr68K8Z_+b$3&w26SzNb5bo=tZ4I1`u?|G2UG%)|P@Ar^k
zXlN)8(9<}y_h&vI*U}8mSHO?#_w(>Gg=45!KW^pLCqVXo+$&DMF1*j@KEK#~t+n5x
zA^F#?s0#5+Upb<;&+I{x&SR~%--efYr1v|0+$+uFc13CEpmONyB;+TLVrb~3km`ZX
zP=iU4vVGU$JeK6vztR)KCn*|x;txlG
z4RO=wKdyg|HPX}CxaiHL7M7}`9%J3SFN-JW73#WNe3-~b=da@G+*>-sE{e%7jjtAU
z3%kf!y_M0)=~DNV8hI(9O~@SVGTu}hiQZ$#>tLVwo_9Xn>!+%;cw3$0kme7uoQ9>J
zH+~%BVDs;%S;*4%NKNylzN-9vOzf4^3<|{!6GXf)pBbf`W$h0+fPS_;g*fQ@;(YCg
zNt4MVXO9#fQaI%F@@Cz=x{x}3x}x9Fi6d{WpY2Xx*oSRWv={JF_B!OHYm!8oo;V-XaQawGtKPDnhQ6*|lKyzsw&SJ31H)-zSB<3%n$OJM+z{A!u;H|E
zbc0JL$=-mH-Eez`iqM8*xm2@hkpUq<_4BI4#*Yd$e17VR3Wsj_DIE
zwkjLOu2$B5TC8kt{ZR{1d81OFSxDm*e;bu6V2%84TwwgU(=IJsy}IHrTMgSN8Iyo?
zkI1z>N8I?HanNJ`lS{}7ctAcn+97d%h17f`}=%u+?ZM<3O`h*
z+OnX0LS#tvF~2w81-=kD7nKEtK9x;HLpf>rA99+GLoE&O{S9RlqRS)7F+phzub0CI
zX$PlXH=Y*JwUMB7x@6sc9ozMF%q6K?DKolBdgk$r&CIc;j;3wH$48~&4xLpnQhuMR
zxG3&=S^mBx+L6($b=ouK(--3}hrSdUsifFlUR6<030I6u&Q;Lx7;x)U6qF;#A>2cT
zs_-u!{ONe|mxa4m!?bW)Or!H$!FLNOi{QjBiMJE~XfsM&P26{ncCY2$nR}H#)-26(
zgL8|1m|K@xZVy`wMU;MU=)LbVnrOf5cCpab?db?DEv?%2`?#Qghrh{cu`U4>#qwdTu%b2G
zHIy0y6oLLH>l%BWDZa^^Twg^@;_v{0E9u$TGx0qp_>t&iQRDKzE_Pjrfs~#9sqp$(
z{GXdy_^dws&ut@-G$(;ArSIQFSNr8~zf;G5!jtU{8HA|G!!-$g+;
z!9hV2fqVfR^ue)M@$SRjki}za8XGQuoxGs_P^XVSPdJH?OQJIZedKlE{PxW&CqhkFoD%PboBt!ZbSNO6p;FhI8G_{?eCfhq8|f
zzv(>^AX#JdhmlpHuhj5K%h<8RPjT{UpJ8*h4=t8j`#Rq^ob5~tgmp+7r4GC^zGhTs5-d%M_7#~sZNGk@lkmw-PS@sA$cLi|MRFut
z51X}LW3^R%Rkn;-Rs?I`kzoD2*#=+gqE4?yR%egAD~S&fc5~ir`Q&6h_Zs!K>ho_d
zk0IG}Vt)}YO8murY~ZgN><4)t&se+Itc5!1Y|C(@*7>M&oFHXb3cS)iYA*Gak3_o@
zVO&x0Ly}s)d(9TBhcV833i1e{XZyaAynSTwUBVg98UuD-N#76OtFD9Fu|$=4*QAzK
z57uFFeUlFT6y+1GQ6hZs630~L9Nc`$>b51t^$@zH96BEIW5?^U>p&09X`)S8T1qBK
z8dq~Fu#uMVrQxKhBctP)=_p@5IMN+10I&rI~F
zf7izSj+6D;Z31S3NfHv8rl>67>!!ed+*4a7&ef#^yZM%h{+ACrq6}xK1%OO
zuWsl5bhW?%VCA__4Jn!M6zf?r>GuFwx5KS_ZhLrEEcg`6y(4}eh$;}r$nqdy{%2NN
zzvYbOkn$Vtmdnyn?D@zR6?M-)JYdxc0n2l%b))+Nj*p!gyM)Vrn!i!A$gV)Eaoo(a
z14tP>4+Ld24N-yF;lwRgEUPC#AuMrz+){+X7Lh}==ltZdlBS|U0ICKuvlS&k*x`_5k?_|F+*Mck8Wtm!bz2ugf$(h$XkG@}<
zgg@A_yme-ROR+A+yRuX5kKZ1uD<{9ma$PCEYx2jQuNXYJ>$PTEy={k1sFXw4_#VROw0nVN)O^Sq#ftdMSdkD-8@-WRBMuc)AxgAF&FAIz^%+eJM@f)J^)jT
zLyz?=eYm)epV|H0qi-rn=Hl9u?s~_<*TTq9-NDmc=7FQ9y^~C!`(yBIE-uYLb?~FR
zlkbBQf$onyeAENAPW^p`I{10_uB{~8Ydr*+E3*Y~lytZYC)fJ}gbjHfqJR!&V#P4=?9ti1ds@Qh17K_0#j
z0xx;^oc?E$f6sH>$;ZLl^|7z3r^kuic^}w&`uS>|I<_`Y{{
zZ@`@s0{@)*=New&pTItuOeDZzKv
z=2{|?5W<^y#*MmRa-{9ZncK%1Q+qlUY+o7^HXj)o6L=RDe|>-QT>aJFKUO{jExH<{
zWVxIWdCmRl`TzJ9>N@$^rdiBzaL?QS*C{R^-A+K{jF}=ggZl>{Hi@1h?1{!>v{J4?6lT(@5d;{T$08owwNT}71FbMb_oGj*p0x#(Uuu>5#R`jLw={L%xjXOQ*j<6jHAT#zxc23
zv*w?@=A08A@A00y2H-H`JcBR$E-AhD37sa_IpE6K9EU+Kv!PT5@ui$MQ!!tFFig=w-CH;l%sAhpatrli8XIUA@&
zwQJ1FrO>D4Z%8IhX@BvH#c_L|^fC0L>dni0{>7bTNbpQ3dw#1lGIY4z09x#Et@&1Z
z@CKo(tL~zfpKB;w=2jO*{SC(fO%t68LhwtX8{8?7bsH)`XBm7`^st&G$N9JXscW~8
zr4=bl??$h9vcB4aOEF=2(xBU2E4UeJlp1=Ll|Uthqd7EdKdoEMx{Bl%E?`uVZ4Or*
zc+k$Hv@ACJ_${aH{Ho;0LZ&fUqqK6kg(vr$-S@6eZEN=Y4RR@*^Cuh6vC*Zfx9x-j
zZ-5St0`t`h?(|Tf=R7NxYTk*}-iP<*(1OoKa|`6|hBT`g-M6GU3`D}PxBibHeh%{3
zZ?m*=49t7Zi|ZP9O9&(D+h*4Rv{>RN0F*tEw!u%l6&G?Hoq_(wkp#5~+yoke6~?)W
zi>2b}y6H=@-Eka6aQopNJB)o#jo)(TS??>4Dfi%qenTk_SL`v{zJJXQ@H?h15t`h3
zF%4rIsK0TpXroKYXtmTZ&t~xj6kNfLTegGmmL-=x_ovXKmJ~M6-$hd36x@hjiD27o
z-o?d2P}5pCacoTRueCEfZ&q+BZon2_mmn|WHFuug?)6UTNK^_b|M?DMBnVMGyEEqa
z_37nT6aEXsD80=#68D+3)z-`F5xlbAFW>?yOF2vVOYO7;I4x-l0vrWW*iuZ-PH4KQ
zH%9~_nI6{{jyy+Ag=4t8bUTUg2>4GBaZDEO+KZD?U>3{c)~uv>!D{hs+5-ptd11OX
zB2LuS&n7`ajodYV@~Xt_e)OZ4-2mPBxN^p7ES^)y`3`>r?oOlb`!R!Nqkg;H<#aGk
zlVnlXIYZK!yJ;Gc|Oiff~xb6RUtB
z?2MCGBIE>j#r%Fv5Atl+J581u?V66T`jzF@XTKzbzMYn$cjJD28vo#?F=7NxZ-RSn
z6f=zup+{Z|R$gvadYNaVyCn0s&JkB|^nIx3%)Ig%ZCor{g(=HCNSl&)$KGC|A^hG%
z#eD*`d+dnl?+=CL(-wYvJzAA?#nlm`<3AQ1>S>6RSvi{A++1~ab
zI=9h!k*y41XBz64%A!34t+sS_{HdJ%;k9C~U&TSsej4YUPQ=3ks~;5@3W)3L=FMbB
z!h+%rD&-OTQB8G1!@lPn&OM(UoWj}xVqvkK@63mTQ1=JZp
zYjmt%L_1NPNa^Zl~o$zn-pSmzk^SRkT{VRRZ*pK(t
zM#qShG-b4kxZ`iPw89x|8z?CUv?Y4iRScsGfq7OzKNs?hStnu!l}aVi@ir^Z8yJ=v
zlqw?)_W_cEiXQ=()f|CTot-mHA}O-99r(0|E1cvKOhWWnZ&NlXyZfk1_VAKa{k){;
zdtCLe(VVykBeZXOmn6lVR?WlMoaJ}&`6AHN@JWrR5=Jk^%s;))uWq_q%7~0v{g@I=
zL~p_e9;|pWWur%oLD@vt9K#;@+f>$R&>mIKSlkpfjxailtqC+Kqrn}o~~MkFUx|yJHUp91l?!~
z6|ah(|2y>fa%d=rkh>%xya^6zM6&ep*}E719ng9zuSLg5l(+x>O{okjCFY7Twxw`2
zf!HSI6DadGpU=yGO2N*hnVoZdLtQFJJlf$!_C^~&feo-aBXJn!%nH4BIW16(FGHFD};q4jDmRi
z6BSISMX0vybZL<=9LGEmjz!*@zvb*#{5A^>aUfuA7IrKzXa7y=C
zFngRVG116K6DuD|uEufixyk$IE@Ex6wPo_z8>@BszSH3lRxWp+!zPru8#B8-n*e7}
zC>k~TsZ`xggc>@E_s}*EA+mIe)geY;a=N26k&V#Qrom0Ul%0EtTSc2D=$?*2(1bji
zHTv`~+W-lXItCxct_OJpWE8pZH`q@0=}V)I2DkcVtcS`ftHDq07t}{xrbD0wErdD@
zPY`o5bWTX%)jIw6S#c5cAQRrl3dPlMJ^`Zz90#BPjjB1%L8ro}foZ^_p0WOmf$m&s
zAEwgX-@|Lh8Kx6R&RtpZ5hWuT47-(R{wu
zzS#EWvSQALPSl@r@3|wf6WjRUdrw_X>RZ7}s|qmr)uRSMv%k)_DR1r96d`TLsl_Tyy&h*_5wU$OqBaYzp;0{L
zGR`B_Lq7%W+GgG8#c#f$ycZZW-Z#ABX?+obXAfBc$BYa|F0I!g1+u4l(d!e{GDONr
zgYro;&RXSq-T|!Ie)tZ)#sLlAg#V{2@DeBCQgKnstQMwZ0rGLW`Z7y!_2i78!=I1e
z`01s3J;KgF=dXMcSD$xybgk0K+p)`&sfl*|Zs>9`1lfs?>;`w(?x%UqwVj~$m+Q^ToR#-}o!(q?)(N}_6#aYy84cc4J(F}5}!&v837gaFi
zUpglIRFAc;+Jyv|Q_;?uN4E{U)7#SF@kv`rTXsU{#^jSj(Ua*$|1Thr?UeWq}kQ1&*UiD?~<
z-aIp#>d^2KU$~xLA>ZOEm79ikN%C*XmR)S@2W6K|t@OGYTX}i#xNLpAb8h?I&qNSp
zmaDF22D{%LHICABiMI?IY;~inUUzJ#+chlQY4EDTG$Esw2Ef}x)a&AxUdgu<fw<7l9^Hx7`cSzI8Sv`iwd$dPR*R
z*J-brK9`Y0F>znNzN#(4{8+j&_9O0M{&cuLx+i?s(`n)Z>h_*4(6dmKIX-H8t8X!`
z^OXfg0Io05+G>*t*{A-lIDXYEk=|GR8PnNNR@Lu!^rOj#V+hMAh&oBfc;r>|BeaUH
zlntyiR4_cS^(Sn`3@IJO=%N>KLeWFpPSxYQ|h#{lzuZ0r=WsU->@cAE6oeF4ti|V|i=I71@;%DH4$`(X-*TRk0>l&v|
z`8_v9cKgut732L5ZeBGJJtVOtbH_F3(cCoz!)aKWFuq{g+O`5RENpZEa{U$
zhwrH{+o7m;KGdFOFYzNl5CKjfHK?xw{#-x7L+r+{C+l#0g#L%N60PNyikkBtZ+bylU}e2-z3o4oB%^!QwxrRIYC
zqq8%wb`3~?b-~7BR{Gj;jJwRuk#m)Y#Xl8O?F(h1la3$L7Pjw)jW*Bb->{M_F6i(PWyKp1
z2s}a)uKklswlc!mjd$2Hehtvcz?yw+bd0{nqpQ(^?9Y
zX9u4Id40TcFz9235o)3F@!00_>yur>i^*HsbQ`M(cpf&ULyOFBU^lZ
zr#_mTZn<8CBr?LwI9m>bQ$Sb!T>_ROe-CeDJQhm)GLB5*MC#1bJ>5AqDl;bYahoiq
z7~T*2s`ed!8Q#!%Kzdc?+Nf*h%7K@wU1rMNCWZVp7ouTH(wZ)Wx+G(XRQ|cQviyaO
z+5GBIl7kGvmA@;&&_gT9Y+F1tnu9+%8I+4#0ErONRPQAu
z8+Z&v0+l;#IB0pg*rR|Cn=?qQAgyWV!Rx^KYYq+S=Rj-+<3aIav>ZXAWo{RxS(4Tn?HJ;T?lrr+R1v_pZqdOV
z_$Jx9m-LB*cDhrZI7rS=X|blPV*O6KcgV`4=M=b|o_n$uhu%SrL7!0$<`I)ZD&Z~o
z{IU=0OkgCN_n
zvIoD0wg(D;5;hz}pHR`)K;;l4sVJW0G{k`#~%&7$2bhz6Zi%gc5|n=`PD7dUp(5nnM-@pm(6@&O7ii3p4jws(JHBK~2~
zW?9ek`{8BVH3vBXtM8^hkvv)HJ{1vwHg`pZA&@B7$tkn+O_+j{)(+_}tOzx@h*!`CBI
zql9u=P!tpNu8K_f(izSfv6FKXmS7myXPdwl^s}cztHT7b-Vfn(ISoA7tq_IXWct?X
z>Ay)0YCW5vM}@Vl`rV|>{D)h_`|E4=-L(!2GGdWw
zujV>rP+dKwX{r3x<`aw(6&IuFTiIzmPh`(Jqed9XYT>C~F=D)BCF5Ri)(^B(8<0QT
z=$*k>MkZdJKy61uU{8Gckc;qLeqr4ngdv{eKqJrqV)Eg2Rveh;d(wyyA7D;5F2Z17
zIy!s>pzAo*qGPuRA12E9gT4e$=jZ(9A<%zLE66O*Y8I=>w35}(-XuBvht{MCwX--o
zknEWMV{>`#x>8`nK;!L5bXdc}vi&E+?;8zYt2WAAE>t@$2%cOH^nHMs-01*VBZQdY
z?1F(jH()obATUU@RKQ|gQLV+ES74sjNnb0m^%n)`*N)S}UXyUlCG`Qvkmi
zg}ljVrBCh|M>q2>S;C{)cP&TAL94R#8AR77;6Zc&j?G375QhLTXNJ%4{Rvd^&(gF&
z_DC)ydde>iumD*eTOxy31Lpx?5K~&|$AK<%GVr`~&61f8pOQPSE(k*!F#LxRTYtLI
zOyEAI0_B>WI>Xr=L^bfnzbov#%QM;aNH0BL%@t?euqg
z-zzKY9m69lsJLNPD)?m7D@;1%U3fAKlIPocw6yg_60L~rSJib9qQ;!efm1qSbw+q_
zQNm9g*TUfhppZ^VLz6LDIRR7(jR0^m0WW$qd!6(*q#>*Ah*Rm$vO#_|K524=5V@OP&{}t
z)&N;4->xxJmm@a%4w1IIS%+*NHj5}XMa+1eH18HsFaOqay8hMlsSK?05jF4Sx{MCF
z-}UuQdX3pwUkMal>&|L;G)svk5b{UOOfEHm)jY}rO}3@Nd5PP8cBt94OklZgP`UF7
zPzx1N&1burF^8b*lU=l=Z8*wd1ugc)9=*&&un;V$$ZLQYqEM0dCOjQvK*rYC_#(#1
zZ3O2kMDr227<`#g^UxrtR?CESAlT@>n*Zn^S(q3!1D&k^G@yjsV{TAX(@SaB7X9WN
zs=3ET&MtJ9`D((46OpyI5i0RKzOHijWB-ufk-peP`((&-zgdIj=`_l>(~zK^ccBE?
zwrj`z-tii#&&GWnC6#?OM_m?^-RxUtAyFZ3$gcE_{HfCcP1zw5tP)0O%g{1?kW9!#
z&qt00aop%js&s4S8_u>h+WY~?U3zt=?ZV^{&QSoz_UChZRegy=+8{-P`-B+iEm()A
zF6Pa!!Y4FkmvGx!=;eA^KRoH05=X5K5VjWLSI4RNC${Yh%a(wZ
zO$I2^r~w8uC>Kq*m(-{$0Ap+J@bt3pLs2fjFH5C+v6??ny44On>2ak9DrycLS&zV6
zx8)l#uE%hGFhUDJ93l?ir|@j(rGGxRE{YZoSoPuDI$M1It(y#t-E%V_
zkuymCz_arXVi}QC;hkYwk0HE|4sq%-HocKQJ9ASJ#Ew4SV-M6{tG$&IYc)WZyT?DG
z9ON82@Dn*xI^KA#^MMaP*7b@f(UsoSwI0}TQc5k!sx%k^(`>GMh??DaO;E9Gc$`X;
zvtyoH?*W45;WJoefI6u#AG`@=AwdQYrK27zQFcX9X%
zd>B3EFo+5ZMyA0%nVj&oJbPe~walsh0VZ4!Rt!T<<#&MT
zQD#kAG}F3c%cpo);Fs$q&fRpy7TG*7|5*8^5qUK8tniBU*;12{hhH~a%=lB~gK65K
z-IZJkZnk{Kg?%L<8Nt()hZC-*9k8V|hGGi+0<*N&?oM8`F26sdJY${=dc_xhoMyWe
zT*YUsuSku9DnHgMf2#iJLDc+VR>7eGFd5y2G)-6eptU1cpysQla
z#soz_XU?}+9atc!KKH&drg8cHYCR=2%mhlXq(?y1e>Ny#DUGLzNKTKM?p8EddqYlX
z1bEM>r>jqVNAE0F{((wEkTZcHBIn=S=F_J8Kv^@WbVz3YZwiir10zxquu=*#sBjdK(@Q47=H3r7dY#qXIZOOj3(kBa
z!lJ&jexC9E{7|c2OL~+aq{jOl76AsZVKbzN>zV9a5vSE0Zew9}ND+63yXVpX=4gGgl->{HJ>JW&Ec`1uq=}ZB3ou2oI6S%gd4I
z66Z@R8~@_N_WL3b(M6
zBM-&|-x>v~5{f9hIZ6JJi)OYAPT(l_({N?;b`{UAzVPn=y5=p!u=0~R^wz_JVo!qF
z7acriB`+^1sO3>7>-SZcwcc{xpzea*>;(hu(Chp1XZz`jhTbl=ihY**rEl<$ANbU6
zl^Dcwaa_)2!Y)z^UVO`Otzjl}X?GI7uMlgmn>xJq@OewO70~rL#_#8zcZ+E^s1z#_0=Z=*zVHHPW`$V9;QXsq!in
z1Tni+@ByMFlUOC&r_MRcL8h8*R?kv!qn=mSpK`wWe>Uo1j@#(&p05Mov(d80zo(IF
z@|W2cE2mf8&_h*=(o9^y`h)`I&&^JKO~+w7ymn=A|Gb+eis8mmSO<<}w|Jo)|d@_aye$EY-*~BkQ)3$_q7JADBYvhZEt
z0!%Z?T&?e7jcTh!J%&33_}&wNbK{U-yVdX_ge%FeL?v`k%iugZnUjW-2kQ--NkJx@
zz#MFnW&T?xSk6l^L?n<066Q}WUl8sfD7a-({t7VaX8f05~eDm
zRbKq7zP^YT*Rxs|Al5bQ5~C~pgwb&^q|tYbd{bKn>+o0jPU^?%b0Om4PL$lZ6lc_u
zW+mBgzB97m0;I+&(HT<1j(Q~~oZSI{*#R%--oC#Ju%4x07!n5FXROym0MWoW_vn+!
zT^vg%LPi1aCasr1y})MvY~?ie$@~|-OCb$%?=E8eT?~r{6`4@kbH?=$-Qiiy>12&g
z?PtN-)BHJG|{9!n;;1*?fVsXnkT}RzM0aHwf3Tl59V{dQwG&!
zh;?nsh6*n^BG`irY}mRc`0#*E$Ouau!io=aW+V0!y$--Zvv
z-YlBUDq;UkO9#U^NY+L8&X6A`5~!}`K%h^Cx1;%vFb7~H#0Rv#zV_#PC29U1vHHHi^SySmVw0I2Fh)EQr`TK=DQ2mDbX54%rsihl!P*lY^yVxh
ziGrAH!_j8<(-6dnKo>0jE~W7VPj7jQIbJ)h;KET$$&pzvv1tR@RS`}N6-%kTF#)oL
z76DVecBFhmKW&g;m3FQD6gkE~`~`)PU*JlB1pu=w_%xioN&`z7qt(~YZ#pK;mpBfh
zZV|iG@ow)0*p3o4!>YBakpX*>uhy(Hk`&rtOhMn~*#`oB%jq7X3=x>mui$hXB|~UP
z-8)ckwsHm6`T{rI%Y)q*Bu}ZWv-;iB;O@tWUa4UO`fNc&syZ6aCmyV+&)jW_yjb!5
zhR%nV4_u2y&bTLL)Lzz?78-RAJ#M)fTSf1Fd@;&>wj`LJ*!T?P_>O+yp55y%6GUS+
zi%yw|3x_f%bLW&$BY{G^CXL$!=WtKBe9)u=C!Vc?D}K{rbIAZf@+8Wz)X}p9db2t|
zk)d&R<88F&5_3`^F?>3-Kp6<8cr6f)f<-Z+IgkXVa`+vz9Ss8FXbPM_0FZy9=5f{*
zUhOsBXR(HagE+B$1}2W7f209vQa;Xk%ygVquLCtJh|?_ZAMv}yu2{)p|Oz(QEEXF
zm@=64z09Dxy!RhAPSd$k*iqq59?<#vymx)N))uj*|s8x
z(-%kE#RJD}AKaJZZ}nC(K7NFI;RWVKeU>4X``jg!*kaeRg!IOS>64gr*uvz=H|}dM
z{A;h8&Ar)E?TtivP3F?O;y_-=8ASpsqlfHVYjrp^^eDF`^K_q65U3&kr)C{O#bTcn!czON%Znd3-_*Y$-)W!5el
zLW3IfQ3^Gx%{lZ4uco9M>J+j3t+)9et2y*$%5CNHjnufR&HlowF0)_JRh@|8t~;nj
z*Q77g7munp$KUFLL8lk~7S|sCf%XeVZOE$eYqo@tHf%8$`=Z_*@!2
z#yG6<#^h;_2mja(uW$sPL!C>rzDNf5nMmJaLQD9*9qEfcU(AgYPF`{c6AFjEi9l18
zlLYkL7tT!qb_o(9USiR{8w6@LY$hi~80$|`Bw)Q>U$sQFO5#fdVVmTcNj>tpr;w0F
zA6CyP2MEV?2X_t@7==mo6=s!e!9w*(0J6AJSn-8`!xgm?#Kq@_6
zmUX;?3Fpdm=yn|{Iyu5qed8d%Uu3L*tH`IYZ@n=Bup{M#3l80PQKQX+JsmDyk-U?0F!9?Y_rsLD?l?ItgX$$$T43D^o1A7TOTvOYDS3*PhM%$ej!jk
z-78Q%)DvUQ$G8BK@2tZJsx_O_K;)*Dl+&Plk4y*w{=p2)gt=bUdJsq#?Y19cSK#TH
z{%N>EpvHok51ZKb@20M=0Lv2A1Y+d`PBD9`zRipPOg`(#e6%slSxMqX=vb$LPoywp
zaMD6zKCO}z&=#l70wf@{y1%GW0gsyRV3AYWJp5I&?JaV*Nw<#&`vKV=@VQ>{qLdn%
zu4mse3VPD-loXGaon@#_K04U02erY;qSZ56eDj-z9C&cld+Ih2nvXgb5qeaS8Wk
zoiJB^M9^%a?JYODeXM~j$T8957Zsl88-^G#N{JzrjGg8Dr<@KI;wElve=e@p#%fC&
z(OAqUUWi0(c``>>vFG!BM$Lg{u*}%(!=ZKGQ0FBw+Udv+qL&6CrwKWB7JtzB5`x4E
z-OY=`xR;W1=6>MDIf&7#y7b&7lSQ+DB_A3NY%PM1#3;XG%QDd=2Z9g!ds`C_d9ll>
zaG@Pa@K!!9iBK_l+%JBi9N_d_-_q3(81S4+}y8?6`FrePtD?@k*8
zH7NM7Nbf&)19zl%^onnX@uaJ!+)KgFctSlTw4M|*da*Swl+H|v@M_M^
zatf9;D<$fER8FKImLjZX5{X+-na`OMA@#Q}0kn8)JenLi@T+dBfp6JwF)Df=6PS%bn40D
zb9(1CNxrZT<#B&1>A0PX&8@B;S=g#V&1Um^N`fuLvb~r24-=CH%+R&3;br?hn4Nh%kK#83{TWO
zy;eq+R$spLD#D(ea^w?$TroWQhOJduXPkKVpH4X%_HKU57qBbO<8)sN)D@Yyf*W@)
ztKI|~sK11z!ta@zc0y&>7@2GZZ>sAmv@;2M_ms>j8YnnUpFG+{o)wcJ&{*Ix7iEm)<=zZ*hoVSAf!3s5jn-mM4?CEez
z2R)~E<|u{>$TLmevunP8M=~;6%;98d#BIy7Qm&8Vt9`$sy83l=#Op)vo$fyudwcP7
zCcj9;xV+k%SNaEHhRIuiKa~5AT)nRJGK_5-8hwi9OJXy}AW$Lp8KCFwb
zB1Dvt#q+;4W2^g`dQ_&oPRj2vYcvJi4Zn3O7p89;e0Wt3y&nVi1rGnTBB~BuW}eMv
z^UQpltKPCAXQCrAMenUdEA`#!espSC5I78khOa&mT2tTzbZgVFUtX`^0$Jsw-DT>9U?{L>!K>ipQkmdUVtiJui8!%K?@U#DXq=hHZyS6$ZmV{D@G0xDkD*mMm9BaIffP)>dstJrF%0(HlS;)FR00qKa
zXvKv#9T^zI91&c?V6UTb<5`BB4%v5U8iG99akW
zot*UC1XitBx|JIp+qJ}F=(ndmIs&HZwXN!*T8tkeW&IF&5w$p--?NDEx+6&ThoDhlltC
zzDw`_om&<2jXUh>gDOe>c=^@&9GU4&U?-k;eR8<|KexA}bd
zM6vZ>ov&@GikVZ!wa3ZvQ3HjGY;9tp?7Q1^dF+6o9sH5<+CO?>llZwlc{uOW54dkF
z4)t#lrTp7!Ix-e7Oh+eK^KE=(emvlF}e;;(*EK*xl?`)uo<|_-H7do?jLOi
zYX@#b0x=IFc=9Qt#YGVpQbH&f-9O*XD|Y|^RI)9|a;aV2ms?hRhM#||0yL9nn5?V!
zQJTN3^Vsonl6)WJlO&}txGEMtj1f7fm~|nkqH4O{V5BkNILsR?t2w0}!xXCiGB<3K
zKCv@}pCa>rK(MAE!2)UMa-ruTl6$X=jhVVRl=sq2mU4qOmA?-(!);>KO*}u29e2A(
zbp7)VDNFoqVutWe|CGWttEX|Qj$85@e@FI0PMEe(y5q9RG&;;~8al0
z{PXw)vXjVcfL^LwC;*#|rn2Ge#d#7O2#OYrB)!H(WV%p~yeMmx%$39XHdr(vH@cl!
z`azdC@jQ%UP~qk;2$ZWU<`>>Cl2FhW(#o9F4%IYm%(PkFya=tz%6M+=cQW&~>*Vaj
z*j48!D>^Q9-INvJJ`_bKU&MIDH1yHklXNrv$L_~8BT*0@`3y{w
zjb$-)OX;|O<8u=fr$I@8hXH4!;o|79
zb4Re8Kv0NNL;pOyW7W)9p1rPgr$;f4N>VkMV?|0>J+zF3uNC_o1MV9cHkLZR{v6RA
z(ejYxBwD_z#Q78no6axscZ_>IxPTdOWSLXWhdZDnf!FZH*a14hf41#(4dp>21oC7g
z$mM8~JgW5+QX)du)n)RYZS%?#hLFC}+19LwSIG8Plv=W5?>=c0IeR0Ua#~yata+3>
z+4NpmSkB2_<9CFDyHA)cu?IXGF@srRqw#|Ohpz98YI5tgK1USv&{U+Ch$yI(0Mfxk
zLPxrz3&}^
zF&zAG4ED3vUVE*%=A3&X8-`WFVHi8}iKaHgRTTOPdvQR{&lH1>;@c<1mS-NhMB9xH
z2KbMj=D$9isop+qNVLD*>%QyvA$P58cRL`(pd?mVjw!pJyPTk@MCY@V1Yjs=VYC~6
zA#{NM;}Z(u?f|@3XOB~-+e*An_jln8e52XDxtcF3O*O9D@h^85uRLnAH+5Z@ONXW<
zz3FcEz}0?@-J!OEmG|nmD%XBFQ+ZRql4N;SwtFG-z{fyN%OKR3$@C#pgz3zc-L}RX
zJe+Iir{yX8!F5+nR;ou;UMYDjeWXUoxk@AlvQGqh5{(zG1@v$NJ%x;&0xFtatN-mC
ztj;If5z}Zx#mic$J+F!{mj5BD$-?gKa!+9=k%|b
z*4x4zx^Ldo(e3*2^eJ0pdUr6^U7iDb-q86@#tL9)&4KXHc5ra~rh4t_)fe)NMRqy_zBMB^(zv9ER6z
zFd`xq89|?kxE>P1#bFOtTbQOme4@z6c4eQSdCmum3==W4-fiAd4H27hLYzBMV60h7
zQc?wC{X4Z=@rkpHo$UFjmoG&hoaBpRzMiTys(5#!V+fKkb0I|M5_Ib^p|qEHPSi-e
zJ9p})1TYWSjQ>zCMvUsDEoEE#kA8#2R3!}3>Lx`g3!kC%#cZG5T?1$5G@hBsgFIM_$aQ*jg`Pkd>)OnUE7sdA0GcSvWE%l$20
zGo)bK;eT_?f2ZEHzSIpM=?tF1oE#+cCI{}0VwKlx4;anCQ50S|pa_{ifdXF3KN2Yqd?0D`YHyavqbo_T8IvK<{
zGDsPBMYwOc9e8{l4s0}DC{Zcmf*!riYwT;0;i2>+_Sa7vx3%oNCD$q2p9SA*QKQU8
zhqFt#<#`EsVF$e)F?pM=d71G@<&MzDn$3ns*&hx+RJ$x7;F^ouVClHBfG<36(D8a-
zD*Z5Y0vbRK4{#{z()^(YY<`Oopw!g(*V=~mOG+|DAWhNh{0#Wvm7T@nAGFS)=%
zBXhYjwG_0c9ZxtOB`aBjY^#GxGLp8H=3yAopg$i-*{G3UU5b~L
zZJFiE_A1rasyu1t&>X3{p
zkTp_P#yyJD?gs9lYKL6Usk~mjJ3hZFF`=mD>6>={A1?r}K}RFK^7|r{blTWs*6_Y3
zd>OX0u2t=13%@Ymk-#$%sweMUT|#dv&~@sk%J(O3D7;oL5J3@m_MduQR_ZrQEQFeH
zTMWOi@wF&f{D?0EN}QLhe+wZ0s|0rG=pYeOHhw`UO+PAsDSafR`PRB~o0F#_rq7=%q!l9croS$t#`re>ytO3uK2d
z*Xr@}MI$e^iX}y!gv`ZShP;uNZ{XH4PfUU=eq^IGOLA^StfyQC=>6PjR_0U>(R!>@
zb!%|jubH$f8SXoaf4=3YI@aiF@*}wz6>w!^)qX^MYjLsm?E856@=UCb>c!aJyUw}6
z_~e*A%Yec%0@eT;W*Goi&+Kf>ZL5Q!&Y184chX5M4>?Wwn3cqB%_?1&
z{$G~UY1C_oG40x9L4uZqVKg*T&Vj2{4OMC+&X2$BOiWG=wAa^4lXvv|)M1+1@+?V7
zb}rG-V)^`rO?e}zZ23Af({v-^?D^=dd4iT!+8QCwaiOR__*@2>RK{uZM8RU2@3KZ$
ztPoaJ;k=_G7hH%M!MWBZR`XSnmqmc~DXmvrTy2s;CRf1bskfH)}~iW`<6vkA?u+FIyLwn+cA^9biE
z0zrNCn~7GYJLvDtYQHQmoyVV_jFP*akKt%#{M6znn`z6yOdO`2h=PI+Y7jG_Y6kv;L+M9k}(#%RVQ_M
z_ZDl1zss`3L=-~3;(+Vj6PbAuppfP|Jwwy&@b;`WmnB?>F+X%GSiHf_fP@tvShOg(
zqT(dQ!f_^k!_>IB>t+X>78USoy$obd_uU{*)wk#4Ya(*jJQ66$9)jM}vdiKlQ3n?V
zF>(4VSZ{;|0y29CV84`-PWT8TmG4R|A-
z(DK3rMQ_AMTC2w3J8GGvK~ek`NEtg?NSmd5^>AS5EoXNHv{^xICAXK|&W+@34`2XtmUezV*VkKg*CJ^v
zl=o2O+s^3y6G29n|SSmgraYp=`=h!d~;
z0h#ll3v3sbA66Sy9F`f@{#8iM1jya2V7pr|j%siE?8fPk3O)0Oy6$^0bxyOEOcxC&
zsPDs`d6k4Fjae>GMTB9k{A0FD5Hw-X;~r`uhi#?o5)=R{qU(6`JZxX`1u@@WQsD8u
zxN*(hToQI)2oF}(u%Toxp{A+E0K5H2-OIu^;CuaOPkV}
z-J{A#=ePeKk#9KIMrXNm3}c8Qss8|jpClYwR55Vvy*hBuoS^(32Y-#IR+aXi_PJcQ
zPddX4fdh}>>wB4uZUSCHtT6T)d+tO=?
zl{$DaRj7rz_Jz4*I#No}O^YXdjD}6O`!pn}FRTWZA*zfBtj+MN5=csn8#>Xu3GC4n
zkOnsn;D+5U<;UE8D$kC0`T9e4r
z7AlznLP}@L+iQM|Dle35aztd}GSWz{=X;JV&?!Ik$(k2~r;VPk@uMr=@3iTpK
z;yvOc-DPTg$@Pyi7>X!aV9Fj8I{URDcRvzbvj}er*l4eH-gm8u-S(R~`L!!EFB;eD
zo}JsUiVAHMg*S0GgYA(U;DrGJg40l+&pg5Yt5+IuSkaP$trR%3SCGBl=X+k!~D
zEUX}K(aZBi5-Sh=B4Tx3ikYt9IGV}L<(JTSa)LEc5#=<+JyGO|><;JT1p11@>KShC
z|7hdK)4f}nEP+DiyXMWguAid{`W;2j^1-gdnKrF3w-+Z7Xx*O
zK+}WSrh6|Yytr0LRMp4XV>@l+WgIWX{x<7MZNvK3sMLan>1GZZ%bVJ9{m(L82hr
z`U~N+&nc#x5^JEKq^CV}NB8|$!ZK}@y`Z)XaE1AMFggkHsLY{F0Pr~!-P;Dpz<>zfVngT(up<;KX6zRt@J1M4gdIwI%wf-7!$IS*u>Z8YarAnu7C^T8_P$zt
znTYEW#GKRD81ehm`zXs=iW1F|X{4m!OaX`w=P8mljiCr2Hr
z2z#RvlI@;Yv2dXMFejdmV0%XZOb}4BA9-i`o;F(=SNy=*uJ3kNTtluQ8$jaobqr+|
zvHv`Eg=k*GE05Ber_A_>*2|JT^~W;;ou?~arkx2HYx-+|519!UPy_E5e7WVZdB^F3
z%mbdY7{3)7MTibqIw)T;0|ZveJa+~r$buB;r|9>QhX;tJd)M<+JzR#}?g!*cyg_$a
zzmXGA+oavCc=h@Ou@jSTTQhwCn^in!t8p2P)l7ZK
z78O(xqARRAyXl#F9a@&1u$GIx0V3YYfL>a={>}h|1w_of4kN*w(w$LwHst9dA?L<2
zJeRarLfryh3Qd~|O?x`-mphntGNGIE!fIU<$I{i@ahc$y_XU{IcblHROV4MJVb5=l
z>kOn;(8Sw{4c(!dP9>9^$G{O-Mrc7JgQxhPK|^n8%W5scogDRh2-EpS1y?YofMmEh
zi!ZEuHiCXxRy75;Rj${J9;|Nd@BT8JzK+mF$UcNTl${2HO;*JQ09y=vxLQ@i6M|$D
zjVrYQbDr$H=hVi6M@_5OL@@mjfFOOQw7`%Jq*Y5U$i^;q@hN1x+bvbdW@)LCeQ8m~
z&ey5R#pVMRTqF=qd=^JG!!sX+hATPh3e2^^a0=YDJ*Imj9Pon371|VnE$61`&Z6gP
zSNvqCjScT5@3}iV92;d);58to25T8x^99NiV|&NStuJVSqYDRjaj{l1=8H=QANs^*
z2Yy^`50*`_ajTB{ghs0HmAF3tJPn)^@f#0`kXx*;rxhR;vfxAv`^&lU#6xAK2i%Q7xv@E
zF)VmZ17|h0kdT4KT>SJ!Eq}T&FO6DPZ@fbN?m60XSPY%^TjFEVd28GK`OiD=;c6p#
zUH-kfwkghcT3*()#K#B;MBOqMc)vNjsSh)F5i_d)iYldLyl_K(##9{0VhNF-F0)tw
zY+9ZX
z`_4S_NDsR5V5v~SKfQ%?!9(8Q`V>Op8x+ur^JphNeXIKSU)eqju
zVEwdi1!T
zaMMxFx$_p^|Coi()k?8nTsLig6f=4@t>SEguJw_kApGMuMxLg3Hz&M$U5;jV%jOq)
zUnu!GfY-Y&@Y}5-{9x^KkhIjU;jf6V`g>xRcEL9tM@TyexsXR?!*1tQF>Hdi-Lrdg
zY&jyO6>&lnJFP(VPJ4pVa!7TkJt;bV?)^EQD>`BfdGTU)kr^o6Db&H>zJau4lMqD4
zF1F6tDO%r2jQ@&7G7n@%b15fNShdvDimuxc??0Cv<>ITmsgHn^J^gM;_E6xJuX=DA
zoR)l(`p%iH+G8g+fyV;ao%(Jg{{-@`MP~e^x~Z
zs?s6{V@7q|QyT>Uf!UT9P1>PCfp$wXSm-z~2*dP_r1eh9zw6%3Rjg!#jCg#Be%~J4
zsN;Gbt1Fi4U2d(qiou7eCLmWw&g`G6R8vj#4
zY&&z$arY<}B?Uzsqa>V6ecl-{dp#h*hEsYVvjGep=Z4GdMwsT}xoO>cK9G(x=zYw{*pcPr%4E)YMPvB5#~)mx%x+aVh03
z7p{obQfL6mE4AEKcD%wdO41_-1;;o7^K_|A3}8{W|CF=|GQTcj8NhpAsced~0`>Fm
z5dH0n;DcJLCF}i!t^=?0=pW%;P8l`q?@LO~!3&0wWvBJ%-B$#HFh%7$2S0Ao!uGwT
zT}(V;)eUi@gJ8aC6Zd%
zBlIhx7x25#irSsM;!b6tk{qf6*eq9x)U?YDcN#nuPrb8uOM?$dmS|beVoZN621Csb`u2XkXXU6DW35W8oxUROmy<_J$PlDT0oW`kKrI5O
zv*nwUZ+EnA2Gh^EO5h39o)8b)JLK=7g?HZgeQ@3sSd-!kjJk_jjKybK{0wpjZ3h{i
zt~DKDp{|(C;%VEhoII{MmqF%KV{z3$%GAd7`S!B$5maqdKG!d7CU3*jQ_>6YHC)jX
zi*dX?Ej2svt>(g72Kma6p_;9;IH)S4^u*(S*X6odN86X3o8BKMIn(&mZfO1{mHlny
z19-u82ptZ^8p~A|I@4?M5{$1;*MH6zZ|kE$5Y!OZ?)dGnA5jxg2aXc+=Qw7@0`dFh
zs{*)T!@Ua!DiiKvAp7p0jVJ86_WR~wkd*Tyk~NfkO6EQ{u5bj)Hm4;CwRa?~fZCvjSx|F)cPG|I`T>q>7S^$duGms^)@i(gCFbQ`im|h6GFYPOj@NbHeUEo@lY~jC
z*d?oPzoNaI1dDriS%xDObYEpT>NuX3wmq#u)j
zhHbsI3H0mdxHD#(zQHg}+KapJ@al}Np%~G%!Mp)A+ki-V7oY4K6F75)9{uCusOjj`
zJ8S-JBRmD4(vbXXePZ3i>0P3Tx%XHXpZW+)Fx=-A4WLQ!6;mJY>DN+}70x?ds%N@7
z^FE`AFPVY31)M};@-*wr1^Qj|^Vur1=oe~v^Zkn#5%&^tNNYZl*nPxv9WkU(OTX^m
zVq>>i<|(-9gG-U}`{(BXg8?T`nS>={kra)b2rwsWFjaX1)r-knA9XLOE!sry&UZXe
zRUK1^({+>|nz0Bj1}Ut;&(pMP_Vr~6Tdj$89IbDc?1EV2at7q=VpxUjWTw@u$-Ii}
z1yU6eK*1!11?)0FXy<<>tpv2_A?oVM5LtOEjKixj_<;5VbqFXj9^q9p1(W%&oJO1q
zIE6J=`gyJ_LteuZ#R6X2jBlGNHc0#06~8DElZ`DMaToLxme2qMn*c{rFD4C{$im}I
z$;@@-t)6)iT7)PS-buh-1<*Ern+ND{f$6eB@W8%uGzaeKSbbr{BWsN&9qZv)GWF}7)#P!=NO^+a=3@7JS?&JXeET+g
z>bga>f3b)83kE&0nUn6gK4M)Frze0~^eK0F{xKun=JASa7x+w7{9GB$M?x({u=^w*
zYBN+O1Tz0rYq#r>sp81WB|6N#@V6t~l_Tx8qnIYqiU-}R_Xp-HoA!pC_j*;nknx6M
zCH~N1Y|vFqObO1#|KRMyAke`ENAjQYJLMN~}iMm2;UgoR{dTtdzOg;G5m
zS+c}>FhOPSx#Y2X^mhdwqG2vSe(5glWmZN<$LAx
zP;&(RK@27`XS|R{o85&)>?vaUAQV~CQ)!*Rn%d+#oxXg+|M-jKpdUp^A6%3B
zk>x#sdE>p?ktkbdp|yY$LAmOi1A~^Y@lz{v8GfhA?W)otN#iGMeorL-At@HNKRQ_R
zpKOcWXp#;0(dBE(`xlwhVg_5emP`*E4-9QPTNojjxOVa;Yv?hP~)g{W_C$bMVbdd&iv+HfT`;cxy{g
zH9A#2Rq1(01?tHkldtX1%eef!HkjXe`WJ3(0WYm6TAgcM^rZ9pG^gwj(aHBcf`aYg
z>5WE!$rxrJ=XGJjZ(9%^<03}=npT>#R@{RZ_j
zb^Dy~9?69~YulR6!+{6|JATgr`OUt$_;L5_Fn4vN-u)VRAqnbYnL(=3%tQodG95aeWH=g!WSDn0Y*rl8B2{91TZWF#o7wNYUM;+xywp}#A-Z1`@~?Yi~PzFCxq=s@?BbTF6Sx}ILVEg5YEkx
zczM^Dc%hvbPSx#8xT?~~2|+A*=HhR}K-VV8ew-UTQB|`yZbqN#He1XGTl?AA4Pjk&
zaxy#RQa%x~`ju_ySgsiGALS4kNa?Rg~k9-%y?*By+0(6o>pxH}J9)O6nW{=w1
z-UPx_?KFY|3;lX{3{5Z9^L-2^NF+HeI1P0&KdpY0dCu9JqdaoLq~pW&Q8x#EXm@7D
zmy$lqvuun{ueFtLs{*A0KDBNeX>S87c(bKEU}$uEkCVY2{5PQy0O>qpgla67rT=B(
z3Shhs^PvO)#%zT%sI#!7Dl<_5
z@|?O0Dzfm*-)&VA75ww$EUPiEN{fU>kK(k!#L);~JfOaHXmVV{Fyo^AC8TqxVYm`h
zV~99&X!#ivbfW`A(>1hF#S}h@Vsh=keVA@|bl2uy??!rU)9}OY6Ov)Y%oDOZKh^`w
z{K%7a&A4iDM!p9(g^$9eN>_A9i18T-`=OfNx5r7z0m;K<`|_2Wm}~H&jO~kj#a2v
zIxJXb(b|xRD07~w79D27m8;%!TKF;k2?%*9-k>eQFm01ipY74P3Fu#uZy&`y(}L=m
zPxsE#5~q~yY%nt=?KAGRK{)|SJ~h^NEnU`pW>1Lho>vU*WiaLUmW-*U$KIJV2@XQ;
z;(y?ipf|>RM={xz$4m;OTo*ttksp2&KtI(s3QKXE`!C_(Ypu2&$%W1fAs;Gp9xbQW
zf`I`OH9H^;Tto9b2x(iv*9M+8J+<+Lh2Pl2L+jj6fZGRFcMPo|X7|JjLhHoX*LuRV
ztanJ~>-04wAoe=|4TRH+Drsmw4L~mAH{m-@5|?;y&EJ$S%~@5+(PFmuXwST$8e^nh
z(!&F^2_9`HENHq(?{Yt9
zzR=?LYw4EF6cY`FEN^$uYqPudyaH4*%cbE=R{`*}1&-dje;_Q66+u_;Yy*m4)*`w{
zZ96P~!rSSo)L6>5AH*dVUah>E~m57DHsD`WQeOy%oyE_KKJVDIUh
z9vOvU{(_*t?IY3f7Dmv4Rh9NOe9TV&Hb|xaC(M(vc-g5Em~6MI4gdT+ihe4`f3fIb
ze0-w`kfnCdH{okW4q8qpi)avS8Z6I0RDI=*oKZi>97}1_f33=q$WbHEB$})=*ROK_
z)>3AkYXhb$KJ4IaJjeoZ9SL;FZwhm0jQU!X^^@J$H)9G|Q`AU*uzyacM++uu=P&2F9AWN~UQ7y_sZsr)!4x!BH>%s88w9LP+s
z2}Kh66+(#rHr@3837^e;m$=ScZiZU2EGUpRHGe2_Fq|I_o)h~IZ)sS5_;Wh<0ALA@
zX!w#J%qZJAiYbXPi=}cs^|RHA(pL8pr$r4q7IUgC=JmRdIaF^4Wm+L$?N8%vFLDWR
zQH679oBEQF&C>2p4c`VuiBfw8H%M(Kv)E7rIr?sm4My_-025!os`EAi_UsM7%U7_s
zq7+LBaKxHGByH^6^=wg~f7MwON<+YS>?u=mI_WYw=rBW$YI%~M
zP_3`kHx=G5!eDAF`_&@A-%di@b&qr!v#rJ}Z+#W82mhb1gjjz5^Wlesz0QNN-*ZFd
zao_BwyMbvh%_34-d^zrG8lJ0p!69(kRyKBz)2ZD$-_73Ue%4@*8H10r7N`J*Ymxe?MODm4n}fFP%#wW4Ce3P2lb
zU%N;2BgbEc?8S7-d;2EdHn?>>q{u;JH@mFatfd)B
z>_JuHjaO`!f%bdMMyok!@Jv^4U?$V(kZw0h5XMj1D<;L8Z2;@v5P&RVcUU8NTpxN
zJ`(;=LIe!CbE1$7;K58womL>a^*NQfNijzk^1CL?T`AR)p{`x|tOdw-C{qifmx`&f
zI!>fSeSWo9GdZj4FFiEwCv9#vi^mkX!!;|KCJ7N@izYZ@IVMCQ6XvA5$K
z%Q!7|8q0tsHzPWN+kRIyO_;r=@nr13zgqFHWx5qJhh;DT$pCtTvAh0@m48}xMq^s?
zw5vv6cxO~|pP;<&Eut^szCUm!*cb!&={x0)G|I}?x61QemgR~pAV}ed_(|{dm|!1Z
zB#Lg4`*jY8@kGfQYJ+m_K3{KiP3)95mHa|c1AZ4>S7Q}@)c>?bkJEX9P;a_l&V3$L
z;-6PT9s>eM7!n1rJJlTQe8bOi%Ik6CDv(nP9-m6%)eUyy8nx6nyUcQL3C6WfE%Tk;#;Fi}Qv5=WT!=8_!hTbCWZu0nI+gm%T-ziyPVbS9TRWIv_&Y-=+Au5Vf35A
zGXN6JT=(w>IR3|(`sUjAhX;49R!D781FrgWvr3Fvt8cv08v(5mhg;(4=v_xK0JG3f
zaKZskz#=d8%1FEBL>T^%<1=jmD)?CCrip>Nw-Zfj@)rdy#U6=pD&zn+&1`=l#U*H-
zujgP~1%14#*ZJA*8cEFs?Z3Ppj@VG*E(9i8
zdSqa|HZ}EMH;IB$wj;4ZHA~-*Y-efv+n+Pl*m@>pN7F}dm?l*`-Z=HYH_3{r!EtYR
z1(;<%+8A-hZ<$35MWl3o#nSSLXo42v+|P5vZeuXCLCX+d7w
zZ(1yJwn`GB(PJDk;BmIMD8;(#9IM61oY=v1Bv=U-NM8y8ncw^m4^&N-IzLlcI#jMH
zaf(Di1J>pu3q`yUiX#^VAN7xIA9Rif{9$2o|JT9pZ9n(F?!VsTcX@V9s_?~`V+!4G
z3r}%3+B}TBDkq*an`^X_-+d-qoH)dgdwl~@-*r?mOD9_w#KIRVqjD?2eZ(ETU5!)U
z&3J076L|M$o5ho#;{gTh7S6bmU)AfM6&KPaiB{2tk-lE5
z=eeCK&6j8GJ13(c&3fX^!0j87CaT-tR&a%Umi7elS+7-7ovH`D`S%Iy>FJ@Rn|adn
z%$z+kW?O{pPe*pmd`YSoH-~;zrN;Ycgf=%f7uhwxLJrx6_InQ6l=451x~6khEcO)X
zxZP3tBfo!rU>Ws?Di;v%()xQXr*F6&6D>Au9TIAV_@!#IL|$5cA?GeQkQ`d$_qIG+
z$#GQm*@eNN!G2GXvR}?OAC~B%sKJQn`4|Pv*XY47%z(h
z5@Eq=lVw6V+Mj03A~K_-LvJ!!0ST|2>t!xjXqDF2PnMA$E1FHeEDQhFGXC4DzA5qj
ziB!hNrb01+(SMX3j|0bQ`QVwnpu4tYMVs
z=UeY*b54}k5Ap)LQpO3tx{t5~4NdVSVS+TponUE%p~w5`15hKY09)F
zU#c7x9LYRyPUV*jVu?c-wLOn*&7He8KA%fm4GTEh{UtARG}5lN@`IFk1M%bx@#Km_
z5qUbR=lbN>aq^YJ1h
zTdC?bu`8f*M%)T65QyEt#TAe>#(oK4y5sp~_5O1pM*pEIY)V=!?u2Y5y$cTyfBm$?
z?_xRM4L1iHovC1}NGpRP&XDw5FR)iP6q;r+^9g{IltOuapk%6`+z0jSuC4i^4?m#t
zuS{s1c5#Lur1O?%BfUx@*^lqY9?;)48m_wNvQh4M#6?;a)n*$LTQrL*w|+Bc&j
zgd&;FamE|U=*WGy;`CEz&j0NHV9B!U8q32^QH8XD5G3{C#{GR&mNiE$w8bW2YKN`Y
zxU9XlE}s=A=g|cpj4|80Np2`%OsVpyFRzi#*c~Sbz8*NXB9nwFbmLIgiF{;ywycpw
zJ%%|GpzA*r^^x@#;ICkaj>{tD-g}Q@D{!=n!9e6u2iwm(MiY54@?Ee*Nd>vY)+W>qaG^jhJiqEY??=z=hn7;nof<~gJLy5ClS{Rc
zyeyz2terL6d;#P1(A=wQ^@8>SsV~?fuaSR*{eZA8WR1Ldw|>26;Q?iAQ_6-nNMw66
z+$vpZN|jgnxCSSg6z9EPKUnK4SGt(54;R{a5nF=xedw}<2X9SoyuacwbiK)QK*ry%
z^|Xf^D7e!;f@BEs7Jix1lJNznF^r%txa~xM)7Sq@rM76B#7qynV1|8HTXt6#^WsJgwOzBRh
z0~?PZ5tZhT;%Xg@CI=(^@O&^&z}lEefyI5`Ky_mSz2;WHqyIaxpB4Ket@G@AEB~W(
zBOYmsd!?gpl>$eQQdCI`s*(i<2^9s=Xp&~;_dn_LZ8B8Du*TQI^ETe+IZPx}gar{^
zsCq%euH!zNw2Cf&AVmzGMwrLaVTK^3FO&dc|_$
zawISKbD7jBHmb7cwT;W33LB=gA_Y-Zk>5l7H`{l=UAhul&x~HZzBwi5;5PC;7`S3o
zEi7qAm~7PNUy}PwVdyWh3}I)M%9AKrx(SGXf1oyHAcQfMmyK^ZyqYCRUye)N)sL~B
z_0jwxG`XZsG#G7SMv&TdC$w`rv&s;@BX6~tiOOeOC0W2B1CR|@*D|zhN2IEJ$ZWUT
zQ@c${tLh?L)&C(m&us-H!kE&OH2HKf*~Z^#>Mwq@iDu3JzmNYbU+_P7l|6Dsw?kv2
zjeZCIIac&3l{euy3y=^+nB(B7`l-$lnr?NCRkyFo&byE+P_8o|!TFexfj5vsd-y0_
z*^tm*$^eq#hNi>MFwDHD;U~nx`_Ha}oGK@7se^V#d^B1PlZKftucsMK+i_0nG_3Mj
z%MUg;9q+jM>{z*5psUIpW=a{_oGt_;@xgFHL5#+iwfFZy2jfYAcyS8LBvL@9j8d^{fsNeqyX&=>Dbpmr_(!Sm
z?(ad1@pw(1+p#4`Pn`|o=X9%r{zso6YA0s~F!sbq4I(|4Q$(HAe5tU$&Z;CYJ*j?=
z8-4tZ*=qMMD+2zuD}Ush;M=Ojbkb7U_7=9F6d!Hm2K#(==6X-jkjq=2!o{b&r=@3Mb@C>|yA*o_R?%YFhWrj)<
z2`v^7G;OiRowJkWjS_iXkgLY?9VY{zoB|Oi>@#G`l)BNX^uHHMI_~0p>MVZdFMf=D
zxX@oBJ6xf5v{u_>2y6vt)gE@U
z;5lbWn_7U?6@PCi;XVr`>-MTsZW^XOIVyFbr^6CjfZ6QpCVnUtGmf>bGvjMMme6#h
zob0+H{>Qqw76*HM9Rg7}ym?8aCeW#|2f3(Kg>;=Nt}IW7sT=Oyxu=O!?L~eGFVwEx
zGMuQtJvO2p%9M(9eW6tr&_RPeLL!SGeZRuQVm<`cN!b5WF8;UhR6H_kpwX8sX-7-E
zfRx&}%gQ-xM*j9H;Q8}5mc}|JCXpTvTIrJ)LN@uK)@#G(Sn@juYBlyqp}Ckf(NID#
ze1V1M$mj@DZ^^Mvzr5bYI+mGqOAWN%fwbZ9#WJz`v7%iv56ZkxO_epge>Pj$<#w%*
zT-Q}ZIs4b?Sd?;lf!Yr_HF^n_-Ix9}JG_?%f{qVwWJc?K(y&-H_o{o(qDFtqsZ&KK
zt@R2BM#uwysAUodbdT)h^Cj^=S6L;;V@ET*2h8nj_qX$p@gwDYg-#Y|Y)ER?-u)h&1znSi=&b!v^Io4rh<2{7whAL6)V=ghj
zQ5rpm-B{_+{#3ACqWtmq9W5K_Q1_zn*m2$>hdGD`YwJ4U%I`Uwp1*
zkk3wotQ<>4C<~N+s~_YD^efKHt-idvtJ&mvFO&y2y%Z2L#1)Eq{P^Saq^jv0VbZU7
z31s
ziPGvyr-}7i{X){a3;IcYFQUE<1a4pXyNmhvu#Eavdgf26q?`FPzdRQgSCB?;dUVp*
zN9mj{3%{xpBA?nYu$dgP)N1n*gw8WQig^a>X%-Th!K|0GQ
zaJN?}Sp?7rbxa5S2&lrLKBz`#i9C#E4;0ErGlFf6yPpC-*Uh*nn#Rr$j90Y)wNGHg)l01Ra{4ZO*RZVF=&9YYU4iZPdsM0LdR^u8?cjni
zGC=-xIRiqh8Z9(r2mb$j|HGJi#C<9N(RS(>H;2$A?U6ZHq8t6lDE=6^WN
zEvqV12-}tiJeb*~&i06E87IqwX8ZhKv_$cb|kRHXZR|P9gREmu#W?iU#0n>Mbu}+Ld@u;#WRoy)nrJCkb(#V&(T{uc_
z;$b+6qQiS@%inr_W9C~JZz;*Vy`Zy_zI7}F)wh#3`X*k(TPVBLPwhQ-c4y1bUp9x$
zTb21AJl*Y$(_hSFQX&W3PWw&{o5iNJv`5|E>PCD*^9rfvU|1C<>ptSa%5f+%?Gc+u
z79P5qwX(Nm$ZA-%(Ot{1K-(>U3$-}d6tyGXP2b+s*9`f6k;&ax{l7tlue}E91yT``
zc8Arprj0#^LkukL$T{o_7W0#toSjFGm5ZW1Z@74nl%Hy`yiwUuwK#E=Ov`e23kT|>
z)a~?apTc*-3&)d~J|T%igHSr%WxrnQy|jn*K}R-FfDjjbIYu&n{N0EbvBJnp0bgQe
zsv@OAe1^W)Wvl!tqAsOWq}h5A+|KOe-F_jC79E)N!
z_5kH=_9Hg_>>vngUJgJZl4PtcL^e{I*gA?0#Q)Dtq)L2_9mX8&uSo09-s_)qYl`3EB>IJMv38Ub7)wE#Au*l}CiFi`c(H}
z+fd=hG4^qdRvc`0F^?d}JH}YwAjq>0vt9(D|3}xC$3xxzdnZL@Z?R+<>aLW1l-;N#
zl?rv+m%)%^C;N;kLK+Nr*&-8)$};wyvF{AYl5Ol`H<)1zGxL1?e&?L0bI$Yp(SPdo
z%3O0@@6UVtvFmPwhx;l{HGdh@#Fdy)Z;vCr`d5W^}9!Mc>G6xA)R?R!QS`f
z^Nq+8NBl`g0{sev;}_0Mv|cM!nU-wC21mC4dQ1A{9+|K98h&KB?1C(
z9gWxJh|EUkt*7Uk5`Q46BSSbwfu2}LlE^;_f8j-*GNUM5kN2rf;emNbSMi9F^v4b{
z)Wh={Rh)v>vEj4&YhN~tW0?(>8$cJ&wO4l_ph)T@Ac
z^>8JwxKb{Vy5(ku)yoD+XGGv6?M{{^>{q9`#ZDa7tT@YB!W(cC4J)e^effV5?s<1#
z)pSG6GQ=+?6O-LPW_wcJs-{;8${ZuGYQP;z=IpK0FMu>@Q?=l*Iz~$}sIiS5X5L=5
zrVgbvOhK!o8g9~bhZd4)#~eYNUy$^X7v-WRVgCKd98|aJJK;g!L!OVDgQf6@D<}oYNrfB!BBBn4z*^T$d|AX9%$#f1EM
zNDD?9X%Iu4a9P2DXLXRvDj@h0VcRZO-*Y4#SK0@BVk3V^e@h8F?!jmF7p?u00ACGQ
zfH1@rGTt)mHg>@ofSLMCH%@cRL`W1XboAC^zBl~Bz44H_Bz|j@qu9u6eEK6qdlRlK
z)|NFl*fq}c=Hl&`&C(ZxKWN3nx?%zL@$+3)%12>^)+<+rNmE~nJYN*Kn5rAKn*NNg
zr9^WX*tLBy+cYRF+>AV}oSE?Z%ywt%0>XP(vpUu6e||-$^7&Y{ejM5duCa@F+%zk&
zxg}!|s}5isJ;M@tf{O*d{vcO$psma|{M^gfaB3O!cGd>l28)kWD(R0LYxS2E(Yr!970yvC&KRjYZ)bAfr|M3
z%bvFo#h=^kiX$&6KXQ(Mum_g8xwy4S@f7f=1OdYam%Y(d=z7Tsmnfgnf`hE0_3)L>suk?j759p|
zT-;Kk1APk`PsuS(i1tkR-AeVz9g(1iJVZi^*1H3x-+6m4Boqdmao7n7bJ*clk2h0a
zvs_p$vPdr8pU9l3?S2>WKDtK~H_)pf>i9ng(e)pEpLsnyuV()`{Jv!OHBl|H_;!N|e{pjjQ(b~``qP_c
zPbGKBR_BU*0ywLW>Jb_8#wa-xOXDnA3-het!rWZuJeR}H=nV(s)iAD=p#;iG#hOeE
zV-l7@UsHwk`lPfCeRaTfzdPC^4UwEhA2sR9Mddgytk>uz7w^_N*!636jwATvRjb$e
zhcEYjvXv4NdTkhe1Ou#M@iDHHKGlm>U#}SO+Sxs}Lf%QZ!HxOXiuwa=G8)*sw;~FIOHOj%b{nHbv?Xoalwng}NE~&u
zpe=sRu>$BC2Z&fqeN)AE
ze2LMWIk&SOva1EgB4F-z{;{2gY27rPlp&m#a%`PbX-=?T6lk;a$CDcI0dMqn3EN
zI-xiiyGC9F%)(8xFHOl_jMXTL(czLBBNIL2S}`4-3}5ibZ)SuJg@2zoUP30%(5pA2x;ijQ_B
za!&4zT!TG?di6$8R0y7;v`bgtyH8oIS4$O~gj9987VI+q2?BP*!IL
z;Af;O#b7TNSD={1B_36}G5TQG?v(F`CG?PY(`7G@Zj;>Yek~c(jyi@Az`+3G?VH?7
zt<=$fyzuE`>G`#VMl}_+F2W_>3lH8`b%P
zxo4eRwRiw_?D+w%eCBi3mbPDkn;f(yHZ3Oj=k7jpl3Xw*)2P1{Fp&YX@?8>X_5NtJyj3u$tN_F-W`=P-g$R4_qP^(Kr4QKoCq1XrCMcmfdtfuRi$p%
zdS`I{mI_DAoWUgV?@Q%ZWYp)J1y%0Wv}tyoypl>1#&qr4ac>0Rp+k*+d6D5*nhg-i9HiNtvloaFx?MaTGAw!R-uPpg2(C(I&&D%M7D=eFMwwXm^y2-1*I
zO>E(L7LxDf)6hsPz(QnHEL{5<{i5J68xDQLcM|<{0NO8qkVR3<^M<*mg>n~=8jq~Oq
z18f!t-en)}YBknnj+F9-y0)&tr%HVnSGq3fbq(YP_hsW7_JZ6>(y(EI1+=!y2y^ax
z>f=Q(m%b(@WNr(AZ^+_01jK680~++Zet6eeDM?$HD>ZKiQWLkTjuwiQ-=hP&ovMKf
zix}4Kf?nR*K9TU$UTrzA#QlR=x2RQDeru$bu{S8W6cPvl}KyJzoFoBMQ1+Rn6HmTk}89{Z0qjS$K
zC5oAh{ltJxp7;Y%;;;hG744XqN>P}72`~I!a7to>V)-#`s8;*IBw?$Ez0g_q
z9!LW`I+X1@w*uAbKBxJ%4l)Q^6`6kB0%6~i)o@M8^hC{-qEag7VVOW(5#rm#+;Y2i?|6Nn4ZGdyxZn!m
z(NKJ-L+}Xe$}`{hE4m^|OWv;rwcsbx)S}{GU0mo?Df@fpD{kLK+Pr6gF5I-l@8370
zOel7?`WtMFR=C=^YJBiRT^$N$^YLFr%VY>u$n>o&Hz!$qY@HQIdA24TmfRF`Z5u?Y
zAEDOjah5+98qQW`z~0WZ-cg20v5$hkP~F8+n9K
zT6z1~=1fbeI7}gA{9O|~=J)G(eUQ*Go(pXmNf$dzB1H0f^rtgJ0Z&Ea(+Ga32=AV_
zhRYI2Y-^~_)SP7Vt3R#sk{5lT4HrxvTwdf6g16-%_trFGGgmcFQ^M%iVQ<8FZixVtO~wjV+A!A8`qrLT%7ucRqDv0*LQJC}=0Ql2
zZ_B%)1Wn<6+ne+of>9AZ;kMzVvgftW7AL|(IKA)4xC}OSH?DQA28sU7FM_a4RuM;W
zG$j~_l}qPENoB&_CljRX-D);lz4~P0Q0}FQOZSyGe~o5rfyx_BFI@`W&}+q-+R;NT_;VtyHh8jn@*qK#q!w2QMLqdDuqRB|Cc
z;3vC2Xr`us2UB@rKunF?q2F+|?eZ5M`Sv+QV>T#eCuhr;1b%W{NFZvcr?K%K$y7y&
zEiVGKypZkydmcJeD167tTq#j${A^Uik8GLEygD#({_;<@Jm0umi+YQt2l-;)0Gdf+
zA(%ce!=uU5%L*}#WGoNntSH)36Az3pUmlY|)mP2qt~vKYN^>=c>JBc0@4&y2;dsuq
z%M3+(@P8d2S99b<&vVDcV;M#qd*jpswD60rMqQ06X_c)Cuwxrsaw$rMEz?@!jLAJA
z926K%hJXd1^UzMhK@Z^egLO>5>pj=4{h(v~vL
z7o_fPA0_eWiWpk&Rt<9nbJLvkgUro*TZ&$!%m@G0gPNxSujS+7{m_aZv*)mZ4m@YJ
zEr79YZ*;iM829DK9Luyja(}D~xTfDf3iSC|D^bo(p9V2(e+TccDR(Fj%r?w|sP*if
z%5|I3MX&J%jJ;tx=!_^v5L@hMo&5c>-ZD$M_}kYVtQVKd9%!;_nRC}He}F5(TZDjf--KYXJBZiN^+!T;Rg0Q
zhGN4L6N0y3%;{e@qoCE6zg0Z&1p8|;LDVVs&WmD{|8V4ttMW~$A1{|K#m7qE
zwn#z8DiiiDwD3ma==_dT-*r*4fxPsceU6&4Zy};D>qEa{wOZjsSd^uv%je-3fN=%P
zf~x7PPB`uv9Y8$^NbzfoC}`@R`IYyItx1p>8yF+yE#-g6y%#>aSa`%u#b1^aq0E6O
zJp0ttMu1xb{Pt0w+Y^7A{zCjno$x#Ux;j@2rX64%fTP;9yol)_?91;lvpQA<)cUeF
zny&9M&*jKC$cS0Y6m--~4HA>?fHK6rf$aP6v12Yjv7pyCh>VI{HHwr2>>%Xy=J*KV
z>EIKs-ho{1*+;C+pHWLOQeVo$tp=TDB?4{X=k0;4ktL98HyTfPG)H(@*t!*w*IwkLDY$xMC7>#K$
zfLieEPr>HDE7DDx(=NNPS)SVhE{76E1JLOUF^13z(Gex(BRqjqtX+fyz{j}w1adIV
zlcB^IoA}``axiTG!wrElfm4?*0w*t+Ns=jR6$o}>%|KN+^WYNGi&0UanPn1oNvdiq$^q1Z&zr+e9lVGMcPLZjI>bgyX(jCma!1_GL7b+joLA3JI($7;B7I+)#n7|^5k4!
z=vo!fJj6yqvg;Uw6p`srSNw6!6S@9G_LzX1dB|0N@$DL?Y3*Y=yS$AF&+m=`dy5(RVc3g=)5d!Gp3@&5$2p2Xk$J5o;M?1_qp
zCV{=)X;0lTUI&a_98P0~5B&|}&|aa_fvDVfs~#qPB_(^CTW(cf3pR_&IAyW5q>mh(
z@)dREa;~H;xEhkaW5W2F0?usDD1B}lVqXOoWDw&O@fxrxG+sN{JTOAk_gj{=ph-Ir
zfdl(?y3i*JX?!PDWSP*AS-LUHjWyN*2&Fy+q6xCiK++jNL3J)synqOlcM$RD(B
z){C18@v^4$7dL7%D}FfbPWZX^^e$omSz-Ru2Heq%ul&Y?2r*jymLF2N_DGiWpW@&8
z$O%$yBc^ttnwOxXBB?^MiU>{33D%ALZuXXs=pu8i@1yz!XHQb;MeB!SKZmslSaWH_
zwuZ|df^%%m@>8`hU~++g+g1B@*`^~Pb$y|NO~fFhF6T9$J)=VGSm$W)3iLqo-!6$0
zfz=hR_0!tYr%k`j8{OT`;7ss!>NkQ~*u*L9ygN;9me^kAQx^8(kQg;&uYr;a`(qkZ
zl&R!u#&r4i7qq51{QJNtpFT$^qZ&qTHjJ$#|I}N-op8pUZ%?#{uKzW!*VV(^!&1^~
zb7mXb3)?l}S`60tb-@Pt$(!i}A>e#jHsvpuc!94ne}*#7Pi~`
zCBCTXy}sn^zGz=*#iN+92-;~Zl0igHu4*GHue}+I$giUXk
zPMp85ANXY2B=9p5s5X(oyoCzPc)zLjg|}Cj-#O~nD&$BGX9=JUq(LNOJW!|c_X>AB
zw)Q7Y#0GJf-Z}pE=;CV@@g4PS+rmKi9?7L6ixWn&WMDa
z&llM&j>a3Fy6A`7p9oSbCk1YGTp8Z>#Ci(HXW?lpBY$fC2W$;C9q>&F78F)!e+%o}3du{mlzvTXyG1XO$o`p~&
z!~l2YhGH;U0yJCAIlD7e{R+0NJ!*_DJVuamGY@KSNOm@V^BT*uNg^-##rZzOT_V?e
zPK7E2$hzv~A?+6;OWIY;8+pL=QW&k5OOxItFkCu3Dn>aRFD)&V(u7;W{P?US3rP|J
zgbQ!{&Cx>%DUUT*8(GI_?rTf$x$HqT45xML^s><<=eIV^888Ocp=6`i8S>qVt301@*VbPG1mMCLl>)!%PSR*
zCmTtb==2gerV_@B?rhGO;e(BTS3oAWJ=J?+45`$uM4TnlM!0_6NPf2xk9fgyXD_dG
zoULWefHEKj;qhnd2_o!I{%BmgH=ghYdY~nfZ$GW7$cy*bD=w#-t@fx%yYhXTyl@V8
zN4%Cch?NB}6mFxS3`t@~8UzEIZn*ua8V95%_F3CcI9d+w8YYr`y7fvx^fY3_kFYhW
z1$Ra8cgw@Oip4Y6+n9iB%$tN@=MA2JYY{cx#uL!4{p_s$p`wS^7S@=f>Op}jj)JuC
zK$xMcS#1n-GWI- pF)N;2H}6!P-SmbJDDkw0>lYKn7at<)@1pty6t1
z5Eza0K~)*gUI#cs#NaOK`YtzT;!%b}G~Fzb8t}mqNVO>M^C0q6g2B?X!_Zjel^>o^EuGH7i-&F%A2po*jtP)^#Le%m6r3MJ8`%1HM`hZTwlp-
zJ$Dk&23lc>q;vJJti3m>+lrqz!w2$bSo|e>F1QO*^L3Z8{M~5Pt$NLP9m(8J+}C-%
zFuloZ8SL{zPY!-~*U0ivzNQu}C0z2X#ni5!v!}wph;OI!Antj(nxT~ImZDji&3mzc
zK`tf5ra2i}wB?<|F1oFl-1K%VZ*=TK8uNhI7Ox@q4f#HN`hDV`RN@(yQ)kcGQ)Qda
zfe&hlFZj*d8&41