@esmx/router-vue 3.0.0-rc.84 → 3.0.0-rc.87

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,64 @@
1
+ /**
2
+ * @vitest-environment happy-dom
3
+ */
4
+ import { describe, expect, it } from 'vitest';
5
+ import type { InjectionKey } from 'vue';
6
+ import {
7
+ createApp,
8
+ getCurrentInstance,
9
+ h,
10
+ inject,
11
+ nextTick,
12
+ provide
13
+ } from 'vue';
14
+
15
+ describe('app.runWithContext()', () => {
16
+ it('should exist on Vue app', () => {
17
+ const app = createApp({ render: () => h('div') });
18
+ expect(typeof app.runWithContext).toBe('function');
19
+ });
20
+
21
+ it('should enable inject to read provided value within context', () => {
22
+ const KEY: InjectionKey<number> = Symbol('ctx-key');
23
+ const app = createApp({ render: () => h('div') });
24
+ app.provide(KEY, 42);
25
+
26
+ const value = app.runWithContext(() => inject(KEY));
27
+ expect(value).toBe(42);
28
+ });
29
+ it('should not read value provided in root setup via runWithContext', async () => {
30
+ const KEY: InjectionKey<number> = Symbol('ctx-key-setup');
31
+ const app = createApp({
32
+ setup() {
33
+ provide(KEY, 7);
34
+ return () => h('div');
35
+ }
36
+ });
37
+ const container = document.createElement('div');
38
+ document.body.appendChild(container);
39
+ app.mount(container);
40
+ await nextTick();
41
+ const value = app.runWithContext(() => inject(KEY));
42
+ expect(value).toBeUndefined();
43
+ app.unmount();
44
+ container.remove();
45
+ });
46
+ it('should read app-level provide set inside setup via appContext', async () => {
47
+ const KEY: InjectionKey<number> = Symbol('ctx-key-setup-app');
48
+ const app = createApp({
49
+ setup() {
50
+ const appInst = getCurrentInstance()!.appContext.app;
51
+ appInst.provide(KEY, 9);
52
+ return () => h('div');
53
+ }
54
+ });
55
+ const container = document.createElement('div');
56
+ document.body.appendChild(container);
57
+ app.mount(container);
58
+ await nextTick();
59
+ const value = app.runWithContext(() => inject(KEY));
60
+ expect(value).toBe(9);
61
+ app.unmount();
62
+ container.remove();
63
+ });
64
+ });
package/src/use.ts CHANGED
@@ -7,7 +7,12 @@ import {
7
7
  provide,
8
8
  ref
9
9
  } from 'vue';
10
- import { createDependentProxy, createSymbolProperty } from './util';
10
+ import {
11
+ createDependentProxy,
12
+ createSymbolProperty,
13
+ defineRouterProperties,
14
+ isVue2
15
+ } from './util';
11
16
 
12
17
  export interface VueInstance {
13
18
  $parent?: VueInstance | null;
@@ -41,12 +46,7 @@ function getCurrentProxy(): VueInstance {
41
46
  return instance.proxy;
42
47
  }
43
48
 
44
- function findRouterContext(vm?: VueInstance): RouterContext {
45
- // If no vm provided, try to get current instance
46
- if (!vm) {
47
- vm = getCurrentProxy();
48
- }
49
-
49
+ function findRouterContext(vm: VueInstance): RouterContext {
50
50
  let context = routerContextProperty.get(vm);
51
51
  if (context) {
52
52
  return context;
@@ -72,7 +72,7 @@ function findRouterContext(vm?: VueInstance): RouterContext {
72
72
  * This is a lower-level function used internally by useRouter().
73
73
  * Use this in Options API, use useRouter() in Composition API.
74
74
  *
75
- * @param instance - Vue component instance (optional, will use getCurrentInstance if not provided)
75
+ * @param instance - Vue component instance
76
76
  * @returns Router instance
77
77
  * @throws {Error} If router context is not found
78
78
  *
@@ -94,12 +94,9 @@ function findRouterContext(vm?: VueInstance): RouterContext {
94
94
  * }
95
95
  * }
96
96
  * });
97
- *
98
- * // Can also be called without instance (uses getCurrentInstance internally)
99
- * const router = getRouter(); // Works in globalProperties getters
100
97
  * ```
101
98
  */
102
- export function getRouter(instance?: VueInstance): Router {
99
+ export function getRouter(instance: VueInstance): Router {
103
100
  return findRouterContext(instance).router;
104
101
  }
105
102
 
@@ -108,7 +105,7 @@ export function getRouter(instance?: VueInstance): Router {
108
105
  * This is a lower-level function used internally by useRoute().
109
106
  * Use this in Options API, use useRoute() in Composition API.
110
107
  *
111
- * @param instance - Vue component instance (optional, will use getCurrentInstance if not provided)
108
+ * @param instance - Vue component instance
112
109
  * @returns Current route object
113
110
  * @throws {Error} If router context is not found
114
111
  *
@@ -130,12 +127,9 @@ export function getRouter(instance?: VueInstance): Router {
130
127
  * }
131
128
  * }
132
129
  * });
133
- *
134
- * // Can also be called without instance (uses getCurrentInstance internally)
135
- * const route = getRoute(); // Works in globalProperties getters
136
130
  * ```
137
131
  */
138
- export function getRoute(instance?: VueInstance): Route {
132
+ export function getRoute(instance: VueInstance): Route {
139
133
  return findRouterContext(instance).route;
140
134
  }
141
135
 
@@ -272,6 +266,16 @@ export function useProvideRouter(router: Router): void {
272
266
  provide(ROUTER_INJECT_KEY, context);
273
267
  routerContextProperty.set(proxy, context);
274
268
 
269
+ if (!isVue2) {
270
+ const app = getCurrentInstance()!.appContext.app;
271
+ defineRouterProperties(
272
+ app.config.globalProperties,
273
+ () => proxiedRouter,
274
+ () => proxiedRoute,
275
+ true
276
+ );
277
+ }
278
+
275
279
  const unwatch = router.afterEach((to: Route) => {
276
280
  if (router.route === to) {
277
281
  to.syncTo(proxiedRoute);
package/src/util.test.ts CHANGED
@@ -1,28 +1,29 @@
1
1
  /**
2
2
  * @vitest-environment happy-dom
3
3
  */
4
+
5
+ import type { Route, Router } from '@esmx/router';
4
6
  import { beforeEach, describe, expect, it, vi } from 'vitest';
5
7
  import { computed, nextTick, ref, version } from 'vue';
6
8
  import {
7
9
  createDependentProxy,
8
10
  createSymbolProperty,
11
+ defineRouterProperties,
9
12
  isESModule,
10
- isVue3,
13
+ isVue2,
11
14
  resolveComponent
12
15
  } from './util';
13
16
 
14
17
  describe('util.ts - Utility Functions', () => {
15
- describe('isVue3', () => {
16
- it('should correctly identify Vue 3', () => {
17
- // Since we're testing in a Vue 3 environment, isVue3 should be true
18
- expect(isVue3).toBe(version.startsWith('3.'));
19
- expect(typeof isVue3).toBe('boolean');
18
+ describe('isVue2', () => {
19
+ it('should correctly identify Vue 2', () => {
20
+ expect(isVue2).toBe(version.startsWith('2.'));
21
+ expect(typeof isVue2).toBe('boolean');
20
22
  });
21
23
 
22
24
  it('should be consistent with Vue version check', () => {
23
- // Verify the logic is correct
24
- const expectedResult = version.startsWith('3.');
25
- expect(isVue3).toBe(expectedResult);
25
+ const expectedResult = version.startsWith('2.');
26
+ expect(isVue2).toBe(expectedResult);
26
27
  });
27
28
  });
28
29
 
@@ -472,4 +473,288 @@ describe('util.ts - Utility Functions', () => {
472
473
  expect(spy).toHaveBeenCalled();
473
474
  });
474
475
  });
476
+
477
+ describe('defineRouterProperties', () => {
478
+ it('should define $router and $route properties on target object', () => {
479
+ const target = {};
480
+ const mockRouter = { push: vi.fn() } as unknown as Router;
481
+ const mockRoute = { path: '/test' } as unknown as Route;
482
+
483
+ const routerGetter = vi.fn(function (this: any) {
484
+ return mockRouter;
485
+ });
486
+ const routeGetter = vi.fn(function (this: any) {
487
+ return mockRoute;
488
+ });
489
+
490
+ defineRouterProperties(target, routerGetter, routeGetter);
491
+
492
+ expect(target).toHaveProperty('$router');
493
+ expect(target).toHaveProperty('$route');
494
+ });
495
+
496
+ it('should call getter functions when accessing properties', () => {
497
+ const target = {};
498
+ const mockRouter = { push: vi.fn() } as unknown as Router;
499
+ const mockRoute = { path: '/test' } as unknown as Route;
500
+
501
+ const routerGetter = vi.fn(function (this: any) {
502
+ return mockRouter;
503
+ });
504
+ const routeGetter = vi.fn(function (this: any) {
505
+ return mockRoute;
506
+ });
507
+
508
+ defineRouterProperties(target, routerGetter, routeGetter);
509
+
510
+ const router = (target as any).$router;
511
+ const route = (target as any).$route;
512
+
513
+ expect(routerGetter).toHaveBeenCalled();
514
+ expect(routeGetter).toHaveBeenCalled();
515
+ expect(router).toBe(mockRouter);
516
+ expect(route).toBe(mockRoute);
517
+ });
518
+
519
+ it('should use correct this context in getter functions', () => {
520
+ const target = { customProp: 'test' };
521
+ const mockRouter = { push: vi.fn() } as unknown as Router;
522
+ const mockRoute = { path: '/test' } as unknown as Route;
523
+
524
+ const routerGetter = vi.fn(function (this: any) {
525
+ return this.customProp ? mockRouter : ({} as Router);
526
+ });
527
+ const routeGetter = vi.fn(function (this: any) {
528
+ return this.customProp ? mockRoute : ({} as Route);
529
+ });
530
+
531
+ defineRouterProperties(target, routerGetter, routeGetter);
532
+
533
+ const router = (target as any).$router;
534
+ const route = (target as any).$route;
535
+
536
+ expect(router).toBe(mockRouter);
537
+ expect(route).toBe(mockRoute);
538
+ });
539
+
540
+ it('should make properties non-enumerable by default', () => {
541
+ const target = {};
542
+ const mockRouter = { push: vi.fn() } as unknown as Router;
543
+ const mockRoute = { path: '/test' } as unknown as Route;
544
+
545
+ const routerGetter = vi.fn(function (this: any) {
546
+ return mockRouter;
547
+ });
548
+ const routeGetter = vi.fn(function (this: any) {
549
+ return mockRoute;
550
+ });
551
+
552
+ defineRouterProperties(target, routerGetter, routeGetter);
553
+
554
+ const descriptors = Object.getOwnPropertyDescriptors(target);
555
+
556
+ expect(descriptors.$router.enumerable).toBe(false);
557
+ expect(descriptors.$route.enumerable).toBe(false);
558
+ });
559
+
560
+ it('should make properties non-configurable by default', () => {
561
+ const target = {};
562
+ const mockRouter = { push: vi.fn() } as unknown as Router;
563
+ const mockRoute = { path: '/test' } as unknown as Route;
564
+
565
+ const routerGetter = vi.fn(function (this: any) {
566
+ return mockRouter;
567
+ });
568
+ const routeGetter = vi.fn(function (this: any) {
569
+ return mockRoute;
570
+ });
571
+
572
+ defineRouterProperties(target, routerGetter, routeGetter);
573
+
574
+ const descriptors = Object.getOwnPropertyDescriptors(target);
575
+
576
+ expect(descriptors.$router.configurable).toBe(false);
577
+ expect(descriptors.$route.configurable).toBe(false);
578
+ });
579
+
580
+ it('should make properties configurable when configurable option is true', () => {
581
+ const target = {};
582
+ const mockRouter = { push: vi.fn() } as unknown as Router;
583
+ const mockRoute = { path: '/test' } as unknown as Route;
584
+
585
+ const routerGetter = vi.fn(function (this: any) {
586
+ return mockRouter;
587
+ });
588
+ const routeGetter = vi.fn(function (this: any) {
589
+ return mockRoute;
590
+ });
591
+
592
+ defineRouterProperties(target, routerGetter, routeGetter, true);
593
+
594
+ const descriptors = Object.getOwnPropertyDescriptors(target);
595
+
596
+ expect(descriptors.$router.configurable).toBe(true);
597
+ expect(descriptors.$route.configurable).toBe(true);
598
+ });
599
+
600
+ it('should define properties as getters', () => {
601
+ const target = {};
602
+ const mockRouter = { push: vi.fn() } as unknown as Router;
603
+ const mockRoute = { path: '/test' } as unknown as Route;
604
+
605
+ const routerGetter = vi.fn(function (this: any) {
606
+ return mockRouter;
607
+ });
608
+ const routeGetter = vi.fn(function (this: any) {
609
+ return mockRoute;
610
+ });
611
+
612
+ defineRouterProperties(target, routerGetter, routeGetter);
613
+
614
+ const descriptors = Object.getOwnPropertyDescriptors(target);
615
+
616
+ expect(typeof descriptors.$router.get).toBe('function');
617
+ expect(typeof descriptors.$route.get).toBe('function');
618
+ expect(descriptors.$router.set).toBeUndefined();
619
+ expect(descriptors.$route.set).toBeUndefined();
620
+ });
621
+
622
+ it('should call getter each time property is accessed', () => {
623
+ const target = {};
624
+ const mockRouter = { push: vi.fn() } as unknown as Router;
625
+
626
+ const routerGetter = vi.fn(function (this: any) {
627
+ return mockRouter;
628
+ });
629
+ const routeGetter = vi.fn(function (this: any) {
630
+ return { path: '/test' } as Route;
631
+ });
632
+
633
+ defineRouterProperties(target, routerGetter, routeGetter);
634
+
635
+ (target as any).$router;
636
+ (target as any).$router;
637
+ (target as any).$router;
638
+
639
+ expect(routerGetter).toHaveBeenCalledTimes(3);
640
+ });
641
+
642
+ it('should work with different target objects', () => {
643
+ const targets = [
644
+ {},
645
+ { existingProp: 'value' },
646
+ Object.create(null),
647
+ new (class Test {})()
648
+ ];
649
+
650
+ const mockRouter = { push: vi.fn() } as unknown as Router;
651
+ const mockRoute = { path: '/test' } as unknown as Route;
652
+
653
+ const routerGetter = vi.fn(function (this: any) {
654
+ return mockRouter;
655
+ });
656
+ const routeGetter = vi.fn(function (this: any) {
657
+ return mockRoute;
658
+ });
659
+
660
+ targets.forEach((target) => {
661
+ defineRouterProperties(target, routerGetter, routeGetter);
662
+
663
+ expect(target).toHaveProperty('$router');
664
+ expect(target).toHaveProperty('$route');
665
+
666
+ const router = (target as any).$router;
667
+ const route = (target as any).$route;
668
+
669
+ expect(router).toBe(mockRouter);
670
+ expect(route).toBe(mockRoute);
671
+ });
672
+ });
673
+
674
+ it('should preserve existing properties on target object', () => {
675
+ const target = {
676
+ existingProp: 'value',
677
+ $existingRouter: 'should not be affected'
678
+ };
679
+
680
+ const mockRouter = { push: vi.fn() } as unknown as Router;
681
+ const mockRoute = { path: '/test' } as unknown as Route;
682
+
683
+ const routerGetter = vi.fn(function (this: any) {
684
+ return mockRouter;
685
+ });
686
+ const routeGetter = vi.fn(function (this: any) {
687
+ return mockRoute;
688
+ });
689
+
690
+ defineRouterProperties(target, routerGetter, routeGetter);
691
+
692
+ expect(target.existingProp).toBe('value');
693
+ expect(target.$existingRouter).toBe('should not be affected');
694
+ expect(target).toHaveProperty('$router');
695
+ expect(target).toHaveProperty('$route');
696
+ });
697
+
698
+ it('should handle getter functions that return undefined', () => {
699
+ const target = {};
700
+
701
+ const routerGetter = vi.fn(function (this: any) {
702
+ return undefined as any;
703
+ });
704
+ const routeGetter = vi.fn(function (this: any) {
705
+ return undefined as any;
706
+ });
707
+
708
+ defineRouterProperties(target, routerGetter, routeGetter);
709
+
710
+ const router = (target as any).$router;
711
+ const route = (target as any).$route;
712
+
713
+ expect(router).toBeUndefined();
714
+ expect(route).toBeUndefined();
715
+ expect(routerGetter).toHaveBeenCalled();
716
+ expect(routeGetter).toHaveBeenCalled();
717
+ });
718
+
719
+ it('should handle getter functions that throw errors', () => {
720
+ const target = {};
721
+
722
+ const routerGetter = vi.fn(function (this: any) {
723
+ throw new Error('Router error');
724
+ });
725
+ const routeGetter = vi.fn(function (this: any) {
726
+ throw new Error('Route error');
727
+ });
728
+
729
+ defineRouterProperties(target, routerGetter, routeGetter);
730
+
731
+ expect(() => (target as any).$router).toThrow('Router error');
732
+ expect(() => (target as any).$route).toThrow('Route error');
733
+ });
734
+
735
+ it('should work with arrow functions as getters', () => {
736
+ const target = {};
737
+ const mockRouter = { push: vi.fn() } as unknown as Router;
738
+ const mockRoute = { path: '/test' } as unknown as Route;
739
+ const context = { customContext: 'test' };
740
+
741
+ const routerGetter = vi.fn(() => mockRouter);
742
+ const routeGetter = vi.fn(() => mockRoute);
743
+
744
+ defineRouterProperties.call(
745
+ context,
746
+ target,
747
+ routerGetter,
748
+ routeGetter
749
+ );
750
+
751
+ const router = (target as any).$router;
752
+ const route = (target as any).$route;
753
+
754
+ expect(router).toBe(mockRouter);
755
+ expect(route).toBe(mockRoute);
756
+ expect(routerGetter).toHaveBeenCalled();
757
+ expect(routeGetter).toHaveBeenCalled();
758
+ });
759
+ });
475
760
  });
package/src/util.ts CHANGED
@@ -1,7 +1,37 @@
1
+ import type { Route, Router } from '@esmx/router';
1
2
  import type { Ref } from 'vue';
2
3
  import { version } from 'vue';
3
4
 
4
- export const isVue3 = version.startsWith('3.');
5
+ export const isVue2 = version.startsWith('2.');
6
+
7
+ /**
8
+ * Define $router and $route properties on a target object.
9
+ * Used to set up global properties for Vue components.
10
+ *
11
+ * @param target - The target object to define properties on (e.g., globalProperties or prototype)
12
+ * @param routerGetter - Getter function for $router (can use `this` in Vue 2)
13
+ * @param routeGetter - Getter function for $route (can use `this` in Vue 2)
14
+ * @param configurable - Whether the properties should be configurable (default: false)
15
+ */
16
+ export function defineRouterProperties(
17
+ target: Record<string, unknown>,
18
+ routerGetter: (this: unknown) => Router,
19
+ routeGetter: (this: unknown) => Route,
20
+ configurable = false
21
+ ): void {
22
+ Object.defineProperties(target, {
23
+ $router: {
24
+ configurable,
25
+ enumerable: false,
26
+ get: routerGetter
27
+ },
28
+ $route: {
29
+ configurable,
30
+ enumerable: false,
31
+ get: routeGetter
32
+ }
33
+ });
34
+ }
5
35
 
6
36
  export function createSymbolProperty<T>(symbol: symbol) {
7
37
  return {