import { describe, expect, it } from 'vitest'; import { saveCapture, type Capture } from '../src/core/capture'; import { initialNote, readVoiceFields } from '../src/core/note'; import { MemoryNotes, MemoryRecordings, TestEnv } from './fakes'; const T = new Date('2026-09-27T11:15:03.000Z'); const buf = (...bytes: number[]) => new Uint8Array(bytes).buffer; function setup() { const env = new TestEnv(T); const notes = new MemoryNotes(); const recordings = new MemoryRecordings(); return { env, notes, recordings, deps: { env, notes, recordings } }; } const capture = (over: Partial = {}): Capture => ({ chunks: [buf(1, 2), buf(3, 4, 5)], mime: 'audio/mp4', startedAt: T, endedAt: new Date(T.getTime() + 47_000), interrupted: false, ...over, }); describe('saveCapture', () => { it('writes concatenated audio and a saved note', async () => { const { deps, notes, recordings } = setup(); const path = await saveCapture(deps, capture()); expect(path).toBe('voice/inbox/2026-09-27 14-15-03.md'); const audio = recordings.files.get('voice/source/20260927-111503-0000.m4a'); expect(new Uint8Array(audio!)).toEqual(new Uint8Array([1, 2, 3, 4, 5])); expect(notes.files.get(path!)).toBe( initialNote( { id: '20260927-111503-0000', status: 'saved', device: 'test-device', audio: 'voice/source/20260927-111503-0000.m4a', mime: 'audio/mp4', attempts: 0, }, 47_000, ), ); }); it.each([[[]], [[new ArrayBuffer(0)]], [[new ArrayBuffer(0), new ArrayBuffer(0)]]])( 'creates nothing for empty data %#', async (chunks) => { const { deps, notes, recordings } = setup(); expect(await saveCapture(deps, capture({ chunks, interrupted: true }))).toBeNull(); expect(notes.files.size).toBe(0); expect(recordings.files.size).toBe(0); }, ); it('marks interrupted recordings with duration', async () => { const { deps, notes } = setup(); const path = await saveCapture(deps, capture({ interrupted: true })); const content = notes.files.get(path!)!; expect(readVoiceFields(content)['voice-interrupted']).toBe('true'); expect(content).toContain('> [!note] Запись прервана, сохранено 0:47'); }); it('adds (2), (3) on name collision', async () => { const { deps, notes } = setup(); notes.files.set('voice/inbox/2026-09-27 14-15-03.md', 'чужая'); expect(await saveCapture(deps, capture())).toBe('voice/inbox/2026-09-27 14-15-03 (2).md'); expect(await saveCapture(deps, capture())).toBe('voice/inbox/2026-09-27 14-15-03 (3).md'); expect(notes.files.get('voice/inbox/2026-09-27 14-15-03.md')).toBe('чужая'); }); it('keeps full mime and picks extension from it', async () => { const { deps, notes, recordings } = setup(); const path = await saveCapture(deps, capture({ mime: 'audio/webm;codecs=opus' })); expect(recordings.files.has('voice/source/20260927-111503-0000.webm')).toBe(true); expect(readVoiceFields(notes.files.get(path!)!)['voice-mime']).toBe('audio/webm;codecs=opus'); }); });