import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { afterEach, expect, it, vi } from "vitest"; import { bindIconAnimation } from "./bind-icon-animation"; afterEach(() => vi.restoreAllMocks()); it("finishes every delayed part after leaving and does not restart on quick re-entry", async () => { vi.spyOn(globalThis, "matchMedia").mockReturnValue({ matches: true, } as MediaQueryList); const { container } = render( , ); // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- Bind browser animation doubles to the decorative SVG, which has no accessible role. const icon = container.querySelector("svg"); if (!icon) throw new Error("Missing icon"); const target = screen.getByRole("button"); let finishFirst!: () => void; let finishLast!: () => void; const getAnimations = vi.fn(() => { const first = Promise.withResolvers(); const last = Promise.withResolvers(); finishFirst = first.resolve; finishLast = last.resolve; return [{ finished: first.promise }, { finished: last.promise }]; }); Object.defineProperty(icon, "getAnimations", { value: getAnimations }); fireEvent.mouseEnter(target); fireEvent.mouseLeave(target); expect(icon).toHaveAttribute("data-icon-animating"); finishFirst(); await Promise.resolve(); expect(icon).toHaveAttribute("data-icon-animating"); fireEvent.mouseEnter(target); expect(getAnimations).toHaveBeenCalledTimes(1); finishLast(); await Promise.resolve(); expect(icon).toHaveAttribute("data-icon-animating"); fireEvent.mouseLeave(target); await waitFor(() => expect(icon).not.toHaveAttribute("data-icon-animating")); fireEvent.mouseEnter(target); expect(getAnimations).toHaveBeenCalledTimes(2); }); it("keeps playing after keyboard blur and releases cancelled animations", async () => { const { container, unmount } = render( , ); // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access -- Bind browser animation doubles to the decorative SVG, which has no accessible role. const icon = container.querySelector("svg"); if (!icon) throw new Error("Missing icon"); const target = screen.getByRole("button"); // JSDOM does not model the browser keyboard-focus modality. vi.spyOn(target, "matches").mockImplementation( selector => selector === ":focus-visible", ); let cancel!: () => void; Object.defineProperty(icon, "getAnimations", { value: () => [ { finished: new Promise((_resolve, reject) => { cancel = () => reject(new Error("cancelled")); }), }, ], }); target.focus(); expect(icon).toHaveAttribute("data-icon-animating"); target.blur(); expect(icon).toHaveAttribute("data-icon-animating"); cancel(); await waitFor(() => expect(icon).not.toHaveAttribute("data-icon-animating")); target.focus(); unmount(); cancel(); await Promise.resolve(); expect(icon).not.toHaveAttribute("data-icon-animating"); });