obsidian-voice-note/tests/capture.test.ts
Andrey Epifancev b126e4bf8b feat(core): save captured audio and initial note; in-memory port fakes
Refs #1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 16:32:34 +04:00

81 lines
3.1 KiB
TypeScript

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> = {}): 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');
});
});