Add initial keyboard shortcut support.
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { KeyAssignerController } from '../../src/components-lib/key-assigner-controller';
|
||||
import { createLitElement } from '../test-utils';
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('KeyAssignerController', () => {
|
||||
it('should be creatable', () => {
|
||||
const controller = new KeyAssignerController(createLitElement());
|
||||
expect(controller).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('should manage value', () => {
|
||||
it('should have no value to start', () => {
|
||||
const controller = new KeyAssignerController(createLitElement());
|
||||
expect(controller.hasValue()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should set value', () => {
|
||||
const element = createLitElement();
|
||||
const valueChangeHandler = vi.fn();
|
||||
element.addEventListener('value-changed', valueChangeHandler);
|
||||
const controller = new KeyAssignerController(element);
|
||||
|
||||
controller.setValue({ key: 'ArrowLeft' });
|
||||
|
||||
expect(controller.hasValue()).toBeTruthy();
|
||||
expect(controller.getValue()).toEqual({ key: 'ArrowLeft' });
|
||||
expect(element.requestUpdate).toBeCalled();
|
||||
expect(valueChangeHandler).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
detail: { value: { key: 'ArrowLeft' } },
|
||||
}),
|
||||
);
|
||||
|
||||
// Set again with the same value.
|
||||
controller.setValue({ key: 'ArrowLeft' });
|
||||
expect(element.requestUpdate).toBeCalledTimes(1);
|
||||
expect(valueChangeHandler).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should manage assignment state', () => {
|
||||
it('should not be assigned to start', () => {
|
||||
const element = createLitElement();
|
||||
const controller = new KeyAssignerController(element);
|
||||
expect(controller.isAssigning()).toBeFalsy();
|
||||
expect(element.getAttribute('assigning')).toBeNull();
|
||||
});
|
||||
|
||||
it('should toggle assigning', () => {
|
||||
const element = createLitElement();
|
||||
const controller = new KeyAssignerController(element);
|
||||
controller.toggleAssigning();
|
||||
expect(element.requestUpdate).toBeCalled();
|
||||
expect(controller.isAssigning()).toBeTruthy();
|
||||
expect(element.getAttribute('assigning')).toBe('');
|
||||
|
||||
expect(controller.hasValue()).toBeFalsy();
|
||||
element.dispatchEvent(new KeyboardEvent('keydown', { key: 'a' }));
|
||||
|
||||
expect(controller.hasValue()).toBeTruthy();
|
||||
expect(controller.isAssigning()).toBeFalsy();
|
||||
|
||||
controller.setValue(null);
|
||||
|
||||
// A key sent when not assigning will do nothing.
|
||||
element.dispatchEvent(new KeyboardEvent('keydown', { key: 'a' }));
|
||||
expect(controller.hasValue()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not assign when focus lost', () => {
|
||||
const element = createLitElement();
|
||||
const controller = new KeyAssignerController(element);
|
||||
|
||||
controller.hostConnected();
|
||||
|
||||
controller.toggleAssigning();
|
||||
expect(controller.isAssigning()).toBeTruthy();
|
||||
|
||||
element.dispatchEvent(new FocusEvent('blur'));
|
||||
expect(controller.isAssigning()).toBeFalsy();
|
||||
|
||||
controller.hostDisconnected();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should manage key down', () => {
|
||||
it('should reject modifiers only', () => {
|
||||
const element = createLitElement();
|
||||
const controller = new KeyAssignerController(element);
|
||||
controller.toggleAssigning();
|
||||
|
||||
element.dispatchEvent(new KeyboardEvent('keydown', { key: 'Control' }));
|
||||
expect(controller.hasValue()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should reject empty key', () => {
|
||||
const element = createLitElement();
|
||||
const controller = new KeyAssignerController(element);
|
||||
controller.toggleAssigning();
|
||||
|
||||
element.dispatchEvent(new KeyboardEvent('keydown', { key: '' }));
|
||||
expect(controller.hasValue()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should accept valid key', () => {
|
||||
const element = createLitElement();
|
||||
const controller = new KeyAssignerController(element);
|
||||
controller.toggleAssigning();
|
||||
|
||||
element.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: 'ArrowLeft',
|
||||
ctrlKey: true,
|
||||
shiftKey: true,
|
||||
altKey: true,
|
||||
metaKey: true,
|
||||
}),
|
||||
);
|
||||
expect(controller.hasValue()).toBeTruthy();
|
||||
expect(controller.getValue()).toEqual({
|
||||
key: 'ArrowLeft',
|
||||
ctrl: true,
|
||||
shift: true,
|
||||
alt: true,
|
||||
meta: true,
|
||||
});
|
||||
expect(controller.isAssigning()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
ViewDisplayMode,
|
||||
} from '../../src/config/types';
|
||||
import { FrigateCardMediaPlayer } from '../../src/types';
|
||||
import { createFrigateCardSimpleAction } from '../../src/utils/action';
|
||||
import { createGeneralAction } from '../../src/utils/action';
|
||||
import { ViewMedia } from '../../src/view/media';
|
||||
import { MediaQueriesResults } from '../../src/view/media-queries-results';
|
||||
import { View } from '../../src/view/view';
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
createView,
|
||||
TestViewMedia,
|
||||
} from '../test-utils';
|
||||
import { Capabilities } from '../../src/camera-manager/capabilities';
|
||||
|
||||
vi.mock('../../src/utils/media-player-controller.js');
|
||||
vi.mock('../../src/card-controller/microphone-manager.js');
|
||||
@@ -88,8 +89,8 @@ describe('MenuButtonController', () => {
|
||||
priority: 50,
|
||||
type: 'custom:frigate-card-menu-icon',
|
||||
title: 'Frigate menu / Default view',
|
||||
tap_action: createFrigateCardSimpleAction('menu_toggle'),
|
||||
hold_action: createFrigateCardSimpleAction('diagnostics'),
|
||||
tap_action: createGeneralAction('menu_toggle'),
|
||||
hold_action: createGeneralAction('diagnostics'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -104,8 +105,8 @@ describe('MenuButtonController', () => {
|
||||
priority: 50,
|
||||
type: 'custom:frigate-card-menu-icon',
|
||||
title: 'Frigate menu / Default view',
|
||||
tap_action: createFrigateCardSimpleAction('default'),
|
||||
hold_action: createFrigateCardSimpleAction('diagnostics'),
|
||||
tap_action: createGeneralAction('default'),
|
||||
hold_action: createGeneralAction('diagnostics'),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1355,13 +1356,14 @@ describe('MenuButtonController', () => {
|
||||
|
||||
describe('should have show ptz button', () => {
|
||||
it('when the selected camera is not PTZ enabled', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getCameraCapabilities).mockReturnValue(
|
||||
createCapabilities(),
|
||||
);
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera-1',
|
||||
},
|
||||
]);
|
||||
|
||||
const buttons = calculateButtons(controller, {
|
||||
cameraManager: cameraManager,
|
||||
cameraManager: createCameraManager(store),
|
||||
view: createView({ view: 'live' }),
|
||||
});
|
||||
|
||||
@@ -1373,13 +1375,15 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
|
||||
it('when not in live view', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getCameraCapabilities).mockReturnValue(
|
||||
createCapabilities({ ptz: { panTilt: ['relative'] } }),
|
||||
);
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera-1',
|
||||
capabilities: new Capabilities({ ptz: { left: ['relative'] } }),
|
||||
},
|
||||
]);
|
||||
|
||||
const buttons = calculateButtons(controller, {
|
||||
cameraManager: cameraManager,
|
||||
cameraManager: createCameraManager(store),
|
||||
view: createView({ view: 'clips' }),
|
||||
});
|
||||
|
||||
@@ -1391,11 +1395,16 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
|
||||
it('when the selected camera is PTZ enabled', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getCameraCapabilities).mockReturnValue(
|
||||
createCapabilities({ ptz: { panTilt: ['relative'] } }),
|
||||
);
|
||||
const buttons = calculateButtons(controller, { cameraManager: cameraManager });
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera-1',
|
||||
capabilities: new Capabilities({ ptz: { left: ['relative'] } }),
|
||||
},
|
||||
]);
|
||||
|
||||
const buttons = calculateButtons(controller, {
|
||||
cameraManager: createCameraManager(store),
|
||||
});
|
||||
|
||||
expect(buttons).toContainEqual({
|
||||
enabled: false,
|
||||
@@ -1406,25 +1415,28 @@ describe('MenuButtonController', () => {
|
||||
},
|
||||
tap_action: {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'show_ptz',
|
||||
show_ptz: false,
|
||||
frigate_card_action: 'ptz_controls',
|
||||
enabled: false,
|
||||
},
|
||||
title: 'Show PTZ controls',
|
||||
type: 'custom:frigate-card-menu-icon',
|
||||
});
|
||||
});
|
||||
|
||||
it('when the context has PTZ visiblity turned off', () => {
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager.getCameraCapabilities).mockReturnValue(
|
||||
createCapabilities({ ptz: { panTilt: ['relative'] } }),
|
||||
);
|
||||
it('when the context has PTZ disabled', () => {
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera-1',
|
||||
capabilities: new Capabilities({ ptz: { left: ['relative'] } }),
|
||||
},
|
||||
]);
|
||||
|
||||
const view = createView({
|
||||
camera: 'camera-1',
|
||||
context: { live: { ptzVisible: false } },
|
||||
context: { ptzControls: { enabled: false } },
|
||||
});
|
||||
const buttons = calculateButtons(controller, {
|
||||
cameraManager: cameraManager,
|
||||
cameraManager: createCameraManager(store),
|
||||
view: view,
|
||||
});
|
||||
|
||||
@@ -1435,8 +1447,8 @@ describe('MenuButtonController', () => {
|
||||
style: {},
|
||||
tap_action: {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'show_ptz',
|
||||
show_ptz: true,
|
||||
frigate_card_action: 'ptz_controls',
|
||||
enabled: true,
|
||||
},
|
||||
title: 'Show PTZ controls',
|
||||
type: 'custom:frigate-card-menu-icon',
|
||||
@@ -1444,24 +1456,16 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
|
||||
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 createCapabilities({ ptz: { panTilt: ['relative'] } });
|
||||
}
|
||||
return createCapabilities();
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera-1',
|
||||
config: createCameraConfig({ dependencies: { cameras: ['camera-2'] } }),
|
||||
},
|
||||
);
|
||||
{
|
||||
cameraID: 'camera-2',
|
||||
capabilities: new Capabilities({ ptz: { left: ['relative'] } }),
|
||||
},
|
||||
]);
|
||||
const view = createView({
|
||||
camera: 'camera-1',
|
||||
context: {
|
||||
@@ -1471,7 +1475,7 @@ describe('MenuButtonController', () => {
|
||||
},
|
||||
});
|
||||
const buttons = calculateButtons(controller, {
|
||||
cameraManager: cameraManager,
|
||||
cameraManager: createCameraManager(store),
|
||||
view: view,
|
||||
});
|
||||
|
||||
@@ -1484,8 +1488,8 @@ describe('MenuButtonController', () => {
|
||||
},
|
||||
tap_action: {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'show_ptz',
|
||||
show_ptz: false,
|
||||
frigate_card_action: 'ptz_controls',
|
||||
enabled: false,
|
||||
},
|
||||
title: 'Show PTZ controls',
|
||||
type: 'custom:frigate-card-menu-icon',
|
||||
@@ -1493,7 +1497,7 @@ describe('MenuButtonController', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('should have change zoom button', () => {
|
||||
describe('should have ptz home button', () => {
|
||||
it.each([
|
||||
['live' as const, true, false],
|
||||
['live' as const, undefined, false],
|
||||
@@ -1521,13 +1525,23 @@ describe('MenuButtonController', () => {
|
||||
results: [new TestViewMedia({ id: 'media-1' })],
|
||||
selectedIndex: 0,
|
||||
}),
|
||||
context: {
|
||||
zoom: {
|
||||
[targetID]: {
|
||||
isDefault: isDefault,
|
||||
...(isDefault !== undefined && {
|
||||
context: {
|
||||
zoom: {
|
||||
[targetID]: {
|
||||
observed: {
|
||||
isDefault: isDefault,
|
||||
unzoomed: false,
|
||||
zoom: 1,
|
||||
pan: {
|
||||
x: 50,
|
||||
y: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
const buttons = calculateButtons(controller, {
|
||||
cameraManager: cameraManager,
|
||||
@@ -1537,19 +1551,19 @@ describe('MenuButtonController', () => {
|
||||
if (expectedResult) {
|
||||
expect(buttons).toContainEqual({
|
||||
enabled: true,
|
||||
icon: 'mdi:magnify-close',
|
||||
icon: 'mdi:home',
|
||||
priority: 50,
|
||||
tap_action: {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'change_zoom',
|
||||
frigate_card_action: 'ptz_multi',
|
||||
target_id: targetID,
|
||||
},
|
||||
title: 'Zoom to default',
|
||||
title: 'PTZ Home',
|
||||
type: 'custom:frigate-card-menu-icon',
|
||||
});
|
||||
} else {
|
||||
expect(buttons).not.toContainEqual(
|
||||
expect.objectContaining({ title: 'Set zoom to default' }),
|
||||
expect.objectContaining({ title: 'Digital zoom to default' }),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -362,53 +362,60 @@ describe('MenuController', () => {
|
||||
describe('should handle actions', () => {
|
||||
it('should bail without config', () => {
|
||||
const controller = new MenuController(createLitElement());
|
||||
controller.actionHandler(createHASS(), createEvent('tap'));
|
||||
controller.actionHandler(createEvent('tap'));
|
||||
expect(vi.mocked(handleActionConfig)).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should execute simple action in non-hidden menu', () => {
|
||||
const host = createLitElement();
|
||||
const hass = createHASS();
|
||||
const handler = vi.fn();
|
||||
host.addEventListener('frigate-card:action:execution-request', handler);
|
||||
|
||||
const controller = new MenuController(host);
|
||||
|
||||
controller.actionHandler(hass, createEvent('tap'), tapActionConfig);
|
||||
expect(vi.mocked(handleActionConfig)).toBeCalledWith(
|
||||
host,
|
||||
hass,
|
||||
tapActionConfig,
|
||||
action,
|
||||
controller.actionHandler(createEvent('tap'), tapActionConfig);
|
||||
expect(handler).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
detail: { action: [action], config: tapActionConfig },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(controller.isExpanded()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should execute simple action in with config in event', () => {
|
||||
const host = createLitElement();
|
||||
const hass = createHASS();
|
||||
const handler = vi.fn();
|
||||
host.addEventListener('frigate-card:action:execution-request', handler);
|
||||
|
||||
const controller = new MenuController(host);
|
||||
|
||||
controller.actionHandler(hass, createEvent('tap', tapActionConfig));
|
||||
expect(vi.mocked(handleActionConfig)).toBeCalledWith(
|
||||
host,
|
||||
hass,
|
||||
tapActionConfig,
|
||||
action,
|
||||
controller.actionHandler(createEvent('tap', tapActionConfig));
|
||||
expect(handler).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
detail: { action: [action], config: tapActionConfig },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should execute simple array of actions in non-hidden menu', () => {
|
||||
const host = createLitElement();
|
||||
const hass = createHASS();
|
||||
const handler = vi.fn();
|
||||
host.addEventListener('frigate-card:action:execution-request', handler);
|
||||
|
||||
const controller = new MenuController(host);
|
||||
|
||||
controller.actionHandler(hass, createEvent('tap'), tapActionConfigMulti);
|
||||
expect(vi.mocked(handleActionConfig)).toBeCalledTimes(3);
|
||||
controller.actionHandler(createEvent('tap'), tapActionConfigMulti);
|
||||
|
||||
expect(handler).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
detail: { action: [action, action, action], config: tapActionConfigMulti },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
describe('should close menu', () => {
|
||||
it('tap', () => {
|
||||
const host = createLitElement();
|
||||
const hass = createHASS();
|
||||
const controller = new MenuController(host);
|
||||
controller.setMenuConfig(
|
||||
createMenuConfig({
|
||||
@@ -419,13 +426,12 @@ describe('MenuController', () => {
|
||||
controller.setExpanded(true);
|
||||
expect(controller.isExpanded()).toBeTruthy();
|
||||
|
||||
controller.actionHandler(hass, createEvent('tap'), tapActionConfig);
|
||||
controller.actionHandler(createEvent('tap'), tapActionConfig);
|
||||
expect(controller.isExpanded()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('end_tap', () => {
|
||||
const host = createLitElement();
|
||||
const hass = createHASS();
|
||||
const controller = new MenuController(host);
|
||||
controller.setMenuConfig(
|
||||
createMenuConfig({
|
||||
@@ -436,7 +442,7 @@ describe('MenuController', () => {
|
||||
controller.setExpanded(true);
|
||||
expect(controller.isExpanded()).toBeTruthy();
|
||||
|
||||
controller.actionHandler(hass, createEvent('end_tap'), {
|
||||
controller.actionHandler(createEvent('end_tap'), {
|
||||
end_tap_action: action,
|
||||
});
|
||||
expect(controller.isExpanded()).toBeFalsy();
|
||||
@@ -446,7 +452,6 @@ describe('MenuController', () => {
|
||||
describe('should not close menu', () => {
|
||||
it('start_tap with later action', () => {
|
||||
const host = createLitElement();
|
||||
const hass = createHASS();
|
||||
const controller = new MenuController(host);
|
||||
controller.setMenuConfig(
|
||||
createMenuConfig({
|
||||
@@ -457,7 +462,7 @@ describe('MenuController', () => {
|
||||
controller.setExpanded(true);
|
||||
expect(controller.isExpanded()).toBeTruthy();
|
||||
|
||||
controller.actionHandler(hass, createEvent('start_tap'), {
|
||||
controller.actionHandler(createEvent('start_tap'), {
|
||||
start_tap_action: action,
|
||||
end_tap_action: action,
|
||||
});
|
||||
@@ -466,7 +471,6 @@ describe('MenuController', () => {
|
||||
|
||||
it('with a menu toggle action', () => {
|
||||
const host = createLitElement();
|
||||
const hass = createHASS();
|
||||
const controller = new MenuController(host);
|
||||
controller.setMenuConfig(
|
||||
createMenuConfig({
|
||||
@@ -477,7 +481,7 @@ describe('MenuController', () => {
|
||||
controller.setExpanded(false);
|
||||
expect(controller.isExpanded()).toBeFalsy();
|
||||
|
||||
controller.actionHandler(hass, createEvent('tap'), {
|
||||
controller.actionHandler(createEvent('tap'), {
|
||||
camera_entity: 'foo',
|
||||
tap_action: menuToggleAction,
|
||||
});
|
||||
@@ -497,7 +501,7 @@ describe('MenuController', () => {
|
||||
controller.setExpanded(true);
|
||||
expect(controller.isExpanded()).toBeTruthy();
|
||||
|
||||
controller.actionHandler(hass, createEvent('end_tap'), tapActionConfig);
|
||||
controller.actionHandler(createEvent('end_tap'), tapActionConfig);
|
||||
expect(controller.isExpanded()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,316 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { PTZController } from '../../src/components-lib/ptz-controller';
|
||||
import {
|
||||
FrigateCardPTZConfig,
|
||||
frigateCardPTZSchema,
|
||||
PTZControlAction,
|
||||
} from '../../src/config/types';
|
||||
import {
|
||||
frigateCardHandleActionConfig,
|
||||
getActionConfigGivenAction,
|
||||
} from '../../src/utils/action.js';
|
||||
import {
|
||||
createCapabilities,
|
||||
createCameraManager,
|
||||
createHASS,
|
||||
} from '../test-utils';
|
||||
|
||||
vi.mock('../../src/utils/action.js');
|
||||
|
||||
const createConfig = (config?: Partial<FrigateCardPTZConfig>): FrigateCardPTZConfig => {
|
||||
return frigateCardPTZSchema.parse({
|
||||
...config,
|
||||
});
|
||||
};
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('PTZController', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should be creatable', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
expect(controller).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should get config creatable', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
const config = createConfig();
|
||||
controller.setConfig(config);
|
||||
expect(controller.getConfig()).toBe(config);
|
||||
});
|
||||
|
||||
describe('should set element attributes', () => {
|
||||
describe('orientation', () => {
|
||||
describe('with config', () => {
|
||||
it.each([['horizontal' as const], ['vertical' as const]])(
|
||||
'%s',
|
||||
(orientation: 'horizontal' | 'vertical') => {
|
||||
const element = document.createElement('div');
|
||||
const controller = new PTZController(element);
|
||||
controller.setConfig(createConfig({ orientation: orientation }));
|
||||
expect(element.getAttribute('data-orientation')).toBe(orientation);
|
||||
},
|
||||
);
|
||||
});
|
||||
it('without config', () => {
|
||||
const element = document.createElement('div');
|
||||
const controller = new PTZController(element);
|
||||
controller.setConfig();
|
||||
expect(element.getAttribute('data-orientation')).toBe('horizontal');
|
||||
});
|
||||
});
|
||||
describe('position', () => {
|
||||
describe('with config', () => {
|
||||
it.each([
|
||||
['top-left' as const],
|
||||
['top-right' as const],
|
||||
['bottom-left' as const],
|
||||
['bottom-right' as const],
|
||||
])(
|
||||
'%s',
|
||||
(position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right') => {
|
||||
const element = document.createElement('div');
|
||||
const controller = new PTZController(element);
|
||||
controller.setConfig(createConfig({ position: position }));
|
||||
expect(element.getAttribute('data-position')).toBe(position);
|
||||
},
|
||||
);
|
||||
});
|
||||
it('without config', () => {
|
||||
const element = document.createElement('div');
|
||||
const controller = new PTZController(element);
|
||||
controller.setConfig();
|
||||
expect(element.getAttribute('data-position')).toBe('bottom-right');
|
||||
});
|
||||
});
|
||||
it('with style in config', () => {
|
||||
const element = document.createElement('div');
|
||||
const controller = new PTZController(element);
|
||||
controller.setConfig(createConfig({ style: { transform: 'none', left: '50%' } }));
|
||||
expect(element.getAttribute('style')).toBe('transform:none;left:50%');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should respect mode', () => {
|
||||
it('off', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
expect(controller.shouldDisplay()).toBeFalsy();
|
||||
});
|
||||
it('off but forced on', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setForceVisibility(true);
|
||||
// No actions, no rendering, no matter what.
|
||||
expect(controller.shouldDisplay()).toBeFalsy();
|
||||
});
|
||||
it('on without actions', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setConfig(createConfig({ mode: 'on' }));
|
||||
expect(controller.shouldDisplay()).toBeFalsy();
|
||||
});
|
||||
it('on with actions', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setConfig(createConfig({ mode: 'on', actions_left: {} }));
|
||||
controller.setCamera(createCameraManager(), 'camera.office');
|
||||
expect(controller.shouldDisplay()).toBeTruthy();
|
||||
});
|
||||
it('on with actions but forced off', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setConfig(createConfig({ mode: 'on', actions_left: {} }));
|
||||
controller.setCamera(createCameraManager(), 'camera.office');
|
||||
controller.setForceVisibility(false);
|
||||
expect(controller.shouldDisplay()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should get PTZ actions', () => {
|
||||
it('without config', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
expect(controller.getPTZActions('left')).toBeNull();
|
||||
});
|
||||
|
||||
describe('using defaults', () => {
|
||||
describe('with continous PTZ', () => {
|
||||
it.each([
|
||||
['left' as const],
|
||||
['right' as const],
|
||||
['up' as const],
|
||||
['down' as const],
|
||||
['zoom_in' as const],
|
||||
['zoom_out' as const],
|
||||
])('%s', (actionName: PTZControlAction) => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setConfig(createConfig());
|
||||
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager).getCameraCapabilities.mockReturnValue(
|
||||
createCapabilities({
|
||||
ptz: {
|
||||
panTilt: ['continuous'],
|
||||
zoom: ['continuous'],
|
||||
},
|
||||
}),
|
||||
);
|
||||
controller.setCamera(cameraManager, 'camera.office');
|
||||
|
||||
expect(controller.getPTZActions(actionName)).toEqual({
|
||||
start_tap_action: {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: actionName,
|
||||
ptz_phase: 'start',
|
||||
},
|
||||
end_tap_action: {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: actionName,
|
||||
ptz_phase: 'stop',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with relative PTZ', () => {
|
||||
it.each([
|
||||
['left' as const],
|
||||
['right' as const],
|
||||
['up' as const],
|
||||
['down' as const],
|
||||
['zoom_in' as const],
|
||||
['zoom_out' as const],
|
||||
])('%s', (actionName: PTZControlAction) => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setConfig(createConfig());
|
||||
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager).getCameraCapabilities.mockReturnValue(
|
||||
createCapabilities({
|
||||
ptz: {
|
||||
panTilt: ['relative'],
|
||||
zoom: ['relative'],
|
||||
},
|
||||
}),
|
||||
);
|
||||
controller.setCamera(cameraManager, 'camera.office');
|
||||
|
||||
expect(controller.getPTZActions(actionName)).toEqual({
|
||||
tap_action: {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: actionName,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('home', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setConfig(createConfig());
|
||||
|
||||
const cameraManager = createCameraManager();
|
||||
vi.mocked(cameraManager).getCameraCapabilities.mockReturnValue(
|
||||
createCapabilities({
|
||||
ptz: {
|
||||
presets: ['preset-foo'],
|
||||
},
|
||||
}),
|
||||
);
|
||||
controller.setCamera(cameraManager, 'camera.office');
|
||||
|
||||
expect(controller.getPTZActions('home')).toEqual({
|
||||
tap_action: {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz',
|
||||
ptz_action: 'preset',
|
||||
ptz_preset: 'preset-foo',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('using config', () => {
|
||||
it.each([
|
||||
['left' as const],
|
||||
['right' as const],
|
||||
['up' as const],
|
||||
['down' as const],
|
||||
['zoom_in' as const],
|
||||
['zoom_out' as const],
|
||||
])('configured %s', (actionName: PTZControlAction) => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setConfig(
|
||||
createConfig({
|
||||
[`actions_${actionName}`]: {
|
||||
argument: actionName,
|
||||
},
|
||||
}),
|
||||
);
|
||||
controller.setCamera(createCameraManager(), 'camera.office');
|
||||
|
||||
expect(controller.getPTZActions(actionName)).toEqual({
|
||||
argument: actionName,
|
||||
});
|
||||
});
|
||||
|
||||
it('configured home', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setConfig(
|
||||
createConfig({
|
||||
actions_home: {
|
||||
argument: 'home',
|
||||
},
|
||||
}),
|
||||
);
|
||||
controller.setCamera(createCameraManager(), 'camera.office');
|
||||
|
||||
expect(controller.getPTZActions('home')).toEqual({
|
||||
argument: 'home',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle action', () => {
|
||||
it('without hass or action', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setHASS();
|
||||
controller.setCamera();
|
||||
controller.handleAction(
|
||||
new CustomEvent<{ action: string }>('@action', { detail: { action: 'tap' } }),
|
||||
);
|
||||
expect(frigateCardHandleActionConfig).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('with action', () => {
|
||||
const element = document.createElement('div');
|
||||
const controller = new PTZController(element);
|
||||
const hass = createHASS();
|
||||
controller.setHASS(hass);
|
||||
controller.setConfig(
|
||||
createConfig({
|
||||
[`actions_left`]: {},
|
||||
}),
|
||||
);
|
||||
const tapAction = {
|
||||
action: 'none' as const,
|
||||
};
|
||||
const actionsConfig = {
|
||||
tap_action: tapAction,
|
||||
};
|
||||
vi.mocked(getActionConfigGivenAction).mockReturnValue(tapAction);
|
||||
|
||||
controller.handleAction(
|
||||
new CustomEvent<{ action: string }>('@action', { detail: { action: 'tap' } }),
|
||||
actionsConfig,
|
||||
);
|
||||
expect(frigateCardHandleActionConfig).toBeCalledWith(
|
||||
element,
|
||||
hass,
|
||||
actionsConfig,
|
||||
'tap',
|
||||
actionsConfig.tap_action,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,378 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { Capabilities } from '../../../src/camera-manager/capabilities';
|
||||
import { PTZController } from '../../../src/components-lib/ptz/ptz-controller';
|
||||
import { PTZControlAction } from '../../../src/config/ptz';
|
||||
import { PTZControlsConfig, ptzControlsConfigSchema } from '../../../src/config/types';
|
||||
import { createCameraManager, createCapabilities, createStore } from '../../test-utils';
|
||||
|
||||
const createConfig = (config?: Partial<PTZControlsConfig>): PTZControlsConfig => {
|
||||
return ptzControlsConfigSchema.parse({
|
||||
...config,
|
||||
});
|
||||
};
|
||||
|
||||
// @vitest-environment jsdom
|
||||
describe('PTZController', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should be creatable', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
expect(controller).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should get config', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
const config = createConfig();
|
||||
controller.setConfig(config);
|
||||
expect(controller.getConfig()).toBe(config);
|
||||
});
|
||||
|
||||
describe('should set element attributes', () => {
|
||||
describe('orientation', () => {
|
||||
describe('with config', () => {
|
||||
it.each([['horizontal' as const], ['vertical' as const]])(
|
||||
'%s',
|
||||
(orientation: 'horizontal' | 'vertical') => {
|
||||
const element = document.createElement('div');
|
||||
const controller = new PTZController(element);
|
||||
controller.setConfig(createConfig({ orientation: orientation }));
|
||||
expect(element.getAttribute('data-orientation')).toBe(orientation);
|
||||
},
|
||||
);
|
||||
});
|
||||
it('without config', () => {
|
||||
const element = document.createElement('div');
|
||||
const controller = new PTZController(element);
|
||||
controller.setConfig();
|
||||
expect(element.getAttribute('data-orientation')).toBe('horizontal');
|
||||
});
|
||||
});
|
||||
describe('position', () => {
|
||||
describe('with config', () => {
|
||||
it.each([
|
||||
['top-left' as const],
|
||||
['top-right' as const],
|
||||
['bottom-left' as const],
|
||||
['bottom-right' as const],
|
||||
])(
|
||||
'%s',
|
||||
(position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right') => {
|
||||
const element = document.createElement('div');
|
||||
const controller = new PTZController(element);
|
||||
controller.setConfig(createConfig({ position: position }));
|
||||
expect(element.getAttribute('data-position')).toBe(position);
|
||||
},
|
||||
);
|
||||
});
|
||||
it('without config', () => {
|
||||
const element = document.createElement('div');
|
||||
const controller = new PTZController(element);
|
||||
controller.setConfig();
|
||||
expect(element.getAttribute('data-position')).toBe('bottom-right');
|
||||
});
|
||||
});
|
||||
it('with style in config', () => {
|
||||
const element = document.createElement('div');
|
||||
const controller = new PTZController(element);
|
||||
controller.setConfig(createConfig({ style: { transform: 'none', left: '50%' } }));
|
||||
expect(element.getAttribute('style')).toBe('transform:none;left:50%');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should respect mode', () => {
|
||||
it('off', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
expect(controller.shouldDisplay()).toBeFalsy();
|
||||
});
|
||||
it('forced on', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setForceVisibility(true);
|
||||
|
||||
expect(controller.shouldDisplay()).toBeTruthy();
|
||||
});
|
||||
it('forced off', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setForceVisibility(false);
|
||||
expect(controller.shouldDisplay()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('configured on', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setConfig(createConfig({ mode: 'on' }));
|
||||
expect(controller.shouldDisplay()).toBeTruthy();
|
||||
});
|
||||
it('configured off', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setConfig(createConfig({ mode: 'off' }));
|
||||
expect(controller.shouldDisplay()).toBeFalsy();
|
||||
});
|
||||
|
||||
it('auto without capability', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setConfig(createConfig({ mode: 'auto' }));
|
||||
controller.setCamera(createCameraManager(), 'camera.office');
|
||||
expect(controller.shouldDisplay()).toBeFalsy();
|
||||
});
|
||||
it('auto with capability', () => {
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({ ptz: { left: ['relative'] } }),
|
||||
},
|
||||
]);
|
||||
const cameraManager = createCameraManager(store);
|
||||
vi.mocked(cameraManager).getCameraCapabilities.mockReturnValue(
|
||||
createCapabilities({
|
||||
ptz: {
|
||||
left: ['relative'],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setConfig(createConfig({ mode: 'auto' }));
|
||||
controller.setCamera(cameraManager, 'camera.office');
|
||||
expect(controller.shouldDisplay()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should get PTZ actions', () => {
|
||||
it.each([
|
||||
['left' as const],
|
||||
['right' as const],
|
||||
['up' as const],
|
||||
['down' as const],
|
||||
['zoom_in' as const],
|
||||
['zoom_out' as const],
|
||||
])('%s', (actionName: PTZControlAction) => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setConfig(createConfig());
|
||||
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({
|
||||
ptz: {
|
||||
left: ['relative'],
|
||||
right: ['relative'],
|
||||
up: ['relative'],
|
||||
down: ['relative'],
|
||||
zoomIn: ['relative'],
|
||||
zoomOut: ['relative'],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
|
||||
const cameraManager = createCameraManager(store);
|
||||
controller.setCamera(cameraManager, 'camera.office');
|
||||
|
||||
expect(controller.getPTZActions()?.[actionName]).toEqual({
|
||||
start_tap_action: {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_multi',
|
||||
ptz_action: actionName,
|
||||
ptz_phase: 'start',
|
||||
},
|
||||
end_tap_action: {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_multi',
|
||||
ptz_action: actionName,
|
||||
ptz_phase: 'stop',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('home', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
controller.setConfig(createConfig());
|
||||
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: new Capabilities({
|
||||
ptz: {
|
||||
left: ['relative'],
|
||||
right: ['relative'],
|
||||
up: ['relative'],
|
||||
down: ['relative'],
|
||||
zoomIn: ['relative'],
|
||||
zoomOut: ['relative'],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
|
||||
const cameraManager = createCameraManager(store);
|
||||
controller.setCamera(cameraManager, 'camera.office');
|
||||
|
||||
expect(controller.getPTZActions()['home']).toEqual({
|
||||
tap_action: {
|
||||
action: 'fire-dom-event',
|
||||
frigate_card_action: 'ptz_multi',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle action', () => {
|
||||
it('successfully', () => {
|
||||
const action = {
|
||||
action: 'more-info' as const,
|
||||
};
|
||||
const config = {
|
||||
tap_action: action,
|
||||
camera_entity: 'camera.office',
|
||||
};
|
||||
|
||||
const element = document.createElement('div');
|
||||
const handler = vi.fn();
|
||||
element.addEventListener('frigate-card:action:execution-request', handler);
|
||||
|
||||
const controller = new PTZController(element);
|
||||
controller.setCamera();
|
||||
controller.handleAction(
|
||||
new CustomEvent<{ action: string }>('@action', { detail: { action: 'tap' } }),
|
||||
config,
|
||||
);
|
||||
|
||||
expect(handler).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
detail: {
|
||||
action: action,
|
||||
config: config,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not call action without actions config', () => {
|
||||
const element = document.createElement('div');
|
||||
const handler = vi.fn();
|
||||
element.addEventListener('frigate-card:action:execution-request', handler);
|
||||
|
||||
const controller = new PTZController(element);
|
||||
controller.setCamera();
|
||||
controller.handleAction(
|
||||
new CustomEvent<{ action: string }>('@action', { detail: { action: 'tap' } }),
|
||||
);
|
||||
|
||||
expect(handler).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should not call action without hass', () => {
|
||||
const element = document.createElement('div');
|
||||
const handler = vi.fn();
|
||||
element.addEventListener('frigate-card:action:execution-request', handler);
|
||||
|
||||
const controller = new PTZController(element);
|
||||
controller.setCamera();
|
||||
controller.handleAction(
|
||||
new CustomEvent<{ action: string }>('@action', { detail: { action: 'tap' } }),
|
||||
);
|
||||
|
||||
expect(handler).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should identify useful actions', () => {
|
||||
it('without a camera', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
expect(controller.hasUsefulAction()).toEqual({
|
||||
pt: true,
|
||||
z: true,
|
||||
home: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('without camera PTZ capabilities', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: createCapabilities({
|
||||
ptz: {},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
const cameraManager = createCameraManager(store);
|
||||
controller.setCamera(cameraManager, 'camera.office');
|
||||
|
||||
expect(controller.hasUsefulAction()).toEqual({
|
||||
pt: true,
|
||||
z: true,
|
||||
home: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('with camera pan and tilt capabilities', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: createCapabilities({
|
||||
ptz: {
|
||||
left: ['relative'],
|
||||
right: ['relative'],
|
||||
up: ['relative'],
|
||||
down: ['relative'],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
controller.setCamera(createCameraManager(store), 'camera.office');
|
||||
|
||||
expect(controller.hasUsefulAction()).toEqual({
|
||||
pt: true,
|
||||
z: false,
|
||||
home: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('with camera zoom capabilities', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: createCapabilities({
|
||||
ptz: {
|
||||
zoomIn: ['relative'],
|
||||
zoomOut: ['relative'],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
controller.setCamera(createCameraManager(store), 'camera.office');
|
||||
|
||||
expect(controller.hasUsefulAction()).toEqual({
|
||||
pt: false,
|
||||
z: true,
|
||||
home: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('with camera presets', () => {
|
||||
const controller = new PTZController(document.createElement('div'));
|
||||
const store = createStore([
|
||||
{
|
||||
cameraID: 'camera.office',
|
||||
capabilities: createCapabilities({
|
||||
ptz: {
|
||||
presets: ['door'],
|
||||
},
|
||||
}),
|
||||
},
|
||||
]);
|
||||
controller.setCamera(createCameraManager(store), 'camera.office');
|
||||
|
||||
expect(controller.hasUsefulAction()).toEqual({
|
||||
pt: false,
|
||||
z: false,
|
||||
home: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,7 @@ import { ZoomController } from '../../../src/components-lib/zoom/zoom-controller
|
||||
import { ResizeObserverMock, requestAnimationFrameMock } from '../../test-utils';
|
||||
|
||||
vi.mock('@dermotduffy/panzoom');
|
||||
vi.mock('lodash-es/debounce', () => ({
|
||||
vi.mock('lodash-es/throttle', () => ({
|
||||
default: vi.fn((fn) => fn),
|
||||
}));
|
||||
|
||||
@@ -311,8 +311,8 @@ describe('ZoomController', () => {
|
||||
const element = document.createElement('div');
|
||||
setElementToDefaultCardSize(element);
|
||||
|
||||
const defaultFunc = vi.fn();
|
||||
element.addEventListener('frigate-card:zoom:default', defaultFunc);
|
||||
const changeFunc = vi.fn();
|
||||
element.addEventListener('frigate-card:zoom:change', changeFunc);
|
||||
|
||||
vi.mocked(Panzoom).mockReturnValueOnce(createMockPanZoom());
|
||||
createAndRegisterZoom(element);
|
||||
@@ -327,8 +327,11 @@ describe('ZoomController', () => {
|
||||
},
|
||||
});
|
||||
element.dispatchEvent(ev_1);
|
||||
expect(defaultFunc).toBeCalledWith(
|
||||
expect.objectContaining({ detail: { isDefault: false } }),
|
||||
|
||||
expect(changeFunc).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
detail: expect.objectContaining({ isDefault: false }),
|
||||
}),
|
||||
);
|
||||
|
||||
const ev_2 = new CustomEvent<PanzoomEventDetail>('panzoomchange', {
|
||||
@@ -341,8 +344,11 @@ describe('ZoomController', () => {
|
||||
},
|
||||
});
|
||||
element.dispatchEvent(ev_2);
|
||||
expect(defaultFunc).toBeCalledWith(
|
||||
expect.objectContaining({ detail: { isDefault: true } }),
|
||||
|
||||
expect(changeFunc).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
detail: expect.objectContaining({ isDefault: true }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -350,12 +356,12 @@ describe('ZoomController', () => {
|
||||
const element = document.createElement('div');
|
||||
setElementToDefaultCardSize(element);
|
||||
|
||||
const defaultFunc = vi.fn();
|
||||
element.addEventListener('frigate-card:zoom:default', defaultFunc);
|
||||
const changeFunc = vi.fn();
|
||||
element.addEventListener('frigate-card:zoom:change', changeFunc);
|
||||
|
||||
vi.mocked(Panzoom).mockReturnValueOnce(createMockPanZoom());
|
||||
const controller = createAndRegisterZoom(element);
|
||||
controller.setDefaultConfig({ zoom: 2, pan: { x: 3, y: 4 } });
|
||||
controller.setDefaultSettings({ zoom: 2, pan: { x: 3, y: 4 } });
|
||||
|
||||
const ev_1 = new CustomEvent<PanzoomEventDetail>('panzoomchange', {
|
||||
detail: {
|
||||
@@ -367,8 +373,11 @@ describe('ZoomController', () => {
|
||||
},
|
||||
});
|
||||
element.dispatchEvent(ev_1);
|
||||
expect(defaultFunc).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ detail: { isDefault: false } }),
|
||||
|
||||
expect(changeFunc).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
detail: expect.objectContaining({ isDefault: false }),
|
||||
}),
|
||||
);
|
||||
|
||||
const ev_2 = new CustomEvent<PanzoomEventDetail>('panzoomchange', {
|
||||
@@ -381,8 +390,11 @@ describe('ZoomController', () => {
|
||||
},
|
||||
});
|
||||
element.dispatchEvent(ev_2);
|
||||
expect(defaultFunc).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ detail: { isDefault: true } }),
|
||||
|
||||
expect(changeFunc).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
detail: expect.objectContaining({ isDefault: true }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -390,8 +402,8 @@ describe('ZoomController', () => {
|
||||
const element = document.createElement('div');
|
||||
setElementToDefaultCardSize(element);
|
||||
|
||||
const defaultFunc = vi.fn();
|
||||
element.addEventListener('frigate-card:zoom:default', defaultFunc);
|
||||
const changeFunc = vi.fn();
|
||||
element.addEventListener('frigate-card:zoom:change', changeFunc);
|
||||
|
||||
vi.mocked(Panzoom).mockReturnValueOnce(createMockPanZoom());
|
||||
const controller = createAndRegisterZoom(element);
|
||||
@@ -406,11 +418,14 @@ describe('ZoomController', () => {
|
||||
},
|
||||
});
|
||||
element.dispatchEvent(ev_1);
|
||||
expect(defaultFunc).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ detail: { isDefault: false } }),
|
||||
|
||||
expect(changeFunc).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
detail: expect.objectContaining({ isDefault: false }),
|
||||
}),
|
||||
);
|
||||
|
||||
controller.setDefaultConfig({});
|
||||
controller.setDefaultSettings({});
|
||||
|
||||
const ev_2 = new CustomEvent<PanzoomEventDetail>('panzoomchange', {
|
||||
detail: {
|
||||
@@ -422,8 +437,11 @@ describe('ZoomController', () => {
|
||||
},
|
||||
});
|
||||
element.dispatchEvent(ev_2);
|
||||
expect(defaultFunc).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ detail: { isDefault: true } }),
|
||||
|
||||
expect(changeFunc).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
detail: expect.objectContaining({ isDefault: true }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -438,7 +456,7 @@ describe('ZoomController', () => {
|
||||
setElementToDefaultCardSize(element);
|
||||
|
||||
const controller = new ZoomController(element);
|
||||
controller.setDefaultConfig({ zoom: 2, pan: { x: 3, y: 4 } });
|
||||
controller.setDefaultSettings({ zoom: 2, pan: { x: 3, y: 4 } });
|
||||
|
||||
// Controller was not activated, config setting will not update pan/zoom.
|
||||
expect(panzoom.zoom).not.toBeCalled();
|
||||
@@ -448,16 +466,16 @@ describe('ZoomController', () => {
|
||||
expect(Panzoom).toBeCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
contain: "outside",
|
||||
contain: 'outside',
|
||||
cursor: undefined,
|
||||
maxScale: 10,
|
||||
minScale: 1,
|
||||
noBind: true,
|
||||
touchAction: "",
|
||||
touchAction: '',
|
||||
startScale: 2,
|
||||
startX: 115.62,
|
||||
startY: 63.6525,
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -469,12 +487,15 @@ describe('ZoomController', () => {
|
||||
setElementToDefaultCardSize(element);
|
||||
|
||||
const controller = createAndRegisterZoom(element);
|
||||
controller.setDefaultConfig({ zoom: 2, pan: { x: 3, y: 4 } });
|
||||
controller.setDefaultSettings({ zoom: 2, pan: { x: 3, y: 4 } });
|
||||
|
||||
triggerResizeObserver();
|
||||
|
||||
expect(panzoom.zoom).toBeCalledWith(2, { animate: false });
|
||||
expect(panzoom.pan).toBeCalledWith(115.62, 63.6525, { animate: false });
|
||||
expect(panzoom.pan).toBeCalledWith(115.62, 63.6525, {
|
||||
animate: true,
|
||||
duration: 100,
|
||||
});
|
||||
});
|
||||
|
||||
it('with set of config when a default is already set', () => {
|
||||
@@ -487,23 +508,25 @@ describe('ZoomController', () => {
|
||||
const controller = createAndRegisterZoom(element);
|
||||
|
||||
// This call will do nothing since this is what zoom/pan already are.
|
||||
controller.setDefaultConfig({ zoom: 1, pan: { x: 0, y: 0 } });
|
||||
controller.setDefaultSettings({ zoom: 1, pan: { x: 0, y: 0 } });
|
||||
|
||||
expect(panzoom.zoom).not.toBeCalled();
|
||||
expect(panzoom.pan).not.toBeCalled();
|
||||
|
||||
controller.setDefaultConfig({ zoom: 2, pan: { x: 3, y: 4 } });
|
||||
|
||||
controller.setDefaultSettings({ zoom: 2, pan: { x: 3, y: 4 } });
|
||||
|
||||
expect(panzoom.zoom).toHaveBeenNthCalledWith(1, 2, { animate: false });
|
||||
expect(panzoom.pan).toHaveBeenNthCalledWith(1, 115.62, 63.6525, {
|
||||
animate: false,
|
||||
animate: true,
|
||||
duration: 100,
|
||||
});
|
||||
|
||||
controller.setConfig({ zoom: 3, pan: { x: 5, y: 6 } });
|
||||
controller.setSettings({ zoom: 3, pan: { x: 5, y: 6 } });
|
||||
|
||||
expect(panzoom.zoom).toHaveBeenNthCalledWith(2, 3, { animate: false });
|
||||
expect(panzoom.pan).toHaveBeenNthCalledWith(2, 147.6, 81.18, {
|
||||
animate: false,
|
||||
animate: true,
|
||||
duration: 100,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -516,30 +539,31 @@ describe('ZoomController', () => {
|
||||
|
||||
const controller = createAndRegisterZoom(element);
|
||||
|
||||
controller.setConfig({ zoom: 1 });
|
||||
controller.setSettings({ zoom: 1 });
|
||||
expect(panzoom.zoom).not.toHaveBeenCalled();
|
||||
|
||||
controller.setConfig({ pan: { x: 50, y: 50 } });
|
||||
controller.setSettings({ pan: { x: 50, y: 50 } });
|
||||
expect(panzoom.zoom).not.toHaveBeenCalled();
|
||||
|
||||
controller.setConfig({ zoom: 1, pan: { x: 50, y: 50 } });
|
||||
controller.setSettings({ zoom: 1, pan: { x: 50, y: 50 } });
|
||||
expect(panzoom.zoom).not.toHaveBeenCalled();
|
||||
|
||||
controller.setConfig({});
|
||||
controller.setSettings({});
|
||||
expect(panzoom.zoom).not.toHaveBeenCalled();
|
||||
|
||||
controller.setConfig({ zoom: 2 });
|
||||
controller.setSettings({ zoom: 2 });
|
||||
|
||||
expect(panzoom.zoom).toBeCalledTimes(1);
|
||||
expect(panzoom.pan).toBeCalledTimes(1);
|
||||
expect(panzoom.zoom).toHaveBeenNthCalledWith(1, 2, { animate: false });
|
||||
expect(panzoom.pan).toHaveBeenNthCalledWith(1, 0, 0, {
|
||||
animate: false,
|
||||
animate: true,
|
||||
duration: 100,
|
||||
});
|
||||
|
||||
vi.mocked(panzoom.getScale).mockReturnValue(2);
|
||||
vi.mocked(panzoom.getPan).mockReturnValue({ x: 0, y: 0 });
|
||||
controller.setConfig({ zoom: 2 });
|
||||
controller.setSettings({ zoom: 2 });
|
||||
|
||||
expect(panzoom.zoom).toBeCalledTimes(1);
|
||||
expect(panzoom.pan).toBeCalledTimes(1);
|
||||
@@ -553,15 +577,16 @@ describe('ZoomController', () => {
|
||||
setElementToDefaultCardSize(element);
|
||||
|
||||
const controller = createAndRegisterZoom(element);
|
||||
controller.setDefaultConfig({ zoom: 2, pan: { x: 3, y: 4 } });
|
||||
controller.setDefaultSettings({ zoom: 2, pan: { x: 3, y: 4 } });
|
||||
mockClear(panzoom);
|
||||
|
||||
controller.setConfig({});
|
||||
controller.setSettings({});
|
||||
|
||||
// Should fall back to default.
|
||||
expect(panzoom.zoom).toBeCalledWith(2, { animate: false });
|
||||
expect(panzoom.pan).toBeCalledWith(115.62, 63.6525, {
|
||||
animate: false,
|
||||
animate: true,
|
||||
duration: 100,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -573,11 +598,12 @@ describe('ZoomController', () => {
|
||||
setElementToDefaultCardSize(element);
|
||||
|
||||
const controller = createAndRegisterZoom(element);
|
||||
controller.setConfig({ zoom: 2, pan: { x: 3, y: 4 } });
|
||||
controller.setSettings({ zoom: 2, pan: { x: 3, y: 4 } });
|
||||
|
||||
expect(panzoom.zoom).toHaveBeenNthCalledWith(1, 2, { animate: false });
|
||||
expect(panzoom.pan).toHaveBeenNthCalledWith(1, 115.62, 63.6525, {
|
||||
animate: false,
|
||||
animate: true,
|
||||
duration: 100,
|
||||
});
|
||||
|
||||
vi.mocked(panzoom.getScale).mockReturnValue(2);
|
||||
@@ -588,7 +614,8 @@ describe('ZoomController', () => {
|
||||
|
||||
expect(panzoom.zoom).toHaveBeenNthCalledWith(2, 2, { animate: false });
|
||||
expect(panzoom.pan).toHaveBeenNthCalledWith(2, 57.81, 31.82625, {
|
||||
animate: false,
|
||||
animate: true,
|
||||
duration: 100,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,14 +1,39 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
generateViewContextForZoomChange,
|
||||
handleZoomDefaultEvent,
|
||||
generateViewContextForZoom,
|
||||
handleZoomSettingsObservedEvent,
|
||||
} from '../../../src/components-lib/zoom/zoom-view-context';
|
||||
|
||||
describe('generateViewContextForZoomChangeRequest', () => {
|
||||
it('with config', () => {
|
||||
describe('generateViewContextForZoom', () => {
|
||||
it('with observed', () => {
|
||||
expect(
|
||||
generateViewContextForZoomChange('target', {
|
||||
zoom: {
|
||||
generateViewContextForZoom('target', {
|
||||
observed: {
|
||||
pan: { x: 1, y: 2 },
|
||||
zoom: 3,
|
||||
isDefault: true,
|
||||
unzoomed: true,
|
||||
},
|
||||
}),
|
||||
).toEqual({
|
||||
zoom: {
|
||||
target: {
|
||||
observed: {
|
||||
pan: { x: 1, y: 2 },
|
||||
zoom: 3,
|
||||
isDefault: true,
|
||||
unzoomed: true,
|
||||
},
|
||||
requested: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('with requested', () => {
|
||||
expect(
|
||||
generateViewContextForZoom('target', {
|
||||
requested: {
|
||||
pan: { x: 1, y: 2 },
|
||||
zoom: 3,
|
||||
},
|
||||
@@ -16,7 +41,7 @@ describe('generateViewContextForZoomChangeRequest', () => {
|
||||
).toEqual({
|
||||
zoom: {
|
||||
target: {
|
||||
zoom: {
|
||||
requested: {
|
||||
pan: { x: 1, y: 2 },
|
||||
zoom: 3,
|
||||
},
|
||||
@@ -24,50 +49,23 @@ describe('generateViewContextForZoomChangeRequest', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('without config', () => {
|
||||
expect(generateViewContextForZoomChange('target')).toEqual({
|
||||
zoom: {
|
||||
target: {
|
||||
zoom: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateViewContextForZoomDefault', () => {
|
||||
it('default', () => {
|
||||
expect(generateViewContextForZoomChange('target', { isDefault: true })).toEqual({
|
||||
zoom: {
|
||||
target: {
|
||||
isDefault: true,
|
||||
zoom: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('not default', () => {
|
||||
expect(generateViewContextForZoomChange('target', { isDefault: false })).toEqual({
|
||||
zoom: {
|
||||
target: {
|
||||
isDefault: false,
|
||||
zoom: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// @vitest-environment jsdom
|
||||
it('handleZoomDefaultEvent', () => {
|
||||
it('handleZoomSettingsObservedEvent', () => {
|
||||
const element = document.createElement('div');
|
||||
const callback = vi.fn();
|
||||
element.addEventListener('frigate-card:view:change-context', callback);
|
||||
handleZoomDefaultEvent(
|
||||
handleZoomSettingsObservedEvent(
|
||||
element,
|
||||
new CustomEvent('frigate-card:zoom:default', { detail: { isDefault: true } }),
|
||||
new CustomEvent('frigate-card:zoom:change', {
|
||||
detail: {
|
||||
pan: { x: 1, y: 2 },
|
||||
zoom: 3,
|
||||
isDefault: true,
|
||||
unzoomed: true,
|
||||
},
|
||||
}),
|
||||
'target',
|
||||
);
|
||||
expect(callback).toBeCalledWith(
|
||||
@@ -75,8 +73,8 @@ it('handleZoomDefaultEvent', () => {
|
||||
detail: {
|
||||
zoom: {
|
||||
target: {
|
||||
zoom: null,
|
||||
isDefault: true,
|
||||
observed: { pan: { x: 1, y: 2 }, zoom: 3, isDefault: true, unzoomed: true },
|
||||
requested: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user