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

This commit is contained in:
rahim
2026-01-21 00:33:28 +11:00
committed by GitHub
parent 5bbec30df9
commit f01a5fcf5c
71 changed files with 777 additions and 978 deletions
+1 -1
View File
@@ -1 +1 @@
export * from './store/slices';
export * from './store/features';
@@ -1,14 +1,14 @@
import type { InferSliceRequests, InferSliceState } from '@videojs/store';
import type { InferFeatureRequests, InferFeatureState } from '@videojs/store';
import { createSlice } from '@videojs/store';
import { createFeature } from '@videojs/store';
import { listen, serializeTimeRanges } from '@videojs/utils/dom';
/**
* Buffer slice for HTMLMediaElement.
* Buffer feature for HTMLMediaElement.
*
* Tracks buffered and seekable time ranges. Read-only (no requests).
*/
export const bufferSlice = createSlice<HTMLMediaElement>()({
export const bufferFeature = createFeature<HTMLMediaElement>()({
initialState: {
/** Buffered time ranges as [start, end] tuples. */
buffered: [] as [number, number][],
@@ -29,6 +29,6 @@ export const bufferSlice = createSlice<HTMLMediaElement>()({
request: {},
});
export type BufferState = InferSliceState<typeof bufferSlice>;
export type BufferState = InferFeatureState<typeof bufferFeature>;
export type BufferRequests = InferSliceRequests<typeof bufferSlice>;
export type BufferRequests = InferFeatureRequests<typeof bufferFeature>;
@@ -0,0 +1,15 @@
import { bufferFeature } from './buffer';
import { playbackFeature } from './playback';
import { sourceFeature } from './source';
import { timeFeature } from './time';
import { volumeFeature } from './volume';
export {
bufferFeature as buffer,
playbackFeature as playback,
sourceFeature as source,
timeFeature as time,
volumeFeature as volume,
};
export const all = [bufferFeature, playbackFeature, sourceFeature, timeFeature, volumeFeature] as const;
@@ -1,14 +1,14 @@
import type { InferSliceRequests, InferSliceState } from '@videojs/store';
import type { InferFeatureRequests, InferFeatureState } from '@videojs/store';
import { createSlice } from '@videojs/store';
import { createFeature } from '@videojs/store';
import { listen } from '@videojs/utils/dom';
/**
* Playback slice for HTMLMediaElement.
* Playback feature for HTMLMediaElement.
*
* Tracks core playback state and provides play/pause control.
*/
export const playbackSlice = createSlice<HTMLMediaElement>()({
export const playbackFeature = createFeature<HTMLMediaElement>()({
initialState: {
/** Whether playback is paused. */
paused: true,
@@ -48,6 +48,6 @@ export const playbackSlice = createSlice<HTMLMediaElement>()({
},
});
export type PlaybackState = InferSliceState<typeof playbackSlice>;
export type PlaybackState = InferFeatureState<typeof playbackFeature>;
export type PlaybackRequests = InferSliceRequests<typeof playbackSlice>;
export type PlaybackRequests = InferFeatureRequests<typeof playbackFeature>;
@@ -1,14 +1,14 @@
import type { InferSliceRequests, InferSliceState } from '@videojs/store';
import type { InferFeatureRequests, InferFeatureState } from '@videojs/store';
import { createSlice } from '@videojs/store';
import { createFeature } from '@videojs/store';
import { listen } from '@videojs/utils/dom';
/**
* Source slice for HTMLMediaElement.
* Source feature for HTMLMediaElement.
*
* Tracks current source and loading state, provides source change control.
*/
export const sourceSlice = createSlice<HTMLMediaElement>()({
export const sourceFeature = createFeature<HTMLMediaElement>()({
initialState: {
/** Current media source URL (null if none). */
source: null as string | null,
@@ -38,6 +38,6 @@ export const sourceSlice = createSlice<HTMLMediaElement>()({
},
});
export type SourceState = InferSliceState<typeof sourceSlice>;
export type SourceState = InferFeatureState<typeof sourceFeature>;
export type SourceRequests = InferSliceRequests<typeof sourceSlice>;
export type SourceRequests = InferFeatureRequests<typeof sourceFeature>;
@@ -1,22 +1,22 @@
import { describe, expect, it, vi } from 'vitest';
import { bufferSlice } from '../buffer';
import { bufferFeature } from '../buffer';
describe('bufferSlice', () => {
describe('slice structure', () => {
describe('bufferFeature', () => {
describe('feature structure', () => {
it('has unique id symbol', () => {
expect(bufferSlice.id).toBeTypeOf('symbol');
expect(bufferFeature.id).toBeTypeOf('symbol');
});
it('has correct initial state', () => {
expect(bufferSlice.initialState).toEqual({
expect(bufferFeature.initialState).toEqual({
buffered: [],
seekable: [],
});
});
it('has no request handlers', () => {
expect(Object.keys(bufferSlice.request)).toHaveLength(0);
expect(Object.keys(bufferFeature.request)).toHaveLength(0);
});
});
@@ -27,9 +27,9 @@ describe('bufferSlice', () => {
seekable: createTimeRanges([[0, 120]]),
});
const snapshot = bufferSlice.getSnapshot({
const snapshot = bufferFeature.getSnapshot({
target: video,
initialState: bufferSlice.initialState,
initialState: bufferFeature.initialState,
});
expect(snapshot).toEqual({
@@ -47,9 +47,9 @@ describe('bufferSlice', () => {
seekable: createTimeRanges([[0, 120]]),
});
const snapshot = bufferSlice.getSnapshot({
const snapshot = bufferFeature.getSnapshot({
target: video,
initialState: bufferSlice.initialState,
initialState: bufferFeature.initialState,
});
expect(snapshot.buffered).toEqual([
@@ -68,7 +68,7 @@ describe('bufferSlice', () => {
const update = vi.fn();
const controller = new AbortController();
bufferSlice.subscribe({ target: video, update, signal: controller.signal });
bufferFeature.subscribe({ target: video, update, signal: controller.signal });
video.dispatchEvent(new Event('progress'));
expect(update).toHaveBeenCalled();
@@ -82,7 +82,7 @@ describe('bufferSlice', () => {
const update = vi.fn();
const controller = new AbortController();
bufferSlice.subscribe({ target: video, update, signal: controller.signal });
bufferFeature.subscribe({ target: video, update, signal: controller.signal });
video.dispatchEvent(new Event('emptied'));
expect(update).toHaveBeenCalled();
@@ -1,15 +1,15 @@
import { describe, expect, it, vi } from 'vitest';
import { playbackSlice } from '../playback';
import { playbackFeature } from '../playback';
describe('playbackSlice', () => {
describe('slice structure', () => {
describe('playbackFeature', () => {
describe('feature structure', () => {
it('has unique id symbol', () => {
expect(playbackSlice.id).toBeTypeOf('symbol');
expect(playbackFeature.id).toBeTypeOf('symbol');
});
it('has correct initial state', () => {
expect(playbackSlice.initialState).toEqual({
expect(playbackFeature.initialState).toEqual({
paused: true,
ended: false,
started: false,
@@ -18,18 +18,18 @@ describe('playbackSlice', () => {
});
it('has all request handlers', () => {
expect(playbackSlice.request.play).toBeDefined();
expect(playbackSlice.request.pause).toBeDefined();
expect(playbackFeature.request.play).toBeDefined();
expect(playbackFeature.request.pause).toBeDefined();
});
it('request handlers have correct structure', () => {
expect(playbackSlice.request.play).toMatchObject({
expect(playbackFeature.request.play).toMatchObject({
key: 'play',
guard: [],
handler: expect.any(Function),
});
expect(playbackSlice.request.pause).toMatchObject({
expect(playbackFeature.request.pause).toMatchObject({
key: 'pause',
guard: [],
handler: expect.any(Function),
@@ -46,9 +46,9 @@ describe('playbackSlice', () => {
readyState: HTMLMediaElement.HAVE_ENOUGH_DATA,
});
const snapshot = playbackSlice.getSnapshot({
const snapshot = playbackFeature.getSnapshot({
target: video,
initialState: playbackSlice.initialState,
initialState: playbackFeature.initialState,
});
expect(snapshot).toEqual({
@@ -65,9 +65,9 @@ describe('playbackSlice', () => {
readyState: HTMLMediaElement.HAVE_CURRENT_DATA,
});
const snapshot = playbackSlice.getSnapshot({
const snapshot = playbackFeature.getSnapshot({
target: video,
initialState: playbackSlice.initialState,
initialState: playbackFeature.initialState,
});
expect(snapshot.waiting).toBe(true);
@@ -79,9 +79,9 @@ describe('playbackSlice', () => {
currentTime: 5,
});
const snapshot = playbackSlice.getSnapshot({
const snapshot = playbackFeature.getSnapshot({
target: video,
initialState: playbackSlice.initialState,
initialState: playbackFeature.initialState,
});
expect(snapshot.started).toBe(true);
@@ -93,9 +93,9 @@ describe('playbackSlice', () => {
currentTime: 0,
});
const snapshot = playbackSlice.getSnapshot({
const snapshot = playbackFeature.getSnapshot({
target: video,
initialState: playbackSlice.initialState,
initialState: playbackFeature.initialState,
});
expect(snapshot.started).toBe(true);
@@ -108,7 +108,7 @@ describe('playbackSlice', () => {
const update = vi.fn();
const controller = new AbortController();
playbackSlice.subscribe({ target: video, update, signal: controller.signal });
playbackFeature.subscribe({ target: video, update, signal: controller.signal });
video.dispatchEvent(new Event('play'));
expect(update).toHaveBeenCalled();
@@ -119,7 +119,7 @@ describe('playbackSlice', () => {
const update = vi.fn();
const controller = new AbortController();
playbackSlice.subscribe({ target: video, update, signal: controller.signal });
playbackFeature.subscribe({ target: video, update, signal: controller.signal });
video.dispatchEvent(new Event('pause'));
expect(update).toHaveBeenCalled();
@@ -130,7 +130,7 @@ describe('playbackSlice', () => {
const update = vi.fn();
const controller = new AbortController();
playbackSlice.subscribe({ target: video, update, signal: controller.signal });
playbackFeature.subscribe({ target: video, update, signal: controller.signal });
video.dispatchEvent(new Event('ended'));
expect(update).toHaveBeenCalled();
@@ -141,7 +141,7 @@ describe('playbackSlice', () => {
const update = vi.fn();
const controller = new AbortController();
playbackSlice.subscribe({ target: video, update, signal: controller.signal });
playbackFeature.subscribe({ target: video, update, signal: controller.signal });
controller.abort();
video.dispatchEvent(new Event('play'));
@@ -155,7 +155,7 @@ describe('playbackSlice', () => {
const video = createMockVideo({});
video.play = vi.fn().mockResolvedValue(undefined);
await playbackSlice.request.play.handler(undefined, {
await playbackFeature.request.play.handler(undefined, {
target: video,
signal: new AbortController().signal,
meta: null,
@@ -170,7 +170,7 @@ describe('playbackSlice', () => {
const video = createMockVideo({});
video.pause = vi.fn();
playbackSlice.request.pause.handler(undefined, {
playbackFeature.request.pause.handler(undefined, {
target: video,
signal: new AbortController().signal,
meta: null,
@@ -1,23 +1,23 @@
import { describe, expect, it, vi } from 'vitest';
import { sourceSlice } from '../source';
import { sourceFeature } from '../source';
describe('sourceSlice', () => {
describe('slice structure', () => {
describe('sourceFeature', () => {
describe('feature structure', () => {
it('has unique id symbol', () => {
expect(sourceSlice.id).toBeTypeOf('symbol');
expect(sourceFeature.id).toBeTypeOf('symbol');
});
it('has correct initial state', () => {
expect(sourceSlice.initialState).toEqual({
expect(sourceFeature.initialState).toEqual({
source: null,
canPlay: false,
});
});
it('has changeSource request handler', () => {
expect(sourceSlice.request.changeSource).toBeDefined();
expect(sourceSlice.request.changeSource).toMatchObject({
expect(sourceFeature.request.changeSource).toBeDefined();
expect(sourceFeature.request.changeSource).toMatchObject({
key: 'changeSource',
guard: [],
handler: expect.any(Function),
@@ -33,9 +33,9 @@ describe('sourceSlice', () => {
readyState: HTMLMediaElement.HAVE_ENOUGH_DATA,
});
const snapshot = sourceSlice.getSnapshot({
const snapshot = sourceFeature.getSnapshot({
target: video,
initialState: sourceSlice.initialState,
initialState: sourceFeature.initialState,
});
expect(snapshot).toEqual({
@@ -50,9 +50,9 @@ describe('sourceSlice', () => {
Object.defineProperty(video, 'currentSrc', { value: '', writable: false });
Object.defineProperty(video, 'readyState', { value: HTMLMediaElement.HAVE_NOTHING, writable: false });
const snapshot = sourceSlice.getSnapshot({
const snapshot = sourceFeature.getSnapshot({
target: video,
initialState: sourceSlice.initialState,
initialState: sourceFeature.initialState,
});
expect(snapshot.source).toBe(null);
@@ -66,7 +66,7 @@ describe('sourceSlice', () => {
const update = vi.fn();
const controller = new AbortController();
sourceSlice.subscribe({ target: video, update, signal: controller.signal });
sourceFeature.subscribe({ target: video, update, signal: controller.signal });
video.dispatchEvent(new Event('canplay'));
expect(update).toHaveBeenCalled();
@@ -80,7 +80,7 @@ describe('sourceSlice', () => {
const update = vi.fn();
const controller = new AbortController();
sourceSlice.subscribe({ target: video, update, signal: controller.signal });
sourceFeature.subscribe({ target: video, update, signal: controller.signal });
video.dispatchEvent(new Event('loadstart'));
expect(update).toHaveBeenCalled();
@@ -91,7 +91,7 @@ describe('sourceSlice', () => {
const update = vi.fn();
const controller = new AbortController();
sourceSlice.subscribe({ target: video, update, signal: controller.signal });
sourceFeature.subscribe({ target: video, update, signal: controller.signal });
video.dispatchEvent(new Event('emptied'));
expect(update).toHaveBeenCalled();
@@ -104,7 +104,7 @@ describe('sourceSlice', () => {
const video = createMockVideo({});
video.load = vi.fn();
const result = sourceSlice.request.changeSource.handler('https://example.com/new.mp4', {
const result = sourceFeature.request.changeSource.handler('https://example.com/new.mp4', {
target: video,
signal: new AbortController().signal,
meta: null,
@@ -1,23 +1,23 @@
import { describe, expect, it, vi } from 'vitest';
import { timeSlice } from '../time';
import { timeFeature } from '../time';
describe('timeSlice', () => {
describe('slice structure', () => {
describe('timeFeature', () => {
describe('feature structure', () => {
it('has unique id symbol', () => {
expect(timeSlice.id).toBeTypeOf('symbol');
expect(timeFeature.id).toBeTypeOf('symbol');
});
it('has correct initial state', () => {
expect(timeSlice.initialState).toEqual({
expect(timeFeature.initialState).toEqual({
currentTime: 0,
duration: 0,
});
});
it('has seek request handler', () => {
expect(timeSlice.request.seek).toBeDefined();
expect(timeSlice.request.seek).toMatchObject({
expect(timeFeature.request.seek).toBeDefined();
expect(timeFeature.request.seek).toMatchObject({
key: 'seek',
guard: [],
handler: expect.any(Function),
@@ -32,9 +32,9 @@ describe('timeSlice', () => {
duration: 120,
});
const snapshot = timeSlice.getSnapshot({
const snapshot = timeFeature.getSnapshot({
target: video,
initialState: timeSlice.initialState,
initialState: timeFeature.initialState,
});
expect(snapshot).toEqual({
@@ -49,9 +49,9 @@ describe('timeSlice', () => {
duration: Number.NaN,
});
const snapshot = timeSlice.getSnapshot({
const snapshot = timeFeature.getSnapshot({
target: video,
initialState: timeSlice.initialState,
initialState: timeFeature.initialState,
});
expect(snapshot.duration).toBe(0);
@@ -64,7 +64,7 @@ describe('timeSlice', () => {
const update = vi.fn();
const controller = new AbortController();
timeSlice.subscribe({ target: video, update, signal: controller.signal });
timeFeature.subscribe({ target: video, update, signal: controller.signal });
video.dispatchEvent(new Event('timeupdate'));
expect(update).toHaveBeenCalled();
@@ -75,7 +75,7 @@ describe('timeSlice', () => {
const update = vi.fn();
const controller = new AbortController();
timeSlice.subscribe({ target: video, update, signal: controller.signal });
timeFeature.subscribe({ target: video, update, signal: controller.signal });
video.dispatchEvent(new Event('durationchange'));
expect(update).toHaveBeenCalled();
@@ -86,7 +86,7 @@ describe('timeSlice', () => {
const update = vi.fn();
const controller = new AbortController();
timeSlice.subscribe({ target: video, update, signal: controller.signal });
timeFeature.subscribe({ target: video, update, signal: controller.signal });
video.dispatchEvent(new Event('seeked'));
expect(update).toHaveBeenCalled();
@@ -97,7 +97,7 @@ describe('timeSlice', () => {
const update = vi.fn();
const controller = new AbortController();
timeSlice.subscribe({ target: video, update, signal: controller.signal });
timeFeature.subscribe({ target: video, update, signal: controller.signal });
video.dispatchEvent(new Event('emptied'));
expect(update).toHaveBeenCalled();
@@ -109,7 +109,7 @@ describe('timeSlice', () => {
it('sets currentTime on target and waits for seeked event', async () => {
const video = createMockVideo({});
const resultPromise = timeSlice.request.seek.handler(45, {
const resultPromise = timeFeature.request.seek.handler(45, {
target: video,
signal: new AbortController().signal,
meta: null,
@@ -128,7 +128,7 @@ describe('timeSlice', () => {
const video = createMockVideo({});
const controller = new AbortController();
const resultPromise = timeSlice.request.seek.handler(30, {
const resultPromise = timeFeature.request.seek.handler(30, {
target: video,
signal: controller.signal,
meta: null,
@@ -1,23 +1,23 @@
import { describe, expect, it, vi } from 'vitest';
import { volumeSlice } from '../volume';
import { volumeFeature } from '../volume';
describe('volumeSlice', () => {
describe('slice structure', () => {
describe('volumeFeature', () => {
describe('feature structure', () => {
it('has unique id symbol', () => {
expect(volumeSlice.id).toBeTypeOf('symbol');
expect(volumeFeature.id).toBeTypeOf('symbol');
});
it('has correct initial state', () => {
expect(volumeSlice.initialState).toEqual({
expect(volumeFeature.initialState).toEqual({
volume: 1,
muted: false,
});
});
it('has all request handlers', () => {
expect(volumeSlice.request.changeVolume).toBeDefined();
expect(volumeSlice.request.toggleMute).toBeDefined();
expect(volumeFeature.request.changeVolume).toBeDefined();
expect(volumeFeature.request.toggleMute).toBeDefined();
});
});
@@ -28,9 +28,9 @@ describe('volumeSlice', () => {
muted: false,
});
const snapshot = volumeSlice.getSnapshot({
const snapshot = volumeFeature.getSnapshot({
target: video,
initialState: volumeSlice.initialState,
initialState: volumeFeature.initialState,
});
expect(snapshot).toEqual({
@@ -46,7 +46,7 @@ describe('volumeSlice', () => {
const update = vi.fn();
const controller = new AbortController();
volumeSlice.subscribe({ target: video, update, signal: controller.signal });
volumeFeature.subscribe({ target: video, update, signal: controller.signal });
video.dispatchEvent(new Event('volumechange'));
expect(update).toHaveBeenCalled();
@@ -58,7 +58,7 @@ describe('volumeSlice', () => {
it('sets volume on target', () => {
const video = createMockVideo({});
const result = volumeSlice.request.changeVolume.handler(0.7, {
const result = volumeFeature.request.changeVolume.handler(0.7, {
target: video,
signal: new AbortController().signal,
meta: null,
@@ -71,7 +71,7 @@ describe('volumeSlice', () => {
it('clamps volume to min 0', () => {
const video = createMockVideo({});
volumeSlice.request.changeVolume.handler(-0.5, {
volumeFeature.request.changeVolume.handler(-0.5, {
target: video,
signal: new AbortController().signal,
meta: null,
@@ -83,7 +83,7 @@ describe('volumeSlice', () => {
it('clamps volume to max 1', () => {
const video = createMockVideo({});
volumeSlice.request.changeVolume.handler(1.5, {
volumeFeature.request.changeVolume.handler(1.5, {
target: video,
signal: new AbortController().signal,
meta: null,
@@ -97,7 +97,7 @@ describe('volumeSlice', () => {
it('toggles mute from false to true', () => {
const video = createMockVideo({ muted: false });
const result = volumeSlice.request.toggleMute.handler(undefined, {
const result = volumeFeature.request.toggleMute.handler(undefined, {
target: video,
signal: new AbortController().signal,
meta: null,
@@ -110,7 +110,7 @@ describe('volumeSlice', () => {
it('toggles mute from true to false', () => {
const video = createMockVideo({ muted: true });
const result = volumeSlice.request.toggleMute.handler(undefined, {
const result = volumeFeature.request.toggleMute.handler(undefined, {
target: video,
signal: new AbortController().signal,
meta: null,
@@ -1,14 +1,14 @@
import type { InferSliceRequests, InferSliceState } from '@videojs/store';
import type { InferFeatureRequests, InferFeatureState } from '@videojs/store';
import { createSlice } from '@videojs/store';
import { createFeature } from '@videojs/store';
import { listen, onEvent } from '@videojs/utils/dom';
/**
* Time slice for HTMLMediaElement.
* Time feature for HTMLMediaElement.
*
* Tracks current time and duration, provides seek control.
*/
export const timeSlice = createSlice<HTMLMediaElement>()({
export const timeFeature = createFeature<HTMLMediaElement>()({
initialState: {
/** Current playback position in seconds. */
currentTime: 0,
@@ -39,6 +39,6 @@ export const timeSlice = createSlice<HTMLMediaElement>()({
},
});
export type TimeState = InferSliceState<typeof timeSlice>;
export type TimeState = InferFeatureState<typeof timeFeature>;
export type TimeRequests = InferSliceRequests<typeof timeSlice>;
export type TimeRequests = InferFeatureRequests<typeof timeFeature>;
@@ -1,14 +1,14 @@
import type { InferSliceRequests, InferSliceState } from '@videojs/store';
import type { InferFeatureRequests, InferFeatureState } from '@videojs/store';
import { createSlice } from '@videojs/store';
import { createFeature } from '@videojs/store';
import { listen } from '@videojs/utils/dom';
/**
* Volume slice for HTMLMediaElement.
* Volume feature for HTMLMediaElement.
*
* Tracks volume and mute state, provides volume control.
*/
export const volumeSlice = createSlice<HTMLMediaElement>()({
export const volumeFeature = createFeature<HTMLMediaElement>()({
initialState: {
/** Volume level from 0 (silent) to 1 (max). */
volume: 1,
@@ -40,6 +40,6 @@ export const volumeSlice = createSlice<HTMLMediaElement>()({
},
});
export type VolumeState = InferSliceState<typeof volumeSlice>;
export type VolumeState = InferFeatureState<typeof volumeFeature>;
export type VolumeRequests = InferSliceRequests<typeof volumeSlice>;
export type VolumeRequests = InferFeatureRequests<typeof volumeFeature>;
@@ -1,15 +0,0 @@
import { bufferSlice } from './buffer';
import { playbackSlice } from './playback';
import { sourceSlice } from './source';
import { timeSlice } from './time';
import { volumeSlice } from './volume';
export {
bufferSlice as buffer,
playbackSlice as playback,
sourceSlice as source,
timeSlice as time,
volumeSlice as volume,
};
export const all = [bufferSlice, playbackSlice, sourceSlice, timeSlice, volumeSlice] as const;