Files
frontend/src/pages/account-operations/ui/AccountOperationsPage/AccountOperationsPage.test.tsx
T
ksv741 b987fd8e78
Frontend security / security (push) Successful in 9s
Frontend verify / verify (push) Successful in 5m10s
Conventional commits / commits (pull_request) Successful in 25s
Frontend security / security (pull_request) Successful in 9s
Frontend verify / verify (pull_request) Successful in 5m10s
refactor: colocate frontend component files
2026-09-23 08:34:58 +03:00

304 lines
8.6 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 { MantineProvider } from "@mantine/core";
import { QueryClientProvider } from "@tanstack/react-query";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { HttpResponse, http } from "msw";
import { MemoryRouter, Route, Routes } from "react-router";
import { describe, expect, it, vi } from "vitest";
import {
type BrokerageOperation,
LedgerEntryRow,
} from "@/entities/broker-operation";
import { configureAuthClient } from "@/shared/api";
import { createTestQueryClient } from "@/test/query";
import { server } from "@/test/server";
import { AccountOperationsPage } from "./AccountOperationsPage";
const operation: BrokerageOperation = {
id: "operation-1",
occurredAt: "2026-08-14T10:00:00Z",
type: "OPERATION_TYPE_BUY",
category: "trade",
description: "Покупка акций",
figi: null,
instrumentUid: null,
ticker: "SBER",
instrumentType: "share",
logoName: null,
payment: null,
quantity: "2",
price: { currency: "RUB", value: "100" },
accruedInterest: null,
commission: { currency: "RUB", value: "1" },
};
function configureTestAuth() {
configureAuthClient({
accessToken: () => "token",
refresh: async () => true,
clear: vi.fn(),
});
}
function renderPage() {
return render(
<QueryClientProvider client={createTestQueryClient()}>
<MantineProvider>
<MemoryRouter initialEntries={["/accounts/account-1/operations"]}>
<Routes>
<Route
path="/accounts/:accountId/operations"
element={<AccountOperationsPage />}
/>
</Routes>
</MemoryRouter>
</MantineProvider>
</QueryClientProvider>,
);
}
describe("LedgerEntryRow", () => {
it("marks cashless operations without rendering operation details", () => {
render(
<MantineProvider>
<MemoryRouter>
<LedgerEntryRow operation={operation} />
</MemoryRouter>
</MantineProvider>,
);
expect(screen.getByText("Без движения денег")).toBeInTheDocument();
expect(
screen.queryByRole("button", { name: "Показать детали" }),
).toBeNull();
expect(screen.queryByText("Цена: 100 ₽")).toBeNull();
expect(screen.queryByText("Количество: 2")).toBeNull();
});
it("labels a positive payment with its category type", () => {
render(
<MantineProvider>
<MemoryRouter>
<LedgerEntryRow
operation={{
...operation,
payment: { currency: "RUB", value: "15" },
}}
/>
</MemoryRouter>
</MantineProvider>,
);
expect(screen.getByText("Сделка")).toBeInTheDocument();
expect(screen.getByText("+15 ₽")).toBeInTheDocument();
});
it("labels a coupon income as Купон", () => {
render(
<MantineProvider>
<MemoryRouter>
<LedgerEntryRow
operation={{
...operation,
type: "OPERATION_TYPE_COUPON",
category: "income",
payment: { currency: "RUB", value: "8" },
}}
/>
</MemoryRouter>
</MantineProvider>,
);
expect(screen.getByText("Купон")).toBeInTheDocument();
expect(screen.getByText("+8 ₽")).toBeInTheDocument();
});
it("labels a dividend income as Дивиденд", () => {
render(
<MantineProvider>
<MemoryRouter>
<LedgerEntryRow
operation={{
...operation,
type: "OPERATION_TYPE_DIVIDEND",
category: "income",
payment: { currency: "RUB", value: "20" },
}}
/>
</MemoryRouter>
</MantineProvider>,
);
expect(screen.getByText("Дивиденд")).toBeInTheDocument();
expect(screen.getByText("+20 ₽")).toBeInTheDocument();
});
it("labels a fee as Комиссия", () => {
render(
<MantineProvider>
<MemoryRouter>
<LedgerEntryRow
operation={{
...operation,
type: "OPERATION_TYPE_SERVICE_FEE",
category: "fee",
payment: { currency: "RUB", value: "-3" },
}}
/>
</MemoryRouter>
</MantineProvider>,
);
expect(screen.getByText("Комиссия")).toBeInTheDocument();
expect(screen.getByText("−3 ₽")).toBeInTheDocument();
});
it("keeps a zero payment neutral", () => {
render(
<MantineProvider>
<MemoryRouter>
<LedgerEntryRow
operation={{
...operation,
payment: { currency: "RUB", value: "0" },
}}
/>
</MemoryRouter>
</MantineProvider>,
);
expect(screen.getByText("Без движения денег")).toBeInTheDocument();
expect(screen.queryByText("Сделка")).toBeNull();
});
it("uses an instrument initial when the ledger entry has no logo", () => {
render(
<MantineProvider>
<MemoryRouter>
<LedgerEntryRow operation={operation} />
</MemoryRouter>
</MantineProvider>,
);
expect(screen.getByText("S")).toBeInTheDocument();
});
});
describe("AccountOperationsPage", () => {
it("resets cursor pagination when filters change", async () => {
configureTestAuth();
const searches: string[] = [];
server.use(
http.get(
"*/api/v1/brokerage-accounts/:accountId/operations",
({ request }) => {
const url = new URL(request.url);
searches.push(url.search);
return HttpResponse.json({
accountId: "account-1",
items: [operation],
nextCursor: url.searchParams.get("cursor") ? null : "next-1",
hasNext: !url.searchParams.get("cursor"),
observedAt: "2026-08-14T00:00:00Z",
});
},
),
);
renderPage();
expect(
await screen.findByRole("heading", { name: "Журнал операций" }),
).toBeInTheDocument();
expect(
screen.queryByRole("link", { name: "К счету" }),
).not.toBeInTheDocument();
fireEvent.click(await screen.findByRole("button", { name: "Далее" }));
await waitFor(() => expect(searches.at(-1)).toContain("cursor=next-1"));
fireEvent.click(screen.getByRole("combobox", { name: "Категории" }));
fireEvent.click(await screen.findByText("Налог"));
fireEvent.click(screen.getByRole("button", { name: "Применить" }));
await waitFor(() => expect(searches.at(-1)).toContain("categories=tax"));
expect(searches.at(-1)).not.toContain("cursor=");
});
it("does not refetch when draft changes without apply", async () => {
configureTestAuth();
const searches: string[] = [];
server.use(
http.get(
"*/api/v1/brokerage-accounts/:accountId/operations",
({ request }) => {
const url = new URL(request.url);
searches.push(url.search);
return HttpResponse.json({
accountId: "account-1",
items: [operation],
nextCursor: null,
hasNext: false,
observedAt: "2026-08-14T00:00:00Z",
});
},
),
);
renderPage();
expect(
await screen.findByRole("heading", { name: "Журнал операций" }),
).toBeInTheDocument();
await waitFor(() => expect(searches.length).toBeGreaterThan(0));
const countBefore = searches.length;
fireEvent.click(screen.getByRole("combobox", { name: "Категории" }));
fireEvent.click(await screen.findByText("Налог"));
await new Promise((resolve) => setTimeout(resolve, 100));
expect(searches.length).toBe(countBefore);
expect(searches.some((s) => s.includes("categories=tax"))).toBe(false);
});
it("reset clears filters and refetches unfiltered", async () => {
configureTestAuth();
const searches: string[] = [];
server.use(
http.get(
"*/api/v1/brokerage-accounts/:accountId/operations",
({ request }) => {
const url = new URL(request.url);
searches.push(url.search);
return HttpResponse.json({
accountId: "account-1",
items: [operation],
nextCursor: null,
hasNext: false,
observedAt: "2026-08-14T00:00:00Z",
});
},
),
);
renderPage();
expect(
await screen.findByRole("heading", { name: "Журнал операций" }),
).toBeInTheDocument();
fireEvent.click(screen.getByRole("combobox", { name: "Категории" }));
fireEvent.click(await screen.findByText("Налог"));
fireEvent.click(screen.getByRole("button", { name: "Применить" }));
await waitFor(() =>
expect(searches.some((s) => s.includes("categories=tax"))).toBe(true),
);
fireEvent.click(screen.getByRole("button", { name: "Сбросить" }));
await waitFor(() => expect(searches.at(-1)).not.toContain("categories="));
expect(screen.getByRole("button", { name: "Применить" })).toBeDisabled();
});
it("renders an error state", async () => {
configureTestAuth();
server.use(
http.get("*/api/v1/brokerage-accounts/:accountId/operations", () =>
HttpResponse.json({ code: "TBANK_UNAVAILABLE" }, { status: 503 }),
),
);
renderPage();
expect(
await screen.findByText("Не удалось загрузить журнал"),
).toBeInTheDocument();
});
});