From 320ea5922a70ab81ce4f05c3c90eb7b6a664fa39 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Mon, 28 Sep 2026 21:04:27 +0300 Subject: [PATCH] test: remove redundant frontend checks --- .../TbankConnectionSection.view.test.tsx | 32 --------- .../account-income/model/display.test.ts | 65 +++++++++++++++++++ .../AccountIncomePage.test.tsx | 58 +---------------- .../ui/AccountPage/AccountPage.test.tsx | 27 ++------ src/test/storybook.test.tsx | 15 ----- 5 files changed, 71 insertions(+), 126 deletions(-) delete mode 100644 src/features/tbank-connection/ui/TbankConnectionSection/TbankConnectionSection.view.test.tsx diff --git a/src/features/tbank-connection/ui/TbankConnectionSection/TbankConnectionSection.view.test.tsx b/src/features/tbank-connection/ui/TbankConnectionSection/TbankConnectionSection.view.test.tsx deleted file mode 100644 index ec68a16..0000000 --- a/src/features/tbank-connection/ui/TbankConnectionSection/TbankConnectionSection.view.test.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { MantineProvider } from "@mantine/core"; -import { fireEvent, render, screen } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; - -import { - TbankConnectionError, - TbankConnectionLoading, -} from "./TbankConnectionSection.view"; - -describe("TbankConnectionSection state views", () => { - it("renders loading and retries a status error", () => { - const retry = vi.fn(); - - const { rerender } = render( - - - , - ); - - expect(screen.getByRole("status", { name: "Загрузка" })).toHaveAttribute( - "aria-busy", - "true", - ); - rerender( - - - , - ); - fireEvent.click(screen.getByRole("button", { name: "Повторить" })); - expect(retry).toHaveBeenCalledOnce(); - }); -}); diff --git a/src/pages/account-income/model/display.test.ts b/src/pages/account-income/model/display.test.ts index 01ce9e7..60b7248 100644 --- a/src/pages/account-income/model/display.test.ts +++ b/src/pages/account-income/model/display.test.ts @@ -44,6 +44,71 @@ function summary(currency: string, coupon: string, dividend: string) { }; } +describe("income membership validation", () => { + const entry: IncomeAnalytics["entries"][number] = { + accruedInterest: null, + category: "income", + commission: null, + description: "Купон", + figi: "FIGI", + id: "coupon-1", + instrumentKey: "income-group", + instrumentType: "bond", + instrumentUid: null, + logoName: null, + occurredAt: "2026-08-01T12:00:00Z", + payment: money("RUB", "10"), + price: null, + quantity: null, + ticker: "BOND", + type: "OPERATION_TYPE_COUPON", + }; + + const instrument: IncomeAnalytics["instruments"][number] = { + ...summary("RUB", "10", "0"), + displayName: "BOND", + figi: "FIGI", + instrumentType: "bond", + instrumentUid: null, + key: entry.instrumentKey, + receiptCount: 1, + ticker: "BOND", + }; + + const valid: IncomeAnalytics = { + ...analytics, + entries: [entry], + instruments: [instrument], + totalsByCurrency: [summary("RUB", "10", "0")], + }; + + it.each([ + ["missing key", undefined], + ["empty key", ""], + ["whitespace key", " "], + ["non-string key", 12], + ["unknown key", "not-in-breakdown"], + ])("rejects an entry with %s", (_name, instrumentKey) => { + // Deliberately malformed API data exercises the runtime validation boundary. + const response = { + ...valid, + entries: [{ ...entry, instrumentKey }], + } as unknown as IncomeAnalytics; + + expect(toIncomeDisplayModel(response)).toBeNull(); + }); + + it.each([ + ["duplicate instrument keys", { instruments: [instrument, instrument] }], + ["currency mismatch", { entries: [{ ...entry, payment: money("USD", "10") }] }], + ["receipt count mismatch", { instruments: [{ ...instrument, receiptCount: 2 }] }], + ["missing entries", { entries: [] }], + ["empty instrument key", { instruments: [{ ...instrument, key: "" }] }], + ])("rejects %s", (_name, invalid) => { + expect(toIncomeDisplayModel({ ...valid, ...invalid })).toBeNull(); + }); +}); + describe("income comparison display", () => { it.each([ ["25", "100", "+25 %"], diff --git a/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.test.tsx b/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.test.tsx index 0ea45e7..0c12ea1 100644 --- a/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.test.tsx +++ b/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.test.tsx @@ -324,17 +324,11 @@ describe("AccountIncomePage", () => { expect(within(panel).getAllByRole("listitem")).toHaveLength(2); }); - it.each([ - ["missing key", undefined], - ["empty key", ""], - ["whitespace key", " "], - ["non-string key", 12], - ["unknown key", "not-in-breakdown"], - ])("rejects %s and recovers through retry", async (_name, instrumentKey) => { + it("rejects inconsistent analytics and recovers through retry", async () => { let response = { ...analytics, entries: [ - { ...analytics.entries[0], instrumentKey }, + { ...analytics.entries[0], instrumentKey: "not-in-breakdown" }, analytics.entries[1], ], }; @@ -356,51 +350,6 @@ describe("AccountIncomePage", () => { ).toBeInTheDocument(); }); - it.each([ - [ - "duplicate instrument keys", - { - ...analytics, - instruments: [...analytics.instruments, analytics.instruments[0]], - }, - ], - [ - "currency mismatch", - { - ...analytics, - entries: [ - { ...analytics.entries[0], payment: money("USD", "10") }, - analytics.entries[1], - ], - }, - ], - [ - "receipt count mismatch", - { - ...analytics, - instruments: [{ ...analytics.instruments[0], receiptCount: 3 }], - }, - ], - ["missing entries", { ...analytics, entries: [] }], - [ - "empty instrument key", - { ...analytics, instruments: [{ ...analytics.instruments[0], key: "" }] }, - ], - ])("shows a local error for %s", async (_name, response) => { - server.use( - http.get("*/api/v1/brokerage-accounts/account-1/income-analytics", () => - HttpResponse.json(resource(response)), - ), - ); - renderPage(); - expect( - await screen.findByRole("button", { name: "Повторить" }), - ).toBeInTheDocument(); - expect( - screen.queryByRole("region", { name: "Выплаты" }), - ).not.toBeInTheDocument(); - }); - it("accepts an empty analytics response", async () => { server.use( http.get("*/api/v1/brokerage-accounts/account-1/income-analytics", () => @@ -870,8 +819,6 @@ describe("income period and snapshot boundary", () => { expect( screen.getByRole("heading", { name: "Выплаты" }), ).toBeInTheDocument(); - expect(screen.queryByRole("heading", { level: 3 })).not.toBeInTheDocument(); - expect(screen.queryByRole("heading", { level: 4 })).not.toBeInTheDocument(); expect(screen.queryByText("RUB")).not.toBeInTheDocument(); expect(screen.queryByText("USD")).not.toBeInTheDocument(); const cards = screen.getAllByRole("listitem"); @@ -881,7 +828,6 @@ describe("income period and snapshot boundary", () => { expect(cards[2]).toHaveTextContent("−3 ₽"); expect(screen.queryByText(/Итого:|Выплат:/)).not.toBeInTheDocument(); expect(screen.queryByRole("button")).not.toBeInTheDocument(); - expect(screen.getAllByText("Купон")).toHaveLength(6); }); }); diff --git a/src/pages/account/ui/AccountPage/AccountPage.test.tsx b/src/pages/account/ui/AccountPage/AccountPage.test.tsx index 1492a67..7b89b18 100644 --- a/src/pages/account/ui/AccountPage/AccountPage.test.tsx +++ b/src/pages/account/ui/AccountPage/AccountPage.test.tsx @@ -302,7 +302,9 @@ describe("AccountPage states", () => { expect( screen.queryByRole("button", { name: "Повторить" }), ).not.toBeInTheDocument(); - expect(screen.getByRole("link")).toHaveAttribute( + expect(screen.getByRole("link", { + name: code === "NOT_FOUND" ? "К счетам" : "Открыть настройки T-Bank", + })).toHaveAttribute( "href", code === "NOT_FOUND" ? "/accounts" : "/settings", ); @@ -427,34 +429,13 @@ describe("AccountPage states", () => { }, ); - it("renders loading and empty states", () => { - renderPage(); - expect(screen.getByRole("status")).toBeInTheDocument(); - + it("renders the empty state", () => { renderPage(); expect( screen.getByRole("heading", { name: "Портфель пока пуст" }), ).toBeInTheDocument(); }); - it("renders allowed error actions", () => { - const retry = vi.fn(); - renderPage(); - expect(screen.getByRole("link", { name: "К счетам" })).toHaveAttribute( - "href", - "/accounts", - ); - - renderPage(); - expect( - screen.getByRole("link", { name: "Открыть настройки T-Bank" }), - ).toHaveAttribute("href", "/settings"); - - renderPage(); - fireEvent.click(screen.getByRole("button", { name: "Повторить" })); - expect(retry).toHaveBeenCalledOnce(); - }); - it("renders a stale portfolio in upstream order with nullable values hidden", () => { renderPage(); diff --git a/src/test/storybook.test.tsx b/src/test/storybook.test.tsx index 2feed41..9a39756 100644 --- a/src/test/storybook.test.tsx +++ b/src/test/storybook.test.tsx @@ -1,7 +1,6 @@ import { composeStories } from "@storybook/react-vite"; // eslint-disable-next-line testing-library/no-manual-cleanup -- Vitest globals are disabled, so RTL cannot register its automatic afterEach cleanup. import { cleanup, fireEvent, render, screen } from "@testing-library/react"; -import { fn } from "storybook/test"; import { afterEach, describe, expect, it, vi } from "vitest"; import preview from "../../.storybook/preview"; @@ -56,18 +55,4 @@ describe("Storybook isolated product examples", () => { for (const spy of spies) expect(spy).not.toHaveBeenCalled(); }); - - it("keeps synthetic totals visible while the refresh example calls a local retry", () => { - const { RefreshError } = composeStories(RefreshStories, preview); - const retry = fn(); - const view = render(); - expect(screen.getByText(/125\s*000,50/)).toBeVisible(); - fireEvent.click(screen.getByRole("button")); - expect(retry).toHaveBeenCalledOnce(); - view.rerender( - , - ); - expect(screen.getByText(/125\s*000,50/)).toBeVisible(); - expect(screen.getByRole("status")).toBeVisible(); - }); });