test: await client settlement in late-response cases (WealthLedger/frontend#61)
This commit is contained in:
@@ -88,7 +88,6 @@ const quote
|
|||||||
function setup(
|
function setup(
|
||||||
options: {
|
options: {
|
||||||
ambiguousTicker?: boolean;
|
ambiguousTicker?: boolean;
|
||||||
failBond?: boolean;
|
|
||||||
failCoupons?: boolean;
|
failCoupons?: boolean;
|
||||||
noPosition?: boolean;
|
noPosition?: boolean;
|
||||||
path?: string;
|
path?: string;
|
||||||
@@ -100,11 +99,7 @@ function setup(
|
|||||||
const resolved = { ...bond, slug: options.slug ?? uid };
|
const resolved = { ...bond, slug: options.slug ?? uid };
|
||||||
let isFailQuote = false;
|
let isFailQuote = false;
|
||||||
server.use(
|
server.use(
|
||||||
http.get("*/api/v1/bonds/:uid", () =>
|
http.get("*/api/v1/bonds/:uid", () => HttpResponse.json(resource(resolved))),
|
||||||
options.failBond
|
|
||||||
? HttpResponse.json({ code: "NOT_FOUND" }, { status: 404 })
|
|
||||||
: HttpResponse.json(resource(resolved)),
|
|
||||||
),
|
|
||||||
http.get("*/api/v1/bonds/by-ticker/:ticker", ({ request }) => {
|
http.get("*/api/v1/bonds/by-ticker/:ticker", ({ request }) => {
|
||||||
requests.push(new URL(request.url));
|
requests.push(new URL(request.url));
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { MantineProvider } from "@mantine/core";
|
import { MantineProvider } from "@mantine/core";
|
||||||
import { QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||||
import { http, HttpResponse } from "msw";
|
import { http, HttpResponse } from "msw";
|
||||||
import { MemoryRouter } from "react-router";
|
import { MemoryRouter } from "react-router";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
@@ -12,19 +12,37 @@ import { server } from "@/test/server";
|
|||||||
import { StocksPage } from "./StocksPage";
|
import { StocksPage } from "./StocksPage";
|
||||||
import { useStocksPageVm } from "./StocksPage.vm";
|
import { useStocksPageVm } from "./StocksPage.vm";
|
||||||
|
|
||||||
|
const pageOperations: Promise<void>[] = [];
|
||||||
|
|
||||||
const StocksPageVmProbe = () => {
|
const StocksPageVmProbe = () => {
|
||||||
const vm = useStocksPageVm();
|
const vm = useStocksPageVm();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button onClick={vm.onSearch}>search</button>
|
<button
|
||||||
<button onClick={() => vm.onPage(2)}>page</button>
|
onClick={() => {
|
||||||
|
pageOperations.push(vm.search());
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
search
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
pageOperations.push(vm.changePage(2));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
page
|
||||||
|
</button>
|
||||||
<output>{vm.items.map(item => item.name).join(", ")}</output>
|
<output>{vm.items.map(item => item.name).join(", ")}</output>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
afterEach(() => vi.useRealTimers());
|
afterEach(() => {
|
||||||
|
pageOperations.length = 0;
|
||||||
|
|
||||||
|
return vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
describe("stock search", () => {
|
describe("stock search", () => {
|
||||||
it("waits for a meaningful query and uses the server-verified ticker link", async () => {
|
it("waits for a meaningful query and uses the server-verified ticker link", async () => {
|
||||||
@@ -230,9 +248,12 @@ describe("stock search", () => {
|
|||||||
fireEvent.click(screen.getByRole("button", { name: "page" }));
|
fireEvent.click(screen.getByRole("button", { name: "page" }));
|
||||||
fireEvent.click(screen.getByRole("button", { name: "search" }));
|
fireEvent.click(screen.getByRole("button", { name: "search" }));
|
||||||
await screen.findByText("Новый");
|
await screen.findByText("Новый");
|
||||||
oldPage.resolve();
|
await act(async () => {
|
||||||
|
oldPage.resolve();
|
||||||
|
await pageOperations[0];
|
||||||
|
});
|
||||||
|
|
||||||
await waitFor(() => expect(screen.getByText("Новый")).toBeVisible());
|
expect(screen.getByText("Новый")).toBeVisible();
|
||||||
expect(screen.queryByText("Старый результат")).not.toBeInTheDocument();
|
expect(screen.queryByText("Старый результат")).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ function useStocksPageVm() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
changePage,
|
||||||
criteria,
|
criteria,
|
||||||
error,
|
error,
|
||||||
items: page?.items ?? [],
|
items: page?.items ?? [],
|
||||||
@@ -111,6 +112,8 @@ function useStocksPageVm() {
|
|||||||
onSearch: () => void search(),
|
onSearch: () => void search(),
|
||||||
page: page?.page,
|
page: page?.page,
|
||||||
resultTime: page?.asOf,
|
resultTime: page?.asOf,
|
||||||
|
// Test seam: settle the actual operation instead of the handler release.
|
||||||
|
search,
|
||||||
searched: page !== null,
|
searched: page !== null,
|
||||||
totalPages: page ? page.page + Number(page.hasNext) : 0,
|
totalPages: page ? page.page + Number(page.hasNext) : 0,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -164,10 +164,16 @@ describe("persistent resource query lifecycle", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => expect(isStarted).toBe(true));
|
await waitFor(() => expect(isStarted).toBe(true));
|
||||||
await act(() => result.current.observe());
|
// Capture the observation promise before releasing the old handler; the
|
||||||
|
// final assertion must follow its settlement, not the handler release.
|
||||||
|
let observation: Promise<unknown> = Promise.resolve();
|
||||||
|
await act(async () => {
|
||||||
|
observation = result.current.observe() ?? Promise.resolve();
|
||||||
|
await observation;
|
||||||
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
release();
|
release();
|
||||||
await blocked;
|
await observation;
|
||||||
});
|
});
|
||||||
expect(result.current.data).toEqual(snapshot);
|
expect(result.current.data).toEqual(snapshot);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user