68 lines
2.5 KiB
TypeScript
68 lines
2.5 KiB
TypeScript
// @vitest-environment jsdom
|
|
//
|
|
// The toast handover under React 18 StrictMode — its own file, because `lastTopIds` is module-level
|
|
// state that leaks between tests.
|
|
//
|
|
// One fix made a taking-over host inherit "what the PREVIOUS top host last had on screen" by reading
|
|
// a module global in a `useMemo` and overwriting it in the same render pass. React re-invokes a
|
|
// component body — and every `useMemo` factory in it — on a StrictMode double render, and the LAST
|
|
// invocation is what commits: so the second pass re-read what the first had just written, the host
|
|
// classified its OWN new toast as inherited, and the dev build behaved exactly like the code it
|
|
// replaced. Measured in Chrome and in jsdom: role "status" without StrictMode, null with it.
|
|
//
|
|
// The named case is UploadDialog's — notify and close the overlay in ONE handler, so the toast is
|
|
// raised in the very commit that hands the app's host the top spot.
|
|
import { describe, it, expect, afterEach, vi } from "vitest";
|
|
import { StrictMode, useState } from "react";
|
|
import { render, screen, cleanup, fireEvent } from "@testing-library/react";
|
|
|
|
vi.mock("react-i18next", async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import("react-i18next")>();
|
|
return { ...actual, useTranslation: () => ({ t: (key: string) => key }) };
|
|
});
|
|
|
|
import Toaster from "./Toaster";
|
|
import { useOverlayOrder } from "./overlayOrder";
|
|
import { clearAll, notify, raw } from "../../lib/notifications";
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
clearAll();
|
|
});
|
|
|
|
/** An overlay with its own toast host, plus the app's — the shape every dialog produces. */
|
|
function Harness() {
|
|
const [open, setOpen] = useState(true);
|
|
return (
|
|
<>
|
|
<button
|
|
onClick={() => {
|
|
notify({ level: "success", message: raw("Uploaded") });
|
|
setOpen(false); // …in the SAME handler: one batch, one commit
|
|
}}
|
|
>
|
|
finish
|
|
</button>
|
|
{open && <Overlay />}
|
|
<Toaster />
|
|
</>
|
|
);
|
|
}
|
|
function Overlay() {
|
|
const order = useOverlayOrder();
|
|
return <Toaster overlay order={order} />;
|
|
}
|
|
|
|
describe("the toast handover", () => {
|
|
it("announces a toast raised in the takeover commit, even under StrictMode", () => {
|
|
render(
|
|
<StrictMode>
|
|
<Harness />
|
|
</StrictMode>,
|
|
);
|
|
fireEvent.click(screen.getByText("finish"));
|
|
// Not inherited → it carries its own role, because the live region is not armed yet.
|
|
expect(screen.getByText("Uploaded").closest("[role]")?.getAttribute("role")).toBe("status");
|
|
});
|
|
});
|