import { act, renderHook } from '@testing-library/react'; import type { ReactNode } from 'react'; import { describe, expect, it } from 'vitest'; import { defineFeature } from '../../core/feature'; import { createStore } from '../create-store'; interface AudioState { volume: number; muted: boolean; setVolume: (volume: number) => Promise; } describe('createStore', () => { // Mock target class MockMedia extends EventTarget { volume = 1; muted = false; } const audioFeature = defineFeature()({ state: ({ task }) => ({ volume: 1, muted: false, setVolume(volume: number) { return task(({ target }) => { target.volume = volume; target.dispatchEvent(new Event('volumechange')); return volume; }); }, }), attach({ target, signal, set }) { const sync = () => set({ volume: target.volume, muted: target.muted, }); sync(); target.addEventListener('volumechange', sync); signal.addEventListener('abort', () => { target.removeEventListener('volumechange', sync); }); }, }); describe('create', () => { it('creates a store instance', () => { const { create } = createStore({ features: [audioFeature] }); const store = create(); expect(store).toBeDefined(); expect(store.state).toMatchObject({ volume: 1, muted: false }); }); }); describe('provider', () => { it('creates store on mount', () => { const { Provider, useStore } = createStore({ features: [audioFeature] }); const { result } = renderHook(() => useStore() as AudioState, { wrapper: ({ children }: { children: ReactNode }) => {children}, }); expect(result.current).toBeDefined(); expect(result.current.volume).toBe(1); expect(typeof result.current.setVolume).toBe('function'); }); it('uses provided store prop without destroying on unmount', () => { const { Provider, useStore, create } = createStore({ features: [audioFeature], }); const providedStore = create(); const { unmount } = renderHook(() => useStore(), { wrapper: ({ children }: { children: ReactNode }) => {children}, }); unmount(); // Store should NOT be destroyed because it was provided expect(providedStore.destroyed).toBe(false); }); it('sets displayName on Provider', () => { const { Provider } = createStore({ features: [audioFeature], displayName: 'TestStore', }); expect(Provider.displayName).toBe('TestStore.Provider'); }); }); describe('useStore', () => { it('returns state and action functions from context', () => { const { Provider, useStore, create } = createStore({ features: [audioFeature], }); const store = create(); const target = new MockMedia(); store.attach(target); const { result } = renderHook(() => useStore() as AudioState, { wrapper: ({ children }: { children: ReactNode }) => {children}, }); expect(result.current.volume).toBe(1); expect(result.current.muted).toBe(false); expect(typeof result.current.setVolume).toBe('function'); }); it('updates when state changes', async () => { const { Provider, useStore, create } = createStore({ features: [audioFeature], }); const store = create(); const target = new MockMedia(); store.attach(target); const { result } = renderHook(() => useStore() as AudioState, { wrapper: ({ children }: { children: ReactNode }) => {children}, }); expect(result.current.volume).toBe(1); await act(async () => { target.volume = 0.5; target.dispatchEvent(new Event('volumechange')); }); expect(result.current.volume).toBe(0.5); }); }); });