// tests/main/services/voice-intent.test.ts // 키 제스처 → 세션 의도 표 테스트 (더블탭 on/off, hold, 처리 중 토글). import { describe, it, expect } from 'vitest' import { deriveVoiceIntentPhase, resolveGestureHoldMode, resolveGestureMode, resolveVoiceIntent, type VoiceGesture, type VoiceIntent, type VoiceIntentPhase, } from '../../../src/main/services/voice-intent' const dictationPress: VoiceGesture = { type: 'press', mode: 'dictation', holdMode: true } const dictationRelease: VoiceGesture = { type: 'release', mode: 'dictation', holdMode: true } const handsFreePress: VoiceGesture = { type: 'press', mode: 'hands-free', holdMode: false } const handsFreeRelease: VoiceGesture = { type: 'release', mode: 'hands-free', holdMode: false } const START_DICTATION: VoiceIntent = { kind: 'start', mode: 'dictation' } const START_HANDS_FREE: VoiceIntent = { kind: 'start', mode: 'hands-free' } const STOP: VoiceIntent = { kind: 'stop' } const IGNORE: VoiceIntent = { kind: 'ignore' } describe('resolveVoiceIntent', () => { it.each<[string, VoiceGesture, VoiceIntentPhase, VoiceIntent]>([ // hold-to-talk ['idle 에서 누르면 받아쓰기를 시작한다', dictationPress, 'idle', START_DICTATION], ['녹음 중 누름은 무시한다', dictationPress, 'recording', IGNORE], ['녹음 중 떼면 정지한다', dictationRelease, 'recording', STOP], ['idle 에서 뗌은 무시한다', dictationRelease, 'idle', IGNORE], ['처리 중 뗌은 무시한다', dictationRelease, 'processing', IGNORE], // 핸즈프리 토글 (더블탭) ['idle 에서 더블탭은 핸즈프리를 켠다', handsFreePress, 'idle', START_HANDS_FREE], ['녹음 중 더블탭은 끈다', handsFreePress, 'recording', STOP], ['처리 중 더블탭은 새 녹음을 시작하지 않는다', handsFreePress, 'processing', IGNORE], ['토글의 release 는 늘 무시한다', handsFreeRelease, 'recording', IGNORE], ['토글의 release 는 idle 에서도 무시한다', handsFreeRelease, 'idle', IGNORE], // 더블탭 정지의 첫 탭(dictation press/release)이 핸즈프리 세션을 멈춘다 (기존 동작 보존) ['핸즈프리 녹음 중 첫 탭 press 는 무시', dictationPress, 'recording', IGNORE], ['핸즈프리 녹음 중 첫 탭 release 가 정지', dictationRelease, 'recording', STOP], ['처리 중 받아쓰기 누름은 무시', dictationPress, 'processing', IGNORE], ])('%s', (_label, gesture, phase, expected) => { expect(resolveVoiceIntent(gesture, phase)).toEqual(expected) }) it('holdMode 가 아닌 dictation release 는 정지하지 않는다', () => { expect(resolveVoiceIntent({ type: 'release', mode: 'dictation', holdMode: false }, 'recording')).toEqual(IGNORE) }) it('더블탭 on → 더블탭 off 시퀀스: 두 번째 탭은 처리 중에 무시된다', () => { // on expect(resolveVoiceIntent(handsFreePress, 'idle')).toEqual(START_HANDS_FREE) // off: 첫 탭 press/release, 정지 후 처리 중에 두 번째 탭 expect(resolveVoiceIntent(dictationPress, 'recording')).toEqual(IGNORE) expect(resolveVoiceIntent(dictationRelease, 'recording')).toEqual(STOP) expect(resolveVoiceIntent(handsFreePress, 'processing')).toEqual(IGNORE) expect(resolveVoiceIntent(handsFreeRelease, 'processing')).toEqual(IGNORE) }) }) describe('deriveVoiceIntentPhase', () => { it('세션 상태에서 단계를 도출한다', () => { expect(deriveVoiceIntentPhase(false, false)).toBe('idle') expect(deriveVoiceIntentPhase(false, true)).toBe('idle') expect(deriveVoiceIntentPhase(true, false)).toBe('recording') expect(deriveVoiceIntentPhase(true, true)).toBe('processing') }) }) describe('제스처 입력 정규화', () => { it('더블탭이거나 hands-free 액션이면 핸즈프리다', () => { expect(resolveGestureMode('dictation', false)).toBe('dictation') expect(resolveGestureMode('dictation', true)).toBe('hands-free') expect(resolveGestureMode('hands-free', false)).toBe('hands-free') expect(resolveGestureMode('command', false)).toBe('dictation') }) it("'command' 는 dictation 파이프라인 fallback 이라 hold-to-talk 이다", () => { expect(resolveGestureHoldMode('command', false)).toBe(true) expect(resolveGestureHoldMode('dictation', true)).toBe(true) expect(resolveGestureHoldMode('hands-free', false)).toBe(false) }) })