diff --git a/frontend/src/Router.test.tsx b/frontend/src/Router.test.tsx new file mode 100644 index 000000000..bd6114807 --- /dev/null +++ b/frontend/src/Router.test.tsx @@ -0,0 +1,79 @@ +import "@testing-library/jest-dom/vitest"; +import { cleanup, render, screen, waitFor } from "@testing-library/react"; +import type { ReactNode } from "react"; +import Router from "src/Router"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +const { authState } = vi.hoisted(() => ({ authState: { authenticated: true } })); + +vi.mock("src/context", () => ({ useAuthState: () => authState })); +vi.mock("src/hooks", () => ({ + useHealth: () => ({ data: { status: "OK", setup: true }, isLoading: false, isError: false }), +})); +vi.mock("src/components", () => ({ + Page: ({ children }: { children: ReactNode }) => children, + SiteContainer: ({ children }: { children: ReactNode }) => children, + SiteHeader: () => null, + SiteMenu: () => null, + SiteFooter: () => null, + LoadingPage: () =>
Loading
, + Unhealthy: () =>
Unhealthy
, + ErrorNotFound: () =>

Not found

, +})); +vi.mock("src/pages/Dashboard", () => ({ default: () =>

Dashboard

})); +vi.mock("src/pages/Login", () => ({ default: () =>

Login

})); +vi.mock("src/pages/Nginx/ProxyHosts", () => ({ default: () =>

Proxy hosts

})); + +describe("Router", () => { + beforeEach(() => { + authState.authenticated = true; + window.history.replaceState(null, "", "/"); + }); + + afterEach(() => { + cleanup(); + vi.restoreAllMocks(); + }); + + it.each(["/login", "/login/", "/login?next=/users#form"])( + "redirects an authenticated visit to %s to the dashboard", + async (path) => { + window.history.replaceState(null, "", path); + const replaceState = vi.spyOn(window.history, "replaceState"); + render(); + + expect(await screen.findByRole("heading", { name: "Dashboard" })).toBeVisible(); + expect(window.location.pathname).toBe("/"); + expect(window.location.search).toBe(""); + expect(window.location.hash).toBe(""); + expect(replaceState).toHaveBeenCalledWith(expect.anything(), "", "/"); + }, + ); + + it("shows the login form when signed out and redirects after sign-in", async () => { + authState.authenticated = false; + window.history.replaceState(null, "", "/login"); + const { rerender } = render(); + + expect(await screen.findByRole("heading", { name: "Login" })).toBeVisible(); + expect(window.location.pathname).toBe("/login"); + + authState.authenticated = true; + rerender(); + + expect(await screen.findByRole("heading", { name: "Dashboard" })).toBeVisible(); + await waitFor(() => expect(window.location.pathname).toBe("/")); + }); + + it.each([ + ["/", "Dashboard"], + ["/nginx/proxy", "Proxy hosts"], + ["/unknown", "Not found"], + ])("preserves the existing route for %s", async (path, heading) => { + window.history.replaceState(null, "", path); + render(); + + expect(await screen.findByRole("heading", { name: heading })).toBeVisible(); + expect(window.location.pathname).toBe(path); + }); +}); diff --git a/frontend/src/Router.tsx b/frontend/src/Router.tsx index 6aa8f0894..d0ee7b18b 100644 --- a/frontend/src/Router.tsx +++ b/frontend/src/Router.tsx @@ -1,5 +1,5 @@ import { lazy, Suspense } from "react"; -import { BrowserRouter, Route, Routes } from "react-router-dom"; +import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; import { ErrorNotFound, LoadingPage, @@ -61,6 +61,7 @@ function Router() { }> } /> + } /> } /> } /> } />