mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
refactor(packages): dry up core, html, and react UI architecture (#699)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { pick } from '@videojs/utils/object';
|
||||
import { AbortControllerRegistry } from './abort-controller-registry';
|
||||
import { throwNoTargetError } from './errors';
|
||||
import type { Selector } from './shallow-equal';
|
||||
import type { AnySlice, InferSliceState, StateContext } from './slice';
|
||||
|
||||
const stateContext: StateContext<unknown> = {
|
||||
@@ -18,20 +19,27 @@ const stateContext: StateContext<unknown> = {
|
||||
* ```ts
|
||||
* const selectPlayback = createSelector(playbackSlice);
|
||||
* selectPlayback(store.state); // { paused, play, pause, ... } | undefined
|
||||
* selectPlayback.displayName; // 'playback' (from slice name)
|
||||
* ```
|
||||
*
|
||||
* @param slice - The feature slice to create a selector for.
|
||||
* @param slice - The slice to create a selector for.
|
||||
*/
|
||||
export function createSelector<S extends AnySlice>(slice: S): (state: object) => InferSliceState<S> | undefined {
|
||||
export function createSelector<S extends AnySlice>(slice: S): Selector<object, InferSliceState<S> | undefined> {
|
||||
const initialState = slice.state(stateContext);
|
||||
const keys = Object.keys(initialState as object);
|
||||
|
||||
const firstKey = keys[0];
|
||||
if (!firstKey) return () => undefined;
|
||||
|
||||
return (state) => {
|
||||
// WARN: Could be the source of a bug if two slices have overlapping state keys
|
||||
if (!(firstKey in state)) return undefined;
|
||||
return pick(state as Record<string, unknown>, keys) as InferSliceState<S>;
|
||||
};
|
||||
if (!firstKey) {
|
||||
return Object.assign(() => undefined, { displayName: slice.name });
|
||||
}
|
||||
|
||||
return Object.assign(
|
||||
(state: object) => {
|
||||
// WARN: Could be the source of a bug if two slices have overlapping state keys
|
||||
if (!(firstKey in state)) return undefined;
|
||||
return pick(state as Record<string, unknown>, keys) as InferSliceState<S>;
|
||||
},
|
||||
{ displayName: slice.name }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export type Selector<State, Result> = (state: State) => Result;
|
||||
export interface Selector<State, Result> {
|
||||
(state: State): Result;
|
||||
displayName?: string | undefined;
|
||||
}
|
||||
|
||||
export type Comparator<T> = (a: T, b: T) => boolean;
|
||||
|
||||
|
||||
@@ -47,6 +47,8 @@ export interface StateContext<Target> {
|
||||
// ----------------------------------------
|
||||
|
||||
export interface SliceConfig<Target, State> {
|
||||
/** Debug label. Used as `displayName` on selectors created from this slice. */
|
||||
name?: string;
|
||||
state: (ctx: StateContext<Target>) => State;
|
||||
attach?: (ctx: AttachContext<Target, State>) => void;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ interface MockMedia {
|
||||
|
||||
describe('createSelector', () => {
|
||||
const volumeSlice = defineSlice<MockMedia>()({
|
||||
name: 'volume',
|
||||
state: ({ target }) => ({
|
||||
volume: 1,
|
||||
muted: false,
|
||||
@@ -19,6 +20,7 @@ describe('createSelector', () => {
|
||||
});
|
||||
|
||||
const playbackSlice = defineSlice<MockMedia>()({
|
||||
name: 'playback',
|
||||
state: () => ({
|
||||
paused: true,
|
||||
ended: false,
|
||||
@@ -86,4 +88,30 @@ describe('createSelector', () => {
|
||||
// But structurally equal (for shallowEqual comparison)
|
||||
expect(selected1).toEqual(selected2);
|
||||
});
|
||||
|
||||
it('exposes displayName from slice name', () => {
|
||||
const selectVolume = createSelector(volumeSlice);
|
||||
|
||||
expect(selectVolume.displayName).toBe('volume');
|
||||
});
|
||||
|
||||
it('omits displayName when slice has no name', () => {
|
||||
const unnamedSlice = defineSlice<MockMedia>()({
|
||||
state: () => ({ paused: true }),
|
||||
});
|
||||
const selector = createSelector(unnamedSlice);
|
||||
|
||||
expect(selector.displayName).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns undefined for empty-state slice', () => {
|
||||
const emptySlice = defineSlice<MockMedia>()({
|
||||
name: 'empty',
|
||||
state: () => ({}),
|
||||
});
|
||||
const selector = createSelector(emptySlice);
|
||||
|
||||
expect(selector({})).toBeUndefined();
|
||||
expect(selector.displayName).toBe('empty');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user