diff --git a/.gitignore b/.gitignore index 9c3b079..7fdd9ce 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ test-results/ # macOS-specific files .DS_Store +package-lock.json diff --git a/src/components/Player.tsx b/src/components/Player.tsx index 1e6afe8..84f74ef 100644 --- a/src/components/Player.tsx +++ b/src/components/Player.tsx @@ -11,7 +11,7 @@ import Slider from './player/Slider'; export default function Player() { const audioPlayer = useRef(null); const progressRef = useRef(null); - const [progress, setProgress] = useState(0); + const [currentTime, setCurrentTime] = useState(0); if (currentEpisode.value === null) { return; @@ -21,9 +21,9 @@ export default function Player() { function whilePlaying() { if (audioPlayer.current?.duration) { - const percentage = - (audioPlayer.current.currentTime / audioPlayer.current.duration) * 100; - setProgress(percentage); + const time = audioPlayer.current.currentTime; + const percentage = (time / audioPlayer.current.duration) * 100; + setCurrentTime(time); const slider = document.querySelector('.slider'); const particles = document.querySelector('.ship-particles'); @@ -65,11 +65,12 @@ export default function Player() { }, [isPlaying.value]); useEffect(() => { - if (progress >= 99.99) { + const duration = audioPlayer.current?.duration ?? 0; + if (duration > 0 && currentTime >= duration - 0.01) { isPlaying.value = false; - setProgress(0); + setCurrentTime(0); } - }, [progress]); + }, [currentTime]); return (
@@ -102,7 +103,7 @@ export default function Player() {
- +
diff --git a/src/components/player/Slider/index.tsx b/src/components/player/Slider/index.tsx index dfb092d..ecc6ed6 100644 --- a/src/components/player/Slider/index.tsx +++ b/src/components/player/Slider/index.tsx @@ -3,7 +3,7 @@ import './styles.css'; type Props = { audioPlayer: MutableRef; - progress: number; + currentTime: number; }; function parseTime(seconds: number) { @@ -23,11 +23,10 @@ function formatTime(seconds: Array, totalSeconds = seconds) { .join(':'); } -export default function Slider({ audioPlayer, progress }: Props) { - let currentTime = parseTime( - Math.floor(audioPlayer.current?.currentTime ?? 0) - ); - let totalTime = parseTime(Math.floor(audioPlayer.current?.duration ?? 0)); +export default function Slider({ audioPlayer, currentTime }: Props) { + let currentTimeFormatted = parseTime(Math.floor(currentTime)); + let duration = Math.floor(audioPlayer.current?.duration ?? 0); + let totalTime = parseTime(duration); return (
@@ -36,20 +35,16 @@ export default function Slider({ audioPlayer, progress }: Props) { role="slider" aria-label="audio timeline" aria-valuemin={0} - aria-valuemax={Math.floor(audioPlayer.current?.duration ?? 0)} - aria-valuenow={Math.floor(audioPlayer.current?.currentTime ?? 0)} - aria-valuetext={`${Math.floor( - audioPlayer.current?.currentTime ?? 0 - )} seconds`} + aria-valuemax={duration} + aria-valuenow={Math.floor(currentTime)} + aria-valuetext={`${Math.floor(currentTime)} seconds`} class="slider group" type="range" - max="100" - value={progress} + max={duration} + value={Math.floor(currentTime)} onInput={(e: InputEvent) => { if (audioPlayer?.current) { - const value = - (Number((e.target as HTMLInputElement).value) / 100) * - audioPlayer.current.duration; + const value = Number((e.target as HTMLInputElement).value); audioPlayer.current.currentTime = value; } }} @@ -62,7 +57,7 @@ export default function Slider({ audioPlayer, progress }: Props) {
); diff --git a/tests/unit/Slider.test.tsx b/tests/unit/Slider.test.tsx index 10cb652..857c853 100644 --- a/tests/unit/Slider.test.tsx +++ b/tests/unit/Slider.test.tsx @@ -20,18 +20,18 @@ describe('Slider', () => { }); it('renders slider with correct attributes', () => { - render(); + render(); const slider = screen.getByRole('slider'); expect(slider).toBeInTheDocument(); expect(slider).toHaveAttribute('aria-label', 'audio timeline'); expect(slider).toHaveAttribute('aria-orientation', 'horizontal'); expect(slider).toHaveAttribute('type', 'range'); - expect(slider).toHaveAttribute('max', '100'); + expect(slider).toHaveAttribute('max', '180'); }); it('displays correct aria values', () => { - render(); + render(); const slider = screen.getByRole('slider'); expect(slider).toHaveAttribute('aria-valuemin', '0'); @@ -41,12 +41,12 @@ describe('Slider', () => { }); it('updates current time when slider is moved', () => { - render(); + render(); const slider = screen.getByRole('slider') as HTMLInputElement; - // Simulate moving slider to 50% (90 seconds into 180 second track) - fireEvent.input(slider, { target: { value: '50' } }); + // Simulate moving slider to 90 seconds + fireEvent.input(slider, { target: { value: '90' } }); expect(mockAudioElement.currentTime).toBe(90); }); @@ -55,7 +55,7 @@ describe('Slider', () => { const nullRef = createRef(); nullRef.current = null; - render(); + render(); const slider = screen.getByRole('slider'); expect(slider).toBeInTheDocument(); @@ -65,7 +65,7 @@ describe('Slider', () => { it('displays time information on desktop', () => { const { container } = render( - + ); // Time display should be present (but may be hidden on mobile) @@ -79,7 +79,7 @@ describe('Slider', () => { mockAudioElement.duration = 0; mockAudioElement.currentTime = 0; - render(); + render(); const slider = screen.getByRole('slider'); expect(slider).toHaveAttribute('aria-valuemax', '0'); @@ -92,7 +92,7 @@ describe('Slider', () => { mockAudioElement.duration = 7200; // 2:00:00 const { container } = render( - + ); // Check that time is formatted correctly with hours @@ -106,7 +106,7 @@ describe('Slider', () => { mockAudioElement.duration = 600; // 10:00 const { container } = render( - + ); // Check that time is formatted without unnecessary leading zeros @@ -116,7 +116,7 @@ describe('Slider', () => { }); it('renders particle animation elements', () => { - render(); + render(); const particles = document.querySelector('.ship-particles'); expect(particles).toBeInTheDocument(); @@ -126,21 +126,21 @@ describe('Slider', () => { expect(dots).toHaveLength(5); }); - it('calculates seek position correctly for different progress values', () => { - render(); + it('calculates seek position correctly for different time values', () => { + render(); const slider = screen.getByRole('slider') as HTMLInputElement; - // Test 0% progress + // Test seeking to 0 seconds fireEvent.input(slider, { target: { value: '0' } }); expect(mockAudioElement.currentTime).toBe(0); - // Test 100% progress - fireEvent.input(slider, { target: { value: '100' } }); + // Test seeking to 180 seconds (end of track) + fireEvent.input(slider, { target: { value: '180' } }); expect(mockAudioElement.currentTime).toBe(180); - // Test 25% progress - fireEvent.input(slider, { target: { value: '25' } }); + // Test seeking to 45 seconds + fireEvent.input(slider, { target: { value: '45' } }); expect(mockAudioElement.currentTime).toBe(45); }); });