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( , ); return close; } describe("BankCardForm", () => { 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[] = []; 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; 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]); }); });