@esmx/router-vue 3.0.0-rc.17 → 3.0.0-rc.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +61 -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 +31 -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 +92 -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 +62 -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
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { defineComponent, h } from "vue";
|
|
2
|
+
import { useLink } from "./use.mjs";
|
|
3
|
+
import { isVue3 } from "./util.mjs";
|
|
4
|
+
export const RouterLink = defineComponent({
|
|
5
|
+
name: "RouterLink",
|
|
6
|
+
props: {
|
|
7
|
+
/**
|
|
8
|
+
* Target route location to navigate to.
|
|
9
|
+
* Can be a string path or route location object.
|
|
10
|
+
* @example '/home' | { path: '/user', query: { id: '123' } }
|
|
11
|
+
*/
|
|
12
|
+
to: {
|
|
13
|
+
type: [String, Object],
|
|
14
|
+
required: true
|
|
15
|
+
},
|
|
16
|
+
/**
|
|
17
|
+
* Navigation type for the link.
|
|
18
|
+
* @default 'push'
|
|
19
|
+
* @example 'push' | 'replace' | 'pushWindow' | 'replaceWindow' | 'pushLayer'
|
|
20
|
+
*/
|
|
21
|
+
type: { type: String, default: "push" },
|
|
22
|
+
/**
|
|
23
|
+
* @deprecated Use 'type="replace"' instead
|
|
24
|
+
* @example replace={true} → type="replace"
|
|
25
|
+
*/
|
|
26
|
+
replace: { type: Boolean, default: false },
|
|
27
|
+
/**
|
|
28
|
+
* How to match the active state.
|
|
29
|
+
* - 'include': Match if current route includes this path
|
|
30
|
+
* - 'exact': Match only if routes are exactly the same
|
|
31
|
+
* - 'route': Match based on route configuration
|
|
32
|
+
* @default 'include'
|
|
33
|
+
*/
|
|
34
|
+
exact: { type: String, default: "include" },
|
|
35
|
+
/**
|
|
36
|
+
* CSS class to apply when link is active (route matches).
|
|
37
|
+
* @example 'nav-active' | 'selected'
|
|
38
|
+
*/
|
|
39
|
+
activeClass: { type: String },
|
|
40
|
+
/**
|
|
41
|
+
* Event(s) that trigger navigation. Can be string or array of strings.
|
|
42
|
+
* @default 'click'
|
|
43
|
+
* @example 'click' | ['click', 'mouseenter']
|
|
44
|
+
*/
|
|
45
|
+
event: {
|
|
46
|
+
type: [String, Array],
|
|
47
|
+
default: "click"
|
|
48
|
+
},
|
|
49
|
+
/**
|
|
50
|
+
* Custom tag to render instead of 'a'.
|
|
51
|
+
* @default 'a'
|
|
52
|
+
* @example 'button' | 'div' | 'span'
|
|
53
|
+
*/
|
|
54
|
+
tag: { type: String, default: "a" },
|
|
55
|
+
/**
|
|
56
|
+
* Layer options for layer-based navigation.
|
|
57
|
+
* Only used when type='pushLayer'.
|
|
58
|
+
* @example { zIndex: 1000, autoPush: false, routerOptions: { mode: 'memory' } }
|
|
59
|
+
*/
|
|
60
|
+
layerOptions: { type: Object }
|
|
61
|
+
},
|
|
62
|
+
setup(props, { slots }) {
|
|
63
|
+
const link = useLink(props);
|
|
64
|
+
return () => {
|
|
65
|
+
var _a;
|
|
66
|
+
const data = link.value;
|
|
67
|
+
const eventHandlers = data.getEventHandlers(
|
|
68
|
+
isVue3 ? (name) => `on${name.charAt(0).toUpperCase()}${name.slice(1)}` : void 0
|
|
69
|
+
);
|
|
70
|
+
const props2 = {};
|
|
71
|
+
if (isVue3) {
|
|
72
|
+
Object.assign(props2, data.attributes, eventHandlers);
|
|
73
|
+
} else {
|
|
74
|
+
const { class: className, ...attrs } = data.attributes;
|
|
75
|
+
Object.assign(props2, {
|
|
76
|
+
attrs,
|
|
77
|
+
class: className,
|
|
78
|
+
on: eventHandlers
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return h(data.tag, props2, (_a = slots.default) == null ? void 0 : _a.call(slots));
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { Router, RouterMode } from "@esmx/router";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
3
|
+
import { createApp, defineComponent, h, nextTick } 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:3000/")
|
|
45
|
+
});
|
|
46
|
+
await router.replace("/");
|
|
47
|
+
await nextTick();
|
|
48
|
+
});
|
|
49
|
+
afterEach(async () => {
|
|
50
|
+
if (app) {
|
|
51
|
+
app.unmount();
|
|
52
|
+
}
|
|
53
|
+
if (router) {
|
|
54
|
+
router.destroy();
|
|
55
|
+
}
|
|
56
|
+
if (container.parentNode) {
|
|
57
|
+
container.parentNode.removeChild(container);
|
|
58
|
+
}
|
|
59
|
+
await nextTick();
|
|
60
|
+
});
|
|
61
|
+
describe("Component Definition", () => {
|
|
62
|
+
it("should have correct component name", () => {
|
|
63
|
+
expect(RouterLink.name).toBe("RouterLink");
|
|
64
|
+
});
|
|
65
|
+
it("should have properly configured props", () => {
|
|
66
|
+
const props = RouterLink.props;
|
|
67
|
+
expect(props.to).toBeDefined();
|
|
68
|
+
expect(props.to.required).toBe(true);
|
|
69
|
+
expect(props.type).toBeDefined();
|
|
70
|
+
expect(props.type.default).toBe("push");
|
|
71
|
+
expect(props.exact).toBeDefined();
|
|
72
|
+
expect(props.exact.default).toBe("include");
|
|
73
|
+
expect(props.tag).toBeDefined();
|
|
74
|
+
expect(props.tag.default).toBe("a");
|
|
75
|
+
expect(props.event).toBeDefined();
|
|
76
|
+
expect(props.event.default).toBe("click");
|
|
77
|
+
expect(props.replace).toBeDefined();
|
|
78
|
+
expect(props.replace.default).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
it("should have setup function defined", () => {
|
|
81
|
+
expect(RouterLink.setup).toBeDefined();
|
|
82
|
+
expect(typeof RouterLink.setup).toBe("function");
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe("Component Rendering", () => {
|
|
86
|
+
it("should render basic router link", async () => {
|
|
87
|
+
const TestApp = defineComponent({
|
|
88
|
+
setup() {
|
|
89
|
+
useProvideRouter(router);
|
|
90
|
+
return () => h(RouterLink, { to: "/about" }, () => "About Link");
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
app = createApp(TestApp);
|
|
94
|
+
app.mount(container);
|
|
95
|
+
await nextTick();
|
|
96
|
+
const linkElement = container.querySelector("a");
|
|
97
|
+
expect(linkElement).toBeTruthy();
|
|
98
|
+
expect(linkElement == null ? void 0 : linkElement.textContent).toBe("About Link");
|
|
99
|
+
});
|
|
100
|
+
it("should render with custom tag", async () => {
|
|
101
|
+
const TestApp = defineComponent({
|
|
102
|
+
setup() {
|
|
103
|
+
useProvideRouter(router);
|
|
104
|
+
return () => h(
|
|
105
|
+
RouterLink,
|
|
106
|
+
{
|
|
107
|
+
to: "/contact",
|
|
108
|
+
tag: "button"
|
|
109
|
+
},
|
|
110
|
+
() => "Contact Button"
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
app = createApp(TestApp);
|
|
115
|
+
app.mount(container);
|
|
116
|
+
await nextTick();
|
|
117
|
+
const buttonElement = container.querySelector("button");
|
|
118
|
+
expect(buttonElement).toBeTruthy();
|
|
119
|
+
expect(buttonElement == null ? void 0 : buttonElement.textContent).toBe("Contact Button");
|
|
120
|
+
});
|
|
121
|
+
it("should render with active class when route matches", async () => {
|
|
122
|
+
await router.push("/about");
|
|
123
|
+
await nextTick();
|
|
124
|
+
const TestApp = defineComponent({
|
|
125
|
+
setup() {
|
|
126
|
+
useProvideRouter(router);
|
|
127
|
+
return () => h(
|
|
128
|
+
RouterLink,
|
|
129
|
+
{
|
|
130
|
+
to: "/about",
|
|
131
|
+
activeClass: "active-link"
|
|
132
|
+
},
|
|
133
|
+
() => "Current Page"
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
app = createApp(TestApp);
|
|
138
|
+
app.mount(container);
|
|
139
|
+
await nextTick();
|
|
140
|
+
const linkElement = container.querySelector("a");
|
|
141
|
+
expect(linkElement).toBeTruthy();
|
|
142
|
+
expect(linkElement == null ? void 0 : linkElement.classList.contains("active-link")).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
it("should handle different navigation types", async () => {
|
|
145
|
+
var _a, _b;
|
|
146
|
+
const TestApp = defineComponent({
|
|
147
|
+
setup() {
|
|
148
|
+
useProvideRouter(router);
|
|
149
|
+
return () => h("div", [
|
|
150
|
+
h(
|
|
151
|
+
RouterLink,
|
|
152
|
+
{
|
|
153
|
+
to: "/about",
|
|
154
|
+
type: "push"
|
|
155
|
+
},
|
|
156
|
+
() => "Push Link"
|
|
157
|
+
),
|
|
158
|
+
h(
|
|
159
|
+
RouterLink,
|
|
160
|
+
{
|
|
161
|
+
to: "/contact",
|
|
162
|
+
type: "replace"
|
|
163
|
+
},
|
|
164
|
+
() => "Replace Link"
|
|
165
|
+
)
|
|
166
|
+
]);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
app = createApp(TestApp);
|
|
170
|
+
app.mount(container);
|
|
171
|
+
await nextTick();
|
|
172
|
+
const links = container.querySelectorAll("a");
|
|
173
|
+
expect(links).toHaveLength(2);
|
|
174
|
+
expect((_a = links[0]) == null ? void 0 : _a.textContent).toBe("Push Link");
|
|
175
|
+
expect((_b = links[1]) == null ? void 0 : _b.textContent).toBe("Replace Link");
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
describe("Navigation Functionality", () => {
|
|
179
|
+
it("should navigate when clicked", async () => {
|
|
180
|
+
const TestApp = defineComponent({
|
|
181
|
+
setup() {
|
|
182
|
+
useProvideRouter(router);
|
|
183
|
+
return () => h(
|
|
184
|
+
RouterLink,
|
|
185
|
+
{ to: "/about" },
|
|
186
|
+
() => "Navigate to About"
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
app = createApp(TestApp);
|
|
191
|
+
app.mount(container);
|
|
192
|
+
await nextTick();
|
|
193
|
+
const linkElement = container.querySelector("a");
|
|
194
|
+
expect(linkElement).toBeTruthy();
|
|
195
|
+
const clickPromise = new Promise((resolve) => {
|
|
196
|
+
router.afterEach(() => resolve());
|
|
197
|
+
});
|
|
198
|
+
linkElement == null ? void 0 : linkElement.click();
|
|
199
|
+
await clickPromise;
|
|
200
|
+
await nextTick();
|
|
201
|
+
expect(router.route.path).toBe("/about");
|
|
202
|
+
});
|
|
203
|
+
it("should handle custom events", async () => {
|
|
204
|
+
const TestApp = defineComponent({
|
|
205
|
+
setup() {
|
|
206
|
+
useProvideRouter(router);
|
|
207
|
+
return () => h(
|
|
208
|
+
RouterLink,
|
|
209
|
+
{
|
|
210
|
+
to: "/contact",
|
|
211
|
+
event: "mouseenter"
|
|
212
|
+
},
|
|
213
|
+
() => "Hover to Navigate"
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
app = createApp(TestApp);
|
|
218
|
+
app.mount(container);
|
|
219
|
+
await nextTick();
|
|
220
|
+
const linkElement = container.querySelector("a");
|
|
221
|
+
expect(linkElement).toBeTruthy();
|
|
222
|
+
const navigationPromise = new Promise((resolve) => {
|
|
223
|
+
router.afterEach(() => resolve());
|
|
224
|
+
});
|
|
225
|
+
const event = new MouseEvent("mouseenter", { bubbles: true });
|
|
226
|
+
linkElement == null ? void 0 : linkElement.dispatchEvent(event);
|
|
227
|
+
await navigationPromise;
|
|
228
|
+
await nextTick();
|
|
229
|
+
expect(router.route.path).toBe("/contact");
|
|
230
|
+
});
|
|
231
|
+
it("should handle object-based route navigation", async () => {
|
|
232
|
+
const TestApp = defineComponent({
|
|
233
|
+
setup() {
|
|
234
|
+
useProvideRouter(router);
|
|
235
|
+
return () => h(
|
|
236
|
+
RouterLink,
|
|
237
|
+
{
|
|
238
|
+
to: { path: "/about", query: { tab: "info" } }
|
|
239
|
+
},
|
|
240
|
+
() => "About with Query"
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
app = createApp(TestApp);
|
|
245
|
+
app.mount(container);
|
|
246
|
+
await nextTick();
|
|
247
|
+
const linkElement = container.querySelector("a");
|
|
248
|
+
expect(linkElement).toBeTruthy();
|
|
249
|
+
const navigationPromise = new Promise((resolve) => {
|
|
250
|
+
router.afterEach(() => resolve());
|
|
251
|
+
});
|
|
252
|
+
linkElement == null ? void 0 : linkElement.click();
|
|
253
|
+
await navigationPromise;
|
|
254
|
+
await nextTick();
|
|
255
|
+
expect(router.route.path).toBe("/about");
|
|
256
|
+
expect(router.route.query.tab).toBe("info");
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
describe("Props Validation", () => {
|
|
260
|
+
it("should accept string as to prop", async () => {
|
|
261
|
+
const TestApp = defineComponent({
|
|
262
|
+
setup() {
|
|
263
|
+
useProvideRouter(router);
|
|
264
|
+
return () => h(RouterLink, { to: "/about" }, () => "String Route");
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
expect(() => {
|
|
268
|
+
app = createApp(TestApp);
|
|
269
|
+
app.mount(container);
|
|
270
|
+
}).not.toThrow();
|
|
271
|
+
});
|
|
272
|
+
it("should accept object as to prop", async () => {
|
|
273
|
+
const TestApp = defineComponent({
|
|
274
|
+
setup() {
|
|
275
|
+
useProvideRouter(router);
|
|
276
|
+
return () => h(
|
|
277
|
+
RouterLink,
|
|
278
|
+
{
|
|
279
|
+
to: { path: "/contact" }
|
|
280
|
+
},
|
|
281
|
+
() => "Object Route"
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
expect(() => {
|
|
286
|
+
app = createApp(TestApp);
|
|
287
|
+
app.mount(container);
|
|
288
|
+
}).not.toThrow();
|
|
289
|
+
});
|
|
290
|
+
it("should handle array of events", async () => {
|
|
291
|
+
const TestApp = defineComponent({
|
|
292
|
+
setup() {
|
|
293
|
+
useProvideRouter(router);
|
|
294
|
+
return () => h(
|
|
295
|
+
RouterLink,
|
|
296
|
+
{
|
|
297
|
+
to: "/about",
|
|
298
|
+
event: ["click", "keydown"]
|
|
299
|
+
},
|
|
300
|
+
() => "Multi Event Link"
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
app = createApp(TestApp);
|
|
305
|
+
app.mount(container);
|
|
306
|
+
await nextTick();
|
|
307
|
+
const linkElement = container.querySelector("a");
|
|
308
|
+
expect(linkElement).toBeTruthy();
|
|
309
|
+
const clickPromise = new Promise((resolve) => {
|
|
310
|
+
router.afterEach(() => resolve());
|
|
311
|
+
});
|
|
312
|
+
linkElement == null ? void 0 : linkElement.click();
|
|
313
|
+
await clickPromise;
|
|
314
|
+
await nextTick();
|
|
315
|
+
expect(router.route.path).toBe("/about");
|
|
316
|
+
await router.push("/");
|
|
317
|
+
await nextTick();
|
|
318
|
+
const keydownPromise = new Promise((resolve) => {
|
|
319
|
+
router.afterEach(() => resolve());
|
|
320
|
+
});
|
|
321
|
+
const keyEvent = new KeyboardEvent("keydown", { key: "Enter" });
|
|
322
|
+
linkElement == null ? void 0 : linkElement.dispatchEvent(keyEvent);
|
|
323
|
+
await keydownPromise;
|
|
324
|
+
await nextTick();
|
|
325
|
+
expect(router.route.path).toBe("/about");
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
describe("Error Handling", () => {
|
|
329
|
+
it("should throw error when router context is missing", () => {
|
|
330
|
+
const TestApp = defineComponent({
|
|
331
|
+
setup() {
|
|
332
|
+
return () => h(RouterLink, { to: "/about" }, () => "No Router");
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
expect(() => {
|
|
336
|
+
app = createApp(TestApp);
|
|
337
|
+
app.mount(container);
|
|
338
|
+
}).toThrow();
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
describe("Slot Rendering", () => {
|
|
342
|
+
it("should render default slot content", async () => {
|
|
343
|
+
const TestApp = defineComponent({
|
|
344
|
+
setup() {
|
|
345
|
+
useProvideRouter(router);
|
|
346
|
+
return () => h(
|
|
347
|
+
RouterLink,
|
|
348
|
+
{ to: "/about" },
|
|
349
|
+
{
|
|
350
|
+
default: () => h(
|
|
351
|
+
"span",
|
|
352
|
+
{ class: "link-text" },
|
|
353
|
+
"Custom Content"
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
app = createApp(TestApp);
|
|
360
|
+
app.mount(container);
|
|
361
|
+
await nextTick();
|
|
362
|
+
const spanElement = container.querySelector("span.link-text");
|
|
363
|
+
expect(spanElement).toBeTruthy();
|
|
364
|
+
expect(spanElement == null ? void 0 : spanElement.textContent).toBe("Custom Content");
|
|
365
|
+
});
|
|
366
|
+
it("should render complex slot content", async () => {
|
|
367
|
+
const TestApp = defineComponent({
|
|
368
|
+
setup() {
|
|
369
|
+
useProvideRouter(router);
|
|
370
|
+
return () => h(
|
|
371
|
+
RouterLink,
|
|
372
|
+
{ to: "/contact" },
|
|
373
|
+
{
|
|
374
|
+
default: () => [
|
|
375
|
+
h("i", { class: "icon" }, "\u2192"),
|
|
376
|
+
h("span", "Contact Us")
|
|
377
|
+
]
|
|
378
|
+
}
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
app = createApp(TestApp);
|
|
383
|
+
app.mount(container);
|
|
384
|
+
await nextTick();
|
|
385
|
+
const iconElement = container.querySelector("i.icon");
|
|
386
|
+
const spanElement = container.querySelector("span");
|
|
387
|
+
expect(iconElement).toBeTruthy();
|
|
388
|
+
expect(spanElement).toBeTruthy();
|
|
389
|
+
expect(iconElement == null ? void 0 : iconElement.textContent).toBe("\u2192");
|
|
390
|
+
expect(spanElement == null ? void 0 : spanElement.textContent).toBe("Contact Us");
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
describe("Active State Management", () => {
|
|
394
|
+
it("should apply active class with exact matching", async () => {
|
|
395
|
+
var _a, _b;
|
|
396
|
+
await router.push("/about");
|
|
397
|
+
await nextTick();
|
|
398
|
+
const TestApp = defineComponent({
|
|
399
|
+
setup() {
|
|
400
|
+
useProvideRouter(router);
|
|
401
|
+
return () => h("div", [
|
|
402
|
+
h(
|
|
403
|
+
RouterLink,
|
|
404
|
+
{
|
|
405
|
+
to: "/about",
|
|
406
|
+
exact: "exact",
|
|
407
|
+
activeClass: "exact-active"
|
|
408
|
+
},
|
|
409
|
+
() => "Exact Match"
|
|
410
|
+
),
|
|
411
|
+
h(
|
|
412
|
+
RouterLink,
|
|
413
|
+
{
|
|
414
|
+
to: "/about/sub",
|
|
415
|
+
exact: "exact",
|
|
416
|
+
activeClass: "exact-active"
|
|
417
|
+
},
|
|
418
|
+
() => "Not Exact"
|
|
419
|
+
)
|
|
420
|
+
]);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
app = createApp(TestApp);
|
|
424
|
+
app.mount(container);
|
|
425
|
+
await nextTick();
|
|
426
|
+
const links = container.querySelectorAll("a");
|
|
427
|
+
expect((_a = links[0]) == null ? void 0 : _a.classList.contains("exact-active")).toBe(true);
|
|
428
|
+
expect((_b = links[1]) == null ? void 0 : _b.classList.contains("exact-active")).toBe(false);
|
|
429
|
+
});
|
|
430
|
+
it("should apply active class with include matching", async () => {
|
|
431
|
+
await router.push("/about");
|
|
432
|
+
await nextTick();
|
|
433
|
+
const TestApp = defineComponent({
|
|
434
|
+
setup() {
|
|
435
|
+
useProvideRouter(router);
|
|
436
|
+
return () => h(
|
|
437
|
+
RouterLink,
|
|
438
|
+
{
|
|
439
|
+
to: "/about",
|
|
440
|
+
exact: "include",
|
|
441
|
+
activeClass: "include-active"
|
|
442
|
+
},
|
|
443
|
+
() => "Include Match"
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
app = createApp(TestApp);
|
|
448
|
+
app.mount(container);
|
|
449
|
+
await nextTick();
|
|
450
|
+
const linkElement = container.querySelector("a");
|
|
451
|
+
expect(linkElement == null ? void 0 : linkElement.classList.contains("include-active")).toBe(
|
|
452
|
+
true
|
|
453
|
+
);
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
});
|
|
@@ -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,17 @@
|
|
|
1
|
+
import { defineComponent, h, inject, provide } from "vue";
|
|
2
|
+
import { useRoute } from "./use.mjs";
|
|
3
|
+
import { resolveComponent } from "./util.mjs";
|
|
4
|
+
const RouterViewDepthKey = Symbol("RouterViewDepth");
|
|
5
|
+
export const RouterView = defineComponent({
|
|
6
|
+
name: "RouterView",
|
|
7
|
+
setup() {
|
|
8
|
+
const route = useRoute();
|
|
9
|
+
const depth = inject(RouterViewDepthKey, 0);
|
|
10
|
+
provide(RouterViewDepthKey, depth + 1);
|
|
11
|
+
return () => {
|
|
12
|
+
const matchedRoute = route.matched[depth];
|
|
13
|
+
const component = matchedRoute ? resolveComponent(matchedRoute.component) : null;
|
|
14
|
+
return component ? h(component) : null;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|