@esmx/router 3.0.0-rc.18 → 3.0.0-rc.20
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/LICENSE +1 -1
- package/README.md +70 -0
- package/README.zh-CN.md +70 -0
- package/dist/error.d.ts +23 -0
- package/dist/error.mjs +61 -0
- package/dist/increment-id.d.ts +7 -0
- package/dist/increment-id.mjs +11 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.mjs +14 -3
- package/dist/index.test.mjs +8 -0
- package/dist/location.d.ts +15 -0
- package/dist/location.mjs +53 -0
- package/dist/location.test.d.ts +8 -0
- package/dist/location.test.mjs +370 -0
- package/dist/matcher.d.ts +3 -0
- package/dist/matcher.mjs +44 -0
- package/dist/matcher.test.mjs +1492 -0
- package/dist/micro-app.d.ts +18 -0
- package/dist/micro-app.dom.test.d.ts +1 -0
- package/dist/micro-app.dom.test.mjs +532 -0
- package/dist/micro-app.mjs +80 -0
- package/dist/navigation.d.ts +43 -0
- package/dist/navigation.mjs +143 -0
- package/dist/navigation.test.d.ts +1 -0
- package/dist/navigation.test.mjs +681 -0
- package/dist/options.d.ts +4 -0
- package/dist/options.mjs +88 -0
- package/dist/route-task.d.ts +40 -0
- package/dist/route-task.mjs +75 -0
- package/dist/route-task.test.d.ts +1 -0
- package/dist/route-task.test.mjs +673 -0
- package/dist/route-transition.d.ts +53 -0
- package/dist/route-transition.mjs +307 -0
- package/dist/route-transition.test.d.ts +1 -0
- package/dist/route-transition.test.mjs +146 -0
- package/dist/route.d.ts +72 -0
- package/dist/route.mjs +194 -0
- package/dist/route.test.d.ts +1 -0
- package/dist/route.test.mjs +1664 -0
- package/dist/router-back.test.d.ts +1 -0
- package/dist/router-back.test.mjs +361 -0
- package/dist/router-forward.test.d.ts +1 -0
- package/dist/router-forward.test.mjs +376 -0
- package/dist/router-go.test.d.ts +1 -0
- package/dist/router-go.test.mjs +73 -0
- package/dist/router-guards-cleanup.test.d.ts +1 -0
- package/dist/router-guards-cleanup.test.mjs +437 -0
- package/dist/router-link.d.ts +10 -0
- package/dist/router-link.mjs +126 -0
- package/dist/router-push.test.d.ts +1 -0
- package/dist/router-push.test.mjs +115 -0
- package/dist/router-replace.test.d.ts +1 -0
- package/dist/router-replace.test.mjs +114 -0
- package/dist/router-resolve.test.d.ts +1 -0
- package/dist/router-resolve.test.mjs +393 -0
- package/dist/router-restart-app.dom.test.d.ts +1 -0
- package/dist/router-restart-app.dom.test.mjs +616 -0
- package/dist/router-window-navigation.test.d.ts +1 -0
- package/dist/router-window-navigation.test.mjs +359 -0
- package/dist/router.d.ts +109 -102
- package/dist/router.mjs +260 -361
- package/dist/types.d.ts +246 -0
- package/dist/types.mjs +18 -0
- package/dist/util.d.ts +26 -0
- package/dist/util.mjs +53 -0
- package/dist/util.test.d.ts +1 -0
- package/dist/util.test.mjs +1020 -0
- package/package.json +10 -13
- package/src/error.ts +84 -0
- package/src/increment-id.ts +12 -0
- package/src/index.test.ts +9 -0
- package/src/index.ts +54 -3
- package/src/location.test.ts +406 -0
- package/src/location.ts +96 -0
- package/src/matcher.test.ts +1685 -0
- package/src/matcher.ts +59 -0
- package/src/micro-app.dom.test.ts +708 -0
- package/src/micro-app.ts +101 -0
- package/src/navigation.test.ts +858 -0
- package/src/navigation.ts +195 -0
- package/src/options.ts +131 -0
- package/src/route-task.test.ts +901 -0
- package/src/route-task.ts +105 -0
- package/src/route-transition.test.ts +178 -0
- package/src/route-transition.ts +425 -0
- package/src/route.test.ts +2014 -0
- package/src/route.ts +308 -0
- package/src/router-back.test.ts +487 -0
- package/src/router-forward.test.ts +506 -0
- package/src/router-go.test.ts +91 -0
- package/src/router-guards-cleanup.test.ts +595 -0
- package/src/router-link.ts +235 -0
- package/src/router-push.test.ts +140 -0
- package/src/router-replace.test.ts +139 -0
- package/src/router-resolve.test.ts +475 -0
- package/src/router-restart-app.dom.test.ts +783 -0
- package/src/router-window-navigation.test.ts +457 -0
- package/src/router.ts +289 -470
- package/src/types.ts +341 -0
- package/src/util.test.ts +1262 -0
- package/src/util.ts +116 -0
- package/dist/history/abstract.d.ts +0 -29
- package/dist/history/abstract.mjs +0 -107
- package/dist/history/base.d.ts +0 -79
- package/dist/history/base.mjs +0 -275
- package/dist/history/html.d.ts +0 -30
- package/dist/history/html.mjs +0 -183
- package/dist/history/index.d.ts +0 -7
- package/dist/history/index.mjs +0 -16
- package/dist/matcher/create-matcher.d.ts +0 -5
- package/dist/matcher/create-matcher.mjs +0 -218
- package/dist/matcher/create-matcher.spec.mjs +0 -0
- package/dist/matcher/index.d.ts +0 -1
- package/dist/matcher/index.mjs +0 -1
- package/dist/task-pipe/index.d.ts +0 -1
- package/dist/task-pipe/index.mjs +0 -1
- package/dist/task-pipe/task.d.ts +0 -30
- package/dist/task-pipe/task.mjs +0 -66
- package/dist/types/index.d.ts +0 -694
- package/dist/types/index.mjs +0 -6
- package/dist/utils/bom.d.ts +0 -5
- package/dist/utils/bom.mjs +0 -10
- package/dist/utils/encoding.d.ts +0 -48
- package/dist/utils/encoding.mjs +0 -44
- package/dist/utils/guards.d.ts +0 -9
- package/dist/utils/guards.mjs +0 -12
- package/dist/utils/index.d.ts +0 -7
- package/dist/utils/index.mjs +0 -27
- package/dist/utils/path.d.ts +0 -60
- package/dist/utils/path.mjs +0 -282
- package/dist/utils/path.spec.mjs +0 -27
- package/dist/utils/scroll.d.ts +0 -25
- package/dist/utils/scroll.mjs +0 -59
- package/dist/utils/utils.d.ts +0 -16
- package/dist/utils/utils.mjs +0 -11
- package/dist/utils/warn.d.ts +0 -2
- package/dist/utils/warn.mjs +0 -12
- package/src/history/abstract.ts +0 -149
- package/src/history/base.ts +0 -408
- package/src/history/html.ts +0 -228
- package/src/history/index.ts +0 -20
- package/src/matcher/create-matcher.spec.ts +0 -3
- package/src/matcher/create-matcher.ts +0 -292
- package/src/matcher/index.ts +0 -1
- package/src/task-pipe/index.ts +0 -1
- package/src/task-pipe/task.ts +0 -97
- package/src/types/index.ts +0 -858
- package/src/utils/bom.ts +0 -14
- package/src/utils/encoding.ts +0 -153
- package/src/utils/guards.ts +0 -25
- package/src/utils/index.ts +0 -27
- package/src/utils/path.spec.ts +0 -32
- package/src/utils/path.ts +0 -418
- package/src/utils/scroll.ts +0 -120
- package/src/utils/utils.ts +0 -30
- package/src/utils/warn.ts +0 -13
- /package/dist/{matcher/create-matcher.spec.d.ts → index.test.d.ts} +0 -0
- /package/dist/{utils/path.spec.d.ts → matcher.test.d.ts} +0 -0
package/src/util.test.ts
ADDED
|
@@ -0,0 +1,1262 @@
|
|
|
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
|
+
});
|