feat: add portfolio
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import { MantineProvider } from "@mantine/core";
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import { MemoryRouter } from "react-router";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { Portfolio } from "../../../entities/portfolio";
|
||||
import {
|
||||
AccountPageEmpty,
|
||||
AccountPageError,
|
||||
AccountPageLoaded,
|
||||
AccountPageLoading,
|
||||
} from "./AccountPage";
|
||||
|
||||
function renderPage(ui: React.ReactNode) {
|
||||
return render(
|
||||
<MantineProvider>
|
||||
<MemoryRouter>{ui}</MemoryRouter>
|
||||
</MantineProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
const portfolio: Portfolio = {
|
||||
lastSuccessfulAt: "2026-08-13T10:00:00Z",
|
||||
isStale: true,
|
||||
totalsByCurrency: [{ currency: "RUB", value: "1234.5" }],
|
||||
positions: [
|
||||
{
|
||||
figi: "FIGI-2",
|
||||
displayName: "Вторая",
|
||||
type: "STOCK",
|
||||
quantity: "2",
|
||||
currentValue: null,
|
||||
averagePurchasePrice: null,
|
||||
absoluteReturn: null,
|
||||
},
|
||||
{
|
||||
figi: "FIGI-1",
|
||||
displayName: "Первая",
|
||||
type: "BOND",
|
||||
quantity: "1",
|
||||
currentValue: { currency: "RUB", value: "10" },
|
||||
averagePurchasePrice: null,
|
||||
absoluteReturn: { currency: "RUB", value: "-1" },
|
||||
},
|
||||
],
|
||||
cashBalances: [{ currency: "USD", value: "0.5" }],
|
||||
};
|
||||
|
||||
describe("AccountPage states", () => {
|
||||
afterEach(cleanup);
|
||||
it("renders loading and empty states", () => {
|
||||
renderPage(<AccountPageLoading />);
|
||||
expect(screen.getByRole("status")).toBeInTheDocument();
|
||||
|
||||
renderPage(<AccountPageEmpty />);
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Портфель пока пуст" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders allowed error actions", () => {
|
||||
const retry = vi.fn();
|
||||
renderPage(<AccountPageError code="NOT_FOUND" retry={retry} />);
|
||||
expect(screen.getByRole("link", { name: "К счетам" })).toHaveAttribute(
|
||||
"href",
|
||||
"/accounts",
|
||||
);
|
||||
|
||||
renderPage(<AccountPageError code="TBANK_NOT_CONNECTED" retry={retry} />);
|
||||
expect(
|
||||
screen.getByRole("link", { name: "Открыть настройки T-Bank" }),
|
||||
).toHaveAttribute("href", "/settings");
|
||||
|
||||
renderPage(<AccountPageError code="TBANK_UNAVAILABLE" retry={retry} />);
|
||||
screen.getByRole("button", { name: "Повторить" }).click();
|
||||
expect(retry).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("renders a stale portfolio in upstream order with nullable values", () => {
|
||||
renderPage(<AccountPageLoaded portfolio={portfolio} />);
|
||||
|
||||
expect(
|
||||
screen.getByRole("heading", { level: 1, name: "Портфель" }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
"T-Bank временно недоступен. Показан последний успешный снимок.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText("—")).toBeInTheDocument();
|
||||
expect(screen.getByText("Доход: —")).toBeInTheDocument();
|
||||
const positions = screen
|
||||
.getAllByText(/Вторая|Первая/)
|
||||
.map((element) => element.textContent);
|
||||
expect(positions).toEqual(["Вторая", "Первая"]);
|
||||
expect(screen.getByRole("heading", { name: "Акции" })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Облигации" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("uses the cached account title when available", () => {
|
||||
renderPage(
|
||||
<AccountPageLoaded
|
||||
portfolio={portfolio}
|
||||
account={{
|
||||
id: "account-1",
|
||||
name: "Основной",
|
||||
type: "BROKERAGE",
|
||||
openedOn: null,
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
screen.getByRole("heading", { level: 1, name: "Основной" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user