@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.
- package/LICENSE +1 -1
- package/README.md +563 -0
- package/README.zh-CN.md +563 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.mjs +11 -4
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.mjs +206 -0
- package/dist/plugin.d.ts +55 -11
- package/dist/plugin.mjs +32 -16
- package/dist/plugin.test.d.ts +1 -0
- package/dist/plugin.test.mjs +436 -0
- package/dist/router-link.d.ts +202 -0
- package/dist/router-link.mjs +84 -0
- package/dist/router-link.test.d.ts +1 -0
- package/dist/router-link.test.mjs +456 -0
- package/dist/router-view.d.ts +30 -0
- package/dist/router-view.mjs +17 -0
- package/dist/router-view.test.d.ts +1 -0
- package/dist/router-view.test.mjs +459 -0
- package/dist/use.d.ts +198 -3
- package/dist/use.mjs +75 -9
- package/dist/use.test.d.ts +1 -0
- package/dist/use.test.mjs +461 -0
- package/dist/util.d.ts +7 -0
- package/dist/util.mjs +24 -0
- package/dist/util.test.d.ts +1 -0
- package/dist/util.test.mjs +319 -0
- package/dist/vue2.d.ts +13 -0
- package/dist/vue2.mjs +0 -0
- package/dist/vue3.d.ts +13 -0
- package/dist/vue3.mjs +0 -0
- package/package.json +31 -14
- package/src/index.test.ts +263 -0
- package/src/index.ts +16 -4
- package/src/plugin.test.ts +574 -0
- package/src/plugin.ts +86 -31
- package/src/router-link.test.ts +569 -0
- package/src/router-link.ts +148 -0
- package/src/router-view.test.ts +599 -0
- package/src/router-view.ts +61 -0
- package/src/use.test.ts +616 -0
- package/src/use.ts +307 -11
- package/src/util.test.ts +418 -0
- package/src/util.ts +32 -0
- package/src/vue2.ts +16 -0
- package/src/vue3.ts +15 -0
- package/dist/link.d.ts +0 -101
- package/dist/link.mjs +0 -103
- package/dist/symbols.d.ts +0 -3
- package/dist/symbols.mjs +0 -3
- package/dist/view.d.ts +0 -21
- package/dist/view.mjs +0 -75
- package/src/link.ts +0 -177
- package/src/symbols.ts +0 -8
- package/src/view.ts +0 -95
package/src/use.test.ts
ADDED
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
import { type Route, type RouteConfig, Router, RouterMode } from '@esmx/router';
|
|
2
|
+
/**
|
|
3
|
+
* @vitest-environment happy-dom
|
|
4
|
+
*/
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
createApp,
|
|
8
|
+
defineComponent,
|
|
9
|
+
getCurrentInstance,
|
|
10
|
+
h,
|
|
11
|
+
nextTick,
|
|
12
|
+
ref
|
|
13
|
+
} from 'vue';
|
|
14
|
+
import {
|
|
15
|
+
type VueInstance,
|
|
16
|
+
getRoute,
|
|
17
|
+
getRouter,
|
|
18
|
+
useLink,
|
|
19
|
+
useProvideRouter,
|
|
20
|
+
useRoute,
|
|
21
|
+
useRouter
|
|
22
|
+
} from './use';
|
|
23
|
+
|
|
24
|
+
describe('use.ts - Vue Router Integration', () => {
|
|
25
|
+
let router: Router;
|
|
26
|
+
let testContainer: HTMLElement;
|
|
27
|
+
|
|
28
|
+
beforeEach(async () => {
|
|
29
|
+
// Create test container
|
|
30
|
+
testContainer = document.createElement('div');
|
|
31
|
+
testContainer.id = 'test-app';
|
|
32
|
+
document.body.appendChild(testContainer);
|
|
33
|
+
|
|
34
|
+
// Create test routes
|
|
35
|
+
const routes: RouteConfig[] = [
|
|
36
|
+
{
|
|
37
|
+
path: '/',
|
|
38
|
+
component: defineComponent({ template: '<div>Home</div>' }),
|
|
39
|
+
meta: { title: 'Home' }
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
path: '/about',
|
|
43
|
+
component: defineComponent({ template: '<div>About</div>' }),
|
|
44
|
+
meta: { title: 'About' }
|
|
45
|
+
}
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// Create router instance
|
|
49
|
+
router = new Router({
|
|
50
|
+
root: '#test-app',
|
|
51
|
+
routes,
|
|
52
|
+
mode: RouterMode.memory,
|
|
53
|
+
base: new URL('http://localhost:3000/')
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Initialize router to root path
|
|
57
|
+
await router.replace('/');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
// Clean up test environment
|
|
62
|
+
if (testContainer.parentNode) {
|
|
63
|
+
testContainer.parentNode.removeChild(testContainer);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Destroy router
|
|
67
|
+
if (router) {
|
|
68
|
+
router.destroy();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('Error Handling - Context Not Found', () => {
|
|
73
|
+
const contextNotFoundError =
|
|
74
|
+
'[@esmx/router-vue] Router context not found. ' +
|
|
75
|
+
'Please ensure useProvideRouter() is called in a parent component.';
|
|
76
|
+
|
|
77
|
+
const contextErrorTestCases = [
|
|
78
|
+
{
|
|
79
|
+
name: 'getRouter called without router context',
|
|
80
|
+
test: () => {
|
|
81
|
+
const app = createApp({ template: '<div>Test</div>' });
|
|
82
|
+
const vm = app.mount(testContainer);
|
|
83
|
+
const result = () => getRouter(vm);
|
|
84
|
+
app.unmount();
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'getRoute called without router context',
|
|
90
|
+
test: () => {
|
|
91
|
+
const app = createApp({ template: '<div>Test</div>' });
|
|
92
|
+
const vm = app.mount(testContainer);
|
|
93
|
+
const result = () => getRoute(vm);
|
|
94
|
+
app.unmount();
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
contextErrorTestCases.forEach(({ name, test }) => {
|
|
101
|
+
it(`should throw error when ${name}`, () => {
|
|
102
|
+
expect(test()).toThrow(contextNotFoundError);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const compositionContextErrorTestCases = [
|
|
107
|
+
{
|
|
108
|
+
name: 'useRouter called without router context',
|
|
109
|
+
setupFn: () => {
|
|
110
|
+
expect(() => useRouter()).toThrow(contextNotFoundError);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'useRoute called without router context',
|
|
115
|
+
setupFn: () => {
|
|
116
|
+
expect(() => useRoute()).toThrow(contextNotFoundError);
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'useLink called without router context',
|
|
121
|
+
setupFn: () => {
|
|
122
|
+
expect(() =>
|
|
123
|
+
useLink({
|
|
124
|
+
to: '/about',
|
|
125
|
+
type: 'push',
|
|
126
|
+
exact: 'include'
|
|
127
|
+
})
|
|
128
|
+
).toThrow(contextNotFoundError);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
compositionContextErrorTestCases.forEach(({ name, setupFn }) => {
|
|
134
|
+
it(`should throw error when ${name}`, () => {
|
|
135
|
+
const TestComponent = defineComponent({
|
|
136
|
+
setup() {
|
|
137
|
+
setupFn();
|
|
138
|
+
return () => '<div>Test</div>';
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const app = createApp(TestComponent);
|
|
143
|
+
app.mount(testContainer);
|
|
144
|
+
app.unmount();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('Error Handling - Setup Only', () => {
|
|
150
|
+
const setupOnlyTestCases = [
|
|
151
|
+
{
|
|
152
|
+
name: 'useRouter called outside setup()',
|
|
153
|
+
fn: () => useRouter(),
|
|
154
|
+
expectedError:
|
|
155
|
+
'[@esmx/router-vue] useRouter() can only be called during setup()'
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'useRoute called outside setup()',
|
|
159
|
+
fn: () => useRoute(),
|
|
160
|
+
expectedError:
|
|
161
|
+
'[@esmx/router-vue] useRoute() can only be called during setup()'
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'useLink called outside setup()',
|
|
165
|
+
fn: () =>
|
|
166
|
+
useLink({
|
|
167
|
+
to: '/about',
|
|
168
|
+
type: 'push',
|
|
169
|
+
exact: 'include'
|
|
170
|
+
}),
|
|
171
|
+
expectedError:
|
|
172
|
+
'[@esmx/router-vue] useRouter() can only be called during setup()'
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'useProvideRouter called outside setup()',
|
|
176
|
+
fn: () => useProvideRouter(router),
|
|
177
|
+
expectedError:
|
|
178
|
+
'[@esmx/router-vue] useProvideRouter() can only be called during setup()'
|
|
179
|
+
}
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
setupOnlyTestCases.forEach(({ name, fn, expectedError }) => {
|
|
183
|
+
it(`should throw error when ${name}`, () => {
|
|
184
|
+
expect(fn).toThrow(expectedError);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('Basic Functionality', () => {
|
|
190
|
+
it('should provide router context and return router instance from getRouter', async () => {
|
|
191
|
+
let routerInstance: Router | null = null;
|
|
192
|
+
|
|
193
|
+
const app = createApp({
|
|
194
|
+
setup() {
|
|
195
|
+
useProvideRouter(router);
|
|
196
|
+
return () => '<div>App</div>';
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const vm = app.mount(testContainer);
|
|
201
|
+
routerInstance = getRouter(vm);
|
|
202
|
+
|
|
203
|
+
expect(routerInstance).toBe(router);
|
|
204
|
+
expect(routerInstance).toBeInstanceOf(Router);
|
|
205
|
+
|
|
206
|
+
app.unmount();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should provide router context and return current route from getRoute', async () => {
|
|
210
|
+
let currentRoute: Route | null = null;
|
|
211
|
+
|
|
212
|
+
const app = createApp({
|
|
213
|
+
setup() {
|
|
214
|
+
useProvideRouter(router);
|
|
215
|
+
return () => '<div>App</div>';
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const vm = app.mount(testContainer);
|
|
220
|
+
currentRoute = getRoute(vm);
|
|
221
|
+
|
|
222
|
+
expect(currentRoute).toBeTruthy();
|
|
223
|
+
expect(currentRoute!.path).toBe('/');
|
|
224
|
+
expect(currentRoute!.meta.title).toBe('Home');
|
|
225
|
+
|
|
226
|
+
app.unmount();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe('Setup() Support - useRouter in setup()', () => {
|
|
231
|
+
it('should allow useRouter to work in setup() via provide/inject', async () => {
|
|
232
|
+
let routerInstance: Router | null = null;
|
|
233
|
+
let childRoute: Route | null = null;
|
|
234
|
+
|
|
235
|
+
// Child component that uses router in setup()
|
|
236
|
+
const ChildComponent = defineComponent({
|
|
237
|
+
name: 'ChildComponent',
|
|
238
|
+
setup() {
|
|
239
|
+
// This should now work in setup() thanks to provide/inject
|
|
240
|
+
routerInstance = useRouter();
|
|
241
|
+
childRoute = useRoute();
|
|
242
|
+
|
|
243
|
+
expect(routerInstance).toBe(router);
|
|
244
|
+
expect(childRoute.path).toBe('/');
|
|
245
|
+
|
|
246
|
+
return () => h('div', 'Child Component');
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Parent component that provides router
|
|
251
|
+
const ParentComponent = defineComponent({
|
|
252
|
+
name: 'ParentComponent',
|
|
253
|
+
components: { ChildComponent },
|
|
254
|
+
setup() {
|
|
255
|
+
useProvideRouter(router);
|
|
256
|
+
return () => h(ChildComponent);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const app = createApp(ParentComponent);
|
|
261
|
+
app.mount(testContainer);
|
|
262
|
+
await nextTick();
|
|
263
|
+
|
|
264
|
+
// Verify that setup() calls succeeded
|
|
265
|
+
expect(routerInstance).toBe(router);
|
|
266
|
+
expect(childRoute).toBeTruthy();
|
|
267
|
+
expect(childRoute!.path).toBe('/');
|
|
268
|
+
|
|
269
|
+
app.unmount();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should work with nested components in setup()', async () => {
|
|
273
|
+
let deepChildRouter: Router | null = null;
|
|
274
|
+
|
|
275
|
+
const DeepChildComponent = defineComponent({
|
|
276
|
+
name: 'DeepChildComponent',
|
|
277
|
+
setup() {
|
|
278
|
+
// Should work even in deeply nested components
|
|
279
|
+
deepChildRouter = useRouter();
|
|
280
|
+
expect(deepChildRouter).toBe(router);
|
|
281
|
+
return () => h('div', 'Deep Child');
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const MiddleComponent = defineComponent({
|
|
286
|
+
name: 'MiddleComponent',
|
|
287
|
+
components: { DeepChildComponent },
|
|
288
|
+
setup() {
|
|
289
|
+
return () => h(DeepChildComponent);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const TopComponent = defineComponent({
|
|
294
|
+
name: 'TopComponent',
|
|
295
|
+
components: { MiddleComponent },
|
|
296
|
+
setup() {
|
|
297
|
+
useProvideRouter(router);
|
|
298
|
+
return () => h(MiddleComponent);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const app = createApp(TopComponent);
|
|
303
|
+
app.mount(testContainer);
|
|
304
|
+
await nextTick();
|
|
305
|
+
|
|
306
|
+
expect(deepChildRouter).toBe(router);
|
|
307
|
+
app.unmount();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
describe('Component Hierarchy Context Finding - Investigation', () => {
|
|
312
|
+
it('should investigate component hierarchy traversal with logging', async () => {
|
|
313
|
+
let childRouterResult: Router | null = null;
|
|
314
|
+
let parentVmInstance: VueInstance | null = null;
|
|
315
|
+
let childVmInstance: VueInstance | null = null;
|
|
316
|
+
|
|
317
|
+
// Create a child component that doesn't have direct router context
|
|
318
|
+
const ChildComponent = defineComponent({
|
|
319
|
+
name: 'ChildComponent',
|
|
320
|
+
setup(_, { expose }) {
|
|
321
|
+
// Get current instance for investigation
|
|
322
|
+
const instance = getCurrentInstance();
|
|
323
|
+
childVmInstance = instance?.proxy || null;
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
// This should trigger hierarchy traversal
|
|
327
|
+
childRouterResult = useRouter();
|
|
328
|
+
} catch (error: unknown) {
|
|
329
|
+
expect((error as Error).message).toContain(
|
|
330
|
+
'Router context not found'
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
expose({ childVmInstance });
|
|
335
|
+
return () => '<div>Child Component</div>';
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Create a parent component that provides router context
|
|
340
|
+
const ParentComponent = defineComponent({
|
|
341
|
+
name: 'ParentComponent',
|
|
342
|
+
components: { ChildComponent },
|
|
343
|
+
setup(_, { expose }) {
|
|
344
|
+
// Get current instance for investigation
|
|
345
|
+
const instance = getCurrentInstance();
|
|
346
|
+
parentVmInstance = instance?.proxy || null;
|
|
347
|
+
|
|
348
|
+
// Provide router context at parent level
|
|
349
|
+
useProvideRouter(router);
|
|
350
|
+
|
|
351
|
+
expose({ parentVmInstance });
|
|
352
|
+
return () => h(ChildComponent);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const app = createApp(ParentComponent);
|
|
357
|
+
const mountedApp = app.mount(testContainer);
|
|
358
|
+
await nextTick();
|
|
359
|
+
|
|
360
|
+
// Investigate the actual component relationship
|
|
361
|
+
if (childVmInstance && parentVmInstance) {
|
|
362
|
+
// Check if router context exists on parent
|
|
363
|
+
const parentHasContext =
|
|
364
|
+
!!(parentVmInstance as Record<symbol, unknown>)[
|
|
365
|
+
Symbol.for('router-context')
|
|
366
|
+
] ||
|
|
367
|
+
Object.getOwnPropertySymbols(parentVmInstance).some((sym) =>
|
|
368
|
+
sym.toString().includes('router-context')
|
|
369
|
+
);
|
|
370
|
+
expect(parentHasContext).toBe(true);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// The test expectation
|
|
374
|
+
if (childRouterResult) {
|
|
375
|
+
expect(childRouterResult).toBe(router);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
app.unmount();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('should investigate direct getRouter call with component instances', async () => {
|
|
382
|
+
let parentInstance: VueInstance | null = null;
|
|
383
|
+
let childInstance: VueInstance | null = null;
|
|
384
|
+
|
|
385
|
+
const ChildComponent = defineComponent({
|
|
386
|
+
name: 'ChildComponent',
|
|
387
|
+
setup() {
|
|
388
|
+
const instance = getCurrentInstance();
|
|
389
|
+
childInstance = instance?.proxy || null;
|
|
390
|
+
return () => '<div>Child</div>';
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const ParentComponent = defineComponent({
|
|
395
|
+
name: 'ParentComponent',
|
|
396
|
+
components: { ChildComponent },
|
|
397
|
+
setup() {
|
|
398
|
+
const instance = getCurrentInstance();
|
|
399
|
+
parentInstance = instance?.proxy || null;
|
|
400
|
+
useProvideRouter(router);
|
|
401
|
+
return () => h(ChildComponent);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const app = createApp(ParentComponent);
|
|
406
|
+
app.mount(testContainer);
|
|
407
|
+
await nextTick();
|
|
408
|
+
|
|
409
|
+
if (childInstance && parentInstance) {
|
|
410
|
+
try {
|
|
411
|
+
const routerFromChild = getRouter(childInstance);
|
|
412
|
+
expect(routerFromChild).toBe(router);
|
|
413
|
+
} catch (error: unknown) {
|
|
414
|
+
expect((error as Error).message).toContain(
|
|
415
|
+
'Router context not found'
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
app.unmount();
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
describe('Navigation', () => {
|
|
425
|
+
it('should handle router navigation correctly', async () => {
|
|
426
|
+
const app = createApp({
|
|
427
|
+
setup() {
|
|
428
|
+
useProvideRouter(router);
|
|
429
|
+
return () => '<div>App</div>';
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
app.mount(testContainer);
|
|
434
|
+
|
|
435
|
+
// Initial route
|
|
436
|
+
expect(router.route.path).toBe('/');
|
|
437
|
+
expect(router.route.meta.title).toBe('Home');
|
|
438
|
+
|
|
439
|
+
// Navigate to different route
|
|
440
|
+
await router.push('/about');
|
|
441
|
+
|
|
442
|
+
// Route should be updated
|
|
443
|
+
expect(router.route.path).toBe('/about');
|
|
444
|
+
expect(router.route.meta.title).toBe('About');
|
|
445
|
+
|
|
446
|
+
app.unmount();
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
describe('Composition API Integration', () => {
|
|
451
|
+
it('should work with composition API functions in same component', async () => {
|
|
452
|
+
let compositionRouter: Router | null = null;
|
|
453
|
+
let compositionRoute: Route | null = null;
|
|
454
|
+
let linkResolver: ReturnType<typeof useLink> | null = null;
|
|
455
|
+
|
|
456
|
+
const TestComponent = defineComponent({
|
|
457
|
+
setup() {
|
|
458
|
+
useProvideRouter(router);
|
|
459
|
+
|
|
460
|
+
compositionRouter = useRouter();
|
|
461
|
+
compositionRoute = useRoute();
|
|
462
|
+
linkResolver = useLink({
|
|
463
|
+
to: '/about',
|
|
464
|
+
type: 'push',
|
|
465
|
+
exact: 'include'
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
return () => '<div>Test Component</div>';
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
const app = createApp(TestComponent);
|
|
473
|
+
app.mount(testContainer);
|
|
474
|
+
await nextTick();
|
|
475
|
+
|
|
476
|
+
// Test useRouter result
|
|
477
|
+
expect(compositionRouter).toBe(router);
|
|
478
|
+
|
|
479
|
+
// Test useRoute result
|
|
480
|
+
expect(compositionRoute).toBeTruthy();
|
|
481
|
+
expect(compositionRoute!.path).toBe('/');
|
|
482
|
+
expect(compositionRoute!.meta.title).toBe('Home');
|
|
483
|
+
|
|
484
|
+
// Test useLink result
|
|
485
|
+
expect(linkResolver).toBeTruthy();
|
|
486
|
+
expect(linkResolver!.value).toBeTruthy();
|
|
487
|
+
|
|
488
|
+
const link = linkResolver!.value;
|
|
489
|
+
expect(link).toHaveProperty('attributes');
|
|
490
|
+
expect(link).toHaveProperty('getEventHandlers');
|
|
491
|
+
expect(link).toHaveProperty('isActive');
|
|
492
|
+
|
|
493
|
+
app.unmount();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('should handle route updates reactively', async () => {
|
|
497
|
+
let routeRef: Route | null = null;
|
|
498
|
+
|
|
499
|
+
const TestComponent = defineComponent({
|
|
500
|
+
setup() {
|
|
501
|
+
useProvideRouter(router);
|
|
502
|
+
const route = useRoute();
|
|
503
|
+
routeRef = route;
|
|
504
|
+
return () => `<div>Current: ${route.path}</div>`;
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
const app = createApp(TestComponent);
|
|
509
|
+
app.mount(testContainer);
|
|
510
|
+
await nextTick();
|
|
511
|
+
|
|
512
|
+
// Initial route
|
|
513
|
+
expect(routeRef).toBeTruthy();
|
|
514
|
+
expect(routeRef!.path).toBe('/');
|
|
515
|
+
|
|
516
|
+
// Navigate and check if route is updated
|
|
517
|
+
await router.push('/about');
|
|
518
|
+
await nextTick();
|
|
519
|
+
|
|
520
|
+
expect(routeRef!.path).toBe('/about');
|
|
521
|
+
|
|
522
|
+
app.unmount();
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
describe('Deep Component Hierarchy', () => {
|
|
527
|
+
it('should cover deep component hierarchy traversal (multi-level parent chain)', async () => {
|
|
528
|
+
// This test specifically targets lines 59-60: the while loop continuation in findRouterContext
|
|
529
|
+
// Create: GrandParent (has router) -> Parent (no router) -> Child (needs router)
|
|
530
|
+
|
|
531
|
+
let childRouterResult: Router | null = null;
|
|
532
|
+
|
|
533
|
+
const ChildComponent = defineComponent({
|
|
534
|
+
name: 'ChildComponent',
|
|
535
|
+
setup() {
|
|
536
|
+
// This will trigger the while loop in findRouterContext
|
|
537
|
+
// First check: parent (no context) -> continue loop (lines 59-60)
|
|
538
|
+
// Second check: grandparent (has context) -> found!
|
|
539
|
+
return () => h('div', 'Deep Child');
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
const ParentComponent = defineComponent({
|
|
544
|
+
name: 'ParentComponent',
|
|
545
|
+
setup() {
|
|
546
|
+
// This parent does NOT provide router context
|
|
547
|
+
// So child will need to traverse up to grandparent
|
|
548
|
+
return () => h(ChildComponent);
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
const GrandParentComponent = defineComponent({
|
|
553
|
+
name: 'GrandParentComponent',
|
|
554
|
+
setup() {
|
|
555
|
+
useProvideRouter(router);
|
|
556
|
+
return () => h(ParentComponent);
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
const app = createApp(GrandParentComponent);
|
|
561
|
+
const mountedApp = app.mount(testContainer);
|
|
562
|
+
await nextTick();
|
|
563
|
+
|
|
564
|
+
// Manually traverse the component tree to find the deep child
|
|
565
|
+
// This simulates what happens when getRouter is called on a deeply nested component
|
|
566
|
+
const deepChildInstance = mountedApp;
|
|
567
|
+
|
|
568
|
+
// Create a mock deep child instance with proper parent chain
|
|
569
|
+
const mockDeepChild = {
|
|
570
|
+
$parent: {
|
|
571
|
+
// This is the middle parent (no router context)
|
|
572
|
+
$parent: mountedApp // This is the grandparent (has router context)
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
// This call will traverse: Child -> Parent (no context) -> GrandParent (has context)
|
|
577
|
+
// This should hit lines 59-60 (current = current.$parent; })
|
|
578
|
+
childRouterResult = getRouter(mockDeepChild as VueInstance);
|
|
579
|
+
|
|
580
|
+
expect(childRouterResult).toBe(router);
|
|
581
|
+
expect(childRouterResult).toBeInstanceOf(Router);
|
|
582
|
+
|
|
583
|
+
app.unmount();
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
it('should handle component hierarchy traversal with manual parent chain setup', () => {
|
|
587
|
+
// Create a chain: Root (has router) -> Middle (no router) -> Leaf (needs router)
|
|
588
|
+
// This ensures the while loop executes multiple iterations
|
|
589
|
+
|
|
590
|
+
const app = createApp({
|
|
591
|
+
setup() {
|
|
592
|
+
useProvideRouter(router);
|
|
593
|
+
return () => h('div', 'Root');
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
const rootInstance = app.mount(testContainer);
|
|
598
|
+
|
|
599
|
+
// Create a mock component hierarchy that requires traversal
|
|
600
|
+
const leafInstance = {
|
|
601
|
+
$parent: {
|
|
602
|
+
// Middle level - no router context
|
|
603
|
+
$parent: rootInstance // Root level - has router context
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
// This should traverse the parent chain and find the router in the root
|
|
608
|
+
const foundRouter = getRouter(leafInstance as VueInstance);
|
|
609
|
+
|
|
610
|
+
expect(foundRouter).toBe(router);
|
|
611
|
+
expect(foundRouter).toBeInstanceOf(Router);
|
|
612
|
+
|
|
613
|
+
app.unmount();
|
|
614
|
+
});
|
|
615
|
+
});
|
|
616
|
+
});
|