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()), useBrokerageEvents: vi.fn(), })); const query = vi.mocked(useBrokerageEvents); function renderPage() { return render( , ); } describe("AccountEventsPage", () => { beforeEach(() => { refresh.mockClear(); query.mockReturnValue( { data: { accountId: "account", calculatedAt: "2026-08-14T00:00:00Z", events: [], window: { from: "2026-08-14", to: "2026-09-13" }, }, 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( } path="/accounts/:accountId/events" /> , ); 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", }, ], window: { from: "2026-08-14", to: "2026-09-13" }, }, 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 error states with retry", async () => { const { rerender } = renderPage(); expect( screen.getByText("На выбранный период предстоящих событий не найдено"), ).toBeInTheDocument(); query.mockReturnValue({ data: undefined, error: { code: "TBANK_UNAVAILABLE", status: 503 }, isError: true, isFetching: false, isLoading: false, refetch: vi.fn(), } as never); rerender( , ); expect(await screen.findByRole("button", { name: "Сбросить" })).toBeVisible(); expect(screen.getByRole("button", { name: "Повторить" })).toBeVisible(); }); 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(); }); it("groups same-day instrument events only in all-account mode", () => { const events = [ { account: { id: "broker", name: "Брокерский" }, date: "2026-08-20", dateKind: "event", displayName: "ОФЗ 26238", figi: "FIGI", instrumentUid: "UID", isin: "RU000A0JX0J2", logoName: null, projectedAmount: { currency: "RUB", value: "200" }, ticker: "OFZ", type: "coupon", }, { account: { id: "iis", name: "ИИС" }, date: "2026-08-20", dateKind: "event", displayName: "ОФЗ 26238", figi: "FIGI", instrumentUid: "UID", isin: "RU000A0JX0J2", logoName: null, projectedAmount: { currency: "RUB", value: "100" }, ticker: "OFZ", type: "coupon", }, ] as const; query.mockReturnValue({ data: { accountId: "all", calculatedAt: "2026-08-14T00:00:00Z", events: [...events], window: { from: "2026-08-14", to: "2026-09-13" }, }, isError: false, isLoading: false, meta: { availability: "available", completeness: "complete", lastAttempt: { finishedAt: null, problems: [], startedAt: null, state: "none", }, }, refetch: vi.fn(), } as never); render( } path="/accounts/:accountId/events" /> , ); expect(screen.getByText("Купон · ОФЗ 26238")).toBeInTheDocument(); expect(screen.getByText("300 ₽")).toBeInTheDocument(); const accounts = within(screen.getByRole("list", { name: /Купон · ОФЗ 26238, 20\.08\.2026, 300 ₽/, })); expect(accounts.getByText("Брокерский")).toBeInTheDocument(); expect(accounts.getByText("ИИС")).toBeInTheDocument(); }); });