@devp0nt/route0 1.0.0-next.67 → 1.0.0-next.68
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/dist/cjs/flat0.cjs +2 -0
- package/dist/cjs/flat0.cjs.map +1 -0
- package/dist/cjs/flat0.d.cts +2 -0
- package/dist/cjs/index.cjs +234 -311
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +91 -181
- package/dist/esm/flat0.d.ts +2 -0
- package/dist/esm/flat0.js +1 -0
- package/dist/esm/flat0.js.map +1 -0
- package/dist/esm/index.d.ts +91 -181
- package/dist/esm/index.js +234 -311
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
- package/src/flat0.ts +0 -0
- package/src/index.test.ts +409 -621
- package/src/index.ts +419 -836
package/src/index.test.ts
CHANGED
|
@@ -5,40 +5,21 @@ import type {
|
|
|
5
5
|
AnyRoute,
|
|
6
6
|
AnyRouteOrDefinition,
|
|
7
7
|
CallableRoute,
|
|
8
|
-
|
|
8
|
+
IsParamsOptional,
|
|
9
9
|
ExactLocation,
|
|
10
10
|
Extended,
|
|
11
11
|
ExtractRoute,
|
|
12
12
|
ExtractRoutesKeys,
|
|
13
|
-
HasLooseSearch,
|
|
14
|
-
HasNamedSearch,
|
|
15
13
|
HasParams,
|
|
16
|
-
HasSearch,
|
|
17
14
|
IsAncestor,
|
|
18
15
|
IsDescendant,
|
|
19
16
|
IsSame,
|
|
20
17
|
IsSameParams,
|
|
21
18
|
KnownLocation,
|
|
22
|
-
LooseFlatInput,
|
|
23
|
-
LooseFlatInputStringOnly,
|
|
24
|
-
LooseFlatInputWithHash,
|
|
25
|
-
LooseFlatOutput,
|
|
26
|
-
LooseFlatOutputWithHash,
|
|
27
|
-
LooseSearchInput,
|
|
28
|
-
LooseSearchInputStringOnly,
|
|
29
|
-
LooseSearchOutput,
|
|
30
19
|
ParamsInput,
|
|
31
20
|
ParamsInputStringOnly,
|
|
32
21
|
ParamsOutput,
|
|
33
22
|
RoutesPretty,
|
|
34
|
-
StrictFlatInput,
|
|
35
|
-
StrictFlatInputStringOnly,
|
|
36
|
-
StrictFlatInputWithHash,
|
|
37
|
-
StrictFlatOutput,
|
|
38
|
-
StrictFlatOutputWithHash,
|
|
39
|
-
StrictSearchInput,
|
|
40
|
-
StrictSearchInputStringOnly,
|
|
41
|
-
StrictSearchOutput,
|
|
42
23
|
UnknownLocation,
|
|
43
24
|
WeakAncestorLocation,
|
|
44
25
|
WeakDescendantLocation,
|
|
@@ -49,124 +30,212 @@ describe('Route0', () => {
|
|
|
49
30
|
it('simple', () => {
|
|
50
31
|
const route0 = Route0.create('/')
|
|
51
32
|
const path = route0.get()
|
|
52
|
-
const pathHash = route0.get({
|
|
33
|
+
const pathHash = route0.get({ '#': 'zxc' })
|
|
53
34
|
expect(route0).toBeInstanceOf(Route0)
|
|
54
|
-
// expectTypeOf<typeof path>().toEqualTypeOf<'/'>()
|
|
55
35
|
expect(path).toBe('/')
|
|
56
36
|
expectTypeOf<HasParams<typeof route0>>().toEqualTypeOf<false>()
|
|
57
|
-
expect(path).toBe(route0.flat())
|
|
58
37
|
expect(pathHash).toBe('/#zxc')
|
|
59
|
-
expect(pathHash).toBe(route0.flat({ hash: 'zxc' }))
|
|
60
38
|
})
|
|
61
39
|
|
|
62
40
|
it('simple, callable', () => {
|
|
63
41
|
const route0 = Route0.create('/')
|
|
64
42
|
const path = route0()
|
|
65
|
-
const pathHash = route0({
|
|
43
|
+
const pathHash = route0({ '#': 'zxc' })
|
|
66
44
|
expect(route0).toBeInstanceOf(Route0)
|
|
67
|
-
// expectTypeOf<typeof path>().toEqualTypeOf<'/'>()
|
|
68
45
|
expect(path).toBe('/')
|
|
69
|
-
expect(path).toBe(route0.flat())
|
|
70
46
|
expect(pathHash).toBe('/#zxc')
|
|
71
|
-
expect(pathHash).toBe(route0.flat({ hash: 'zxc' }))
|
|
72
47
|
})
|
|
73
48
|
|
|
74
|
-
it('
|
|
49
|
+
it('search', () => {
|
|
75
50
|
const route0 = Route0.create('/')
|
|
76
|
-
const path = route0.get({
|
|
77
|
-
const pathHash = route0.get({
|
|
78
|
-
// expectTypeOf<typeof path>().toEqualTypeOf<`/?${string}`>()
|
|
51
|
+
const path = route0.get({ '?': { q: '1' } })
|
|
52
|
+
const pathHash = route0.get({ '?': { q: '1' }, '#': 'zxc' })
|
|
79
53
|
expect(path).toBe('/?q=1')
|
|
80
|
-
expect(path).toBe(route0.flat({ q: '1' }, false, true))
|
|
81
|
-
expect(path).toBe(route0.flatLoose({ q: '1' }))
|
|
82
54
|
expect(pathHash).toBe('/?q=1#zxc')
|
|
83
|
-
expect(
|
|
84
|
-
expect(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
55
|
+
expect(route0({ '?': { q: '1' } })).toBe(path)
|
|
56
|
+
expect(route0({ '?': { q: '1' }, '#': 'zxc' })).toBe(pathHash)
|
|
57
|
+
expectTypeOf<(typeof route0)['Infer']['SearchInput']>().toEqualTypeOf<Record<string, unknown>>()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('typed search input', () => {
|
|
61
|
+
const route0 = Route0.create('/').search<{ q: string }>()
|
|
62
|
+
const path = route0.get({ '?': { q: '1' } })
|
|
63
|
+
const pathHash = route0.get({ '?': { q: '1' }, '#': 'zxc' })
|
|
64
|
+
const pathNoQuery = route0.get({ '#': 'zxc' })
|
|
65
|
+
// @ts-expect-error invalid search param type
|
|
66
|
+
const pathInvalidQueryType = route0.get({ '?': { q: 1 } })
|
|
67
|
+
// @ts-expect-error invalid search param key
|
|
68
|
+
const pathInvalidQueryKey = route0.get({ '?': { x: '1' } })
|
|
69
|
+
expect(path).toBe('/?q=1')
|
|
70
|
+
expect(pathHash).toBe('/?q=1#zxc')
|
|
71
|
+
expect(pathNoQuery).toBe('/#zxc')
|
|
72
|
+
expect(pathInvalidQueryType).toBe('/?q=1')
|
|
73
|
+
expect(pathInvalidQueryKey).toBe('/?x=1')
|
|
74
|
+
expectTypeOf<(typeof route0)['Infer']['SearchInput']>().toEqualTypeOf<{ q: string }>()
|
|
75
|
+
expect(route0({ '?': { q: '1' } })).toBe(path)
|
|
76
|
+
expect(route0({ '?': { q: '1' }, '#': 'zxc' })).toBe(pathHash)
|
|
77
|
+
expect(route0({ '#': 'zxc' })).toBe(pathNoQuery)
|
|
78
|
+
// @ts-expect-error invalid search param type
|
|
79
|
+
expect(route0({ '?': { q: 1 } })).toBe(pathInvalidQueryType)
|
|
80
|
+
// @ts-expect-error invalid search param key
|
|
81
|
+
expect(route0({ '?': { x: '1' } })).toBe(pathInvalidQueryKey)
|
|
89
82
|
})
|
|
90
83
|
|
|
91
84
|
it('params', () => {
|
|
92
85
|
const route0 = Route0.create('/prefix/:x/some/:y/:z')
|
|
93
86
|
const path = route0.get({ x: '1', y: 2, z: '3' })
|
|
94
|
-
const pathHash = route0.get({ x: '1', y: 2, z: '3',
|
|
95
|
-
// expectTypeOf<typeof path>().toEqualTypeOf<`/prefix/${string}/some/${string}/${string}`>()
|
|
87
|
+
const pathHash = route0.get({ x: '1', y: 2, z: '3', '#': 'zxc' })
|
|
96
88
|
expect(path).toBe('/prefix/1/some/2/3')
|
|
97
89
|
expectTypeOf<HasParams<typeof route0>>().toEqualTypeOf<true>()
|
|
98
|
-
expect(path).toBe(route0.flat({ x: '1', y: 2, z: '3' }))
|
|
99
90
|
expect(pathHash).toBe('/prefix/1/some/2/3#zxc')
|
|
100
|
-
|
|
91
|
+
expectTypeOf<(typeof route0)['Infer']['ParamsInput']>().toEqualTypeOf<{
|
|
92
|
+
x: string | number
|
|
93
|
+
y: string | number
|
|
94
|
+
z: string | number
|
|
95
|
+
}>()
|
|
96
|
+
expectTypeOf<(typeof route0)['Infer']['ParamsOutput']>().toEqualTypeOf<{ x: string; y: string; z: string }>()
|
|
97
|
+
expect(route0({ x: '1', y: 2, z: '3' })).toBe(path)
|
|
98
|
+
expect(route0({ x: '1', y: 2, z: '3', '#': 'zxc' })).toBe(pathHash)
|
|
101
99
|
})
|
|
102
100
|
|
|
103
|
-
it('params and
|
|
101
|
+
it('params and search', () => {
|
|
104
102
|
const route0 = Route0.create('/prefix/:x/some/:y/:z')
|
|
105
|
-
const path = route0.get({ x: '1', y: 2, z: '3',
|
|
106
|
-
const pathHash = route0.get({ x: '1', y: 2, z: '3',
|
|
107
|
-
// expectTypeOf<typeof path>().toEqualTypeOf<`/prefix/${string}/some/${string}/${string}?${string}`>()
|
|
103
|
+
const path = route0.get({ x: '1', y: 2, z: '3', '?': { q: '1' } })
|
|
104
|
+
const pathHash = route0.get({ x: '1', y: 2, z: '3', '?': { q: '1' }, '#': 'zxc' })
|
|
108
105
|
expect(path).toBe('/prefix/1/some/2/3?q=1')
|
|
109
|
-
expect(path).toBe(route0.flat({ x: '1', y: 2, z: '3', q: '1' }, false, true))
|
|
110
106
|
expect(pathHash).toBe('/prefix/1/some/2/3?q=1#zxc')
|
|
111
|
-
expect(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
107
|
+
expect(route0({ x: '1', y: 2, z: '3', '?': { q: '1' } })).toBe(path)
|
|
108
|
+
expect(route0({ x: '1', y: 2, z: '3', '?': { q: '1' }, '#': 'zxc' })).toBe(pathHash)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('optional named params', () => {
|
|
112
|
+
const route0 = Route0.create('/prefix/:x?/:y')
|
|
113
|
+
expect(route0.get({ y: '2' })).toBe('/prefix/2')
|
|
114
|
+
expect(route0.get({ x: '1', y: '2' })).toBe('/prefix/1/2')
|
|
115
|
+
expectTypeOf<(typeof route0)['Infer']['ParamsDefinition']>().toEqualTypeOf<{ x: false; y: true }>()
|
|
116
|
+
expectTypeOf<(typeof route0)['Infer']['ParamsInput']>().toEqualTypeOf<{
|
|
117
|
+
y: string | number
|
|
118
|
+
x?: string | number | undefined
|
|
119
|
+
}>()
|
|
120
|
+
expectTypeOf<(typeof route0)['Infer']['ParamsOutput']>().toEqualTypeOf<{
|
|
121
|
+
y: string
|
|
122
|
+
x: string | undefined
|
|
123
|
+
}>()
|
|
115
124
|
})
|
|
116
125
|
|
|
117
|
-
it('
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
expect(
|
|
124
|
-
expect(
|
|
125
|
-
expect(
|
|
126
|
-
expect(
|
|
126
|
+
it('wildcards and optional wildcards', () => {
|
|
127
|
+
const routeWildcard = Route0.create('/app*')
|
|
128
|
+
const routeOptionalWildcard = Route0.create('/orders/*?')
|
|
129
|
+
expect(routeWildcard.get({ '*': '' })).toBe('/app')
|
|
130
|
+
expect(routeWildcard.get({ '*': '/home' })).toBe('/app/home')
|
|
131
|
+
expect(routeWildcard.get({ '*': '-1' })).toBe('/app-1')
|
|
132
|
+
expect(routeWildcard.getLocation('/app').exact).toBe(true)
|
|
133
|
+
expect(routeWildcard.getLocation('/app/home').exact).toBe(true)
|
|
134
|
+
expect(routeOptionalWildcard.get()).toBe('/orders')
|
|
135
|
+
expect(routeOptionalWildcard.get({ '*': 'completed/list' })).toBe('/orders/completed/list')
|
|
136
|
+
expect(routeOptionalWildcard.getLocation('/orders').exact).toBe(true)
|
|
137
|
+
expect(routeOptionalWildcard.getLocation('/orders/').exact).toBe(true)
|
|
138
|
+
expect(routeOptionalWildcard.getLocation('/orders/completed/list').exact).toBe(true)
|
|
139
|
+
expectTypeOf<(typeof routeWildcard)['Infer']['ParamsDefinition']>().toEqualTypeOf<{ '*': true }>()
|
|
140
|
+
expectTypeOf<(typeof routeOptionalWildcard)['Infer']['ParamsDefinition']>().toEqualTypeOf<{ '*': false }>()
|
|
141
|
+
expectTypeOf<(typeof routeWildcard)['Infer']['ParamsOutput']>().toEqualTypeOf<{ '*': string }>()
|
|
142
|
+
expectTypeOf<(typeof routeOptionalWildcard)['Infer']['ParamsOutput']>().toEqualTypeOf<{ '*': string | undefined }>()
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('difference: /path/x* vs /path/x/* matching', () => {
|
|
146
|
+
const inlineWildcard = Route0.create('/path/x*')
|
|
147
|
+
const segmentWildcard = Route0.create('/path/x/*')
|
|
148
|
+
|
|
149
|
+
// /path/x*:
|
|
150
|
+
// - matches '/path/x'
|
|
151
|
+
// - matches '/path/x123' (same segment continuation)
|
|
152
|
+
// - matches '/path/x/123' (slash continuation)
|
|
153
|
+
expect(inlineWildcard.getLocation('/path/x').exact).toBe(true)
|
|
154
|
+
expect(inlineWildcard.getLocation('/path/x123').exact).toBe(true)
|
|
155
|
+
expect(inlineWildcard.getLocation('/path/x/123').exact).toBe(true)
|
|
156
|
+
|
|
157
|
+
// /path/x/*:
|
|
158
|
+
// - matches '/path/x' and '/path/x/...'
|
|
159
|
+
// - does NOT match '/path/x123' (because 'x' is a full segment here)
|
|
160
|
+
expect(segmentWildcard.getLocation('/path/x').exact).toBe(true)
|
|
161
|
+
expect(segmentWildcard.getLocation('/path/x/123').exact).toBe(true)
|
|
162
|
+
expect(segmentWildcard.getLocation('/path/x123').exact).toBe(false)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('difference: /path/x* vs /path/x/* URL building', () => {
|
|
166
|
+
const inlineWildcard = Route0.create('/path/x*')
|
|
167
|
+
const segmentWildcard = Route0.create('/path/x/*')
|
|
168
|
+
|
|
169
|
+
// Inline wildcard appends directly to the "x" prefix.
|
|
170
|
+
expect(inlineWildcard.get({ '*': '123' })).toBe('/path/x123')
|
|
171
|
+
expect(inlineWildcard.get({ '*': '/123' })).toBe('/path/x/123')
|
|
172
|
+
|
|
173
|
+
// Segment wildcard appends as a new segment after '/path/x/'.
|
|
174
|
+
expect(segmentWildcard.get({ '*': '123' })).toBe('/path/x/123')
|
|
175
|
+
expect(segmentWildcard.get({ '*': '/123' })).toBe('/path/x/123')
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('mixed required and optional named params combinations', () => {
|
|
179
|
+
const route = Route0.create('/org/:orgId/user/:userId?/:tab')
|
|
180
|
+
expect(route.get({ orgId: 'acme', tab: 'settings' })).toBe('/org/acme/user/settings')
|
|
181
|
+
expect(route.get({ orgId: 'acme', userId: '42', tab: 'settings' })).toBe('/org/acme/user/42/settings')
|
|
182
|
+
|
|
183
|
+
const locNoOptional = route.getLocation('/org/acme/user/settings')
|
|
184
|
+
expect(locNoOptional.exact).toBe(true)
|
|
185
|
+
if (locNoOptional.exact) {
|
|
186
|
+
expect(locNoOptional.params).toMatchObject({
|
|
187
|
+
orgId: 'acme',
|
|
188
|
+
userId: undefined,
|
|
189
|
+
tab: 'settings',
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const locWithOptional = route.getLocation('/org/acme/user/42/settings')
|
|
194
|
+
expect(locWithOptional.exact).toBe(true)
|
|
195
|
+
if (locWithOptional.exact) {
|
|
196
|
+
expect(locWithOptional.params).toMatchObject({
|
|
197
|
+
orgId: 'acme',
|
|
198
|
+
userId: '42',
|
|
199
|
+
tab: 'settings',
|
|
200
|
+
})
|
|
201
|
+
}
|
|
127
202
|
})
|
|
128
203
|
|
|
129
|
-
it('
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
expect(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
expect(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
)
|
|
153
|
-
const route1 = Route0.create('/prefix/:x/some/:y/:z&z&c&')
|
|
154
|
-
expect(route1.flat({ x: '1', y: '2', z: '4', c: '5', o: '6' })).toBe('/prefix/1/some/2/4?z=4&c=5&o=6')
|
|
155
|
-
expect(route1.flat({ x: '1', y: '2', z: '4', c: '5', o: '6', hash: 'zxc' })).toBe(
|
|
156
|
-
'/prefix/1/some/2/4?z=4&c=5&o=6#zxc',
|
|
157
|
-
)
|
|
204
|
+
it('optional wildcard before required static segment', () => {
|
|
205
|
+
const route = Route0.create('/orders/*?/details')
|
|
206
|
+
expect(route.get()).toBe('/orders/details')
|
|
207
|
+
expect(route.get({ '*': 'completed/list' })).toBe('/orders/completed/list/details')
|
|
208
|
+
expect(route.getLocation('/orders/details').exact).toBe(true)
|
|
209
|
+
expect(route.getLocation('/orders/completed/list/details').exact).toBe(true)
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it('paramsSchema accepts optional-only and mixed params', () => {
|
|
213
|
+
const optionalOnly = Route0.create('/x/:id?')
|
|
214
|
+
expect(optionalOnly.paramsSchema.safeParse(undefined)).toMatchObject({
|
|
215
|
+
success: true,
|
|
216
|
+
data: { id: undefined },
|
|
217
|
+
error: undefined,
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
const mixed = Route0.create('/x/:id/:slug?')
|
|
221
|
+
expect(mixed.paramsSchema.safeParse({ id: '1' })).toMatchObject({
|
|
222
|
+
success: true,
|
|
223
|
+
data: { id: '1', slug: undefined },
|
|
224
|
+
error: undefined,
|
|
225
|
+
})
|
|
226
|
+
expect(mixed.paramsSchema.safeParse({ slug: 'x' }).success).toBe(false)
|
|
158
227
|
})
|
|
159
228
|
|
|
160
229
|
it('simple extend', () => {
|
|
161
230
|
const route0 = Route0.create('/prefix')
|
|
162
231
|
const route1 = route0.extend('/suffix')
|
|
163
232
|
const path = route1.get()
|
|
164
|
-
const pathHash = route1.get({
|
|
233
|
+
const pathHash = route1.get({ '#': 'zxc' })
|
|
165
234
|
// expectTypeOf<typeof path>().toEqualTypeOf<`/prefix/suffix`>()
|
|
166
235
|
expect(path).toBe('/prefix/suffix')
|
|
167
|
-
expect(path).toBe(route1.flat())
|
|
168
236
|
expect(pathHash).toBe('/prefix/suffix#zxc')
|
|
169
|
-
expect(
|
|
237
|
+
expect(route1()).toBe(path)
|
|
238
|
+
expect(route1({ '#': 'zxc' })).toBe(pathHash)
|
|
170
239
|
})
|
|
171
240
|
|
|
172
241
|
it('simple extend double slash', () => {
|
|
@@ -176,12 +245,10 @@ describe('Route0', () => {
|
|
|
176
245
|
expect(route1.get()).toBe('/suffix1/')
|
|
177
246
|
const route2 = route1.extend('/suffix2')
|
|
178
247
|
const path = route2.get()
|
|
179
|
-
const pathHash = route2.get({
|
|
248
|
+
const pathHash = route2.get({ '#': 'zxc' })
|
|
180
249
|
// expectTypeOf<typeof path>().toEqualTypeOf<`/suffix1/suffix2`>()
|
|
181
250
|
expect(path).toBe('/suffix1/suffix2')
|
|
182
|
-
expect(path).toBe(route2.flat())
|
|
183
251
|
expect(pathHash).toBe('/suffix1/suffix2#zxc')
|
|
184
|
-
expect(pathHash).toBe(route2.flat({ hash: 'zxc' }))
|
|
185
252
|
})
|
|
186
253
|
|
|
187
254
|
it('simple extend no slash', () => {
|
|
@@ -189,12 +256,10 @@ describe('Route0', () => {
|
|
|
189
256
|
const route1 = route0.extend('suffix1')
|
|
190
257
|
const route2 = route1.extend('suffix2')
|
|
191
258
|
const path = route2.get()
|
|
192
|
-
const pathHash = route2.get({
|
|
259
|
+
const pathHash = route2.get({ '#': 'zxc' })
|
|
193
260
|
// expectTypeOf<typeof path>().toEqualTypeOf<`/suffix1/suffix2`>()
|
|
194
261
|
expect(path).toBe('/suffix1/suffix2')
|
|
195
|
-
expect(path).toBe(route2.flat())
|
|
196
262
|
expect(pathHash).toBe('/suffix1/suffix2#zxc')
|
|
197
|
-
expect(pathHash).toBe(route2.flat({ hash: 'zxc' }))
|
|
198
263
|
})
|
|
199
264
|
|
|
200
265
|
it('simple extend no slash chaos', () => {
|
|
@@ -231,143 +296,124 @@ describe('Route0', () => {
|
|
|
231
296
|
const route0 = Route0.create('/prefix/:x')
|
|
232
297
|
const route1 = route0.extend('/suffix/:y')
|
|
233
298
|
const path = route1.get({ x: '1', y: '2' })
|
|
234
|
-
const pathHash = route1.get({ x: '1', y: '2',
|
|
235
|
-
// expectTypeOf<typeof path>().toEqualTypeOf<`/prefix/${string}/suffix/${string}`>()
|
|
299
|
+
const pathHash = route1.get({ x: '1', y: '2', '#': 'zxc' })
|
|
236
300
|
expect(path).toBe('/prefix/1/suffix/2')
|
|
237
|
-
expect(path).toBe(route1.flat({ x: '1', y: '2' }))
|
|
238
301
|
expect(pathHash).toBe('/prefix/1/suffix/2#zxc')
|
|
239
|
-
expect(pathHash).toBe(route1.flat({ x: '1', y: '2', hash: 'zxc' }))
|
|
240
302
|
})
|
|
241
303
|
|
|
242
|
-
it('extend with search
|
|
243
|
-
const route0 = Route0.create('/prefix
|
|
244
|
-
const route1 = route0.extend('/suffix
|
|
245
|
-
const path = route1.get({
|
|
246
|
-
expectTypeOf<(typeof route1)['
|
|
247
|
-
z:
|
|
248
|
-
|
|
304
|
+
it('extend with typed search', () => {
|
|
305
|
+
const route0 = Route0.create('/prefix').search<{ y: string; z: string }>()
|
|
306
|
+
const route1 = route0.extend('/suffix')
|
|
307
|
+
const path = route1.get({ '?': { y: '2', z: '3' } })
|
|
308
|
+
expectTypeOf<(typeof route1)['Infer']['SearchInput']>().toEqualTypeOf<{
|
|
309
|
+
z: string
|
|
310
|
+
y: string
|
|
249
311
|
}>()
|
|
250
|
-
|
|
251
|
-
expect(path).toBe('/prefix/suffix?y=2&c=3&a=4')
|
|
312
|
+
expect(path).toBe('/prefix/suffix?y=2&z=3')
|
|
252
313
|
const path1 = route1.get()
|
|
253
|
-
const pathHash1 = route1.get({
|
|
254
|
-
// expectTypeOf<typeof path1>().toEqualTypeOf<`/prefix/suffix`>()
|
|
314
|
+
const pathHash1 = route1.get({ '#': 'zxc' })
|
|
255
315
|
expect(path1).toBe('/prefix/suffix')
|
|
256
|
-
expect(path1).toBe(route1.flat())
|
|
257
316
|
expect(pathHash1).toBe('/prefix/suffix#zxc')
|
|
258
|
-
expect(pathHash1).toBe(route1.flat({ hash: 'zxc' }))
|
|
259
317
|
})
|
|
260
318
|
|
|
261
|
-
it('extend with params and search', () => {
|
|
262
|
-
const route0 = Route0.create('/prefix/:id
|
|
263
|
-
const route1 = route0.extend('/:sn/suffix
|
|
264
|
-
const path = route1.get({ id: 'myid', sn: 'mysn',
|
|
265
|
-
expectTypeOf<(typeof route1)['
|
|
266
|
-
z:
|
|
267
|
-
|
|
319
|
+
it('extend with params and typed search', () => {
|
|
320
|
+
const route0 = Route0.create('/prefix/:id').search<{ y: string; z: string }>()
|
|
321
|
+
const route1 = route0.extend('/:sn/suffix')
|
|
322
|
+
const path = route1.get({ id: 'myid', sn: 'mysn', '?': { y: '2', z: '3' } })
|
|
323
|
+
expectTypeOf<(typeof route1)['Infer']['SearchInput']>().toEqualTypeOf<{
|
|
324
|
+
z: string
|
|
325
|
+
y: string
|
|
268
326
|
}>()
|
|
269
|
-
|
|
270
|
-
expect(path).toBe('/prefix/myid/mysn/suffix?y=2&c=3&a=4')
|
|
327
|
+
expect(path).toBe('/prefix/myid/mysn/suffix?y=2&z=3')
|
|
271
328
|
const path1 = route1.get({ id: 'myid', sn: 'mysn' })
|
|
272
|
-
const pathHash1 = route1.get({ id: 'myid', sn: 'mysn',
|
|
273
|
-
// expectTypeOf<typeof path1>().toEqualTypeOf<`/prefix/${string}/${string}/suffix`>()
|
|
329
|
+
const pathHash1 = route1.get({ id: 'myid', sn: 'mysn', '#': 'zxc' })
|
|
274
330
|
expect(path1).toBe('/prefix/myid/mysn/suffix')
|
|
275
|
-
expect(path1).toBe(route1.flat({ id: 'myid', sn: 'mysn' }))
|
|
276
331
|
expect(pathHash1).toBe('/prefix/myid/mysn/suffix#zxc')
|
|
277
|
-
expect(pathHash1).toBe(route1.flat({ id: 'myid', sn: 'mysn', hash: 'zxc' }))
|
|
278
332
|
})
|
|
279
333
|
|
|
280
|
-
it('extend with params and search, callable', () => {
|
|
281
|
-
const route0 = Route0.create('/prefix/:id&y&z')
|
|
282
|
-
const route1 = route0.extend('/:sn/suffix&z&c')
|
|
283
|
-
const path = route1({ id: 'myid', sn: 'mysn',
|
|
284
|
-
expectTypeOf<(typeof route1)['searchDefinition']>().toEqualTypeOf<{
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}>()
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
//
|
|
292
|
-
expect(
|
|
293
|
-
expect(path1).toBe(route1.flat({ id: 'myid', sn: 'mysn' }))
|
|
294
|
-
const pathHash1 = route1({ id: 'myid', sn: 'mysn', hash: 'zxc' })
|
|
295
|
-
expect(pathHash1).toBe('/prefix/myid/mysn/suffix#zxc')
|
|
296
|
-
expect(pathHash1).toBe(route1.flat({ id: 'myid', sn: 'mysn', hash: 'zxc' }))
|
|
334
|
+
it('extend with params and typed search, callable', () => {
|
|
335
|
+
// const route0 = Route0.create('/prefix/:id&y&z')
|
|
336
|
+
// const route1 = route0.extend('/:sn/suffix&z&c')
|
|
337
|
+
// const path = route1({ id: 'myid', sn: 'mysn', '?': { y: '2', c: '3', a: '4' } })
|
|
338
|
+
// expectTypeOf<(typeof route1)['searchDefinition']>().toEqualTypeOf<{
|
|
339
|
+
// z: true
|
|
340
|
+
// c: true
|
|
341
|
+
// }>()
|
|
342
|
+
// expect(path).toBe('/prefix/myid/mysn/suffix?y=2&c=3&a=4')
|
|
343
|
+
// const path1 = route1({ id: 'myid', sn: 'mysn' })
|
|
344
|
+
// expect(path1).toBe('/prefix/myid/mysn/suffix')
|
|
345
|
+
// const pathHash1 = route1({ id: 'myid', sn: 'mysn', '#': 'zxc' })
|
|
346
|
+
// expect(pathHash1).toBe('/prefix/myid/mysn/suffix#zxc')
|
|
297
347
|
})
|
|
298
348
|
|
|
299
349
|
it('abs default throw if no window.location.origin', () => {
|
|
300
350
|
const route0 = Route0.create('/path')
|
|
301
|
-
expect(() => route0.get(
|
|
351
|
+
expect(() => route0.get(undefined, true)).toThrow()
|
|
302
352
|
// const route0 = Route0.create('/path')
|
|
303
353
|
// const path = route0.get({ abs: true })
|
|
304
|
-
// const pathHash = route0.get({ abs: true,
|
|
354
|
+
// const pathHash = route0.get({ abs: true, '#': 'zxc' })
|
|
305
355
|
// // expectTypeOf<typeof path>().toEqualTypeOf<`${string}/path`>()
|
|
306
356
|
// expect(path).toBe('https://example.com/path')
|
|
307
357
|
// expect(path).toBe(route0.flat({}, true))
|
|
308
358
|
// expect(pathHash).toBe('https://example.com/path#zxc')
|
|
309
|
-
// expect(pathHash).toBe(route0.flat({
|
|
359
|
+
// expect(pathHash).toBe(route0.flat({ '#': 'zxc' }, true))
|
|
310
360
|
})
|
|
311
361
|
|
|
312
362
|
it('abs as string not throw if no window.location.origin', () => {
|
|
313
363
|
const route0 = Route0.create('/path')
|
|
314
|
-
const path = route0.get(
|
|
364
|
+
const path = route0.get('https://example.com')
|
|
315
365
|
expect(path).toBe('https://example.com/path')
|
|
316
366
|
})
|
|
317
367
|
|
|
318
368
|
it('abs as string not throw if no window.location.origin and not used additional path', () => {
|
|
319
369
|
const route0 = Route0.create('/path')
|
|
320
|
-
const path = route0.get(
|
|
370
|
+
const path = route0.get('https://example.com/x')
|
|
321
371
|
expect(path).toBe('https://example.com/path')
|
|
322
372
|
})
|
|
323
373
|
|
|
324
374
|
it('abs default set window.location.origin', () => {
|
|
325
375
|
;(globalThis as unknown as { location?: { origin?: string } }).location = { origin: 'https://example.com' }
|
|
326
376
|
const route0 = Route0.create('/path')
|
|
327
|
-
const path = route0.get(
|
|
328
|
-
const pathHash = route0.get({
|
|
329
|
-
// expectTypeOf<typeof path>().toEqualTypeOf<`${string}/path`>()
|
|
377
|
+
const path = route0.get(true)
|
|
378
|
+
const pathHash = route0.get({ '#': 'zxc' }, true)
|
|
330
379
|
expect(path).toBe('https://example.com/path')
|
|
331
|
-
expect(path).toBe(route0.
|
|
380
|
+
expect(path).toBe(route0.get({}, true))
|
|
332
381
|
expect(pathHash).toBe('https://example.com/path#zxc')
|
|
333
|
-
expect(pathHash).toBe(route0.
|
|
382
|
+
expect(pathHash).toBe(route0.get({ '#': 'zxc' }, true))
|
|
334
383
|
delete (globalThis as unknown as { location?: { origin?: string } }).location?.origin
|
|
335
384
|
})
|
|
336
385
|
|
|
337
386
|
it('abs set', () => {
|
|
338
387
|
const route0 = Route0.create('/path', { origin: 'https://x.com' })
|
|
339
|
-
const path = route0.get(
|
|
340
|
-
const pathHash = route0.get({
|
|
341
|
-
// expectTypeOf<typeof path>().toEqualTypeOf<`${string}/path`>()
|
|
388
|
+
const path = route0.get(true)
|
|
389
|
+
const pathHash = route0.get({ '#': 'zxc' }, true)
|
|
342
390
|
expect(path).toBe('https://x.com/path')
|
|
343
|
-
expect(path).toBe(route0.
|
|
391
|
+
expect(path).toBe(route0.get({}, true))
|
|
344
392
|
expect(pathHash).toBe('https://x.com/path#zxc')
|
|
345
|
-
expect(pathHash).toBe(route0.
|
|
393
|
+
expect(pathHash).toBe(route0.get({ '#': 'zxc' }, true))
|
|
346
394
|
})
|
|
347
395
|
|
|
348
396
|
it('abs override', () => {
|
|
349
397
|
const route0 = Route0.create('/path', { origin: 'https://x.com' })
|
|
350
398
|
route0.origin = 'https://y.com'
|
|
351
|
-
const path = route0.get(
|
|
352
|
-
const pasthHash = route0.get({
|
|
353
|
-
// expectTypeOf<typeof path>().toEqualTypeOf<`${string}/path`>()
|
|
399
|
+
const path = route0.get(true)
|
|
400
|
+
const pasthHash = route0.get({ '#': 'zxc' }, true)
|
|
354
401
|
expect(path).toBe('https://y.com/path')
|
|
355
|
-
expect(path).toBe(route0.
|
|
402
|
+
expect(path).toBe(route0.get({}, true))
|
|
356
403
|
expect(pasthHash).toBe('https://y.com/path#zxc')
|
|
357
|
-
expect(pasthHash).toBe(route0.
|
|
404
|
+
expect(pasthHash).toBe(route0.get({ '#': 'zxc' }, true))
|
|
358
405
|
})
|
|
359
406
|
|
|
360
407
|
it('abs override extend', () => {
|
|
361
408
|
const route0 = Route0.create('/path', { origin: 'https://x.com' })
|
|
362
409
|
route0.origin = 'https://y.com'
|
|
363
410
|
const route1 = route0.extend('/suffix')
|
|
364
|
-
const path = route1.get(
|
|
365
|
-
const pathHash = route1.get({
|
|
366
|
-
// expectTypeOf<typeof path>().toEqualTypeOf<`${string}/path/suffix`>()
|
|
411
|
+
const path = route1.get(true)
|
|
412
|
+
const pathHash = route1.get({ '#': 'zxc' }, true)
|
|
367
413
|
expect(path).toBe('https://y.com/path/suffix')
|
|
368
|
-
expect(path).toBe(route1.
|
|
414
|
+
expect(path).toBe(route1.get({}, true))
|
|
369
415
|
expect(pathHash).toBe('https://y.com/path/suffix#zxc')
|
|
370
|
-
expect(pathHash).toBe(route1.
|
|
416
|
+
expect(pathHash).toBe(route1.get({ '#': 'zxc' }, true))
|
|
371
417
|
})
|
|
372
418
|
|
|
373
419
|
// it('abs override many', () => {
|
|
@@ -387,53 +433,40 @@ describe('Route0', () => {
|
|
|
387
433
|
const rWith = Route0.create('/a/:id', { origin: 'https://example.com' })
|
|
388
434
|
// @ts-expect-error missing required path params
|
|
389
435
|
expect(rWith.get()).toBe('/a/undefined')
|
|
390
|
-
// @ts-expect-error missing required path params
|
|
391
|
-
expect(rWith.flat()).toBe('/a/undefined')
|
|
392
436
|
|
|
393
437
|
// @ts-expect-error missing required path params
|
|
394
438
|
expect(rWith.get({})).toBe('/a/undefined')
|
|
395
|
-
// @ts-expect-error missing required path params
|
|
396
|
-
expect(rWith.flat({})).toBe('/a/undefined')
|
|
397
|
-
// @ts-expect-error missing required path params (object form abs)
|
|
398
|
-
expect(rWith.get({ abs: true })).toBe('https://example.com/a/undefined')
|
|
399
439
|
// @ts-expect-error missing required path params (object form abs)
|
|
400
|
-
expect(rWith.
|
|
440
|
+
expect(rWith.get(true)).toBe('https://example.com/a/undefined')
|
|
401
441
|
// @ts-expect-error missing required path params (object form search)
|
|
402
|
-
expect(rWith.get({
|
|
403
|
-
// @ts-expect-error missing required path params (object form search), and loose search not allowed
|
|
404
|
-
expect(rWith.flat({ q: '1' })).toBe('/a/undefined')
|
|
442
|
+
expect(rWith.get({ '?': { q: '1' } })).toBe('/a/undefined?q=1')
|
|
405
443
|
|
|
406
444
|
// @ts-expect-error params can not be sent as object value it should be argument
|
|
407
445
|
rWith.get({ params: { id: '1' } }) // not throw becouse this will not used
|
|
408
|
-
expect(rWith.
|
|
446
|
+
expect(rWith.get({ id: '1' })).toBe('/a/1')
|
|
409
447
|
|
|
410
448
|
const rNo = Route0.create('/b')
|
|
411
449
|
// @ts-expect-error no path params allowed for this route (shorthand)
|
|
412
450
|
expect(rNo.get({ id: '1' })).toBe('/b')
|
|
413
|
-
// @ts-expect-error loose search not allowed
|
|
414
|
-
expect(rNo.flat({ id: '1' })).toBe('/b')
|
|
415
|
-
// @ts-expect-error loose search not allowed
|
|
416
|
-
expect(rNo.flatStrict({ id: '1' })).toBe('/b')
|
|
417
|
-
expect(rNo.flat({ id: '1' }, false, true)).toBe('/b?id=1')
|
|
418
|
-
expect(Route0.create('/b&').flat({ id: '1' })).toBe('/b?id=1')
|
|
419
|
-
expect(Route0.create('/b').flatLoose({ id: '1' })).toBe('/b?id=1')
|
|
420
451
|
})
|
|
421
452
|
|
|
422
453
|
it('really any route assignable to AnyRoute', () => {
|
|
423
454
|
expectTypeOf<Route0<string>>().toExtend<AnyRoute>()
|
|
455
|
+
expectTypeOf<Route0<string, { x: string }>>().toExtend<AnyRoute>()
|
|
424
456
|
expectTypeOf<Route0<string>>().toExtend<AnyRouteOrDefinition>()
|
|
457
|
+
expectTypeOf<Route0<string, { x: string }>>().toExtend<AnyRouteOrDefinition>()
|
|
425
458
|
expectTypeOf<Route0<'/path'>>().toExtend<AnyRoute>()
|
|
426
|
-
expectTypeOf<Route0<'/path'>>().toExtend<AnyRouteOrDefinition>()
|
|
459
|
+
expectTypeOf<Route0<'/path', { x: string }>>().toExtend<AnyRouteOrDefinition>()
|
|
427
460
|
expectTypeOf<Route0<'/path/:id'>>().toExtend<AnyRoute>()
|
|
428
|
-
expectTypeOf<Route0<'/path/:id'>>().toExtend<AnyRouteOrDefinition>()
|
|
429
|
-
expectTypeOf<Route0<'/path/:id&x'>>().toExtend<AnyRoute>()
|
|
461
|
+
expectTypeOf<Route0<'/path/:id', { x?: string }>>().toExtend<AnyRouteOrDefinition>()
|
|
430
462
|
expectTypeOf<CallableRoute<'/path'>>().toExtend<AnyRouteOrDefinition>()
|
|
463
|
+
expectTypeOf<CallableRoute<'/path', { x: string }>>().toExtend<AnyRouteOrDefinition>()
|
|
431
464
|
expectTypeOf<CallableRoute<'/path'>>().toExtend<AnyRoute>()
|
|
432
|
-
expectTypeOf<CallableRoute<'/path'>>().toExtend<
|
|
465
|
+
expectTypeOf<CallableRoute<'/path', { x: string }>>().toExtend<AnyRoute>()
|
|
433
466
|
expectTypeOf<CallableRoute<'/path/:id'>>().toExtend<AnyRoute>()
|
|
467
|
+
expectTypeOf<CallableRoute<'/path/:id', { x?: string }>>().toExtend<AnyRoute>()
|
|
434
468
|
expectTypeOf<CallableRoute<'/path/:id'>>().toExtend<AnyRouteOrDefinition>()
|
|
435
|
-
expectTypeOf<CallableRoute<'/path/:id
|
|
436
|
-
expectTypeOf<CallableRoute<'/path/:id&x'>>().toExtend<AnyRouteOrDefinition>()
|
|
469
|
+
expectTypeOf<CallableRoute<'/path/:id', { x?: string }>>().toExtend<AnyRouteOrDefinition>()
|
|
437
470
|
expectTypeOf<CallableRoute>().toExtend<AnyRoute>()
|
|
438
471
|
expectTypeOf<CallableRoute>().toExtend<AnyRouteOrDefinition>()
|
|
439
472
|
|
|
@@ -446,6 +479,11 @@ describe('Route0', () => {
|
|
|
446
479
|
expectTypeOf<typeof route2>().toExtend<AnyRoute>()
|
|
447
480
|
expectTypeOf<typeof route2>().toExtend<AnyRouteOrDefinition>()
|
|
448
481
|
|
|
482
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
483
|
+
const route3 = route.extend('/path3').search<{ x: string }>()
|
|
484
|
+
expectTypeOf<typeof route3>().toExtend<AnyRoute>()
|
|
485
|
+
expectTypeOf<typeof route3>().toExtend<AnyRouteOrDefinition>()
|
|
486
|
+
|
|
449
487
|
// Test that specific CallableRoute with literal path IS assignable to AnyRouteOrDefinition
|
|
450
488
|
expectTypeOf<CallableRoute<'/ideas/best'>>().toExtend<AnyRouteOrDefinition>()
|
|
451
489
|
|
|
@@ -505,39 +543,6 @@ describe('type utilities', () => {
|
|
|
505
543
|
expectTypeOf<HasParams<Route0<'/path/:id'>>>().toEqualTypeOf<true>()
|
|
506
544
|
})
|
|
507
545
|
|
|
508
|
-
it('HasSearch', () => {
|
|
509
|
-
expectTypeOf<HasSearch<'/path'>>().toEqualTypeOf<false>()
|
|
510
|
-
expectTypeOf<HasSearch<'/path&x'>>().toEqualTypeOf<true>()
|
|
511
|
-
expectTypeOf<HasSearch<'/path&x&y'>>().toEqualTypeOf<true>()
|
|
512
|
-
expectTypeOf<HasSearch<'/path&'>>().toEqualTypeOf<true>()
|
|
513
|
-
|
|
514
|
-
expectTypeOf<HasSearch<Route0<'/path'>>>().toEqualTypeOf<false>()
|
|
515
|
-
expectTypeOf<HasSearch<Route0<'/path&x&y'>>>().toEqualTypeOf<true>()
|
|
516
|
-
expectTypeOf<HasSearch<Route0<'/path&'>>>().toEqualTypeOf<true>()
|
|
517
|
-
})
|
|
518
|
-
|
|
519
|
-
it('HasNamedSearch', () => {
|
|
520
|
-
expectTypeOf<HasNamedSearch<'/path'>>().toEqualTypeOf<false>()
|
|
521
|
-
expectTypeOf<HasNamedSearch<'/path&x'>>().toEqualTypeOf<true>()
|
|
522
|
-
expectTypeOf<HasNamedSearch<'/path&x&y'>>().toEqualTypeOf<true>()
|
|
523
|
-
expectTypeOf<HasNamedSearch<'/path&'>>().toEqualTypeOf<false>()
|
|
524
|
-
|
|
525
|
-
expectTypeOf<HasNamedSearch<Route0<'/path'>>>().toEqualTypeOf<false>()
|
|
526
|
-
expectTypeOf<HasNamedSearch<Route0<'/path&x&y'>>>().toEqualTypeOf<true>()
|
|
527
|
-
expectTypeOf<HasNamedSearch<Route0<'/path&'>>>().toEqualTypeOf<false>()
|
|
528
|
-
})
|
|
529
|
-
|
|
530
|
-
it('HasLooseSearch', () => {
|
|
531
|
-
expectTypeOf<HasLooseSearch<'/path'>>().toEqualTypeOf<false>()
|
|
532
|
-
expectTypeOf<HasLooseSearch<'/path&x'>>().toEqualTypeOf<false>()
|
|
533
|
-
expectTypeOf<HasLooseSearch<'/path&x&y'>>().toEqualTypeOf<false>()
|
|
534
|
-
expectTypeOf<HasLooseSearch<'/path&'>>().toEqualTypeOf<true>()
|
|
535
|
-
|
|
536
|
-
expectTypeOf<HasLooseSearch<Route0<'/path'>>>().toEqualTypeOf<false>()
|
|
537
|
-
expectTypeOf<HasLooseSearch<Route0<'/path&x&y'>>>().toEqualTypeOf<false>()
|
|
538
|
-
expectTypeOf<HasLooseSearch<Route0<'/path&'>>>().toEqualTypeOf<true>()
|
|
539
|
-
})
|
|
540
|
-
|
|
541
546
|
it('ParamsInput', () => {
|
|
542
547
|
expectTypeOf<ParamsInput<'/path'>>().toEqualTypeOf<Record<never, never>>()
|
|
543
548
|
expectTypeOf<ParamsInput<'/path/:id'>>().toEqualTypeOf<{ id: string | number }>()
|
|
@@ -557,186 +562,6 @@ describe('type utilities', () => {
|
|
|
557
562
|
expectTypeOf<ParamsOutput<typeof route>>().toEqualTypeOf<{ id: string; name: string }>()
|
|
558
563
|
})
|
|
559
564
|
|
|
560
|
-
it('LooseSearchInput', () => {
|
|
561
|
-
type T1 = LooseSearchInput<'/path'>
|
|
562
|
-
expectTypeOf<T1>().toEqualTypeOf<Record<string, string | number>>()
|
|
563
|
-
|
|
564
|
-
type T2 = LooseSearchInput<'/path&x&y'>
|
|
565
|
-
expectTypeOf<T2>().toEqualTypeOf<
|
|
566
|
-
Partial<{
|
|
567
|
-
x: string | number
|
|
568
|
-
y: string | number
|
|
569
|
-
}> &
|
|
570
|
-
Record<string, string | number>
|
|
571
|
-
>()
|
|
572
|
-
})
|
|
573
|
-
|
|
574
|
-
it('LooseSearchOutput', () => {
|
|
575
|
-
type T1 = LooseSearchOutput<'/path'>
|
|
576
|
-
expectTypeOf<T1>().toEqualTypeOf<{
|
|
577
|
-
[key: string]: string | undefined
|
|
578
|
-
}>()
|
|
579
|
-
|
|
580
|
-
type T2 = LooseSearchOutput<'/path&x&y'>
|
|
581
|
-
expectTypeOf<T2>().toEqualTypeOf<{
|
|
582
|
-
[key: string]: string | undefined
|
|
583
|
-
x?: string | undefined
|
|
584
|
-
y?: string | undefined
|
|
585
|
-
}>()
|
|
586
|
-
})
|
|
587
|
-
|
|
588
|
-
it('StrictSearchInput', () => {
|
|
589
|
-
type T1 = StrictSearchInput<'/path&x&y'>
|
|
590
|
-
expectTypeOf<T1>().toEqualTypeOf<{ x?: string | number; y?: string | number }>()
|
|
591
|
-
})
|
|
592
|
-
|
|
593
|
-
it('StrictSearchOutput', () => {
|
|
594
|
-
type T1 = StrictSearchOutput<'/path&x&y'>
|
|
595
|
-
expectTypeOf<T1>().toEqualTypeOf<{ x?: string | undefined; y?: string | undefined }>()
|
|
596
|
-
})
|
|
597
|
-
|
|
598
|
-
it('LooseFlatInput', () => {
|
|
599
|
-
type T1 = LooseFlatInput<'/path&x&y'>
|
|
600
|
-
expectTypeOf<T1>().toEqualTypeOf<
|
|
601
|
-
Partial<{
|
|
602
|
-
x: string | number
|
|
603
|
-
y: string | number
|
|
604
|
-
}> &
|
|
605
|
-
Record<string, string | number>
|
|
606
|
-
>()
|
|
607
|
-
|
|
608
|
-
type T2 = LooseFlatInput<'/path/:id&x&y'>
|
|
609
|
-
expectTypeOf<T2>().toEqualTypeOf<
|
|
610
|
-
{
|
|
611
|
-
id: string | number
|
|
612
|
-
} & Partial<{
|
|
613
|
-
x: string | number
|
|
614
|
-
y: string | number
|
|
615
|
-
}> &
|
|
616
|
-
Record<string, string | number>
|
|
617
|
-
>()
|
|
618
|
-
})
|
|
619
|
-
it('StrictFlatInput', () => {
|
|
620
|
-
type T1 = StrictFlatInput<'/path&x&y'>
|
|
621
|
-
expectTypeOf<T1>().toEqualTypeOf<{ x?: string | number; y?: string | number }>()
|
|
622
|
-
type T2 = StrictFlatInput<'/path/:id&x&y'>
|
|
623
|
-
expectTypeOf<T2>().toEqualTypeOf<
|
|
624
|
-
Partial<{
|
|
625
|
-
x: string | number
|
|
626
|
-
y: string | number
|
|
627
|
-
}> & {
|
|
628
|
-
id: string | number
|
|
629
|
-
}
|
|
630
|
-
>()
|
|
631
|
-
})
|
|
632
|
-
|
|
633
|
-
it('LooseFlatOutput', () => {
|
|
634
|
-
type T1 = LooseFlatOutput<'/path&x&y'>
|
|
635
|
-
expectTypeOf<T1>().toEqualTypeOf<{
|
|
636
|
-
[x: string]: string | undefined
|
|
637
|
-
x?: string | undefined
|
|
638
|
-
y?: string | undefined
|
|
639
|
-
}>()
|
|
640
|
-
|
|
641
|
-
type T2 = LooseFlatOutput<'/path/:id&x&y'>
|
|
642
|
-
expectTypeOf<T2>().toEqualTypeOf<
|
|
643
|
-
{
|
|
644
|
-
id: string
|
|
645
|
-
} & {
|
|
646
|
-
[x: string]: string | undefined
|
|
647
|
-
x?: string | undefined
|
|
648
|
-
y?: string | undefined
|
|
649
|
-
}
|
|
650
|
-
>()
|
|
651
|
-
})
|
|
652
|
-
it('StrictLooseFlatOutput', () => {
|
|
653
|
-
type T1 = StrictFlatOutput<'/path&x&y'>
|
|
654
|
-
expectTypeOf<T1>().toEqualTypeOf<{ x?: string | undefined; y?: string | undefined }>()
|
|
655
|
-
type T2 = StrictFlatOutput<'/path/:id&x&y'>
|
|
656
|
-
expectTypeOf<T2>().toEqualTypeOf<
|
|
657
|
-
{ id: string } & Partial<{
|
|
658
|
-
x?: string | undefined
|
|
659
|
-
y?: string | undefined
|
|
660
|
-
}>
|
|
661
|
-
>()
|
|
662
|
-
})
|
|
663
|
-
|
|
664
|
-
it('LooseFlatInputWithHash', () => {
|
|
665
|
-
type T1 = LooseFlatInputWithHash<'/path&x&y'>
|
|
666
|
-
expectTypeOf<T1>().toEqualTypeOf<
|
|
667
|
-
{ hash?: string | number } & Partial<{
|
|
668
|
-
x: string | number
|
|
669
|
-
y: string | number
|
|
670
|
-
}> &
|
|
671
|
-
Record<string, string | number>
|
|
672
|
-
>()
|
|
673
|
-
|
|
674
|
-
type T2 = LooseFlatInputWithHash<'/path/:id&x&y'>
|
|
675
|
-
expectTypeOf<T2>().toEqualTypeOf<
|
|
676
|
-
{ hash?: string | number } & {
|
|
677
|
-
id: string | number
|
|
678
|
-
} & Partial<{
|
|
679
|
-
x: string | number
|
|
680
|
-
y: string | number
|
|
681
|
-
}> &
|
|
682
|
-
Record<string, string | number>
|
|
683
|
-
>()
|
|
684
|
-
})
|
|
685
|
-
it('StrictFlatInputWithHash', () => {
|
|
686
|
-
type T1 = StrictFlatInputWithHash<'/path&x&y'>
|
|
687
|
-
expectTypeOf<T1>().toEqualTypeOf<{ hash?: string | number } & { x?: string | number; y?: string | number }>()
|
|
688
|
-
type T2 = StrictFlatInputWithHash<'/path/:id&x&y'>
|
|
689
|
-
expectTypeOf<T2>().toEqualTypeOf<
|
|
690
|
-
{ hash?: string | number } & Partial<{
|
|
691
|
-
x: string | number
|
|
692
|
-
y: string | number
|
|
693
|
-
}> & {
|
|
694
|
-
id: string | number
|
|
695
|
-
}
|
|
696
|
-
>()
|
|
697
|
-
})
|
|
698
|
-
|
|
699
|
-
it('LooseFlatOutputWithHash', () => {
|
|
700
|
-
type T1 = LooseFlatOutputWithHash<'/path&x&y'>
|
|
701
|
-
expectTypeOf<T1>().toEqualTypeOf<
|
|
702
|
-
{
|
|
703
|
-
[x: string]: string | undefined
|
|
704
|
-
x?: string | undefined
|
|
705
|
-
y?: string | undefined
|
|
706
|
-
} & {
|
|
707
|
-
hash?: string | undefined
|
|
708
|
-
}
|
|
709
|
-
>()
|
|
710
|
-
|
|
711
|
-
type T2 = LooseFlatOutputWithHash<'/path/:id&x&y'>
|
|
712
|
-
expectTypeOf<T2>().toEqualTypeOf<
|
|
713
|
-
{
|
|
714
|
-
id: string
|
|
715
|
-
} & {
|
|
716
|
-
[x: string]: string | undefined
|
|
717
|
-
x?: string | undefined
|
|
718
|
-
y?: string | undefined
|
|
719
|
-
} & {
|
|
720
|
-
hash?: string | undefined
|
|
721
|
-
}
|
|
722
|
-
>()
|
|
723
|
-
})
|
|
724
|
-
it('StrictFlatOutputWithHash', () => {
|
|
725
|
-
type T1 = StrictFlatOutputWithHash<'/path&x&y'>
|
|
726
|
-
expectTypeOf<T1>().toEqualTypeOf<
|
|
727
|
-
{ x?: string | undefined; y?: string | undefined } & { hash?: string | undefined }
|
|
728
|
-
>()
|
|
729
|
-
type T2 = StrictFlatOutputWithHash<'/path/:id&x&y'>
|
|
730
|
-
expectTypeOf<T2>().toEqualTypeOf<
|
|
731
|
-
{ id: string } & Partial<{
|
|
732
|
-
x?: string | undefined
|
|
733
|
-
y?: string | undefined
|
|
734
|
-
}> & {
|
|
735
|
-
hash?: string | undefined
|
|
736
|
-
}
|
|
737
|
-
>()
|
|
738
|
-
})
|
|
739
|
-
|
|
740
565
|
it('ParamsInputStringOnly', () => {
|
|
741
566
|
expectTypeOf<ParamsInputStringOnly<'/path'>>().toEqualTypeOf<Record<never, never>>()
|
|
742
567
|
expectTypeOf<ParamsInputStringOnly<'/path/:id'>>().toEqualTypeOf<{ id: string }>()
|
|
@@ -747,69 +572,21 @@ describe('type utilities', () => {
|
|
|
747
572
|
expectTypeOf<ParamsInputStringOnly<typeof route>>().toEqualTypeOf<{ id: string; name: string }>()
|
|
748
573
|
})
|
|
749
574
|
|
|
750
|
-
it('
|
|
751
|
-
type T1 =
|
|
752
|
-
expectTypeOf<T1>().toEqualTypeOf<Record<string, string>>()
|
|
753
|
-
|
|
754
|
-
type T2 = LooseSearchInputStringOnly<'/path&x&y'>
|
|
755
|
-
expectTypeOf<T2>().toEqualTypeOf<
|
|
756
|
-
Partial<{
|
|
757
|
-
x: string
|
|
758
|
-
y: string
|
|
759
|
-
}> &
|
|
760
|
-
Record<string, string>
|
|
761
|
-
>()
|
|
762
|
-
})
|
|
763
|
-
|
|
764
|
-
it('StrictSearchInputStringOnly', () => {
|
|
765
|
-
type T1 = StrictSearchInputStringOnly<'/path&x&y'>
|
|
766
|
-
expectTypeOf<T1>().toEqualTypeOf<{ x?: string; y?: string }>()
|
|
767
|
-
})
|
|
768
|
-
|
|
769
|
-
it('LooseFlatInputStringOnly', () => {
|
|
770
|
-
type T1 = LooseFlatInputStringOnly<'/path&x&y'>
|
|
771
|
-
expectTypeOf<T1>().toEqualTypeOf<
|
|
772
|
-
Partial<{
|
|
773
|
-
x: string
|
|
774
|
-
y: string
|
|
775
|
-
}> &
|
|
776
|
-
Record<string, string>
|
|
777
|
-
>()
|
|
778
|
-
|
|
779
|
-
type T2 = LooseFlatInputStringOnly<'/path/:id&x&y'>
|
|
780
|
-
expectTypeOf<T2>().toEqualTypeOf<
|
|
781
|
-
{
|
|
782
|
-
id: string
|
|
783
|
-
} & Partial<{
|
|
784
|
-
x: string
|
|
785
|
-
y: string
|
|
786
|
-
}> &
|
|
787
|
-
Record<string, string>
|
|
788
|
-
>()
|
|
789
|
-
})
|
|
790
|
-
it('StrictFlatInputStringOnly', () => {
|
|
791
|
-
type T1 = StrictFlatInputStringOnly<'/path&x&y'>
|
|
792
|
-
expectTypeOf<T1>().toEqualTypeOf<{ x?: string; y?: string }>()
|
|
793
|
-
type T2 = StrictFlatInputStringOnly<'/path/:id&x&y'>
|
|
794
|
-
expectTypeOf<T2>().toEqualTypeOf<
|
|
795
|
-
Partial<{
|
|
796
|
-
x: string
|
|
797
|
-
y: string
|
|
798
|
-
}> & {
|
|
799
|
-
id: string
|
|
800
|
-
}
|
|
801
|
-
>()
|
|
802
|
-
})
|
|
803
|
-
|
|
804
|
-
it('CanInputBeEmpty', () => {
|
|
805
|
-
type T1 = CanInputBeEmpty<'/path'>
|
|
575
|
+
it('IsParamsOptional', () => {
|
|
576
|
+
type T1 = IsParamsOptional<'/path'>
|
|
806
577
|
expectTypeOf<T1>().toEqualTypeOf<true>()
|
|
807
|
-
type T2 =
|
|
578
|
+
type T2 = IsParamsOptional<'/path/:id'>
|
|
808
579
|
expectTypeOf<T2>().toEqualTypeOf<false>()
|
|
809
|
-
type T3 =
|
|
580
|
+
type T3 = IsParamsOptional<'/path'>
|
|
810
581
|
expectTypeOf<T3>().toEqualTypeOf<true>()
|
|
811
|
-
type T4 =
|
|
582
|
+
type T4 = IsParamsOptional<'/path/:id'>
|
|
812
583
|
expectTypeOf<T4>().toEqualTypeOf<false>()
|
|
584
|
+
type T5 = IsParamsOptional<'/path/:id?'>
|
|
585
|
+
expectTypeOf<T5>().toEqualTypeOf<true>()
|
|
586
|
+
type T6 = IsParamsOptional<'/path*'>
|
|
587
|
+
expectTypeOf<T6>().toEqualTypeOf<false>()
|
|
588
|
+
type T7 = IsParamsOptional<'/path*?'>
|
|
589
|
+
expectTypeOf<T7>().toEqualTypeOf<true>()
|
|
813
590
|
})
|
|
814
591
|
|
|
815
592
|
it('IsAncestor', () => {
|
|
@@ -857,9 +634,13 @@ describe('type utilities', () => {
|
|
|
857
634
|
it('Extended', () => {
|
|
858
635
|
expectTypeOf<Extended<'/path', '/child'>>().toEqualTypeOf<Route0<'/path/child'>>()
|
|
859
636
|
expectTypeOf<Extended<'/path', '/:id'>>().toEqualTypeOf<Route0<'/path/:id'>>()
|
|
860
|
-
expectTypeOf<Extended<'/path', '
|
|
861
|
-
|
|
862
|
-
|
|
637
|
+
expectTypeOf<Extended<'/path', '', { x: string; y: string }>>().toEqualTypeOf<
|
|
638
|
+
Route0<'/path', { x: string; y: string }>
|
|
639
|
+
>()
|
|
640
|
+
expectTypeOf<Extended<'/path/:id', '/child', { x: string }>>().toEqualTypeOf<
|
|
641
|
+
Route0<'/path/:id/child', { x: string }>
|
|
642
|
+
>()
|
|
643
|
+
expectTypeOf<Extended<undefined, '/path', { x: string }>>().toEqualTypeOf<Route0<'/path', { x: string }>>()
|
|
863
644
|
|
|
864
645
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
865
646
|
const ancestor = Route0.create('/path')
|
|
@@ -1181,7 +962,7 @@ describe('getLocation', () => {
|
|
|
1181
962
|
|
|
1182
963
|
it('with search params', () => {
|
|
1183
964
|
const routes = Routes.create({
|
|
1184
|
-
search: '/search
|
|
965
|
+
search: '/search',
|
|
1185
966
|
users: '/users',
|
|
1186
967
|
})
|
|
1187
968
|
|
|
@@ -1265,7 +1046,7 @@ describe('getLocation', () => {
|
|
|
1265
1046
|
api,
|
|
1266
1047
|
users: api.extend('/users'),
|
|
1267
1048
|
userDetail: api.extend('/users/:id'),
|
|
1268
|
-
userPosts: api.extend('/users/:id/posts
|
|
1049
|
+
userPosts: api.extend('/users/:id/posts').search<{ sort: string; filter: string }>(),
|
|
1269
1050
|
})
|
|
1270
1051
|
|
|
1271
1052
|
const loc = routes._.getLocation('/api/v1/users/42/posts?sort=date&filter=published&extra=value')
|
|
@@ -1281,6 +1062,87 @@ describe('getLocation', () => {
|
|
|
1281
1062
|
}
|
|
1282
1063
|
})
|
|
1283
1064
|
|
|
1065
|
+
it('resolves overlaps: static > required param > optional > wildcard', () => {
|
|
1066
|
+
const routes = Routes.create({
|
|
1067
|
+
usersStatic: '/users/new',
|
|
1068
|
+
usersRequired: '/users/:id',
|
|
1069
|
+
usersOptional: '/users/:id?',
|
|
1070
|
+
usersWildcard: '/users/*?',
|
|
1071
|
+
})
|
|
1072
|
+
|
|
1073
|
+
const locStatic = routes._.getLocation('/users/new')
|
|
1074
|
+
expect(locStatic.exact).toBe(true)
|
|
1075
|
+
if (locStatic.exact) {
|
|
1076
|
+
expect(locStatic.route).toBe('/users/new')
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
const locRequired = routes._.getLocation('/users/123')
|
|
1080
|
+
expect(locRequired.exact).toBe(true)
|
|
1081
|
+
if (locRequired.exact) {
|
|
1082
|
+
expect(locRequired.route).toBe('/users/:id')
|
|
1083
|
+
expect(locRequired.params).toMatchObject({ id: '123' })
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
const locOptional = routes._.getLocation('/users')
|
|
1087
|
+
expect(locOptional.exact).toBe(true)
|
|
1088
|
+
if (locOptional.exact) {
|
|
1089
|
+
expect(locOptional.route).toBe('/users/:id?')
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
const locWildcard = routes._.getLocation('/users/a/b/c')
|
|
1093
|
+
expect(locWildcard.exact).toBe(true)
|
|
1094
|
+
if (locWildcard.exact) {
|
|
1095
|
+
expect(locWildcard.route).toBe('/users/*?')
|
|
1096
|
+
}
|
|
1097
|
+
})
|
|
1098
|
+
|
|
1099
|
+
it('resolves app wildcard compatibility cases like wouter', () => {
|
|
1100
|
+
const routes = Routes.create({
|
|
1101
|
+
appRoot: '/app',
|
|
1102
|
+
appHome: '/app/home',
|
|
1103
|
+
appId: '/app/:id',
|
|
1104
|
+
appSplat: '/app*',
|
|
1105
|
+
})
|
|
1106
|
+
|
|
1107
|
+
const m1 = routes._.getLocation('/app')
|
|
1108
|
+
expect(m1.exact).toBe(true)
|
|
1109
|
+
if (m1.exact) expect(m1.route).toBe('/app')
|
|
1110
|
+
|
|
1111
|
+
const m2 = routes._.getLocation('/app/home')
|
|
1112
|
+
expect(m2.exact).toBe(true)
|
|
1113
|
+
if (m2.exact) expect(m2.route).toBe('/app/home')
|
|
1114
|
+
|
|
1115
|
+
const m3 = routes._.getLocation('/app/123')
|
|
1116
|
+
expect(m3.exact).toBe(true)
|
|
1117
|
+
if (m3.exact) expect(m3.route).toBe('/app/:id')
|
|
1118
|
+
|
|
1119
|
+
const m4 = routes._.getLocation('/app-1')
|
|
1120
|
+
expect(m4.exact).toBe(true)
|
|
1121
|
+
if (m4.exact) expect(m4.route).toBe('/app*')
|
|
1122
|
+
})
|
|
1123
|
+
|
|
1124
|
+
it('resolves /path/x* and /path/x/* differently in Routes', () => {
|
|
1125
|
+
const routes = Routes.create({
|
|
1126
|
+
inlineWildcard: '/path/x*',
|
|
1127
|
+
segmentWildcard: '/path/x/*',
|
|
1128
|
+
})
|
|
1129
|
+
|
|
1130
|
+
// '/path/x123' only matches inline wildcard.
|
|
1131
|
+
const a = routes._.getLocation('/path/x123')
|
|
1132
|
+
expect(a.exact).toBe(true)
|
|
1133
|
+
if (a.exact) expect(a.route).toBe('/path/x*')
|
|
1134
|
+
|
|
1135
|
+
// '/path/x/123' matches both, but '/path/x/*' should win as more specific.
|
|
1136
|
+
const b = routes._.getLocation('/path/x/123')
|
|
1137
|
+
expect(b.exact).toBe(true)
|
|
1138
|
+
if (b.exact) expect(b.route).toBe('/path/x/*')
|
|
1139
|
+
|
|
1140
|
+
// '/path/x' also matches both; segment wildcard remains preferred.
|
|
1141
|
+
const c = routes._.getLocation('/path/x')
|
|
1142
|
+
expect(c.exact).toBe(true)
|
|
1143
|
+
if (c.exact) expect(c.route).toBe('/path/x/*')
|
|
1144
|
+
})
|
|
1145
|
+
|
|
1284
1146
|
it('get location for extedned routes', () => {
|
|
1285
1147
|
const a = Route0.create('/')
|
|
1286
1148
|
const b = a.extend('/b')
|
|
@@ -1311,61 +1173,10 @@ describe('getLocation', () => {
|
|
|
1311
1173
|
})
|
|
1312
1174
|
})
|
|
1313
1175
|
|
|
1314
|
-
describe('
|
|
1315
|
-
it('
|
|
1316
|
-
const route = Route0.create('/:id&a&')
|
|
1317
|
-
const result = route.flatInputSchema['~standard'].validate({ id: 1, a: 2, b: 3, c: 4 })
|
|
1318
|
-
if (result instanceof Promise) {
|
|
1319
|
-
throw new Error('Unexpected async schema result')
|
|
1320
|
-
}
|
|
1321
|
-
expect(result).toMatchObject({
|
|
1322
|
-
value: {
|
|
1323
|
-
id: '1',
|
|
1324
|
-
a: '2',
|
|
1325
|
-
b: '3',
|
|
1326
|
-
c: '4',
|
|
1327
|
-
},
|
|
1328
|
-
})
|
|
1329
|
-
})
|
|
1330
|
-
|
|
1331
|
-
it('flatInputSchema validate error', () => {
|
|
1332
|
-
const route = Route0.create('/:id&a&b')
|
|
1333
|
-
const result = route.flatInputSchema['~standard'].validate(undefined)
|
|
1334
|
-
if (result instanceof Promise) {
|
|
1335
|
-
throw new Error('Unexpected async schema result')
|
|
1336
|
-
}
|
|
1337
|
-
expect(result).toMatchObject({
|
|
1338
|
-
issues: [{ message: 'Missing params: "id"' }],
|
|
1339
|
-
})
|
|
1340
|
-
})
|
|
1341
|
-
|
|
1342
|
-
it('flatInputSchema parse and safeParse', () => {
|
|
1343
|
-
const route = Route0.create('/:id&a&')
|
|
1344
|
-
expect(route.flatInputSchema.parse({ id: 1, a: 2, c: 3 })).toMatchObject({
|
|
1345
|
-
id: '1',
|
|
1346
|
-
a: '2',
|
|
1347
|
-
c: '3',
|
|
1348
|
-
})
|
|
1349
|
-
expect(route.flatInputSchema.safeParse({ id: 1, a: 2, c: 3 })).toMatchObject({
|
|
1350
|
-
success: true,
|
|
1351
|
-
data: {
|
|
1352
|
-
id: '1',
|
|
1353
|
-
a: '2',
|
|
1354
|
-
c: '3',
|
|
1355
|
-
},
|
|
1356
|
-
error: undefined,
|
|
1357
|
-
})
|
|
1358
|
-
expect(route.flatInputSchema.safeParse(undefined)).toMatchObject({
|
|
1359
|
-
success: false,
|
|
1360
|
-
data: undefined,
|
|
1361
|
-
error: new Error('Missing params: "id"'),
|
|
1362
|
-
})
|
|
1363
|
-
expect(() => route.flatInputSchema.parse(undefined)).toThrow('Missing params: "id"')
|
|
1364
|
-
})
|
|
1365
|
-
|
|
1366
|
-
it('paramsInputSchema validate', () => {
|
|
1176
|
+
describe('params schema', () => {
|
|
1177
|
+
it('paramsSchema validate', () => {
|
|
1367
1178
|
const route = Route0.create('/:id/:sn')
|
|
1368
|
-
const result = route.
|
|
1179
|
+
const result = route.paramsSchema['~standard'].validate({ id: 1, sn: 'x', extra: 'ignored' })
|
|
1369
1180
|
if (result instanceof Promise) {
|
|
1370
1181
|
throw new Error('Unexpected async schema result')
|
|
1371
1182
|
}
|
|
@@ -1374,78 +1185,20 @@ describe('input schemas', () => {
|
|
|
1374
1185
|
})
|
|
1375
1186
|
})
|
|
1376
1187
|
|
|
1377
|
-
it('
|
|
1378
|
-
const route = Route0.create('/:id
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
throw new Error('Unexpected async schema result')
|
|
1382
|
-
}
|
|
1383
|
-
expect(result).toMatchObject({
|
|
1384
|
-
value: { a: '2', b: '3' },
|
|
1385
|
-
})
|
|
1386
|
-
})
|
|
1387
|
-
|
|
1388
|
-
it('looseSearchInputSchema validate', () => {
|
|
1389
|
-
const route = Route0.create('/:id&a&')
|
|
1390
|
-
const result = route.looseSearchInputSchema['~standard'].validate({ id: '1', a: 2, b: '3', hash: 'x' })
|
|
1391
|
-
if (result instanceof Promise) {
|
|
1392
|
-
throw new Error('Unexpected async schema result')
|
|
1393
|
-
}
|
|
1394
|
-
expect(result).toMatchObject({
|
|
1395
|
-
value: { a: '2', b: '3' },
|
|
1396
|
-
})
|
|
1397
|
-
})
|
|
1398
|
-
|
|
1399
|
-
it('params/search schemas parse and safeParse', () => {
|
|
1400
|
-
const route = Route0.create('/:id&a&b')
|
|
1401
|
-
|
|
1402
|
-
expect(route.paramsInputSchema.parse({ id: 1, x: '2' })).toMatchObject({ id: '1' })
|
|
1403
|
-
expect(route.paramsInputSchema.safeParse(undefined)).toMatchObject({
|
|
1188
|
+
it('paramsSchema parse and safeParse', () => {
|
|
1189
|
+
const route = Route0.create('/:id')
|
|
1190
|
+
expect(route.paramsSchema.parse({ id: 1, x: '2' })).toMatchObject({ id: '1' })
|
|
1191
|
+
expect(route.paramsSchema.safeParse(undefined)).toMatchObject({
|
|
1404
1192
|
success: false,
|
|
1405
1193
|
data: undefined,
|
|
1406
1194
|
error: new Error('Missing params: "id"'),
|
|
1407
1195
|
})
|
|
1408
|
-
|
|
1409
|
-
expect(route.strictSearchInputSchema.parse({ id: '1', a: 2, b: 3, c: 4 })).toMatchObject({
|
|
1410
|
-
a: '2',
|
|
1411
|
-
b: '3',
|
|
1412
|
-
})
|
|
1413
|
-
expect(route.strictSearchInputSchema.safeParse({ a: () => ({}) })).toMatchObject({
|
|
1414
|
-
success: false,
|
|
1415
|
-
data: undefined,
|
|
1416
|
-
error: new Error('Invalid input: expected string, number, or undefined, got function for "a"'),
|
|
1417
|
-
})
|
|
1418
|
-
|
|
1419
|
-
expect(route.looseSearchInputSchema.parse({ id: '1', a: 2, c: 3 })).toMatchObject({
|
|
1420
|
-
a: '2',
|
|
1421
|
-
c: '3',
|
|
1422
|
-
})
|
|
1423
|
-
expect(route.looseSearchInputSchema.safeParse({ a: () => ({}) })).toMatchObject({
|
|
1424
|
-
success: false,
|
|
1425
|
-
data: undefined,
|
|
1426
|
-
error: new Error('Invalid input: expected string, number, or undefined, got function for "a"'),
|
|
1427
|
-
})
|
|
1428
1196
|
})
|
|
1429
1197
|
|
|
1430
1198
|
it('schema types are assignable to StandardSchemaV1', () => {
|
|
1431
|
-
const route = Route0.create('/:id
|
|
1432
|
-
expectTypeOf(route.
|
|
1433
|
-
expectTypeOf(route.
|
|
1434
|
-
expectTypeOf(route.looseSearchInputSchema).toMatchTypeOf<StandardSchemaV1>()
|
|
1435
|
-
expectTypeOf(route.flatInputSchema).toMatchTypeOf<StandardSchemaV1>()
|
|
1436
|
-
|
|
1437
|
-
expectTypeOf(route.paramsInputSchema).toMatchTypeOf<
|
|
1438
|
-
StandardSchemaV1<ParamsInput<'/:id&a&'>, ParamsOutput<'/:id&a&'>>
|
|
1439
|
-
>()
|
|
1440
|
-
expectTypeOf(route.strictSearchInputSchema).toMatchTypeOf<
|
|
1441
|
-
StandardSchemaV1<StrictSearchInput<'/:id&a&'>, StrictSearchOutput<'/:id&a&'>>
|
|
1442
|
-
>()
|
|
1443
|
-
expectTypeOf(route.looseSearchInputSchema).toMatchTypeOf<
|
|
1444
|
-
StandardSchemaV1<LooseSearchInput<'/:id&a&'>, LooseSearchOutput<'/:id&a&'>>
|
|
1445
|
-
>()
|
|
1446
|
-
expectTypeOf(route.flatInputSchema).toMatchTypeOf<
|
|
1447
|
-
StandardSchemaV1<LooseFlatInput<'/:id&a&'>, LooseFlatOutput<'/:id&a&'>>
|
|
1448
|
-
>()
|
|
1199
|
+
const route = Route0.create('/:id')
|
|
1200
|
+
expectTypeOf(route.paramsSchema).toExtend<StandardSchemaV1>()
|
|
1201
|
+
expectTypeOf(route.paramsSchema).toExtend<StandardSchemaV1<ParamsInput<'/:id'>, ParamsOutput<'/:id'>>>()
|
|
1449
1202
|
})
|
|
1450
1203
|
})
|
|
1451
1204
|
|
|
@@ -1501,18 +1254,17 @@ describe('Routes', () => {
|
|
|
1501
1254
|
it('create with params and search', () => {
|
|
1502
1255
|
const collection = Routes.create({
|
|
1503
1256
|
user: '/user/:id',
|
|
1504
|
-
search: '/search
|
|
1505
|
-
userWithSearch: '/user/:id
|
|
1257
|
+
search: Route0.create('/search').search<{ q: string; filter: string }>(),
|
|
1258
|
+
userWithSearch: Route0.create('/user/:id').search<{ tab: string }>(),
|
|
1506
1259
|
})
|
|
1507
|
-
|
|
1508
1260
|
const user = collection.user
|
|
1509
1261
|
expect(user.get({ id: '123' })).toBe('/user/123')
|
|
1510
|
-
|
|
1511
1262
|
const search = collection.search
|
|
1512
|
-
expect(search.get({
|
|
1513
|
-
|
|
1263
|
+
expect(search.get({ '?': { q: 'test', filter: 'all' } })).toBe('/search?q=test&filter=all')
|
|
1514
1264
|
const userWithSearch = collection.userWithSearch
|
|
1515
|
-
expect(userWithSearch.get({ id: '456',
|
|
1265
|
+
expect(userWithSearch.get({ id: '456', '?': { tab: 'posts' } })).toBe('/user/456?tab=posts')
|
|
1266
|
+
// @ts-expect-error invalid search param key
|
|
1267
|
+
expect(userWithSearch.get({ id: '456', '?': { zxc: 'posts' } })).toBe('/user/456?zxc=posts')
|
|
1516
1268
|
})
|
|
1517
1269
|
|
|
1518
1270
|
it('get maintains route definitions', () => {
|
|
@@ -1527,8 +1279,6 @@ describe('Routes', () => {
|
|
|
1527
1279
|
// Verify route definitions are preserved
|
|
1528
1280
|
expect(home.definition).toBe('/')
|
|
1529
1281
|
expect(user.definition).toBe('/user/:id')
|
|
1530
|
-
expect(home.pathDefinition).toBe('/')
|
|
1531
|
-
expect(user.pathDefinition).toBe('/user/:id')
|
|
1532
1282
|
|
|
1533
1283
|
// Verify params work correctly
|
|
1534
1284
|
expect(user.get({ id: '123' })).toBe('/user/123')
|
|
@@ -1545,8 +1295,8 @@ describe('Routes', () => {
|
|
|
1545
1295
|
const home = overridden.home
|
|
1546
1296
|
const about = overridden.about
|
|
1547
1297
|
|
|
1548
|
-
expect(home.get(
|
|
1549
|
-
expect(about.get(
|
|
1298
|
+
expect(home.get(true)).toBe('https://example.com')
|
|
1299
|
+
expect(about.get(true)).toBe('https://example.com/about')
|
|
1550
1300
|
})
|
|
1551
1301
|
|
|
1552
1302
|
it('clone does not mutate original', () => {
|
|
@@ -1558,13 +1308,13 @@ describe('Routes', () => {
|
|
|
1558
1308
|
)
|
|
1559
1309
|
|
|
1560
1310
|
const original = collection.home
|
|
1561
|
-
expect(original.get(
|
|
1311
|
+
expect(original.get(true)).toBe('https://example.com')
|
|
1562
1312
|
|
|
1563
1313
|
const overridden = collection._.clone({ origin: 'https://newdomain.com' })
|
|
1564
1314
|
const newRoute = overridden.home
|
|
1565
1315
|
|
|
1566
|
-
expect(original.get(
|
|
1567
|
-
expect(newRoute.get(
|
|
1316
|
+
expect(original.get(true)).toBe('https://example.com')
|
|
1317
|
+
expect(newRoute.get(true)).toBe('https://newdomain.com')
|
|
1568
1318
|
})
|
|
1569
1319
|
|
|
1570
1320
|
it('clone with extended routes', () => {
|
|
@@ -1576,14 +1326,14 @@ describe('Routes', () => {
|
|
|
1576
1326
|
users: usersRoute,
|
|
1577
1327
|
})
|
|
1578
1328
|
|
|
1579
|
-
expect(collection.api.get(
|
|
1580
|
-
expect(collection.api(
|
|
1581
|
-
expect(collection.users.get(
|
|
1329
|
+
expect(collection.api.get(true)).toBe('https://api.example.com/api')
|
|
1330
|
+
expect(collection.api(true)).toBe('https://api.example.com/api')
|
|
1331
|
+
expect(collection.users.get(true)).toBe('https://api.example.com/api/users')
|
|
1582
1332
|
|
|
1583
1333
|
const overridden = collection._.clone({ origin: 'https://new-api.example.com' })
|
|
1584
1334
|
|
|
1585
|
-
expect(overridden.api.get(
|
|
1586
|
-
expect(overridden.users.get(
|
|
1335
|
+
expect(overridden.api.get(true)).toBe('https://new-api.example.com/api')
|
|
1336
|
+
expect(overridden.users.get(true)).toBe('https://new-api.example.com/api/users')
|
|
1587
1337
|
})
|
|
1588
1338
|
|
|
1589
1339
|
it('hydrate static method', () => {
|
|
@@ -1626,21 +1376,23 @@ describe('Routes', () => {
|
|
|
1626
1376
|
api,
|
|
1627
1377
|
users: api.extend('/users'),
|
|
1628
1378
|
userDetail: api.extend('/users/:id'),
|
|
1629
|
-
userPosts: api.extend('/users/:id/posts
|
|
1379
|
+
userPosts: api.extend('/users/:id/posts').search<{ sort: string; filter: string }>(),
|
|
1630
1380
|
})
|
|
1631
1381
|
|
|
1632
1382
|
expect(collection.root.get()).toBe('/')
|
|
1633
|
-
expect(collection.api(
|
|
1634
|
-
expect(collection.users.get(
|
|
1383
|
+
expect(collection.api(true)).toBe('https://api.example.com/api/v1')
|
|
1384
|
+
expect(collection.users.get(true)).toBe('https://api.example.com/api/v1/users')
|
|
1635
1385
|
|
|
1636
|
-
const userDetailPath = collection.userDetail.get({ id: '42',
|
|
1386
|
+
const userDetailPath = collection.userDetail.get({ id: '42' }, true)
|
|
1637
1387
|
expect(userDetailPath).toBe('https://api.example.com/api/v1/users/42')
|
|
1638
1388
|
|
|
1639
|
-
const userPostsPath = collection.userPosts.get(
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1389
|
+
const userPostsPath = collection.userPosts.get(
|
|
1390
|
+
{
|
|
1391
|
+
id: '42',
|
|
1392
|
+
'?': { sort: 'date', filter: 'published' },
|
|
1393
|
+
},
|
|
1394
|
+
true,
|
|
1395
|
+
)
|
|
1644
1396
|
expect(userPostsPath).toBe('https://api.example.com/api/v1/users/42/posts?sort=date&filter=published')
|
|
1645
1397
|
})
|
|
1646
1398
|
})
|
|
@@ -1750,6 +1502,19 @@ describe('specificity', () => {
|
|
|
1750
1502
|
// Same depth but different static segments
|
|
1751
1503
|
expect(route1.isConflict(route2)).toBe(false)
|
|
1752
1504
|
})
|
|
1505
|
+
|
|
1506
|
+
it('isMayBeSame: optional params can overlap static', () => {
|
|
1507
|
+
const optional = Route0.create('/users/:id?')
|
|
1508
|
+
const staticUsers = Route0.create('/users')
|
|
1509
|
+
expect(optional.isSame(staticUsers)).toBe(false)
|
|
1510
|
+
expect(optional.isMayBeSame(staticUsers)).toBe(true)
|
|
1511
|
+
})
|
|
1512
|
+
|
|
1513
|
+
it('isConflict: wildcard overlaps deeper static routes', () => {
|
|
1514
|
+
const wildcard = Route0.create('/app*')
|
|
1515
|
+
const staticRoute = Route0.create('/app/home')
|
|
1516
|
+
expect(wildcard.isConflict(staticRoute)).toBe(true)
|
|
1517
|
+
})
|
|
1753
1518
|
})
|
|
1754
1519
|
|
|
1755
1520
|
describe('regex', () => {
|
|
@@ -2153,6 +1918,29 @@ describe('ordering', () => {
|
|
|
2153
1918
|
expect(ordering).toEqual(['/about', '/contact', '/home'])
|
|
2154
1919
|
})
|
|
2155
1920
|
|
|
1921
|
+
it('_makeOrdering: keeps concrete routes before wildcard overlaps', () => {
|
|
1922
|
+
const routes = {
|
|
1923
|
+
appWildcard: '/app*',
|
|
1924
|
+
appHome: '/app/home',
|
|
1925
|
+
app: '/app',
|
|
1926
|
+
}
|
|
1927
|
+
const { pathsOrdering: ordering } = Routes._.makeOrdering(routes)
|
|
1928
|
+
expect(ordering).toEqual(['/app', '/app/home', '/app*'])
|
|
1929
|
+
})
|
|
1930
|
+
|
|
1931
|
+
it('_makeOrdering: mixed optional and required params are deterministic', () => {
|
|
1932
|
+
const routes = {
|
|
1933
|
+
usersOptional: '/users/:id?',
|
|
1934
|
+
usersRequired: '/users/:id',
|
|
1935
|
+
usersStatic: '/users/new',
|
|
1936
|
+
usersWildcard: '/users/*?',
|
|
1937
|
+
}
|
|
1938
|
+
const { pathsOrdering: ordering } = Routes._.makeOrdering(routes)
|
|
1939
|
+
expect(ordering.indexOf('/users/new')).toBeLessThan(ordering.indexOf('/users/:id'))
|
|
1940
|
+
expect(ordering.indexOf('/users/:id')).toBeLessThan(ordering.indexOf('/users/:id?'))
|
|
1941
|
+
expect(ordering.indexOf('/users/:id?')).toBeLessThan(ordering.indexOf('/users/*?'))
|
|
1942
|
+
})
|
|
1943
|
+
|
|
2156
1944
|
it('_makeOrdering: complex nested structure', () => {
|
|
2157
1945
|
const api = Route0.create('/api/v1')
|
|
2158
1946
|
const routes = {
|