refactor: simplify finance view models
This commit is contained in:
@@ -203,6 +203,37 @@ describe("AccountsPage", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["idle", "failed"] as const)(
|
||||
"keeps partial empty membership distinct from no accounts (sync=%s)",
|
||||
async (state) => {
|
||||
server.use(
|
||||
http.get("*/api/v1/brokerage-accounts", () =>
|
||||
HttpResponse.json(resource([], {
|
||||
availability: "partial",
|
||||
sync: { errorCode: null, id: null, state },
|
||||
})),
|
||||
),
|
||||
);
|
||||
const client = createTestQueryClient();
|
||||
renderPage(client);
|
||||
await waitFor(() =>
|
||||
expect(client.getQueryData(brokerageAccountsQueryKey)).toMatchObject({
|
||||
data: [],
|
||||
meta: { availability: "partial" },
|
||||
}),
|
||||
);
|
||||
expect(screen.queryByText("Нет инвестиционных счетов"))
|
||||
.not.toBeInTheDocument();
|
||||
if (state === "failed")
|
||||
// eslint-disable-next-line vitest/no-conditional-expect -- Each fixed state has a different observable recovery contract.
|
||||
expect(await screen.findByRole("button", { name: "Повторить" }))
|
||||
.toBeVisible();
|
||||
else
|
||||
// eslint-disable-next-line vitest/no-conditional-expect -- Each fixed state has a different observable recovery contract.
|
||||
expect(screen.getByRole("status")).toBeVisible();
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps unknown membership loading instead of declaring no accounts", async () => {
|
||||
server.use(
|
||||
http.get("*/api/v1/brokerage-accounts", () =>
|
||||
|
||||
@@ -15,31 +15,31 @@ function useAccountsPageViewModel() {
|
||||
) {
|
||||
if (!accounts.isError && accounts.meta?.sync.state !== "failed")
|
||||
return { state: "loading" as const };
|
||||
if (accounts.isError || accounts.meta?.sync.state === "failed")
|
||||
return {
|
||||
code: apiErrorCode(accounts.error),
|
||||
retry: sync.retry,
|
||||
state: "error" as const,
|
||||
};
|
||||
|
||||
return {
|
||||
code: apiErrorCode(accounts.error),
|
||||
retry: sync.retry,
|
||||
state: "error" as const,
|
||||
};
|
||||
}
|
||||
|
||||
if (!accounts.data?.length)
|
||||
return {
|
||||
lastSuccessfulAt: accounts.meta?.lastSuccessfulAt,
|
||||
refreshError: accounts.isError || sync.transportError || sync.sourceError,
|
||||
refreshing: accounts.isFetching || sync.refreshing,
|
||||
retry: sync.retry,
|
||||
sourceError: sync.sourceError,
|
||||
state: "empty" as const,
|
||||
};
|
||||
|
||||
return {
|
||||
accounts: brokerageAccountCards(accounts.data, accounts.meta?.dependencies),
|
||||
const refresh = {
|
||||
lastSuccessfulAt: accounts.meta?.lastSuccessfulAt,
|
||||
refreshError: accounts.isError || sync.transportError || sync.sourceError,
|
||||
refreshing: accounts.isFetching || sync.refreshing,
|
||||
retry: sync.retry,
|
||||
sourceError: sync.sourceError,
|
||||
};
|
||||
|
||||
if (accounts.data.length === 0)
|
||||
return {
|
||||
...refresh,
|
||||
state: "empty" as const,
|
||||
};
|
||||
|
||||
return {
|
||||
accounts: brokerageAccountCards(accounts.data, accounts.meta?.dependencies),
|
||||
...refresh,
|
||||
state: "loaded" as const,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,7 +9,11 @@ import {
|
||||
useBondQuote,
|
||||
} from "@/entities/bond";
|
||||
import { useBrokerageOperationHistory } from "@/entities/broker-operation";
|
||||
import { section, useInstrumentParameters } from "@/features/instrument-details";
|
||||
import {
|
||||
section,
|
||||
toInstrumentEventRows,
|
||||
useInstrumentParameters,
|
||||
} from "@/features/instrument-details";
|
||||
import { useScreenSync } from "@/features/tbank-connection";
|
||||
import { apiErrorCode } from "@/shared/api";
|
||||
import { compareDateDesc, instrumentLogoUrl } from "@/shared/lib";
|
||||
@@ -62,20 +66,7 @@ function useBondPageVm() {
|
||||
code: apiErrorCode(bond.error),
|
||||
coupons: coupons.data,
|
||||
couponsState: section(coupons, sync.retry),
|
||||
eventRows:
|
||||
operations.data?.items.map(item => ({
|
||||
...item,
|
||||
logoName:
|
||||
item.logoName
|
||||
?? bond.data?.logoName
|
||||
|
||||
?? null,
|
||||
ticker:
|
||||
item.ticker
|
||||
?? bond.data?.ticker
|
||||
|
||||
?? null,
|
||||
})) ?? [],
|
||||
eventRows: toInstrumentEventRows(operations.data?.items, bond.data),
|
||||
filter,
|
||||
hasPrevious: history.hasPrevious,
|
||||
logoSrc: instrumentLogoUrl(bond.data?.logoName),
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "@/entities/stock";
|
||||
import {
|
||||
section,
|
||||
toInstrumentEventRows,
|
||||
useInstrumentParameters,
|
||||
} from "@/features/instrument-details";
|
||||
import { useScreenSync } from "@/features/tbank-connection";
|
||||
@@ -65,20 +66,7 @@ function useStockPageVm() {
|
||||
companyState: section(company, sync.retry),
|
||||
dividends: dividends.data,
|
||||
dividendsState: section(dividends, sync.retry),
|
||||
eventRows:
|
||||
operations.data?.items.map(item => ({
|
||||
...item,
|
||||
logoName:
|
||||
item.logoName
|
||||
?? stock.data?.logoName
|
||||
|
||||
?? null,
|
||||
ticker:
|
||||
item.ticker
|
||||
?? stock.data?.ticker
|
||||
|
||||
?? null,
|
||||
})) ?? [],
|
||||
eventRows: toInstrumentEventRows(operations.data?.items, stock.data),
|
||||
filter,
|
||||
hasPrevious: history.hasPrevious,
|
||||
logoSrc: instrumentLogoUrl(stock.data?.logoName),
|
||||
|
||||
Reference in New Issue
Block a user