refactor: enforce account page view model boundary
This commit is contained in:
@@ -13,6 +13,11 @@ import {
|
||||
AccountPageLoaded,
|
||||
AccountPageLoading,
|
||||
} from "./AccountPage";
|
||||
import type {
|
||||
AccountPageRecentEvents,
|
||||
AccountPageRecentOperations,
|
||||
} from "./AccountPage.vm";
|
||||
import { toAccountPagePositionSections } from "./AccountPage.vm";
|
||||
|
||||
vi.mock("@/features/auth", () => ({
|
||||
useAuth: () => ({ accessToken: "test-token" }),
|
||||
@@ -73,13 +78,39 @@ const portfolio: Portfolio = {
|
||||
cashBalances: [{ currency: "USD", value: "0.5" }],
|
||||
};
|
||||
|
||||
const emptyRecentOperations: AccountPageRecentOperations = {
|
||||
state: "empty",
|
||||
to: "/accounts/account-1/operations",
|
||||
};
|
||||
|
||||
const emptyRecentEvents: AccountPageRecentEvents = {
|
||||
state: "empty",
|
||||
to: "/accounts/account-1/events",
|
||||
};
|
||||
|
||||
function loadedProps(nextPortfolio: Portfolio = portfolio) {
|
||||
return {
|
||||
portfolio: nextPortfolio,
|
||||
positionSections: toAccountPagePositionSections(nextPortfolio),
|
||||
recentOperations: emptyRecentOperations,
|
||||
recentEvents: emptyRecentEvents,
|
||||
};
|
||||
}
|
||||
|
||||
function emptyProps() {
|
||||
return {
|
||||
recentOperations: emptyRecentOperations,
|
||||
recentEvents: emptyRecentEvents,
|
||||
};
|
||||
}
|
||||
|
||||
describe("AccountPage states", () => {
|
||||
afterEach(cleanup);
|
||||
it("renders loading and empty states", () => {
|
||||
renderPage(<AccountPageLoading />);
|
||||
expect(screen.getByRole("status")).toBeInTheDocument();
|
||||
|
||||
renderPage(<AccountPageEmpty />);
|
||||
renderPage(<AccountPageEmpty {...emptyProps()} />);
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Портфель пока пуст" }),
|
||||
).toBeInTheDocument();
|
||||
@@ -104,7 +135,7 @@ describe("AccountPage states", () => {
|
||||
});
|
||||
|
||||
it("renders a stale portfolio in upstream order with nullable values hidden", () => {
|
||||
renderPage(<AccountPageLoaded portfolio={portfolio} />);
|
||||
renderPage(<AccountPageLoaded {...loadedProps()} />);
|
||||
|
||||
expect(
|
||||
screen.getByRole("heading", { level: 1, name: "Портфель" }),
|
||||
@@ -138,7 +169,7 @@ describe("AccountPage states", () => {
|
||||
it("renders stock cards with logo fallback and signed unlabeled return", () => {
|
||||
renderPage(
|
||||
<AccountPageLoaded
|
||||
portfolio={{
|
||||
{...loadedProps({
|
||||
...portfolio,
|
||||
positions: [
|
||||
{
|
||||
@@ -164,7 +195,7 @@ describe("AccountPage states", () => {
|
||||
logoName: null,
|
||||
},
|
||||
],
|
||||
}}
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("А")).toBeInTheDocument();
|
||||
@@ -177,7 +208,7 @@ describe("AccountPage states", () => {
|
||||
it("renders bond cards with logo, fallback, and signed unlabeled return like stocks", () => {
|
||||
renderPage(
|
||||
<AccountPageLoaded
|
||||
portfolio={{
|
||||
{...loadedProps({
|
||||
...portfolio,
|
||||
positions: [
|
||||
{
|
||||
@@ -203,7 +234,7 @@ describe("AccountPage states", () => {
|
||||
logoName: null,
|
||||
},
|
||||
],
|
||||
}}
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByRole("img", { name: "Долг" })).toHaveAttribute(
|
||||
@@ -222,10 +253,10 @@ describe("AccountPage states", () => {
|
||||
it("renders the bonds section as a single-column grid on any screen width", () => {
|
||||
renderPage(
|
||||
<AccountPageLoaded
|
||||
portfolio={{
|
||||
{...loadedProps({
|
||||
...portfolio,
|
||||
positions: [portfolio.positions[1]],
|
||||
}}
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
const singleColumnGrids = Array.from(document.querySelectorAll("style"))
|
||||
@@ -242,7 +273,7 @@ describe("AccountPage states", () => {
|
||||
it("keeps other-position cards compact with labeled return and no logo", () => {
|
||||
renderPage(
|
||||
<AccountPageLoaded
|
||||
portfolio={{
|
||||
{...loadedProps({
|
||||
...portfolio,
|
||||
positions: [
|
||||
{
|
||||
@@ -257,7 +288,7 @@ describe("AccountPage states", () => {
|
||||
logoName: "fond.png",
|
||||
},
|
||||
],
|
||||
}}
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("Доход: -5 ₽")).toBeInTheDocument();
|
||||
@@ -267,7 +298,7 @@ describe("AccountPage states", () => {
|
||||
it("uses the cached account title when available", () => {
|
||||
renderPage(
|
||||
<AccountPageLoaded
|
||||
portfolio={portfolio}
|
||||
{...loadedProps()}
|
||||
account={{
|
||||
id: "account-1",
|
||||
name: "Основной",
|
||||
@@ -281,47 +312,37 @@ describe("AccountPage states", () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps the portfolio visible when recent events fail", async () => {
|
||||
server.use(
|
||||
http.get("*/api/v1/brokerage-accounts/:accountId/operations", () =>
|
||||
HttpResponse.json({ code: "TBANK_UNAVAILABLE" }, { status: 503 }),
|
||||
),
|
||||
);
|
||||
it("keeps the portfolio visible when recent operations fail", () => {
|
||||
renderPage(
|
||||
<AccountPageLoaded
|
||||
accountId="account-1"
|
||||
accessToken="token"
|
||||
portfolio={portfolio}
|
||||
{...loadedProps()}
|
||||
recentOperations={{
|
||||
state: "error",
|
||||
to: "/accounts/account-1/operations",
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByText("Не удалось загрузить последние события."),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
expect(
|
||||
screen.getByText("Не удалось загрузить последние события."),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText("Вторая")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps the portfolio visible when upcoming events fail", async () => {
|
||||
server.use(
|
||||
http.get("*/api/v1/brokerage-accounts/:accountId/events", () =>
|
||||
HttpResponse.json({ code: "MOEX_UNAVAILABLE" }, { status: 503 }),
|
||||
),
|
||||
);
|
||||
it("keeps the portfolio visible when upcoming events fail", () => {
|
||||
renderPage(
|
||||
<AccountPageLoaded
|
||||
accountId="account-1"
|
||||
accessToken="token"
|
||||
portfolio={portfolio}
|
||||
{...loadedProps()}
|
||||
recentEvents={{
|
||||
state: "error",
|
||||
to: "/accounts/account-1/events",
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByText("Не удалось загрузить будущие события."),
|
||||
).toBeInTheDocument(),
|
||||
);
|
||||
expect(
|
||||
screen.getByText("Не удалось загрузить будущие события."),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText("Вторая")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user