jazz-tools 0.19.8 → 0.19.11
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/.turbo/turbo-build.log +56 -50
- package/CHANGELOG.md +30 -3
- package/dist/{chunk-2S3Z2CN6.js → chunk-HX5S6W5E.js} +372 -103
- package/dist/chunk-HX5S6W5E.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/inspector/account-switcher.d.ts +4 -0
- package/dist/inspector/account-switcher.d.ts.map +1 -0
- package/dist/inspector/chunk-C6BJPHBQ.js +4096 -0
- package/dist/inspector/chunk-C6BJPHBQ.js.map +1 -0
- package/dist/inspector/contexts/node.d.ts +19 -0
- package/dist/inspector/contexts/node.d.ts.map +1 -0
- package/dist/inspector/{custom-element-P76EIWEV.js → custom-element-GJVBPZES.js} +1011 -884
- package/dist/inspector/custom-element-GJVBPZES.js.map +1 -0
- package/dist/inspector/{viewer/new-app.d.ts → in-app.d.ts} +3 -3
- package/dist/inspector/in-app.d.ts.map +1 -0
- package/dist/inspector/index.d.ts +0 -11
- package/dist/inspector/index.d.ts.map +1 -1
- package/dist/inspector/index.js +56 -3910
- package/dist/inspector/index.js.map +1 -1
- package/dist/inspector/pages/home.d.ts +2 -0
- package/dist/inspector/pages/home.d.ts.map +1 -0
- package/dist/inspector/register-custom-element.js +1 -1
- package/dist/inspector/router/context.d.ts +12 -0
- package/dist/inspector/router/context.d.ts.map +1 -0
- package/dist/inspector/router/hash-router.d.ts +7 -0
- package/dist/inspector/router/hash-router.d.ts.map +1 -0
- package/dist/inspector/router/in-memory-router.d.ts +7 -0
- package/dist/inspector/router/in-memory-router.d.ts.map +1 -0
- package/dist/inspector/router/index.d.ts +5 -0
- package/dist/inspector/router/index.d.ts.map +1 -0
- package/dist/inspector/standalone.d.ts +6 -0
- package/dist/inspector/standalone.d.ts.map +1 -0
- package/dist/inspector/standalone.js +420 -0
- package/dist/inspector/standalone.js.map +1 -0
- package/dist/inspector/tests/router/hash-router.test.d.ts +2 -0
- package/dist/inspector/tests/router/hash-router.test.d.ts.map +1 -0
- package/dist/inspector/tests/router/in-memory-router.test.d.ts +2 -0
- package/dist/inspector/tests/router/in-memory-router.test.d.ts.map +1 -0
- package/dist/inspector/ui/modal.d.ts +1 -0
- package/dist/inspector/ui/modal.d.ts.map +1 -1
- package/dist/inspector/viewer/breadcrumbs.d.ts +1 -7
- package/dist/inspector/viewer/breadcrumbs.d.ts.map +1 -1
- package/dist/inspector/viewer/header.d.ts +7 -0
- package/dist/inspector/viewer/header.d.ts.map +1 -0
- package/dist/inspector/viewer/page-stack.d.ts +4 -13
- package/dist/inspector/viewer/page-stack.d.ts.map +1 -1
- package/dist/inspector/viewer/page.d.ts.map +1 -1
- package/dist/react/hooks.d.ts +1 -1
- package/dist/react/hooks.d.ts.map +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +5 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react-core/hooks.d.ts +59 -0
- package/dist/react-core/hooks.d.ts.map +1 -1
- package/dist/react-core/index.js +124 -36
- package/dist/react-core/index.js.map +1 -1
- package/dist/react-core/tests/testUtils.d.ts +1 -0
- package/dist/react-core/tests/testUtils.d.ts.map +1 -1
- package/dist/react-core/tests/useSuspenseAccount.test.d.ts +2 -0
- package/dist/react-core/tests/useSuspenseAccount.test.d.ts.map +1 -0
- package/dist/react-core/tests/useSuspenseCoState.test.d.ts +2 -0
- package/dist/react-core/tests/useSuspenseCoState.test.d.ts.map +1 -0
- package/dist/react-core/use.d.ts +3 -0
- package/dist/react-core/use.d.ts.map +1 -0
- package/dist/react-native/index.js +5 -1
- package/dist/react-native/index.js.map +1 -1
- package/dist/react-native-core/crypto/RNCrypto.d.ts +2 -0
- package/dist/react-native-core/crypto/RNCrypto.d.ts.map +1 -0
- package/dist/react-native-core/crypto/RNCrypto.js +3 -0
- package/dist/react-native-core/crypto/RNCrypto.js.map +1 -0
- package/dist/react-native-core/hooks.d.ts +1 -1
- package/dist/react-native-core/hooks.d.ts.map +1 -1
- package/dist/react-native-core/index.js +5 -1
- package/dist/react-native-core/index.js.map +1 -1
- package/dist/react-native-core/platform.d.ts +2 -1
- package/dist/react-native-core/platform.d.ts.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/tools/coValues/account.d.ts +7 -1
- package/dist/tools/coValues/account.d.ts.map +1 -1
- package/dist/tools/coValues/interfaces.d.ts +1 -1
- package/dist/tools/coValues/interfaces.d.ts.map +1 -1
- package/dist/tools/implementation/ContextManager.d.ts +3 -0
- package/dist/tools/implementation/ContextManager.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts +8 -1
- package/dist/tools/implementation/zodSchema/schemaTypes/AccountSchema.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/zodCo.d.ts.map +1 -1
- package/dist/tools/subscribe/CoValueCoreSubscription.d.ts +8 -22
- package/dist/tools/subscribe/CoValueCoreSubscription.d.ts.map +1 -1
- package/dist/tools/subscribe/SubscriptionCache.d.ts +51 -0
- package/dist/tools/subscribe/SubscriptionCache.d.ts.map +1 -0
- package/dist/tools/subscribe/SubscriptionScope.d.ts +17 -1
- package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
- package/dist/tools/subscribe/utils.d.ts +9 -1
- package/dist/tools/subscribe/utils.d.ts.map +1 -1
- package/dist/tools/testing.d.ts +2 -2
- package/dist/tools/testing.d.ts.map +1 -1
- package/dist/tools/tests/SubscriptionCache.test.d.ts +2 -0
- package/dist/tools/tests/SubscriptionCache.test.d.ts.map +1 -0
- package/package.json +18 -6
- package/src/inspector/account-switcher.tsx +440 -0
- package/src/inspector/contexts/node.tsx +129 -0
- package/src/inspector/custom-element.tsx +2 -2
- package/src/inspector/in-app.tsx +61 -0
- package/src/inspector/index.tsx +2 -22
- package/src/inspector/pages/home.tsx +77 -0
- package/src/inspector/router/context.ts +21 -0
- package/src/inspector/router/hash-router.tsx +128 -0
- package/src/inspector/{viewer/use-page-path.ts → router/in-memory-router.tsx} +31 -29
- package/src/inspector/router/index.ts +4 -0
- package/src/inspector/standalone.tsx +60 -0
- package/src/inspector/tests/router/hash-router.test.tsx +847 -0
- package/src/inspector/tests/router/in-memory-router.test.tsx +724 -0
- package/src/inspector/ui/modal.tsx +5 -2
- package/src/inspector/viewer/breadcrumbs.tsx +5 -11
- package/src/inspector/viewer/header.tsx +67 -0
- package/src/inspector/viewer/page-stack.tsx +18 -26
- package/src/inspector/viewer/page.tsx +0 -1
- package/src/react/hooks.tsx +2 -0
- package/src/react/index.ts +1 -14
- package/src/react-core/hooks.ts +167 -18
- package/src/react-core/tests/createCoValueSubscriptionContext.test.tsx +18 -8
- package/src/react-core/tests/testUtils.tsx +67 -5
- package/src/react-core/tests/useCoState.test.ts +3 -7
- package/src/react-core/tests/useSubscriptionSelector.test.ts +3 -7
- package/src/react-core/tests/useSuspenseAccount.test.tsx +343 -0
- package/src/react-core/tests/useSuspenseCoState.test.tsx +1182 -0
- package/src/react-core/use.ts +46 -0
- package/src/react-native-core/crypto/RNCrypto.ts +1 -0
- package/src/react-native-core/hooks.tsx +2 -0
- package/src/react-native-core/platform.ts +2 -1
- package/src/tools/coValues/account.ts +13 -2
- package/src/tools/coValues/interfaces.ts +2 -3
- package/src/tools/implementation/ContextManager.ts +13 -0
- package/src/tools/implementation/zodSchema/schemaTypes/AccountSchema.ts +8 -1
- package/src/tools/subscribe/CoValueCoreSubscription.ts +71 -100
- package/src/tools/subscribe/SubscriptionCache.ts +272 -0
- package/src/tools/subscribe/SubscriptionScope.ts +113 -7
- package/src/tools/subscribe/utils.ts +77 -0
- package/src/tools/testing.ts +0 -3
- package/src/tools/tests/CoValueCoreSubscription.test.ts +46 -12
- package/src/tools/tests/ContextManager.test.ts +85 -0
- package/src/tools/tests/SubscriptionCache.test.ts +237 -0
- package/src/tools/tests/account.test.ts +11 -4
- package/src/tools/tests/coMap.test.ts +5 -7
- package/src/tools/tests/schema.resolved.test.ts +3 -3
- package/tsup.config.ts +2 -0
- package/dist/chunk-2S3Z2CN6.js.map +0 -1
- package/dist/inspector/custom-element-P76EIWEV.js.map +0 -1
- package/dist/inspector/viewer/new-app.d.ts.map +0 -1
- package/dist/inspector/viewer/use-page-path.d.ts +0 -10
- package/dist/inspector/viewer/use-page-path.d.ts.map +0 -1
- package/src/inspector/viewer/new-app.tsx +0 -156
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { act, renderHook, screen, waitFor } from "@testing-library/react";
|
|
4
|
+
import React, { type PropsWithChildren } from "react";
|
|
5
|
+
import { HashRouterProvider } from "../../router/hash-router.js";
|
|
6
|
+
import { useRouter } from "../../router/context.js";
|
|
7
|
+
import type { PageInfo } from "../../viewer/types.js";
|
|
8
|
+
import type { CoID, RawCoValue } from "cojson";
|
|
9
|
+
|
|
10
|
+
function Wrapper({ children }: PropsWithChildren) {
|
|
11
|
+
return <HashRouterProvider>{children}</HashRouterProvider>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function encodePathToHash(path: PageInfo[]): string {
|
|
15
|
+
return path
|
|
16
|
+
.map((page) => {
|
|
17
|
+
if (page.name && page.name !== "Root") {
|
|
18
|
+
return `${page.coId}:${encodeURIComponent(page.name)}`;
|
|
19
|
+
}
|
|
20
|
+
return page.coId;
|
|
21
|
+
})
|
|
22
|
+
.join("/");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function setHash(path: PageInfo[]) {
|
|
26
|
+
window.history.replaceState({}, "", `#/${encodePathToHash(path)}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("HashRouterProvider", () => {
|
|
30
|
+
afterEach(async () => {
|
|
31
|
+
setHash([]);
|
|
32
|
+
expect(window.location.hash).toBe("#/");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("initialization", () => {
|
|
36
|
+
it("should initialize with empty path when no hash and no defaultPath", async () => {
|
|
37
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
38
|
+
|
|
39
|
+
await waitFor(() => {
|
|
40
|
+
expect(result.current.path).toEqual([]);
|
|
41
|
+
expect(window.location.hash).toBe("#/");
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should initialize with defaultPath when provided", async () => {
|
|
46
|
+
const defaultPath: PageInfo[] = [
|
|
47
|
+
{ coId: "co_test1" as CoID<RawCoValue>, name: "Test1" },
|
|
48
|
+
{ coId: "co_test2" as CoID<RawCoValue>, name: "Test2" },
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
function WrapperWithDefaultPath({ children }: PropsWithChildren) {
|
|
52
|
+
return (
|
|
53
|
+
<HashRouterProvider defaultPath={defaultPath}>
|
|
54
|
+
{children}
|
|
55
|
+
</HashRouterProvider>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const { result } = renderHook(() => useRouter(), {
|
|
60
|
+
wrapper: WrapperWithDefaultPath,
|
|
61
|
+
});
|
|
62
|
+
expect(result.current.path).toEqual(defaultPath);
|
|
63
|
+
await waitFor(() => {
|
|
64
|
+
expect(window.location.hash).toBe(`#/${encodePathToHash(defaultPath)}`);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("should initialize from hash when available", async () => {
|
|
69
|
+
const storedPath: PageInfo[] = [
|
|
70
|
+
{ coId: "co_stored1" as CoID<RawCoValue>, name: "Stored1" },
|
|
71
|
+
];
|
|
72
|
+
await setHash(storedPath);
|
|
73
|
+
|
|
74
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
75
|
+
|
|
76
|
+
await waitFor(() => {
|
|
77
|
+
expect(result.current.path).toEqual(storedPath);
|
|
78
|
+
expect(window.location.hash).toBe(`#/${encodePathToHash(storedPath)}`);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should sync defaultPath over hash when defaultPath is provided", async () => {
|
|
83
|
+
const storedPath: PageInfo[] = [
|
|
84
|
+
{ coId: "co_stored" as CoID<RawCoValue>, name: "Stored" },
|
|
85
|
+
];
|
|
86
|
+
const defaultPath: PageInfo[] = [
|
|
87
|
+
{ coId: "co_default" as CoID<RawCoValue>, name: "Default" },
|
|
88
|
+
];
|
|
89
|
+
await setHash(storedPath);
|
|
90
|
+
|
|
91
|
+
function WrapperWithDefaultPath({ children }: PropsWithChildren) {
|
|
92
|
+
return (
|
|
93
|
+
<HashRouterProvider defaultPath={defaultPath}>
|
|
94
|
+
{children}
|
|
95
|
+
</HashRouterProvider>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const { result } = renderHook(() => useRouter(), {
|
|
100
|
+
wrapper: WrapperWithDefaultPath,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await waitFor(() => {
|
|
104
|
+
expect(result.current.path).toEqual(defaultPath);
|
|
105
|
+
expect(window.location.hash).toBe(`#/${encodePathToHash(defaultPath)}`);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should handle invalid hash gracefully", async () => {
|
|
110
|
+
// The decodePathFromHash function doesn't actually throw errors for invalid formats
|
|
111
|
+
// It just parses what it can. So we test with a malformed hash that might cause issues
|
|
112
|
+
await setHash([
|
|
113
|
+
{
|
|
114
|
+
coId: "invalid:hash:format" as CoID<RawCoValue>,
|
|
115
|
+
name: "Invalid Hash Format",
|
|
116
|
+
},
|
|
117
|
+
]);
|
|
118
|
+
const consoleErrorSpy = vi
|
|
119
|
+
.spyOn(console, "error")
|
|
120
|
+
.mockImplementation(() => {});
|
|
121
|
+
|
|
122
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
123
|
+
|
|
124
|
+
// Wait for initialization
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
// The hash will be parsed, might not be empty
|
|
127
|
+
expect(result.current.path).toBeDefined();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// decodePathFromHash doesn't throw, it just parses segments
|
|
131
|
+
// So we might not get an error, but the path should be valid
|
|
132
|
+
expect(Array.isArray(result.current.path)).toBe(true);
|
|
133
|
+
consoleErrorSpy.mockRestore();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should handle SSR scenario - component initializes with empty path when no defaultPath", async () => {
|
|
137
|
+
// In SSR, window is undefined, so the initial state should be empty array
|
|
138
|
+
// We test this by ensuring the component works correctly without hash
|
|
139
|
+
// Note: We can't actually set window to undefined in happy-dom environment
|
|
140
|
+
// So we just verify it works when hash is empty
|
|
141
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
142
|
+
|
|
143
|
+
// When no hash and no defaultPath, should initialize with empty array
|
|
144
|
+
await waitFor(() => {
|
|
145
|
+
expect(result.current.path).toEqual([]);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should decode hash with names correctly", async () => {
|
|
150
|
+
const path: PageInfo[] = [
|
|
151
|
+
{ coId: "co_test1" as CoID<RawCoValue>, name: "Test Name" },
|
|
152
|
+
{ coId: "co_test2" as CoID<RawCoValue>, name: "Another Name" },
|
|
153
|
+
];
|
|
154
|
+
await setHash(path);
|
|
155
|
+
|
|
156
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
157
|
+
|
|
158
|
+
await waitFor(() => {
|
|
159
|
+
expect(result.current.path).toEqual(path);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("should decode hash without names correctly", async () => {
|
|
164
|
+
const path: PageInfo[] = [
|
|
165
|
+
{ coId: "co_test1" as CoID<RawCoValue> },
|
|
166
|
+
{ coId: "co_test2" as CoID<RawCoValue> },
|
|
167
|
+
];
|
|
168
|
+
setHash(path);
|
|
169
|
+
|
|
170
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
171
|
+
|
|
172
|
+
await waitFor(() => {
|
|
173
|
+
expect(window.location.hash).toBe(`#/${encodePathToHash(path)}`);
|
|
174
|
+
expect(result.current.path).toEqual(path);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("should handle Root name in hash", async () => {
|
|
179
|
+
const path: PageInfo[] = [
|
|
180
|
+
{ coId: "co_test1" as CoID<RawCoValue>, name: "Root" },
|
|
181
|
+
];
|
|
182
|
+
// Root name should not be encoded in hash
|
|
183
|
+
await setHash([{ coId: "co_test1" as CoID<RawCoValue>, name: "Root" }]);
|
|
184
|
+
|
|
185
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
186
|
+
|
|
187
|
+
await waitFor(() => {
|
|
188
|
+
expect(result.current.path[0]?.coId).toBe("co_test1");
|
|
189
|
+
// Root name might be undefined or "Root" depending on implementation
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should update path when defaultPath changes", async () => {
|
|
194
|
+
const initialDefaultPath: PageInfo[] = [
|
|
195
|
+
{ coId: "co_initial" as CoID<RawCoValue>, name: "Initial" },
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
const newPage: PageInfo = {
|
|
199
|
+
coId: "co_page1" as CoID<RawCoValue>,
|
|
200
|
+
name: "Page1",
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const newDefaultPath: PageInfo[] = [
|
|
204
|
+
{ coId: "co_new" as CoID<RawCoValue>, name: "New" },
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
function WrapperWithInitialPath({ children }: PropsWithChildren) {
|
|
208
|
+
const [defaultPath, setDefaultPath] =
|
|
209
|
+
React.useState(initialDefaultPath);
|
|
210
|
+
return (
|
|
211
|
+
<HashRouterProvider defaultPath={defaultPath}>
|
|
212
|
+
{children}
|
|
213
|
+
<button onClick={() => setDefaultPath(newDefaultPath)}>
|
|
214
|
+
Set Default Path
|
|
215
|
+
</button>
|
|
216
|
+
</HashRouterProvider>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const { result } = renderHook(() => useRouter(), {
|
|
221
|
+
wrapper: WrapperWithInitialPath,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
await waitFor(() => {
|
|
225
|
+
expect(result.current.path).toEqual(initialDefaultPath);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
act(() => {
|
|
229
|
+
result.current.addPages([newPage]);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
await waitFor(() => {
|
|
233
|
+
expect(result.current.path).toEqual(
|
|
234
|
+
initialDefaultPath.concat([newPage]),
|
|
235
|
+
);
|
|
236
|
+
expect(window.location.hash).toBe(
|
|
237
|
+
`#/${encodePathToHash(initialDefaultPath.concat([newPage]))}`,
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
act(() => {
|
|
242
|
+
screen.getByRole("button", { name: "Set Default Path" }).click();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
await waitFor(() => {
|
|
246
|
+
expect(result.current.path).toEqual(newDefaultPath);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe("hash persistence", () => {
|
|
252
|
+
it("should persist path changes to hash", async () => {
|
|
253
|
+
await setHash([]);
|
|
254
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
255
|
+
|
|
256
|
+
await waitFor(() => {
|
|
257
|
+
expect(result.current.path).toEqual([]);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const newPages: PageInfo[] = [
|
|
261
|
+
{ coId: "co_new1" as CoID<RawCoValue>, name: "New1" },
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
act(() => {
|
|
265
|
+
result.current.addPages(newPages);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
await waitFor(() => {
|
|
269
|
+
expect(result.current.path).toEqual(newPages);
|
|
270
|
+
const hash = window.location.hash.slice(2); // Remove '#/'
|
|
271
|
+
expect(hash).toBeTruthy();
|
|
272
|
+
const decoded = hash.split("/").map((segment) => {
|
|
273
|
+
const [coId, encodedName] = segment.split(":");
|
|
274
|
+
return {
|
|
275
|
+
coId,
|
|
276
|
+
name: encodedName ? decodeURIComponent(encodedName) : undefined,
|
|
277
|
+
} as PageInfo;
|
|
278
|
+
});
|
|
279
|
+
expect(decoded).toEqual(newPages);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("should update hash when path changes", async () => {
|
|
284
|
+
// Ensure hash is cleared before starting
|
|
285
|
+
await setHash([]);
|
|
286
|
+
|
|
287
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
288
|
+
|
|
289
|
+
await waitFor(() => {
|
|
290
|
+
expect(result.current.path).toEqual([]);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const firstPages: PageInfo[] = [
|
|
294
|
+
{ coId: "co_first" as CoID<RawCoValue>, name: "First" },
|
|
295
|
+
];
|
|
296
|
+
|
|
297
|
+
act(() => {
|
|
298
|
+
result.current.addPages(firstPages);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
await waitFor(() => {
|
|
302
|
+
expect(result.current.path).toEqual(firstPages);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const secondPages: PageInfo[] = [
|
|
306
|
+
{ coId: "co_second" as CoID<RawCoValue>, name: "Second" },
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
act(() => {
|
|
310
|
+
result.current.addPages(secondPages);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
await waitFor(() => {
|
|
314
|
+
expect(result.current.path).toEqual([...firstPages, ...secondPages]);
|
|
315
|
+
const hash = window.location.hash.slice(2);
|
|
316
|
+
const decoded = hash.split("/").map((segment) => {
|
|
317
|
+
const [coId, encodedName] = segment.split(":");
|
|
318
|
+
return {
|
|
319
|
+
coId,
|
|
320
|
+
name: encodedName ? decodeURIComponent(encodedName) : undefined,
|
|
321
|
+
} as PageInfo;
|
|
322
|
+
});
|
|
323
|
+
expect(decoded).toHaveLength(2);
|
|
324
|
+
expect(decoded[0]).toEqual(firstPages[0]);
|
|
325
|
+
expect(decoded[1]).toEqual(secondPages[0]);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe("hashchange event", () => {
|
|
331
|
+
it("should update path when hash changes", async () => {
|
|
332
|
+
// Ensure hash is cleared before starting
|
|
333
|
+
await setHash([]);
|
|
334
|
+
|
|
335
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
336
|
+
|
|
337
|
+
await waitFor(() => {
|
|
338
|
+
expect(result.current.path).toEqual([]);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const newPath: PageInfo[] = [
|
|
342
|
+
{ coId: "co_new" as CoID<RawCoValue>, name: "New" },
|
|
343
|
+
];
|
|
344
|
+
|
|
345
|
+
await setHash(newPath);
|
|
346
|
+
|
|
347
|
+
// Wait for hashchange event to be processed
|
|
348
|
+
await waitFor(
|
|
349
|
+
() => {
|
|
350
|
+
expect(result.current.path).toEqual(newPath);
|
|
351
|
+
},
|
|
352
|
+
{ timeout: 1000 },
|
|
353
|
+
);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("should use defaultPath when hash is cleared", async () => {
|
|
357
|
+
const defaultPath: PageInfo[] = [
|
|
358
|
+
{ coId: "co_default" as CoID<RawCoValue>, name: "Default" },
|
|
359
|
+
];
|
|
360
|
+
|
|
361
|
+
function WrapperWithDefaultPath({ children }: PropsWithChildren) {
|
|
362
|
+
return (
|
|
363
|
+
<HashRouterProvider defaultPath={defaultPath}>
|
|
364
|
+
{children}
|
|
365
|
+
</HashRouterProvider>
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const { result } = renderHook(() => useRouter(), {
|
|
370
|
+
wrapper: WrapperWithDefaultPath,
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
await waitFor(() => {
|
|
374
|
+
expect(result.current.path).toEqual(defaultPath);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
await setHash([]);
|
|
378
|
+
|
|
379
|
+
await waitFor(
|
|
380
|
+
() => {
|
|
381
|
+
expect(result.current.path).toEqual(defaultPath);
|
|
382
|
+
},
|
|
383
|
+
{ timeout: 1000 },
|
|
384
|
+
);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it("should handle hash changes in hashchange event", async () => {
|
|
388
|
+
// Ensure hash is cleared before starting
|
|
389
|
+
await setHash([]);
|
|
390
|
+
|
|
391
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
392
|
+
|
|
393
|
+
await waitFor(() => {
|
|
394
|
+
expect(result.current.path).toEqual([]);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
const newPath: PageInfo[] = [
|
|
398
|
+
{ coId: "co_testhash" as CoID<RawCoValue>, name: "TestHash" },
|
|
399
|
+
];
|
|
400
|
+
|
|
401
|
+
await setHash(newPath);
|
|
402
|
+
|
|
403
|
+
// Wait for hashchange to process
|
|
404
|
+
await waitFor(
|
|
405
|
+
() => {
|
|
406
|
+
expect(result.current.path).toEqual(newPath);
|
|
407
|
+
},
|
|
408
|
+
{ timeout: 1000 },
|
|
409
|
+
);
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe("addPages", () => {
|
|
414
|
+
it("should add pages to the current path", async () => {
|
|
415
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
416
|
+
|
|
417
|
+
await waitFor(() => {
|
|
418
|
+
expect(result.current.path).toEqual([]);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
const newPages: PageInfo[] = [
|
|
422
|
+
{ coId: "co_page1" as CoID<RawCoValue>, name: "Page1" },
|
|
423
|
+
{ coId: "co_page2" as CoID<RawCoValue>, name: "Page2" },
|
|
424
|
+
];
|
|
425
|
+
|
|
426
|
+
act(() => {
|
|
427
|
+
result.current.addPages(newPages);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
await waitFor(() => {
|
|
431
|
+
expect(result.current.path).toEqual(newPages);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it("should append pages to existing path", async () => {
|
|
436
|
+
// Don't use defaultPath here since it will override manual changes
|
|
437
|
+
const initialPath: PageInfo[] = [
|
|
438
|
+
{ coId: "co_initial" as CoID<RawCoValue>, name: "Initial" },
|
|
439
|
+
];
|
|
440
|
+
await setHash(initialPath);
|
|
441
|
+
|
|
442
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
443
|
+
|
|
444
|
+
await waitFor(() => {
|
|
445
|
+
expect(result.current.path).toEqual(initialPath);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const newPages: PageInfo[] = [
|
|
449
|
+
{ coId: "co_new" as CoID<RawCoValue>, name: "New" },
|
|
450
|
+
];
|
|
451
|
+
|
|
452
|
+
act(() => {
|
|
453
|
+
result.current.addPages(newPages);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
await waitFor(() => {
|
|
457
|
+
expect(result.current.path).toHaveLength(2);
|
|
458
|
+
expect(result.current.path[0]).toEqual(initialPath[0]);
|
|
459
|
+
expect(result.current.path[1]).toEqual(newPages[0]);
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it("should handle adding empty array", async () => {
|
|
464
|
+
// Don't use defaultPath here since it will override manual changes
|
|
465
|
+
const initialPath: PageInfo[] = [
|
|
466
|
+
{ coId: "co_initial" as CoID<RawCoValue>, name: "Initial" },
|
|
467
|
+
];
|
|
468
|
+
await setHash(initialPath);
|
|
469
|
+
|
|
470
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
471
|
+
|
|
472
|
+
await waitFor(() => {
|
|
473
|
+
expect(result.current.path).toEqual(initialPath);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
act(() => {
|
|
477
|
+
result.current.addPages([]);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
await waitFor(() => {
|
|
481
|
+
expect(result.current.path).toEqual(initialPath);
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
describe("goToIndex", () => {
|
|
487
|
+
it("should navigate to a specific index", async () => {
|
|
488
|
+
// Don't use defaultPath here since it will override manual changes
|
|
489
|
+
const initialPath: PageInfo[] = [
|
|
490
|
+
{ coId: "co_page1" as CoID<RawCoValue>, name: "Page1" },
|
|
491
|
+
{ coId: "co_page2" as CoID<RawCoValue>, name: "Page2" },
|
|
492
|
+
{ coId: "co_page3" as CoID<RawCoValue>, name: "Page3" },
|
|
493
|
+
];
|
|
494
|
+
|
|
495
|
+
await setHash(initialPath);
|
|
496
|
+
|
|
497
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
498
|
+
|
|
499
|
+
await waitFor(() => {
|
|
500
|
+
expect(result.current.path).toEqual(initialPath);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
act(() => {
|
|
504
|
+
result.current.goToIndex(1);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
await waitFor(() => {
|
|
508
|
+
expect(result.current.path).toHaveLength(2);
|
|
509
|
+
expect(result.current.path[0]).toEqual(initialPath[0]);
|
|
510
|
+
expect(result.current.path[1]).toEqual(initialPath[1]);
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it("should navigate to index 0", async () => {
|
|
515
|
+
// Don't use defaultPath here since it will override manual changes
|
|
516
|
+
const initialPath: PageInfo[] = [
|
|
517
|
+
{ coId: "co_page1" as CoID<RawCoValue>, name: "Page1" },
|
|
518
|
+
{ coId: "co_page2" as CoID<RawCoValue>, name: "Page2" },
|
|
519
|
+
];
|
|
520
|
+
await setHash(initialPath);
|
|
521
|
+
|
|
522
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
523
|
+
|
|
524
|
+
await waitFor(() => {
|
|
525
|
+
expect(result.current.path).toEqual(initialPath);
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
act(() => {
|
|
529
|
+
result.current.goToIndex(0);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
await waitFor(() => {
|
|
533
|
+
expect(result.current.path).toHaveLength(1);
|
|
534
|
+
expect(result.current.path[0]).toEqual(initialPath[0]);
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it("should handle going to last index", async () => {
|
|
539
|
+
const initialPath: PageInfo[] = [
|
|
540
|
+
{ coId: "co_page1" as CoID<RawCoValue>, name: "Page1" },
|
|
541
|
+
{ coId: "co_page2" as CoID<RawCoValue>, name: "Page2" },
|
|
542
|
+
{ coId: "co_page3" as CoID<RawCoValue>, name: "Page3" },
|
|
543
|
+
];
|
|
544
|
+
|
|
545
|
+
function WrapperWithDefaultPath({ children }: PropsWithChildren) {
|
|
546
|
+
return (
|
|
547
|
+
<HashRouterProvider defaultPath={initialPath}>
|
|
548
|
+
{children}
|
|
549
|
+
</HashRouterProvider>
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const { result } = renderHook(() => useRouter(), {
|
|
554
|
+
wrapper: WrapperWithDefaultPath,
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
await waitFor(() => {
|
|
558
|
+
expect(result.current.path).toEqual(initialPath);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
act(() => {
|
|
562
|
+
result.current.goToIndex(2);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
await waitFor(() => {
|
|
566
|
+
expect(result.current.path).toEqual(initialPath);
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it("should handle empty path", async () => {
|
|
571
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
572
|
+
|
|
573
|
+
await waitFor(() => {
|
|
574
|
+
expect(result.current.path).toEqual([]);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
act(() => {
|
|
578
|
+
result.current.goToIndex(0);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
await waitFor(() => {
|
|
582
|
+
expect(result.current.path).toEqual([]);
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
describe("setPage", () => {
|
|
588
|
+
it("should set path to a single page with Root name", async () => {
|
|
589
|
+
// Don't use defaultPath here since it will override manual changes
|
|
590
|
+
const initialPath: PageInfo[] = [
|
|
591
|
+
{ coId: "co_initial" as CoID<RawCoValue>, name: "Initial" },
|
|
592
|
+
{ coId: "co_initial2" as CoID<RawCoValue>, name: "Initial2" },
|
|
593
|
+
];
|
|
594
|
+
await setHash(initialPath);
|
|
595
|
+
|
|
596
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
597
|
+
|
|
598
|
+
await waitFor(() => {
|
|
599
|
+
expect(result.current.path).toEqual(initialPath);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const newCoId = "co_newroot" as CoID<RawCoValue>;
|
|
603
|
+
|
|
604
|
+
act(() => {
|
|
605
|
+
result.current.setPage(newCoId);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
await waitFor(() => {
|
|
609
|
+
expect(result.current.path).toHaveLength(1);
|
|
610
|
+
expect(result.current.path[0]).toEqual({
|
|
611
|
+
coId: newCoId,
|
|
612
|
+
name: "Root",
|
|
613
|
+
});
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it("should replace existing path", async () => {
|
|
618
|
+
// Don't use defaultPath here since it will override manual changes
|
|
619
|
+
const initialPath: PageInfo[] = [
|
|
620
|
+
{ coId: "co_initial" as CoID<RawCoValue>, name: "Initial" },
|
|
621
|
+
{ coId: "co_initial2" as CoID<RawCoValue>, name: "Initial2" },
|
|
622
|
+
{ coId: "co_initial3" as CoID<RawCoValue>, name: "Initial3" },
|
|
623
|
+
];
|
|
624
|
+
await setHash(initialPath);
|
|
625
|
+
|
|
626
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
627
|
+
|
|
628
|
+
await waitFor(() => {
|
|
629
|
+
expect(result.current.path).toEqual(initialPath);
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
const newCoId = "co_newroot" as CoID<RawCoValue>;
|
|
633
|
+
|
|
634
|
+
act(() => {
|
|
635
|
+
result.current.setPage(newCoId);
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
await waitFor(() => {
|
|
639
|
+
expect(result.current.path).toHaveLength(1);
|
|
640
|
+
const firstPage = result.current.path[0];
|
|
641
|
+
expect(firstPage).toBeDefined();
|
|
642
|
+
expect(firstPage?.coId).toBe(newCoId);
|
|
643
|
+
expect(firstPage?.name).toBe("Root");
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
describe("goBack", () => {
|
|
649
|
+
it("should remove the last page from path", async () => {
|
|
650
|
+
// Don't use defaultPath here since it will override manual changes
|
|
651
|
+
const initialPath: PageInfo[] = [
|
|
652
|
+
{ coId: "co_page1" as CoID<RawCoValue>, name: "Page1" },
|
|
653
|
+
{ coId: "co_page2" as CoID<RawCoValue>, name: "Page2" },
|
|
654
|
+
{ coId: "co_page3" as CoID<RawCoValue>, name: "Page3" },
|
|
655
|
+
];
|
|
656
|
+
await setHash(initialPath);
|
|
657
|
+
|
|
658
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
659
|
+
|
|
660
|
+
await waitFor(() => {
|
|
661
|
+
expect(result.current.path).toEqual(initialPath);
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
act(() => {
|
|
665
|
+
result.current.goBack();
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
await waitFor(() => {
|
|
669
|
+
expect(result.current.path).toHaveLength(2);
|
|
670
|
+
expect(result.current.path[0]).toEqual(initialPath[0]);
|
|
671
|
+
expect(result.current.path[1]).toEqual(initialPath[1]);
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it("should handle going back from single page", async () => {
|
|
676
|
+
// Don't use defaultPath here since it will override manual changes
|
|
677
|
+
const initialPath: PageInfo[] = [
|
|
678
|
+
{ coId: "co_page1" as CoID<RawCoValue>, name: "Page1" },
|
|
679
|
+
];
|
|
680
|
+
await setHash(initialPath);
|
|
681
|
+
|
|
682
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
683
|
+
|
|
684
|
+
await waitFor(() => {
|
|
685
|
+
expect(result.current.path).toEqual(initialPath);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
act(() => {
|
|
689
|
+
result.current.goBack();
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
await waitFor(() => {
|
|
693
|
+
expect(result.current.path).toEqual([]);
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it("should handle going back from empty path", async () => {
|
|
698
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
699
|
+
|
|
700
|
+
await waitFor(() => {
|
|
701
|
+
expect(result.current.path).toEqual([]);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
act(() => {
|
|
705
|
+
result.current.goBack();
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
await waitFor(() => {
|
|
709
|
+
expect(result.current.path).toEqual([]);
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it("should handle multiple goBack calls", async () => {
|
|
714
|
+
// Don't use defaultPath here since it will override manual changes
|
|
715
|
+
const initialPath: PageInfo[] = [
|
|
716
|
+
{ coId: "co_page1" as CoID<RawCoValue>, name: "Page1" },
|
|
717
|
+
{ coId: "co_page2" as CoID<RawCoValue>, name: "Page2" },
|
|
718
|
+
{ coId: "co_page3" as CoID<RawCoValue>, name: "Page3" },
|
|
719
|
+
];
|
|
720
|
+
await setHash(initialPath);
|
|
721
|
+
|
|
722
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
723
|
+
|
|
724
|
+
await waitFor(() => {
|
|
725
|
+
expect(result.current.path).toEqual(initialPath);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
act(() => {
|
|
729
|
+
result.current.goBack();
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
await waitFor(() => {
|
|
733
|
+
expect(result.current.path).toHaveLength(2);
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
act(() => {
|
|
737
|
+
result.current.goBack();
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
await waitFor(() => {
|
|
741
|
+
expect(result.current.path).toHaveLength(1);
|
|
742
|
+
expect(result.current.path[0]).toEqual(initialPath[0]);
|
|
743
|
+
});
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
describe("integration scenarios", () => {
|
|
748
|
+
it("should handle complex navigation flow", async () => {
|
|
749
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
750
|
+
|
|
751
|
+
expect(result.current.path).toEqual([]);
|
|
752
|
+
|
|
753
|
+
const page1: PageInfo = {
|
|
754
|
+
coId: "co_page1" as CoID<RawCoValue>,
|
|
755
|
+
name: "Page1",
|
|
756
|
+
};
|
|
757
|
+
const page2: PageInfo = {
|
|
758
|
+
coId: "co_page2" as CoID<RawCoValue>,
|
|
759
|
+
name: "Page2",
|
|
760
|
+
};
|
|
761
|
+
const page3: PageInfo = {
|
|
762
|
+
coId: "co_page3" as CoID<RawCoValue>,
|
|
763
|
+
name: "Page3",
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
act(() => {
|
|
767
|
+
result.current.addPages([page1]);
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
expect(result.current.path).toEqual([page1]);
|
|
771
|
+
|
|
772
|
+
act(() => {
|
|
773
|
+
result.current.addPages([page2]);
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
expect(result.current.path).toEqual([page1, page2]);
|
|
777
|
+
|
|
778
|
+
act(() => {
|
|
779
|
+
result.current.addPages([page3]);
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
expect(result.current.path).toEqual([page1, page2, page3]);
|
|
783
|
+
|
|
784
|
+
act(() => {
|
|
785
|
+
result.current.goBack();
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
expect(result.current.path).toEqual([page1, page2]);
|
|
789
|
+
|
|
790
|
+
act(() => {
|
|
791
|
+
result.current.goToIndex(0);
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
expect(result.current.path).toEqual([page1]);
|
|
795
|
+
|
|
796
|
+
act(() => {
|
|
797
|
+
result.current.setPage("co_newroot" as CoID<RawCoValue>);
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
expect(result.current.path).toEqual([
|
|
801
|
+
{ coId: "co_newroot" as CoID<RawCoValue>, name: "Root" },
|
|
802
|
+
]);
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
it("should persist complex navigation flow to hash", async () => {
|
|
806
|
+
const { result } = renderHook(() => useRouter(), { wrapper: Wrapper });
|
|
807
|
+
|
|
808
|
+
expect(result.current.path).toEqual([]);
|
|
809
|
+
|
|
810
|
+
const page1: PageInfo = {
|
|
811
|
+
coId: "co_page1" as CoID<RawCoValue>,
|
|
812
|
+
name: "Page1",
|
|
813
|
+
};
|
|
814
|
+
const page2: PageInfo = {
|
|
815
|
+
coId: "co_page2" as CoID<RawCoValue>,
|
|
816
|
+
name: "Page2",
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
act(() => {
|
|
820
|
+
result.current.addPages([page1]);
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
expect(result.current.path).toEqual([page1]);
|
|
824
|
+
|
|
825
|
+
act(() => {
|
|
826
|
+
result.current.addPages([page2]);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
expect(result.current.path).toEqual([page1, page2]);
|
|
830
|
+
|
|
831
|
+
act(() => {
|
|
832
|
+
result.current.goBack();
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
expect(result.current.path).toEqual([page1]);
|
|
836
|
+
const hash = window.location.hash.slice(2);
|
|
837
|
+
const decoded = hash.split("/").map((segment) => {
|
|
838
|
+
const [coId, encodedName] = segment.split(":");
|
|
839
|
+
return {
|
|
840
|
+
coId,
|
|
841
|
+
name: encodedName ? decodeURIComponent(encodedName) : undefined,
|
|
842
|
+
} as PageInfo;
|
|
843
|
+
});
|
|
844
|
+
expect(decoded).toEqual([page1]);
|
|
845
|
+
});
|
|
846
|
+
});
|
|
847
|
+
});
|