Files
mechanic-pwa/driver-pwa/frontend/src/pages/BalancePage.test.tsx
T
tremble7681andClaude Opus 5 0a09570d98 refactor(pwa): штрафы живут только на главном экране
Отдельный список штрафов удалён: два списка одного и того же в разных местах
сбивали с толку. Фильтр «Штрафы» внизу главного экрана теперь показывает сами
постановления, а не проводки по ним, и ведёт в карточку с фотографией.

Разница между постановлением и проводкой здесь принципиальная: штраф, который
водитель оплатил сам, парк не выставлял — проводки нет, и в выписке он бы не
появился вовсе (Морозов, штраф от 16.08). Строка «Долг по штрафам» вверху теперь
включает этот фильтр, а не уводит на другой экран.

Период действует и на штрафы: календарь над списком не должен стоять, ничего не
меняя.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 19:40:06 +10:00

147 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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(
<QueryClientProvider client={qc}>
<MemoryRouter>
<Routes>
<Route path="/" element={<BalancePage />} />
<Route path="/topup" element={<p>экран пополнения</p>} />
</Routes>
</MemoryRouter>
</QueryClientProvider>
);
}
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();
});
});