refactor: align frontend ownership boundaries
Close audit gaps across API drift, query policy, UI, and tests.
This commit is contained in:
@@ -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([]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user