Initial version of status bar.

This commit is contained in:
Dermot Duffy
2024-08-16 20:08:21 -07:00
parent db9303c60d
commit e503f0e429
77 changed files with 3072 additions and 1061 deletions
@@ -0,0 +1,67 @@
import { describe, expect, it } from 'vitest';
import { StatusBarAction } from '../../../../src/card-controller/actions/actions/status-bar';
import { createCardAPI } from '../../../test-utils';
describe('should handle status bar action', () => {
it('reset', async () => {
const api = createCardAPI();
const action = new StatusBarAction(
{},
{
action: 'fire-dom-event',
frigate_card_action: 'status_bar',
status_bar_action: 'reset',
},
);
await action.execute(api);
expect(api.getStatusBarItemManager().removeAllDynamicStatusBarItems).toBeCalled();
});
it('add', async () => {
const api = createCardAPI();
const item = {
type: 'custom:frigate-card-status-bar-string',
string: 'Item',
};
const action = new StatusBarAction(
{},
{
action: 'fire-dom-event',
frigate_card_action: 'status_bar',
status_bar_action: 'add',
items: [item],
},
);
await action.execute(api);
expect(api.getStatusBarItemManager().addDynamicStatusBarItem).toBeCalledWith(item);
});
it('remove', async () => {
const api = createCardAPI();
const item = {
type: 'custom:frigate-card-status-bar-string',
string: 'Item',
};
const action = new StatusBarAction(
{},
{
action: 'fire-dom-event',
frigate_card_action: 'status_bar',
status_bar_action: 'remove',
items: [item],
},
);
await action.execute(api);
expect(api.getStatusBarItemManager().removeDynamicStatusBarItem).toBeCalledWith(
item,
);
});
});
@@ -1,4 +1,4 @@
import { describe, expect, it, vi } from 'vitest';
import { describe, expect, it } from 'vitest';
import { CameraSelectAction } from '../../../src/card-controller/actions/actions/camera-select';
import { CameraUIAction } from '../../../src/card-controller/actions/actions/camera-ui';
import { DefaultAction } from '../../../src/card-controller/actions/actions/default';
@@ -21,6 +21,7 @@ import { PTZDigitalAction } from '../../../src/card-controller/actions/actions/p
import { PTZMultiAction } from '../../../src/card-controller/actions/actions/ptz-multi';
import { ScreenshotAction } from '../../../src/card-controller/actions/actions/screenshot';
import { SleepAction } from '../../../src/card-controller/actions/actions/sleep';
import { StatusBarAction } from '../../../src/card-controller/actions/actions/status-bar';
import { SubstreamOffAction } from '../../../src/card-controller/actions/actions/substream-off';
import { SubstreamOnAction } from '../../../src/card-controller/actions/actions/substream-on';
import { SubstreamSelectAction } from '../../../src/card-controller/actions/actions/substream-select';
@@ -125,6 +126,10 @@ describe('ActionFactory', () => {
[{ frigate_card_action: 'snapshots' as const }, ViewAction],
[{ frigate_card_action: 'timeline' as const }, ViewAction],
[{ frigate_card_action: 'unmute' as const }, UnmuteAction],
[
{ frigate_card_action: 'status_bar' as const, status_bar_action: 'reset' },
StatusBarAction,
],
])(
'frigate_card_action: $frigate_card_action',
(action: Partial<FrigateCardCustomAction>, classObject: object) => {
@@ -306,28 +306,6 @@ describe('getOverriddenConfig', () => {
}),
).toEqual({});
});
// it('with empty value and object', () => {
// const manager = new ConditionsManager(createCardAPI());
// manager.setState({ fullscreen: true });
// expect(
// getOverriddenConfig(manager, config, {
// configOverrides: [
// {
// delete: [''],
// conditions: [
// {
// condition: 'fullscreen' as const,
// fullscreen: true,
// },
// ],
// },
// ],
// emptyKeyReplaces: true,
// }),
// ).toEqual({});
// });
});
describe('should validate schema', () => {
@@ -538,6 +516,11 @@ describe('ConditionsManager', () => {
state: 'on',
},
],
actions: [
{
action: 'fire-dom-event',
},
],
},
],
});
+8
View File
@@ -29,6 +29,7 @@ import { ViewManager } from '../../src/card-controller/view/view-manager';
import { FrigateCardEditor } from '../../src/editor';
import { EntityRegistryManager } from '../../src/utils/ha/entity-registry';
import { ResolvedMediaCache } from '../../src/utils/ha/resolved-media';
import { StatusBarItemManager } from '../../src/card-controller/status-bar-item-manager';
vi.mock('../../src/camera-manager/manager');
vi.mock('../../src/card-controller/actions/actions-manager');
@@ -50,6 +51,7 @@ vi.mock('../../src/card-controller/media-player-manager');
vi.mock('../../src/card-controller/message-manager');
vi.mock('../../src/card-controller/microphone-manager');
vi.mock('../../src/card-controller/query-string-manager');
vi.mock('../../src/card-controller/status-bar-item-manager');
vi.mock('../../src/card-controller/style-manager');
vi.mock('../../src/card-controller/triggers-manager');
vi.mock('../../src/card-controller/view/view-manager');
@@ -247,6 +249,12 @@ describe('CardController', () => {
);
});
it('getStatusBarItemManager', () => {
expect(createController().getStatusBarItemManager()).toBe(
vi.mocked(StatusBarItemManager).mock.instances[0],
);
});
it('getStyleManager', () => {
expect(createController().getStyleManager()).toBe(
vi.mocked(StyleManager).mock.instances[0],
@@ -0,0 +1,243 @@
import { describe, expect, it, vi } from 'vitest';
import { StatusBarItemManager } from '../../src/card-controller/status-bar-item-manager';
import { StatusBarString } from '../../src/config/types';
import { MediaQueriesResults } from '../../src/view/media-queries-results';
import {
TestViewMedia,
createCameraManager,
createCardAPI,
createStore,
createView,
} from '../test-utils';
describe('StatusBarItemManager', () => {
const testItem: StatusBarString = {
type: 'custom:frigate-card-status-bar-string' as const,
string: 'test',
};
it('should add', () => {
const manager = new StatusBarItemManager(createCardAPI());
manager.addDynamicStatusBarItem(testItem);
manager.addDynamicStatusBarItem(testItem);
expect(manager.calculateItems()).toContain(testItem);
expect(manager.calculateItems().find((item) => item === testItem).length === 1);
});
it('should remove', () => {
const manager = new StatusBarItemManager(createCardAPI());
manager.addDynamicStatusBarItem(testItem);
manager.removeDynamicStatusBarItem({ ...testItem });
expect(manager.calculateItems()).not.toContain(testItem);
manager.removeDynamicStatusBarItem({ ...testItem, string: 'not-present' });
expect(manager.calculateItems()).not.toContain({
...testItem,
string: 'not-present',
});
});
it('should remove all', () => {
const manager = new StatusBarItemManager(createCardAPI());
manager.addDynamicStatusBarItem(testItem);
manager.removeAllDynamicStatusBarItems();
expect(manager.calculateItems()).not.toContain(testItem);
});
describe('should have standard status bar items', () => {
describe('should have title', () => {
describe('live', () => {
it('with metadata', () => {
const manager = new StatusBarItemManager(createCardAPI());
const store = createStore([
{
cameraID: 'camera-1',
},
]);
const cameraManager = createCameraManager(store);
vi.mocked(cameraManager.getCameraMetadata).mockReturnValue({
title: 'Camera Title',
icon: 'mdi:camera',
});
expect(
manager.calculateItems({
cameraManager: cameraManager,
view: createView({ view: 'live', camera: 'camera-1' }),
}),
).toContainEqual({
type: 'custom:frigate-card-status-bar-string' as const,
string: 'Camera Title',
expand: true,
sufficient: true,
});
});
it('without metadata', () => {
const manager = new StatusBarItemManager(createCardAPI());
const cameraManager = createCameraManager();
expect(
manager.calculateItems({
cameraManager: cameraManager,
view: createView({ view: 'live', camera: 'MISSING-CAMERA' }),
}),
).not.toContainEqual(expect.objectContaining({ sufficient: true }));
});
});
describe('media', () => {
it('with a title', () => {
const manager = new StatusBarItemManager(createCardAPI());
const cameraManager = createCameraManager();
const media = [new TestViewMedia({ title: 'Media Title' })];
const queryResults = new MediaQueriesResults({
results: media,
});
const view = createView({
view: 'media',
queryResults: queryResults,
});
expect(
manager.calculateItems({
cameraManager: cameraManager,
view: view,
}),
).toContainEqual({
type: 'custom:frigate-card-status-bar-string' as const,
string: 'Media Title',
expand: true,
sufficient: true,
});
});
it('without a title', () => {
const manager = new StatusBarItemManager(createCardAPI());
const cameraManager = createCameraManager();
const media = [new TestViewMedia()];
const queryResults = new MediaQueriesResults({
results: media,
});
const view = createView({
view: 'media',
queryResults: queryResults,
});
expect(
manager.calculateItems({
cameraManager: cameraManager,
view: view,
}),
).not.toContainEqual(expect.objectContaining({ sufficient: true }));
});
});
});
describe('should have resolution', () => {
it.each([
['1080p landscape', '1080p', 1920, 1080],
['1080p portrait', '1080p', 1080, 1920],
['1080p approximate', '1080p', 1922, 1082],
['720p landscape', '720p', 1280, 720],
['720p portrait', '720p', 720, 1280],
['720p approximate', '720p', 1282, 722],
['VGA landscape', 'VGA', 640, 480],
['VGA portrait', 'VGA', 480, 640],
['VGA approximate', 'VGA', 642, 482],
['4K landscape', '4K', 3840, 2160],
['4K portrait', '4K', 2160, 3840],
['4K approximate', '4K', 3842, 2162],
['480p landscape', '480p', 720, 480],
['480p portrait', '480p', 480, 720],
['480p approximate', '480p', 722, 482],
['576p landscape', '576p', 720, 576],
['576p portrait', '576p', 576, 720],
['576p approximate', '576p', 722, 578],
['8K landscape', '8K', 7680, 4320],
['8K portrait', '8K', 4320, 7680],
['8K approximate', '8K', 7682, 4322],
['random', '123x456', 123, 456],
])(
'%s',
(_testName: string, expectedName: string, width: number, height: number) => {
const manager = new StatusBarItemManager(createCardAPI());
expect(
manager.calculateItems({
mediaLoadedInfo: { width, height },
}),
).toContainEqual({
type: 'custom:frigate-card-status-bar-string' as const,
string: expectedName,
});
},
);
});
describe('should have technology', () => {
it('webrtc', () => {
const manager = new StatusBarItemManager(createCardAPI());
expect(
manager.calculateItems({
mediaLoadedInfo: { width: 640, height: 480, technology: ['webrtc'] },
}),
).toContainEqual({
type: 'custom:frigate-card-status-bar-icon' as const,
icon: 'mdi:webrtc',
});
});
it('non-webrtc', () => {
const manager = new StatusBarItemManager(createCardAPI());
expect(
manager.calculateItems({
mediaLoadedInfo: { width: 640, height: 480, technology: ['hls'] },
}),
).toContainEqual({
type: 'custom:frigate-card-status-bar-string' as const,
string: 'HLS',
});
});
});
it('should have engine', () => {
const manager = new StatusBarItemManager(createCardAPI());
const store = createStore([
{
cameraID: 'camera-1',
},
]);
const cameraManager = createCameraManager(store);
vi.mocked(cameraManager.getCameraMetadata).mockReturnValue({
title: 'Camera Title',
icon: 'mdi:camera',
engineLogo: 'IMAGE_LOGO',
});
expect(
manager.calculateItems({
cameraManager: cameraManager,
view: createView({ view: 'live', camera: 'camera-1' }),
}),
).toContainEqual({
type: 'custom:frigate-card-status-bar-image' as const,
image: 'IMAGE_LOGO',
});
});
});
});