From 92fc8dd84402701dc4cb3b74a8ad37fd84a495de Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Fri, 21 Mar 2025 20:14:24 -0700 Subject: [PATCH] test: Add tests for config actions, elements and conditions (#1976) [skip ci] --- docs/configuration/actions/custom/README.md | 50 +- docs/configuration/actions/stock/README.md | 11 - docs/configuration/conditions.md | 17 + docs/configuration/elements/custom/README.md | 1 + src/components/submenu/select-button.ts | 1 + tests/config/types.test.ts | 687 +++++++++++++++++++ 6 files changed, 737 insertions(+), 30 deletions(-) diff --git a/docs/configuration/actions/custom/README.md b/docs/configuration/actions/custom/README.md index 4a3232c2..925b4ccb 100644 --- a/docs/configuration/actions/custom/README.md +++ b/docs/configuration/actions/custom/README.md @@ -616,36 +616,48 @@ elements: advanced_camera_card_action: menu_toggle - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-p-circle + title: Microphone connect + tap_action: + action: custom:advanced-camera-card-action + advanced_camera_card_action: microphone_connect + - type: custom:advanced-camera-card-menu-icon + icon: mdi:alpha-q-circle + title: Microphone disconnect + tap_action: + action: custom:advanced-camera-card-action + advanced_camera_card_action: microphone_disconnect + - type: custom:advanced-camera-card-menu-icon + icon: mdi:alpha-r-circle title: Microphone mute tap_action: action: custom:advanced-camera-card-action advanced_camera_card_action: microphone_mute - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-q-circle + icon: mdi:alpha-s-circle title: Microphone unmute tap_action: action: custom:advanced-camera-card-action advanced_camera_card_action: microphone_unmute - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-r-circle + icon: mdi:alpha-t-circle title: Mute tap_action: action: custom:advanced-camera-card-action advanced_camera_card_action: mute - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-s-circle + icon: mdi:alpha-u-circle title: Pause tap_action: action: custom:advanced-camera-card-action advanced_camera_card_action: pause - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-t-circle + icon: mdi:alpha-v-circle title: Play tap_action: action: custom:advanced-camera-card-action advanced_camera_card_action: play - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-u-circle + icon: mdi:alpha-x-circle title: Real PTZ Preset tap_action: action: custom:advanced-camera-card-action @@ -653,14 +665,14 @@ elements: ptz_action: preset ptz_preset: doorway - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-v-circle + icon: mdi:alpha-y-circle title: Show PTZ Controls tap_action: action: custom:advanced-camera-card-action advanced_camera_card_action: ptz_controls enabled: true - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-w-circle + icon: mdi:alpha-z-circle title: Go to precise digital location tap_action: action: custom:advanced-camera-card-action @@ -671,32 +683,32 @@ elements: x: 58 y: 14 - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-x-circle + icon: mdi:alpha-a-circle-outline title: Smart select between real and digital PTZ tap_action: action: custom:advanced-camera-card-action advanced_camera_card_action: ptz_multi ptz_action: left - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-y-circle + icon: mdi:alpha-b-circle-outline title: Show most recent recording tap_action: action: custom:advanced-camera-card-action advanced_camera_card_action: recording - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-a-circle-outline + icon: mdi:alpha-c-circle-outline title: Show recordings tap_action: action: custom:advanced-camera-card-action advanced_camera_card_action: recordings - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-b-circle-outline + icon: mdi:alpha-d-circle-outline title: Screenshot tap_action: action: custom:advanced-camera-card-action advanced_camera_card_action: screenshot - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-c-circle-outline + icon: mdi:alpha-e-circle-outline title: Sleep tap_action: action: custom:advanced-camera-card-action @@ -707,31 +719,31 @@ elements: s: 56 ms: 422 - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-d-circle-outline + icon: mdi:alpha-f-circle-outline title: Show most recent snapshot tap_action: action: custom:advanced-camera-card-action advanced_camera_card_action: snapshot - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-e-circle-outline + icon: mdi:alpha-g-circle-outline title: Show snapshots tap_action: action: custom:advanced-camera-card-action advanced_camera_card_action: snapshots - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-f-circle-outline + icon: mdi:alpha-h-circle-outline title: Show timeline tap_action: action: custom:advanced-camera-card-action advanced_camera_card_action: timeline - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-g-circle-outline + icon: mdi:alpha-i-circle-outline title: Unmute tap_action: action: custom:advanced-camera-card-action advanced_camera_card_action: unmute - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-h-circle-outline + icon: mdi:alpha-j-circle-outline title: Add status bar contents tap_action: - action: custom:advanced-camera-card-action @@ -760,7 +772,7 @@ elements: priority: 50 sufficient: false - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-i-circle-outline + icon: mdi:alpha-k-circle-outline title: Remove status bar contents tap_action: - action: custom:advanced-camera-card-action @@ -789,7 +801,7 @@ elements: priority: 50 sufficient: false - type: custom:advanced-camera-card-menu-icon - icon: mdi:alpha-i-circle-outline + icon: mdi:alpha-l-circle-outline title: Reset status bar contents tap_action: - action: custom:advanced-camera-card-action diff --git a/docs/configuration/actions/stock/README.md b/docs/configuration/actions/stock/README.md index b2daa30c..71a7b646 100644 --- a/docs/configuration/actions/stock/README.md +++ b/docs/configuration/actions/stock/README.md @@ -117,15 +117,4 @@ elements: tap_action: action: fire-dom-event key: value - - type: icon - icon: mdi:numeric-8-box - title: Perform action - style: - left: 200px - top: 400px - tap_action: - action: perform-action - perform_action: homeassistant.toggle - target: - entity_id: light.office_main_lights ``` diff --git a/docs/configuration/conditions.md b/docs/configuration/conditions.md index 937436ea..0f25cfab 100644 --- a/docs/configuration/conditions.md +++ b/docs/configuration/conditions.md @@ -45,6 +45,21 @@ conditions: | `condition` | Must be `config`. | | `paths` | An optional array of configuration paths (e.g. `menu.style`). If provided condition matches if _ANY_ of the provided configuration paths has changed. | +## `display_mode` + +Matches when card display mode changes (e.g. `single` or `grid` mode). See the display settings for [`live`](live.md?id=display) or [`media_viewer`](media-viewer.md?id=display). + +```yaml +conditions: + - condition: display_mode + # [...] +``` + +| Parameter | Description | +| -------------- | --------------------------- | +| `condition` | Must be `display_mode`. | +| `display_mode` | Must be `single` or `grid`. | + ## `expand` Matches based on whether the card is in "expanded" mode. @@ -291,6 +306,8 @@ conditions: - condition: config paths: - "menu.style" + - condition: display_mode + display_mode: single - condition: expand expand: true - condition: fullscreen diff --git a/docs/configuration/elements/custom/README.md b/docs/configuration/elements/custom/README.md index e7d3e677..b85eca07 100644 --- a/docs/configuration/elements/custom/README.md +++ b/docs/configuration/elements/custom/README.md @@ -245,6 +245,7 @@ elements: - enabled: true icon: mdi:google selected: false + state_color: false style: color: white tap_action: diff --git a/src/components/submenu/select-button.ts b/src/components/submenu/select-button.ts index ddd45900..a541d207 100644 --- a/src/components/submenu/select-button.ts +++ b/src/components/submenu/select-button.ts @@ -121,6 +121,7 @@ export class AdvancedCameraCardSubmenuSelectButton extends LitElement { icon: this.submenuSelect.icon, entity: entityID, fallback: 'mdi:format-list-bulleted', + stateColor: this.submenuSelect.state_color, }; } diff --git a/tests/config/types.test.ts b/tests/config/types.test.ts index 9b6b94b1..a29d0ed6 100644 --- a/tests/config/types.test.ts +++ b/tests/config/types.test.ts @@ -391,6 +391,693 @@ describe('config defaults', () => { }, }); }); + + it('should include all stock elements', () => { + const stockElements = [ + { + type: 'icon', + icon: 'mdi:dog', + entity: 'camera.office', + }, + { + type: 'custom:element', + data: 'foo', + }, + { + type: 'image', + entity: 'camera.office', + image: 'image', + camera_image: 'camera_image', + camera_view: 'camera_view', + state_image: {}, + filter: 'filter', + state_filter: { on: '/foo' }, + aspect_ratio: '16 / 9', + }, + { + type: 'custom:advanced-camera-card-menu-icon', + icon: 'mdi:cat', + entity: 'camera.kitchen', + }, + { + type: 'custom:advanced-camera-card-menu-state-icon', + entity: 'camera.kitchen', + icon: 'mdi:sheep', + state_color: false, + }, + { + type: 'state-badge', + entity: 'sensor.kitchen_dining_multisensor_air_temperature', + style: { + left: '100px', + top: '50px', + }, + title: 'Temperature', + }, + { + type: 'state-icon', + entity: 'light.office_main_lights', + icon: 'mdi:lamp', + state_color: true, + style: { + left: '100px', + top: '100px', + }, + }, + { + type: 'state-label', + entity: 'sensor.kitchen_motion_sensor_battery', + attribute: 'battery_voltage', + prefix: 'Volts', + title: 'Battery Voltage', + style: { + left: '100px', + top: '150px', + }, + }, + { + type: 'state-label', + entity: 'sensor.kitchen_motion_sensor_battery', + attribute: 'battery_voltage', + prefix: 'Volts: ', + title: 'Battery Voltage', + style: { + backgroundColor: 'black', + left: '100px', + top: '200px', + }, + }, + { + type: 'service-button', + title: 'Light on', + service: 'homeassistant.turn_on', + service_data: { + entity: 'light.office_main_lights', + }, + style: { + left: '100px', + top: '250px', + }, + }, + { + type: 'icon', + icon: 'mdi:cow', + title: 'Moo', + style: { + left: '100px', + top: '300px', + }, + }, + { + type: 'image', + entity: 'light.office_main_lights', + title: 'Image', + state_image: { + on: 'https://picsum.photos/id/1003/1181/1772', + off: 'https://picsum.photos/id/102/4320/3240', + }, + state_filter: { + on: 'brightness(110%) saturate(1.2)', + off: 'brightness(50%) hue-rotate(45deg)', + }, + style: { + left: '100px', + top: '350px', + height: '50px', + width: '100px', + }, + }, + { + type: 'conditional', + conditions: [ + { + condition: 'state', + entity: 'light.office_main_lights', + state: 'on', + state_not: 'off', + }, + ], + elements: [ + { + type: 'icon', + icon: 'mdi:dog', + title: 'Woof', + style: { + left: '100px', + top: '400px', + }, + }, + ], + }, + ]; + + expect( + createConfig({ + elements: stockElements, + }).elements, + ).toEqual(stockElements); + }); + + it('should include all custom elements', () => { + const customElements = [ + { + type: 'custom:advanced-camera-card-menu-icon', + alignment: 'matching', + enabled: true, + entity: 'light.office_main_lights', + icon: 'mdi:car', + permanent: false, + priority: 50, + style: { + color: 'white', + }, + title: 'Vroom', + }, + { + type: 'custom:advanced-camera-card-menu-state-icon', + alignment: 'matching', + enabled: true, + entity: 'light.office_main_lights', + icon: 'mdi:chair-rolling', + permanent: false, + priority: 50, + state_color: true, + style: { + color: 'white', + }, + title: 'Office lights', + }, + { + type: 'custom:advanced-camera-card-menu-submenu', + alignment: 'matching', + enabled: true, + entity: 'light.office_main_lights', + icon: 'mdi:menu', + items: [ + { + enabled: true, + entity: 'light.office_main_lights', + icon: 'mdi:lightbulb', + selected: false, + state_color: true, + style: { + color: 'white', + }, + tap_action: { + action: 'toggle', + }, + title: 'Lights', + }, + { + enabled: true, + icon: 'mdi:google', + selected: false, + state_color: false, + style: { + color: 'white', + }, + tap_action: { + action: 'url', + url_path: 'https://www.google.com', + }, + title: 'Google', + }, + ], + permanent: false, + priority: 50, + style: { + color: 'white', + }, + title: 'Office lights', + }, + { + type: 'custom:advanced-camera-card-menu-submenu-select', + alignment: 'matching', + enabled: true, + entity: 'input_select.kitchen_scene', + icon: 'mdi:lamps', + options: { + 'scene.kitchen_cooking_scene': { + enabled: true, + icon: 'mdi:chef-hat', + selected: false, + state_color: true, + style: { + color: 'white', + }, + title: 'Cooking time!', + }, + 'scene.kitchen_tv_scene': { + icon: 'mdi:television', + title: 'TV!', + }, + }, + permanent: false, + priority: 50, + state_color: true, + style: { + color: 'white', + }, + title: 'Kitchen Scene', + }, + { + type: 'custom:advanced-camera-card-conditional', + elements: [ + { + type: 'icon', + icon: 'mdi:pig', + title: 'Oink', + style: { + left: '300px', + top: '100px', + }, + }, + ], + conditions: [ + { + condition: 'view', + views: ['live'], + }, + ], + }, + { + type: 'custom:advanced-camera-card-status-bar-string', + enabled: true, + exclusive: false, + expand: false, + string: 'Intruder alert!', + priority: 50, + sufficient: false, + }, + { + type: 'custom:advanced-camera-card-status-bar-icon', + enabled: true, + exclusive: false, + expand: false, + icon: 'mdi:cow', + priority: 50, + sufficient: false, + }, + { + type: 'custom:advanced-camera-card-status-bar-image', + enabled: true, + exclusive: false, + expand: false, + image: 'https://my.site.com/status.png', + priority: 50, + sufficient: false, + }, + ]; + + expect( + createConfig({ + elements: customElements, + }).elements, + ).toEqual(customElements); + }); + + it('should include all conditions', () => { + const conditions = [ + { condition: 'camera', cameras: ['camera.office'] }, + { condition: 'config', paths: ['menu.style'] }, + { condition: 'display_mode', display_mode: 'single' }, + { condition: 'expand', expand: true }, + { condition: 'fullscreen', fullscreen: true }, + { condition: 'initialized' }, + { condition: 'interaction', interaction: true }, + { + condition: 'key', + alt: false, + ctrl: false, + key: 'F', + meta: false, + shift: false, + state: 'down', + }, + { condition: 'media_loaded', media_loaded: true }, + { condition: 'microphone', connected: true, muted: true }, + { + condition: 'numeric_state', + entity: 'sensor.office_temperature', + above: 10, + below: 20, + }, + { condition: 'screen', media_query: '(orientation: landscape)' }, + { + condition: 'state', + entity: 'climate.office', + state: 'heat', + state_not: 'off', + }, + { condition: 'triggered', triggered: ['camera.office'] }, + { condition: 'user', users: ['581fca7fdc014b8b894519cc531f9a04'] }, + { + condition: 'user_agent', + user_agent: + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + user_agent_re: 'Chrome/', + companion: true, + }, + { condition: 'view', views: ['live'] }, + ]; + + expect( + createConfig({ + elements: [ + { + type: 'custom:advanced-camera-card-conditional', + elements: [ + { + type: 'icon', + icon: 'mdi:pig', + }, + ], + conditions: conditions, + }, + ], + }).elements[0].conditions, + ).toEqual(conditions); + }); + + it('should include all stock actions', () => { + const stockActions = [ + { + action: 'more-info', + }, + { + action: 'toggle', + }, + { + action: 'call-service', + service: 'homeassistant.toggle', + data: { + entity_id: 'light.office_main_lights', + }, + }, + { + action: 'navigate', + navigation_path: '/lovelace/2', + }, + { + action: 'url', + url_path: 'https://www.home-assistant.io/', + }, + { + action: 'none', + }, + { + action: 'fire-dom-event', + key: 'value', + }, + { + action: 'perform-action', + perform_action: 'homeassistant.toggle', + target: { + entity_id: 'light.office_main_lights', + }, + }, + ]; + + expect( + createConfig({ + live: { + actions: { + tap_action: stockActions, + }, + }, + }).live.actions?.tap_action, + ).toEqual(stockActions); + }); + + it('should include all custom actions', () => { + const customActions = [ + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'camera_select', + camera: 'camera.front_door', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'camera_ui', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'clip', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'clips', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'default', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'diagnostics', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'display_mode_select', + display_mode: 'grid', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'download', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'expand', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'fullscreen', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'image', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'live', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'live_substream_off', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'live_substream_on', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'live_substream_select', + camera: 'camera.front_door_hd', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'log', + message: 'Hello, world!', + level: 'debug', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'media_player', + media_player: 'media_player.nesthub50be', + media_player_action: 'play', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'media_player', + media_player: 'media_player.nesthub', + media_player_action: 'stop', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'menu_toggle', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'microphone_connect', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'microphone_disconnect', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'microphone_mute', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'microphone_unmute', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'mute', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'pause', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'play', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'ptz', + ptz_action: 'preset', + ptz_preset: 'doorway', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'ptz_controls', + enabled: true, + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'ptz_digital', + absolute: { + zoom: 5, + pan: { + x: 58, + y: 14, + }, + }, + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'ptz_multi', + ptz_action: 'left', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'recording', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'recordings', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'screenshot', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'sleep', + duration: { + h: 1, + m: 20, + s: 56, + ms: 422, + }, + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'snapshot', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'snapshots', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'timeline', + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'unmute', + }, + + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'status_bar', + status_bar_action: 'add', + items: [ + { + type: 'custom:advanced-camera-card-status-bar-string', + enabled: true, + exclusive: false, + expand: false, + string: 'Intruder alert!', + priority: 50, + sufficient: false, + }, + { + type: 'custom:advanced-camera-card-status-bar-icon', + enabled: true, + exclusive: false, + expand: false, + icon: 'mdi:cow', + priority: 50, + sufficient: false, + }, + { + type: 'custom:advanced-camera-card-status-bar-image', + enabled: true, + exclusive: false, + expand: false, + image: 'https://my.site.com/status.png', + priority: 50, + sufficient: false, + }, + ], + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'status_bar', + status_bar_action: 'remove', + items: [ + { + type: 'custom:advanced-camera-card-status-bar-string', + enabled: true, + exclusive: false, + expand: false, + string: 'Intruder alert!', + priority: 50, + sufficient: false, + }, + { + type: 'custom:advanced-camera-card-status-bar-icon', + enabled: true, + exclusive: false, + expand: false, + icon: 'mdi:cow', + priority: 50, + sufficient: false, + }, + { + type: 'custom:advanced-camera-card-status-bar-image', + enabled: true, + exclusive: false, + expand: false, + image: 'https://my.site.com/status.png', + priority: 50, + sufficient: false, + }, + ], + }, + { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'status_bar', + status_bar_action: 'reset', + }, + ]; + + expect( + createConfig({ + live: { + actions: { + tap_action: customActions, + }, + }, + }).live.actions?.tap_action, + ).toEqual( + // Action type will be transformed to fire-dom-event. + customActions.map((action) => ({ ...action, action: 'fire-dom-event' })), + ); + }); }); it('should transform dimensions.aspect_ratio', () => {