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:
@@ -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