// @ts-nocheck import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals'; import { render, act } from '@testing-library/react'; import '@testing-library/jest-dom'; // ─── Mocks ─────────────────────────────────────────────────────────────── const mockPathname = jest.fn(() => '/'); jest.mock('next/navigation', () => ({ usePathname: () => mockPathname(), })); const mockTrackScrollDepth = jest.fn(); jest.mock('@/lib/analytics', () => ({ trackScrollDepth: (...args: unknown[]) => mockTrackScrollDepth(...args), })); // ─── Helpers ───────────────────────────────────────────────────────────── function setScrollGeometry(scrollY: number, scrollHeight: number, innerHeight: number) { Object.defineProperty(window, 'scrollY', { value: scrollY, configurable: true, writable: true, }); Object.defineProperty(document.documentElement, 'scrollHeight', { value: scrollHeight, configurable: true, writable: true, }); Object.defineProperty(window, 'innerHeight', { value: innerHeight, configurable: true, writable: true, }); } function dispatchScroll() { window.dispatchEvent(new Event('scroll', { cancelable: true })); } // ─── Tests ─────────────────────────────────────────────────────────────── describe('ScrollDepthTracker', () => { beforeEach(() => { jest.clearAllMocks(); mockPathname.mockReturnValue('/'); setScrollGeometry(0, 2000, 1000); }); afterEach(() => { // Reset scroll position Object.defineProperty(window, 'scrollY', { value: 0, configurable: true, writable: true, }); }); it('should return null (render nothing)', () => { const { ScrollDepthTracker } = require('./ScrollDepthTracker'); const { container } = render(); expect(container.innerHTML).toBe(''); }); it('should track 25% scroll milestone', () => { const { ScrollDepthTracker } = require('./ScrollDepthTracker'); render(); // docHeight = 2000 - 1000 = 1000 // 25% = 250 setScrollGeometry(250, 2000, 1000); act(() => { dispatchScroll(); }); expect(mockTrackScrollDepth).toHaveBeenCalledWith(25); }); it('should track 50% scroll milestone', () => { const { ScrollDepthTracker } = require('./ScrollDepthTracker'); render(); setScrollGeometry(500, 2000, 1000); act(() => { dispatchScroll(); }); expect(mockTrackScrollDepth).toHaveBeenCalledWith(50); }); it('should track 75% scroll milestone', () => { const { ScrollDepthTracker } = require('./ScrollDepthTracker'); render(); setScrollGeometry(750, 2000, 1000); act(() => { dispatchScroll(); }); expect(mockTrackScrollDepth).toHaveBeenCalledWith(75); }); it('should track 100% scroll milestone', () => { const { ScrollDepthTracker } = require('./ScrollDepthTracker'); render(); setScrollGeometry(1000, 2000, 1000); act(() => { dispatchScroll(); }); expect(mockTrackScrollDepth).toHaveBeenCalledWith(100); }); it('should track all milestones cumulatively when scrolling to bottom', () => { const { ScrollDepthTracker } = require('./ScrollDepthTracker'); render(); // Scroll to 100% setScrollGeometry(1000, 2000, 1000); act(() => { dispatchScroll(); }); // Should have tracked all 4 milestones expect(mockTrackScrollDepth).toHaveBeenCalledWith(25); expect(mockTrackScrollDepth).toHaveBeenCalledWith(50); expect(mockTrackScrollDepth).toHaveBeenCalledWith(75); expect(mockTrackScrollDepth).toHaveBeenCalledWith(100); }); it('should not track the same milestone twice', () => { const { ScrollDepthTracker } = require('./ScrollDepthTracker'); render(); // Scroll to 50% setScrollGeometry(500, 2000, 1000); act(() => { dispatchScroll(); }); expect(mockTrackScrollDepth).toHaveBeenCalledTimes(2); // 25% and 50% // Scroll again to 50% act(() => { dispatchScroll(); }); // Should not track again expect(mockTrackScrollDepth).toHaveBeenCalledTimes(2); }); it('should not track when docHeight is <= 0', () => { const { ScrollDepthTracker } = require('./ScrollDepthTracker'); render(); setScrollGeometry(100, 500, 500); // docHeight = 0 act(() => { dispatchScroll(); }); expect(mockTrackScrollDepth).not.toHaveBeenCalled(); }); it('should reset tracked milestones on pathname change', () => { const { ScrollDepthTracker } = require('./ScrollDepthTracker'); const { rerender } = render(); // Scroll to 50% setScrollGeometry(500, 2000, 1000); act(() => { dispatchScroll(); }); expect(mockTrackScrollDepth).toHaveBeenCalledTimes(2); // Change pathname (simulates navigation) mockPathname.mockReturnValue('/products'); rerender(); // Clear mocks to reset call count jest.clearAllMocks(); // Scroll again to 50% - should track again act(() => { dispatchScroll(); }); expect(mockTrackScrollDepth).toHaveBeenCalledWith(25); expect(mockTrackScrollDepth).toHaveBeenCalledWith(50); }); it('should clean up scroll event listener on unmount', () => { const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); const { ScrollDepthTracker } = require('./ScrollDepthTracker'); const { unmount } = render(); expect(addEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function), { passive: true }); unmount(); expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function)); addEventListenerSpy.mockRestore(); removeEventListenerSpy.mockRestore(); }); });