Files
frontend/src/pages/account/ui/AccountPage/position-groups.test.tsx
T
ksv741 a4046dbe87
Frontend verify / commits (pull_request) Successful in 29s
Frontend verify / security (pull_request) Successful in 9s
Frontend verify / verify (pull_request) Successful in 8m54s
Frontend verify / commits (push) Skipped
Frontend verify / security (push) Successful in 12s
Frontend verify / verify (push) Successful in 9m59s
refactor: address frontend review feedback
2026-09-28 19:20:32 +03:00

151 lines
3.9 KiB
TypeScript

import { MantineProvider } from "@mantine/core";
import { render, screen, within } from "@testing-library/react";
import { MemoryRouter } from "react-router";
import { describe, expect, it } from "vitest";
import type { Portfolio } from "@/entities/portfolio";
import { toAccountPagePositionSections } from "./AccountPage.vm";
import { PositionSection } from "./components/PositionSection.view";
const position: Portfolio["positions"][number]
= {
absoluteReturn: { currency: "RUB", value: "25" },
account: { id: "a", name: "Основной" },
averagePurchasePrice: { currency: "RUB", value: "280" },
currentValue: { currency: "RUB", value: "375" },
displayName: "Сбербанк",
figi: "FIGI-SBER",
isin: "RU0009029540",
logoName: null,
quantity: "1.25",
ticker: "SBER",
type: "STOCK",
};
const second
= {
...position,
absoluteReturn: null,
account: { id: "b", name: "ИИС" },
averagePurchasePrice: null,
currentValue: null,
quantity: "-2",
};
function sections(positions: Portfolio["positions"]) {
return toAccountPagePositionSections({
cashBalances: [],
isStale: false,
lastSuccessfulAt: "2026-09-09T12:00:00Z",
positions,
structure: { currencies: [], unvaluedCategories: [] },
totalsByCurrency: [],
});
}
describe("Positions grouped by security", () => {
it("shows one security with exact per-account quantities and valuations", () => {
const section = sections([position, second])[0];
render(
<MantineProvider>
<MemoryRouter>
<PositionSection section={section} />
</MemoryRouter>
</MantineProvider>,
);
expect(screen.getAllByText("Сбербанк")).toHaveLength(1);
const rows = within(
screen.getByRole("list", { name: "Позиции по счетам" }),
).getAllByRole("listitem");
expect(rows).toHaveLength(2);
expect(
within(rows[0]).getByRole("link", { name: "Основной" }),
).toHaveAttribute("href", "/accounts/a");
expect(within(rows[0]).getByText("Количество: 1.25")).toBeInTheDocument();
expect(within(rows[0]).getByText("375 ₽")).toBeInTheDocument();
expect(
within(rows[0]).getByText("Средняя цена: 280 ₽"),
).toBeInTheDocument();
expect(within(rows[1]).getByRole("link", { name: "ИИС" })).toHaveAttribute(
"href",
"/accounts/b",
);
expect(within(rows[1]).getByText("Количество: -2")).toBeInTheDocument();
expect(rows[1]).not.toHaveTextContent("₽");
});
it("uses ISIN only when FIGI is absent, never merging by name or ticker", () => {
expect(
sections([
{
...position,
figi: null,
},
{ ...second, figi: null },
])[0].groups,
).toHaveLength(1);
expect(
sections([position, { ...second, figi: "OTHER-FIGI" }])[0].groups,
).toHaveLength(2);
expect(
sections([
{
...position,
figi: null,
isin: null,
},
{
...second,
figi: null,
isin: null,
},
])[0].groups,
).toHaveLength(2);
});
it("preserves one-account cards and does not merge repeated rows on the same account", () => {
expect(
sections([position, { ...second, account: position.account }])[0].groups,
).toHaveLength(2);
expect(
sections([
{ ...position, account: undefined },
{ ...second, account: undefined },
])[0].groups,
).toHaveLength(2);
});
it("keeps currencies and purchase prices attached to their source positions", () => {
const source = [
position,
{
...second,
averagePurchasePrice: { currency: "USD", value: "0.99" },
currentValue: { currency: "USD", value: "1.01" },
},
];
const before = structuredClone(source);
const [group] = sections(source)[0].groups;
expect(
group.positions.map(item => [
item.quantity,
item.currentValue,
item.averagePurchasePrice,
]),
).toEqual(
source.map(item => [
item.quantity,
item.currentValue,
item.averagePurchasePrice,
]),
);
expect(source).toEqual(before);
});
});