fix(auth,bank): guard switch-auth nav and surface banking retry
Frontend verify / commits (pull_request) Successful in 39s
Frontend verify / security (pull_request) Successful in 10s
Frontend verify / verify (pull_request) Failing after 5m50s

AuthForm disables the switch-auth Link while submit is pending so pointer
click and Enter can no longer leave the auth route and lose the draft/error
context. The form restores a true Link after the request settles.

CardsPage/CardPage distinguish cold vs cached banking failures:
- cold card/section errors expose accessible alert + retry
- cached failures keep the previous data and offer a section GET retry
- summary-only/history-only outages do not hide successful sections
- notFound still skips the retry CTA

Tests cover the pending link guard, cold/cached cards error with retry,
summary failure with retry, and the prerequisite card list recovery.

Closes #56, #57
This commit is contained in:
2026-10-14 10:43:34 +03:00
parent e5f8bbe33c
commit e38ecd87d6
10 changed files with 251 additions and 32 deletions
+44 -9
View File
@@ -316,12 +316,47 @@ describe("CardPage", () => {
setup({ summaryError: true });
await screen.findByRole("heading", { name: "Повседневная" });
expect(
screen.queryByRole("alert"),
).not.toBeInTheDocument();
await screen.findByText("Перевод на «Запасная»"),
).toBeInTheDocument();
expect(await screen.findByRole("alert", {}, { timeout: 5000 })).toHaveTextContent(
"Не удалось загрузить данные карт.",
);
expect(screen.queryByText("0 ₽")).not.toBeInTheDocument();
});
it("recovers summary with retry while keeping card and journal", async () => {
const { summaryRequests } = setup({ summaryError: true });
await screen.findByRole("heading", { name: "Повседневная" });
expect(
await screen.findByText("Перевод на «Запасная»"),
).toBeInTheDocument();
expect(screen.queryByText("0 ₽")).not.toBeInTheDocument();
const retry = await screen.findByRole(
"button",
{ name: "Повторить" },
{ timeout: 5000 },
);
const requestsBefore = summaryRequests.length;
server.use(
http.get("*/api/v1/bank-cards/summary", ({ request }) => {
summaryRequests.push(new URL(request.url).search);
return HttpResponse.json({
cardId: "a",
categories: [],
expenses: money("0"),
net: money("-50"),
refunds: money("50"),
});
}),
);
fireEvent.click(retry);
await waitFor(() =>
expect(screen.queryByRole("alert")).not.toBeInTheDocument(),
);
expect(summaryRequests).toHaveLength(requestsBefore + 1);
});
it("keeps cached card and history after refetch failure", async () => {
const { client } = setup();
@@ -331,9 +366,8 @@ describe("CardPage", () => {
http.get("*/api/v1/bank-cards/a/operations", () => HttpResponse.error()),
);
await client.invalidateQueries();
await waitFor(() => {
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
});
const alerts = await screen.findAllByRole("alert", {}, { timeout: 5000 });
expect(alerts.length).toBeGreaterThanOrEqual(1);
expect(
screen.getByRole("heading", { name: "Повседневная" }),
).toBeInTheDocument();
@@ -401,11 +435,11 @@ describe("CardPage", () => {
]);
});
it("recovers the prerequisite card list while preserving the visible balance and journal", async () => {
setup({ cardsError: true });
const { client } = setup({ cardsError: true });
await screen.findByText("Перевод на «Запасная»");
expect(
screen.queryByRole("alert"),
).not.toBeInTheDocument();
await screen.findByRole("alert", {}, { timeout: 5000 }),
).toHaveTextContent("Не удалось загрузить данные карт.");
expect(
screen.getByRole("button", { name: "Добавить операцию" }),
).toBeDisabled();
@@ -414,6 +448,7 @@ describe("CardPage", () => {
HttpResponse.json([card, { ...card, id: "b", name: "Запасная" }]),
),
);
await client.invalidateQueries();
await waitFor(() =>
expect(
screen.getByRole("button", { name: "Добавить операцию" }),