@esmx/router 3.0.0-rc.29 → 3.0.0-rc.30

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 (59) hide show
  1. package/README.zh-CN.md +82 -1
  2. package/dist/index.d.ts +1 -2
  3. package/dist/index.mjs +0 -1
  4. package/package.json +3 -3
  5. package/src/index.ts +0 -3
  6. package/dist/index.test.d.ts +0 -1
  7. package/dist/index.test.mjs +0 -8
  8. package/dist/location.test.d.ts +0 -8
  9. package/dist/location.test.mjs +0 -370
  10. package/dist/matcher.test.d.ts +0 -1
  11. package/dist/matcher.test.mjs +0 -1492
  12. package/dist/micro-app.dom.test.d.ts +0 -1
  13. package/dist/micro-app.dom.test.mjs +0 -532
  14. package/dist/navigation.test.d.ts +0 -1
  15. package/dist/navigation.test.mjs +0 -681
  16. package/dist/route-task.test.d.ts +0 -1
  17. package/dist/route-task.test.mjs +0 -673
  18. package/dist/route-transition.test.d.ts +0 -1
  19. package/dist/route-transition.test.mjs +0 -146
  20. package/dist/route.test.d.ts +0 -1
  21. package/dist/route.test.mjs +0 -1664
  22. package/dist/router-back.test.d.ts +0 -1
  23. package/dist/router-back.test.mjs +0 -361
  24. package/dist/router-forward.test.d.ts +0 -1
  25. package/dist/router-forward.test.mjs +0 -376
  26. package/dist/router-go.test.d.ts +0 -1
  27. package/dist/router-go.test.mjs +0 -73
  28. package/dist/router-guards-cleanup.test.d.ts +0 -1
  29. package/dist/router-guards-cleanup.test.mjs +0 -437
  30. package/dist/router-push.test.d.ts +0 -1
  31. package/dist/router-push.test.mjs +0 -115
  32. package/dist/router-replace.test.d.ts +0 -1
  33. package/dist/router-replace.test.mjs +0 -114
  34. package/dist/router-resolve.test.d.ts +0 -1
  35. package/dist/router-resolve.test.mjs +0 -393
  36. package/dist/router-restart-app.dom.test.d.ts +0 -1
  37. package/dist/router-restart-app.dom.test.mjs +0 -616
  38. package/dist/router-window-navigation.test.d.ts +0 -1
  39. package/dist/router-window-navigation.test.mjs +0 -359
  40. package/dist/util.test.d.ts +0 -1
  41. package/dist/util.test.mjs +0 -1020
  42. package/src/index.test.ts +0 -9
  43. package/src/location.test.ts +0 -406
  44. package/src/matcher.test.ts +0 -1685
  45. package/src/micro-app.dom.test.ts +0 -708
  46. package/src/navigation.test.ts +0 -858
  47. package/src/route-task.test.ts +0 -901
  48. package/src/route-transition.test.ts +0 -178
  49. package/src/route.test.ts +0 -2014
  50. package/src/router-back.test.ts +0 -487
  51. package/src/router-forward.test.ts +0 -506
  52. package/src/router-go.test.ts +0 -91
  53. package/src/router-guards-cleanup.test.ts +0 -595
  54. package/src/router-push.test.ts +0 -140
  55. package/src/router-replace.test.ts +0 -139
  56. package/src/router-resolve.test.ts +0 -475
  57. package/src/router-restart-app.dom.test.ts +0 -783
  58. package/src/router-window-navigation.test.ts +0 -457
  59. package/src/util.test.ts +0 -1262
package/src/util.test.ts DELETED
@@ -1,1262 +0,0 @@
1
- import { describe, expect, test } from 'vitest';
2
- import { parsedOptions } from './options';
3
- import { Route } from './route';
4
- import type { RouterParsedOptions } from './types';
5
- import { RouteType } from './types';
6
- import {
7
- isNonEmptyPlainObject,
8
- isNotNullish,
9
- isPlainObject,
10
- isRouteMatched,
11
- isUrlEqual,
12
- isValidConfirmHookResult,
13
- removeFromArray
14
- } from './util';
15
-
16
- const AsyncFunction = (async () => {}).constructor;
17
-
18
- // There are differences between literals and object wrappers:
19
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#string_primitives_and_string_objects
20
-
21
- describe('isNotNullish', () => {
22
- test('should return true for non-nullish values', () => {
23
- // Special values
24
- expect(isNotNullish(null)).toBe(false);
25
- expect(isNotNullish(void 0)).toBe(false);
26
- // Numbers & bigint
27
- expect(isNotNullish(+'a')).toBe(false);
28
- expect(isNotNullish(Number.NaN)).toBe(false);
29
- expect(isNotNullish(Number('a'))).toBe(false);
30
- expect(isNotNullish(new Number('a'))).toBe(false);
31
- expect(isNotNullish(0)).toBe(true);
32
- expect(isNotNullish(123)).toBe(true);
33
- expect(isNotNullish(123n)).toBe(true);
34
- expect(isNotNullish(0n)).toBe(true);
35
- expect(isNotNullish(new Number('1'))).toBe(true);
36
- expect(isNotNullish(Number.POSITIVE_INFINITY)).toBe(true);
37
- expect(isNotNullish(Number.NEGATIVE_INFINITY)).toBe(true);
38
- expect(isNotNullish(Number.EPSILON)).toBe(true);
39
- // Strings
40
- expect(isNotNullish('')).toBe(true);
41
- expect(isNotNullish('0')).toBe(true);
42
- expect(isNotNullish('1')).toBe(true);
43
- expect(isNotNullish('test')).toBe(true);
44
- expect(isNotNullish(new String(''))).toBe(true);
45
- expect(isNotNullish(new String('0'))).toBe(true);
46
- expect(isNotNullish(new String('1'))).toBe(true);
47
- expect(isNotNullish(new String('test'))).toBe(true);
48
- // Boolean values
49
- expect(isNotNullish(true)).toBe(true);
50
- expect(isNotNullish(false)).toBe(true);
51
- expect(isNotNullish(new Boolean(true))).toBe(true);
52
- expect(isNotNullish(new Boolean(false))).toBe(true);
53
- // Object
54
- expect(isNotNullish({})).toBe(true);
55
- expect(isNotNullish({ key: 'value' })).toBe(true);
56
- expect(isNotNullish(new Object())).toBe(true);
57
- expect(isNotNullish(Object.create(null))).toBe(true);
58
- expect(isNotNullish(Object.create({}))).toBe(true);
59
- expect(isNotNullish(Object.create(Object.prototype))).toBe(true);
60
- expect(isNotNullish({ __proto__: null })).toBe(true);
61
- expect(isNotNullish({ [Symbol.toStringTag]: 'Tag' })).toBe(true);
62
- expect(isNotNullish({ toString: () => '[object CustomObject]' })).toBe(
63
- true
64
- );
65
- // Arrays & typed arrays related
66
- expect(isNotNullish([])).toBe(true);
67
- expect(isNotNullish(['a', 'b'])).toBe(true);
68
- expect(isNotNullish(new Array())).toBe(true);
69
- expect(isNotNullish(new Array(1, 2, 3))).toBe(true);
70
- expect(isNotNullish(new Array(1))).toBe(true);
71
- expect(isNotNullish(new Uint8Array(8))).toBe(true);
72
- expect(isNotNullish(new ArrayBuffer(8))).toBe(true);
73
- expect(isNotNullish(new DataView(new ArrayBuffer(8)))).toBe(true);
74
- // Functions
75
- expect(isNotNullish(() => {})).toBe(true);
76
- expect(isNotNullish(async () => {})).toBe(true);
77
- expect(isNotNullish(new Function('return 1;'))).toBe(true);
78
- expect(isNotNullish(AsyncFunction('return 1;'))).toBe(true);
79
- // Special objects
80
- expect(isNotNullish(new Date())).toBe(true);
81
- expect(isNotNullish(Symbol('test'))).toBe(true);
82
- expect(isNotNullish(new Map())).toBe(true);
83
- expect(isNotNullish(new Set())).toBe(true);
84
- expect(isNotNullish(new WeakMap())).toBe(true);
85
- expect(isNotNullish(new WeakSet())).toBe(true);
86
- expect(isNotNullish(/test/)).toBe(true);
87
- expect(isNotNullish(new Error('test'))).toBe(true);
88
- expect(isNotNullish(Promise.resolve())).toBe(true);
89
- expect(isNotNullish(new URL('https://example.com'))).toBe(true);
90
- expect(isNotNullish(new URLSearchParams('key=value'))).toBe(true);
91
- expect(isNotNullish(new Blob(['test']))).toBe(true);
92
- expect(isNotNullish(new File(['test'], 'file.txt'))).toBe(true);
93
- expect(isNotNullish(Math)).toBe(true);
94
- expect(isNotNullish(JSON)).toBe(true);
95
- expect(isNotNullish(console)).toBe(true);
96
- });
97
-
98
- test('should handle edge cases with Number constructor', () => {
99
- // Various usages of Number constructor
100
- expect(isNotNullish(Number())).toBe(true); // Number() returns 0
101
- expect(isNotNullish(Number(undefined))).toBe(false); // Number(undefined) returns NaN
102
- expect(isNotNullish(Number(null))).toBe(true); // Number(null) returns 0
103
- expect(isNotNullish(Number(''))).toBe(true); // Number('') returns 0
104
- expect(isNotNullish(Number('0'))).toBe(true); // Number('0') returns 0
105
- expect(isNotNullish(Number('123'))).toBe(true); // Number('123') returns 123
106
-
107
- // Various usages of new Number constructor
108
- expect(isNotNullish(new Number())).toBe(true); // new Number() returns Number object
109
- expect(isNotNullish(new Number(undefined))).toBe(false); // new Number(undefined) wraps NaN
110
- expect(isNotNullish(new Number(null))).toBe(true); // new Number(null) wraps 0
111
- expect(isNotNullish(new Number(''))).toBe(true); // new Number('') wraps 0
112
- expect(isNotNullish(new Number('0'))).toBe(true); // new Number('0') wraps 0
113
- expect(isNotNullish(new Number('123'))).toBe(true); // new Number('123') wraps 123
114
- });
115
-
116
- test('should handle various NaN cases', () => {
117
- // Various situations that produce NaN
118
- expect(isNotNullish(0 / 0)).toBe(false);
119
- expect(isNotNullish(Math.sqrt(-1))).toBe(false);
120
- expect(isNotNullish(Number.parseInt('abc'))).toBe(false);
121
- expect(isNotNullish(Number.parseFloat('abc'))).toBe(false);
122
- expect(isNotNullish(Number.NaN)).toBe(false);
123
- expect(isNotNullish(Number.NaN)).toBe(false);
124
-
125
- // NaN wrapped in Number objects
126
- expect(isNotNullish(new Number(Number.NaN))).toBe(false);
127
- expect(isNotNullish(new Number(0 / 0))).toBe(false);
128
- expect(isNotNullish(new Number(Number.parseInt('abc')))).toBe(false);
129
- });
130
-
131
- test('should handle complex objects', () => {
132
- // Objects created by custom constructors
133
- class CustomClass {
134
- constructor(public value: number) {}
135
- }
136
- expect(isNotNullish(new CustomClass(123))).toBe(true);
137
-
138
- // Frozen and sealed objects
139
- const frozenObj = Object.freeze({ a: 1 });
140
- const sealedObj = Object.seal({ b: 2 });
141
- expect(isNotNullish(frozenObj)).toBe(true);
142
- expect(isNotNullish(sealedObj)).toBe(true);
143
-
144
- // Objects created using defineProperty
145
- const objWithDescriptor = {};
146
- Object.defineProperty(objWithDescriptor, 'prop', {
147
- value: 'test',
148
- writable: false
149
- });
150
- expect(isNotNullish(objWithDescriptor)).toBe(true);
151
- });
152
- });
153
-
154
- describe('isPlainObject', () => {
155
- test('should return true for plain objects', () => {
156
- expect(isPlainObject({})).toBe(true);
157
- expect(isPlainObject({ key: 'value' })).toBe(true);
158
- expect(isPlainObject(new Object())).toBe(true);
159
- expect(isPlainObject(Object.create(null))).toBe(true);
160
- expect(isPlainObject(Object.create({}))).toBe(true);
161
- expect(isPlainObject(Object.create(Object.prototype))).toBe(true);
162
- expect(isPlainObject(Object.create({ parent: 'value' }))).toBe(true);
163
- expect(isPlainObject({ __proto__: null })).toBe(true);
164
- expect(isPlainObject({ [Symbol.toStringTag]: 'Tag' })).toBe(true);
165
- expect(isPlainObject({ toString: () => '[object CustomObject]' })).toBe(
166
- true
167
- );
168
- expect(isPlainObject(Math)).toBe(true);
169
- expect(isPlainObject(JSON)).toBe(true);
170
- class TestClass {}
171
- expect(isPlainObject(new TestClass())).toBe(true);
172
- });
173
-
174
- test('should return false for non-plain objects', () => {
175
- // Special values
176
- expect(isPlainObject(null)).toBe(false);
177
- expect(isPlainObject(void 0)).toBe(false);
178
- // Arrays & typed arrays related
179
- expect(isPlainObject([])).toBe(false);
180
- expect(isPlainObject(['a', 'b'])).toBe(false);
181
- expect(isPlainObject(new Array())).toBe(false);
182
- expect(isPlainObject(new Array(1, 2, 3))).toBe(false);
183
- expect(isPlainObject(new Array(1))).toBe(false);
184
- expect(isPlainObject(new Uint8Array(8))).toBe(false);
185
- expect(isPlainObject(new ArrayBuffer(8))).toBe(false);
186
- expect(isPlainObject(new DataView(new ArrayBuffer(8)))).toBe(false);
187
- // Strings
188
- expect(isPlainObject('')).toBe(false);
189
- expect(isPlainObject('0')).toBe(false);
190
- expect(isPlainObject('1')).toBe(false);
191
- expect(isPlainObject('string')).toBe(false);
192
- expect(isPlainObject(new String(''))).toBe(false); // typeof (new String('')) === 'object'
193
- expect(isPlainObject(new String('0'))).toBe(false);
194
- expect(isPlainObject(new String('1'))).toBe(false);
195
- expect(isPlainObject(new String('string'))).toBe(false);
196
- // Boolean values
197
- expect(isPlainObject(true)).toBe(false);
198
- expect(isPlainObject(false)).toBe(false);
199
- expect(isPlainObject(new Boolean(true))).toBe(false);
200
- expect(isPlainObject(new Boolean(false))).toBe(false);
201
- // Numbers & bigint
202
- expect(isPlainObject(0)).toBe(false);
203
- expect(isPlainObject(0n)).toBe(false);
204
- expect(isPlainObject(123)).toBe(false);
205
- expect(isPlainObject(123n)).toBe(false);
206
- expect(isPlainObject(new Number('1'))).toBe(false);
207
- expect(isPlainObject(+'a')).toBe(false);
208
- expect(isPlainObject(Number.NaN)).toBe(false);
209
- expect(isPlainObject(new Number('a'))).toBe(false);
210
- // Functions
211
- expect(isPlainObject(() => {})).toBe(false);
212
- expect(isPlainObject(async () => {})).toBe(false);
213
- expect(isPlainObject(new Function('return 1;'))).toBe(false);
214
- expect(isPlainObject(AsyncFunction('return 1;'))).toBe(false);
215
- // Special objects
216
- expect(isPlainObject(new Date())).toBe(false);
217
- expect(isPlainObject(Symbol('test'))).toBe(false);
218
- expect(isPlainObject(new Map())).toBe(false);
219
- expect(isPlainObject(new Set())).toBe(false);
220
- expect(isPlainObject(new WeakMap())).toBe(false);
221
- expect(isPlainObject(new WeakSet())).toBe(false);
222
- expect(isPlainObject(/test/)).toBe(false);
223
- expect(isPlainObject(new Error('test'))).toBe(false);
224
- expect(isPlainObject(Promise.resolve())).toBe(false);
225
- expect(isPlainObject(new URL('https://example.com'))).toBe(false);
226
- expect(isPlainObject(new URLSearchParams('key=value'))).toBe(false);
227
- expect(isPlainObject(new Blob(['test']))).toBe(false);
228
- expect(isPlainObject(new File(['test'], 'file.txt'))).toBe(false);
229
- });
230
-
231
- test('should distinguish between objects and boxed primitives', () => {
232
- expect(isPlainObject(Object(42))).toBe(false); // Equivalent to new Number(42)
233
- expect(isPlainObject(Object('str'))).toBe(false); // Equivalent to new String('str')
234
- expect(isPlainObject(Object(true))).toBe(false); // Equivalent to new Boolean(true)
235
- expect(isPlainObject(Object(Symbol('sym')))).toBe(false); // Symbol wrapper object
236
- });
237
- });
238
-
239
- describe('isNonEmptyPlainObject', () => {
240
- test('should return true for non-empty plain objects', () => {
241
- expect(isNonEmptyPlainObject({ key: 'value' })).toBe(true);
242
- expect(isNonEmptyPlainObject({ a: 1, b: 2 })).toBe(true);
243
- expect(isNonEmptyPlainObject({ nested: { value: 'test' } })).toBe(true);
244
- expect(
245
- isNonEmptyPlainObject({ toString: () => '[object CustomObject]' })
246
- ).toBe(true);
247
-
248
- const obj = new Object() as any;
249
- obj.prop = 'value';
250
- expect(isNonEmptyPlainObject(obj)).toBe(true);
251
-
252
- const objWithProto = Object.create({ parent: 'value' });
253
- objWithProto.own = 'property';
254
- expect(isNonEmptyPlainObject(objWithProto)).toBe(true);
255
-
256
- class TestClass {}
257
- const instance = new TestClass() as any;
258
- instance.prop = 'value';
259
- expect(isNonEmptyPlainObject(instance)).toBe(true);
260
- });
261
-
262
- test('should return false for empty objects', () => {
263
- expect(isNonEmptyPlainObject({})).toBe(false);
264
- expect(isNonEmptyPlainObject(new Object())).toBe(false);
265
- expect(isNonEmptyPlainObject(Object.create(null))).toBe(false);
266
- expect(isNonEmptyPlainObject(Object.create({}))).toBe(false);
267
- expect(isNonEmptyPlainObject(Object.create(Object.prototype))).toBe(
268
- false
269
- );
270
- expect(isNonEmptyPlainObject({ __proto__: null })).toBe(false);
271
-
272
- class TestClass {}
273
- expect(isNonEmptyPlainObject(new TestClass())).toBe(false);
274
-
275
- // Objects with Symbol properties should be considered empty
276
- expect(isNonEmptyPlainObject({ [Symbol.toStringTag]: 'Tag' })).toBe(
277
- false
278
- );
279
- expect(isNonEmptyPlainObject({ [Symbol('key')]: 'value' })).toBe(false);
280
- });
281
-
282
- test('should return false for non-objects', () => {
283
- // Special values
284
- expect(isNonEmptyPlainObject(null)).toBe(false);
285
- expect(isNonEmptyPlainObject(void 0)).toBe(false);
286
-
287
- // Primitive types
288
- expect(isNonEmptyPlainObject('')).toBe(false);
289
- expect(isNonEmptyPlainObject('non-empty')).toBe(false);
290
- expect(isNonEmptyPlainObject(0)).toBe(false);
291
- expect(isNonEmptyPlainObject(123)).toBe(false);
292
- expect(isNonEmptyPlainObject(0n)).toBe(false);
293
- expect(isNonEmptyPlainObject(123n)).toBe(false);
294
- expect(isNonEmptyPlainObject(true)).toBe(false);
295
- expect(isNonEmptyPlainObject(false)).toBe(false);
296
- expect(isNonEmptyPlainObject(Symbol('test'))).toBe(false);
297
-
298
- // Arrays
299
- expect(isNonEmptyPlainObject([])).toBe(false);
300
- expect(isNonEmptyPlainObject(['a', 'b'])).toBe(false);
301
-
302
- // Functions
303
- expect(isNonEmptyPlainObject(() => {})).toBe(false);
304
- expect(isNonEmptyPlainObject(async () => {})).toBe(false);
305
-
306
- // Special object types
307
- expect(isNonEmptyPlainObject(new Date())).toBe(false);
308
- expect(isNonEmptyPlainObject(new Map())).toBe(false);
309
- expect(isNonEmptyPlainObject(new Set())).toBe(false);
310
- expect(isNonEmptyPlainObject(/test/)).toBe(false);
311
- expect(isNonEmptyPlainObject(new Error('test'))).toBe(false);
312
- expect(isNonEmptyPlainObject(Promise.resolve())).toBe(false);
313
-
314
- // Wrapper objects
315
- expect(isNonEmptyPlainObject(new String('test'))).toBe(false);
316
- expect(isNonEmptyPlainObject(new Number(123))).toBe(false);
317
- expect(isNonEmptyPlainObject(new Boolean(true))).toBe(false);
318
- });
319
-
320
- test('should handle objects with only inherited properties', () => {
321
- // Objects with only inherited properties, no own properties
322
- const parentObj = { parentProp: 'value' };
323
- const childObj = Object.create(parentObj);
324
- expect(isNonEmptyPlainObject(childObj)).toBe(false);
325
-
326
- childObj.ownProp = 'own';
327
- expect(isNonEmptyPlainObject(childObj)).toBe(true);
328
- });
329
-
330
- test('should handle edge cases', () => {
331
- // Objects with non-enumerable properties
332
- const objWithNonEnum = {};
333
- Object.defineProperty(objWithNonEnum, 'nonEnum', {
334
- value: 'hidden',
335
- enumerable: false
336
- });
337
- expect(isNonEmptyPlainObject(objWithNonEnum)).toBe(false); // Object.keys will not include non-enumerable properties
338
-
339
- Object.defineProperty(objWithNonEnum, 'visible', {
340
- value: 'visible',
341
- enumerable: true
342
- });
343
- expect(isNonEmptyPlainObject(objWithNonEnum)).toBe(true); // Now has enumerable properties
344
-
345
- // Frozen objects
346
- const frozenObj = Object.freeze({ prop: 'value' });
347
- expect(isNonEmptyPlainObject(frozenObj)).toBe(true);
348
-
349
- // Sealed objects
350
- const sealedObj = Object.seal({ prop: 'value' });
351
- expect(isNonEmptyPlainObject(sealedObj)).toBe(true);
352
- });
353
- });
354
-
355
- describe('removeFromArray', () => {
356
- test('should remove first occurrence when duplicates exist', () => {
357
- const arr = [1, 2, 2, 3];
358
- removeFromArray(arr, 2);
359
- expect(arr).toEqual([1, 2, 3]);
360
-
361
- // `new Number(2)` is not the same as `2`
362
- const num = new Number(2);
363
- let arrWithObj = [1, num, 2, 3];
364
- removeFromArray(arrWithObj, 2);
365
- expect(arrWithObj).toEqual([1, num, 3]);
366
- arrWithObj = [1, num, 2, 3];
367
- removeFromArray(arrWithObj, num);
368
- expect(arrWithObj).toEqual([1, 2, 3]);
369
- arrWithObj = [1, 2, num, 3];
370
- removeFromArray(arrWithObj, num);
371
- expect(arrWithObj).toEqual([1, 2, 3]);
372
- arrWithObj = [1, 2, num, 3];
373
- removeFromArray(arrWithObj, 2);
374
- expect(arrWithObj).toEqual([1, num, 3]);
375
-
376
- arrWithObj = [1, num, num, 3];
377
- removeFromArray(arrWithObj, num);
378
- expect(arrWithObj).toEqual([1, num, 3]);
379
- });
380
-
381
- test('should remove existing element from array', () => {
382
- const arr = [1, 2, 3];
383
- removeFromArray(arr, 2);
384
- expect(arr).toEqual([1, 3]);
385
- });
386
-
387
- test('should do nothing when element not found', () => {
388
- const arr = [1, 2, 3];
389
- removeFromArray(arr, 4);
390
- expect(arr).toEqual([1, 2, 3]);
391
- });
392
-
393
- test('should work with object references', () => {
394
- const obj1 = { id: 1 };
395
- const obj2 = { id: 2 };
396
- const arr = [obj1, obj2];
397
- removeFromArray(arr, obj1);
398
- expect(arr).toEqual([obj2]);
399
- });
400
-
401
- test('should handle edge cases', () => {
402
- // Empty array
403
- const emptyArr: any[] = [];
404
- removeFromArray(emptyArr, 1);
405
- expect(emptyArr).toEqual([]);
406
-
407
- const singleArr = [42];
408
- removeFromArray(singleArr, 42);
409
- expect(singleArr).toEqual([]);
410
-
411
- const singleArr2 = [42];
412
- removeFromArray(singleArr2, 99);
413
- expect(singleArr2).toEqual([42]);
414
-
415
- const sameArr = [5, 5, 5, 5];
416
- removeFromArray(sameArr, 5);
417
- expect(sameArr).toEqual([5, 5, 5]);
418
-
419
- // Contains undefined and null
420
- const nullishArr = [1, null, void 0, 2];
421
- removeFromArray(nullishArr, null);
422
- expect(nullishArr).toEqual([1, void 0, 2]);
423
-
424
- const nullishArr2 = [1, null, void 0, 2];
425
- removeFromArray(nullishArr2, void 0);
426
- expect(nullishArr2).toEqual([1, null, 2]);
427
- });
428
-
429
- test('should handle NaN correctly', () => {
430
- const nanArr = [1, Number.NaN, 2, Number.NaN];
431
- removeFromArray(nanArr, Number.NaN);
432
- expect(nanArr).toEqual([1, 2, Number.NaN]);
433
- });
434
-
435
- test('should handle sparse arrays', () => {
436
- // Sparse array - array with empty slots
437
- // biome-ignore lint/suspicious/noSparseArray: skip
438
- const sparseArr = [1, , 3, , 5];
439
- removeFromArray(sparseArr, void 0);
440
- // Length should not change, empty slots cannot be treated as undefined placeholders
441
- expect(sparseArr).toHaveLength(5);
442
- // biome-ignore lint/suspicious/noSparseArray: skip
443
- expect(sparseArr).toEqual([1, , 3, , 5]);
444
- });
445
-
446
- test('should handle arrays with complex objects', () => {
447
- // Complex object array
448
- const complexArr = [
449
- { id: 1, nested: { value: 'a' } },
450
- { id: 2, nested: { value: 'b' } },
451
- { id: 1, nested: { value: 'a' } } // Same content but different reference
452
- ];
453
- const targetObj = complexArr[0];
454
- removeFromArray(complexArr, targetObj);
455
- expect(complexArr).toHaveLength(2);
456
- expect(complexArr[0]).toEqual({ id: 2, nested: { value: 'b' } });
457
- expect(complexArr[1]).toEqual({ id: 1, nested: { value: 'a' } });
458
-
459
- // Remove non-existing similar object
460
- removeFromArray(complexArr, { id: 1, nested: { value: 'a' } });
461
- expect(complexArr).toHaveLength(2); // No change
462
- });
463
-
464
- test('should handle arrays with different primitive types', () => {
465
- let mixedArr: any[] = [1, '1', true, 1n, Symbol('test')];
466
-
467
- // Remove number 1
468
- removeFromArray(mixedArr, 1);
469
- expect(mixedArr).toEqual(['1', true, 1n, expect.any(Symbol)]);
470
-
471
- mixedArr = [1, '1', true, 1n, Symbol('test')];
472
-
473
- // Remove string '1'
474
- removeFromArray(mixedArr, '1');
475
- expect(mixedArr).toEqual([1, true, 1n, expect.any(Symbol)]);
476
-
477
- const mixedArray: any[] = [
478
- 'string',
479
- 42,
480
- true,
481
- null,
482
- undefined,
483
- { obj: 'value' },
484
- [1, 2, 3],
485
- Symbol('test'),
486
- () => 'fn',
487
- new Date(),
488
- /regex/,
489
- new Map(),
490
- new Set()
491
- ];
492
-
493
- const originalLength = mixedArray.length;
494
-
495
- removeFromArray(mixedArray, 'nonexistent');
496
- expect(mixedArray).toHaveLength(originalLength);
497
-
498
- // Delete existing element
499
- removeFromArray(mixedArray, 42);
500
- expect(mixedArray).toHaveLength(originalLength - 1);
501
- expect(mixedArray.includes(42)).toBe(false);
502
- });
503
-
504
- test('should preserve array structure', () => {
505
- const arr: any[] = [1, 2, 3];
506
- (arr as any).customProperty = 'test';
507
-
508
- removeFromArray(arr, 2);
509
-
510
- expect(arr.length).toBe(2);
511
- expect(arr[0]).toBe(1);
512
- expect(arr[1]).toBe(3);
513
-
514
- expect(arr).toHaveProperty('customProperty', 'test');
515
- expect((arr as any).customProperty).toBe('test');
516
- });
517
-
518
- test('should handle array with getter/setter elements', () => {
519
- const arr: any[] = [1, 2, 3];
520
-
521
- // Add element with getter/setter
522
- Object.defineProperty(arr, '1', {
523
- get() {
524
- return 'getter';
525
- },
526
- set() {
527
- /* setter */
528
- },
529
- enumerable: true
530
- });
531
-
532
- removeFromArray(arr, 'getter');
533
- expect(arr).toHaveLength(2);
534
- expect(arr[0]).toBe(1);
535
- expect(arr[1]).toBe('getter'); // getter still exists
536
- });
537
-
538
- test('should handle strict equality in removeFromArray', () => {
539
- const obj1 = { id: 1 };
540
- const obj2 = { id: 1 }; // Same content but different reference
541
- const arr = [obj1, obj2];
542
-
543
- removeFromArray(arr, obj1);
544
- expect(arr).toHaveLength(1);
545
- expect(arr[0]).toBe(obj2);
546
-
547
- // Objects with same content but different reference will not be deleted
548
- removeFromArray(arr, { id: 1 });
549
- expect(arr).toHaveLength(1);
550
- });
551
- });
552
-
553
- describe('isValidConfirmHookResult', () => {
554
- test('should return true for boolean values', () => {
555
- expect(isValidConfirmHookResult(true)).toBe(false);
556
- expect(isValidConfirmHookResult(false)).toBe(true);
557
- // Does not accept new Boolean() wrapped boolean values
558
- expect(isValidConfirmHookResult(new Boolean(true))).toBe(false);
559
- expect(isValidConfirmHookResult(new Boolean(false))).toBe(false);
560
- });
561
-
562
- test('should return true for string values', () => {
563
- expect(isValidConfirmHookResult('')).toBe(true);
564
- expect(isValidConfirmHookResult('0')).toBe(true);
565
- expect(isValidConfirmHookResult('1')).toBe(true);
566
- expect(isValidConfirmHookResult('test')).toBe(true);
567
- // Does not accept new String() wrapped strings
568
- expect(isValidConfirmHookResult(new String(''))).toBe(false);
569
- expect(isValidConfirmHookResult(new String('0'))).toBe(false);
570
- expect(isValidConfirmHookResult(new String('1'))).toBe(false);
571
- expect(isValidConfirmHookResult(new String('test'))).toBe(false);
572
- });
573
-
574
- test('should return true for function values', () => {
575
- expect(isValidConfirmHookResult(() => {})).toBe(true);
576
- expect(isValidConfirmHookResult(async () => {})).toBe(true);
577
- expect(isValidConfirmHookResult(new Function('return 1;'))).toBe(true);
578
- expect(isValidConfirmHookResult(AsyncFunction('return 1;'))).toBe(true);
579
- });
580
-
581
- test('should return true for plain objects', () => {
582
- expect(isValidConfirmHookResult({})).toBe(true);
583
- expect(isValidConfirmHookResult({ key: 'value' })).toBe(true);
584
- expect(isValidConfirmHookResult(new Object())).toBe(true);
585
- expect(isValidConfirmHookResult(Object.create(null))).toBe(true);
586
- expect(isValidConfirmHookResult(Object.create({}))).toBe(true);
587
- expect(isValidConfirmHookResult(Object.create(Object.prototype))).toBe(
588
- true
589
- );
590
- expect(isValidConfirmHookResult({ __proto__: null })).toBe(true);
591
- expect(isValidConfirmHookResult({ [Symbol.toStringTag]: 'Tag' })).toBe(
592
- true
593
- );
594
- expect(
595
- isValidConfirmHookResult({
596
- toString: () => '[object CustomObject]'
597
- })
598
- ).toBe(true);
599
- expect(isValidConfirmHookResult(Math)).toBe(true);
600
- expect(isValidConfirmHookResult(JSON)).toBe(true);
601
- });
602
-
603
- test('should return false for invalid types', () => {
604
- // Special values
605
- expect(isValidConfirmHookResult(null)).toBe(false);
606
- expect(isValidConfirmHookResult(void 0)).toBe(false);
607
- // Numbers & bigint
608
- expect(isValidConfirmHookResult(123)).toBe(false);
609
- expect(isValidConfirmHookResult(0)).toBe(false);
610
- expect(isValidConfirmHookResult(123n)).toBe(false);
611
- expect(isValidConfirmHookResult(0n)).toBe(false);
612
- expect(isValidConfirmHookResult(new Number('1'))).toBe(false);
613
- expect(isValidConfirmHookResult(+'a')).toBe(false);
614
- expect(isValidConfirmHookResult(Number.NaN)).toBe(false);
615
- expect(isValidConfirmHookResult(new Number('a'))).toBe(false);
616
- // Arrays
617
- expect(isValidConfirmHookResult([])).toBe(false);
618
- expect(isValidConfirmHookResult(['a', 'b'])).toBe(false);
619
- expect(isValidConfirmHookResult(new Array())).toBe(false);
620
- expect(isValidConfirmHookResult(new Array(1, 2, 3))).toBe(false);
621
- expect(isValidConfirmHookResult(new Array(1))).toBe(false);
622
- expect(isValidConfirmHookResult(new Uint8Array(8))).toBe(false);
623
- expect(isValidConfirmHookResult(new ArrayBuffer(8))).toBe(false);
624
- expect(isValidConfirmHookResult(new DataView(new ArrayBuffer(8)))).toBe(
625
- false
626
- );
627
- // Special objects
628
- expect(isValidConfirmHookResult(new Date())).toBe(false);
629
- expect(isValidConfirmHookResult(Symbol('test'))).toBe(false);
630
- expect(isValidConfirmHookResult(new Map())).toBe(false);
631
- expect(isValidConfirmHookResult(new Set())).toBe(false);
632
- expect(isValidConfirmHookResult(new WeakMap())).toBe(false);
633
- expect(isValidConfirmHookResult(new WeakSet())).toBe(false);
634
- expect(isValidConfirmHookResult(/test/)).toBe(false);
635
- expect(isValidConfirmHookResult(new Error('test'))).toBe(false);
636
- expect(isValidConfirmHookResult(Promise.resolve())).toBe(false);
637
- expect(isValidConfirmHookResult(new URL('https://example.com'))).toBe(
638
- false
639
- );
640
- expect(isValidConfirmHookResult(new URLSearchParams('key=value'))).toBe(
641
- false
642
- );
643
- expect(isValidConfirmHookResult(new Blob(['test']))).toBe(false);
644
- expect(isValidConfirmHookResult(new File(['test'], 'file.txt'))).toBe(
645
- false
646
- );
647
- });
648
-
649
- test('should handle edge cases for confirmation hook results', () => {
650
- // Promise related - should return false
651
- expect(isValidConfirmHookResult(Promise.resolve(true))).toBe(false);
652
-
653
- // Handle rejected promise to avoid unhandled rejection
654
- const rejectedPromise = Promise.reject(false);
655
- rejectedPromise.catch(() => {}); // Prevent unhandled rejection
656
- expect(isValidConfirmHookResult(rejectedPromise)).toBe(false);
657
-
658
- // Async functions return Promise, but the function itself is valid
659
- const asyncFn = async () => true;
660
- expect(isValidConfirmHookResult(asyncFn)).toBe(true);
661
-
662
- // Arrow functions and regular functions
663
- expect(isValidConfirmHookResult((x: number) => x > 0)).toBe(true);
664
- expect(isValidConfirmHookResult((x: number) => x > 0)).toBe(true);
665
-
666
- // Generator function
667
- expect(
668
- isValidConfirmHookResult(function* () {
669
- yield 1;
670
- })
671
- ).toBe(true);
672
-
673
- // Class constructor
674
- class TestClass {}
675
- expect(isValidConfirmHookResult(TestClass)).toBe(true);
676
- });
677
-
678
- test('should handle objects with Symbol properties', () => {
679
- // Objects containing Symbol properties
680
- const symKey = Symbol('key');
681
- const objWithSymbol = { [symKey]: 'value', regular: 'prop' };
682
- expect(isValidConfirmHookResult(objWithSymbol)).toBe(true);
683
-
684
- // Symbol.toStringTag object
685
- const objWithToStringTag = { [Symbol.toStringTag]: 'CustomObject' };
686
- expect(isValidConfirmHookResult(objWithToStringTag)).toBe(true);
687
- });
688
-
689
- test('should handle frozen and sealed objects', () => {
690
- // Frozen objects
691
- const frozenObj = Object.freeze({ frozen: true });
692
- expect(isValidConfirmHookResult(frozenObj)).toBe(true);
693
-
694
- // Sealed objects
695
- const sealedObj = Object.seal({ sealed: true });
696
- expect(isValidConfirmHookResult(sealedObj)).toBe(true);
697
-
698
- // Non-extensible object
699
- const nonExtensibleObj = Object.preventExtensions({
700
- nonExtensible: true
701
- });
702
- expect(isValidConfirmHookResult(nonExtensibleObj)).toBe(true);
703
- });
704
-
705
- test('should handle function edge cases', () => {
706
- // Bound function
707
- const originalFn = function (this: any, x: number) {
708
- return this.value + x;
709
- };
710
- const boundFn = originalFn.bind({ value: 10 });
711
- expect(isValidConfirmHookResult(boundFn)).toBe(true);
712
-
713
- // Function call and apply methods
714
- expect(isValidConfirmHookResult(originalFn.call)).toBe(true);
715
- expect(isValidConfirmHookResult(originalFn.apply)).toBe(true);
716
-
717
- // Built-in functions
718
- expect(isValidConfirmHookResult(console.log)).toBe(true);
719
- expect(isValidConfirmHookResult(Math.max)).toBe(true);
720
- expect(isValidConfirmHookResult(Array.prototype.push)).toBe(true);
721
- });
722
-
723
- test('should handle class and constructor edge cases', () => {
724
- // Class instance - according to actual implementation, class instances are identified as objects through toString check
725
- class TestClass {
726
- constructor(public value: number) {}
727
- }
728
- expect(isValidConfirmHookResult(new TestClass(42))).toBe(true); // Class instance passes isPlainObject check
729
-
730
- // Inherited class instance
731
- class ChildClass extends TestClass {}
732
- expect(isValidConfirmHookResult(new ChildClass(42))).toBe(true); // Also identified as object
733
-
734
- // Error instances
735
- expect(isValidConfirmHookResult(new Error('test'))).toBe(false);
736
- expect(isValidConfirmHookResult(new TypeError('test'))).toBe(false);
737
- });
738
- });
739
-
740
- describe('Performance Tests', () => {
741
- test('should handle large arrays efficiently in removeFromArray', () => {
742
- const largeArray = Array.from({ length: 10000 }, (_, i) => i);
743
- const target = 5000;
744
-
745
- const start = performance.now();
746
- removeFromArray(largeArray, target);
747
- const end = performance.now();
748
-
749
- expect(largeArray).toHaveLength(9999);
750
- expect(largeArray.includes(target)).toBe(false);
751
- expect(end - start).toBeLessThan(100); // Should complete within 100ms
752
- });
753
-
754
- test('should handle multiple rapid calls to utility functions', () => {
755
- const iterations = 1000;
756
- const start = performance.now();
757
-
758
- for (let i = 0; i < iterations; ++i) {
759
- isNotNullish(i);
760
- isPlainObject({ value: i });
761
- isValidConfirmHookResult(false);
762
- }
763
-
764
- const end = performance.now();
765
- expect(end - start).toBeLessThan(50); // Should complete within 50ms
766
- });
767
-
768
- test('should handle rapid array modifications', () => {
769
- const arr = [1, 2, 3, 4, 5];
770
- const start = performance.now();
771
-
772
- // Perform multiple delete operations quickly
773
- removeFromArray(arr, 3);
774
- removeFromArray(arr, 1);
775
- removeFromArray(arr, 5);
776
- removeFromArray(arr, 99); // Non-existing element
777
-
778
- const end = performance.now();
779
- expect(arr).toEqual([2, 4]);
780
- expect(end - start).toBeLessThan(10); // Should be very fast
781
- });
782
- });
783
-
784
- // Error handling and boundary tests
785
- describe('Error Handling Tests', () => {
786
- test('should handle circular references in objects', () => {
787
- const circularObj: any = { name: 'circular' };
788
- circularObj.self = circularObj;
789
-
790
- // These functions should handle circular references without crashing
791
- expect(() => isPlainObject(circularObj)).not.toThrow();
792
- expect(() => isValidConfirmHookResult(circularObj)).not.toThrow();
793
- expect(() => isNotNullish(circularObj)).not.toThrow();
794
-
795
- expect(isPlainObject(circularObj)).toBe(true);
796
- expect(isValidConfirmHookResult(circularObj)).toBe(true);
797
- expect(isNotNullish(circularObj)).toBe(true);
798
- });
799
- });
800
-
801
- describe('isUrlEqual', () => {
802
- test('should return false when url2 is null or undefined', () => {
803
- const url1 = new URL('https://example.com/path');
804
-
805
- // Test comparison with null
806
- expect(isUrlEqual(url1, null)).toBe(false);
807
-
808
- // Test comparison with undefined (optional parameter not provided)
809
- expect(isUrlEqual(url1, undefined)).toBe(false);
810
-
811
- // Test omitting second parameter (automatically undefined)
812
- expect(isUrlEqual(url1)).toBe(false);
813
-
814
- const urlWithQuery = new URL('https://example.com/path?a=1&b=2');
815
- expect(isUrlEqual(urlWithQuery, null)).toBe(false);
816
- expect(isUrlEqual(urlWithQuery, undefined)).toBe(false);
817
- expect(isUrlEqual(urlWithQuery)).toBe(false);
818
-
819
- const urlWithHash = new URL('https://example.com/path#section');
820
- expect(isUrlEqual(urlWithHash, null)).toBe(false);
821
- expect(isUrlEqual(urlWithHash)).toBe(false);
822
-
823
- const urlWithUserInfo = new URL('https://user:pass@example.com/path');
824
- expect(isUrlEqual(urlWithUserInfo, null)).toBe(false);
825
- expect(isUrlEqual(urlWithUserInfo)).toBe(false);
826
- });
827
-
828
- test('should return true for identical URL objects', () => {
829
- const url1 = new URL('https://example.com/path?a=1&b=2');
830
- const url2 = new URL('https://example.com/path?a=1&b=2');
831
- expect(isUrlEqual(url1, url2)).toBe(true);
832
- });
833
-
834
- test('should return true for same reference', () => {
835
- const url = new URL('https://example.com/path');
836
- expect(isUrlEqual(url, url)).toBe(true);
837
- });
838
-
839
- test('should return false for different protocols', () => {
840
- const url1 = new URL('http://example.com/path');
841
- const url2 = new URL('https://example.com/path');
842
- expect(isUrlEqual(url1, url2)).toBe(false);
843
- });
844
-
845
- test('should return false for different hostnames', () => {
846
- const url1 = new URL('https://example.com/path');
847
- const url2 = new URL('https://test.com/path');
848
- expect(isUrlEqual(url1, url2)).toBe(false);
849
- });
850
-
851
- test('should return false for different ports', () => {
852
- const url1 = new URL('https://example.com:8080/path');
853
- const url2 = new URL('https://example.com:9090/path');
854
- expect(isUrlEqual(url1, url2)).toBe(false);
855
- });
856
-
857
- test('should return false for different pathnames', () => {
858
- const url1 = new URL('https://example.com/path1');
859
- const url2 = new URL('https://example.com/path2');
860
- expect(isUrlEqual(url1, url2)).toBe(false);
861
- });
862
-
863
- test('should return false for different hashes', () => {
864
- const url1 = new URL('https://example.com/path#section1');
865
- const url2 = new URL('https://example.com/path#section2');
866
- expect(isUrlEqual(url1, url2)).toBe(false);
867
- });
868
-
869
- test('should handle empty hashes correctly', () => {
870
- const url1 = new URL('https://example.com/path');
871
- const url2 = new URL('https://example.com/path#');
872
- expect(isUrlEqual(url1, url2)).toBe(true); // Both hashes are empty strings, should be equal
873
- });
874
-
875
- test('should ignore query parameter order', () => {
876
- const url1 = new URL('https://example.com/path?a=1&b=2&c=3');
877
- const url2 = new URL('https://example.com/path?c=3&a=1&b=2');
878
- expect(isUrlEqual(url1, url2)).toBe(true);
879
- });
880
-
881
- test('should handle duplicate query parameters correctly', () => {
882
- const url1 = new URL('https://example.com/path?a=1&a=2&b=3');
883
- const url2 = new URL('https://example.com/path?b=3&a=2&a=1');
884
- // url1's query `a` should be 2, but url2's query `a` should be 1, so they should not be equal
885
- expect(isUrlEqual(url1, url2)).toBe(false);
886
- });
887
-
888
- test('should return false for different duplicate parameter values', () => {
889
- const url1 = new URL('https://example.com/path?a=1&a=2');
890
- const url2 = new URL('https://example.com/path?a=1&a=3');
891
- expect(isUrlEqual(url1, url2)).toBe(false);
892
- });
893
-
894
- test('should return false for different number of duplicate parameters', () => {
895
- const url1 = new URL('https://example.com/path?a=1&a=2');
896
- const url2 = new URL('https://example.com/path?a=1');
897
- expect(isUrlEqual(url1, url2)).toBe(false);
898
- });
899
-
900
- test('should handle complex query parameter scenarios', () => {
901
- const url1 = new URL(
902
- 'https://example.com/path?tag=red&tag=blue&category=tech&tag=green'
903
- );
904
- const url2 = new URL(
905
- 'https://example.com/path?category=tech&tag=blue&tag=green&tag=red'
906
- );
907
- // query `tag` one is green and one is red, so they are different
908
- expect(isUrlEqual(url1, url2)).toBe(false);
909
-
910
- const url3 = new URL(
911
- 'https://example.com/path?search=hello%20world&filter=a%26b'
912
- );
913
- const url4 = new URL(
914
- 'https://example.com/path?filter=a%26b&search=hello%20world'
915
- );
916
- expect(isUrlEqual(url3, url4)).toBe(true);
917
- });
918
-
919
- test('should handle empty query parameters', () => {
920
- const url1 = new URL('https://example.com/path?');
921
- const url2 = new URL('https://example.com/path');
922
- expect(isUrlEqual(url1, url2)).toBe(true);
923
-
924
- const url3 = new URL('https://example.com/path?a=');
925
- const url4 = new URL('https://example.com/path?a=');
926
- expect(isUrlEqual(url3, url4)).toBe(true);
927
- });
928
-
929
- test('should handle URLs with userinfo', () => {
930
- const url1 = new URL('https://user:pass@example.com/path');
931
- const url2 = new URL('https://user:pass@example.com/path');
932
- expect(isUrlEqual(url1, url2)).toBe(true);
933
-
934
- const url3 = new URL('https://user1:pass@example.com/path');
935
- const url4 = new URL('https://user2:pass@example.com/path');
936
- expect(isUrlEqual(url3, url4)).toBe(false);
937
- });
938
-
939
- test('should handle default ports correctly', () => {
940
- const url1 = new URL('https://example.com/path');
941
- const url2 = new URL('https://example.com:443/path');
942
- expect(isUrlEqual(url1, url2)).toBe(true);
943
-
944
- const url3 = new URL('http://example.com/path');
945
- const url4 = new URL('http://example.com:80/path');
946
- expect(isUrlEqual(url3, url4)).toBe(true);
947
- });
948
-
949
- test('should handle trailing slashes in pathnames', () => {
950
- const url1 = new URL('https://example.com/path/');
951
- const url2 = new URL('https://example.com/path');
952
- expect(isUrlEqual(url1, url2)).toBe(false);
953
- });
954
-
955
- test('should handle case sensitivity correctly', () => {
956
- // Hostname should be case-insensitive (handled by URL constructor)
957
- const url1 = new URL('https://Example.Com/path');
958
- const url2 = new URL('https://example.com/path');
959
- expect(isUrlEqual(url1, url2)).toBe(true);
960
-
961
- // Paths are case-sensitive
962
- const url3 = new URL('https://example.com/Path');
963
- const url4 = new URL('https://example.com/path');
964
- expect(isUrlEqual(url3, url4)).toBe(false);
965
-
966
- // Query parameters are case-sensitive
967
- const url5 = new URL('https://example.com/path?Key=Value');
968
- const url6 = new URL('https://example.com/path?key=value');
969
- expect(isUrlEqual(url5, url6)).toBe(false);
970
- });
971
-
972
- test('should handle special characters in URLs', () => {
973
- const url1 = new URL(
974
- 'https://example.com/path with spaces?q=hello world'
975
- );
976
- const url2 = new URL(
977
- 'https://example.com/path%20with%20spaces?q=hello%20world'
978
- );
979
- expect(isUrlEqual(url1, url2)).toBe(true);
980
-
981
- const url3 = new URL('https://example.com/path?unicode=测试');
982
- const url4 = new URL(
983
- 'https://example.com/path?unicode=%E6%B5%8B%E8%AF%95'
984
- );
985
- expect(isUrlEqual(url3, url4)).toBe(true);
986
- });
987
-
988
- test('should handle edge cases with empty values', () => {
989
- // Empty query parameter values
990
- const url1 = new URL('https://example.com/path?a=&b=test');
991
- const url2 = new URL('https://example.com/path?b=test&a=');
992
- expect(isUrlEqual(url1, url2)).toBe(true);
993
-
994
- // Parameters with only keys but no values
995
- const url3 = new URL('https://example.com/path?flag');
996
- const url4 = new URL('https://example.com/path?flag=');
997
- expect(isUrlEqual(url3, url4)).toBe(true);
998
- });
999
-
1000
- test('should handle performance with many parameters', () => {
1001
- // Build URL with many parameters
1002
- const params1 = new URLSearchParams();
1003
- const params2 = new URLSearchParams();
1004
-
1005
- // Add 100 parameters in different order
1006
- for (let i = 0; i < 100; i++) {
1007
- params1.append(`param${i}`, `value${i}`);
1008
- params2.append(`param${99 - i}`, `value${99 - i}`);
1009
- }
1010
-
1011
- const url1 = new URL(`https://example.com/path?${params1}`);
1012
- const url2 = new URL(`https://example.com/path?${params2}`);
1013
-
1014
- const start = performance.now();
1015
- const result = isUrlEqual(url1, url2);
1016
- const end = performance.now();
1017
-
1018
- expect(result).toBe(true);
1019
- expect(end - start).toBeLessThan(50); // Should complete within 50ms
1020
- });
1021
-
1022
- test('should handle complex real-world scenarios', () => {
1023
- const baseUrl = 'https://api.example.com/v1/users';
1024
-
1025
- // Pagination and filter parameters
1026
- const url1 = new URL(
1027
- `${baseUrl}?page=1&limit=20&sort=name&filter=active&tag=user&tag=admin`
1028
- );
1029
- const url2 = new URL(
1030
- `${baseUrl}?limit=20&tag=user&page=1&tag=admin&filter=active&sort=name`
1031
- );
1032
- expect(isUrlEqual(url1, url2)).toBe(true);
1033
-
1034
- // OAuth parameters
1035
- const oauthUrl1 = new URL(
1036
- 'https://oauth.example.com/authorize?client_id=123&redirect_uri=https%3A%2F%2Fapp.com%2Fcallback&scope=read%20write&state=random123'
1037
- );
1038
- const oauthUrl2 = new URL(
1039
- 'https://oauth.example.com/authorize?scope=read%20write&state=random123&client_id=123&redirect_uri=https%3A%2F%2Fapp.com%2Fcallback'
1040
- );
1041
- expect(isUrlEqual(oauthUrl1, oauthUrl2)).toBe(true);
1042
- });
1043
- });
1044
-
1045
- describe('isRouteMatched', () => {
1046
- const createOptions = (): RouterParsedOptions => {
1047
- return parsedOptions({
1048
- base: new URL('http://localhost:3000/'),
1049
- routes: [
1050
- { path: '/user/:id', component: 'UserComponent' },
1051
- { path: '/settings', component: 'SettingsComponent' }
1052
- ]
1053
- });
1054
- };
1055
-
1056
- describe('route match type', () => {
1057
- test('should match routes with same config', () => {
1058
- const options = createOptions();
1059
- const route1 = new Route({
1060
- options,
1061
- toType: RouteType.push,
1062
- toInput: '/user/123'
1063
- });
1064
- const route2 = new Route({
1065
- options,
1066
- toType: RouteType.push,
1067
- toInput: '/user/456'
1068
- });
1069
-
1070
- expect(isRouteMatched(route1, route2, 'route')).toBe(true);
1071
- });
1072
-
1073
- test('should not match routes with different config', () => {
1074
- const options = createOptions();
1075
- const route1 = new Route({
1076
- options,
1077
- toType: RouteType.push,
1078
- toInput: '/user/123'
1079
- });
1080
- const route2 = new Route({
1081
- options,
1082
- toType: RouteType.push,
1083
- toInput: '/settings'
1084
- });
1085
-
1086
- expect(isRouteMatched(route1, route2, 'route')).toBe(false);
1087
- });
1088
-
1089
- test('should return false when second route is null', () => {
1090
- const options = createOptions();
1091
- const route1 = new Route({
1092
- options,
1093
- toType: RouteType.push,
1094
- toInput: '/user/123'
1095
- });
1096
-
1097
- expect(isRouteMatched(route1, null, 'route')).toBe(false);
1098
- });
1099
- });
1100
-
1101
- describe('exact match type', () => {
1102
- test('should match routes with same fullPath', () => {
1103
- const options = createOptions();
1104
- const route1 = new Route({
1105
- options,
1106
- toType: RouteType.push,
1107
- toInput: '/user/123?tab=profile'
1108
- });
1109
- const route2 = new Route({
1110
- options,
1111
- toType: RouteType.push,
1112
- toInput: '/user/123?tab=profile'
1113
- });
1114
-
1115
- expect(isRouteMatched(route1, route2, 'exact')).toBe(true);
1116
- });
1117
-
1118
- test('should not match routes with different fullPath', () => {
1119
- const options = createOptions();
1120
- const route1 = new Route({
1121
- options,
1122
- toType: RouteType.push,
1123
- toInput: '/user/123?tab=profile'
1124
- });
1125
- const route2 = new Route({
1126
- options,
1127
- toType: RouteType.push,
1128
- toInput: '/user/123?tab=settings'
1129
- });
1130
-
1131
- expect(isRouteMatched(route1, route2, 'exact')).toBe(false);
1132
- });
1133
-
1134
- test('should not match routes with same path but different query', () => {
1135
- const options = createOptions();
1136
- const route1 = new Route({
1137
- options,
1138
- toType: RouteType.push,
1139
- toInput: '/user/123'
1140
- });
1141
- const route2 = new Route({
1142
- options,
1143
- toType: RouteType.push,
1144
- toInput: '/user/123?tab=profile'
1145
- });
1146
-
1147
- expect(isRouteMatched(route1, route2, 'exact')).toBe(false);
1148
- });
1149
- });
1150
-
1151
- describe('include match type', () => {
1152
- test('should match when route1 fullPath starts with route2 fullPath', () => {
1153
- const options = createOptions();
1154
- const route1 = new Route({
1155
- options,
1156
- toType: RouteType.push,
1157
- toInput: '/user/123/profile/settings'
1158
- });
1159
- const route2 = new Route({
1160
- options,
1161
- toType: RouteType.push,
1162
- toInput: '/user/123'
1163
- });
1164
-
1165
- expect(isRouteMatched(route1, route2, 'include')).toBe(true);
1166
- });
1167
-
1168
- test('should match when fullPaths are exactly the same', () => {
1169
- const options = createOptions();
1170
- const route1 = new Route({
1171
- options,
1172
- toType: RouteType.push,
1173
- toInput: '/user/123'
1174
- });
1175
- const route2 = new Route({
1176
- options,
1177
- toType: RouteType.push,
1178
- toInput: '/user/123'
1179
- });
1180
-
1181
- expect(isRouteMatched(route1, route2, 'include')).toBe(true);
1182
- });
1183
-
1184
- test('should not match when route1 fullPath does not start with route2 fullPath', () => {
1185
- const options = createOptions();
1186
- const route1 = new Route({
1187
- options,
1188
- toType: RouteType.push,
1189
- toInput: '/user/123'
1190
- });
1191
- const route2 = new Route({
1192
- options,
1193
- toType: RouteType.push,
1194
- toInput: '/user/456'
1195
- });
1196
-
1197
- expect(isRouteMatched(route1, route2, 'include')).toBe(false);
1198
- });
1199
-
1200
- test('should not match when route2 fullPath is longer', () => {
1201
- const options = createOptions();
1202
- const route1 = new Route({
1203
- options,
1204
- toType: RouteType.push,
1205
- toInput: '/user'
1206
- });
1207
- const route2 = new Route({
1208
- options,
1209
- toType: RouteType.push,
1210
- toInput: '/user/123'
1211
- });
1212
-
1213
- expect(isRouteMatched(route1, route2, 'include')).toBe(false);
1214
- });
1215
- });
1216
-
1217
- describe('edge cases', () => {
1218
- test('should handle root path correctly', () => {
1219
- const options = createOptions();
1220
- const route1 = new Route({ options });
1221
- const route2 = new Route({ options });
1222
-
1223
- expect(isRouteMatched(route1, route2, 'route')).toBe(true);
1224
- expect(isRouteMatched(route1, route2, 'exact')).toBe(true);
1225
- expect(isRouteMatched(route1, route2, 'include')).toBe(true);
1226
- });
1227
-
1228
- test('should handle hash in fullPath', () => {
1229
- const options = createOptions();
1230
- const route1 = new Route({
1231
- options,
1232
- toType: RouteType.push,
1233
- toInput: '/user/123#section1'
1234
- });
1235
- const route2 = new Route({
1236
- options,
1237
- toType: RouteType.push,
1238
- toInput: '/user/123#section2'
1239
- });
1240
-
1241
- expect(isRouteMatched(route1, route2, 'route')).toBe(true);
1242
- expect(isRouteMatched(route1, route2, 'exact')).toBe(false);
1243
- });
1244
-
1245
- test('should return false for invalid match type', () => {
1246
- const options = createOptions();
1247
- const route1 = new Route({
1248
- options,
1249
- toType: RouteType.push,
1250
- toInput: '/user/123'
1251
- });
1252
- const route2 = new Route({
1253
- options,
1254
- toType: RouteType.push,
1255
- toInput: '/user/123'
1256
- });
1257
-
1258
- // @ts-expect-error - testing invalid match type
1259
- expect(isRouteMatched(route1, route2, 'invalid')).toBe(false);
1260
- });
1261
- });
1262
- });