fix: Increase reliability of media auto actions (e.g. play) (#1934)
Refactors media player handling entirely to make the code more consistent and less boilerplate. Add testing for media player actions. - Closes #1921 - Closes #1916
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
import { expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { MuteAction } from '../../../../src/card-controller/actions/actions/mute';
|
||||
import { AdvancedCameraCardMediaPlayer } from '../../../../src/types';
|
||||
import { MediaPlayerController } from '../../../../src/types';
|
||||
import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils';
|
||||
|
||||
it('should handle mute action', async () => {
|
||||
const api = createCardAPI();
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
const mediaPlayerController = mock<MediaPlayerController>();
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
);
|
||||
const action = new MuteAction(
|
||||
@@ -22,5 +22,5 @@ it('should handle mute action', async () => {
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(player.mute).toBeCalled();
|
||||
expect(mediaPlayerController.mute).toBeCalled();
|
||||
});
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { PauseAction } from '../../../../src/card-controller/actions/actions/pause';
|
||||
import { AdvancedCameraCardMediaPlayer } from '../../../../src/types';
|
||||
import { MediaPlayerController } from '../../../../src/types';
|
||||
import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils';
|
||||
|
||||
it('should handle pause action', async () => {
|
||||
const api = createCardAPI();
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
const mediaPlayerController = mock<MediaPlayerController>();
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
);
|
||||
const action = new PauseAction(
|
||||
@@ -22,5 +22,5 @@ it('should handle pause action', async () => {
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(player.pause).toBeCalled();
|
||||
expect(mediaPlayerController.pause).toBeCalled();
|
||||
});
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { PlayAction } from '../../../../src/card-controller/actions/actions/play';
|
||||
import { AdvancedCameraCardMediaPlayer } from '../../../../src/types';
|
||||
import { MediaPlayerController } from '../../../../src/types';
|
||||
import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils';
|
||||
|
||||
it('should handle play action', async () => {
|
||||
const api = createCardAPI();
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
const mediaPlayerController = mock<MediaPlayerController>();
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
);
|
||||
const action = new PlayAction(
|
||||
@@ -22,5 +22,5 @@ it('should handle play action', async () => {
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(player.play).toBeCalled();
|
||||
expect(mediaPlayerController.play).toBeCalled();
|
||||
});
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { UnmuteAction } from '../../../../src/card-controller/actions/actions/unmute';
|
||||
import { AdvancedCameraCardMediaPlayer } from '../../../../src/types';
|
||||
import { MediaPlayerController } from '../../../../src/types';
|
||||
import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils';
|
||||
|
||||
it('should handle unmute action', async () => {
|
||||
const api = createCardAPI();
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
const mediaPlayerController = mock<MediaPlayerController>();
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
);
|
||||
const action = new UnmuteAction(
|
||||
@@ -22,5 +22,5 @@ it('should handle unmute action', async () => {
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(player.unmute).toBeCalled();
|
||||
expect(mediaPlayerController.unmute).toBeCalled();
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { DownloadManager } from '../../src/card-controller/download-manager';
|
||||
import { AdvancedCameraCardMediaPlayer } from '../../src/types';
|
||||
import { MediaPlayerController } from '../../src/types';
|
||||
import { downloadMedia, downloadURL } from '../../src/utils/download.js';
|
||||
import {
|
||||
createCardAPI,
|
||||
@@ -62,12 +62,12 @@ describe('DownloadManager.downloadScreenshot', () => {
|
||||
|
||||
it('with url', async () => {
|
||||
const api = createCardAPI();
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
player.getScreenshotURL.mockResolvedValue('http://screenshot');
|
||||
const mediaPlayerController = mock<MediaPlayerController>();
|
||||
mediaPlayerController.getScreenshotURL.mockResolvedValue('http://screenshot');
|
||||
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
);
|
||||
const manager = new DownloadManager(api);
|
||||
@@ -78,12 +78,12 @@ describe('DownloadManager.downloadScreenshot', () => {
|
||||
|
||||
it('without url', async () => {
|
||||
const api = createCardAPI();
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
player.getScreenshotURL.mockResolvedValue(null);
|
||||
const mediaPlayerController = mock<MediaPlayerController>();
|
||||
mediaPlayerController.getScreenshotURL.mockResolvedValue(null);
|
||||
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
);
|
||||
const manager = new DownloadManager(api);
|
||||
|
||||
@@ -2,10 +2,7 @@ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { WebkitFullScreenProvider } from '../../../../src/card-controller/fullscreen/webkit';
|
||||
import { ConditionStateManager } from '../../../../src/conditions/state-manager';
|
||||
import {
|
||||
AdvancedCameraCardMediaPlayer,
|
||||
WebkitHTMLVideoElement,
|
||||
} from '../../../../src/types';
|
||||
import { MediaPlayerController, WebkitHTMLVideoElement } from '../../../../src/types';
|
||||
import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils';
|
||||
|
||||
const createWebkitVideoElement = (): HTMLVideoElement &
|
||||
@@ -13,10 +10,10 @@ const createWebkitVideoElement = (): HTMLVideoElement &
|
||||
return document.createElement('video');
|
||||
};
|
||||
|
||||
const createPlayer = (element: HTMLElement): AdvancedCameraCardMediaPlayer => {
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
player.getFullscreenElement.mockReturnValue(element);
|
||||
return player;
|
||||
const createMediaPlayerController = (element: HTMLElement): MediaPlayerController => {
|
||||
const mediaPlayerController = mock<MediaPlayerController>();
|
||||
mediaPlayerController.getFullscreenElement.mockReturnValue(element);
|
||||
return mediaPlayerController;
|
||||
};
|
||||
|
||||
// @vitest-environment jsdom
|
||||
@@ -57,10 +54,10 @@ describe('WebkitFullScreenProvider', () => {
|
||||
const element = createWebkitVideoElement();
|
||||
element.webkitDisplayingFullscreen = fullscreen;
|
||||
|
||||
const player = createPlayer(element);
|
||||
const mediaPlayerController = createMediaPlayerController(element);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -76,10 +73,10 @@ describe('WebkitFullScreenProvider', () => {
|
||||
const element = createWebkitVideoElement();
|
||||
element.webkitSupportsFullscreen = supported;
|
||||
|
||||
const player = createPlayer(element);
|
||||
const mediaPlayerController = createMediaPlayerController(element);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -96,10 +93,10 @@ describe('WebkitFullScreenProvider', () => {
|
||||
element.webkitEnterFullscreen = vi.fn();
|
||||
element.webkitSupportsFullscreen = true;
|
||||
|
||||
const player = createPlayer(element);
|
||||
const mediaPlayerController = createMediaPlayerController(element);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -116,10 +113,10 @@ describe('WebkitFullScreenProvider', () => {
|
||||
element.webkitExitFullscreen = vi.fn();
|
||||
element.webkitSupportsFullscreen = true;
|
||||
|
||||
const player = createPlayer(element);
|
||||
const mediaPlayerController = createMediaPlayerController(element);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -137,10 +134,10 @@ describe('WebkitFullScreenProvider', () => {
|
||||
element.webkitExitFullscreen = vi.fn();
|
||||
element.webkitSupportsFullscreen = false;
|
||||
|
||||
const player = createPlayer(element);
|
||||
const mediaPlayerController = createMediaPlayerController(element);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -160,10 +157,10 @@ describe('WebkitFullScreenProvider', () => {
|
||||
element.webkitEnterFullscreen = vi.fn();
|
||||
element.webkitExitFullscreen = vi.fn();
|
||||
|
||||
const player = createPlayer(element);
|
||||
const mediaPlayerController = createMediaPlayerController(element);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -190,9 +187,11 @@ describe('WebkitFullScreenProvider', () => {
|
||||
provider.connect();
|
||||
|
||||
const element_1 = createWebkitVideoElement();
|
||||
const player_1 = mock<AdvancedCameraCardMediaPlayer>();
|
||||
player_1.getFullscreenElement.mockReturnValue(element_1);
|
||||
const mediaLoadedInfo_1 = createMediaLoadedInfo({ player: player_1 });
|
||||
const mediaPlayerController_1 = mock<MediaPlayerController>();
|
||||
mediaPlayerController_1.getFullscreenElement.mockReturnValue(element_1);
|
||||
const mediaLoadedInfo_1 = createMediaLoadedInfo({
|
||||
mediaPlayerController: mediaPlayerController_1,
|
||||
});
|
||||
|
||||
stateManager.setState({ mediaLoadedInfo: mediaLoadedInfo_1 });
|
||||
|
||||
@@ -201,9 +200,11 @@ describe('WebkitFullScreenProvider', () => {
|
||||
expect(handler).toBeCalledTimes(1);
|
||||
|
||||
const element_2 = createWebkitVideoElement();
|
||||
const player_2 = mock<AdvancedCameraCardMediaPlayer>();
|
||||
player_2.getFullscreenElement.mockReturnValue(element_2);
|
||||
const mediaLoadedInfo_2 = createMediaLoadedInfo({ player: player_2 });
|
||||
const mediaPlayerController_2 = mock<MediaPlayerController>();
|
||||
mediaPlayerController_2.getFullscreenElement.mockReturnValue(element_2);
|
||||
const mediaLoadedInfo_2 = createMediaLoadedInfo({
|
||||
mediaPlayerController: mediaPlayerController_2,
|
||||
});
|
||||
|
||||
stateManager.setState({ mediaLoadedInfo: mediaLoadedInfo_2 });
|
||||
|
||||
@@ -243,14 +244,14 @@ describe('WebkitFullScreenProvider', () => {
|
||||
const element = createWebkitVideoElement();
|
||||
element.play = vi.fn();
|
||||
|
||||
const player = createPlayer(element);
|
||||
const mediaPlayerController = createMediaPlayerController(element);
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
);
|
||||
|
||||
const mediaLoadedInfo = createMediaLoadedInfo({ player });
|
||||
const mediaLoadedInfo = createMediaLoadedInfo({ mediaPlayerController });
|
||||
|
||||
stateManager.setState({ mediaLoadedInfo });
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { MicrophoneState } from '../../src/card-controller/types';
|
||||
import {
|
||||
MediaActionsController,
|
||||
MediaActionsControllerOptions,
|
||||
} from '../../src/components-lib/media-actions-controller';
|
||||
import { AdvancedCameraCardMediaPlayer } from '../../src/types';
|
||||
import { MediaPlayerController, MediaPlayerElement } from '../../src/types';
|
||||
import {
|
||||
IntersectionObserverMock,
|
||||
MutationObserverMock,
|
||||
@@ -18,30 +19,22 @@ import { callVisibilityHandler, createTestSlideNodes } from '../utils/embla/test
|
||||
const getPlayer = (
|
||||
element: HTMLElement,
|
||||
selector: string,
|
||||
): (HTMLElement & AdvancedCameraCardMediaPlayer) | null => {
|
||||
): MediaPlayerElement | null => {
|
||||
return element.querySelector(selector);
|
||||
};
|
||||
|
||||
const createPlayer = (): HTMLElement & AdvancedCameraCardMediaPlayer => {
|
||||
const createPlayerElement = (controller?: MediaPlayerController): MediaPlayerElement => {
|
||||
const player = document.createElement('video');
|
||||
|
||||
player['play'] = vi.fn();
|
||||
player['pause'] = vi.fn();
|
||||
player['mute'] = vi.fn();
|
||||
player['unmute'] = vi.fn();
|
||||
player['isMuted'] = vi.fn().mockReturnValue(true);
|
||||
player['seek'] = vi.fn();
|
||||
player['getScreenshotURL'] = vi.fn();
|
||||
player['setControls'] = vi.fn();
|
||||
player['isPaused'] = vi.fn();
|
||||
|
||||
return player as unknown as HTMLElement & AdvancedCameraCardMediaPlayer;
|
||||
player['getMediaPlayerController'] = vi
|
||||
.fn()
|
||||
.mockResolvedValue(controller ?? mock<MediaPlayerController>());
|
||||
return player as unknown as MediaPlayerElement;
|
||||
};
|
||||
|
||||
const createPlayerSlideNodes = (n = 10): HTMLElement[] => {
|
||||
const divs = createTestSlideNodes({ n: n });
|
||||
for (const div of divs) {
|
||||
div.appendChild(createPlayer());
|
||||
div.appendChild(createPlayerElement());
|
||||
}
|
||||
return divs;
|
||||
};
|
||||
@@ -61,11 +54,11 @@ describe('MediaActionsController', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('should initialize', () => {
|
||||
describe('should set root', () => {
|
||||
it('should have root', async () => {
|
||||
const controller = new MediaActionsController();
|
||||
|
||||
controller.initialize(createParent());
|
||||
controller.setRoot(createParent());
|
||||
|
||||
expect(controller.hasRoot()).toBeTruthy();
|
||||
});
|
||||
@@ -76,13 +69,23 @@ describe('MediaActionsController', () => {
|
||||
const children = createPlayerSlideNodes();
|
||||
const parent = createParent({ children: children });
|
||||
|
||||
controller.initialize(parent);
|
||||
controller.setRoot(parent);
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.play).not.toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.play,
|
||||
).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should re-initialize after mutation', async () => {
|
||||
it('should do nothing on resetting same root', () => {
|
||||
const controller = new MediaActionsController();
|
||||
const parent = createParent({ children: createPlayerSlideNodes() });
|
||||
|
||||
expect(controller.setRoot(parent)).toBeTruthy();
|
||||
expect(controller.setRoot(parent)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should re-setRoot after mutation', async () => {
|
||||
const controller = new MediaActionsController();
|
||||
controller.setOptions({
|
||||
playerSelector: 'video',
|
||||
@@ -90,9 +93,11 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const parent = createParent({ children: createPlayerSlideNodes(1) });
|
||||
controller.initialize(parent);
|
||||
controller.setRoot(parent);
|
||||
|
||||
const newPlayer = createPlayer();
|
||||
const mediaPlayerController = mock<MediaPlayerController>();
|
||||
|
||||
const newPlayer = createPlayerElement(mediaPlayerController);
|
||||
const newChild = document.createElement('div');
|
||||
newChild.appendChild(newPlayer);
|
||||
parent.append(newChild);
|
||||
@@ -101,7 +106,7 @@ describe('MediaActionsController', () => {
|
||||
|
||||
await controller.setTarget(1, true);
|
||||
|
||||
expect(newPlayer.play).toBeCalled();
|
||||
expect(mediaPlayerController.play).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,13 +120,15 @@ describe('MediaActionsController', () => {
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
const parent = createParent({ children: children });
|
||||
controller.initialize(parent);
|
||||
controller.setRoot(parent);
|
||||
|
||||
controller.destroy();
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.play).not.toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.play,
|
||||
).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -146,11 +153,13 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.[func]).toBeCalledTimes(called ? 1 : 0);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.[func],
|
||||
).toBeCalledTimes(called ? 1 : 0);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -162,13 +171,19 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(1);
|
||||
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.play,
|
||||
).toBeCalledTimes(1);
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(1);
|
||||
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.play,
|
||||
).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should unselect before selecting a new target', async () => {
|
||||
@@ -180,13 +195,17 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
await controller.setTarget(1, true);
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.pause).toBeCalled();
|
||||
expect(getPlayer(children[0], 'video')?.mute).toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.pause,
|
||||
).toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.mute,
|
||||
).toBeCalled();
|
||||
});
|
||||
|
||||
it('should select after target was previously visible', async () => {
|
||||
@@ -198,17 +217,25 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
|
||||
await controller.setTarget(0, false);
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.play).not.toBeCalled();
|
||||
expect(getPlayer(children[0], 'video')?.unmute).not.toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.play,
|
||||
).not.toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.unmute,
|
||||
).not.toBeCalled();
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.play).toBeCalled();
|
||||
expect(getPlayer(children[0], 'video')?.unmute).toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.play,
|
||||
).toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.unmute,
|
||||
).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -221,12 +248,16 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(1);
|
||||
expect(getPlayer(children[0], 'video')?.unmute).toBeCalledTimes(1);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.play,
|
||||
).toBeCalledTimes(1);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.unmute,
|
||||
).toBeCalledTimes(1);
|
||||
|
||||
controller.unsetTarget();
|
||||
|
||||
@@ -236,8 +267,12 @@ describe('MediaActionsController', () => {
|
||||
await flushPromises();
|
||||
|
||||
// Play/Mute will not have been called again.
|
||||
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(1);
|
||||
expect(getPlayer(children[0], 'video')?.unmute).toBeCalledTimes(1);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.play,
|
||||
).toBeCalledTimes(1);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.unmute,
|
||||
).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
describe('should respond to media loaded', () => {
|
||||
@@ -249,10 +284,13 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(1);
|
||||
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.play,
|
||||
).toBeCalledTimes(1);
|
||||
|
||||
getPlayer(children[0], 'video')?.dispatchEvent(
|
||||
new Event('advanced-camera-card:media:loaded'),
|
||||
@@ -260,7 +298,9 @@ describe('MediaActionsController', () => {
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(2);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.play,
|
||||
).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should unmute after media load', async () => {
|
||||
@@ -271,10 +311,12 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
expect(getPlayer(children[0], 'video')?.unmute).toBeCalledTimes(1);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.unmute,
|
||||
).toBeCalledTimes(1);
|
||||
|
||||
getPlayer(children[0], 'video')?.dispatchEvent(
|
||||
new Event('advanced-camera-card:media:loaded'),
|
||||
@@ -282,7 +324,9 @@ describe('MediaActionsController', () => {
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.unmute).toBeCalledTimes(2);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.unmute,
|
||||
).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should take no action on unrelated media load', async () => {
|
||||
@@ -294,7 +338,7 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
@@ -304,8 +348,12 @@ describe('MediaActionsController', () => {
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(getPlayer(children[9], 'video')?.play).not.toBeCalled();
|
||||
expect(getPlayer(children[9], 'video')?.unmute).not.toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[9], 'video')?.getMediaPlayerController())?.play,
|
||||
).not.toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[9], 'video')?.getMediaPlayerController())?.unmute,
|
||||
).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should play and unmute on unselected but targeted media load', async () => {
|
||||
@@ -317,12 +365,16 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
|
||||
await controller.setTarget(0, false);
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(1);
|
||||
expect(getPlayer(children[0], 'video')?.unmute).toBeCalledTimes(1);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.play,
|
||||
).toBeCalledTimes(1);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.unmute,
|
||||
).toBeCalledTimes(1);
|
||||
|
||||
getPlayer(children[0], 'video')?.dispatchEvent(
|
||||
new Event('advanced-camera-card:media:loaded'),
|
||||
@@ -330,8 +382,12 @@ describe('MediaActionsController', () => {
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(2);
|
||||
expect(getPlayer(children[0], 'video')?.unmute).toBeCalledTimes(2);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.play,
|
||||
).toBeCalledTimes(2);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.unmute,
|
||||
).toBeCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -356,12 +412,14 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
await controller.setTarget(0, false);
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.[func]).toBeCalledTimes(called ? 1 : 0);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.[func],
|
||||
).toBeCalledTimes(called ? 1 : 0);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -389,11 +447,13 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
// Not configured to take action on selection.
|
||||
expect(getPlayer(children[0], 'video')?.[func]).not.toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.[func],
|
||||
).not.toBeCalled();
|
||||
|
||||
Object.defineProperty(document, 'visibilityState', {
|
||||
value: 'visible',
|
||||
@@ -402,7 +462,9 @@ describe('MediaActionsController', () => {
|
||||
await callVisibilityHandler();
|
||||
|
||||
// Not configured to take action on selection.
|
||||
expect(getPlayer(children[0], 'video')?.[func]).toBeCalledTimes(called ? 1 : 0);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.[func],
|
||||
).toBeCalledTimes(called ? 1 : 0);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -432,11 +494,13 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
// Not configured to take action on selection.
|
||||
expect(getPlayer(children[0], 'video')?.[func]).not.toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.[func],
|
||||
).not.toBeCalled();
|
||||
|
||||
Object.defineProperty(document, 'visibilityState', {
|
||||
value: 'hidden',
|
||||
@@ -445,7 +509,9 @@ describe('MediaActionsController', () => {
|
||||
await callVisibilityHandler();
|
||||
|
||||
// Not configured to take action on selection.
|
||||
expect(getPlayer(children[0], 'video')?.[func]).toBeCalledTimes(called ? 1 : 0);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.[func],
|
||||
).toBeCalledTimes(called ? 1 : 0);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -471,11 +537,13 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
// Not configured to take action on selection.
|
||||
expect(getPlayer(children[0], 'video')?.[func]).not.toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.[func],
|
||||
).not.toBeCalled();
|
||||
|
||||
// There's always a first call to an intersection observer handler. In
|
||||
// this case the MediaActionsController ignores it.
|
||||
@@ -484,7 +552,9 @@ describe('MediaActionsController', () => {
|
||||
await callIntersectionHandler(true);
|
||||
|
||||
// Not configured to take action on selection.
|
||||
expect(getPlayer(children[0], 'video')?.[func]).toBeCalledTimes(called ? 1 : 0);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.[func],
|
||||
).toBeCalledTimes(called ? 1 : 0);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -510,11 +580,13 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
// Not configured to take action on selection.
|
||||
expect(getPlayer(children[0], 'video')?.[func]).not.toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.[func],
|
||||
).not.toBeCalled();
|
||||
|
||||
// There's always a first call to an intersection observer handler. In
|
||||
// this case the MediaActionsController ignores it.
|
||||
@@ -523,7 +595,9 @@ describe('MediaActionsController', () => {
|
||||
await callIntersectionHandler(true);
|
||||
|
||||
// Not configured to take action on selection.
|
||||
expect(getPlayer(children[0], 'video')?.[func]).toBeCalledTimes(called ? 1 : 0);
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.[func],
|
||||
).toBeCalledTimes(called ? 1 : 0);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -558,7 +632,7 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
@@ -568,7 +642,9 @@ describe('MediaActionsController', () => {
|
||||
microphoneState: createMicrophoneState({ muted: false }),
|
||||
});
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.unmute).toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.unmute,
|
||||
).toBeCalled();
|
||||
});
|
||||
|
||||
it('should mute after delay after microphone muted', async () => {
|
||||
@@ -581,7 +657,7 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
@@ -593,7 +669,9 @@ describe('MediaActionsController', () => {
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.mute).toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.mute,
|
||||
).toBeCalled();
|
||||
});
|
||||
|
||||
it('should not mute after delay after microphone muted', async () => {
|
||||
@@ -606,7 +684,7 @@ describe('MediaActionsController', () => {
|
||||
});
|
||||
|
||||
const children = createPlayerSlideNodes();
|
||||
controller.initialize(createParent({ children: children }));
|
||||
controller.setRoot(createParent({ children: children }));
|
||||
|
||||
await controller.setTarget(0, true);
|
||||
|
||||
@@ -618,7 +696,9 @@ describe('MediaActionsController', () => {
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(getPlayer(children[0], 'video')?.mute).not.toBeCalled();
|
||||
expect(
|
||||
(await getPlayer(children[0], 'video')?.getMediaPlayerController())?.mute,
|
||||
).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { ImageMediaPlayerController } from '../../../src/components-lib/media-player/image';
|
||||
import { createLitElement } from '../../test-utils';
|
||||
import { screenshotImage } from '../../../src/utils/screenshot';
|
||||
|
||||
vi.mock('../../../src/utils/screenshot.js');
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('ImageMediaPlayerController', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should ignore play', async () => {
|
||||
const image = mock<HTMLImageElement>();
|
||||
const controller = new ImageMediaPlayerController(createLitElement(), () => image);
|
||||
|
||||
await controller.play();
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
|
||||
it('should ignore pause', async () => {
|
||||
const image = mock<HTMLImageElement>();
|
||||
const controller = new ImageMediaPlayerController(createLitElement(), () => image);
|
||||
|
||||
await controller.pause();
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
|
||||
it('should ignore mute', async () => {
|
||||
const image = mock<HTMLImageElement>();
|
||||
const controller = new ImageMediaPlayerController(createLitElement(), () => image);
|
||||
|
||||
await controller.mute();
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
|
||||
it('should ignore unmute', async () => {
|
||||
const image = mock<HTMLImageElement>();
|
||||
const controller = new ImageMediaPlayerController(createLitElement(), () => image);
|
||||
|
||||
await controller.unmute();
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
|
||||
it('should always report muted', () => {
|
||||
const image = mock<HTMLImageElement>();
|
||||
const controller = new ImageMediaPlayerController(createLitElement(), () => image);
|
||||
|
||||
expect(controller.isMuted()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should ignore seek', async () => {
|
||||
const image = mock<HTMLImageElement>();
|
||||
const controller = new ImageMediaPlayerController(createLitElement(), () => image);
|
||||
|
||||
await controller.seek(10);
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
|
||||
it('should ignore set controls', async () => {
|
||||
const image = mock<HTMLImageElement>();
|
||||
const controller = new ImageMediaPlayerController(createLitElement(), () => image);
|
||||
|
||||
await controller.setControls(true);
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
|
||||
it('should always report unpaused', () => {
|
||||
const image = mock<HTMLImageElement>();
|
||||
const controller = new ImageMediaPlayerController(createLitElement(), () => image);
|
||||
|
||||
expect(controller.isPaused()).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('should get screenshot URL', async () => {
|
||||
it('should return screenshot URL with image', async () => {
|
||||
const url = 'data:image/png;base64,';
|
||||
vi.mocked(screenshotImage).mockReturnValue(url);
|
||||
|
||||
const image = mock<HTMLImageElement>();
|
||||
|
||||
const controller = new ImageMediaPlayerController(createLitElement(), () => image);
|
||||
|
||||
expect(await controller.getScreenshotURL()).toBe(url);
|
||||
});
|
||||
|
||||
it('should return null without image', async () => {
|
||||
const controller = new ImageMediaPlayerController(createLitElement(), () => null);
|
||||
|
||||
expect(await controller.getScreenshotURL()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should get fullscreen element', async () => {
|
||||
it('should return fullscreen element with image', async () => {
|
||||
const image = mock<HTMLImageElement>();
|
||||
|
||||
const controller = new ImageMediaPlayerController(createLitElement(), () => image);
|
||||
|
||||
expect(await controller.getFullscreenElement()).toBe(image);
|
||||
});
|
||||
|
||||
it('should return null without image', async () => {
|
||||
const controller = new ImageMediaPlayerController(createLitElement(), () => null);
|
||||
|
||||
expect(controller.getFullscreenElement()).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,252 @@
|
||||
import JSMpeg from '@cycjimmy/jsmpeg-player';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { JSMPEGMediaPlayerController } from '../../../src/components-lib/media-player/jsmpeg';
|
||||
import { createLitElement } from '../../test-utils';
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('JSMPEGMediaPlayerController', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should play', async () => {
|
||||
const videoElement = mock<JSMpeg.VideoElement>();
|
||||
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => videoElement,
|
||||
() => mock<HTMLCanvasElement>(),
|
||||
);
|
||||
|
||||
await controller.play();
|
||||
|
||||
expect(videoElement.play).toBeCalled();
|
||||
});
|
||||
|
||||
it('should pause', async () => {
|
||||
const videoElement = mock<JSMpeg.VideoElement>();
|
||||
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => videoElement,
|
||||
() => mock<HTMLCanvasElement>(),
|
||||
);
|
||||
|
||||
await controller.pause();
|
||||
|
||||
expect(videoElement.stop).toBeCalled();
|
||||
});
|
||||
|
||||
describe('should mute', async () => {
|
||||
it('should mute with player', async () => {
|
||||
const videoElement = mock<JSMpeg.VideoElement>();
|
||||
videoElement.player = mock<JSMpeg.Player>();
|
||||
videoElement.player.volume = 1;
|
||||
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => videoElement,
|
||||
() => mock<HTMLCanvasElement>(),
|
||||
);
|
||||
|
||||
await controller.mute();
|
||||
|
||||
expect(videoElement.player.volume).toBe(0);
|
||||
});
|
||||
|
||||
it('should ignore calls without player', async () => {
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => null,
|
||||
() => mock<HTMLCanvasElement>(),
|
||||
);
|
||||
|
||||
await controller.mute();
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
});
|
||||
|
||||
describe('should unmute', async () => {
|
||||
it('should mute with player', async () => {
|
||||
const videoElement = mock<JSMpeg.VideoElement>();
|
||||
videoElement.player = mock<JSMpeg.Player>();
|
||||
videoElement.player.volume = 0;
|
||||
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => videoElement,
|
||||
() => mock<HTMLCanvasElement>(),
|
||||
);
|
||||
|
||||
await controller.unmute();
|
||||
|
||||
expect(videoElement.player.volume).toBe(1);
|
||||
});
|
||||
|
||||
it('should ignore calls without player', async () => {
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => null,
|
||||
() => mock<HTMLCanvasElement>(),
|
||||
);
|
||||
|
||||
await controller.unmute();
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
});
|
||||
|
||||
describe('should return muted state', () => {
|
||||
it('should return true when muted', () => {
|
||||
const videoElement = mock<JSMpeg.VideoElement>();
|
||||
videoElement.player = mock<JSMpeg.Player>();
|
||||
videoElement.player.volume = 0;
|
||||
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => videoElement,
|
||||
() => mock<HTMLCanvasElement>(),
|
||||
);
|
||||
|
||||
expect(controller.isMuted()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false when not muted', () => {
|
||||
const videoElement = mock<JSMpeg.VideoElement>();
|
||||
videoElement.player = mock<JSMpeg.Player>();
|
||||
videoElement.player.volume = 1;
|
||||
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => videoElement,
|
||||
() => mock<HTMLCanvasElement>(),
|
||||
);
|
||||
|
||||
expect(controller.isMuted()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return true when no player', () => {
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => null,
|
||||
() => mock<HTMLCanvasElement>(),
|
||||
);
|
||||
|
||||
expect(controller.isMuted()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should ignore seek', async () => {
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<JSMpeg.VideoElement>(),
|
||||
() => mock<HTMLCanvasElement>(),
|
||||
);
|
||||
|
||||
await controller.seek(10);
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
|
||||
it('should ignore set controls', async () => {
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<JSMpeg.VideoElement>(),
|
||||
() => mock<HTMLCanvasElement>(),
|
||||
);
|
||||
await controller.setControls(true);
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
|
||||
describe('should return paused state', () => {
|
||||
it('should return true when paused', async () => {
|
||||
const videoElement = mock<JSMpeg.VideoElement>();
|
||||
videoElement.player = mock<JSMpeg.Player>();
|
||||
videoElement.player.paused = true;
|
||||
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => videoElement,
|
||||
() => mock<HTMLCanvasElement>(),
|
||||
);
|
||||
|
||||
expect(controller.isPaused()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false when not paused', async () => {
|
||||
const videoElement = mock<JSMpeg.VideoElement>();
|
||||
videoElement.player = mock<JSMpeg.Player>();
|
||||
videoElement.player.paused = false;
|
||||
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => videoElement,
|
||||
() => mock<HTMLCanvasElement>(),
|
||||
);
|
||||
|
||||
expect(controller.isPaused()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return true when no video', () => {
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => null,
|
||||
() => mock<HTMLCanvasElement>(),
|
||||
);
|
||||
|
||||
expect(controller.isPaused()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should get screenshot URL', async () => {
|
||||
it('should return screenshot URL with canvas', async () => {
|
||||
const url = 'data:image/png;base64,';
|
||||
const canvas = mock<HTMLCanvasElement>();
|
||||
canvas.toDataURL.mockReturnValue(url);
|
||||
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<JSMpeg.VideoElement>(),
|
||||
() => canvas,
|
||||
);
|
||||
|
||||
expect(await controller.getScreenshotURL()).toBe(url);
|
||||
});
|
||||
|
||||
it('should return null without canvas', async () => {
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<JSMpeg.VideoElement>(),
|
||||
() => null,
|
||||
);
|
||||
|
||||
expect(await controller.getScreenshotURL()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should get fullscreen element', async () => {
|
||||
it('should return fullscreen element with canvas', async () => {
|
||||
const canvas = mock<HTMLCanvasElement>();
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<JSMpeg.VideoElement>(),
|
||||
() => canvas,
|
||||
);
|
||||
|
||||
expect(await controller.getFullscreenElement()).toBe(canvas);
|
||||
});
|
||||
|
||||
it('should return null without cancas', async () => {
|
||||
const controller = new JSMPEGMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<JSMpeg.VideoElement>(),
|
||||
() => null,
|
||||
);
|
||||
|
||||
expect(controller.getFullscreenElement()).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,192 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { CachedValueController } from '../../../src/components-lib/cached-value-controller';
|
||||
import { ImageMediaPlayerController } from '../../../src/components-lib/media-player/image';
|
||||
import { UpdatingImageMediaPlayerController } from '../../../src/components-lib/media-player/updating-image';
|
||||
import { createLitElement } from '../../test-utils';
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('UpdatingImageMediaPlayerController', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should play', async () => {
|
||||
const cachedValueController = mock<CachedValueController<string>>();
|
||||
const controller = new UpdatingImageMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<HTMLImageElement>(),
|
||||
() => cachedValueController,
|
||||
);
|
||||
|
||||
await controller.play();
|
||||
|
||||
expect(cachedValueController.startTimer).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should pause', async () => {
|
||||
const cachedValueController = mock<CachedValueController<string>>();
|
||||
const controller = new UpdatingImageMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<HTMLImageElement>(),
|
||||
() => cachedValueController,
|
||||
);
|
||||
|
||||
await controller.pause();
|
||||
|
||||
expect(cachedValueController.stopTimer).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should ignore mute', async () => {
|
||||
const controller = new UpdatingImageMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<HTMLImageElement>(),
|
||||
() => mock<CachedValueController<string>>(),
|
||||
);
|
||||
|
||||
await controller.mute();
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
|
||||
it('should ignore unmute', async () => {
|
||||
const controller = new UpdatingImageMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<HTMLImageElement>(),
|
||||
() => mock<CachedValueController<string>>(),
|
||||
);
|
||||
|
||||
await controller.unmute();
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
|
||||
it('should always report muted', () => {
|
||||
const controller = new UpdatingImageMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<HTMLImageElement>(),
|
||||
() => mock<CachedValueController<string>>(),
|
||||
);
|
||||
|
||||
expect(controller.isMuted()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should ignore seek', async () => {
|
||||
const controller = new UpdatingImageMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<HTMLImageElement>(),
|
||||
() => mock<CachedValueController<string>>(),
|
||||
);
|
||||
|
||||
await controller.seek(10);
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
|
||||
it('should ignore set controls', async () => {
|
||||
const controller = new UpdatingImageMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<HTMLImageElement>(),
|
||||
() => mock<CachedValueController<string>>(),
|
||||
);
|
||||
|
||||
await controller.setControls(true);
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
|
||||
it('should always report unpaused', () => {
|
||||
const image = mock<HTMLImageElement>();
|
||||
const controller = new ImageMediaPlayerController(createLitElement(), () => image);
|
||||
|
||||
expect(controller.isPaused()).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('should get paused state', () => {
|
||||
it('should return true when the cached value controller does not have a timer', () => {
|
||||
const cachedValueController = mock<CachedValueController<string>>();
|
||||
cachedValueController.hasTimer.mockReturnValue(false);
|
||||
|
||||
const controller = new UpdatingImageMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<HTMLImageElement>(),
|
||||
() => cachedValueController,
|
||||
);
|
||||
|
||||
expect(controller.isPaused()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false when the cached value controller has a timer', () => {
|
||||
const cachedValueController = mock<CachedValueController<string>>();
|
||||
cachedValueController.hasTimer.mockReturnValue(true);
|
||||
|
||||
const controller = new UpdatingImageMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<HTMLImageElement>(),
|
||||
() => cachedValueController,
|
||||
);
|
||||
|
||||
expect(controller.isPaused()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return true without cached value controller', () => {
|
||||
const controller = new UpdatingImageMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<HTMLImageElement>(),
|
||||
() => null,
|
||||
);
|
||||
|
||||
expect(controller.isPaused()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should get screenshot URL', () => {
|
||||
it('should return screenshot URL with cached value controller', async () => {
|
||||
const url = 'data:image/png;base64,';
|
||||
const cachedValueController = mock<CachedValueController<string>>();
|
||||
Object.defineProperty(cachedValueController, 'value', { value: url });
|
||||
|
||||
const controller = new UpdatingImageMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<HTMLImageElement>(),
|
||||
() => cachedValueController,
|
||||
);
|
||||
|
||||
expect(await controller.getScreenshotURL()).toBe(url);
|
||||
});
|
||||
|
||||
it('should return null without cached value controller', async () => {
|
||||
const controller = new UpdatingImageMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => mock<HTMLImageElement>(),
|
||||
() => null,
|
||||
);
|
||||
|
||||
expect(await controller.getScreenshotURL()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should get fullscreen element', async () => {
|
||||
it('should return fullscreen element with image', async () => {
|
||||
const image = mock<HTMLImageElement>();
|
||||
|
||||
const controller = new UpdatingImageMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => image,
|
||||
() => mock<CachedValueController<string>>(),
|
||||
);
|
||||
|
||||
expect(await controller.getFullscreenElement()).toBe(image);
|
||||
});
|
||||
|
||||
it('should return null without image', async () => {
|
||||
const controller = new UpdatingImageMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => null,
|
||||
() => mock<CachedValueController<string>>(),
|
||||
);
|
||||
|
||||
expect(controller.getFullscreenElement()).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,273 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { VideoMediaPlayerController } from '../../../src/components-lib/media-player/video';
|
||||
import {
|
||||
hideMediaControlsTemporarily,
|
||||
setControlsOnVideo,
|
||||
} from '../../../src/utils/controls';
|
||||
import { screenshotVideo } from '../../../src/utils/screenshot';
|
||||
import { createLitElement } from '../../test-utils';
|
||||
|
||||
vi.mock('../../../src/utils/controls.js');
|
||||
vi.mock('../../../src/utils/screenshot.js');
|
||||
|
||||
class NotAllowedError extends Error {
|
||||
name = 'NotAllowedError';
|
||||
}
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('VideoMediaPlayerController', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('should play', () => {
|
||||
it('should play when no error', async () => {
|
||||
const video = mock<HTMLVideoElement>();
|
||||
video.play.mockResolvedValue();
|
||||
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => video);
|
||||
|
||||
await controller.play();
|
||||
|
||||
expect(video.play).toBeCalled();
|
||||
});
|
||||
|
||||
it('should mute if not allowed to play and unmuted', async () => {
|
||||
const video = mock<HTMLVideoElement>();
|
||||
video.play.mockRejectedValueOnce(new NotAllowedError()).mockResolvedValueOnce();
|
||||
video.muted = false;
|
||||
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => video);
|
||||
|
||||
await controller.play();
|
||||
|
||||
expect(video.play).toBeCalledTimes(2);
|
||||
expect(video.muted).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not mute if not allowed to play and already unmuted', async () => {
|
||||
const video = mock<HTMLVideoElement>();
|
||||
video.play.mockRejectedValueOnce(new NotAllowedError());
|
||||
video.muted = true;
|
||||
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => video);
|
||||
|
||||
await controller.play();
|
||||
|
||||
expect(video.play).toBeCalledTimes(1);
|
||||
expect(video.muted).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should ignore exception if subsequent play call throws', async () => {
|
||||
const video = mock<HTMLVideoElement>();
|
||||
video.play.mockRejectedValue(new NotAllowedError());
|
||||
video.muted = false;
|
||||
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => video);
|
||||
|
||||
await controller.play();
|
||||
|
||||
expect(video.play).toBeCalledTimes(2);
|
||||
expect(video.muted).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should ignore calls without a video', async () => {
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => null);
|
||||
|
||||
await controller.play();
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
});
|
||||
|
||||
it('should pause', async () => {
|
||||
const video = mock<HTMLVideoElement>();
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => video);
|
||||
|
||||
await controller.pause();
|
||||
|
||||
expect(video.pause).toBeCalled();
|
||||
});
|
||||
|
||||
describe('should mute', async () => {
|
||||
it('should mute with video', async () => {
|
||||
const video = mock<HTMLVideoElement>();
|
||||
video.muted = false;
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => video);
|
||||
|
||||
await controller.mute();
|
||||
|
||||
expect(video.muted).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should ignore calls without a video', async () => {
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => null);
|
||||
|
||||
await controller.mute();
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
});
|
||||
|
||||
describe('should unmute', async () => {
|
||||
it('should unmute with video', async () => {
|
||||
const video = mock<HTMLVideoElement>();
|
||||
video.muted = true;
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => video);
|
||||
|
||||
await controller.unmute();
|
||||
|
||||
expect(video.muted).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should ignore calls without a video', async () => {
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => null);
|
||||
|
||||
await controller.unmute();
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
});
|
||||
|
||||
describe('should return muted state', () => {
|
||||
it('should return true when muted', () => {
|
||||
const video = mock<HTMLVideoElement>();
|
||||
video.muted = true;
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => video);
|
||||
|
||||
expect(controller.isMuted()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false when not muted', () => {
|
||||
const video = mock<HTMLVideoElement>();
|
||||
video.muted = false;
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => video);
|
||||
|
||||
expect(controller.isMuted()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return true when no video', () => {
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => null);
|
||||
|
||||
expect(controller.isMuted()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should seek', () => {
|
||||
it('should seek', async () => {
|
||||
const video = mock<HTMLVideoElement>();
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => video);
|
||||
|
||||
await controller.seek(10);
|
||||
|
||||
expect(hideMediaControlsTemporarily).toBeCalled();
|
||||
expect(video.currentTime).toBe(10);
|
||||
});
|
||||
|
||||
it('should ignore calls without a video', async () => {
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => null);
|
||||
|
||||
await controller.seek(10);
|
||||
|
||||
// Currently no observable side effects.
|
||||
});
|
||||
});
|
||||
|
||||
describe('should set controls', () => {
|
||||
it('should set controls', async () => {
|
||||
const video = mock<HTMLVideoElement>();
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => video);
|
||||
|
||||
await controller.setControls(true);
|
||||
|
||||
expect(setControlsOnVideo).toBeCalledWith(video, true);
|
||||
});
|
||||
|
||||
it('should set controls to default', async () => {
|
||||
const video = mock<HTMLVideoElement>();
|
||||
const controller = new VideoMediaPlayerController(
|
||||
createLitElement(),
|
||||
() => video,
|
||||
() => true,
|
||||
);
|
||||
|
||||
await controller.setControls();
|
||||
|
||||
expect(setControlsOnVideo).toBeCalledWith(video, true);
|
||||
});
|
||||
|
||||
it('should ignore calls without a default or value', async () => {
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => null);
|
||||
|
||||
await controller.setControls(true);
|
||||
|
||||
expect(setControlsOnVideo).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should return paused state', () => {
|
||||
it('should return true when paused', async () => {
|
||||
const video = mock<HTMLVideoElement>();
|
||||
Object.defineProperty(video, 'paused', { value: true });
|
||||
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => video);
|
||||
|
||||
await controller.pause();
|
||||
|
||||
expect(controller.isPaused()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false when not paused', async () => {
|
||||
const video = mock<HTMLVideoElement>();
|
||||
Object.defineProperty(video, 'paused', { value: false });
|
||||
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => video);
|
||||
|
||||
await controller.pause();
|
||||
|
||||
expect(controller.isPaused()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return true when no video', () => {
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => null);
|
||||
|
||||
expect(controller.isPaused()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should get screenshot URL', async () => {
|
||||
it('should return screenshot URL with video', async () => {
|
||||
const url = 'data:image/png;base64,';
|
||||
vi.mocked(screenshotVideo).mockReturnValue(url);
|
||||
|
||||
const video = mock<HTMLVideoElement>();
|
||||
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => video);
|
||||
|
||||
expect(await controller.getScreenshotURL()).toBe(url);
|
||||
});
|
||||
|
||||
it('should return null without video', async () => {
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => null);
|
||||
|
||||
expect(await controller.getScreenshotURL()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should get fullscreen element', async () => {
|
||||
it('should return fullscreen element with video', async () => {
|
||||
const video = mock<HTMLVideoElement>();
|
||||
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => video);
|
||||
|
||||
expect(await controller.getFullscreenElement()).toBe(video);
|
||||
});
|
||||
|
||||
it('should return null without video', async () => {
|
||||
const controller = new VideoMediaPlayerController(createLitElement(), () => null);
|
||||
|
||||
expect(controller.getFullscreenElement()).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
MenuItem,
|
||||
ViewDisplayMode,
|
||||
} from '../../src/config/types';
|
||||
import { AdvancedCameraCardMediaPlayer } from '../../src/types';
|
||||
import { MediaPlayerController } from '../../src/types';
|
||||
import { createGeneralAction, createViewAction } from '../../src/utils/action';
|
||||
import { ViewMedia } from '../../src/view/media';
|
||||
import { MediaQueriesResults } from '../../src/view/media-queries-results';
|
||||
@@ -1349,13 +1349,13 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
|
||||
it('should have pause button', () => {
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
const mediaPlayerController = mock<MediaPlayerController>();
|
||||
const buttons = calculateButtons(controller, {
|
||||
currentMediaLoadedInfo: createMediaLoadedInfo({
|
||||
capabilities: {
|
||||
supportsPause: true,
|
||||
},
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1370,14 +1370,14 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
|
||||
it('should have play button', () => {
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
player.isPaused.mockReturnValue(true);
|
||||
const mediaPlayerController = mock<MediaPlayerController>();
|
||||
mediaPlayerController.isPaused.mockReturnValue(true);
|
||||
const buttons = calculateButtons(controller, {
|
||||
currentMediaLoadedInfo: createMediaLoadedInfo({
|
||||
capabilities: {
|
||||
supportsPause: true,
|
||||
},
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1392,13 +1392,13 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
|
||||
it('should have mute button', () => {
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
const mediaPlayerController = mock<MediaPlayerController>();
|
||||
const buttons = calculateButtons(controller, {
|
||||
currentMediaLoadedInfo: createMediaLoadedInfo({
|
||||
capabilities: {
|
||||
hasAudio: true,
|
||||
},
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1413,14 +1413,14 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
|
||||
it('should have unmute button', () => {
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
player.isMuted.mockReturnValue(true);
|
||||
const mediaPlayerController = mock<MediaPlayerController>();
|
||||
mediaPlayerController.isMuted.mockReturnValue(true);
|
||||
const buttons = calculateButtons(controller, {
|
||||
currentMediaLoadedInfo: createMediaLoadedInfo({
|
||||
capabilities: {
|
||||
hasAudio: true,
|
||||
},
|
||||
player: player,
|
||||
mediaPlayerController,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1437,7 +1437,7 @@ describe('MenuButtonController', () => {
|
||||
it('should have screenshot button', () => {
|
||||
const buttons = calculateButtons(controller, {
|
||||
currentMediaLoadedInfo: createMediaLoadedInfo({
|
||||
player: mock<AdvancedCameraCardMediaPlayer>(),
|
||||
mediaPlayerController: mock<MediaPlayerController>(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@@ -473,6 +473,15 @@ export const createLitElement = (): LitElement => {
|
||||
const element = document.createElement('div') as unknown as LitElement;
|
||||
element.addController = vi.fn();
|
||||
element.requestUpdate = vi.fn();
|
||||
|
||||
const promise: Promise<boolean> = new Promise((resolve) => {
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
// Need to overwrite a read-only property.
|
||||
Object.defineProperty(element, 'updateComplete', {
|
||||
value: promise,
|
||||
});
|
||||
return element;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
AdvancedCameraCardHTMLVideoElement,
|
||||
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
||||
hideMediaControlsTemporarily,
|
||||
setControlsOnVideo,
|
||||
} from '../../src/utils/controls.js';
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('setControlsOnVideo', () => {
|
||||
it('should set controls', () => {
|
||||
const video = document.createElement('video');
|
||||
|
||||
setControlsOnVideo(video, false);
|
||||
expect(video.controls).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should stop timer', () => {
|
||||
const video: AdvancedCameraCardHTMLVideoElement = document.createElement('video');
|
||||
hideMediaControlsTemporarily(video);
|
||||
|
||||
expect(video._controlsHideTimer).toBeTruthy();
|
||||
expect(video._controlsHideTimer?.isRunning()).toBeTruthy();
|
||||
|
||||
setControlsOnVideo(video, false);
|
||||
expect(video.controls).toBeFalsy();
|
||||
expect(video._controlsHideTimer).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('hideMediaControlsTemporarily', () => {
|
||||
beforeAll(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should set controls', () => {
|
||||
const video: AdvancedCameraCardHTMLVideoElement = document.createElement('video');
|
||||
video.controls = true;
|
||||
hideMediaControlsTemporarily(video);
|
||||
|
||||
expect(video.controls).toBeFalsy();
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(video.controls).toBeTruthy();
|
||||
expect(video._controlsHideTimer).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should add event listener that resets controls', () => {
|
||||
const video: AdvancedCameraCardHTMLVideoElement = document.createElement('video');
|
||||
video.controls = true;
|
||||
|
||||
hideMediaControlsTemporarily(video);
|
||||
expect(video.controls).toBeFalsy();
|
||||
|
||||
// After a new media starts to load, the controls should reset.
|
||||
video.dispatchEvent(new Event('loadstart'));
|
||||
expect(video.controls).toBeTruthy();
|
||||
|
||||
// ... but only once, future loadstart events without subsequent calls to
|
||||
// hideMediaControlsTemporarily() should do nothing.
|
||||
video.controls = false;
|
||||
video.dispatchEvent(new Event('loadstart'));
|
||||
expect(video._controlsHideTimer).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('constants', () => {
|
||||
it('MEDIA_LOAD_CONTROLS_HIDE_SECONDS', () => {
|
||||
expect(MEDIA_LOAD_CONTROLS_HIDE_SECONDS).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -1,145 +0,0 @@
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { AdvancedCameraCardMediaPlayer } from '../../src/types.js';
|
||||
import {
|
||||
AdvancedCameraCardHTMLVideoElement,
|
||||
MEDIA_LOAD_CONTROLS_HIDE_SECONDS,
|
||||
hideMediaControlsTemporarily,
|
||||
playMediaMutingIfNecessary,
|
||||
setControlsOnVideo,
|
||||
} from '../../src/utils/media.js';
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('setControlsOnVideo', () => {
|
||||
it('should set controls', () => {
|
||||
const video = document.createElement('video');
|
||||
|
||||
setControlsOnVideo(video, false);
|
||||
expect(video.controls).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should stop timer', () => {
|
||||
const video: AdvancedCameraCardHTMLVideoElement = document.createElement('video');
|
||||
hideMediaControlsTemporarily(video);
|
||||
|
||||
expect(video._controlsHideTimer).toBeTruthy();
|
||||
expect(video._controlsHideTimer?.isRunning()).toBeTruthy();
|
||||
|
||||
setControlsOnVideo(video, false);
|
||||
expect(video.controls).toBeFalsy();
|
||||
expect(video._controlsHideTimer).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('hideMediaControlsTemporarily', () => {
|
||||
beforeAll(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should set controls', () => {
|
||||
const video: AdvancedCameraCardHTMLVideoElement = document.createElement('video');
|
||||
video.controls = true;
|
||||
hideMediaControlsTemporarily(video);
|
||||
|
||||
expect(video.controls).toBeFalsy();
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(video.controls).toBeTruthy();
|
||||
expect(video._controlsHideTimer).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should add event listener that resets controls', () => {
|
||||
const video: AdvancedCameraCardHTMLVideoElement = document.createElement('video');
|
||||
video.controls = true;
|
||||
|
||||
hideMediaControlsTemporarily(video);
|
||||
expect(video.controls).toBeFalsy();
|
||||
|
||||
// After a new media starts to load, the controls should reset.
|
||||
video.dispatchEvent(new Event('loadstart'));
|
||||
expect(video.controls).toBeTruthy();
|
||||
|
||||
// ... but only once, future loadstart events without subsequent calls to
|
||||
// hideMediaControlsTemporarily() should do nothing.
|
||||
video.controls = false;
|
||||
video.dispatchEvent(new Event('loadstart'));
|
||||
expect(video._controlsHideTimer).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
class NotAllowedError extends Error {
|
||||
name = 'NotAllowedError';
|
||||
}
|
||||
|
||||
describe('playMediaMutingIfNecessary', () => {
|
||||
it('should play', async () => {
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
const video = mock<HTMLVideoElement>();
|
||||
video.play.mockResolvedValue();
|
||||
await playMediaMutingIfNecessary(player, video);
|
||||
expect(video.play).toBeCalled();
|
||||
});
|
||||
|
||||
it('should mute if not allowed to play and unmuted', async () => {
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
player.isMuted.mockReturnValue(false);
|
||||
player.mute.mockResolvedValue();
|
||||
|
||||
const video = mock<HTMLVideoElement>();
|
||||
video.play.mockRejectedValueOnce(new NotAllowedError()).mockResolvedValueOnce();
|
||||
|
||||
await playMediaMutingIfNecessary(player, video);
|
||||
|
||||
expect(video.play).toBeCalledTimes(2);
|
||||
expect(player.isMuted).toBeCalled();
|
||||
expect(player.mute).toBeCalled();
|
||||
});
|
||||
|
||||
it('should not mute if not allowed to play and already unmuted', async () => {
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
player.isMuted.mockReturnValue(true);
|
||||
|
||||
const video = mock<HTMLVideoElement>();
|
||||
video.play.mockRejectedValueOnce(new NotAllowedError());
|
||||
|
||||
await playMediaMutingIfNecessary(player, video);
|
||||
|
||||
expect(video.play).toBeCalledTimes(1);
|
||||
expect(player.isMuted).toBeCalled();
|
||||
expect(player.mute).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should ignore exception if subsequent play call throws', async () => {
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
player.isMuted.mockReturnValue(false);
|
||||
|
||||
const video = mock<HTMLVideoElement>();
|
||||
video.play.mockRejectedValue(new NotAllowedError());
|
||||
|
||||
await playMediaMutingIfNecessary(player, video);
|
||||
|
||||
expect(video.play).toBeCalledTimes(2);
|
||||
expect(player.isMuted).toBeCalled();
|
||||
expect(player.mute).toBeCalled();
|
||||
});
|
||||
|
||||
it('should ignore calls without a video', async () => {
|
||||
const player = mock<AdvancedCameraCardMediaPlayer>();
|
||||
player.isMuted.mockReturnValue(false);
|
||||
|
||||
await playMediaMutingIfNecessary(player);
|
||||
|
||||
expect(player.isMuted).not.toBeCalled();
|
||||
expect(player.mute).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('constants', () => {
|
||||
it('MEDIA_LOAD_CONTROLS_HIDE_SECONDS', () => {
|
||||
expect(MEDIA_LOAD_CONTROLS_HIDE_SECONDS).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,16 @@
|
||||
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { generateScreenshotTitle, screenshotMedia } from '../../src/utils/screenshot';
|
||||
import {
|
||||
generateScreenshotTitle,
|
||||
screenshotImage,
|
||||
screenshotVideo,
|
||||
} from '../../src/utils/screenshot';
|
||||
import { MediaQueriesResults } from '../../src/view/media-queries-results';
|
||||
import { View } from '../../src/view/view';
|
||||
import { TestViewMedia, createView } from '../test-utils';
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('screenshotMedia', () => {
|
||||
describe('screenshotVideo', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
@@ -19,7 +23,7 @@ describe('screenshotMedia', () => {
|
||||
canvas.getContext = getContext;
|
||||
vi.spyOn(document, 'createElement').mockReturnValue(canvas);
|
||||
|
||||
expect(screenshotMedia(video)).toBeNull();
|
||||
expect(screenshotVideo(video)).toBeNull();
|
||||
});
|
||||
|
||||
it('should screenshot', () => {
|
||||
@@ -31,7 +35,36 @@ describe('screenshotMedia', () => {
|
||||
canvas.toDataURL = vi.fn().mockReturnValue('data:image/jpeg;base64');
|
||||
vi.spyOn(document, 'createElement').mockReturnValue(canvas);
|
||||
|
||||
expect(screenshotMedia(video)).toBe('data:image/jpeg;base64');
|
||||
expect(screenshotVideo(video)).toBe('data:image/jpeg;base64');
|
||||
});
|
||||
});
|
||||
|
||||
describe('screenshotImage', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should not screenshot without context', () => {
|
||||
const image = document.createElement('img');
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const getContext = vi.fn().mockReturnValue(null);
|
||||
canvas.getContext = getContext;
|
||||
vi.spyOn(document, 'createElement').mockReturnValue(canvas);
|
||||
|
||||
expect(screenshotImage(image)).toBeNull();
|
||||
});
|
||||
|
||||
it('should screenshot', () => {
|
||||
const image = document.createElement('img');
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const getContext = vi.fn().mockReturnValue(mock<CanvasRenderingContext2D>());
|
||||
canvas.getContext = getContext;
|
||||
canvas.toDataURL = vi.fn().mockReturnValue('data:image/jpeg;base64');
|
||||
vi.spyOn(document, 'createElement').mockReturnValue(canvas);
|
||||
|
||||
expect(screenshotImage(image)).toBe('data:image/jpeg;base64');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user