import { fireEvent, render, 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(window, "matchMedia").mockReturnValue({ matches: true, } as MediaQueryList); const { container, getByRole } = render( , ); const icon = container.querySelector("svg"); if (!icon) throw new Error("Missing icon"); const target = getByRole("button"); let finishFirst!: () => void; let finishLast!: () => void; const getAnimations = vi.fn(() => [ { finished: new Promise((resolve) => { finishFirst = resolve; }), }, { finished: new Promise((resolve) => { finishLast = resolve; }), }, ]); 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, getByRole, unmount } = render( , ); const icon = container.querySelector("svg"); if (!icon) throw new Error("Missing icon"); const target = 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"); });