chore: Standardize code, comments and docs on US-English spelling (#2707)

This commit is contained in:
Dermot Duffy
2026-08-23 08:57:41 -07:00
committed by GitHub
parent 965f2b4c9d
commit 03d9df8282
48 changed files with 94 additions and 89 deletions
+1 -1
View File
@@ -420,7 +420,7 @@ cameras:
| ------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `event_type` | | The Home Assistant event type to subscribe to (e.g. `zha_event`, `deconz_event`, or a custom event name fired by one of your automations). Same field name and meaning as in HA automation YAML. May be a single string or a list of strings to match any of them. | | `event_type` | | The Home Assistant event type to subscribe to (e.g. `zha_event`, `deconz_event`, or a custom event name fired by one of your automations). Same field name and meaning as in HA automation YAML. May be a single string or a list of strings to match any of them. |
| `event_data` | | Optional dictionary of key/value pairs the event's payload must contain for this entry to trigger. Mirrors Home Assistant's `event_data` matching exactly: the top-level keys you list must be present in the payload (extra payload keys are ignored), and nested objects are matched the same way -- list only the keys you care about and extra nested keys are ignored. Omit entirely to trigger on every fire of this `event_type`. | | `event_data` | | Optional dictionary of key/value pairs the event's payload must contain for this entry to trigger. Mirrors Home Assistant's `event_data` matching exactly: the top-level keys you list must be present in the payload (extra payload keys are ignored), and nested objects are matched the same way -- list only the keys you care about and extra nested keys are ignored. Omit entirely to trigger on every fire of this `event_type`. |
| `context` | | Optional filter on the event's `context` object. Recognised fields: `id`, `user_id`, `parent_id`. Each field may be a single value (equality) or a list (membership). All listed fields must match. | | `context` | | Optional filter on the event's `context` object. Recognized fields: `id`, `user_id`, `parent_id`. Each field may be a single value (equality) or a list (membership). All listed fields must match. |
> [!TIP] Shared `event_type` values like `zha_event` and `deconz_event` fire for **every** device on that integration. Without an `event_data` filter the camera would trigger on any Zigbee/deCONZ device press in your home. Use `event_data` to narrow down to the specific device you care about; you can copy values straight out of **Developer tools → Events** in Home Assistant. > [!TIP] Shared `event_type` values like `zha_event` and `deconz_event` fire for **every** device on that integration. Without an `event_data` filter the camera would trigger on any Zigbee/deCONZ device press in your home. Use `event_data` to narrow down to the specific device you care about; you can copy values straight out of **Developer tools → Events** in Home Assistant.
+1 -1
View File
@@ -229,7 +229,7 @@ triggers:
| `trigger` | Must be `event`. | | `trigger` | Must be `event`. |
| `event_type` | The Home Assistant event type to subscribe to (e.g. `zha_event`, `deconz_event`, or a custom event fired by one of your automations). May be a single string or a list of strings to match any of them. | | `event_type` | The Home Assistant event type to subscribe to (e.g. `zha_event`, `deconz_event`, or a custom event fired by one of your automations). May be a single string or a list of strings to match any of them. |
| `event_data` | Optional dictionary of key/value pairs the event's payload must contain for this entry to match. Mirrors Home Assistant's `event_data` matching exactly: the top-level keys you list must be present in the payload (extra payload keys are ignored), and nested objects are matched the same way -- list only the keys you care about and extra nested keys are ignored. Omit entirely to match every fire of `event_type`. | | `event_data` | Optional dictionary of key/value pairs the event's payload must contain for this entry to match. Mirrors Home Assistant's `event_data` matching exactly: the top-level keys you list must be present in the payload (extra payload keys are ignored), and nested objects are matched the same way -- list only the keys you care about and extra nested keys are ignored. Omit entirely to match every fire of `event_type`. |
| `context` | Optional filter on the event's `context` object. Recognised fields: `id`, `user_id`, `parent_id`. Each field may be a single value (equality) or a list (membership). All listed fields must match. | | `context` | Optional filter on the event's `context` object. Recognized fields: `id`, `user_id`, `parent_id`. Each field may be a single value (equality) or a list (membership). All listed fields must match. |
The fired event is exposed to action templates as `trigger.event.*`, matching HA's event trigger template surface (`trigger.event.event_type`, `trigger.event.data`, `trigger.event.context`, `trigger.event.origin`, `trigger.event.time_fired`). The fired event is exposed to action templates as `trigger.event.*`, matching HA's event trigger template surface (`trigger.event.event_type`, `trigger.event.data`, `trigger.event.context`, `trigger.event.origin`, `trigger.event.time_fired`).
+1 -1
View File
@@ -64,7 +64,7 @@ folders:
``` ```
> [!TIP] > [!TIP]
> To match everything at a given level whilst parsing nothing would simply be > To match everything at a given level while parsing nothing would simply be
> represented by an empty object `{}` > represented by an empty object `{}`
#### Matchers #### Matchers
+1 -1
View File
@@ -61,7 +61,7 @@ live:
| `enabled` | `true` | Whether the on-screen call controls are shown during a call. Set to `false` to hide them and drive calls from the [`menu`](./menu.md) instead. When hiding the controls, be sure to also remove `call` from `menu.auto_hide` so the menu stays visible allowing the call to be controlled. See [Driving calls from the menu](../examples.md?id=driving-calls-from-the-menu) for a complete example. | | `enabled` | `true` | Whether the on-screen call controls are shown during a call. Set to `false` to hide them and drive calls from the [`menu`](./menu.md) instead. When hiding the controls, be sure to also remove `call` from `menu.auto_hide` so the menu stays visible allowing the call to be controlled. See [Driving calls from the menu](../examples.md?id=driving-calls-from-the-menu) for a complete example. |
| `lock` | `true` | Whether to lock the rest of the card controls/actions while a call is in progress. Prevents an accidental tap, swipe or action mid-call. Set to `false` to allow interactions regardless of call state. | | `lock` | `true` | Whether to lock the rest of the card controls/actions while a call is in progress. Prevents an accidental tap, swipe or action mid-call. Set to `false` to allow interactions regardless of call state. |
| `ringtone` | | The audible chime played while an inbound call (e.g. one started by [`view.triggers.actions.trigger: call`](./view.md?id=trigger-action-configuration)) is ringing. Stops as soon as the call is answered or ended. Manual calls never ring. See [`ringtone`](#ringtone). | | `ringtone` | | The audible chime played while an inbound call (e.g. one started by [`view.triggers.actions.trigger: call`](./view.md?id=trigger-action-configuration)) is ringing. Stops as soon as the call is answered or ended. Manual calls never ring. See [`ringtone`](#ringtone). |
| `unanswered_timeout_seconds` | `60` | The number of seconds an inbound call may ring unanswered before it is automatically ended. The timer is cancelled the moment the call is answered. Set to `0` to disable the timeout. | | `unanswered_timeout_seconds` | `60` | The number of seconds an inbound call may ring unanswered before it is automatically ended. The timer is canceled the moment the call is answered. Set to `0` to disable the timeout. |
> [!NOTE] Browser autoplay restrictions may prevent the ringtone from playing > [!NOTE] Browser autoplay restrictions may prevent the ringtone from playing
> until the page has received a user gesture (e.g. a tap or click). When that > until the page has received a user gesture (e.g. a tap or click). When that
+1 -1
View File
@@ -64,7 +64,7 @@ menu:
| `alignment` | `matching` | Whether this menu item should have an alignment that is `matching` the menu alignment or `opposing` the menu alignment. Can be used to create two separate groups of buttons on the menu. The `priority` option orders buttons within a given `alignment`. | | `alignment` | `matching` | Whether this menu item should have an alignment that is `matching` the menu alignment or `opposing` the menu alignment. Can be used to create two separate groups of buttons on the menu. The `priority` option orders buttons within a given `alignment`. |
| `enabled` | `true` for `call`, `camera_ui`, `cameras`, `display_mode`, `download`, `folders`, `fullscreen`, `gallery`, `info`, `iris`, `live`, `media_player`, `set_review`, `substreams` and `timeline`. `false` for `clips`, `expand`, `image`, `microphone`, `mute`, `pip`, `play`, `ptz_controls`, `ptz_home`, `recordings`, `reviews`, `screenshot` and `snapshots`. | Whether or not to show the button. | | `enabled` | `true` for `call`, `camera_ui`, `cameras`, `display_mode`, `download`, `folders`, `fullscreen`, `gallery`, `info`, `iris`, `live`, `media_player`, `set_review`, `substreams` and `timeline`. `false` for `clips`, `expand`, `image`, `microphone`, `mute`, `pip`, `play`, `ptz_controls`, `ptz_home`, `recordings`, `reviews`, `screenshot` and `snapshots`. | Whether or not to show the button. |
| `icon` | | An icon to overriding the default for that button, e.g. `mdi:camera-front`. See also [custom icons](../usage/custom-icons.md). | | `icon` | | An icon to overriding the default for that button, e.g. `mdi:camera-front`. See also [custom icons](../usage/custom-icons.md). |
| `inert` | `false` | If `true` the button is shown but rendered as inert (greyed out, non-interactive). Differs from `enabled: false`, which removes the button entirely. | | `inert` | `false` | If `true` the button is shown but rendered as inert (grayed out, non-interactive). Differs from `enabled: false`, which removes the button entirely. |
| `permanent` | `false` | If `false` the menu item is hidden when the menu has the `hidden` style and the menu is closed, otherwise it is shown (and sorted to the front). | | `permanent` | `false` | If `false` the menu item is hidden when the menu has the `hidden` style and the menu is closed, otherwise it is shown (and sorted to the front). |
| `priority` | `50` | The menu item priority. Higher priority items are ordered closer to the start of the menu alignment (i.e. a button with priority `70` will order further to the left than a button with priority `60`). Priority applies separately to `matching` and `opposing` groups (see `alignment` above). Minimum `0`, maximum `100`. | | `priority` | `50` | The menu item priority. Higher priority items are ordered closer to the start of the menu alignment (i.e. a button with priority `70` will order further to the left than a button with priority `60`). Priority applies separately to `matching` and `opposing` groups (see `alignment` above). Minimum `0`, maximum `100`. |
| `state_color` | `true` | Whether to colorize the button based on the state of a related entity (where applicable). | | `state_color` | `true` | Whether to colorize the button based on the state of a related entity (where applicable). |
+4 -1
View File
@@ -11,7 +11,10 @@ overrides:
``` ```
> [!WARNING] > [!WARNING]
> Whilst all configuration parameters are theoretically overridable, in some instances a configuration variable may only be consulted on startup or changing its value may negatively impact behavior -- override results may vary! > While all configuration parameters are theoretically overridable, in some
> instances a configuration variable may only be consulted on startup or
> changing its value may negatively impact behavior -- override results may
> vary!
> [!WARNING] > [!WARNING]
> Avoid an override whose `conditions` depend on a value that the override itself > Avoid an override whose `conditions` depend on a value that the override itself
+6 -6
View File
@@ -2,9 +2,9 @@
The behavior of the timeline during seeking/dragging can be controlled by means of the icon on the bottom-right of the timeline, or by using the `pan_mode` configuration variable for the relevant timeline controls (e.g. `live.controls.timeline.pan_mode`). The behavior of the timeline during seeking/dragging can be controlled by means of the icon on the bottom-right of the timeline, or by using the `pan_mode` configuration variable for the relevant timeline controls (e.g. `live.controls.timeline.pan_mode`).
| Configuration name | UI Icon | Behavior | | Configuration name | UI Icon | Behavior |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pan` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>pan-horizontal</title><path d="M7,8L2.5,12L7,16V8M17,8V16L21.5,12L17,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10Z" /></svg> | Dragging the timeline will pan only without selected or seeking any media. | | `pan` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>pan-horizontal</title><path d="M7,8L2.5,12L7,16V8M17,8V16L21.5,12L17,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10Z" /></svg> | Dragging the timeline will pan only without selected or seeking any media. |
| `seek-in-camera` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>camera-lock</title><path d="M4 4H7L9 2H15L17 4H20C21.11 4 22 4.89 22 6V12C21.16 11.37 20.13 11 19 11C18.21 11 17.46 11.18 16.79 11.5C16.18 9.22 14.27 7 12 7C9.24 7 7 9.24 7 12C7 14.76 9.24 17 12 17C12.42 17 12.84 16.95 13.23 16.85C13.08 17.2 13 17.59 13 18V20H4C2.9 20 2 19.11 2 18V6C2 4.89 2.9 4 4 4M12 9C13.66 9 15 10.34 15 12C15 13.66 13.66 15 12 15C10.34 15 9 13.66 9 12C9 10.34 10.34 9 12 9M23 18.3V21.8C23 22.4 22.4 23 21.7 23H16.2C15.6 23 15 22.4 15 21.7V18.2C15 17.6 15.6 17 16.2 17V15.5C16.2 14.1 17.6 13 19 13C20.4 13 21.8 14.1 21.8 15.5V17C22.4 17 23 17.6 23 18.3M20.5 15.5C20.5 14.7 19.8 14.2 19 14.2C18.2 14.2 17.5 14.7 17.5 15.5V17H20.5V15.5Z" /></svg> | Dragging the timeline will seek / select across all available media from the selected camera only, selecting the media item with the longest duration. | | `seek-in-camera` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>camera-lock</title><path d="M4 4H7L9 2H15L17 4H20C21.11 4 22 4.89 22 6V12C21.16 11.37 20.13 11 19 11C18.21 11 17.46 11.18 16.79 11.5C16.18 9.22 14.27 7 12 7C9.24 7 7 9.24 7 12C7 14.76 9.24 17 12 17C12.42 17 12.84 16.95 13.23 16.85C13.08 17.2 13 17.59 13 18V20H4C2.9 20 2 19.11 2 18V6C2 4.89 2.9 4 4 4M12 9C13.66 9 15 10.34 15 12C15 13.66 13.66 15 12 15C10.34 15 9 13.66 9 12C9 10.34 10.34 9 12 9M23 18.3V21.8C23 22.4 22.4 23 21.7 23H16.2C15.6 23 15 22.4 15 21.7V18.2C15 17.6 15.6 17 16.2 17V15.5C16.2 14.1 17.6 13 19 13C20.4 13 21.8 14.1 21.8 15.5V17C22.4 17 23 17.6 23 18.3M20.5 15.5C20.5 14.7 19.8 14.2 19 14.2C18.2 14.2 17.5 14.7 17.5 15.5V17H20.5V15.5Z" /></svg> | Dragging the timeline will seek / select across all available media from the selected camera only, selecting the media item with the longest duration. |
| `seek-in-media` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>play-box-lock</title><path d="M23 17.3V20.8C23 21.4 22.4 22 21.7 22H16.2C15.6 22 15 21.4 15 20.7V17.2C15 16.6 15.6 16 16.2 16V14.5C16.2 13.1 17.6 12 19 12C20.4 12 21.8 13.1 21.8 14.5V16C22.4 16 23 16.6 23 17.3M13 19V21H4C2.89 21 2 20.1 2 19V5C2 3.89 2.89 3 4 3H18C19.1 3 20 3.89 20 5V10.1L19 10L18 10.1C15.79 10.55 14.12 12.45 14 14.76C13.39 15.31 13 16.11 13 17V19M20.5 14.5C20.5 13.7 19.8 13.2 19 13.2C18.2 13.2 17.5 13.7 17.5 14.5V16H20.5V14.5M9 8V16L14 12L9 8Z" /></svg> | Dragging the timeline will seek within the selected media item only. | | `seek-in-media` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>play-box-lock</title><path d="M23 17.3V20.8C23 21.4 22.4 22 21.7 22H16.2C15.6 22 15 21.4 15 20.7V17.2C15 16.6 15.6 16 16.2 16V14.5C16.2 13.1 17.6 12 19 12C20.4 12 21.8 13.1 21.8 14.5V16C22.4 16 23 16.6 23 17.3M13 19V21H4C2.89 21 2 20.1 2 19V5C2 3.89 2.89 3 4 3H18C19.1 3 20 3.89 20 5V10.1L19 10L18 10.1C15.79 10.55 14.12 12.45 14 14.76C13.39 15.31 13 16.11 13 17V19M20.5 14.5C20.5 13.7 19.8 13.2 19 13.2C18.2 13.2 17.5 13.7 17.5 14.5V16H20.5V14.5M9 8V16L14 12L9 8Z" /></svg> | Dragging the timeline will seek within the selected media item only. |
| `seek` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>filmstrip-box-multiple</title><path d="M4,6H2V20A2,2 0 0,0 4,22H18V20H4V6M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M10,15H8V13H10V15M10,11H8V9H10V11M10,7H8V5H10V7M20,15H18V13H20V15M20,11H18V9H20V11M20,7H18V5H20V7Z" /></svg> | Dragging the timeline will seek / select across all available media from all cameras, selecting the media item with the longest duration whilst favoring (but not limited to) the currently selected camera. | | `seek` | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>filmstrip-box-multiple</title><path d="M4,6H2V20A2,2 0 0,0 4,22H18V20H4V6M20,2H8A2,2 0 0,0 6,4V16A2,2 0 0,0 8,18H20A2,2 0 0,0 22,16V4A2,2 0 0,0 20,2M10,15H8V13H10V15M10,11H8V9H10V11M10,7H8V5H10V7M20,15H18V13H20V15M20,11H18V9H20V11M20,7H18V5H20V7Z" /></svg> | Dragging the timeline will seek / select across all available media from all cameras, selecting the media item with the longest duration while favoring (but not limited to) the currently selected camera. |
+2 -2
View File
@@ -376,7 +376,7 @@ elements:
The [`doorbell` profile](configuration/profiles.md?id=doorbell) turns a dashboard into a phone-like ringer when somebody presses the doorbell, by setting [`view.triggers.actions.trigger: call`](configuration/view.md?id=triggers) and auto-discovering [HA `event.*` entities](https://www.home-assistant.io/integrations/event/#device-class) with `device_class: doorbell` on the camera's device (Ring, UniFi Protect, Nest, DoorBird, Reolink, etc.). The intended deployment is a wall-mounted tablet sitting on the dashboard. The [`doorbell` profile](configuration/profiles.md?id=doorbell) turns a dashboard into a phone-like ringer when somebody presses the doorbell, by setting [`view.triggers.actions.trigger: call`](configuration/view.md?id=triggers) and auto-discovering [HA `event.*` entities](https://www.home-assistant.io/integrations/event/#device-class) with `device_class: doorbell` on the camera's device (Ring, UniFi Protect, Nest, DoorBird, Reolink, etc.). The intended deployment is a wall-mounted tablet sitting on the dashboard.
A doorbell press is instantaneous, so the card synthesises a ring window from [`view.triggers.event_hold_seconds`](configuration/view.md?id=triggers) (default `30`s) -- long enough for a typical phone-style answer window. `untrigger_delay_seconds` then lingers past that, same as for any stateful trigger. A doorbell press is instantaneous, so the card synthesizes a ring window from [`view.triggers.event_hold_seconds`](configuration/view.md?id=triggers) (default `30`s) -- long enough for a typical phone-style answer window. `untrigger_delay_seconds` then lingers past that, same as for any stateful trigger.
`triggers.motion`, `triggers.occupancy`, and `triggers.media_events` are off by default -- only the explicit doorbell press triggers the call, so casual motion won't make the card ring. `triggers.motion`, `triggers.occupancy`, and `triggers.media_events` are off by default -- only the explicit doorbell press triggers the call, so casual motion won't make the card ring.
@@ -506,7 +506,7 @@ automations:
> [!WARNING] > [!WARNING]
> This example is only relevant to those doorbells with a design flaw > This example is only relevant to those doorbells with a design flaw
> that prevents the doorbell being rung whilst a 2-way channel is enabled. This > that prevents the doorbell being rung while a 2-way channel is enabled. This
> means simply watching the stream, when you might _in future_ hold a 2-way > means simply watching the stream, when you might _in future_ hold a 2-way
> conversation on that stream, can disable the actual whole point of a doorbell -- > conversation on that stream, can disable the actual whole point of a doorbell --
> to ring! > to ring!
+1 -1
View File
@@ -55,7 +55,7 @@ To resolve it:
1. Re-create each entry under it in the current format (see the relevant 1. Re-create each entry under it in the current format (see the relevant
configuration documentation in the sidebar). An upgrade usually fails this configuration documentation in the sidebar). An upgrade usually fails this
way when a feature has been retired, or changed enough that the migration way when a feature has been retired, or changed enough that the migration
needs human judgement. needs human judgment.
1. Once you're happy with the result, delete the `__UPGRADE_FAILURE__` key. 1. Once you're happy with the result, delete the `__UPGRADE_FAILURE__` key.
The notification clears once the `__UPGRADE_FAILURE__` key is gone. The notification clears once the `__UPGRADE_FAILURE__` key is gone.
+1 -1
View File
@@ -12,7 +12,7 @@ The Advanced Camera Card will execute these actions in the following circumstanc
- On 'tab' change in a dashboard. - On 'tab' change in a dashboard.
- When a `navigate` [action](https://www.home-assistant.io/dashboards/actions/) - When a `navigate` [action](https://www.home-assistant.io/dashboards/actions/)
is called on the dashboard (e.g. a button click requests navigation). is called on the dashboard (e.g. a button click requests navigation).
- When the user uses the `back` / `forward` browser buttons whilst viewing a - When the user uses the `back` / `forward` browser buttons while viewing a
dashboard. dashboard.
## Instructions ## Instructions
@@ -96,7 +96,7 @@ export class ActionsManager implements ActionsExecutor {
ev: Event | CustomEvent<ActionConfig>, ev: Event | CustomEvent<ActionConfig>,
): Promise<void> => { ): Promise<void> => {
if (!('detail' in ev)) { if (!('detail' in ev)) {
// The event may or may not be a CustomEvent object. For example, whilst // The event may or may not be a CustomEvent object. For example, while
// this card doesn't use custom-card-helpers, embedded elements may: // this card doesn't use custom-card-helpers, embedded elements may:
// https://github.com/custom-cards/custom-card-helpers/blob/master/src/fire-event.ts#L70 // https://github.com/custom-cards/custom-card-helpers/blob/master/src/fire-event.ts#L70
return; return;
+1 -1
View File
@@ -1,7 +1,7 @@
import type { AdvancedCameraCardConfig } from '../../config/schema/types'; import type { AdvancedCameraCardConfig } from '../../config/schema/types';
// The card state exposed via the `acc` namespace AND as the // The card state exposed via the `acc` namespace AND as the
// `trigger.from_acc`/`to_acc` before/after snapshots (the card analogue of HA's // `trigger.from_acc`/`to_acc` before/after snapshots (the card analog of HA's
// full `trigger.from_state`/`to_state`). // full `trigger.from_state`/`to_state`).
export interface TemplateAdvancedCameraCardState { export interface TemplateAdvancedCameraCardState {
camera?: string; camera?: string;
@@ -25,7 +25,7 @@ export class SubstreamViewModifier implements ViewModifier {
return; return;
} }
// A stream equal to the camera itself is semantically "no substream"; // A stream equal to the camera itself is semantically "no substream";
// normalise it to a cleared override so the map doesn't carry self- // normalize it to a cleared override so the map doesn't carry self-
// referential entries. // referential entries.
if (!this._options.stream || this._options.stream === cameraID) { if (!this._options.stream || this._options.stream === cameraID) {
view.context?.live?.overrides?.delete(cameraID); view.context?.live?.overrides?.delete(cameraID);
+2 -2
View File
@@ -20,7 +20,7 @@ export const getLocalizationKeyForPath = (path: ConfigPath): string =>
* Compute the label for a form field. Intended for use as an `ha-form` * Compute the label for a form field. Intended for use as an `ha-form`
* `computeLabel` callback. A field is named for the setting it edits, which for * `computeLabel` callback. A field is named for the setting it edits, which for
* a bound field is wherever that setting is stored rather than where the field * a bound field is wherever that setting is stored rather than where the field
* sits in the form. Container nodes are labelled by their explicit title, never * sits in the form. Container nodes are labeled by their explicit title, never
* by configuration path (their headers already render the title). * by configuration path (their headers already render the title).
* @param form The form the field belongs to. * @param form The form the field belongs to.
* @param schema The field's schema. * @param schema The field's schema.
@@ -37,7 +37,7 @@ export const computeFormLabel = (
return schema.label; return schema.label;
} }
if (!isFormFieldSchema(schema)) { if (!isFormFieldSchema(schema)) {
// Only an expandable has a title: a grid lays out fields that are labelled // Only an expandable has a title: a grid lays out fields that are labeled
// individually, and shows nothing of its own. // individually, and shows nothing of its own.
return (schema.type === 'expandable' ? schema.title : null) ?? schema.name ?? ''; return (schema.type === 'expandable' ? schema.title : null) ?? schema.name ?? '';
} }
+1 -1
View File
@@ -20,7 +20,7 @@ const getShownPaths = (forms: EditorForm[]): Set<string> =>
// What the full editor shows and the simple editor does not. Configuration // What the full editor shows and the simple editor does not. Configuration
// outside this is either something the simple editor shows too, or something // outside this is either something the simple editor shows too, or something
// neither shows (`elements`, `debug`, anything the card does not recognise at // neither shows (`elements`, `debug`, anything the card does not recognize at
// all), which the full editor would be no better at showing. // all), which the full editor would be no better at showing.
const getFullOnlyPaths = (): Set<string> => { const getFullOnlyPaths = (): Set<string> => {
const simplePaths = getShownPaths([ const simplePaths = getShownPaths([
@@ -34,7 +34,7 @@ import { isWebKitUserAgent } from '../utils/user-agent';
// MSE Tuning // MSE Tuning
// =========================================================================== // ===========================================================================
// These are judgement-based budgets (time, buffered seconds, staged bytes) // These are judgment-based budgets (time, buffered seconds, staged bytes)
// balancing latency and resilience, not derived values. The retained-buffer // balancing latency and resilience, not derived values. The retained-buffer
// window and the staged-bytes cap follow go2rtc's reference web client; the // window and the staged-bytes cap follow go2rtc's reference web client; the
// negotiation timeout has no counterpart there and is added by this // negotiation timeout has no counterpart there and is added by this
@@ -89,7 +89,7 @@ export class MediaDimensionsContainerController implements ReactiveController {
private _mediaLoadedHandler = (ev: CustomEvent<MediaLoadedInfoEventDetail>): void => { private _mediaLoadedHandler = (ev: CustomEvent<MediaLoadedInfoEventDetail>): void => {
const info = ev.detail.info; const info = ev.detail.info;
// Only resize if the media dimensions have changed (otherwise the loading // Only resize if the media dimensions have changed (otherwise the loading
// image whilst waiting for the stream, will trigger aresize every second). // image while waiting for the stream, will trigger aresize every second).
if ( if (
this._mediaDimensions?.width === info.width && this._mediaDimensions?.width === info.width &&
this._mediaDimensions.height === info.height this._mediaDimensions.height === info.height
+1 -1
View File
@@ -186,7 +186,7 @@ export class AdvancedCameraCardImageUpdatingPlayer
livenessOptions: { livenessOptions: {
// Frames are only due while the refresh timer runs. A snapshot with // Frames are only due while the refresh timer runs. A snapshot with
// refreshing switched off shows one picture forever, which is the // refreshing switched off shows one picture forever, which is the
// configured behaviour and never a stall. // configured behavior and never a stall.
isFrameExpected: () => this._cachedValueController.hasTimer(), isFrameExpected: () => this._cachedValueController.hasTimer(),
// Allow a whole refresh interval to pass, plus the standard window, so // Allow a whole refresh interval to pass, plus the standard window, so
+2 -2
View File
@@ -154,7 +154,7 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
/** /**
* Determine if a camera image should be shown in lieu of the real stream * Determine if a camera image should be shown in lieu of the real stream
* whilst loading. * while loading.
* @returns`true` if an image should be shown. * @returns`true` if an image should be shown.
*/ */
private _shouldShowImageDuringLoading(): boolean { private _shouldShowImageDuringLoading(): boolean {
@@ -287,7 +287,7 @@ export class AdvancedCameraCardLiveProvider extends LitElement implements MediaP
private _getNotification(failure: StreamFailure | null): TemplateResult | null { private _getNotification(failure: StreamFailure | null): TemplateResult | null {
// If a card *re*-initializes (e.g. was already initialized and then there's // If a card *re*-initializes (e.g. was already initialized and then there's
// a use of the editor to change the config), cameras will re-initialize in // a use of the editor to change the config), cameras will re-initialize in
// place, which means they might be asked to render (here) whilst not yet // place, which means they might be asked to render (here) while not yet
// being initialized. This can cause spurious errors (e.g. lack of resolved // being initialized. This can cause spurious errors (e.g. lack of resolved
// endpoints). Instead, simply never render uninitialized cameras. // endpoints). Instead, simply never render uninitialized cameras.
if (!this.camera?.isInitialized()) { if (!this.camera?.isInitialized()) {
@@ -5,9 +5,10 @@ import type { ConditionsEvaluationResult, ConditionState } from '../types';
import type { ConditionEvaluator, ConditionOfType, EvaluatorContext } from './types'; import type { ConditionEvaluator, ConditionOfType, EvaluatorContext } from './types';
// Home Assistant resolves an expected value that names an `input_*` helper to // Home Assistant resolves an expected value that names an `input_*` helper to
// that helper's current state (its Lovelace state-condition behaviour), on both // that helper's current state (its Lovelace state-condition behavior), on both
// the state and the attribute path; only these helper domains are resolved. // the state and the attribute path; only these helper domains are resolved.
// Regexp directly from: https://github.com/home-assistant/core/blob/dev/homeassistant/helpers/condition.py // Regexp directly from:
// https://github.com/home-assistant/core/blob/dev/homeassistant/helpers/condition.py
const INPUT_ENTITY_ID = const INPUT_ENTITY_ID =
/^input_(?:select|text|number|boolean|datetime)\.(?!.+__)(?!_)[\da-z_]+(?<!_)$/; /^input_(?:select|text|number|boolean|datetime)\.(?!.+__)(?!_)[\da-z_]+(?<!_)$/;
+2 -2
View File
@@ -587,7 +587,7 @@ const dropTriggerOnlyConditions = (conditions: unknown[]): unknown[] => {
// composite the user wrote empty is kept; only one the recursion emptied // composite the user wrote empty is kept; only one the recursion emptied
// is dropped. // is dropped.
if (inner.length || !children.length) { if (inner.length || !children.length) {
// `arrayify` has already normalised `children`, so rebuilding a // `arrayify` has already normalized `children`, so rebuilding a
// composite the recursion left alone would rewrite Home Assistant's // composite the recursion left alone would rewrite Home Assistant's
// single-condition spelling (`or: <condition>`) into a list the user // single-condition spelling (`or: <condition>`) into a list the user
// never wrote. // never wrote.
@@ -672,7 +672,7 @@ const getAutomationList = (
plural: string, plural: string,
singular: string, singular: string,
): { key: string; items: unknown[] } => { ): { key: string; items: unknown[] } => {
// Mirroring the schema's rename, the singular key is honoured only when the // Mirroring the schema's rename, the singular key is honored only when the
// plural is absent; an automation with neither is written under the plural. // plural is absent; an automation with neither is written under the plural.
const key = singular in automation && !(plural in automation) ? singular : plural; const key = singular in automation && !(plural in automation) ? singular : plural;
return { key, items: arrayify(automation[key]) }; return { key, items: arrayify(automation[key]) };
@@ -2,7 +2,7 @@ import { z } from 'zod';
import { arrayify } from '../../../utils/basic'; import { arrayify } from '../../../utils/basic';
// Normalise a single item to an array like Home Assistant's `cv.ensure_list` (a // Normalize a single item to an array like Home Assistant's `cv.ensure_list` (a
// key written with no value is a list of nothing). The schema output is always // key written with no value is a list of nothing). The schema output is always
// the list, so the rest of the code only ever sees the canonical form. An // the list, so the rest of the code only ever sees the canonical form. An
// absent value is passed through so that an optional field stays absent rather // absent value is passed through so that an optional field stays absent rather
@@ -10,7 +10,7 @@ export interface SubscriptionFailure<K> {
failureCount?: number; failureCount?: number;
} }
// Recoverable health of a set of keyed subscriptions. A named specialisation // Recoverable health of a set of keyed subscriptions. A named specialization
// for readability and the extension point for any subscription-specific health // for readability and the extension point for any subscription-specific health
// surface later. // surface later.
export type SubscriptionHealthInterface<K> = RecoverableHealthInterface< export type SubscriptionHealthInterface<K> = RecoverableHealthInterface<
+1 -1
View File
@@ -88,7 +88,7 @@ const matchesSchemaValue = (expected: unknown, actual: unknown): boolean => {
// HA-faithful match for the event `context` object: every defined filter field // HA-faithful match for the event `context` object: every defined filter field
// must match the event's corresponding field by equality (scalar filter) or // must match the event's corresponding field by equality (scalar filter) or
// list-membership (array filter). A null event-side field never satisfies an // list-membership (array filter). A null event-side field never satisfies an
// explicit filter, mirroring HA's behaviour. // explicit filter, mirroring HA's behavior.
// https://www.home-assistant.io/docs/automation/trigger/#event-trigger // https://www.home-assistant.io/docs/automation/trigger/#event-trigger
export const matchesEventContext = ( export const matchesEventContext = (
filter: HAEventContextFilter, filter: HAEventContextFilter,
+4 -4
View File
@@ -55,11 +55,11 @@
} }
// Each top-level section is an HA expansion panel. An open section draws its // Each top-level section is an HA expansion panel. An open section draws its
// own outline, which would otherwise sit directly against the neighbouring // own outline, which would otherwise sit directly against the neighboring
// sections, so every section is spaced whether it is open or not: a gap that // sections, so every section is spaced whether it is open or not: a gap that
// appeared only on opening would move the sections below as the panel grew. // appeared only on opening would move the sections below as the panel grew. The
// The margins of two adjacent sections collapse together, so the gap is the // margins of two adjacent sections collapse together, so the gap is the same
// same wherever it falls. // wherever it falls.
.section { .section {
display: block; display: block;
+4 -4
View File
@@ -38,12 +38,12 @@
// Triggered and transmitting cameras are highlighted with a soft inset glow, // Triggered and transmitting cameras are highlighted with a soft inset glow,
// drawn by an overlay stacked above the camera feed. The overlays come before // drawn by an overlay stacked above the camera feed. The overlays come before
// the carousel controls in source order, so they stack above the feed but // the carousel controls in source order, so they stack above the feed but below
// below the controls; `pointer-events: none` keeps them inert. The glow is an // the controls; `pointer-events: none` keeps them inert. The glow is an
// interior effect, so it coexists with the grid selection border (see // interior effect, so it coexists with the grid selection border (see
// media-grid.scss) without competing for the `border` property. // media-grid.scss) without competing for the `border` property.
// //
// The two states differ by colour and rhythm: triggered is an amber glow that // The two states differ by color and rhythm: triggered is an amber glow that
// pulses fully out and back (a "something happened" beacon), transmitting is a // pulses fully out and back (a "something happened" beacon), transmitting is a
// red glow that breathes gently without ever fully fading ("you are live"). // red glow that breathes gently without ever fully fading ("you are live").
// Transmitting wins: while it is active the trigger overlay is faded out. // Transmitting wins: while it is active the trigger overlay is faded out.
@@ -82,7 +82,7 @@
// Hidden by default; activation/deactivation is a plain opacity fade. Each // Hidden by default; activation/deactivation is a plain opacity fade. Each
// overlay carries its own static box-shadow (not scoped to the // overlay carries its own static box-shadow (not scoped to the
// `[triggered]`/`[transmitting]` selectors) so the glow stays painted in its // `[triggered]`/`[transmitting]` selectors) so the glow stays painted in its
// own colour throughout the fade-out, after its animation has been removed. // own color throughout the fade-out, after its animation has been removed.
opacity: 0; opacity: 0;
transition: opacity 0.5s ease-in-out; transition: opacity 0.5s ease-in-out;
} }
+1 -2
View File
@@ -26,8 +26,7 @@ img {
border-radius: var(--advanced-camera-card-border-radius-final); border-radius: var(--advanced-camera-card-border-radius-final);
// Not 'contain' as some thumbnails may vary in aspect-ratio slightly and // Not 'contain' as some thumbnails may vary in aspect-ratio slightly and
// should be clipped to fill the thumbnail div whilst maintaining // should be clipped to fill the thumbnail div while maintaining aspect-ratio.
// aspect-ratio.
object-fit: cover; object-fit: cover;
} }
+1 -1
View File
@@ -10,7 +10,7 @@ import { errorToConsole } from './basic';
* @param host The host object. * @param host The host object.
* @param task The Lit task. * @param task The Lit task.
* @param completeFunc The function to call with the result. * @param completeFunc The function to call with the result.
* @param inProgressFunc The function to call whilst in progress. * @param inProgressFunc The function to call while in progress.
* @returns A template. * @returns A template.
*/ */
export const renderTask = <R>( export const renderTask = <R>(
+1 -1
View File
@@ -92,7 +92,7 @@ export const tabUntil = async (
/** /**
* Click an element with a real pointer, which is the only kind that carries the * Click an element with a real pointer, which is the only kind that carries the
* browser's own behaviour: the press moves focus, and an element that stops the * browser's own behavior: the press moves focus, and an element that stops the
* press doing so leaves it where it was. * press doing so leaves it where it was.
*/ */
export const clickElement = async (element: Element): Promise<void> => export const clickElement = async (element: Element): Promise<void> =>
+6 -6
View File
@@ -92,12 +92,12 @@ const createRegistryEntry = (entityID: string, overrides?: Partial<Entity>): Ent
* A driveable stand-in for the `hass` object the card is handed by Home * A driveable stand-in for the `hass` object the card is handed by Home
* Assistant. * Assistant.
* *
* Home Assistant updates immutably, and that is the behaviour reproduced here: * Home Assistant updates immutably, and that is the behavior reproduced here: a
* a change hands consumers a new top-level object and a new `states` map * change hands consumers a new top-level object and a new `states` map holding
* holding a new state object for the entity that changed, while every other * a new state object for the entity that changed, while every other entity
* entity keeps the object it already had. Anything the card can observe about * keeps the object it already had. Anything the card can observe about a real
* a real `hass` it must be able to observe about this one, so a divergence here * `hass` it must be able to observe about this one, so a divergence here is a
* is a bug in the fake rather than a shortcut worth taking. * bug in the fake rather than a shortcut worth taking.
*/ */
export class FakeHASS { export class FakeHASS {
private _hass: HomeAssistant; private _hass: HomeAssistant;
+2 -2
View File
@@ -40,8 +40,8 @@ class HAPlayerStandIn extends LitElement {
} }
/** /**
* A stand-in that renders its children and nothing else. Behaviour is added * A stand-in that renders its children and nothing else. Behavior is added only
* only for elements the card is observed to depend on. * for elements the card is observed to depend on.
*/ */
class HAElementStub extends LitElement { class HAElementStub extends LitElement {
static styles = css` static styles = css`
+2 -2
View File
@@ -158,11 +158,11 @@ interface EventEntry {
target: EventTarget | null; target: EventTarget | null;
} }
interface LabelledElement extends Element { interface LabeledElement extends Element {
label?: string; label?: string;
} }
const hasLabel = (element: Element): element is LabelledElement => 'label' in element; const hasLabel = (element: Element): element is LabeledElement => 'label' in element;
/** /**
* What a control calls itself to the user. The card titles the controls it * What a control calls itself to the user. The card titles the controls it
+1 -1
View File
@@ -36,7 +36,7 @@ const HOME_ASSISTANT_THEME = `
color: var(--primary-text-color); color: var(--primary-text-color);
} }
/* A dashboard sits the card on the background colour with room around it. */ /* A dashboard sits the card on the background color with room around it. */
body { body {
background: var(--primary-background-color); background: var(--primary-background-color);
margin: 0; margin: 0;
+7 -7
View File
@@ -87,13 +87,13 @@ export const getTestMediaRequestCount = (url: string): number => {
/** /**
* Serves a fixture at `/test-media/<file>`, behaving as the query asks: * Serves a fixture at `/test-media/<file>`, behaving as the query asks:
* *
* token Which counter the request belongs to, so that a test's * token Which counter the request belongs to, so that a test's behavior
* behaviour does not depend on what ran before it. * does not depend on what ran before it. responses The status to
* responses The status to answer each request with, in order: `200` serves * answer each request with, in order: `200` serves the file and
* the file and anything else is sent as an empty error. * anything else is sent as an empty error. repeat What to do
* repeat What to do once the `responses` list is exhausted: answer every * once the `responses` list is exhausted: answer every request
* request after it as the last one was, or never answer again * after it as the last one was, or never answer again (i.e. camera
* (i.e. camera going quiet). * going quiet).
* *
* Answered from within the page rather than by a server, because a request the * Answered from within the page rather than by a server, because a request the
* page is still waiting on holds one of the handful of connections a browser * page is still waiting on holds one of the handful of connections a browser
+2 -2
View File
@@ -1373,8 +1373,8 @@ describe('answer', () => {
expect(getRingtone().stop).toHaveBeenCalled(); expect(getRingtone().stop).toHaveBeenCalled();
// Timer was armed and should now be cancelled: advancing past the // Timer was armed and should now be canceled: advancing past the timeout
// timeout must not end the (now-answered) call. // must not end the (now-answered) call.
vi.advanceTimersByTime(60_000); vi.advanceTimersByTime(60_000);
expect(manager.isActive()).toBe(true); expect(manager.isActive()).toBe(true);
} finally { } finally {
@@ -12,8 +12,8 @@ interface AudioMocks {
} }
// Uses real jsdom HTMLAudioElement instances and only stubs the parts jsdom // Uses real jsdom HTMLAudioElement instances and only stubs the parts jsdom
// can't fulfil (`play()` / `pause()` -- no audio backend). Tests then exercise // can't fulfill (`play()` / `pause()` -- no audio backend). Tests then exercise
// observable behaviour: registered listeners fire via `dispatchEvent`, property // observable behavior: registered listeners fire via `dispatchEvent`, property
// writes round-trip on the element, etc. // writes round-trip on the element, etc.
// //
// Called once at module load. The `beforeEach`/`afterEach` calls inside this // Called once at module load. The `beforeEach`/`afterEach` calls inside this
@@ -538,7 +538,8 @@ describe('CameraTriggersManager', () => {
vi.runOnlyPendingTimers(); vi.runOnlyPendingTimers();
await flushPromises(); await flushPromises();
// Should still be triggered because the second 'new' event should have cancelled the first timer. // Should still be triggered because the second 'new' event should have
// canceled the first timer.
expect(manager.isTriggered()).toBeTruthy(); expect(manager.isTriggered()).toBeTruthy();
expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled(); expect(api.getViewManager().setViewDefaultWithNewQuery).not.toHaveBeenCalled();
}); });
@@ -245,7 +245,7 @@ describe('SessionManager', () => {
}); });
}); });
// The user-facing behaviour the machine exists for, driven through the real // The user-facing behavior the machine exists for, driven through the real
// schema, trigger factory and evaluator rather than hand-written state. // schema, trigger factory and evaluator rather than hand-written state.
describe('should drive the initialized trigger', () => { describe('should drive the initialized trigger', () => {
it('should fire once per session and not when a session ends', () => { it('should fire once per session and not when a session ends', () => {
@@ -873,7 +873,7 @@ describe('IssueManager', () => {
// The attempt is now in flight: the problem is still unresolved // The attempt is now in flight: the problem is still unresolved
// (needsRetry) but cannot be retried right now (canRetryNow). The running // (needsRetry) but cannot be retried right now (canRetryNow). The running
// timer is cancelled and no further attempt fires, however long we wait. // timer is canceled and no further attempt fires, however long we wait.
canRetryNow.mockReturnValue(false); canRetryNow.mockReturnValue(false);
manager.evaluate(); manager.evaluate();
vi.advanceTimersByTime(RETRY_EXPONENTIAL_MAX_SECONDS * 1000); vi.advanceTimersByTime(RETRY_EXPONENTIAL_MAX_SECONDS * 1000);
@@ -488,7 +488,7 @@ describe('MediaUnavailableIssue', () => {
await card.events.waitForFirst('advanced-camera-card:issue:trigger'); await card.events.waitForFirst('advanced-camera-card:issue:trigger');
// The status bar only summarises. Everything a user can do about the // The status bar only summarizes. Everything a user can do about the
// failure is behind it, which is the point of the issue report being // failure is behind it, which is the point of the issue report being
// clickable. // clickable.
await card.clickControl(MEDIA_ISSUE_TITLE); await card.clickControl(MEDIA_ISSUE_TITLE);
@@ -7,9 +7,9 @@ import {
createStillImageCardConfig, createStillImageCardConfig,
} from '../browser/test-utils'; } from '../browser/test-utils';
// A colour the dark theme sets and the card carries no other way. Reading it // A color the dark theme sets and the card carries no other way. Reading it
// back proves the stylesheet reached the card rather than merely compiling. // back proves the stylesheet reached the card rather than merely compiling. See
// See src/scss/themes/dark.scss . // src/scss/themes/dark.scss .
const DARK_PRIMARY_BACKGROUND = '#111111'; const DARK_PRIMARY_BACKGROUND = '#111111';
const mountThemed = async (themes: ThemeName[]) => const mountThemed = async (themes: ThemeName[]) =>
@@ -118,7 +118,7 @@ describe('computeFormLabel', () => {
}); });
it('should return an empty label for a grid', () => { it('should return an empty label for a grid', () => {
// The fields a grid lays out are labelled individually; the grid itself // The fields a grid lays out are labeled individually; the grid itself
// shows nothing. // shows nothing.
expect(computeFormLabel(createForm([]), { type: 'grid', schema: [] })).toBe(''); expect(computeFormLabel(createForm([]), { type: 'grid', schema: [] })).toBe('');
}); });
@@ -379,7 +379,7 @@ describe('NumericStateTrigger', () => {
setHass(stateManager, { [SENSOR]: { state: '25' } }); setHass(stateManager, { [SENSOR]: { state: '25' } });
vi.advanceTimersByTime(3000); vi.advanceTimersByTime(3000);
// Leaves the range before the hold elapses: cancelled. // Leaves the range before the hold elapses: canceled.
setHass(stateManager, { [SENSOR]: { state: '10' } }); setHass(stateManager, { [SENSOR]: { state: '10' } });
vi.advanceTimersByTime(5000); vi.advanceTimersByTime(5000);
expect(callback).not.toHaveBeenCalled(); expect(callback).not.toHaveBeenCalled();
@@ -14,7 +14,7 @@ describe('preprocessToArray', () => {
}); });
}); });
it('should normalise a single item to a list', () => { it('should normalize a single item to a list', () => {
expect(schema.parse({ items: { name: 'office' } })).toEqual({ expect(schema.parse({ items: { name: 'office' } })).toEqual({
items: [{ name: 'office' }], items: [{ name: 'office' }],
}); });
+3 -3
View File
@@ -1389,7 +1389,7 @@ describe('conditions should accept Home Assistant composite shorthand', () => {
}); });
}); });
it('should normalise a single shorthand condition to a list', () => { it('should normalize a single shorthand condition to a list', () => {
expect( expect(
conditionSchema.parse({ or: { condition: 'fullscreen', fullscreen: true } }), conditionSchema.parse({ or: { condition: 'fullscreen', fullscreen: true } }),
).toMatchObject({ ).toMatchObject({
@@ -1760,7 +1760,7 @@ describe('should lazy evaluate schemas', () => {
).toBeFalsy(); ).toBeFalsy();
}); });
it('should normalise single if/then/else items to lists', () => { it('should normalize single if/then/else items to lists', () => {
const result = actionConfigSchema.parse({ const result = actionConfigSchema.parse({
if: { condition: 'state', entity_id: 'light.office', state: 'on' }, if: { condition: 'state', entity_id: 'light.office', state: 'on' },
then: { then: {
@@ -1912,7 +1912,7 @@ describe('automations should require actions', () => {
}); });
describe('automations should accept Home Assistant input shorthands', () => { describe('automations should accept Home Assistant input shorthands', () => {
it('should normalise singular keys and single items to lists', () => { it('should normalize singular keys and single items to lists', () => {
const result = automationsSchema.parse([ const result = automationsSchema.parse([
{ {
trigger: { trigger: 'state', entity_id: 'binary_sensor.door', to: 'on' }, trigger: { trigger: 'state', entity_id: 'binary_sensor.door', to: 'on' },
+1 -1
View File
@@ -227,7 +227,7 @@ describe('the built card', () => {
// only through the accessors `@property` installs. Were the decorator // only through the accessors `@property` installs. Were the decorator
// output wrong, writes would land on plain instance fields and none of this // output wrong, writes would land on plain instance fields and none of this
// would work. This verifies the build's treatment of decorators rather than // would work. This verifies the build's treatment of decorators rather than
// the card's behaviour per se (already well covered in other tests). // the card's behavior per se (already well covered in other tests).
await mounted.events.waitForFirst('advanced-camera-card:media:loaded'); await mounted.events.waitForFirst('advanced-camera-card:media:loaded');
expect(isLiveMediaShowing(mounted.card)).toBe(true); expect(isLiveMediaShowing(mounted.card)).toBe(true);
+7 -6
View File
@@ -46,12 +46,13 @@ export default defineConfig(({ mode }) => {
}, },
resolve: { resolve: {
// Same as Vite's default, minus the trailing `jsnext:main` and `jsnext`: an // Same as Vite's default, minus the trailing `jsnext:main` and `jsnext`:
// early convention for naming an ES module build that `module` replaced. // an early convention for naming an ES module build that `module`
// `moment` is the one dependency still shipping it, and honouring it hands // replaced. `moment` is the one dependency still shipping it, and
// out moment as an ES module, which `vis-timeline`'s CommonJS build cannot // honoring it hands out moment as an ES module, which `vis-timeline`'s
// then use -- causing timeline views to throw an error. Requiring an ES // CommonJS build cannot then use -- causing timeline views to throw an
// module yields the namespace object rather than moment itself. // error. Requiring an ES module yields the namespace object rather than
// moment itself.
mainFields: ['browser', 'module'], mainFields: ['browser', 'module'],
}, },
+1 -1
View File
@@ -24,7 +24,7 @@ export default defineConfig({
resolve: { resolve: {
// Several dependencies declare their own Lit. Two copies in one page do not // Several dependencies declare their own Lit. Two copies in one page do not
// recognise each other's directives and template results, which surfaces as // recognize each other's directives and template results, which surfaces as
// "Multiple versions of Lit loaded" followed by render-time type errors. // "Multiple versions of Lit loaded" followed by render-time type errors.
// The build gets one copy from its own resolver; the tests have to be told. // The build gets one copy from its own resolver; the tests have to be told.
dedupe: ['lit', 'lit-html', 'lit-element', '@lit/reactive-element'], dedupe: ['lit', 'lit-html', 'lit-element', '@lit/reactive-element'],