Files
hermes-web-ui/tests/client/completion-sound.test.ts
2026-05-06 14:23:12 +08:00

82 lines
2.3 KiB
TypeScript

// @vitest-environment jsdom
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { __resetCompletionSoundForTests, playCompletionSound, primeCompletionSound } from '@/utils/completion-sound'
function installMockAudioContext(initialState: AudioContextState = 'running') {
const oscillator = {
type: 'sine' as OscillatorType,
frequency: {
setValueAtTime: vi.fn(),
exponentialRampToValueAtTime: vi.fn(),
},
connect: vi.fn(),
start: vi.fn(),
stop: vi.fn(),
}
const gain = {
gain: {
setValueAtTime: vi.fn(),
exponentialRampToValueAtTime: vi.fn(),
},
connect: vi.fn(),
}
const context = {
state: initialState,
currentTime: 10,
destination: {},
resume: vi.fn(async () => {
context.state = 'running'
}),
createOscillator: vi.fn(() => oscillator),
createGain: vi.fn(() => gain),
}
const AudioContextMock = vi.fn(() => context)
Object.defineProperty(window, 'AudioContext', {
configurable: true,
writable: true,
value: AudioContextMock,
})
return { AudioContextMock, context, oscillator, gain }
}
describe('completion sound', () => {
beforeEach(() => {
__resetCompletionSoundForTests()
vi.restoreAllMocks()
Object.defineProperty(window, 'AudioContext', {
configurable: true,
writable: true,
value: undefined,
})
})
it('returns false when Web Audio is unavailable', async () => {
await expect(playCompletionSound()).resolves.toBe(false)
})
it('primes a suspended audio context from user interaction', () => {
const { context } = installMockAudioContext('suspended')
primeCompletionSound()
expect(context.resume).toHaveBeenCalledTimes(1)
})
it('plays a short tone through Web Audio', async () => {
const { context, oscillator, gain } = installMockAudioContext('running')
await expect(playCompletionSound()).resolves.toBe(true)
expect(context.createOscillator).toHaveBeenCalledTimes(1)
expect(context.createGain).toHaveBeenCalledTimes(1)
expect(oscillator.connect).toHaveBeenCalledWith(gain)
expect(gain.connect).toHaveBeenCalledWith(context.destination)
expect(oscillator.start).toHaveBeenCalledWith(10)
expect(oscillator.stop).toHaveBeenCalledWith(10.16)
})
})