import { expect, test } from "vitest" import { createRouter, Router, ROUTE_ACTION_TYPE, ROUTE_TYPE } from "#Source/route/index.ts" test("Router.getCurrentRouteHistoryEntry returns a defensive clone of the latest history entry", () => { const router = createRouter({ url: "/home" }) const historyEntry = router.getCurrentRouteHistoryEntry() historyEntry.id = "mutated" historyEntry.to.route.partialUrl("/changed") const latestHistoryEntry = router.getCurrentRouteHistoryEntry() expect(latestHistoryEntry.type).toBe(ROUTE_TYPE.INITIALIZE) expect(latestHistoryEntry.to.route.getRecord().partialUrl).toBe("/home") expect(latestHistoryEntry.id).not.toBe("mutated") }) test("Router.getCurrentRouteHistoryEntryId returns the latest history entry id", () => { const router = createRouter({ url: "/home" }) expect(router.getCurrentRouteHistoryEntryId()).toBe(router.getCurrentRouteHistoryEntry().id) }) test("Router.isRoaming reflects whether the current position is behind the tail", () => { const router = createRouter({ url: "/home" }) expect(router.isRoaming()).toBe(false) router.navigateTo({ url: "/projects" }) router.roamingBackwardBy({ step: 1 }) expect(router.isRoaming()).toBe(true) }) test("Router.getMaxRoamingForwardSteps reports how many entries exist ahead of the current position", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.navigateTo({ url: "/projects/alpha" }) router.roamingBackwardBy({ step: 1 }) expect(router.getMaxRoamingForwardSteps()).toBe(1) }) test("Router.getMaxRoamingBackwardSteps reports how many entries exist behind the current position", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.navigateTo({ url: "/projects/alpha" }) router.roamingBackwardBy({ step: 1 }) expect(router.getMaxRoamingBackwardSteps()).toBe(1) }) test("Router.canRoamingForward reports whether forward roaming is possible", () => { const router = createRouter({ url: "/home" }) expect(router.canRoamingForward()).toBe(false) router.navigateTo({ url: "/projects" }) router.roamingBackwardBy({ step: 1 }) expect(router.canRoamingForward()).toBe(true) }) test("Router.canRoamingBackward reports whether backward roaming is possible", () => { const router = createRouter({ url: "/home" }) expect(router.canRoamingBackward()).toBe(false) router.navigateTo({ url: "/projects" }) expect(router.canRoamingBackward()).toBe(true) }) test("Router.refresh emits a refresh history entry without moving away from the current route", () => { const router = createRouter({ url: "/home" }) const eventHistoryTypes: string[] = [] router.eventManager.subscribe("history", (historyEntry) => { eventHistoryTypes.push(historyEntry.type) }) router.refresh({}) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.REFRESH) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/home") expect(historyEntry.actions[0]?.type).toBe(ROUTE_ACTION_TYPE.MOVE) expect(eventHistoryTypes.at(-1)).toBe(ROUTE_TYPE.REFRESH) }) test("Router.replaceTo replaces the current route in place", () => { const router = createRouter({ url: "/home" }) router.replaceTo({ url: "/projects" }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.REPLACE_TO) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/projects") expect(historyEntry.actions.map((action) => action.type)).toEqual([ROUTE_ACTION_TYPE.REPLACE]) }) test("Router.switchTo moves to the tail trims forward history and replaces the tail route", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.navigateTo({ url: "/projects/alpha" }) router.roamingBackwardBy({ step: 1 }) router.switchTo({ url: "/settings" }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.SWITCH_TO) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/settings") expect(historyEntry.actions.map((action) => action.type)).toEqual([ ROUTE_ACTION_TYPE.MOVE, ROUTE_ACTION_TYPE.TRIM, ROUTE_ACTION_TYPE.REPLACE, ]) }) test("Router.navigateTo trims forward history and appends a new route entry", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.roamingBackwardBy({ step: 1 }) router.navigateTo({ url: "/settings" }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.NAVIGATE_TO) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/settings") expect(historyEntry.actions.map((action) => action.type)).toEqual([ ROUTE_ACTION_TYPE.TRIM, ROUTE_ACTION_TYPE.PUSH, ]) }) test("Router.navigateSearch appends a new route entry with updated search", () => { const router = createRouter({ url: "/projects" }) router.navigateSearch({ search: { page: "2", filter: "active" } }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.NAVIGATE_SEARCH) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/projects?page=2&filter=active") }) test("Router.navigateHash appends a new route entry with updated hash", () => { const router = createRouter({ url: "/projects" }) router.navigateHash({ hash: { panel: "files" } }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.NAVIGATE_HASH) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/projects#panel=files") }) test("Router.redirectTo trims forward history and replaces the current route", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.roamingBackwardBy({ step: 1 }) router.redirectTo({ url: "/settings" }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.REDIRECT_TO) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/settings") expect(historyEntry.actions.map((action) => action.type)).toEqual([ ROUTE_ACTION_TYPE.TRIM, ROUTE_ACTION_TYPE.REPLACE, ]) }) test("Router.redirectSearch replaces the current route with updated search", () => { const router = createRouter({ url: "/projects?page=1" }) router.redirectSearch({ search: { page: "2", mode: "grid" } }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.REDIRECT_SEARCH) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/projects?page=2&mode=grid") }) test("Router.redirectHash replaces the current route with updated hash", () => { const router = createRouter({ url: "/projects#panel=list" }) router.redirectHash({ hash: { panel: "files", tab: "info" } }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.REDIRECT_HASH) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/projects#panel=files&tab=info") }) test("Router.roamingBy moves relative to the current position without trimming history", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.navigateTo({ url: "/settings" }) router.roamingBy({ step: -2 }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.ROAMING_BY) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/home") expect(router.canRoamingForward()).toBe(true) }) test("Router.roamingForwardBy validates step input and moves forward without trimming history", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.roamingBackwardBy({ step: 1 }) expect(() => router.roamingForwardBy({ step: -1 })).toThrow("Step must be a non-negative integer") router.roamingForwardBy({ step: 1 }) expect(router.getCurrentRouteHistoryEntry().type).toBe(ROUTE_TYPE.ROAMING_FORWARD_BY) expect(router.getCurrentRouteHistoryEntry().to.route.getRecord().partialUrl).toBe("/projects") }) test("Router.roamingBackwardBy validates step input and moves backward without trimming history", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) expect(() => router.roamingBackwardBy({ step: -1 })).toThrow( "Step must be a non-negative integer", ) router.roamingBackwardBy({ step: 1 }) expect(router.getCurrentRouteHistoryEntry().type).toBe(ROUTE_TYPE.ROAMING_BACKWARD_BY) expect(router.getCurrentRouteHistoryEntry().to.route.getRecord().partialUrl).toBe("/home") }) test("Router.roamingTo roams to the first matching entry across the whole history", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.navigateTo({ url: "/settings" }) router.roamingTo({ predicate: (routeEntry) => routeEntry.route.getRecord().pathname === "/projects", }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.ROAMING_TO) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/projects") }) test("Router.roamingForwardTo roams only within entries ahead of the current position", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.navigateTo({ url: "/settings" }) router.roamingBackwardBy({ step: 2 }) router.roamingForwardTo({ predicate: (routeEntry) => routeEntry.route.getRecord().pathname === "/settings", }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.ROAMING_FORWARD_TO) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/settings") }) test("Router.roamingBackwardTo roams only within entries behind the current position", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.navigateTo({ url: "/settings" }) router.roamingBackwardTo({ predicate: (routeEntry) => routeEntry.route.getRecord().pathname === "/home", }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.ROAMING_BACKWARD_TO) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/home") }) test("Router.goBy moves relative to the current position and trims forward history", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.navigateTo({ url: "/settings" }) router.goBy({ step: -1 }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.GO_BY) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/projects") expect(router.canRoamingForward()).toBe(false) }) test("Router.goForwardBy validates step input and trims forward history at the target entry", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.navigateTo({ url: "/settings" }) router.roamingBackwardBy({ step: 2 }) expect(() => router.goForwardBy({ step: -1 })).toThrow("Step must be a non-negative integer") router.goForwardBy({ step: 1 }) expect(router.getCurrentRouteHistoryEntry().type).toBe(ROUTE_TYPE.GO_FORWARD_BY) expect(router.getCurrentRouteHistoryEntry().to.route.getRecord().partialUrl).toBe("/projects") expect(router.canRoamingForward()).toBe(false) }) test("Router.goBackwardBy validates step input and trims forward history at the target entry", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.navigateTo({ url: "/settings" }) expect(() => router.goBackwardBy({ step: -1 })).toThrow("Step must be a non-negative integer") router.goBackwardBy({ step: 2 }) expect(router.getCurrentRouteHistoryEntry().type).toBe(ROUTE_TYPE.GO_BACKWARD_BY) expect(router.getCurrentRouteHistoryEntry().to.route.getRecord().partialUrl).toBe("/home") expect(router.canRoamingForward()).toBe(false) }) test("Router.goTo moves to the first matching entry across the whole history and trims forward entries", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.navigateTo({ url: "/settings" }) router.goTo({ predicate: (routeEntry) => routeEntry.route.getRecord().pathname === "/projects" }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.GO_TO) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/projects") expect(router.canRoamingForward()).toBe(false) }) test("Router.goForwardTo moves to a matching later entry and trims any following entries", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.navigateTo({ url: "/settings" }) router.navigateTo({ url: "/about" }) router.roamingBackwardBy({ step: 3 }) router.goForwardTo({ predicate: (routeEntry) => routeEntry.route.getRecord().pathname === "/settings", }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.GO_FORWARD_TO) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/settings") expect(router.canRoamingForward()).toBe(false) }) test("Router.goBackwardTo moves to a matching earlier entry and trims any following entries", () => { const router = createRouter({ url: "/home" }) router.navigateTo({ url: "/projects" }) router.navigateTo({ url: "/settings" }) router.goBackwardTo({ predicate: (routeEntry) => routeEntry.route.getRecord().pathname === "/home", }) const historyEntry = router.getCurrentRouteHistoryEntry() expect(historyEntry.type).toBe(ROUTE_TYPE.GO_BACKWARD_TO) expect(historyEntry.to.route.getRecord().partialUrl).toBe("/home") expect(router.canRoamingForward()).toBe(false) }) test("Router.roamingBackwardByOrRedirectTo roams backward when possible and otherwise redirects", () => { const withHistory = createRouter({ url: "/home" }) withHistory.navigateTo({ url: "/projects" }) withHistory.roamingBackwardByOrRedirectTo({ step: 1, url: "/fallback" }) expect(withHistory.getCurrentRouteHistoryEntry().type).toBe(ROUTE_TYPE.ROAMING_BACKWARD_BY) expect(withHistory.getCurrentRouteHistoryEntry().to.route.getRecord().partialUrl).toBe("/home") const withoutHistory = createRouter({ url: "/home" }) withoutHistory.roamingBackwardByOrRedirectTo({ step: 1, url: "/fallback" }) expect(withoutHistory.getCurrentRouteHistoryEntry().type).toBe(ROUTE_TYPE.REDIRECT_TO) expect(withoutHistory.getCurrentRouteHistoryEntry().to.route.getRecord().partialUrl).toBe( "/fallback", ) }) test("Router.goBackwardByOrRedirectTo goes backward when possible and otherwise redirects", () => { const withHistory = createRouter({ url: "/home" }) withHistory.navigateTo({ url: "/projects" }) withHistory.goBackwardByOrRedirectTo({ step: 1, url: "/fallback" }) expect(withHistory.getCurrentRouteHistoryEntry().type).toBe(ROUTE_TYPE.GO_BACKWARD_BY) expect(withHistory.getCurrentRouteHistoryEntry().to.route.getRecord().partialUrl).toBe("/home") const withoutHistory = createRouter({ url: "/home" }) withoutHistory.goBackwardByOrRedirectTo({ step: 1, url: "/fallback" }) expect(withoutHistory.getCurrentRouteHistoryEntry().type).toBe(ROUTE_TYPE.REDIRECT_TO) expect(withoutHistory.getCurrentRouteHistoryEntry().to.route.getRecord().partialUrl).toBe( "/fallback", ) }) test("createRouter creates a Router instance", () => { const router = createRouter({ url: "/home" }) expect(router).toBeInstanceOf(Router) expect(router.getCurrentRouteHistoryEntry().to.route.getRecord().partialUrl).toBe("/home") })