mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
141 lines
4.0 KiB
TypeScript
141 lines
4.0 KiB
TypeScript
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<number>;
|
|
}
|
|
|
|
describe('createStore', () => {
|
|
// Mock target
|
|
class MockMedia extends EventTarget {
|
|
volume = 1;
|
|
muted = false;
|
|
}
|
|
|
|
const audioFeature = defineFeature<MockMedia>()({
|
|
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 }) => <Provider>{children}</Provider>,
|
|
});
|
|
|
|
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 }) => <Provider store={providedStore}>{children}</Provider>,
|
|
});
|
|
|
|
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 }) => <Provider store={store}>{children}</Provider>,
|
|
});
|
|
|
|
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 }) => <Provider store={store}>{children}</Provider>,
|
|
});
|
|
|
|
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);
|
|
});
|
|
});
|
|
});
|