import { describe, it, expect, vi } from "vitest"; import { render, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { MemoryRouter, Route, Routes } from "react-router-dom"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; // Ответ как у настоящего водителя: статьи 1С, итог отдельным числом. vi.mock("@/api/driver", () => ({ getBalance: vi.fn(async () => ({ accounts: [ { bucket: "Долг аренда", balance: -3050, payable: true }, { bucket: "Долг по штрафам", balance: -5156, payable: true }, { bucket: "Страховка", balance: 200, payable: false }, ], total: -8976.25, synced_at: null, pending_topup: 1500, })), getFines: vi.fn(async () => ({ items: [{ id: 12, number: "18810525260813035846", date: "2026-08-13", article: "12.09.2 - Превышение скорости", address: "", car_number: "А947УТ125", amount: 618.75, settled: "owed" as const, cancelled: false, has_photo: true, }], open_count: 1, debt: -5156, })), getStatement: vi.fn(async () => ({ items: [ { date: "2026-08-17", title: "Аренда автомобиля", description: "ТС А947УТ125", amount: -2600, kind: "charges" }, { date: "2026-08-17", title: "Пополнение баланса", description: "", amount: 1000, kind: "payments" }, ], from: "2026-08-11", to: "2026-08-18", kind: "all", })), })); import { BalancePage } from "./BalancePage"; import * as driver from "@/api/driver"; function wrap() { const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } }); return render( } /> экран пополнения

} />
); } describe("главный экран", () => { it("сверху деньги: итог с сервера и строка «зачисляется»", async () => { wrap(); // Сумма статей дала бы −8 006, а в CRM у водителя −8 976,25. Показываем её. expect(await screen.findByText("−8 976,25 ₽")).toBeInTheDocument(); expect(screen.getByText("Зачисляется")).toBeInTheDocument(); expect(screen.getByText("1 500 ₽")).toBeInTheDocument(); }); it("снизу выписка за период", async () => { wrap(); expect(await screen.findByText("Аренда автомобиля")).toBeInTheDocument(); expect(screen.getAllByText(/17\.08\.2026/).length).toBe(2); // обе операции того дня expect(screen.getByRole("button", { name: "Все операции" })).toBeInTheDocument(); expect(driver.getStatement).toHaveBeenCalled(); }); it("фильтр перезапрашивает операции выбранного вида", async () => { wrap(); await userEvent.click(await screen.findByRole("button", { name: "Начисления" })); const calls = vi.mocked(driver.getStatement).mock.calls; expect(calls[calls.length - 1][2]).toBe("charges"); }); it("фильтр «Штрафы» показывает постановления, а не проводки", async () => { // Штраф, оплаченный водителем самостоятельно, парк не выставлял — проводки по // нему нет, и в выписке он бы не появился вовсе (Морозов, 18.08.2026). wrap(); await userEvent.click(await screen.findByRole("button", { name: "Штрафы" })); expect(await screen.findByText(/Превышение скорости/)).toBeInTheDocument(); expect(driver.getFines).toHaveBeenCalled(); }); it("аренда и штрафы видны сразу, остальные статьи — нет", async () => { wrap(); await screen.findByText("−8 976,25 ₽"); // Эти две водитель проверяет каждый день — они на экране. expect(screen.getByText("Долг аренда")).toBeInTheDocument(); expect(screen.getByText("Долг по штрафам")).toBeInTheDocument(); expect(screen.getByText("−3 050 ₽")).toBeInTheDocument(); // Редкие статьи на экран не лезут: для них есть шторка. expect(screen.queryByText("Страховка")).not.toBeInTheDocument(); }); it("аренда ведёт к оплате, а штрафы — в свой список", async () => { wrap(); await userEvent.click(await screen.findByRole("button", { name: /Долг аренда/ })); expect(await screen.findByText("экран пополнения")).toBeInTheDocument(); }); it("строка штрафов включает фильтр внизу, а не уводит на другой экран", async () => { wrap(); await userEvent.click(await screen.findByRole("button", { name: /Долг по штрафам/ })); expect(await screen.findByText(/Превышение скорости/)).toBeInTheDocument(); expect(driver.getFines).toHaveBeenCalled(); }); it("категории для выбора живут в шторке «Пополнить»", async () => { wrap(); await screen.findByText("−8 976,25 ₽"); await userEvent.click(screen.getByRole("button", { name: /^Пополнить$/ })); const sheet = await screen.findByRole("dialog", { name: "Куда пополнить" }); const inSheet = within(sheet); expect(inSheet.getByText("Долг аренда")).toBeInTheDocument(); expect(inSheet.getByText("Долг по штрафам")).toBeInTheDocument(); // «Страховка» платежом не закрывается — в списке выбора ей делать нечего. expect(inSheet.queryByText("Страховка")).not.toBeInTheDocument(); }); it("шторка закрывается тапом мимо", async () => { wrap(); await screen.findByText("−8 976,25 ₽"); await userEvent.click(screen.getByRole("button", { name: /^Пополнить$/ })); await userEvent.click(await screen.findByRole("button", { name: "Закрыть" })); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); }); it("отдельного раздела пополнений нет — они в выписке", async () => { // Раздел «Мои пополнения» показывал наши платежи и у большинства водителей был // пуст: 1С проводит пополнения сама, и они уже видны фильтром в выписке. wrap(); await screen.findByText("−8 976,25 ₽"); expect(screen.queryByText(/Мои пополнения/)).not.toBeInTheDocument(); expect(screen.getByRole("button", { name: "Пополнения" })).toBeInTheDocument(); }); it("баланс не загрузился — экран говорит об этом, а не рисует ноль", async () => { vi.mocked(driver.getBalance).mockRejectedValueOnce(new Error("boom")); wrap(); expect(await screen.findByRole("button", { name: /Обновить баланс/ })).toBeInTheDocument(); }); });