@esmx/router 3.0.0-rc.17 → 3.0.0-rc.19

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 (155) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +70 -0
  3. package/README.zh-CN.md +70 -0
  4. package/dist/error.d.ts +23 -0
  5. package/dist/error.mjs +61 -0
  6. package/dist/increment-id.d.ts +7 -0
  7. package/dist/increment-id.mjs +11 -0
  8. package/dist/index.d.ts +5 -3
  9. package/dist/index.mjs +14 -3
  10. package/dist/index.test.mjs +8 -0
  11. package/dist/location.d.ts +15 -0
  12. package/dist/location.mjs +53 -0
  13. package/dist/location.test.d.ts +8 -0
  14. package/dist/location.test.mjs +370 -0
  15. package/dist/matcher.d.ts +3 -0
  16. package/dist/matcher.mjs +44 -0
  17. package/dist/matcher.test.mjs +1492 -0
  18. package/dist/micro-app.d.ts +18 -0
  19. package/dist/micro-app.dom.test.d.ts +1 -0
  20. package/dist/micro-app.dom.test.mjs +532 -0
  21. package/dist/micro-app.mjs +80 -0
  22. package/dist/navigation.d.ts +43 -0
  23. package/dist/navigation.mjs +143 -0
  24. package/dist/navigation.test.d.ts +1 -0
  25. package/dist/navigation.test.mjs +681 -0
  26. package/dist/options.d.ts +4 -0
  27. package/dist/options.mjs +88 -0
  28. package/dist/route-task.d.ts +40 -0
  29. package/dist/route-task.mjs +75 -0
  30. package/dist/route-task.test.d.ts +1 -0
  31. package/dist/route-task.test.mjs +673 -0
  32. package/dist/route-transition.d.ts +53 -0
  33. package/dist/route-transition.mjs +307 -0
  34. package/dist/route-transition.test.d.ts +1 -0
  35. package/dist/route-transition.test.mjs +146 -0
  36. package/dist/route.d.ts +72 -0
  37. package/dist/route.mjs +194 -0
  38. package/dist/route.test.d.ts +1 -0
  39. package/dist/route.test.mjs +1664 -0
  40. package/dist/router-back.test.d.ts +1 -0
  41. package/dist/router-back.test.mjs +361 -0
  42. package/dist/router-forward.test.d.ts +1 -0
  43. package/dist/router-forward.test.mjs +376 -0
  44. package/dist/router-go.test.d.ts +1 -0
  45. package/dist/router-go.test.mjs +73 -0
  46. package/dist/router-guards-cleanup.test.d.ts +1 -0
  47. package/dist/router-guards-cleanup.test.mjs +437 -0
  48. package/dist/router-link.d.ts +10 -0
  49. package/dist/router-link.mjs +126 -0
  50. package/dist/router-push.test.d.ts +1 -0
  51. package/dist/router-push.test.mjs +115 -0
  52. package/dist/router-replace.test.d.ts +1 -0
  53. package/dist/router-replace.test.mjs +114 -0
  54. package/dist/router-resolve.test.d.ts +1 -0
  55. package/dist/router-resolve.test.mjs +393 -0
  56. package/dist/router-restart-app.dom.test.d.ts +1 -0
  57. package/dist/router-restart-app.dom.test.mjs +616 -0
  58. package/dist/router-window-navigation.test.d.ts +1 -0
  59. package/dist/router-window-navigation.test.mjs +359 -0
  60. package/dist/router.d.ts +109 -102
  61. package/dist/router.mjs +260 -361
  62. package/dist/types.d.ts +246 -0
  63. package/dist/types.mjs +18 -0
  64. package/dist/util.d.ts +26 -0
  65. package/dist/util.mjs +53 -0
  66. package/dist/util.test.d.ts +1 -0
  67. package/dist/util.test.mjs +1020 -0
  68. package/package.json +10 -13
  69. package/src/error.ts +84 -0
  70. package/src/increment-id.ts +12 -0
  71. package/src/index.test.ts +9 -0
  72. package/src/index.ts +54 -3
  73. package/src/location.test.ts +406 -0
  74. package/src/location.ts +96 -0
  75. package/src/matcher.test.ts +1685 -0
  76. package/src/matcher.ts +59 -0
  77. package/src/micro-app.dom.test.ts +708 -0
  78. package/src/micro-app.ts +101 -0
  79. package/src/navigation.test.ts +858 -0
  80. package/src/navigation.ts +195 -0
  81. package/src/options.ts +131 -0
  82. package/src/route-task.test.ts +901 -0
  83. package/src/route-task.ts +105 -0
  84. package/src/route-transition.test.ts +178 -0
  85. package/src/route-transition.ts +425 -0
  86. package/src/route.test.ts +2014 -0
  87. package/src/route.ts +308 -0
  88. package/src/router-back.test.ts +487 -0
  89. package/src/router-forward.test.ts +506 -0
  90. package/src/router-go.test.ts +91 -0
  91. package/src/router-guards-cleanup.test.ts +595 -0
  92. package/src/router-link.ts +235 -0
  93. package/src/router-push.test.ts +140 -0
  94. package/src/router-replace.test.ts +139 -0
  95. package/src/router-resolve.test.ts +475 -0
  96. package/src/router-restart-app.dom.test.ts +783 -0
  97. package/src/router-window-navigation.test.ts +457 -0
  98. package/src/router.ts +289 -470
  99. package/src/types.ts +341 -0
  100. package/src/util.test.ts +1262 -0
  101. package/src/util.ts +116 -0
  102. package/dist/history/abstract.d.ts +0 -29
  103. package/dist/history/abstract.mjs +0 -107
  104. package/dist/history/base.d.ts +0 -79
  105. package/dist/history/base.mjs +0 -275
  106. package/dist/history/html.d.ts +0 -22
  107. package/dist/history/html.mjs +0 -183
  108. package/dist/history/index.d.ts +0 -7
  109. package/dist/history/index.mjs +0 -16
  110. package/dist/matcher/create-matcher.d.ts +0 -5
  111. package/dist/matcher/create-matcher.mjs +0 -218
  112. package/dist/matcher/create-matcher.spec.mjs +0 -0
  113. package/dist/matcher/index.d.ts +0 -1
  114. package/dist/matcher/index.mjs +0 -1
  115. package/dist/task-pipe/index.d.ts +0 -1
  116. package/dist/task-pipe/index.mjs +0 -1
  117. package/dist/task-pipe/task.d.ts +0 -30
  118. package/dist/task-pipe/task.mjs +0 -66
  119. package/dist/utils/bom.d.ts +0 -5
  120. package/dist/utils/bom.mjs +0 -10
  121. package/dist/utils/encoding.d.ts +0 -48
  122. package/dist/utils/encoding.mjs +0 -44
  123. package/dist/utils/guards.d.ts +0 -9
  124. package/dist/utils/guards.mjs +0 -12
  125. package/dist/utils/index.d.ts +0 -7
  126. package/dist/utils/index.mjs +0 -27
  127. package/dist/utils/path.d.ts +0 -60
  128. package/dist/utils/path.mjs +0 -281
  129. package/dist/utils/path.spec.mjs +0 -27
  130. package/dist/utils/scroll.d.ts +0 -25
  131. package/dist/utils/scroll.mjs +0 -59
  132. package/dist/utils/utils.d.ts +0 -16
  133. package/dist/utils/utils.mjs +0 -11
  134. package/dist/utils/warn.d.ts +0 -2
  135. package/dist/utils/warn.mjs +0 -12
  136. package/src/history/abstract.ts +0 -149
  137. package/src/history/base.ts +0 -408
  138. package/src/history/html.ts +0 -228
  139. package/src/history/index.ts +0 -20
  140. package/src/matcher/create-matcher.spec.ts +0 -3
  141. package/src/matcher/create-matcher.ts +0 -293
  142. package/src/matcher/index.ts +0 -1
  143. package/src/task-pipe/index.ts +0 -1
  144. package/src/task-pipe/task.ts +0 -97
  145. package/src/utils/bom.ts +0 -14
  146. package/src/utils/encoding.ts +0 -153
  147. package/src/utils/guards.ts +0 -25
  148. package/src/utils/index.ts +0 -27
  149. package/src/utils/path.spec.ts +0 -32
  150. package/src/utils/path.ts +0 -417
  151. package/src/utils/scroll.ts +0 -120
  152. package/src/utils/utils.ts +0 -30
  153. package/src/utils/warn.ts +0 -13
  154. /package/dist/{matcher/create-matcher.spec.d.ts → index.test.d.ts} +0 -0
  155. /package/dist/{utils/path.spec.d.ts → matcher.test.d.ts} +0 -0
@@ -0,0 +1,681 @@
1
+ import {
2
+ assert,
3
+ afterEach,
4
+ beforeEach,
5
+ describe,
6
+ expect,
7
+ it,
8
+ test,
9
+ vi
10
+ } from "vitest";
11
+ import { MemoryHistory, Navigation } from "./navigation.mjs";
12
+ import { parsedOptions } from "./options.mjs";
13
+ import { Route } from "./route.mjs";
14
+ import { RouteType, RouterMode } from "./types.mjs";
15
+ const sleep = (ms) => new Promise((s) => setTimeout(s, ms));
16
+ const awaitGo = (history, delta) => {
17
+ const p = Promise.withResolvers();
18
+ const un = history.onPopState(() => {
19
+ un();
20
+ p.resolve();
21
+ });
22
+ history.go(delta);
23
+ return p.promise;
24
+ };
25
+ describe("MemoryHistory", () => {
26
+ test("should initialize with root path", () => {
27
+ const history = new MemoryHistory();
28
+ assert.equal(history.length, 1);
29
+ assert.equal(history.state, null);
30
+ });
31
+ describe("pushState", () => {
32
+ test("should add new entry", () => {
33
+ const history = new MemoryHistory();
34
+ history.pushState({ id: 1 }, "", "/page1");
35
+ assert.equal(history.length, 2);
36
+ assert.deepEqual(history.state, { id: 1 });
37
+ });
38
+ test("should clear forward history when pushing new state", () => {
39
+ const history = new MemoryHistory();
40
+ history.pushState({ id: 1 }, "", "/page1");
41
+ history.pushState({ id: 2 }, "", "/page2");
42
+ history.back();
43
+ history.pushState({ id: 3 }, "", "/page3");
44
+ assert.equal(history.length, 3);
45
+ assert.deepEqual(history.state, { id: 3 });
46
+ });
47
+ });
48
+ describe("replaceState", () => {
49
+ test("should replace current entry", () => {
50
+ const history = new MemoryHistory();
51
+ history.pushState({ id: 1 }, "", "/page1");
52
+ history.replaceState({ id: 2 }, "", "/page1-updated");
53
+ assert.equal(history.length, 2);
54
+ assert.deepEqual(history.state, { id: 2 });
55
+ });
56
+ });
57
+ describe("navigation", () => {
58
+ test("back() should navigate to previous entry", () => {
59
+ const history = new MemoryHistory();
60
+ history.pushState({ id: 1 }, "", "/page1");
61
+ history.pushState({ id: 2 }, "", "/page2");
62
+ history.back();
63
+ assert.deepEqual(history.state, { id: 1 });
64
+ });
65
+ test("forward() should navigate to next entry", () => {
66
+ const history = new MemoryHistory();
67
+ history.pushState({ id: 1 }, "", "/page1");
68
+ history.pushState({ id: 2 }, "", "/page2");
69
+ history.back();
70
+ history.forward();
71
+ assert.deepEqual(history.state, { id: 2 });
72
+ });
73
+ test("go() should navigate to specific delta", () => {
74
+ const history = new MemoryHistory();
75
+ history.pushState({ id: 1 }, "", "/page1");
76
+ history.pushState({ id: 2 }, "", "/page2");
77
+ history.pushState({ id: 3 }, "", "/page3");
78
+ history.go(-2);
79
+ assert.deepEqual(history.state, { id: 1 });
80
+ history.go(2);
81
+ assert.deepEqual(history.state, { id: 3 });
82
+ });
83
+ test("go() should not navigate beyond bounds", () => {
84
+ const history = new MemoryHistory();
85
+ history.pushState({ id: 1 }, "", "/page1");
86
+ const originalState = history.state;
87
+ history.go(-2);
88
+ assert.deepEqual(history.state, originalState);
89
+ history.go(2);
90
+ assert.deepEqual(history.state, originalState);
91
+ });
92
+ test("go() should do nothing when delta is 0 or undefined", () => {
93
+ const history = new MemoryHistory();
94
+ history.pushState({ id: 1 }, "", "/page1");
95
+ const originalState = history.state;
96
+ history.go(0);
97
+ assert.deepEqual(history.state, originalState);
98
+ history.go();
99
+ assert.deepEqual(history.state, originalState);
100
+ });
101
+ });
102
+ });
103
+ describe.concurrent("subscribeMemory", () => {
104
+ test("should preserve original go functionality after subscription", async () => {
105
+ const history = new MemoryHistory();
106
+ history.pushState({ id: 1 }, "", "/page1");
107
+ history.pushState({ id: 2 }, "", "/page2");
108
+ void history.onPopState(() => {
109
+ });
110
+ history.go(-1);
111
+ await sleep();
112
+ assert.equal(history.url, "/page1");
113
+ assert.deepEqual(history.state, { id: 1 });
114
+ history.go(1);
115
+ await sleep();
116
+ assert.equal(history.url, "/page2");
117
+ assert.deepEqual(history.state, { id: 2 });
118
+ });
119
+ test("should trigger callback only on actual navigation (like popstate)", async () => {
120
+ const history = new MemoryHistory();
121
+ history.pushState({ id: 1 }, "", "/page1");
122
+ history.pushState({ id: 2 }, "", "/page2");
123
+ const callbacks = [];
124
+ void history.onPopState((url, state) => {
125
+ callbacks.push({ url, state });
126
+ });
127
+ await awaitGo(history, -1);
128
+ assert.equal(callbacks.length, 1);
129
+ assert.equal(callbacks[0].url, "/page1");
130
+ assert.deepEqual(callbacks[0].state, { id: 1 });
131
+ });
132
+ test("should call callback multiple times for multiple navigation", async () => {
133
+ const history = new MemoryHistory();
134
+ history.pushState({ id: 1 }, "", "/page1");
135
+ history.pushState({ id: 2 }, "", "/page2");
136
+ history.pushState({ id: 3 }, "", "/page3");
137
+ const callbacks = [];
138
+ void history.onPopState((url, state) => {
139
+ callbacks.push({ url, state });
140
+ });
141
+ history.go(-1);
142
+ history.go(-1);
143
+ history.go(2);
144
+ await sleep();
145
+ assert.equal(callbacks.length, 3);
146
+ assert.deepEqual(callbacks, [
147
+ { url: "/page2", state: { id: 2 } },
148
+ { url: "/page1", state: { id: 1 } },
149
+ { url: "/page3", state: { id: 3 } }
150
+ ]);
151
+ });
152
+ test("should NOT trigger callback on pushState/replaceState (like real popstate)", async () => {
153
+ const history = new MemoryHistory();
154
+ const callbacks = [];
155
+ void history.onPopState((url, state) => {
156
+ callbacks.push({ url, state });
157
+ });
158
+ history.pushState({ id: 1 }, "", "/page1");
159
+ history.replaceState({ id: 2 }, "", "/page1-updated");
160
+ await sleep();
161
+ assert.equal(callbacks.length, 0);
162
+ });
163
+ test("should NOT trigger callback when go() with delta 0 (no actual navigation)", async () => {
164
+ const history = new MemoryHistory();
165
+ history.pushState({ id: 1 }, "", "/page1");
166
+ const callbacks = [];
167
+ void history.onPopState((url, state) => {
168
+ callbacks.push({ url, state });
169
+ });
170
+ history.go(0);
171
+ history.go();
172
+ await sleep();
173
+ assert.equal(callbacks.length, 0);
174
+ });
175
+ test("should NOT trigger callback when navigation is out of bounds", async () => {
176
+ const history = new MemoryHistory();
177
+ history.pushState({ id: 1 }, "", "/page1");
178
+ const callbacks = [];
179
+ void history.onPopState((url, state) => {
180
+ callbacks.push({ url, state });
181
+ });
182
+ history.go(-10);
183
+ await sleep();
184
+ history.go(10);
185
+ await sleep();
186
+ assert.equal(callbacks.length, 0);
187
+ });
188
+ test("should trigger callback for valid back/forward navigation", async () => {
189
+ const history = new MemoryHistory();
190
+ history.pushState({ id: 1 }, "", "/page1");
191
+ history.pushState({ id: 2 }, "", "/page2");
192
+ history.pushState({ id: 3 }, "", "/page3");
193
+ const callbacks = [];
194
+ void history.onPopState((url, state) => {
195
+ callbacks.push({ url, state });
196
+ });
197
+ history.back();
198
+ history.back();
199
+ history.forward();
200
+ history.go(1);
201
+ await sleep();
202
+ assert.equal(callbacks.length, 4);
203
+ assert.deepEqual(callbacks, [
204
+ { url: "/page2", state: { id: 2 } },
205
+ { url: "/page1", state: { id: 1 } },
206
+ { url: "/page2", state: { id: 2 } },
207
+ { url: "/page3", state: { id: 3 } }
208
+ ]);
209
+ });
210
+ test("should provide correct state and url in callback (like popstate event)", async () => {
211
+ const history = new MemoryHistory();
212
+ history.pushState({ userId: 123, page: "profile" }, "", "/user/123");
213
+ history.pushState(
214
+ { userId: 456, page: "settings" },
215
+ "",
216
+ "/user/456/settings"
217
+ );
218
+ let callbackData = null;
219
+ void history.onPopState((url, state) => {
220
+ callbackData = { url, state };
221
+ });
222
+ await awaitGo(history, -1);
223
+ assert.isNotNull(callbackData);
224
+ assert.equal(callbackData.url, "/user/123");
225
+ assert.deepEqual(callbackData.state, {
226
+ userId: 123,
227
+ page: "profile"
228
+ });
229
+ });
230
+ test("should support multiple subscribers like multiple event listeners", async () => {
231
+ const history = new MemoryHistory();
232
+ history.pushState({ id: 1 }, "", "/page1");
233
+ history.pushState({ id: 2 }, "", "/page2");
234
+ const callbacks1 = [];
235
+ const callbacks2 = [];
236
+ void history.onPopState((url, state) => {
237
+ callbacks1.push({ url, state });
238
+ });
239
+ void history.onPopState((url, state) => {
240
+ callbacks2.push({ url, state });
241
+ });
242
+ await awaitGo(history, -1);
243
+ assert.equal(callbacks1.length, 1);
244
+ assert.deepEqual(callbacks1, [{ url: "/page1", state: { id: 1 } }]);
245
+ assert.equal(callbacks2.length, 1);
246
+ assert.deepEqual(callbacks2, [{ url: "/page1", state: { id: 1 } }]);
247
+ });
248
+ test("should return cleanup function to unsubscribe", () => {
249
+ const history = new MemoryHistory();
250
+ const unsubscribe = history.onPopState(() => {
251
+ });
252
+ assert.equal(typeof unsubscribe, "function");
253
+ });
254
+ test("cleanup function should stop triggering callbacks", async () => {
255
+ const history = new MemoryHistory();
256
+ history.pushState({ id: 1 }, "", "/page1");
257
+ history.pushState({ id: 2 }, "", "/page2");
258
+ const callbacks = [];
259
+ const unsubscribe = history.onPopState((url, state) => {
260
+ callbacks.push({ url, state });
261
+ });
262
+ await awaitGo(history, -1);
263
+ assert.equal(callbacks.length, 1);
264
+ unsubscribe();
265
+ await awaitGo(history, 1);
266
+ assert.equal(callbacks.length, 1);
267
+ });
268
+ test("should allow multiple subscriptions and cleanup only the current one", async () => {
269
+ const history = new MemoryHistory();
270
+ history.pushState({ id: 1 }, "", "/page1");
271
+ history.pushState({ id: 2 }, "", "/page2");
272
+ const callbacks1 = [];
273
+ const callbacks2 = [];
274
+ const callbacks3 = [];
275
+ void history.onPopState((url, state) => {
276
+ callbacks1.push({ url, state });
277
+ });
278
+ const unsubscribe2 = history.onPopState((url, state) => {
279
+ callbacks2.push({ url, state });
280
+ });
281
+ void history.onPopState((url, state) => {
282
+ callbacks3.push({ url, state });
283
+ });
284
+ await awaitGo(history, -1);
285
+ assert.equal(callbacks1.length, 1);
286
+ assert.deepEqual(callbacks1, [{ url: "/page1", state: { id: 1 } }]);
287
+ assert.equal(callbacks2.length, 1);
288
+ assert.deepEqual(callbacks2, [{ url: "/page1", state: { id: 1 } }]);
289
+ assert.equal(callbacks3.length, 1);
290
+ assert.deepEqual(callbacks3, [{ url: "/page1", state: { id: 1 } }]);
291
+ unsubscribe2();
292
+ await awaitGo(history, 1);
293
+ assert.equal(callbacks1.length, 2);
294
+ assert.equal(callbacks2.length, 1);
295
+ assert.equal(callbacks3.length, 2);
296
+ });
297
+ test("should not subscribe the same callback multiple times", async () => {
298
+ const history = new MemoryHistory();
299
+ history.pushState({ id: 1 }, "", "/page1");
300
+ history.pushState({ id: 2 }, "", "/page2");
301
+ const callbacks = [];
302
+ const callback = (url, state) => {
303
+ callbacks.push({ url, state });
304
+ };
305
+ const unSub1 = history.onPopState(callback);
306
+ const unSub2 = history.onPopState(callback);
307
+ await awaitGo(history, -1);
308
+ assert.equal(callbacks.length, 1);
309
+ assert.deepEqual(callbacks[0], { url: "/page1", state: { id: 1 } });
310
+ unSub1();
311
+ unSub2();
312
+ await awaitGo(history, 1);
313
+ assert.equal(callbacks.length, 1);
314
+ void history.onPopState(callback);
315
+ unSub1();
316
+ await awaitGo(history, -1);
317
+ assert.equal(callbacks.length, 1);
318
+ });
319
+ });
320
+ describe("Navigation", () => {
321
+ const createTestOptions = () => {
322
+ const baseOptions = {
323
+ context: {},
324
+ routes: [],
325
+ mode: RouterMode.memory,
326
+ base: new URL("http://test.com"),
327
+ req: null,
328
+ res: null,
329
+ apps: {},
330
+ normalizeURL: (url) => url,
331
+ fallback: () => {
332
+ },
333
+ rootStyle: false,
334
+ handleBackBoundary: () => {
335
+ },
336
+ handleLayerClose: () => {
337
+ }
338
+ };
339
+ return parsedOptions(baseOptions);
340
+ };
341
+ test("should provide access to history length", () => {
342
+ const nav = new Navigation({ mode: RouterMode.memory });
343
+ assert.equal(nav.length, 1);
344
+ const route1 = new Route({
345
+ options: createTestOptions(),
346
+ toType: RouteType.push,
347
+ toInput: { path: "/foo", state: { a: 1 } }
348
+ });
349
+ nav.push(route1);
350
+ assert.equal(nav.length, 2);
351
+ const route2 = new Route({
352
+ options: createTestOptions(),
353
+ toType: RouteType.push,
354
+ toInput: { path: "/bar", state: { b: 2 } }
355
+ });
356
+ nav.push(route2);
357
+ assert.equal(nav.length, 3);
358
+ const route3 = new Route({
359
+ options: createTestOptions(),
360
+ toType: RouteType.push,
361
+ toInput: { path: "/baz", state: { c: 3 } }
362
+ });
363
+ nav.replace(route3);
364
+ assert.equal(nav.length, 3);
365
+ nav.destroy();
366
+ });
367
+ test("should push and replace state correctly", () => {
368
+ const nav = new Navigation({ mode: RouterMode.memory });
369
+ const state1 = nav.push({ state: { a: 1 } }, "/foo");
370
+ assert.deepEqual(state1.state.a, 1);
371
+ const state2 = nav.replace({ state: { b: 2 } }, "/bar");
372
+ assert.deepEqual(state2.state.b, 2);
373
+ nav.destroy();
374
+ });
375
+ test("should resolve go/back/forward with correct url and state", async () => {
376
+ const nav = new Navigation({ mode: RouterMode.memory });
377
+ nav.push({ state: { a: 1 } }, "/a");
378
+ nav.push({ state: { b: 2 } }, "/b");
379
+ nav.push({ state: { c: 3 } }, "/c");
380
+ const res1 = await nav.go(-2);
381
+ assert.equal(res1 == null ? void 0 : res1.url, "/a");
382
+ assert.deepEqual((res1 == null ? void 0 : res1.state).state.a, 1);
383
+ const res2 = await nav.forward();
384
+ assert.equal(res2 == null ? void 0 : res2.url, "/b");
385
+ assert.deepEqual((res2 == null ? void 0 : res2.state).state.b, 2);
386
+ const res3 = await nav.back();
387
+ assert.equal(res3 == null ? void 0 : res3.url, "/a");
388
+ assert.deepEqual((res3 == null ? void 0 : res3.state).state.a, 1);
389
+ nav.destroy();
390
+ });
391
+ test("should call onUpdated callback on navigation", async () => {
392
+ const updates = [];
393
+ const nav = new Navigation(
394
+ { mode: RouterMode.memory },
395
+ (url, state) => updates.push({ url, state })
396
+ );
397
+ nav.push({ state: { a: 1 } }, "/a");
398
+ nav.push({ state: { b: 2 } }, "/b");
399
+ nav._history.back();
400
+ await sleep(100);
401
+ assert.ok(updates.length > 0);
402
+ assert.equal(updates[0].url, "/a");
403
+ nav.destroy();
404
+ });
405
+ test("should resolve null if go is called while pending", async () => {
406
+ const nav = new Navigation({ mode: RouterMode.memory });
407
+ nav.push(
408
+ new Route({
409
+ options: createTestOptions(),
410
+ toType: RouteType.push,
411
+ toInput: { path: "/a", state: { a: 1 } }
412
+ })
413
+ );
414
+ nav.push(
415
+ new Route({
416
+ options: createTestOptions(),
417
+ toType: RouteType.push,
418
+ toInput: { path: "/b", state: { b: 2 } }
419
+ })
420
+ );
421
+ const p1 = nav.go(-1);
422
+ const p2 = nav.go(1);
423
+ const r2 = await p2;
424
+ assert.equal(r2, null);
425
+ await p1;
426
+ nav.destroy();
427
+ });
428
+ test("should cleanup listeners on destroy", async () => {
429
+ const updates = [];
430
+ const nav = new Navigation(
431
+ { mode: RouterMode.memory },
432
+ (url, state) => updates.push({ url, state })
433
+ );
434
+ nav.push(
435
+ new Route({
436
+ options: createTestOptions(),
437
+ toType: RouteType.push,
438
+ toInput: { path: "/a", state: { a: 1 } }
439
+ })
440
+ );
441
+ nav.push(
442
+ new Route({
443
+ options: createTestOptions(),
444
+ toType: RouteType.push,
445
+ toInput: { path: "/b", state: { b: 2 } }
446
+ })
447
+ );
448
+ nav.destroy();
449
+ nav.go(-1);
450
+ const res = await nav.go(1);
451
+ assert.equal(res, null);
452
+ assert.equal(updates.length, 0);
453
+ });
454
+ describe("in history mode", () => {
455
+ let mockHistory;
456
+ let popstateHandler;
457
+ beforeEach(() => {
458
+ mockHistory = new MemoryHistory();
459
+ mockHistory.replaceState({ startPoint: true }, "", "/startPoint");
460
+ mockHistory.pushState = vi.fn(
461
+ mockHistory.pushState.bind(mockHistory)
462
+ );
463
+ mockHistory.replaceState = vi.fn(
464
+ mockHistory.replaceState.bind(mockHistory)
465
+ );
466
+ mockHistory.go = vi.fn(mockHistory.go.bind(mockHistory));
467
+ mockHistory.onPopState(() => popstateHandler == null ? void 0 : popstateHandler());
468
+ const mockLocation = {
469
+ get href() {
470
+ return mockHistory.url;
471
+ }
472
+ };
473
+ const mockAddEventListener = vi.fn((type, handler) => {
474
+ if (type === "popstate") popstateHandler = handler;
475
+ });
476
+ const mockRemoveEventListener = vi.fn((type, handler) => {
477
+ if (type === "popstate" && popstateHandler === handler) {
478
+ popstateHandler = null;
479
+ }
480
+ });
481
+ vi.stubGlobal("window", {
482
+ get history() {
483
+ return mockHistory;
484
+ },
485
+ get location() {
486
+ return mockLocation;
487
+ },
488
+ addEventListener: mockAddEventListener,
489
+ removeEventListener: mockRemoveEventListener
490
+ });
491
+ vi.stubGlobal("location", mockLocation);
492
+ vi.stubGlobal("history", mockHistory);
493
+ });
494
+ afterEach(() => {
495
+ vi.unstubAllGlobals();
496
+ popstateHandler = null;
497
+ mockHistory = null;
498
+ });
499
+ it("should call pushState/replaceState and handle go/back/forward", async () => {
500
+ const nav = new Navigation({ mode: RouterMode.history });
501
+ const state1 = nav.push({ state: { a: 1 } }, "/foo");
502
+ expect(mockHistory.pushState).toHaveBeenCalledWith(
503
+ expect.objectContaining({ state: { a: 1 } }),
504
+ "",
505
+ "/foo"
506
+ );
507
+ const state2 = nav.replace({ state: { b: 2 } }, "/bar");
508
+ expect(mockHistory.replaceState).toHaveBeenCalledWith(
509
+ expect.objectContaining({ state: { b: 2 } }),
510
+ "",
511
+ "/bar"
512
+ );
513
+ const res = await nav.go(-1);
514
+ expect(res).toEqual({
515
+ type: "success",
516
+ url: "/startPoint",
517
+ state: { startPoint: true }
518
+ });
519
+ nav.destroy();
520
+ });
521
+ });
522
+ describe("Uncovered branches tests", () => {
523
+ test("should handle null/undefined route.state in push method (line 43)", () => {
524
+ const nav = new Navigation({ mode: RouterMode.memory });
525
+ const routeWithNullState = new Route({
526
+ options: createTestOptions(),
527
+ toType: RouteType.push,
528
+ toInput: { path: "/test", state: null }
529
+ });
530
+ const state1 = nav.push(routeWithNullState);
531
+ assert.ok(state1);
532
+ assert.ok("__pageId__" in state1);
533
+ const routeWithUndefinedState = new Route({
534
+ options: createTestOptions(),
535
+ toType: RouteType.push,
536
+ toInput: { path: "/test2", state: void 0 }
537
+ });
538
+ const state2 = nav.push(routeWithUndefinedState);
539
+ assert.ok(state2);
540
+ assert.ok("__pageId__" in state2);
541
+ nav.destroy();
542
+ });
543
+ test("should handle null/undefined route.state in replace method (line 53)", () => {
544
+ const nav = new Navigation({ mode: RouterMode.memory });
545
+ nav.push(
546
+ new Route({
547
+ options: createTestOptions(),
548
+ toType: RouteType.push,
549
+ toInput: "/initial"
550
+ })
551
+ );
552
+ const routeWithNullState = new Route({
553
+ options: createTestOptions(),
554
+ toType: RouteType.push,
555
+ toInput: { path: "/test", state: null }
556
+ });
557
+ const state1 = nav.replace(routeWithNullState);
558
+ assert.ok(state1);
559
+ assert.ok("__pageId__" in state1);
560
+ const routeWithUndefinedState = new Route({
561
+ options: createTestOptions(),
562
+ toType: RouteType.push,
563
+ toInput: { path: "/test2", state: void 0 }
564
+ });
565
+ const state2 = nav.replace(routeWithUndefinedState);
566
+ assert.ok(state2);
567
+ assert.ok("__pageId__" in state2);
568
+ nav.destroy();
569
+ });
570
+ test("should call _promiseResolve when destroying with pending promise (line 82)", async () => {
571
+ const nav = new Navigation({ mode: RouterMode.memory });
572
+ nav.push(
573
+ new Route({
574
+ options: createTestOptions(),
575
+ toType: RouteType.push,
576
+ toInput: "/test1"
577
+ })
578
+ );
579
+ nav.push(
580
+ new Route({
581
+ options: createTestOptions(),
582
+ toType: RouteType.push,
583
+ toInput: "/test2"
584
+ })
585
+ );
586
+ const goPromise = nav.go(-1);
587
+ nav.destroy();
588
+ const result = await goPromise;
589
+ assert.equal(result, null);
590
+ });
591
+ test("should return null when _curEntry index is out of bounds (line 92)", () => {
592
+ const history = new MemoryHistory();
593
+ history._index = -1;
594
+ assert.equal(history._curEntry, null);
595
+ history._index = 999;
596
+ assert.equal(history._curEntry, null);
597
+ });
598
+ test("should return empty string when _curEntry.url is null/undefined (line 101)", () => {
599
+ const history = new MemoryHistory();
600
+ const mockEntry = { state: {}, url: void 0 };
601
+ history._entries = [mockEntry];
602
+ history._index = 0;
603
+ assert.equal(history.url, "");
604
+ });
605
+ test("should use this.url when pushState url parameter is null/undefined (line 108)", () => {
606
+ const history = new MemoryHistory();
607
+ const currentUrl = history.url;
608
+ history.pushState({ test: 1 }, "", null);
609
+ assert.equal(history.url, currentUrl);
610
+ history.pushState({ test: 2 }, "", void 0);
611
+ assert.equal(history.url, currentUrl);
612
+ });
613
+ test("should return early when curEntry is null in replaceState (line 128)", () => {
614
+ const history = new MemoryHistory();
615
+ history._index = -1;
616
+ const originalEntries = [...history._entries];
617
+ history.replaceState({ test: 1 }, "", "/test");
618
+ assert.deepEqual(history._entries, originalEntries);
619
+ });
620
+ test("should return empty function when cb is not a function in onPopState (line 152)", () => {
621
+ const history = new MemoryHistory();
622
+ const unsubscribe1 = history.onPopState(null);
623
+ const unsubscribe2 = history.onPopState(void 0);
624
+ const unsubscribe3 = history.onPopState("not a function");
625
+ const unsubscribe4 = history.onPopState(123);
626
+ assert.equal(typeof unsubscribe1, "function");
627
+ assert.equal(typeof unsubscribe2, "function");
628
+ assert.equal(typeof unsubscribe3, "function");
629
+ assert.equal(typeof unsubscribe4, "function");
630
+ assert.doesNotThrow(() => {
631
+ unsubscribe1();
632
+ unsubscribe2();
633
+ unsubscribe3();
634
+ unsubscribe4();
635
+ });
636
+ });
637
+ test("should handle null history.state in subscribeHtmlHistory (line 159)", () => {
638
+ const mockHistory = {
639
+ state: null
640
+ // Simulate history.state being null
641
+ };
642
+ const mockLocation = {
643
+ href: "http://test.com/page"
644
+ };
645
+ const mockWindow = {
646
+ addEventListener: vi.fn(),
647
+ removeEventListener: vi.fn()
648
+ };
649
+ let capturedCallback = null;
650
+ mockWindow.addEventListener.mockImplementation(
651
+ (event, callback) => {
652
+ if (event === "popstate") {
653
+ capturedCallback = callback;
654
+ }
655
+ }
656
+ );
657
+ vi.stubGlobal("window", mockWindow);
658
+ vi.stubGlobal("history", mockHistory);
659
+ vi.stubGlobal("location", mockLocation);
660
+ const callbackData = [];
661
+ const nav = new Navigation(
662
+ { mode: RouterMode.history },
663
+ (url, state) => {
664
+ callbackData.push({ url, state });
665
+ }
666
+ );
667
+ expect(mockWindow.addEventListener).toHaveBeenCalledWith(
668
+ "popstate",
669
+ expect.any(Function)
670
+ );
671
+ if (capturedCallback) {
672
+ capturedCallback();
673
+ }
674
+ expect(callbackData.length).toBe(1);
675
+ expect(callbackData[0].url).toBe("http://test.com/page");
676
+ expect(callbackData[0].state).toEqual({});
677
+ nav.destroy();
678
+ vi.unstubAllGlobals();
679
+ });
680
+ });
681
+ });
@@ -0,0 +1,4 @@
1
+ import type { Router } from './router';
2
+ import type { Route, RouterOptions, RouterParsedOptions } from './types';
3
+ export declare function parsedOptions(options?: RouterOptions): RouterParsedOptions;
4
+ export declare function fallback(to: Route, from: Route | null, router: Router): Window | null | undefined;