МЕРА v2: клик по «Предыдущим оценкам» после отправки открывает выбранную оценку #3549
2 changed files with 126 additions and 0 deletions
|
|
@ -0,0 +1,121 @@
|
||||||
|
/**
|
||||||
|
* #2425 — клик по строке «Предыдущие оценки» после отправки оценки в той же
|
||||||
|
* SPA-сессии должен показать ВЫБРАННУЮ запись, а не только что посчитанную.
|
||||||
|
*
|
||||||
|
* Раньше `freshResult` оставался выставленным: `useEstimate` получал null
|
||||||
|
* (восстановление по id не запускалось вовсе), а экран продолжал показывать
|
||||||
|
* свежий результат. Проверяется по значению: какой id ушёл в `useEstimate`,
|
||||||
|
* какой id получил HeroBar и чей адрес лёг в форму.
|
||||||
|
*/
|
||||||
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { FIXTURE_ESTIMATE } from "@/app/ui-preview/estimate/fixture";
|
||||||
|
import type { AggregatedEstimate } from "@/types/trade-in";
|
||||||
|
|
||||||
|
const FRESH: AggregatedEstimate = {
|
||||||
|
...FIXTURE_ESTIMATE,
|
||||||
|
estimate_id: "fresh-1",
|
||||||
|
target_address: "Екатеринбург, ул. Свежая, 1",
|
||||||
|
};
|
||||||
|
const HIST: AggregatedEstimate = {
|
||||||
|
...FIXTURE_ESTIMATE,
|
||||||
|
estimate_id: "hist-1",
|
||||||
|
target_address: "Екатеринбург, ул. Историческая, 2",
|
||||||
|
};
|
||||||
|
|
||||||
|
const useEstimateMock = vi.hoisted(() => vi.fn());
|
||||||
|
const router = vi.hoisted(() => ({ replace: vi.fn(), push: vi.fn() }));
|
||||||
|
|
||||||
|
vi.mock("next/navigation", () => ({ useRouter: () => router }));
|
||||||
|
vi.mock("@tanstack/react-query", () => ({
|
||||||
|
useQueryClient: () => ({ invalidateQueries: vi.fn() }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/lib/trade-in-api", () => {
|
||||||
|
const empty = () => ({ data: undefined });
|
||||||
|
return {
|
||||||
|
useEstimate: useEstimateMock,
|
||||||
|
useEstimateMutation: () => ({
|
||||||
|
mutate: (_input: unknown, opts: { onSuccess: (e: AggregatedEstimate) => void }) =>
|
||||||
|
opts.onSuccess(FRESH),
|
||||||
|
isPending: false,
|
||||||
|
error: null,
|
||||||
|
}),
|
||||||
|
useEstimateHistory: empty,
|
||||||
|
useEstimateHouseAnalytics: empty,
|
||||||
|
useEstimatePlacementHistory: empty,
|
||||||
|
useEstimateSellTimeSensitivity: empty,
|
||||||
|
useLocationIndex: empty,
|
||||||
|
useSalesVsListings: empty,
|
||||||
|
useStreetDeals: empty,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
vi.mock("@/lib/useQuota", () => ({ useQuota: () => ({ data: undefined }) }));
|
||||||
|
vi.mock("@/lib/useMe", () => ({ useMe: () => ({ data: undefined }) }));
|
||||||
|
vi.mock("@/lib/useLogout", () => ({ useLogout: () => ({ mutate: vi.fn() }) }));
|
||||||
|
|
||||||
|
// Дочерние компоненты — заглушки: проверяется только, что страница им отдаёт.
|
||||||
|
vi.mock("@/components/trade-in/v2/TopNav", () => ({
|
||||||
|
default: ({ onNavigate }: { onNavigate: (n: number) => void }) => (
|
||||||
|
<button onClick={() => onNavigate(4)}>open-history</button>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
vi.mock("@/components/trade-in/v2/HeroBar", () => ({
|
||||||
|
default: ({ estimateId }: { estimateId: string | null }) => (
|
||||||
|
<div data-testid="hero-id">{estimateId ?? ""}</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
vi.mock("@/components/trade-in/v2/ParamsPanel", () => ({
|
||||||
|
default: ({
|
||||||
|
onSubmit,
|
||||||
|
initialValues,
|
||||||
|
}: {
|
||||||
|
onSubmit: (i: unknown) => void;
|
||||||
|
initialValues?: { address?: string };
|
||||||
|
}) => (
|
||||||
|
<div>
|
||||||
|
<div data-testid="form-address">{initialValues?.address ?? ""}</div>
|
||||||
|
<button onClick={() => onSubmit({})}>submit</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
vi.mock("@/components/trade-in/v2/SectionOverlay", () => ({
|
||||||
|
default: ({ onSelectEstimate }: { onSelectEstimate: (id: string) => void }) => (
|
||||||
|
<button onClick={() => onSelectEstimate("hist-1")}>row-hist-1</button>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
vi.mock("@/components/trade-in/v2/ResultPanel", () => ({ default: () => null }));
|
||||||
|
vi.mock("@/components/trade-in/v2/LowConfidenceBanner", () => ({
|
||||||
|
LowConfidenceBanner: () => null,
|
||||||
|
}));
|
||||||
|
vi.mock("@/components/trade-in/v2/ObjectSummary", () => ({ ObjectSummary: () => null }));
|
||||||
|
vi.mock("@/components/trade-in/v2/LeadForm", () => ({ LeadForm: () => null }));
|
||||||
|
vi.mock("@/components/trade-in/v2/Footer", () => ({ Footer: () => null }));
|
||||||
|
vi.mock("@/components/trade-in/v2/LocationDrawer", () => ({ LocationDrawer: () => null }));
|
||||||
|
|
||||||
|
import TradeInV2Page from "../page";
|
||||||
|
|
||||||
|
describe("#2425 — строка истории после submit", () => {
|
||||||
|
it("клик по другой оценке показывает её, а не только что посчитанную", () => {
|
||||||
|
useEstimateMock.mockImplementation((id: string | null) =>
|
||||||
|
id === "hist-1"
|
||||||
|
? { data: HIST, isPending: false, isError: false, error: null }
|
||||||
|
: { data: undefined, isPending: id !== null, isError: false, error: null },
|
||||||
|
);
|
||||||
|
|
||||||
|
render(<TradeInV2Page />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("submit"));
|
||||||
|
expect(screen.getByTestId("hero-id")).toHaveTextContent("fresh-1");
|
||||||
|
expect(screen.getByTestId("form-address")).toHaveTextContent("ул. Свежая, 1");
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText("open-history"));
|
||||||
|
fireEvent.click(screen.getByText("row-hist-1"));
|
||||||
|
|
||||||
|
expect(useEstimateMock).toHaveBeenLastCalledWith("hist-1");
|
||||||
|
expect(router.replace).toHaveBeenLastCalledWith("/v2?id=hist-1", { scroll: false });
|
||||||
|
expect(screen.getByTestId("hero-id")).toHaveTextContent("hist-1");
|
||||||
|
expect(screen.getByTestId("form-address")).toHaveTextContent("ул. Историческая, 2");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1190,6 +1190,11 @@ export default function TradeInV2Page() {
|
||||||
// hands `urlId` the correct target id, mirroring how
|
// hands `urlId` the correct target id, mirroring how
|
||||||
// `freshResult` already overrides this same race for the
|
// `freshResult` already overrides this same race for the
|
||||||
// post-submit path (handleSubmit above).
|
// post-submit path (handleSubmit above).
|
||||||
|
//
|
||||||
|
// #2425: a fresh submit and restore-by-id are mutually exclusive
|
||||||
|
// both ways (as in handleNew) — a leftover freshResult would
|
||||||
|
// disable useEstimate(urlId) and keep shadowing the clicked row.
|
||||||
|
setFreshResult(null);
|
||||||
setPendingRestoreId(id);
|
setPendingRestoreId(id);
|
||||||
router.replace(`/v2?id=${id}`, { scroll: false });
|
router.replace(`/v2?id=${id}`, { scroll: false });
|
||||||
setNav(0);
|
setNav(0);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue