@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.
package/dist/plugin.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { RouterLink } from "./router-link.mjs";
2
2
  import { RouterView } from "./router-view.mjs";
3
3
  import { getRoute, getRouter } from "./use.mjs";
4
- import { isVue3 } from "./util.mjs";
4
+ import { defineRouterProperties, isVue2 } from "./util.mjs";
5
5
  export const RouterPlugin = {
6
6
  /**
7
7
  * Install the router plugin.
@@ -17,18 +17,24 @@ export const RouterPlugin = {
17
17
  if (!target) {
18
18
  throw new Error("[@esmx/router-vue] Invalid Vue app instance");
19
19
  }
20
- Object.defineProperties(target, {
21
- $router: {
22
- get() {
23
- return getRouter(isVue3 ? null : this);
20
+ if (isVue2) {
21
+ defineRouterProperties(
22
+ target,
23
+ function() {
24
+ return getRouter(this);
25
+ },
26
+ function() {
27
+ return getRoute(this);
24
28
  }
25
- },
26
- $route: {
27
- get() {
28
- return getRoute(isVue3 ? null : this);
29
- }
30
- }
31
- });
29
+ );
30
+ } else {
31
+ const throwError = () => {
32
+ throw new Error(
33
+ "[@esmx/router-vue] Router not provided. Please call useProvideRouter() in your root component setup."
34
+ );
35
+ };
36
+ defineRouterProperties(target, throwError, throwError, true);
37
+ }
32
38
  vueApp.component("RouterLink", RouterLink);
33
39
  vueApp.component("RouterView", RouterView);
34
40
  }
@@ -126,7 +126,7 @@ describe("plugin.ts - RouterPlugin", () => {
126
126
  expect(routeDescriptor == null ? void 0 : routeDescriptor.get).toBeDefined();
127
127
  expect(typeof (routeDescriptor == null ? void 0 : routeDescriptor.get)).toBe("function");
128
128
  expect(routeDescriptor == null ? void 0 : routeDescriptor.enumerable).toBe(false);
129
- expect(routeDescriptor == null ? void 0 : routeDescriptor.configurable).toBe(false);
129
+ expect(routeDescriptor == null ? void 0 : routeDescriptor.configurable).toBe(true);
130
130
  });
131
131
  it("should provide consistent $router instance across components", async () => {
132
132
  const ChildComponent = defineComponent({
@@ -374,6 +374,208 @@ describe("plugin.ts - RouterPlugin", () => {
374
374
  expect(typeof routerViewComponent.setup).toBe("function");
375
375
  });
376
376
  });
377
+ describe("$router and $route Access", () => {
378
+ it("should throw error when accessing $router without useProvideRouter", () => {
379
+ app = createApp({
380
+ render: () => h("div", "Test App")
381
+ });
382
+ app.use(RouterPlugin);
383
+ const globalProperties = app.config.globalProperties;
384
+ expect(() => {
385
+ globalProperties.$router;
386
+ }).toThrow(
387
+ "[@esmx/router-vue] Router not provided. Please call useProvideRouter() in your root component setup."
388
+ );
389
+ });
390
+ it("should throw error when accessing $route without useProvideRouter", () => {
391
+ app = createApp({
392
+ render: () => h("div", "Test App")
393
+ });
394
+ app.use(RouterPlugin);
395
+ const globalProperties = app.config.globalProperties;
396
+ expect(() => {
397
+ globalProperties.$route;
398
+ }).toThrow(
399
+ "[@esmx/router-vue] Router not provided. Please call useProvideRouter() in your root component setup."
400
+ );
401
+ });
402
+ it("should override default getters after useProvideRouter is called", async () => {
403
+ app = createApp({
404
+ setup() {
405
+ useProvideRouter(router);
406
+ return () => h("div", "Test App");
407
+ }
408
+ });
409
+ app.use(RouterPlugin);
410
+ app.mount(container);
411
+ await nextTick();
412
+ const globalProperties = app.config.globalProperties;
413
+ expect(() => {
414
+ globalProperties.$router;
415
+ }).not.toThrow();
416
+ expect(() => {
417
+ globalProperties.$route;
418
+ }).not.toThrow();
419
+ });
420
+ it("should return correct router instance via $router", async () => {
421
+ let capturedRouter = null;
422
+ const TestComponent = defineComponent({
423
+ mounted() {
424
+ capturedRouter = this.$router;
425
+ },
426
+ render() {
427
+ return h("div", "Test");
428
+ }
429
+ });
430
+ app = createApp({
431
+ setup() {
432
+ useProvideRouter(router);
433
+ return () => h(TestComponent);
434
+ }
435
+ });
436
+ app.use(RouterPlugin);
437
+ app.mount(container);
438
+ await nextTick();
439
+ expect(capturedRouter).toBeDefined();
440
+ expect(capturedRouter).toBeInstanceOf(Router);
441
+ expect(capturedRouter.route.path).toBe("/");
442
+ });
443
+ it("should return correct route via $route", async () => {
444
+ var _a;
445
+ let capturedRoute = null;
446
+ const TestComponent = defineComponent({
447
+ mounted() {
448
+ capturedRoute = this.$route;
449
+ },
450
+ render() {
451
+ return h("div", "Test");
452
+ }
453
+ });
454
+ app = createApp({
455
+ setup() {
456
+ useProvideRouter(router);
457
+ return () => h(TestComponent);
458
+ }
459
+ });
460
+ app.use(RouterPlugin);
461
+ app.mount(container);
462
+ await nextTick();
463
+ expect(capturedRoute).toBeDefined();
464
+ expect(capturedRoute.path).toBe("/");
465
+ expect((_a = capturedRoute.meta) == null ? void 0 : _a.title).toBe(
466
+ "Home"
467
+ );
468
+ });
469
+ it("should update $route when navigation occurs", async () => {
470
+ const routes = [];
471
+ const TestComponent = defineComponent({
472
+ mounted() {
473
+ routes.push(this.$route.path);
474
+ },
475
+ updated() {
476
+ routes.push(this.$route.path);
477
+ },
478
+ render() {
479
+ return h("div", this.$route.path);
480
+ }
481
+ });
482
+ app = createApp({
483
+ setup() {
484
+ useProvideRouter(router);
485
+ return () => h(TestComponent);
486
+ }
487
+ });
488
+ app.use(RouterPlugin);
489
+ app.mount(container);
490
+ await nextTick();
491
+ expect(routes).toContain("/");
492
+ await router.push("/about");
493
+ await nextTick();
494
+ expect(router.route.path).toBe("/about");
495
+ });
496
+ it("should provide same $router instance in nested components", async () => {
497
+ const routerInstances = [];
498
+ const ChildComponent = defineComponent({
499
+ mounted() {
500
+ routerInstances.push(this.$router);
501
+ },
502
+ render() {
503
+ return h("div", "Child");
504
+ }
505
+ });
506
+ const ParentComponent = defineComponent({
507
+ mounted() {
508
+ routerInstances.push(this.$router);
509
+ },
510
+ render() {
511
+ return h("div", [h("span", "Parent"), h(ChildComponent)]);
512
+ }
513
+ });
514
+ app = createApp({
515
+ setup() {
516
+ useProvideRouter(router);
517
+ return () => h(ParentComponent);
518
+ }
519
+ });
520
+ app.use(RouterPlugin);
521
+ app.mount(container);
522
+ await nextTick();
523
+ expect(routerInstances.length).toBe(2);
524
+ expect(routerInstances[0]).toBe(routerInstances[1]);
525
+ });
526
+ it("should allow navigation via $router.push", async () => {
527
+ let capturedRouter = null;
528
+ const TestComponent = defineComponent({
529
+ mounted() {
530
+ capturedRouter = this.$router;
531
+ },
532
+ render() {
533
+ return h("div", "Test");
534
+ }
535
+ });
536
+ app = createApp({
537
+ setup() {
538
+ useProvideRouter(router);
539
+ return () => h(TestComponent);
540
+ }
541
+ });
542
+ app.use(RouterPlugin);
543
+ app.mount(container);
544
+ await nextTick();
545
+ expect(capturedRouter).toBeDefined();
546
+ const routerInstance = capturedRouter;
547
+ expect(typeof routerInstance.push).toBe("function");
548
+ await routerInstance.push("/about");
549
+ await nextTick();
550
+ expect(router.route.path).toBe("/about");
551
+ });
552
+ it("should allow navigation via $router.replace", async () => {
553
+ let capturedRouter = null;
554
+ const TestComponent = defineComponent({
555
+ mounted() {
556
+ capturedRouter = this.$router;
557
+ },
558
+ render() {
559
+ return h("div", "Test");
560
+ }
561
+ });
562
+ app = createApp({
563
+ setup() {
564
+ useProvideRouter(router);
565
+ return () => h(TestComponent);
566
+ }
567
+ });
568
+ app.use(RouterPlugin);
569
+ app.mount(container);
570
+ await nextTick();
571
+ expect(capturedRouter).toBeDefined();
572
+ const routerInstance = capturedRouter;
573
+ expect(typeof routerInstance.replace).toBe("function");
574
+ await routerInstance.replace("/about");
575
+ await nextTick();
576
+ expect(router.route.path).toBe("/about");
577
+ });
578
+ });
377
579
  describe("Advanced Plugin Features", () => {
378
580
  it("should support property descriptor configuration", () => {
379
581
  const testApp = {
@@ -394,10 +596,10 @@ describe("plugin.ts - RouterPlugin", () => {
394
596
  );
395
597
  expect(routerDescriptor == null ? void 0 : routerDescriptor.get).toBeDefined();
396
598
  expect(routerDescriptor == null ? void 0 : routerDescriptor.enumerable).toBe(false);
397
- expect(routerDescriptor == null ? void 0 : routerDescriptor.configurable).toBe(false);
599
+ expect(routerDescriptor == null ? void 0 : routerDescriptor.configurable).toBe(true);
398
600
  expect(routeDescriptor == null ? void 0 : routeDescriptor.get).toBeDefined();
399
601
  expect(routeDescriptor == null ? void 0 : routeDescriptor.enumerable).toBe(false);
400
- expect(routeDescriptor == null ? void 0 : routeDescriptor.configurable).toBe(false);
602
+ expect(routeDescriptor == null ? void 0 : routeDescriptor.configurable).toBe(true);
401
603
  });
402
604
  it("should handle different app instance structures", () => {
403
605
  const minimalApp = {
@@ -1,6 +1,6 @@
1
1
  import { defineComponent, h } from "vue";
2
2
  import { useLink } from "./use.mjs";
3
- import { isVue3 } from "./util.mjs";
3
+ import { isVue2 } from "./util.mjs";
4
4
  export const RouterLink = defineComponent({
5
5
  name: "RouterLink",
6
6
  props: {
@@ -83,17 +83,19 @@ export const RouterLink = defineComponent({
83
83
  },
84
84
  setup(props, context) {
85
85
  const link = useLink(props);
86
- if (isVue3) {
86
+ if (isVue2) {
87
87
  return () => {
88
88
  var _a, _b;
89
+ const { class: className, ...attributes } = link.value.attributes;
89
90
  return h(
90
91
  link.value.tag,
91
92
  {
92
- ...link.value.attributes,
93
- ...context.attrs,
94
- ...link.value.createEventHandlers(
95
- (name) => "on".concat(name.charAt(0).toUpperCase()).concat(name.slice(1))
96
- )
93
+ attrs: {
94
+ ...attributes,
95
+ ...context.attrs
96
+ },
97
+ class: className,
98
+ on: link.value.createEventHandlers()
97
99
  },
98
100
  (_b = (_a = context.slots).default) == null ? void 0 : _b.call(_a)
99
101
  );
@@ -101,16 +103,14 @@ export const RouterLink = defineComponent({
101
103
  }
102
104
  return () => {
103
105
  var _a, _b;
104
- const { class: className, ...attributes } = link.value.attributes;
105
106
  return h(
106
107
  link.value.tag,
107
108
  {
108
- attrs: {
109
- ...attributes,
110
- ...context.attrs
111
- },
112
- class: className,
113
- on: link.value.createEventHandlers()
109
+ ...link.value.attributes,
110
+ ...context.attrs,
111
+ ...link.value.createEventHandlers(
112
+ (name) => "on".concat(name.charAt(0).toUpperCase()).concat(name.slice(1))
113
+ )
114
114
  },
115
115
  (_b = (_a = context.slots).default) == null ? void 0 : _b.call(_a)
116
116
  );
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ createApp,
4
+ getCurrentInstance,
5
+ h,
6
+ inject,
7
+ nextTick,
8
+ provide
9
+ } from "vue";
10
+ describe("app.runWithContext()", () => {
11
+ it("should exist on Vue app", () => {
12
+ const app = createApp({ render: () => h("div") });
13
+ expect(typeof app.runWithContext).toBe("function");
14
+ });
15
+ it("should enable inject to read provided value within context", () => {
16
+ const KEY = Symbol("ctx-key");
17
+ const app = createApp({ render: () => h("div") });
18
+ app.provide(KEY, 42);
19
+ const value = app.runWithContext(() => inject(KEY));
20
+ expect(value).toBe(42);
21
+ });
22
+ it("should not read value provided in root setup via runWithContext", async () => {
23
+ const KEY = Symbol("ctx-key-setup");
24
+ const app = createApp({
25
+ setup() {
26
+ provide(KEY, 7);
27
+ return () => h("div");
28
+ }
29
+ });
30
+ const container = document.createElement("div");
31
+ document.body.appendChild(container);
32
+ app.mount(container);
33
+ await nextTick();
34
+ const value = app.runWithContext(() => inject(KEY));
35
+ expect(value).toBeUndefined();
36
+ app.unmount();
37
+ container.remove();
38
+ });
39
+ it("should read app-level provide set inside setup via appContext", async () => {
40
+ const KEY = Symbol("ctx-key-setup-app");
41
+ const app = createApp({
42
+ setup() {
43
+ const appInst = getCurrentInstance().appContext.app;
44
+ appInst.provide(KEY, 9);
45
+ return () => h("div");
46
+ }
47
+ });
48
+ const container = document.createElement("div");
49
+ document.body.appendChild(container);
50
+ app.mount(container);
51
+ await nextTick();
52
+ const value = app.runWithContext(() => inject(KEY));
53
+ expect(value).toBe(9);
54
+ app.unmount();
55
+ container.remove();
56
+ });
57
+ });
package/dist/use.d.ts CHANGED
@@ -9,7 +9,7 @@ export interface VueInstance {
9
9
  * This is a lower-level function used internally by useRouter().
10
10
  * Use this in Options API, use useRouter() in Composition API.
11
11
  *
12
- * @param instance - Vue component instance (optional, will use getCurrentInstance if not provided)
12
+ * @param instance - Vue component instance
13
13
  * @returns Router instance
14
14
  * @throws {Error} If router context is not found
15
15
  *
@@ -31,18 +31,15 @@ export interface VueInstance {
31
31
  * }
32
32
  * }
33
33
  * });
34
- *
35
- * // Can also be called without instance (uses getCurrentInstance internally)
36
- * const router = getRouter(); // Works in globalProperties getters
37
34
  * ```
38
35
  */
39
- export declare function getRouter(instance?: VueInstance): Router;
36
+ export declare function getRouter(instance: VueInstance): Router;
40
37
  /**
41
38
  * Get current route from a Vue component instance.
42
39
  * This is a lower-level function used internally by useRoute().
43
40
  * Use this in Options API, use useRoute() in Composition API.
44
41
  *
45
- * @param instance - Vue component instance (optional, will use getCurrentInstance if not provided)
42
+ * @param instance - Vue component instance
46
43
  * @returns Current route object
47
44
  * @throws {Error} If router context is not found
48
45
  *
@@ -64,12 +61,9 @@ export declare function getRouter(instance?: VueInstance): Router;
64
61
  * }
65
62
  * }
66
63
  * });
67
- *
68
- * // Can also be called without instance (uses getCurrentInstance internally)
69
- * const route = getRoute(); // Works in globalProperties getters
70
64
  * ```
71
65
  */
72
- export declare function getRoute(instance?: VueInstance): Route;
66
+ export declare function getRoute(instance: VueInstance): Route;
73
67
  /**
74
68
  * Get the router instance in a Vue component.
75
69
  * Must be called within setup() or other composition functions.
package/dist/use.mjs CHANGED
@@ -6,7 +6,12 @@ import {
6
6
  provide,
7
7
  ref
8
8
  } from "vue";
9
- import { createDependentProxy, createSymbolProperty } from "./util.mjs";
9
+ import {
10
+ createDependentProxy,
11
+ createSymbolProperty,
12
+ defineRouterProperties,
13
+ isVue2
14
+ } from "./util.mjs";
10
15
  const ROUTER_CONTEXT_KEY = Symbol("router-context");
11
16
  const ROUTER_INJECT_KEY = Symbol("router-inject");
12
17
  const ROUTER_VIEW_DEPTH_KEY = Symbol("router-view-depth");
@@ -24,9 +29,6 @@ function getCurrentProxy() {
24
29
  return instance.proxy;
25
30
  }
26
31
  function findRouterContext(vm) {
27
- if (!vm) {
28
- vm = getCurrentProxy();
29
- }
30
32
  let context = routerContextProperty.get(vm);
31
33
  if (context) {
32
34
  return context;
@@ -75,6 +77,15 @@ export function useProvideRouter(router) {
75
77
  };
76
78
  provide(ROUTER_INJECT_KEY, context);
77
79
  routerContextProperty.set(proxy, context);
80
+ if (!isVue2) {
81
+ const app = getCurrentInstance().appContext.app;
82
+ defineRouterProperties(
83
+ app.config.globalProperties,
84
+ () => proxiedRouter,
85
+ () => proxiedRoute,
86
+ true
87
+ );
88
+ }
78
89
  const unwatch = router.afterEach((to) => {
79
90
  if (router.route === to) {
80
91
  to.syncTo(proxiedRoute);
package/dist/util.d.ts CHANGED
@@ -1,5 +1,16 @@
1
+ import type { Route, Router } from '@esmx/router';
1
2
  import type { Ref } from 'vue';
2
- export declare const isVue3: boolean;
3
+ export declare const isVue2: boolean;
4
+ /**
5
+ * Define $router and $route properties on a target object.
6
+ * Used to set up global properties for Vue components.
7
+ *
8
+ * @param target - The target object to define properties on (e.g., globalProperties or prototype)
9
+ * @param routerGetter - Getter function for $router (can use `this` in Vue 2)
10
+ * @param routeGetter - Getter function for $route (can use `this` in Vue 2)
11
+ * @param configurable - Whether the properties should be configurable (default: false)
12
+ */
13
+ export declare function defineRouterProperties(target: Record<string, unknown>, routerGetter: (this: unknown) => Router, routeGetter: (this: unknown) => Route, configurable?: boolean): void;
3
14
  export declare function createSymbolProperty<T>(symbol: symbol): {
4
15
  readonly set: (instance: any, value: T) => void;
5
16
  readonly get: (instance: any) => T | undefined;
package/dist/util.mjs CHANGED
@@ -1,5 +1,19 @@
1
1
  import { version } from "vue";
2
- export const isVue3 = version.startsWith("3.");
2
+ export const isVue2 = version.startsWith("2.");
3
+ export function defineRouterProperties(target, routerGetter, routeGetter, configurable = false) {
4
+ Object.defineProperties(target, {
5
+ $router: {
6
+ configurable,
7
+ enumerable: false,
8
+ get: routerGetter
9
+ },
10
+ $route: {
11
+ configurable,
12
+ enumerable: false,
13
+ get: routeGetter
14
+ }
15
+ });
16
+ }
3
17
  export function createSymbolProperty(symbol) {
4
18
  return {
5
19
  set(instance, value) {
@@ -1 +1,4 @@
1
+ /**
2
+ * @vitest-environment happy-dom
3
+ */
1
4
  export {};