177 lines
5.3 KiB
TypeScript
177 lines
5.3 KiB
TypeScript
// 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)
|
|
})
|
|
})
|