Ensure timeline menu buttons shows up when it should.
This commit is contained in:
@@ -3,7 +3,6 @@ import {
|
||||
ActionsConfig,
|
||||
ActionType,
|
||||
FrigateCardCustomAction,
|
||||
FRIGATE_CARD_VIEW_DEFAULT,
|
||||
} from '../config/types.js';
|
||||
import {
|
||||
convertActionToFrigateCardCustomAction,
|
||||
@@ -177,14 +176,10 @@ export class ActionsManager {
|
||||
const viewOnCameraSelect = config?.view.camera_select ?? 'current';
|
||||
const targetViewName =
|
||||
viewOnCameraSelect === 'current' ? view.view : viewOnCameraSelect;
|
||||
const verifiedViewName = this._api
|
||||
.getViewManager()
|
||||
.isViewSupportedByCamera(selectCameraID, targetViewName)
|
||||
? targetViewName
|
||||
: FRIGATE_CARD_VIEW_DEFAULT;
|
||||
this._api.getViewManager().setViewByParameters({
|
||||
viewName: verifiedViewName,
|
||||
viewName: targetViewName,
|
||||
cameraID: selectCameraID,
|
||||
failSafe: true,
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -190,7 +190,17 @@ export class ViewManager {
|
||||
}
|
||||
|
||||
public isViewSupportedByCamera(cameraID: string, view: FrigateCardView): boolean {
|
||||
const capabilities = this._api.getCameraManager().getCameraCapabilities(cameraID);
|
||||
const dependentCamerasCapabilities = this._api
|
||||
.getCameraManager()
|
||||
.getAggregateCameraCapabilities(
|
||||
this._api.getCameraManager().getStore().getAllDependentCameras(cameraID),
|
||||
);
|
||||
const allCamerasCapabilities = this._api
|
||||
.getCameraManager()
|
||||
.getAggregateCameraCapabilities(
|
||||
this._api.getCameraManager().getStore().getCameraIDs(),
|
||||
);
|
||||
|
||||
switch (view) {
|
||||
case 'live':
|
||||
case 'image':
|
||||
@@ -198,20 +208,22 @@ export class ViewManager {
|
||||
return true;
|
||||
case 'clip':
|
||||
case 'clips':
|
||||
return !!capabilities?.supportsClips;
|
||||
return !!dependentCamerasCapabilities?.supportsClips;
|
||||
case 'snapshot':
|
||||
case 'snapshots':
|
||||
return !!capabilities?.supportsSnapshots;
|
||||
return !!dependentCamerasCapabilities?.supportsSnapshots;
|
||||
case 'recording':
|
||||
case 'recordings':
|
||||
return !!capabilities?.supportsRecordings;
|
||||
return !!dependentCamerasCapabilities?.supportsRecordings;
|
||||
case 'timeline':
|
||||
return !!capabilities?.supportsTimeline;
|
||||
// Show the timeline if any camera supports it, even cameras unrelated
|
||||
// to the currently selected camera.
|
||||
return !!allCamerasCapabilities?.supportsTimeline;
|
||||
case 'media':
|
||||
return (
|
||||
!!capabilities?.supportsClips ||
|
||||
!!capabilities?.supportsSnapshots ||
|
||||
!!capabilities?.supportsRecordings
|
||||
!!dependentCamerasCapabilities?.supportsClips ||
|
||||
!!dependentCamerasCapabilities?.supportsSnapshots ||
|
||||
!!dependentCamerasCapabilities?.supportsRecordings
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +209,7 @@ class FrigateCard extends LitElement {
|
||||
showCameraUIButton: this._controller.getCameraURLManager().hasCameraURL(),
|
||||
mediaPlayerController: this._controller.getMediaPlayerManager(),
|
||||
microphoneManager: this._controller.getMicrophoneManager(),
|
||||
viewManager: this._controller.getViewManager(),
|
||||
},
|
||||
)}
|
||||
.entityRegistryManager=${this._controller.getEntityRegistryManager()}
|
||||
|
||||
@@ -3,10 +3,11 @@ import { StyleInfo } from 'lit/directives/style-map';
|
||||
import { CameraManager } from '../camera-manager/manager';
|
||||
import { MediaPlayerManager } from '../card-controller/media-player-manager';
|
||||
import { MicrophoneManager } from '../card-controller/microphone-manager';
|
||||
import { ViewManager } from '../card-controller/view-manager';
|
||||
import {
|
||||
FRIGATE_CARD_VIEWS_USER_SPECIFIED,
|
||||
FrigateCardConfig,
|
||||
FrigateCardCustomAction,
|
||||
FRIGATE_CARD_VIEWS_USER_SPECIFIED,
|
||||
MenuItem,
|
||||
} from '../config/types';
|
||||
import { FRIGATE_BUTTON_MENU_ICON } from '../const';
|
||||
@@ -31,6 +32,7 @@ export interface MenuButtonControllerOptions {
|
||||
inExpandedMode?: boolean;
|
||||
microphoneManager?: MicrophoneManager | null;
|
||||
mediaPlayerController?: MediaPlayerManager | null;
|
||||
viewManager?: ViewManager | null;
|
||||
}
|
||||
|
||||
export class MenuButtonController {
|
||||
@@ -62,18 +64,19 @@ export class MenuButtonController {
|
||||
): MenuItem[] {
|
||||
const visibleCameraIDs = cameraManager.getStore().getVisibleCameraIDs();
|
||||
const selectedCameraID = view.camera;
|
||||
const substreamAwareCameraID =
|
||||
view.context?.live?.overrides?.get(selectedCameraID) ?? selectedCameraID;
|
||||
const selectedCameraConfig = cameraManager
|
||||
.getStore()
|
||||
.getCameraConfig(selectedCameraID);
|
||||
const allSelectedCameraIDs = cameraManager
|
||||
.getStore()
|
||||
.getAllDependentCameras(selectedCameraID);
|
||||
const selectedMedia = view.queryResults?.getSelectedResult();
|
||||
|
||||
const selectedCameraCapabilities =
|
||||
cameraManager.getCameraCapabilities(selectedCameraID);
|
||||
const aggregateCapabilities =
|
||||
cameraManager.getAggregateCameraCapabilities(allSelectedCameraIDs);
|
||||
const substreamAwareCameraCapabilities =
|
||||
cameraManager.getCameraCapabilities(substreamAwareCameraID);
|
||||
|
||||
const selectedMedia = view.queryResults?.getSelectedResult();
|
||||
const mediaCapabilities = selectedMedia
|
||||
? cameraManager?.getMediaCapabilities(selectedMedia)
|
||||
: null;
|
||||
@@ -125,7 +128,6 @@ export class MenuButtonController {
|
||||
|
||||
if (selectedCameraID && allSelectedCameraIDs && view.is('live')) {
|
||||
const dependencies = [...allSelectedCameraIDs];
|
||||
const override = view.context?.live?.overrides?.get(selectedCameraID);
|
||||
|
||||
if (dependencies.length === 2) {
|
||||
// If there are only two dependencies (the main camera, and 1 other)
|
||||
@@ -133,7 +135,9 @@ export class MenuButtonController {
|
||||
buttons.push({
|
||||
icon: 'mdi:video-input-component',
|
||||
style:
|
||||
override && override !== selectedCameraID ? this._getEmphasizedStyle() : {},
|
||||
substreamAwareCameraID !== selectedCameraID
|
||||
? this._getEmphasizedStyle()
|
||||
: {},
|
||||
title: localize('config.menu.buttons.substreams'),
|
||||
...config.menu.buttons.substreams,
|
||||
type: 'custom:frigate-card-menu-icon',
|
||||
@@ -166,7 +170,9 @@ export class MenuButtonController {
|
||||
icon: 'mdi:video-input-component',
|
||||
title: localize('config.menu.buttons.substreams'),
|
||||
style:
|
||||
override && override !== selectedCameraID ? this._getEmphasizedStyle() : {},
|
||||
substreamAwareCameraID !== selectedCameraID
|
||||
? this._getEmphasizedStyle()
|
||||
: {},
|
||||
...config.menu.buttons.substreams,
|
||||
type: 'custom:frigate-card-menu-submenu',
|
||||
items: menuItems,
|
||||
@@ -183,7 +189,7 @@ export class MenuButtonController {
|
||||
tap_action: createFrigateCardSimpleAction('live') as FrigateCardCustomAction,
|
||||
});
|
||||
|
||||
if (aggregateCapabilities?.supportsClips) {
|
||||
if (options?.viewManager?.isViewSupportedByCamera(selectedCameraID, 'clips')) {
|
||||
buttons.push({
|
||||
icon: 'mdi:filmstrip',
|
||||
...config.menu.buttons.clips,
|
||||
@@ -195,7 +201,7 @@ export class MenuButtonController {
|
||||
});
|
||||
}
|
||||
|
||||
if (aggregateCapabilities?.supportsSnapshots) {
|
||||
if (options?.viewManager?.isViewSupportedByCamera(selectedCameraID, 'snapshots')) {
|
||||
buttons.push({
|
||||
icon: 'mdi:camera',
|
||||
...config.menu.buttons.snapshots,
|
||||
@@ -211,7 +217,7 @@ export class MenuButtonController {
|
||||
});
|
||||
}
|
||||
|
||||
if (aggregateCapabilities?.supportsRecordings) {
|
||||
if (options?.viewManager?.isViewSupportedByCamera(selectedCameraID, 'recordings')) {
|
||||
buttons.push({
|
||||
icon: 'mdi:album',
|
||||
...config.menu.buttons.recordings,
|
||||
@@ -236,9 +242,7 @@ export class MenuButtonController {
|
||||
tap_action: createFrigateCardSimpleAction('image') as FrigateCardCustomAction,
|
||||
});
|
||||
|
||||
// Don't show the timeline button unless there's at least one non-birdseye
|
||||
// camera with a Frigate camera name.
|
||||
if (aggregateCapabilities?.supportsTimeline) {
|
||||
if (options?.viewManager?.isViewSupportedByCamera(selectedCameraID, 'timeline')) {
|
||||
buttons.push({
|
||||
icon: 'mdi:chart-gantt',
|
||||
...config.menu.buttons.timeline,
|
||||
@@ -419,7 +423,7 @@ export class MenuButtonController {
|
||||
});
|
||||
}
|
||||
|
||||
if (hasUsablePTZ(selectedCameraCapabilities, config.live.controls.ptz)) {
|
||||
if (hasUsablePTZ(substreamAwareCameraCapabilities, config.live.controls.ptz)) {
|
||||
const isOn =
|
||||
view.context?.live?.ptzVisible === false
|
||||
? false
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import { afterAll, describe, expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import {
|
||||
@@ -382,6 +383,7 @@ describe('ActionsManager.executeAction', () => {
|
||||
expect.objectContaining({
|
||||
viewName: 'live',
|
||||
cameraID: 'camera',
|
||||
failSafe: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -409,6 +411,7 @@ describe('ActionsManager.executeAction', () => {
|
||||
expect.objectContaining({
|
||||
viewName: 'timeline',
|
||||
cameraID: 'camera',
|
||||
failSafe: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -442,6 +445,7 @@ describe('ActionsManager.executeAction', () => {
|
||||
expect.objectContaining({
|
||||
viewName: 'clips',
|
||||
cameraID: 'camera',
|
||||
failSafe: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -467,6 +471,7 @@ describe('ActionsManager.executeAction', () => {
|
||||
expect.objectContaining({
|
||||
viewName: 'live',
|
||||
cameraID: 'camera',
|
||||
failSafe: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -503,32 +508,6 @@ describe('ActionsManager.executeAction', () => {
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('with an unsupported view', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
view: 'timeline',
|
||||
}),
|
||||
);
|
||||
|
||||
await manager.executeFrigateAction(
|
||||
createAction({
|
||||
frigate_card_action: 'camera_select',
|
||||
camera: 'camera',
|
||||
})!,
|
||||
);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
// Should have fallen back to the default view.
|
||||
viewName: 'live',
|
||||
cameraID: 'camera',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle live_substream_select action', async () => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { EventMediaQueries } from '../../src/view/media-queries';
|
||||
import { MediaQueriesResults } from '../../src/view/media-queries-results';
|
||||
import { View } from '../../src/view/view';
|
||||
import {
|
||||
createAggregateCameraCapabilities,
|
||||
createCameraCapabilities,
|
||||
createCameraConfig,
|
||||
createCameraManager,
|
||||
@@ -15,7 +16,7 @@ import {
|
||||
createHASS,
|
||||
createStore,
|
||||
createView,
|
||||
generateViewMediaArray
|
||||
generateViewMediaArray,
|
||||
} from '../test-utils';
|
||||
|
||||
vi.mock('../../src/utils/media-to-view');
|
||||
@@ -218,8 +219,8 @@ describe('ViewManager.setViewByParameters', () => {
|
||||
},
|
||||
]),
|
||||
);
|
||||
vi.mocked(api.getCameraManager().getCameraCapabilities).mockReturnValue(
|
||||
createCameraCapabilities({
|
||||
vi.mocked(api.getCameraManager().getAggregateCameraCapabilities).mockReturnValue(
|
||||
createAggregateCameraCapabilities({
|
||||
supportsClips: true,
|
||||
}),
|
||||
);
|
||||
@@ -247,8 +248,8 @@ describe('ViewManager.setViewByParameters', () => {
|
||||
},
|
||||
]),
|
||||
);
|
||||
vi.mocked(api.getCameraManager().getCameraCapabilities).mockReturnValue(
|
||||
createCameraCapabilities({
|
||||
vi.mocked(api.getCameraManager().getAggregateCameraCapabilities).mockReturnValue(
|
||||
createAggregateCameraCapabilities({
|
||||
supportsClips: true,
|
||||
}),
|
||||
);
|
||||
@@ -336,9 +337,11 @@ describe('ViewManager.setViewByParameters', () => {
|
||||
]),
|
||||
);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getCameraManager().getCameraCapabilities).mockReturnValue(createCameraCapabilities({
|
||||
vi.mocked(api.getCameraManager().getAggregateCameraCapabilities).mockReturnValue(
|
||||
createAggregateCameraCapabilities({
|
||||
supportsSnapshots: false,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
const manager = new ViewManager(api);
|
||||
manager.setViewByParameters({
|
||||
@@ -360,9 +363,11 @@ describe('ViewManager.setViewByParameters', () => {
|
||||
]),
|
||||
);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
|
||||
vi.mocked(api.getCameraManager().getCameraCapabilities).mockReturnValue(createCameraCapabilities({
|
||||
vi.mocked(api.getCameraManager().getAggregateCameraCapabilities).mockReturnValue(
|
||||
createAggregateCameraCapabilities({
|
||||
supportsSnapshots: false,
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
const manager = new ViewManager(api);
|
||||
manager.setViewByParameters({
|
||||
@@ -392,8 +397,8 @@ describe('ViewManager.setViewByParameters', () => {
|
||||
},
|
||||
]),
|
||||
);
|
||||
vi.mocked(api.getCameraManager().getCameraCapabilities).mockReturnValue(
|
||||
createCameraCapabilities({
|
||||
vi.mocked(api.getCameraManager().getAggregateCameraCapabilities).mockReturnValue(
|
||||
createAggregateCameraCapabilities({
|
||||
supportsClips: true,
|
||||
supportsRecordings: true,
|
||||
supportsSnapshots: true,
|
||||
@@ -443,8 +448,8 @@ describe('ViewManager.setViewByParameters', () => {
|
||||
},
|
||||
]),
|
||||
);
|
||||
vi.mocked(api.getCameraManager().getCameraCapabilities).mockReturnValue(
|
||||
createCameraCapabilities({
|
||||
vi.mocked(api.getCameraManager().getAggregateCameraCapabilities).mockReturnValue(
|
||||
createAggregateCameraCapabilities({
|
||||
supportsClips: true,
|
||||
supportsRecordings: true,
|
||||
supportsSnapshots: true,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { CameraManager } from '../../src/camera-manager/manager';
|
||||
import { CameraManagerCameraMetadata } from '../../src/camera-manager/types';
|
||||
import { MediaPlayerManager } from '../../src/card-controller/media-player-manager';
|
||||
import { MicrophoneManager } from '../../src/card-controller/microphone-manager';
|
||||
import { ViewManager } from '../../src/card-controller/view-manager';
|
||||
import {
|
||||
MenuButtonController,
|
||||
MenuButtonControllerOptions,
|
||||
@@ -17,7 +18,6 @@ import { ViewMedia } from '../../src/view/media';
|
||||
import { MediaQueriesResults } from '../../src/view/media-queries-results';
|
||||
import { View } from '../../src/view/view';
|
||||
import {
|
||||
createAggregateCameraCapabilities,
|
||||
createCameraCapabilities,
|
||||
createCameraConfig,
|
||||
createCameraManager,
|
||||
@@ -41,6 +41,7 @@ const calculateButtons = (
|
||||
config?: FrigateCardConfig;
|
||||
cameraManager?: CameraManager;
|
||||
view?: View;
|
||||
viewManager?: ViewManager;
|
||||
},
|
||||
): MenuItem[] => {
|
||||
let cameraManager: CameraManager | null = options?.cameraManager ?? null;
|
||||
@@ -71,8 +72,10 @@ describe('MenuButtonController', () => {
|
||||
controller = new MenuButtonController();
|
||||
});
|
||||
|
||||
it('should have frigate menu button with hidden menu style', () => {
|
||||
describe('should have frigate menu button', () => {
|
||||
it('with hidden menu style', () => {
|
||||
const buttons = calculateButtons(controller);
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'frigate',
|
||||
enabled: true,
|
||||
@@ -84,10 +87,11 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have frigate menu button without hidden menu style', () => {
|
||||
it('without hidden menu style', () => {
|
||||
const buttons = calculateButtons(controller, {
|
||||
config: createConfig({ menu: { style: 'overlay' } }),
|
||||
});
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'frigate',
|
||||
enabled: true,
|
||||
@@ -98,8 +102,10 @@ describe('MenuButtonController', () => {
|
||||
hold_action: createFrigateCardSimpleAction('diagnostics'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should have cameras menu', () => {
|
||||
describe('should have cameras menu', () => {
|
||||
it('with multiple cameras', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore).mockReturnValue(
|
||||
createStore([{ cameraID: 'camera-1' }, { cameraID: 'camera-2' }]),
|
||||
@@ -108,8 +114,8 @@ describe('MenuButtonController', () => {
|
||||
title: 'title',
|
||||
icon: 'icon',
|
||||
});
|
||||
|
||||
const buttons = calculateButtons(controller, { cameraManager: cameraManager });
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:video-switch',
|
||||
enabled: true,
|
||||
@@ -147,15 +153,15 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not have a cameras menu without a visible camera', () => {
|
||||
it('without a visible camera', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore).mockReturnValue(
|
||||
createStore([
|
||||
{ cameraID: 'camera-1', config: createCameraConfig({ hide: true }) },
|
||||
]),
|
||||
);
|
||||
|
||||
const buttons = calculateButtons(controller, { cameraManager: cameraManager });
|
||||
|
||||
expect(buttons).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
@@ -164,8 +170,10 @@ describe('MenuButtonController', () => {
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have substream button with single dependency', () => {
|
||||
describe('should have substream button', () => {
|
||||
it('with single dependency', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore).mockReturnValue(
|
||||
createStore([
|
||||
@@ -176,8 +184,8 @@ describe('MenuButtonController', () => {
|
||||
{ cameraID: 'camera-2' },
|
||||
]),
|
||||
);
|
||||
|
||||
const buttons = calculateButtons(controller, { cameraManager: cameraManager });
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:video-input-component',
|
||||
style: {},
|
||||
@@ -192,7 +200,7 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have substream button selected with single dependency', () => {
|
||||
it('with substream selected and single dependency', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore).mockReturnValue(
|
||||
createStore([
|
||||
@@ -203,7 +211,6 @@ describe('MenuButtonController', () => {
|
||||
{ cameraID: 'camera-2' },
|
||||
]),
|
||||
);
|
||||
|
||||
const view = createView({
|
||||
camera: 'camera-1',
|
||||
context: {
|
||||
@@ -212,11 +219,11 @@ describe('MenuButtonController', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const buttons = calculateButtons(controller, {
|
||||
cameraManager: cameraManager,
|
||||
view: view,
|
||||
});
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:video-input-component',
|
||||
style: { color: 'var(--primary-color, white)' },
|
||||
@@ -231,7 +238,7 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have substream menu without substream on with multiple dependencies', () => {
|
||||
it('with substream selected and multiple dependencies', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore).mockReturnValue(
|
||||
createStore([
|
||||
@@ -271,6 +278,7 @@ describe('MenuButtonController', () => {
|
||||
);
|
||||
|
||||
const buttons = calculateButtons(controller, { cameraManager: cameraManager });
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:video-input-component',
|
||||
title: 'Substream(s)',
|
||||
@@ -322,7 +330,7 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have substream menu with substream on with multiple dependencies', () => {
|
||||
it('with substream selected and with multiple dependencies', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore).mockReturnValue(
|
||||
createStore([
|
||||
@@ -347,7 +355,6 @@ describe('MenuButtonController', () => {
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
const view = createView({
|
||||
camera: 'camera-1',
|
||||
context: {
|
||||
@@ -356,7 +363,6 @@ describe('MenuButtonController', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const buttons = calculateButtons(controller, {
|
||||
cameraManager: cameraManager,
|
||||
view: view,
|
||||
@@ -414,9 +420,12 @@ describe('MenuButtonController', () => {
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should have styled live menu button in live view', () => {
|
||||
describe('should have live menu button', () => {
|
||||
it('when in live view', () => {
|
||||
const buttons = calculateButtons(controller);
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:cctv',
|
||||
enabled: true,
|
||||
@@ -428,9 +437,10 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have unstyled live menu button in non-live views', () => {
|
||||
it('when not in live view', () => {
|
||||
const view = createView({ view: 'clips' });
|
||||
const buttons = calculateButtons(controller, { view: view });
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:cctv',
|
||||
enabled: true,
|
||||
@@ -441,16 +451,17 @@ describe('MenuButtonController', () => {
|
||||
tap_action: { action: 'fire-dom-event', frigate_card_action: 'live' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should have styled clips menu button in clips view', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getAggregateCameraCapabilities).mockReturnValue(
|
||||
createAggregateCameraCapabilities({ supportsClips: true }),
|
||||
);
|
||||
describe('should have clips menu button', () => {
|
||||
it('when in clips view', () => {
|
||||
const viewManager = mock<ViewManager>();
|
||||
vi.mocked(viewManager.isViewSupportedByCamera).mockReturnValue(true);
|
||||
const buttons = calculateButtons(controller, {
|
||||
cameraManager: cameraManager,
|
||||
view: createView({ view: 'clips' }),
|
||||
viewManager: viewManager,
|
||||
});
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:filmstrip',
|
||||
enabled: true,
|
||||
@@ -463,12 +474,13 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have unstyled clips menu button in non-clips view', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getAggregateCameraCapabilities).mockReturnValue(
|
||||
createAggregateCameraCapabilities({ supportsClips: true }),
|
||||
);
|
||||
const buttons = calculateButtons(controller, { cameraManager: cameraManager });
|
||||
it('when not in clips view', () => {
|
||||
const viewManager = mock<ViewManager>();
|
||||
vi.mocked(viewManager.isViewSupportedByCamera).mockReturnValue(true);
|
||||
const buttons = calculateButtons(controller, {
|
||||
viewManager: viewManager,
|
||||
});
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:filmstrip',
|
||||
enabled: true,
|
||||
@@ -481,15 +493,28 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have styled snapshots menu button in snapshots view', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getAggregateCameraCapabilities).mockReturnValue(
|
||||
createAggregateCameraCapabilities({ supportsSnapshots: true }),
|
||||
it('when not supported', () => {
|
||||
const viewManager = mock<ViewManager>();
|
||||
vi.mocked(viewManager.isViewSupportedByCamera).mockReturnValue(false);
|
||||
const buttons = calculateButtons(controller, {
|
||||
viewManager: viewManager,
|
||||
});
|
||||
|
||||
expect(buttons).not.toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ title: 'Clips gallery' })]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should have snapshots menu button', () => {
|
||||
it('when in snapshots view', () => {
|
||||
const viewManager = mock<ViewManager>();
|
||||
vi.mocked(viewManager.isViewSupportedByCamera).mockReturnValue(true);
|
||||
const buttons = calculateButtons(controller, {
|
||||
cameraManager: cameraManager,
|
||||
view: createView({ view: 'snapshots' }),
|
||||
viewManager: viewManager,
|
||||
});
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:camera',
|
||||
enabled: true,
|
||||
@@ -502,33 +527,49 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have unstyled snapshots menu button in non-snapshots view', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getAggregateCameraCapabilities).mockReturnValue(
|
||||
createAggregateCameraCapabilities({ supportsSnapshots: true }),
|
||||
);
|
||||
const buttons = calculateButtons(controller, { cameraManager: cameraManager });
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:camera',
|
||||
enabled: true,
|
||||
priority: 50,
|
||||
type: 'custom:frigate-card-menu-icon',
|
||||
title: 'Snapshots gallery',
|
||||
style: {},
|
||||
tap_action: { action: 'fire-dom-event', frigate_card_action: 'snapshots' },
|
||||
hold_action: { action: 'fire-dom-event', frigate_card_action: 'snapshot' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should have styled recordings menu button in recordings view', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getAggregateCameraCapabilities).mockReturnValue(
|
||||
createAggregateCameraCapabilities({ supportsRecordings: true }),
|
||||
);
|
||||
it('when not in snapshots view', () => {
|
||||
const viewManager = mock<ViewManager>();
|
||||
vi.mocked(viewManager.isViewSupportedByCamera).mockReturnValue(true);
|
||||
const buttons = calculateButtons(controller, {
|
||||
cameraManager: cameraManager,
|
||||
view: createView({ view: 'recordings' }),
|
||||
viewManager: viewManager,
|
||||
});
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:camera',
|
||||
enabled: true,
|
||||
priority: 50,
|
||||
type: 'custom:frigate-card-menu-icon',
|
||||
title: 'Snapshots gallery',
|
||||
style: {},
|
||||
tap_action: { action: 'fire-dom-event', frigate_card_action: 'snapshots' },
|
||||
hold_action: { action: 'fire-dom-event', frigate_card_action: 'snapshot' },
|
||||
});
|
||||
});
|
||||
|
||||
it('when not supported', () => {
|
||||
const viewManager = mock<ViewManager>();
|
||||
vi.mocked(viewManager.isViewSupportedByCamera).mockReturnValue(false);
|
||||
const buttons = calculateButtons(controller, {
|
||||
viewManager: viewManager,
|
||||
});
|
||||
|
||||
expect(buttons).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ title: 'Snapshots gallery' }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should have recordings menu button', () => {
|
||||
it('when in recordings view', () => {
|
||||
const viewManager = mock<ViewManager>();
|
||||
vi.mocked(viewManager.isViewSupportedByCamera).mockReturnValue(true);
|
||||
const buttons = calculateButtons(controller, {
|
||||
view: createView({ view: 'recordings' }),
|
||||
viewManager: viewManager,
|
||||
});
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:album',
|
||||
enabled: false,
|
||||
@@ -541,12 +582,13 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have unstyled recordings menu button in non-recordings view', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getAggregateCameraCapabilities).mockReturnValue(
|
||||
createAggregateCameraCapabilities({ supportsRecordings: true }),
|
||||
);
|
||||
const buttons = calculateButtons(controller, { cameraManager: cameraManager });
|
||||
it('when not in recordings view', () => {
|
||||
const viewManager = mock<ViewManager>();
|
||||
vi.mocked(viewManager.isViewSupportedByCamera).mockReturnValue(true);
|
||||
const buttons = calculateButtons(controller, {
|
||||
viewManager: viewManager,
|
||||
});
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:album',
|
||||
enabled: false,
|
||||
@@ -559,10 +601,27 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have styled image menu button in image view', () => {
|
||||
it('when not supported', () => {
|
||||
const viewManager = mock<ViewManager>();
|
||||
vi.mocked(viewManager.isViewSupportedByCamera).mockReturnValue(false);
|
||||
const buttons = calculateButtons(controller, {
|
||||
viewManager: viewManager,
|
||||
});
|
||||
|
||||
expect(buttons).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ title: 'Recordings gallery' }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should have image menu button', () => {
|
||||
it('when in image view', () => {
|
||||
const buttons = calculateButtons(controller, {
|
||||
view: createView({ view: 'image' }),
|
||||
});
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:image',
|
||||
enabled: false,
|
||||
@@ -574,8 +633,9 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have unstyled image menu button in non-image view', () => {
|
||||
it('when not in image view', () => {
|
||||
const buttons = calculateButtons(controller);
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:image',
|
||||
enabled: false,
|
||||
@@ -586,16 +646,17 @@ describe('MenuButtonController', () => {
|
||||
tap_action: { action: 'fire-dom-event', frigate_card_action: 'image' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should have styled timeline menu button in timeline view', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getAggregateCameraCapabilities).mockReturnValue(
|
||||
createAggregateCameraCapabilities({ supportsTimeline: true }),
|
||||
);
|
||||
const buttons = calculateButtons(controller, {
|
||||
cameraManager: cameraManager,
|
||||
view: createView({ view: 'timeline' }),
|
||||
});
|
||||
|
||||
describe('should have timeline button', () => {
|
||||
it('when in timeline view', () => {
|
||||
const viewManager = mock<ViewManager>();
|
||||
vi.mocked(viewManager.isViewSupportedByCamera).mockReturnValue(true);
|
||||
const buttons = calculateButtons(controller, {
|
||||
view: createView({ view: 'timeline' }),
|
||||
viewManager: viewManager,
|
||||
});
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:chart-gantt',
|
||||
enabled: true,
|
||||
@@ -607,14 +668,14 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have unstyled timeline menu button in non-timeline view', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getAggregateCameraCapabilities).mockReturnValue(
|
||||
createAggregateCameraCapabilities({ supportsTimeline: true }),
|
||||
);
|
||||
it('when not in timeline view', () => {
|
||||
const viewManager = mock<ViewManager>();
|
||||
vi.mocked(viewManager.isViewSupportedByCamera).mockReturnValue(true);
|
||||
const buttons = calculateButtons(controller, {
|
||||
cameraManager: cameraManager,
|
||||
view: createView({ view: 'live' }),
|
||||
viewManager: viewManager,
|
||||
});
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:chart-gantt',
|
||||
enabled: true,
|
||||
@@ -626,7 +687,22 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have download menu button', () => {
|
||||
it('when not supported', () => {
|
||||
const viewManager = mock<ViewManager>();
|
||||
vi.mocked(viewManager.isViewSupportedByCamera).mockReturnValue(false);
|
||||
const buttons = calculateButtons(controller, {
|
||||
view: createView({ view: 'live' }),
|
||||
viewManager: viewManager,
|
||||
});
|
||||
|
||||
expect(buttons).not.toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ title: 'Timeline view' })]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should have download button', () => {
|
||||
it('when media available', () => {
|
||||
vi.stubGlobal('navigator', { userAgent: 'foo' });
|
||||
|
||||
const cameraManager = createCameraManager();
|
||||
@@ -643,6 +719,7 @@ describe('MenuButtonController', () => {
|
||||
cameraManager: cameraManager,
|
||||
view: view,
|
||||
});
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:download',
|
||||
enabled: true,
|
||||
@@ -653,7 +730,7 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not have download menu button when being casted', () => {
|
||||
it('not when being casted', () => {
|
||||
vi.stubGlobal('navigator', {
|
||||
userAgent:
|
||||
'Mozilla/5.0 (Fuchsia) AppleWebKit/537.36 (KHTML, like Gecko) ' +
|
||||
@@ -674,15 +751,18 @@ describe('MenuButtonController', () => {
|
||||
cameraManager: cameraManager,
|
||||
view: view,
|
||||
});
|
||||
|
||||
expect(buttons).not.toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ title: 'Download' })]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have camera UI button', () => {
|
||||
const buttons = calculateButtons(controller, {
|
||||
showCameraUIButton: true,
|
||||
});
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
icon: 'mdi:web',
|
||||
enabled: true,
|
||||
@@ -693,7 +773,8 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have microphone button', () => {
|
||||
describe('should have microphone button', () => {
|
||||
it('with suitable loaded media', () => {
|
||||
const microphoneManager = new MicrophoneManager(createCardAPI());
|
||||
const buttons = calculateButtons(controller, {
|
||||
microphoneManager: microphoneManager,
|
||||
@@ -725,7 +806,7 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not have microphone button when media does not support it', () => {
|
||||
it('without suitable loaded media', () => {
|
||||
const microphoneManager = new MicrophoneManager(createCardAPI());
|
||||
const buttons = calculateButtons(controller, {
|
||||
microphoneManager: microphoneManager,
|
||||
@@ -741,7 +822,7 @@ describe('MenuButtonController', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should have microphone button when microphone forbidden', () => {
|
||||
it('with forbidden microphone', () => {
|
||||
const microphoneManager = new MicrophoneManager(createCardAPI());
|
||||
mock<MicrophoneManager>(microphoneManager).isForbidden.mockReturnValue(true);
|
||||
const buttons = calculateButtons(controller, {
|
||||
@@ -763,7 +844,7 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have microphone button when microphone muted', () => {
|
||||
it('with muted microphone', () => {
|
||||
const microphoneManager = new MicrophoneManager(createCardAPI());
|
||||
mock<MicrophoneManager>(microphoneManager).isMuted.mockReturnValue(true);
|
||||
const buttons = calculateButtons(controller, {
|
||||
@@ -793,7 +874,7 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have microphone button when microphone muted with toggle type', () => {
|
||||
it('with muted toggle type microphone', () => {
|
||||
const microphoneManager = new MicrophoneManager(createCardAPI());
|
||||
mock<MicrophoneManager>(microphoneManager).isMuted.mockReturnValue(true);
|
||||
const buttons = calculateButtons(controller, {
|
||||
@@ -822,7 +903,7 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have microphone button when microphone unmuted with toggle type', () => {
|
||||
it('with unmuted toggle type microphone', () => {
|
||||
const microphoneManager = new MicrophoneManager(createCardAPI());
|
||||
mock<MicrophoneManager>(microphoneManager).isMuted.mockReturnValue(false);
|
||||
const buttons = calculateButtons(controller, {
|
||||
@@ -853,8 +934,10 @@ describe('MenuButtonController', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should have fullscreen button', () => {
|
||||
describe('should have fullscreen button', () => {
|
||||
it('when not in fullscreen mode', () => {
|
||||
// Need to write a readonly property.
|
||||
vi.stubGlobal('navigator', { userAgent: 'foo' });
|
||||
const buttons = calculateButtons(controller, { inFullscreenMode: false });
|
||||
@@ -870,7 +953,7 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have unfullscreen', () => {
|
||||
it('when in fullscreen mode', () => {
|
||||
vi.stubGlobal('navigator', { userAgent: 'foo' });
|
||||
const buttons = calculateButtons(controller, { inFullscreenMode: true });
|
||||
|
||||
@@ -884,8 +967,10 @@ describe('MenuButtonController', () => {
|
||||
style: { color: 'var(--primary-color, white)' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should have expand button', () => {
|
||||
describe('should have expand button', () => {
|
||||
it('when not expanded', () => {
|
||||
const buttons = calculateButtons(controller, { inExpandedMode: false });
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
@@ -899,7 +984,7 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should have unexpand button', () => {
|
||||
it('when expanded', () => {
|
||||
const buttons = calculateButtons(controller, { inExpandedMode: true });
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
@@ -912,8 +997,10 @@ describe('MenuButtonController', () => {
|
||||
style: { color: 'var(--primary-color, white)' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should have media players button', () => {
|
||||
describe('should have media players button', () => {
|
||||
it('with media players', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore).mockReturnValue(
|
||||
createStore([
|
||||
@@ -970,7 +1057,7 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should disable media players button when entity not found', () => {
|
||||
it('when entity not found', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore).mockReturnValue(
|
||||
createStore([
|
||||
@@ -1011,6 +1098,7 @@ describe('MenuButtonController', () => {
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should have pause button', () => {
|
||||
const player = mock<FrigateCardMediaPlayer>();
|
||||
@@ -1124,6 +1212,7 @@ describe('MenuButtonController', () => {
|
||||
vi.mocked(cameraManager.getStore).mockReturnValue(
|
||||
createStore([{ cameraID: 'camera-1' }, { cameraID: 'camera-2' }]),
|
||||
);
|
||||
|
||||
expect(
|
||||
calculateButtons(controller, { cameraManager: cameraManager, view: view }),
|
||||
).toContainEqual({
|
||||
@@ -1154,6 +1243,7 @@ describe('MenuButtonController', () => {
|
||||
);
|
||||
|
||||
const buttons = calculateButtons(controller, { cameraManager: cameraManager });
|
||||
|
||||
expect(buttons).not.toContainEqual({
|
||||
enabled: false,
|
||||
icon: 'mdi:pan',
|
||||
@@ -1176,8 +1266,8 @@ describe('MenuButtonController', () => {
|
||||
vi.mocked(cameraManager.getCameraCapabilities).mockReturnValue(
|
||||
createCameraCapabilities({ ptz: {} }),
|
||||
);
|
||||
|
||||
const buttons = calculateButtons(controller, { cameraManager: cameraManager });
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
enabled: false,
|
||||
icon: 'mdi:pan',
|
||||
@@ -1204,11 +1294,11 @@ describe('MenuButtonController', () => {
|
||||
camera: 'camera-1',
|
||||
context: { live: { ptzVisible: false } },
|
||||
});
|
||||
|
||||
const buttons = calculateButtons(controller, {
|
||||
cameraManager: cameraManager,
|
||||
view: view,
|
||||
});
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
enabled: false,
|
||||
icon: 'mdi:pan',
|
||||
@@ -1223,14 +1313,65 @@ describe('MenuButtonController', () => {
|
||||
type: 'custom:frigate-card-menu-icon',
|
||||
});
|
||||
});
|
||||
|
||||
it('when a substream is PTZ enabled', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getStore).mockReturnValue(
|
||||
createStore([
|
||||
{
|
||||
cameraID: 'camera-1',
|
||||
config: createCameraConfig({ dependencies: { cameras: ['camera-2'] } }),
|
||||
},
|
||||
{ cameraID: 'camera-2' },
|
||||
]),
|
||||
);
|
||||
vi.mocked(cameraManager.getCameraCapabilities).mockImplementation(
|
||||
(cameraID: string) => {
|
||||
if (cameraID === 'camera-2') {
|
||||
return createCameraCapabilities({ ptz: {} });
|
||||
}
|
||||
return createCameraCapabilities();
|
||||
},
|
||||
);
|
||||
const view = createView({
|
||||
camera: 'camera-1',
|
||||
context: {
|
||||
live: {
|
||||
overrides: new Map([['camera-1', 'camera-2']]),
|
||||
},
|
||||
},
|
||||
});
|
||||
const buttons = calculateButtons(controller, {
|
||||
cameraManager: cameraManager,
|
||||
view: view,
|
||||
});
|
||||
|
||||
it('should handle dynamic buttons', () => {
|
||||
expect(buttons).toContainEqual({
|
||||
enabled: false,
|
||||
icon: 'mdi:pan',
|
||||
priority: 50,
|
||||
style: {
|
||||
color: 'var(--primary-color, white)',
|
||||
},
|
||||
tap_action: {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'show_ptz',
|
||||
show_ptz: false,
|
||||
},
|
||||
title: 'Show PTZ controls',
|
||||
type: 'custom:frigate-card-menu-icon',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle dynamic buttons', () => {
|
||||
it('successfully', () => {
|
||||
const button: MenuItem = {
|
||||
...dynamicButton,
|
||||
style: {},
|
||||
};
|
||||
controller.addDynamicMenuButton(button);
|
||||
|
||||
expect(
|
||||
calculateButtons(controller).filter((menuButton) => isEqual(button, menuButton))
|
||||
.length,
|
||||
@@ -1247,7 +1388,7 @@ describe('MenuButtonController', () => {
|
||||
expect(calculateButtons(controller)).not.toContainEqual(button);
|
||||
});
|
||||
|
||||
it('should not set style for dynamic button with stock action', () => {
|
||||
it('with stock HA action', () => {
|
||||
const button: MenuItem = {
|
||||
...dynamicButton,
|
||||
tap_action: { action: 'navigate', navigation_path: 'foo' },
|
||||
@@ -1260,61 +1401,61 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should not set style for dynamic button with non-Frigate fire-dom-event action', () => {
|
||||
it('with non-Frigate fire-dom-event action', () => {
|
||||
const button: MenuItem = {
|
||||
...dynamicButton,
|
||||
tap_action: { action: 'fire-dom-event' },
|
||||
};
|
||||
controller.addDynamicMenuButton(button);
|
||||
|
||||
controller.addDynamicMenuButton(dynamicButton);
|
||||
|
||||
expect(calculateButtons(controller)).toContainEqual({
|
||||
...button,
|
||||
style: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should set style for dynamic button with Frigate view action', () => {
|
||||
it('with Frigate view action', () => {
|
||||
const button: MenuItem = {
|
||||
...dynamicButton,
|
||||
tap_action: { action: 'fire-dom-event', frigate_card_action: 'clips' },
|
||||
};
|
||||
|
||||
const view = createView({ view: 'clips' });
|
||||
controller.addDynamicMenuButton(button);
|
||||
|
||||
expect(calculateButtons(controller, { view: view })).toContainEqual({
|
||||
...button,
|
||||
style: { color: 'var(--primary-color, white)' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should set style for dynamic button with Frigate default action', () => {
|
||||
it('with Frigate default action', () => {
|
||||
const button: MenuItem = {
|
||||
...dynamicButton,
|
||||
tap_action: { action: 'fire-dom-event', frigate_card_action: 'default' },
|
||||
};
|
||||
|
||||
controller.addDynamicMenuButton(button);
|
||||
|
||||
expect(calculateButtons(controller)).toContainEqual({
|
||||
...button,
|
||||
style: { color: 'var(--primary-color, white)' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should set style for dynamic button with fullscreen action', () => {
|
||||
it('with fullscreen action', () => {
|
||||
const button: MenuItem = {
|
||||
...dynamicButton,
|
||||
tap_action: { action: 'fire-dom-event', frigate_card_action: 'fullscreen' },
|
||||
};
|
||||
|
||||
controller.addDynamicMenuButton(button);
|
||||
|
||||
expect(calculateButtons(controller, { inFullscreenMode: true })).toContainEqual({
|
||||
...button,
|
||||
style: { color: 'var(--primary-color, white)' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should set style for dynamic button with camera_select action', () => {
|
||||
it('with camera_select action', () => {
|
||||
const button: MenuItem = {
|
||||
...dynamicButton,
|
||||
tap_action: {
|
||||
@@ -1323,16 +1464,16 @@ describe('MenuButtonController', () => {
|
||||
camera: 'foo',
|
||||
},
|
||||
};
|
||||
|
||||
const view = createView({ camera: 'foo' });
|
||||
controller.addDynamicMenuButton(button);
|
||||
|
||||
expect(calculateButtons(controller, { view: view })).toContainEqual({
|
||||
...button,
|
||||
style: { color: 'var(--primary-color, white)' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should set style for dynamic button with array of actions', () => {
|
||||
it('with array of actions', () => {
|
||||
const button: MenuItem = {
|
||||
...dynamicButton,
|
||||
tap_action: [
|
||||
@@ -1340,12 +1481,13 @@ describe('MenuButtonController', () => {
|
||||
{ action: 'fire-dom-event', frigate_card_action: 'clips' },
|
||||
],
|
||||
};
|
||||
|
||||
const view = createView({ camera: 'clips' });
|
||||
controller.addDynamicMenuButton(button);
|
||||
|
||||
expect(calculateButtons(controller, { view: view })).toContainEqual({
|
||||
...button,
|
||||
style: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user