d3ro-voice/apps/desktop/tests/main/services/voice-intent.test.ts

84 lines
4.3 KiB
TypeScript

// 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)
})
})