Merge pull request 'МЕРА v2: клик по «Предыдущим оценкам» после отправки открывает выбранную оценку' (#3549) from fix/v2-history-click into main
Some checks failed
Deploy Trade-In / build-backend (push) Has been cancelled
Deploy Trade-In / build-frontend (push) Has been cancelled
Deploy Trade-In / build-browser (push) Has been cancelled
Deploy Trade-In / deploy (push) Has been cancelled
Deploy Trade-In / test (push) Has been cancelled
Deploy Trade-In / perimeter-smoke (push) Has been cancelled
Deploy Trade-In / deploy-status (push) Has been cancelled
Deploy Trade-In / changes (push) Successful in 23s

This commit is contained in:
bot-backend 2026-09-17 09:17:00 +00:00
commit 404b583682
2 changed files with 126 additions and 0 deletions

View file

@ -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");
});
});

View file

@ -1190,6 +1190,11 @@ export default function TradeInV2Page() {
// hands `urlId` the correct target id, mirroring how
// `freshResult` already overrides this same race for the
// 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);
router.replace(`/v2?id=${id}`, { scroll: false });
setNav(0);