fix: preserve latest stock screener result
This commit is contained in:
@@ -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 (
|
||||
<>
|
||||
<button onClick={vm.onSearch}>search</button>
|
||||
<button onClick={() => vm.onPage(2)}>page</button>
|
||||
<output>{vm.items.map(item => item.name).join(", ")}</output>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
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<void>();
|
||||
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(<StocksPageVmProbe />);
|
||||
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<void>();
|
||||
server.use(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user