// @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(); 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 ( <> {open && } ); } function Overlay() { const order = useOverlayOrder(); return ; } describe("the toast handover", () => { it("announces a toast raised in the takeover commit, even under StrictMode", () => { render( , ); 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"); }); });