chore: finish frontend ESLint migration #18
Frontend security / security (push) Successful in 9s
Frontend verify / verify (push) Failing after 37m50s
Frontend security / security (pull_request) Successful in 9s
Frontend verify / verify (pull_request) Failing after 42m10s
Conventional commits / commits (pull_request) Successful in 29s

Enable every plugin recommended preset and replace Biome with ESLint formatting.

Preserve API contracts and JSX whitespace; verify source, tests, builds and tooling.
This commit is contained in:
2026-09-28 07:31:28 +03:00
parent 851457e03c
commit 3eafad2657
253 changed files with 10059 additions and 8864 deletions
+19 -14
View File
@@ -1,9 +1,10 @@
import { HttpResponse, http } from "msw";
import { http, HttpResponse } from "msw";
import { resource } from "@/test/resource";
const authentication = {
accessToken: "test-token",
user: { id: "user-1", email: "user@example.com" },
user: { email: "user@example.com", id: "user-1" },
};
const handlers = [
@@ -18,7 +19,9 @@ const handlers = [
),
http.get("*/api/v1/auth/me", () => HttpResponse.json(authentication.user)),
http.get("*/api/v1/tbank-connection", () =>
HttpResponse.json({ connected: false, connectedAt: null }),
HttpResponse.json((/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
{ connected: false, connectedAt: null }
/* eslint-enable unicorn/no-null */)),
),
http.put("*/api/v1/tbank-connection", () =>
HttpResponse.json({ connected: true, connectedAt: "2026-08-14T00:00:00Z" }),
@@ -33,24 +36,26 @@ const handlers = [
http.get("*/api/v1/brokerage-accounts/:accountId/portfolio", () =>
HttpResponse.json(
resource({
lastSuccessfulAt: "2026-08-14T00:00:00Z",
cashBalances: [],
isStale: false,
totalsByCurrency: [],
lastSuccessfulAt: "2026-08-14T00:00:00Z",
positions: [],
structure: { currencies: [], unvaluedCategories: [] },
cashBalances: [],
totalsByCurrency: [],
}),
),
),
http.get("*/api/v1/brokerage-accounts/:accountId/operations", () =>
HttpResponse.json(
resource({
accountId: "account-1",
items: [],
nextCursor: null,
hasNext: false,
observedAt: "2026-08-14T00:00:00Z",
}),
resource((/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
{
accountId: "account-1",
hasNext: false,
items: [],
nextCursor: null,
observedAt: "2026-08-14T00:00:00Z",
}
/* eslint-enable unicorn/no-null */)),
),
),
http.get("*/api/v1/brokerage-accounts/:accountId/events", () =>
@@ -66,9 +71,9 @@ const handlers = [
HttpResponse.json(
resource({
accountId: "account-1",
calculatedAt: "2026-08-14T00:00:00Z",
period: { from: "2026-01-01", to: "2026-08-14" },
totalsByCurrency: [],
calculatedAt: "2026-08-14T00:00:00Z",
}),
),
),
+11 -10
View File
@@ -1,17 +1,9 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { FC, PropsWithChildren } from "react";
function createTestQueryClient(): QueryClient {
return new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
});
}
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
function createQueryWrapper(queryClient = createTestQueryClient()) {
const QueryWrapper: FC<PropsWithChildren> = (props) => (
const QueryWrapper: FC<PropsWithChildren> = props => (
<QueryClientProvider client={queryClient}>
{props.children}
</QueryClientProvider>
@@ -20,4 +12,13 @@ function createQueryWrapper(queryClient = createTestQueryClient()) {
return { queryClient, wrapper: QueryWrapper };
}
function createTestQueryClient(): QueryClient {
return new QueryClient({
defaultOptions: {
mutations: { retry: false },
queries: { retry: false },
},
});
}
export { createQueryWrapper, createTestQueryClient };
+17 -15
View File
@@ -1,21 +1,23 @@
import type { paths } from "@/shared/api";
type Meta =
paths["/api/v1/brokerage-accounts"]["get"]["responses"][200]["content"]["application/json"]["meta"];
type Meta
= paths["/api/v1/brokerage-accounts"]["get"]["responses"][200]["content"]["application/json"]["meta"];
function resource<T>(data: T, meta: Partial<Meta> = {}) {
return {
data,
meta: {
resourceKey: "fixture",
dataRevision: "1",
stateVersion: "1",
availability: data === null ? "missing" : "complete",
freshness: "fresh",
lastSuccessfulAt: "2026-08-14T00:00:00Z",
sync: { id: null, state: "idle", errorCode: null },
...meta,
} satisfies Meta,
};
return (/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */
{
data,
meta: {
availability: data === null ? "missing" : "complete",
dataRevision: "1",
freshness: "fresh",
lastSuccessfulAt: "2026-08-14T00:00:00Z",
resourceKey: "fixture",
stateVersion: "1",
sync: { errorCode: null, id: null, state: "idle" },
...meta,
} satisfies Meta,
}
/* eslint-enable unicorn/no-null */);
}
export { resource };
+1
View File
@@ -1,4 +1,5 @@
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
const server = setupServer(...handlers);
+13 -11
View File
@@ -1,28 +1,30 @@
import "@testing-library/jest-dom/vitest";
import { cleanup, configure } from "@testing-library/react";
import { afterAll, afterEach, beforeAll } from "vitest";
import { afterAll, afterEach, beforeAll, vi } from "vitest";
import { configureAuthClient } from "@/shared/api";
import "../shared/i18n";
import { server } from "./server";
// Allow async UI updates on the slower CI runner; local waits stay unchanged.
if (process.env.CI) configure({ asyncUtilTimeout: 5_000 });
if (process.env.CI) configure({ asyncUtilTimeout: 5000 });
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => {
cleanup();
configureAuthClient(null);
configureAuthClient();
server.resetHandlers();
});
afterAll(() => server.close());
class ResizeObserver {
disconnect() {}
observe() {}
unobserve() {}
disconnect() {}
}
window.ResizeObserver = ResizeObserver;
vi.stubGlobal("ResizeObserver", ResizeObserver);
// JSDOM has no layout/scrolling; Mantine Combobox scrolls the active option.
Object.defineProperty(HTMLElement.prototype, "scrollIntoView", {
@@ -30,16 +32,16 @@ Object.defineProperty(HTMLElement.prototype, "scrollIntoView", {
value: () => {},
});
Object.defineProperty(window, "matchMedia", {
writable: true,
Object.defineProperty(globalThis, "matchMedia", {
value: (query: string) => ({
addEventListener: () => {},
addListener: () => {},
dispatchEvent: () => false,
matches: false,
media: query,
onchange: null,
addEventListener: () => {},
onchange: /* eslint-disable-line unicorn/no-null -- Exercise the explicit null returned by the production API/DOM contract. */ null,
removeEventListener: () => {},
addListener: () => {},
removeListener: () => {},
dispatchEvent: () => false,
}),
writable: true,
});
+4 -3
View File
@@ -1,17 +1,18 @@
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";
import * as UiLoadingStories from "../../packages/ui/src/LoadingSkeleton/LoadingSkeleton.stories";
import * as StateStories from "../../packages/ui/src/StateView/StateView.stories";
import * as AccountStories from "../entities/brokerage-account/ui/BrokerageAccountList/BrokerageAccountList.stories";
import * as EventStories from "../entities/brokerage-event/ui/BrokerageEventsPreview/BrokerageEventsPreview.stories";
import * as IncomePeriodStories from "../pages/account-income/ui/AccountIncomePage/components/IncomePeriodForm/IncomePeriodForm.stories";
import * as LoadingStories from "../pages/account/ui/AccountPage/components/AccountPageLoading/AccountPageLoading.stories";
import * as RefreshStories from "../pages/account/ui/AccountPage/components/PortfolioRefreshState/PortfolioRefreshState.stories";
import * as PositionStories from "../pages/account/ui/AccountPage/components/PositionCard/PositionCard.stories";
import * as IncomePeriodStories from "../pages/account-income/ui/AccountIncomePage/components/IncomePeriodForm/IncomePeriodForm.stories";
import * as OperationStories from "../pages/card/ui/OperationForm/OperationForm.stories";
afterEach(() => vi.restoreAllMocks());
@@ -59,7 +60,7 @@ describe("Storybook isolated product examples", () => {
fireEvent.click(screen.getByRole("button"));
expect(retry).toHaveBeenCalledOnce();
view.rerender(
<RefreshError refreshing refreshError={false} retry={retry} />,
<RefreshError refreshError={false} refreshing retry={retry} />,
);
expect(screen.getByText(/125\s*000,50/)).toBeVisible();
expect(screen.getByRole("status")).toBeVisible();