import type { ReactNode } from "react"; import { MantineProvider } from "@mantine/core"; import { QueryClientProvider } from "@tanstack/react-query"; import { fireEvent, render, screen } from "@testing-library/react"; import { cssVariablesResolver, theme } from "@wealthledger/ui/theme"; import { http, HttpResponse } from "msw"; import { type InitialEntry, Link, MemoryRouter } from "react-router"; import { describe, expect, it } from "vitest"; import { AuthProvider } from "@/features/auth"; import { createTestQueryClient } from "@/test/query"; import { server } from "@/test/server"; import { AppRoutes } from "./AppRoutes"; function renderRoutes(path: InitialEntry, controls?: ReactNode) { return render( {controls} , ); } describe("AppRoutes", () => { it.each([ ["/", "Вход"], ["/login", "Вход"], ["/register", "Регистрация"], ["/accounts", "Вход"], ["/accounts/demo", "Вход"], ["/accounts/demo/operations", "Вход"], ["/settings", "Вход"], ["/cards?month=2026-08", "Вход"], ["/unknown", "Страница не найдена"], ])("renders %s with its page title", async (path, title) => { renderRoutes(path); expect( await screen.findByRole("heading", { level: 1, name: title }), ).toBeInTheDocument(); expect(document.title).toBe(`${title} — WealthLedger`); }); it("redirects anonymous users from accounts", async () => { renderRoutes("/accounts"); expect( await screen.findByRole("heading", { level: 1, name: "Вход" }), ).toBeInTheDocument(); }); it.each([ { endpoint: "login", path: "/accounts", title: "Вход" }, { endpoint: "register", path: "/register", title: "Регистрация" }, { endpoint: "login", path: { pathname: "/login", state: { returnTo: "//example.com" } }, title: "Вход", }, { endpoint: "login", path: { pathname: "/login", state: { returnTo: 42 } }, title: "Вход" }, { endpoint: "login", path: { pathname: "/login", state: 42 }, title: "Вход" }, ])("authenticates via $endpoint and reaches the protected landing from $path", async ({ endpoint, path, title }) => { let credentials: unknown; server.use( http.post(`*/api/v1/auth/${endpoint}`, async ({ request }) => { credentials = await request.json(); return HttpResponse.json({ accessToken: "login-token", user: { email: "user@example.com", id: "user-1" }, }); }), ); renderRoutes(path); await screen.findByRole("heading", { level: 1, name: title }); fireEvent.change(screen.getByLabelText("Email"), { target: { value: " User@Example.com " }, }); fireEvent.change(screen.getByLabelText("Пароль"), { target: { value: "a secure password" }, }); if (endpoint === "register") { fireEvent.change(screen.getByLabelText("Подтвердите пароль"), { target: { value: "a secure password" }, }); } fireEvent.click(screen.getByRole("button", { name: title })); expect( await screen.findByRole("heading", { level: 2, name: "Нет инвестиционных счетов", }), ).toBeInTheDocument(); expect(document.title).toBe("Счета — WealthLedger"); expect(credentials).toEqual({ email: "user@example.com", password: "a secure password", }); }); it.each([ { isFailure: false, isNewDestination: false }, { isFailure: true, isNewDestination: false }, { isFailure: true, isNewDestination: true }, ])("finishes logout safely: failure=$isFailure, new destination=$isNewDestination", async ({ isFailure, isNewDestination }) => { const { promise: pendingLogout, resolve: finishLogout } = Promise.withResolvers(); server.use( http.post("*/api/v1/auth/refresh", () => HttpResponse.json({ accessToken: "test-token", user: { email: "user@example.com", id: "user-1" }, })), http.post("*/api/v1/auth/logout", async () => { await pendingLogout; return isFailure ? HttpResponse.error() : new HttpResponse(null, { status: 204 }); }), ); renderRoutes("/settings", Открыть настройки); fireEvent.click(await screen.findByRole("button", { name: "Выйти" })); await screen.findByRole("heading", { level: 1, name: "Вход" }); if (isNewDestination) fireEvent.click(screen.getByRole("link", { name: "Открыть настройки" })); fireEvent.change(screen.getByLabelText("Email"), { target: { value: "user@example.com" } }); fireEvent.change(screen.getByLabelText("Пароль"), { target: { value: "a secure password" } }); fireEvent.click(screen.getByRole("button", { name: "Вход" })); finishLogout(); const destination = isNewDestination ? { level: 1, name: "Настройки" } : { level: 2, name: "Нет инвестиционных счетов" }; expect(await screen.findByRole("heading", destination)).toBeInTheDocument(); expect(document.title).toBe(isNewDestination ? "Настройки — WealthLedger" : "Счета — WealthLedger"); }); it("returns after login to cards with the selected month without T-Bank", async () => { server.use( http.get("*/api/v1/bank-cards/categories", () => HttpResponse.json([])), http.post("*/api/v1/auth/login", () => HttpResponse.json({ accessToken: "test", user: { email: "user@example.com", id: "user-1" }, }), ), http.get("*/api/v1/bank-cards", () => HttpResponse.json([])), http.get("*/api/v1/bank-cards/summary", () => HttpResponse.json( { cardId: null, categories: [], expenses: { currency: "RUB", value: "0" }, month: "2026-08", net: { currency: "RUB", value: "0" }, refunds: { currency: "RUB", value: "0" }, }, ), ), ); renderRoutes("/cards?month=2026-08"); await screen.findByRole("heading", { name: "Вход" }); fireEvent.change(screen.getByLabelText("Email"), { target: { value: "user@example.com" }, }); fireEvent.change(screen.getByLabelText("Пароль"), { target: { value: "a secure password" }, }); fireEvent.click(screen.getByRole("button", { name: "Вход" })); await screen.findByRole("heading", { name: "Добавьте первую карту" }); expect(screen.getByLabelText("Период")).toHaveTextContent( "01.08.2026 – 31.08.2026", ); expect(document.title).toBe("Карты — WealthLedger"); }); it("offers a retry instead of the login form while the server is unreachable, then restores the route", async () => { let isOutage = true; server.use( http.post("*/api/v1/auth/refresh", () => isOutage ? new HttpResponse(null, { status: 502 }) : HttpResponse.json({ accessToken: "test-token", user: { email: "user@example.com", id: "user-1" }, }), ), ); renderRoutes("/accounts"); expect(await screen.findByRole("alert")).toHaveTextContent("Не удалось подключиться"); expect(screen.queryByRole("heading", { name: "Вход" })).not.toBeInTheDocument(); isOutage = false; fireEvent.click(screen.getByRole("button", { name: "Повторить" })); expect( await screen.findByRole("heading", { level: 2, name: "Нет инвестиционных счетов" }), ).toBeVisible(); }); it("lazy-loads the protected shell for an authenticated accounts route", async () => { server.use( http.post("*/api/v1/auth/refresh", () => HttpResponse.json({ accessToken: "test-token", user: { email: "user@example.com", id: "user-1" }, }), ), ); renderRoutes("/accounts"); expect( await screen.findByRole("heading", { level: 2, name: "Нет инвестиционных счетов", }), ).toBeInTheDocument(); expect( screen.getAllByRole("navigation", { name: "Основная навигация" }), ).toHaveLength(2); expect(screen.queryByRole("banner")).not.toBeInTheDocument(); }); });