From cec9c6348a389f16f272dac5533aebc9d12ceae8 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sun, 11 Oct 2026 11:08:46 +0300 Subject: [PATCH] fix: preserve latest stock screener result --- .../stocks/ui/StocksPage/StocksPage.test.tsx | 77 +++++++++++++++++++ .../stocks/ui/StocksPage/StocksPage.vm.ts | 9 ++- 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx b/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx index 038529a..d36d5b0 100644 --- a/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx +++ b/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx @@ -10,6 +10,19 @@ import { createTestQueryClient } from "@/test/query"; import { server } from "@/test/server"; import { StocksPage } from "./StocksPage"; +import { useStocksPageVm } from "./StocksPage.vm"; + +const StocksPageVmProbe = () => { + const vm = useStocksPageVm(); + + return ( + <> + + + {vm.items.map(item => item.name).join(", ")} + + ); +}; afterEach(() => vi.useRealTimers()); @@ -159,6 +172,70 @@ describe("stock search", () => { ); }); + it("keeps a newer search when an earlier page request finishes late", async () => { + const oldPage = Promise.withResolvers(); + let searches = 0; + + server.use( + http.post("*/api/v1/stocks/screener/search", () => { + searches += 1; + + return HttpResponse.json({ + asOf: "2026-10-10T10:00:00Z", + hasNext: searches === 1, + items: [{ + countryOfRisk: "RU", + currency: "RUB", + instrumentUid: searches === 1 ? "old" : "new", + isin: null, + logoName: null, + lot: 1, + name: searches === 1 ? "Старый" : "Новый", + realExchange: "REAL_EXCHANGE_MOEX", + sector: "financial", + shareType: "SHARE_TYPE_COMMON", + ticker: searches === 1 ? "OLD" : "NEW", + }], + page: 1, + searchId: searches === 1 ? "old-search" : "new-search", + }); + }), + http.get("*/api/v1/stocks/screener/search/old-search", async () => { + await oldPage.promise; + + return HttpResponse.json({ + asOf: "2026-10-10T10:00:00Z", + hasNext: false, + items: [{ + countryOfRisk: "RU", + currency: "RUB", + instrumentUid: "old-page", + isin: null, + logoName: null, + lot: 1, + name: "Старый результат", + realExchange: "REAL_EXCHANGE_MOEX", + sector: "financial", + shareType: "SHARE_TYPE_COMMON", + ticker: "OLD", + }], + page: 2, + searchId: "old-search", + }); + }), + ); + render(); + fireEvent.click(screen.getByRole("button", { name: "search" })); + await screen.findByText("Старый"); + fireEvent.click(screen.getByRole("button", { name: "page" })); + fireEvent.click(screen.getByRole("button", { name: "search" })); + await screen.findByText("Новый"); + oldPage.resolve(); + + await waitFor(() => expect(screen.getByText("Новый")).toBeVisible()); + expect(screen.queryByText("Старый результат")).not.toBeInTheDocument(); + }); + it("disables filters and shows loading while searching", async () => { const { promise, resolve } = Promise.withResolvers(); server.use( diff --git a/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts b/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts index fe1de7a..8ce87d7 100644 --- a/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts +++ b/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts @@ -83,17 +83,20 @@ function useStocksPageVm() { const changePage = async (nextPage: number) => { if (!page) return; + const currentRequestId = requestIdRef.current + 1; + requestIdRef.current = currentRequestId; setPending(true); setError(false); try { - setPage(await readPage(page.searchId, nextPage)); + const result = await readPage(page.searchId, nextPage); + if (requestIdRef.current === currentRequestId) setPage(result); } catch { - setError(true); + if (requestIdRef.current === currentRequestId) setError(true); } finally { - setPending(false); + if (requestIdRef.current === currentRequestId) setPending(false); } };