lib/content-toaster.test.ts
Rama main · réplica de lectura del repositorio original.
// @vitest-environment jsdom
import { act, createElement } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { toast } from "sonner";
import { ContentToaster } from "@/components/content-toaster";
let pane: HTMLDivElement;
let main: HTMLElement;
let root: Root;
beforeEach(() => {
toast.dismiss();
window.localStorage.clear();
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
vi.stubGlobal("matchMedia", () => ({ matches: false, addEventListener() {}, removeEventListener() {} }));
vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => window.setTimeout(() => callback(Date.now()), 1));
vi.stubGlobal("cancelAnimationFrame", (id: number) => window.clearTimeout(id));
vi.spyOn(document, "hidden", "get").mockReturnValue(false);
vi.useFakeTimers();
pane = document.createElement("div");
main = document.createElement("main");
pane.appendChild(main);
document.body.appendChild(pane);
root = createRoot(main);
});
afterEach(() => {
act(() => root.unmount());
pane.remove();
vi.restoreAllMocks();
vi.unstubAllGlobals();
vi.useRealTimers();
});
function render() {
act(() => root.render(createElement(ContentToaster)));
}
function fire(fn: () => void) {
act(fn);
act(() => { vi.advanceTimersByTime(20); });
}
const title = () => pane.querySelector("[data-title]")?.textContent;
const notification = () => pane.querySelector("[data-sonner-toast]");
describe("the content toaster", () => {
it("anchors a single toast surface to the pane outside the scrolling main", () => {
render();
fire(() => toast.success("Copied"));
const surface = pane.querySelector(".content-toaster");
expect(surface?.getAttribute("data-x-position")).toBe("center");
expect(surface?.getAttribute("data-y-position")).toBe("bottom");
expect(pane.querySelectorAll("[data-sonner-toaster]")).toHaveLength(1);
expect(main.querySelector("[data-sonner-toaster]")).toBeNull();
expect(title()).toBe("Copied");
expect(pane.querySelector('[aria-live="polite"]')).not.toBeNull();
});
it.each(["info", "success", "warning", "error"] as const)("keeps %s feedback typed for its colored badge", (kind) => {
render();
fire(() => toast[kind](`${kind} feedback`));
expect(notification()?.getAttribute("data-type")).toBe(kind);
expect(title()).toBe(`${kind} feedback`);
});
it("keeps bare copy confirmations visible", () => {
render();
fire(() => toast("Copied"));
expect(title()).toBe("Copied");
expect(notification()?.getAttribute("data-type")).toBeNull();
});
it("updates the same toast id without duplicating the message", () => {
render();
fire(() => toast.info("Dictating…", { id: "dictation-in-flight" }));
fire(() => toast.success("Done", { id: "dictation-in-flight" }));
expect(title()).toBe("Done");
expect(pane.querySelectorAll("[data-sonner-toast]")).toHaveLength(1);
expect(pane.textContent).not.toContain("Dictating…");
});
it("automatically expires messages and removes them from Sonner's active store", () => {
render();
fire(() => toast.success("Saved", { id: "expires" }));
act(() => { vi.advanceTimersByTime(4_000); });
act(() => { vi.advanceTimersByTime(250); });
expect(notification()).toBeNull();
expect(toast.getToasts().some((entry) => entry.id === "expires")).toBe(false);
});
it("honors explicit dismissal", () => {
render();
fire(() => toast.info("In flight", { id: "dismissed" }));
fire(() => toast.dismiss("dismissed"));
act(() => { vi.advanceTimersByTime(500); });
act(() => { vi.advanceTimersByTime(250); });
expect(notification()).toBeNull();
});
it("purges legacy error history without restoring or saving errors", () => {
window.localStorage.setItem("minddy:status-errors", '{"format":"minddy-local-v1","ciphertext":"old"}');
const save = vi.spyOn(Storage.prototype, "setItem");
const fetch = vi.fn();
vi.stubGlobal("fetch", fetch);
render();
fire(() => toast.error("Failure"));
expect(window.localStorage.getItem("minddy:status-errors")).toBeNull();
expect(save).not.toHaveBeenCalled();
expect(fetch).not.toHaveBeenCalled();
expect(pane.querySelectorAll("button")).toHaveLength(1); // Only Sonner's dismiss control.
});
});