refactor: align frontend ownership boundaries

Close audit gaps across API drift, query policy, UI, and tests.
This commit is contained in:
2026-09-06 18:20:05 +03:00
parent 175727bc93
commit c97f62bd98
118 changed files with 2190 additions and 2254 deletions
@@ -1,19 +1,17 @@
import { MantineProvider } from "@mantine/core";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import {
cleanup,
fireEvent,
render,
screen,
waitFor,
} from "@testing-library/react";
import { QueryClientProvider } from "@tanstack/react-query";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { HttpResponse, http } from "msw";
import { MemoryRouter, Route, Routes } from "react-router";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { BrokerageOperation } from "@/entities/broker-operation";
import { describe, expect, it, vi } from "vitest";
import {
type BrokerageOperation,
LedgerEntryRow,
} from "@/entities/broker-operation";
import { configureAuthClient } from "@/shared/api";
import { createTestQueryClient } from "@/test/query";
import { server } from "@/test/server";
import { AccountOperationsPage, OperationRow } from "./AccountOperationsPage";
import { AccountOperationsPage } from "./AccountOperationsPage";
const operation: BrokerageOperation = {
id: "operation-1",
@@ -41,14 +39,29 @@ function configureTestAuth() {
});
}
afterEach(cleanup);
function renderPage() {
return render(
<QueryClientProvider client={createTestQueryClient()}>
<MantineProvider>
<MemoryRouter initialEntries={["/accounts/account-1/operations"]}>
<Routes>
<Route
path="/accounts/:accountId/operations"
element={<AccountOperationsPage />}
/>
</Routes>
</MemoryRouter>
</MantineProvider>
</QueryClientProvider>,
);
}
describe("OperationRow", () => {
describe("LedgerEntryRow", () => {
it("marks cashless operations without rendering operation details", () => {
render(
<MantineProvider>
<MemoryRouter>
<OperationRow operation={operation} />
<LedgerEntryRow operation={operation} />
</MemoryRouter>
</MantineProvider>,
);
@@ -65,7 +78,7 @@ describe("OperationRow", () => {
render(
<MantineProvider>
<MemoryRouter>
<OperationRow
<LedgerEntryRow
operation={{
...operation,
payment: { currency: "RUB", value: "15" },
@@ -83,7 +96,7 @@ describe("OperationRow", () => {
render(
<MantineProvider>
<MemoryRouter>
<OperationRow
<LedgerEntryRow
operation={{
...operation,
type: "OPERATION_TYPE_COUPON",
@@ -103,7 +116,7 @@ describe("OperationRow", () => {
render(
<MantineProvider>
<MemoryRouter>
<OperationRow
<LedgerEntryRow
operation={{
...operation,
type: "OPERATION_TYPE_DIVIDEND",
@@ -123,7 +136,7 @@ describe("OperationRow", () => {
render(
<MantineProvider>
<MemoryRouter>
<OperationRow
<LedgerEntryRow
operation={{
...operation,
type: "OPERATION_TYPE_SERVICE_FEE",
@@ -143,7 +156,7 @@ describe("OperationRow", () => {
render(
<MantineProvider>
<MemoryRouter>
<OperationRow
<LedgerEntryRow
operation={{
...operation,
payment: { currency: "RUB", value: "0" },
@@ -161,7 +174,7 @@ describe("OperationRow", () => {
render(
<MantineProvider>
<MemoryRouter>
<OperationRow operation={operation} />
<LedgerEntryRow operation={operation} />
</MemoryRouter>
</MantineProvider>,
);
@@ -171,8 +184,6 @@ describe("OperationRow", () => {
});
describe("AccountOperationsPage", () => {
afterEach(() => configureAuthClient(null));
it("resets cursor pagination when filters change", async () => {
configureTestAuth();
const searches: string[] = [];
@@ -192,20 +203,7 @@ describe("AccountOperationsPage", () => {
},
),
);
render(
<QueryClientProvider client={new QueryClient()}>
<MantineProvider>
<MemoryRouter initialEntries={["/accounts/account-1/operations"]}>
<Routes>
<Route
path="/accounts/:accountId/operations"
element={<AccountOperationsPage />}
/>
</Routes>
</MemoryRouter>
</MantineProvider>
</QueryClientProvider>,
);
renderPage();
expect(
await screen.findByRole("heading", { name: "Журнал операций" }),
@@ -241,20 +239,7 @@ describe("AccountOperationsPage", () => {
},
),
);
render(
<QueryClientProvider client={new QueryClient()}>
<MantineProvider>
<MemoryRouter initialEntries={["/accounts/account-1/operations"]}>
<Routes>
<Route
path="/accounts/:accountId/operations"
element={<AccountOperationsPage />}
/>
</Routes>
</MemoryRouter>
</MantineProvider>
</QueryClientProvider>,
);
renderPage();
expect(
await screen.findByRole("heading", { name: "Журнал операций" }),
@@ -287,20 +272,7 @@ describe("AccountOperationsPage", () => {
},
),
);
render(
<QueryClientProvider client={new QueryClient()}>
<MantineProvider>
<MemoryRouter initialEntries={["/accounts/account-1/operations"]}>
<Routes>
<Route
path="/accounts/:accountId/operations"
element={<AccountOperationsPage />}
/>
</Routes>
</MemoryRouter>
</MantineProvider>
</QueryClientProvider>,
);
renderPage();
expect(
await screen.findByRole("heading", { name: "Журнал операций" }),
@@ -323,20 +295,7 @@ describe("AccountOperationsPage", () => {
HttpResponse.json({ code: "TBANK_UNAVAILABLE" }, { status: 503 }),
),
);
render(
<QueryClientProvider client={new QueryClient()}>
<MantineProvider>
<MemoryRouter initialEntries={["/accounts/account-1/operations"]}>
<Routes>
<Route
path="/accounts/:accountId/operations"
element={<AccountOperationsPage />}
/>
</Routes>
</MemoryRouter>
</MantineProvider>
</QueryClientProvider>,
);
renderPage();
expect(
await screen.findByText("Не удалось загрузить журнал"),
).toBeInTheDocument();
@@ -1,5 +1,4 @@
import type { FC } from "react";
import { LedgerEntryRow } from "@/entities/broker-operation";
import { useAccountOperationsPageVm } from "./AccountOperationsPage.vm";
import { AccountOperationsPageError } from "./components/AccountOperationsPageError.view";
import { AccountOperationsPageLoaded } from "./components/AccountOperationsPageLoaded.view";
@@ -19,4 +18,4 @@ const AccountOperationsPage: FC = () => {
return <AccountOperationsPageLoaded {...viewModel} />;
};
export { AccountOperationsPage, LedgerEntryRow as OperationRow };
export { AccountOperationsPage };
@@ -3,10 +3,10 @@ import { useState } from "react";
import { useParams } from "react-router";
import {
type BrokerageOperation,
brokerageOperationsQueryOptions,
type OperationCategory,
useBrokerageOperations,
} from "@/entities/broker-operation";
import { apiQueryClient } from "@/shared/api";
type AccountOperationsPageViewModel =
| { state: "loading" }
@@ -51,11 +51,9 @@ const useAccountOperationsPageVm = (): AccountOperationsPageViewModel => {
...(cursor ? { cursor } : {}),
limit: 100,
});
const resetQueryKey = apiQueryClient.queryOptions(
"get",
"/api/v1/brokerage-accounts/{accountId}/operations",
{ params: { path: { accountId: accountId ?? "" }, query: { limit: 100 } } },
).queryKey;
const resetQueryKey = brokerageOperationsQueryOptions(accountId, {
limit: 100,
}).queryKey;
const resetCursors = () => setCursors([]);