From 6b6da60276932eae2ea10dffd68df26e156141a0 Mon Sep 17 00:00:00 2001 From: xx7412421-cloud Date: Sun, 21 Jun 2026 17:30:26 +0800 Subject: [PATCH] test: cover ToastProvider behavior Covers info/status, error/alert, queued toast, auto-dismiss, and missing-provider behavior. Validation: npm test -- ToastProvider.test.tsx --runInBand; npm run lint; npm test -- --coverage --runInBand. --- .../__tests__/ToastProvider.test.tsx | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/components/__tests__/ToastProvider.test.tsx diff --git a/src/components/__tests__/ToastProvider.test.tsx b/src/components/__tests__/ToastProvider.test.tsx new file mode 100644 index 0000000..a684bef --- /dev/null +++ b/src/components/__tests__/ToastProvider.test.tsx @@ -0,0 +1,116 @@ +import { act, fireEvent, render, screen } from "@testing-library/react"; +import { ToastProvider, useToast } from "../ToastProvider"; + +function ToastHarness() { + const { push } = useToast(); + + return ( +
+ + + +
+ ); +} + +function MissingProviderHarness() { + useToast(); + return null; +} + +describe("ToastProvider", () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + act(() => { + jest.runOnlyPendingTimers(); + }); + jest.useRealTimers(); + }); + + it("renders info toasts as polite status messages", () => { + render( + + + , + ); + + fireEvent.click(screen.getByRole("button", { name: "Show info" })); + + expect(screen.getByRole("status")).toHaveTextContent("Saved route"); + expect(screen.getByText("Saved route").parentElement).toHaveAttribute( + "aria-live", + "polite", + ); + }); + + it("renders error toasts with an alert role", () => { + render( + + + , + ); + + fireEvent.click(screen.getByRole("button", { name: "Show error" })); + + expect(screen.getByRole("alert")).toHaveTextContent("Route failed"); + }); + + it("auto-dismisses toasts after four seconds", () => { + render( + + + , + ); + + fireEvent.click(screen.getByRole("button", { name: "Show info" })); + expect(screen.getByText("Saved route")).toBeInTheDocument(); + + act(() => { + jest.advanceTimersByTime(4000); + }); + + expect(screen.queryByText("Saved route")).not.toBeInTheDocument(); + }); + + it("keeps multiple queued toasts visible until their timers expire", () => { + render( + + + , + ); + + fireEvent.click(screen.getByRole("button", { name: "Show two" })); + + expect(screen.getByText("First route")).toBeInTheDocument(); + expect(screen.getByText("Second route")).toBeInTheDocument(); + + act(() => { + jest.advanceTimersByTime(3999); + }); + + expect(screen.getByText("First route")).toBeInTheDocument(); + expect(screen.getByText("Second route")).toBeInTheDocument(); + }); + + it("throws a clear error when useToast is rendered outside the provider", () => { + const consoleError = jest.spyOn(console, "error").mockImplementation(); + + expect(() => render()).toThrow( + "useToast must be used inside ", + ); + + consoleError.mockRestore(); + }); +});