@remix-run/router 0.1.0 → 0.2.0-pre.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +12 -0
- package/CHANGELOG.md +14 -0
- package/README.md +44 -96
- package/__tests__/.eslintrc +8 -0
- package/__tests__/TestSequences/EncodedReservedCharacters.ts +27 -0
- package/__tests__/TestSequences/GoBack.ts +43 -0
- package/__tests__/TestSequences/GoForward.ts +70 -0
- package/__tests__/TestSequences/{InitialLocationDefaultKey.d.ts → InitialLocationDefaultKey.ts} +4 -1
- package/__tests__/TestSequences/InitialLocationHasKey.ts +5 -0
- package/__tests__/TestSequences/Listen.ts +10 -0
- package/__tests__/TestSequences/ListenPopOnly.ts +14 -0
- package/__tests__/TestSequences/PushMissingPathname.ts +23 -0
- package/__tests__/TestSequences/PushNewLocation.ts +17 -0
- package/__tests__/TestSequences/PushRelativePathname.ts +23 -0
- package/__tests__/TestSequences/PushRelativePathnameWarning.ts +28 -0
- package/__tests__/TestSequences/PushSamePath.ts +33 -0
- package/__tests__/TestSequences/PushState.ts +16 -0
- package/__tests__/TestSequences/ReplaceNewLocation.ts +26 -0
- package/__tests__/TestSequences/ReplaceSamePath.ts +23 -0
- package/__tests__/TestSequences/ReplaceState.ts +16 -0
- package/__tests__/browser-test.ts +145 -0
- package/__tests__/create-path-test.ts +61 -0
- package/__tests__/custom-environment.js +19 -0
- package/__tests__/hash-base-test.ts +52 -0
- package/__tests__/hash-test.ts +149 -0
- package/__tests__/memory-test.ts +174 -0
- package/__tests__/resolveTo-test.tsx +26 -0
- package/__tests__/router-test.ts +6300 -0
- package/__tests__/setup.ts +15 -0
- package/history.ts +622 -0
- package/index.ts +96 -0
- package/jest-transformer.js +10 -0
- package/jest.config.js +10 -0
- package/node-main.js +7 -0
- package/package.json +4 -1
- package/router.ts +2033 -0
- package/tsconfig.json +19 -0
- package/utils.ts +822 -0
- package/LICENSE.md +0 -22
- package/__tests__/TestSequences/EncodedReservedCharacters.d.ts +0 -2
- package/__tests__/TestSequences/GoBack.d.ts +0 -2
- package/__tests__/TestSequences/GoForward.d.ts +0 -2
- package/__tests__/TestSequences/InitialLocationHasKey.d.ts +0 -2
- package/__tests__/TestSequences/Listen.d.ts +0 -2
- package/__tests__/TestSequences/ListenPopOnly.d.ts +0 -2
- package/__tests__/TestSequences/PushMissingPathname.d.ts +0 -2
- package/__tests__/TestSequences/PushNewLocation.d.ts +0 -2
- package/__tests__/TestSequences/PushRelativePathname.d.ts +0 -2
- package/__tests__/TestSequences/PushRelativePathnameWarning.d.ts +0 -2
- package/__tests__/TestSequences/PushSamePath.d.ts +0 -2
- package/__tests__/TestSequences/PushState.d.ts +0 -2
- package/__tests__/TestSequences/ReplaceNewLocation.d.ts +0 -2
- package/__tests__/TestSequences/ReplaceSamePath.d.ts +0 -2
- package/__tests__/TestSequences/ReplaceState.d.ts +0 -2
- package/__tests__/browser-test.d.ts +0 -4
- package/__tests__/create-path-test.d.ts +0 -1
- package/__tests__/hash-base-test.d.ts +0 -4
- package/__tests__/hash-test.d.ts +0 -4
- package/__tests__/memory-test.d.ts +0 -1
- package/__tests__/router-test.d.ts +0 -2
- package/__tests__/setup.d.ts +0 -1
- package/history.d.ts +0 -226
- package/index.d.ts +0 -11
- package/index.js +0 -2392
- package/index.js.map +0 -1
- package/main.js +0 -19
- package/router.d.ts +0 -298
- package/router.development.js +0 -2226
- package/router.development.js.map +0 -1
- package/router.production.min.js +0 -12
- package/router.production.min.js.map +0 -1
- package/umd/router.development.js +0 -2418
- package/umd/router.development.js.map +0 -1
- package/umd/router.production.min.js +0 -12
- package/umd/router.production.min.js.map +0 -1
- package/utils.d.ts +0 -248
package/.eslintrc
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# @remix-run/router
|
|
2
|
+
|
|
3
|
+
## 0.2.0-pre.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Change `formMethod=GET` to be a loading navigation instead of submitting
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Make `fallbackElement` optional and change type to `ReactNode` (type changes only) (#8896)
|
|
12
|
+
- Properly trigger error boundaries on 404 routes
|
|
13
|
+
- Fix `resolveTo` so that it does not mutate the provided pathname (#8839)
|
|
14
|
+
- Pass fetcher `actionResult` through to `shouldRevalidate` on fetcher submissions
|
package/README.md
CHANGED
|
@@ -12,37 +12,49 @@ packages re-export everything from `@remix-run/router` and `react-router`.
|
|
|
12
12
|
A Router instance can be created using `createRouter`:
|
|
13
13
|
|
|
14
14
|
```js
|
|
15
|
+
// Create and initialize a router. "initialize" contains all side effects
|
|
16
|
+
// including history listeners and kicking off the initial data fetch
|
|
15
17
|
let router = createRouter({
|
|
16
18
|
// Routes array using react-router RouteObject's
|
|
17
19
|
routes,
|
|
18
20
|
// History instance
|
|
19
21
|
history,
|
|
20
|
-
// Callback function executed on every state change
|
|
21
|
-
onChange: (state) => { ... },
|
|
22
22
|
// Optional hydration data for SSR apps
|
|
23
23
|
hydrationData?: HydrationState;
|
|
24
|
-
}
|
|
24
|
+
}).initialize()
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Internally, the Router represents the state in an object of the following format, which is available through `router.state
|
|
27
|
+
Internally, the Router represents the state in an object of the following format, which is available through `router.state`. You can also register a subscriber of the signature `(state: RouterState) => void` to execute when the state updates via `router.subscribe()`;
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
30
|
interface RouterState {
|
|
31
31
|
// The `history` action of the most recently completed navigation
|
|
32
|
-
|
|
32
|
+
historyAction: Action;
|
|
33
33
|
// The current location of the router. During a navigation this reflects
|
|
34
34
|
// the "old" location and is updated upon completion of the navigation
|
|
35
35
|
location: Location;
|
|
36
36
|
// The current set of route matches
|
|
37
|
-
matches:
|
|
37
|
+
matches: DataRouteMatch[];
|
|
38
|
+
// False during the initial data load, true once we have our initial data
|
|
39
|
+
initialized: boolean;
|
|
38
40
|
// The state of the current navigation
|
|
39
41
|
navigation: Navigation;
|
|
42
|
+
// The state of an in-progress router.revalidate() calls
|
|
43
|
+
revalidation: RevalidationState;
|
|
44
|
+
// Scroll position to restore to for the active Location, false if we
|
|
45
|
+
// should not restore,m or null if we don't have a saved position
|
|
46
|
+
// Note: must be enabled via router.enableScrollRestoration()
|
|
47
|
+
restoreScrollPosition: number | false | null;
|
|
48
|
+
// Proxied `resetScroll` value passed to router.navigate() (default true)
|
|
49
|
+
resetScrollPosition: boolean;
|
|
40
50
|
// Data from the loaders for the current matches
|
|
41
51
|
loaderData: RouteData;
|
|
42
52
|
// Data from the action for the current matches
|
|
43
53
|
actionData: RouteData | null;
|
|
44
54
|
// Errors thrown from loaders/actions for the current matches
|
|
45
55
|
errors: RouteData | null;
|
|
56
|
+
// Map of all active fetchers
|
|
57
|
+
fetchers: Map<string, Fetcher>;
|
|
46
58
|
}
|
|
47
59
|
```
|
|
48
60
|
|
|
@@ -52,104 +64,40 @@ All navigations are done through the `router.navigate` API which is overloaded t
|
|
|
52
64
|
|
|
53
65
|
```js
|
|
54
66
|
// Link navigation (pushes onto the history stack by default)
|
|
55
|
-
router.navigate(
|
|
67
|
+
router.navigate("/page");
|
|
56
68
|
|
|
57
69
|
// Link navigation (replacing the history stack)
|
|
58
|
-
router.navigate(
|
|
70
|
+
router.navigate("/page", { replace: true });
|
|
59
71
|
|
|
60
72
|
// Pop navigation (moving backward/forward in the history stack)
|
|
61
73
|
router.navigate(-1);
|
|
62
74
|
|
|
63
|
-
// Form navigation
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
// Form submission navigation
|
|
76
|
+
let formData = new FormData();
|
|
77
|
+
formData.append(key, value);
|
|
78
|
+
router.navigate("/page", {
|
|
79
|
+
formMethod: "post",
|
|
80
|
+
formData,
|
|
67
81
|
});
|
|
68
82
|
```
|
|
69
83
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
Each navigation (link click or form submission) in the Router is reflected via an internal `state.navigation` that indicates the state of the navigation. This concept of a `navigation` is a complex and heavily async bit of logic that is foundational to the Router's ability to manage data loading, submission, error handling, redirects, interruptions, and so on. Due to the user-driven nature of interruptions we don't quite believe it can be modeled as a finite state machine, however we have modeled some of the happy path flows below for clarity.
|
|
73
|
-
|
|
74
|
-
_Note: This does not depict error or interruption flows._
|
|
75
|
-
|
|
76
|
-
```mermaid
|
|
77
|
-
graph LR
|
|
78
|
-
%% Link click
|
|
79
|
-
idle -->|link clicked| loading/normalLoad
|
|
80
|
-
subgraph "<Link> click"
|
|
81
|
-
loading/normalLoad -->|loader redirected| loading/normalRedirect
|
|
82
|
-
loading/normalRedirect --> loading/normalRedirect
|
|
83
|
-
end
|
|
84
|
-
loading/normalLoad -->|loaders completed| idle
|
|
85
|
-
loading/normalRedirect -->|loaders completed| idle
|
|
86
|
-
|
|
87
|
-
%% Form method=get
|
|
88
|
-
idle -->|form method=get| submitting/loaderSubmission
|
|
89
|
-
subgraph "<Form method=get>"
|
|
90
|
-
submitting/loaderSubmission -->|loader redirected| R1[loading/submissionRedirect]
|
|
91
|
-
R1[loading/submissionRedirect] --> R1[loading/submissionRedirect]
|
|
92
|
-
end
|
|
93
|
-
submitting/loaderSubmission -->|loaders completed| idle
|
|
94
|
-
R1[loading/submissionRedirect] -->|loaders completed| idle
|
|
95
|
-
|
|
96
|
-
%% Form method=post
|
|
97
|
-
idle -->|form method=post| submitting/actionSubmission
|
|
98
|
-
subgraph "<Form method=post>"
|
|
99
|
-
submitting/actionSubmission -->|action returned| loading/actionReload
|
|
100
|
-
submitting/actionSubmission -->|action redirected| R2[loading/submissionRedirect]
|
|
101
|
-
loading/actionReload -->|loader redirected| R2[loading/submissionRedirect]
|
|
102
|
-
R2[loading/submissionRedirect] --> R2[loading/submissionRedirect]
|
|
103
|
-
end
|
|
104
|
-
loading/actionReload -->|loaders completed| idle
|
|
105
|
-
R2[loading/submissionRedirect] -->|loaders completed| idle
|
|
106
|
-
|
|
107
|
-
idle -->|fetcher action redirect| R3[loading/submissionRedirect]
|
|
108
|
-
subgraph "Fetcher action redirect"
|
|
109
|
-
R3[loading/submissionRedirect] --> R3[loading/submissionRedirect]
|
|
110
|
-
end
|
|
111
|
-
R3[loading/submissionRedirect] -->|loaders completed| idle
|
|
112
|
-
```
|
|
84
|
+
### Fetchers
|
|
113
85
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
T1{{navigation}} -.-> idle/done
|
|
128
|
-
|
|
129
|
-
idle/init -->|"submit (get)"| submitting/loaderSubmission
|
|
130
|
-
subgraph "Loader Submission"
|
|
131
|
-
submitting/loaderSubmission -.->|"loader redirected"| T2{{navigation}}
|
|
132
|
-
end
|
|
133
|
-
submitting/loaderSubmission -->|loader completed| idle/done
|
|
134
|
-
T2{{navigation}} -.-> idle/done
|
|
135
|
-
|
|
136
|
-
idle/init -->|"submit (post)"| submitting/actionSubmission
|
|
137
|
-
subgraph "Action Submission"
|
|
138
|
-
submitting/actionSubmission -->|action completed| loading/actionReload
|
|
139
|
-
submitting/actionSubmission -->|action redirected| loading/submissionRedirect
|
|
140
|
-
loading/submissionRedirect -.-> T3{{navigation}}
|
|
141
|
-
loading/actionReload -.-> |loaders redirected| T3{{navigation}}
|
|
142
|
-
end
|
|
143
|
-
T3{{navigation}} -.-> idle/done
|
|
144
|
-
loading/actionReload --> |loaders completed| idle/done
|
|
145
|
-
|
|
146
|
-
idle/done -->|"revalidate"| loading/revalidate
|
|
147
|
-
subgraph "Fetcher Revalidation"
|
|
148
|
-
loading/revalidate -.->|loader redirected| T4{{navigation}}
|
|
149
|
-
end
|
|
150
|
-
loading/revalidate -->|loader completed| idle/done
|
|
151
|
-
T4{{navigation}} -.-> idle/done
|
|
152
|
-
|
|
153
|
-
classDef navigation fill:lightgreen;
|
|
154
|
-
class T1,T2,T3,T4 navigation;
|
|
86
|
+
Fetchers are a mechanism to call loaders/actions without triggering a navigation, and are done through the `router.fetch()` API. All fetch calls require a unique key to identify the fetcher.
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
// Execute the loader for /page
|
|
90
|
+
router.fetch("key", "/page");
|
|
91
|
+
|
|
92
|
+
// Submit to the action for /page
|
|
93
|
+
let formData = new FormData();
|
|
94
|
+
formData.append(key, value);
|
|
95
|
+
router.fetch("key", "/page", {
|
|
96
|
+
formMethod: "post",
|
|
97
|
+
formData,
|
|
98
|
+
});
|
|
155
99
|
```
|
|
100
|
+
|
|
101
|
+
## Revalidation
|
|
102
|
+
|
|
103
|
+
By default, active loaders will revalidate after any navigation or fetcher mutation. If you need to kick off a revalidation for other use-cases, you can use `router.revalidate()` to re-execute all active loaders.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { History } from "../../history";
|
|
2
|
+
|
|
3
|
+
export default function EncodeReservedCharacters(history: History) {
|
|
4
|
+
let pathname;
|
|
5
|
+
|
|
6
|
+
// encoded string
|
|
7
|
+
pathname = "/view/%23abc";
|
|
8
|
+
history.replace(pathname);
|
|
9
|
+
expect(history.location).toMatchObject({
|
|
10
|
+
pathname: "/view/%23abc",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// encoded object
|
|
14
|
+
pathname = "/view/%23abc";
|
|
15
|
+
history.replace({ pathname });
|
|
16
|
+
expect(history.location).toMatchObject({
|
|
17
|
+
pathname: "/view/%23abc",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// unencoded string
|
|
21
|
+
pathname = "/view/#abc";
|
|
22
|
+
history.replace(pathname);
|
|
23
|
+
expect(history.location).toMatchObject({
|
|
24
|
+
pathname: "/view/",
|
|
25
|
+
hash: "#abc",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { History } from "../../history";
|
|
2
|
+
|
|
3
|
+
export default async function GoBack(history: History) {
|
|
4
|
+
let spy: jest.SpyInstance = jest.fn();
|
|
5
|
+
|
|
6
|
+
expect(history.location).toMatchObject({
|
|
7
|
+
pathname: "/",
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
history.push("/home");
|
|
11
|
+
expect(history.action).toEqual("PUSH");
|
|
12
|
+
expect(history.location).toMatchObject({
|
|
13
|
+
pathname: "/home",
|
|
14
|
+
});
|
|
15
|
+
expect(spy).not.toHaveBeenCalled();
|
|
16
|
+
|
|
17
|
+
// JSDom doesn't fire the listener synchronously :(
|
|
18
|
+
let promise = new Promise((resolve) => {
|
|
19
|
+
let unlisten = history.listen((...args) => {
|
|
20
|
+
//@ts-expect-error
|
|
21
|
+
spy(...args);
|
|
22
|
+
unlisten();
|
|
23
|
+
resolve(null);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
history.go(-1);
|
|
27
|
+
await promise;
|
|
28
|
+
expect(history.action).toEqual("POP");
|
|
29
|
+
expect(history.location).toMatchObject({
|
|
30
|
+
pathname: "/",
|
|
31
|
+
});
|
|
32
|
+
expect(spy).toHaveBeenCalledWith({
|
|
33
|
+
action: "POP",
|
|
34
|
+
location: {
|
|
35
|
+
hash: "",
|
|
36
|
+
key: expect.any(String),
|
|
37
|
+
pathname: "/",
|
|
38
|
+
search: "",
|
|
39
|
+
state: null,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
expect(spy.mock.calls.length).toBe(1);
|
|
43
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { History } from "../../history";
|
|
2
|
+
|
|
3
|
+
export default async function GoForward(history: History) {
|
|
4
|
+
let spy: jest.SpyInstance = jest.fn();
|
|
5
|
+
|
|
6
|
+
expect(history.location).toMatchObject({
|
|
7
|
+
pathname: "/",
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
history.push("/home");
|
|
11
|
+
expect(history.action).toEqual("PUSH");
|
|
12
|
+
expect(history.location).toMatchObject({
|
|
13
|
+
pathname: "/home",
|
|
14
|
+
});
|
|
15
|
+
expect(spy).not.toHaveBeenCalled();
|
|
16
|
+
|
|
17
|
+
// JSDom doesn't fire the listener synchronously :(
|
|
18
|
+
let promise1 = new Promise((resolve) => {
|
|
19
|
+
let unlisten = history.listen((...args) => {
|
|
20
|
+
//@ts-expect-error
|
|
21
|
+
spy(...args);
|
|
22
|
+
unlisten();
|
|
23
|
+
resolve(null);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
history.go(-1);
|
|
27
|
+
await promise1;
|
|
28
|
+
expect(history.action).toEqual("POP");
|
|
29
|
+
expect(history.location).toMatchObject({
|
|
30
|
+
pathname: "/",
|
|
31
|
+
});
|
|
32
|
+
expect(spy).toHaveBeenCalledWith({
|
|
33
|
+
action: "POP",
|
|
34
|
+
location: {
|
|
35
|
+
hash: "",
|
|
36
|
+
key: expect.any(String),
|
|
37
|
+
pathname: "/",
|
|
38
|
+
search: "",
|
|
39
|
+
state: null,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
expect(spy.mock.calls.length).toBe(1);
|
|
43
|
+
|
|
44
|
+
// JSDom doesn't fire the listener synchronously :(
|
|
45
|
+
let promise2 = new Promise((resolve) => {
|
|
46
|
+
let unlisten = history.listen((...args) => {
|
|
47
|
+
//@ts-expect-error
|
|
48
|
+
spy(...args);
|
|
49
|
+
unlisten();
|
|
50
|
+
resolve(null);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
history.go(1);
|
|
54
|
+
await promise2;
|
|
55
|
+
expect(history.action).toEqual("POP");
|
|
56
|
+
expect(history.location).toMatchObject({
|
|
57
|
+
pathname: "/home",
|
|
58
|
+
});
|
|
59
|
+
expect(spy).toHaveBeenCalledWith({
|
|
60
|
+
action: "POP",
|
|
61
|
+
location: {
|
|
62
|
+
hash: "",
|
|
63
|
+
key: expect.any(String),
|
|
64
|
+
pathname: "/home",
|
|
65
|
+
search: "",
|
|
66
|
+
state: null,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
expect(spy.mock.calls.length).toBe(2);
|
|
70
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { History } from "../../history";
|
|
2
|
+
|
|
3
|
+
export default function ListenPopOnly(history: History) {
|
|
4
|
+
let spy = jest.fn();
|
|
5
|
+
let unlisten = history.listen(spy);
|
|
6
|
+
|
|
7
|
+
history.push("/2");
|
|
8
|
+
expect(history.location.pathname).toBe("/2");
|
|
9
|
+
history.replace("/3");
|
|
10
|
+
expect(history.location.pathname).toBe("/3");
|
|
11
|
+
|
|
12
|
+
expect(spy).not.toHaveBeenCalled();
|
|
13
|
+
unlisten();
|
|
14
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { History } from "../../history";
|
|
2
|
+
|
|
3
|
+
export default function PushMissingPathname(history: History) {
|
|
4
|
+
expect(history.location).toMatchObject({
|
|
5
|
+
pathname: "/",
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
history.push("/home?the=query#the-hash");
|
|
9
|
+
expect(history.action).toBe("PUSH");
|
|
10
|
+
expect(history.location).toMatchObject({
|
|
11
|
+
pathname: "/home",
|
|
12
|
+
search: "?the=query",
|
|
13
|
+
hash: "#the-hash",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
history.push("?another=query#another-hash");
|
|
17
|
+
expect(history.action).toBe("PUSH");
|
|
18
|
+
expect(history.location).toMatchObject({
|
|
19
|
+
pathname: "/home",
|
|
20
|
+
search: "?another=query",
|
|
21
|
+
hash: "#another-hash",
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { History } from "../../history";
|
|
2
|
+
|
|
3
|
+
export default function PushNewLocation(history: History) {
|
|
4
|
+
expect(history.location).toMatchObject({
|
|
5
|
+
pathname: "/",
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
history.push("/home?the=query#the-hash");
|
|
9
|
+
expect(history.action).toBe("PUSH");
|
|
10
|
+
expect(history.location).toMatchObject({
|
|
11
|
+
pathname: "/home",
|
|
12
|
+
search: "?the=query",
|
|
13
|
+
hash: "#the-hash",
|
|
14
|
+
state: null,
|
|
15
|
+
key: expect.any(String),
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { History } from "../../history";
|
|
2
|
+
|
|
3
|
+
export default function PushRelativePathname(history: History) {
|
|
4
|
+
expect(history.location).toMatchObject({
|
|
5
|
+
pathname: "/",
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
history.push("/the/path?the=query#the-hash");
|
|
9
|
+
expect(history.action).toBe("PUSH");
|
|
10
|
+
expect(history.location).toMatchObject({
|
|
11
|
+
pathname: "/the/path",
|
|
12
|
+
search: "?the=query",
|
|
13
|
+
hash: "#the-hash",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
history.push("../other/path?another=query#another-hash");
|
|
17
|
+
expect(history.action).toBe("PUSH");
|
|
18
|
+
expect(history.location).toMatchObject({
|
|
19
|
+
pathname: "/other/path",
|
|
20
|
+
search: "?another=query",
|
|
21
|
+
hash: "#another-hash",
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { History } from "../../history";
|
|
2
|
+
|
|
3
|
+
export default function PushRelativePathnameWarning(history: History) {
|
|
4
|
+
expect(history.location).toMatchObject({
|
|
5
|
+
pathname: "/",
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
history.push("/the/path?the=query#the-hash");
|
|
9
|
+
expect(history.action).toBe("PUSH");
|
|
10
|
+
expect(history.location).toMatchObject({
|
|
11
|
+
pathname: "/the/path",
|
|
12
|
+
search: "?the=query",
|
|
13
|
+
hash: "#the-hash",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
let spy = jest.spyOn(console, "warn").mockImplementation(() => {});
|
|
17
|
+
history.push("../other/path?another=query#another-hash");
|
|
18
|
+
expect(spy).toHaveBeenCalledWith(
|
|
19
|
+
expect.stringContaining("relative pathnames are not supported")
|
|
20
|
+
);
|
|
21
|
+
spy.mockReset();
|
|
22
|
+
|
|
23
|
+
expect(history.location).toMatchObject({
|
|
24
|
+
pathname: "../other/path",
|
|
25
|
+
search: "?another=query",
|
|
26
|
+
hash: "#another-hash",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { History } from "../../history";
|
|
2
|
+
|
|
3
|
+
export default async function PushSamePath(history: History) {
|
|
4
|
+
expect(history.location).toMatchObject({
|
|
5
|
+
pathname: "/",
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
history.push("/home");
|
|
9
|
+
expect(history.action).toBe("PUSH");
|
|
10
|
+
expect(history.location).toMatchObject({
|
|
11
|
+
pathname: "/home",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
history.push("/home");
|
|
15
|
+
expect(history.action).toBe("PUSH");
|
|
16
|
+
expect(history.location).toMatchObject({
|
|
17
|
+
pathname: "/home",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// JSDom doesn't fire the listener synchronously :(
|
|
21
|
+
let promise = new Promise((resolve) => {
|
|
22
|
+
let unlisten = history.listen(() => {
|
|
23
|
+
unlisten();
|
|
24
|
+
resolve(null);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
history.go(-1);
|
|
28
|
+
await promise;
|
|
29
|
+
expect(history.action).toBe("POP");
|
|
30
|
+
expect(history.location).toMatchObject({
|
|
31
|
+
pathname: "/home",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { History } from "../../history";
|
|
2
|
+
|
|
3
|
+
export default function PushState(history: History) {
|
|
4
|
+
expect(history.location).toMatchObject({
|
|
5
|
+
pathname: "/",
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
history.push("/home?the=query#the-hash", { the: "state" });
|
|
9
|
+
expect(history.action).toBe("PUSH");
|
|
10
|
+
expect(history.location).toMatchObject({
|
|
11
|
+
pathname: "/home",
|
|
12
|
+
search: "?the=query",
|
|
13
|
+
hash: "#the-hash",
|
|
14
|
+
state: { the: "state" },
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { History } from "../../history";
|
|
2
|
+
|
|
3
|
+
export default function ReplaceNewLocation(history: History) {
|
|
4
|
+
expect(history.location).toMatchObject({
|
|
5
|
+
pathname: "/",
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
history.replace("/home?the=query#the-hash");
|
|
9
|
+
expect(history.action).toBe("REPLACE");
|
|
10
|
+
expect(history.location).toMatchObject({
|
|
11
|
+
pathname: "/home",
|
|
12
|
+
search: "?the=query",
|
|
13
|
+
hash: "#the-hash",
|
|
14
|
+
state: null,
|
|
15
|
+
key: expect.any(String),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
history.replace("/");
|
|
19
|
+
expect(history.action).toBe("REPLACE");
|
|
20
|
+
expect(history.location).toMatchObject({
|
|
21
|
+
pathname: "/",
|
|
22
|
+
search: "",
|
|
23
|
+
state: null,
|
|
24
|
+
key: expect.any(String),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { History } from "../../history";
|
|
2
|
+
|
|
3
|
+
export default function ReplaceSamePath(history: History) {
|
|
4
|
+
expect(history.location).toMatchObject({
|
|
5
|
+
pathname: "/",
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
history.replace("/home");
|
|
9
|
+
expect(history.action).toBe("REPLACE");
|
|
10
|
+
expect(history.location).toMatchObject({
|
|
11
|
+
pathname: "/home",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
let prevLocation = history.location;
|
|
15
|
+
|
|
16
|
+
history.replace("/home");
|
|
17
|
+
expect(history.action).toBe("REPLACE");
|
|
18
|
+
expect(history.location).toMatchObject({
|
|
19
|
+
pathname: "/home",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect(history.location).not.toBe(prevLocation);
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { History } from "../../history";
|
|
2
|
+
|
|
3
|
+
export default function ReplaceState(history: History) {
|
|
4
|
+
expect(history.location).toMatchObject({
|
|
5
|
+
pathname: "/",
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
history.replace("/home?the=query#the-hash", { the: "state" });
|
|
9
|
+
expect(history.action).toBe("REPLACE");
|
|
10
|
+
expect(history.location).toMatchObject({
|
|
11
|
+
pathname: "/home",
|
|
12
|
+
search: "?the=query",
|
|
13
|
+
hash: "#the-hash",
|
|
14
|
+
state: { the: "state" },
|
|
15
|
+
});
|
|
16
|
+
}
|