- Raise use-swipe-gesture mutation score to 66.13% (target 65%+) - Maintain use-reduced-motion mutation score at 76.32% (target 50%+) - Fix use-reduced-motion.ts ESLint set-state-in-effect warning - Add UJ-10 deep searcher journey (category browse → article read → content discovery) - Add 40 new test files (analytics, detail, sections, ui, lib components) - Update test-strategy-plan.md to v2.0 (sync test count to 1591) - Sync README.md with final release metrics Quality gates: TS 0 errors, ESLint 0 errors, 121 suites / 1591 tests passed
208 lines
6.2 KiB
TypeScript
208 lines
6.2 KiB
TypeScript
// @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(<ScrollDepthTracker />);
|
|
expect(container.innerHTML).toBe('');
|
|
});
|
|
|
|
it('should track 25% scroll milestone', () => {
|
|
const { ScrollDepthTracker } = require('./ScrollDepthTracker');
|
|
render(<ScrollDepthTracker />);
|
|
|
|
// 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(<ScrollDepthTracker />);
|
|
|
|
setScrollGeometry(500, 2000, 1000);
|
|
act(() => {
|
|
dispatchScroll();
|
|
});
|
|
|
|
expect(mockTrackScrollDepth).toHaveBeenCalledWith(50);
|
|
});
|
|
|
|
it('should track 75% scroll milestone', () => {
|
|
const { ScrollDepthTracker } = require('./ScrollDepthTracker');
|
|
render(<ScrollDepthTracker />);
|
|
|
|
setScrollGeometry(750, 2000, 1000);
|
|
act(() => {
|
|
dispatchScroll();
|
|
});
|
|
|
|
expect(mockTrackScrollDepth).toHaveBeenCalledWith(75);
|
|
});
|
|
|
|
it('should track 100% scroll milestone', () => {
|
|
const { ScrollDepthTracker } = require('./ScrollDepthTracker');
|
|
render(<ScrollDepthTracker />);
|
|
|
|
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(<ScrollDepthTracker />);
|
|
|
|
// 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(<ScrollDepthTracker />);
|
|
|
|
// 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(<ScrollDepthTracker />);
|
|
|
|
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(<ScrollDepthTracker />);
|
|
|
|
// Scroll to 50%
|
|
setScrollGeometry(500, 2000, 1000);
|
|
act(() => {
|
|
dispatchScroll();
|
|
});
|
|
expect(mockTrackScrollDepth).toHaveBeenCalledTimes(2);
|
|
|
|
// Change pathname (simulates navigation)
|
|
mockPathname.mockReturnValue('/products');
|
|
rerender(<ScrollDepthTracker />);
|
|
|
|
// 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(<ScrollDepthTracker />);
|
|
|
|
expect(addEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function), { passive: true });
|
|
|
|
unmount();
|
|
|
|
expect(removeEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
|
|
|
|
addEventListenerSpy.mockRestore();
|
|
removeEventListenerSpy.mockRestore();
|
|
});
|
|
}); |