@esportsplus/routing 0.7.2 → 0.7.4

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.
@@ -0,0 +1,363 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { PARAMETER, STATIC, WILDCARD } from '../constants';
3
+ import { Node } from './node';
4
+
5
+
6
+ type MockRoute = { name: string; path: null; middleware: never[]; subdomain: null };
7
+
8
+
9
+ function route(name: string): MockRoute {
10
+ return { name, path: null, middleware: [], subdomain: null };
11
+ }
12
+
13
+
14
+ describe('Node', () => {
15
+ describe('add', () => {
16
+ it('adds static path (single segment)', () => {
17
+ let root = new Node<unknown>(),
18
+ r = route('home');
19
+
20
+ root.add('home', r);
21
+
22
+ expect(root.static?.get('home')).toBeDefined();
23
+ expect(root.static?.get('home')?.route).toBe(r);
24
+ });
25
+
26
+ it('adds multi-segment static path', () => {
27
+ let root = new Node<unknown>(),
28
+ r = route('about-team');
29
+
30
+ root.add('about/team', r);
31
+
32
+ let aboutNode = root.static?.get('about');
33
+
34
+ expect(aboutNode).toBeDefined();
35
+ expect(aboutNode?.static?.get('team')?.route).toBe(r);
36
+ });
37
+
38
+ it('adds parameter path and creates parameter child with correct name', () => {
39
+ let root = new Node<unknown>(),
40
+ r = route('user');
41
+
42
+ root.add('users/:id', r);
43
+
44
+ let usersNode = root.static?.get('users');
45
+
46
+ expect(usersNode).toBeDefined();
47
+ expect(usersNode?.parameter).toBeDefined();
48
+ expect(usersNode?.parameter?.name).toBe('id');
49
+ expect(usersNode?.parameter?.route).toBe(r);
50
+ });
51
+
52
+ it('adds wildcard path and creates wildcard child with correct name', () => {
53
+ let root = new Node<unknown>(),
54
+ r = route('catchall');
55
+
56
+ root.add('files/*:path', r);
57
+
58
+ let filesNode = root.static?.get('files');
59
+
60
+ expect(filesNode).toBeDefined();
61
+ expect(filesNode?.wildcard).toBeDefined();
62
+ expect(filesNode?.wildcard?.name).toBe('path');
63
+ expect(filesNode?.wildcard?.route).toBe(r);
64
+ });
65
+
66
+ it('adds mixed path (static + parameter segments)', () => {
67
+ let root = new Node<unknown>(),
68
+ r = route('user-post');
69
+
70
+ root.add('users/:id/posts', r);
71
+
72
+ let usersNode = root.static?.get('users');
73
+
74
+ expect(usersNode?.parameter?.name).toBe('id');
75
+ expect(usersNode?.parameter?.static?.get('posts')?.route).toBe(r);
76
+ });
77
+
78
+ it('reuses existing static nodes for shared prefixes', () => {
79
+ let root = new Node<unknown>(),
80
+ r1 = route('team'),
81
+ r2 = route('contact');
82
+
83
+ root.add('about/team', r1);
84
+ root.add('about/contact', r2);
85
+
86
+ let aboutNode = root.static?.get('about');
87
+
88
+ expect(aboutNode?.static?.get('team')?.route).toBe(r1);
89
+ expect(aboutNode?.static?.get('contact')?.route).toBe(r2);
90
+ });
91
+
92
+ it('reuses existing parameter node', () => {
93
+ let root = new Node<unknown>(),
94
+ r1 = route('user-posts'),
95
+ r2 = route('user-comments');
96
+
97
+ root.add('users/:id/posts', r1);
98
+ root.add('users/:id/comments', r2);
99
+
100
+ let paramNode = root.static?.get('users')?.parameter;
101
+
102
+ expect(paramNode?.name).toBe('id');
103
+ expect(paramNode?.static?.get('posts')?.route).toBe(r1);
104
+ expect(paramNode?.static?.get('comments')?.route).toBe(r2);
105
+ });
106
+
107
+ it('handles unnamed parameters with auto-increment naming', () => {
108
+ let root = new Node<unknown>(),
109
+ r = route('unnamed');
110
+
111
+ root.add(':/:', r);
112
+
113
+ expect(root.parameter?.name).toBe('0');
114
+ expect(root.parameter?.parameter?.name).toBe('1');
115
+ });
116
+
117
+ it('handles unnamed wildcard parameters', () => {
118
+ let root = new Node<unknown>(),
119
+ r = route('unnamed-wildcard');
120
+
121
+ root.add('files/*:', r);
122
+
123
+ let filesNode = root.static?.get('files');
124
+
125
+ expect(filesNode?.wildcard?.name).toBe('0');
126
+ });
127
+
128
+ it('sets path, route, and type on terminal node', () => {
129
+ let root = new Node<unknown>(),
130
+ r1 = route('static-route'),
131
+ r2 = route('param-route'),
132
+ r3 = route('wild-route');
133
+
134
+ let n1 = root.add('about', r1),
135
+ n2 = root.add('users/:id', r2),
136
+ n3 = root.add('files/*:path', r3);
137
+
138
+ expect(n1.path).toBe('about');
139
+ expect(n1.route).toBe(r1);
140
+ expect(n1.type).toBe(STATIC);
141
+
142
+ expect(n2.path).toBe('users/:id');
143
+ expect(n2.route).toBe(r2);
144
+ expect(n2.type).toBe(PARAMETER);
145
+
146
+ expect(n3.path).toBe('files/*:path');
147
+ expect(n3.route).toBe(r3);
148
+ expect(n3.type).toBe(WILDCARD);
149
+ });
150
+
151
+ it('reuses existing wildcard node', () => {
152
+ let root = new Node<unknown>(),
153
+ r1 = route('catchall-1'),
154
+ r2 = route('catchall-2');
155
+
156
+ root.add('files/*:path', r1);
157
+ root.add('docs/*:path', r2);
158
+
159
+ let filesWildcard = root.static?.get('files')?.wildcard,
160
+ docsWildcard = root.static?.get('docs')?.wildcard;
161
+
162
+ expect(filesWildcard?.route).toBe(r1);
163
+ expect(docsWildcard?.route).toBe(r2);
164
+ });
165
+
166
+ it('returns the terminal node', () => {
167
+ let root = new Node<unknown>(),
168
+ r = route('terminal');
169
+
170
+ let result = root.add('a/b/c', r);
171
+
172
+ expect(result).toBeInstanceOf(Node);
173
+ expect(result.route).toBe(r);
174
+ expect(result.path).toBe('a/b/c');
175
+ });
176
+
177
+ it('sets parent on child nodes', () => {
178
+ let root = new Node<unknown>(),
179
+ r = route('user');
180
+
181
+ root.add('users/:id', r);
182
+
183
+ let usersNode = root.static?.get('users');
184
+
185
+ expect(usersNode?.parent).toBe(root);
186
+ expect(usersNode?.parameter?.parent).toBe(usersNode);
187
+ });
188
+ });
189
+
190
+ describe('find', () => {
191
+ it('finds exact static path', () => {
192
+ let root = new Node<unknown>(),
193
+ r = route('home');
194
+
195
+ root.add('home', r);
196
+
197
+ let result = root.find('home');
198
+
199
+ expect(result.route).toBe(r);
200
+ expect(result.parameters).toBeUndefined();
201
+ });
202
+
203
+ it('returns empty object for no match', () => {
204
+ let root = new Node<unknown>();
205
+
206
+ root.add('home', route('home'));
207
+
208
+ let result = root.find('about');
209
+
210
+ expect(result.route).toBeUndefined();
211
+ expect(result.parameters).toBeUndefined();
212
+ });
213
+
214
+ it('finds parameter path and extracts params', () => {
215
+ let root = new Node<unknown>(),
216
+ r = route('user');
217
+
218
+ root.add('users/:id', r);
219
+
220
+ let result = root.find('users/42');
221
+
222
+ expect(result.route).toBe(r);
223
+ expect(result.parameters).toEqual({ id: '42' });
224
+ });
225
+
226
+ it('static segment takes priority over parameter', () => {
227
+ let root = new Node<unknown>(),
228
+ rStatic = route('users-all'),
229
+ rParam = route('user-by-id');
230
+
231
+ root.add('users/:id', rParam);
232
+ root.add('users/all', rStatic);
233
+
234
+ let result = root.find('users/all');
235
+
236
+ expect(result.route).toBe(rStatic);
237
+ expect(result.parameters).toBeUndefined();
238
+ });
239
+
240
+ it('falls back to wildcard when no match', () => {
241
+ let root = new Node<unknown>(),
242
+ r = route('catchall');
243
+
244
+ root.add('files/*:path', r);
245
+
246
+ let result = root.find('files/docs/readme.txt');
247
+
248
+ expect(result.route).toBe(r);
249
+ expect(result.parameters).toEqual({ path: 'docs/readme.txt' });
250
+ });
251
+
252
+ it('wildcard captures remaining segments joined with /', () => {
253
+ let root = new Node<unknown>(),
254
+ r = route('catchall');
255
+
256
+ root.add('api/*:rest', r);
257
+
258
+ let result = root.find('api/v1/users/42/posts');
259
+
260
+ expect(result.route).toBe(r);
261
+ expect(result.parameters).toEqual({ rest: 'v1/users/42/posts' });
262
+ });
263
+
264
+ it('finds mixed static + parameter paths', () => {
265
+ let root = new Node<unknown>(),
266
+ r = route('user-posts');
267
+
268
+ root.add('users/:id/posts', r);
269
+
270
+ let result = root.find('users/7/posts');
271
+
272
+ expect(result.route).toBe(r);
273
+ expect(result.parameters).toEqual({ id: '7' });
274
+ });
275
+
276
+ it('returns empty when no parameter node and no static match', () => {
277
+ let root = new Node<unknown>();
278
+
279
+ root.add('users/list', route('users-list'));
280
+
281
+ let result = root.find('users/999');
282
+
283
+ expect(result.route).toBeUndefined();
284
+ expect(result.parameters).toBeUndefined();
285
+ });
286
+
287
+ it('finds root-level segments', () => {
288
+ let root = new Node<unknown>(),
289
+ r = route('root-param');
290
+
291
+ root.add(':slug', r);
292
+
293
+ let result = root.find('hello');
294
+
295
+ expect(result.route).toBe(r);
296
+ expect(result.parameters).toEqual({ slug: 'hello' });
297
+ });
298
+
299
+ it('wildcard fallback when terminal node has no route', () => {
300
+ let root = new Node<unknown>(),
301
+ rWild = route('catchall');
302
+
303
+ // Add wildcard at root level
304
+ root.add('*:rest', rWild);
305
+
306
+ // Add a static node that does NOT have a route (intermediate only)
307
+ root.add('api/v1/endpoint', route('endpoint'));
308
+
309
+ // "api/v1" matches static nodes but the "v1" node has no route
310
+ let result = root.find('api/v1');
311
+
312
+ expect(result.route).toBe(rWild);
313
+ expect(result.parameters).toEqual({ rest: 'api/v1' });
314
+ });
315
+
316
+ it('multiple parameters in path', () => {
317
+ let root = new Node<unknown>(),
318
+ r = route('user-post');
319
+
320
+ root.add('users/:userId/posts/:postId', r);
321
+
322
+ let result = root.find('users/5/posts/99');
323
+
324
+ expect(result.route).toBe(r);
325
+ expect(result.parameters).toEqual({ userId: '5', postId: '99' });
326
+ });
327
+
328
+ it('finds multi-segment static path', () => {
329
+ let root = new Node<unknown>(),
330
+ r = route('deep');
331
+
332
+ root.add('a/b/c/d', r);
333
+
334
+ let result = root.find('a/b/c/d');
335
+
336
+ expect(result.route).toBe(r);
337
+ expect(result.parameters).toBeUndefined();
338
+ });
339
+
340
+ it('returns empty on empty root with no routes', () => {
341
+ let root = new Node<unknown>();
342
+
343
+ let result = root.find('anything');
344
+
345
+ expect(result.route).toBeUndefined();
346
+ expect(result.parameters).toBeUndefined();
347
+ });
348
+
349
+ it('parameter fallback when static does not exist for segment', () => {
350
+ let root = new Node<unknown>(),
351
+ rParam = route('by-id'),
352
+ rStatic = route('known');
353
+
354
+ root.add('items/:id', rParam);
355
+ root.add('items/known', rStatic);
356
+
357
+ let result = root.find('items/unknown-value');
358
+
359
+ expect(result.route).toBe(rParam);
360
+ expect(result.parameters).toEqual({ id: 'unknown-value' });
361
+ });
362
+ });
363
+ });
@@ -0,0 +1,14 @@
1
+ import { resolve } from 'path';
2
+ import { defineConfig } from 'vitest/config';
3
+
4
+
5
+ export default defineConfig({
6
+ resolve: {
7
+ alias: {
8
+ '~': resolve(__dirname, 'src')
9
+ }
10
+ },
11
+ test: {
12
+ include: ['src/**/*.test.ts']
13
+ }
14
+ });
File without changes
File without changes
File without changes
File without changes