Files
advanced-camera-card/tests/card-controller/view/factory.test.ts
T

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);
});
});
});
});
});