Add initial keyboard shortcut support.
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
ActionsManager,
|
||||
Interaction,
|
||||
InteractionName,
|
||||
} from '../../../src/card-controller/actions/actions-manager';
|
||||
import { FrigateCardView } from '../../../src/config/types';
|
||||
import { createLogAction } from '../../../src/utils/action';
|
||||
import {
|
||||
createAction,
|
||||
createCardAPI,
|
||||
createConfig,
|
||||
createHASS,
|
||||
createView,
|
||||
} from '../../test-utils';
|
||||
|
||||
describe('ActionsManager', () => {
|
||||
describe('getMergedActions', () => {
|
||||
const config = {
|
||||
view: {
|
||||
actions: {
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '1',
|
||||
},
|
||||
},
|
||||
},
|
||||
live: {
|
||||
actions: {
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '2',
|
||||
},
|
||||
},
|
||||
},
|
||||
media_gallery: {
|
||||
actions: {
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '3',
|
||||
},
|
||||
},
|
||||
},
|
||||
media_viewer: {
|
||||
actions: {
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '4',
|
||||
},
|
||||
},
|
||||
},
|
||||
image: {
|
||||
actions: {
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '5',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
afterAll(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should get no merged actions with a message', () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ view: 'live' }),
|
||||
);
|
||||
vi.mocked(api.getMessageManager().hasMessage).mockReturnValue(true);
|
||||
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
expect(manager.getMergedActions()).toEqual({});
|
||||
});
|
||||
|
||||
describe('should get merged actions with live view', () => {
|
||||
it.each([
|
||||
[
|
||||
'live' as const,
|
||||
{
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '2',
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'clips' as const,
|
||||
{
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '3',
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'clip' as const,
|
||||
{
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '4',
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'image' as const,
|
||||
{
|
||||
tap_action: {
|
||||
action: 'navigate',
|
||||
navigation_path: '5',
|
||||
},
|
||||
},
|
||||
],
|
||||
['timeline' as const, {}],
|
||||
])('%s', (viewName: FrigateCardView, result: Record<string, unknown>) => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ view: viewName }),
|
||||
);
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig(config),
|
||||
);
|
||||
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
expect(manager.getMergedActions()).toEqual(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('handleInteractionEvent', () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should handle interaction', () => {
|
||||
const api = createCardAPI();
|
||||
const element = document.createElement('div');
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
actions: {
|
||||
tap_action: createLogAction("Hello, world!"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
const hass = createHASS();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
|
||||
|
||||
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
|
||||
manager.handleInteractionEvent(
|
||||
new CustomEvent<Interaction>('event', { detail: { action: 'tap' } }),
|
||||
);
|
||||
expect(consoleSpy).toBeCalled();
|
||||
});
|
||||
|
||||
describe('should handle unexpected interactions', () => {
|
||||
it.each([['malformed_type_of_tap' as const], ['double_tap' as const]])(
|
||||
'%s',
|
||||
(interaction: string) => {
|
||||
const api = createCardAPI();
|
||||
const element = document.createElement('div');
|
||||
vi.mocked(api.getCardElementManager().getElement).mockReturnValue(element);
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
actions: {
|
||||
tap_action: createLogAction("Hello, world!"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
const hass = createHASS();
|
||||
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(hass);
|
||||
|
||||
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
|
||||
manager.handleInteractionEvent(
|
||||
new CustomEvent<Interaction>('event', {
|
||||
detail: { action: interaction as unknown as InteractionName },
|
||||
}),
|
||||
);
|
||||
expect(consoleSpy).not.toBeCalled();
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleCustomActionEvent', () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should handle event', () => {
|
||||
const action = createLogAction("Hello, world!");
|
||||
const event = new CustomEvent('ll-custom', {
|
||||
detail: action,
|
||||
});
|
||||
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
|
||||
manager.handleCustomActionEvent(event);
|
||||
expect(consoleSpy).toBeCalled();
|
||||
});
|
||||
|
||||
it('should not handle event without detail', () => {
|
||||
const manager = new ActionsManager(createCardAPI());
|
||||
|
||||
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
|
||||
manager.handleCustomActionEvent(new Event('ll-custom'));
|
||||
expect(consoleSpy).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleActionExecutionRequestEvent', () => {
|
||||
it('should execute actions', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
|
||||
await manager.handleActionExecutionRequestEvent(
|
||||
new CustomEvent('frigate-card:action:execution-request', {
|
||||
detail: { action: createLogAction("Hello, world!") },
|
||||
}),
|
||||
);
|
||||
expect(consoleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('executeAction', () => {
|
||||
it('should execute actions', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
|
||||
await manager.executeActions(createLogAction("Hello, world!"));
|
||||
expect(consoleSpy).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('uninitialize', () => {
|
||||
beforeAll(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
afterAll(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should stop actions', async () => {
|
||||
const api = createCardAPI();
|
||||
const manager = new ActionsManager(api);
|
||||
|
||||
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
|
||||
const promise = manager.executeActions([
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
createAction({
|
||||
frigate_card_action: 'sleep',
|
||||
duration: {
|
||||
m: 1,
|
||||
},
|
||||
})!,
|
||||
createLogAction("Hello, world!"),
|
||||
]);
|
||||
|
||||
// Stop inflight actions.
|
||||
manager.uninitialize();
|
||||
|
||||
// Advance timers (causes the sleep to end).
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
await promise;
|
||||
|
||||
// Action set will not continue.
|
||||
expect(consoleSpy).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { it } from 'vitest';
|
||||
import { BaseAction } from '../../../../src/card-controller/actions/actions/base';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
|
||||
it('should construct', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new BaseAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
await action.stop();
|
||||
|
||||
// These methods have no observable effect on the base class, so this test is
|
||||
// currently only providing coverage and proof of no exceptions!
|
||||
});
|
||||
@@ -0,0 +1,163 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { CameraSelectAction } from '../../../../src/card-controller/actions/actions/camera-select';
|
||||
import { createCardAPI, createConfig, createView } from '../../../test-utils';
|
||||
|
||||
describe('should handle camera_select action', () => {
|
||||
it('with valid camera and view', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||
|
||||
const action = new CameraSelectAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'camera_select',
|
||||
camera: 'camera',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
viewName: 'live',
|
||||
cameraID: 'camera',
|
||||
failSafe: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('without config', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(null);
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
view: 'timeline',
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||
|
||||
const action = new CameraSelectAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'camera_select',
|
||||
camera: 'camera',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
viewName: 'timeline',
|
||||
cameraID: 'camera',
|
||||
failSafe: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('with target view', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
|
||||
createConfig({
|
||||
view: {
|
||||
// Change to clips view when the camera changes.
|
||||
camera_select: 'clips',
|
||||
},
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
view: 'live',
|
||||
}),
|
||||
);
|
||||
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||
|
||||
const action = new CameraSelectAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'camera_select',
|
||||
camera: 'camera',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
viewName: 'clips',
|
||||
cameraID: 'camera',
|
||||
failSafe: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('with triggered camera', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||
vi.mocked(api.getTriggersManager().getMostRecentlyTriggeredCameraID).mockReturnValue(
|
||||
'camera',
|
||||
);
|
||||
|
||||
const action = new CameraSelectAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'camera_select',
|
||||
triggered: true,
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
viewName: 'live',
|
||||
cameraID: 'camera',
|
||||
failSafe: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('without camera or triggered camera', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||
vi.mocked(api.getViewManager().isViewSupportedByCamera).mockReturnValue(true);
|
||||
vi.mocked(api.getTriggersManager().getMostRecentlyTriggeredCameraID).mockReturnValue(
|
||||
'camera',
|
||||
);
|
||||
|
||||
const action = new CameraSelectAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'camera_select',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('without a current view', async () => {
|
||||
const api = createCardAPI();
|
||||
|
||||
const action = new CameraSelectAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'camera_select',
|
||||
camera: 'camera',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { expect, it } from 'vitest';
|
||||
import { CameraUIAction } from '../../../../src/card-controller/actions/actions/camera-ui';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
|
||||
it('should handle camera_ui action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new CameraUIAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'camera_ui',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraURLManager().openURL).toBeCalled();
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { DefaultAction } from "../../../../src/card-controller/actions/actions/default";
|
||||
import { createCardAPI } from "../../../test-utils";
|
||||
|
||||
it('should handle default action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new DefaultAction({}, {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'default',
|
||||
});
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewDefault).toBeCalled();
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { DisplayModeSelectAction } from "../../../../src/card-controller/actions/actions/display-mode-select";
|
||||
import { createCardAPI } from "../../../test-utils";
|
||||
|
||||
it('should handle default action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new DisplayModeSelectAction({}, {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'display_mode_select',
|
||||
display_mode: 'grid',
|
||||
});
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewWithNewDisplayMode).toBeCalledWith('grid');
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { expect, it } from 'vitest';
|
||||
import { DownloadAction } from '../../../../src/card-controller/actions/actions/download';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
|
||||
it('should handle download action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new DownloadAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'download',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getDownloadManager().downloadViewerMedia).toBeCalled();
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { expect, it } from 'vitest';
|
||||
import { ExpandAction } from '../../../../src/card-controller/actions/actions/expand';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
|
||||
it('should handle expand action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new ExpandAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'expand',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getExpandManager().toggleExpanded).toBeCalled();
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { expect, it } from 'vitest';
|
||||
import { FullscreenAction } from '../../../../src/card-controller/actions/actions/fullscreen';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
|
||||
it('should handle fullscreen action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new FullscreenAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'fullscreen',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getFullscreenManager().toggleFullscreen).toBeCalled();
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { createCardAPI, createHASS, createLitElement } from '../../../test-utils';
|
||||
import { GenericAction } from '../../../../src/card-controller/actions/actions/generic';
|
||||
import { handleActionConfig } from '@dermotduffy/custom-card-helpers';
|
||||
|
||||
vi.mock('@dermotduffy/custom-card-helpers');
|
||||
|
||||
describe('should handle generic action', () => {
|
||||
it('without hass', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new GenericAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(handleActionConfig).not.toBeCalled();
|
||||
});
|
||||
|
||||
// @vitest-environment jsdom
|
||||
it('with hass', async () => {
|
||||
const api = createCardAPI();
|
||||
const hass = createHASS();
|
||||
const element = createLitElement();
|
||||
vi.mocked(api.getCardElementManager()).getElement.mockReturnValue(element);
|
||||
vi.mocked(api.getHASSManager()).getHASS.mockReturnValue(hass);
|
||||
const action = new GenericAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(handleActionConfig).toBeCalledWith(
|
||||
element,
|
||||
hass,
|
||||
{},
|
||||
{ action: 'fire-dom-event' },
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { afterEach, expect, it, vi } from 'vitest';
|
||||
import { LogAction } from '../../../../src/card-controller/actions/actions/log';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should handle log action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new LogAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'log',
|
||||
message: 'Hello, world!',
|
||||
level: 'warn',
|
||||
},
|
||||
);
|
||||
|
||||
const spy = vi.spyOn(global.console, 'warn').mockImplementation(() => true);
|
||||
await action.execute(api);
|
||||
expect(spy).toBeCalledWith('Hello, world!');
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { MediaPlayerAction } from '../../../../src/card-controller/actions/actions/media-player';
|
||||
import { createCardAPI, createView, createViewWithMedia } from '../../../test-utils';
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('should handle media_player action', () => {
|
||||
it('to stop', async () => {
|
||||
const api = createCardAPI();
|
||||
|
||||
const action = new MediaPlayerAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'media_player',
|
||||
media_player_action: 'stop',
|
||||
media_player: 'this_is_a_media_player',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getMediaPlayerManager().stop).toBeCalledWith('this_is_a_media_player');
|
||||
});
|
||||
|
||||
it('to play live', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
camera: 'camera',
|
||||
view: 'live',
|
||||
}),
|
||||
);
|
||||
|
||||
const action = new MediaPlayerAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'media_player',
|
||||
media_player_action: 'play',
|
||||
media_player: 'this_is_a_media_player',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getMediaPlayerManager().playLive).toBeCalledWith(
|
||||
'this_is_a_media_player',
|
||||
'camera',
|
||||
);
|
||||
});
|
||||
|
||||
it('to play media', async () => {
|
||||
const api = createCardAPI();
|
||||
const view = createViewWithMedia({
|
||||
camera: 'camera',
|
||||
view: 'media',
|
||||
});
|
||||
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(view);
|
||||
|
||||
const action = new MediaPlayerAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'media_player',
|
||||
media_player_action: 'play',
|
||||
media_player: 'this_is_a_media_player',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getMediaPlayerManager().playMedia).toBeCalledWith(
|
||||
'this_is_a_media_player',
|
||||
view.queryResults?.getSelectedResult(),
|
||||
);
|
||||
});
|
||||
|
||||
it('to play media without selected media', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
view: 'media',
|
||||
}),
|
||||
);
|
||||
|
||||
const action = new MediaPlayerAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'media_player',
|
||||
media_player_action: 'play',
|
||||
media_player: 'this_is_a_media_player',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getMediaPlayerManager().playMedia).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { expect, it } from 'vitest';
|
||||
import { MenuToggleAction } from '../../../../src/card-controller/actions/actions/menu-toggle';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
|
||||
it('should handle menu toggle action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new MenuToggleAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'menu_toggle',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCardElementManager().toggleMenu).toBeCalled();
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { expect, it } from 'vitest';
|
||||
import { MicrophoneMuteAction } from '../../../../src/card-controller/actions/actions/microphone-mute';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
|
||||
it('should handle microphone_mute action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new MicrophoneMuteAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'microphone_mute',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getMicrophoneManager().mute).toBeCalled();
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { expect, it } from 'vitest';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
import { MicrophoneUnmuteAction } from '../../../../src/card-controller/actions/actions/microphone-unmute';
|
||||
|
||||
it('should handle microphone_unmute action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new MicrophoneUnmuteAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'microphone_unmute',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getMicrophoneManager().unmute).toBeCalled();
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { MuteAction } from '../../../../src/card-controller/actions/actions/mute';
|
||||
import { FrigateCardMediaPlayer } from '../../../../src/types';
|
||||
import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils';
|
||||
|
||||
it('should handle mute action', async () => {
|
||||
const api = createCardAPI();
|
||||
const player = mock<FrigateCardMediaPlayer>();
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
}),
|
||||
);
|
||||
const action = new MuteAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'mute',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(player.mute).toBeCalled();
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { PauseAction } from '../../../../src/card-controller/actions/actions/pause';
|
||||
import { FrigateCardMediaPlayer } from '../../../../src/types';
|
||||
import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils';
|
||||
|
||||
it('should handle pause action', async () => {
|
||||
const api = createCardAPI();
|
||||
const player = mock<FrigateCardMediaPlayer>();
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
}),
|
||||
);
|
||||
const action = new PauseAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'pause',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(player.pause).toBeCalled();
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { PlayAction } from '../../../../src/card-controller/actions/actions/play';
|
||||
import { FrigateCardMediaPlayer } from '../../../../src/types';
|
||||
import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils';
|
||||
|
||||
it('should handle play action', async () => {
|
||||
const api = createCardAPI();
|
||||
const player = mock<FrigateCardMediaPlayer>();
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
}),
|
||||
);
|
||||
const action = new PlayAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'play',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(player.play).toBeCalled();
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { expect, it } from 'vitest';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
import { PTZControlsAction } from '../../../../src/card-controller/actions/actions/ptz-controls';
|
||||
|
||||
it('should handle ptz_controls action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new PTZControlsAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_controls',
|
||||
enabled: true,
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewWithMergedContext).toBeCalledWith(
|
||||
expect.objectContaining({ ptzControls: { enabled: true } }),
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,479 @@
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import { PTZDigitalAction } from '../../../../src/card-controller/actions/actions/ptz-digital';
|
||||
import {
|
||||
PartialZoomSettings,
|
||||
ZoomSettingsObserved,
|
||||
} from '../../../../src/components-lib/zoom/types';
|
||||
import { PTZAction } from '../../../../src/config/ptz';
|
||||
import { createCardAPI, createView } from '../../../test-utils';
|
||||
|
||||
describe('should handle ptz digital action', () => {
|
||||
const defaultSettings = {
|
||||
pan: {
|
||||
x: 50,
|
||||
y: 50,
|
||||
},
|
||||
zoom: 1,
|
||||
};
|
||||
|
||||
const createObserved = (
|
||||
observed?: Partial<ZoomSettingsObserved>,
|
||||
): ZoomSettingsObserved => ({
|
||||
...defaultSettings,
|
||||
isDefault: true,
|
||||
unzoomed: true,
|
||||
...observed,
|
||||
});
|
||||
|
||||
it('should honor absolute parameters', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||
|
||||
const action = new PTZDigitalAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_digital',
|
||||
absolute: {
|
||||
zoom: 2,
|
||||
pan: {
|
||||
x: 3,
|
||||
y: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewWithMergedContext).toBeCalledWith({
|
||||
zoom: {
|
||||
camera: {
|
||||
observed: undefined,
|
||||
requested: {
|
||||
pan: {
|
||||
x: 3,
|
||||
y: 4,
|
||||
},
|
||||
zoom: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return to default without absolute parameters or action', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||
|
||||
const action = new PTZDigitalAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_digital',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewWithMergedContext).toBeCalledWith({
|
||||
zoom: {
|
||||
camera: {
|
||||
observed: undefined,
|
||||
requested: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should do nothing without a view', async () => {
|
||||
const api = createCardAPI();
|
||||
|
||||
const action = new PTZDigitalAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_digital',
|
||||
ptz_action: 'left',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewWithMergedContext).not.toBeCalledWith();
|
||||
});
|
||||
|
||||
it('should do nothing without a camera', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
// There is no media associated with a timeline, so there's no camera to
|
||||
// change the PTZ settings for.
|
||||
view: 'timeline',
|
||||
}),
|
||||
);
|
||||
|
||||
const action = new PTZDigitalAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_digital',
|
||||
ptz_action: 'left',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewWithMergedContext).not.toBeCalled();
|
||||
});
|
||||
|
||||
describe('should honor ptz_action', () => {
|
||||
it.each([
|
||||
[
|
||||
'zoom_in',
|
||||
'zoom_in' as const,
|
||||
{
|
||||
zoom: 1.1,
|
||||
},
|
||||
createObserved(),
|
||||
],
|
||||
[
|
||||
'zoom_in at maximum zoom',
|
||||
'zoom_in' as const,
|
||||
{
|
||||
zoom: 10,
|
||||
},
|
||||
createObserved({
|
||||
zoom: 10,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'zoom_out',
|
||||
'zoom_out' as const,
|
||||
{
|
||||
zoom: 1.9,
|
||||
},
|
||||
createObserved({
|
||||
zoom: 2,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'zoom_out at minimum zoom',
|
||||
'zoom_out' as const,
|
||||
{
|
||||
zoom: 1,
|
||||
},
|
||||
createObserved({
|
||||
zoom: 1,
|
||||
}),
|
||||
],
|
||||
[
|
||||
'left',
|
||||
'left' as const,
|
||||
{
|
||||
pan: {
|
||||
x: 45,
|
||||
y: 50,
|
||||
},
|
||||
},
|
||||
createObserved({
|
||||
pan: {
|
||||
x: 50,
|
||||
y: 50,
|
||||
},
|
||||
}),
|
||||
],
|
||||
[
|
||||
'left at left edge',
|
||||
'left' as const,
|
||||
{
|
||||
pan: {
|
||||
x: 0,
|
||||
y: 50,
|
||||
},
|
||||
},
|
||||
createObserved({
|
||||
pan: {
|
||||
x: 0,
|
||||
y: 50,
|
||||
},
|
||||
}),
|
||||
],
|
||||
[
|
||||
'right',
|
||||
'right' as const,
|
||||
{
|
||||
pan: {
|
||||
x: 55,
|
||||
y: 50,
|
||||
},
|
||||
},
|
||||
createObserved({
|
||||
pan: {
|
||||
x: 50,
|
||||
y: 50,
|
||||
},
|
||||
}),
|
||||
],
|
||||
[
|
||||
'right at right edge',
|
||||
'right' as const,
|
||||
{
|
||||
pan: {
|
||||
x: 100,
|
||||
y: 50,
|
||||
},
|
||||
},
|
||||
createObserved({
|
||||
pan: {
|
||||
x: 100,
|
||||
y: 50,
|
||||
},
|
||||
}),
|
||||
],
|
||||
[
|
||||
'up',
|
||||
'up' as const,
|
||||
{
|
||||
pan: {
|
||||
x: 50,
|
||||
y: 45,
|
||||
},
|
||||
},
|
||||
createObserved({
|
||||
pan: {
|
||||
x: 50,
|
||||
y: 50,
|
||||
},
|
||||
}),
|
||||
],
|
||||
[
|
||||
'up at top edge',
|
||||
'up' as const,
|
||||
{
|
||||
pan: {
|
||||
x: 50,
|
||||
y: 0,
|
||||
},
|
||||
},
|
||||
createObserved({
|
||||
pan: {
|
||||
x: 50,
|
||||
y: 0,
|
||||
},
|
||||
}),
|
||||
],
|
||||
[
|
||||
'down',
|
||||
'down' as const,
|
||||
{
|
||||
pan: {
|
||||
x: 50,
|
||||
y: 55,
|
||||
},
|
||||
},
|
||||
createObserved({
|
||||
pan: {
|
||||
x: 50,
|
||||
y: 50,
|
||||
},
|
||||
}),
|
||||
],
|
||||
[
|
||||
'down at bottom edge',
|
||||
'down' as const,
|
||||
{
|
||||
pan: {
|
||||
x: 50,
|
||||
y: 100,
|
||||
},
|
||||
},
|
||||
createObserved({
|
||||
pan: {
|
||||
x: 50,
|
||||
y: 100,
|
||||
},
|
||||
}),
|
||||
],
|
||||
[
|
||||
'action with undefined observed',
|
||||
'down' as const,
|
||||
{
|
||||
pan: {
|
||||
x: 50,
|
||||
y: 55,
|
||||
},
|
||||
},
|
||||
],
|
||||
])(
|
||||
'%s',
|
||||
async (
|
||||
_testTitle: string,
|
||||
ptzAction: PTZAction,
|
||||
expectedSettings: PartialZoomSettings,
|
||||
current?: ZoomSettingsObserved,
|
||||
) => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
context: {
|
||||
zoom: {
|
||||
camera: {
|
||||
observed: current,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const action = new PTZDigitalAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_digital',
|
||||
ptz_action: ptzAction,
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewWithMergedContext).toBeCalledWith({
|
||||
zoom: {
|
||||
camera: {
|
||||
observed: undefined,
|
||||
requested: {
|
||||
...defaultSettings,
|
||||
...expectedSettings,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('should honor ptz_phase', () => {
|
||||
beforeAll(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('start', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||
|
||||
const action = new PTZDigitalAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_digital',
|
||||
ptz_action: 'right',
|
||||
ptz_phase: 'start',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenLastCalledWith({
|
||||
zoom: {
|
||||
camera: {
|
||||
observed: undefined,
|
||||
requested: {
|
||||
...defaultSettings,
|
||||
pan: {
|
||||
x: 55,
|
||||
y: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Update the context to reflect the first step.
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
context: {
|
||||
zoom: {
|
||||
camera: {
|
||||
observed: createObserved({
|
||||
pan: {
|
||||
x: 55,
|
||||
y: 50,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenLastCalledWith({
|
||||
zoom: {
|
||||
camera: {
|
||||
observed: undefined,
|
||||
requested: {
|
||||
...defaultSettings,
|
||||
pan: {
|
||||
x: 60,
|
||||
y: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(api.getViewManager().setViewWithMergedContext).toBeCalledTimes(2);
|
||||
|
||||
action.stop();
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(api.getViewManager().setViewWithMergedContext).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it('stop', async () => {
|
||||
const api = createCardAPI();
|
||||
const context = {};
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(createView());
|
||||
|
||||
const startAction = new PTZDigitalAction(context, {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_digital',
|
||||
ptz_action: 'right',
|
||||
ptz_phase: 'start',
|
||||
});
|
||||
await startAction.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenLastCalledWith({
|
||||
zoom: {
|
||||
camera: {
|
||||
observed: undefined,
|
||||
requested: {
|
||||
...defaultSettings,
|
||||
pan: {
|
||||
x: 55,
|
||||
y: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(api.getViewManager().setViewWithMergedContext).toBeCalledTimes(1);
|
||||
|
||||
const stopAction = new PTZDigitalAction(context, {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_digital',
|
||||
ptz_phase: 'stop',
|
||||
});
|
||||
await stopAction.execute(api);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(api.getViewManager().setViewWithMergedContext).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,150 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { Capabilities } from '../../../../src/camera-manager/capabilities';
|
||||
import { PTZMultiAction } from '../../../../src/card-controller/actions/actions/ptz-multi';
|
||||
import {
|
||||
createCameraManager,
|
||||
createCardAPI,
|
||||
createStore,
|
||||
createView,
|
||||
} from '../../../test-utils';
|
||||
|
||||
describe('should handle ptz multi action', () => {
|
||||
describe.each([
|
||||
['with explicit target_id', 'camera.office'],
|
||||
['without explicit target_id', null],
|
||||
])('%s', async (_testTitle: string, targetID: string | null) => {
|
||||
it('should use real ptz when camera has ptz support', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
camera: 'camera.office',
|
||||
}),
|
||||
);
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({ ptz: { left: ['relative'] } }),
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
|
||||
const action = new PTZMultiAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_multi',
|
||||
ptz_action: 'left',
|
||||
...(targetID && { target_id: targetID }),
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
|
||||
'camera.office',
|
||||
'left',
|
||||
{
|
||||
phase: undefined,
|
||||
preset: undefined,
|
||||
},
|
||||
);
|
||||
expect(api.getViewManager().setViewWithMergedContext).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should use digital ptz when camera does not have ptz support', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
camera: 'camera.office',
|
||||
}),
|
||||
);
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
|
||||
const action = new PTZMultiAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_multi',
|
||||
ptz_action: 'right',
|
||||
...(targetID && { target_id: targetID }),
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewWithMergedContext).toHaveBeenLastCalledWith({
|
||||
zoom: {
|
||||
'camera.office': {
|
||||
observed: undefined,
|
||||
requested: expect.objectContaining({
|
||||
pan: {
|
||||
x: 55,
|
||||
y: 50,
|
||||
},
|
||||
zoom: 1,
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should do nothing without a view or explicit target_id', async () => {
|
||||
const api = createCardAPI();
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
|
||||
const action = new PTZMultiAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_multi',
|
||||
ptz_action: 'right',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewWithMergedContext).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should do nothing with a media-less view without an explicit target_id', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
view: 'timeline',
|
||||
}),
|
||||
);
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
|
||||
const action = new PTZMultiAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_multi',
|
||||
ptz_action: 'right',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).not.toBeCalled();
|
||||
expect(api.getViewManager().setViewWithMergedContext).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,513 @@
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import { PTZAction } from '../../../../src/card-controller/actions/actions/ptz';
|
||||
import {
|
||||
createCameraConfig,
|
||||
createCameraManager,
|
||||
createCardAPI,
|
||||
createStore,
|
||||
createView,
|
||||
} from '../../../test-utils';
|
||||
import { Capabilities } from '../../../../src/camera-manager/capabilities';
|
||||
|
||||
describe('should handle ptz action', () => {
|
||||
it('should execute simple action', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
camera: 'camera.office',
|
||||
}),
|
||||
);
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({ ptz: { left: ['relative'] } }),
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
|
||||
const action = new PTZAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: 'left',
|
||||
camera: 'camera.office',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
|
||||
'camera.office',
|
||||
'left',
|
||||
{
|
||||
phase: undefined,
|
||||
preset: undefined,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('without explicit camera', () => {
|
||||
it('when current camera supports PTZ', async () => {
|
||||
const api = createCardAPI();
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({ ptz: { left: ['relative'] } }),
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ camera: 'camera.office' }),
|
||||
);
|
||||
|
||||
const action = new PTZAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: 'left',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
|
||||
'camera.office',
|
||||
'left',
|
||||
{
|
||||
phase: undefined,
|
||||
preset: undefined,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('when substream supports PTZ', async () => {
|
||||
const api = createCardAPI();
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
config: createCameraConfig({
|
||||
dependencies: { cameras: ['camera.office_hd'] },
|
||||
}),
|
||||
},
|
||||
{
|
||||
cameraID: 'camera.office_hd',
|
||||
capabilities: new Capabilities({ ptz: { left: ['relative'] } }),
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({
|
||||
camera: 'camera.office',
|
||||
context: {
|
||||
live: {
|
||||
overrides: new Map([['camera.office', 'camera.office_hd']]),
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const action = new PTZAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: 'left',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
|
||||
'camera.office_hd',
|
||||
'left',
|
||||
{
|
||||
phase: undefined,
|
||||
preset: undefined,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('when no camera supports PTZ', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ camera: 'camera.office' }),
|
||||
);
|
||||
|
||||
const action = new PTZAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: 'left',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('when there is no view', async () => {
|
||||
const api = createCardAPI();
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager());
|
||||
|
||||
const action = new PTZAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: 'left',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).not.toBeCalled();
|
||||
});
|
||||
|
||||
describe('when there is no action', () => {
|
||||
it('should call first preset', async () => {
|
||||
const api = createCardAPI();
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({ ptz: { presets: ['home'] } }),
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ camera: 'camera.office' }),
|
||||
);
|
||||
|
||||
const action = new PTZAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
|
||||
'camera.office',
|
||||
'preset',
|
||||
{
|
||||
phase: undefined,
|
||||
preset: 'home',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should not call preset when there are no presets', async () => {
|
||||
const api = createCardAPI();
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({
|
||||
ptz: {
|
||||
left: ['relative'],
|
||||
presets: [],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ camera: 'camera.office' }),
|
||||
);
|
||||
|
||||
const action = new PTZAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should execute preset', async () => {
|
||||
const api = createCardAPI();
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({
|
||||
ptz: {
|
||||
presets: ['window'],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ camera: 'camera.office' }),
|
||||
);
|
||||
|
||||
const action = new PTZAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: 'preset',
|
||||
ptz_preset: 'window',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
|
||||
'camera.office',
|
||||
'preset',
|
||||
{
|
||||
phase: undefined,
|
||||
preset: 'window',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should execute action with phase', async () => {
|
||||
const api = createCardAPI();
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({
|
||||
ptz: {
|
||||
left: ['continuous'],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ camera: 'camera.office' }),
|
||||
);
|
||||
|
||||
const action = new PTZAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: 'left',
|
||||
ptz_phase: 'start',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
|
||||
'camera.office',
|
||||
'left',
|
||||
{
|
||||
phase: 'start',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('when relative is requested but unsupported', () => {
|
||||
beforeAll(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should emulate relative', async () => {
|
||||
const api = createCardAPI();
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({
|
||||
ptz: {
|
||||
left: ['continuous'],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ camera: 'camera.office' }),
|
||||
);
|
||||
|
||||
const action = new PTZAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: 'left',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
|
||||
'camera.office',
|
||||
'left',
|
||||
{
|
||||
phase: 'start',
|
||||
},
|
||||
);
|
||||
|
||||
vi.runOnlyPendingTimers();
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
|
||||
'camera.office',
|
||||
'left',
|
||||
{
|
||||
phase: 'stop',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should honor stop', async () => {
|
||||
const api = createCardAPI();
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({
|
||||
ptz: {
|
||||
left: ['continuous'],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ camera: 'camera.office' }),
|
||||
);
|
||||
|
||||
const context = {};
|
||||
const action = new PTZAction(context, {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: 'left',
|
||||
});
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(1);
|
||||
|
||||
action.stop();
|
||||
vi.runOnlyPendingTimers();
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when continuous is requested but unsupported', () => {
|
||||
beforeAll(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should emulate continuous', async () => {
|
||||
const api = createCardAPI();
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({
|
||||
ptz: {
|
||||
left: ['relative'],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ camera: 'camera.office' }),
|
||||
);
|
||||
|
||||
const context = {};
|
||||
const startAction = new PTZAction(
|
||||
context,
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: 'left',
|
||||
ptz_phase: 'start',
|
||||
},
|
||||
);
|
||||
await startAction.execute(api);
|
||||
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledWith(
|
||||
'camera.office',
|
||||
'left',
|
||||
{
|
||||
phase: undefined,
|
||||
},
|
||||
);
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(1);
|
||||
|
||||
await vi.runOnlyPendingTimersAsync()
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(2);
|
||||
|
||||
await vi.runOnlyPendingTimersAsync()
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(3);
|
||||
|
||||
const stopAction = new PTZAction(
|
||||
context,
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: 'left',
|
||||
ptz_phase: 'stop',
|
||||
},
|
||||
);
|
||||
await stopAction.execute(api);
|
||||
|
||||
// There should be no additional calls.
|
||||
await vi.runOnlyPendingTimersAsync()
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should honor stop', async () => {
|
||||
const api = createCardAPI();
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({
|
||||
ptz: {
|
||||
left: ['relative'],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager(store));
|
||||
vi.mocked(api.getViewManager().getView).mockReturnValue(
|
||||
createView({ camera: 'camera.office' }),
|
||||
);
|
||||
|
||||
const context = {};
|
||||
const action = new PTZAction(context, {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: 'left',
|
||||
ptz_phase: 'start',
|
||||
});
|
||||
|
||||
await action.execute(api);
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(1);
|
||||
|
||||
await vi.runOnlyPendingTimersAsync()
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(2);
|
||||
|
||||
action.stop();
|
||||
await vi.runOnlyPendingTimersAsync()
|
||||
|
||||
// There should be no additional calls.
|
||||
expect(api.getCameraManager().executePTZAction).toBeCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { expect, it } from 'vitest';
|
||||
import { ScreenshotAction } from '../../../../src/card-controller/actions/actions/screenshot';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
|
||||
it('should handle screenshot action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new ScreenshotAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'screenshot',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getDownloadManager().downloadScreenshot).toBeCalled();
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { ActionSet } from '../../../../src/card-controller/actions/actions/set';
|
||||
import { createLogAction } from '../../../../src/utils/action';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
|
||||
describe('ActionSet', () => {
|
||||
it('should execute single action', async () => {
|
||||
const api = createCardAPI();
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const set = new ActionSet({}, createLogAction('Hello, world!'));
|
||||
|
||||
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
|
||||
await set.execute(api);
|
||||
expect(consoleSpy).toBeCalled();
|
||||
});
|
||||
|
||||
it('should not execute invalid action', async () => {
|
||||
const api = createCardAPI();
|
||||
const set = new ActionSet(
|
||||
{},
|
||||
createLogAction('Hello, world!', {
|
||||
cardID: 'another-card',
|
||||
}),
|
||||
);
|
||||
|
||||
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
|
||||
await set.execute(api);
|
||||
expect(consoleSpy).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should stop execution', async () => {
|
||||
const api = createCardAPI();
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const set = new ActionSet({}, createLogAction('Hello, world!'));
|
||||
|
||||
const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined);
|
||||
await set.stop();
|
||||
await set.execute(api);
|
||||
expect(consoleSpy).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { afterAll, expect, it, vi } from 'vitest';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
import { SleepAction } from '../../../../src/card-controller/actions/actions/sleep';
|
||||
import { sleep } from '../../../../src/utils/basic';
|
||||
|
||||
vi.mock('../../../../src/utils/basic');
|
||||
|
||||
afterAll(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('should handle sleep action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new SleepAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'sleep',
|
||||
duration: {
|
||||
s: 5,
|
||||
ms: 200
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(sleep).toBeCalledWith(5.2);
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { expect, it } from 'vitest';
|
||||
import { SubstreamOffAction } from '../../../../src/card-controller/actions/actions/substream-off';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
|
||||
it('should handle live_substream_off action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new SubstreamOffAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'live_substream_off',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewWithoutSubstream).toBeCalled();
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { expect, it } from 'vitest';
|
||||
import { SubstreamOnAction } from '../../../../src/card-controller/actions/actions/substream-on';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
|
||||
it('should handle live_substream_on action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new SubstreamOnAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'live_substream_on',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewWithSubstream).toBeCalledWith();
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { expect, it } from 'vitest';
|
||||
import { SubstreamSelectAction } from '../../../../src/card-controller/actions/actions/substream-select';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
|
||||
it('should handle live_substream_select action', async () => {
|
||||
const api = createCardAPI();
|
||||
const action = new SubstreamSelectAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'live_substream_select',
|
||||
camera: 'substream',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewWithSubstream).toBeCalledWith('substream');
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { expect, it, vi } from 'vitest';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { UnmuteAction } from '../../../../src/card-controller/actions/actions/unmute';
|
||||
import { FrigateCardMediaPlayer } from '../../../../src/types';
|
||||
import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils';
|
||||
|
||||
it('should handle unmute action', async () => {
|
||||
const api = createCardAPI();
|
||||
const player = mock<FrigateCardMediaPlayer>();
|
||||
vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue(
|
||||
createMediaLoadedInfo({
|
||||
player: player,
|
||||
}),
|
||||
);
|
||||
const action = new UnmuteAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'unmute',
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(player.unmute).toBeCalled();
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { ViewAction } from '../../../../src/card-controller/actions/actions/view';
|
||||
import { createCardAPI } from '../../../test-utils';
|
||||
|
||||
describe('should handle view action', () => {
|
||||
it.each([
|
||||
['clip' as const],
|
||||
['clips' as const],
|
||||
['diagnostics' as const],
|
||||
['image' as const],
|
||||
['live' as const],
|
||||
['recording' as const],
|
||||
['recordings' as const],
|
||||
['snapshot' as const],
|
||||
['snapshots' as const],
|
||||
['timeline' as const],
|
||||
])('%s', async (viewName) => {
|
||||
const api = createCardAPI();
|
||||
|
||||
const action = new ViewAction(
|
||||
{},
|
||||
{
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: viewName,
|
||||
},
|
||||
);
|
||||
|
||||
await action.execute(api);
|
||||
|
||||
expect(api.getViewManager().setViewByParameters).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
viewName: viewName,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,138 @@
|
||||
import { describe, expect, it, vi } 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';
|
||||
import { DisplayModeSelectAction } from '../../../src/card-controller/actions/actions/display-mode-select';
|
||||
import { DownloadAction } from '../../../src/card-controller/actions/actions/download';
|
||||
import { ExpandAction } from '../../../src/card-controller/actions/actions/expand';
|
||||
import { FullscreenAction } from '../../../src/card-controller/actions/actions/fullscreen';
|
||||
import { GenericAction } from '../../../src/card-controller/actions/actions/generic';
|
||||
import { LogAction } from '../../../src/card-controller/actions/actions/log';
|
||||
import { MediaPlayerAction } from '../../../src/card-controller/actions/actions/media-player';
|
||||
import { MenuToggleAction } from '../../../src/card-controller/actions/actions/menu-toggle';
|
||||
import { MicrophoneMuteAction } from '../../../src/card-controller/actions/actions/microphone-mute';
|
||||
import { MicrophoneUnmuteAction } from '../../../src/card-controller/actions/actions/microphone-unmute';
|
||||
import { MuteAction } from '../../../src/card-controller/actions/actions/mute';
|
||||
import { PauseAction } from '../../../src/card-controller/actions/actions/pause';
|
||||
import { PlayAction } from '../../../src/card-controller/actions/actions/play';
|
||||
import { PTZAction } from '../../../src/card-controller/actions/actions/ptz';
|
||||
import { PTZControlsAction } from '../../../src/card-controller/actions/actions/ptz-controls';
|
||||
import { PTZDigitalAction } from '../../../src/card-controller/actions/actions/ptz-digital';
|
||||
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 { 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';
|
||||
import { UnmuteAction } from '../../../src/card-controller/actions/actions/unmute';
|
||||
import { ViewAction } from '../../../src/card-controller/actions/actions/view';
|
||||
import { ActionFactory } from '../../../src/card-controller/actions/factory';
|
||||
import { FrigateCardCustomAction } from '../../../src/config/types';
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('ActionFactory', () => {
|
||||
it('mismatched card-id', () => {
|
||||
const factory = new ActionFactory();
|
||||
expect(
|
||||
factory.createAction(
|
||||
{},
|
||||
{ action: 'fire-dom-event', frigate_card_action: 'clip', card_id: 'card_id' },
|
||||
{
|
||||
cardID: 'different_card_id',
|
||||
},
|
||||
),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
describe('generic', () => {
|
||||
it('non frigate card action', () => {
|
||||
const factory = new ActionFactory();
|
||||
expect(factory.createAction({}, { action: 'fire-dom-event' })).toBeInstanceOf(
|
||||
GenericAction,
|
||||
);
|
||||
});
|
||||
|
||||
it('non fire-dom-event', () => {
|
||||
const factory = new ActionFactory();
|
||||
expect(factory.createAction({}, { action: 'more-info' })).toBeInstanceOf(
|
||||
GenericAction,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
it.each([
|
||||
[{ frigate_card_action: 'camera_select' as const }, CameraSelectAction],
|
||||
[{ frigate_card_action: 'camera_ui' as const }, CameraUIAction],
|
||||
[{ frigate_card_action: 'clip' as const }, ViewAction],
|
||||
[{ frigate_card_action: 'clips' as const }, ViewAction],
|
||||
[{ frigate_card_action: 'default' as const }, DefaultAction],
|
||||
[{ frigate_card_action: 'diagnostics' as const }, ViewAction],
|
||||
[
|
||||
{
|
||||
frigate_card_action: 'display_mode_select' as const,
|
||||
display_mode: 'single' as const,
|
||||
},
|
||||
DisplayModeSelectAction,
|
||||
],
|
||||
[{ frigate_card_action: 'download' as const }, DownloadAction],
|
||||
[{ frigate_card_action: 'expand' as const }, ExpandAction],
|
||||
[{ frigate_card_action: 'fullscreen' as const }, FullscreenAction],
|
||||
[{ frigate_card_action: 'image' as const }, ViewAction],
|
||||
[{ frigate_card_action: 'live_substream_off' as const }, SubstreamOffAction],
|
||||
[{ frigate_card_action: 'live_substream_on' as const }, SubstreamOnAction],
|
||||
[
|
||||
{
|
||||
frigate_card_action: 'live_substream_select' as const,
|
||||
camera: 'camera.office',
|
||||
},
|
||||
SubstreamSelectAction,
|
||||
],
|
||||
[{ frigate_card_action: 'live' as const }, ViewAction],
|
||||
[
|
||||
{ frigate_card_action: 'log' as const, message: 'Hello, world!' as const },
|
||||
LogAction,
|
||||
],
|
||||
[
|
||||
{
|
||||
frigate_card_action: 'media_player' as const,
|
||||
media_player: 'media_player.foo' as const,
|
||||
media_player_action: 'play' as const,
|
||||
},
|
||||
MediaPlayerAction,
|
||||
],
|
||||
[{ frigate_card_action: 'menu_toggle' as const }, MenuToggleAction],
|
||||
[{ frigate_card_action: 'microphone_mute' as const }, MicrophoneMuteAction],
|
||||
[{ frigate_card_action: 'microphone_unmute' as const }, MicrophoneUnmuteAction],
|
||||
[{ frigate_card_action: 'mute' as const }, MuteAction],
|
||||
[{ frigate_card_action: 'pause' as const }, PauseAction],
|
||||
[{ frigate_card_action: 'play' as const }, PlayAction],
|
||||
[{ frigate_card_action: 'ptz_digital' as const }, PTZDigitalAction],
|
||||
[
|
||||
{ frigate_card_action: 'ptz_multi' as const, ptz_action: 'right' as const },
|
||||
PTZMultiAction,
|
||||
],
|
||||
[{ frigate_card_action: 'ptz' as const, ptz_action: 'right' as const }, PTZAction],
|
||||
[{ frigate_card_action: 'recording' as const }, ViewAction],
|
||||
[{ frigate_card_action: 'recordings' as const }, ViewAction],
|
||||
[{ frigate_card_action: 'screenshot' as const }, ScreenshotAction],
|
||||
[
|
||||
{ frigate_card_action: 'ptz_controls' as const, enabled: true },
|
||||
PTZControlsAction,
|
||||
],
|
||||
[{ frigate_card_action: 'sleep' as const }, SleepAction],
|
||||
[{ frigate_card_action: 'snapshot' as const }, ViewAction],
|
||||
[{ frigate_card_action: 'snapshots' as const }, ViewAction],
|
||||
[{ frigate_card_action: 'timeline' as const }, ViewAction],
|
||||
[{ frigate_card_action: 'unmute' as const }, UnmuteAction],
|
||||
])(
|
||||
'frigate_card_action: $frigate_card_action',
|
||||
(action: Partial<FrigateCardCustomAction>, classObject: object) => {
|
||||
const factory = new ActionFactory();
|
||||
expect(
|
||||
factory.createAction({}, { action: 'fire-dom-event', ...action }),
|
||||
).toBeInstanceOf(classObject);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { timeDeltaToSeconds } from '../../../../src/card-controller/actions/utils/time-delta';
|
||||
|
||||
describe('timeDeltaToSeconds', () => {
|
||||
it('hours', () => {
|
||||
expect(timeDeltaToSeconds({ h: 1 })).toBe(3600);
|
||||
});
|
||||
it('minutes', () => {
|
||||
expect(timeDeltaToSeconds({ m: 1 })).toBe(60);
|
||||
});
|
||||
it('seconds', () => {
|
||||
expect(timeDeltaToSeconds({ s: 1 })).toBe(1);
|
||||
});
|
||||
it('milliseconds', () => {
|
||||
expect(timeDeltaToSeconds({ ms: 1 })).toBe(0.001);
|
||||
});
|
||||
it('combination', () => {
|
||||
expect(timeDeltaToSeconds({ h: 1, m: 2, s: 3, ms: 4 })).toBe(3723.004);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user