refactor: colocate frontend component files
Frontend security / security (push) Successful in 9s
Frontend verify / verify (push) Successful in 5m10s
Conventional commits / commits (pull_request) Successful in 25s
Frontend security / security (pull_request) Successful in 9s
Frontend verify / verify (pull_request) Successful in 5m10s

This commit is contained in:
2026-09-23 08:34:58 +03:00
parent 85fcfe3d07
commit b987fd8e78
190 changed files with 131 additions and 108 deletions
@@ -0,0 +1,165 @@
import { MantineProvider } from "@mantine/core";
import { type QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { render, screen, waitFor } from "@testing-library/react";
import { HttpResponse, http } from "msw";
import { MemoryRouter } from "react-router";
import { describe, expect, it, vi } from "vitest";
import { brokerageAccountsQueryKey } from "@/entities/brokerage-account";
import { configureAuthClient } from "@/shared/api";
import { createTestQueryClient } from "@/test/query";
import { server } from "@/test/server";
import { AccountsPage } from "./AccountsPage";
import { AccountsPageEmpty } from "./components/AccountsPageEmpty.view";
import { AccountsPageError } from "./components/AccountsPageError.view";
import { AccountsPageLoading } from "./components/AccountsPageLoading.view";
const account = {
id: "account-1",
name: "Основной",
type: "BROKERAGE",
openedOn: "2026-01-01",
} as const;
function configureTestAuth() {
configureAuthClient({
accessToken: () => "test-token",
refresh: async () => true,
clear: vi.fn(),
});
}
function renderPage(queryClient: QueryClient) {
configureTestAuth();
return render(
<QueryClientProvider client={queryClient}>
<MantineProvider>
<MemoryRouter>
<AccountsPage />
</MemoryRouter>
</MantineProvider>
</QueryClientProvider>,
);
}
describe("AccountsPage", () => {
it.each([
["loading", AccountsPageLoading, "status"],
["empty", AccountsPageEmpty, undefined],
] as const)("renders only the %s state", (_name, State, role) => {
render(
<MantineProvider>
<State />
</MantineProvider>,
);
if (role) expect(screen.getByRole(role)).toBeInTheDocument();
else
expect(
screen.getByRole("heading", { name: "Нет инвестиционных счетов" }),
).toBeInTheDocument();
});
it("routes connection errors to settings and keeps retry available", () => {
const retry = vi.fn();
render(
<MantineProvider>
<MemoryRouter>
<AccountsPageError code="TBANK_UNAVAILABLE" retry={retry} />
</MemoryRouter>
</MantineProvider>,
);
screen.getByRole("button", { name: "Повторить" }).click();
expect(retry).toHaveBeenCalledOnce();
expect(
screen.getByRole("link", { name: "Открыть настройки T-Bank" }),
).toHaveAttribute("href", "/settings");
});
it("keeps the cached account list visible during a background refresh", async () => {
let requestCount = 0;
let resolveRefresh: ((response: Response) => void) | undefined;
server.use(
http.get("*/api/v1/brokerage-accounts", () => {
requestCount += 1;
if (requestCount === 1) return HttpResponse.json([account]);
return new Promise<Response>((resolve) => {
resolveRefresh = resolve;
});
}),
);
const queryClient = createTestQueryClient();
renderPage(queryClient);
await screen.findByText(account.name);
void queryClient.invalidateQueries({
queryKey: brokerageAccountsQueryKey,
});
await waitFor(() => expect(requestCount).toBe(2));
expect(screen.getByText(account.name)).toBeInTheDocument();
expect(
screen.getByRole("status", { name: "Обновляем данные" }),
).toBeInTheDocument();
resolveRefresh?.(HttpResponse.json([account]));
});
it("keeps the cached account list and offers retry after a refresh error", async () => {
let requestCount = 0;
server.use(
http.get("*/api/v1/brokerage-accounts", () => {
requestCount += 1;
if (requestCount === 1) return HttpResponse.json([account]);
if (requestCount === 2)
return HttpResponse.json(
{ code: "TBANK_UNAVAILABLE" },
{ status: 503 },
);
return HttpResponse.json([account]);
}),
);
const queryClient = createTestQueryClient();
renderPage(queryClient);
await screen.findByText(account.name);
void queryClient.invalidateQueries({
queryKey: brokerageAccountsQueryKey,
});
const warning = await screen.findByText(
"Не удалось обновить данные. Показаны последние полученные.",
);
expect(warning).toBeInTheDocument();
expect(screen.getByText(account.name)).toBeInTheDocument();
screen.getByRole("button", { name: "Повторить" }).click();
await waitFor(() => expect(requestCount).toBe(3));
});
it("keeps a cached empty account list visible after a refresh error", async () => {
let requestCount = 0;
server.use(
http.get("*/api/v1/brokerage-accounts", () => {
requestCount += 1;
if (requestCount === 1) return HttpResponse.json([]);
return HttpResponse.json(
{ code: "TBANK_UNAVAILABLE" },
{ status: 503 },
);
}),
);
const queryClient = createTestQueryClient();
renderPage(queryClient);
await screen.findByText("Нет инвестиционных счетов");
void queryClient.invalidateQueries({
queryKey: brokerageAccountsQueryKey,
});
expect(
await screen.findByText(
"Не удалось обновить данные. Показаны последние полученные.",
),
).toBeInTheDocument();
expect(screen.getByText("Нет инвестиционных счетов")).toBeInTheDocument();
});
});