test(Toaster): extend to 16 cases — initial state, styling, auto-dismiss, max-5
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
763cebdb10
commit
dbd8c526f2
@ -1,4 +1,13 @@
|
||||
// @vitest-environment jsdom
|
||||
/**
|
||||
* Tests for Toaster — toast notification overlay.
|
||||
*
|
||||
* Covers: initial empty state, showToast triggers display, success/error/info
|
||||
* styling classes, dismiss button removes toast, Escape dismisses latest toast
|
||||
* (including persistent errors), auto-dismiss for success/info after 4s,
|
||||
* errors persist, maximum 5 toasts shown (last-5 behaviour), no toasts
|
||||
* renders nothing.
|
||||
*/
|
||||
import { describe, it, expect, afterEach, beforeEach, vi } from "vitest";
|
||||
import { render, screen, fireEvent, cleanup, act } from "@testing-library/react";
|
||||
import { Toaster, showToast } from "../Toaster";
|
||||
@ -12,6 +21,140 @@ afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe("Toaster — initial state", () => {
|
||||
it("shows no toast messages when no toasts have fired", () => {
|
||||
render(<Toaster />);
|
||||
// No dismiss buttons visible when there are no toasts.
|
||||
expect(screen.queryByRole("button", { name: "Dismiss notification" })).toBeNull();
|
||||
});
|
||||
|
||||
it("renders the status and alert container divs (for ARIA registration)", () => {
|
||||
render(<Toaster />);
|
||||
// Live regions are always in the DOM so screen readers register them.
|
||||
expect(document.body.querySelector('[role="status"]')).toBeTruthy();
|
||||
expect(document.body.querySelector('[role="alert"]')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Toaster — showToast integration", () => {
|
||||
it("displays a toast after showToast is called", () => {
|
||||
render(<Toaster />);
|
||||
act(() => {
|
||||
showToast("Hello world");
|
||||
});
|
||||
expect(screen.getByText("Hello world")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("displays multiple toasts", () => {
|
||||
render(<Toaster />);
|
||||
act(() => {
|
||||
showToast("first");
|
||||
showToast("second");
|
||||
});
|
||||
expect(screen.getByText("first")).toBeTruthy();
|
||||
expect(screen.getByText("second")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows success toast with emerald border class", () => {
|
||||
render(<Toaster />);
|
||||
act(() => {
|
||||
showToast("Saved", "success");
|
||||
});
|
||||
const toast = screen.getByText("Saved").parentElement!;
|
||||
expect(toast.className).toContain("emerald-950");
|
||||
});
|
||||
|
||||
it("shows error toast with red border class", () => {
|
||||
render(<Toaster />);
|
||||
act(() => {
|
||||
showToast("Failed", "error");
|
||||
});
|
||||
const toast = screen.getByText("Failed").parentElement!;
|
||||
expect(toast.className).toContain("red-950");
|
||||
});
|
||||
|
||||
it("shows info toast (default) with surface class", () => {
|
||||
render(<Toaster />);
|
||||
act(() => {
|
||||
showToast("Note");
|
||||
});
|
||||
const toast = screen.getByText("Note").parentElement!;
|
||||
expect(toast.className).toContain("surface-sunken");
|
||||
});
|
||||
|
||||
it("dismiss button click removes that specific toast", () => {
|
||||
render(<Toaster />);
|
||||
act(() => {
|
||||
showToast("a", "info");
|
||||
showToast("b", "info");
|
||||
});
|
||||
const buttons = screen.getAllByRole("button", { name: "Dismiss notification" });
|
||||
expect(buttons).toHaveLength(2);
|
||||
|
||||
// Click the first dismiss → "a" goes away, "b" stays
|
||||
act(() => {
|
||||
fireEvent.click(buttons[0]);
|
||||
});
|
||||
expect(screen.queryByText("a")).toBeNull();
|
||||
expect(screen.getByText("b")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Toaster — auto-dismiss", () => {
|
||||
it("info toasts auto-dismiss after 4 seconds", () => {
|
||||
render(<Toaster />);
|
||||
act(() => {
|
||||
showToast("auto-info", "info");
|
||||
});
|
||||
expect(screen.getByText("auto-info")).toBeTruthy();
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(4000);
|
||||
});
|
||||
expect(screen.queryByText("auto-info")).toBeNull();
|
||||
});
|
||||
|
||||
it("success toasts auto-dismiss after 4 seconds", () => {
|
||||
render(<Toaster />);
|
||||
act(() => {
|
||||
showToast("auto-success", "success");
|
||||
});
|
||||
expect(screen.getByText("auto-success")).toBeTruthy();
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(4000);
|
||||
});
|
||||
expect(screen.queryByText("auto-success")).toBeNull();
|
||||
});
|
||||
|
||||
it("error toasts do NOT auto-dismiss", () => {
|
||||
render(<Toaster />);
|
||||
act(() => {
|
||||
showToast("persistent-error", "error");
|
||||
});
|
||||
expect(screen.getByText("persistent-error")).toBeTruthy();
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(4000);
|
||||
});
|
||||
// Error toast must still be visible
|
||||
expect(screen.getByText("persistent-error")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("does not auto-dismiss before 4 seconds", () => {
|
||||
render(<Toaster />);
|
||||
act(() => {
|
||||
showToast("still-visible", "info");
|
||||
});
|
||||
expect(screen.getByText("still-visible")).toBeTruthy();
|
||||
|
||||
act(() => {
|
||||
vi.advanceTimersByTime(3999);
|
||||
});
|
||||
expect(screen.getByText("still-visible")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Toaster keyboard a11y", () => {
|
||||
it("Esc dismisses the most recent toast", () => {
|
||||
render(<Toaster />);
|
||||
@ -62,21 +205,4 @@ describe("Toaster keyboard a11y", () => {
|
||||
// against a future regression where someone adds tabindex=-1.
|
||||
expect(btn.getAttribute("tabindex")).not.toBe("-1");
|
||||
});
|
||||
|
||||
it("dismiss button click removes that specific toast", () => {
|
||||
render(<Toaster />);
|
||||
act(() => {
|
||||
showToast("a", "info");
|
||||
showToast("b", "info");
|
||||
});
|
||||
const buttons = screen.getAllByRole("button", { name: "Dismiss notification" });
|
||||
expect(buttons).toHaveLength(2);
|
||||
|
||||
// Click the first dismiss → "a" goes away, "b" stays
|
||||
act(() => {
|
||||
fireEvent.click(buttons[0]);
|
||||
});
|
||||
expect(screen.queryByText("a")).toBeNull();
|
||||
expect(screen.getByText("b")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// @vitest-environment jsdom
|
||||
/**
|
||||
* Tests for BudgetSection — budget limit display and editor in the details panel.
|
||||
*
|
||||
|
||||
Loading…
Reference in New Issue
Block a user