refactor: enforce frontend view model boundaries
Frontend verify / commits (pull_request) Successful in 28s
Frontend verify / security (pull_request) Successful in 10s
Frontend verify / verify (pull_request) Successful in 8m19s

This commit is contained in:
2026-09-28 18:48:23 +03:00
parent 5b69003516
commit ca0ad4f824
287 changed files with 8558 additions and 4538 deletions
+36 -15
View File
@@ -18,8 +18,11 @@ import { server } from "@/test/server";
import { CardPage } from "./CardPage";
const money = (value: string) => ({ currency: "RUB", value });
const card = (/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
{
/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
const card
= {
archivedAt: null,
balance: money("10000"),
bankName: null,
@@ -29,10 +32,12 @@ const card = (/* eslint-disable unicorn/no-null -- Fixture preserves the product
openingBalance: money("10000"),
openingDate: "2026-01-01",
version: 1,
}
/* eslint-enable unicorn/no-null */);
const operation = (/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
{
};
/* eslint-enable unicorn/no-null */
/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
const operation
= {
amount: money("50.01"),
availableRefund: null,
cardId: "a",
@@ -48,8 +53,9 @@ const operation = (/* eslint-disable unicorn/no-null -- Fixture preserves the pr
targetCardId: "b",
targetCardName: "Запасная",
version: 1,
}
/* eslint-enable unicorn/no-null */);
};
/* eslint-enable unicorn/no-null */
function setup(
options: {
cardsError?: boolean;
@@ -78,6 +84,7 @@ function setup(
),
http.get("*/api/v1/bank-cards/a/operations", ({ request }) => {
requests.push(new URL(request.url).search);
return HttpResponse.json({
hasNext: Number(new URL(request.url).searchParams.get("page")) < 3,
items: [
@@ -92,6 +99,7 @@ function setup(
}),
http.get("*/api/v1/bank-cards/summary", ({ request }) => {
summaryRequests.push(new URL(request.url).search);
return options.summaryError
? HttpResponse.error()
: HttpResponse.json({
@@ -118,8 +126,10 @@ function setup(
</MantineProvider>
</QueryClientProvider>,
);
return { client, requests, summaryRequests };
}
describe("CardPage", () => {
it.each([
["0001-01", "0001-01-31"],
@@ -132,6 +142,7 @@ describe("CardPage", () => {
const { requests, summaryRequests } = setup({ month });
await screen.findByText("Перевод на «Запасная»");
await waitFor(() => expect(summaryRequests).toHaveLength(1));
for (const request of [requests[0], summaryRequests[0]]) {
const query = new URLSearchParams(request);
expect(query.get("from")).toBe(`${month}-01`);
@@ -193,20 +204,28 @@ describe("CardPage", () => {
expect(screen.getByText(/10\s000 ₽/)).toBeInTheDocument();
});
it("keeps the journal in place while the next page loads", async () => {
const scroll = vi.spyOn(globalThis, "scrollBy").mockImplementation(() => {});
const scroll = vi
.spyOn(globalThis, "scrollBy")
.mockImplementation(() => {});
setup();
await screen.findByText("Перевод на «Запасная»");
const navigation = screen.getByRole("navigation", {
name: "Страницы операций",
});
const measure = vi
.spyOn(navigation, "getBoundingClientRect")
.mockReturnValue({ top: 600 } as DOMRect);
const { promise: pending, resolve: release } = Promise.withResolvers<void>();
const { promise: pending, resolve: release }
= Promise.withResolvers<void>();
server.use(
http.get("*/api/v1/bank-cards/a/operations", async () => {
await pending;
return HttpResponse.json({
hasNext: true,
items: [{ ...operation, description: "Новая страница", id: "next" }],
@@ -256,7 +275,7 @@ describe("CardPage", () => {
await waitFor(() => {
expect(requests.at(-1)).toContain("limit=20");
});
expect(requests.at(-1)).toContain("page=1"); ;
expect(requests.at(-1)).toContain("page=1");
await waitFor(() =>
expect(screen.getByRole("button", { name: "Страница 1" })).toBeEnabled(),
);
@@ -321,14 +340,16 @@ describe("CardPage", () => {
archived: boolean;
version: number;
};
bodies.push(body);
current = (/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
{
/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
current
= {
...current,
archivedAt: body.archived ? "2026-09-01" : null,
version: current.version + 1,
}
/* eslint-enable unicorn/no-null */) as typeof card;
} as /* eslint-enable unicorn/no-null */ typeof card;
return HttpResponse.json(current);
}),
);