190 lines
4.3 KiB
TypeScript
190 lines
4.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import type { Portfolio } from "@/entities/portfolio";
|
|
|
|
import { toAccountPagePositionSections } from "./positions";
|
|
import { portfolio, position, second } from "./positions.fixtures";
|
|
|
|
function sections(positions: Portfolio["positions"]) {
|
|
return toAccountPagePositionSections({
|
|
...portfolio,
|
|
positions,
|
|
});
|
|
}
|
|
|
|
describe("account position sections", () => {
|
|
it("excludes currency instruments only from display, preserving order and share denominators", () => {
|
|
const otherTypes = [
|
|
"OPTION",
|
|
"OTHER",
|
|
"FUTURE",
|
|
"STRUCTURED_NOTE",
|
|
"DIGITAL_FINANCIAL_ASSET",
|
|
"FUND",
|
|
] as const;
|
|
|
|
const snapshot: Portfolio = {
|
|
...portfolio,
|
|
cashBalances: [{ currency: "RUB", value: "1000" }],
|
|
positions: [
|
|
...portfolio.positions,
|
|
{
|
|
...portfolio.positions[0],
|
|
currentValue: { currency: "RUB", value: "50" },
|
|
figi: "FIGI-CURRENCY",
|
|
type: "CURRENCY",
|
|
},
|
|
...otherTypes.map(
|
|
|
|
type =>
|
|
({
|
|
...portfolio.positions[2],
|
|
currentValue:
|
|
type === "OPTION" ? { currency: "RUB", value: "50" } : null,
|
|
figi: `FIGI-${type}-NEXT`,
|
|
type,
|
|
}),
|
|
),
|
|
],
|
|
structure: { currencies: [], unvaluedCategories: [] },
|
|
totalsByCurrency: [{ currency: "RUB", value: "1250" }],
|
|
};
|
|
|
|
const original = structuredClone(snapshot);
|
|
|
|
const sections = toAccountPagePositionSections(snapshot);
|
|
|
|
expect(sections.map(section => section.kind)).toEqual([
|
|
"stocks",
|
|
"bonds",
|
|
"other",
|
|
]);
|
|
expect(sections[2]?.groups.map(group => group.positions[0].figi)).toEqual([
|
|
"FIGI-FUND",
|
|
...otherTypes.map(type => `FIGI-${type}-NEXT`),
|
|
]);
|
|
expect(sections[0]?.groups[0]?.positions[0].shareText).toBe("40 %");
|
|
expect(sections[1]?.groups[0]?.positions[0].shareText).toBe("20 %");
|
|
expect(sections[2]?.groups[1]?.positions[0].shareText).toBe("20 %");
|
|
expect(snapshot).toEqual(original);
|
|
});
|
|
|
|
it("prepares grouped position sections with derived display data", () => {
|
|
expect(
|
|
toAccountPagePositionSections(portfolio).map(({ groups, kind }) => ({
|
|
kind,
|
|
positions: groups.flatMap(group => group.positions),
|
|
})),
|
|
).toMatchObject([
|
|
{
|
|
kind: "stocks",
|
|
positions: [
|
|
{
|
|
displayName: "Акция",
|
|
logoSrc: "https://invest-brands.cdn-tinkoff.ru/akciyax160.png",
|
|
returnText: "+10 ₽",
|
|
returnTone: "positive",
|
|
shareText: "66,7 %",
|
|
},
|
|
],
|
|
|
|
},
|
|
{
|
|
kind: "bonds",
|
|
positions: [
|
|
{
|
|
displayName: "Облигация",
|
|
logoSrc: null,
|
|
returnText: "-5 ₽",
|
|
returnTone: "negative",
|
|
shareText: "33,3 %",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
kind: "other",
|
|
positions: [
|
|
{
|
|
displayName: "Фонд",
|
|
logoSrc: null,
|
|
returnText: null,
|
|
returnTone: undefined,
|
|
shareText: null,
|
|
},
|
|
],
|
|
},
|
|
]);
|
|
});
|
|
|
|
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[0];
|
|
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);
|
|
});
|
|
});
|