import { describe, it, expect } from 'vitest' import { IPC_CHANNELS } from '@d3ro/core/ipc-channels' import { getVoiceCommandService } from '../../src/main/services/VoiceCommandService' import { registerVoiceCommandHandlers } from '../../src/main/ipc/voice-command-handlers' import { invokeIpc, useRedHarness } from './harness' useRedHarness() describe('유스케이스: 음성 단축키 매칭 / 토글 / 키워드 CRUD', () => { it('초기에는 비활성이다', () => { const svc = getVoiceCommandService() svc.initialize() expect(svc.isEnabled()).toBe(false) }) it('비활성 상태에서는 키워드가 있어도 매칭하지 않는다', () => { const svc = getVoiceCommandService() svc.initialize() svc.initDefaultKeywords() const match = svc.match('번역해줘 이 문장') expect(match.matched).toBe(false) expect(match.cleanedText).toBe('번역해줘 이 문장') }) it('활성 + 기본 키워드면 prefix 매칭한다', () => { const svc = getVoiceCommandService() svc.initialize() svc.initDefaultKeywords() svc.setEnabled(true) const match = svc.match('번역해줘 이 문장') expect(match.matched).toBe(true) expect(match.instructionId).toBe('builtin-translate') expect(match.cleanedText).toBe('이 문장') expect(match.matchedKeyword).toBe('번역해줘') }) it('요약 키워드도 매칭한다', () => { const svc = getVoiceCommandService() svc.initialize() svc.initDefaultKeywords() svc.setEnabled(true) expect(svc.match('요약해줘 긴 글').instructionId).toBe('builtin-summarize') }) it('매칭 실패 시 cleanedText 는 원문이다', () => { const svc = getVoiceCommandService() svc.initialize() svc.initDefaultKeywords() svc.setEnabled(true) const match = svc.match('그냥 받아쓰기') expect(match.matched).toBe(false) expect(match.cleanedText).toBe('그냥 받아쓰기') }) it('빈 텍스트는 매칭하지 않는다', () => { const svc = getVoiceCommandService() svc.initialize() svc.initDefaultKeywords() svc.setEnabled(true) expect(svc.match(' ').matched).toBe(false) }) it('사용자 키워드를 추가하면 contains 로 잡힌다', () => { const svc = getVoiceCommandService() svc.initialize() svc.setEnabled(true) svc.setKeywordsForInstruction('builtin-formal', [ { keyword: '정중하게', matchMode: 'contains' }, ]) const match = svc.match('이 메일 정중하게 써줘') expect(match.matched).toBe(true) expect(match.instructionId).toBe('builtin-formal') }) it('같은 instruction 키워드를 덮어쓴다', () => { const svc = getVoiceCommandService() svc.initialize() svc.setEnabled(true) svc.setKeywordsForInstruction('builtin-formal', [ { keyword: 'oldkw', matchMode: 'prefix' }, ]) svc.setKeywordsForInstruction('builtin-formal', [ { keyword: 'newkw', matchMode: 'prefix' }, ]) expect(svc.match('oldkw x').matched).toBe(false) expect(svc.match('newkw x').matched).toBe(true) }) it('initDefaultKeywords 두 번은 규칙을 복제하지 않는다', () => { const svc = getVoiceCommandService() svc.initialize() svc.initDefaultKeywords() const n = svc.getAllRules().length svc.initDefaultKeywords() expect(svc.getAllRules().length).toBe(n) }) it('비활성화하면 다시 매칭하지 않는다', () => { const svc = getVoiceCommandService() svc.initialize() svc.initDefaultKeywords() svc.setEnabled(true) svc.setEnabled(false) expect(svc.match('번역해줘 x').matched).toBe(false) }) it('IPC setEnabled / isEnabled 왕복', async () => { getVoiceCommandService().initialize() registerVoiceCommandHandlers() await invokeIpc(IPC_CHANNELS.VOICE_COMMAND.SET_ENABLED, { enabled: true }) const res = await invokeIpc(IPC_CHANNELS.VOICE_COMMAND.IS_ENABLED) expect(res.success).toBe(true) if (res.success) expect(res.data).toBe(true) }) it('IPC getAll 은 배열이다', async () => { const svc = getVoiceCommandService() svc.initialize() svc.initDefaultKeywords() registerVoiceCommandHandlers() const res = await invokeIpc(IPC_CHANNELS.VOICE_COMMAND.GET_ALL) expect(res.success).toBe(true) if (res.success) expect(Array.isArray(res.data)).toBe(true) }) it('IPC setKeywords 후 매칭된다', async () => { const svc = getVoiceCommandService() svc.initialize() svc.setEnabled(true) registerVoiceCommandHandlers() const set = await invokeIpc(IPC_CHANNELS.VOICE_COMMAND.SET_KEYWORDS, { instructionId: 'builtin-translate', keywords: [{ keyword: '영작', matchMode: 'prefix' }], }) expect(set.success).toBe(true) expect(svc.match('영작 hello').matched).toBe(true) }) })