d3ro-voice/apps/desktop/tests/main/services/ConfigService.test.ts
Yun Chan 4b0f685941 feat(caption): let live captions use their own speech model
The speech engine now keeps an auxiliary model next to the dictation model
and transcribes with whichever the request names, reloading it once if the
engine restarted. Settings > STT gains a live-caption model so captions can
run on large-v3-turbo while dictation keeps its own model. The runtime
minimum rises to 1.7.0 because older engines would silently ignore the
model choice.

Suggestion paging moves to Up/Down: the page follows the selection and the
last item waits while more candidates are being generated. The Left/Right
page shortcuts are removed; they did nothing until a page had filled and
clash with Intel's display-rotation hotkeys.
2026-09-24 21:46:54 +09:00

147 lines
5.2 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest'
describe('ConfigService suggestion tuning migration', () => {
afterEach(() => {
vi.doUnmock('electron-store')
vi.resetModules()
})
it('revision 3은 새 delay와 분당 상한만 바꾸고 기존 개인 설정을 보존한다', async () => {
const savedBindings = {
'suggestion-accept': [
{ device: 'keyboard' as const, code: 65, ctrl: true, alt: false, shift: false, meta: false }
]
}
const persisted = {
suggestionTuningRevision: 3,
suggestionTriggerDelayMs: 300,
suggestionMaxRequestsPerMinute: 12,
suggestionMinPrefixChars: 17,
suggestionDailyBudget: 777,
suggestionRequestTimeoutMs: 23000,
keyBindings: savedBindings
}
class TestStore<T extends Record<string, unknown>> {
store: T
constructor(options: { defaults: T }) {
this.store = { ...options.defaults, ...persisted } as T
}
get<K extends keyof T>(key: K): T[K] {
return this.store[key]
}
set<K extends keyof T>(key: K, value: T[K]): void {
this.store[key] = value
}
delete(key: string): void {
delete this.store[key as keyof T]
}
}
vi.doMock('electron-store', () => ({ default: TestStore }))
const { configGet, initConfigService, resetInMemoryConfig } = await import(
'../../../src/main/services/ConfigService'
)
await initConfigService()
expect(configGet('suggestionTuningRevision')).toBe(5)
expect(configGet('suggestionTriggerDelayMs')).toBe(600)
expect(configGet('suggestionMaxRequestsPerMinute')).toBe(6)
expect(configGet('suggestionMinPrefixChars')).toBe(17)
expect(configGet('suggestionDailyBudget')).toBe(777)
expect(configGet('suggestionRequestTimeoutMs')).toBe(23000)
// 옛 기본값이 아닌 커스터마이즈(Ctrl+A)는 revision 5 마이그레이션도 건드리지 않는다.
expect(configGet('keyBindings')['suggestion-accept']).toEqual(savedBindings['suggestion-accept'])
resetInMemoryConfig()
})
function makeTestStore<T extends Record<string, unknown>>(persisted: Partial<T>) {
return class TestStore {
store: T
constructor(options: { defaults: T }) {
this.store = { ...options.defaults, ...persisted } as T
}
get<K extends keyof T>(key: K): T[K] {
return this.store[key]
}
set<K extends keyof T>(key: K, value: T[K]): void {
this.store[key] = value
}
delete(key: string): void {
delete this.store[key as keyof T]
}
}
}
it('revision 5: 정확히 옛 기본값(accept=Ctrl+Alt+→, dismiss=Ctrl+Alt+←)이면 새 기본값으로 옮기고 페이지 이동을 그 자리에 채운다', async () => {
const OLD_ACCEPT = [{ device: 'keyboard' as const, code: 39, ctrl: true, alt: true, shift: false, meta: false }]
const OLD_NEXT = [{ device: 'keyboard' as const, code: 40, ctrl: true, alt: true, shift: false, meta: false }]
const OLD_PREV = [{ device: 'keyboard' as const, code: 38, ctrl: true, alt: true, shift: false, meta: false }]
const OLD_DISMISS = [{ device: 'keyboard' as const, code: 37, ctrl: true, alt: true, shift: false, meta: false }]
vi.doMock('electron-store', () => ({
default: makeTestStore({
suggestionTuningRevision: 4,
keyBindings: {
'suggestion-accept': OLD_ACCEPT,
'suggestion-next': OLD_NEXT,
'suggestion-prev': OLD_PREV,
'suggestion-dismiss': OLD_DISMISS
}
})
}))
const { configGet, initConfigService, resetInMemoryConfig } = await import(
'../../../src/main/services/ConfigService'
)
await initConfigService()
const bindings = configGet('keyBindings')
expect(bindings['suggestion-accept']).toEqual([
{ device: 'keyboard', code: 0x0d, ctrl: true, alt: true, shift: false, meta: false }
])
expect(bindings['suggestion-dismiss']).toEqual([
{ device: 'keyboard', code: 0x08, ctrl: true, alt: true, shift: false, meta: false }
])
// 옮기지 않는 액션은 그대로 남는다.
expect(bindings['suggestion-next']).toEqual(OLD_NEXT)
expect(bindings['suggestion-prev']).toEqual(OLD_PREV)
expect(configGet('suggestionTuningRevision')).toBe(5)
resetInMemoryConfig()
})
it('revision 5: 옛 기본값이 아닌 커스터마이즈는 절대 건드리지 않는다', async () => {
const CUSTOM_ACCEPT = [{ device: 'keyboard' as const, code: 0x41, ctrl: true, alt: false, shift: false, meta: false }]
const CUSTOM_DISMISS = [{ device: 'keyboard' as const, code: 0x44, ctrl: true, alt: false, shift: false, meta: false }]
vi.doMock('electron-store', () => ({
default: makeTestStore({
suggestionTuningRevision: 4,
keyBindings: {
'suggestion-accept': CUSTOM_ACCEPT,
'suggestion-dismiss': CUSTOM_DISMISS
}
})
}))
const { configGet, initConfigService, resetInMemoryConfig } = await import(
'../../../src/main/services/ConfigService'
)
await initConfigService()
const bindings = configGet('keyBindings')
expect(bindings['suggestion-accept']).toEqual(CUSTOM_ACCEPT)
expect(bindings['suggestion-dismiss']).toEqual(CUSTOM_DISMISS)
resetInMemoryConfig()
})
})