feat: unify upcoming event cards across pages

This commit is contained in:
2026-09-09 17:58:02 +03:00
parent 3017fc810e
commit 451400b63b
20 changed files with 313 additions and 140 deletions
+105
View File
@@ -10,6 +10,7 @@ import {
import { HttpResponse, http } from "msw";
import { MemoryRouter, Route, Routes } from "react-router";
import { describe, expect, it, vi } from "vitest";
import type { BrokerageEvent } from "@/entities/brokerage-event";
import { type Portfolio, portfolioQueryOptions } from "@/entities/portfolio";
import { configureAuthClient } from "@/shared/api";
import { createTestQueryClient } from "@/test/query";
@@ -637,6 +638,110 @@ describe("AccountPage states", () => {
expect(screen.getByText("−12 ₽")).toBeInTheDocument();
});
it("keeps future events ordered with calendar dates and projected rather than posted amounts", () => {
const event: BrokerageEvent = {
type: "coupon",
date: "2026-09-10",
dateKind: "event",
figi: null,
instrumentUid: null,
ticker: "DEMO",
displayName: "Облигация",
logoName: null,
isin: "RU000A0JX0J2",
projectedAmount: { currency: "RUB", value: "850.50" },
};
renderPage(
<AccountPageLoaded
{...loadedProps()}
recentEvents={{
state: "loaded",
to: "/accounts/account-1/events",
items: [
event,
{
...event,
type: "dividend",
date: "2026-09-11",
dateKind: "registry",
displayName: "Акция",
projectedAmount: { currency: "USD", value: "0" },
},
{
...event,
type: "maturity",
date: "2026-09-12",
projectedAmount: { currency: "RUB", value: "1000" },
},
{
...event,
type: "offer",
date: "2026-09-13",
projectedAmount: null,
},
],
}}
/>,
);
const section = screen.getByRole("region", { name: "Будущие события" });
const content = within(section);
const text = section.textContent ?? "";
const titles = [
"Купон · Облигация",
"Дивиденд · Акция",
"Погашение · Облигация",
"Оферта · Облигация",
];
for (const title of titles) expect(content.getByText(title)).toBeVisible();
expect(titles.map((title) => text.indexOf(title))).toEqual(
titles.map((title) => text.indexOf(title)).sort((a, b) => a - b),
);
expect(content.getByText("10 сент. 2026 г. · RU000A0JX0J2")).toBeVisible();
expect(content.getAllByText(/RU000A0JX0J2$/)).toHaveLength(4);
expect(content.queryByText(/Дата события/)).not.toBeInTheDocument();
expect(
content.getByText("11 сент. 2026 г. · Дата реестра · RU000A0JX0J2"),
).toBeVisible();
for (const amount of ["850,50 ₽", "0 USD", "1 000 ₽", "Сумма неизвестна"])
expect(content.getByText(amount)).toBeVisible();
expect(content.getAllByText("Ожидаемая сумма")).toHaveLength(4);
expect(content.queryByText("Без движения денег")).not.toBeInTheDocument();
expect(content.queryByText(/^\+/)).not.toBeInTheDocument();
expect(
content.getByRole("link", { name: "Открыть будущие события" }),
).toHaveAttribute("href", "/accounts/account-1/events");
});
it.each(["loading", "empty"] as const)(
"keeps the upcoming %s state and calendar action",
(state) => {
renderPage(
<AccountPageLoaded
{...loadedProps()}
recentEvents={{ state, to: "/accounts/account-1/events" }}
/>,
);
const content = within(
screen.getByRole("region", { name: "Будущие события" }),
);
if (state === "loading") {
const skeleton = content.getByRole("status", {
name: "Загрузка будущих событий",
});
expect(skeleton).toHaveAttribute("aria-busy", "true");
expect(within(skeleton).queryByRole("link")).not.toBeInTheDocument();
} else {
expect(
content.getByText("На ближайшие 30 дней событий не найдено."),
).toBeVisible();
}
expect(
content.getByRole("link", { name: "Открыть будущие события" }),
).toHaveAttribute("href", "/accounts/account-1/events");
},
);
it("keeps the portfolio visible when upcoming events fail", () => {
renderPage(
<AccountPageLoaded