// @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();
});
});