import { describe, expect, it } from 'vitest'; import { calculateCls, calculateLabTbt, calculateLostPresentationShare, evaluateEventToNextPaint, evaluateSceneContinuity, latestLcpBeforeCutoff, nearestRankP95, postRunningLongTasks, unexpectedLateShifts, type ScriptedActionSample, } from '@/testing/performance-instrumentation'; describe('performance metric math', () => { it('uses exact nearest-rank p95 without interpolation', () => { expect(nearestRankP95([100, 1, 3, 2, 4])).toBe(100); expect(nearestRankP95(Array.from({ length: 20 }, (_, index) => index + 1))).toBe(19); expect(() => nearestRankP95([])).toThrow(/sample/u); }); it('cuts LCP off at the first trusted input or hidden transition', () => { expect(latestLcpBeforeCutoff([100, 200, 450], 400)).toBe(200); expect(() => latestLcpBeforeCutoff([], 400)).toThrow(/LCP/u); }); it('calculates CLS with exact one-second and five-second session windows', () => { expect(calculateCls([ { startTime: 0, value: 0.01, hadRecentInput: false }, { startTime: 1_000, value: 0.01, hadRecentInput: false }, { startTime: 2_000, value: 0.01, hadRecentInput: false }, { startTime: 3_000, value: 0.01, hadRecentInput: false }, { startTime: 4_000, value: 0.01, hadRecentInput: false }, { startTime: 5_000, value: 0.01, hadRecentInput: false }, { startTime: 5_001, value: 0.2, hadRecentInput: false }, { startTime: 5_100, value: 1, hadRecentInput: true }, ])).toBeCloseTo(0.2); }); it('clips lab TBT to the post-50ms portion before running', () => { expect(calculateLabTbt([ { startTime: 0, duration: 100 }, { startTime: 180, duration: 100 }, { startTime: 250, duration: 100 }, ], 20, 300)).toBe(100); expect(() => calculateLabTbt([], null, 300)).toThrow(/FCP/u); }); it('detects every long task intersecting the post-running trace', () => { expect(postRunningLongTasks([ { startTime: 900, duration: 200 }, { startTime: 20_999, duration: 2 }, { startTime: 21_000, duration: 100 }, ], 1_000, 21_000)).toEqual([ { startTime: 900, duration: 200 }, { startTime: 20_999, duration: 2 }, ]); }); it('requires the exact eight action confirmations and applies the conservative 16ms bound', () => { const ids = ['start', 'volume', 'mute', 'unmute', 'pause', 'resume', 'toolkit', 'contact'] as const; const actions: ScriptedActionSample[] = ids.map((id, index) => ({ id, target: `target-${id}`, dispatchTime: index * 1_000, captureEndTime: null, acknowledgementCount: 1, mutationTime: index * 1_000 + 20, doubleRafTime: index * 1_000 + 40, eventTimingSupported: true, eventEntries: index === 7 ? [{ target: `target-${id}`, startTime: index * 1_000, duration: 80, interactionId: 7 }] : [], })); expect(evaluateEventToNextPaint(actions)).toEqual({ values: [16, 16, 16, 16, 16, 16, 16, 80], p95: 80 }); expect(() => evaluateEventToNextPaint(actions.slice(0, 7))).toThrow(/eight/u); expect(() => evaluateEventToNextPaint([{ ...actions[0]!, acknowledgementCount: 2 }, ...actions.slice(1)])).toThrow(/acknowledgement/u); }); it('excludes a later compressed interaction when sequential actions share one DOM target', () => { const ids = ['start', 'volume', 'mute', 'unmute', 'pause', 'resume', 'toolkit', 'contact'] as const; const actions = ids.map((id, index): ScriptedActionSample => ({ id, target: id, dispatchTime: index === 4 ? 400 : index === 5 ? 450 : index * 1_000, captureEndTime: index === 4 ? 450 : null, acknowledgementCount: 1, mutationTime: (index === 4 ? 400 : index === 5 ? 450 : index * 1_000) + 5, doubleRafTime: (index === 4 ? 400 : index === 5 ? 450 : index * 1_000) + 10, eventTimingSupported: true, eventEntries: id === 'pause' ? [ { target: 'pause', startTime: 410, duration: 30, interactionId: 41 }, { target: 'pause', startTime: 460, duration: 40, interactionId: 42 }, ] : id === 'resume' ? [{ target: 'resume', startTime: 460, duration: 40, interactionId: 42 }] : [], })); expect(evaluateEventToNextPaint(actions).values.slice(4, 6)).toEqual([30, 40]); }); it('still rejects genuinely ambiguous interaction groups within one capture window', () => { const ids = ['start', 'volume', 'mute', 'unmute', 'pause', 'resume', 'toolkit', 'contact'] as const; const actions = ids.map((id, index): ScriptedActionSample => ({ id, target: id, dispatchTime: index * 1_000, captureEndTime: null, acknowledgementCount: 1, mutationTime: index * 1_000 + 5, doubleRafTime: index * 1_000 + 10, eventTimingSupported: true, eventEntries: id === 'pause' ? [ { target: 'pause', startTime: 4_010, duration: 30, interactionId: 41 }, { target: 'pause', startTime: 4_020, duration: 40, interactionId: 42 }, ] : [], })); expect(() => evaluateEventToNextPaint(actions)).toThrow(/ambiguous/u); }); it('evaluates scene continuity at interval boundaries and callback coverage', () => { const result = evaluateSceneContinuity( [{ start: 0, end: 100 }], [ { start: 10, end: 12 }, { start: 28, end: 30 }, { start: 46, end: 48 }, { start: 64, end: 66 }, { start: 82, end: 84 }, ], 16.667, ); expect(result.passes).toBe(true); expect(result.expectedCallbacks).toBe(5); expect(result.actualCallbacks).toBe(5); expect(evaluateSceneContinuity([{ start: 0, end: 100 }], [{ start: 60, end: 61 }], 16.667).passes).toBe(false); }); it('counts lost presentation opportunities from page-level RAF intervals', () => { const result = calculateLostPresentationShare([0, 16.667, 50.001, 66.668], 16.667, 3); expect(result.opportunities).toBe(4); expect(result.lost).toBe(1); expect(result.share).toBeCloseTo(0.25); expect(result.hasSufficientCoverage).toBe(true); }); it('allows no unexpected late shifts', () => { expect(unexpectedLateShifts([ { startTime: 99, value: 1, hadRecentInput: false }, { startTime: 101, value: 1, hadRecentInput: true }, { startTime: 102, value: 0.1, hadRecentInput: false }, { startTime: 201, value: 0.1, hadRecentInput: false }, ], 100, 200)).toEqual([{ startTime: 102, value: 0.1, hadRecentInput: false }]); }); });