import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { render } from '@testing-library/svelte'; import InfiniteScrollSentinel from './InfiniteScrollSentinel.svelte'; // jsdom doesn't ship IntersectionObserver. We install a minimal mock that // captures the registered callback so tests can drive intersection events // synchronously. type IOInstance = { observe: ReturnType; disconnect: ReturnType; fire: (isIntersecting: boolean) => void; }; const created: IOInstance[] = []; beforeEach(() => { created.length = 0; (globalThis as unknown as { IntersectionObserver: unknown }).IntersectionObserver = class MockIO { private cb: IntersectionObserverCallback; observe = vi.fn(); disconnect = vi.fn(); constructor(cb: IntersectionObserverCallback) { this.cb = cb; created.push({ observe: this.observe, disconnect: this.disconnect, fire: (isIntersecting: boolean) => { this.cb( [{ isIntersecting } as unknown as IntersectionObserverEntry], this as unknown as IntersectionObserver ); } }); } }; }); afterEach(() => { vi.clearAllMocks(); }); describe('InfiniteScrollSentinel', () => { test('observes the sentinel when enabled', () => { const onIntersect = vi.fn(); render(InfiniteScrollSentinel, { props: { onIntersect, enabled: true } }); expect(created).toHaveLength(1); expect(created[0].observe).toHaveBeenCalledTimes(1); }); test('does not create an observer when enabled is false', () => { const onIntersect = vi.fn(); render(InfiniteScrollSentinel, { props: { onIntersect, enabled: false } }); expect(created).toHaveLength(0); }); test('fires onIntersect when the sentinel enters view', () => { const onIntersect = vi.fn(); render(InfiniteScrollSentinel, { props: { onIntersect, enabled: true } }); created[0].fire(true); expect(onIntersect).toHaveBeenCalledTimes(1); }); test('does not fire onIntersect when the entry is not intersecting', () => { const onIntersect = vi.fn(); render(InfiniteScrollSentinel, { props: { onIntersect, enabled: true } }); created[0].fire(false); expect(onIntersect).not.toHaveBeenCalled(); }); test('disconnects the observer when the component unmounts', () => { const onIntersect = vi.fn(); const { unmount } = render(InfiniteScrollSentinel, { props: { onIntersect, enabled: true } }); unmount(); expect(created[0].disconnect).toHaveBeenCalledTimes(1); }); });