Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion packages/core/src/core/ui/controls/controls-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ export interface ControlsState {
}

export class ControlsCore {
getState(media: MediaControlsState): ControlsState {
#media: MediaControlsState | null = null;

setMedia(media: MediaControlsState): void {
this.#media = media;
}

getState(): ControlsState {
const media = this.#media!;
return {
visible: media.controlsVisible,
userActive: media.userActive,
Expand Down
24 changes: 16 additions & 8 deletions packages/core/src/core/ui/controls/tests/controls-core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,59 @@ describe('ControlsCore', () => {
const core = new ControlsCore();
const media = createControlsState({ controlsVisible: true });

expect(core.getState(media)).toEqual({ visible: true, userActive: true });
core.setMedia(media);
expect(core.getState()).toEqual({ visible: true, userActive: true });
});

it('returns visible: false when controlsVisible is false', () => {
const core = new ControlsCore();
const media = createControlsState({ controlsVisible: false });

expect(core.getState(media)).toEqual({ visible: false, userActive: true });
core.setMedia(media);
expect(core.getState()).toEqual({ visible: false, userActive: true });
});

it('returns userActive: false when userActive is false', () => {
const core = new ControlsCore();
const media = createControlsState({ userActive: false });

expect(core.getState(media)).toEqual({ visible: true, userActive: false });
core.setMedia(media);
expect(core.getState()).toEqual({ visible: true, userActive: false });
});

it('projects visible and userActive independently', () => {
const core = new ControlsCore();

// All four quadrants of the 2x2 boolean matrix
expect(core.getState(createControlsState({ controlsVisible: true, userActive: true }))).toEqual({
core.setMedia(createControlsState({ controlsVisible: true, userActive: true }));
expect(core.getState()).toEqual({
visible: true,
userActive: true,
});

expect(core.getState(createControlsState({ controlsVisible: true, userActive: false }))).toEqual({
core.setMedia(createControlsState({ controlsVisible: true, userActive: false }));
expect(core.getState()).toEqual({
visible: true,
userActive: false,
});

expect(core.getState(createControlsState({ controlsVisible: false, userActive: true }))).toEqual({
core.setMedia(createControlsState({ controlsVisible: false, userActive: true }));
expect(core.getState()).toEqual({
visible: false,
userActive: true,
});

expect(core.getState(createControlsState({ controlsVisible: false, userActive: false }))).toEqual({
core.setMedia(createControlsState({ controlsVisible: false, userActive: false }));
expect(core.getState()).toEqual({
visible: false,
userActive: false,
});
});

it('returns only primitive values', () => {
const core = new ControlsCore();
const state = core.getState(createControlsState());
core.setMedia(createControlsState());
const state = core.getState();

const functionKeys = Object.entries(state).filter(([, value]) => typeof value === 'function');
expect(functionKeys).toHaveLength(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export class FullscreenButtonCore {
};

#props = { ...FullscreenButtonCore.defaultProps };
#media: MediaFullscreenState | null = null;

constructor(props?: FullscreenButtonProps) {
if (props) this.setProps(props);
Expand Down Expand Up @@ -52,7 +53,12 @@ export class FullscreenButtonCore {
};
}

getState(media: MediaFullscreenState): FullscreenButtonState {
setMedia(media: MediaFullscreenState): void {
this.#media = media;
}

getState(): FullscreenButtonState {
const media = this.#media!;
return {
fullscreen: media.fullscreen,
availability: media.fullscreenAvailability,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ describe('FullscreenButtonCore', () => {
it('projects fullscreen and availability', () => {
const core = new FullscreenButtonCore();
const media = createMediaState({ fullscreen: true });
const state = core.getState(media);
core.setMedia(media);
const state = core.getState();

expect(state.fullscreen).toBe(true);
expect(state.availability).toBe('available');
});

it('reflects unsupported availability', () => {
const core = new FullscreenButtonCore();
const state = core.getState(createMediaState({ fullscreenAvailability: 'unsupported' }));
core.setMedia(createMediaState({ fullscreenAvailability: 'unsupported' }));
const state = core.getState();

expect(state.availability).toBe('unsupported');
});
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/core/ui/mute-button/mute-button-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class MuteButtonCore {
};

#props = { ...MuteButtonCore.defaultProps };
#media: MediaVolumeState | null = null;

constructor(props?: MuteButtonProps) {
if (props) this.setProps(props);
Expand Down Expand Up @@ -60,7 +61,12 @@ export class MuteButtonCore {
};
}

getState(media: MediaVolumeState): MuteButtonState {
setMedia(media: MediaVolumeState): void {
this.#media = media;
}

getState(): MuteButtonState {
const media = this.#media!;
return {
muted: media.muted,
volumeLevel: getVolumeLevel(media),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,42 +28,44 @@ describe('MuteButtonCore', () => {
it('projects muted and volumeLevel', () => {
const core = new MuteButtonCore();
const media = createMediaState({ muted: false, volume: 1 });
const state = core.getState(media);
core.setMedia(media);
const state = core.getState();

expect(state.muted).toBe(false);
expect(state.volumeLevel).toBe('high');
});

it('returns off when muted', () => {
const core = new MuteButtonCore();
const state = core.getState(createMediaState({ muted: true, volume: 0.8 }));
core.setMedia(createMediaState({ muted: true, volume: 0.8 }));
const state = core.getState();

expect(state.muted).toBe(true);
expect(state.volumeLevel).toBe('off');
});

it('returns off when volume is 0', () => {
const core = new MuteButtonCore();
const state = core.getState(createMediaState({ volume: 0 }));
expect(state.volumeLevel).toBe('off');
core.setMedia(createMediaState({ volume: 0 }));
expect(core.getState().volumeLevel).toBe('off');
});

it('returns low when volume < 0.5', () => {
const core = new MuteButtonCore();
const state = core.getState(createMediaState({ volume: 0.3 }));
expect(state.volumeLevel).toBe('low');
core.setMedia(createMediaState({ volume: 0.3 }));
expect(core.getState().volumeLevel).toBe('low');
});

it('returns medium when volume < 0.75', () => {
const core = new MuteButtonCore();
const state = core.getState(createMediaState({ volume: 0.6 }));
expect(state.volumeLevel).toBe('medium');
core.setMedia(createMediaState({ volume: 0.6 }));
expect(core.getState().volumeLevel).toBe('medium');
});

it('returns high when volume >= 0.75', () => {
const core = new MuteButtonCore();
const state = core.getState(createMediaState({ volume: 0.75 }));
expect(state.volumeLevel).toBe('high');
core.setMedia(createMediaState({ volume: 0.75 }));
expect(core.getState().volumeLevel).toBe('high');
});
});

Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/core/ui/pip-button/pip-button-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export class PiPButtonCore {
};

#props = { ...PiPButtonCore.defaultProps };
#media: MediaPictureInPictureState | null = null;

constructor(props?: PiPButtonProps) {
if (props) this.setProps(props);
Expand Down Expand Up @@ -52,7 +53,12 @@ export class PiPButtonCore {
};
}

getState(media: MediaPictureInPictureState): PiPButtonState {
setMedia(media: MediaPictureInPictureState): void {
this.#media = media;
}

getState(): PiPButtonState {
const media = this.#media!;
return {
pip: media.pip,
availability: media.pipAvailability,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ describe('PiPButtonCore', () => {
it('projects pip and availability', () => {
const core = new PiPButtonCore();
const media = createMediaState({ pip: true });
const state = core.getState(media);
core.setMedia(media);
const state = core.getState();

expect(state.pip).toBe(true);
expect(state.availability).toBe('available');
});

it('reflects unsupported availability', () => {
const core = new PiPButtonCore();
const state = core.getState(createMediaState({ pipAvailability: 'unsupported' }));
core.setMedia(createMediaState({ pipAvailability: 'unsupported' }));
const state = core.getState();

expect(state.availability).toBe('unsupported');
});
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/core/ui/play-button/play-button-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class PlayButtonCore {
};

#props = { ...PlayButtonCore.defaultProps };
#media: MediaPlaybackState | null = null;

constructor(props?: PlayButtonProps) {
if (props) this.setProps(props);
Expand Down Expand Up @@ -50,7 +51,12 @@ export class PlayButtonCore {
};
}

getState(media: MediaPlaybackState): PlayButtonState {
setMedia(media: MediaPlaybackState): void {
this.#media = media;
}

getState(): PlayButtonState {
const media = this.#media!;
return {
paused: media.paused,
ended: media.ended,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ describe('PlayButtonCore', () => {
it('projects data fields from media state', () => {
const core = new PlayButtonCore();
const media = createMediaState({ paused: true, ended: false, started: true });
const state = core.getState(media);
core.setMedia(media);
const state = core.getState();

expect(state.paused).toBe(true);
expect(state.ended).toBe(false);
Expand All @@ -53,7 +54,8 @@ describe('PlayButtonCore', () => {

it('reflects playing state', () => {
const core = new PlayButtonCore();
const state = core.getState(createMediaState({ paused: false, started: true }));
core.setMedia(createMediaState({ paused: false, started: true }));
const state = core.getState();

expect(state.paused).toBe(false);
expect(state.started).toBe(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class PlaybackRateButtonCore {
};

#props = { ...PlaybackRateButtonCore.defaultProps };
#media: MediaPlaybackRateState | null = null;

constructor(props?: PlaybackRateButtonProps) {
if (props) this.setProps(props);
Expand Down Expand Up @@ -51,7 +52,12 @@ export class PlaybackRateButtonCore {
};
}

getState(media: MediaPlaybackRateState): PlaybackRateButtonState {
setMedia(media: MediaPlaybackRateState): void {
this.#media = media;
}

getState(): PlaybackRateButtonState {
const media = this.#media!;
return {
rate: media.playbackRate,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ describe('PlaybackRateButtonCore', () => {
it('projects playbackRate to rate', () => {
const core = new PlaybackRateButtonCore();
const media = createMediaState({ playbackRate: 1.5 });
const state = core.getState(media);
core.setMedia(media);
const state = core.getState();

expect(state.rate).toBe(1.5);
});
Expand Down
22 changes: 15 additions & 7 deletions packages/core/src/core/ui/popover/popover-core.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { defaults } from '@videojs/utils/object';
import type { NonNullableObject } from '@videojs/utils/types';

import type { TransitionFlags, TransitionState, TransitionStatus } from '../transition';
import { getTransitionFlags } from '../transition';

Expand Down Expand Up @@ -35,11 +36,11 @@ export interface PopoverProps {
}

/**
* The raw transition state managed by `createTransitionHandler`. Uses `active`
* The raw transition state managed by `createTransition`. Uses `active`
* (not `open`) to distinguish the generic transition state machine from the
* domain-specific `PopoverState.open`.
*/
export interface PopoverInteraction extends TransitionState {}
export interface PopoverInput extends TransitionState {}

export interface PopoverState extends TransitionFlags {
open: boolean;
Expand Down Expand Up @@ -73,14 +74,21 @@ export class PopoverCore {
this.#props = defaults(props, PopoverCore.defaultProps);
}

getState(interaction: PopoverInteraction): PopoverState {
#input: PopoverInput | null = null;

setInput(input: PopoverInput): void {
this.#input = input;
}

getState(): PopoverState {
const input = this.#input!;
return {
open: interaction.active,
status: interaction.status,
open: input.active,
status: input.status,
side: this.#props.side,
align: this.#props.align,
modal: this.#props.modal,
...getTransitionFlags(interaction.status),
...getTransitionFlags(input.status),
};
}

Expand All @@ -104,5 +112,5 @@ export class PopoverCore {
export namespace PopoverCore {
export type Props = PopoverProps;
export type State = PopoverState;
export type Interaction = PopoverInteraction;
export type Input = PopoverInput;
}
Loading
Loading