refactor: enforce frontend view model boundaries
Frontend verify / commits (pull_request) Successful in 28s
Frontend verify / security (pull_request) Successful in 10s
Frontend verify / verify (pull_request) Successful in 8m19s

This commit is contained in:
2026-09-28 18:48:23 +03:00
parent 5b69003516
commit ca0ad4f824
287 changed files with 8558 additions and 4538 deletions
+48 -26
View File
@@ -14,9 +14,11 @@ import { BondPage } from "./BondPage";
const LocationProbe = () => {
const location = useLocation();
return (
<output data-testid="location">
{location.pathname}
{location.search}
</output>
);
@@ -24,8 +26,11 @@ const LocationProbe = () => {
const uid = "e6123145-9665-43e0-8413-cd61b8aa9b13";
const money = (value: string) => ({ currency: "RUB", value });
const bond = (/* 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 bond
= {
aci: money("12"),
amortization: true,
callDate: null,
@@ -45,8 +50,9 @@ const bond = (/* eslint-disable unicorn/no-null -- Fixture preserves the product
sector: "financial",
subordinated: false,
ticker: "TEST",
}
/* eslint-enable unicorn/no-null */);
};
/* eslint-enable unicorn/no-null */
const position = {
absoluteReturn: money("1000"),
accounts: [
@@ -67,8 +73,11 @@ const position = {
quantity: "10",
returnPercent: "50",
};
const quote = (/* 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 quote
= {
aci: money("12"),
changePercentagePoints: "-0.875",
cleanPrice: money("490.625"),
@@ -79,8 +88,9 @@ const quote = (/* eslint-disable unicorn/no-null -- Fixture preserves the produc
pricePercent: "98.125",
tradingStatus: null,
unavailable: [],
}
/* eslint-enable unicorn/no-null */);
};
/* eslint-enable unicorn/no-null */
function setup(
options: {
ambiguousTicker?: boolean;
@@ -103,14 +113,17 @@ function setup(
),
http.get("*/api/v1/bonds/by-ticker/:ticker", ({ request }) => {
requests.push(new URL(request.url));
return options.ambiguousTicker
? HttpResponse.json({ code: "BOND_TICKER_AMBIGUOUS" }, { status: 409 })
: HttpResponse.json(resource(resolved));
}),
http.get("*/api/v1/bonds/:uid/metrics", ({ request }) => {
requests.push(new URL(request.url));
return HttpResponse.json(
resource((/* 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. */
resource(
{
coupon: money("32.88"),
couponDate: "2026-10-27",
@@ -144,8 +157,9 @@ function setup(
observedAt: quote.observedAt,
unavailable:
options.ratingState === "unavailable" ? ["rating"] : [],
}
/* eslint-enable unicorn/no-null */)),
},
/* eslint-enable unicorn/no-null */
),
);
}),
http.get("*/api/v1/bonds/:uid/:section", ({ params, request }) => {
@@ -165,7 +179,8 @@ function setup(
);
if (params.section === "candles")
return HttpResponse.json(
resource((/* 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. */
resource(
{
candles: [
{
@@ -177,16 +192,19 @@ function setup(
currency: null,
historyFrom: null,
observedAt: quote.observedAt,
}
/* eslint-enable unicorn/no-null */)),
},
/* eslint-enable unicorn/no-null */
),
);
if (options.failCoupons)
return HttpResponse.json(
{ code: "TBANK_UNAVAILABLE" },
{ status: 503 },
);
return HttpResponse.json(
resource((/* 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. */
resource(
{
coupons: [
{
@@ -216,16 +234,19 @@ function setup(
positionAt: null,
positionIsStale: false,
to: "2027-09-10",
}
/* eslint-enable unicorn/no-null */)),
},
/* eslint-enable unicorn/no-null */
),
);
}),
http.get("*/api/v1/brokerage-accounts/all/operations", ({ request }) => {
const url = new URL(request.url);
requests.push(url);
const isNext = url.searchParams.has("cursor");
return HttpResponse.json(
resource((/* 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. */
resource(
{
accountId: "all",
hasNext: !isNext,
@@ -246,8 +267,9 @@ function setup(
],
nextCursor: isNext ? null : "next",
observedAt: quote.observedAt,
}
/* eslint-enable unicorn/no-null */)),
},
/* eslint-enable unicorn/no-null */
),
);
}),
);
@@ -262,6 +284,7 @@ function setup(
<MantineProvider>
<MemoryRouter initialEntries={[options.path ?? `/bonds/${uid}`]}>
<LocationProbe />
<Routes>
<Route element={<BondPage />} path="/bonds/:bondId" />
</Routes>
@@ -269,6 +292,7 @@ function setup(
</MantineProvider>
</QueryClientProvider>,
);
return {
failQuote: () => {
isFailQuote = true;
@@ -276,6 +300,7 @@ function setup(
requests,
};
}
describe("bond page", () => {
it("opens a direct ticker, keeps percentages distinct from money, and filters personal history by UID", async () => {
const { requests } = setup({ path: "/bonds/TEST", slug: "TEST" });
@@ -339,9 +364,7 @@ describe("bond page", () => {
const message = await screen.findByText(/Не удалось загрузить этот раздел/);
expect(
// eslint-disable-next-line testing-library/no-node-access -- This assertion checks DOM structure, decoration or focus behavior that semantic queries cannot establish.
message.closest(
"[role=\"status\"]",
),
message.closest("[role=\"status\"]"),
).toHaveAttribute("aria-live", "polite");
expect(
screen.queryByRole("heading", { name: "Моя позиция" }),
@@ -355,6 +378,7 @@ describe("bond page", () => {
server.use(
http.post("*/api/v1/tbank-sync", async ({ request }) => {
scopes.push(await request.json());
return HttpResponse.json(
{ code: "TBANK_UNAVAILABLE" },
{ status: 503 },
@@ -368,9 +392,7 @@ describe("bond page", () => {
const message = await screen.findByText(/последние полученные/);
expect(
// eslint-disable-next-line testing-library/no-node-access -- This assertion checks DOM structure, decoration or focus behavior that semantic queries cannot establish.
message.closest(
"[role=\"status\"]",
),
message.closest("[role=\"status\"]"),
).toHaveAttribute("aria-live", "polite");
expect(screen.getByText("98,125 %")).toBeVisible();
expect(scopes).toEqual([