UNPKG

react-h5-audio-player

Version:

A customizable React audio player. Written in TypeScript. Mobile compatible. Keyboard friendly

149 lines (130 loc) 4.98 kB
import React from 'react' import { render, fireEvent, act } from '@testing-library/react' import VolumeBar from './VolumeBar' // Helper to dispatch a native volumechange event on audio const dispatchVolumeChange = (audio) => { const event = new Event('volumechange') audio.dispatchEvent(event) } // Provide a predictable getBoundingClientRect for the bar root const mockBarRect = (el, { left = 100, width = 200 } = {}) => { el.getBoundingClientRect = () => ({ x: left, y: 0, top: 0, left, bottom: 0, right: left + width, width, height: 10, toJSON: () => undefined, }) } describe('VolumeBar component', () => { beforeAll(() => { // Some tests rely on timers (animation timeout) jest.useFakeTimers() }) afterAll(() => { jest.useRealTimers() }) const setup = (options = {}) => { const audio = document.createElement('audio') const initialVolume = options.initialVolume != null ? options.initialVolume : 0.5 audio.volume = initialVolume const onMuteChange = jest.fn() const utils = render( <VolumeBar audio={audio} volume={initialVolume} onMuteChange={onMuteChange} showFilledVolume={options.showFilledVolume != null ? options.showFilledVolume : false} i18nVolumeControl="Volume" /> ) // Force componentDidUpdate to attach native audio event listener (component uses componentDidUpdate instead of componentDidMount) utils.rerender( <VolumeBar audio={audio} volume={initialVolume} onMuteChange={onMuteChange} showFilledVolume={options.showFilledVolume != null ? options.showFilledVolume : false} i18nVolumeControl="Volume" /> ) const area = utils.getByRole('progressbar') mockBarRect(area) return { ...utils, audio, onMuteChange, area, initialVolume } } test('renders with correct aria attributes and initial indicator position', () => { const { area, initialVolume } = setup() expect(area).toHaveAttribute('aria-valuemin', '0') expect(area).toHaveAttribute('aria-valuemax', '100') expect(area).toHaveAttribute('aria-valuenow', String(Math.round(initialVolume * 100))) const indicator = area.querySelector('.rhap_volume-indicator') expect(indicator.style.left).toBe(`${(initialVolume * 100).toFixed(2)}%`) }) test('responds to native audio volumechange events with animation then resets', () => { const { area, audio } = setup() act(() => { audio.volume = 0.8 dispatchVolumeChange(audio) }) const indicator = area.querySelector('.rhap_volume-indicator') // Immediately after change we should have animation duration .1s expect(indicator.style.transitionDuration).toBe('.1s') expect(indicator.style.left).toBe('80.00%') // Advance timers to clear animation flag act(() => { jest.advanceTimersByTime(110) }) expect(indicator.style.transitionDuration).toBe('0s') }) test('fires onMuteChange when crossing mute boundary (non-zero -> 0 -> non-zero)', () => { const { audio, onMuteChange } = setup({ initialVolume: 0.4 }) act(() => { audio.volume = 0 dispatchVolumeChange(audio) audio.volume = 0.3 dispatchVolumeChange(audio) }) expect(onMuteChange).toHaveBeenCalledTimes(2) }) test('dragging updates volume (mid, max, min) and animation flag not forced during drag sequence', () => { const { area, audio } = setup({ initialVolume: 0.2 }) const indicator = area.querySelector('.rhap_volume-indicator') // Start drag at 25% (left + width * .25 => 100 + 50) fireEvent.mouseDown(area, { clientX: 150 }) expect(audio.volume).toBeCloseTo(0.25, 2) expect(indicator.style.left).toBe('25%') // Move to >100% (right edge +) should clamp to 1 fireEvent.mouseMove(window, { clientX: 310 }) // left + width (100 + 200) + 10 overflow expect(audio.volume).toBe(1) expect(indicator.style.left).toBe('100%') // Move to <0 (left - 20) should clamp to 0 fireEvent.mouseMove(window, { clientX: 80 }) expect(audio.volume).toBe(0) expect(indicator.style.left).toBe('0%') // Release fireEvent.mouseUp(window) // We don't assert exact transitionDuration because implementation may briefly set animation; ensure left reflects final volume expect(indicator.style.left).toBe('0%') }) test('touch dragging updates volume', () => { const { area, audio } = setup({ initialVolume: 0.1 }) const indicator = area.querySelector('.rhap_volume-indicator') // Start touch at 50% fireEvent.touchStart(area, { touches: [{ clientX: 200 }] }) // left 100 + 100 (50%) expect(audio.volume).toBeCloseTo(0.5, 2) expect(indicator.style.left).toBe('50%') fireEvent.touchMove(window, { touches: [{ clientX: 300 }] }) // 100% expect(audio.volume).toBe(1) fireEvent.touchEnd(window) }) test('shows filled volume bar when showFilledVolume=true', () => { const { area } = setup({ showFilledVolume: true, initialVolume: 0.33 }) const filled = area.querySelector('.rhap_volume-filled') expect(filled).toBeTruthy() expect(filled.style.width).toBe('33.00%') }) })