378 lines
11 KiB
TypeScript
378 lines
11 KiB
TypeScript
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { LiveController } from '../../../src/components-lib/live/live-controller';
|
|
import { dispatchMessageEvent } from '../../../src/components/message';
|
|
import {
|
|
changeViewToRecentEventsForCameraAndDependents,
|
|
changeViewToRecentRecordingForCameraAndDependents,
|
|
} from '../../../src/utils/media-to-view';
|
|
import { EventMediaQueries } from '../../../src/view/media-queries';
|
|
import {
|
|
IntersectionObserverMock,
|
|
callIntersectionHandler,
|
|
createCameraManager,
|
|
createConfig,
|
|
createLitElement,
|
|
createMediaLoadedInfo,
|
|
createMediaLoadedInfoEvent,
|
|
createParent,
|
|
createView,
|
|
createViewChangeEvent,
|
|
} from '../../test-utils';
|
|
|
|
vi.mock('../../../src/utils/media-to-view');
|
|
|
|
// @vitest-environment jsdom
|
|
describe('LiveController', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.stubGlobal('IntersectionObserver', IntersectionObserverMock);
|
|
});
|
|
|
|
it('should be constructable', () => {
|
|
const controller = new LiveController(createLitElement());
|
|
expect(controller).toBeTruthy();
|
|
});
|
|
|
|
it('should connect and disconnect', () => {
|
|
const host = createLitElement();
|
|
const parent = createParent({ children: [host] });
|
|
const eventListener = vi.fn();
|
|
parent.addEventListener('frigate-card:message', eventListener);
|
|
|
|
const controller = new LiveController(host);
|
|
|
|
controller.hostConnected();
|
|
|
|
callIntersectionHandler(false);
|
|
dispatchMessageEvent(host, 'message', 'info');
|
|
|
|
expect(eventListener).toBeCalledTimes(0);
|
|
|
|
controller.hostDisconnected();
|
|
dispatchMessageEvent(host, 'message', 'info');
|
|
|
|
expect(eventListener).toBeCalledTimes(1);
|
|
});
|
|
|
|
describe('should handle background / foreground', () => {
|
|
it('should start in the foreground', () => {
|
|
const controller = new LiveController(createLitElement());
|
|
expect(controller.isInBackground()).toBeFalsy();
|
|
});
|
|
|
|
it('should handle changing to background', () => {
|
|
const element = createLitElement();
|
|
const controller = new LiveController(element);
|
|
expect(controller.isInBackground()).toBeFalsy();
|
|
expect(element.requestUpdate).toBeCalledTimes(0);
|
|
|
|
callIntersectionHandler(true);
|
|
expect(controller.isInBackground()).toBeFalsy();
|
|
expect(element.requestUpdate).toBeCalledTimes(0);
|
|
|
|
callIntersectionHandler(false);
|
|
expect(controller.isInBackground()).toBeTruthy();
|
|
expect(element.requestUpdate).toBeCalledTimes(1);
|
|
});
|
|
|
|
it('should dispatch media loaded on background change', () => {
|
|
const host = createLitElement();
|
|
const parent = createParent({ children: [host] });
|
|
const eventListener = vi.fn();
|
|
parent.addEventListener('frigate-card:media:loaded', eventListener);
|
|
|
|
const controller = new LiveController(host);
|
|
const mediaLoadedInfo = createMediaLoadedInfo();
|
|
|
|
controller.hostConnected();
|
|
|
|
callIntersectionHandler(false);
|
|
expect(controller.isInBackground()).toBeTruthy();
|
|
|
|
host.dispatchEvent(createMediaLoadedInfoEvent(mediaLoadedInfo));
|
|
expect(eventListener).toBeCalledTimes(0);
|
|
|
|
callIntersectionHandler(true);
|
|
expect(eventListener).toBeCalledTimes(1);
|
|
expect(eventListener).toHaveBeenLastCalledWith(
|
|
expect.objectContaining({
|
|
detail: mediaLoadedInfo,
|
|
}),
|
|
);
|
|
|
|
host.dispatchEvent(createMediaLoadedInfoEvent(mediaLoadedInfo));
|
|
expect(eventListener).toBeCalledTimes(2);
|
|
|
|
callIntersectionHandler(false);
|
|
dispatchMessageEvent(host, 'message', 'info');
|
|
callIntersectionHandler(true);
|
|
expect(eventListener).toBeCalledTimes(2);
|
|
|
|
controller.clearMessageReceived();
|
|
callIntersectionHandler(true);
|
|
expect(eventListener).toBeCalledTimes(3);
|
|
});
|
|
});
|
|
|
|
describe('should correctly allow updates', () => {
|
|
it('when not in background', () => {
|
|
const controller = new LiveController(createLitElement());
|
|
expect(controller.shouldUpdate()).toBeTruthy();
|
|
});
|
|
|
|
it('when in background without message', () => {
|
|
const host = createLitElement();
|
|
const controller = new LiveController(host);
|
|
controller.hostConnected();
|
|
|
|
callIntersectionHandler(false);
|
|
|
|
expect(controller.shouldUpdate()).toBeTruthy();
|
|
});
|
|
|
|
it('when in background with message', () => {
|
|
const host = createLitElement();
|
|
const controller = new LiveController(host);
|
|
controller.hostConnected();
|
|
|
|
callIntersectionHandler(false);
|
|
dispatchMessageEvent(host, 'message', 'info');
|
|
|
|
expect(controller.shouldUpdate()).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
it('should handle message', () => {
|
|
const host = createLitElement();
|
|
const parent = createParent({ children: [host] });
|
|
const eventListener = vi.fn();
|
|
parent.addEventListener('frigate-card:message', eventListener);
|
|
|
|
const controller = new LiveController(host);
|
|
controller.hostConnected();
|
|
|
|
callIntersectionHandler(false);
|
|
expect(controller.isInBackground()).toBeTruthy();
|
|
|
|
const firstRenderEpoch = controller.getRenderEpoch();
|
|
|
|
dispatchMessageEvent(host, 'message', 'info');
|
|
expect(eventListener).toBeCalledTimes(0);
|
|
|
|
const secondRenderEpoch = controller.getRenderEpoch();
|
|
expect(secondRenderEpoch).not.toBe(firstRenderEpoch);
|
|
|
|
callIntersectionHandler(true);
|
|
|
|
dispatchMessageEvent(host, 'message', 'info');
|
|
expect(eventListener).toBeCalledTimes(1);
|
|
expect(controller.getRenderEpoch()).toBe(secondRenderEpoch);
|
|
});
|
|
|
|
it('should handle view change', () => {
|
|
const host = createLitElement();
|
|
const parent = createParent({ children: [host] });
|
|
const eventListener = vi.fn();
|
|
parent.addEventListener('frigate-card:view:change', eventListener);
|
|
|
|
const controller = new LiveController(host);
|
|
controller.hostConnected();
|
|
const view = createView();
|
|
|
|
callIntersectionHandler(false);
|
|
expect(controller.isInBackground()).toBeTruthy();
|
|
host.dispatchEvent(createViewChangeEvent(view));
|
|
|
|
expect(eventListener).toBeCalledTimes(0);
|
|
|
|
callIntersectionHandler(true);
|
|
expect(controller.isInBackground()).toBeFalsy();
|
|
|
|
host.dispatchEvent(createViewChangeEvent(view));
|
|
|
|
expect(eventListener).toBeCalledTimes(1);
|
|
});
|
|
|
|
describe('should fetch media', () => {
|
|
it('when in background', async () => {
|
|
const controller = new LiveController(createLitElement());
|
|
|
|
callIntersectionHandler(false);
|
|
|
|
await controller.fetchMediaInBackgroundIfNecessary(
|
|
createView(),
|
|
createCameraManager(),
|
|
{},
|
|
createConfig().live,
|
|
);
|
|
|
|
expect(changeViewToRecentEventsForCameraAndDependents).not.toBeCalled();
|
|
expect(changeViewToRecentRecordingForCameraAndDependents).not.toBeCalled();
|
|
});
|
|
|
|
it('when has existing query', async () => {
|
|
const controller = new LiveController(createLitElement());
|
|
|
|
await controller.fetchMediaInBackgroundIfNecessary(
|
|
createView({ query: new EventMediaQueries() }),
|
|
createCameraManager(),
|
|
{},
|
|
createConfig().live,
|
|
);
|
|
|
|
expect(changeViewToRecentEventsForCameraAndDependents).not.toBeCalled();
|
|
expect(changeViewToRecentRecordingForCameraAndDependents).not.toBeCalled();
|
|
});
|
|
|
|
it('when has no thumbnails', async () => {
|
|
const controller = new LiveController(createLitElement());
|
|
|
|
await controller.fetchMediaInBackgroundIfNecessary(
|
|
createView(),
|
|
createCameraManager(),
|
|
{},
|
|
createConfig({
|
|
live: {
|
|
controls: {
|
|
thumbnails: {
|
|
mode: 'none',
|
|
},
|
|
},
|
|
},
|
|
}).live,
|
|
);
|
|
|
|
expect(changeViewToRecentEventsForCameraAndDependents).not.toBeCalled();
|
|
expect(changeViewToRecentRecordingForCameraAndDependents).not.toBeCalled();
|
|
});
|
|
|
|
it('when fetch disabled in context', async () => {
|
|
const controller = new LiveController(createLitElement());
|
|
|
|
await controller.fetchMediaInBackgroundIfNecessary(
|
|
createView({
|
|
context: {
|
|
live: {
|
|
fetchThumbnails: false,
|
|
},
|
|
},
|
|
}),
|
|
createCameraManager(),
|
|
{},
|
|
createConfig().live,
|
|
);
|
|
|
|
expect(changeViewToRecentEventsForCameraAndDependents).not.toBeCalled();
|
|
expect(changeViewToRecentRecordingForCameraAndDependents).not.toBeCalled();
|
|
});
|
|
|
|
describe('with fetch', () => {
|
|
const now = new Date('2024-04-07T19:43');
|
|
beforeAll(() => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(now);
|
|
});
|
|
|
|
afterAll(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it('events', async () => {
|
|
const host = createLitElement();
|
|
const controller = new LiveController(host);
|
|
const view = createView();
|
|
const cameraManager = createCameraManager();
|
|
const cardWideConfig = {};
|
|
|
|
await controller.fetchMediaInBackgroundIfNecessary(
|
|
view,
|
|
cameraManager,
|
|
cardWideConfig,
|
|
createConfig({
|
|
live: {
|
|
controls: {
|
|
thumbnails: {
|
|
media_type: 'events',
|
|
events_media_type: 'all',
|
|
},
|
|
timeline: {
|
|
window_seconds: 3600,
|
|
},
|
|
},
|
|
},
|
|
}).live,
|
|
);
|
|
|
|
expect(changeViewToRecentEventsForCameraAndDependents).toBeCalledWith(
|
|
host,
|
|
cameraManager,
|
|
cardWideConfig,
|
|
view,
|
|
expect.objectContaining({
|
|
allCameras: false,
|
|
targetView: 'live',
|
|
eventsMediaType: 'all',
|
|
select: 'latest',
|
|
viewContext: expect.objectContaining({
|
|
timeline: {
|
|
window: {
|
|
start: new Date('2024-04-07T18:43'),
|
|
end: now,
|
|
},
|
|
},
|
|
}),
|
|
}),
|
|
);
|
|
expect(changeViewToRecentRecordingForCameraAndDependents).not.toBeCalled();
|
|
});
|
|
|
|
it('recordings', async () => {
|
|
const host = createLitElement();
|
|
const controller = new LiveController(host);
|
|
const view = createView();
|
|
const cameraManager = createCameraManager();
|
|
const cardWideConfig = {};
|
|
|
|
await controller.fetchMediaInBackgroundIfNecessary(
|
|
view,
|
|
cameraManager,
|
|
cardWideConfig,
|
|
createConfig({
|
|
live: {
|
|
controls: {
|
|
thumbnails: {
|
|
media_type: 'recordings',
|
|
},
|
|
timeline: {
|
|
window_seconds: 3600,
|
|
},
|
|
},
|
|
},
|
|
}).live,
|
|
);
|
|
|
|
expect(changeViewToRecentEventsForCameraAndDependents).not.toBeCalled();
|
|
expect(changeViewToRecentRecordingForCameraAndDependents).toBeCalledWith(
|
|
host,
|
|
cameraManager,
|
|
cardWideConfig,
|
|
view,
|
|
expect.objectContaining({
|
|
allCameras: false,
|
|
targetView: 'live',
|
|
select: 'latest',
|
|
viewContext: expect.objectContaining({
|
|
timeline: {
|
|
window: {
|
|
start: new Date('2024-04-07T18:43'),
|
|
end: now,
|
|
},
|
|
},
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|