Files
mechanic-pwa/driver-pwa/frontend/src/pages/BalancePage.test.tsx
T
tremble7681andClaude Opus 5 3bdd5ce818 feat(pwa): переход в штраф из выписки, заметные даты, минус раздел пополнений
Дата в выписке и в списке штрафов встала первой строкой чернильным цветом: в
этих списках ищут «что было 12-го», а бледная подпись под заголовком для этого
не годилась.

Строка штрафа в выписке стала кнопкой в карточку — там фотография и подробности.
Остальные строки не притворяются ссылками.

Раздел «Мои пополнения» удалён вместе с кнопкой: он показывал только наши
платежи и у большинства водителей был пуст (1С проводит пополнения сама), а те
же операции видны в выписке фильтром «Пополнения».

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 17:58:28 +10:00

130 lines
6.5 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,
})),
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>} />
<Route path="/fines" 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("fines");
});
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();
});
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();
});
});