Refactor views to support dynamic updates.
This commit is contained in:
+21
-344
@@ -1,12 +1,8 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { QueryType } from '../../src/camera-manager/types';
|
||||
import { ViewMedia } from '../../src/view/media';
|
||||
import { EventMediaQueries, RecordingMediaQueries } from '../../src/view/media-queries';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { EventMediaQueries } from '../../src/view/media-queries';
|
||||
import { MediaQueriesResults } from '../../src/view/media-queries-results';
|
||||
import { View, dispatchViewContextChangeEvent } from '../../src/view/view';
|
||||
import { createView } from '../test-utils';
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('View Basics', () => {
|
||||
it('should construct from parameters', () => {
|
||||
const query = new EventMediaQueries();
|
||||
@@ -28,21 +24,28 @@ describe('View Basics', () => {
|
||||
expect(view.context).toBe(context);
|
||||
});
|
||||
|
||||
it('should clone', () => {
|
||||
const query = new EventMediaQueries();
|
||||
const queryResults = new MediaQueriesResults();
|
||||
const context = {};
|
||||
describe('should clone', () => {
|
||||
it('with query and queryResults', () => {
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
query: new EventMediaQueries(),
|
||||
queryResults: new MediaQueriesResults(),
|
||||
context: {},
|
||||
});
|
||||
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
query: query,
|
||||
queryResults: queryResults,
|
||||
context: context,
|
||||
expect(view.clone()).toEqual(view);
|
||||
});
|
||||
|
||||
const clone = view.clone();
|
||||
expect(clone).toEqual(view);
|
||||
it('without query and queryResults', () => {
|
||||
const view = createView({
|
||||
view: 'live',
|
||||
camera: 'camera',
|
||||
context: {},
|
||||
});
|
||||
|
||||
expect(view.clone()).toEqual(view);
|
||||
});
|
||||
});
|
||||
|
||||
it('should evolve with everything set', () => {
|
||||
@@ -201,316 +204,6 @@ describe('View Basics', () => {
|
||||
expect(createView({ view: 'timeline' }).getDefaultMediaType()).toBeNull();
|
||||
});
|
||||
|
||||
it('should dispatch view', () => {
|
||||
const element = document.createElement('div');
|
||||
const view = createView();
|
||||
const handler = vi.fn((ev) => {
|
||||
expect(ev.detail).toBe(view);
|
||||
});
|
||||
|
||||
element.addEventListener('frigate-card:view:change', handler);
|
||||
view.dispatchChangeEvent(element);
|
||||
expect(handler).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('View.isMajorMediaChange', () => {
|
||||
it('should consider undefined views as major', () => {
|
||||
expect(View.isMajorMediaChange(createView(), undefined)).toBeTruthy();
|
||||
expect(View.isMajorMediaChange(undefined, createView())).toBeTruthy();
|
||||
expect(View.isMajorMediaChange()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should consider view change as major', () => {
|
||||
expect(
|
||||
View.isMajorMediaChange(
|
||||
createView({ view: 'live' }),
|
||||
createView({ view: 'snapshots' }),
|
||||
),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should consider camera change as major', () => {
|
||||
expect(
|
||||
View.isMajorMediaChange(
|
||||
createView({ camera: 'camera-1' }),
|
||||
createView({ camera: 'camera-2' }),
|
||||
),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should consider live substream change as major in live view', () => {
|
||||
const overrides_1: Map<string, string> = new Map();
|
||||
overrides_1.set('camera', 'camera-2');
|
||||
|
||||
const overrides_2: Map<string, string> = new Map();
|
||||
overrides_2.set('camera', 'camera-3');
|
||||
|
||||
expect(
|
||||
View.isMajorMediaChange(
|
||||
createView({ context: { live: { overrides: overrides_1 } } }),
|
||||
createView({ context: { live: { overrides: overrides_2 } } }),
|
||||
),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not consider live substream change as major in other view', () => {
|
||||
const overrides_1: Map<string, string> = new Map();
|
||||
overrides_1.set('camera', 'camera-2');
|
||||
|
||||
const overrides_2: Map<string, string> = new Map();
|
||||
overrides_2.set('camera', 'camera-3');
|
||||
|
||||
expect(
|
||||
View.isMajorMediaChange(
|
||||
createView({ view: 'clips', context: { live: { overrides: overrides_1 } } }),
|
||||
createView({ view: 'clips', context: { live: { overrides: overrides_2 } } }),
|
||||
),
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should consider result change as major in other view', () => {
|
||||
const media = [new ViewMedia('clip', 'camera-1'), new ViewMedia('clip', 'camera-2')];
|
||||
const queryResults_1 = new MediaQueriesResults({ results: media, selectedIndex: 0 });
|
||||
const queryResults_2 = new MediaQueriesResults({ results: media, selectedIndex: 1 });
|
||||
expect(
|
||||
View.isMajorMediaChange(
|
||||
createView({ view: 'media', queryResults: queryResults_1 }),
|
||||
createView({ view: 'media', queryResults: queryResults_2 }),
|
||||
),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not consider selected result change as major in live view', () => {
|
||||
const media = [new ViewMedia('clip', 'camera-1'), new ViewMedia('clip', 'camera-2')];
|
||||
const queryResults_1 = new MediaQueriesResults({ results: media, selectedIndex: 0 });
|
||||
const queryResults_2 = new MediaQueriesResults({ results: media, selectedIndex: 1 });
|
||||
expect(
|
||||
View.isMajorMediaChange(
|
||||
createView({ queryResults: queryResults_1 }),
|
||||
createView({ queryResults: queryResults_2 }),
|
||||
),
|
||||
).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('View.adoptFromViewIfAppropriate', () => {
|
||||
it('should adopt for gallery case', () => {
|
||||
const query = new EventMediaQueries([
|
||||
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasClip: true },
|
||||
]);
|
||||
const queryResults = new MediaQueriesResults();
|
||||
|
||||
const current = createView({
|
||||
view: 'clip',
|
||||
query: query,
|
||||
queryResults: queryResults,
|
||||
});
|
||||
const next = createView({ view: 'clips' });
|
||||
View.adoptFromViewIfAppropriate(next, current);
|
||||
expect(next.view).toBe('clips');
|
||||
expect(next.query).toBe(query);
|
||||
expect(next.queryResults).toBe(queryResults);
|
||||
});
|
||||
|
||||
it('should not adopt for gallery case if neither query nor results in current view', () => {
|
||||
const current = createView({
|
||||
view: 'clip',
|
||||
query: null,
|
||||
queryResults: null,
|
||||
});
|
||||
|
||||
const nextQuery = new EventMediaQueries([
|
||||
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasClip: true },
|
||||
]);
|
||||
const nextResults = new MediaQueriesResults();
|
||||
const next = createView({
|
||||
view: 'clips',
|
||||
query: nextQuery,
|
||||
queryResults: nextResults,
|
||||
});
|
||||
View.adoptFromViewIfAppropriate(next, current);
|
||||
expect(next.view).toBe('clips');
|
||||
expect(next.query).toBe(nextQuery);
|
||||
expect(next.queryResults).toBe(nextResults);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[
|
||||
new EventMediaQueries([
|
||||
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasClip: true },
|
||||
]),
|
||||
'clip',
|
||||
],
|
||||
[
|
||||
new EventMediaQueries([
|
||||
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasSnapshot: true },
|
||||
]),
|
||||
'snapshot',
|
||||
],
|
||||
[
|
||||
new RecordingMediaQueries([
|
||||
{ type: QueryType.Recording, cameraIDs: new Set(['camera']) },
|
||||
]),
|
||||
'recording',
|
||||
],
|
||||
])('should adopt in media case', (mediaQueries, expectedView) => {
|
||||
const current = createView({
|
||||
view: 'media',
|
||||
query: mediaQueries,
|
||||
queryResults: new MediaQueriesResults(),
|
||||
});
|
||||
const next = createView({ view: 'media' });
|
||||
View.adoptFromViewIfAppropriate(next, current);
|
||||
expect(next.view).toBe(expectedView);
|
||||
expect(next.query).toBeFalsy();
|
||||
expect(next.queryResults).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not adopt for mixed queries in media case', () => {
|
||||
const query = new EventMediaQueries([
|
||||
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasClip: true },
|
||||
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasSnapshot: true },
|
||||
]);
|
||||
const results = new MediaQueriesResults();
|
||||
const current = createView({
|
||||
view: 'media',
|
||||
query: query,
|
||||
queryResults: results,
|
||||
});
|
||||
const next = createView({ view: 'media' });
|
||||
View.adoptFromViewIfAppropriate(next, current);
|
||||
|
||||
expect(next.view).toBe('media');
|
||||
expect(next.query).toBeNull();
|
||||
expect(next.queryResults).toBeNull();
|
||||
});
|
||||
|
||||
it('should not adopt when queries and results present in next view in media case', () => {
|
||||
const currentQuery = new EventMediaQueries([
|
||||
{ type: QueryType.Event, cameraIDs: new Set(['camera-1']), hasClip: true },
|
||||
{ type: QueryType.Event, cameraIDs: new Set(['camera-1']), hasSnapshot: true },
|
||||
]);
|
||||
const currentResults = new MediaQueriesResults();
|
||||
const current = createView({
|
||||
view: 'media',
|
||||
query: currentQuery,
|
||||
queryResults: currentResults,
|
||||
});
|
||||
|
||||
const nextQuery = new EventMediaQueries([
|
||||
{ type: QueryType.Event, cameraIDs: new Set(['camera-2']), hasClip: true },
|
||||
{ type: QueryType.Event, cameraIDs: new Set(['camera-2']), hasSnapshot: true },
|
||||
]);
|
||||
const nextResults = new MediaQueriesResults();
|
||||
const next = createView({
|
||||
view: 'media',
|
||||
query: nextQuery,
|
||||
queryResults: nextResults,
|
||||
});
|
||||
View.adoptFromViewIfAppropriate(next, current);
|
||||
|
||||
expect(next.view).toBe('media');
|
||||
expect(next.query).toBe(nextQuery);
|
||||
expect(next.queryResults).toBe(nextResults);
|
||||
});
|
||||
|
||||
it('should not adopt for other case', () => {
|
||||
const query = new EventMediaQueries([
|
||||
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasClip: true },
|
||||
]);
|
||||
const queryResults = new MediaQueriesResults();
|
||||
|
||||
const current = createView({
|
||||
view: 'media',
|
||||
query: query,
|
||||
queryResults: queryResults,
|
||||
});
|
||||
const next = createView({ view: 'live' });
|
||||
View.adoptFromViewIfAppropriate(next, current);
|
||||
expect(next.view).toBe('live');
|
||||
expect(next.query).toBeFalsy();
|
||||
expect(next.queryResults).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should do nothing with undefined next view', () => {
|
||||
const view_1 = createView();
|
||||
const view_2 = view_1.clone();
|
||||
View.adoptFromViewIfAppropriate(view_1);
|
||||
expect(view_1).toEqual(view_2);
|
||||
});
|
||||
|
||||
it('should adopt with query but no queryResults', () => {
|
||||
const query = new EventMediaQueries([
|
||||
{ type: QueryType.Event, cameraIDs: new Set(['camera']), hasClip: true },
|
||||
]);
|
||||
|
||||
const current = createView({
|
||||
view: 'media',
|
||||
query: query,
|
||||
});
|
||||
const next = createView({
|
||||
view: 'media',
|
||||
query: query,
|
||||
});
|
||||
View.adoptFromViewIfAppropriate(next, current);
|
||||
expect(next.view).toBe('clip');
|
||||
});
|
||||
|
||||
it('should adopt live context overrides for substreams', () => {
|
||||
const current = createView({
|
||||
view: 'live',
|
||||
context: {
|
||||
live: {
|
||||
overrides: new Map([['camera', 'camera2']]),
|
||||
},
|
||||
},
|
||||
});
|
||||
const next = createView({
|
||||
view: 'live',
|
||||
});
|
||||
View.adoptFromViewIfAppropriate(next, current);
|
||||
expect(next.context?.live).toEqual(current.context?.live);
|
||||
});
|
||||
|
||||
it('should not adopt live context overrides if there are new overrides', () => {
|
||||
const current = createView({
|
||||
view: 'live',
|
||||
context: {
|
||||
live: {
|
||||
overrides: new Map([['camera', 'camera2']]),
|
||||
},
|
||||
},
|
||||
});
|
||||
const next = createView({
|
||||
view: 'live',
|
||||
context: {
|
||||
live: {
|
||||
overrides: new Map([['camera', 'camera3']]),
|
||||
},
|
||||
},
|
||||
});
|
||||
View.adoptFromViewIfAppropriate(next, current);
|
||||
expect(next.context?.live?.overrides).toEqual(new Map([['camera', 'camera3']]));
|
||||
});
|
||||
|
||||
it('should adopt live context overrides even if there is new context', () => {
|
||||
const current = createView({
|
||||
view: 'live',
|
||||
context: {
|
||||
live: {
|
||||
overrides: new Map([['camera', 'camera2']]),
|
||||
},
|
||||
},
|
||||
});
|
||||
const next = createView({
|
||||
view: 'live',
|
||||
context: {},
|
||||
});
|
||||
View.adoptFromViewIfAppropriate(next, current);
|
||||
expect(next.context?.live).toEqual(current.context?.live);
|
||||
});
|
||||
|
||||
it('should determine if display mode is grid', () => {
|
||||
expect(createView({ displayMode: 'grid' }).isGrid()).toBeTruthy();
|
||||
expect(createView({ displayMode: 'single' }).isGrid()).toBeFalsy();
|
||||
@@ -535,19 +228,3 @@ describe('View.adoptFromViewIfAppropriate', () => {
|
||||
expect(createView({ view: 'timeline' }).supportsMultipleDisplayModes()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('dispatchViewContextChangeEvent', () => {
|
||||
it('should dispatch event', () => {
|
||||
const context = {};
|
||||
const handler = vi.fn((ev) => {
|
||||
expect(ev.detail).toBe(context);
|
||||
});
|
||||
|
||||
const element = document.createElement('div');
|
||||
element.addEventListener('frigate-card:view:change-context', handler);
|
||||
|
||||
dispatchViewContextChangeEvent(element, context);
|
||||
expect(handler).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user