241 lines
6.5 KiB
TypeScript
241 lines
6.5 KiB
TypeScript
import { MantineProvider } from "@mantine/core";
|
|
import { fireEvent, render, screen, within } from "@testing-library/react";
|
|
import { MemoryRouter, Route, Routes } from "react-router";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import type * as BrokerageEvents from "@/entities/brokerage-event";
|
|
|
|
import { useBrokerageEvents } from "@/entities/brokerage-event";
|
|
import { defaultPeriod } from "@/shared/lib";
|
|
|
|
import { AccountEventsPage } from "./AccountEventsPage";
|
|
|
|
const { refresh } = vi.hoisted(() => ({ refresh: vi.fn() }));
|
|
vi.mock("@/features/tbank-connection", () => ({
|
|
useScreenSync: () => ({
|
|
refresh,
|
|
refreshing: false,
|
|
retry: refresh,
|
|
sourceError: false,
|
|
transportError: false,
|
|
}),
|
|
}));
|
|
vi.mock("@/entities/brokerage-event", async importOriginal => ({
|
|
...(await importOriginal<typeof BrokerageEvents>()),
|
|
useBrokerageEvents: vi.fn(),
|
|
}));
|
|
|
|
const query = vi.mocked(useBrokerageEvents);
|
|
|
|
function renderPage() {
|
|
return render(
|
|
<MantineProvider>
|
|
<MemoryRouter>
|
|
<AccountEventsPage />
|
|
</MemoryRouter>
|
|
</MantineProvider>,
|
|
);
|
|
}
|
|
|
|
describe("AccountEventsPage", () => {
|
|
beforeEach(() => {
|
|
refresh.mockClear();
|
|
|
|
query.mockReturnValue(
|
|
{
|
|
data: {
|
|
accountId: "account",
|
|
calculatedAt: "2026-08-14T00:00:00Z",
|
|
events: [],
|
|
},
|
|
isError: false,
|
|
isLoading: false,
|
|
meta: {
|
|
availability: "available",
|
|
completeness: "complete",
|
|
lastAttempt: { finishedAt: null, problems: [], startedAt: null, state: "none" },
|
|
},
|
|
refetch: vi.fn(),
|
|
} as never,
|
|
);
|
|
});
|
|
|
|
it("passes the selected route account to the query", () => {
|
|
render(
|
|
<MantineProvider>
|
|
<MemoryRouter initialEntries={["/accounts/selected-account/events"]}>
|
|
<Routes>
|
|
<Route
|
|
element={<AccountEventsPage />}
|
|
path="/accounts/:accountId/events"
|
|
/>
|
|
</Routes>
|
|
</MemoryRouter>
|
|
</MantineProvider>,
|
|
);
|
|
expect(query).toHaveBeenCalledWith("selected-account", {
|
|
from: defaultPeriod()[0],
|
|
to: defaultPeriod()[1],
|
|
});
|
|
});
|
|
|
|
it("keeps the loading fallback when no result or account is available", () => {
|
|
query.mockReturnValue(
|
|
{
|
|
data: undefined,
|
|
isError: false,
|
|
isLoading: false,
|
|
meta: {
|
|
availability: "available",
|
|
completeness: "complete",
|
|
lastAttempt: { finishedAt: null, problems: [], startedAt: null, state: "none" },
|
|
},
|
|
refetch: vi.fn(),
|
|
} as never,
|
|
);
|
|
renderPage();
|
|
expect(query).toHaveBeenCalledWith("", {
|
|
from: defaultPeriod()[0],
|
|
to: defaultPeriod()[1],
|
|
});
|
|
expect(screen.getByRole("status", { name: "Загрузка" })).toHaveAttribute(
|
|
"aria-busy",
|
|
"true",
|
|
);
|
|
});
|
|
|
|
it("renders events and marks an offer without an amount", () => {
|
|
query.mockReturnValue(
|
|
{
|
|
data: {
|
|
accountId: "account",
|
|
calculatedAt: "2026-08-14T00:00:00Z",
|
|
events: [
|
|
{
|
|
date: "2026-08-20",
|
|
dateKind: "event",
|
|
displayName: "ОФЗ 26238",
|
|
figi: "FIGI",
|
|
instrumentUid: "UID",
|
|
isin: "RU000A0JX0J2",
|
|
logoName: "ofz.png",
|
|
projectedAmount: { currency: "RUB", value: "1250" },
|
|
ticker: "OFZ",
|
|
type: "coupon",
|
|
},
|
|
{
|
|
date: "2026-08-25",
|
|
dateKind: "event",
|
|
displayName: "ОФЗ 26238",
|
|
figi: "FIGI",
|
|
instrumentUid: "UID",
|
|
isin: null,
|
|
logoName: null,
|
|
projectedAmount: null,
|
|
ticker: "OFZ",
|
|
type: "offer",
|
|
},
|
|
],
|
|
},
|
|
isError: false,
|
|
isLoading: false,
|
|
meta: {
|
|
availability: "available",
|
|
completeness: "complete",
|
|
lastAttempt: { finishedAt: null, problems: [], startedAt: null, state: "none" },
|
|
},
|
|
refetch: vi.fn(),
|
|
} as never,
|
|
);
|
|
renderPage();
|
|
expect(screen.getByText("1 250 ₽")).toBeInTheDocument();
|
|
expect(screen.getByText("Сумма неизвестна")).toBeInTheDocument();
|
|
expect(screen.getByText("Купон · ОФЗ 26238")).toBeVisible();
|
|
expect(screen.getByText("Оферта · ОФЗ 26238")).toBeVisible();
|
|
expect(screen.getByText("20.08.2026 · RU000A0JX0J2")).toBeVisible();
|
|
expect(screen.getAllByText(/RU000A0JX0J2$/)).toHaveLength(1);
|
|
expect(screen.getByText("25.08.2026")).toBeVisible();
|
|
expect(screen.queryByText(/Дата события/)).not.toBeInTheDocument();
|
|
expect(screen.getAllByText("Прогноз")).toHaveLength(2);
|
|
const logo = screen.queryByAltText("");
|
|
expect(logo).toHaveAttribute(
|
|
"src",
|
|
"https://invest-brands.cdn-tinkoff.ru/ofzx160.png",
|
|
);
|
|
expect(screen.getAllByText("O")).toHaveLength(1);
|
|
if (!logo) throw new Error("Expected the event logo");
|
|
fireEvent.error(logo);
|
|
expect(screen.queryByAltText("")).not.toBeInTheDocument();
|
|
expect(screen.getAllByText("O")).toHaveLength(2);
|
|
});
|
|
|
|
it("renders empty and quiet error states", async () => {
|
|
const { rerender } = renderPage();
|
|
expect(
|
|
screen.getByText("За выбранный период событий не найдено"),
|
|
).toBeInTheDocument();
|
|
query.mockReturnValue({
|
|
data: undefined,
|
|
isError: true,
|
|
isLoading: false,
|
|
} as never);
|
|
rerender(
|
|
<MantineProvider>
|
|
<MemoryRouter>
|
|
<AccountEventsPage />
|
|
</MemoryRouter>
|
|
</MantineProvider>,
|
|
);
|
|
expect(await screen.findByRole("button", { name: "Сбросить" })).toBeVisible();
|
|
expect(screen.queryByRole("button", { name: "Повторить" })).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("shows a structural busy state before events are available", () => {
|
|
query.mockReturnValue(
|
|
{
|
|
data: undefined,
|
|
isError: false,
|
|
isLoading: true,
|
|
meta: {
|
|
availability: "available",
|
|
completeness: "complete",
|
|
lastAttempt: { finishedAt: null, problems: [], startedAt: null, state: "none" },
|
|
},
|
|
refetch: vi.fn(),
|
|
} as never,
|
|
);
|
|
|
|
renderPage();
|
|
|
|
const status = screen.getByRole("status", { name: "Загрузка" });
|
|
expect(status).toHaveAttribute("aria-busy", "true");
|
|
expect(within(status).queryByRole("button")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("keeps a null events envelope loading instead of an empty period", () => {
|
|
query.mockReturnValue(
|
|
{
|
|
data: null,
|
|
isError: false,
|
|
isLoading: false,
|
|
meta: {
|
|
availability: "available",
|
|
completeness: "complete",
|
|
lastAttempt: { finishedAt: null, problems: [], startedAt: null, state: "none" },
|
|
},
|
|
refetch: vi.fn(),
|
|
} as never,
|
|
);
|
|
|
|
renderPage();
|
|
|
|
expect(screen.getByRole("status", { name: "Загрузка" })).toHaveAttribute(
|
|
"aria-busy",
|
|
"true",
|
|
);
|
|
expect(
|
|
screen.queryByText("За выбранный период событий не найдено"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
});
|