Set timeline to now not to most recent event.

This commit is contained in:
Dermot Duffy
2024-04-07 20:21:09 -07:00
parent 14a4f89726
commit f67620397f
22 changed files with 828 additions and 336 deletions
@@ -0,0 +1,377 @@
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,
},
},
}),
}),
);
});
});
});
});