116 lines
4.2 KiB
TypeScript
116 lines
4.2 KiB
TypeScript
import { EventEmitter } from 'events'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { IPC_CHANNELS } from '@d3ro/core/ipc-channels'
|
|
import { ErrorCode } from '@d3ro/core/errors'
|
|
import type { IPCResult } from '@d3ro/core/errors'
|
|
import type { CaptionSegment, MeetingModeStateInfo } from '@d3ro/core/types'
|
|
|
|
// 회의 전사 세그먼트 편집이 main 에 핸들러가 없는 채널을 호출하던 회귀
|
|
// (preload editSegment → 'meetingMode:editSegment' → No handler registered).
|
|
|
|
const invokeHandlers = vi.hoisted(
|
|
() => new Map<string, (event: unknown, ...args: unknown[]) => unknown>(),
|
|
)
|
|
|
|
type FakeMeetingService = EventEmitter & {
|
|
stateInfo: MeetingModeStateInfo
|
|
getStateInfo: () => MeetingModeStateInfo
|
|
updateTranscript: ReturnType<typeof vi.fn>
|
|
}
|
|
|
|
// vi.mock 팩토리는 모듈을 처음 import 할 때(beforeEach 안) 불리므로 이 상수는 이미 초기화되어 있다.
|
|
const service = Object.assign(new EventEmitter(), {
|
|
stateInfo: { state: 'recording', sessionId: 's1', elapsedMs: 0, memoCount: 0, segmentCount: 0 },
|
|
getStateInfo(this: FakeMeetingService): MeetingModeStateInfo {
|
|
return this.stateInfo
|
|
},
|
|
updateTranscript: vi.fn(),
|
|
}) as FakeMeetingService
|
|
|
|
vi.mock('electron', () => ({
|
|
ipcMain: {
|
|
handle: vi.fn((channel: string, handler: (event: unknown, ...args: unknown[]) => unknown) => {
|
|
invokeHandlers.set(channel, handler)
|
|
}),
|
|
on: vi.fn(),
|
|
},
|
|
}))
|
|
|
|
vi.mock('../../../src/main/services/MeetingModeService', () => ({
|
|
getMeetingModeService: () => service,
|
|
}))
|
|
|
|
async function invoke<T>(channel: string, ...args: unknown[]): Promise<IPCResult<T>> {
|
|
const handler = invokeHandlers.get(channel)
|
|
if (!handler) throw new Error(`No handler registered for '${channel}'`)
|
|
return (await handler({}, ...args)) as IPCResult<T>
|
|
}
|
|
|
|
function segment(id: string, text: string): CaptionSegment {
|
|
return { id, text, timestamp: 0, isFinal: true }
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
vi.resetModules()
|
|
invokeHandlers.clear()
|
|
service.removeAllListeners()
|
|
service.updateTranscript.mockReset()
|
|
service.stateInfo = { state: 'recording', sessionId: 's1', elapsedMs: 0, memoCount: 0, segmentCount: 0 }
|
|
const mod = await import('../../../src/main/ipc/meeting-mode-handlers')
|
|
mod.registerMeetingModeHandlers()
|
|
})
|
|
|
|
describe('MEETING_MODE.EDIT_SEGMENT', () => {
|
|
it('핸들러가 등록되어 있다', () => {
|
|
expect(invokeHandlers.has(IPC_CHANNELS.MEETING_MODE.EDIT_SEGMENT)).toBe(true)
|
|
})
|
|
|
|
it('녹음 중 수정이 세션 완료 시 editedTranscript 로 저장된다', async () => {
|
|
service.emit('segment', segment('a', '안녕하세요'))
|
|
service.emit('segment', segment('b', '오늘 안건'))
|
|
|
|
const res = await invoke(IPC_CHANNELS.MEETING_MODE.EDIT_SEGMENT, {
|
|
sessionId: 's1',
|
|
segmentId: 'b',
|
|
text: '오늘 안건은 예산',
|
|
})
|
|
expect(res.success).toBe(true)
|
|
|
|
service.emit('session-completed', {
|
|
id: 's1',
|
|
rawTranscript: '[00:01] 안녕하세요\n[00:02] 오늘 안건',
|
|
})
|
|
expect(service.updateTranscript).toHaveBeenCalledWith(
|
|
's1',
|
|
'[00:01] 안녕하세요\n[00:02] 오늘 안건은 예산',
|
|
)
|
|
})
|
|
|
|
it('진행 중이 아닌 세션이면 MeetingNotRecording 으로 거부한다', async () => {
|
|
service.stateInfo = { ...service.stateInfo, state: 'idle', sessionId: null }
|
|
const res = await invoke(IPC_CHANNELS.MEETING_MODE.EDIT_SEGMENT, {
|
|
sessionId: 's1',
|
|
segmentId: 'a',
|
|
text: 'x',
|
|
})
|
|
expect(res.success).toBe(false)
|
|
if (!res.success) expect(res.error.code).toBe(ErrorCode.MeetingNotRecording)
|
|
})
|
|
|
|
it('모르는 세그먼트는 MeetingTranscriptUpdateFailed 로 거부한다', async () => {
|
|
const res = await invoke(IPC_CHANNELS.MEETING_MODE.EDIT_SEGMENT, {
|
|
sessionId: 's1',
|
|
segmentId: 'nope',
|
|
text: 'x',
|
|
})
|
|
expect(res.success).toBe(false)
|
|
if (!res.success) expect(res.error.code).toBe(ErrorCode.MeetingTranscriptUpdateFailed)
|
|
})
|
|
|
|
it('다시 등록해도 세션 완료 리스너가 중복되지 않는다', async () => {
|
|
const mod = await import('../../../src/main/ipc/meeting-mode-handlers')
|
|
mod.registerMeetingModeHandlers()
|
|
expect(service.listenerCount('session-completed')).toBe(1)
|
|
expect(service.listenerCount('segment')).toBe(1)
|
|
})
|
|
})
|