196 lines
6.8 KiB
TypeScript
196 lines
6.8 KiB
TypeScript
import { assert, describe, expect, it, onTestFinished, vi } from 'vitest';
|
|
|
|
import { MEDIA_LOADING_TIMEOUT_SECONDS } from '../../../src/components-lib/media-load-watchdog-controller';
|
|
import type { PartialAdvancedCameraCardConfig } from '../../../src/config/types';
|
|
import { clickElement, deepQuery } from '../../browser/dom';
|
|
import {
|
|
createTestFrigateEvent,
|
|
EVENT_TIME_NEWER,
|
|
EVENT_TIME_OLDER,
|
|
mountCardWithFrigate,
|
|
} from '../../browser/fake-frigate';
|
|
import type { MountedCard } from '../../browser/mounted-card';
|
|
import { createFailingMediaURL, useTestMedia } from '../../browser/test-media';
|
|
import {
|
|
clickNextPreviousMedia,
|
|
clickThumbnail,
|
|
getMediaViewerMediaURLs,
|
|
getSelectedThumbnail,
|
|
getStatusBarItem,
|
|
getStatusBarStrings,
|
|
getThumbnails,
|
|
waitForMediaViewerMedia,
|
|
waitForThumbnails,
|
|
} from '../../browser/test-utils';
|
|
|
|
const EVENTS = [
|
|
createTestFrigateEvent('older', EVENT_TIME_OLDER),
|
|
createTestFrigateEvent('newer', EVENT_TIME_NEWER),
|
|
];
|
|
|
|
// What the status bar calls a media failure, which is where the viewer shows
|
|
// the issue: the media it cannot show is still on screen behind the carousel.
|
|
const MEDIA_ISSUE_TITLE = 'Media unavailable';
|
|
|
|
// The thumbnail carousel sits in a drawer by default, which is harder to click.
|
|
const CONFIG_THUMBNAILS_BELOW: PartialAdvancedCameraCardConfig = {
|
|
media_viewer: { controls: { thumbnails: { mode: 'below' } } },
|
|
};
|
|
|
|
interface ViewerOptions {
|
|
config?: PartialAdvancedCameraCardConfig;
|
|
|
|
// Which gallery thumbnail to open the viewer on. The newest event is first.
|
|
thumbnail?: number;
|
|
}
|
|
|
|
const mountViewer = async (
|
|
events = EVENTS,
|
|
options?: ViewerOptions,
|
|
): Promise<MountedCard> => {
|
|
const { card } = await mountCardWithFrigate(events, {
|
|
view: { default: 'clips' },
|
|
...options?.config,
|
|
});
|
|
|
|
await waitForThumbnails(card, events.length);
|
|
await clickThumbnail(card.card, options?.thumbnail ?? 0);
|
|
await card.events.waitForFirst('advanced-camera-card:media:loaded');
|
|
|
|
return card;
|
|
};
|
|
|
|
// One test here has a clip that never loads, which is served from within the
|
|
// page rather than by the dev server.
|
|
useTestMedia();
|
|
|
|
describe('AdvancedCameraCardViewerCarousel', () => {
|
|
it('should play the clip that was opened', async () => {
|
|
const card = await mountViewer();
|
|
|
|
expect(getMediaViewerMediaURLs(card.card)).toEqual([
|
|
expect.stringContaining('clip.webm?event=newer'),
|
|
]);
|
|
expect(deepQuery(card.card, 'advanced-camera-card-video-player')).not.toBeNull();
|
|
});
|
|
|
|
it('should show a snapshot as a still image', async () => {
|
|
const card = await mountViewer(EVENTS, {
|
|
config: { view: { default: 'snapshots' } },
|
|
});
|
|
|
|
expect(getMediaViewerMediaURLs(card.card)).toEqual([
|
|
expect.stringContaining('still-red.png?event=newer'),
|
|
]);
|
|
expect(deepQuery(card.card, 'advanced-camera-card-image-player')).not.toBeNull();
|
|
expect(deepQuery(card.card, 'advanced-camera-card-video-player')).toBeNull();
|
|
});
|
|
|
|
it('should show the next media when the next control is used', async () => {
|
|
// Opened on the older event, so there is a newer one to move on to.
|
|
const card = await mountViewer(EVENTS, { thumbnail: 1 });
|
|
|
|
await clickNextPreviousMedia(card.card, 'right');
|
|
|
|
await waitForMediaViewerMedia(card, 'clip.webm?event=newer');
|
|
expect(getSelectedThumbnail(card.card)).toBe(getThumbnails(card.card)[1]);
|
|
});
|
|
|
|
it('should show the media selected in the thumbnail carousel', async () => {
|
|
const card = await mountViewer(EVENTS, { config: CONFIG_THUMBNAILS_BELOW });
|
|
|
|
// The viewer runs oldest first (gallery is the opposite). This is the oldest.
|
|
await clickThumbnail(card.card, 0);
|
|
|
|
await waitForMediaViewerMedia(card, 'clip.webm?event=older');
|
|
expect(getSelectedThumbnail(card.card)).toBe(getThumbnails(card.card)[0]);
|
|
});
|
|
|
|
it('should name the media being viewed in the status bar', async () => {
|
|
const card = await mountViewer(
|
|
[
|
|
createTestFrigateEvent('older', EVENT_TIME_OLDER, { label: 'car' }),
|
|
createTestFrigateEvent('newer', EVENT_TIME_NEWER, { label: 'person' }),
|
|
],
|
|
{ config: { status_bar: { style: 'outside' }, ...CONFIG_THUMBNAILS_BELOW } },
|
|
);
|
|
|
|
expect(getStatusBarStrings(card.card).join(' ')).toContain('Person');
|
|
|
|
await clickThumbnail(card.card, 0);
|
|
await waitForMediaViewerMedia(card, 'clip.webm?event=older');
|
|
|
|
expect(getStatusBarStrings(card.card).join(' ')).toContain('Car');
|
|
});
|
|
|
|
it('should play the clip when a snapshot is clicked', async () => {
|
|
const card = await mountViewer(EVENTS, {
|
|
config: { view: { default: 'snapshots' } },
|
|
});
|
|
|
|
const snapshot = deepQuery(card.card, 'advanced-camera-card-image-player');
|
|
assert(snapshot);
|
|
|
|
await clickElement(snapshot);
|
|
|
|
await waitForMediaViewerMedia(card, 'clip.webm?event=newer');
|
|
});
|
|
|
|
it('should pause the media that is moved away from', async () => {
|
|
// Opened on the older event, so there is a newer one to move on to.
|
|
const card = await mountViewer(EVENTS, { thumbnail: 1 });
|
|
|
|
// `auto_play` covers the selected media, so the clip starts on its own.
|
|
await card.events.waitForFirst('advanced-camera-card:media:play');
|
|
|
|
await clickNextPreviousMedia(card.card, 'right');
|
|
|
|
// Leaving a clip playing behind the one on screen would have two videos
|
|
// running at once, and the user hearing the one they cannot see.
|
|
await card.events.waitForFirst('advanced-camera-card:media:pause');
|
|
});
|
|
|
|
it('should report media that cannot be loaded', async () => {
|
|
vi.useFakeTimers();
|
|
onTestFinished(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
const { card, frigate } = await mountCardWithFrigate(EVENTS, {
|
|
view: { default: 'clips' },
|
|
status_bar: { style: 'outside' },
|
|
});
|
|
frigate.setMediaURL('newer', 'clips', createFailingMediaURL());
|
|
|
|
await waitForThumbnails(card, EVENTS.length);
|
|
await clickThumbnail(card.card, 0);
|
|
|
|
// The card starts its load timer when the player is rendered, so waiting for
|
|
// the player means the timer is running and the clock can be jumped.
|
|
await card.waitForSelector('video');
|
|
|
|
// A clip that refuses to load says nothing of its own, so the card has only
|
|
// silence to go on and triggers an issue once it has waited long enough.
|
|
await card.advanceSeconds(MEDIA_LOADING_TIMEOUT_SECONDS);
|
|
|
|
await card.waitForRender(
|
|
() => getStatusBarItem(card.card, MEDIA_ISSUE_TITLE),
|
|
`the ${MEDIA_ISSUE_TITLE} issue being reported`,
|
|
);
|
|
});
|
|
|
|
it('should return to the gallery from the viewer', async () => {
|
|
const card = await mountViewer(EVENTS, {
|
|
config: {
|
|
// The clips button is hidden by default.
|
|
menu: { style: 'outside', buttons: { clips: { enabled: true } } },
|
|
},
|
|
});
|
|
|
|
await card.clickControl('Clips gallery');
|
|
|
|
await card.waitForSelector('advanced-camera-card-gallery');
|
|
expect(deepQuery(card.card, 'advanced-camera-card-viewer-carousel')).toBeNull();
|
|
});
|
|
});
|