/** * Unit tests for streamTts — the pluggable streaming TTS surface. * Anchored to § 5.3 of docs/analysis/voice-call-streaming-design.md. */ import { describe, it, expect } from 'vitest'; import { createStreamingTts, streamTtsInternals } from './streamTts'; const { splitIntoSentences, NullStreamTts } = streamTtsInternals; describe('splitIntoSentences', () => { it('splits on sentence enders followed by whitespace', () => { const { sentences, remainder } = splitIntoSentences('hello there. how are you? fine! and you'); expect(sentences).toEqual([ 'hello there.', 'how are you?', 'fine!', ]); expect(remainder).toBe('and you'); }); it('keeps partial trailing text as remainder', () => { const { sentences, remainder } = splitIntoSentences('partial sentence'); expect(sentences).toEqual([]); expect(remainder).toBe('partial sentence'); }); it('is safe on empty input', () => { const { sentences, remainder } = splitIntoSentences(''); expect(sentences).toEqual([]); expect(remainder).toBe(''); }); it('splits on newlines + semicolons', () => { const { sentences, remainder } = splitIntoSentences('one thing;\n another thing.\n tail'); // Exact count + order matters so regressions are obvious. expect(sentences.length).toBeGreaterThanOrEqual(2); expect(sentences[0]).toContain('one thing'); expect(remainder.trim()).toBe('tail'); }); }); describe('NullStreamTts fallback', () => { it('is safe to call every method; never claims to be speaking', () => { const tts = new NullStreamTts(); expect(tts.isSpeaking).toBe(false); tts.appendDelta('hello'); tts.flush(); tts.stop(); expect(tts.isSpeaking).toBe(false); }); }); describe('createStreamingTts factory', () => { it('returns an object implementing the StreamingTts contract', () => { const tts = createStreamingTts(); expect(typeof tts.appendDelta).toBe('function'); expect(typeof tts.flush).toBe('function'); expect(typeof tts.stop).toBe('function'); expect(typeof tts.isSpeaking).toBe('boolean'); }); });