// @ts-nocheck import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals'; import { render } from '@testing-library/react'; import '@testing-library/jest-dom'; // ─── Mocks ─────────────────────────────────────────────────────────────── const mockTrackPerformance = jest.fn(); jest.mock('@/lib/analytics', () => ({ trackPerformance: (...args: unknown[]) => mockTrackPerformance(...args), })); // ─── PerformanceObserver Mock ──────────────────────────────────────────── type ObserverCallback = (list: { getEntries: () => PerformanceEntry[] }) => void; interface MockObserverInstance { observe: jest.Mock; disconnect: jest.Mock; callback: ObserverCallback; type: string; } const mockObservers: MockObserverInstance[] = []; let originalPerformanceObserver: typeof PerformanceObserver; // ─── Helpers ───────────────────────────────────────────────────────────── function simulateLCPEntry(startTime: number) { const lcpObserver = mockObservers.find((o) => o.type === 'largest-contentful-paint'); if (!lcpObserver) return; lcpObserver.callback({ getEntries: () => { const entries = [{ startTime }] as PerformanceEntry[]; entries.push({ startTime: startTime + 100 } as PerformanceEntry); return entries; }, }); } function simulateFIDEntry(processingStart: number, startTime: number) { const fidObserver = mockObservers.find((o) => o.type === 'first-input'); if (!fidObserver) return; fidObserver.callback({ getEntries: () => { const entries = [ { processingStart, startTime } as unknown as PerformanceEntry, ]; return entries; }, }); } function simulateCLSEntry(value: number, hadRecentInput: boolean = false) { const clsObserver = mockObservers.find((o) => o.type === 'layout-shift'); if (!clsObserver) return; clsObserver.callback({ getEntries: () => { const entries = [ { value, hadRecentInput } as unknown as PerformanceEntry, ]; return entries; }, }); } // ─── Tests ─────────────────────────────────────────────────────────────── describe('PerformanceTracker', () => { beforeEach(() => { jest.clearAllMocks(); mockObservers.length = 0; // Mock PerformanceObserver with proper jest.fn tracking originalPerformanceObserver = window.PerformanceObserver; (window as any).PerformanceObserver = class MockPerformanceObserver { callback: ObserverCallback; type: string; _instance: MockObserverInstance; constructor(callback: ObserverCallback) { this.callback = callback; this.type = ''; this._instance = { observe: jest.fn(), disconnect: jest.fn(), callback, type: '', }; mockObservers.push(this._instance); } observe(options: { type: string; buffered: boolean }) { this.type = options.type; this._instance.type = options.type; this._instance.observe(options); } disconnect() { this._instance.disconnect(); } }; }); afterEach(() => { (window as any).PerformanceObserver = originalPerformanceObserver; }); it('should return null (render nothing)', () => { const { PerformanceTracker } = require('./PerformanceTracker'); const { container } = render(); expect(container.innerHTML).toBe(''); }); it('should create observers for LCP, FID, and CLS', () => { const { PerformanceTracker } = require('./PerformanceTracker'); render(); expect(mockObservers.length).toBe(3); const types = mockObservers.map((o) => o.type); expect(types).toContain('largest-contentful-paint'); expect(types).toContain('first-input'); expect(types).toContain('layout-shift'); }); it('should call observe with buffered: true for all observers', () => { const { PerformanceTracker } = require('./PerformanceTracker'); render(); mockObservers.forEach((observer) => { expect(observer.observe).toHaveBeenCalledWith({ type: observer.type, buffered: true, }); }); }); it('should track LCP performance metric', () => { const { PerformanceTracker } = require('./PerformanceTracker'); render(); simulateLCPEntry(2500); // LCP should track the last entry's startTime expect(mockTrackPerformance).toHaveBeenCalledWith('LCP', 2600); }); it('should track FID performance metric', () => { const { PerformanceTracker } = require('./PerformanceTracker'); render(); simulateFIDEntry(150, 50); // FID = processingStart - startTime expect(mockTrackPerformance).toHaveBeenCalledWith('FID', 100); }); it('should track CLS performance metric', () => { const { PerformanceTracker } = require('./PerformanceTracker'); render(); simulateCLSEntry(0.15); // CLS value * 1000 expect(mockTrackPerformance).toHaveBeenCalledWith('CLS', 150); }); it('should not track CLS when entry has recent input', () => { const { PerformanceTracker } = require('./PerformanceTracker'); render(); simulateCLSEntry(0.15, true); expect(mockTrackPerformance).not.toHaveBeenCalledWith('CLS', 150); }); it('should not track CLS when value is 0', () => { const { PerformanceTracker } = require('./PerformanceTracker'); render(); simulateCLSEntry(0); expect(mockTrackPerformance).not.toHaveBeenCalled(); }); it('should disconnect observers on unmount', () => { const { PerformanceTracker } = require('./PerformanceTracker'); const { unmount } = render(); const disconnectFns = mockObservers.map((o) => o.disconnect); unmount(); disconnectFns.forEach((disconnect) => { expect(disconnect).toHaveBeenCalled(); }); }); it('should handle missing PerformanceObserver gracefully', () => { delete (window as any).PerformanceObserver; const { PerformanceTracker } = require('./PerformanceTracker'); expect(() => { render(); }).not.toThrow(); }); });