// @vitest-environment jsdom import * as React from "react"; import { act, cleanup, render } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { getOverflowTitle, useOverflowTitle } from "./use-overflow-title"; class ResizeObserverStub implements ResizeObserver { readonly observedElements: Element[] = []; disconnectCount = 0; constructor(private readonly callback: ResizeObserverCallback) { resizeObservers.push(this); } disconnect(): void { this.disconnectCount += 1; } observe(target: Element): void { this.observedElements.push(target); } unobserve(target: Element): void { const targetIndex = this.observedElements.indexOf(target); if (targetIndex >= 0) { this.observedElements.splice(targetIndex, 1); } } notify(): void { this.callback([], this); } } let resizeObservers: ResizeObserverStub[] = []; afterEach(() => { cleanup(); resizeObservers = []; vi.unstubAllGlobals(); }); describe("overflow-only native titles", () => { it("returns no title without both a viewport and text title", () => { expect(getOverflowTitle(null, "Title")).toBeUndefined(); expect( getOverflowTitle({ clientWidth: 100, scrollWidth: 200 }, undefined), ).toBeUndefined(); }); it("uses the shared one-pixel overflow tolerance", () => { expect( getOverflowTitle({ clientWidth: 100, scrollWidth: 101 }, "Title"), ).toBeUndefined(); expect( getOverflowTitle({ clientWidth: 100, scrollWidth: 102 }, "Title"), ).toBe("Title"); }); it("recomputes into and out of overflow without changing visible text", () => { vi.stubGlobal("ResizeObserver", ResizeObserverStub); let clientWidth = 100; let scrollWidth = 100; function Harness({ textTitle }: { textTitle: string | undefined }) { const viewportRef = React.useRef(null); const resolvedTitle = useOverflowTitle(viewportRef, textTitle); return (
Incremental Revenue
); } const { container, rerender, unmount } = render( , ); const viewport = container.firstElementChild as HTMLDivElement; const visibleText = viewport.firstElementChild as HTMLSpanElement; Object.defineProperties(viewport, { clientWidth: { configurable: true, get: () => clientWidth, }, scrollWidth: { configurable: true, get: () => scrollWidth, }, }); expect(resizeObservers).toHaveLength(1); expect(resizeObservers[0]?.observedElements).toEqual([ viewport, visibleText, ]); expect(viewport.dataset.resolvedTitle).toBeUndefined(); scrollWidth = 102; act(() => resizeObservers[0]?.notify()); expect(viewport.dataset.resolvedTitle).toBe("Incremental Revenue"); expect(viewport.textContent).toBe("Incremental Revenue"); scrollWidth = 101; act(() => resizeObservers[0]?.notify()); expect(viewport.dataset.resolvedTitle).toBeUndefined(); expect(viewport.textContent).toBe("Incremental Revenue"); scrollWidth = 200; act(() => resizeObservers[0]?.notify()); expect(viewport.dataset.resolvedTitle).toBe("Incremental Revenue"); rerender(); expect(viewport.dataset.resolvedTitle).toBeUndefined(); expect(viewport.textContent).toBe("Incremental Revenue"); expect(resizeObservers[0]?.disconnectCount).toBe(1); unmount(); expect(resizeObservers[0]?.disconnectCount).toBe(1); }); });