refactor: enforce frontend view model boundaries
This commit is contained in:
@@ -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);
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user