@esmx/router-vue 3.0.0-rc.29 → 3.0.0-rc.31
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.test.mjs +1 -1
- package/dist/router-link.d.ts +1 -1
- package/dist/use.mjs +10 -8
- package/dist/use.test.mjs +113 -415
- package/dist/util.d.ts +2 -0
- package/dist/util.mjs +8 -0
- package/dist/util.test.mjs +123 -97
- package/package.json +4 -4
- package/src/plugin.test.ts +1 -1
- package/src/use.test.ts +135 -534
- package/src/use.ts +13 -13
- package/src/util.test.ts +128 -111
- package/src/util.ts +13 -0
package/src/use.test.ts
CHANGED
|
@@ -1,616 +1,217 @@
|
|
|
1
|
-
import { type Route,
|
|
1
|
+
import { type Route, Router, RouterMode } from '@esmx/router';
|
|
2
2
|
/**
|
|
3
3
|
* @vitest-environment happy-dom
|
|
4
4
|
*/
|
|
5
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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', () => {
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { nextTick } from 'vue';
|
|
7
|
+
import { createApp, h } from 'vue';
|
|
8
|
+
import { useProvideRouter, useRoute, useRouter } from './use';
|
|
9
|
+
|
|
10
|
+
describe('Router Vue Integration', () => {
|
|
11
|
+
let app: ReturnType<typeof createApp>;
|
|
25
12
|
let router: Router;
|
|
26
|
-
let
|
|
13
|
+
let mountPoint: HTMLElement;
|
|
27
14
|
|
|
28
15
|
beforeEach(async () => {
|
|
29
|
-
// Create
|
|
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
|
|
16
|
+
// Create a real Router instance
|
|
49
17
|
router = new Router({
|
|
50
|
-
root: '#test-app',
|
|
51
|
-
routes,
|
|
52
18
|
mode: RouterMode.memory,
|
|
19
|
+
routes: [
|
|
20
|
+
{ path: '/initial', component: {} },
|
|
21
|
+
{ path: '/new-route', component: {} },
|
|
22
|
+
{ path: '/user/:id', component: {} },
|
|
23
|
+
{ path: '/new-path', component: {} }
|
|
24
|
+
],
|
|
53
25
|
base: new URL('http://localhost:3000/')
|
|
54
26
|
});
|
|
55
27
|
|
|
56
|
-
//
|
|
57
|
-
await router.replace('/');
|
|
28
|
+
// Ensure navigation to initial route is complete
|
|
29
|
+
await router.replace('/initial');
|
|
30
|
+
|
|
31
|
+
// Create mount point
|
|
32
|
+
mountPoint = document.createElement('div');
|
|
33
|
+
mountPoint.id = 'app';
|
|
34
|
+
document.body.appendChild(mountPoint);
|
|
58
35
|
});
|
|
59
36
|
|
|
60
37
|
afterEach(() => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
testContainer.parentNode.removeChild(testContainer);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Destroy router
|
|
67
|
-
if (router) {
|
|
68
|
-
router.destroy();
|
|
38
|
+
if (app) {
|
|
39
|
+
app.unmount();
|
|
69
40
|
}
|
|
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
|
-
});
|
|
41
|
+
document.body.removeChild(mountPoint);
|
|
148
42
|
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
});
|
|
43
|
+
// Clean up router
|
|
44
|
+
router.destroy();
|
|
187
45
|
});
|
|
188
46
|
|
|
189
|
-
describe('
|
|
190
|
-
it('should provide router
|
|
191
|
-
let
|
|
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);
|
|
47
|
+
describe('Router and Route Access', () => {
|
|
48
|
+
it('should provide router and route access', async () => {
|
|
49
|
+
let routerResult: Router | undefined;
|
|
50
|
+
let routeResult: Route | undefined;
|
|
205
51
|
|
|
206
|
-
|
|
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({
|
|
52
|
+
const TestApp = {
|
|
213
53
|
setup() {
|
|
214
54
|
useProvideRouter(router);
|
|
215
|
-
|
|
55
|
+
routerResult = useRouter();
|
|
56
|
+
routeResult = useRoute();
|
|
57
|
+
return () => h('div', 'Test App');
|
|
216
58
|
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const vm = app.mount(testContainer);
|
|
220
|
-
currentRoute = getRoute(vm);
|
|
59
|
+
};
|
|
221
60
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
expect(currentRoute!.meta.title).toBe('Home');
|
|
61
|
+
app = createApp(TestApp);
|
|
62
|
+
app.mount('#app');
|
|
225
63
|
|
|
226
|
-
|
|
64
|
+
// Check retrieved objects
|
|
65
|
+
expect(routerResult).toEqual(router);
|
|
66
|
+
expect(routeResult).toBeDefined();
|
|
67
|
+
expect(routeResult?.path).toBe('/initial');
|
|
227
68
|
});
|
|
228
69
|
});
|
|
229
70
|
|
|
230
|
-
describe('
|
|
231
|
-
it('should
|
|
232
|
-
let
|
|
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
|
-
});
|
|
71
|
+
describe('Route Reactivity', () => {
|
|
72
|
+
it('should update route properties when route changes', async () => {
|
|
73
|
+
let routeRef: Route | undefined;
|
|
249
74
|
|
|
250
|
-
|
|
251
|
-
const ParentComponent = defineComponent({
|
|
252
|
-
name: 'ParentComponent',
|
|
253
|
-
components: { ChildComponent },
|
|
75
|
+
const TestApp = {
|
|
254
76
|
setup() {
|
|
255
77
|
useProvideRouter(router);
|
|
256
|
-
|
|
78
|
+
routeRef = useRoute();
|
|
79
|
+
return () => h('div', routeRef?.path);
|
|
257
80
|
}
|
|
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;
|
|
81
|
+
};
|
|
274
82
|
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
});
|
|
83
|
+
app = createApp(TestApp);
|
|
84
|
+
app.mount('#app');
|
|
284
85
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
components: { DeepChildComponent },
|
|
288
|
-
setup() {
|
|
289
|
-
return () => h(DeepChildComponent);
|
|
290
|
-
}
|
|
291
|
-
});
|
|
86
|
+
// Initial state
|
|
87
|
+
expect(routeRef?.path).toBe('/initial');
|
|
292
88
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
components: { MiddleComponent },
|
|
296
|
-
setup() {
|
|
297
|
-
useProvideRouter(router);
|
|
298
|
-
return () => h(MiddleComponent);
|
|
299
|
-
}
|
|
300
|
-
});
|
|
89
|
+
// Save reference to check identity
|
|
90
|
+
const initialRouteRef = routeRef;
|
|
301
91
|
|
|
302
|
-
|
|
303
|
-
|
|
92
|
+
// Navigate to new route
|
|
93
|
+
await router.replace('/new-route');
|
|
304
94
|
await nextTick();
|
|
305
95
|
|
|
306
|
-
|
|
307
|
-
|
|
96
|
+
// Check that reference is preserved but properties are updated
|
|
97
|
+
expect(routeRef).toBe(initialRouteRef);
|
|
98
|
+
expect(routeRef?.path).toBe('/new-route');
|
|
308
99
|
});
|
|
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
100
|
|
|
378
|
-
|
|
379
|
-
|
|
101
|
+
it('should update route params when route changes', async () => {
|
|
102
|
+
let routeRef: Route | undefined;
|
|
380
103
|
|
|
381
|
-
|
|
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({
|
|
104
|
+
const TestApp = {
|
|
427
105
|
setup() {
|
|
428
106
|
useProvideRouter(router);
|
|
429
|
-
|
|
107
|
+
routeRef = useRoute();
|
|
108
|
+
return () =>
|
|
109
|
+
h('div', [
|
|
110
|
+
h('span', routeRef?.path),
|
|
111
|
+
h('span', routeRef?.params?.id || 'no-id')
|
|
112
|
+
]);
|
|
430
113
|
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
app.mount(testContainer);
|
|
434
|
-
|
|
435
|
-
// Initial route
|
|
436
|
-
expect(router.route.path).toBe('/');
|
|
437
|
-
expect(router.route.meta.title).toBe('Home');
|
|
114
|
+
};
|
|
438
115
|
|
|
439
|
-
|
|
440
|
-
|
|
116
|
+
app = createApp(TestApp);
|
|
117
|
+
app.mount('#app');
|
|
441
118
|
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
|
|
119
|
+
// Navigate to route with params
|
|
120
|
+
await router.replace('/user/123');
|
|
121
|
+
await nextTick();
|
|
445
122
|
|
|
446
|
-
|
|
123
|
+
// Check if params are updated
|
|
124
|
+
expect(routeRef?.path).toBe('/user/123');
|
|
125
|
+
expect(routeRef?.params?.id).toBe('123');
|
|
447
126
|
});
|
|
448
|
-
});
|
|
449
127
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
let
|
|
454
|
-
let linkResolver: ReturnType<typeof useLink> | null = null;
|
|
128
|
+
it('should automatically update view when route changes', async () => {
|
|
129
|
+
// Track render count
|
|
130
|
+
const renderCount = { value: 0 };
|
|
131
|
+
let routeRef: Route | undefined;
|
|
455
132
|
|
|
456
|
-
const
|
|
133
|
+
const TestApp = {
|
|
457
134
|
setup() {
|
|
458
135
|
useProvideRouter(router);
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
type: 'push',
|
|
465
|
-
exact: 'include'
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
return () => '<div>Test Component</div>';
|
|
136
|
+
routeRef = useRoute();
|
|
137
|
+
return () => {
|
|
138
|
+
renderCount.value++;
|
|
139
|
+
return h('div', routeRef?.path);
|
|
140
|
+
};
|
|
469
141
|
}
|
|
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
|
-
});
|
|
142
|
+
};
|
|
495
143
|
|
|
496
|
-
|
|
497
|
-
|
|
144
|
+
app = createApp(TestApp);
|
|
145
|
+
app.mount('#app');
|
|
498
146
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const route = useRoute();
|
|
503
|
-
routeRef = route;
|
|
504
|
-
return () => `<div>Current: ${route.path}</div>`;
|
|
505
|
-
}
|
|
506
|
-
});
|
|
147
|
+
// Initial render
|
|
148
|
+
const initialRenderCount = renderCount.value;
|
|
149
|
+
expect(routeRef?.path).toBe('/initial');
|
|
507
150
|
|
|
508
|
-
|
|
509
|
-
|
|
151
|
+
// Navigate to new route
|
|
152
|
+
await router.replace('/new-route');
|
|
510
153
|
await nextTick();
|
|
511
154
|
|
|
512
|
-
//
|
|
513
|
-
expect(
|
|
514
|
-
expect(routeRef
|
|
155
|
+
// Check if render count increased, confirming view update
|
|
156
|
+
expect(renderCount.value).toBeGreaterThan(initialRenderCount);
|
|
157
|
+
expect(routeRef?.path).toBe('/new-route');
|
|
515
158
|
|
|
516
|
-
// Navigate
|
|
517
|
-
|
|
159
|
+
// Navigate to another route
|
|
160
|
+
const previousRenderCount = renderCount.value;
|
|
161
|
+
await router.replace('/new-path');
|
|
518
162
|
await nextTick();
|
|
519
163
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
164
|
+
// Check if render count increased again
|
|
165
|
+
expect(renderCount.value).toBeGreaterThan(previousRenderCount);
|
|
166
|
+
expect(routeRef?.path).toBe('/new-path');
|
|
523
167
|
});
|
|
524
168
|
});
|
|
525
169
|
|
|
526
|
-
describe('
|
|
527
|
-
it('should
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
let childRouterResult: Router | null = null;
|
|
170
|
+
describe('Nested Components', () => {
|
|
171
|
+
it('should provide route context to child components', async () => {
|
|
172
|
+
let parentRoute: Route | undefined;
|
|
173
|
+
let childRoute: Route | undefined;
|
|
532
174
|
|
|
533
|
-
const ChildComponent =
|
|
534
|
-
name: 'ChildComponent',
|
|
175
|
+
const ChildComponent = {
|
|
535
176
|
setup() {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
// Second check: grandparent (has context) -> found!
|
|
539
|
-
return () => h('div', 'Deep Child');
|
|
177
|
+
childRoute = useRoute();
|
|
178
|
+
return () => h('div', 'Child: ' + childRoute?.path);
|
|
540
179
|
}
|
|
541
|
-
}
|
|
180
|
+
};
|
|
542
181
|
|
|
543
|
-
const ParentComponent =
|
|
544
|
-
name: 'ParentComponent',
|
|
182
|
+
const ParentComponent = {
|
|
545
183
|
setup() {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
184
|
+
parentRoute = useRoute();
|
|
185
|
+
return () =>
|
|
186
|
+
h('div', [
|
|
187
|
+
h('span', 'Parent: ' + parentRoute?.path),
|
|
188
|
+
h(ChildComponent)
|
|
189
|
+
]);
|
|
549
190
|
}
|
|
550
|
-
}
|
|
191
|
+
};
|
|
551
192
|
|
|
552
|
-
const
|
|
553
|
-
name: 'GrandParentComponent',
|
|
193
|
+
const TestApp = {
|
|
554
194
|
setup() {
|
|
555
195
|
useProvideRouter(router);
|
|
556
196
|
return () => h(ParentComponent);
|
|
557
197
|
}
|
|
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
198
|
};
|
|
575
199
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
childRouterResult = getRouter(mockDeepChild as VueInstance);
|
|
200
|
+
app = createApp(TestApp);
|
|
201
|
+
app.mount('#app');
|
|
579
202
|
|
|
580
|
-
expect(
|
|
581
|
-
expect(
|
|
203
|
+
expect(parentRoute).toBeDefined();
|
|
204
|
+
expect(childRoute).toBeDefined();
|
|
205
|
+
expect(parentRoute?.path).toBe('/initial');
|
|
206
|
+
expect(childRoute?.path).toBe('/initial');
|
|
582
207
|
|
|
583
|
-
|
|
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);
|
|
208
|
+
// Navigate to new path
|
|
209
|
+
await router.replace('/new-path');
|
|
210
|
+
await nextTick();
|
|
612
211
|
|
|
613
|
-
|
|
212
|
+
// Both parent and child components should see updates
|
|
213
|
+
expect(parentRoute?.path).toBe('/new-path');
|
|
214
|
+
expect(childRoute?.path).toBe('/new-path');
|
|
614
215
|
});
|
|
615
216
|
});
|
|
616
217
|
});
|