import { describe, it, expect, beforeEach } from 'vitest'; import { RingBuffer } from './RingBuffer'; describe('RingBuffer', () => { let ringBuffer: RingBuffer; const SAMPLE_RATE = 16000; const DURATION_SECONDS = 1; // 1 second buffer for easy calculations const MAX_FRAMES = SAMPLE_RATE * DURATION_SECONDS; beforeEach(() => { ringBuffer = new RingBuffer(SAMPLE_RATE, DURATION_SECONDS); }); describe('Initialization', () => { it('should initialize with correct parameters', () => { expect(ringBuffer.sampleRate).toBe(SAMPLE_RATE); expect(ringBuffer.maxFrames).toBe(MAX_FRAMES); expect(ringBuffer.getSize()).toBe(MAX_FRAMES); expect(ringBuffer.getCurrentFrame()).toBe(0); expect(ringBuffer.getFillCount()).toBe(0); }); it('should calculate maxFrames based on duration', () => { const rb = new RingBuffer(8000, 0.5); expect(rb.maxFrames).toBe(4000); }); }); describe('Writing Data', () => { it('should write data correctly when buffer is empty', () => { const chunk = new Float32Array([1, 2, 3]); ringBuffer.write(chunk); expect(ringBuffer.getCurrentFrame()).toBe(3); expect(ringBuffer.getFillCount()).toBe(3); const readData = ringBuffer.read(0, 3); expect(readData).toEqual(chunk); }); it('should append data correctly', () => { const chunk1 = new Float32Array([1, 2]); const chunk2 = new Float32Array([3, 4]); ringBuffer.write(chunk1); ringBuffer.write(chunk2); expect(ringBuffer.getCurrentFrame()).toBe(4); const readData = ringBuffer.read(0, 4); expect(readData).toEqual(new Float32Array([1, 2, 3, 4])); }); it('should handle wrap-around correctly', () => { // Fill buffer almost to the end const initialFill = new Float32Array(MAX_FRAMES - 2); initialFill.fill(0.5); ringBuffer.write(initialFill); // Write a chunk that wraps around const chunk = new Float32Array([1, 2, 3, 4]); ringBuffer.write(chunk); expect(ringBuffer.getCurrentFrame()).toBe(MAX_FRAMES - 2 + 4); // Read the wrapped chunk // Start reading from where we wrote the chunk const startFrame = MAX_FRAMES - 2; const endFrame = startFrame + 4; const readData = ringBuffer.read(startFrame, endFrame); expect(readData).toEqual(chunk); }); it('should handle chunk larger than buffer size', () => { const largeChunk = new Float32Array(MAX_FRAMES + 10); for(let i = 0; i < largeChunk.length; i++) { largeChunk[i] = i; } ringBuffer.write(largeChunk); expect(ringBuffer.getCurrentFrame()).toBe(MAX_FRAMES + 10); expect(ringBuffer.getFillCount()).toBe(MAX_FRAMES); // Should contain the last MAX_FRAMES of the large chunk const expectedData = largeChunk.subarray(10); // The buffer now holds frames from 10 to MAX_FRAMES + 10 const readData = ringBuffer.read(10, MAX_FRAMES + 10); expect(readData).toEqual(expectedData); }); }); describe('Reading Data', () => { it('should read valid range correctly', () => { const chunk = new Float32Array([1, 2, 3, 4, 5]); ringBuffer.write(chunk); const readData = ringBuffer.read(1, 4); // indices 1, 2, 3 expect(readData).toEqual(new Float32Array([2, 3, 4])); }); it('should return empty array when startFrame >= endFrame', () => { const chunk = new Float32Array([1, 2, 3]); ringBuffer.write(chunk); expect(ringBuffer.read(1, 1).length).toBe(0); expect(ringBuffer.read(2, 1).length).toBe(0); }); it('should throw RangeError when startFrame is negative', () => { expect(() => ringBuffer.read(-1, 5)).toThrow(RangeError); }); it('should throw RangeError when reading overwritten data', () => { // Write more than capacity const chunk = new Float32Array(MAX_FRAMES + 10); ringBuffer.write(chunk); // Oldest available frame is 10 // Trying to read frame 5 should fail expect(() => ringBuffer.read(5, 15)).toThrow(RangeError); }); it('should throw RangeError when reading future data', () => { const chunk = new Float32Array([1, 2, 3]); ringBuffer.write(chunk); // Current frame is 3. Requesting up to 5 should fail. expect(() => ringBuffer.read(0, 5)).toThrow(RangeError); }); it('should handle reading across wrap-around point', () => { // Fill buffer almost to the end const initialFill = new Float32Array(MAX_FRAMES - 2); for (let i = 0; i < MAX_FRAMES - 2; i++) initialFill[i] = i; ringBuffer.write(initialFill); // Write more to wrap around const chunk = new Float32Array([100, 101, 102, 103]); ringBuffer.write(chunk); // Buffer now has: // [ ... (MAX_FRAMES-2 items), 100, 101, 102, 103 ] logically // Physically: // Indices [MAX_FRAMES-2, MAX_FRAMES-1] have [100, 101] // Indices [0, 1] have [102, 103] // Read across the boundary const startFrame = MAX_FRAMES - 3; // One before the new chunk const endFrame = MAX_FRAMES + 1; // Into the wrapped part const readData = ringBuffer.read(startFrame, endFrame); // Expected: [last of initial, 100, 101, 102] const expected = new Float32Array([ initialFill[initialFill.length - 1], 100, 101, 102 ]); expect(readData).toEqual(expected); }); }); describe('Helper Methods', () => { it('getCurrentTime should return correct time in seconds', () => { // 1 second buffer const chunk = new Float32Array(SAMPLE_RATE / 2); // 0.5 seconds ringBuffer.write(chunk); expect(ringBuffer.getCurrentTime()).toBe(0.5); }); it('getBaseFrameOffset should return 0 when not full', () => { const chunk = new Float32Array(100); ringBuffer.write(chunk); expect(ringBuffer.getBaseFrameOffset()).toBe(0); }); it('getBaseFrameOffset should update when overwritten', () => { const chunk = new Float32Array(MAX_FRAMES + 50); ringBuffer.write(chunk); expect(ringBuffer.getBaseFrameOffset()).toBe(50); }); it('reset should clear buffer and reset counters', () => { const chunk = new Float32Array([1, 2, 3]); ringBuffer.write(chunk); ringBuffer.reset(); expect(ringBuffer.getCurrentFrame()).toBe(0); expect(ringBuffer.getFillCount()).toBe(0); expect(ringBuffer.read(0, 0).length).toBe(0); // Check consistency // Verify buffer content is cleared (or at least pointer is reset) // Writing new data should start from 0 const newChunk = new Float32Array([9, 9]); ringBuffer.write(newChunk); expect(ringBuffer.read(0, 2)).toEqual(newChunk); }); }); });