@esmx/router-vue 3.0.0-rc.103
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 +21 -0
- package/README.md +570 -0
- package/README.zh-CN.md +570 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.mjs +13 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.mjs +216 -0
- package/dist/plugin.d.ts +61 -0
- package/dist/plugin.mjs +41 -0
- package/dist/plugin.test.d.ts +1 -0
- package/dist/plugin.test.mjs +631 -0
- package/dist/router-link.d.ts +220 -0
- package/dist/router-link.mjs +119 -0
- package/dist/router-link.test.d.ts +1 -0
- package/dist/router-link.test.mjs +663 -0
- package/dist/router-view.d.ts +31 -0
- package/dist/router-view.mjs +15 -0
- package/dist/router-view.test.d.ts +1 -0
- package/dist/router-view.test.mjs +676 -0
- package/dist/run-with-context.test.d.ts +1 -0
- package/dist/run-with-context.test.mjs +57 -0
- package/dist/use.d.ts +260 -0
- package/dist/use.mjs +125 -0
- package/dist/use.test.d.ts +1 -0
- package/dist/use.test.mjs +381 -0
- package/dist/util.d.ts +20 -0
- package/dist/util.mjs +49 -0
- package/dist/util.test.d.ts +4 -0
- package/dist/util.test.mjs +604 -0
- package/dist/vue2.d.ts +15 -0
- package/dist/vue2.mjs +0 -0
- package/dist/vue3.d.ts +13 -0
- package/dist/vue3.mjs +0 -0
- package/package.json +85 -0
- package/src/index.test.ts +273 -0
- package/src/index.ts +15 -0
- package/src/plugin.test.ts +812 -0
- package/src/plugin.ts +107 -0
- package/src/router-link.test.ts +830 -0
- package/src/router-link.ts +172 -0
- package/src/router-view.test.ts +840 -0
- package/src/router-view.ts +59 -0
- package/src/run-with-context.test.ts +64 -0
- package/src/use.test.ts +484 -0
- package/src/use.ts +416 -0
- package/src/util.test.ts +760 -0
- package/src/util.ts +85 -0
- package/src/vue2.ts +18 -0
- package/src/vue3.ts +15 -0
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
import { Router, RouterMode } from "@esmx/router";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
3
|
+
import { createApp, defineComponent, h, nextTick, ref } from "vue";
|
|
4
|
+
import { RouterLink } from "./router-link.mjs";
|
|
5
|
+
import { useProvideRouter } from "./use.mjs";
|
|
6
|
+
describe("router-link.ts - RouterLink Component", () => {
|
|
7
|
+
let router;
|
|
8
|
+
let app;
|
|
9
|
+
let container;
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
container = document.createElement("div");
|
|
12
|
+
container.id = "test-app";
|
|
13
|
+
document.body.appendChild(container);
|
|
14
|
+
const routes = [
|
|
15
|
+
{
|
|
16
|
+
path: "/",
|
|
17
|
+
component: defineComponent({
|
|
18
|
+
name: "Home",
|
|
19
|
+
template: "<div>Home Page</div>"
|
|
20
|
+
}),
|
|
21
|
+
meta: { title: "Home" }
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
path: "/about",
|
|
25
|
+
component: defineComponent({
|
|
26
|
+
name: "About",
|
|
27
|
+
template: "<div>About Page</div>"
|
|
28
|
+
}),
|
|
29
|
+
meta: { title: "About" }
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
path: "/contact",
|
|
33
|
+
component: defineComponent({
|
|
34
|
+
name: "Contact",
|
|
35
|
+
template: "<div>Contact Page</div>"
|
|
36
|
+
}),
|
|
37
|
+
meta: { title: "Contact" }
|
|
38
|
+
}
|
|
39
|
+
];
|
|
40
|
+
router = new Router({
|
|
41
|
+
root: "#test-app",
|
|
42
|
+
routes,
|
|
43
|
+
mode: RouterMode.memory,
|
|
44
|
+
base: new URL("http://localhost:8000/")
|
|
45
|
+
});
|
|
46
|
+
await router.replace("/");
|
|
47
|
+
await nextTick();
|
|
48
|
+
});
|
|
49
|
+
afterEach(async () => {
|
|
50
|
+
if (app) {
|
|
51
|
+
app.unmount();
|
|
52
|
+
}
|
|
53
|
+
if (router) {
|
|
54
|
+
try {
|
|
55
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
56
|
+
router.destroy();
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (!(error instanceof Error) || !error.message.includes("RouteTaskCancelledError")) {
|
|
59
|
+
console.warn("Router destruction error:", error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (container.parentNode) {
|
|
64
|
+
container.parentNode.removeChild(container);
|
|
65
|
+
}
|
|
66
|
+
await nextTick();
|
|
67
|
+
});
|
|
68
|
+
describe("Component Definition", () => {
|
|
69
|
+
it("should have correct component name", () => {
|
|
70
|
+
expect(RouterLink.name).toBe("RouterLink");
|
|
71
|
+
});
|
|
72
|
+
it("should have properly configured props", () => {
|
|
73
|
+
const props = RouterLink.props;
|
|
74
|
+
expect(props.to).toBeDefined();
|
|
75
|
+
expect(props.to.required).toBe(true);
|
|
76
|
+
expect(props.type).toBeDefined();
|
|
77
|
+
expect(props.type.default).toBe("push");
|
|
78
|
+
expect(props.exact).toBeDefined();
|
|
79
|
+
expect(props.exact.default).toBe("include");
|
|
80
|
+
expect(props.tag).toBeDefined();
|
|
81
|
+
expect(props.tag.default).toBe("a");
|
|
82
|
+
expect(props.event).toBeDefined();
|
|
83
|
+
expect(props.event.default).toBe("click");
|
|
84
|
+
expect(props.replace).toBeDefined();
|
|
85
|
+
expect(props.replace.default).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
it("should have setup function defined", () => {
|
|
88
|
+
expect(RouterLink.setup).toBeDefined();
|
|
89
|
+
expect(typeof RouterLink.setup).toBe("function");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe("Component Rendering", () => {
|
|
93
|
+
it("should render basic router link", async () => {
|
|
94
|
+
const TestApp = defineComponent({
|
|
95
|
+
setup() {
|
|
96
|
+
useProvideRouter(router);
|
|
97
|
+
return () => h(RouterLink, { to: "/about" }, () => "About Link");
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
app = createApp(TestApp);
|
|
101
|
+
app.mount(container);
|
|
102
|
+
await nextTick();
|
|
103
|
+
const linkElement = container.querySelector("a");
|
|
104
|
+
expect(linkElement).toBeTruthy();
|
|
105
|
+
expect(linkElement == null ? void 0 : linkElement.textContent).toBe("About Link");
|
|
106
|
+
});
|
|
107
|
+
it("should render router link with custom attributes", async () => {
|
|
108
|
+
const TestApp = defineComponent({
|
|
109
|
+
setup() {
|
|
110
|
+
useProvideRouter(router);
|
|
111
|
+
return () => h(
|
|
112
|
+
RouterLink,
|
|
113
|
+
{
|
|
114
|
+
to: "/about",
|
|
115
|
+
"data-test": "custom-attr",
|
|
116
|
+
title: "Custom Title"
|
|
117
|
+
},
|
|
118
|
+
() => "Link with Attributes"
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
app = createApp(TestApp);
|
|
123
|
+
app.mount(container);
|
|
124
|
+
await nextTick();
|
|
125
|
+
const linkElement = container.querySelector("a");
|
|
126
|
+
expect(linkElement).toBeTruthy();
|
|
127
|
+
expect(linkElement == null ? void 0 : linkElement.getAttribute("data-test")).toBe("custom-attr");
|
|
128
|
+
expect(linkElement == null ? void 0 : linkElement.getAttribute("title")).toBe("Custom Title");
|
|
129
|
+
});
|
|
130
|
+
it("should render with custom tag", async () => {
|
|
131
|
+
const TestApp = defineComponent({
|
|
132
|
+
setup() {
|
|
133
|
+
useProvideRouter(router);
|
|
134
|
+
return () => h(
|
|
135
|
+
RouterLink,
|
|
136
|
+
{
|
|
137
|
+
to: "/contact",
|
|
138
|
+
tag: "button"
|
|
139
|
+
},
|
|
140
|
+
() => "Contact Button"
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
app = createApp(TestApp);
|
|
145
|
+
app.mount(container);
|
|
146
|
+
await nextTick();
|
|
147
|
+
const buttonElement = container.querySelector("button");
|
|
148
|
+
expect(buttonElement).toBeTruthy();
|
|
149
|
+
expect(buttonElement == null ? void 0 : buttonElement.textContent).toBe("Contact Button");
|
|
150
|
+
});
|
|
151
|
+
it("should render with active class when route matches", async () => {
|
|
152
|
+
await router.push("/about");
|
|
153
|
+
await nextTick();
|
|
154
|
+
const TestApp = defineComponent({
|
|
155
|
+
setup() {
|
|
156
|
+
useProvideRouter(router);
|
|
157
|
+
return () => h(
|
|
158
|
+
RouterLink,
|
|
159
|
+
{
|
|
160
|
+
to: "/about",
|
|
161
|
+
activeClass: "active-link"
|
|
162
|
+
},
|
|
163
|
+
() => "Current Page"
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
app = createApp(TestApp);
|
|
168
|
+
app.mount(container);
|
|
169
|
+
await nextTick();
|
|
170
|
+
const linkElement = container.querySelector("a");
|
|
171
|
+
expect(linkElement).toBeTruthy();
|
|
172
|
+
expect(linkElement == null ? void 0 : linkElement.classList.contains("active-link")).toBe(true);
|
|
173
|
+
});
|
|
174
|
+
it("should handle different navigation types", async () => {
|
|
175
|
+
var _a, _b;
|
|
176
|
+
const TestApp = defineComponent({
|
|
177
|
+
setup() {
|
|
178
|
+
useProvideRouter(router);
|
|
179
|
+
return () => h("div", [
|
|
180
|
+
h(
|
|
181
|
+
RouterLink,
|
|
182
|
+
{
|
|
183
|
+
to: "/about",
|
|
184
|
+
type: "push"
|
|
185
|
+
},
|
|
186
|
+
() => "Push Link"
|
|
187
|
+
),
|
|
188
|
+
h(
|
|
189
|
+
RouterLink,
|
|
190
|
+
{
|
|
191
|
+
to: "/contact",
|
|
192
|
+
type: "replace"
|
|
193
|
+
},
|
|
194
|
+
() => "Replace Link"
|
|
195
|
+
)
|
|
196
|
+
]);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
app = createApp(TestApp);
|
|
200
|
+
app.mount(container);
|
|
201
|
+
await nextTick();
|
|
202
|
+
const links = container.querySelectorAll("a");
|
|
203
|
+
expect(links).toHaveLength(2);
|
|
204
|
+
expect((_a = links[0]) == null ? void 0 : _a.textContent).toBe("Push Link");
|
|
205
|
+
expect((_b = links[1]) == null ? void 0 : _b.textContent).toBe("Replace Link");
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
describe("Navigation Functionality", () => {
|
|
209
|
+
it("should navigate when clicked", async () => {
|
|
210
|
+
const TestApp = defineComponent({
|
|
211
|
+
setup() {
|
|
212
|
+
useProvideRouter(router);
|
|
213
|
+
return () => h(
|
|
214
|
+
RouterLink,
|
|
215
|
+
{ to: "/about" },
|
|
216
|
+
() => "Navigate to About"
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
app = createApp(TestApp);
|
|
221
|
+
app.mount(container);
|
|
222
|
+
await nextTick();
|
|
223
|
+
const linkElement = container.querySelector("a");
|
|
224
|
+
expect(linkElement).toBeTruthy();
|
|
225
|
+
const clickPromise = new Promise((resolve) => {
|
|
226
|
+
router.afterEach(() => resolve());
|
|
227
|
+
});
|
|
228
|
+
linkElement == null ? void 0 : linkElement.click();
|
|
229
|
+
await clickPromise;
|
|
230
|
+
await nextTick();
|
|
231
|
+
expect(router.route.path).toBe("/about");
|
|
232
|
+
});
|
|
233
|
+
it("should handle custom events", async () => {
|
|
234
|
+
const TestApp = defineComponent({
|
|
235
|
+
setup() {
|
|
236
|
+
useProvideRouter(router);
|
|
237
|
+
return () => h(
|
|
238
|
+
RouterLink,
|
|
239
|
+
{
|
|
240
|
+
to: "/contact",
|
|
241
|
+
event: "mouseenter"
|
|
242
|
+
},
|
|
243
|
+
() => "Hover to Navigate"
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
app = createApp(TestApp);
|
|
248
|
+
app.mount(container);
|
|
249
|
+
await nextTick();
|
|
250
|
+
const linkElement = container.querySelector("a");
|
|
251
|
+
expect(linkElement).toBeTruthy();
|
|
252
|
+
const navigationPromise = new Promise((resolve) => {
|
|
253
|
+
router.afterEach(() => resolve());
|
|
254
|
+
});
|
|
255
|
+
const event = new MouseEvent("mouseenter", { bubbles: true });
|
|
256
|
+
linkElement == null ? void 0 : linkElement.dispatchEvent(event);
|
|
257
|
+
await navigationPromise;
|
|
258
|
+
await nextTick();
|
|
259
|
+
expect(router.route.path).toBe("/contact");
|
|
260
|
+
});
|
|
261
|
+
it("should handle object-based route navigation", async () => {
|
|
262
|
+
const TestApp = defineComponent({
|
|
263
|
+
setup() {
|
|
264
|
+
useProvideRouter(router);
|
|
265
|
+
return () => h(
|
|
266
|
+
RouterLink,
|
|
267
|
+
{
|
|
268
|
+
to: { path: "/about", query: { tab: "info" } }
|
|
269
|
+
},
|
|
270
|
+
() => "About with Query"
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
app = createApp(TestApp);
|
|
275
|
+
app.mount(container);
|
|
276
|
+
await nextTick();
|
|
277
|
+
const linkElement = container.querySelector("a");
|
|
278
|
+
expect(linkElement).toBeTruthy();
|
|
279
|
+
const navigationPromise = new Promise((resolve) => {
|
|
280
|
+
router.afterEach(() => resolve());
|
|
281
|
+
});
|
|
282
|
+
linkElement == null ? void 0 : linkElement.click();
|
|
283
|
+
await navigationPromise;
|
|
284
|
+
await nextTick();
|
|
285
|
+
expect(router.route.path).toBe("/about");
|
|
286
|
+
expect(router.route.query.tab).toBe("info");
|
|
287
|
+
});
|
|
288
|
+
it("should handle custom navigation handler", async () => {
|
|
289
|
+
let customHandlerCalled = false;
|
|
290
|
+
let receivedEventName = "";
|
|
291
|
+
const TestApp = defineComponent({
|
|
292
|
+
setup() {
|
|
293
|
+
useProvideRouter(router);
|
|
294
|
+
return () => h(
|
|
295
|
+
RouterLink,
|
|
296
|
+
{
|
|
297
|
+
to: "/about",
|
|
298
|
+
beforeNavigate: (event, eventName) => {
|
|
299
|
+
customHandlerCalled = true;
|
|
300
|
+
receivedEventName = eventName;
|
|
301
|
+
event.preventDefault();
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
() => "Custom Handler Link"
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
app = createApp(TestApp);
|
|
309
|
+
app.mount(container);
|
|
310
|
+
await nextTick();
|
|
311
|
+
const linkElement = container.querySelector("a");
|
|
312
|
+
expect(linkElement).toBeTruthy();
|
|
313
|
+
linkElement == null ? void 0 : linkElement.click();
|
|
314
|
+
await nextTick();
|
|
315
|
+
expect(customHandlerCalled).toBe(true);
|
|
316
|
+
expect(receivedEventName).toBe("click");
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
describe("Props Validation", () => {
|
|
320
|
+
it("should accept string as to prop", async () => {
|
|
321
|
+
const TestApp = defineComponent({
|
|
322
|
+
setup() {
|
|
323
|
+
useProvideRouter(router);
|
|
324
|
+
return () => h(RouterLink, { to: "/about" }, () => "String Route");
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
expect(() => {
|
|
328
|
+
app = createApp(TestApp);
|
|
329
|
+
app.mount(container);
|
|
330
|
+
}).not.toThrow();
|
|
331
|
+
});
|
|
332
|
+
it("should accept object as to prop", async () => {
|
|
333
|
+
const TestApp = defineComponent({
|
|
334
|
+
setup() {
|
|
335
|
+
useProvideRouter(router);
|
|
336
|
+
return () => h(
|
|
337
|
+
RouterLink,
|
|
338
|
+
{
|
|
339
|
+
to: { path: "/contact" }
|
|
340
|
+
},
|
|
341
|
+
() => "Object Route"
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
expect(() => {
|
|
346
|
+
app = createApp(TestApp);
|
|
347
|
+
app.mount(container);
|
|
348
|
+
}).not.toThrow();
|
|
349
|
+
});
|
|
350
|
+
it("should handle array of events", async () => {
|
|
351
|
+
const TestApp = defineComponent({
|
|
352
|
+
setup() {
|
|
353
|
+
useProvideRouter(router);
|
|
354
|
+
return () => h(
|
|
355
|
+
RouterLink,
|
|
356
|
+
{
|
|
357
|
+
to: "/about",
|
|
358
|
+
event: ["click", "keydown"]
|
|
359
|
+
},
|
|
360
|
+
() => "Multi Event Link"
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
app = createApp(TestApp);
|
|
365
|
+
app.mount(container);
|
|
366
|
+
await nextTick();
|
|
367
|
+
const linkElement = container.querySelector("a");
|
|
368
|
+
expect(linkElement).toBeTruthy();
|
|
369
|
+
const clickPromise = new Promise((resolve) => {
|
|
370
|
+
router.afterEach(() => resolve());
|
|
371
|
+
});
|
|
372
|
+
linkElement == null ? void 0 : linkElement.click();
|
|
373
|
+
await clickPromise;
|
|
374
|
+
await nextTick();
|
|
375
|
+
expect(router.route.path).toBe("/about");
|
|
376
|
+
await router.push("/");
|
|
377
|
+
await nextTick();
|
|
378
|
+
const keydownPromise = new Promise((resolve) => {
|
|
379
|
+
router.afterEach(() => resolve());
|
|
380
|
+
});
|
|
381
|
+
const keyEvent = new KeyboardEvent("keydown", { key: "Enter" });
|
|
382
|
+
linkElement == null ? void 0 : linkElement.dispatchEvent(keyEvent);
|
|
383
|
+
await keydownPromise;
|
|
384
|
+
await nextTick();
|
|
385
|
+
expect(router.route.path).toBe("/about");
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
describe("Error Handling", () => {
|
|
389
|
+
it("should throw error when router context is missing", () => {
|
|
390
|
+
const TestApp = defineComponent({
|
|
391
|
+
setup() {
|
|
392
|
+
return () => h(RouterLink, { to: "/about" }, () => "No Router");
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
expect(() => {
|
|
396
|
+
app = createApp(TestApp);
|
|
397
|
+
app.mount(container);
|
|
398
|
+
}).toThrow();
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
describe("Slot Rendering", () => {
|
|
402
|
+
it("should render default slot content", async () => {
|
|
403
|
+
const TestApp = defineComponent({
|
|
404
|
+
setup() {
|
|
405
|
+
useProvideRouter(router);
|
|
406
|
+
return () => h(
|
|
407
|
+
RouterLink,
|
|
408
|
+
{ to: "/about" },
|
|
409
|
+
{
|
|
410
|
+
default: () => h(
|
|
411
|
+
"span",
|
|
412
|
+
{ class: "link-text" },
|
|
413
|
+
"Custom Content"
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
app = createApp(TestApp);
|
|
420
|
+
app.mount(container);
|
|
421
|
+
await nextTick();
|
|
422
|
+
const spanElement = container.querySelector("span.link-text");
|
|
423
|
+
expect(spanElement).toBeTruthy();
|
|
424
|
+
expect(spanElement == null ? void 0 : spanElement.textContent).toBe("Custom Content");
|
|
425
|
+
});
|
|
426
|
+
it("should render complex slot content", async () => {
|
|
427
|
+
const TestApp = defineComponent({
|
|
428
|
+
setup() {
|
|
429
|
+
useProvideRouter(router);
|
|
430
|
+
return () => h(
|
|
431
|
+
RouterLink,
|
|
432
|
+
{ to: "/contact" },
|
|
433
|
+
{
|
|
434
|
+
default: () => [
|
|
435
|
+
h("i", { class: "icon" }, "\u2192"),
|
|
436
|
+
h("span", "Contact Us")
|
|
437
|
+
]
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
app = createApp(TestApp);
|
|
443
|
+
app.mount(container);
|
|
444
|
+
await nextTick();
|
|
445
|
+
const iconElement = container.querySelector("i.icon");
|
|
446
|
+
const spanElement = container.querySelector("span");
|
|
447
|
+
expect(iconElement).toBeTruthy();
|
|
448
|
+
expect(spanElement).toBeTruthy();
|
|
449
|
+
expect(iconElement == null ? void 0 : iconElement.textContent).toBe("\u2192");
|
|
450
|
+
expect(spanElement == null ? void 0 : spanElement.textContent).toBe("Contact Us");
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
describe("Active State Management", () => {
|
|
454
|
+
it("should apply active class with exact matching", async () => {
|
|
455
|
+
var _a, _b;
|
|
456
|
+
await router.push("/about");
|
|
457
|
+
await nextTick();
|
|
458
|
+
const TestApp = defineComponent({
|
|
459
|
+
setup() {
|
|
460
|
+
useProvideRouter(router);
|
|
461
|
+
return () => h("div", [
|
|
462
|
+
h(
|
|
463
|
+
RouterLink,
|
|
464
|
+
{
|
|
465
|
+
to: "/about",
|
|
466
|
+
exact: "exact",
|
|
467
|
+
activeClass: "exact-active"
|
|
468
|
+
},
|
|
469
|
+
() => "Exact Match"
|
|
470
|
+
),
|
|
471
|
+
h(
|
|
472
|
+
RouterLink,
|
|
473
|
+
{
|
|
474
|
+
to: "/about/sub",
|
|
475
|
+
exact: "exact",
|
|
476
|
+
activeClass: "exact-active"
|
|
477
|
+
},
|
|
478
|
+
() => "Not Exact"
|
|
479
|
+
)
|
|
480
|
+
]);
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
app = createApp(TestApp);
|
|
484
|
+
app.mount(container);
|
|
485
|
+
await nextTick();
|
|
486
|
+
const links = container.querySelectorAll("a");
|
|
487
|
+
expect((_a = links[0]) == null ? void 0 : _a.classList.contains("exact-active")).toBe(true);
|
|
488
|
+
expect((_b = links[1]) == null ? void 0 : _b.classList.contains("exact-active")).toBe(false);
|
|
489
|
+
});
|
|
490
|
+
it("should apply active class with include matching", async () => {
|
|
491
|
+
await router.push("/about");
|
|
492
|
+
await nextTick();
|
|
493
|
+
const TestApp = defineComponent({
|
|
494
|
+
setup() {
|
|
495
|
+
useProvideRouter(router);
|
|
496
|
+
return () => h(
|
|
497
|
+
RouterLink,
|
|
498
|
+
{
|
|
499
|
+
to: "/about",
|
|
500
|
+
exact: "include",
|
|
501
|
+
activeClass: "include-active"
|
|
502
|
+
},
|
|
503
|
+
() => "Include Match"
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
app = createApp(TestApp);
|
|
508
|
+
app.mount(container);
|
|
509
|
+
await nextTick();
|
|
510
|
+
const linkElement = container.querySelector("a");
|
|
511
|
+
expect(linkElement == null ? void 0 : linkElement.classList.contains("include-active")).toBe(
|
|
512
|
+
true
|
|
513
|
+
);
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
describe("Reactivity", () => {
|
|
517
|
+
it("should update active class when route changes", async () => {
|
|
518
|
+
await router.replace("/");
|
|
519
|
+
await nextTick();
|
|
520
|
+
const TestApp = defineComponent({
|
|
521
|
+
setup() {
|
|
522
|
+
useProvideRouter(router);
|
|
523
|
+
return () => h(
|
|
524
|
+
RouterLink,
|
|
525
|
+
{
|
|
526
|
+
to: "/about",
|
|
527
|
+
activeClass: "active-link"
|
|
528
|
+
},
|
|
529
|
+
() => "About"
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
app = createApp(TestApp);
|
|
534
|
+
app.mount(container);
|
|
535
|
+
await nextTick();
|
|
536
|
+
const linkElement = container.querySelector("a");
|
|
537
|
+
expect(linkElement == null ? void 0 : linkElement.classList.contains("active-link")).toBe(false);
|
|
538
|
+
const toAbout = new Promise((resolve) => {
|
|
539
|
+
router.afterEach(() => resolve());
|
|
540
|
+
});
|
|
541
|
+
await router.push("/about");
|
|
542
|
+
await toAbout;
|
|
543
|
+
await nextTick();
|
|
544
|
+
expect(linkElement == null ? void 0 : linkElement.classList.contains("active-link")).toBe(true);
|
|
545
|
+
const toContact = new Promise((resolve) => {
|
|
546
|
+
router.afterEach(() => resolve());
|
|
547
|
+
});
|
|
548
|
+
await router.push("/contact");
|
|
549
|
+
await toContact;
|
|
550
|
+
await nextTick();
|
|
551
|
+
expect(linkElement == null ? void 0 : linkElement.classList.contains("active-link")).toBe(false);
|
|
552
|
+
});
|
|
553
|
+
it("should update rendering when props.to changes", async () => {
|
|
554
|
+
await router.replace("/about");
|
|
555
|
+
await nextTick();
|
|
556
|
+
const toProp = ref("/about");
|
|
557
|
+
const TestApp = defineComponent({
|
|
558
|
+
setup() {
|
|
559
|
+
useProvideRouter(router);
|
|
560
|
+
return () => h(
|
|
561
|
+
RouterLink,
|
|
562
|
+
{
|
|
563
|
+
to: toProp.value,
|
|
564
|
+
activeClass: "active-link"
|
|
565
|
+
},
|
|
566
|
+
() => "Dynamic To"
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
app = createApp(TestApp);
|
|
571
|
+
app.mount(container);
|
|
572
|
+
await nextTick();
|
|
573
|
+
const linkElement = container.querySelector("a");
|
|
574
|
+
expect(linkElement == null ? void 0 : linkElement.classList.contains("active-link")).toBe(true);
|
|
575
|
+
toProp.value = "/contact";
|
|
576
|
+
await nextTick();
|
|
577
|
+
expect(linkElement == null ? void 0 : linkElement.classList.contains("active-link")).toBe(false);
|
|
578
|
+
const toContact = new Promise((resolve) => {
|
|
579
|
+
router.afterEach(() => resolve());
|
|
580
|
+
});
|
|
581
|
+
await router.push("/contact");
|
|
582
|
+
await toContact;
|
|
583
|
+
await nextTick();
|
|
584
|
+
expect(linkElement == null ? void 0 : linkElement.classList.contains("active-link")).toBe(true);
|
|
585
|
+
});
|
|
586
|
+
it("should update event handlers when props.event changes", async () => {
|
|
587
|
+
await router.replace("/");
|
|
588
|
+
await nextTick();
|
|
589
|
+
const eventProp = ref("click");
|
|
590
|
+
const TestApp = defineComponent({
|
|
591
|
+
setup() {
|
|
592
|
+
useProvideRouter(router);
|
|
593
|
+
return () => h(
|
|
594
|
+
RouterLink,
|
|
595
|
+
{
|
|
596
|
+
to: "/about",
|
|
597
|
+
event: eventProp.value
|
|
598
|
+
},
|
|
599
|
+
() => "Event Link"
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
app = createApp(TestApp);
|
|
604
|
+
app.mount(container);
|
|
605
|
+
await nextTick();
|
|
606
|
+
const linkElement = container.querySelector("a");
|
|
607
|
+
expect(linkElement).toBeTruthy();
|
|
608
|
+
const clickNav = new Promise((resolve) => {
|
|
609
|
+
router.afterEach(() => resolve());
|
|
610
|
+
});
|
|
611
|
+
linkElement == null ? void 0 : linkElement.click();
|
|
612
|
+
await clickNav;
|
|
613
|
+
await nextTick();
|
|
614
|
+
expect(router.route.path).toBe("/about");
|
|
615
|
+
const backNav = new Promise((resolve) => {
|
|
616
|
+
router.afterEach(() => resolve());
|
|
617
|
+
});
|
|
618
|
+
await router.replace("/");
|
|
619
|
+
await backNav;
|
|
620
|
+
await nextTick();
|
|
621
|
+
eventProp.value = "mouseenter";
|
|
622
|
+
await nextTick();
|
|
623
|
+
linkElement == null ? void 0 : linkElement.click();
|
|
624
|
+
await nextTick();
|
|
625
|
+
expect(router.route.path).toBe("/");
|
|
626
|
+
const hoverNav = new Promise((resolve) => {
|
|
627
|
+
router.afterEach(() => resolve());
|
|
628
|
+
});
|
|
629
|
+
const event = new MouseEvent("mouseenter", { bubbles: true });
|
|
630
|
+
linkElement == null ? void 0 : linkElement.dispatchEvent(event);
|
|
631
|
+
await hoverNav;
|
|
632
|
+
await nextTick();
|
|
633
|
+
expect(router.route.path).toBe("/about");
|
|
634
|
+
});
|
|
635
|
+
it("should re-render when tag prop changes", async () => {
|
|
636
|
+
const tagProp = ref("a");
|
|
637
|
+
const TestApp = defineComponent({
|
|
638
|
+
setup() {
|
|
639
|
+
useProvideRouter(router);
|
|
640
|
+
return () => h(
|
|
641
|
+
RouterLink,
|
|
642
|
+
{
|
|
643
|
+
to: "/about",
|
|
644
|
+
tag: tagProp.value
|
|
645
|
+
},
|
|
646
|
+
() => "Tag Link"
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
app = createApp(TestApp);
|
|
651
|
+
app.mount(container);
|
|
652
|
+
await nextTick();
|
|
653
|
+
const anchorElement = container.querySelector("a");
|
|
654
|
+
expect(anchorElement).toBeTruthy();
|
|
655
|
+
tagProp.value = "button";
|
|
656
|
+
await nextTick();
|
|
657
|
+
const buttonElement = container.querySelector("button");
|
|
658
|
+
const oldAnchor = container.querySelector("a");
|
|
659
|
+
expect(buttonElement).toBeTruthy();
|
|
660
|
+
expect(oldAnchor).toBeFalsy();
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RouterView component for rendering matched route components.
|
|
3
|
+
* Acts as a placeholder where route components are rendered based on the current route.
|
|
4
|
+
* Supports nested routing with proper depth tracking using Vue's provide/inject mechanism.
|
|
5
|
+
*
|
|
6
|
+
* @param props - Component properties (RouterView accepts no props)
|
|
7
|
+
* @param context - Vue setup context (not used)
|
|
8
|
+
* @param context.slots - Component slots (not used)
|
|
9
|
+
* @returns Vue render function that renders the matched route component at current depth
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
*
|
|
13
|
+
* ```vue
|
|
14
|
+
* <template>
|
|
15
|
+
* <div id="app">
|
|
16
|
+
* <!-- Navigation links -->
|
|
17
|
+
* <nav>
|
|
18
|
+
* <RouterLink to="/">Home</RouterLink>
|
|
19
|
+
* <RouterLink to="/about">About</RouterLink>
|
|
20
|
+
* <RouterLink to="/users">Users</RouterLink>
|
|
21
|
+
* </nav>
|
|
22
|
+
*
|
|
23
|
+
* <!-- Root level route components render here -->
|
|
24
|
+
* <RouterView />
|
|
25
|
+
* </div>
|
|
26
|
+
* </template>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare const RouterView: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
30
|
+
[key: string]: any;
|
|
31
|
+
}> | null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineComponent, h } from "vue";
|
|
2
|
+
import { _useRouterViewDepth, useRoute } from "./use.mjs";
|
|
3
|
+
import { resolveComponent } from "./util.mjs";
|
|
4
|
+
export const RouterView = defineComponent({
|
|
5
|
+
name: "RouterView",
|
|
6
|
+
setup() {
|
|
7
|
+
const route = useRoute();
|
|
8
|
+
const depth = _useRouterViewDepth(true);
|
|
9
|
+
return () => {
|
|
10
|
+
const matchedRoute = route.matched[depth];
|
|
11
|
+
const component = matchedRoute ? resolveComponent(matchedRoute.component) : null;
|
|
12
|
+
return component ? h(component, { key: matchedRoute.compilePath }) : null;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
});
|