import { endOfDay, startOfDay, sub } from 'date-fns'; import { afterAll, assert, beforeAll, beforeEach, describe, expect, it, vi, } from 'vitest'; import { mock, type MockProxy } from 'vitest-mock-extended'; import { Capabilities } from '../../src/camera-manager/capabilities'; import { QueryType, type EventQuery, type MediaQuery, type RecordingQuery, type ReviewQuery, } from '../../src/camera-manager/types'; import type { FoldersManager } from '../../src/card-controller/folders/manager'; import type { ViewManager } from '../../src/card-controller/view/view-manager'; import { MediaFilterController, MediaFilterCoreFavoriteSelection, MediaFilterCoreReviewedSelection, MediaFilterCoreWhen, MediaFilterMediaType, type MediaFilterCoreDefaults, } from '../../src/components-lib/media-filter-controller'; import { QuerySource } from '../../src/query-source'; import type { Severity } from '../../src/severity'; import { UnifiedQuery } from '../../src/view/unified-query'; import { createCameraManager, createStore } from '../camera-manager/test-utils'; import { createCameraConfig, createPerformanceConfig } from '../config/test-utils'; import { createLitElement } from '../test-utils'; import { createView } from '../view/test-utils'; const createCameraStore = (options?: { capabilities: Capabilities }) => { return createStore([ { cameraID: 'camera.kitchen', config: createCameraConfig({ camera_entity: 'camera.kitchen', }), capabilities: options?.capabilities ?? new Capabilities({ clips: true, snapshots: true, recordings: true, }), }, ]); }; const getQueryNodes = (viewManager: MockProxy, n = 0): MediaQuery[] => { const query = viewManager.setViewByParametersWithExistingQuery.mock.calls[n]?.[0]?.params?.query; return query ? query.getMediaQueries() : []; }; type TestQueryNode = ( | Partial> | Partial> | Partial> ) & { type: QueryType; cameraID?: string; cameraIDs?: Set; }; const createQueryWithNodes = (nodes: TestQueryNode[]): UnifiedQuery => { const query = new UnifiedQuery(); for (const node of nodes) { const { cameraID, cameraIDs: explicitCameraIDs, type, ...rest } = node; const cameraIDs = explicitCameraIDs ?? new Set([cameraID ?? 'camera.kitchen']); const mediaQuery: MediaQuery = { source: QuerySource.Camera, type, cameraIDs, ...rest, }; query.addNode(mediaQuery); } return query; }; const queryDefaultTestCases: Array<[string, UnifiedQuery, MediaFilterCoreDefaults]> = [ [ 'same cameras', createQueryWithNodes([ { type: QueryType.Event, cameraID: 'camera.kitchen', hasClip: true }, { type: QueryType.Event, cameraID: 'camera.kitchen', hasClip: true }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Clips], }, ], [ 'different cameras', createQueryWithNodes([ { type: QueryType.Event, cameraID: 'camera.kitchen', hasClip: true }, { type: QueryType.Event, cameraID: 'camera.living_room', hasClip: true }, ]), { // cameraIDs included because they differ from visible cameras cameraIDs: ['camera.kitchen', 'camera.living_room'], mediaTypes: [MediaFilterMediaType.Clips], }, ], [ 'all favorites', createQueryWithNodes([ { type: QueryType.Event, favorite: true, hasClip: true }, { type: QueryType.Event, favorite: true, hasClip: true }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Clips], favorite: MediaFilterCoreFavoriteSelection.Favorite, }, ], [ 'all not favorites', createQueryWithNodes([ { type: QueryType.Event, favorite: false, hasClip: true }, { type: QueryType.Event, favorite: false, hasClip: true }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Clips], favorite: MediaFilterCoreFavoriteSelection.NotFavorite, }, ], [ 'different favorites', createQueryWithNodes([ { type: QueryType.Event, favorite: true, hasClip: true }, { type: QueryType.Event, favorite: false, hasClip: true }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Clips], }, ], [ 'same hasClip', createQueryWithNodes([ { type: QueryType.Event, hasClip: true }, { type: QueryType.Event, hasClip: true }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Clips], }, ], [ 'different hasClip', createQueryWithNodes([ { type: QueryType.Event, hasClip: true }, { type: QueryType.Event, hasClip: false }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Clips], }, ], [ 'same hasSnapshot', createQueryWithNodes([ { type: QueryType.Event, hasSnapshot: true }, { type: QueryType.Event, hasSnapshot: true }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Snapshots], }, ], [ 'different hasSnapshot', createQueryWithNodes([ { type: QueryType.Event, hasSnapshot: true }, { type: QueryType.Event, hasSnapshot: false }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Snapshots], }, ], [ 'same what', createQueryWithNodes([ { type: QueryType.Event, what: new Set(['person']), hasClip: true }, { type: QueryType.Event, what: new Set(['person']), hasClip: true }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Clips], what: ['person'], }, ], [ 'different what', createQueryWithNodes([ { type: QueryType.Event, what: new Set(['person']), hasClip: true }, { type: QueryType.Event, what: new Set(['car']), hasClip: true }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Clips], }, ], [ 'same where', createQueryWithNodes([ { type: QueryType.Event, where: new Set(['front_door']), hasClip: true }, { type: QueryType.Event, where: new Set(['front_door']), hasClip: true }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Clips], where: ['front_door'], }, ], [ 'different where', createQueryWithNodes([ { type: QueryType.Event, where: new Set(['front_door']), hasClip: true }, { type: QueryType.Event, where: new Set(['back_steps']), hasClip: true }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Clips], }, ], [ 'same tags', createQueryWithNodes([ { type: QueryType.Event, tags: new Set(['tag-1']), hasClip: true }, { type: QueryType.Event, tags: new Set(['tag-1']), hasClip: true }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Clips], tags: ['tag-1'], }, ], [ 'different tags', createQueryWithNodes([ { type: QueryType.Event, tags: new Set(['tag-1']), hasClip: true }, { type: QueryType.Event, tags: new Set(['tag-2']), hasClip: true }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Clips], }, ], [ 'recordings', createQueryWithNodes([{ type: QueryType.Recording }]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Recordings], }, ], [ 'reviews', createQueryWithNodes([{ type: QueryType.Review }]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Reviews], }, ], [ 'reviewed true', createQueryWithNodes([{ type: QueryType.Review, reviewed: true }]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Reviews], reviewed: MediaFilterCoreReviewedSelection.Reviewed, }, ], [ 'reviewed false', createQueryWithNodes([{ type: QueryType.Review, reviewed: false }]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Reviews], reviewed: MediaFilterCoreReviewedSelection.NotReviewed, }, ], [ 'mixed favorites', createQueryWithNodes([ { type: QueryType.Event, favorite: true, hasClip: true }, { type: QueryType.Event, favorite: false, hasClip: true }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Clips], }, ], [ 'mixed reviewed', createQueryWithNodes([ { type: QueryType.Review, reviewed: true }, { type: QueryType.Review, reviewed: false }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Reviews], }, ], [ 'severity', createQueryWithNodes([ { type: QueryType.Review, severity: new Set(['high']) }, ]), { cameraIDs: ['camera.kitchen'], mediaTypes: [MediaFilterMediaType.Reviews], severity: ['high'], }, ], ]; // @vitest-environment jsdom describe('MediaFilterController', () => { beforeEach(() => { vi.restoreAllMocks(); }); describe('should have correct default options', () => { it('media type', () => { const controller = new MediaFilterController(createLitElement()); expect(controller.getMediaTypeOptions()).toEqual([ { value: MediaFilterMediaType.Clips, label: 'Clips' }, { value: MediaFilterMediaType.Snapshots, label: 'Snapshots' }, { value: MediaFilterMediaType.Recordings, label: 'Recordings' }, { value: MediaFilterMediaType.Reviews, label: 'Reviews' }, ]); }); it('favorite', () => { const controller = new MediaFilterController(createLitElement()); expect(controller.getFavoriteOptions()).toEqual([ { value: MediaFilterCoreFavoriteSelection.Favorite, label: 'Favorite' }, { value: MediaFilterCoreFavoriteSelection.NotFavorite, label: 'Not Favorite' }, ]); }); it('reviewed', () => { const controller = new MediaFilterController(createLitElement()); expect(controller.getReviewedOptions()).toEqual([ { value: MediaFilterCoreReviewedSelection.Reviewed, label: 'Reviewed' }, { value: MediaFilterCoreReviewedSelection.NotReviewed, label: 'Not Reviewed' }, ]); }); it('when', () => { const controller = new MediaFilterController(createLitElement()); expect(controller.getWhenOptions()).toEqual([ { value: MediaFilterCoreWhen.Today, label: 'Today' }, { value: MediaFilterCoreWhen.Yesterday, label: 'Yesterday' }, { value: MediaFilterCoreWhen.PastWeek, label: 'Past Week' }, { value: MediaFilterCoreWhen.PastMonth, label: 'Past Month' }, { value: MediaFilterCoreWhen.Custom, label: 'Custom' }, ]); }); it('cameras', () => { const controller = new MediaFilterController(createLitElement()); expect(controller.getCameraOptions()).toEqual([]); }); it('what', () => { const controller = new MediaFilterController(createLitElement()); expect(controller.getWhatOptions()).toEqual([]); }); it('where', () => { const controller = new MediaFilterController(createLitElement()); expect(controller.getWhereOptions()).toEqual([]); }); it('tags', () => { const controller = new MediaFilterController(createLitElement()); expect(controller.getTagsOptions()).toEqual([]); }); it('severity', () => { const controller = new MediaFilterController(createLitElement()); expect(controller.getSeverityOptions()).toEqual([ { value: 'high', label: 'High' }, { value: 'medium', label: 'Medium' }, { value: 'low', label: 'Low' }, ]); }); }); describe('should calculate correct dynamic options', () => { describe('cameras', () => { it('with valid camera', () => { const cameraManager = createCameraManager(); vi.mocked(cameraManager.getCameraMetadata).mockReturnValue({ title: 'Kitchen Camera', icon: { icon: 'mdi:camera' }, }); vi.mocked(cameraManager.getStore).mockReturnValue(createCameraStore()); const controller = new MediaFilterController(createLitElement()); controller.computeCameraOptions(cameraManager, mock()); expect(controller.getCameraOptions()).toEqual([ { label: 'Kitchen Camera', value: 'camera.kitchen' }, ]); }); it('with camera that does not support media', () => { const cameraManager = createCameraManager(); vi.mocked(cameraManager.getCameraMetadata).mockReturnValue({ title: 'Kitchen Camera', icon: { icon: 'mdi:camera' }, }); vi.mocked(cameraManager.getStore).mockReturnValue( createCameraStore({ capabilities: new Capabilities({ clips: false, snapshots: false, recordings: false, }), }), ); const controller = new MediaFilterController(createLitElement()); controller.computeCameraOptions(cameraManager, mock()); expect(controller.getCameraOptions()).toEqual([]); }); it('without camera metadata', () => { const cameraManager = createCameraManager(); vi.mocked(cameraManager.getCameraMetadata).mockReturnValue(null); vi.mocked(cameraManager.getStore).mockReturnValue(createCameraStore()); const controller = new MediaFilterController(createLitElement()); controller.computeCameraOptions(cameraManager, mock()); expect(controller.getCameraOptions()).toEqual([ { label: 'camera.kitchen', value: 'camera.kitchen' }, ]); }); }); describe('metadata', () => { it('with failed getMediaMetadata call', async () => { vi.spyOn(global.console, 'warn').mockReturnValue(undefined); const cameraManager = createCameraManager(); vi.mocked(cameraManager.getMediaMetadata).mockRejectedValue(new Error('error')); const host = createLitElement(); const controller = new MediaFilterController(host); await controller.computeMetadataOptions(cameraManager); expect(host.requestUpdate).not.toHaveBeenCalled(); }); it('with metadata for what', async () => { const cameraManager = createCameraManager(); vi.mocked(cameraManager.getMediaMetadata).mockResolvedValue({ what: new Set(['person', 'car']), }); const host = createLitElement(); const controller = new MediaFilterController(host); await controller.computeMetadataOptions(cameraManager); expect(controller.getWhatOptions()).toEqual([ { value: 'car', label: 'Car' }, { value: 'person', label: 'Person' }, ]); expect(host.requestUpdate).toHaveBeenCalled(); }); it('with metadata for where', async () => { const cameraManager = createCameraManager(); vi.mocked(cameraManager.getMediaMetadata).mockResolvedValue({ where: new Set(['front_door', 'back_yard']), }); const host = createLitElement(); const controller = new MediaFilterController(host); await controller.computeMetadataOptions(cameraManager); expect(controller.getWhereOptions()).toEqual([ { value: 'back_yard', label: 'Back Yard' }, { value: 'front_door', label: 'Front Door' }, ]); expect(host.requestUpdate).toHaveBeenCalled(); }); it('with metadata for tags', async () => { const cameraManager = createCameraManager(); vi.mocked(cameraManager.getMediaMetadata).mockResolvedValue({ tags: new Set(['tag-1', 'tag-2']), }); const host = createLitElement(); const controller = new MediaFilterController(host); await controller.computeMetadataOptions(cameraManager); expect(controller.getTagsOptions()).toEqual([ { value: 'tag-1', label: 'Tag-1' }, { value: 'tag-2', label: 'Tag-2' }, ]); expect(host.requestUpdate).toHaveBeenCalled(); }); it('with metadata for days', async () => { const cameraManager = createCameraManager(); vi.mocked(cameraManager.getMediaMetadata).mockResolvedValue({ days: new Set(['2024-02-04', '2024-02-05']), }); const host = createLitElement(); const controller = new MediaFilterController(host); await controller.computeMetadataOptions(cameraManager); expect(controller.getWhenOptions()).toEqual( expect.arrayContaining([ expect.objectContaining({ value: '2024-02-01,2024-02-29', label: 'February 2024', }), ]), ); expect(host.requestUpdate).toHaveBeenCalled(); }); }); }); describe('should handle value change', () => { it('must have visible cameras', async () => { const viewManager = mock(); const controller = new MediaFilterController(createLitElement()); controller.setViewManager(viewManager); await controller.valueChangeHandler( createCameraManager(), mock(), {}, { when: {} }, ); expect(viewManager.setViewByParametersWithExistingQuery).not.toHaveBeenCalled(); }); describe('with events media type', () => { it.each([MediaFilterMediaType.Clips, MediaFilterMediaType.Snapshots])( '%s', async (mediaType: MediaFilterMediaType) => { const host = createLitElement(); const viewManager = mock(); viewManager.getView.mockReturnValue(createView()); const controller = new MediaFilterController(host); controller.setViewManager(viewManager); const cameraManager = createCameraManager(createCameraStore()); const from = new Date('2024-02-06T21:59'); const to = new Date('2024-02-06T22:00'); await controller.valueChangeHandler( cameraManager, mock(), { performance: createPerformanceConfig({ features: { media_chunk_size: 11 }, }), }, { camera: ['camera.kitchen'], mediaTypes: [mediaType], when: { to, from }, tags: ['tag-1', 'tag-2'], what: ['what-1', 'what-2'], where: ['where-1', 'where-2'], favorite: MediaFilterCoreFavoriteSelection.Favorite, }, ); expect(viewManager.setViewByParametersWithExistingQuery).toHaveBeenCalledWith({ params: expect.objectContaining({ camera: 'camera.kitchen', }), }); const nodes = getQueryNodes(viewManager); expect(nodes).toHaveLength(1); expect(nodes?.[0]).toMatchObject({ type: QueryType.Event, cameraIDs: new Set(['camera.kitchen']), ...(mediaType === MediaFilterMediaType.Clips && { hasClip: true }), ...(mediaType === MediaFilterMediaType.Snapshots && { hasSnapshot: true }), tags: new Set(['tag-1', 'tag-2']), what: new Set(['what-1', 'what-2']), where: new Set(['where-1', 'where-2']), favorite: true, start: from, end: to, limit: 11, }); expect(host.requestUpdate).toHaveBeenCalled(); }, ); }); it('with recordings media type', async () => { const host = createLitElement(); const cameraManager = createCameraManager(createCameraStore()); const viewManager = mock(); viewManager.getView.mockReturnValue(createView()); const controller = new MediaFilterController(host); controller.setViewManager(viewManager); const from = new Date('2024-02-06T21:59'); const to = new Date('2024-02-06T22:00'); await controller.valueChangeHandler( cameraManager, mock(), { performance: createPerformanceConfig({ features: { media_chunk_size: 11 }, }), }, { mediaTypes: [MediaFilterMediaType.Recordings], when: { to, from }, favorite: MediaFilterCoreFavoriteSelection.Favorite, }, ); expect(viewManager.setViewByParametersWithExistingQuery).toHaveBeenCalledWith({ params: expect.objectContaining({ camera: 'camera.kitchen', }), }); const nodes = getQueryNodes(viewManager); expect(nodes).toHaveLength(1); expect(nodes?.[0]).toMatchObject({ type: QueryType.Recording, cameraIDs: new Set(['camera.kitchen']), favorite: true, start: from, end: to, limit: 11, }); expect(host.requestUpdate).toHaveBeenCalled(); }); it('with reviews media type', async () => { const host = createLitElement(); const cameraManager = createCameraManager(createCameraStore()); const viewManager = mock(); viewManager.getView.mockReturnValue(createView()); const controller = new MediaFilterController(host); controller.setViewManager(viewManager); await controller.valueChangeHandler( cameraManager, mock(), {}, { mediaTypes: [MediaFilterMediaType.Reviews], when: {}, reviewed: MediaFilterCoreReviewedSelection.NotReviewed, }, ); expect(viewManager.setViewByParametersWithExistingQuery).toHaveBeenCalled(); const nodes = getQueryNodes(viewManager); expect(nodes).toHaveLength(1); expect(nodes?.[0]).toMatchObject({ type: QueryType.Review, reviewed: false, }); }); it('with severity', async () => { const host = createLitElement(); const cameraManager = createCameraManager(createCameraStore()); const viewManager = mock(); viewManager.getView.mockReturnValue(createView()); const controller = new MediaFilterController(host); controller.setViewManager(viewManager); await controller.valueChangeHandler( cameraManager, mock(), {}, { mediaTypes: [MediaFilterMediaType.Reviews], when: {}, severity: ['high'], }, ); expect(viewManager.setViewByParametersWithExistingQuery).toHaveBeenCalled(); const nodes = getQueryNodes(viewManager); expect(nodes).toHaveLength(1); expect(nodes?.[0]).toMatchObject({ type: QueryType.Review, severity: new Set(['high']), }); }); it('with multiple media types', async () => { const host = createLitElement(); const cameraManager = createCameraManager(createCameraStore()); const viewManager = mock(); viewManager.getView.mockReturnValue(createView()); const controller = new MediaFilterController(host); controller.setViewManager(viewManager); await controller.valueChangeHandler( cameraManager, mock(), {}, { mediaTypes: [MediaFilterMediaType.Clips, MediaFilterMediaType.Recordings], when: {}, }, ); expect(viewManager.setViewByParametersWithExistingQuery).toHaveBeenCalled(); const nodes = getQueryNodes(viewManager); expect(nodes).toHaveLength(2); expect(nodes?.map((s) => s.type)).toEqual([QueryType.Event, QueryType.Recording]); }); it('all favorites', async () => { const cameraManager = createCameraManager(createCameraStore()); const viewManager = mock(); viewManager.getView.mockReturnValue(createView()); const controller = new MediaFilterController(createLitElement()); controller.setViewManager(viewManager); await controller.valueChangeHandler( cameraManager, mock(), {}, { mediaTypes: [MediaFilterMediaType.Clips], when: {}, favorite: MediaFilterCoreFavoriteSelection.Favorite, }, ); expect(viewManager.setViewByParametersWithExistingQuery).toHaveBeenCalledWith({ params: expect.objectContaining({ camera: 'camera.kitchen', }), }); const nodes = getQueryNodes(viewManager); expect(nodes).toHaveLength(1); expect(nodes?.[0]).toMatchObject({ type: QueryType.Event, cameraIDs: new Set(['camera.kitchen']), favorite: true, }); }); it('without favorites', async () => { const cameraManager = createCameraManager(createCameraStore()); const viewManager = mock(); viewManager.getView.mockReturnValue(createView()); const controller = new MediaFilterController(createLitElement()); controller.setViewManager(viewManager); await controller.valueChangeHandler( cameraManager, mock(), {}, { mediaTypes: [MediaFilterMediaType.Recordings], when: {}, }, ); expect(viewManager.setViewByParametersWithExistingQuery).toHaveBeenCalledWith({ params: expect.objectContaining({ camera: 'camera.kitchen', }), }); const nodes = getQueryNodes(viewManager); expect(nodes).toHaveLength(1); expect(nodes?.[0]).toMatchObject({ type: QueryType.Recording, cameraIDs: new Set(['camera.kitchen']), }); }); it('with no media types selects all types', async () => { const cameraManager = createCameraManager(createCameraStore()); const viewManager = mock(); viewManager.getView.mockReturnValue(createView()); const controller = new MediaFilterController(createLitElement()); controller.setViewManager(viewManager); await controller.valueChangeHandler( cameraManager, mock(), {}, { mediaTypes: [], when: {}, }, ); expect(viewManager.setViewByParametersWithExistingQuery).toHaveBeenCalled(); const nodes = getQueryNodes(viewManager); // All 4 types selected for the single camera expect(nodes).toHaveLength(4); expect(nodes?.map((s) => s.type).sort()).toEqual([ QueryType.Event, QueryType.Event, QueryType.Recording, QueryType.Review, ]); }); it('with multiple camera selections', async () => { const cameraManager = createCameraManager(createCameraStore()); const viewManager = mock(); viewManager.getView.mockReturnValue(createView()); const controller = new MediaFilterController(createLitElement()); controller.setViewManager(viewManager); await controller.valueChangeHandler( cameraManager, mock(), {}, { camera: ['camera.kitchen', 'camera.living_room'], mediaTypes: [MediaFilterMediaType.Clips], when: {}, }, ); expect(viewManager.setViewByParametersWithExistingQuery).toHaveBeenCalledWith({ params: { query: expect.any(UnifiedQuery), }, }); const mockCall = vi.mocked(viewManager.setViewByParametersWithExistingQuery).mock .calls[0]; assert(mockCall && mockCall[0]); const callParams = mockCall[0].params; assert(callParams); expect(callParams.camera).toBeUndefined(); }); it('computeInitialDefaultsFromView with no cameras in query', () => { const cameraManager = createCameraManager(createCameraStore()); const foldersManager = mock(); const viewManager = mock(); const query = new UnifiedQuery(); const node: EventQuery = { source: QuerySource.Camera, type: QueryType.Event, cameraIDs: new Set(), // Empty camera IDs hasClip: true, }; query.addNode(node); const view = createView({ query }); viewManager.getView.mockReturnValue(view); const controller = new MediaFilterController(createLitElement()); controller.setViewManager(viewManager); controller.computeInitialDefaultsFromView(cameraManager, foldersManager); expect(controller.getDefaults()?.cameraIDs).toBeUndefined(); }); describe('with fixed when selection', () => { const date = new Date('2024-10-01T17:14'); beforeAll(() => { vi.useFakeTimers(); vi.setSystemTime(date); }); afterAll(() => { vi.useRealTimers(); }); it.each([ [MediaFilterCoreWhen.Today, startOfDay(date), endOfDay(date)], [ MediaFilterCoreWhen.Yesterday, startOfDay(sub(date, { days: 1 })), endOfDay(sub(date, { days: 1 })), ], [ MediaFilterCoreWhen.PastWeek, startOfDay(sub(date, { days: 7 })), endOfDay(date), ], [ MediaFilterCoreWhen.PastMonth, startOfDay(sub(date, { months: 1 })), endOfDay(date), ], [ '2024-02-01,2024-02-29', new Date('2024-02-01T00:00:00'), new Date('2024-02-29T23:59:59.999'), ], ])('%s', async (value: MediaFilterCoreWhen | string, from: Date, to: Date) => { const cameraManager = createCameraManager(createCameraStore()); const viewManager = mock(); viewManager.getView.mockReturnValue(createView()); const controller = new MediaFilterController(createLitElement()); controller.setViewManager(viewManager); await controller.valueChangeHandler( cameraManager, mock(), {}, { mediaTypes: [MediaFilterMediaType.Recordings], when: { selected: value }, }, ); const nodes = getQueryNodes(viewManager); expect(nodes?.[0]).toMatchObject({ type: QueryType.Recording, cameraIDs: new Set(['camera.kitchen']), start: from, end: to, }); }); it('custom without values', async () => { const cameraManager = createCameraManager(createCameraStore()); const viewManager = mock(); viewManager.getView.mockReturnValue(createView()); const controller = new MediaFilterController(createLitElement()); controller.setViewManager(viewManager); await controller.valueChangeHandler( cameraManager, mock(), {}, { mediaTypes: [MediaFilterMediaType.Recordings], when: { selected: MediaFilterCoreWhen.Custom }, }, ); const nodes = getQueryNodes(viewManager); expect(nodes?.[0]).toMatchObject({ type: QueryType.Recording, cameraIDs: new Set(['camera.kitchen']), }); expect(nodes?.[0].start).toBeUndefined(); expect(nodes?.[0].end).toBeUndefined(); }); }); }); describe('should calculate correct defaults', () => { it('with no query', () => { const cameraManager = createCameraManager(createCameraStore()); const viewManager = mock(); viewManager.getView.mockReturnValue(createView()); const controller = new MediaFilterController(createLitElement()); controller.setViewManager(viewManager); controller.computeInitialDefaultsFromView(cameraManager, mock()); expect(controller.getDefaults()).toBeNull(); }); it('with empty query', () => { const cameraManager = createCameraManager(createCameraStore()); const viewManager = mock(); viewManager.getView.mockReturnValue(createView({ query: new UnifiedQuery() })); const controller = new MediaFilterController(createLitElement()); controller.setViewManager(viewManager); controller.computeInitialDefaultsFromView(cameraManager, mock()); expect(controller.getDefaults()).toBeNull(); }); it('with no cameras', () => { const viewManager = mock(); viewManager.getView.mockReturnValue(createView()); const controller = new MediaFilterController(createLitElement()); controller.setViewManager(viewManager); controller.computeInitialDefaultsFromView( createCameraManager(), mock(), ); expect(controller.getDefaults()).toBeNull(); }); describe('for queries', () => { it.each(queryDefaultTestCases)( '%s', (_name: string, query: UnifiedQuery, expected: MediaFilterCoreDefaults) => { const cameraManager = createCameraManager(createCameraStore()); const viewManager = mock(); viewManager.getView.mockReturnValue( createView({ camera: 'camera.kitchen', query: query, }), ); const controller = new MediaFilterController(createLitElement()); controller.setViewManager(viewManager); controller.computeInitialDefaultsFromView( cameraManager, mock(), ); expect(controller.getDefaults()).toEqual(expected); }, ); }); }); });