/** * @file useOnScreen.test.tsx * @description useOnScreen follows the IntersectionObserver: on screen by * default (and always without an observer, as in jsdom), off once the element * stops intersecting, and `onShow` runs only on the way back on screen. * @author Nguyễn Ngọc Trí Vĩ */ import { act, render } from "@testing-library/react"; import { useRef } from "react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { useOnScreen, isShown } from "../useOnScreen"; const original = globalThis.IntersectionObserver; afterEach(() => { globalThis.IntersectionObserver = original; }); let seen: { current: boolean | null } = { current: null }; function Probe({ onShow }: { onShow?: () => void }) { const ref = useRef(null); seen = useOnScreen(ref, onShow); return
; } describe("useOnScreen", () => { it("counts as on screen without an IntersectionObserver", () => { // @ts-expect-error removed for the test delete globalThis.IntersectionObserver; render(); expect(seen.current).toBe(true); expect(isShown(seen as never)).toBe(true); }); it("follows the observer and calls onShow only when it comes back", () => { let fire: (v: boolean) => void = () => {}; // @ts-expect-error test stub globalThis.IntersectionObserver = class { constructor(cb: (e: { isIntersecting: boolean }[]) => void) { fire = (v) => cb([{ isIntersecting: v }]); } observe() {} disconnect() {} }; const onShow = vi.fn(); render(); act(() => fire(true)); expect(onShow).not.toHaveBeenCalled(); act(() => fire(false)); expect(seen.current).toBe(false); expect(isShown(seen as never)).toBe(false); act(() => fire(true)); expect(seen.current).toBe(true); expect(onShow).toHaveBeenCalledTimes(1); }); });