@esmx/router-vue
Version:
Vue integration for @esmx/router - A universal router that works seamlessly with both Vue 2.7+ and Vue 3
460 lines (459 loc) • 14.7 kB
JavaScript
import { Router, RouterMode } from "@esmx/router";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { createApp, defineComponent, h, inject, nextTick, provide } from "vue";
import { RouterView } from "./router-view.mjs";
import { useProvideRouter } from "./use.mjs";
const HomeComponent = defineComponent({
name: "HomeComponent",
setup() {
return () => h("div", { class: "home" }, "Home Page");
}
});
const AboutComponent = defineComponent({
name: "AboutComponent",
setup() {
return () => h("div", { class: "about" }, "About Page");
}
});
const UserComponent = defineComponent({
name: "UserComponent",
setup() {
return () => h("div", { class: "user" }, "User Component");
}
});
const ESModuleComponent = {
__esModule: true,
default: defineComponent({
name: "ESModuleComponent",
setup() {
return () => h("div", { class: "es-module" }, "ES Module Component");
}
})
};
describe("router-view.ts - RouterView Component", () => {
let router;
let testContainer;
beforeEach(async () => {
testContainer = document.createElement("div");
testContainer.id = "test-app";
document.body.appendChild(testContainer);
const routes = [
{
path: "/",
component: HomeComponent,
meta: { title: "Home" }
},
{
path: "/about",
component: AboutComponent,
meta: { title: "About" }
},
{
path: "/users/:id",
component: UserComponent,
meta: { title: "User" }
},
{
path: "/es-module",
component: ESModuleComponent,
meta: { title: "ES Module" }
}
];
router = new Router({
root: "#test-app",
routes,
mode: RouterMode.memory,
base: new URL("http://localhost:8000/")
});
await router.replace("/");
await new Promise((resolve) => setTimeout(resolve, 10));
});
afterEach(() => {
if (testContainer.parentNode) {
testContainer.parentNode.removeChild(testContainer);
}
if (router) {
router.destroy();
}
});
describe("Basic Functionality", () => {
it("should render matched route component at depth 0", async () => {
const TestApp = defineComponent({
setup() {
useProvideRouter(router);
return () => h("div", [h(RouterView)]);
}
});
const app = createApp(TestApp);
app.mount(testContainer);
await nextTick();
expect(testContainer.textContent).toContain("Home Page");
app.unmount();
});
it("should render different components when route changes", async () => {
const TestApp = defineComponent({
setup() {
useProvideRouter(router);
return () => h("div", [h(RouterView)]);
}
});
const app = createApp(TestApp);
app.mount(testContainer);
await nextTick();
expect(testContainer.textContent).toContain("Home Page");
await router.push("/about");
await nextTick();
expect(testContainer.textContent).toContain("About Page");
app.unmount();
});
it("should handle routes with parameters", async () => {
const TestApp = defineComponent({
setup() {
useProvideRouter(router);
return () => h("div", [h(RouterView)]);
}
});
const app = createApp(TestApp);
app.mount(testContainer);
await router.push("/users/123");
await nextTick();
expect(testContainer.textContent).toContain("User Component");
app.unmount();
});
});
describe("Component Resolution", () => {
it("should resolve ES module components correctly", async () => {
const TestApp = defineComponent({
setup() {
useProvideRouter(router);
return () => h("div", [h(RouterView)]);
}
});
const app = createApp(TestApp);
app.mount(testContainer);
await router.push("/es-module");
await nextTick();
expect(testContainer.textContent).toContain("ES Module Component");
app.unmount();
});
it("should handle function components", async () => {
const FunctionComponent = () => h("div", "Function Component");
const routes = [
{
path: "/function",
component: FunctionComponent,
meta: { title: "Function" }
}
];
const functionRouter = new Router({
root: "#test-app",
routes,
mode: RouterMode.memory,
base: new URL("http://localhost:8000/")
});
await functionRouter.replace("/function");
await new Promise((resolve) => setTimeout(resolve, 10));
const TestApp = defineComponent({
setup() {
useProvideRouter(functionRouter);
return () => h("div", [h(RouterView)]);
}
});
const app = createApp(TestApp);
app.mount(testContainer);
await nextTick();
expect(testContainer.textContent).toContain("Function Component");
app.unmount();
functionRouter.destroy();
});
});
describe("Depth Tracking", () => {
it("should inject depth 0 by default", async () => {
let injectedDepth;
const RouterViewDepth = Symbol("RouterViewDepth");
const TestRouterView = defineComponent({
name: "TestRouterView",
setup() {
injectedDepth = inject(RouterViewDepth, -1);
return () => h(RouterView);
}
});
const TestApp = defineComponent({
setup() {
useProvideRouter(router);
return () => h("div", [h(TestRouterView)]);
}
});
const app = createApp(TestApp);
app.mount(testContainer);
await nextTick();
expect(injectedDepth).toBe(-1);
app.unmount();
});
it("should provide correct depth in nested RouterViews", async () => {
let parentDepth;
let childDepth;
const RouterViewDepth = Symbol("RouterViewDepth");
const ParentTestComponent = defineComponent({
name: "ParentTestComponent",
setup() {
parentDepth = inject(RouterViewDepth, -1);
provide(RouterViewDepth, 0);
return () => h("div", [h("span", "Parent"), h(ChildTestComponent)]);
}
});
const ChildTestComponent = defineComponent({
name: "ChildTestComponent",
setup() {
childDepth = inject(RouterViewDepth, -1);
return () => h("div", "Child");
}
});
const TestApp = defineComponent({
setup() {
useProvideRouter(router);
return () => h("div", [h(ParentTestComponent)]);
}
});
const app = createApp(TestApp);
app.mount(testContainer);
await nextTick();
expect(parentDepth).toBe(-1);
expect(childDepth).toBe(0);
app.unmount();
});
it("should handle nested RouterViews with correct depth", async () => {
const Level1Component = defineComponent({
name: "Level1Component",
setup() {
return () => h("div", [h("span", "Level 1"), h(RouterView)]);
}
});
const Level2Component = defineComponent({
name: "Level2Component",
setup() {
return () => h("div", "Level 2");
}
});
const nestedRoutes = [
{
path: "/level1",
component: Level1Component,
children: [
{
path: "level2",
component: Level2Component
}
]
}
];
const nestedRouter = new Router({
root: "#test-app",
routes: nestedRoutes,
mode: RouterMode.memory,
base: new URL("http://localhost:8000/")
});
await nestedRouter.replace("/level1/level2");
await new Promise((resolve) => setTimeout(resolve, 10));
const TestApp = defineComponent({
setup() {
useProvideRouter(nestedRouter);
return () => h("div", [h(RouterView)]);
}
});
const app = createApp(TestApp);
app.mount(testContainer);
await nextTick();
expect(testContainer.textContent).toContain("Level 1");
expect(testContainer.textContent).toContain("Level 2");
app.unmount();
nestedRouter.destroy();
});
});
describe("Edge Cases and Error Handling", () => {
it("should render null when no route matches at current depth", async () => {
const RouterViewDepth = Symbol("RouterViewDepth");
const DeepRouterView = defineComponent({
name: "DeepRouterView",
setup() {
const currentDepth = inject(RouterViewDepth, 0);
provide(RouterViewDepth, currentDepth + 1);
return () => h(RouterView);
}
});
const TestApp = defineComponent({
setup() {
useProvideRouter(router);
return () => h("div", [
h("span", "App"),
h(RouterView),
// This renders Home component at depth 0
h(DeepRouterView)
// This tries to render at depth 1, but no match
]);
}
});
const app = createApp(TestApp);
app.mount(testContainer);
await nextTick();
expect(testContainer.textContent).toContain("App");
expect(testContainer.textContent).toContain("Home Page");
app.unmount();
});
it("should handle null components gracefully", async () => {
var _a, _b, _c;
const routesWithNull = [
{
path: "/null-component",
component: null,
meta: { title: "Null Component" }
}
];
const nullRouter = new Router({
root: "#test-app",
routes: routesWithNull,
mode: RouterMode.memory,
base: new URL("http://localhost:8000/")
});
await nullRouter.replace("/null-component");
await new Promise((resolve) => setTimeout(resolve, 10));
const TestApp = defineComponent({
setup() {
useProvideRouter(nullRouter);
return () => h("div", [h("span", "App"), h(RouterView)]);
}
});
const app = createApp(TestApp);
app.mount(testContainer);
await nextTick();
expect((_a = testContainer.textContent) == null ? void 0 : _a.trim()).toBe("App");
expect((_b = testContainer.querySelector("div")) == null ? void 0 : _b.children.length).toBe(1);
expect((_c = testContainer.querySelector("span")) == null ? void 0 : _c.textContent).toBe(
"App"
);
app.unmount();
nullRouter.destroy();
});
it("should handle non-existent routes", async () => {
var _a, _b, _c;
const nonExistentRouter = new Router({
root: "#test-app",
routes: [
{
path: "/",
component: null
// Initial route with null component
}
],
mode: RouterMode.memory,
base: new URL("http://localhost:8000/")
});
await nonExistentRouter.replace("/");
await new Promise((resolve) => setTimeout(resolve, 10));
const TestApp = defineComponent({
setup() {
useProvideRouter(nonExistentRouter);
return () => h("div", [h("span", "App"), h(RouterView)]);
}
});
const app = createApp(TestApp);
app.mount(testContainer);
await nonExistentRouter.push("/non-existent");
await nextTick();
await new Promise((resolve) => setTimeout(resolve, 10));
expect((_a = testContainer.textContent) == null ? void 0 : _a.trim()).toBe("App");
expect((_b = testContainer.querySelector("div")) == null ? void 0 : _b.children.length).toBe(1);
expect((_c = testContainer.querySelector("span")) == null ? void 0 : _c.textContent).toBe(
"App"
);
app.unmount();
nonExistentRouter.destroy();
});
it("should handle malformed ES modules", async () => {
const MalformedModule = {
__esModule: true,
default: null
};
const malformedRoutes = [
{
path: "/malformed",
component: MalformedModule,
meta: { title: "Malformed" }
}
];
const malformedRouter = new Router({
root: "#test-app",
routes: malformedRoutes,
mode: RouterMode.memory,
base: new URL("http://localhost:8000/")
});
await malformedRouter.replace("/malformed");
await new Promise((resolve) => setTimeout(resolve, 10));
const TestApp = defineComponent({
setup() {
useProvideRouter(malformedRouter);
return () => h("div", [h("span", "App"), h(RouterView)]);
}
});
const app = createApp(TestApp);
app.mount(testContainer);
await nextTick();
expect(testContainer.textContent).toBe("App");
app.unmount();
malformedRouter.destroy();
});
});
describe("Component Properties", () => {
it("should have correct component name", () => {
expect(RouterView.name).toBe("RouterView");
});
it("should be a valid Vue component", () => {
expect(RouterView).toHaveProperty("setup");
expect(typeof RouterView.setup).toBe("function");
});
it("should not define props", () => {
expect(RouterView.props).toBeUndefined();
});
});
describe("Integration Tests", () => {
it("should re-render when route changes", async () => {
const TestApp = defineComponent({
setup() {
useProvideRouter(router);
return () => h("div", [h(RouterView)]);
}
});
const app = createApp(TestApp);
app.mount(testContainer);
await nextTick();
expect(testContainer.textContent).toContain("Home Page");
await router.push("/about");
await nextTick();
expect(testContainer.textContent).toContain("About Page");
await router.push("/users/123");
await nextTick();
expect(testContainer.textContent).toContain("User Component");
app.unmount();
});
it("should work with router navigation methods", async () => {
const TestApp = defineComponent({
setup() {
useProvideRouter(router);
return () => h("div", [h(RouterView)]);
}
});
const app = createApp(TestApp);
app.mount(testContainer);
await router.push("/about");
await nextTick();
expect(testContainer.textContent).toContain("About Page");
await router.replace("/users/456");
await nextTick();
expect(testContainer.textContent).toContain("User Component");
await router.back();
await nextTick();
expect(testContainer.textContent).toContain("Home Page");
app.unmount();
});
});
});