1import { describe, it, expect, beforeEach } from 'vitest'; 2import { 3 enableFrameCost, 4 isFrameCostEnabled, 5 resetFrameCost, 6 sampleFrame, 7 getFrameCostReport, 8} from './frame-cost.js'; 9 10describe('frame-cost sampler', () => { 11 beforeEach(() => { 12 enableFrameCost(false); 13 resetFrameCost(); 14 }); 15 16 it('records nothing while disabled', () => { 17 expect(isFrameCostEnabled()).toBe(false); 18 sampleFrame('layer', 12, 'compute'); 19 expect(getFrameCostReport()).toEqual({}); 20 }); 21 22 it('records per-layer percentiles once enabled', () => { 23 enableFrameCost(); 24 // 1..100 ms so the percentile positions are trivially checkable. 25 for (let i = 1; i <= 100; i++) sampleFrame('layer', i); 26 27 const r = getFrameCostReport().layer; 28 expect(r.frames).toBe(100); 29 expect(r.p50).toBe(50); 30 expect(r.p95).toBe(95); 31 expect(r.max).toBe(100); 32 expect(r.mean).toBe(50.5); 33 }); 34 35 it('keeps layers independent', () => { 36 enableFrameCost(); 37 sampleFrame('a', 5); 38 sampleFrame('b', 50); 39 40 const r = getFrameCostReport(); 41 expect(r.a.max).toBe(5); 42 expect(r.b.max).toBe(50); 43 }); 44 45 it('counts render paths so the compute-vs-remap ratio is readable', () => { 46 enableFrameCost(); 47 sampleFrame('heatmap', 20, 'compute'); 48 for (let i = 0; i < 9; i++) sampleFrame('heatmap', 0.4, 'remap'); 49 50 const r = getFrameCostReport().heatmap; 51 expect(r.paths).toEqual({ compute: 1, remap: 9 }); 52 // The whole point of viewport decoupling: the expensive path is the rare one. 53 expect(r.paths.remap / r.frames).toBeGreaterThan(0.8); 54 }); 55 56 it('ages path counts out of the window with their samples', () => { 57 enableFrameCost(); 58 // A burst of computes, then a long drag that pushes them all out of the 240-frame window. 59 for (let i = 0; i < 10; i++) sampleFrame('heatmap', 20, 'compute'); 60 for (let i = 0; i < 240; i++) sampleFrame('heatmap', 0.4, 'remap'); 61 62 const r = getFrameCostReport().heatmap; 63 // Counts must describe the SAME frames the percentiles do -- a lifetime tally would still 64 // be claiming 10 computes here, long after none of them were in the reported window. 65 expect(r.paths).toEqual({ remap: 240 }); 66 expect(r.frames).toBe(240); 67 expect(r.max).toBe(0.4); 68 }); 69 70 it('bounds memory by overwriting the oldest samples', () => { 71 enableFrameCost(); 72 // 500 samples into a 240-slot window: only the last 240 survive, so the early 73 // (cheap) frames must have been evicted by the later (expensive) ones. 74 for (let i = 1; i <= 500; i++) sampleFrame('layer', i); 75 76 const r = getFrameCostReport().layer; 77 expect(r.frames).toBe(240); 78 expect(r.max).toBe(500); 79 expect(r.p50).toBeGreaterThan(240); 80 }); 81 82 it('ignores non-finite durations rather than poisoning the percentiles', () => { 83 enableFrameCost(); 84 sampleFrame('layer', NaN); 85 sampleFrame('layer', Infinity); 86 sampleFrame('layer', undefined); 87 sampleFrame('layer', 7); 88 89 const r = getFrameCostReport().layer; 90 expect(r.frames).toBe(1); 91 expect(r.max).toBe(7); 92 }); 93 94 it('drops all samples when disabled again', () => { 95 enableFrameCost(); 96 sampleFrame('layer', 9); 97 enableFrameCost(false); 98 expect(getFrameCostReport()).toEqual({}); 99 }); 100});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.