@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.
Files changed (76) hide show
  1. package/.eslintrc +12 -0
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +44 -96
  4. package/__tests__/.eslintrc +8 -0
  5. package/__tests__/TestSequences/EncodedReservedCharacters.ts +27 -0
  6. package/__tests__/TestSequences/GoBack.ts +43 -0
  7. package/__tests__/TestSequences/GoForward.ts +70 -0
  8. package/__tests__/TestSequences/{InitialLocationDefaultKey.d.ts → InitialLocationDefaultKey.ts} +4 -1
  9. package/__tests__/TestSequences/InitialLocationHasKey.ts +5 -0
  10. package/__tests__/TestSequences/Listen.ts +10 -0
  11. package/__tests__/TestSequences/ListenPopOnly.ts +14 -0
  12. package/__tests__/TestSequences/PushMissingPathname.ts +23 -0
  13. package/__tests__/TestSequences/PushNewLocation.ts +17 -0
  14. package/__tests__/TestSequences/PushRelativePathname.ts +23 -0
  15. package/__tests__/TestSequences/PushRelativePathnameWarning.ts +28 -0
  16. package/__tests__/TestSequences/PushSamePath.ts +33 -0
  17. package/__tests__/TestSequences/PushState.ts +16 -0
  18. package/__tests__/TestSequences/ReplaceNewLocation.ts +26 -0
  19. package/__tests__/TestSequences/ReplaceSamePath.ts +23 -0
  20. package/__tests__/TestSequences/ReplaceState.ts +16 -0
  21. package/__tests__/browser-test.ts +145 -0
  22. package/__tests__/create-path-test.ts +61 -0
  23. package/__tests__/custom-environment.js +19 -0
  24. package/__tests__/hash-base-test.ts +52 -0
  25. package/__tests__/hash-test.ts +149 -0
  26. package/__tests__/memory-test.ts +174 -0
  27. package/__tests__/resolveTo-test.tsx +26 -0
  28. package/__tests__/router-test.ts +6300 -0
  29. package/__tests__/setup.ts +15 -0
  30. package/history.ts +622 -0
  31. package/index.ts +96 -0
  32. package/jest-transformer.js +10 -0
  33. package/jest.config.js +10 -0
  34. package/node-main.js +7 -0
  35. package/package.json +4 -1
  36. package/router.ts +2033 -0
  37. package/tsconfig.json +19 -0
  38. package/utils.ts +822 -0
  39. package/LICENSE.md +0 -22
  40. package/__tests__/TestSequences/EncodedReservedCharacters.d.ts +0 -2
  41. package/__tests__/TestSequences/GoBack.d.ts +0 -2
  42. package/__tests__/TestSequences/GoForward.d.ts +0 -2
  43. package/__tests__/TestSequences/InitialLocationHasKey.d.ts +0 -2
  44. package/__tests__/TestSequences/Listen.d.ts +0 -2
  45. package/__tests__/TestSequences/ListenPopOnly.d.ts +0 -2
  46. package/__tests__/TestSequences/PushMissingPathname.d.ts +0 -2
  47. package/__tests__/TestSequences/PushNewLocation.d.ts +0 -2
  48. package/__tests__/TestSequences/PushRelativePathname.d.ts +0 -2
  49. package/__tests__/TestSequences/PushRelativePathnameWarning.d.ts +0 -2
  50. package/__tests__/TestSequences/PushSamePath.d.ts +0 -2
  51. package/__tests__/TestSequences/PushState.d.ts +0 -2
  52. package/__tests__/TestSequences/ReplaceNewLocation.d.ts +0 -2
  53. package/__tests__/TestSequences/ReplaceSamePath.d.ts +0 -2
  54. package/__tests__/TestSequences/ReplaceState.d.ts +0 -2
  55. package/__tests__/browser-test.d.ts +0 -4
  56. package/__tests__/create-path-test.d.ts +0 -1
  57. package/__tests__/hash-base-test.d.ts +0 -4
  58. package/__tests__/hash-test.d.ts +0 -4
  59. package/__tests__/memory-test.d.ts +0 -1
  60. package/__tests__/router-test.d.ts +0 -2
  61. package/__tests__/setup.d.ts +0 -1
  62. package/history.d.ts +0 -226
  63. package/index.d.ts +0 -11
  64. package/index.js +0 -2392
  65. package/index.js.map +0 -1
  66. package/main.js +0 -19
  67. package/router.d.ts +0 -298
  68. package/router.development.js +0 -2226
  69. package/router.development.js.map +0 -1
  70. package/router.production.min.js +0 -12
  71. package/router.production.min.js.map +0 -1
  72. package/umd/router.development.js +0 -2418
  73. package/umd/router.development.js.map +0 -1
  74. package/umd/router.production.min.js +0 -12
  75. package/umd/router.production.min.js.map +0 -1
  76. package/utils.d.ts +0 -248
package/.eslintrc ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "env": {
3
+ "browser": true,
4
+ "commonjs": true
5
+ },
6
+ "globals": {
7
+ "__DEV__": true
8
+ },
9
+ "rules": {
10
+ "strict": 0
11
+ }
12
+ }
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` or via the `onChange` callback function:
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
- action: Action;
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: RouteMatch[];
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('/page');
67
+ router.navigate("/page");
56
68
 
57
69
  // Link navigation (replacing the history stack)
58
- router.navigate('/page', { replace: true });
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
- router.navigate('/page', {
65
- formMethod: 'GET',
66
- formData: new FormData(...),
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
- ## Navigation Flows
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 "&lt;Link&gt; 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 "&lt;Form method=get&gt;"
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 "&lt;Form method=post&gt;"
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
- ## Fetcher Flows
115
-
116
- Fetcher submissions and loads in the Router can each have their own internal states, indicated on `fetcher.state` and `fetcher.type`. As with navigations, these states are 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.
117
-
118
- _Note: This does not depict error or interruption flows, nor the ability to re-use fetchers once they've reached `idle/done`._
119
-
120
- ```mermaid
121
- graph LR
122
- idle/init -->|"load"| loading/normalLoad
123
- subgraph "Normal Fetch"
124
- loading/normalLoad -.->|loader redirected| T1{{navigation}}
125
- end
126
- loading/normalLoad -->|loader completed| idle/done
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,8 @@
1
+ {
2
+ "env": {
3
+ "jest": true
4
+ },
5
+ "rules": {
6
+ "no-console": 0
7
+ }
8
+ }
@@ -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
+ }
@@ -1,2 +1,5 @@
1
1
  import type { History } from "../../history";
2
- export default function InitialLocationDefaultKey(history: History): void;
2
+
3
+ export default function InitialLocationDefaultKey(history: History) {
4
+ expect(history.location.key).toBe("default");
5
+ }
@@ -0,0 +1,5 @@
1
+ import type { History } from "../../history";
2
+
3
+ export default function InitialLocationHasKey(history: History) {
4
+ expect(history.location.key).toBeTruthy();
5
+ }
@@ -0,0 +1,10 @@
1
+ import type { History } from "../../history";
2
+
3
+ export default function Listen(history: History) {
4
+ let spy = jest.fn();
5
+ let unlisten = history.listen(spy);
6
+
7
+ expect(spy).not.toHaveBeenCalled();
8
+
9
+ unlisten();
10
+ }
@@ -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
+ }