refactor: simplify frontend state and mutation boundaries
Frontend verify / commits (pull_request) Successful in 31s
Frontend verify / security (pull_request) Successful in 11s
Frontend verify / verify (pull_request) Successful in 9m1s
Frontend verify / commits (push) Skipped
Frontend verify / security (push) Successful in 10s
Frontend verify / verify (push) Successful in 9m59s

This commit was merged in pull request #44.
This commit is contained in:
2026-09-28 20:01:37 +03:00
parent a4046dbe87
commit a7537da344
25 changed files with 414 additions and 547 deletions
@@ -102,6 +102,24 @@ const portfolio: Portfolio
totalsByCurrency: [{ currency: "RUB", value: "1234.5" }],
};
function position(
overrides: Partial<Portfolio["positions"][number]>,
): Portfolio["positions"][number] {
return {
absoluteReturn: null,
averagePurchasePrice: null,
currentValue: null,
displayName: "Инструмент",
figi: null,
isin: null,
logoName: null,
quantity: "1",
ticker: null,
type: "STOCK",
...overrides,
};
}
const emptyRecentOperations: AccountPageRecentOperations = {
state: "empty",
to: "/accounts/account-1/operations",
@@ -474,242 +492,106 @@ describe("AccountPage states", () => {
);
});
it("renders stock cards with logo fallback and signed unlabeled return", () => {
renderPage(
<AccountPageLoaded
{...loadedProps(
{
it.each(["STOCK", "BOND"] as const)(
"renders %s logos, fallbacks and signed returns including zero",
(type) => {
renderPage(
<AccountPageLoaded
{...loadedProps({
...portfolio,
positions: [
{
position({
absoluteReturn: { currency: "RUB", value: "3" },
averagePurchasePrice: null,
currentValue: { currency: "RUB", value: "5" },
displayName: "Абра",
figi: "FIGI-A",
isin: null,
logoName: null,
quantity: "1",
ticker: null,
type: "STOCK",
},
{
displayName: "С логотипом",
logoName: "dolg.png",
type,
}),
position({
absoluteReturn: { currency: "RUB", value: "2" },
displayName: "Без логотипа",
type,
}),
position({
absoluteReturn: { currency: "RUB", value: "0" },
averagePurchasePrice: null,
currentValue: { currency: "RUB", value: "7" },
displayName: "Кадабра",
figi: "FIGI-B",
isin: null,
logoName: null,
quantity: "1",
ticker: null,
type: "STOCK",
},
displayName: "Нулевой доход",
type,
}),
],
},
)}
/>,
);
expect(screen.getByText("А")).toBeInTheDocument();
expect(screen.getByText("+3 ₽")).toBeInTheDocument();
expect(screen.getByText("0 ₽")).toBeInTheDocument();
expect(screen.queryByText(/Доход:/)).not.toBeInTheDocument();
expect(screen.queryByRole("img", { name: "Абра" })).not.toBeInTheDocument();
});
})}
/>,
);
expect(screen.getByRole("img", { name: "С логотипом" })).toHaveAttribute(
"src", "https://invest-brands.cdn-tinkoff.ru/dolgx160.png",
);
expect(screen.getByText("Б")).toBeInTheDocument();
expect(screen.getByText("Н")).toBeInTheDocument();
expect(screen.getAllByRole("img")).toHaveLength(1);
for (const value of ["+3 ₽", "+2 ₽", "0 ₽"])
expect(screen.getByText(value)).toBeInTheDocument();
expect(screen.queryByText(/Доход:/)).not.toBeInTheDocument();
},
);
it("groups stock supporting metrics into quiet single lines", () => {
renderPage(
<AccountPageLoaded
{...loadedProps(
{
it.each(["STOCK", "BOND"] as const)(
"groups %s supporting metrics into quiet single lines",
(type) => {
renderPage(
<AccountPageLoaded
{...loadedProps({
...portfolio,
positions: [
{
absoluteReturn: { currency: "RUB", value: "3" },
averagePurchasePrice: { currency: "RUB", value: "11" },
currentValue: { currency: "RUB", value: "10" },
displayName: "Тихая акция",
figi: "FIGI-QUIET",
isin: "RU000QUIET1",
logoName: null,
quantity: "2",
ticker: null,
type: "STOCK",
},
],
},
)}
/>,
);
expect(screen.getByText("Количество: 2 · RU000QUIET1")).not.toHaveAttribute(
"data-line-clamp",
);
expect(
screen.getByText("Средняя цена: 11 ₽ · Доля: 100 %"),
).toBeInTheDocument();
});
positions: [position({
averagePurchasePrice: { currency: "RUB", value: "11" },
currentValue: { currency: "RUB", value: "10" },
isin: "RU000QUIET1",
quantity: "2",
type,
})],
})}
/>,
);
expect(screen.getByText("Количество: 2 · RU000QUIET1"))
.not.toHaveAttribute("data-line-clamp");
expect(screen.getByText("Средняя цена: 11 ₽ · Доля: 100 %"))
.toBeInTheDocument();
},
);
it("uses the ticker only for stock cards and otherwise keeps the ISIN", () => {
renderPage(
<AccountPageLoaded
{...loadedProps(
{
...portfolio,
positions: [
{
absoluteReturn: null,
averagePurchasePrice: null,
currentValue: null,
displayName: "Акция с тикером",
figi: "FIGI-STOCK",
isin: "STOCK-ISIN",
logoName: null,
quantity: "1",
ticker: "SBER",
type: "STOCK",
},
{
absoluteReturn: null,
averagePurchasePrice: null,
currentValue: null,
displayName: "Акция без тикера",
figi: "FIGI-FALLBACK",
isin: "FALLBACK-ISIN",
logoName: null,
quantity: "2",
ticker: null,
type: "STOCK",
},
{
absoluteReturn: null,
averagePurchasePrice: null,
currentValue: null,
displayName: "Облигация",
figi: "FIGI-BOND",
isin: "BOND-ISIN",
logoName: null,
quantity: "3",
ticker: "OFZ",
type: "BOND",
},
],
},
)}
{...loadedProps({
...portfolio,
positions: [
position({ isin: "STOCK-ISIN", ticker: "SBER" }),
position({ isin: "FALLBACK-ISIN", quantity: "2" }),
position({
isin: "BOND-ISIN", quantity: "3", ticker: "OFZ", type: "BOND",
}),
],
})}
/>,
);
expect(screen.getByText("Количество: 1 · SBER")).toBeInTheDocument();
expect(screen.queryByText("STOCK-ISIN")).not.toBeInTheDocument();
expect(
screen.getByText("Количество: 2 · FALLBACK-ISIN"),
).toBeInTheDocument();
expect(screen.getByText("Количество: 3 · BOND-ISIN")).toBeInTheDocument();
expect(screen.getByText("Количество: 2 · FALLBACK-ISIN"))
.toBeInTheDocument();
expect(screen.getByText("Количество: 3 · BOND-ISIN"))
.toBeInTheDocument();
expect(screen.queryByText("OFZ")).not.toBeInTheDocument();
});
it("renders bond cards with logo, fallback, and signed unlabeled return like stocks", () => {
renderPage(
<AccountPageLoaded
{...loadedProps(
{
...portfolio,
positions: [
{
absoluteReturn: { currency: "RUB", value: "2" },
averagePurchasePrice: null,
currentValue: { currency: "RUB", value: "50" },
displayName: "Долг",
figi: "FIGI-C",
isin: "RU000C0XY1Z3",
logoName: "dolg.png",
quantity: "5",
ticker: null,
type: "BOND",
},
{
absoluteReturn: { currency: "RUB", value: "0" },
averagePurchasePrice: null,
currentValue: null,
displayName: "Купон",
figi: "FIGI-D",
isin: null,
logoName: null,
quantity: "1",
ticker: null,
type: "BOND",
},
],
},
)}
/>,
);
expect(screen.getByRole("img", { name: "Долг" })).toHaveAttribute(
"src",
"https://invest-brands.cdn-tinkoff.ru/dolgx160.png",
);
expect(screen.getByText("К")).toBeInTheDocument();
expect(
screen.queryByRole("img", { name: "Купон" }),
).not.toBeInTheDocument();
expect(screen.getByText("+2 ₽")).toBeInTheDocument();
expect(screen.getByText("0 ₽")).toBeInTheDocument();
expect(screen.queryByText(/Доход:/)).not.toBeInTheDocument();
});
it("renders bond cards in expanded layout with bond-specific classes", () => {
renderPage(
<AccountPageLoaded
{...loadedProps(
{
...portfolio,
positions: [
{
absoluteReturn: { currency: "RUB", value: "50" },
averagePurchasePrice: { currency: "RUB", value: "950" },
currentValue: { currency: "RUB", value: "1000" },
displayName: "Облигация 2030",
figi: "FIGI-BOND",
isin: "RU000B0ND123",
logoName: null,
quantity: "10",
ticker: null,
type: "BOND",
},
],
},
)}
/>,
);
expect(screen.getByText("О")).toBeInTheDocument();
expect(screen.getByText("+50 ₽")).toBeInTheDocument();
expect(
screen.getByText("Средняя цена: 950 ₽ · Доля: 100 %"),
).toBeInTheDocument();
});
it("keeps other-position cards compact with labeled return and no logo", () => {
renderPage(
<AccountPageLoaded
{...loadedProps(
{
...portfolio,
positions: [
{
absoluteReturn: { currency: "RUB", value: "-5" },
averagePurchasePrice: null,
currentValue: { currency: "RUB", value: "30" },
displayName: "Фонд",
figi: "FIGI-E",
isin: null,
logoName: "fond.png",
quantity: "3",
ticker: null,
type: "FUND",
},
],
},
)}
{...loadedProps({
...portfolio,
positions: [position({
absoluteReturn: { currency: "RUB", value: "-5" },
displayName: "Фонд",
logoName: "fond.png",
type: "FUND",
})],
})}
/>,
);
expect(screen.getByText("Доход: -5 ₽")).toBeInTheDocument();