refactor(store): rename feature to slice (#373)

This commit is contained in:
rahim
2026-02-02 17:30:47 +11:00
committed by GitHub
parent d28dda114c
commit a1d0383d41
40 changed files with 862 additions and 741 deletions
+7 -7
View File
@@ -2,12 +2,16 @@
// Re-exports from core/dom
export {
type AnyPlayerFeature,
type BufferState,
definePlayerFeature,
type FeatureAvailability,
features,
type Media,
type MediaContainer,
type PlaybackState,
type PlayerFeature,
type PlayerStore,
type PlayerTarget,
type SourceState,
selectBuffer,
@@ -19,13 +23,9 @@ export {
type VolumeState,
} from '@videojs/core/dom';
export type { AnyFeature, Feature, InferFeatureState } from '@videojs/store';
// Re-exports (for custom features)
export { createFeatureSelector, defineFeature } from '@videojs/store';
export type { Comparator, Selector } from '@videojs/store/react';
// Re-exports (for advanced store access)
// Re-exports from store (utilities)
export type { Comparator, Selector } from '@videojs/store';
export { createSelector, shallowEqual } from '@videojs/store';
export { useSelector, useStore } from '@videojs/store/react';
// Media primitives
+14 -14
View File
@@ -1,15 +1,15 @@
'use client';
import type { Media, PlayerTarget } from '@videojs/core/dom';
import type { AnyFeature, FeatureStore, UnionFeatureState } from '@videojs/store';
import { createStore } from '@videojs/store';
import type { AnyPlayerFeature, Media, PlayerStore, PlayerTarget } from '@videojs/core/dom';
import type { UnionSliceState } from '@videojs/store';
import { combine, createStore } from '@videojs/store';
import { useStore } from '@videojs/store/react';
import type { ReactNode } from 'react';
import type { FC, ReactNode } from 'react';
import { useEffect, useState } from 'react';
import { Container, PlayerContextProvider, useMedia, usePlayerContext } from './context';
export interface CreatePlayerConfig<Features extends AnyFeature[]> {
export interface CreatePlayerConfig<Features extends AnyPlayerFeature[]> {
features: Features;
displayName?: string;
}
@@ -18,26 +18,26 @@ export interface ProviderProps {
children: ReactNode;
}
export interface CreatePlayerResult<Features extends AnyFeature[]> {
Provider: (props: ProviderProps) => ReactNode;
export interface CreatePlayerResult<Features extends AnyPlayerFeature[]> {
Provider: FC<ProviderProps>;
Container: typeof Container;
usePlayer: UsePlayerHook<Features>;
useMedia: () => Media | null;
}
type UsePlayerHook<Features extends AnyFeature[]> = {
(): FeatureStore<Features>;
<R>(selector: (state: UnionFeatureState<Features>) => R): R;
type UsePlayerHook<Features extends AnyPlayerFeature[]> = {
(): PlayerStore<Features>;
<R>(selector: (state: UnionSliceState<Features>) => R): R;
};
export function createPlayer<const Features extends AnyFeature<PlayerTarget>[]>(
export function createPlayer<const Features extends AnyPlayerFeature[]>(
config: CreatePlayerConfig<Features>
): CreatePlayerResult<Features> {
type Store = FeatureStore<Features>;
type State = UnionFeatureState<Features>;
type Store = PlayerStore<Features>;
type State = UnionSliceState<Features>;
function Provider({ children }: ProviderProps): ReactNode {
const [store] = useState(() => createStore({ features: config.features }));
const [store] = useState(() => createStore<PlayerTarget>()(combine(...config.features)));
const [media, setMedia] = useState<Media | null>(null);
useEffect(() => () => store.destroy(), [store]);
@@ -1,13 +1,13 @@
import { render, renderHook } from '@testing-library/react';
import { defineFeature } from '@videojs/store';
import type { PlayerStore } from '@videojs/core/dom';
import { defineSlice } from '@videojs/store';
import type { ReactNode } from 'react';
import { describe, expect, it, vi } from 'vitest';
import { createPlayer } from '../create-player';
describe('createPlayer', () => {
// Create a mock feature that works with any target
const mockFeature = defineFeature<any>()({
// Create a mock slice that works with any target
const mockSlice = defineSlice()({
state: () => ({
volume: 1,
muted: false,
@@ -17,9 +17,9 @@ describe('createPlayer', () => {
describe('Provider', () => {
it('creates store on mount', () => {
const { Provider, usePlayer } = createPlayer({ features: [mockFeature] as any });
const { Provider, usePlayer } = createPlayer({ features: [mockSlice] });
let store: unknown;
let store!: PlayerStore;
function TestComponent() {
store = usePlayer();
@@ -33,15 +33,15 @@ describe('createPlayer', () => {
);
expect(store).toBeDefined();
expect(typeof (store as any).subscribe).toBe('function');
expect(typeof (store as any).attach).toBe('function');
expect(typeof (store as any).destroy).toBe('function');
expect(typeof store.subscribe).toBe('function');
expect(typeof store.attach).toBe('function');
expect(typeof store.destroy).toBe('function');
});
it('destroys store on unmount', () => {
const { Provider, usePlayer } = createPlayer({ features: [mockFeature] as any });
const { Provider, usePlayer } = createPlayer({ features: [mockSlice] });
let store: any;
let store!: PlayerStore;
function TestComponent() {
store = usePlayer();
@@ -62,15 +62,15 @@ describe('createPlayer', () => {
it('uses displayName when provided', () => {
const { Provider } = createPlayer({
features: [mockFeature] as any,
features: [mockSlice],
displayName: 'VideoPlayer',
});
expect((Provider as any).displayName).toBe('VideoPlayer.Provider');
expect(Provider.displayName).toBe('VideoPlayer.Provider');
});
it('renders children', () => {
const { Provider } = createPlayer({ features: [mockFeature] as any });
const { Provider } = createPlayer({ features: [mockSlice] });
const { container } = render(
<Provider>
@@ -84,7 +84,7 @@ describe('createPlayer', () => {
describe('usePlayer', () => {
it('returns store without selector', () => {
const { Provider, usePlayer } = createPlayer({ features: [mockFeature] as any });
const { Provider, usePlayer } = createPlayer({ features: [mockSlice] });
const wrapper = ({ children }: { children: ReactNode }) => <Provider>{children}</Provider>;
@@ -96,7 +96,7 @@ describe('createPlayer', () => {
});
it('returns selected state with selector', () => {
const { Provider, usePlayer } = createPlayer({ features: [mockFeature] as any });
const { Provider, usePlayer } = createPlayer({ features: [mockSlice] });
const wrapper = ({ children }: { children: ReactNode }) => <Provider>{children}</Provider>;
@@ -106,7 +106,7 @@ describe('createPlayer', () => {
});
it('throws outside Provider', () => {
const { usePlayer } = createPlayer({ features: [mockFeature] as any });
const { usePlayer } = createPlayer({ features: [mockSlice] });
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
@@ -120,16 +120,16 @@ describe('createPlayer', () => {
describe('Container', () => {
it('is exported from createPlayer result', () => {
const { Container } = createPlayer({ features: [mockFeature] as any });
const { Container } = createPlayer({ features: [mockSlice] });
expect(Container).toBeDefined();
});
});
describe('full integration', () => {
it('Provider → Container → media attach flow', () => {
const { Provider, Container, usePlayer } = createPlayer({ features: [mockFeature] as any });
const { Provider, Container, usePlayer } = createPlayer({ features: [mockSlice] });
let store: any;
let store!: PlayerStore;
function TestComponent() {
store = usePlayer();