import { resolve } from "node:path"; import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; import { render, fireEvent, act } from "@testing-library/preact"; import { Tooltip } from "./Tooltip.js"; describe("Tooltip", () => { beforeEach(() => { vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); }); it("renders children", () => { const { getByText } = render( , ); expect(getByText("Hover me")).toBeTruthy(); }); it("renders tooltip content text", () => { const { container } = render( , ); const tooltip = container.querySelector('[role="tooltip"]'); expect(tooltip).toBeTruthy(); expect(tooltip!.textContent).toBe("Helpful tip"); }); it("tooltip is hidden by default", () => { const { container } = render( , ); const tooltip = container.querySelector('[role="tooltip"]'); expect(tooltip).toHaveAttribute("aria-hidden", "true"); }); it("shows tooltip on mouseenter after delay", () => { const { container } = render( , ); const wrapper = container.querySelector(".strand-tooltip__wrapper")!; const tooltip = container.querySelector('[role="tooltip"]')!; fireEvent.mouseEnter(wrapper); act(() => { vi.advanceTimersByTime(100); }); expect(tooltip).toHaveAttribute("aria-hidden", "false"); }); it("hides tooltip on mouseleave", () => { const { container } = render( , ); const wrapper = container.querySelector(".strand-tooltip__wrapper")!; const tooltip = container.querySelector('[role="tooltip"]')!; fireEvent.mouseEnter(wrapper); act(() => { vi.advanceTimersByTime(100); }); expect(tooltip).toHaveAttribute("aria-hidden", "false"); fireEvent.mouseLeave(wrapper); expect(tooltip).toHaveAttribute("aria-hidden", "true"); }); it("shows for a keyboard user when the trigger takes focus, and hides when it leaves", () => { const { container } = render( , ); const button = container.querySelector("button")!; const tooltip = container.querySelector('[role="tooltip"]')!; act(() => { button.focus(); }); act(() => { vi.advanceTimersByTime(100); }); expect(tooltip).toHaveAttribute("aria-hidden", "false"); act(() => { button.blur(); }); expect(tooltip).toHaveAttribute("aria-hidden", "true"); }); it("tooltip has role tooltip", () => { const { container } = render( , ); expect(container.querySelector('[role="tooltip"]')).toBeTruthy(); }); it("the trigger element itself is described by the tooltip", () => { const { container } = render( , ); const button = container.querySelector("button")!; const tooltip = container.querySelector('[role="tooltip"]')!; expect(button).toHaveAttribute("aria-describedby", tooltip.getAttribute("id")); expect(container.querySelector(".strand-tooltip__wrapper")).not.toHaveAttribute("aria-describedby"); }); it("describes through the wrapper when the trigger is plain text", () => { const { container } = render(Target); const tooltip = container.querySelector('[role="tooltip"]')!; expect(container.querySelector(".strand-tooltip__wrapper")).toHaveAttribute("aria-describedby", tooltip.getAttribute("id")); }); it("Escape dismisses an open tooltip and reports the change", () => { const onOpenChange = vi.fn(); const { container } = render( , ); const wrapper = container.querySelector(".strand-tooltip__wrapper")!; const tooltip = container.querySelector('[role="tooltip"]')!; act(() => { container.querySelector("button")!.focus(); }); act(() => { vi.advanceTimersByTime(1); }); expect(tooltip).toHaveAttribute("aria-hidden", "false"); expect(onOpenChange).toHaveBeenLastCalledWith(true); fireEvent.keyDown(wrapper, { key: "Escape" }); expect(tooltip).toHaveAttribute("aria-hidden", "true"); expect(onOpenChange).toHaveBeenLastCalledWith(false); }); it("a controlled tooltip shows what its owner says, whatever the pointer does", () => { const { container, rerender } = render( , ); const tooltip = container.querySelector('[role="tooltip"]')!; expect(tooltip).toHaveAttribute("aria-hidden", "false"); fireEvent.mouseLeave(container.querySelector(".strand-tooltip__wrapper")!); expect(tooltip).toHaveAttribute("aria-hidden", "false"); rerender( , ); expect(tooltip).toHaveAttribute("aria-hidden", "true"); }); it("starts open when asked, and still closes on leave", () => { const { container } = render( , ); const tooltip = container.querySelector('[role="tooltip"]')!; expect(tooltip).toHaveAttribute("aria-hidden", "false"); fireEvent.mouseLeave(container.querySelector(".strand-tooltip__wrapper")!); expect(tooltip).toHaveAttribute("aria-hidden", "true"); }); it("cancels show if mouseleave before delay completes", () => { const { container } = render( , ); const wrapper = container.querySelector(".strand-tooltip__wrapper")!; const tooltip = container.querySelector('[role="tooltip"]')!; fireEvent.mouseEnter(wrapper); act(() => { vi.advanceTimersByTime(100); }); fireEvent.mouseLeave(wrapper); act(() => { vi.advanceTimersByTime(300); }); expect(tooltip).toHaveAttribute("aria-hidden", "true"); }); }); import { snapshotFixtures } from "../../test/snapshot.js"; import { snapshotStylesheet } from "../../test/stylesheet.js"; import { fixtures } from "./Tooltip.fixtures.js"; snapshotFixtures(Tooltip, fixtures); snapshotStylesheet(resolve(__dirname, "./Tooltip.css"));