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
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:
@@ -0,0 +1,151 @@
|
||||
import { MantineProvider } from "@mantine/core";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { HttpResponse, http } from "msw";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { configureAuthClient } from "@/shared/api";
|
||||
import { createTestQueryClient } from "@/test/query";
|
||||
import { server } from "@/test/server";
|
||||
import type { BankCard } from "../../api/queries";
|
||||
import { BankCardForm } from "./BankCardForm";
|
||||
|
||||
const card: BankCard = {
|
||||
id: "a",
|
||||
name: "Основная",
|
||||
bankName: "Банк",
|
||||
lastFour: "1234",
|
||||
paymentSystem: null,
|
||||
openingBalance: { currency: "RUB", value: "100" },
|
||||
balance: { currency: "RUB", value: "50" },
|
||||
openingDate: "2026-01-01",
|
||||
archivedAt: null,
|
||||
version: 1,
|
||||
};
|
||||
function setup(value: BankCard) {
|
||||
configureAuthClient({
|
||||
accessToken: () => "test",
|
||||
refresh: async () => false,
|
||||
clear: () => {},
|
||||
});
|
||||
server.use(http.get("*/api/v1/bank-cards/a", () => HttpResponse.json(value)));
|
||||
const close = vi.fn();
|
||||
render(
|
||||
<QueryClientProvider client={createTestQueryClient()}>
|
||||
<MantineProvider env="test">
|
||||
<BankCardForm card={value} onClose={close} />
|
||||
</MantineProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
return close;
|
||||
}
|
||||
describe("BankCardForm", () => {
|
||||
it("waits for the exit transition before notifying the parent", async () => {
|
||||
const close = setup(card);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Отмена" }));
|
||||
expect(close).not.toHaveBeenCalled();
|
||||
await waitFor(() => expect(close).toHaveBeenCalledTimes(1));
|
||||
});
|
||||
it("clears optional metadata on archive while omitting protected financial fields", async () => {
|
||||
const close = setup({ ...card, archivedAt: "2026-09-01" });
|
||||
expect(screen.getByLabelText(/Начальный остаток/)).toHaveAttribute(
|
||||
"readonly",
|
||||
);
|
||||
expect(screen.getByLabelText(/Дата начала/)).toHaveAttribute("readonly");
|
||||
fireEvent.change(screen.getByLabelText(/Банк/), { target: { value: "" } });
|
||||
fireEvent.change(screen.getByLabelText(/Последние четыре/), {
|
||||
target: { value: "" },
|
||||
});
|
||||
let body: unknown;
|
||||
server.use(
|
||||
http.patch("*/api/v1/bank-cards/a", async ({ request }) => {
|
||||
body = await request.json();
|
||||
return HttpResponse.json(card);
|
||||
}),
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Сохранить" }));
|
||||
await waitFor(() => expect(close).toHaveBeenCalled());
|
||||
expect(body).toEqual({
|
||||
version: 1,
|
||||
name: "Основная",
|
||||
bankName: null,
|
||||
lastFour: null,
|
||||
paymentSystem: null,
|
||||
});
|
||||
});
|
||||
it("loads and saves the selected payment system", async () => {
|
||||
const close = setup({ ...card, paymentSystem: "mir" });
|
||||
expect(
|
||||
screen.getByRole("combobox", { name: /Платёжная система/ }),
|
||||
).toHaveValue("МИР");
|
||||
fireEvent.click(
|
||||
screen.getByRole("combobox", { name: /Платёжная система/ }),
|
||||
);
|
||||
fireEvent.click(await screen.findByRole("option", { name: "Visa" }));
|
||||
let body: unknown;
|
||||
server.use(
|
||||
http.patch("*/api/v1/bank-cards/a", async ({ request }) => {
|
||||
body = await request.json();
|
||||
return HttpResponse.json({ ...card, paymentSystem: "visa" });
|
||||
}),
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Сохранить" }));
|
||||
await waitFor(() => expect(close).toHaveBeenCalled());
|
||||
expect(body).toMatchObject({ paymentSystem: "visa" });
|
||||
});
|
||||
|
||||
it("retains an invalid opening date draft and explains conflict with saved history", async () => {
|
||||
const close = setup(card);
|
||||
fireEvent.change(screen.getByLabelText(/Дата начала/), {
|
||||
target: { value: "01.08.2026" },
|
||||
});
|
||||
server.use(
|
||||
http.patch("*/api/v1/bank-cards/a", () =>
|
||||
HttpResponse.json(
|
||||
{ code: "BANK_OPENING_DATE_CONFLICT" },
|
||||
{ status: 400 },
|
||||
),
|
||||
),
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Сохранить" }));
|
||||
await screen.findByText(
|
||||
"Дата начала противоречит сохранённой истории карты.",
|
||||
);
|
||||
expect(screen.getByLabelText(/Дата начала/)).toHaveValue("01.08.2026");
|
||||
expect(close).not.toHaveBeenCalled();
|
||||
});
|
||||
it("keeps card edit version until explicit refresh adoption", async () => {
|
||||
const close = setup(card);
|
||||
let version = 1;
|
||||
const bodies: Record<string, unknown>[] = [];
|
||||
server.use(
|
||||
http.get("*/api/v1/bank-cards/a", () =>
|
||||
HttpResponse.json({ ...card, version }),
|
||||
),
|
||||
http.patch("*/api/v1/bank-cards/a", async ({ request }) => {
|
||||
const body = (await request.json()) as Record<string, unknown>;
|
||||
bodies.push(body);
|
||||
version = 2;
|
||||
return body.version === 1
|
||||
? HttpResponse.json(
|
||||
{ code: "BANK_VERSION_CONFLICT" },
|
||||
{ status: 409 },
|
||||
)
|
||||
: HttpResponse.json(card);
|
||||
}),
|
||||
);
|
||||
fireEvent.change(screen.getByLabelText(/Название карты/), {
|
||||
target: { value: "Мой черновик" },
|
||||
});
|
||||
fireEvent.click(screen.getByRole("button", { name: "Сохранить" }));
|
||||
await screen.findByText(/Запись уже изменена/);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Перечитать запись" }));
|
||||
const adopt = await screen.findByRole("button", {
|
||||
name: "Использовать актуальную версию 2",
|
||||
});
|
||||
expect(screen.getByLabelText(/Название карты/)).toHaveValue("Мой черновик");
|
||||
fireEvent.click(adopt);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Сохранить" }));
|
||||
await waitFor(() => expect(close).toHaveBeenCalled());
|
||||
expect(bodies.map((b) => b.version)).toEqual([1, 2]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user