Files
advanced-camera-card/tests/components-lib/media-filter-controller.test.ts
T

1069 lines
33 KiB
TypeScript

import { endOfDay, startOfDay, sub } from 'date-fns';
import {
afterAll,
assert,
beforeAll,
beforeEach,
describe,
expect,
it,
vi,
} from 'vitest';
import { mock, MockProxy } from 'vitest-mock-extended';
import { Capabilities } from '../../src/camera-manager/capabilities';
import {
EventQuery,
MediaQuery,
QueryType,
RecordingQuery,
ReviewQuery,
} from '../../src/camera-manager/types';
import { FoldersManager } from '../../src/card-controller/folders/manager';
import { ViewManager } from '../../src/card-controller/view/view-manager';
import {
MediaFilterController,
MediaFilterCoreDefaults,
MediaFilterCoreFavoriteSelection,
MediaFilterCoreReviewedSelection,
MediaFilterCoreWhen,
MediaFilterMediaType,
} from '../../src/components-lib/media-filter-controller';
import { QuerySource } from '../../src/query-source';
import { Severity } from '../../src/severity';
import { UnifiedQuery } from '../../src/view/unified-query';
import {
createCameraConfig,
createCameraManager,
createLitElement,
createPerformanceConfig,
createStore,
createView,
} from '../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<ViewManager>, n = 0): MediaQuery[] => {
const query =
viewManager.setViewByParametersWithExistingQuery.mock.calls[n]?.[0]?.params?.query;
return query ? query.getMediaQueries() : [];
};
type TestQueryNode = (
| Partial<Omit<EventQuery, 'source' | 'cameraIDs'>>
| Partial<Omit<ReviewQuery, 'source' | 'cameraIDs'>>
| Partial<Omit<RecordingQuery, 'source' | 'cameraIDs'>>
) & {
type: QueryType;
cameraID?: string;
cameraIDs?: Set<string>;
};
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<Severity>(['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<FoldersManager>());
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<FoldersManager>());
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<FoldersManager>());
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.toBeCalled();
});
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).toBeCalled();
});
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).toBeCalled();
});
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).toBeCalled();
});
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).toBeCalled();
});
});
});
describe('should handle value change', () => {
it('must have visible cameras', async () => {
const viewManager = mock<ViewManager>();
const controller = new MediaFilterController(createLitElement());
controller.setViewManager(viewManager);
await controller.valueChangeHandler(
createCameraManager(),
mock<FoldersManager>(),
{},
{ when: {} },
);
expect(viewManager.setViewByParametersWithExistingQuery).not.toBeCalled();
});
describe('with events media type', () => {
it.each([MediaFilterMediaType.Clips, MediaFilterMediaType.Snapshots])(
'%s',
async (mediaType: MediaFilterMediaType) => {
const host = createLitElement();
const viewManager = mock<ViewManager>();
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<FoldersManager>(),
{
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).toBeCalledWith({
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).toBeCalled();
},
);
});
it('with recordings media type', async () => {
const host = createLitElement();
const cameraManager = createCameraManager(createCameraStore());
const viewManager = mock<ViewManager>();
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<FoldersManager>(),
{
performance: createPerformanceConfig({
features: { media_chunk_size: 11 },
}),
},
{
mediaTypes: [MediaFilterMediaType.Recordings],
when: { to, from },
favorite: MediaFilterCoreFavoriteSelection.Favorite,
},
);
expect(viewManager.setViewByParametersWithExistingQuery).toBeCalledWith({
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).toBeCalled();
});
it('with reviews media type', async () => {
const host = createLitElement();
const cameraManager = createCameraManager(createCameraStore());
const viewManager = mock<ViewManager>();
viewManager.getView.mockReturnValue(createView());
const controller = new MediaFilterController(host);
controller.setViewManager(viewManager);
await controller.valueChangeHandler(
cameraManager,
mock<FoldersManager>(),
{},
{
mediaTypes: [MediaFilterMediaType.Reviews],
when: {},
reviewed: MediaFilterCoreReviewedSelection.NotReviewed,
},
);
expect(viewManager.setViewByParametersWithExistingQuery).toBeCalled();
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>();
viewManager.getView.mockReturnValue(createView());
const controller = new MediaFilterController(host);
controller.setViewManager(viewManager);
await controller.valueChangeHandler(
cameraManager,
mock<FoldersManager>(),
{},
{
mediaTypes: [MediaFilterMediaType.Reviews],
when: {},
severity: ['high'],
},
);
expect(viewManager.setViewByParametersWithExistingQuery).toBeCalled();
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>();
viewManager.getView.mockReturnValue(createView());
const controller = new MediaFilterController(host);
controller.setViewManager(viewManager);
await controller.valueChangeHandler(
cameraManager,
mock<FoldersManager>(),
{},
{
mediaTypes: [MediaFilterMediaType.Clips, MediaFilterMediaType.Recordings],
when: {},
},
);
expect(viewManager.setViewByParametersWithExistingQuery).toBeCalled();
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>();
viewManager.getView.mockReturnValue(createView());
const controller = new MediaFilterController(createLitElement());
controller.setViewManager(viewManager);
await controller.valueChangeHandler(
cameraManager,
mock<FoldersManager>(),
{},
{
mediaTypes: [MediaFilterMediaType.Clips],
when: {},
favorite: MediaFilterCoreFavoriteSelection.Favorite,
},
);
expect(viewManager.setViewByParametersWithExistingQuery).toBeCalledWith({
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>();
viewManager.getView.mockReturnValue(createView());
const controller = new MediaFilterController(createLitElement());
controller.setViewManager(viewManager);
await controller.valueChangeHandler(
cameraManager,
mock<FoldersManager>(),
{},
{
mediaTypes: [MediaFilterMediaType.Recordings],
when: {},
},
);
expect(viewManager.setViewByParametersWithExistingQuery).toBeCalledWith({
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>();
viewManager.getView.mockReturnValue(createView());
const controller = new MediaFilterController(createLitElement());
controller.setViewManager(viewManager);
await controller.valueChangeHandler(
cameraManager,
mock<FoldersManager>(),
{},
{
mediaTypes: [],
when: {},
},
);
expect(viewManager.setViewByParametersWithExistingQuery).toBeCalled();
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>();
viewManager.getView.mockReturnValue(createView());
const controller = new MediaFilterController(createLitElement());
controller.setViewManager(viewManager);
await controller.valueChangeHandler(
cameraManager,
mock<FoldersManager>(),
{},
{
camera: ['camera.kitchen', 'camera.living_room'],
mediaTypes: [MediaFilterMediaType.Clips],
when: {},
},
);
expect(viewManager.setViewByParametersWithExistingQuery).toBeCalledWith({
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<FoldersManager>();
const viewManager = mock<ViewManager>();
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>();
viewManager.getView.mockReturnValue(createView());
const controller = new MediaFilterController(createLitElement());
controller.setViewManager(viewManager);
await controller.valueChangeHandler(
cameraManager,
mock<FoldersManager>(),
{},
{
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>();
viewManager.getView.mockReturnValue(createView());
const controller = new MediaFilterController(createLitElement());
controller.setViewManager(viewManager);
await controller.valueChangeHandler(
cameraManager,
mock<FoldersManager>(),
{},
{
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>();
viewManager.getView.mockReturnValue(createView());
const controller = new MediaFilterController(createLitElement());
controller.setViewManager(viewManager);
controller.computeInitialDefaultsFromView(cameraManager, mock<FoldersManager>());
expect(controller.getDefaults()).toBeNull();
});
it('with empty query', () => {
const cameraManager = createCameraManager(createCameraStore());
const viewManager = mock<ViewManager>();
viewManager.getView.mockReturnValue(createView({ query: new UnifiedQuery() }));
const controller = new MediaFilterController(createLitElement());
controller.setViewManager(viewManager);
controller.computeInitialDefaultsFromView(cameraManager, mock<FoldersManager>());
expect(controller.getDefaults()).toBeNull();
});
it('with no cameras', () => {
const viewManager = mock<ViewManager>();
viewManager.getView.mockReturnValue(createView());
const controller = new MediaFilterController(createLitElement());
controller.setViewManager(viewManager);
controller.computeInitialDefaultsFromView(
createCameraManager(),
mock<FoldersManager>(),
);
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>();
viewManager.getView.mockReturnValue(
createView({
camera: 'camera.kitchen',
query: query,
}),
);
const controller = new MediaFilterController(createLitElement());
controller.setViewManager(viewManager);
controller.computeInitialDefaultsFromView(
cameraManager,
mock<FoldersManager>(),
);
expect(controller.getDefaults()).toEqual(expected);
},
);
});
});
});