@esmx/router-vue 3.0.0-rc.16 → 3.0.0-rc.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +563 -0
  3. package/README.zh-CN.md +563 -0
  4. package/dist/index.d.ts +6 -4
  5. package/dist/index.mjs +11 -4
  6. package/dist/index.test.d.ts +1 -0
  7. package/dist/index.test.mjs +206 -0
  8. package/dist/plugin.d.ts +55 -11
  9. package/dist/plugin.mjs +32 -16
  10. package/dist/plugin.test.d.ts +1 -0
  11. package/dist/plugin.test.mjs +436 -0
  12. package/dist/router-link.d.ts +202 -0
  13. package/dist/router-link.mjs +84 -0
  14. package/dist/router-link.test.d.ts +1 -0
  15. package/dist/router-link.test.mjs +456 -0
  16. package/dist/router-view.d.ts +30 -0
  17. package/dist/router-view.mjs +17 -0
  18. package/dist/router-view.test.d.ts +1 -0
  19. package/dist/router-view.test.mjs +459 -0
  20. package/dist/use.d.ts +198 -3
  21. package/dist/use.mjs +75 -9
  22. package/dist/use.test.d.ts +1 -0
  23. package/dist/use.test.mjs +461 -0
  24. package/dist/util.d.ts +7 -0
  25. package/dist/util.mjs +24 -0
  26. package/dist/util.test.d.ts +1 -0
  27. package/dist/util.test.mjs +319 -0
  28. package/dist/vue2.d.ts +13 -0
  29. package/dist/vue2.mjs +0 -0
  30. package/dist/vue3.d.ts +13 -0
  31. package/dist/vue3.mjs +0 -0
  32. package/package.json +31 -14
  33. package/src/index.test.ts +263 -0
  34. package/src/index.ts +16 -4
  35. package/src/plugin.test.ts +574 -0
  36. package/src/plugin.ts +86 -31
  37. package/src/router-link.test.ts +569 -0
  38. package/src/router-link.ts +148 -0
  39. package/src/router-view.test.ts +599 -0
  40. package/src/router-view.ts +61 -0
  41. package/src/use.test.ts +616 -0
  42. package/src/use.ts +307 -11
  43. package/src/util.test.ts +418 -0
  44. package/src/util.ts +32 -0
  45. package/src/vue2.ts +16 -0
  46. package/src/vue3.ts +15 -0
  47. package/dist/link.d.ts +0 -101
  48. package/dist/link.mjs +0 -103
  49. package/dist/symbols.d.ts +0 -3
  50. package/dist/symbols.mjs +0 -3
  51. package/dist/view.d.ts +0 -21
  52. package/dist/view.mjs +0 -75
  53. package/src/link.ts +0 -177
  54. package/src/symbols.ts +0 -8
  55. package/src/view.ts +0 -95
package/src/index.ts CHANGED
@@ -1,4 +1,16 @@
1
- export { RouterVuePlugin } from './plugin';
2
- export { useRouter, useRoute } from './use';
3
- export { RouterLink } from './link';
4
- export { RouterView } from './view';
1
+ export type * from './vue2';
2
+ export type * from './vue3';
3
+
4
+ export {
5
+ useRouter,
6
+ useRoute,
7
+ useProvideRouter,
8
+ useLink,
9
+ getRoute,
10
+ getRouter
11
+ } from './use';
12
+
13
+ export { RouterLink } from './router-link';
14
+ export { RouterView } from './router-view';
15
+
16
+ export { RouterPlugin } from './plugin';
@@ -0,0 +1,574 @@
1
+ import type { Route, RouteConfig } from '@esmx/router';
2
+ import { Router, RouterMode } from '@esmx/router';
3
+ /**
4
+ * @vitest-environment happy-dom
5
+ */
6
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
7
+ import { createApp, defineComponent, h, nextTick } from 'vue';
8
+ import { RouterPlugin } from './plugin';
9
+ import { RouterLink } from './router-link';
10
+ import { RouterView } from './router-view';
11
+ import { useProvideRouter } from './use';
12
+
13
+ describe('plugin.ts - RouterPlugin', () => {
14
+ let router: Router;
15
+ let app: ReturnType<typeof createApp>;
16
+ let container: HTMLElement;
17
+
18
+ beforeEach(async () => {
19
+ // Create DOM container
20
+ container = document.createElement('div');
21
+ container.id = 'test-app';
22
+ document.body.appendChild(container);
23
+
24
+ // Create test routes with render functions
25
+ const routes: RouteConfig[] = [
26
+ {
27
+ path: '/',
28
+ component: defineComponent({
29
+ name: 'Home',
30
+ render: () => h('div', 'Home Page')
31
+ }),
32
+ meta: { title: 'Home' }
33
+ },
34
+ {
35
+ path: '/about',
36
+ component: defineComponent({
37
+ name: 'About',
38
+ render: () => h('div', 'About Page')
39
+ }),
40
+ meta: { title: 'About' }
41
+ }
42
+ ];
43
+
44
+ // Create and initialize router
45
+ router = new Router({
46
+ mode: RouterMode.memory,
47
+ routes,
48
+ base: new URL('http://localhost:3000/')
49
+ });
50
+
51
+ await router.replace('/');
52
+ await nextTick();
53
+ });
54
+
55
+ afterEach(() => {
56
+ if (app) {
57
+ app.unmount();
58
+ }
59
+ if (router) {
60
+ router.destroy();
61
+ }
62
+ if (container?.parentNode) {
63
+ container.parentNode.removeChild(container);
64
+ }
65
+ });
66
+
67
+ describe('Plugin Installation', () => {
68
+ it('should install plugin without errors', () => {
69
+ app = createApp({
70
+ setup() {
71
+ useProvideRouter(router);
72
+ return () => h('div', 'Test App');
73
+ }
74
+ });
75
+
76
+ expect(() => {
77
+ app.use(RouterPlugin);
78
+ }).not.toThrow();
79
+ });
80
+
81
+ it('should throw error for invalid Vue app instance', () => {
82
+ const invalidApp = {};
83
+ expect(() => {
84
+ RouterPlugin.install(invalidApp);
85
+ }).toThrow('[@esmx/router-vue] Invalid Vue app instance');
86
+ });
87
+
88
+ it('should throw error for null app instance', () => {
89
+ expect(() => {
90
+ RouterPlugin.install(null);
91
+ }).toThrow();
92
+ });
93
+
94
+ it('should throw error for undefined app instance', () => {
95
+ expect(() => {
96
+ RouterPlugin.install(undefined);
97
+ }).toThrow();
98
+ });
99
+ });
100
+
101
+ describe('Global Properties Injection', () => {
102
+ it('should inject $router and $route properties', async () => {
103
+ app = createApp({
104
+ setup() {
105
+ useProvideRouter(router);
106
+ return () => h('div', 'Test App');
107
+ }
108
+ });
109
+
110
+ app.use(RouterPlugin);
111
+ app.mount(container);
112
+ await nextTick();
113
+
114
+ // Check that properties are defined using descriptors to avoid triggering getters
115
+ const globalProperties = app.config.globalProperties;
116
+ const routerDescriptor = Object.getOwnPropertyDescriptor(
117
+ globalProperties,
118
+ '$router'
119
+ );
120
+ const routeDescriptor = Object.getOwnPropertyDescriptor(
121
+ globalProperties,
122
+ '$route'
123
+ );
124
+
125
+ expect(routerDescriptor).toBeDefined();
126
+ expect(routeDescriptor).toBeDefined();
127
+ expect(routerDescriptor?.get).toBeDefined();
128
+ expect(routeDescriptor?.get).toBeDefined();
129
+ expect(typeof routerDescriptor?.get).toBe('function');
130
+ expect(typeof routeDescriptor?.get).toBe('function');
131
+ });
132
+
133
+ it('should provide reactive $route property', async () => {
134
+ app = createApp({
135
+ setup() {
136
+ useProvideRouter(router);
137
+ return () => h('div', 'Test App');
138
+ }
139
+ });
140
+
141
+ app.use(RouterPlugin);
142
+ app.mount(container);
143
+ await nextTick();
144
+
145
+ // Navigate to different route
146
+ await router.push('/about');
147
+ await nextTick();
148
+
149
+ // Check that global properties are reactive (structure test)
150
+ const globalProperties = app.config.globalProperties;
151
+ const routeDescriptor = Object.getOwnPropertyDescriptor(
152
+ globalProperties,
153
+ '$route'
154
+ );
155
+ expect(routeDescriptor?.get).toBeDefined();
156
+ expect(typeof routeDescriptor?.get).toBe('function');
157
+
158
+ // Verify the descriptor is properly configured for reactivity
159
+ expect(routeDescriptor?.enumerable).toBe(false);
160
+ expect(routeDescriptor?.configurable).toBe(false);
161
+ });
162
+
163
+ it('should provide consistent $router instance across components', async () => {
164
+ const ChildComponent = defineComponent({
165
+ render() {
166
+ return h('div', 'Child');
167
+ }
168
+ });
169
+
170
+ app = createApp({
171
+ setup() {
172
+ useProvideRouter(router);
173
+ return () =>
174
+ h('div', [h('div', 'Parent'), h(ChildComponent)]);
175
+ }
176
+ });
177
+
178
+ app.use(RouterPlugin);
179
+ app.mount(container);
180
+ await nextTick();
181
+
182
+ // Verify that global properties are consistently available
183
+ const globalProperties = app.config.globalProperties;
184
+ const routerDescriptor = Object.getOwnPropertyDescriptor(
185
+ globalProperties,
186
+ '$router'
187
+ );
188
+ const routeDescriptor = Object.getOwnPropertyDescriptor(
189
+ globalProperties,
190
+ '$route'
191
+ );
192
+
193
+ expect(routerDescriptor).toBeDefined();
194
+ expect(routeDescriptor).toBeDefined();
195
+ expect(routerDescriptor?.get).toBeDefined();
196
+ expect(typeof routerDescriptor?.get).toBe('function');
197
+ expect(routeDescriptor?.get).toBeDefined();
198
+ expect(typeof routeDescriptor?.get).toBe('function');
199
+ });
200
+
201
+ it('should actually call $router getter when accessed in component', async () => {
202
+ let routerResult: Router | null = null;
203
+
204
+ const TestComponent = defineComponent({
205
+ mounted() {
206
+ // This will trigger the $router getter defined in the plugin
207
+ routerResult = this.$router;
208
+ },
209
+ render() {
210
+ return h('div', 'Test Component');
211
+ }
212
+ });
213
+
214
+ app = createApp({
215
+ setup() {
216
+ useProvideRouter(router);
217
+ return () => h(TestComponent);
218
+ }
219
+ });
220
+
221
+ app.use(RouterPlugin);
222
+ app.mount(container);
223
+ await nextTick();
224
+
225
+ // Verify the getter was called and returned correct value
226
+ expect(routerResult).toBe(router);
227
+ expect(routerResult).toBeInstanceOf(Router);
228
+ });
229
+
230
+ it('should actually call $route getter when accessed in component', async () => {
231
+ let routeResult: Route | null = null;
232
+
233
+ const TestComponent = defineComponent({
234
+ mounted() {
235
+ routeResult = this.$route;
236
+ },
237
+ render() {
238
+ return h('div', 'Test Component');
239
+ }
240
+ });
241
+
242
+ app = createApp({
243
+ setup() {
244
+ useProvideRouter(router);
245
+ return () => h(TestComponent);
246
+ }
247
+ });
248
+
249
+ app.use(RouterPlugin);
250
+ app.mount(container);
251
+ await nextTick();
252
+
253
+ // Navigate to ensure route state is set
254
+ await router.push('/about');
255
+ await nextTick();
256
+
257
+ // Verify the getter was called and returned correct value
258
+ expect(routeResult).toBeTruthy();
259
+ expect(routeResult).toHaveProperty('path', '/about');
260
+ expect(routeResult).toHaveProperty('meta.title', 'About');
261
+ });
262
+ });
263
+
264
+ describe('Component Registration', () => {
265
+ it('should register components with correct names', () => {
266
+ app = createApp({
267
+ setup() {
268
+ useProvideRouter(router);
269
+ return () => h('div', 'Test App');
270
+ }
271
+ });
272
+
273
+ app.use(RouterPlugin);
274
+
275
+ const globalComponents = app._context.components;
276
+ expect(globalComponents).toHaveProperty('RouterLink');
277
+ expect(globalComponents).toHaveProperty('RouterView');
278
+ expect(globalComponents.RouterLink).toBe(RouterLink);
279
+ expect(globalComponents.RouterView).toBe(RouterView);
280
+ });
281
+
282
+ it('should register RouterLink component for global use', async () => {
283
+ app = createApp({
284
+ setup() {
285
+ useProvideRouter(router);
286
+ return () => h('div', 'Test App with RouterLink available');
287
+ }
288
+ });
289
+
290
+ app.use(RouterPlugin);
291
+ app.mount(container);
292
+ await nextTick();
293
+
294
+ // Verify the component is registered globally
295
+ const globalComponents = app._context.components;
296
+ expect(globalComponents.RouterLink).toBeDefined();
297
+ expect(typeof globalComponents.RouterLink).toBe('object');
298
+ });
299
+
300
+ it('should register RouterView component for global use', async () => {
301
+ app = createApp({
302
+ setup() {
303
+ useProvideRouter(router);
304
+ return () => h('div', 'Test App with RouterView available');
305
+ }
306
+ });
307
+
308
+ app.use(RouterPlugin);
309
+ app.mount(container);
310
+ await nextTick();
311
+
312
+ // Verify the component is registered globally
313
+ const globalComponents = app._context.components;
314
+ expect(globalComponents.RouterView).toBeDefined();
315
+ expect(typeof globalComponents.RouterView).toBe('object');
316
+ });
317
+ });
318
+
319
+ describe('Error Handling', () => {
320
+ it('should handle missing router context in global properties', () => {
321
+ // Create a mock component instance without router context
322
+ const mockComponent = {
323
+ $: {
324
+ provides: {}
325
+ }
326
+ };
327
+
328
+ // Simulate accessing $router without context
329
+ const target = {};
330
+ Object.defineProperties(target, {
331
+ $router: {
332
+ get() {
333
+ // This simulates the getter function from the plugin
334
+ return require('./use').getRouter(mockComponent);
335
+ }
336
+ }
337
+ });
338
+
339
+ expect(() => {
340
+ (target as Record<string, unknown>).$router;
341
+ }).toThrow();
342
+ });
343
+
344
+ it('should handle missing router context in $route property', () => {
345
+ // Create a mock component instance without router context
346
+ const mockComponent = {
347
+ $: {
348
+ provides: {}
349
+ }
350
+ };
351
+
352
+ // Simulate accessing $route without context
353
+ const target = {};
354
+ Object.defineProperties(target, {
355
+ $route: {
356
+ get() {
357
+ // This simulates the getter function from the plugin
358
+ return require('./use').getRoute(mockComponent);
359
+ }
360
+ }
361
+ });
362
+
363
+ expect(() => {
364
+ (target as Record<string, unknown>).$route;
365
+ }).toThrow();
366
+ });
367
+ });
368
+
369
+ describe('Plugin Integration', () => {
370
+ it('should work with multiple plugin installations', () => {
371
+ app = createApp({
372
+ setup() {
373
+ useProvideRouter(router);
374
+ return () => h('div', 'Test App');
375
+ }
376
+ });
377
+
378
+ // Install plugin multiple times
379
+ app.use(RouterPlugin);
380
+ app.use(RouterPlugin);
381
+
382
+ expect(() => {
383
+ app.mount(container);
384
+ }).not.toThrow();
385
+ });
386
+
387
+ it('should maintain global properties after installation', async () => {
388
+ app = createApp({
389
+ setup() {
390
+ useProvideRouter(router);
391
+ return () => h('div', 'Test App');
392
+ }
393
+ });
394
+
395
+ app.use(RouterPlugin);
396
+ app.mount(container);
397
+ await nextTick();
398
+
399
+ // Check that global properties are accessible using descriptors
400
+ const globalProperties = app.config.globalProperties;
401
+ const routerDescriptor = Object.getOwnPropertyDescriptor(
402
+ globalProperties,
403
+ '$router'
404
+ );
405
+ const routeDescriptor = Object.getOwnPropertyDescriptor(
406
+ globalProperties,
407
+ '$route'
408
+ );
409
+
410
+ expect(routerDescriptor).toBeDefined();
411
+ expect(routeDescriptor).toBeDefined();
412
+ expect(routerDescriptor?.get).toBeDefined();
413
+ expect(routeDescriptor?.get).toBeDefined();
414
+ expect(typeof routerDescriptor?.get).toBe('function');
415
+ expect(typeof routeDescriptor?.get).toBe('function');
416
+ });
417
+ });
418
+
419
+ describe('Type Safety', () => {
420
+ it('should provide properly typed global properties', async () => {
421
+ app = createApp({
422
+ setup() {
423
+ useProvideRouter(router);
424
+ return () => h('div', 'Test App');
425
+ }
426
+ });
427
+
428
+ app.use(RouterPlugin);
429
+ app.mount(container);
430
+ await nextTick();
431
+
432
+ // Check type safety through property descriptors
433
+ const globalProperties = app.config.globalProperties;
434
+ const routerDescriptor = Object.getOwnPropertyDescriptor(
435
+ globalProperties,
436
+ '$router'
437
+ );
438
+ const routeDescriptor = Object.getOwnPropertyDescriptor(
439
+ globalProperties,
440
+ '$route'
441
+ );
442
+
443
+ expect(routerDescriptor).toBeDefined();
444
+ expect(routeDescriptor).toBeDefined();
445
+ expect(typeof routerDescriptor?.get).toBe('function');
446
+ expect(typeof routeDescriptor?.get).toBe('function');
447
+
448
+ // Verify properties exist in global properties
449
+ expect(
450
+ Object.prototype.hasOwnProperty.call(
451
+ globalProperties,
452
+ '$router'
453
+ )
454
+ ).toBe(true);
455
+ expect(
456
+ Object.prototype.hasOwnProperty.call(globalProperties, '$route')
457
+ ).toBe(true);
458
+ });
459
+
460
+ it('should provide correct component types', () => {
461
+ app = createApp({
462
+ setup() {
463
+ useProvideRouter(router);
464
+ return () => h('div', 'Test App');
465
+ }
466
+ });
467
+
468
+ app.use(RouterPlugin);
469
+
470
+ const globalComponents = app._context.components;
471
+
472
+ // Check component properties exist
473
+ expect(globalComponents.RouterLink.name).toBe('RouterLink');
474
+ expect(globalComponents.RouterView.name).toBe('RouterView');
475
+
476
+ // Check if components have setup functions (safely)
477
+ const routerLinkComponent = globalComponents.RouterLink as Record<
478
+ string,
479
+ unknown
480
+ >;
481
+ const routerViewComponent = globalComponents.RouterView as Record<
482
+ string,
483
+ unknown
484
+ >;
485
+
486
+ expect(typeof routerLinkComponent.setup).toBe('function');
487
+ expect(typeof routerViewComponent.setup).toBe('function');
488
+ });
489
+ });
490
+
491
+ describe('Advanced Plugin Features', () => {
492
+ it('should support property descriptor configuration', () => {
493
+ interface TestApp {
494
+ config: {
495
+ globalProperties: Record<string, unknown>;
496
+ };
497
+ component: (
498
+ name: string,
499
+ component: Record<string, unknown>
500
+ ) => void;
501
+ }
502
+
503
+ const testApp: TestApp = {
504
+ config: {
505
+ globalProperties: {}
506
+ },
507
+ component: (
508
+ name: string,
509
+ component: Record<string, unknown>
510
+ ) => {
511
+ // Mock component registration
512
+ }
513
+ };
514
+
515
+ RouterPlugin.install(testApp);
516
+
517
+ const routerDescriptor = Object.getOwnPropertyDescriptor(
518
+ testApp.config.globalProperties,
519
+ '$router'
520
+ );
521
+ const routeDescriptor = Object.getOwnPropertyDescriptor(
522
+ testApp.config.globalProperties,
523
+ '$route'
524
+ );
525
+
526
+ // Check descriptor properties - Object.defineProperties sets these to false by default
527
+ expect(routerDescriptor?.get).toBeDefined();
528
+ expect(routerDescriptor?.enumerable).toBe(false); // Default value from Object.defineProperty
529
+ expect(routerDescriptor?.configurable).toBe(false); // Default value from Object.defineProperty
530
+
531
+ expect(routeDescriptor?.get).toBeDefined();
532
+ expect(routeDescriptor?.enumerable).toBe(false); // Default value from Object.defineProperty
533
+ expect(routeDescriptor?.configurable).toBe(false); // Default value from Object.defineProperty
534
+ });
535
+
536
+ it('should handle different app instance structures', () => {
537
+ // Test with minimal app structure
538
+ interface MinimalApp {
539
+ config: {
540
+ globalProperties: Record<string, unknown>;
541
+ };
542
+ component: () => void;
543
+ }
544
+
545
+ const minimalApp: MinimalApp = {
546
+ config: {
547
+ globalProperties: {}
548
+ },
549
+ component: () => {}
550
+ };
551
+
552
+ expect(() => {
553
+ RouterPlugin.install(minimalApp);
554
+ }).not.toThrow();
555
+
556
+ // Verify property descriptors are properly set using descriptors
557
+ const routerDescriptor = Object.getOwnPropertyDescriptor(
558
+ minimalApp.config.globalProperties,
559
+ '$router'
560
+ );
561
+ const routeDescriptor = Object.getOwnPropertyDescriptor(
562
+ minimalApp.config.globalProperties,
563
+ '$route'
564
+ );
565
+
566
+ expect(routerDescriptor).toBeDefined();
567
+ expect(routeDescriptor).toBeDefined();
568
+ expect(routerDescriptor?.get).toBeDefined();
569
+ expect(routeDescriptor?.get).toBeDefined();
570
+ expect(typeof routerDescriptor?.get).toBe('function');
571
+ expect(typeof routeDescriptor?.get).toBe('function');
572
+ });
573
+ });
574
+ });