feat: Implement basic general folder support (#2051)

- Related: #1748
This commit is contained in:
Dermot Duffy
2025-05-21 19:59:21 -07:00
committed by GitHub
parent 2eb0d9e35e
commit c6a4c8aea2
350 changed files with 12837 additions and 4509 deletions
+76
View File
@@ -0,0 +1,76 @@
import { describe, expect, it } from 'vitest';
import { ViewFolder, ViewMediaType } from '../../src/view/item';
import { ViewItemClassifier } from '../../src/view/item-classifier';
import { createFolder, TestViewMedia } from '../test-utils';
describe('ViewItemClassifier', () => {
it('isMedia', () => {
expect(ViewItemClassifier.isMedia(new TestViewMedia())).toBe(true);
expect(ViewItemClassifier.isMedia(new ViewFolder(createFolder()))).toBe(false);
});
it('isFolder', () => {
expect(ViewItemClassifier.isFolder(new ViewFolder(createFolder()))).toBe(true);
expect(ViewItemClassifier.isFolder(new TestViewMedia())).toBe(false);
});
describe('isEvent', () => {
it.each([
[ViewMediaType.Clip as const, true],
[ViewMediaType.Snapshot as const, true],
[ViewMediaType.Recording as const, false],
])('%s', (mediaType: ViewMediaType, expectedResult: boolean) => {
expect(
ViewItemClassifier.isEvent(new TestViewMedia({ mediaType: mediaType })),
).toBe(expectedResult);
});
});
describe('isRecording', () => {
it.each([
[ViewMediaType.Clip as const, false],
[ViewMediaType.Snapshot as const, false],
[ViewMediaType.Recording as const, true],
])('%s', (mediaType: ViewMediaType, expectedResult: boolean) => {
expect(
ViewItemClassifier.isRecording(new TestViewMedia({ mediaType: mediaType })),
).toBe(expectedResult);
});
});
describe('isClip', () => {
it.each([
[ViewMediaType.Clip as const, true],
[ViewMediaType.Snapshot as const, false],
[ViewMediaType.Recording as const, false],
])('%s', (mediaType: ViewMediaType, expectedResult: boolean) => {
expect(
ViewItemClassifier.isClip(new TestViewMedia({ mediaType: mediaType })),
).toBe(expectedResult);
});
});
describe('isSnapshot', () => {
it.each([
[ViewMediaType.Clip as const, false],
[ViewMediaType.Snapshot as const, true],
[ViewMediaType.Recording as const, false],
])('%s', (mediaType: ViewMediaType, expectedResult: boolean) => {
expect(
ViewItemClassifier.isSnapshot(new TestViewMedia({ mediaType: mediaType })),
).toBe(expectedResult);
});
});
describe('isVideo', () => {
it.each([
[ViewMediaType.Clip as const, true],
[ViewMediaType.Snapshot as const, false],
[ViewMediaType.Recording as const, true],
])('%s', (mediaType: ViewMediaType, expectedResult: boolean) => {
expect(
ViewItemClassifier.isVideo(new TestViewMedia({ mediaType: mediaType })),
).toBe(expectedResult);
});
});
});
@@ -1,6 +1,20 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { VideoContentType, ViewMedia } from '../../src/view/media';
import { TestViewMedia } from '../test-utils';
import {
VideoContentType,
ViewFolder,
ViewMedia,
ViewMediaType,
} from '../../src/view/item';
import { createFolder, TestViewMedia } from '../test-utils';
describe('VideoContentType', () => {
it('MP4', () => {
expect(VideoContentType.MP4).toBe('mp4');
});
it('HLS', () => {
expect(VideoContentType.HLS).toBe('hls');
});
});
describe('ViewMedia', () => {
beforeEach(() => {
@@ -8,7 +22,9 @@ describe('ViewMedia', () => {
});
it('should construct', () => {
const media = new ViewMedia('clip', 'camera');
const media = new ViewMedia(ViewMediaType.Clip, {
cameraID: 'camera',
});
expect(media.getCameraID()).toBe('camera');
expect(media.getMediaType()).toBe('clip');
expect(media.getVideoContentType()).toBeNull();
@@ -25,6 +41,7 @@ describe('ViewMedia', () => {
expect(media.getWhere()).toBeNull();
expect(media.setFavorite(true)).toBeUndefined();
expect(media.isFavorite()).toBeNull();
expect(media.getIcon()).toBeNull();
});
it('should correctly determine if a media item includes a time', () => {
@@ -50,11 +67,21 @@ describe('ViewMedia', () => {
});
});
describe('VideoContentType', () => {
it('MP4', () => {
expect(VideoContentType.MP4).toBe('mp4');
});
it('HLS', () => {
expect(VideoContentType.HLS).toBe('hls');
describe('ViewFolder', () => {
it('should construct', () => {
const folder = createFolder();
const item = new ViewFolder(folder, {
icon: 'icon',
id: 'id',
title: 'title',
thumbnail: 'thumbnail',
});
expect(item.getFolder()).toEqual(folder);
expect(item.getID()).toBe('id');
expect(item.getTitle()).toBe('title');
expect(item.getThumbnail()).toBe('thumbnail');
expect(item.getIcon()).toBe('icon');
expect(item.isFavorite()).toBeNull();
});
});
-66
View File
@@ -1,66 +0,0 @@
import { describe, expect, it } from 'vitest';
import { ViewMediaType } from '../../src/view/media';
import { ViewMediaClassifier } from '../../src/view/media-classifier';
import { TestViewMedia } from '../test-utils';
describe('ViewMediaClassifier', () => {
describe('isEvent', () => {
it.each([
['clip' as const, true],
['snapshot' as const, true],
['recording' as const, false],
])('%s', (mediaType: ViewMediaType, expectedResult: boolean) => {
expect(
ViewMediaClassifier.isEvent(new TestViewMedia({ mediaType: mediaType })),
).toBe(expectedResult);
});
});
describe('isRecording', () => {
it.each([
['clip' as const, false],
['snapshot' as const, false],
['recording' as const, true],
])('%s', (mediaType: ViewMediaType, expectedResult: boolean) => {
expect(
ViewMediaClassifier.isRecording(new TestViewMedia({ mediaType: mediaType })),
).toBe(expectedResult);
});
});
describe('isClip', () => {
it.each([
['clip' as const, true],
['snapshot' as const, false],
['recording' as const, false],
])('%s', (mediaType: ViewMediaType, expectedResult: boolean) => {
expect(
ViewMediaClassifier.isClip(new TestViewMedia({ mediaType: mediaType })),
).toBe(expectedResult);
});
});
describe('isSnapshot', () => {
it.each([
['clip' as const, false],
['snapshot' as const, true],
['recording' as const, false],
])('%s', (mediaType: ViewMediaType, expectedResult: boolean) => {
expect(
ViewMediaClassifier.isSnapshot(new TestViewMedia({ mediaType: mediaType })),
).toBe(expectedResult);
});
});
describe('isVideo', () => {
it.each([
['clip' as const, true],
['snapshot' as const, false],
['recording' as const, true],
])('%s', (mediaType: ViewMediaType, expectedResult: boolean) => {
expect(
ViewMediaClassifier.isVideo(new TestViewMedia({ mediaType: mediaType })),
).toBe(expectedResult);
});
});
});
@@ -1,52 +1,50 @@
import { describe, expect, it } from 'vitest';
import { EventMediaQueries, RecordingMediaQueries } from '../../src/view/media-queries';
import { MediaQueriesClassifier } from '../../src/view/media-queries-classifier';
import { QueryType } from '../../src/camera-manager/types';
import {
EventMediaQuery,
FolderViewQuery,
RecordingMediaQuery,
} from '../../src/view/query';
import { QueryClassifier } from '../../src/view/query-classifier';
import { createFolder } from '../test-utils';
describe('MediaQueriesClassifier', () => {
it('areEventQueries', () => {
expect(MediaQueriesClassifier.areEventQueries(new EventMediaQueries())).toBeTruthy();
expect(
MediaQueriesClassifier.areEventQueries(new RecordingMediaQueries()),
).toBeFalsy();
describe('QueryClassifier', () => {
it('isEventQuery', () => {
expect(QueryClassifier.isEventQuery(new EventMediaQuery())).toBeTruthy();
expect(QueryClassifier.isEventQuery(new RecordingMediaQuery())).toBeFalsy();
});
it('areRecordingQueries', () => {
expect(
MediaQueriesClassifier.areRecordingQueries(new RecordingMediaQueries()),
).toBeTruthy();
expect(
MediaQueriesClassifier.areRecordingQueries(new EventMediaQueries()),
).toBeFalsy();
it('isRecordingQuery', () => {
expect(QueryClassifier.isRecordingQuery(new RecordingMediaQuery())).toBeTruthy();
expect(QueryClassifier.isRecordingQuery(new EventMediaQuery())).toBeFalsy();
});
it('getQueriesType', () => {
expect(MediaQueriesClassifier.getQueriesType(new EventMediaQueries())).toBe('event');
expect(MediaQueriesClassifier.getQueriesType(new RecordingMediaQueries())).toBe(
'recording',
);
expect(MediaQueriesClassifier.getQueriesType()).toBeNull();
it('getQueryType', () => {
expect(QueryClassifier.getQueryType(new EventMediaQuery())).toBe('event');
expect(QueryClassifier.getQueryType(new FolderViewQuery())).toBe('folder');
expect(QueryClassifier.getQueryType(new RecordingMediaQuery())).toBe('recording');
expect(QueryClassifier.getQueryType()).toBeNull();
});
it('areClipsQueries', () => {
it('isClipsQuery', () => {
expect(
MediaQueriesClassifier.areClipsQueries(
new EventMediaQueries([
QueryClassifier.isClipsQuery(
new EventMediaQuery([
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasClip: true },
]),
),
).toBeTruthy();
expect(
MediaQueriesClassifier.areClipsQueries(
new EventMediaQueries([
QueryClassifier.isClipsQuery(
new EventMediaQuery([
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasClip: true },
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasClip: false },
]),
),
).toBeFalsy();
expect(
MediaQueriesClassifier.areClipsQueries(
new EventMediaQueries([
QueryClassifier.isClipsQuery(
new EventMediaQuery([
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasClip: true },
{ type: QueryType.Event, cameraIDs: new Set(['camera']) },
]),
@@ -54,25 +52,25 @@ describe('MediaQueriesClassifier', () => {
).toBeFalsy();
});
it('areSnapshotQueries', () => {
it('isSnapshotQuery', () => {
expect(
MediaQueriesClassifier.areSnapshotQueries(
new EventMediaQueries([
QueryClassifier.isSnapshotQuery(
new EventMediaQuery([
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasSnapshot: true },
]),
),
).toBeTruthy();
expect(
MediaQueriesClassifier.areSnapshotQueries(
new EventMediaQueries([
QueryClassifier.isSnapshotQuery(
new EventMediaQuery([
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasSnapshot: true },
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasSnapshot: false },
]),
),
).toBeFalsy();
expect(
MediaQueriesClassifier.areSnapshotQueries(
new EventMediaQueries([
QueryClassifier.isSnapshotQuery(
new EventMediaQuery([
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasSnapshot: true },
{ type: QueryType.Event, cameraIDs: new Set(['camera']) },
]),
@@ -80,10 +78,30 @@ describe('MediaQueriesClassifier', () => {
).toBeFalsy();
});
it('isFolderQuery', () => {
expect(
QueryClassifier.isFolderQuery(
new EventMediaQuery([
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasSnapshot: true },
]),
),
).toBeFalsy();
expect(
QueryClassifier.isFolderQuery(
new RecordingMediaQuery([
{ type: QueryType.Recording, cameraIDs: new Set(['camera']) },
]),
),
).toBeFalsy();
expect(
QueryClassifier.isFolderQuery(new FolderViewQuery({ folder: createFolder() })),
).toBeTruthy();
});
it('getMediaType', () => {
expect(
MediaQueriesClassifier.getMediaType(
new EventMediaQueries([
QueryClassifier.getMediaType(
new EventMediaQuery([
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasSnapshot: true },
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasSnapshot: true },
]),
@@ -91,8 +109,8 @@ describe('MediaQueriesClassifier', () => {
).toBe('snapshots');
expect(
MediaQueriesClassifier.getMediaType(
new EventMediaQueries([
QueryClassifier.getMediaType(
new EventMediaQuery([
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasClip: true },
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasClip: true },
]),
@@ -100,8 +118,8 @@ describe('MediaQueriesClassifier', () => {
).toBe('clips');
expect(
MediaQueriesClassifier.getMediaType(
new RecordingMediaQueries([
QueryClassifier.getMediaType(
new RecordingMediaQuery([
{ type: QueryType.Recording, cameraIDs: new Set(['camera']) },
{ type: QueryType.Recording, cameraIDs: new Set(['camera']) },
]),
@@ -109,8 +127,8 @@ describe('MediaQueriesClassifier', () => {
).toBe('recordings');
expect(
MediaQueriesClassifier.getMediaType(
new EventMediaQueries([
QueryClassifier.getMediaType(
new EventMediaQuery([
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasSnapshot: true },
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasSnapshot: false },
]),
@@ -1,7 +1,7 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { ViewMedia } from '../../src/view/media';
import { MediaQueriesResults } from '../../src/view/media-queries-results';
import { generateViewMediaArray } from '../test-utils';
import { ViewFolder, ViewItem } from '../../src/view/item';
import { QueryResults } from '../../src/view/query-results';
import { createFolder, generateViewMediaArray } from '../test-utils';
describe('dispatchViewContextChangeEvent', () => {
beforeEach(() => {
@@ -13,7 +13,7 @@ describe('dispatchViewContextChangeEvent', () => {
vi.useFakeTimers();
vi.setSystemTime(fakeNow);
const results = new MediaQueriesResults();
const results = new QueryResults();
expect(results.isSupersetOf(results)).toBeFalsy();
expect(results.getCameraIDs()).toEqual(new Set());
expect(results.getResults()).toEqual([]);
@@ -31,18 +31,18 @@ describe('dispatchViewContextChangeEvent', () => {
expect(results.getSelectedResult()).toBeNull();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
expect(results.selectResultIfFound((_media: ViewMedia) => true)).toEqual(results);
expect(results.selectResultIfFound((_item: ViewItem) => true)).toEqual(results);
expect(results.getSelectedResult()).toBeNull();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
expect(results.selectBestResult((_media: ViewMedia[]) => null)).toEqual(results);
expect(results.selectBestResult((_item: ViewItem[]) => null)).toEqual(results);
expect(results.getSelectedResult()).toBeNull();
expect(results.getMultipleSelectedResults()).toEqual([]);
});
it('should function with basic results', () => {
const testResults = generateViewMediaArray();
const results = new MediaQueriesResults({ results: testResults });
const results = new QueryResults({ results: testResults });
expect(results.isSupersetOf(results)).toBeTruthy();
expect(results.getCameraIDs()).toEqual(new Set(['kitchen', 'office']));
@@ -61,15 +61,13 @@ describe('dispatchViewContextChangeEvent', () => {
expect(results.getSelectedIndex()).toBe(100);
expect(
results.selectResultIfFound(
(media: ViewMedia) => media.getID() === 'id-kitchen-42',
),
results.selectResultIfFound((item: ViewItem) => item.getID() === 'id-kitchen-42'),
).toEqual(results);
expect(results.getSelectedResult()?.getID()).toBe('id-kitchen-42');
expect(
results.selectBestResult((mediaArray: ViewMedia[]) =>
mediaArray.findIndex((media) => media.getID() === 'id-kitchen-43'),
results.selectBestResult((itemArray: ViewItem[]) =>
itemArray.findIndex((item) => item.getID() === 'id-kitchen-43'),
),
).toEqual(results);
expect(results.getSelectedResult()?.getID()).toBe('id-kitchen-43');
@@ -77,11 +75,11 @@ describe('dispatchViewContextChangeEvent', () => {
it('should function with camera slice', () => {
const testResults = generateViewMediaArray();
const results = new MediaQueriesResults({ results: testResults });
const results = new QueryResults({ results: testResults });
const slice = results.getSlice('office');
expect(slice).not.toBeNull();
expect(slice!.getResults()).toEqual(
testResults.filter((media) => media.getCameraID() === 'office'),
testResults.filter((item) => item.getCameraID() === 'office'),
);
expect(slice!.getResultsCount()).toEqual(100);
expect(slice!.hasResults()).toBeTruthy();
@@ -103,11 +101,11 @@ describe('dispatchViewContextChangeEvent', () => {
expect(slice!.selectIndex(-10000));
expect(slice!.getSelectedIndex()).toBe(10);
slice!.selectResultIfFound((media: ViewMedia) => media.getID() === 'id-office-42');
slice!.selectResultIfFound((item: ViewItem) => item.getID() === 'id-office-42');
expect(slice!.getSelectedResult()?.getID()).toBe('id-office-42');
slice!.selectBestResult((mediaArray: ViewMedia[]) =>
mediaArray.findIndex((media) => media.getID() === 'id-office-43'),
slice!.selectBestResult((itemArray: ViewItem[]) =>
itemArray.findIndex((item) => item.getID() === 'id-office-43'),
);
expect(slice!.getSelectedResult()?.getID()).toBe('id-office-43');
});
@@ -117,7 +115,7 @@ describe('dispatchViewContextChangeEvent', () => {
['first' as const, 0],
['last' as const, 199],
])('%s', async (selectApproach, expectedIndex) => {
const results = new MediaQueriesResults({
const results = new QueryResults({
results: generateViewMediaArray(),
selectApproach: selectApproach,
});
@@ -126,7 +124,7 @@ describe('dispatchViewContextChangeEvent', () => {
});
it('should respect selectIndex during construction', () => {
const results = new MediaQueriesResults({
const results = new QueryResults({
results: generateViewMediaArray(),
selectedIndex: 42,
});
@@ -134,7 +132,7 @@ describe('dispatchViewContextChangeEvent', () => {
});
it('should correctly clone a slice', () => {
const results = new MediaQueriesResults({
const results = new QueryResults({
results: generateViewMediaArray(),
});
const slice = results.getSlice('office');
@@ -144,21 +142,21 @@ describe('dispatchViewContextChangeEvent', () => {
});
it('should not get slice for non-existent camera', () => {
const results = new MediaQueriesResults({
const results = new QueryResults({
results: generateViewMediaArray(),
});
expect(results.getSlice('not-a-camera')).toBeNull();
});
it('should get main slice', () => {
const results = new MediaQueriesResults({
const results = new QueryResults({
results: generateViewMediaArray(),
});
expect(results.getSlice()?.getResults()).toBe(results.getResults());
});
it('should correctly clone', () => {
const results = new MediaQueriesResults({
const results = new QueryResults({
results: generateViewMediaArray(),
});
const clone = results.clone();
@@ -172,7 +170,7 @@ describe('dispatchViewContextChangeEvent', () => {
});
it('should not getResults on invalid slice', () => {
const results = new MediaQueriesResults({
const results = new QueryResults({
results: generateViewMediaArray(),
});
expect(results.getResults('not-a-camera')).toBeNull();
@@ -181,33 +179,33 @@ describe('dispatchViewContextChangeEvent', () => {
});
it('should always demote main selection', () => {
const results = new MediaQueriesResults({
const results = new QueryResults({
results: generateViewMediaArray(),
});
results
.getSlice('office')
?.selectResultIfFound((media) => media.getID() === 'id-office-42');
?.selectResultIfFound((item) => item.getID() === 'id-office-42');
// Verify main and office selections are as expected.
expect(results.getSelectedIndex()).toBe(199);
expect(results.getSelectedResult('office')?.getID()).toBe('id-office-42');
// Select a different main result...
results?.selectResultIfFound((media) => media.getID() === 'id-office-80');
results?.selectResultIfFound((item) => item.getID() === 'id-office-80');
// ... and ensure that selection has been demoted into the camera slice.
expect(results.getSelectedResult('office')?.getID()).toBe('id-office-80');
});
it('should promote camera selection', () => {
const results = new MediaQueriesResults({
const results = new QueryResults({
results: generateViewMediaArray(),
});
results
.getSlice('office')
?.selectResultIfFound((media) => media.getID() === 'id-office-42');
?.selectResultIfFound((item) => item.getID() === 'id-office-42');
expect(results.getSelectedIndex()).toBe(199);
@@ -218,13 +216,13 @@ describe('dispatchViewContextChangeEvent', () => {
});
it('should selectBestResult via advanced selection criteria', () => {
const results = new MediaQueriesResults({
const results = new QueryResults({
results: generateViewMediaArray(),
});
results.selectBestResult(
(mediaArray: ViewMedia[]) => {
const index = mediaArray.findIndex((media) => media.getID()?.endsWith('-42'));
(itemArray: ViewItem[]) => {
const index = itemArray.findIndex((item) => item.getID()?.endsWith('-42'));
return index < 0 ? null : index;
},
{ allCameras: true },
@@ -235,43 +233,43 @@ describe('dispatchViewContextChangeEvent', () => {
});
it('should get multiple selected results', () => {
const results = new MediaQueriesResults({
const results = new QueryResults({
results: generateViewMediaArray(),
});
expect(
results
.getMultipleSelectedResults({ main: true, allCameras: true })
.map((media) => media.getID()),
.map((item) => item.getID()),
).toEqual(['id-office-99', 'id-kitchen-99', 'id-office-99']);
});
it('should get multiple selected results without main', () => {
const results = new MediaQueriesResults({
const results = new QueryResults({
results: generateViewMediaArray(),
});
expect(
results
.getMultipleSelectedResults({ main: false, allCameras: true })
.map((media) => media.getID()),
.map((item) => item.getID()),
).toEqual(['id-kitchen-99', 'id-office-99']);
});
it('should get no results with invalid camera ID without main', () => {
const results = new MediaQueriesResults({
const results = new QueryResults({
results: generateViewMediaArray(),
});
expect(
results
.getMultipleSelectedResults({ main: false, cameraID: 'not-a-real-camera' })
.map((media) => media.getID()),
.map((item) => item.getID()),
).toEqual([]);
});
it('should not demote main selection when selecting from a specific camera', () => {
const results = new MediaQueriesResults({
const results = new QueryResults({
results: generateViewMediaArray(),
});
@@ -281,4 +279,20 @@ describe('dispatchViewContextChangeEvent', () => {
expect(results.getSelectedIndex()).toBe(42);
expect(results.getSelectedIndex('office')).toBe(24);
});
it('should keep selected folder in main slice but not camera slices', () => {
const folder = new ViewFolder(createFolder());
const results = new QueryResults({
results: [
folder,
...generateViewMediaArray({
cameraIDs: ['camera.office', 'camera.kitchen'],
}),
],
selectedIndex: 0,
});
expect(results.getSelectedResult()).toBe(folder);
expect(results.getSelectedResult('camera.office')).not.toBe(folder);
});
});
@@ -7,9 +7,14 @@ import {
RecordingQuery,
} from '../../src/camera-manager/types';
import { setify } from '../../src/utils/basic';
import { EventMediaQueries, RecordingMediaQueries } from '../../src/view/media-queries';
import {
EventMediaQuery,
FolderViewQuery,
RecordingMediaQuery,
} from '../../src/view/query';
import { createFolder } from '../test-utils';
describe('EventMediaQueries', () => {
describe('EventMediaQuery', () => {
const createRawEventQueries = (
cameraIDs: string | Set<string>,
query?: PartialEventQuery,
@@ -25,35 +30,33 @@ describe('EventMediaQueries', () => {
it('should construct', () => {
const rawQueries = createRawEventQueries('office');
const queries = new EventMediaQueries(rawQueries);
expect(queries.getQueries()).toBe(rawQueries);
const query = new EventMediaQuery(rawQueries);
expect(query.getQuery()).toBe(rawQueries);
});
it('should set', () => {
const rawQueries = createRawEventQueries('office');
const queries = new EventMediaQueries(rawQueries);
const query = new EventMediaQuery(rawQueries);
const newRawQueries = createRawEventQueries('kitchen');
queries.setQueries(newRawQueries);
expect(queries.getQueries()).toBe(newRawQueries);
query.setQuery(newRawQueries);
expect(query.getQuery()).toBe(newRawQueries);
});
it('should determine if queries exist for CameraIDs', () => {
const rawQueries = createRawEventQueries(new Set(['office', 'kitchen']));
const queries = new EventMediaQueries(rawQueries);
const query = new EventMediaQuery(rawQueries);
expect(queries.hasQueriesForCameraIDs(new Set(['office']))).toBeTruthy();
expect(queries.hasQueriesForCameraIDs(new Set(['office', 'kitchen']))).toBeTruthy();
expect(
queries.hasQueriesForCameraIDs(new Set(['office', 'front_door'])),
).toBeFalsy();
expect(query.hasQueriesForCameraIDs(new Set(['office']))).toBeTruthy();
expect(query.hasQueriesForCameraIDs(new Set(['office', 'kitchen']))).toBeTruthy();
expect(query.hasQueriesForCameraIDs(new Set(['office', 'front_door']))).toBeFalsy();
});
it('should convert to clips querys', () => {
const rawQueries = createRawEventQueries('office', { hasSnapshot: true });
const queries = new EventMediaQueries(rawQueries);
const query = new EventMediaQuery(rawQueries);
expect(queries.convertToClipsQueries().getQueries()).toEqual([
expect(query.convertToClipsQueries().getQuery()).toEqual([
{
type: QueryType.Event,
cameraIDs: new Set(['office']),
@@ -63,43 +66,43 @@ describe('EventMediaQueries', () => {
});
it('should convert when queries are null', () => {
const queries = new EventMediaQueries();
expect(queries.convertToClipsQueries().getQueries()).toBeNull();
const query = new EventMediaQuery();
expect(query.convertToClipsQueries().getQuery()).toBeNull();
});
it('should clone', () => {
const rawQueries = createRawEventQueries('office', { hasSnapshot: true });
const queries = new EventMediaQueries(rawQueries);
expect(queries.clone().getQueries()).toEqual(queries.getQueries());
const query = new EventMediaQuery(rawQueries);
expect(query.clone().getQuery()).toEqual(query.getQuery());
});
it('should get camera IDs when queries are null', () => {
expect(new EventMediaQueries().getQueryCameraIDs()).toBeNull();
expect(new EventMediaQuery().getQueryCameraIDs()).toBeNull();
});
it('should get camera IDs', () => {
const cameraIDs = ['office', 'kitchen'];
const queries = new EventMediaQueries(createRawEventQueries(new Set(cameraIDs)));
expect(queries.getQueryCameraIDs()).toEqual(new Set(cameraIDs));
const query = new EventMediaQuery(createRawEventQueries(new Set(cameraIDs)));
expect(query.getQueryCameraIDs()).toEqual(new Set(cameraIDs));
});
it('should set camera IDs when queries are null', () => {
expect(
new EventMediaQueries().setQueryCameraIDs(new Set(['office'])).getQueryCameraIDs(),
new EventMediaQuery().setQueryCameraIDs(new Set(['office'])).getQueryCameraIDs(),
).toBeNull();
});
it('should set camera IDs', () => {
const queries = new EventMediaQueries(createRawEventQueries('sitting_room'));
const query = new EventMediaQuery(createRawEventQueries('sitting_room'));
const newCameraIDs = new Set(['office', 'kitchen']);
expect(queries.setQueryCameraIDs(newCameraIDs).getQueryCameraIDs()).toEqual(
expect(query.setQueryCameraIDs(newCameraIDs).getQueryCameraIDs()).toEqual(
newCameraIDs,
);
});
describe('should determine when queries are a superset', () => {
it('should return true with itself', () => {
const queries_1 = new EventMediaQueries([
const query_1 = new EventMediaQuery([
{
type: QueryType.Event,
cameraIDs: new Set(['office']),
@@ -107,11 +110,11 @@ describe('EventMediaQueries', () => {
end: new Date('2025-03-08T00:00:00.000Z'),
},
]);
expect(queries_1.isSupersetOf(queries_1)).toBeTruthy();
expect(query_1.isSupersetOf(query_1)).toBeTruthy();
});
it('should return true with an identical but shorter query', () => {
const queries_1 = new EventMediaQueries([
const query_1 = new EventMediaQuery([
{
type: QueryType.Event,
cameraIDs: new Set(['office']),
@@ -119,7 +122,7 @@ describe('EventMediaQueries', () => {
end: new Date('2025-03-08T00:00:00.000Z'),
},
]);
const queries_2 = new EventMediaQueries([
const query_2 = new EventMediaQuery([
{
type: QueryType.Event,
cameraIDs: new Set(['office']),
@@ -127,11 +130,11 @@ describe('EventMediaQueries', () => {
end: new Date('2025-03-07T23:00:00.000Z'),
},
]);
expect(queries_1.isSupersetOf(queries_2)).toBeTruthy();
expect(query_1.isSupersetOf(query_2)).toBeTruthy();
});
it('should return false with an identical but longer query', () => {
const queries_1 = new EventMediaQueries([
const query_1 = new EventMediaQuery([
{
type: QueryType.Event,
cameraIDs: new Set(['office']),
@@ -139,7 +142,7 @@ describe('EventMediaQueries', () => {
end: new Date('2025-03-08T00:00:00.000Z'),
},
]);
const queries_2 = new EventMediaQueries([
const query_2 = new EventMediaQuery([
{
type: QueryType.Event,
cameraIDs: new Set(['office']),
@@ -147,11 +150,11 @@ describe('EventMediaQueries', () => {
end: new Date('2025-03-08T00:00:00.000Z'),
},
]);
expect(queries_1.isSupersetOf(queries_2)).toBeFalsy();
expect(query_1.isSupersetOf(query_2)).toBeFalsy();
});
it('should return false with a non-matching query', () => {
const queries_1 = new EventMediaQueries([
const query_1 = new EventMediaQuery([
{
type: QueryType.Event,
cameraIDs: new Set(['office']),
@@ -159,7 +162,7 @@ describe('EventMediaQueries', () => {
end: new Date('2025-03-08T00:00:00.000Z'),
},
]);
const queries_2 = new EventMediaQueries([
const query_2 = new EventMediaQuery([
{
type: QueryType.Event,
cameraIDs: new Set(['DIFFERENT']),
@@ -167,11 +170,11 @@ describe('EventMediaQueries', () => {
end: new Date('2025-03-08T00:00:00.000Z'),
},
]);
expect(queries_1.isSupersetOf(queries_2)).toBeFalsy();
expect(query_1.isSupersetOf(query_2)).toBeFalsy();
});
it('should return true with a matching query where the source has multiple', () => {
const queries_1 = new EventMediaQueries([
const query_1 = new EventMediaQuery([
{
type: QueryType.Event,
cameraIDs: new Set(['office']),
@@ -185,7 +188,7 @@ describe('EventMediaQueries', () => {
end: new Date('2025-03-08T00:00:00.000Z'),
},
]);
const queries_2 = new EventMediaQueries([
const query_2 = new EventMediaQuery([
{
type: QueryType.Event,
cameraIDs: new Set(['office']),
@@ -193,11 +196,11 @@ describe('EventMediaQueries', () => {
end: new Date('2025-03-08T00:00:00.000Z'),
},
]);
expect(queries_1.isSupersetOf(queries_2)).toBeTruthy();
expect(query_1.isSupersetOf(query_2)).toBeTruthy();
});
it('should return false with a matching query where the target has multiple', () => {
const queries_1 = new EventMediaQueries([
const query_1 = new EventMediaQuery([
{
type: QueryType.Event,
cameraIDs: new Set(['office']),
@@ -205,7 +208,7 @@ describe('EventMediaQueries', () => {
end: new Date('2025-03-08T00:00:00.000Z'),
},
]);
const queries_2 = new EventMediaQueries([
const query_2 = new EventMediaQuery([
{
type: QueryType.Event,
cameraIDs: new Set(['office']),
@@ -219,50 +222,50 @@ describe('EventMediaQueries', () => {
end: new Date('2025-03-08T00:00:00.000Z'),
},
]);
expect(queries_1.isSupersetOf(queries_2)).toBeFalsy();
expect(query_1.isSupersetOf(query_2)).toBeFalsy();
});
it('should return true when queries do not have start or end', () => {
const queries_1 = new EventMediaQueries([
const query_1 = new EventMediaQuery([
{
type: QueryType.Event,
cameraIDs: new Set(['office']),
},
]);
const queries_2 = new EventMediaQueries([
const query_2 = new EventMediaQuery([
{
type: QueryType.Event,
cameraIDs: new Set(['office']),
},
]);
expect(queries_1.isSupersetOf(queries_2)).toBeTruthy();
expect(query_1.isSupersetOf(query_2)).toBeTruthy();
});
it('should return true when target has no queries', () => {
const queries_1 = new EventMediaQueries([
const query_1 = new EventMediaQuery([
{
type: QueryType.Event,
cameraIDs: new Set(['office']),
},
]);
const queries_2 = new EventMediaQueries();
expect(queries_1.isSupersetOf(queries_2)).toBeTruthy();
const query_2 = new EventMediaQuery();
expect(query_1.isSupersetOf(query_2)).toBeTruthy();
});
it('should return false when source has no queries', () => {
const queries_1 = new EventMediaQueries();
const queries_2 = new EventMediaQueries([
const query_1 = new EventMediaQuery();
const query_2 = new EventMediaQuery([
{
type: QueryType.Event,
cameraIDs: new Set(['office']),
},
]);
expect(queries_1.isSupersetOf(queries_2)).toBeFalsy();
expect(query_1.isSupersetOf(query_2)).toBeFalsy();
});
});
});
describe('RecordingMediaQueries', () => {
describe('RecordingMediaQuery', () => {
const createRawRecordingQueries = (
cameraIDs: string | Set<string>,
query?: PartialRecordingQuery,
@@ -278,7 +281,19 @@ describe('RecordingMediaQueries', () => {
it('should construct', () => {
const rawQueries = createRawRecordingQueries('office');
const queries = new RecordingMediaQueries(rawQueries);
expect(queries.getQueries()).toBe(rawQueries);
const query = new RecordingMediaQuery(rawQueries);
expect(query.getQuery()).toBe(rawQueries);
});
});
describe('FolderViewQuery', () => {
it('should construct', () => {
const rawQuery = {
folder: createFolder(),
path: 'path',
};
const query = new FolderViewQuery(rawQuery);
expect(query.getQuery()).toBe(rawQuery);
});
});
+18 -17
View File
@@ -1,12 +1,12 @@
import { describe, expect, it } from 'vitest';
import { EventMediaQueries } from '../../src/view/media-queries';
import { MediaQueriesResults } from '../../src/view/media-queries-results';
import { EventMediaQuery } from '../../src/view/query';
import { QueryResults } from '../../src/view/query-results';
import { createView } from '../test-utils';
describe('View Basics', () => {
it('should construct from parameters', () => {
const query = new EventMediaQueries();
const queryResults = new MediaQueriesResults();
const query = new EventMediaQuery();
const queryResults = new QueryResults();
const context = {};
const view = createView({
@@ -29,8 +29,8 @@ describe('View Basics', () => {
const view = createView({
view: 'live',
camera: 'camera',
query: new EventMediaQueries(),
queryResults: new MediaQueriesResults(),
query: new EventMediaQuery(),
queryResults: new QueryResults(),
context: {},
});
@@ -52,8 +52,8 @@ describe('View Basics', () => {
const view = createView({
view: 'live',
camera: 'camera-1',
query: new EventMediaQueries(),
queryResults: new MediaQueriesResults(),
query: new EventMediaQuery(),
queryResults: new QueryResults(),
context: {},
displayMode: 'single',
});
@@ -61,8 +61,8 @@ describe('View Basics', () => {
const evolved = view.evolve({
view: 'clips',
camera: 'camera-2',
query: new EventMediaQueries(),
queryResults: new MediaQueriesResults(),
query: new EventMediaQuery(),
queryResults: new QueryResults(),
context: {},
displayMode: 'grid',
});
@@ -78,8 +78,8 @@ describe('View Basics', () => {
const view = createView({
view: 'live',
camera: 'camera-1',
query: new EventMediaQueries(),
queryResults: new MediaQueriesResults(),
query: new EventMediaQuery(),
queryResults: new QueryResults(),
context: {},
});
@@ -154,14 +154,15 @@ describe('View Basics', () => {
});
it('should detect gallery views', () => {
expect(createView({ view: 'clips' }).isGalleryView()).toBeTruthy();
expect(createView({ view: 'snapshots' }).isGalleryView()).toBeTruthy();
expect(createView({ view: 'recordings' }).isGalleryView()).toBeTruthy();
expect(createView({ view: 'clips' }).isMediaGalleryView()).toBeTruthy();
expect(createView({ view: 'folder' }).isMediaGalleryView()).toBeTruthy();
expect(createView({ view: 'snapshots' }).isMediaGalleryView()).toBeTruthy();
expect(createView({ view: 'recordings' }).isMediaGalleryView()).toBeTruthy();
});
it('should not detect gallery view', () => {
expect(createView({ view: 'live' }).isGalleryView()).toBeFalsy();
expect(createView({ view: 'timeline' }).isGalleryView()).toBeFalsy();
expect(createView({ view: 'live' }).isMediaGalleryView()).toBeFalsy();
expect(createView({ view: 'timeline' }).isMediaGalleryView()).toBeFalsy();
});
it('should detect any media views', () => {