490 lines
15 KiB
TypeScript
490 lines
15 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { mock } from 'vitest-mock-extended';
|
|
|
|
import type { FoldersManager } from '../../../src/card-controller/folders/manager';
|
|
import { ViewFactory } from '../../../src/card-controller/view/factory';
|
|
import {
|
|
ViewIncompatible,
|
|
type ViewModifier,
|
|
} from '../../../src/card-controller/view/types';
|
|
import type { AdvancedCameraCardView } from '../../../src/config/schema/common/const';
|
|
import type { ViewDisplayMode } from '../../../src/config/schema/common/display';
|
|
import { View } from '../../../src/view/view';
|
|
import {
|
|
createCameraManager,
|
|
createCapabilities,
|
|
createStore,
|
|
} from '../../camera-manager/test-utils';
|
|
import { createConfig } from '../../config/test-utils';
|
|
import { createCardAPI, createFolder } from '../../test-utils';
|
|
import { createView } from '../../view/test-utils';
|
|
import { createPopulatedAPI } from './test-utils';
|
|
|
|
describe('getViewDefault', () => {
|
|
it('should return null without config', () => {
|
|
const factory = new ViewFactory(createCardAPI());
|
|
expect(factory.getViewDefault()).toBeNull();
|
|
});
|
|
|
|
it('should throw when no cameras support view', () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
|
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
|
|
|
// No cameras support live.
|
|
vi.mocked(api.getCameraManager().getStore).mockReturnValue(
|
|
createStore([
|
|
{
|
|
cameraID: 'camera.office',
|
|
capabilities: createCapabilities({
|
|
live: false,
|
|
}),
|
|
},
|
|
{
|
|
cameraID: 'camera.kitchen',
|
|
capabilities: createCapabilities({
|
|
live: false,
|
|
}),
|
|
},
|
|
]),
|
|
);
|
|
|
|
const factory = new ViewFactory(api);
|
|
expect(() => factory.getViewDefault()).toThrow(ViewIncompatible);
|
|
});
|
|
|
|
it('should use folders view as default when folders exist without cameras', () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
|
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
|
vi.mocked(api.getFoldersManager).mockReturnValue(mock<FoldersManager>());
|
|
vi.mocked(api.getFoldersManager().hasFolders).mockReturnValue(true);
|
|
vi.mocked(api.getFoldersManager().getFolder).mockReturnValue(createFolder());
|
|
|
|
vi.mocked(api.getCameraManager().getStore).mockReturnValue(createStore([]));
|
|
|
|
const factory = new ViewFactory(api);
|
|
const view = factory.getViewDefault();
|
|
|
|
expect(view?.view).toBe('folders');
|
|
expect(view?.camera).toBeNull();
|
|
});
|
|
|
|
it('should create view', () => {
|
|
const factory = new ViewFactory(createPopulatedAPI());
|
|
const view = factory.getViewDefault();
|
|
|
|
expect(view?.is('live')).toBeTruthy();
|
|
expect(view?.camera).toBe('camera.office');
|
|
});
|
|
|
|
it('should cycle camera when configured', () => {
|
|
const api = createPopulatedAPI();
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
|
createConfig({
|
|
view: {
|
|
default_cycle_camera: true,
|
|
},
|
|
}),
|
|
);
|
|
|
|
const factory = new ViewFactory(api);
|
|
|
|
let view = factory.getViewDefault();
|
|
expect(view?.camera).toBe('camera.office');
|
|
|
|
view = factory.getViewDefault({ baseView: view });
|
|
expect(view?.camera).toBe('camera.kitchen');
|
|
|
|
view = factory.getViewDefault({ baseView: view });
|
|
expect(view?.camera).toBe('camera.office');
|
|
|
|
// When a parameter is specified, it will not cycle.
|
|
view = factory.getViewDefault({
|
|
params: { camera: 'camera.office' },
|
|
baseView: view,
|
|
});
|
|
expect(view?.camera).toBe('camera.office');
|
|
});
|
|
|
|
it('should use default camera when camera unspecified', () => {
|
|
// Even though baseView has a camera, since default is called it should
|
|
// use that camera.
|
|
|
|
const factory = new ViewFactory(createPopulatedAPI());
|
|
const baseView = createView({ camera: 'camera.kitchen' });
|
|
const view = factory.getViewDefault({
|
|
baseView: baseView,
|
|
});
|
|
|
|
expect(view?.is('live')).toBeTruthy();
|
|
expect(view?.camera).toBe('camera.office');
|
|
});
|
|
|
|
it('should respect parameters', () => {
|
|
const factory = new ViewFactory(createPopulatedAPI());
|
|
const view = factory.getViewDefault({
|
|
params: {
|
|
camera: 'camera.kitchen',
|
|
},
|
|
});
|
|
|
|
expect(view?.is('live')).toBeTruthy();
|
|
expect(view?.camera).toBe('camera.kitchen');
|
|
});
|
|
});
|
|
|
|
describe('getViewByParameters', () => {
|
|
it('should get view by parameters specifying camera and view', () => {
|
|
const api = createPopulatedAPI();
|
|
vi.mocked(api.getCameraManager().getAggregateCameraCapabilities).mockReturnValue(
|
|
createCapabilities({
|
|
clips: true,
|
|
}),
|
|
);
|
|
|
|
const factory = new ViewFactory(api);
|
|
const view = factory.getViewByParameters({
|
|
params: {
|
|
camera: 'camera.kitchen',
|
|
view: 'clips',
|
|
},
|
|
});
|
|
|
|
expect(view?.is('clips')).toBeTruthy();
|
|
expect(view?.camera).toBe('camera.kitchen');
|
|
});
|
|
|
|
it('should get view by parameters using base view if unspecified', () => {
|
|
const api = createPopulatedAPI();
|
|
vi.mocked(api.getCameraManager().getAggregateCameraCapabilities).mockReturnValue(
|
|
createCapabilities({
|
|
clips: true,
|
|
}),
|
|
);
|
|
|
|
const factory = new ViewFactory(api);
|
|
const baseView = new View({
|
|
camera: 'camera.kitchen',
|
|
view: 'clips',
|
|
});
|
|
|
|
const view = factory.getViewByParameters({
|
|
baseView: baseView,
|
|
params: {
|
|
camera: 'camera.office',
|
|
},
|
|
});
|
|
|
|
expect(view?.view).toBe('clips');
|
|
expect(view?.camera).toBe('camera.office');
|
|
});
|
|
|
|
it('should set view by parameters using config as fallback', () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
|
vi.mocked(api.getCameraManager().getStore).mockReturnValue(
|
|
createStore([
|
|
{
|
|
cameraID: 'camera.kitchen',
|
|
capabilities: createCapabilities({ live: true }),
|
|
},
|
|
]),
|
|
);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
|
|
|
const factory = new ViewFactory(api);
|
|
const view = factory.getViewByParameters({
|
|
params: {
|
|
camera: 'camera.kitchen',
|
|
|
|
// No prior view, and no specified view. This could happen during query
|
|
// string based initialization.
|
|
},
|
|
});
|
|
|
|
expect(view?.is('live')).toBeTruthy();
|
|
expect(view?.camera).toBe('camera.kitchen');
|
|
});
|
|
|
|
it('should not set view by parameters without config', () => {
|
|
const factory = new ViewFactory(createCardAPI());
|
|
|
|
const view = factory.getViewByParameters({
|
|
params: {
|
|
view: 'live',
|
|
},
|
|
});
|
|
|
|
expect(view).toBeNull();
|
|
});
|
|
|
|
it('should throw without camera and without failsafe', () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
|
vi.mocked(api.getCameraManager().getStore).mockReturnValue(
|
|
createStore([
|
|
{
|
|
cameraID: 'camera.kitchen',
|
|
capabilities: createCapabilities({ snapshots: false }),
|
|
},
|
|
{
|
|
cameraID: 'camera.office',
|
|
// No capabilities.
|
|
capabilities: null,
|
|
},
|
|
]),
|
|
);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
|
|
|
const factory = new ViewFactory(api);
|
|
expect(() =>
|
|
factory.getViewByParameters({
|
|
params: {
|
|
view: 'snapshots',
|
|
},
|
|
}),
|
|
).toThrow(ViewIncompatible);
|
|
});
|
|
|
|
describe('should handle no camera for view with failsafe', () => {
|
|
it('should choose default view when it does not require a camera', () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
|
vi.mocked(api.getCameraManager().getStore).mockReturnValue(createStore([]));
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
|
createConfig({
|
|
view: {
|
|
default: 'diagnostics',
|
|
},
|
|
}),
|
|
);
|
|
|
|
const factory = new ViewFactory(api);
|
|
const view = factory.getViewByParameters({
|
|
params: {
|
|
view: 'live',
|
|
},
|
|
failSafe: true,
|
|
});
|
|
expect(view?.is('diagnostics')).toBeTruthy();
|
|
expect(view?.camera).toBeNull();
|
|
});
|
|
|
|
it('should choose default view and camera when it requires a camera', () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
|
vi.mocked(api.getCameraManager().getStore).mockReturnValue(
|
|
createStore([{ cameraID: 'camera.one' }]),
|
|
);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
|
createConfig({
|
|
view: {
|
|
default: 'live',
|
|
},
|
|
}),
|
|
);
|
|
|
|
const factory = new ViewFactory(api);
|
|
const view = factory.getViewByParameters({
|
|
params: {
|
|
view: 'snapshots',
|
|
},
|
|
failSafe: true,
|
|
});
|
|
expect(view?.is('live')).toBeTruthy();
|
|
expect(view?.camera).toBe('camera.one');
|
|
});
|
|
});
|
|
|
|
describe('should handle unsupported view', () => {
|
|
it('should throw without failsafe', () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
|
vi.mocked(api.getCameraManager().getStore).mockReturnValue(
|
|
createStore([
|
|
{
|
|
cameraID: 'camera.kitchen',
|
|
capabilities: createCapabilities({ snapshots: false }),
|
|
},
|
|
]),
|
|
);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
|
vi.mocked(api.getCameraManager().getAggregateCameraCapabilities).mockReturnValue(
|
|
createCapabilities({
|
|
snapshots: false,
|
|
}),
|
|
);
|
|
|
|
const factory = new ViewFactory(api);
|
|
expect(() =>
|
|
factory.getViewByParameters({
|
|
params: {
|
|
camera: 'camera.kitchen',
|
|
view: 'snapshots',
|
|
},
|
|
}),
|
|
).toThrow(ViewIncompatible);
|
|
});
|
|
|
|
it('should choose live view with failsafe', () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
|
vi.mocked(api.getCameraManager().getStore).mockReturnValue(
|
|
createStore([
|
|
{
|
|
cameraID: 'camera.kitchen',
|
|
capabilities: createCapabilities({ live: true, snapshots: false }),
|
|
},
|
|
]),
|
|
);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
|
vi.mocked(api.getCameraManager().getAggregateCameraCapabilities).mockReturnValue(
|
|
createCapabilities({
|
|
live: true,
|
|
snapshots: false,
|
|
}),
|
|
);
|
|
|
|
const factory = new ViewFactory(api);
|
|
const view = factory.getViewByParameters({
|
|
params: {
|
|
camera: 'camera.kitchen',
|
|
view: 'snapshots',
|
|
},
|
|
failSafe: true,
|
|
});
|
|
expect(view?.is('live')).toBeTruthy();
|
|
});
|
|
|
|
it('should throw and omit capabilities when camera object is missing from store', () => {
|
|
const api = createCardAPI();
|
|
const store = createStore([{ cameraID: 'camera.one' }]);
|
|
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
|
vi.spyOn(store, 'getCamera').mockReturnValue(null);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
|
|
|
const factory = new ViewFactory(api);
|
|
expect(() =>
|
|
factory.getViewByParameters({
|
|
params: {
|
|
camera: 'camera.one',
|
|
view: 'snapshots',
|
|
},
|
|
}),
|
|
).toThrow(ViewIncompatible);
|
|
});
|
|
});
|
|
|
|
it('should call modifiers', () => {
|
|
const api = createCardAPI();
|
|
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
|
vi.mocked(api.getCameraManager().getStore).mockReturnValue(
|
|
createStore([
|
|
{
|
|
cameraID: 'camera.office',
|
|
capabilities: createCapabilities({ live: true }),
|
|
},
|
|
]),
|
|
);
|
|
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
|
|
|
const modifyCallback = vi.fn();
|
|
class TestViewModifier implements ViewModifier {
|
|
modify = modifyCallback;
|
|
}
|
|
|
|
const view = new View({
|
|
view: 'live',
|
|
camera: 'camera.office',
|
|
});
|
|
const factory = new ViewFactory(api);
|
|
const modifiedView = factory.getViewByParameters({
|
|
baseView: view,
|
|
modifiers: [new TestViewModifier()],
|
|
});
|
|
|
|
expect(modifiedView?.is('live')).toBeTruthy();
|
|
expect(modifiedView?.camera).toBe('camera.office');
|
|
expect(view).not.toBe(modifiedView);
|
|
expect(modifyCallback).toHaveBeenCalledWith(modifiedView);
|
|
});
|
|
|
|
describe('should get correct display mode', () => {
|
|
it('should use config display mode when changing views', () => {
|
|
const api = createPopulatedAPI({
|
|
media_viewer: {
|
|
display: { mode: 'single' },
|
|
},
|
|
});
|
|
|
|
const factory = new ViewFactory(api);
|
|
expect(
|
|
factory.getViewByParameters({
|
|
params: {
|
|
view: 'clip',
|
|
displayMode: 'grid',
|
|
},
|
|
})?.displayMode,
|
|
).toBe('single');
|
|
});
|
|
|
|
it('should ignore config display mode with a view', () => {
|
|
const api = createPopulatedAPI({
|
|
media_viewer: {
|
|
display: { mode: 'single' },
|
|
},
|
|
});
|
|
|
|
const baseView = createView({
|
|
view: 'live',
|
|
});
|
|
|
|
const factory = new ViewFactory(api);
|
|
expect(
|
|
factory.getViewByParameters({
|
|
baseView: baseView,
|
|
params: {
|
|
view: 'live',
|
|
displayMode: 'grid',
|
|
},
|
|
})?.displayMode,
|
|
).toBe('grid');
|
|
});
|
|
|
|
describe('should get correct default display mode', () => {
|
|
describe.each([
|
|
['single' as const, { mode: 'single' as const }],
|
|
['grid' as const, { mode: 'grid' as const }],
|
|
['single' as const, undefined],
|
|
])('%s', (expectedDisplayMode: ViewDisplayMode, displayConfig?: unknown) => {
|
|
it.each([
|
|
['media' as const],
|
|
['clip' as const],
|
|
['recording' as const],
|
|
['snapshot' as const],
|
|
['live' as const],
|
|
])('%s', (viewName: AdvancedCameraCardView) => {
|
|
const api = createPopulatedAPI({
|
|
media_viewer: {
|
|
display: displayConfig,
|
|
},
|
|
live: {
|
|
display: displayConfig,
|
|
},
|
|
});
|
|
|
|
const factory = new ViewFactory(api);
|
|
expect(
|
|
factory.getViewByParameters({
|
|
params: {
|
|
view: viewName,
|
|
},
|
|
})?.displayMode,
|
|
).toBe(expectedDisplayMode);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|