fix(sound): apply the Sound switch at once instead of after a restart
This commit is contained in:
parent
91d4b4974c
commit
06e96c58f4
3 changed files with 300 additions and 69 deletions
|
|
@ -0,0 +1,177 @@
|
|||
// tests/main/services/SoundEffectService-redteam-r1-15.test.ts
|
||||
// 회귀: Settings에서 효과음을 끄면(config.set → configSet) 재시작 없이 즉시 반영되어야 한다.
|
||||
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import fs from 'fs'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
|
||||
const SOUND_DIR = path.join(os.tmpdir(), 'd3ro-vitest-sounds-r1-15')
|
||||
|
||||
const { execMock, execFileMock } = vi.hoisted(() => ({
|
||||
execMock: vi.fn(),
|
||||
execFileMock: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('child_process', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('child_process')>()
|
||||
return { ...actual, exec: execMock, execFile: execFileMock }
|
||||
})
|
||||
|
||||
vi.mock('../../../src/main/utils/paths', () => ({
|
||||
getSoundPath: (filename: string) => path.join(SOUND_DIR, filename)
|
||||
}))
|
||||
|
||||
import {
|
||||
configSet,
|
||||
initInMemoryConfig,
|
||||
resetInMemoryConfig
|
||||
} from '../../../src/main/services/ConfigService'
|
||||
import {
|
||||
SoundEffectService,
|
||||
getSoundEffectService,
|
||||
type SoundEffectDeps
|
||||
} from '../../../src/main/services/SoundEffectService'
|
||||
|
||||
function playCount(): number {
|
||||
return execMock.mock.calls.length + execFileMock.mock.calls.length
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
fs.mkdirSync(SOUND_DIR, { recursive: true })
|
||||
for (const name of ['recording-start.wav', 'recording-stop.wav', 'error.wav']) {
|
||||
fs.writeFileSync(path.join(SOUND_DIR, name), Buffer.from('RIFF0000WAVE'))
|
||||
}
|
||||
})
|
||||
|
||||
describe('SoundEffectService — soundEnabled 변경 즉시 반영 (singleton + ConfigService)', () => {
|
||||
beforeEach(() => {
|
||||
execMock.mockReset()
|
||||
execFileMock.mockReset()
|
||||
initInMemoryConfig({ soundEnabled: true })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
getSoundEffectService().dispose()
|
||||
resetInMemoryConfig()
|
||||
})
|
||||
|
||||
it('Settings가 configSet으로 soundEnabled=false를 쓰면 다음 play()는 재생하지 않는다', () => {
|
||||
const service = getSoundEffectService()
|
||||
service.initialize()
|
||||
expect(service.isEnabled()).toBe(true)
|
||||
|
||||
// SettingsModal → config:set 핸들러 → configSet 경로 (setEnabled를 거치지 않음)
|
||||
configSet('soundEnabled', false)
|
||||
expect(service.isEnabled()).toBe(false)
|
||||
service.play('recording-start')
|
||||
service.play('error')
|
||||
expect(playCount()).toBe(0)
|
||||
|
||||
// 다시 켜면 곧바로 재생된다 (동기화로 들어온 값도 같은 경로)
|
||||
configSet('soundEnabled', true)
|
||||
expect(service.isEnabled()).toBe(true)
|
||||
service.play('recording-stop')
|
||||
expect(playCount()).toBe(1)
|
||||
})
|
||||
|
||||
it('dispose 후에는 config 변경을 더 이상 구독하지 않는다', () => {
|
||||
const service = getSoundEffectService()
|
||||
service.initialize()
|
||||
service.dispose()
|
||||
|
||||
configSet('soundEnabled', false)
|
||||
expect(service.isEnabled()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SoundEffectService — 포트 주입 단위 테스트', () => {
|
||||
function createDeps(initial: boolean): {
|
||||
deps: SoundEffectDeps
|
||||
emit: (enabled: boolean) => void
|
||||
listenerCount: () => number
|
||||
played: string[]
|
||||
written: boolean[]
|
||||
} {
|
||||
const listeners = new Set<(enabled: boolean) => void>()
|
||||
const played: string[] = []
|
||||
const written: boolean[] = []
|
||||
let value = initial
|
||||
const deps: SoundEffectDeps = {
|
||||
settings: {
|
||||
read: () => value,
|
||||
write: (enabled) => {
|
||||
value = enabled
|
||||
written.push(enabled)
|
||||
listeners.forEach((l) => l(enabled))
|
||||
},
|
||||
subscribe: (listener) => {
|
||||
listeners.add(listener)
|
||||
return () => listeners.delete(listener)
|
||||
}
|
||||
},
|
||||
output: {
|
||||
resolvePath: (filename) => `/sounds/${filename}`,
|
||||
load: (filePath) => (filePath.endsWith('error.wav') ? null : Buffer.from('wav')),
|
||||
play: (filePath) => played.push(filePath)
|
||||
}
|
||||
}
|
||||
return {
|
||||
deps,
|
||||
emit: (enabled) => {
|
||||
value = enabled
|
||||
listeners.forEach((l) => l(enabled))
|
||||
},
|
||||
listenerCount: () => listeners.size,
|
||||
played,
|
||||
written
|
||||
}
|
||||
}
|
||||
|
||||
it('초기값을 설정 포트에서 읽고, 외부 변경을 따라간다', () => {
|
||||
const h = createDeps(false)
|
||||
const service = new SoundEffectService(h.deps)
|
||||
service.initialize()
|
||||
|
||||
service.play('recording-start')
|
||||
expect(h.played).toEqual([])
|
||||
|
||||
h.emit(true)
|
||||
service.play('recording-start')
|
||||
service.play('chime')
|
||||
expect(h.played).toEqual(['/sounds/recording-start.wav', '/sounds/recording-stop.wav'])
|
||||
})
|
||||
|
||||
it('로드되지 않은 효과음은 건너뛴다', () => {
|
||||
const h = createDeps(true)
|
||||
const service = new SoundEffectService(h.deps)
|
||||
service.initialize()
|
||||
|
||||
service.play('error')
|
||||
service.play('cancel')
|
||||
expect(h.played).toEqual([])
|
||||
})
|
||||
|
||||
it('setEnabled는 설정 포트에 기록하고 상태를 갱신한다', () => {
|
||||
const h = createDeps(true)
|
||||
const service = new SoundEffectService(h.deps)
|
||||
service.initialize()
|
||||
|
||||
service.setEnabled(false)
|
||||
expect(h.written).toEqual([false])
|
||||
expect(service.isEnabled()).toBe(false)
|
||||
service.play('recording-start')
|
||||
expect(h.played).toEqual([])
|
||||
})
|
||||
|
||||
it('initialize를 두 번 호출해도 구독은 하나만 유지되고 dispose가 해제한다', () => {
|
||||
const h = createDeps(true)
|
||||
const service = new SoundEffectService(h.deps)
|
||||
service.initialize()
|
||||
service.initialize()
|
||||
expect(h.listenerCount()).toBe(1)
|
||||
|
||||
service.dispose()
|
||||
expect(h.listenerCount()).toBe(0)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue