UNPKG

@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
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(); }); }); });