@esmx/router-vue 3.0.0-rc.17 → 3.0.0-rc.20

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 +61 -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 +31 -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 +92 -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 +62 -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
@@ -0,0 +1,319 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import { version } from "vue";
3
+ import {
4
+ createSymbolProperty,
5
+ isESModule,
6
+ isVue3,
7
+ resolveComponent
8
+ } from "./util.mjs";
9
+ describe("util.ts - Utility Functions", () => {
10
+ describe("isVue3", () => {
11
+ it("should correctly identify Vue 3", () => {
12
+ expect(isVue3).toBe(version.startsWith("3."));
13
+ expect(typeof isVue3).toBe("boolean");
14
+ });
15
+ it("should be consistent with Vue version check", () => {
16
+ const expectedResult = version.startsWith("3.");
17
+ expect(isVue3).toBe(expectedResult);
18
+ });
19
+ });
20
+ describe("createSymbolProperty", () => {
21
+ let testSymbol;
22
+ let symbolProperty;
23
+ let testInstance;
24
+ beforeEach(() => {
25
+ testSymbol = Symbol("test-symbol");
26
+ symbolProperty = createSymbolProperty(testSymbol);
27
+ testInstance = {};
28
+ });
29
+ describe("set method", () => {
30
+ it("should set value using symbol as key", () => {
31
+ const testValue = "test-value";
32
+ symbolProperty.set(testInstance, testValue);
33
+ expect(testInstance[testSymbol]).toBe(testValue);
34
+ });
35
+ it("should handle different value types", () => {
36
+ const stringValue = "string-value";
37
+ const numberValue = 42;
38
+ const objectValue = { key: "value" };
39
+ const arrayValue = [1, 2, 3];
40
+ const stringProperty = createSymbolProperty(
41
+ Symbol("string")
42
+ );
43
+ const numberProperty = createSymbolProperty(
44
+ Symbol("number")
45
+ );
46
+ const objectProperty = createSymbolProperty(
47
+ Symbol("object")
48
+ );
49
+ const arrayProperty = createSymbolProperty(
50
+ Symbol("array")
51
+ );
52
+ stringProperty.set(testInstance, stringValue);
53
+ numberProperty.set(testInstance, numberValue);
54
+ objectProperty.set(testInstance, objectValue);
55
+ arrayProperty.set(testInstance, arrayValue);
56
+ expect(stringProperty.get(testInstance)).toBe(stringValue);
57
+ expect(numberProperty.get(testInstance)).toBe(numberValue);
58
+ expect(objectProperty.get(testInstance)).toBe(objectValue);
59
+ expect(arrayProperty.get(testInstance)).toBe(arrayValue);
60
+ });
61
+ it("should overwrite existing value", () => {
62
+ const firstValue = "first-value";
63
+ const secondValue = "second-value";
64
+ symbolProperty.set(testInstance, firstValue);
65
+ expect(symbolProperty.get(testInstance)).toBe(firstValue);
66
+ symbolProperty.set(testInstance, secondValue);
67
+ expect(symbolProperty.get(testInstance)).toBe(secondValue);
68
+ });
69
+ });
70
+ describe("get method", () => {
71
+ it("should return undefined for non-existent symbol", () => {
72
+ const result = symbolProperty.get(testInstance);
73
+ expect(result).toBeUndefined();
74
+ });
75
+ it("should return correct value after setting", () => {
76
+ const testValue = "retrieved-value";
77
+ symbolProperty.set(testInstance, testValue);
78
+ const result = symbolProperty.get(testInstance);
79
+ expect(result).toBe(testValue);
80
+ });
81
+ it("should return undefined after value is deleted", () => {
82
+ const testValue = "temporary-value";
83
+ symbolProperty.set(testInstance, testValue);
84
+ delete testInstance[testSymbol];
85
+ const result = symbolProperty.get(testInstance);
86
+ expect(result).toBeUndefined();
87
+ });
88
+ });
89
+ describe("symbol isolation", () => {
90
+ it("should not interfere with different symbols", () => {
91
+ const symbol1 = Symbol("symbol1");
92
+ const symbol2 = Symbol("symbol2");
93
+ const property1 = createSymbolProperty(symbol1);
94
+ const property2 = createSymbolProperty(symbol2);
95
+ const value1 = "value1";
96
+ const value2 = "value2";
97
+ property1.set(testInstance, value1);
98
+ property2.set(testInstance, value2);
99
+ expect(property1.get(testInstance)).toBe(value1);
100
+ expect(property2.get(testInstance)).toBe(value2);
101
+ expect(property1.get(testInstance)).not.toBe(value2);
102
+ expect(property2.get(testInstance)).not.toBe(value1);
103
+ });
104
+ it("should work with multiple instances", () => {
105
+ const instance1 = {};
106
+ const instance2 = {};
107
+ const testValue1 = "instance1-value";
108
+ const testValue2 = "instance2-value";
109
+ symbolProperty.set(instance1, testValue1);
110
+ symbolProperty.set(instance2, testValue2);
111
+ expect(symbolProperty.get(instance1)).toBe(testValue1);
112
+ expect(symbolProperty.get(instance2)).toBe(testValue2);
113
+ });
114
+ });
115
+ describe("type safety", () => {
116
+ it("should maintain type information through generic", () => {
117
+ const interfaceSymbol = Symbol("interface");
118
+ const interfaceProperty = createSymbolProperty(interfaceSymbol);
119
+ const testObject = { name: "test", count: 5 };
120
+ interfaceProperty.set(testInstance, testObject);
121
+ const result = interfaceProperty.get(testInstance);
122
+ expect(result).toEqual(testObject);
123
+ expect(result == null ? void 0 : result.name).toBe("test");
124
+ expect(result == null ? void 0 : result.count).toBe(5);
125
+ });
126
+ });
127
+ });
128
+ describe("isESModule", () => {
129
+ describe("should return true for ES modules", () => {
130
+ it("should identify module with __esModule property", () => {
131
+ const esModule = { __esModule: true };
132
+ expect(isESModule(esModule)).toBe(true);
133
+ });
134
+ it("should identify module with Symbol.toStringTag", () => {
135
+ const esModule = { [Symbol.toStringTag]: "Module" };
136
+ expect(isESModule(esModule)).toBe(true);
137
+ });
138
+ it("should identify module with both properties", () => {
139
+ const esModule = {
140
+ __esModule: true,
141
+ [Symbol.toStringTag]: "Module"
142
+ };
143
+ expect(isESModule(esModule)).toBe(true);
144
+ });
145
+ it("should handle truthy __esModule values", () => {
146
+ const testCases = [
147
+ { __esModule: true },
148
+ { __esModule: 1 },
149
+ { __esModule: "true" },
150
+ { __esModule: {} },
151
+ { __esModule: [] }
152
+ ];
153
+ testCases.forEach((moduleObj) => {
154
+ expect(isESModule(moduleObj)).toBe(true);
155
+ });
156
+ });
157
+ });
158
+ describe("should return false for non-ES modules", () => {
159
+ it("should return false for null", () => {
160
+ expect(isESModule(null)).toBe(false);
161
+ });
162
+ it("should return false for undefined", () => {
163
+ expect(isESModule(void 0)).toBe(false);
164
+ });
165
+ it("should return false for plain objects", () => {
166
+ const plainObject = { key: "value" };
167
+ expect(isESModule(plainObject)).toBe(false);
168
+ });
169
+ it("should return false for objects with falsy __esModule", () => {
170
+ const testCases = [
171
+ { __esModule: false },
172
+ { __esModule: 0 },
173
+ { __esModule: "" },
174
+ { __esModule: null },
175
+ { __esModule: void 0 }
176
+ ];
177
+ testCases.forEach((moduleObj) => {
178
+ expect(isESModule(moduleObj)).toBe(false);
179
+ });
180
+ });
181
+ it("should return false for objects with wrong Symbol.toStringTag", () => {
182
+ const testCases = [
183
+ { [Symbol.toStringTag]: "Object" },
184
+ { [Symbol.toStringTag]: "Function" },
185
+ { [Symbol.toStringTag]: "Array" },
186
+ { [Symbol.toStringTag]: "" },
187
+ { [Symbol.toStringTag]: null }
188
+ ];
189
+ testCases.forEach((moduleObj) => {
190
+ expect(isESModule(moduleObj)).toBe(false);
191
+ });
192
+ });
193
+ it("should return false for primitive values", () => {
194
+ const primitives = ["string", 42, true, false, Symbol("test")];
195
+ primitives.forEach((primitive) => {
196
+ expect(isESModule(primitive)).toBe(false);
197
+ });
198
+ });
199
+ });
200
+ });
201
+ describe("resolveComponent", () => {
202
+ describe("should return null for falsy inputs", () => {
203
+ const falsyValues = [null, void 0, false, 0, "", Number.NaN];
204
+ falsyValues.forEach((value) => {
205
+ it(`should return null for ${value}`, () => {
206
+ expect(resolveComponent(value)).toBeNull();
207
+ });
208
+ });
209
+ });
210
+ describe("should handle ES modules", () => {
211
+ it("should return default export when available", () => {
212
+ const defaultComponent = { name: "DefaultComponent" };
213
+ const esModule = {
214
+ __esModule: true,
215
+ default: defaultComponent,
216
+ namedExport: { name: "NamedComponent" }
217
+ };
218
+ const result = resolveComponent(esModule);
219
+ expect(result).toBe(defaultComponent);
220
+ });
221
+ it("should return the module itself when no default export", () => {
222
+ const esModule = {
223
+ __esModule: true,
224
+ namedExport: { name: "NamedComponent" }
225
+ };
226
+ const result = resolveComponent(esModule);
227
+ expect(result).toBe(esModule);
228
+ });
229
+ it("should prefer default export over module when both exist", () => {
230
+ const defaultComponent = { name: "DefaultComponent" };
231
+ const esModule = {
232
+ __esModule: true,
233
+ default: defaultComponent,
234
+ name: "ModuleComponent"
235
+ };
236
+ const result = resolveComponent(esModule);
237
+ expect(result).toBe(defaultComponent);
238
+ expect(result).not.toBe(esModule);
239
+ });
240
+ it("should handle modules with Symbol.toStringTag", () => {
241
+ const defaultComponent = { name: "SymbolTagComponent" };
242
+ const esModule = {
243
+ [Symbol.toStringTag]: "Module",
244
+ default: defaultComponent
245
+ };
246
+ const result = resolveComponent(esModule);
247
+ expect(result).toBe(defaultComponent);
248
+ });
249
+ it("should handle falsy default export", () => {
250
+ const falsyDefaults = [null, void 0, false, 0, ""];
251
+ falsyDefaults.forEach((falsyDefault) => {
252
+ const esModule = {
253
+ __esModule: true,
254
+ default: falsyDefault,
255
+ fallback: { name: "FallbackComponent" }
256
+ };
257
+ const result = resolveComponent(esModule);
258
+ expect(result).toBe(esModule);
259
+ });
260
+ });
261
+ });
262
+ describe("should handle non-ES modules", () => {
263
+ it("should return component directly for non-ES modules", () => {
264
+ const component = { name: "RegularComponent" };
265
+ const result = resolveComponent(component);
266
+ expect(result).toBe(component);
267
+ });
268
+ it("should return function components directly", () => {
269
+ const functionComponent = () => ({ name: "FunctionComponent" });
270
+ const result = resolveComponent(functionComponent);
271
+ expect(result).toBe(functionComponent);
272
+ });
273
+ it("should return class components directly", () => {
274
+ class ClassComponent {
275
+ name = "ClassComponent";
276
+ }
277
+ const result = resolveComponent(ClassComponent);
278
+ expect(result).toBe(ClassComponent);
279
+ });
280
+ });
281
+ describe("edge cases", () => {
282
+ it("should handle circular references in modules", () => {
283
+ const esModule = {
284
+ __esModule: true
285
+ };
286
+ esModule.default = esModule;
287
+ const result = resolveComponent(esModule);
288
+ expect(result).toBe(esModule);
289
+ });
290
+ it("should handle deeply nested default exports", () => {
291
+ const actualComponent = { name: "DeepComponent" };
292
+ const esModule = {
293
+ __esModule: true,
294
+ default: {
295
+ default: {
296
+ default: actualComponent
297
+ }
298
+ }
299
+ };
300
+ const result = resolveComponent(esModule);
301
+ expect(result).toEqual({
302
+ default: {
303
+ default: actualComponent
304
+ }
305
+ });
306
+ });
307
+ it("should handle modules with both __esModule and Symbol.toStringTag", () => {
308
+ const defaultComponent = { name: "BothPropertiesComponent" };
309
+ const esModule = {
310
+ __esModule: true,
311
+ [Symbol.toStringTag]: "Module",
312
+ default: defaultComponent
313
+ };
314
+ const result = resolveComponent(esModule);
315
+ expect(result).toBe(defaultComponent);
316
+ });
317
+ });
318
+ });
319
+ });
package/dist/vue2.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { Route, Router } from '@esmx/router';
2
+ declare module 'vue/types/vue' {
3
+ interface Vue {
4
+ readonly $router: Router;
5
+ readonly $route: Route;
6
+ }
7
+ }
8
+ declare module 'vue2/types/vue' {
9
+ interface Vue {
10
+ readonly $router: Router;
11
+ readonly $route: Route;
12
+ }
13
+ }
package/dist/vue2.mjs ADDED
File without changes
package/dist/vue3.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { Route, Router } from '@esmx/router';
2
+ import type { RouterLink } from './router-link';
3
+ import type { RouterView } from './router-view';
4
+ declare module 'vue' {
5
+ interface ComponentCustomProperties {
6
+ readonly $router: Router;
7
+ readonly $route: Route;
8
+ }
9
+ interface GlobalComponents {
10
+ RouterLink: typeof RouterLink;
11
+ RouterView: typeof RouterView;
12
+ }
13
+ }
package/dist/vue3.mjs ADDED
File without changes
package/package.json CHANGED
@@ -1,5 +1,24 @@
1
1
  {
2
2
  "name": "@esmx/router-vue",
3
+ "description": "Vue integration for @esmx/router - A universal router that works seamlessly with both Vue 2.7+ and Vue 3",
4
+ "keywords": [
5
+ "vue",
6
+ "router",
7
+ "routing",
8
+ "vue-router",
9
+ "composition-api",
10
+ "typescript",
11
+ "universal",
12
+ "vue2",
13
+ "vue3",
14
+ "navigation",
15
+ "esmx",
16
+ "esm",
17
+ "single-page-application",
18
+ "spa",
19
+ "framework",
20
+ "frontend"
21
+ ],
3
22
  "template": "library",
4
23
  "scripts": {
5
24
  "lint:js": "biome check --write --no-errors-on-unmatched",
@@ -28,26 +47,24 @@
28
47
  }
29
48
  ],
30
49
  "peerDependencies": {
31
- "vue": "^3.0.0"
50
+ "vue": "^3.5.0 || ^2.7.0"
32
51
  },
33
52
  "dependencies": {
34
- "@esmx/router": "3.0.0-rc.17"
53
+ "@esmx/router": "3.0.0-rc.20"
35
54
  },
36
55
  "devDependencies": {
37
56
  "@biomejs/biome": "1.9.4",
38
- "@esmx/lint": "3.0.0-rc.17",
39
- "@gez/lint": "3.0.0-rc.9",
40
- "@types/node": "22.13.10",
41
- "@vitest/coverage-v8": "3.0.8",
42
- "@vue/runtime-core": "^3.4.27",
43
- "@vue/runtime-dom": "^3.4.27",
44
- "stylelint": "16.15.0",
57
+ "@esmx/lint": "3.0.0-rc.20",
58
+ "@types/node": "22.15.18",
59
+ "@vitest/coverage-v8": "3.1.3",
60
+ "stylelint": "16.19.1",
45
61
  "typescript": "5.8.2",
46
- "unbuild": "2.0.0",
47
- "vitest": "3.0.8",
48
- "vue": "^3.4.27"
62
+ "unbuild": "3.5.0",
63
+ "vitest": "3.1.3",
64
+ "vue": "3.5.13",
65
+ "vue2": "npm:vue@2.7.16"
49
66
  },
50
- "version": "3.0.0-rc.17",
67
+ "version": "3.0.0-rc.20",
51
68
  "type": "module",
52
69
  "private": false,
53
70
  "exports": {
@@ -66,5 +83,5 @@
66
83
  "template",
67
84
  "public"
68
85
  ],
69
- "gitHead": "5304ac6ef7a3b5d3bcb2db59cceb3f35723c2409"
86
+ "gitHead": "4c6490c23cbc148cb189b3f2cdae930eed901607"
70
87
  }
@@ -0,0 +1,263 @@
1
+ /**
2
+ * @vitest-environment happy-dom
3
+ */
4
+ import { describe, expect, it } from 'vitest';
5
+ import * as RouterVueModule from './index';
6
+
7
+ describe('index.ts - Package Entry Point', () => {
8
+ describe('Composition API Exports', () => {
9
+ it('should export useRouter function', () => {
10
+ expect(RouterVueModule.useRouter).toBeDefined();
11
+ expect(typeof RouterVueModule.useRouter).toBe('function');
12
+ });
13
+
14
+ it('should export useRoute function', () => {
15
+ expect(RouterVueModule.useRoute).toBeDefined();
16
+ expect(typeof RouterVueModule.useRoute).toBe('function');
17
+ });
18
+
19
+ it('should export useProvideRouter function', () => {
20
+ expect(RouterVueModule.useProvideRouter).toBeDefined();
21
+ expect(typeof RouterVueModule.useProvideRouter).toBe('function');
22
+ });
23
+
24
+ it('should export useLink function', () => {
25
+ expect(RouterVueModule.useLink).toBeDefined();
26
+ expect(typeof RouterVueModule.useLink).toBe('function');
27
+ });
28
+ });
29
+
30
+ describe('Options API Exports', () => {
31
+ it('should export getRouter function', () => {
32
+ expect(RouterVueModule.getRouter).toBeDefined();
33
+ expect(typeof RouterVueModule.getRouter).toBe('function');
34
+ });
35
+
36
+ it('should export getRoute function', () => {
37
+ expect(RouterVueModule.getRoute).toBeDefined();
38
+ expect(typeof RouterVueModule.getRoute).toBe('function');
39
+ });
40
+ });
41
+
42
+ describe('Component Exports', () => {
43
+ it('should export RouterLink component', () => {
44
+ expect(RouterVueModule.RouterLink).toBeDefined();
45
+ expect(typeof RouterVueModule.RouterLink).toBe('object');
46
+ expect(RouterVueModule.RouterLink.name).toBe('RouterLink');
47
+ expect(typeof RouterVueModule.RouterLink.setup).toBe('function');
48
+ });
49
+
50
+ it('should export RouterView component', () => {
51
+ expect(RouterVueModule.RouterView).toBeDefined();
52
+ expect(typeof RouterVueModule.RouterView).toBe('object');
53
+ expect(RouterVueModule.RouterView.name).toBe('RouterView');
54
+ expect(typeof RouterVueModule.RouterView.setup).toBe('function');
55
+ });
56
+ });
57
+
58
+ describe('Plugin Exports', () => {
59
+ it('should export RouterPlugin', () => {
60
+ expect(RouterVueModule.RouterPlugin).toBeDefined();
61
+ expect(typeof RouterVueModule.RouterPlugin).toBe('object');
62
+ expect(typeof RouterVueModule.RouterPlugin.install).toBe(
63
+ 'function'
64
+ );
65
+ });
66
+ });
67
+
68
+ describe('Export Completeness', () => {
69
+ it('should export all expected functions and components', () => {
70
+ const expectedExports = [
71
+ // Composition API
72
+ 'useRouter',
73
+ 'useRoute',
74
+ 'useProvideRouter',
75
+ 'useLink',
76
+ // Options API
77
+ 'getRouter',
78
+ 'getRoute',
79
+ // Components
80
+ 'RouterLink',
81
+ 'RouterView',
82
+ // Plugin
83
+ 'RouterPlugin'
84
+ ];
85
+
86
+ expectedExports.forEach((exportName) => {
87
+ expect(RouterVueModule).toHaveProperty(exportName);
88
+ expect(
89
+ RouterVueModule[exportName as keyof typeof RouterVueModule]
90
+ ).toBeDefined();
91
+ });
92
+ });
93
+
94
+ it('should not export unexpected items', () => {
95
+ const actualExports = Object.keys(RouterVueModule);
96
+ const expectedExports = [
97
+ 'useRouter',
98
+ 'useRoute',
99
+ 'useProvideRouter',
100
+ 'useLink',
101
+ 'getRouter',
102
+ 'getRoute',
103
+ 'RouterLink',
104
+ 'RouterView',
105
+ 'RouterPlugin'
106
+ ];
107
+
108
+ // Check that we don't have unexpected exports
109
+ const unexpectedExports = actualExports.filter(
110
+ (exportName) => !expectedExports.includes(exportName)
111
+ );
112
+
113
+ expect(unexpectedExports).toEqual([]);
114
+ });
115
+ });
116
+
117
+ describe('Function Signatures', () => {
118
+ it('should have correct function signatures for Composition API', () => {
119
+ // These should throw expected errors when called without proper context
120
+ expect(() => {
121
+ RouterVueModule.useRouter();
122
+ }).toThrow('useRouter() can only be called during setup()');
123
+
124
+ expect(() => {
125
+ RouterVueModule.useRoute();
126
+ }).toThrow('useRoute() can only be called during setup()');
127
+
128
+ expect(() => {
129
+ RouterVueModule.useLink({
130
+ to: '/test',
131
+ type: 'push',
132
+ exact: 'include'
133
+ });
134
+ }).toThrow('useRouter() can only be called during setup()');
135
+ });
136
+
137
+ it('should have correct function signatures for Options API', () => {
138
+ expect(() => {
139
+ try {
140
+ RouterVueModule.getRouter({} as Record<string, unknown>);
141
+ } catch (error: unknown) {
142
+ // Expected to throw context error when called without router
143
+ expect((error as Error).message).toContain(
144
+ 'Router context not found'
145
+ );
146
+ }
147
+ }).not.toThrow();
148
+
149
+ expect(() => {
150
+ try {
151
+ RouterVueModule.getRoute({} as Record<string, unknown>);
152
+ } catch (error: unknown) {
153
+ // Expected to throw context error when called without router
154
+ expect((error as Error).message).toContain(
155
+ 'Router context not found'
156
+ );
157
+ }
158
+ }).not.toThrow();
159
+ });
160
+ });
161
+
162
+ describe('Component Properties', () => {
163
+ it('should have RouterLink with correct properties', () => {
164
+ const { RouterLink } = RouterVueModule;
165
+
166
+ expect(RouterLink.name).toBe('RouterLink');
167
+ expect(RouterLink.props).toBeDefined();
168
+ expect(RouterLink.setup).toBeDefined();
169
+
170
+ // Check required props
171
+ expect(RouterLink.props.to).toBeDefined();
172
+ expect(RouterLink.props.to.required).toBe(true);
173
+
174
+ // Check default props
175
+ expect(RouterLink.props.type.default).toBe('push');
176
+ expect(RouterLink.props.exact.default).toBe('include');
177
+ expect(RouterLink.props.tag.default).toBe('a');
178
+ expect(RouterLink.props.event.default).toBe('click');
179
+ });
180
+
181
+ it('should have RouterView with correct properties', () => {
182
+ const { RouterView } = RouterVueModule;
183
+
184
+ expect(RouterView.name).toBe('RouterView');
185
+ expect(RouterView.setup).toBeDefined();
186
+ // RouterView should not have props
187
+ expect(RouterView.props).toBeUndefined();
188
+ });
189
+ });
190
+
191
+ describe('Plugin Interface', () => {
192
+ it('should have RouterPlugin with install method', () => {
193
+ const { RouterPlugin } = RouterVueModule;
194
+
195
+ expect(RouterPlugin.install).toBeDefined();
196
+ expect(typeof RouterPlugin.install).toBe('function');
197
+
198
+ // Test plugin install signature - should throw for null input
199
+ expect(() => {
200
+ RouterPlugin.install(null);
201
+ }).toThrow();
202
+ });
203
+ });
204
+
205
+ describe('Module Structure', () => {
206
+ it('should be a proper ES module', () => {
207
+ // Check that the module exports are properly structured
208
+ expect(typeof RouterVueModule).toBe('object');
209
+ expect(RouterVueModule).not.toBeNull();
210
+
211
+ // Verify it's not a default export
212
+ expect('default' in RouterVueModule).toBe(false);
213
+ });
214
+
215
+ it('should have consistent export naming', () => {
216
+ // All function exports should be camelCase
217
+ const functionExports = [
218
+ 'useRouter',
219
+ 'useRoute',
220
+ 'useProvideRouter',
221
+ 'useLink',
222
+ 'getRouter',
223
+ 'getRoute'
224
+ ];
225
+
226
+ functionExports.forEach((exportName) => {
227
+ expect(exportName).toMatch(/^[a-z][a-zA-Z]*$/);
228
+ });
229
+
230
+ // Component exports should be PascalCase
231
+ const componentExports = [
232
+ 'RouterLink',
233
+ 'RouterView',
234
+ 'RouterPlugin'
235
+ ];
236
+
237
+ componentExports.forEach((exportName) => {
238
+ expect(exportName).toMatch(/^[A-Z][a-zA-Z]*$/);
239
+ });
240
+ });
241
+ });
242
+
243
+ describe('TypeScript Integration', () => {
244
+ it('should provide proper TypeScript types', () => {
245
+ // These checks verify that TypeScript types are properly exported
246
+ // The actual type checking is done by the TypeScript compiler
247
+
248
+ // Verify functions have proper types
249
+ expect(typeof RouterVueModule.useRouter).toBe('function');
250
+ expect(typeof RouterVueModule.useRoute).toBe('function');
251
+ expect(typeof RouterVueModule.getRouter).toBe('function');
252
+ expect(typeof RouterVueModule.getRoute).toBe('function');
253
+
254
+ // Verify components have proper structure
255
+ expect(RouterVueModule.RouterLink).toHaveProperty('name');
256
+ expect(RouterVueModule.RouterLink).toHaveProperty('props');
257
+ expect(RouterVueModule.RouterLink).toHaveProperty('setup');
258
+
259
+ expect(RouterVueModule.RouterView).toHaveProperty('name');
260
+ expect(RouterVueModule.RouterView).toHaveProperty('setup');
261
+ });
262
+ });
263
+ });