Files
advanced-camera-card/tests/utils/action.test.ts
T
Dermot Duffy abcba884e5 feat: Add 'call' support to improve 2-way audio experience (#2486)
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
```
2026-06-30 17:45:13 -07:00

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();
});
});