With a 100 ms trigger left in the installed config, generation started in the gaps between keystrokes; the next key ended the session silently, and that session still counted against the 5 s minimum interval, so the moment the user actually paused nothing came (rate-limited). The log showed a generate-then-vanish cycle every 5-6 s. The trigger delay now has a 500 ms floor and settings revision 6 resets a stored value below it to 600 ms. A session ended because the user kept typing no longer blocks the next request by the minimum interval (per-minute and daily caps still apply), and that dismissal is logged.
186 lines
6.5 KiB
TypeScript
186 lines
6.5 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(6)
|
|
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(6)
|
|
|
|
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()
|
|
})
|
|
|
|
it('revision 6: 하한(500ms) 미만으로 남은 멈춤 판단 시간만 기본값 600ms로 되돌린다', async () => {
|
|
for (const [stored, expected] of [
|
|
[100, 600],
|
|
[800, 800],
|
|
] as const) {
|
|
const persisted = { suggestionTuningRevision: 5, suggestionTriggerDelayMs: stored }
|
|
|
|
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(6)
|
|
expect(configGet('suggestionTriggerDelayMs')).toBe(expected)
|
|
resetInMemoryConfig()
|
|
vi.doUnmock('electron-store')
|
|
vi.resetModules()
|
|
}
|
|
})
|
|
})
|