chore: finish frontend ESLint migration #18
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:
+19
-14
@@ -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
@@ -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
@@ -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,4 +1,5 @@
|
||||
import { setupServer } from "msw/node";
|
||||
|
||||
import { handlers } from "./handlers";
|
||||
|
||||
const server = setupServer(...handlers);
|
||||
|
||||
+13
-11
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user