From 2e73a21151235294ca07a14866e78c2e04d8f12e Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 15 May 2026 17:23:50 +0200 Subject: [PATCH] test(01-02): add RED ring-buffer tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four RED tests pin D-10 (header pinning) and D-11 (30s trim) contracts: - 'first chunk is header' — isFirst marker on first addChunk - 'second chunk is NOT header' — only the first is pinned - 'trim 30s — keeps header, evicts aged tail' — header survives indefinitely - 'trim with empty buffer does not throw' — defensive edge case Plan 03 must export {addChunk, trimAged, getBuffer, resetBuffer} from src/offscreen/recorder.ts to flip these to GREEN. Also stages tests/fixtures/.gitkeep so the fixture dir survives clean checkouts (Plan 07 drops a known-good last_30sec.webm into it after the manual smoke test). Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/fixtures/.gitkeep | 0 tests/offscreen/ring-buffer.test.ts | 40 +++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 tests/fixtures/.gitkeep create mode 100644 tests/offscreen/ring-buffer.test.ts diff --git a/tests/fixtures/.gitkeep b/tests/fixtures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/offscreen/ring-buffer.test.ts b/tests/offscreen/ring-buffer.test.ts new file mode 100644 index 0000000..861db77 --- /dev/null +++ b/tests/offscreen/ring-buffer.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { addChunk, trimAged, getBuffer, resetBuffer } from '../../src/offscreen/recorder'; + +describe('ring buffer', () => { + beforeEach(() => resetBuffer()); + + it('first chunk is header', () => { + addChunk({ size: 1024 } as unknown as Blob, 1_000); + const buf = getBuffer(); + expect(buf.length).toBe(1); + expect(buf[0].isFirst).toBe(true); + }); + + it('second chunk is NOT header', () => { + addChunk({ size: 1024 } as unknown as Blob, 1_000); + addChunk({ size: 512 } as unknown as Blob, 2_000); + const buf = getBuffer(); + expect(buf.length).toBe(2); + expect(buf[0].isFirst).toBe(true); + expect(buf[1].isFirst).toBeFalsy(); + }); + + it('trim 30s — keeps header, evicts aged tail', () => { + addChunk({ size: 1024 } as unknown as Blob, 0); // header at t=0 + addChunk({ size: 512 } as unknown as Blob, 10_000); // t=10s + addChunk({ size: 512 } as unknown as Blob, 35_000); // t=35s + trimAged(40_000); // now=40s + const buf = getBuffer(); + expect(buf[0].isFirst).toBe(true); // header survives unconditionally + expect(buf.length).toBeGreaterThanOrEqual(2); // header + at least the t=35s chunk + // The header chunk's age (40s) does NOT cause it to be trimmed. + const headerStillThere = buf.some((c) => c.isFirst); + expect(headerStillThere).toBe(true); + }); + + it('trim with empty buffer does not throw', () => { + expect(() => trimAged(0)).not.toThrow(); + expect(getBuffer()).toEqual([]); + }); +});