refactor: remove redundant page view models
This commit is contained in:
@@ -18,7 +18,7 @@ import {
|
||||
useLocation,
|
||||
useNavigate,
|
||||
} from "react-router";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { configureAuthClient } from "@/shared/api";
|
||||
import { createTestQueryClient } from "@/test/query";
|
||||
@@ -47,13 +47,40 @@ const Navigation: FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
function renderEvents(accountId: string, searches: string[]) {
|
||||
return render(
|
||||
<QueryClientProvider client={createTestQueryClient()}>
|
||||
<MantineProvider env="test">
|
||||
<MemoryRouter
|
||||
initialEntries={searches.map(search =>
|
||||
`/accounts/${accountId}/events${search}`,
|
||||
)}
|
||||
initialIndex={0}
|
||||
>
|
||||
<Navigation />
|
||||
|
||||
<Routes>
|
||||
<Route
|
||||
element={<AccountEventsPage />}
|
||||
path="/accounts/:accountId/events"
|
||||
/>
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</MantineProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe("Upcoming event date filters", () => {
|
||||
it("keeps reads and sync disabled without an account scope", async () => {
|
||||
beforeEach(() => {
|
||||
configureAuthClient({
|
||||
accessToken: () => "token",
|
||||
clear: vi.fn(),
|
||||
refresh: () => Promise.resolve(true),
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps reads and sync disabled without an account scope", async () => {
|
||||
const requests: string[] = [];
|
||||
server.use(
|
||||
http.all("*/api/v1/*", ({ request }) => {
|
||||
@@ -87,11 +114,6 @@ describe("Upcoming event date filters", () => {
|
||||
])(
|
||||
"keeps event calendar dates in GET and source sync requests: %s",
|
||||
async (search) => {
|
||||
configureAuthClient({
|
||||
accessToken: () => "token",
|
||||
clear: vi.fn(),
|
||||
refresh: () => Promise.resolve(true),
|
||||
});
|
||||
const requested: string[] = [];
|
||||
const syncRequests: unknown[] = [];
|
||||
server.use(
|
||||
@@ -153,11 +175,6 @@ describe("Upcoming event date filters", () => {
|
||||
it.each(["account-1", "all"])(
|
||||
"normalizes invalid links and history while preserving one-sided future dates for %s",
|
||||
async (accountId) => {
|
||||
configureAuthClient({
|
||||
accessToken: () => "token",
|
||||
clear: vi.fn(),
|
||||
refresh: () => Promise.resolve(true),
|
||||
});
|
||||
const searches: string[] = [];
|
||||
server.use(
|
||||
http.get(
|
||||
@@ -169,31 +186,13 @@ describe("Upcoming event date filters", () => {
|
||||
},
|
||||
),
|
||||
);
|
||||
render(
|
||||
<QueryClientProvider client={createTestQueryClient()}>
|
||||
<MantineProvider env="test">
|
||||
<MemoryRouter
|
||||
initialEntries={[
|
||||
"?from=bad&to=2099-02-28",
|
||||
"?from=2099-02-30",
|
||||
"?from=2099-03-01&to=2099-02-28",
|
||||
"?from=2099-02-01",
|
||||
"?to=2099-02-28",
|
||||
].map(search => `/accounts/${accountId}/events${search}`)}
|
||||
initialIndex={0}
|
||||
>
|
||||
<Navigation />
|
||||
|
||||
<Routes>
|
||||
<Route
|
||||
element={<AccountEventsPage />}
|
||||
path="/accounts/:accountId/events"
|
||||
/>
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</MantineProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
renderEvents(accountId, [
|
||||
"?from=bad&to=2099-02-28",
|
||||
"?from=2099-02-30",
|
||||
"?from=2099-03-01&to=2099-02-28",
|
||||
"?from=2099-02-01",
|
||||
"?to=2099-02-28",
|
||||
]);
|
||||
await screen.findByLabelText("Период");
|
||||
|
||||
for (const expected of ["", "", "?from=2099-02-01", "?to=2099-02-28"]) {
|
||||
@@ -221,11 +220,6 @@ describe("Upcoming event date filters", () => {
|
||||
it.each(["account-1", "all"])(
|
||||
"lets users reset filters after a request failure for %s",
|
||||
async (accountId) => {
|
||||
configureAuthClient({
|
||||
accessToken: () => "token",
|
||||
clear: vi.fn(),
|
||||
refresh: () => Promise.resolve(true),
|
||||
});
|
||||
server.use(
|
||||
http.get(
|
||||
"*/api/v1/brokerage-accounts/:accountId/events",
|
||||
@@ -235,24 +229,7 @@ describe("Upcoming event date filters", () => {
|
||||
: HttpResponse.json(resource({ events: [] })),
|
||||
),
|
||||
);
|
||||
render(
|
||||
<QueryClientProvider client={createTestQueryClient()}>
|
||||
<MantineProvider env="test">
|
||||
<MemoryRouter
|
||||
initialEntries={[`/accounts/${accountId}/events?from=2099-02-01`]}
|
||||
>
|
||||
<Navigation />
|
||||
|
||||
<Routes>
|
||||
<Route
|
||||
element={<AccountEventsPage />}
|
||||
path="/accounts/:accountId/events"
|
||||
/>
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</MantineProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
renderEvents(accountId, ["?from=2099-02-01"]);
|
||||
await screen.findByRole("button", { name: "Повторить" });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Сбросить" }));
|
||||
await screen.findByLabelText("Период");
|
||||
@@ -263,11 +240,6 @@ describe("Upcoming event date filters", () => {
|
||||
it.each(["account-1", "all"])(
|
||||
"applies dates only on submit, restores URL dates, and resets from empty results for %s",
|
||||
async (accountId) => {
|
||||
configureAuthClient({
|
||||
accessToken: () => "token",
|
||||
clear: vi.fn(),
|
||||
refresh: () => Promise.resolve(true),
|
||||
});
|
||||
const searches: string[] = [];
|
||||
server.use(
|
||||
http.get(
|
||||
@@ -286,26 +258,7 @@ describe("Upcoming event date filters", () => {
|
||||
},
|
||||
),
|
||||
);
|
||||
render(
|
||||
<QueryClientProvider client={createTestQueryClient()}>
|
||||
<MantineProvider env="test">
|
||||
<MemoryRouter
|
||||
initialEntries={[
|
||||
`/accounts/${accountId}/events?from=2099-02-01&to=2099-02-28`,
|
||||
]}
|
||||
>
|
||||
<Navigation />
|
||||
|
||||
<Routes>
|
||||
<Route
|
||||
element={<AccountEventsPage />}
|
||||
path="/accounts/:accountId/events"
|
||||
/>
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</MantineProvider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
renderEvents(accountId, ["?from=2099-02-01&to=2099-02-28"]);
|
||||
const from = await screen.findByLabelText("Период");
|
||||
expect(from).toHaveTextContent("01.02.2099 – 28.02.2099");
|
||||
expect(screen.getByRole("button", { name: "Применить" })).toBeDisabled();
|
||||
|
||||
@@ -11,13 +11,11 @@ import { isValidPeriod } from "@/shared/lib";
|
||||
type AccountEventsPageViewModel
|
||||
= | {
|
||||
canApply: boolean;
|
||||
draftFrom: null | string;
|
||||
draftTo: null | string;
|
||||
draftPeriod: [null | string, null | string];
|
||||
events: BrokerageEvent[];
|
||||
lastSuccessfulAt?: null | string;
|
||||
onApply: () => void;
|
||||
onDraftFromChange: (value: null | string) => void;
|
||||
onDraftToChange: (value: null | string) => void;
|
||||
onPeriodChange: (value: [null | string, null | string]) => void;
|
||||
onReset: () => void;
|
||||
partial?: boolean;
|
||||
refreshError: boolean;
|
||||
@@ -47,8 +45,12 @@ function useAccountEventsPageViewModel(
|
||||
? urlTo
|
||||
: null;
|
||||
|
||||
const [draftFrom, setDraftFrom] = useState<null | string>(appliedFrom);
|
||||
const [draftTo, setDraftTo] = useState<null | string>(appliedTo);
|
||||
const [draftPeriod, setDraftPeriod] = useState<[
|
||||
null | string,
|
||||
null | string,
|
||||
]>([appliedFrom, appliedTo]);
|
||||
|
||||
const [draftFrom, draftTo] = draftPeriod;
|
||||
useEffect(() => {
|
||||
if (isValidUrlPeriod) return;
|
||||
const normalized = new URLSearchParams(searchParameters);
|
||||
@@ -64,8 +66,7 @@ function useAccountEventsPageViewModel(
|
||||
|
||||
if (previousPeriod[0] !== appliedFrom || previousPeriod[1] !== appliedTo) {
|
||||
setPreviousPeriod([appliedFrom, appliedTo]);
|
||||
setDraftFrom(appliedFrom);
|
||||
setDraftTo(appliedTo);
|
||||
setDraftPeriod([appliedFrom, appliedTo]);
|
||||
}
|
||||
|
||||
const events = useBrokerageEvents(accountId, {
|
||||
@@ -86,8 +87,7 @@ function useAccountEventsPageViewModel(
|
||||
if (events.isLoading) return { state: "loading" };
|
||||
|
||||
const onReset = () => {
|
||||
setDraftFrom(null);
|
||||
setDraftTo(null);
|
||||
setDraftPeriod([null, null]);
|
||||
setSearchParameters({});
|
||||
};
|
||||
|
||||
@@ -105,8 +105,7 @@ function useAccountEventsPageViewModel(
|
||||
canApply:
|
||||
(draftFrom !== appliedFrom || draftTo !== appliedTo)
|
||||
&& isValidPeriod(draftFrom, draftTo),
|
||||
draftFrom,
|
||||
draftTo,
|
||||
draftPeriod,
|
||||
events: events.data.events,
|
||||
lastSuccessfulAt: events.meta?.lastSuccessfulAt,
|
||||
onApply: () => {
|
||||
@@ -116,8 +115,7 @@ function useAccountEventsPageViewModel(
|
||||
...(draftTo && { to: draftTo }),
|
||||
});
|
||||
},
|
||||
onDraftFromChange: setDraftFrom,
|
||||
onDraftToChange: setDraftTo,
|
||||
onPeriodChange: setDraftPeriod,
|
||||
onReset,
|
||||
partial: events.meta?.availability !== "complete",
|
||||
refreshError: events.isError || sync.sourceError || sync.transportError,
|
||||
|
||||
+4
-4
@@ -10,6 +10,7 @@ import {
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { DatePickerInput } from "@mantine/dates";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BrokerageEventCard } from "@/entities/brokerage-event";
|
||||
import { formatSnapshotTime } from "@/shared/lib";
|
||||
@@ -17,7 +18,6 @@ import { formatSnapshotTime } from "@/shared/lib";
|
||||
import type { AccountEventsPageViewModel } from "../AccountEventsPage.vm";
|
||||
|
||||
import { AccountEventsPageEmpty } from "./AccountEventsPageEmpty.view";
|
||||
import { useAccountEventsPageLoadedVm } from "./AccountEventsPageLoaded.vm";
|
||||
|
||||
type AccountEventsPageLoadedProps = Extract<
|
||||
AccountEventsPageViewModel,
|
||||
@@ -56,7 +56,7 @@ const AccountEventsPageLoadedEvents: FC<AccountEventsPageLoadedProps> = (
|
||||
};
|
||||
|
||||
const AccountEventsPageLoaded: FC<AccountEventsPageLoadedProps> = (props) => {
|
||||
const { onPeriodChange, t } = useAccountEventsPageLoadedVm(props);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
@@ -100,13 +100,13 @@ const AccountEventsPageLoaded: FC<AccountEventsPageLoadedProps> = (props) => {
|
||||
clearable
|
||||
clearButtonProps={{ "aria-label": t("ledger.clearPeriod") }}
|
||||
label={t("ledger.period")}
|
||||
onChange={onPeriodChange}
|
||||
onChange={props.onPeriodChange}
|
||||
popoverProps={{
|
||||
middlewares: { shift: { crossAxis: true } },
|
||||
returnFocus: true,
|
||||
}}
|
||||
type="range"
|
||||
value={[props.draftFrom, props.draftTo]}
|
||||
value={props.draftPeriod}
|
||||
valueFormat="DD.MM.YYYY"
|
||||
/>
|
||||
</Group>
|
||||
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { AccountEventsPageViewModel } from "../AccountEventsPage.vm";
|
||||
|
||||
type AccountEventsPageLoadedProps = Extract<
|
||||
AccountEventsPageViewModel,
|
||||
{ state: "loaded" }
|
||||
>;
|
||||
|
||||
function useAccountEventsPageLoadedVm(props: AccountEventsPageLoadedProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onPeriodChange: (value: [null | string, null | string]) => void = ([
|
||||
from,
|
||||
to,
|
||||
]) => {
|
||||
props.onDraftFromChange(from);
|
||||
props.onDraftToChange(to);
|
||||
};
|
||||
|
||||
return { onPeriodChange, t };
|
||||
}
|
||||
|
||||
export { useAccountEventsPageLoadedVm };
|
||||
Reference in New Issue
Block a user