diff --git a/packages/fuselage/src/components/AudioPlayer/AudioPlayer.spec.tsx b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.spec.tsx new file mode 100644 index 0000000000..ddda27b058 --- /dev/null +++ b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.spec.tsx @@ -0,0 +1,69 @@ +import { composeStories } from '@storybook/react-webpack5'; +import { fireEvent } from '@testing-library/dom'; + +import { render } from '../../testing'; + +import AudioPlayer from './AudioPlayer'; +import * as stories from './AudioPlayer.stories'; + +const { AudioPlayerDefault, AudioPlayerWithDownload } = composeStories(stories); + +describe('[AudioPlayer Component]', () => { + describe('Story renders without crashing', () => { + it('AudioPlayerDefault', () => { + render(); + }); + it('AudioPlayerWithDownload', () => { + render(); + }); + }); +}); + +it('should display the audio player', () => { + const { container } = render(); + expect(container.querySelector('audio')).toBeInTheDocument(); +}); + +it('should display the download button when download prop is true', () => { + const { container } = render(); + expect(container.querySelector('a[download]')).toBeInTheDocument(); +}); + +it('should display the loading state when the audio is loading', () => { + const { container } = render(); + const audio = container.querySelector('audio'); + + if (audio) { + fireEvent(audio, new Event('loadstart')); + } + + expect( + container.querySelector('.rcx-audio-player--loading'), + ).toBeInTheDocument(); +}); + +it('should display the play button when the audio is not playing', () => { + const { container } = render(); + const audio = container.querySelector('audio'); + + if (audio) { + fireEvent(audio, new Event('canplay')); + } + + expect( + container.querySelector('button[aria-label="Play"]'), + ).toBeInTheDocument(); +}); + +it('should display the pause button when the audio is playing', () => { + const { container } = render(); + const audio = container.querySelector('audio'); + + if (audio) { + fireEvent(audio, new Event('play')); + } + + expect( + container.querySelector('button[aria-label="Pause"]'), + ).toBeInTheDocument(); +}); diff --git a/packages/fuselage/src/components/AudioPlayer/AudioPlayer.stories.tsx b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.stories.tsx index aa063feafe..baf0031520 100644 --- a/packages/fuselage/src/components/AudioPlayer/AudioPlayer.stories.tsx +++ b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.stories.tsx @@ -1,4 +1,4 @@ -import type { Meta } from '@storybook/react-webpack5'; +import type { Meta, StoryFn } from '@storybook/react-webpack5'; import AudioPlayer from './AudioPlayer'; @@ -10,4 +10,17 @@ export default { const AUDIO_URL = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-17.mp3'; -export const AudioPlayerDefault = () => ; +const Template: StoryFn = (args) => ( + +); + +export const AudioPlayerDefault = Template.bind({}); +AudioPlayerDefault.args = { + src: AUDIO_URL, +}; + +export const AudioPlayerWithDownload = Template.bind({}); +AudioPlayerWithDownload.args = { + src: AUDIO_URL, + download: true, +}; diff --git a/packages/fuselage/src/components/AudioPlayer/AudioPlayer.styles.scss b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.styles.scss new file mode 100644 index 0000000000..436ba3c8df --- /dev/null +++ b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.styles.scss @@ -0,0 +1,16 @@ +.rcx-audio-player { + &--loading { + .rcx-icon--name-loading { + animation: spin-animation 0.8s linear infinite; + } + } +} + +@keyframes spin-animation { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} \ No newline at end of file diff --git a/packages/fuselage/src/components/AudioPlayer/AudioPlayer.tsx b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.tsx index 61b44839f8..974059f105 100644 --- a/packages/fuselage/src/components/AudioPlayer/AudioPlayer.tsx +++ b/packages/fuselage/src/components/AudioPlayer/AudioPlayer.tsx @@ -4,6 +4,7 @@ import { useState, useRef, forwardRef } from 'react'; import { Box, Button, IconButton, Margins } from '../..'; import { Slider } from '../Slider'; +import './AudioPlayer.styles.scss'; const getMaskTime = (durationTime: number) => new Date(durationTime * 1000) @@ -73,6 +74,16 @@ export type AudioPlayerProps = { trackProps?: TrackHTMLAttributes; }; +const getIcon = (isLoading: boolean, isPlaying: boolean) => { + if (isLoading) { + return 'loading'; + } + if (isPlaying) { + return 'pause-shape-filled'; + } + return 'play-shape-filled'; +}; + const AudioPlayer = forwardRef( ( { @@ -93,6 +104,8 @@ const AudioPlayer = forwardRef( ) => { const audioRef = useRef(null); const refs = useMergedRefs(ref, audioRef); + const [isReady, setIsReady] = useState(false); + const [isLoading, setIsLoading] = useState(true); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [durationTime, setDurationTime] = useState(0); @@ -128,6 +141,8 @@ const AudioPlayer = forwardRef( return ( ( medium onClick={handlePlay} aria-label={isPlaying ? pauseLabel : playLabel} - icon={isPlaying ? 'pause-shape-filled' : 'play-shape-filled'} + icon={getIcon(isLoading, isPlaying)} + disabled={isLoading} /> @@ -160,6 +176,7 @@ const AudioPlayer = forwardRef( showOutput={false} value={currentTime} maxValue={durationTime} + disabled={!isReady} onChange={(value) => { if (audioRef.current) { audioRef.current.currentTime = value; @@ -171,6 +188,7 @@ const AudioPlayer = forwardRef(