84 lines
4.3 KiB
TypeScript
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)
|
|
})
|
|
})
|