Draws significant inspiration (and direct styling) from https://github.com/dermotduffy/advanced-camera-card/pull/2447 . Thank you @Maudfer ! BREAKING CHANGE: The microphone condition previously bundled two unrelated signals — whether a two-way-audio session was connected and whether the microphone was muted. Connection state is now its own dedicated call condition, and microphone is reserved purely for mute state. Configs are upgraded automatically (the card rewrites affected conditions under overrides, elements, and automations). If you maintain config by hand, convert as follows: If you only used connected: # Before ```yaml condition: microphone connected: true ``` # After ```yaml condition: call call: true ``` If you used both connected and muted — they must be split into two conditions, since they no longer live together: # Before ```yaml condition: microphone connected: true muted: false ``` # After ```yaml condition: and conditions: - condition: call call: true - condition: microphone muted: false ```
511 lines
13 KiB
TypeScript
511 lines
13 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import { mock } from 'vitest-mock-extended';
|
|
import { INTERNAL_CALLBACK_ACTION } from '../../src/config/schema/actions/custom/internal.js';
|
|
import { ActionConfig } from '../../src/config/schema/actions/types.js';
|
|
import {
|
|
createCallEndAction,
|
|
createCallStartAction,
|
|
createCameraAction,
|
|
createDisplayModeAction,
|
|
createEffectAction,
|
|
createGeneralAction,
|
|
createInternalCallbackAction,
|
|
createLogAction,
|
|
createMediaPlayerAction,
|
|
createNotificationAction,
|
|
createPerformAction,
|
|
createPTZAction,
|
|
createPTZControlsAction,
|
|
createPTZDigitalAction,
|
|
createPTZMultiAction,
|
|
createSelectOptionAction,
|
|
createSetReviewAction,
|
|
createViewAction,
|
|
getActionConfigGivenAction,
|
|
hasAction,
|
|
stopEventFromActivatingCardWideActions,
|
|
} from '../../src/utils/action.js';
|
|
|
|
describe('createGeneralAction', () => {
|
|
it('should create general action', () => {
|
|
expect(
|
|
createGeneralAction('camera_ui', {
|
|
cardID: 'card_id',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'camera_ui',
|
|
card_id: 'card_id',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createViewAction', () => {
|
|
it('should create view action', () => {
|
|
expect(
|
|
createViewAction('clips', {
|
|
cardID: 'card_id',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'clips',
|
|
card_id: 'card_id',
|
|
});
|
|
});
|
|
|
|
it.each([['folder' as const], ['folders' as const]])(
|
|
'%s',
|
|
(viewName: 'folder' | 'folders') => {
|
|
expect(
|
|
createViewAction(viewName, { folderID: 'folderID', cardID: 'card_id' }),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: viewName,
|
|
card_id: 'card_id',
|
|
folder: 'folderID',
|
|
});
|
|
},
|
|
);
|
|
});
|
|
|
|
describe('createCameraAction', () => {
|
|
it('should create camera_select action', () => {
|
|
expect(createCameraAction('camera_select', 'camera', { cardID: 'card_id' })).toEqual(
|
|
{
|
|
action: 'fire-dom-event',
|
|
camera: 'camera',
|
|
advanced_camera_card_action: 'camera_select',
|
|
card_id: 'card_id',
|
|
},
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('createMediaPlayerAction', () => {
|
|
it('should create media_player action', () => {
|
|
expect(
|
|
createMediaPlayerAction('device', 'play', {
|
|
cardID: 'card_id',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'media_player',
|
|
media_player: 'device',
|
|
media_player_action: 'play',
|
|
card_id: 'card_id',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createDisplayModeAction', () => {
|
|
it('should create display mode action', () => {
|
|
expect(
|
|
createDisplayModeAction('grid', {
|
|
cardID: 'card_id',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'display_mode_select',
|
|
display_mode: 'grid',
|
|
card_id: 'card_id',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createPTZControlsAction', () => {
|
|
it('should create PTZ controls action with enabled', () => {
|
|
expect(
|
|
createPTZControlsAction({
|
|
enabled: true,
|
|
cardID: 'card_id',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'ptz_controls',
|
|
enabled: true,
|
|
card_id: 'card_id',
|
|
});
|
|
});
|
|
|
|
it('should create PTZ controls action with type', () => {
|
|
expect(
|
|
createPTZControlsAction({
|
|
type: 'gestures',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'ptz_controls',
|
|
type: 'gestures',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createPTZAction', () => {
|
|
it('should create ptz action without parameters', () => {
|
|
expect(
|
|
createPTZAction({
|
|
cardID: 'card_id',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'ptz',
|
|
card_id: 'card_id',
|
|
});
|
|
});
|
|
|
|
it('should create ptz action with parameters', () => {
|
|
expect(
|
|
createPTZAction({
|
|
cardID: 'card_id',
|
|
ptzAction: 'right',
|
|
ptzPhase: 'start',
|
|
ptzPreset: 'preset',
|
|
cameraID: 'camera_id',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'ptz',
|
|
card_id: 'card_id',
|
|
camera: 'camera_id',
|
|
ptz_action: 'right',
|
|
ptz_phase: 'start',
|
|
ptz_preset: 'preset',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createPTZDigitalAction', () => {
|
|
it('should create ptz digital without parameters', () => {
|
|
expect(
|
|
createPTZDigitalAction({
|
|
cardID: 'card_id',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'ptz_digital',
|
|
card_id: 'card_id',
|
|
});
|
|
});
|
|
|
|
it('should create ptz digital with parameters', () => {
|
|
expect(
|
|
createPTZDigitalAction({
|
|
cardID: 'card_id',
|
|
targetID: 'target_id',
|
|
absolute: {
|
|
pan: { x: 1, y: 2 },
|
|
zoom: 3,
|
|
},
|
|
ptzAction: 'right',
|
|
ptzPhase: 'start',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'ptz_digital',
|
|
card_id: 'card_id',
|
|
target_id: 'target_id',
|
|
absolute: {
|
|
pan: { x: 1, y: 2 },
|
|
zoom: 3,
|
|
},
|
|
ptz_action: 'right',
|
|
ptz_phase: 'start',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createPTZMultiAction', () => {
|
|
it('should create ptz multi with parameters', () => {
|
|
expect(
|
|
createPTZMultiAction({
|
|
cardID: 'card_id',
|
|
ptzAction: 'right',
|
|
ptzPreset: 'preset',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'ptz_multi',
|
|
card_id: 'card_id',
|
|
ptz_action: 'right',
|
|
ptz_preset: 'preset',
|
|
});
|
|
});
|
|
|
|
it('should create ptz multi without parameters', () => {
|
|
expect(
|
|
createPTZMultiAction({
|
|
cardID: 'card_id',
|
|
ptzAction: 'right',
|
|
ptzPhase: 'start',
|
|
targetID: 'target_id',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'ptz_multi',
|
|
card_id: 'card_id',
|
|
ptz_action: 'right',
|
|
ptz_phase: 'start',
|
|
target_id: 'target_id',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createLogAction', () => {
|
|
it('should create log action', () => {
|
|
expect(
|
|
createLogAction('Hello, world!', {
|
|
cardID: 'card_id',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'log',
|
|
message: 'Hello, world!',
|
|
card_id: 'card_id',
|
|
level: 'info',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createInternalCallbackAction', () => {
|
|
it('should create internal callback action', () => {
|
|
const callback = vi.fn();
|
|
expect(
|
|
createInternalCallbackAction(callback, {
|
|
cardID: 'card_id',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: INTERNAL_CALLBACK_ACTION,
|
|
callback: callback,
|
|
card_id: 'card_id',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createPerformAction', () => {
|
|
it('should create perform action', () => {
|
|
expect(
|
|
createPerformAction('toggle', {
|
|
cardID: 'card_id',
|
|
target: { entity_id: 'light.office_main_lights' },
|
|
data: {},
|
|
}),
|
|
).toEqual({
|
|
action: 'perform-action',
|
|
perform_action: 'toggle',
|
|
card_id: 'card_id',
|
|
target: { entity_id: 'light.office_main_lights' },
|
|
data: {},
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createSelectOptionAction', () => {
|
|
it('should create select option action', () => {
|
|
expect(
|
|
createSelectOptionAction('select', 'select.foo', 'option', {
|
|
cardID: 'card_id',
|
|
}),
|
|
).toEqual({
|
|
action: 'perform-action',
|
|
perform_action: 'select.select_option',
|
|
card_id: 'card_id',
|
|
target: { entity_id: 'select.foo' },
|
|
data: {
|
|
option: 'option',
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createEffectAction', () => {
|
|
it('should create effect action', () => {
|
|
expect(
|
|
createEffectAction('snow', 'start', {
|
|
cardID: 'card_id',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'effect',
|
|
effect: 'snow',
|
|
effect_action: 'start',
|
|
card_id: 'card_id',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createSetReviewAction', () => {
|
|
it('should create set review action', () => {
|
|
expect(createSetReviewAction(true)).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'set_review',
|
|
reviewed: true,
|
|
});
|
|
});
|
|
|
|
it('should create set review action with false', () => {
|
|
expect(createSetReviewAction(false)).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'set_review',
|
|
reviewed: false,
|
|
});
|
|
});
|
|
|
|
it('should create set review action with undefined', () => {
|
|
expect(createSetReviewAction()).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'set_review',
|
|
reviewed: undefined,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createCallStartAction', () => {
|
|
it('should create call start action without a camera', () => {
|
|
expect(createCallStartAction()).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'call_start',
|
|
});
|
|
});
|
|
|
|
it('should create call start action with a camera, stream and cardID', () => {
|
|
expect(
|
|
createCallStartAction('camera.front', 'camera.front_doorbell', {
|
|
cardID: 'card_id',
|
|
}),
|
|
).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'call_start',
|
|
camera: 'camera.front',
|
|
stream: 'camera.front_doorbell',
|
|
card_id: 'card_id',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createCallEndAction', () => {
|
|
it('should create call end action', () => {
|
|
expect(createCallEndAction()).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'call_end',
|
|
});
|
|
});
|
|
|
|
it('should create call end action with a cardID', () => {
|
|
expect(createCallEndAction({ cardID: 'card_id' })).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'call_end',
|
|
card_id: 'card_id',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createNotificationAction', () => {
|
|
it('should create notification action', () => {
|
|
const notification = { body: { text: 'test' } };
|
|
expect(createNotificationAction(notification)).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'notification',
|
|
notification,
|
|
});
|
|
});
|
|
|
|
it('should create notification action with cardID', () => {
|
|
const notification = { body: { text: 'test' } };
|
|
expect(createNotificationAction(notification, { cardID: 'card_id' })).toEqual({
|
|
action: 'fire-dom-event',
|
|
advanced_camera_card_action: 'notification',
|
|
notification,
|
|
card_id: 'card_id',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getActionConfigGivenAction', () => {
|
|
const action = createViewAction('clips');
|
|
|
|
it('should not handle undefined arguments', () => {
|
|
expect(getActionConfigGivenAction()).toBeNull();
|
|
});
|
|
|
|
it('should not handle unknown interactions', () => {
|
|
expect(
|
|
getActionConfigGivenAction('triple_poke', { triple_poke_action: action }),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('should handle tap actions', () => {
|
|
expect(getActionConfigGivenAction('tap', { tap_action: action })).toBe(action);
|
|
});
|
|
|
|
it('should handle default tap action without an entity', () => {
|
|
expect(getActionConfigGivenAction('tap', {})).toBeNull();
|
|
});
|
|
|
|
it('should handle default tap action with an entity', () => {
|
|
expect(
|
|
getActionConfigGivenAction('tap', { entity: 'light.office_main_lights' }),
|
|
).toEqual({
|
|
action: 'more-info',
|
|
});
|
|
});
|
|
|
|
it('should handle hold actions', () => {
|
|
expect(getActionConfigGivenAction('hold', { hold_action: action })).toBe(action);
|
|
});
|
|
|
|
it('should handle double_tap actions', () => {
|
|
expect(getActionConfigGivenAction('double_tap', { double_tap_action: action })).toBe(
|
|
action,
|
|
);
|
|
});
|
|
|
|
it('should handle end_tap actions', () => {
|
|
expect(getActionConfigGivenAction('end_tap', { end_tap_action: action })).toBe(
|
|
action,
|
|
);
|
|
});
|
|
|
|
it('should handle start_tap actions', () => {
|
|
expect(getActionConfigGivenAction('start_tap', { start_tap_action: action })).toBe(
|
|
action,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('hasAction', () => {
|
|
const realAction = createViewAction('clips');
|
|
const noneAction: ActionConfig = {
|
|
action: 'none',
|
|
};
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('should return true for real action', () => {
|
|
expect(hasAction(realAction)).toBeTruthy();
|
|
});
|
|
|
|
it('should return false for none action', () => {
|
|
expect(hasAction(noneAction)).toBeFalsy();
|
|
});
|
|
|
|
it('should return true with an array of actions some real', () => {
|
|
expect(hasAction([noneAction, noneAction, realAction])).toBeTruthy();
|
|
});
|
|
|
|
it('should return false with an array of actions none real', () => {
|
|
expect(hasAction([noneAction, noneAction, noneAction])).toBeFalsy();
|
|
});
|
|
});
|
|
|
|
// @vitest-environment jsdom
|
|
describe('stopEventFromActivatingCardWideActions', () => {
|
|
it('should stop event from propogating', () => {
|
|
const event = mock<Event>();
|
|
stopEventFromActivatingCardWideActions(event);
|
|
expect(event.stopPropagation).toBeCalled();
|
|
});
|
|
});
|