From 6273622f1c006d916a586e79aab45815dc5ddfd5 Mon Sep 17 00:00:00 2001 From: Tnalxmsk Date: Tue, 3 Feb 2026 16:24:03 +0900 Subject: [PATCH 1/2] chore: include tests in CI --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bbfcd1e..4f59936 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,5 +31,8 @@ jobs: - name: Lint run: npm run lint + - name: Test + run: npm run test + - name: Build run: npm run build From 55318fd64db555e200a9e4044ac774466ea5f1c1 Mon Sep 17 00:00:00 2001 From: Tnalxmsk Date: Tue, 3 Feb 2026 16:24:30 +0900 Subject: [PATCH 2/2] test: add global audio integration test --- .../globalAudio.integration.test.tsx | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 test/integration/globalAudio.integration.test.tsx diff --git a/test/integration/globalAudio.integration.test.tsx b/test/integration/globalAudio.integration.test.tsx new file mode 100644 index 0000000..3f0c80d --- /dev/null +++ b/test/integration/globalAudio.integration.test.tsx @@ -0,0 +1,107 @@ +import { waitFor } from '@testing-library/dom'; +import { cleanup, renderHook } from '@testing-library/react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { audioManager, useGlobalAudio } from '../../src'; + +const getAudioOrThrow = () => { + const audio = audioManager.getAudio(); + if (!audio) { + throw new Error('Audio is not available in this environment.'); + } + return audio; +}; + +beforeEach(() => { + cleanup(); + audioManager.setSource(null); + audioManager.configure({}); + vi.clearAllMocks(); + audioManager.dispose(); +}); + +afterEach(() => { + cleanup(); + audioManager.dispose(); +}); + +describe('global audio integration', () => { + it('switching tracks stops A and plays B, updating shared state', async () => { + const playSpy = vi.spyOn(HTMLMediaElement.prototype, 'play'); + const pauseSpy = vi.spyOn(HTMLMediaElement.prototype, 'pause'); + + const first = renderHook(({ src }) => useGlobalAudio({ src, autoPlay: true }), { + initialProps: { src: 'https://example.com/a.mp3' }, + }); + + await waitFor(() => { + expect(first.result.current.state.src).toBe('https://example.com/a.mp3'); + }); + + expect(playSpy).toHaveBeenCalled(); + getAudioOrThrow().dispatchEvent(new Event('playing')); + + first.rerender({ src: 'https://example.com/b.mp3' }); + + await waitFor(() => { + expect(first.result.current.state.src).toBe('https://example.com/b.mp3'); + }); + + expect(playSpy).toHaveBeenCalledTimes(2); + expect(pauseSpy).toHaveBeenCalled(); + getAudioOrThrow().dispatchEvent(new Event('playing')); + + await waitFor(() => { + expect(first.result.current.state.isPlaying).toBe(true); + }); + }); + + it('restores progress from storage after loadedmetadata', async () => { + localStorage.setItem('audio:progress:https://example.com/a.mp3', '42'); + + const hook = renderHook(() => useGlobalAudio({ src: 'https://example.com/a.mp3' })); + + await waitFor(() => { + expect(hook.result.current.state.src).toBe('https://example.com/a.mp3'); + }); + + const audio = audioManager.getAudio(); + if (!audio) { + throw new Error('Audio is not available in this environment.'); + } + + audio.dispatchEvent(new Event('loadedmetadata')); + + await waitFor(() => { + expect(hook.result.current.state.currentTime).toBe(42); + }); + }); + + it('keeps singleton state after unmount/remount', async () => { + const playSpy = vi.spyOn(HTMLMediaElement.prototype, 'play'); + + const first = renderHook(() => + useGlobalAudio({ src: 'https://example.com/a.mp3', autoPlay: true }), + ); + + await waitFor(() => { + expect(first.result.current.state.src).toBe('https://example.com/a.mp3'); + }); + + expect(playSpy).toHaveBeenCalled(); + getAudioOrThrow().dispatchEvent(new Event('playing')); + + await waitFor(() => { + expect(first.result.current.state.isPlaying).toBe(true); + }); + + first.unmount(); + + const second = renderHook(() => useGlobalAudio()); + + await waitFor(() => { + expect(second.result.current.state.src).toBe('https://example.com/a.mp3'); + }); + + expect(second.result.current.state.isPlaying).toBe(true); + }); +});