@esmx/router-vue 3.0.0-rc.87 → 3.0.0-rc.93
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/router-view.mjs +1 -1
- package/dist/router-view.test.mjs +220 -3
- package/package.json +3 -3
- package/src/router-view.test.ts +281 -40
- package/src/router-view.ts +5 -2
package/dist/router-view.mjs
CHANGED
|
@@ -9,7 +9,7 @@ export const RouterView = defineComponent({
|
|
|
9
9
|
return () => {
|
|
10
10
|
const matchedRoute = route.matched[depth];
|
|
11
11
|
const component = matchedRoute ? resolveComponent(matchedRoute.component) : null;
|
|
12
|
-
return component ? h(component) : null;
|
|
12
|
+
return component ? h(component, { key: matchedRoute.compilePath }) : null;
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
});
|
|
@@ -287,9 +287,7 @@ describe("router-view.ts - RouterView Component", () => {
|
|
|
287
287
|
return () => h("div", [
|
|
288
288
|
h("span", "App"),
|
|
289
289
|
h(RouterView),
|
|
290
|
-
// This renders Home component at depth 0
|
|
291
290
|
h(DeepRouterView)
|
|
292
|
-
// This tries to render at depth 1, but no match
|
|
293
291
|
]);
|
|
294
292
|
}
|
|
295
293
|
});
|
|
@@ -342,7 +340,6 @@ describe("router-view.ts - RouterView Component", () => {
|
|
|
342
340
|
{
|
|
343
341
|
path: "/",
|
|
344
342
|
component: null
|
|
345
|
-
// Initial route with null component
|
|
346
343
|
}
|
|
347
344
|
],
|
|
348
345
|
mode: RouterMode.memory,
|
|
@@ -456,4 +453,224 @@ describe("router-view.ts - RouterView Component", () => {
|
|
|
456
453
|
app.unmount();
|
|
457
454
|
});
|
|
458
455
|
});
|
|
456
|
+
describe("compilePath as Key", () => {
|
|
457
|
+
it("should use compilePath as key for component rendering", async () => {
|
|
458
|
+
let mountCount = 0;
|
|
459
|
+
const TrackedComponent = defineComponent({
|
|
460
|
+
name: "TrackedComponent",
|
|
461
|
+
setup() {
|
|
462
|
+
mountCount++;
|
|
463
|
+
return () => h(
|
|
464
|
+
"div",
|
|
465
|
+
{ class: "tracked" },
|
|
466
|
+
"Mounted ".concat(mountCount, " times")
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
const routes = [
|
|
471
|
+
{
|
|
472
|
+
path: "/route1",
|
|
473
|
+
component: TrackedComponent
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
path: "/route2",
|
|
477
|
+
component: TrackedComponent
|
|
478
|
+
}
|
|
479
|
+
];
|
|
480
|
+
const testRouter = new Router({
|
|
481
|
+
root: "#test-app",
|
|
482
|
+
routes,
|
|
483
|
+
mode: RouterMode.memory,
|
|
484
|
+
base: new URL("http://localhost:8000/")
|
|
485
|
+
});
|
|
486
|
+
await testRouter.replace("/route1");
|
|
487
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
488
|
+
const TestApp = defineComponent({
|
|
489
|
+
setup() {
|
|
490
|
+
useProvideRouter(testRouter);
|
|
491
|
+
return () => h("div", [h(RouterView)]);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
const app = createApp(TestApp);
|
|
495
|
+
app.mount(testContainer);
|
|
496
|
+
await nextTick();
|
|
497
|
+
expect(mountCount).toBe(1);
|
|
498
|
+
expect(testContainer.textContent).toContain("Mounted 1 times");
|
|
499
|
+
await testRouter.push("/route2");
|
|
500
|
+
await nextTick();
|
|
501
|
+
expect(mountCount).toBe(2);
|
|
502
|
+
expect(testContainer.textContent).toContain("Mounted 2 times");
|
|
503
|
+
await testRouter.push("/route1");
|
|
504
|
+
await nextTick();
|
|
505
|
+
expect(mountCount).toBe(3);
|
|
506
|
+
expect(testContainer.textContent).toContain("Mounted 3 times");
|
|
507
|
+
app.unmount();
|
|
508
|
+
testRouter.destroy();
|
|
509
|
+
});
|
|
510
|
+
it("should force re-render when compilePath changes for same route", async () => {
|
|
511
|
+
let mountCount = 0;
|
|
512
|
+
const TrackedComponent = defineComponent({
|
|
513
|
+
name: "TrackedComponent",
|
|
514
|
+
setup() {
|
|
515
|
+
mountCount++;
|
|
516
|
+
return () => h("div", { class: "tracked" }, "Mount #".concat(mountCount));
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
const routes = [
|
|
520
|
+
{
|
|
521
|
+
path: "/test",
|
|
522
|
+
component: TrackedComponent
|
|
523
|
+
}
|
|
524
|
+
];
|
|
525
|
+
const testRouter = new Router({
|
|
526
|
+
root: "#test-app",
|
|
527
|
+
routes,
|
|
528
|
+
mode: RouterMode.memory,
|
|
529
|
+
base: new URL("http://localhost:8000/")
|
|
530
|
+
});
|
|
531
|
+
await testRouter.replace("/test");
|
|
532
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
533
|
+
const TestApp = defineComponent({
|
|
534
|
+
setup() {
|
|
535
|
+
useProvideRouter(testRouter);
|
|
536
|
+
return () => h("div", [h(RouterView)]);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
const app = createApp(TestApp);
|
|
540
|
+
app.mount(testContainer);
|
|
541
|
+
await nextTick();
|
|
542
|
+
expect(mountCount).toBe(1);
|
|
543
|
+
expect(testContainer.textContent).toContain("Mount #1");
|
|
544
|
+
const newRoutes = [
|
|
545
|
+
{
|
|
546
|
+
path: "/test",
|
|
547
|
+
component: TrackedComponent,
|
|
548
|
+
meta: { updated: true }
|
|
549
|
+
}
|
|
550
|
+
];
|
|
551
|
+
const newRouter = new Router({
|
|
552
|
+
root: "#test-app",
|
|
553
|
+
routes: newRoutes,
|
|
554
|
+
mode: RouterMode.memory,
|
|
555
|
+
base: new URL("http://localhost:8000/")
|
|
556
|
+
});
|
|
557
|
+
await newRouter.replace("/test");
|
|
558
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
559
|
+
app.unmount();
|
|
560
|
+
const NewTestApp = defineComponent({
|
|
561
|
+
setup() {
|
|
562
|
+
useProvideRouter(newRouter);
|
|
563
|
+
return () => h("div", [h(RouterView)]);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
const newApp = createApp(NewTestApp);
|
|
567
|
+
newApp.mount(testContainer);
|
|
568
|
+
await nextTick();
|
|
569
|
+
expect(mountCount).toBe(2);
|
|
570
|
+
expect(testContainer.textContent).toContain("Mount #2");
|
|
571
|
+
newApp.unmount();
|
|
572
|
+
newRouter.destroy();
|
|
573
|
+
});
|
|
574
|
+
it("should handle same component with same compilePath without unnecessary re-renders", async () => {
|
|
575
|
+
let mountCount = 0;
|
|
576
|
+
const TrackedComponent = defineComponent({
|
|
577
|
+
name: "TrackedComponent",
|
|
578
|
+
setup() {
|
|
579
|
+
mountCount++;
|
|
580
|
+
return () => h("div", { class: "tracked" }, "Component");
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
const routes = [
|
|
584
|
+
{
|
|
585
|
+
path: "/test",
|
|
586
|
+
component: TrackedComponent
|
|
587
|
+
}
|
|
588
|
+
];
|
|
589
|
+
const testRouter = new Router({
|
|
590
|
+
root: "#test-app",
|
|
591
|
+
routes,
|
|
592
|
+
mode: RouterMode.memory,
|
|
593
|
+
base: new URL("http://localhost:8000/")
|
|
594
|
+
});
|
|
595
|
+
await testRouter.replace("/test");
|
|
596
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
597
|
+
const TestApp = defineComponent({
|
|
598
|
+
setup() {
|
|
599
|
+
useProvideRouter(testRouter);
|
|
600
|
+
return () => h("div", [h(RouterView)]);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
const app = createApp(TestApp);
|
|
604
|
+
app.mount(testContainer);
|
|
605
|
+
await nextTick();
|
|
606
|
+
expect(mountCount).toBe(1);
|
|
607
|
+
await testRouter.push("/");
|
|
608
|
+
await nextTick();
|
|
609
|
+
await testRouter.push("/test");
|
|
610
|
+
await nextTick();
|
|
611
|
+
expect(mountCount).toBe(1);
|
|
612
|
+
app.unmount();
|
|
613
|
+
testRouter.destroy();
|
|
614
|
+
});
|
|
615
|
+
it("should work with nested routes and compilePath keys", async () => {
|
|
616
|
+
let parentMountCount = 0;
|
|
617
|
+
let childMountCount = 0;
|
|
618
|
+
const ParentComponent = defineComponent({
|
|
619
|
+
name: "ParentComponent",
|
|
620
|
+
setup() {
|
|
621
|
+
parentMountCount++;
|
|
622
|
+
return () => h("div", [
|
|
623
|
+
h("h1", "Parent Mount #".concat(parentMountCount)),
|
|
624
|
+
h(RouterView)
|
|
625
|
+
]);
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
const ChildComponent = defineComponent({
|
|
629
|
+
name: "ChildComponent",
|
|
630
|
+
setup() {
|
|
631
|
+
childMountCount++;
|
|
632
|
+
return () => h(
|
|
633
|
+
"div",
|
|
634
|
+
{ class: "child" },
|
|
635
|
+
"Child Mount #".concat(childMountCount)
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
const nestedRoutes = [
|
|
640
|
+
{
|
|
641
|
+
path: "/parent",
|
|
642
|
+
component: ParentComponent,
|
|
643
|
+
children: [
|
|
644
|
+
{
|
|
645
|
+
path: "child",
|
|
646
|
+
component: ChildComponent
|
|
647
|
+
}
|
|
648
|
+
]
|
|
649
|
+
}
|
|
650
|
+
];
|
|
651
|
+
const nestedRouter = new Router({
|
|
652
|
+
root: "#test-app",
|
|
653
|
+
routes: nestedRoutes,
|
|
654
|
+
mode: RouterMode.memory,
|
|
655
|
+
base: new URL("http://localhost:8000/")
|
|
656
|
+
});
|
|
657
|
+
await nestedRouter.replace("/parent/child");
|
|
658
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
659
|
+
const TestApp = defineComponent({
|
|
660
|
+
setup() {
|
|
661
|
+
useProvideRouter(nestedRouter);
|
|
662
|
+
return () => h("div", [h(RouterView)]);
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
const app = createApp(TestApp);
|
|
666
|
+
app.mount(testContainer);
|
|
667
|
+
await nextTick();
|
|
668
|
+
expect(parentMountCount).toBe(1);
|
|
669
|
+
expect(childMountCount).toBe(1);
|
|
670
|
+
expect(testContainer.textContent).toContain("Parent Mount #1");
|
|
671
|
+
expect(testContainer.textContent).toContain("Child Mount #1");
|
|
672
|
+
app.unmount();
|
|
673
|
+
nestedRouter.destroy();
|
|
674
|
+
});
|
|
675
|
+
});
|
|
459
676
|
});
|
package/package.json
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"vue": "^3.5.0 || ^2.7.0"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@esmx/router": "3.0.0-rc.
|
|
53
|
+
"@esmx/router": "3.0.0-rc.93"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@biomejs/biome": "2.3.7",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"vue": "3.5.23",
|
|
63
63
|
"vue2": "npm:vue@2.7.16"
|
|
64
64
|
},
|
|
65
|
-
"version": "3.0.0-rc.
|
|
65
|
+
"version": "3.0.0-rc.93",
|
|
66
66
|
"type": "module",
|
|
67
67
|
"private": false,
|
|
68
68
|
"exports": {
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
"template",
|
|
82
82
|
"public"
|
|
83
83
|
],
|
|
84
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "680b3b3549c50a2817d7e8ab72b3bb84d07966ce"
|
|
85
85
|
}
|
package/src/router-view.test.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { createApp, defineComponent, h, inject, nextTick, provide } from 'vue';
|
|
|
7
7
|
import { RouterView } from './router-view';
|
|
8
8
|
import { useProvideRouter } from './use';
|
|
9
9
|
|
|
10
|
-
// Mock components for testing
|
|
11
10
|
const HomeComponent = defineComponent({
|
|
12
11
|
name: 'HomeComponent',
|
|
13
12
|
setup() {
|
|
@@ -29,7 +28,6 @@ const UserComponent = defineComponent({
|
|
|
29
28
|
}
|
|
30
29
|
});
|
|
31
30
|
|
|
32
|
-
// ES Module component for testing resolveComponent
|
|
33
31
|
const ESModuleComponent = {
|
|
34
32
|
__esModule: true,
|
|
35
33
|
default: defineComponent({
|
|
@@ -46,12 +44,10 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
46
44
|
let testContainer: HTMLElement;
|
|
47
45
|
|
|
48
46
|
beforeEach(async () => {
|
|
49
|
-
// Create test container
|
|
50
47
|
testContainer = document.createElement('div');
|
|
51
48
|
testContainer.id = 'test-app';
|
|
52
49
|
document.body.appendChild(testContainer);
|
|
53
50
|
|
|
54
|
-
// Create test routes
|
|
55
51
|
const routes: RouteConfig[] = [
|
|
56
52
|
{
|
|
57
53
|
path: '/',
|
|
@@ -75,7 +71,6 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
75
71
|
}
|
|
76
72
|
];
|
|
77
73
|
|
|
78
|
-
// Create router instance
|
|
79
74
|
router = new Router({
|
|
80
75
|
root: '#test-app',
|
|
81
76
|
routes,
|
|
@@ -83,19 +78,15 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
83
78
|
base: new URL('http://localhost:8000/')
|
|
84
79
|
});
|
|
85
80
|
|
|
86
|
-
// Initialize router to root path and wait for it to be ready
|
|
87
81
|
await router.replace('/');
|
|
88
|
-
// Wait for route to be fully initialized
|
|
89
82
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
90
83
|
});
|
|
91
84
|
|
|
92
85
|
afterEach(() => {
|
|
93
|
-
// Clean up test environment
|
|
94
86
|
if (testContainer.parentNode) {
|
|
95
87
|
testContainer.parentNode.removeChild(testContainer);
|
|
96
88
|
}
|
|
97
89
|
|
|
98
|
-
// Destroy router
|
|
99
90
|
if (router) {
|
|
100
91
|
router.destroy();
|
|
101
92
|
}
|
|
@@ -114,7 +105,6 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
114
105
|
app.mount(testContainer);
|
|
115
106
|
await nextTick();
|
|
116
107
|
|
|
117
|
-
// Check if HomeComponent is rendered
|
|
118
108
|
expect(testContainer.textContent).toContain('Home Page');
|
|
119
109
|
|
|
120
110
|
app.unmount();
|
|
@@ -132,10 +122,8 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
132
122
|
app.mount(testContainer);
|
|
133
123
|
await nextTick();
|
|
134
124
|
|
|
135
|
-
// Initially should show Home
|
|
136
125
|
expect(testContainer.textContent).toContain('Home Page');
|
|
137
126
|
|
|
138
|
-
// Navigate to About
|
|
139
127
|
await router.push('/about');
|
|
140
128
|
await nextTick();
|
|
141
129
|
|
|
@@ -155,7 +143,6 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
155
143
|
const app = createApp(TestApp);
|
|
156
144
|
app.mount(testContainer);
|
|
157
145
|
|
|
158
|
-
// Navigate to user route with parameter
|
|
159
146
|
await router.push('/users/123');
|
|
160
147
|
await nextTick();
|
|
161
148
|
|
|
@@ -177,7 +164,6 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
177
164
|
const app = createApp(TestApp);
|
|
178
165
|
app.mount(testContainer);
|
|
179
166
|
|
|
180
|
-
// Navigate to ES module route
|
|
181
167
|
await router.push('/es-module');
|
|
182
168
|
await nextTick();
|
|
183
169
|
|
|
@@ -204,7 +190,6 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
204
190
|
base: new URL('http://localhost:8000/')
|
|
205
191
|
});
|
|
206
192
|
|
|
207
|
-
// Initialize the router and wait for it to be ready
|
|
208
193
|
await functionRouter.replace('/function');
|
|
209
194
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
210
195
|
|
|
@@ -230,10 +215,8 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
230
215
|
it('should inject depth 0 by default', async () => {
|
|
231
216
|
let injectedDepth: number | undefined;
|
|
232
217
|
|
|
233
|
-
// Use the same symbol key that RouterView uses internally
|
|
234
218
|
const RouterViewDepth = Symbol('RouterViewDepth');
|
|
235
219
|
|
|
236
|
-
// Create a custom RouterView component that can capture the injected depth
|
|
237
220
|
const TestRouterView = defineComponent({
|
|
238
221
|
name: 'TestRouterView',
|
|
239
222
|
setup() {
|
|
@@ -253,8 +236,7 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
253
236
|
app.mount(testContainer);
|
|
254
237
|
await nextTick();
|
|
255
238
|
|
|
256
|
-
|
|
257
|
-
expect(injectedDepth).toBe(-1); // Default value since no parent RouterView provides depth
|
|
239
|
+
expect(injectedDepth).toBe(-1);
|
|
258
240
|
|
|
259
241
|
app.unmount();
|
|
260
242
|
});
|
|
@@ -269,7 +251,7 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
269
251
|
name: 'ParentTestComponent',
|
|
270
252
|
setup() {
|
|
271
253
|
parentDepth = inject(RouterViewDepth, -1);
|
|
272
|
-
provide(RouterViewDepth, 0);
|
|
254
|
+
provide(RouterViewDepth, 0);
|
|
273
255
|
return () =>
|
|
274
256
|
h('div', [h('span', 'Parent'), h(ChildTestComponent)]);
|
|
275
257
|
}
|
|
@@ -294,9 +276,8 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
294
276
|
app.mount(testContainer);
|
|
295
277
|
await nextTick();
|
|
296
278
|
|
|
297
|
-
|
|
298
|
-
expect(
|
|
299
|
-
expect(childDepth).toBe(0); // Value provided by parent
|
|
279
|
+
expect(parentDepth).toBe(-1);
|
|
280
|
+
expect(childDepth).toBe(0);
|
|
300
281
|
|
|
301
282
|
app.unmount();
|
|
302
283
|
});
|
|
@@ -337,7 +318,6 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
337
318
|
base: new URL('http://localhost:8000/')
|
|
338
319
|
});
|
|
339
320
|
|
|
340
|
-
// Initialize the router and wait for it to be ready
|
|
341
321
|
await nestedRouter.replace('/level1/level2');
|
|
342
322
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
343
323
|
|
|
@@ -367,7 +347,6 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
367
347
|
const DeepRouterView = defineComponent({
|
|
368
348
|
name: 'DeepRouterView',
|
|
369
349
|
setup() {
|
|
370
|
-
// Inject depth 0 from parent RouterView and provide depth 1
|
|
371
350
|
const currentDepth = inject(RouterViewDepth, 0);
|
|
372
351
|
provide(RouterViewDepth, currentDepth + 1);
|
|
373
352
|
return () => h(RouterView);
|
|
@@ -380,8 +359,8 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
380
359
|
return () =>
|
|
381
360
|
h('div', [
|
|
382
361
|
h('span', 'App'),
|
|
383
|
-
h(RouterView),
|
|
384
|
-
h(DeepRouterView)
|
|
362
|
+
h(RouterView),
|
|
363
|
+
h(DeepRouterView)
|
|
385
364
|
]);
|
|
386
365
|
}
|
|
387
366
|
});
|
|
@@ -390,8 +369,6 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
390
369
|
app.mount(testContainer);
|
|
391
370
|
await nextTick();
|
|
392
371
|
|
|
393
|
-
// Should contain "App" and "Home Page" from the first RouterView
|
|
394
|
-
// but no additional content from the deep RouterView
|
|
395
372
|
expect(testContainer.textContent).toContain('App');
|
|
396
373
|
expect(testContainer.textContent).toContain('Home Page');
|
|
397
374
|
|
|
@@ -414,7 +391,6 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
414
391
|
base: new URL('http://localhost:8000/')
|
|
415
392
|
});
|
|
416
393
|
|
|
417
|
-
// Initialize the router and wait for it to be ready
|
|
418
394
|
await nullRouter.replace('/null-component');
|
|
419
395
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
420
396
|
|
|
@@ -429,9 +405,8 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
429
405
|
app.mount(testContainer);
|
|
430
406
|
await nextTick();
|
|
431
407
|
|
|
432
|
-
// Verify that only the "App" text is rendered and RouterView renders nothing
|
|
433
408
|
expect(testContainer.textContent?.trim()).toBe('App');
|
|
434
|
-
expect(testContainer.querySelector('div')?.children.length).toBe(1);
|
|
409
|
+
expect(testContainer.querySelector('div')?.children.length).toBe(1);
|
|
435
410
|
expect(testContainer.querySelector('span')?.textContent).toBe(
|
|
436
411
|
'App'
|
|
437
412
|
);
|
|
@@ -441,20 +416,18 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
441
416
|
});
|
|
442
417
|
|
|
443
418
|
it('should handle non-existent routes', async () => {
|
|
444
|
-
// Create a new router instance with a valid initial route
|
|
445
419
|
const nonExistentRouter = new Router({
|
|
446
420
|
root: '#test-app',
|
|
447
421
|
routes: [
|
|
448
422
|
{
|
|
449
423
|
path: '/',
|
|
450
|
-
component: null
|
|
424
|
+
component: null
|
|
451
425
|
}
|
|
452
426
|
],
|
|
453
427
|
mode: RouterMode.memory,
|
|
454
428
|
base: new URL('http://localhost:8000/')
|
|
455
429
|
});
|
|
456
430
|
|
|
457
|
-
// Initialize router with root path
|
|
458
431
|
await nonExistentRouter.replace('/');
|
|
459
432
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
460
433
|
|
|
@@ -468,16 +441,13 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
468
441
|
const app = createApp(TestApp);
|
|
469
442
|
app.mount(testContainer);
|
|
470
443
|
|
|
471
|
-
// Navigate to non-existent route
|
|
472
444
|
await nonExistentRouter.push('/non-existent');
|
|
473
445
|
await nextTick();
|
|
474
446
|
|
|
475
|
-
// Wait for any pending route changes
|
|
476
447
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
477
448
|
|
|
478
|
-
// Verify that only the "App" text is rendered and RouterView renders nothing
|
|
479
449
|
expect(testContainer.textContent?.trim()).toBe('App');
|
|
480
|
-
expect(testContainer.querySelector('div')?.children.length).toBe(1);
|
|
450
|
+
expect(testContainer.querySelector('div')?.children.length).toBe(1);
|
|
481
451
|
expect(testContainer.querySelector('span')?.textContent).toBe(
|
|
482
452
|
'App'
|
|
483
453
|
);
|
|
@@ -507,7 +477,6 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
507
477
|
base: new URL('http://localhost:8000/')
|
|
508
478
|
});
|
|
509
479
|
|
|
510
|
-
// Initialize the router and wait for it to be ready
|
|
511
480
|
await malformedRouter.replace('/malformed');
|
|
512
481
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
513
482
|
|
|
@@ -596,4 +565,276 @@ describe('router-view.ts - RouterView Component', () => {
|
|
|
596
565
|
app.unmount();
|
|
597
566
|
});
|
|
598
567
|
});
|
|
568
|
+
|
|
569
|
+
describe('compilePath as Key', () => {
|
|
570
|
+
it('should use compilePath as key for component rendering', async () => {
|
|
571
|
+
let mountCount = 0;
|
|
572
|
+
const TrackedComponent = defineComponent({
|
|
573
|
+
name: 'TrackedComponent',
|
|
574
|
+
setup() {
|
|
575
|
+
mountCount++;
|
|
576
|
+
return () =>
|
|
577
|
+
h(
|
|
578
|
+
'div',
|
|
579
|
+
{ class: 'tracked' },
|
|
580
|
+
`Mounted ${mountCount} times`
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
const routes: RouteConfig[] = [
|
|
586
|
+
{
|
|
587
|
+
path: '/route1',
|
|
588
|
+
component: TrackedComponent
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
path: '/route2',
|
|
592
|
+
component: TrackedComponent
|
|
593
|
+
}
|
|
594
|
+
];
|
|
595
|
+
|
|
596
|
+
const testRouter = new Router({
|
|
597
|
+
root: '#test-app',
|
|
598
|
+
routes,
|
|
599
|
+
mode: RouterMode.memory,
|
|
600
|
+
base: new URL('http://localhost:8000/')
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
await testRouter.replace('/route1');
|
|
604
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
605
|
+
|
|
606
|
+
const TestApp = defineComponent({
|
|
607
|
+
setup() {
|
|
608
|
+
useProvideRouter(testRouter);
|
|
609
|
+
return () => h('div', [h(RouterView)]);
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
const app = createApp(TestApp);
|
|
614
|
+
app.mount(testContainer);
|
|
615
|
+
await nextTick();
|
|
616
|
+
|
|
617
|
+
expect(mountCount).toBe(1);
|
|
618
|
+
expect(testContainer.textContent).toContain('Mounted 1 times');
|
|
619
|
+
|
|
620
|
+
await testRouter.push('/route2');
|
|
621
|
+
await nextTick();
|
|
622
|
+
|
|
623
|
+
expect(mountCount).toBe(2);
|
|
624
|
+
expect(testContainer.textContent).toContain('Mounted 2 times');
|
|
625
|
+
|
|
626
|
+
await testRouter.push('/route1');
|
|
627
|
+
await nextTick();
|
|
628
|
+
|
|
629
|
+
expect(mountCount).toBe(3);
|
|
630
|
+
expect(testContainer.textContent).toContain('Mounted 3 times');
|
|
631
|
+
|
|
632
|
+
app.unmount();
|
|
633
|
+
testRouter.destroy();
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
it('should force re-render when compilePath changes for same route', async () => {
|
|
637
|
+
let mountCount = 0;
|
|
638
|
+
const TrackedComponent = defineComponent({
|
|
639
|
+
name: 'TrackedComponent',
|
|
640
|
+
setup() {
|
|
641
|
+
mountCount++;
|
|
642
|
+
return () =>
|
|
643
|
+
h('div', { class: 'tracked' }, `Mount #${mountCount}`);
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
const routes: RouteConfig[] = [
|
|
648
|
+
{
|
|
649
|
+
path: '/test',
|
|
650
|
+
component: TrackedComponent
|
|
651
|
+
}
|
|
652
|
+
];
|
|
653
|
+
|
|
654
|
+
const testRouter = new Router({
|
|
655
|
+
root: '#test-app',
|
|
656
|
+
routes,
|
|
657
|
+
mode: RouterMode.memory,
|
|
658
|
+
base: new URL('http://localhost:8000/')
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
await testRouter.replace('/test');
|
|
662
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
663
|
+
|
|
664
|
+
const TestApp = defineComponent({
|
|
665
|
+
setup() {
|
|
666
|
+
useProvideRouter(testRouter);
|
|
667
|
+
return () => h('div', [h(RouterView)]);
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
const app = createApp(TestApp);
|
|
672
|
+
app.mount(testContainer);
|
|
673
|
+
await nextTick();
|
|
674
|
+
|
|
675
|
+
expect(mountCount).toBe(1);
|
|
676
|
+
expect(testContainer.textContent).toContain('Mount #1');
|
|
677
|
+
|
|
678
|
+
const newRoutes: RouteConfig[] = [
|
|
679
|
+
{
|
|
680
|
+
path: '/test',
|
|
681
|
+
component: TrackedComponent,
|
|
682
|
+
meta: { updated: true }
|
|
683
|
+
}
|
|
684
|
+
];
|
|
685
|
+
|
|
686
|
+
const newRouter = new Router({
|
|
687
|
+
root: '#test-app',
|
|
688
|
+
routes: newRoutes,
|
|
689
|
+
mode: RouterMode.memory,
|
|
690
|
+
base: new URL('http://localhost:8000/')
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
await newRouter.replace('/test');
|
|
694
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
695
|
+
|
|
696
|
+
app.unmount();
|
|
697
|
+
|
|
698
|
+
const NewTestApp = defineComponent({
|
|
699
|
+
setup() {
|
|
700
|
+
useProvideRouter(newRouter);
|
|
701
|
+
return () => h('div', [h(RouterView)]);
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
const newApp = createApp(NewTestApp);
|
|
706
|
+
newApp.mount(testContainer);
|
|
707
|
+
await nextTick();
|
|
708
|
+
|
|
709
|
+
expect(mountCount).toBe(2);
|
|
710
|
+
expect(testContainer.textContent).toContain('Mount #2');
|
|
711
|
+
|
|
712
|
+
newApp.unmount();
|
|
713
|
+
newRouter.destroy();
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
it('should handle same component with same compilePath without unnecessary re-renders', async () => {
|
|
717
|
+
let mountCount = 0;
|
|
718
|
+
const TrackedComponent = defineComponent({
|
|
719
|
+
name: 'TrackedComponent',
|
|
720
|
+
setup() {
|
|
721
|
+
mountCount++;
|
|
722
|
+
return () => h('div', { class: 'tracked' }, `Component`);
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
const routes: RouteConfig[] = [
|
|
727
|
+
{
|
|
728
|
+
path: '/test',
|
|
729
|
+
component: TrackedComponent
|
|
730
|
+
}
|
|
731
|
+
];
|
|
732
|
+
|
|
733
|
+
const testRouter = new Router({
|
|
734
|
+
root: '#test-app',
|
|
735
|
+
routes,
|
|
736
|
+
mode: RouterMode.memory,
|
|
737
|
+
base: new URL('http://localhost:8000/')
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
await testRouter.replace('/test');
|
|
741
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
742
|
+
|
|
743
|
+
const TestApp = defineComponent({
|
|
744
|
+
setup() {
|
|
745
|
+
useProvideRouter(testRouter);
|
|
746
|
+
return () => h('div', [h(RouterView)]);
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
const app = createApp(TestApp);
|
|
751
|
+
app.mount(testContainer);
|
|
752
|
+
await nextTick();
|
|
753
|
+
|
|
754
|
+
expect(mountCount).toBe(1);
|
|
755
|
+
|
|
756
|
+
await testRouter.push('/');
|
|
757
|
+
await nextTick();
|
|
758
|
+
await testRouter.push('/test');
|
|
759
|
+
await nextTick();
|
|
760
|
+
|
|
761
|
+
expect(mountCount).toBe(1);
|
|
762
|
+
|
|
763
|
+
app.unmount();
|
|
764
|
+
testRouter.destroy();
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
it('should work with nested routes and compilePath keys', async () => {
|
|
768
|
+
let parentMountCount = 0;
|
|
769
|
+
let childMountCount = 0;
|
|
770
|
+
|
|
771
|
+
const ParentComponent = defineComponent({
|
|
772
|
+
name: 'ParentComponent',
|
|
773
|
+
setup() {
|
|
774
|
+
parentMountCount++;
|
|
775
|
+
return () =>
|
|
776
|
+
h('div', [
|
|
777
|
+
h('h1', `Parent Mount #${parentMountCount}`),
|
|
778
|
+
h(RouterView)
|
|
779
|
+
]);
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
const ChildComponent = defineComponent({
|
|
784
|
+
name: 'ChildComponent',
|
|
785
|
+
setup() {
|
|
786
|
+
childMountCount++;
|
|
787
|
+
return () =>
|
|
788
|
+
h(
|
|
789
|
+
'div',
|
|
790
|
+
{ class: 'child' },
|
|
791
|
+
`Child Mount #${childMountCount}`
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
const nestedRoutes: RouteConfig[] = [
|
|
797
|
+
{
|
|
798
|
+
path: '/parent',
|
|
799
|
+
component: ParentComponent,
|
|
800
|
+
children: [
|
|
801
|
+
{
|
|
802
|
+
path: 'child',
|
|
803
|
+
component: ChildComponent
|
|
804
|
+
}
|
|
805
|
+
]
|
|
806
|
+
}
|
|
807
|
+
];
|
|
808
|
+
|
|
809
|
+
const nestedRouter = new Router({
|
|
810
|
+
root: '#test-app',
|
|
811
|
+
routes: nestedRoutes,
|
|
812
|
+
mode: RouterMode.memory,
|
|
813
|
+
base: new URL('http://localhost:8000/')
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
await nestedRouter.replace('/parent/child');
|
|
817
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
818
|
+
|
|
819
|
+
const TestApp = defineComponent({
|
|
820
|
+
setup() {
|
|
821
|
+
useProvideRouter(nestedRouter);
|
|
822
|
+
return () => h('div', [h(RouterView)]);
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
const app = createApp(TestApp);
|
|
827
|
+
app.mount(testContainer);
|
|
828
|
+
await nextTick();
|
|
829
|
+
|
|
830
|
+
expect(parentMountCount).toBe(1);
|
|
831
|
+
expect(childMountCount).toBe(1);
|
|
832
|
+
|
|
833
|
+
expect(testContainer.textContent).toContain('Parent Mount #1');
|
|
834
|
+
expect(testContainer.textContent).toContain('Child Mount #1');
|
|
835
|
+
|
|
836
|
+
app.unmount();
|
|
837
|
+
nestedRouter.destroy();
|
|
838
|
+
});
|
|
839
|
+
});
|
|
599
840
|
});
|
package/src/router-view.ts
CHANGED
|
@@ -49,8 +49,11 @@ export const RouterView = defineComponent({
|
|
|
49
49
|
? resolveComponent(matchedRoute.component)
|
|
50
50
|
: null;
|
|
51
51
|
|
|
52
|
-
// Render the component
|
|
53
|
-
|
|
52
|
+
// Render the component with compilePath as key to force re-render when route config changes
|
|
53
|
+
// Using compilePath ensures component is recreated when navigating to different route configs
|
|
54
|
+
return component
|
|
55
|
+
? h(component, { key: matchedRoute.compilePath })
|
|
56
|
+
: null;
|
|
54
57
|
};
|
|
55
58
|
}
|
|
56
59
|
});
|