81 lines
2.9 KiB
TypeScript
81 lines
2.9 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
|
|
|
|
// ipcMain.handle(invoke 전용)과 ipcMain.on(send 전용)을 분리해 기록한다.
|
|
// Electron에서 ipcRenderer.send 메시지는 handle 등록에 절대 도달하지 않으므로
|
|
// 이 둘을 섞어 쓰는 하네스로는 RecordingTip 클릭 취소 회귀를 잡을 수 없다.
|
|
const invokeHandlers = vi.hoisted(
|
|
() => new Map<string, (event: unknown, ...args: unknown[]) => Promise<unknown>>()
|
|
)
|
|
const sendListeners = vi.hoisted(
|
|
() => new Map<string, Array<(event: unknown, ...args: unknown[]) => void>>()
|
|
)
|
|
const voice = vi.hoisted(() => ({
|
|
cancelSession: vi.fn(),
|
|
on: vi.fn(),
|
|
startSession: vi.fn(async () => undefined),
|
|
stopSession: vi.fn(async () => undefined),
|
|
getState: vi.fn(() => ({})),
|
|
currentSession: null
|
|
}))
|
|
|
|
vi.mock('electron', () => ({
|
|
ipcMain: {
|
|
handle: vi.fn((channel: string, handler: (event: unknown, ...args: unknown[]) => Promise<unknown>) => {
|
|
invokeHandlers.set(channel, handler)
|
|
}),
|
|
on: vi.fn((channel: string, listener: (event: unknown, ...args: unknown[]) => void) => {
|
|
const list = sendListeners.get(channel) ?? []
|
|
list.push(listener)
|
|
sendListeners.set(channel, list)
|
|
})
|
|
}
|
|
}))
|
|
|
|
vi.mock('../../../src/main/services/VoiceModeService', () => ({
|
|
getVoiceModeService: () => voice
|
|
}))
|
|
vi.mock('../../../src/main/windows/WindowManager', () => ({
|
|
getMainWindow: () => null
|
|
}))
|
|
|
|
/** ipcRenderer.send 의미론 — ipcMain.on 리스너에만 전달된다 */
|
|
function simulateRendererSend(channel: string, ...args: unknown[]): number {
|
|
const listeners = sendListeners.get(channel) ?? []
|
|
for (const listener of listeners) listener({}, ...args)
|
|
return listeners.length
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
vi.resetModules()
|
|
vi.clearAllMocks()
|
|
invokeHandlers.clear()
|
|
sendListeners.clear()
|
|
const mod = await import('../../../src/main/ipc/voice-handlers')
|
|
mod.registerVoiceHandlers()
|
|
})
|
|
|
|
describe('VOICE.CANCEL_RECORDING — RecordingTip 팝업(send) 경로', () => {
|
|
it('popupAPI.send로 보낸 취소가 세션을 취소한다', () => {
|
|
const delivered = simulateRendererSend(IPC_CHANNELS.VOICE.CANCEL_RECORDING, {})
|
|
|
|
expect(delivered).toBe(1)
|
|
expect(voice.cancelSession).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('RecordingTip 스크립트가 보내는 채널 문자열과 IPC 상수가 일치한다', () => {
|
|
expect(IPC_CHANNELS.VOICE.CANCEL_RECORDING).toBe('voice:cancelRecording')
|
|
})
|
|
})
|
|
|
|
describe('VOICE.CANCEL_RECORDING — 메인 윈도우(invoke) 경로', () => {
|
|
it('invoke 경로도 여전히 세션을 취소하고 성공을 반환한다', async () => {
|
|
const handler = invokeHandlers.get(IPC_CHANNELS.VOICE.CANCEL_RECORDING)
|
|
if (!handler) throw new Error('cancel invoke handler was not registered')
|
|
|
|
const result = await handler({}, {})
|
|
|
|
expect(voice.cancelSession).toHaveBeenCalledTimes(1)
|
|
expect(result).toMatchObject({ success: true })
|
|
})
|
|
})
|