@esmx/router-vue 3.0.0-rc.103

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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +570 -0
  3. package/README.zh-CN.md +570 -0
  4. package/dist/index.d.ts +6 -0
  5. package/dist/index.mjs +13 -0
  6. package/dist/index.test.d.ts +1 -0
  7. package/dist/index.test.mjs +216 -0
  8. package/dist/plugin.d.ts +61 -0
  9. package/dist/plugin.mjs +41 -0
  10. package/dist/plugin.test.d.ts +1 -0
  11. package/dist/plugin.test.mjs +631 -0
  12. package/dist/router-link.d.ts +220 -0
  13. package/dist/router-link.mjs +119 -0
  14. package/dist/router-link.test.d.ts +1 -0
  15. package/dist/router-link.test.mjs +663 -0
  16. package/dist/router-view.d.ts +31 -0
  17. package/dist/router-view.mjs +15 -0
  18. package/dist/router-view.test.d.ts +1 -0
  19. package/dist/router-view.test.mjs +676 -0
  20. package/dist/run-with-context.test.d.ts +1 -0
  21. package/dist/run-with-context.test.mjs +57 -0
  22. package/dist/use.d.ts +260 -0
  23. package/dist/use.mjs +125 -0
  24. package/dist/use.test.d.ts +1 -0
  25. package/dist/use.test.mjs +381 -0
  26. package/dist/util.d.ts +20 -0
  27. package/dist/util.mjs +49 -0
  28. package/dist/util.test.d.ts +4 -0
  29. package/dist/util.test.mjs +604 -0
  30. package/dist/vue2.d.ts +15 -0
  31. package/dist/vue2.mjs +0 -0
  32. package/dist/vue3.d.ts +13 -0
  33. package/dist/vue3.mjs +0 -0
  34. package/package.json +85 -0
  35. package/src/index.test.ts +273 -0
  36. package/src/index.ts +15 -0
  37. package/src/plugin.test.ts +812 -0
  38. package/src/plugin.ts +107 -0
  39. package/src/router-link.test.ts +830 -0
  40. package/src/router-link.ts +172 -0
  41. package/src/router-view.test.ts +840 -0
  42. package/src/router-view.ts +59 -0
  43. package/src/run-with-context.test.ts +64 -0
  44. package/src/use.test.ts +484 -0
  45. package/src/use.ts +416 -0
  46. package/src/util.test.ts +760 -0
  47. package/src/util.ts +85 -0
  48. package/src/vue2.ts +18 -0
  49. package/src/vue3.ts +15 -0
@@ -0,0 +1,812 @@
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:8000/')
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('[@esmx/router-vue] Invalid Vue app instance');
92
+ });
93
+
94
+ it('should throw error for undefined app instance', () => {
95
+ expect(() => {
96
+ RouterPlugin.install(undefined);
97
+ }).toThrow('[@esmx/router-vue] Invalid Vue app instance');
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
+ expect(routeDescriptor?.enumerable).toBe(false);
159
+ expect(routeDescriptor?.configurable).toBe(true);
160
+ });
161
+
162
+ it('should provide consistent $router instance across components', async () => {
163
+ const ChildComponent = defineComponent({
164
+ render() {
165
+ return h('div', 'Child');
166
+ }
167
+ });
168
+
169
+ app = createApp({
170
+ setup() {
171
+ useProvideRouter(router);
172
+ return () =>
173
+ h('div', [h('div', 'Parent'), h(ChildComponent)]);
174
+ }
175
+ });
176
+
177
+ app.use(RouterPlugin);
178
+ app.mount(container);
179
+ await nextTick();
180
+
181
+ // Verify that global properties are consistently available
182
+ const globalProperties = app.config.globalProperties;
183
+ const routerDescriptor = Object.getOwnPropertyDescriptor(
184
+ globalProperties,
185
+ '$router'
186
+ );
187
+ const routeDescriptor = Object.getOwnPropertyDescriptor(
188
+ globalProperties,
189
+ '$route'
190
+ );
191
+
192
+ expect(routerDescriptor).toBeDefined();
193
+ expect(routeDescriptor).toBeDefined();
194
+ expect(routerDescriptor?.get).toBeDefined();
195
+ expect(typeof routerDescriptor?.get).toBe('function');
196
+ expect(routeDescriptor?.get).toBeDefined();
197
+ expect(typeof routeDescriptor?.get).toBe('function');
198
+ });
199
+
200
+ it('should actually call $router getter when accessed in component', async () => {
201
+ let routerResult: Router | null = null;
202
+
203
+ const TestComponent = defineComponent({
204
+ mounted() {
205
+ // This will trigger the $router getter defined in the plugin
206
+ routerResult = this.$router;
207
+ },
208
+ render() {
209
+ return h('div', 'Test Component');
210
+ }
211
+ });
212
+
213
+ app = createApp({
214
+ setup() {
215
+ useProvideRouter(router);
216
+ return () => h(TestComponent);
217
+ }
218
+ });
219
+
220
+ app.use(RouterPlugin);
221
+ app.mount(container);
222
+ await nextTick();
223
+
224
+ // Verify the getter was called and returned correct value
225
+ expect(routerResult).toEqual(router);
226
+ expect(routerResult).toBeInstanceOf(Router);
227
+ });
228
+
229
+ it('should actually call $route getter when accessed in component', async () => {
230
+ let routeResult: Route | null = null;
231
+
232
+ const TestComponent = defineComponent({
233
+ mounted() {
234
+ routeResult = this.$route;
235
+ },
236
+ render() {
237
+ return h('div', 'Test Component');
238
+ }
239
+ });
240
+
241
+ app = createApp({
242
+ setup() {
243
+ useProvideRouter(router);
244
+ return () => h(TestComponent);
245
+ }
246
+ });
247
+
248
+ app.use(RouterPlugin);
249
+ app.mount(container);
250
+ await nextTick();
251
+
252
+ // Navigate to ensure route state is set
253
+ await router.push('/about');
254
+ await nextTick();
255
+
256
+ // Verify the getter was called and returned correct value
257
+ expect(routeResult).toBeTruthy();
258
+ expect(routeResult).toHaveProperty('path', '/about');
259
+ expect(routeResult).toHaveProperty('meta.title', 'About');
260
+ });
261
+ });
262
+
263
+ describe('Component Registration', () => {
264
+ it('should register components with correct names', () => {
265
+ app = createApp({
266
+ setup() {
267
+ useProvideRouter(router);
268
+ return () => h('div', 'Test App');
269
+ }
270
+ });
271
+
272
+ app.use(RouterPlugin);
273
+
274
+ const globalComponents = app._context.components;
275
+ expect(globalComponents).toHaveProperty('RouterLink');
276
+ expect(globalComponents).toHaveProperty('RouterView');
277
+ expect(globalComponents.RouterLink).toBe(RouterLink);
278
+ expect(globalComponents.RouterView).toBe(RouterView);
279
+ });
280
+
281
+ it('should register RouterLink component for global use', async () => {
282
+ app = createApp({
283
+ setup() {
284
+ useProvideRouter(router);
285
+ return () => h('div', 'Test App with RouterLink available');
286
+ }
287
+ });
288
+
289
+ app.use(RouterPlugin);
290
+ app.mount(container);
291
+ await nextTick();
292
+
293
+ // Verify the component is registered globally
294
+ const globalComponents = app._context.components;
295
+ expect(globalComponents.RouterLink).toBeDefined();
296
+ expect(typeof globalComponents.RouterLink).toBe('object');
297
+ });
298
+
299
+ it('should register RouterView component for global use', async () => {
300
+ app = createApp({
301
+ setup() {
302
+ useProvideRouter(router);
303
+ return () => h('div', 'Test App with RouterView available');
304
+ }
305
+ });
306
+
307
+ app.use(RouterPlugin);
308
+ app.mount(container);
309
+ await nextTick();
310
+
311
+ // Verify the component is registered globally
312
+ const globalComponents = app._context.components;
313
+ expect(globalComponents.RouterView).toBeDefined();
314
+ expect(typeof globalComponents.RouterView).toBe('object');
315
+ });
316
+ });
317
+
318
+ describe('Error Handling', () => {
319
+ it('should handle missing router context in global properties', () => {
320
+ // Create a mock component instance without router context
321
+ const mockComponent = {
322
+ $: {
323
+ provides: {}
324
+ }
325
+ };
326
+
327
+ // Simulate accessing $router without context
328
+ const target = {};
329
+ Object.defineProperties(target, {
330
+ $router: {
331
+ get() {
332
+ // This simulates the getter function from the plugin
333
+ return require('./use').getRouter(mockComponent);
334
+ }
335
+ }
336
+ });
337
+
338
+ expect(() => {
339
+ (target as Record<string, unknown>).$router;
340
+ }).toThrow();
341
+ });
342
+
343
+ it('should handle missing router context in $route property', () => {
344
+ // Create a mock component instance without router context
345
+ const mockComponent = {
346
+ $: {
347
+ provides: {}
348
+ }
349
+ };
350
+
351
+ // Simulate accessing $route without context
352
+ const target = {};
353
+ Object.defineProperties(target, {
354
+ $route: {
355
+ get() {
356
+ // This simulates the getter function from the plugin
357
+ return require('./use').getRoute(mockComponent);
358
+ }
359
+ }
360
+ });
361
+
362
+ expect(() => {
363
+ (target as Record<string, unknown>).$route;
364
+ }).toThrow();
365
+ });
366
+ });
367
+
368
+ describe('Plugin Integration', () => {
369
+ it('should work with multiple plugin installations', () => {
370
+ app = createApp({
371
+ setup() {
372
+ useProvideRouter(router);
373
+ return () => h('div', 'Test App');
374
+ }
375
+ });
376
+
377
+ // Install plugin multiple times
378
+ app.use(RouterPlugin);
379
+ app.use(RouterPlugin);
380
+
381
+ expect(() => {
382
+ app.mount(container);
383
+ }).not.toThrow();
384
+ });
385
+
386
+ it('should maintain global properties after installation', async () => {
387
+ app = createApp({
388
+ setup() {
389
+ useProvideRouter(router);
390
+ return () => h('div', 'Test App');
391
+ }
392
+ });
393
+
394
+ app.use(RouterPlugin);
395
+ app.mount(container);
396
+ await nextTick();
397
+
398
+ // Check that global properties are accessible using descriptors
399
+ const globalProperties = app.config.globalProperties;
400
+ const routerDescriptor = Object.getOwnPropertyDescriptor(
401
+ globalProperties,
402
+ '$router'
403
+ );
404
+ const routeDescriptor = Object.getOwnPropertyDescriptor(
405
+ globalProperties,
406
+ '$route'
407
+ );
408
+
409
+ expect(routerDescriptor).toBeDefined();
410
+ expect(routeDescriptor).toBeDefined();
411
+ expect(routerDescriptor?.get).toBeDefined();
412
+ expect(routeDescriptor?.get).toBeDefined();
413
+ expect(typeof routerDescriptor?.get).toBe('function');
414
+ expect(typeof routeDescriptor?.get).toBe('function');
415
+ });
416
+ });
417
+
418
+ describe('Type Safety', () => {
419
+ it('should provide properly typed global properties', async () => {
420
+ app = createApp({
421
+ setup() {
422
+ useProvideRouter(router);
423
+ return () => h('div', 'Test App');
424
+ }
425
+ });
426
+
427
+ app.use(RouterPlugin);
428
+ app.mount(container);
429
+ await nextTick();
430
+
431
+ // Check type safety through property descriptors
432
+ const globalProperties = app.config.globalProperties;
433
+ const routerDescriptor = Object.getOwnPropertyDescriptor(
434
+ globalProperties,
435
+ '$router'
436
+ );
437
+ const routeDescriptor = Object.getOwnPropertyDescriptor(
438
+ globalProperties,
439
+ '$route'
440
+ );
441
+
442
+ expect(routerDescriptor).toBeDefined();
443
+ expect(routeDescriptor).toBeDefined();
444
+ expect(typeof routerDescriptor?.get).toBe('function');
445
+ expect(typeof routeDescriptor?.get).toBe('function');
446
+
447
+ // Verify properties exist in global properties
448
+ expect(Object.hasOwn(globalProperties, '$router')).toBe(true);
449
+ expect(Object.hasOwn(globalProperties, '$route')).toBe(true);
450
+ });
451
+
452
+ it('should provide correct component types', () => {
453
+ app = createApp({
454
+ setup() {
455
+ useProvideRouter(router);
456
+ return () => h('div', 'Test App');
457
+ }
458
+ });
459
+
460
+ app.use(RouterPlugin);
461
+
462
+ const globalComponents = app._context.components;
463
+
464
+ // Check component properties exist
465
+ expect(globalComponents.RouterLink.name).toBe('RouterLink');
466
+ expect(globalComponents.RouterView.name).toBe('RouterView');
467
+
468
+ // Check if components have setup functions (safely)
469
+ const routerLinkComponent = globalComponents.RouterLink as Record<
470
+ string,
471
+ unknown
472
+ >;
473
+ const routerViewComponent = globalComponents.RouterView as Record<
474
+ string,
475
+ unknown
476
+ >;
477
+
478
+ expect(typeof routerLinkComponent.setup).toBe('function');
479
+ expect(typeof routerViewComponent.setup).toBe('function');
480
+ });
481
+ });
482
+
483
+ describe('$router and $route Access', () => {
484
+ it('should throw error when accessing $router without useProvideRouter', () => {
485
+ app = createApp({
486
+ render: () => h('div', 'Test App')
487
+ });
488
+
489
+ app.use(RouterPlugin);
490
+
491
+ const globalProperties = app.config.globalProperties;
492
+ expect(() => {
493
+ (globalProperties as any).$router;
494
+ }).toThrow(
495
+ '[@esmx/router-vue] Router not provided. Please call useProvideRouter() in your root component setup.'
496
+ );
497
+ });
498
+
499
+ it('should throw error when accessing $route without useProvideRouter', () => {
500
+ app = createApp({
501
+ render: () => h('div', 'Test App')
502
+ });
503
+
504
+ app.use(RouterPlugin);
505
+
506
+ const globalProperties = app.config.globalProperties;
507
+ expect(() => {
508
+ (globalProperties as any).$route;
509
+ }).toThrow(
510
+ '[@esmx/router-vue] Router not provided. Please call useProvideRouter() in your root component setup.'
511
+ );
512
+ });
513
+
514
+ it('should override default getters after useProvideRouter is called', async () => {
515
+ app = createApp({
516
+ setup() {
517
+ useProvideRouter(router);
518
+ return () => h('div', 'Test App');
519
+ }
520
+ });
521
+
522
+ app.use(RouterPlugin);
523
+ app.mount(container);
524
+ await nextTick();
525
+
526
+ const globalProperties = app.config.globalProperties;
527
+ expect(() => {
528
+ (globalProperties as any).$router;
529
+ }).not.toThrow();
530
+ expect(() => {
531
+ (globalProperties as any).$route;
532
+ }).not.toThrow();
533
+ });
534
+
535
+ it('should return correct router instance via $router', async () => {
536
+ let capturedRouter: Router | null = null;
537
+
538
+ const TestComponent = defineComponent({
539
+ mounted() {
540
+ capturedRouter = this.$router;
541
+ },
542
+ render() {
543
+ return h('div', 'Test');
544
+ }
545
+ });
546
+
547
+ app = createApp({
548
+ setup() {
549
+ useProvideRouter(router);
550
+ return () => h(TestComponent);
551
+ }
552
+ });
553
+
554
+ app.use(RouterPlugin);
555
+ app.mount(container);
556
+ await nextTick();
557
+
558
+ expect(capturedRouter).toBeDefined();
559
+ expect(capturedRouter).toBeInstanceOf(Router);
560
+ expect((capturedRouter as unknown as Router).route.path).toBe('/');
561
+ });
562
+
563
+ it('should return correct route via $route', async () => {
564
+ let capturedRoute: Route | null = null;
565
+
566
+ const TestComponent = defineComponent({
567
+ mounted() {
568
+ capturedRoute = this.$route;
569
+ },
570
+ render() {
571
+ return h('div', 'Test');
572
+ }
573
+ });
574
+
575
+ app = createApp({
576
+ setup() {
577
+ useProvideRouter(router);
578
+ return () => h(TestComponent);
579
+ }
580
+ });
581
+
582
+ app.use(RouterPlugin);
583
+ app.mount(container);
584
+ await nextTick();
585
+
586
+ expect(capturedRoute).toBeDefined();
587
+ expect((capturedRoute as unknown as Route).path).toBe('/');
588
+ expect((capturedRoute as unknown as Route).meta?.title).toBe(
589
+ 'Home'
590
+ );
591
+ });
592
+
593
+ it('should update $route when navigation occurs', async () => {
594
+ const routes: string[] = [];
595
+
596
+ const TestComponent = defineComponent({
597
+ mounted() {
598
+ routes.push(this.$route.path);
599
+ },
600
+ updated() {
601
+ routes.push(this.$route.path);
602
+ },
603
+ render() {
604
+ return h('div', this.$route.path);
605
+ }
606
+ });
607
+
608
+ app = createApp({
609
+ setup() {
610
+ useProvideRouter(router);
611
+ return () => h(TestComponent);
612
+ }
613
+ });
614
+
615
+ app.use(RouterPlugin);
616
+ app.mount(container);
617
+ await nextTick();
618
+
619
+ expect(routes).toContain('/');
620
+
621
+ await router.push('/about');
622
+ await nextTick();
623
+
624
+ expect(router.route.path).toBe('/about');
625
+ });
626
+
627
+ it('should provide same $router instance in nested components', async () => {
628
+ const routerInstances: Router[] = [];
629
+
630
+ const ChildComponent = defineComponent({
631
+ mounted() {
632
+ routerInstances.push(this.$router);
633
+ },
634
+ render() {
635
+ return h('div', 'Child');
636
+ }
637
+ });
638
+
639
+ const ParentComponent = defineComponent({
640
+ mounted() {
641
+ routerInstances.push(this.$router);
642
+ },
643
+ render() {
644
+ return h('div', [h('span', 'Parent'), h(ChildComponent)]);
645
+ }
646
+ });
647
+
648
+ app = createApp({
649
+ setup() {
650
+ useProvideRouter(router);
651
+ return () => h(ParentComponent);
652
+ }
653
+ });
654
+
655
+ app.use(RouterPlugin);
656
+ app.mount(container);
657
+ await nextTick();
658
+
659
+ expect(routerInstances.length).toBe(2);
660
+ expect(routerInstances[0]).toBe(routerInstances[1]);
661
+ });
662
+
663
+ it('should allow navigation via $router.push', async () => {
664
+ let capturedRouter: Router | null = null;
665
+
666
+ const TestComponent = defineComponent({
667
+ mounted() {
668
+ capturedRouter = this.$router;
669
+ },
670
+ render() {
671
+ return h('div', 'Test');
672
+ }
673
+ });
674
+
675
+ app = createApp({
676
+ setup() {
677
+ useProvideRouter(router);
678
+ return () => h(TestComponent);
679
+ }
680
+ });
681
+
682
+ app.use(RouterPlugin);
683
+ app.mount(container);
684
+ await nextTick();
685
+
686
+ expect(capturedRouter).toBeDefined();
687
+ const routerInstance = capturedRouter as unknown as Router;
688
+ expect(typeof routerInstance.push).toBe('function');
689
+
690
+ await routerInstance.push('/about');
691
+ await nextTick();
692
+
693
+ expect(router.route.path).toBe('/about');
694
+ });
695
+
696
+ it('should allow navigation via $router.replace', async () => {
697
+ let capturedRouter: Router | null = null;
698
+
699
+ const TestComponent = defineComponent({
700
+ mounted() {
701
+ capturedRouter = this.$router;
702
+ },
703
+ render() {
704
+ return h('div', 'Test');
705
+ }
706
+ });
707
+
708
+ app = createApp({
709
+ setup() {
710
+ useProvideRouter(router);
711
+ return () => h(TestComponent);
712
+ }
713
+ });
714
+
715
+ app.use(RouterPlugin);
716
+ app.mount(container);
717
+ await nextTick();
718
+
719
+ expect(capturedRouter).toBeDefined();
720
+ const routerInstance = capturedRouter as unknown as Router;
721
+ expect(typeof routerInstance.replace).toBe('function');
722
+
723
+ await routerInstance.replace('/about');
724
+ await nextTick();
725
+
726
+ expect(router.route.path).toBe('/about');
727
+ });
728
+ });
729
+
730
+ describe('Advanced Plugin Features', () => {
731
+ it('should support property descriptor configuration', () => {
732
+ interface TestApp {
733
+ config: {
734
+ globalProperties: Record<string, unknown>;
735
+ };
736
+ component: (
737
+ name: string,
738
+ component: Record<string, unknown>
739
+ ) => void;
740
+ }
741
+
742
+ const testApp: TestApp = {
743
+ config: {
744
+ globalProperties: {}
745
+ },
746
+ component: (
747
+ name: string,
748
+ component: Record<string, unknown>
749
+ ) => {
750
+ // Mock component registration
751
+ }
752
+ };
753
+
754
+ RouterPlugin.install(testApp);
755
+
756
+ const routerDescriptor = Object.getOwnPropertyDescriptor(
757
+ testApp.config.globalProperties,
758
+ '$router'
759
+ );
760
+ const routeDescriptor = Object.getOwnPropertyDescriptor(
761
+ testApp.config.globalProperties,
762
+ '$route'
763
+ );
764
+
765
+ expect(routerDescriptor?.get).toBeDefined();
766
+ expect(routerDescriptor?.enumerable).toBe(false);
767
+ expect(routerDescriptor?.configurable).toBe(true);
768
+
769
+ expect(routeDescriptor?.get).toBeDefined();
770
+ expect(routeDescriptor?.enumerable).toBe(false);
771
+ expect(routeDescriptor?.configurable).toBe(true);
772
+ });
773
+
774
+ it('should handle different app instance structures', () => {
775
+ // Test with minimal app structure
776
+ interface MinimalApp {
777
+ config: {
778
+ globalProperties: Record<string, unknown>;
779
+ };
780
+ component: () => void;
781
+ }
782
+
783
+ const minimalApp: MinimalApp = {
784
+ config: {
785
+ globalProperties: {}
786
+ },
787
+ component: () => {}
788
+ };
789
+
790
+ expect(() => {
791
+ RouterPlugin.install(minimalApp);
792
+ }).not.toThrow();
793
+
794
+ // Verify property descriptors are properly set using descriptors
795
+ const routerDescriptor = Object.getOwnPropertyDescriptor(
796
+ minimalApp.config.globalProperties,
797
+ '$router'
798
+ );
799
+ const routeDescriptor = Object.getOwnPropertyDescriptor(
800
+ minimalApp.config.globalProperties,
801
+ '$route'
802
+ );
803
+
804
+ expect(routerDescriptor).toBeDefined();
805
+ expect(routeDescriptor).toBeDefined();
806
+ expect(routerDescriptor?.get).toBeDefined();
807
+ expect(routeDescriptor?.get).toBeDefined();
808
+ expect(typeof routerDescriptor?.get).toBe('function');
809
+ expect(typeof routeDescriptor?.get).toBe('function');
810
+ });
811
+ });
812
+ });