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.
147 lines
5.2 KiB
TypeScript
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()
|
|
})
|
|
|
|
})
|