feat(portfolio): visualize asset allocation
This commit is contained in:
@@ -90,6 +90,7 @@ const portfolio: Portfolio = {
|
||||
logoName: null,
|
||||
},
|
||||
],
|
||||
structure: { currencies: [], unvaluedCategories: [] },
|
||||
cashBalances: [{ currency: "USD", value: "0.5" }],
|
||||
};
|
||||
|
||||
@@ -120,6 +121,21 @@ function emptyProps() {
|
||||
}
|
||||
|
||||
describe("AccountPage states", () => {
|
||||
it("keeps the account and positions visible for an older HTTP response without structure", async () => {
|
||||
server.use(
|
||||
http.get("*/api/v1/brokerage-accounts/:accountId/portfolio", () =>
|
||||
HttpResponse.json({ ...portfolio, structure: undefined }),
|
||||
),
|
||||
);
|
||||
renderAccountRoute(createTestQueryClient());
|
||||
expect(await screen.findByText("Вторая")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("region", { name: "Структура портфеля" }),
|
||||
).toHaveTextContent("Данные о структуре портфеля пока недоступны.");
|
||||
expect(
|
||||
screen.getAllByRole("definition").map((node) => node.textContent),
|
||||
).toEqual(["1 234,5 ₽", "0,5 $"]);
|
||||
});
|
||||
it("keeps initial placeholders decorative without amounts or focus targets", () => {
|
||||
const { container } = renderPage(<AccountPageLoading />);
|
||||
expect(screen.getByRole("status")).toHaveAttribute("aria-busy", "true");
|
||||
@@ -167,6 +183,7 @@ describe("AccountPage states", () => {
|
||||
currentValue: { currency: "RUB", value: "1000" },
|
||||
},
|
||||
],
|
||||
structure: { currencies: [], unvaluedCategories: [] },
|
||||
cashBalances: [
|
||||
{ currency: "RUB", value: "234.500000000000000001" },
|
||||
{ currency: "USD", value: "-0.5" },
|
||||
@@ -226,6 +243,7 @@ describe("AccountPage states", () => {
|
||||
currentValue: { currency: "RUB", value: "100" },
|
||||
},
|
||||
],
|
||||
structure: { currencies: [], unvaluedCategories: [] },
|
||||
cashBalances: [],
|
||||
};
|
||||
server.use(
|
||||
@@ -762,12 +780,26 @@ describe("AccountPage states", () => {
|
||||
it.each([503, 404])(
|
||||
"preserves totals and UTC snapshot through refresh error %i, retry and success",
|
||||
async (status) => {
|
||||
const initialPortfolio: Portfolio = {
|
||||
...portfolio,
|
||||
structure: {
|
||||
unvaluedCategories: [{ type: "STOCK", count: 1 }],
|
||||
currencies: [
|
||||
{
|
||||
currency: "RUB",
|
||||
totalValue: "10",
|
||||
hasNegativeValues: false,
|
||||
categories: [{ type: "BOND", value: "10", sharePercent: null }],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
let requestCount = 0;
|
||||
let resolveRetry: ((response: Response) => void) | undefined;
|
||||
server.use(
|
||||
http.get("*/api/v1/brokerage-accounts/:accountId/portfolio", () => {
|
||||
requestCount += 1;
|
||||
if (requestCount === 1) return HttpResponse.json(portfolio);
|
||||
if (requestCount === 1) return HttpResponse.json(initialPortfolio);
|
||||
if (requestCount === 3)
|
||||
return new Promise<Response>((resolve) => {
|
||||
resolveRetry = resolve;
|
||||
@@ -782,6 +814,10 @@ describe("AccountPage states", () => {
|
||||
renderAccountRoute(queryClient);
|
||||
|
||||
await screen.findByText("Вторая");
|
||||
const structure = screen.getByRole("region", {
|
||||
name: "Структура портфеля",
|
||||
});
|
||||
expect(structure).toHaveTextContent("10 ₽");
|
||||
const snapshotText = screen.getByText(/Данные получены/).textContent;
|
||||
expect(snapshotText).toContain("10:00");
|
||||
void queryClient.invalidateQueries({
|
||||
@@ -807,6 +843,7 @@ describe("AccountPage states", () => {
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Повторить" }));
|
||||
await waitFor(() => expect(requestCount).toBe(3));
|
||||
expect(structure).toHaveTextContent("10 ₽");
|
||||
expect(
|
||||
screen.getAllByRole("definition").map((node) => node.textContent),
|
||||
).toEqual(["1 234,5 ₽", "0,5 $"]);
|
||||
@@ -819,6 +856,7 @@ describe("AccountPage states", () => {
|
||||
isStale: false,
|
||||
lastSuccessfulAt: "2026-08-14T11:30:00Z",
|
||||
totalsByCurrency: [{ currency: "USD", value: "-0.5" }],
|
||||
structure: { currencies: [], unvaluedCategories: [] },
|
||||
cashBalances: [{ currency: "EUR", value: "2.50" }],
|
||||
}),
|
||||
);
|
||||
@@ -828,6 +866,8 @@ describe("AccountPage states", () => {
|
||||
).toEqual(["-0,5 $", "2,50 €"]),
|
||||
);
|
||||
expect(screen.getByText(/Данные получены/)).toHaveTextContent("11:30");
|
||||
expect(structure).not.toHaveTextContent("10 ₽");
|
||||
expect(structure).toHaveTextContent("Нет положительной стоимости");
|
||||
expect(
|
||||
screen.queryByText(/Не удалось обновить портфель/),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
Reference in New Issue
Block a user