diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 46bd4b5d881eb7..629765cef2247a 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -30,6 +30,7 @@ entity, entity_registry, issue_registry, + label_registry, recorder, template, ) @@ -245,6 +246,7 @@ def _cache_uname_processor() -> None: device_registry.async_load(hass), entity_registry.async_load(hass), issue_registry.async_load(hass), + label_registry.async_load(hass), hass.async_add_executor_job(_cache_uname_processor), template.async_load_custom_templates(hass), ) diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py index def7edd4950cf7..11caa81a9e6686 100644 --- a/homeassistant/components/config/__init__.py +++ b/homeassistant/components/config/__init__.py @@ -26,6 +26,7 @@ "core", "device_registry", "entity_registry", + "label_registry", "script", "scene", ) diff --git a/homeassistant/components/config/label_registry.py b/homeassistant/components/config/label_registry.py new file mode 100644 index 00000000000000..c453f1c26e1a77 --- /dev/null +++ b/homeassistant/components/config/label_registry.py @@ -0,0 +1,128 @@ +"""Websocket API to interact with the label registry.""" +from typing import Any + +import voluptuous as vol + +from homeassistant.components import websocket_api +from homeassistant.components.websocket_api.connection import ActiveConnection +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.label_registry import LabelEntry, async_get + + +async def async_setup(hass: HomeAssistant) -> bool: + """Register the Label Registry WS commands.""" + websocket_api.async_register_command(hass, websocket_list_labels) + websocket_api.async_register_command(hass, websocket_create_label) + websocket_api.async_register_command(hass, websocket_delete_label) + websocket_api.async_register_command(hass, websocket_update_label) + return True + + +@websocket_api.websocket_command( + { + vol.Required("type"): "config/label_registry/list", + } +) +@callback +def websocket_list_labels( + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] +) -> None: + """Handle list labels command.""" + registry = async_get(hass) + connection.send_result( + msg["id"], + [_entry_dict(entry) for entry in registry.async_list_labels()], + ) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "config/label_registry/create", + vol.Required("name"): str, + vol.Optional("color"): vol.Any(str, None), + vol.Optional("description"): vol.Any(str, None), + vol.Optional("icon"): vol.Any(str, None), + } +) +@websocket_api.require_admin +@callback +def websocket_create_label( + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] +) -> None: + """Create label command.""" + registry = async_get(hass) + + data = dict(msg) + data.pop("type") + data.pop("id") + + try: + entry = registry.async_create(**data) + except ValueError as err: + connection.send_error(msg["id"], "invalid_info", str(err)) + else: + connection.send_result(msg["id"], _entry_dict(entry)) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "config/label_registry/delete", + vol.Required("label_id"): str, + } +) +@websocket_api.require_admin +@callback +def websocket_delete_label( + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] +) -> None: + """Delete label command.""" + registry = async_get(hass) + + try: + registry.async_delete(msg["label_id"]) + except KeyError: + connection.send_error(msg["id"], "invalid_info", "Label ID doesn't exist") + else: + connection.send_message(websocket_api.result_message(msg["id"], "success")) + + +@websocket_api.websocket_command( + { + vol.Required("type"): "config/label_registry/update", + vol.Required("label_id"): str, + vol.Optional("color"): vol.Any(str, None), + vol.Optional("description"): vol.Any(str, None), + vol.Optional("icon"): vol.Any(str, None), + vol.Optional("name"): str, + } +) +@websocket_api.require_admin +@callback +def websocket_update_label( + hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] +) -> None: + """Handle update label websocket command.""" + registry = async_get(hass) + + data = dict(msg) + data.pop("type") + data.pop("id") + + try: + entry = registry.async_update(**data) + except ValueError as err: + connection.send_error(msg["id"], "invalid_info", str(err)) + else: + connection.send_result(msg["id"], _entry_dict(entry)) + + +@callback +def _entry_dict(entry: LabelEntry) -> dict[str, Any]: + """Convert entry to API format.""" + return { + "color": entry.color, + "description": entry.description, + "icon": entry.icon, + "label_id": entry.label_id, + "name": entry.name, + } diff --git a/homeassistant/const.py b/homeassistant/const.py index 46fc8b57568b72..42ad8ae5777787 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -387,6 +387,10 @@ class Platform(StrEnum): # Contains one string, the device ID ATTR_DEVICE_ID: Final = "device_id" +# Label identifier. Also used as service calls target parameter in which case +# it contains one string or a list of strings, each being an label id. +ATTR_LABEL_ID: Final = "label_id" + # String with a friendly name for the entity ATTR_FRIENDLY_NAME: Final = "friendly_name" diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index e0924b928ca505..b4ecba705d5a00 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -29,6 +29,7 @@ ATTR_AREA_ID, ATTR_DEVICE_ID, ATTR_ENTITY_ID, + ATTR_LABEL_ID, CONF_ABOVE, CONF_ALIAS, CONF_ATTRIBUTE, @@ -1068,6 +1069,9 @@ def expand_condition_shorthand(value: Any | None) -> Any: vol.Optional(ATTR_AREA_ID): vol.Any( ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) ), + vol.Optional(ATTR_LABEL_ID): vol.Any( + ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) + ), } TARGET_SERVICE_FIELDS = { @@ -1085,6 +1089,9 @@ def expand_condition_shorthand(value: Any | None) -> Any: vol.Optional(ATTR_AREA_ID): vol.Any( ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) ), + vol.Optional(ATTR_LABEL_ID): vol.Any( + ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) + ), } diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py index 29e64639722ec8..805e3c299eacce 100644 --- a/homeassistant/helpers/device_registry.py +++ b/homeassistant/helpers/device_registry.py @@ -33,7 +33,7 @@ EVENT_DEVICE_REGISTRY_UPDATED = "device_registry_updated" STORAGE_KEY = "core.device_registry" STORAGE_VERSION_MAJOR = 1 -STORAGE_VERSION_MINOR = 3 +STORAGE_VERSION_MINOR = 4 SAVE_DELAY = 10 CLEANUP_DELAY = 10 @@ -80,6 +80,7 @@ class DeviceEntry: hw_version: str | None = attr.ib(default=None) id: str = attr.ib(factory=uuid_util.random_uuid_hex) identifiers: set[tuple[str, str]] = attr.ib(converter=set, factory=set) + labels: set[str] = attr.ib(converter=set, factory=set) manufacturer: str | None = attr.ib(default=None) model: str | None = attr.ib(default=None) name_by_user: str | None = attr.ib(default=None) @@ -219,6 +220,10 @@ async def _async_migrate_func( # Version 1.3 adds hw_version for device in old_data["devices"]: device["hw_version"] = None + if old_minor_version < 4: + # Introduced in 2022.10 + for device in old_data["devices"]: + device["labels"] = device.get("labels", []) if old_major_version > 1: raise NotImplementedError @@ -432,6 +437,7 @@ def async_update_device( disabled_by: DeviceEntryDisabler | None | UndefinedType = UNDEFINED, entry_type: DeviceEntryType | None | UndefinedType = UNDEFINED, hw_version: str | None | UndefinedType = UNDEFINED, + labels: set[str] | UndefinedType = UNDEFINED, manufacturer: str | None | UndefinedType = UNDEFINED, merge_connections: set[tuple[str, str]] | UndefinedType = UNDEFINED, merge_identifiers: set[tuple[str, str]] | UndefinedType = UNDEFINED, @@ -522,10 +528,11 @@ def async_update_device( ("disabled_by", disabled_by), ("entry_type", entry_type), ("hw_version", hw_version), + ("labels", labels), ("manufacturer", manufacturer), ("model", model), - ("name", name), ("name_by_user", name_by_user), + ("name", name), ("suggested_area", suggested_area), ("sw_version", sw_version), ("via_device_id", via_device_id), @@ -615,6 +622,7 @@ async def async_load(self) -> None: tuple(iden) # type: ignore[misc] for iden in device["identifiers"] }, + labels=set(device["labels"]), manufacturer=device["manufacturer"], model=device["model"], name_by_user=device["name_by_user"], @@ -663,6 +671,7 @@ def _data_to_save(self) -> dict[str, list[dict[str, Any]]]: "hw_version": entry.hw_version, "id": entry.id, "identifiers": list(entry.identifiers), + "labels": list(entry.labels), "manufacturer": entry.manufacturer, "model": entry.model, "name_by_user": entry.name_by_user, @@ -734,6 +743,15 @@ def async_clear_area_id(self, area_id: str) -> None: if area_id == device.area_id: self.async_update_device(dev_id, area_id=None) + @callback + def async_clear_label_id(self, label_id: str) -> None: + """Clear label from registry entries.""" + for device_id, entry in self.devices.items(): + if label_id in entry.labels: + labels = entry.labels.copy() + labels.remove(label_id) + self.async_update_device(device_id, labels=labels) + @callback def async_get(hass: HomeAssistant) -> DeviceRegistry: @@ -754,6 +772,14 @@ def async_entries_for_area(registry: DeviceRegistry, area_id: str) -> list[Devic return [device for device in registry.devices.values() if device.area_id == area_id] +@callback +def async_entries_for_label( + registry: DeviceRegistry, label_id: str +) -> list[DeviceEntry]: + """Return entries that match an label.""" + return [device for device in registry.devices.values() if label_id in device.labels] + + @callback def async_entries_for_config_entry( registry: DeviceRegistry, config_entry_id: str diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index d8c5a6c1cf6724..a7b9f34c763e25 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -61,7 +61,7 @@ _LOGGER = logging.getLogger(__name__) STORAGE_VERSION_MAJOR = 1 -STORAGE_VERSION_MINOR = 10 +STORAGE_VERSION_MINOR = 11 STORAGE_KEY = "core.entity_registry" ENTITY_CATEGORY_VALUE_TO_INDEX: dict[EntityCategory | None, int] = { @@ -105,6 +105,7 @@ class RegistryEntryHider(StrEnum): DISLAY_DICT_OPTIONAL = ( ("ai", "area_id"), + ("lb", "labels"), ("di", "device_id"), ("tk", "translation_key"), ) @@ -153,6 +154,7 @@ class RegistryEntry: icon: str | None = attr.ib(default=None) id: str = attr.ib(factory=uuid_util.random_uuid_hex) has_entity_name: bool = attr.ib(default=False) + labels: set[str] = attr.ib(factory=set) name: str | None = attr.ib(default=None) options: _EntityOptions = attr.ib(default=None, converter=_EntityOptions) # As set by integration @@ -256,6 +258,7 @@ def as_partial_dict(self) -> dict[str, Any]: "platform": self.platform, "translation_key": self.translation_key, "unique_id": self.unique_id, + "labels": self.labels, } @property @@ -383,6 +386,11 @@ async def _async_migrate_func( for entity in data["entities"]: entity["aliases"] = [] + if old_major_version == 1 and old_minor_version < 11: + # Version 1.11 adds labels + for entity in data["entities"]: + entity["labels"] = [] + if old_major_version > 1: raise NotImplementedError return data @@ -741,9 +749,10 @@ def _async_update_entity( device_id: str | None | UndefinedType = UNDEFINED, disabled_by: RegistryEntryDisabler | None | UndefinedType = UNDEFINED, entity_category: EntityCategory | None | UndefinedType = UNDEFINED, + has_entity_name: bool | UndefinedType = UNDEFINED, hidden_by: RegistryEntryHider | None | UndefinedType = UNDEFINED, icon: str | None | UndefinedType = UNDEFINED, - has_entity_name: bool | UndefinedType = UNDEFINED, + labels: set[str] | UndefinedType = UNDEFINED, name: str | None | UndefinedType = UNDEFINED, new_entity_id: str | UndefinedType = UNDEFINED, new_unique_id: str | UndefinedType = UNDEFINED, @@ -791,9 +800,10 @@ def _async_update_entity( ("device_id", device_id), ("disabled_by", disabled_by), ("entity_category", entity_category), + ("has_entity_name", has_entity_name), ("hidden_by", hidden_by), ("icon", icon), - ("has_entity_name", has_entity_name), + ("labels", labels), ("name", name), ("options", options), ("original_device_class", original_device_class), @@ -867,9 +877,10 @@ def async_update_entity( device_id: str | None | UndefinedType = UNDEFINED, disabled_by: RegistryEntryDisabler | None | UndefinedType = UNDEFINED, entity_category: EntityCategory | None | UndefinedType = UNDEFINED, + has_entity_name: bool | UndefinedType = UNDEFINED, hidden_by: RegistryEntryHider | None | UndefinedType = UNDEFINED, icon: str | None | UndefinedType = UNDEFINED, - has_entity_name: bool | UndefinedType = UNDEFINED, + labels: set[str] | UndefinedType = UNDEFINED, name: str | None | UndefinedType = UNDEFINED, new_entity_id: str | UndefinedType = UNDEFINED, new_unique_id: str | UndefinedType = UNDEFINED, @@ -891,9 +902,10 @@ def async_update_entity( device_id=device_id, disabled_by=disabled_by, entity_category=entity_category, + has_entity_name=has_entity_name, hidden_by=hidden_by, icon=icon, - has_entity_name=has_entity_name, + labels=labels, name=name, new_entity_id=new_entity_id, new_unique_id=new_unique_id, @@ -983,12 +995,13 @@ async def async_load(self) -> None: if entity["entity_category"] else None, entity_id=entity["entity_id"], + has_entity_name=entity["has_entity_name"], hidden_by=RegistryEntryHider(entity["hidden_by"]) if entity["hidden_by"] else None, icon=entity["icon"], id=entity["id"], - has_entity_name=entity["has_entity_name"], + labels=set(entity["labels"]), name=entity["name"], options=entity["options"], original_device_class=entity["original_device_class"], @@ -1024,10 +1037,11 @@ def _data_to_save(self) -> dict[str, Any]: "disabled_by": entry.disabled_by, "entity_category": entry.entity_category, "entity_id": entry.entity_id, + "has_entity_name": entry.has_entity_name, "hidden_by": entry.hidden_by, "icon": entry.icon, "id": entry.id, - "has_entity_name": entry.has_entity_name, + "labels": list(entry.labels), "name": entry.name, "options": entry.options.as_dict(), "original_device_class": entry.original_device_class, @@ -1061,6 +1075,15 @@ def async_clear_area_id(self, area_id: str) -> None: if area_id == entry.area_id: self.async_update_entity(entity_id, area_id=None) + @callback + def async_clear_label_id(self, label_id: str) -> None: + """Clear label from registry entries.""" + for entity_id, entry in self.entities.items(): + if label_id in entry.labels: + labels = entry.labels.copy() + labels.remove(label_id) + self.async_update_entity(entity_id, labels=labels) + @callback def async_get(hass: HomeAssistant) -> EntityRegistry: @@ -1096,6 +1119,14 @@ def async_entries_for_area( return [entry for entry in registry.entities.values() if entry.area_id == area_id] +@callback +def async_entries_for_label( + registry: EntityRegistry, label_id: str +) -> list[RegistryEntry]: + """Return entries that match an label.""" + return [entry for entry in registry.entities.values() if label_id in entry.labels] + + @callback def async_entries_for_config_entry( registry: EntityRegistry, config_entry_id: str diff --git a/homeassistant/helpers/label_registry.py b/homeassistant/helpers/label_registry.py new file mode 100644 index 00000000000000..952479e9b940ab --- /dev/null +++ b/homeassistant/helpers/label_registry.py @@ -0,0 +1,242 @@ +"""Provide a way to label and categorize anything.""" +from __future__ import annotations + +from collections.abc import Iterable, MutableMapping +import dataclasses +from dataclasses import dataclass +from typing import cast + +from homeassistant.core import HomeAssistant, callback +from homeassistant.util import slugify + +from . import device_registry as dr, entity_registry as er +from .typing import UNDEFINED, UndefinedType + +DATA_REGISTRY = "label_registry" +EVENT_LABEL_REGISTRY_UPDATED = "label_registry_updated" +STORAGE_KEY = "core.label_registry" +STORAGE_VERSION_MAJOR = 1 +SAVE_DELAY = 10 + + +@dataclass(slots=True, frozen=True) +class LabelEntry: + """Label Registry Entry.""" + + label_id: str + name: str + normalized_name: str + description: str | None = None + color: str | None = None + icon: str | None = None + + +class LabelRegistry: + """Class to hold a registry of labels.""" + + def __init__(self, hass: HomeAssistant) -> None: + """Initialize the label registry.""" + self.hass = hass + self.labels: MutableMapping[str, LabelEntry] = {} + self._store = hass.helpers.storage.Store( + STORAGE_VERSION_MAJOR, + STORAGE_KEY, + atomic_writes=True, + ) + self._normalized_name_label_idx: dict[str, str] = {} + self.children: dict[str, set[str]] = {} + + @callback + def async_get_label(self, label_id: str) -> LabelEntry | None: + """Get label by id.""" + return self.labels.get(label_id) + + @callback + def async_get_label_by_name(self, name: str) -> LabelEntry | None: + """Get label by name.""" + normalized_name = normalize_label_name(name) + if normalized_name not in self._normalized_name_label_idx: + return None + return self.labels[self._normalized_name_label_idx[normalized_name]] + + @callback + def async_list_labels(self) -> Iterable[LabelEntry]: + """Get all labels.""" + return self.labels.values() + + @callback + def async_get_or_create(self, name: str) -> LabelEntry: + """Get or create an label.""" + if label := self.async_get_label_by_name(name): + return label + return self.async_create(name) + + @callback + def _generate_id(self, name: str) -> str: + """Initialize ID.""" + suggestion = suggestion_base = slugify(name) + tries = 1 + while suggestion in self.labels: + tries += 1 + suggestion = f"{suggestion_base}_{tries}" + return suggestion + + @callback + def async_create( + self, + name: str, + *, + color: str | None = None, + icon: str | None = None, + description: str | None = None, + ) -> LabelEntry: + """Create a new label.""" + normalized_name = normalize_label_name(name) + + if self.async_get_label_by_name(name): + raise ValueError(f"The name {name} ({normalized_name}) is already in use") + + label = LabelEntry( + color=color, + description=description, + icon=icon, + label_id=self._generate_id(name), + name=name, + normalized_name=normalized_name, + ) + self.labels[label.label_id] = label + self._normalized_name_label_idx[normalized_name] = label.label_id + self.async_schedule_save() + self.hass.bus.async_fire( + EVENT_LABEL_REGISTRY_UPDATED, + {"action": "create", "label_id": label.label_id}, + ) + return label + + @callback + def async_delete(self, label_id: str) -> None: + """Delete label.""" + label = self.labels[label_id] + + # Clean up all references + dr.async_get(self.hass).async_clear_label_id(label_id) + er.async_get(self.hass).async_clear_label_id(label_id) + + del self.labels[label_id] + del self._normalized_name_label_idx[label.normalized_name] + + self.hass.bus.async_fire( + EVENT_LABEL_REGISTRY_UPDATED, {"action": "remove", "label_id": label_id} + ) + + self.async_schedule_save() + + @callback + def async_update( + self, + label_id: str, + color: str | None | UndefinedType = UNDEFINED, + description: str | None | UndefinedType = UNDEFINED, + icon: str | None | UndefinedType = UNDEFINED, + name: str | UndefinedType = UNDEFINED, + ) -> LabelEntry: + """Update name of label.""" + old = self.labels[label_id] + changes = { + attr_name: value + for attr_name, value in ( + ("color", color), + ("description", description), + ("icon", icon), + ) + if value is not UNDEFINED and getattr(old, attr_name) != value + } + + normalized_name = None + + if name is not UNDEFINED and name != old.name: + normalized_name = normalize_label_name(name) + if normalized_name != old.normalized_name and self.async_get_label_by_name( + name + ): + raise ValueError( + f"The name {name} ({normalized_name}) is already in use" + ) + + changes["name"] = name + changes["normalized_name"] = normalized_name + + if not changes: + return old + + new = self.labels[label_id] = dataclasses.replace(old, **changes) + if normalized_name is not None: + self._normalized_name_label_idx[ + normalized_name + ] = self._normalized_name_label_idx.pop(old.normalized_name) + + self.async_schedule_save() + self.hass.bus.async_fire( + EVENT_LABEL_REGISTRY_UPDATED, {"action": "update", "label_id": label_id} + ) + + return new + + async def async_load(self) -> None: + """Load the label registry.""" + data = await self._store.async_load() + labels: MutableMapping[str, LabelEntry] = {} + + if data is not None: + for label in data["labels"]: + normalized_name = normalize_label_name(label["name"]) + labels[label["label_id"]] = LabelEntry( + color=label["color"], + description=label["description"], + icon=label["icon"], + label_id=label["label_id"], + name=label["name"], + normalized_name=normalized_name, + ) + self._normalized_name_label_idx[normalized_name] = label["label_id"] + + self.labels = labels + + @callback + def async_schedule_save(self) -> None: + """Schedule saving the label registry.""" + self._store.async_delay_save(self._data_to_save, SAVE_DELAY) + + @callback + def _data_to_save(self) -> dict[str, list[dict[str, str | None]]]: + """Return data of label registry to store in a file.""" + return { + "labels": [ + { + "color": entry.color, + "description": entry.description, + "icon": entry.icon, + "label_id": entry.label_id, + "name": entry.name, + } + for entry in self.labels.values() + ] + } + + +@callback +def async_get(hass: HomeAssistant) -> LabelRegistry: + """Get label registry.""" + return cast(LabelRegistry, hass.data[DATA_REGISTRY]) + + +async def async_load(hass: HomeAssistant) -> None: + """Load label registry.""" + assert DATA_REGISTRY not in hass.data + hass.data[DATA_REGISTRY] = LabelRegistry(hass) + await hass.data[DATA_REGISTRY].async_load() + + +def normalize_label_name(label_name: str) -> str: + """Normalize an label name by removing whitespace and case folding.""" + return label_name.casefold().replace(" ", "") diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index a9d7b906e73303..cf7eedae096d14 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -17,6 +17,7 @@ ATTR_AREA_ID, ATTR_DEVICE_ID, ATTR_ENTITY_ID, + ATTR_LABEL_ID, CONF_ENTITY_ID, CONF_SERVICE, CONF_SERVICE_DATA, @@ -42,6 +43,7 @@ config_validation as cv, device_registry, entity_registry, + label_registry, template, ) from .selector import TargetSelector @@ -185,6 +187,7 @@ def __init__(self, service_call: ServiceCall) -> None: entity_ids: str | list | None = service_call.data.get(ATTR_ENTITY_ID) device_ids: str | list | None = service_call.data.get(ATTR_DEVICE_ID) area_ids: str | list | None = service_call.data.get(ATTR_AREA_ID) + label_ids: str | list | None = service_call.data.get(ATTR_LABEL_ID) self.entity_ids = ( set(cv.ensure_list(entity_ids)) if _has_match(entity_ids) else set() @@ -193,11 +196,16 @@ def __init__(self, service_call: ServiceCall) -> None: set(cv.ensure_list(device_ids)) if _has_match(device_ids) else set() ) self.area_ids = set(cv.ensure_list(area_ids)) if _has_match(area_ids) else set() + self.label_ids = ( + set(cv.ensure_list(label_ids)) if _has_match(label_ids) else set() + ) @property def has_any_selector(self) -> bool: """Determine if any selectors are present.""" - return bool(self.entity_ids or self.device_ids or self.area_ids) + return bool( + self.entity_ids or self.device_ids or self.area_ids or self.label_ids + ) @dataclasses.dataclass(slots=True) @@ -207,16 +215,19 @@ class SelectedEntities: # Entities that were explicitly mentioned. referenced: set[str] = dataclasses.field(default_factory=set) - # Entities that were referenced via device/area ID. + # Entities that were referenced via device/area/label ID. # Should not trigger a warning when they don't exist. indirectly_referenced: set[str] = dataclasses.field(default_factory=set) # Referenced items that could not be found. missing_devices: set[str] = dataclasses.field(default_factory=set) missing_areas: set[str] = dataclasses.field(default_factory=set) + missing_labels: set[str] = dataclasses.field(default_factory=set) # Referenced devices referenced_devices: set[str] = dataclasses.field(default_factory=set) + referenced_devices_by_labels: set[str] = dataclasses.field(default_factory=set) + referenced_devices_by_areas: set[str] = dataclasses.field(default_factory=set) def log_missing(self, missing_entities: set[str]) -> None: """Log about missing items.""" @@ -225,6 +236,7 @@ def log_missing(self, missing_entities: set[str]) -> None: ("areas", self.missing_areas), ("devices", self.missing_devices), ("entities", missing_entities), + ("labels", self.missing_labels), ): if items: parts.append(f"{label} {', '.join(sorted(items))}") @@ -455,12 +467,13 @@ def async_extract_referenced_entity_ids( selected.referenced.update(entity_ids) - if not selector.device_ids and not selector.area_ids: + if not selector.device_ids and not selector.area_ids and not selector.label_ids: return selected ent_reg = entity_registry.async_get(hass) dev_reg = device_registry.async_get(hass) area_reg = area_registry.async_get(hass) + label_reg = label_registry.async_get(hass) for device_id in selector.device_ids: if device_id not in dev_reg.devices: @@ -470,13 +483,29 @@ def async_extract_referenced_entity_ids( if area_id not in area_reg.areas: selected.missing_areas.add(area_id) + for label_id in selector.label_ids: + if label_id not in label_reg.labels: + selected.missing_labels.add(label_id) + # Find devices for targeted areas selected.referenced_devices.update(selector.device_ids) for device_entry in dev_reg.devices.values(): if device_entry.area_id in selector.area_ids: selected.referenced_devices.add(device_entry.id) + selected.referenced_devices_by_areas.add(device_entry.id) + + # Find devices for targeted labels + selected.referenced_devices.update(selector.device_ids) + for device_entry in dev_reg.devices.values(): + if device_entry.labels.intersection(selector.label_ids): + selected.referenced_devices.add(device_entry.id) + selected.referenced_devices_by_labels.add(device_entry.id) - if not selector.area_ids and not selected.referenced_devices: + if ( + not selector.area_ids + and not selector.label_ids + and not selected.referenced_devices + ): return selected for ent_entry in ent_reg.entities.values(): @@ -492,10 +521,14 @@ def async_extract_referenced_entity_ids( # has no explicitly set area or ( not ent_entry.area_id - and ent_entry.device_id in selected.referenced_devices + and ent_entry.device_id in selected.referenced_devices_by_areas ) # The entity's device matches a targeted device or ent_entry.device_id in selector.device_ids + # The entity's label matches a targeted label + or ent_entry.labels.intersection(selector.label_ids) + # The entity's device matches a device referenced by an label + or ent_entry.device_id in selected.referenced_devices_by_labels ): selected.indirectly_referenced.add(ent_entry.entity_id) diff --git a/pyproject.toml b/pyproject.toml index caf904d86d3114..4f88359e9e6899 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -284,6 +284,7 @@ voluptuous = "vol" "homeassistant.helpers.device_registry" = "dr" "homeassistant.helpers.entity_registry" = "er" "homeassistant.helpers.issue_registry" = "ir" +"homeassistant.helpers.label_registry" = "lr" [tool.ruff.flake8-pytest-style] fixture-parentheses = false diff --git a/tests/common.py b/tests/common.py index 632294a50fbc0b..2c91a2a579f5e4 100644 --- a/tests/common.py +++ b/tests/common.py @@ -59,6 +59,7 @@ entity_registry as er, intent, issue_registry as ir, + label_registry as lr, recorder as recorder_helper, restore_state, storage, @@ -257,6 +258,7 @@ def async_create_task(coroutine, name=None): dr.async_load(hass), er.async_load(hass), ir.async_load(hass), + lr.async_load(hass), ) hass.data[bootstrap.DATA_REGISTRIES_LOADED] = None diff --git a/tests/components/config/test_entity_registry.py b/tests/components/config/test_entity_registry.py index 9cad68c9c99246..8c3d11db456332 100644 --- a/tests/components/config/test_entity_registry.py +++ b/tests/components/config/test_entity_registry.py @@ -74,6 +74,7 @@ async def test_list_entities(hass: HomeAssistant, client) -> None: "hidden_by": None, "icon": None, "id": ANY, + "labels": [], "name": "Hello World", "options": {}, "original_name": None, @@ -92,6 +93,7 @@ async def test_list_entities(hass: HomeAssistant, client) -> None: "hidden_by": None, "icon": None, "id": ANY, + "labels": [], "name": None, "options": {}, "original_name": None, @@ -137,6 +139,7 @@ class Unserializable: "hidden_by": None, "icon": None, "id": ANY, + "labels": [], "name": "Hello World", "options": {}, "original_name": None, @@ -231,6 +234,7 @@ async def test_list_entities_for_display( "ec": 1, "ei": "test_domain.test", "en": "Hello World", + "lb": [], "pl": "test_platform", "tk": "translations_galore", }, @@ -239,31 +243,37 @@ async def test_list_entities_for_display( "di": "device123", "ei": "test_domain.nameless", "en": None, + "lb": [], "pl": "test_platform", }, { "ai": "area52", "di": "device123", "ei": "test_domain.renamed", + "lb": [], "pl": "test_platform", }, { "ei": "test_domain.boring", + "lb": [], "pl": "test_platform", }, { "ei": "test_domain.hidden", + "lb": [], "hb": True, "pl": "test_platform", }, { "dp": 0, "ei": "sensor.default_precision", + "lb": [], "pl": "test_platform", }, { "dp": 0, "ei": "sensor.user_precision", + "lb": [], "pl": "test_platform", }, ], @@ -305,6 +315,7 @@ class Unserializable: "di": "device123", "ei": "test_domain.test", "en": "Hello World", + "lb": [], "pl": "test_platform", }, ], @@ -349,6 +360,7 @@ async def test_get_entity(hass: HomeAssistant, client) -> None: "hidden_by": None, "icon": None, "id": ANY, + "labels": [], "name": "Hello World", "options": {}, "original_device_class": None, @@ -382,6 +394,7 @@ async def test_get_entity(hass: HomeAssistant, client) -> None: "hidden_by": None, "icon": None, "id": ANY, + "labels": [], "name": None, "options": {}, "original_device_class": None, @@ -440,6 +453,7 @@ async def test_get_entities(hass: HomeAssistant, client) -> None: "hidden_by": None, "icon": None, "id": ANY, + "labels": [], "name": "Hello World", "options": {}, "original_device_class": None, @@ -463,6 +477,7 @@ async def test_get_entities(hass: HomeAssistant, client) -> None: "hidden_by": None, "icon": None, "id": ANY, + "labels": [], "name": None, "options": {}, "original_device_class": None, @@ -532,6 +547,7 @@ async def test_update_entity(hass: HomeAssistant, client) -> None: "hidden_by": "user", # We exchange strings over the WS API, not enums "icon": "icon:after update", "id": ANY, + "labels": [], "name": "after update", "options": {}, "original_device_class": None, @@ -607,6 +623,7 @@ async def test_update_entity(hass: HomeAssistant, client) -> None: "hidden_by": "user", # We exchange strings over the WS API, not enums "icon": "icon:after update", "id": ANY, + "labels": [], "name": "after update", "options": {}, "original_device_class": None, @@ -647,6 +664,7 @@ async def test_update_entity(hass: HomeAssistant, client) -> None: "hidden_by": "user", # We exchange strings over the WS API, not enums "icon": "icon:after update", "id": ANY, + "labels": [], "name": "after update", "options": {"sensor": {"unit_of_measurement": "beard_second"}}, "original_device_class": None, @@ -699,6 +717,7 @@ async def test_update_entity_require_restart(hass: HomeAssistant, client) -> Non "hidden_by": None, "icon": None, "id": ANY, + "labels": [], "name": None, "options": {}, "original_device_class": None, @@ -809,6 +828,7 @@ async def test_update_entity_no_changes(hass: HomeAssistant, client) -> None: "hidden_by": None, "icon": None, "id": ANY, + "labels": [], "name": "name of entity", "options": {}, "original_device_class": None, @@ -898,6 +918,7 @@ async def test_update_entity_id(hass: HomeAssistant, client) -> None: "hidden_by": None, "icon": None, "id": ANY, + "labels": [], "name": None, "options": {}, "original_device_class": None, diff --git a/tests/components/config/test_label_registry.py b/tests/components/config/test_label_registry.py new file mode 100644 index 00000000000000..d55cb216f37804 --- /dev/null +++ b/tests/components/config/test_label_registry.py @@ -0,0 +1,251 @@ +"""Test label registry API.""" +from collections.abc import Awaitable, Callable, Generator +from typing import Any + +from aiohttp import ClientWebSocketResponse +import pytest + +from homeassistant.components.config import label_registry +from homeassistant.core import HomeAssistant +from homeassistant.helpers import label_registry as lr + + +@pytest.fixture(name="client") +def client_fixture( + hass: HomeAssistant, + hass_ws_client: Callable[ + [HomeAssistant], Awaitable[Generator[ClientWebSocketResponse, Any, Any]] + ], +) -> Generator[ClientWebSocketResponse, None, None]: + """Fixture that can interact with the config manager API.""" + hass.loop.run_until_complete(label_registry.async_setup(hass)) + return hass.loop.run_until_complete(hass_ws_client(hass)) + + +async def test_list_labels( + hass: HomeAssistant, + client: ClientWebSocketResponse, + label_registry: lr.LabelRegistry, +) -> None: + """Test list entries.""" + label_registry.async_create("mock 1") + label_registry.async_create( + name="mock 2", + color="#00FF00", + icon="mdi:two", + description="This is the second label", + ) + + assert len(label_registry.labels) == 2 + + await client.send_json({"id": 1, "type": "config/label_registry/list"}) + + msg = await client.receive_json() + + assert len(msg["result"]) == len(label_registry.labels) + assert msg["result"][0] == { + "color": None, + "description": None, + "icon": None, + "label_id": "mock_1", + "name": "mock 1", + } + assert msg["result"][1] == { + "color": "#00FF00", + "description": "This is the second label", + "icon": "mdi:two", + "label_id": "mock_2", + "name": "mock 2", + } + + +async def test_create_label( + hass: HomeAssistant, + client: ClientWebSocketResponse, + label_registry: lr.LabelRegistry, +) -> None: + """Test create entry.""" + await client.send_json( + { + "id": 1, + "name": "MOCK", + "type": "config/label_registry/create", + } + ) + + msg = await client.receive_json() + + assert len(label_registry.labels) == 1 + assert msg["result"] == { + "color": None, + "description": None, + "icon": None, + "label_id": "mock", + "name": "MOCK", + } + + await client.send_json( + { + "id": 2, + "name": "MOCKERY", + "type": "config/label_registry/create", + "color": "#00FF00", + "description": "This is the second label", + "icon": "mdi:two", + } + ) + + msg = await client.receive_json() + + assert len(label_registry.labels) == 2 + assert msg["result"] == { + "color": "#00FF00", + "description": "This is the second label", + "icon": "mdi:two", + "label_id": "mockery", + "name": "MOCKERY", + } + + +async def test_create_label_with_name_already_in_use( + hass: HomeAssistant, + client: ClientWebSocketResponse, + label_registry: lr.LabelRegistry, +) -> None: + """Test create entry that should fail.""" + label_registry.async_create("mock") + assert len(label_registry.labels) == 1 + + await client.send_json( + {"id": 1, "name": "mock", "type": "config/label_registry/create"} + ) + + msg = await client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "invalid_info" + assert msg["error"]["message"] == "The name mock (mock) is already in use" + assert len(label_registry.labels) == 1 + + +async def test_delete_label( + hass: HomeAssistant, + client: ClientWebSocketResponse, + label_registry: lr.LabelRegistry, +) -> None: + """Test delete entry.""" + label = label_registry.async_create("mock") + assert len(label_registry.labels) == 1 + + await client.send_json( + {"id": 1, "label_id": label.label_id, "type": "config/label_registry/delete"} + ) + + msg = await client.receive_json() + + assert msg["success"] + assert not label_registry.labels + + +async def test_delete_non_existing_label( + hass: HomeAssistant, + client: ClientWebSocketResponse, + label_registry: lr.LabelRegistry, +) -> None: + """Test delete entry that should fail.""" + label_registry.async_create("mock") + assert len(label_registry.labels) == 1 + + await client.send_json( + {"id": 1, "label_id": "omg_puppies", "type": "config/label_registry/delete"} + ) + + msg = await client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "invalid_info" + assert msg["error"]["message"] == "Label ID doesn't exist" + assert len(label_registry.labels) == 1 + + +async def test_update_label( + hass: HomeAssistant, + client: ClientWebSocketResponse, + label_registry: lr.LabelRegistry, +) -> None: + """Test update entry.""" + label = label_registry.async_create("mock") + assert len(label_registry.labels) == 1 + + await client.send_json( + { + "id": 1, + "label_id": label.label_id, + "name": "UPDATED", + "icon": "mdi:test", + "color": "#00FF00", + "description": "This is an label description", + "type": "config/label_registry/update", + } + ) + + msg = await client.receive_json() + + assert len(label_registry.labels) == 1 + assert msg["result"] == { + "color": "#00FF00", + "description": "This is an label description", + "icon": "mdi:test", + "label_id": "mock", + "name": "UPDATED", + } + + await client.send_json( + { + "id": 2, + "label_id": label.label_id, + "name": "UPDATED AGAIN", + "icon": None, + "color": None, + "description": None, + "type": "config/label_registry/update", + } + ) + + msg = await client.receive_json() + + assert len(label_registry.labels) == 1 + assert msg["result"] == { + "color": None, + "description": None, + "icon": None, + "label_id": "mock", + "name": "UPDATED AGAIN", + } + + +async def test_update_with_name_already_in_use( + hass: HomeAssistant, + client: ClientWebSocketResponse, + label_registry: lr.LabelRegistry, +) -> None: + """Test update entry.""" + label = label_registry.async_create("mock 1") + label_registry.async_create("mock 2") + assert len(label_registry.labels) == 2 + + await client.send_json( + { + "id": 1, + "label_id": label.label_id, + "name": "mock 2", + "type": "config/label_registry/update", + } + ) + + msg = await client.receive_json() + + assert not msg["success"] + assert msg["error"]["code"] == "invalid_info" + assert msg["error"]["message"] == "The name mock 2 (mock2) is already in use" + assert len(label_registry.labels) == 2 diff --git a/tests/components/devolo_home_control/snapshots/test_binary_sensor.ambr b/tests/components/devolo_home_control/snapshots/test_binary_sensor.ambr index 0c86cc94321b11..4cde67e0267811 100644 --- a/tests/components/devolo_home_control/snapshots/test_binary_sensor.ambr +++ b/tests/components/devolo_home_control/snapshots/test_binary_sensor.ambr @@ -29,6 +29,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -72,6 +74,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -114,6 +118,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/devolo_home_control/snapshots/test_climate.ambr b/tests/components/devolo_home_control/snapshots/test_climate.ambr index e0066a106561c1..ae33b14661fd04 100644 --- a/tests/components/devolo_home_control/snapshots/test_climate.ambr +++ b/tests/components/devolo_home_control/snapshots/test_climate.ambr @@ -44,6 +44,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/devolo_home_control/snapshots/test_cover.ambr b/tests/components/devolo_home_control/snapshots/test_cover.ambr index b2872d0c912a12..83326ce6ee5df3 100644 --- a/tests/components/devolo_home_control/snapshots/test_cover.ambr +++ b/tests/components/devolo_home_control/snapshots/test_cover.ambr @@ -31,6 +31,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/devolo_home_control/snapshots/test_light.ambr b/tests/components/devolo_home_control/snapshots/test_light.ambr index 81c1e9b4293eb9..67fd725df59e7f 100644 --- a/tests/components/devolo_home_control/snapshots/test_light.ambr +++ b/tests/components/devolo_home_control/snapshots/test_light.ambr @@ -38,6 +38,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -90,6 +92,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/devolo_home_control/snapshots/test_sensor.ambr b/tests/components/devolo_home_control/snapshots/test_sensor.ambr index cb97ce77af07df..753fec9b627257 100644 --- a/tests/components/devolo_home_control/snapshots/test_sensor.ambr +++ b/tests/components/devolo_home_control/snapshots/test_sensor.ambr @@ -33,6 +33,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -80,6 +82,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -127,6 +131,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -174,6 +180,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/devolo_home_control/snapshots/test_siren.ambr b/tests/components/devolo_home_control/snapshots/test_siren.ambr index df1d514a11d161..f673f3aa33a2c2 100644 --- a/tests/components/devolo_home_control/snapshots/test_siren.ambr +++ b/tests/components/devolo_home_control/snapshots/test_siren.ambr @@ -36,6 +36,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -86,6 +88,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -136,6 +140,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/devolo_home_control/snapshots/test_switch.ambr b/tests/components/devolo_home_control/snapshots/test_switch.ambr index 4aa95944be05da..705736a2367256 100644 --- a/tests/components/devolo_home_control/snapshots/test_switch.ambr +++ b/tests/components/devolo_home_control/snapshots/test_switch.ambr @@ -28,6 +28,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/elgato/snapshots/test_button.ambr b/tests/components/elgato/snapshots/test_button.ambr index cb420c486b4718..353a49a72c89fa 100644 --- a/tests/components/elgato/snapshots/test_button.ambr +++ b/tests/components/elgato/snapshots/test_button.ambr @@ -29,6 +29,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -64,6 +66,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Elgato', 'model': 'Elgato Key Light Mini', 'name': 'Frenck', @@ -103,6 +107,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -138,6 +144,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Elgato', 'model': 'Elgato Key Light Mini', 'name': 'Frenck', diff --git a/tests/components/elgato/snapshots/test_light.ambr b/tests/components/elgato/snapshots/test_light.ambr index 31f5dfba217488..8d1a70e21c9dbe 100644 --- a/tests/components/elgato/snapshots/test_light.ambr +++ b/tests/components/elgato/snapshots/test_light.ambr @@ -61,6 +61,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -96,6 +98,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Elgato', 'model': 'Elgato Key Light', 'name': 'Frenck', @@ -169,6 +173,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -204,6 +210,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Elgato', 'model': 'Elgato Key Light', 'name': 'Frenck', @@ -275,6 +283,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -310,6 +320,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Elgato', 'model': 'Elgato Key Light', 'name': 'Frenck', diff --git a/tests/components/elgato/snapshots/test_sensor.ambr b/tests/components/elgato/snapshots/test_sensor.ambr index 35429b8a320cda..13f9b8da35bb0f 100644 --- a/tests/components/elgato/snapshots/test_sensor.ambr +++ b/tests/components/elgato/snapshots/test_sensor.ambr @@ -33,6 +33,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ 'sensor': dict({ @@ -71,6 +73,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Elgato', 'model': 'Elgato Key Light Mini', 'name': 'Frenck', @@ -114,6 +118,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ 'sensor': dict({ @@ -155,6 +161,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Elgato', 'model': 'Elgato Key Light Mini', 'name': 'Frenck', @@ -198,6 +206,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ 'sensor': dict({ @@ -239,6 +249,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Elgato', 'model': 'Elgato Key Light Mini', 'name': 'Frenck', @@ -282,6 +294,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ 'sensor': dict({ @@ -320,6 +334,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Elgato', 'model': 'Elgato Key Light Mini', 'name': 'Frenck', @@ -363,6 +379,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ 'sensor': dict({ @@ -404,6 +422,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Elgato', 'model': 'Elgato Key Light Mini', 'name': 'Frenck', diff --git a/tests/components/elgato/snapshots/test_switch.ambr b/tests/components/elgato/snapshots/test_switch.ambr index dcba00c0a9e009..6327f9ce6f45d3 100644 --- a/tests/components/elgato/snapshots/test_switch.ambr +++ b/tests/components/elgato/snapshots/test_switch.ambr @@ -29,6 +29,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -64,6 +66,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Elgato', 'model': 'Elgato Key Light Mini', 'name': 'Frenck', @@ -103,6 +107,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -138,6 +144,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Elgato', 'model': 'Elgato Key Light Mini', 'name': 'Frenck', diff --git a/tests/components/energyzero/snapshots/test_sensor.ambr b/tests/components/energyzero/snapshots/test_sensor.ambr index f758e8f53ca560..9b1c375a9f3fab 100644 --- a/tests/components/energyzero/snapshots/test_sensor.ambr +++ b/tests/components/energyzero/snapshots/test_sensor.ambr @@ -487,6 +487,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -511,6 +513,8 @@ 'hw_version': None, 'id': , 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'EnergyZero', 'model': None, 'name': 'Energy market price', @@ -554,6 +558,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -578,6 +584,8 @@ 'hw_version': None, 'id': , 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'EnergyZero', 'model': None, 'name': 'Energy market price', @@ -618,6 +626,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -642,6 +652,8 @@ 'hw_version': None, 'id': , 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'EnergyZero', 'model': None, 'name': 'Energy market price', @@ -682,6 +694,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -706,6 +720,8 @@ 'hw_version': None, 'id': , 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'EnergyZero', 'model': None, 'name': 'Energy market price', @@ -749,6 +765,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -773,6 +791,8 @@ 'hw_version': None, 'id': , 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'EnergyZero', 'model': None, 'name': 'Gas market price', diff --git a/tests/components/gree/snapshots/test_climate.ambr b/tests/components/gree/snapshots/test_climate.ambr index f1479cad3d386c..108dc93a407988 100644 --- a/tests/components/gree/snapshots/test_climate.ambr +++ b/tests/components/gree/snapshots/test_climate.ambr @@ -102,6 +102,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/gree/snapshots/test_switch.ambr b/tests/components/gree/snapshots/test_switch.ambr index 73056fcc4650e7..4835ca3dabcb3b 100644 --- a/tests/components/gree/snapshots/test_switch.ambr +++ b/tests/components/gree/snapshots/test_switch.ambr @@ -78,6 +78,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -106,6 +108,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -134,6 +138,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -162,6 +168,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -190,6 +198,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/onewire/snapshots/test_binary_sensor.ambr b/tests/components/onewire/snapshots/test_binary_sensor.ambr index 702196d4574bfa..373eced523baef 100644 --- a/tests/components/onewire/snapshots/test_binary_sensor.ambr +++ b/tests/components/onewire/snapshots/test_binary_sensor.ambr @@ -30,6 +30,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2405', 'name': '05.111111111111', @@ -67,6 +69,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS18S20', 'name': '10.111111111111', @@ -104,6 +108,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2406', 'name': '12.111111111111', @@ -132,6 +138,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -160,6 +168,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -221,6 +231,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2423', 'name': '1D.111111111111', @@ -258,6 +270,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2409', 'name': '1F.111111111111', @@ -283,6 +297,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2423', 'name': '1D.111111111111', @@ -320,6 +336,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS1822', 'name': '22.111111111111', @@ -357,6 +375,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2438', 'name': '26.111111111111', @@ -394,6 +414,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS18B20', 'name': '28.111111111111', @@ -431,6 +453,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS18B20', 'name': '28.222222222222', @@ -468,6 +492,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS18B20', 'name': '28.222222222223', @@ -505,6 +531,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2408', 'name': '29.111111111111', @@ -533,6 +561,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -561,6 +591,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -589,6 +621,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -617,6 +651,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -645,6 +681,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -673,6 +711,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -701,6 +741,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -729,6 +771,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -862,6 +906,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2760', 'name': '30.111111111111', @@ -899,6 +945,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2413', 'name': '3A.111111111111', @@ -927,6 +975,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -955,6 +1005,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1016,6 +1068,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS1825', 'name': '3B.111111111111', @@ -1053,6 +1107,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS28EA00', 'name': '42.111111111111', @@ -1090,6 +1146,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Embedded Data Systems', 'model': 'EDS0068', 'name': '7E.111111111111', @@ -1127,6 +1185,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Embedded Data Systems', 'model': 'EDS0066', 'name': '7E.222222222222', @@ -1164,6 +1224,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Hobby Boards', 'model': 'HobbyBoards_EF', 'name': 'EF.111111111111', @@ -1201,6 +1263,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Hobby Boards', 'model': 'HB_MOISTURE_METER', 'name': 'EF.111111111112', @@ -1238,6 +1302,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Hobby Boards', 'model': 'HB_HUB', 'name': 'EF.111111111113', @@ -1266,6 +1332,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1294,6 +1362,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1322,6 +1392,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1350,6 +1422,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/onewire/snapshots/test_sensor.ambr b/tests/components/onewire/snapshots/test_sensor.ambr index 6c18c1ec652f2d..e225c15b24845e 100644 --- a/tests/components/onewire/snapshots/test_sensor.ambr +++ b/tests/components/onewire/snapshots/test_sensor.ambr @@ -30,6 +30,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2405', 'name': '05.111111111111', @@ -67,6 +69,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS18S20', 'name': '10.111111111111', @@ -97,6 +101,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -149,6 +155,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2406', 'name': '12.111111111111', @@ -179,6 +187,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -209,6 +219,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -276,6 +288,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2423', 'name': '1D.111111111111', @@ -306,6 +320,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -336,6 +352,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -401,6 +419,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2409', 'name': '1F.111111111111', @@ -426,6 +446,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2423', 'name': '1D.111111111111', @@ -456,6 +478,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -486,6 +510,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -551,6 +577,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS1822', 'name': '22.111111111111', @@ -581,6 +609,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -633,6 +663,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2438', 'name': '26.111111111111', @@ -663,6 +695,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -693,6 +727,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -723,6 +759,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -753,6 +791,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -783,6 +823,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -813,6 +855,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -843,6 +887,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -873,6 +919,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -903,6 +951,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -933,6 +983,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -963,6 +1015,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1165,6 +1219,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS18B20', 'name': '28.111111111111', @@ -1195,6 +1251,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1247,6 +1305,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS18B20', 'name': '28.222222222222', @@ -1277,6 +1337,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1329,6 +1391,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS18B20', 'name': '28.222222222223', @@ -1359,6 +1423,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1411,6 +1477,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2408', 'name': '29.111111111111', @@ -1448,6 +1516,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2760', 'name': '30.111111111111', @@ -1478,6 +1548,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1508,6 +1580,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1538,6 +1612,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1568,6 +1644,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1665,6 +1743,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2413', 'name': '3A.111111111111', @@ -1702,6 +1782,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS1825', 'name': '3B.111111111111', @@ -1732,6 +1814,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1784,6 +1868,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS28EA00', 'name': '42.111111111111', @@ -1814,6 +1900,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1866,6 +1954,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Embedded Data Systems', 'model': 'EDS0068', 'name': '7E.111111111111', @@ -1896,6 +1986,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1926,6 +2018,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1956,6 +2050,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1986,6 +2082,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2083,6 +2181,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Embedded Data Systems', 'model': 'EDS0066', 'name': '7E.222222222222', @@ -2113,6 +2213,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2143,6 +2245,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2210,6 +2314,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Hobby Boards', 'model': 'HobbyBoards_EF', 'name': 'EF.111111111111', @@ -2240,6 +2346,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2270,6 +2378,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2300,6 +2410,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2382,6 +2494,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Hobby Boards', 'model': 'HB_MOISTURE_METER', 'name': 'EF.111111111112', @@ -2412,6 +2526,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2442,6 +2558,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2472,6 +2590,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2502,6 +2622,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2599,6 +2721,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Hobby Boards', 'model': 'HB_HUB', 'name': 'EF.111111111113', diff --git a/tests/components/onewire/snapshots/test_switch.ambr b/tests/components/onewire/snapshots/test_switch.ambr index 55ea7be1fa63f2..6e427b4efb9e5d 100644 --- a/tests/components/onewire/snapshots/test_switch.ambr +++ b/tests/components/onewire/snapshots/test_switch.ambr @@ -30,6 +30,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2405', 'name': '05.111111111111', @@ -58,6 +60,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -107,6 +111,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS18S20', 'name': '10.111111111111', @@ -144,6 +150,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2406', 'name': '12.111111111111', @@ -172,6 +180,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -200,6 +210,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -228,6 +240,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -256,6 +270,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -341,6 +357,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2423', 'name': '1D.111111111111', @@ -378,6 +396,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2409', 'name': '1F.111111111111', @@ -403,6 +423,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2423', 'name': '1D.111111111111', @@ -440,6 +462,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS1822', 'name': '22.111111111111', @@ -477,6 +501,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2438', 'name': '26.111111111111', @@ -505,6 +531,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -554,6 +582,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS18B20', 'name': '28.111111111111', @@ -591,6 +621,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS18B20', 'name': '28.222222222222', @@ -628,6 +660,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS18B20', 'name': '28.222222222223', @@ -665,6 +699,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2408', 'name': '29.111111111111', @@ -693,6 +729,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -721,6 +759,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -749,6 +789,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -777,6 +819,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -805,6 +849,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -833,6 +879,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -861,6 +909,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -889,6 +939,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -917,6 +969,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -945,6 +999,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -973,6 +1029,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1001,6 +1059,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1029,6 +1089,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1057,6 +1119,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1085,6 +1149,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1113,6 +1179,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1342,6 +1410,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2760', 'name': '30.111111111111', @@ -1379,6 +1449,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS2413', 'name': '3A.111111111111', @@ -1407,6 +1479,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1435,6 +1509,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1496,6 +1572,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS1825', 'name': '3B.111111111111', @@ -1533,6 +1611,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Maxim Integrated', 'model': 'DS28EA00', 'name': '42.111111111111', @@ -1570,6 +1650,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Embedded Data Systems', 'model': 'EDS0068', 'name': '7E.111111111111', @@ -1607,6 +1689,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Embedded Data Systems', 'model': 'EDS0066', 'name': '7E.222222222222', @@ -1644,6 +1728,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Hobby Boards', 'model': 'HobbyBoards_EF', 'name': 'EF.111111111111', @@ -1681,6 +1767,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Hobby Boards', 'model': 'HB_MOISTURE_METER', 'name': 'EF.111111111112', @@ -1709,6 +1797,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1737,6 +1827,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1765,6 +1857,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1793,6 +1887,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1821,6 +1917,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1849,6 +1947,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1877,6 +1977,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1905,6 +2007,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2038,6 +2142,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Hobby Boards', 'model': 'HB_HUB', 'name': 'EF.111111111113', @@ -2066,6 +2172,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2094,6 +2202,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2122,6 +2232,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2150,6 +2262,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/renault/snapshots/test_binary_sensor.ambr b/tests/components/renault/snapshots/test_binary_sensor.ambr index dc10dd839f0c7c..1647ca9fd56b44 100644 --- a/tests/components/renault/snapshots/test_binary_sensor.ambr +++ b/tests/components/renault/snapshots/test_binary_sensor.ambr @@ -18,6 +18,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -46,6 +48,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -74,6 +78,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -102,6 +108,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -130,6 +138,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -158,6 +168,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -186,6 +198,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -289,6 +303,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -317,6 +333,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -345,6 +363,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -373,6 +393,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -401,6 +423,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -429,6 +453,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -457,6 +483,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -485,6 +513,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -513,6 +543,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -638,6 +670,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -666,6 +700,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -694,6 +730,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -722,6 +760,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -792,6 +832,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -820,6 +862,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -848,6 +892,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -876,6 +922,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -904,6 +952,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -932,6 +982,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -960,6 +1012,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -988,6 +1042,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1016,6 +1072,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1044,6 +1102,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1180,6 +1240,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -1208,6 +1270,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1236,6 +1300,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1264,6 +1330,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1292,6 +1360,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1320,6 +1390,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1348,6 +1420,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1451,6 +1525,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -1479,6 +1555,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1507,6 +1585,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1535,6 +1615,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1563,6 +1645,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1591,6 +1675,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1619,6 +1705,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1647,6 +1735,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1675,6 +1765,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1800,6 +1892,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -1828,6 +1922,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1856,6 +1952,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1884,6 +1982,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1954,6 +2054,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -1982,6 +2084,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2010,6 +2114,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2038,6 +2144,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2066,6 +2174,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2094,6 +2204,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2122,6 +2234,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2150,6 +2264,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2178,6 +2294,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2206,6 +2324,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/renault/snapshots/test_button.ambr b/tests/components/renault/snapshots/test_button.ambr index 8c56a3842ea62d..82ea06a0e19dec 100644 --- a/tests/components/renault/snapshots/test_button.ambr +++ b/tests/components/renault/snapshots/test_button.ambr @@ -18,6 +18,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -46,6 +48,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -94,6 +98,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -122,6 +128,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -150,6 +158,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -178,6 +188,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -248,6 +260,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -276,6 +290,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -304,6 +320,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -332,6 +350,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -402,6 +422,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -430,6 +452,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -458,6 +482,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -486,6 +512,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -556,6 +584,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -584,6 +614,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -632,6 +664,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -660,6 +694,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -688,6 +724,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -716,6 +754,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -786,6 +826,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -814,6 +856,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -842,6 +886,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -870,6 +916,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -940,6 +988,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -968,6 +1018,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -996,6 +1048,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1024,6 +1078,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/renault/snapshots/test_device_tracker.ambr b/tests/components/renault/snapshots/test_device_tracker.ambr index 474791791d992c..49dc158b32ca27 100644 --- a/tests/components/renault/snapshots/test_device_tracker.ambr +++ b/tests/components/renault/snapshots/test_device_tracker.ambr @@ -18,6 +18,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -46,6 +48,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -95,6 +99,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -123,6 +129,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -172,6 +180,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -209,6 +219,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -237,6 +249,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -286,6 +300,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -314,6 +330,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -366,6 +384,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -394,6 +414,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -446,6 +468,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -483,6 +507,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -511,6 +537,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/renault/snapshots/test_select.ambr b/tests/components/renault/snapshots/test_select.ambr index c5bbc6b20024b6..b29b35cbf2e235 100644 --- a/tests/components/renault/snapshots/test_select.ambr +++ b/tests/components/renault/snapshots/test_select.ambr @@ -18,6 +18,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -55,6 +57,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -89,6 +93,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -142,6 +148,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -176,6 +184,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -229,6 +239,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -263,6 +275,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -316,6 +330,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -353,6 +369,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -387,6 +405,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -440,6 +460,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -474,6 +496,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -527,6 +551,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -561,6 +587,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/renault/snapshots/test_sensor.ambr b/tests/components/renault/snapshots/test_sensor.ambr index 72f9201b7a426f..73ed94d8189ac9 100644 --- a/tests/components/renault/snapshots/test_sensor.ambr +++ b/tests/components/renault/snapshots/test_sensor.ambr @@ -18,6 +18,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -48,6 +50,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -78,6 +82,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -108,6 +114,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -136,6 +144,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -164,6 +174,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -192,6 +204,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -302,6 +316,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -332,6 +348,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -371,6 +389,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -401,6 +421,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -431,6 +453,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -466,6 +490,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -496,6 +522,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -526,6 +554,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -556,6 +586,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -584,6 +616,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -614,6 +648,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -644,6 +680,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -674,6 +712,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -702,6 +742,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -730,6 +772,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -758,6 +802,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -998,6 +1044,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -1028,6 +1076,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1067,6 +1117,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1097,6 +1149,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1127,6 +1181,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1162,6 +1218,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1192,6 +1250,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1222,6 +1282,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1252,6 +1314,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1280,6 +1344,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1310,6 +1376,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1340,6 +1408,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1368,6 +1438,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1396,6 +1468,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1424,6 +1498,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1452,6 +1528,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1688,6 +1766,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -1718,6 +1798,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1757,6 +1839,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1787,6 +1871,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1817,6 +1903,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1852,6 +1940,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1882,6 +1972,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1912,6 +2004,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1942,6 +2036,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -1970,6 +2066,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2000,6 +2098,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2030,6 +2130,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2058,6 +2160,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2086,6 +2190,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2114,6 +2220,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2142,6 +2250,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2170,6 +2280,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2417,6 +2529,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -2447,6 +2561,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2477,6 +2593,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2507,6 +2625,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2535,6 +2655,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2563,6 +2685,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2591,6 +2715,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2701,6 +2827,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Captur ii', 'name': 'REG-NUMBER', @@ -2731,6 +2859,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2770,6 +2900,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2800,6 +2932,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2830,6 +2964,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2865,6 +3001,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2895,6 +3033,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2925,6 +3065,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2955,6 +3097,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -2983,6 +3127,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3013,6 +3159,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3043,6 +3191,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3073,6 +3223,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3101,6 +3253,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3129,6 +3283,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3157,6 +3313,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3397,6 +3555,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -3427,6 +3587,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3466,6 +3628,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3496,6 +3660,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3526,6 +3692,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3561,6 +3729,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3591,6 +3761,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3621,6 +3793,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3651,6 +3825,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3679,6 +3855,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3709,6 +3887,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3739,6 +3919,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3767,6 +3949,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3795,6 +3979,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3823,6 +4009,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -3851,6 +4039,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4087,6 +4277,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Renault', 'model': 'Zoe', 'name': 'REG-NUMBER', @@ -4117,6 +4309,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4156,6 +4350,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4186,6 +4382,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4216,6 +4414,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4251,6 +4451,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4281,6 +4483,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4311,6 +4515,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4341,6 +4547,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4369,6 +4577,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4399,6 +4609,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4429,6 +4641,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4457,6 +4671,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4485,6 +4701,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4513,6 +4731,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4541,6 +4761,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -4569,6 +4791,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/sfr_box/snapshots/test_binary_sensor.ambr b/tests/components/sfr_box/snapshots/test_binary_sensor.ambr index b308b5ab3af6fe..f94f64cb939c2c 100644 --- a/tests/components/sfr_box/snapshots/test_binary_sensor.ambr +++ b/tests/components/sfr_box/snapshots/test_binary_sensor.ambr @@ -18,6 +18,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': None, 'model': 'NB6VAC-FXC-r0', 'name': 'SFR Box', @@ -46,6 +48,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -74,6 +78,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -133,6 +139,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': None, 'model': 'NB6VAC-FXC-r0', 'name': 'SFR Box', @@ -161,6 +169,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -189,6 +199,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/sfr_box/snapshots/test_button.ambr b/tests/components/sfr_box/snapshots/test_button.ambr index dc6ccc1f25d1ec..497c3469fe3d9b 100644 --- a/tests/components/sfr_box/snapshots/test_button.ambr +++ b/tests/components/sfr_box/snapshots/test_button.ambr @@ -18,6 +18,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': None, 'model': 'NB6VAC-FXC-r0', 'name': 'SFR Box', @@ -46,6 +48,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/sfr_box/snapshots/test_sensor.ambr b/tests/components/sfr_box/snapshots/test_sensor.ambr index 2390ba625eb3ff..05f3812d2727cb 100644 --- a/tests/components/sfr_box/snapshots/test_sensor.ambr +++ b/tests/components/sfr_box/snapshots/test_sensor.ambr @@ -18,6 +18,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': None, 'model': 'NB6VAC-FXC-r0', 'name': 'SFR Box', @@ -53,6 +55,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -81,6 +85,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -109,6 +115,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -145,6 +153,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -173,6 +183,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -201,6 +213,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -229,6 +243,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -259,6 +275,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -289,6 +307,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -319,6 +339,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -349,6 +371,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -379,6 +403,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -409,6 +435,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -446,6 +474,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -487,6 +517,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/twentemilieu/snapshots/test_calendar.ambr b/tests/components/twentemilieu/snapshots/test_calendar.ambr index d004084e063c5d..c97a66039ac044 100644 --- a/tests/components/twentemilieu/snapshots/test_calendar.ambr +++ b/tests/components/twentemilieu/snapshots/test_calendar.ambr @@ -61,6 +61,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -92,6 +94,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Twente Milieu', 'model': None, 'name': 'Twente Milieu', diff --git a/tests/components/twentemilieu/snapshots/test_sensor.ambr b/tests/components/twentemilieu/snapshots/test_sensor.ambr index 46b21ebab3294f..6e250fee6bf1f3 100644 --- a/tests/components/twentemilieu/snapshots/test_sensor.ambr +++ b/tests/components/twentemilieu/snapshots/test_sensor.ambr @@ -30,6 +30,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -61,6 +63,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Twente Milieu', 'model': None, 'name': 'Twente Milieu', @@ -101,6 +105,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -132,6 +138,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Twente Milieu', 'model': None, 'name': 'Twente Milieu', @@ -172,6 +180,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -203,6 +213,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Twente Milieu', 'model': None, 'name': 'Twente Milieu', @@ -243,6 +255,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -274,6 +288,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Twente Milieu', 'model': None, 'name': 'Twente Milieu', @@ -314,6 +330,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -345,6 +363,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'Twente Milieu', 'model': None, 'name': 'Twente Milieu', diff --git a/tests/components/uptime/snapshots/test_sensor.ambr b/tests/components/uptime/snapshots/test_sensor.ambr index 539ba640d806d6..14a27ca5f46a5f 100644 --- a/tests/components/uptime/snapshots/test_sensor.ambr +++ b/tests/components/uptime/snapshots/test_sensor.ambr @@ -29,6 +29,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -53,6 +55,8 @@ 'hw_version': None, 'id': , 'is_new': False, + 'labels': set({ + }), 'manufacturer': None, 'model': None, 'name': 'Uptime', diff --git a/tests/components/vesync/snapshots/test_fan.ambr b/tests/components/vesync/snapshots/test_fan.ambr index 82a31b5fc14aff..2d5d9a0b8f2774 100644 --- a/tests/components/vesync/snapshots/test_fan.ambr +++ b/tests/components/vesync/snapshots/test_fan.ambr @@ -18,6 +18,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'LV-PUR131S', 'name': 'Air Purifier 131s', @@ -51,6 +53,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -101,6 +105,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'Core200S', 'name': 'Air Purifier 200s', @@ -133,6 +139,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -189,6 +197,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'LAP-C401S-WJP', 'name': 'Air Purifier 400s', @@ -222,6 +232,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -279,6 +291,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'LAP-C601S-WUS', 'name': 'Air Purifier 600s', @@ -312,6 +326,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -369,6 +385,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESL100', 'name': 'Dimmable Light', @@ -402,6 +420,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESWD16', 'name': 'Dimmer Switch', @@ -451,6 +471,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'wifi-switch-1.3', 'name': 'Outlet', @@ -484,6 +506,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESL100CW', 'name': 'Temperature Light', @@ -517,6 +541,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESWL01', 'name': 'Wall Switch', diff --git a/tests/components/vesync/snapshots/test_light.ambr b/tests/components/vesync/snapshots/test_light.ambr index 1f7b0aa9bafddf..97f7e33dcf7300 100644 --- a/tests/components/vesync/snapshots/test_light.ambr +++ b/tests/components/vesync/snapshots/test_light.ambr @@ -18,6 +18,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'LV-PUR131S', 'name': 'Air Purifier 131s', @@ -51,6 +53,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'Core200S', 'name': 'Air Purifier 200s', @@ -84,6 +88,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'LAP-C401S-WJP', 'name': 'Air Purifier 400s', @@ -117,6 +123,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'LAP-C601S-WUS', 'name': 'Air Purifier 600s', @@ -150,6 +158,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESL100', 'name': 'Dimmable Light', @@ -182,6 +192,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -231,6 +243,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESWD16', 'name': 'Dimmer Switch', @@ -263,6 +277,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -330,6 +346,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'wifi-switch-1.3', 'name': 'Outlet', @@ -363,6 +381,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESL100CW', 'name': 'Temperature Light', @@ -399,6 +419,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -452,6 +474,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESWL01', 'name': 'Wall Switch', diff --git a/tests/components/vesync/snapshots/test_sensor.ambr b/tests/components/vesync/snapshots/test_sensor.ambr index 040e41747a27c6..ff7830ec99e3fb 100644 --- a/tests/components/vesync/snapshots/test_sensor.ambr +++ b/tests/components/vesync/snapshots/test_sensor.ambr @@ -18,6 +18,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'LV-PUR131S', 'name': 'Air Purifier 131s', @@ -48,6 +50,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -76,6 +80,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -135,6 +141,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'Core200S', 'name': 'Air Purifier 200s', @@ -165,6 +173,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -212,6 +222,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'LAP-C401S-WJP', 'name': 'Air Purifier 400s', @@ -242,6 +254,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -270,6 +284,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -300,6 +316,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -374,6 +392,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'LAP-C601S-WUS', 'name': 'Air Purifier 600s', @@ -404,6 +424,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -432,6 +454,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -462,6 +486,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -536,6 +562,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESL100', 'name': 'Dimmable Light', @@ -569,6 +597,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESWD16', 'name': 'Dimmer Switch', @@ -618,6 +648,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'wifi-switch-1.3', 'name': 'Outlet', @@ -648,6 +680,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -678,6 +712,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -708,6 +744,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -738,6 +776,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -768,6 +808,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -798,6 +840,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -921,6 +965,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESL100CW', 'name': 'Temperature Light', @@ -954,6 +1000,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESWL01', 'name': 'Wall Switch', diff --git a/tests/components/vesync/snapshots/test_switch.ambr b/tests/components/vesync/snapshots/test_switch.ambr index 77f4011a532916..5e320fcd05077b 100644 --- a/tests/components/vesync/snapshots/test_switch.ambr +++ b/tests/components/vesync/snapshots/test_switch.ambr @@ -18,6 +18,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'LV-PUR131S', 'name': 'Air Purifier 131s', @@ -51,6 +53,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'Core200S', 'name': 'Air Purifier 200s', @@ -84,6 +88,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'LAP-C401S-WJP', 'name': 'Air Purifier 400s', @@ -117,6 +123,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'LAP-C601S-WUS', 'name': 'Air Purifier 600s', @@ -150,6 +158,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESL100', 'name': 'Dimmable Light', @@ -183,6 +193,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESWD16', 'name': 'Dimmer Switch', @@ -232,6 +244,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'wifi-switch-1.3', 'name': 'Outlet', @@ -260,6 +274,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -305,6 +321,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESL100CW', 'name': 'Temperature Light', @@ -338,6 +356,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'VeSync', 'model': 'ESWL01', 'name': 'Wall Switch', @@ -366,6 +386,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/whois/snapshots/test_sensor.ambr b/tests/components/whois/snapshots/test_sensor.ambr index d0bcff20b0ed86..4798b6714e37a5 100644 --- a/tests/components/whois/snapshots/test_sensor.ambr +++ b/tests/components/whois/snapshots/test_sensor.ambr @@ -29,6 +29,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -60,6 +62,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': None, 'model': None, 'name': 'home-assistant.io', @@ -99,6 +103,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -130,6 +136,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': None, 'model': None, 'name': 'home-assistant.io', @@ -174,6 +182,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -205,6 +215,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': None, 'model': None, 'name': 'home-assistant.io', @@ -244,6 +256,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -275,6 +289,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': None, 'model': None, 'name': 'home-assistant.io', @@ -314,6 +330,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -345,6 +363,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': None, 'model': None, 'name': 'home-assistant.io', @@ -384,6 +404,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -415,6 +437,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': None, 'model': None, 'name': 'home-assistant.io', @@ -454,6 +478,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -485,6 +511,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': None, 'model': None, 'name': 'home-assistant.io', @@ -524,6 +552,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -555,6 +585,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': None, 'model': None, 'name': 'home-assistant.io', @@ -594,6 +626,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -625,6 +659,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': None, 'model': None, 'name': 'home-assistant.io', @@ -664,6 +700,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), diff --git a/tests/components/wled/snapshots/test_binary_sensor.ambr b/tests/components/wled/snapshots/test_binary_sensor.ambr index 7520ea7a6a615d..c845b82be63002 100644 --- a/tests/components/wled/snapshots/test_binary_sensor.ambr +++ b/tests/components/wled/snapshots/test_binary_sensor.ambr @@ -29,6 +29,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -64,6 +66,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'WLED', 'model': 'DIY light', 'name': 'WLED RGB Light', diff --git a/tests/components/wled/snapshots/test_button.ambr b/tests/components/wled/snapshots/test_button.ambr index da487b49489bf3..82cd788b126100 100644 --- a/tests/components/wled/snapshots/test_button.ambr +++ b/tests/components/wled/snapshots/test_button.ambr @@ -29,6 +29,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -64,6 +66,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'WLED', 'model': 'DIY light', 'name': 'WLED RGB Light', diff --git a/tests/components/wled/snapshots/test_number.ambr b/tests/components/wled/snapshots/test_number.ambr index 96b465616c4cfc..1bf2423b4c1012 100644 --- a/tests/components/wled/snapshots/test_number.ambr +++ b/tests/components/wled/snapshots/test_number.ambr @@ -37,6 +37,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -72,6 +74,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'WLED', 'model': 'DIY light', 'name': 'WLED RGB Light', @@ -120,6 +124,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -155,6 +161,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'WLED', 'model': 'DIY light', 'name': 'WLED RGB Light', diff --git a/tests/components/wled/snapshots/test_select.ambr b/tests/components/wled/snapshots/test_select.ambr index 05d61fc18cb253..23ea693af86f20 100644 --- a/tests/components/wled/snapshots/test_select.ambr +++ b/tests/components/wled/snapshots/test_select.ambr @@ -40,6 +40,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -75,6 +77,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'WLED', 'model': 'DIY light', 'name': 'WLED RGB Light', @@ -219,6 +223,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -254,6 +260,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'WLED', 'model': 'DIY light', 'name': 'WLED RGB Light', @@ -302,6 +310,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -337,6 +347,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'WLED', 'model': 'DIY light', 'name': 'WLED RGBW Light', @@ -385,6 +397,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -420,6 +434,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'WLED', 'model': 'DIY light', 'name': 'WLED RGBW Light', diff --git a/tests/components/wled/snapshots/test_switch.ambr b/tests/components/wled/snapshots/test_switch.ambr index f89bde6ee17421..7fd2015f1116c0 100644 --- a/tests/components/wled/snapshots/test_switch.ambr +++ b/tests/components/wled/snapshots/test_switch.ambr @@ -32,6 +32,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -67,6 +69,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'WLED', 'model': 'DIY light', 'name': 'WLED RGB Light', @@ -106,6 +110,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -141,6 +147,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'WLED', 'model': 'DIY light', 'name': 'WLED RGB Light', @@ -181,6 +189,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -216,6 +226,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'WLED', 'model': 'DIY light', 'name': 'WLED RGB Light', @@ -256,6 +268,8 @@ 'hidden_by': None, 'icon': None, 'id': , + 'labels': set({ + }), 'name': None, 'options': dict({ }), @@ -291,6 +305,8 @@ ), }), 'is_new': False, + 'labels': set({ + }), 'manufacturer': 'WLED', 'model': 'DIY light', 'name': 'WLED RGB Light', diff --git a/tests/conftest.py b/tests/conftest.py index 7184fac81891aa..f4189c9d3fc801 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -55,6 +55,7 @@ entity_registry as er, event, issue_registry as ir, + label_registry as lr, recorder as recorder_helper, ) from homeassistant.helpers.typing import ConfigType @@ -1593,6 +1594,12 @@ def issue_registry(hass: HomeAssistant) -> ir.IssueRegistry: return ir.async_get(hass) +@pytest.fixture +def label_registry(hass: HomeAssistant) -> lr.LabelRegistry: + """Return the label registry from the current hass instance.""" + return lr.async_get(hass) + + @pytest.fixture def snapshot(snapshot: SnapshotAssertion) -> SnapshotAssertion: """Return snapshot assertion fixture with the Home Assistant extension.""" diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index e183bd4c38047f..19b33f79b30854 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -180,6 +180,7 @@ async def test_loading_from_storage( "hw_version": "hw_version", "id": "abcdefghijklm", "identifiers": [["serial", "12:34:56:AB:CD:EF"]], + "labels": {"label1", "label2"}, "manufacturer": "manufacturer", "model": "model", "name_by_user": "Test Friendly Name", @@ -222,6 +223,7 @@ async def test_loading_from_storage( hw_version="hw_version", id="abcdefghijklm", identifiers={("serial", "12:34:56:AB:CD:EF")}, + labels={"label1", "label2"}, manufacturer="manufacturer", model="model", name_by_user="Test Friendly Name", @@ -256,10 +258,10 @@ async def test_loading_from_storage( @pytest.mark.parametrize("load_registries", [False]) -async def test_migration_1_1_to_1_3( +async def test_migration_1_1_to_1_4( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: - """Test migration from version 1.1 to 1.3.""" + """Test migration from version 1.1 to 1.4.""" hass_storage[dr.STORAGE_KEY] = { "version": 1, "minor_version": 1, @@ -343,6 +345,7 @@ async def test_migration_1_1_to_1_3( "hw_version": None, "id": "abcdefghijklm", "identifiers": [["serial", "12:34:56:AB:CD:EF"]], + "labels": [], "manufacturer": "manufacturer", "model": "model", "name": "name", @@ -360,6 +363,7 @@ async def test_migration_1_1_to_1_3( "hw_version": None, "id": "invalid-entry-type", "identifiers": [["serial", "mock-id-invalid-entry"]], + "labels": [], "manufacturer": None, "model": None, "name_by_user": None, @@ -382,10 +386,10 @@ async def test_migration_1_1_to_1_3( @pytest.mark.parametrize("load_registries", [False]) -async def test_migration_1_2_to_1_3( +async def test_migration_1_2_to_1_4( hass: HomeAssistant, hass_storage: dict[str, Any] ) -> None: - """Test migration from version 1.2 to 1.3.""" + """Test migration from version 1.2 to 1.4.""" hass_storage[dr.STORAGE_KEY] = { "version": 1, "minor_version": 2, @@ -468,10 +472,63 @@ async def test_migration_1_2_to_1_3( "hw_version": None, "id": "abcdefghijklm", "identifiers": [["serial", "12:34:56:AB:CD:EF"]], + "labels": [], "manufacturer": "manufacturer", "model": "model", + "name_by_user": None, "name": "name", + "sw_version": "new_version", + "via_device_id": None, + }, + { + "area_id": None, + "config_entries": [None], + "configuration_url": None, + "connections": [], + "disabled_by": None, + "entry_type": None, + "hw_version": None, + "id": "invalid-entry-type", + "identifiers": [["serial", "mock-id-invalid-entry"]], + "labels": [], + "manufacturer": None, + "model": None, "name_by_user": None, + "name": None, + "sw_version": None, + "via_device_id": None, + }, + ], + "deleted_devices": [], + }, + } + + +@pytest.mark.parametrize("load_registries", [False]) +async def test_migration_1_3_to_1_4( + hass: HomeAssistant, hass_storage: dict[str, Any] +) -> None: + """Test migration from version 1.3 to 1.4.""" + hass_storage[dr.STORAGE_KEY] = { + "version": 1, + "minor_version": 3, + "key": dr.STORAGE_KEY, + "data": { + "devices": [ + { + "area_id": None, + "config_entries": ["1234"], + "configuration_url": None, + "connections": [["Zigbee", "01.23.45.67.89"]], + "disabled_by": None, + "entry_type": "service", + "hw_version": None, + "id": "abcdefghijklm", + "identifiers": [["serial", "12:34:56:AB:CD:EF"]], + "manufacturer": "manufacturer", + "model": "model", + "name_by_user": None, + "name": "name", "sw_version": "new_version", "via_device_id": None, }, @@ -497,6 +554,76 @@ async def test_migration_1_2_to_1_3( }, } + await dr.async_load(hass) + registry = dr.async_get(hass) + + # Test data was loaded + entry = registry.async_get_or_create( + config_entry_id="1234", + connections={("Zigbee", "01.23.45.67.89")}, + identifiers={("serial", "12:34:56:AB:CD:EF")}, + ) + assert entry.id == "abcdefghijklm" + + # Update to trigger a store + entry = registry.async_get_or_create( + config_entry_id="1234", + connections={("Zigbee", "01.23.45.67.89")}, + identifiers={("serial", "12:34:56:AB:CD:EF")}, + hw_version="new_version", + ) + assert entry.id == "abcdefghijklm" + + # Check we store migrated data + await flush_store(registry._store) + + assert hass_storage[dr.STORAGE_KEY] == { + "version": dr.STORAGE_VERSION_MAJOR, + "minor_version": dr.STORAGE_VERSION_MINOR, + "key": dr.STORAGE_KEY, + "data": { + "devices": [ + { + "area_id": None, + "config_entries": ["1234"], + "configuration_url": None, + "connections": [["Zigbee", "01.23.45.67.89"]], + "disabled_by": None, + "entry_type": "service", + "hw_version": "new_version", + "id": "abcdefghijklm", + "identifiers": [["serial", "12:34:56:AB:CD:EF"]], + "labels": [], + "manufacturer": "manufacturer", + "model": "model", + "name_by_user": None, + "name": "name", + "sw_version": "new_version", + "via_device_id": None, + }, + { + "area_id": None, + "config_entries": [None], + "configuration_url": None, + "connections": [], + "disabled_by": None, + "entry_type": None, + "hw_version": None, + "id": "invalid-entry-type", + "identifiers": [["serial", "mock-id-invalid-entry"]], + "labels": [], + "manufacturer": None, + "model": None, + "name_by_user": None, + "name": None, + "sw_version": None, + "via_device_id": None, + }, + ], + "deleted_devices": [], + }, + } + async def test_removing_config_entries( hass: HomeAssistant, device_registry: dr.DeviceRegistry, update_events @@ -809,7 +936,10 @@ async def test_loading_saving_data( assert len(device_registry.deleted_devices) == 1 orig_via = device_registry.async_update_device( - orig_via.id, area_id="mock-area-id", name_by_user="mock-name-by-user" + orig_via.id, + area_id="mock-area-id", + name_by_user="mock-name-by-user", + labels={"mock-label1", "mock-label2"}, ) # Now load written data in new registry @@ -910,6 +1040,7 @@ async def test_update( ) new_identifiers = {("hue", "654"), ("bla", "321")} assert not entry.area_id + assert not entry.labels assert not entry.name_by_user with patch.object(device_registry, "async_schedule_save") as mock_save: @@ -920,6 +1051,7 @@ async def test_update( disabled_by=dr.DeviceEntryDisabler.USER, entry_type=dr.DeviceEntryType.SERVICE, hw_version="hw_version", + labels={"label1", "label2"}, manufacturer="Test Producer", model="Test Model", name_by_user="Test Friendly Name", @@ -942,6 +1074,7 @@ async def test_update( hw_version="hw_version", id=entry.id, identifiers={("bla", "321"), ("hue", "654")}, + labels={"label1", "label2"}, manufacturer="Test Producer", model="Test Model", name_by_user="Test Friendly Name", @@ -981,6 +1114,7 @@ async def test_update( "entry_type": None, "hw_version": None, "identifiers": {("bla", "123"), ("hue", "456")}, + "labels": set(), "manufacturer": None, "model": None, "name": None, @@ -1664,3 +1798,78 @@ async def test_only_disable_device_if_all_config_entries_are_disabled( entry1 = device_registry.async_get(entry1.id) assert not entry1.disabled + + +async def test_removing_labels(device_registry: dr.DeviceRegistry) -> None: + """Make sure we can clear labels.""" + entry = device_registry.async_get_or_create( + config_entry_id="123", + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")}, + identifiers={("bridgeid", "0123")}, + manufacturer="manufacturer", + model="model", + ) + entry = device_registry.async_update_device(entry.id, labels={"label1", "label2"}) + + device_registry.async_clear_label_id("label1") + entry_cleared_label1 = device_registry.async_get_device({("bridgeid", "0123")}) + + device_registry.async_clear_label_id("label2") + entry_cleared_label2 = device_registry.async_get_device({("bridgeid", "0123")}) + + assert entry_cleared_label1 + assert entry_cleared_label2 + assert entry != entry_cleared_label1 + assert entry != entry_cleared_label2 + assert entry_cleared_label1 != entry_cleared_label2 + assert entry.labels == {"label1", "label2"} + assert entry_cleared_label1.labels == {"label2"} + assert not entry_cleared_label2.labels + + +async def test_entries_for_label(device_registry: dr.DeviceRegistry) -> None: + """Test getting device entries by label.""" + device_registry.async_get_or_create( + config_entry_id="000", + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:00")}, + identifiers={("bridgeid", "0000")}, + manufacturer="manufacturer", + model="model", + ) + entry_1 = device_registry.async_get_or_create( + config_entry_id="123", + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:23")}, + identifiers={("bridgeid", "0123")}, + manufacturer="manufacturer", + model="model", + ) + entry_1 = device_registry.async_update_device(entry_1.id, labels={"label1"}) + entry_2 = device_registry.async_get_or_create( + config_entry_id="456", + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:56")}, + identifiers={("bridgeid", "0456")}, + manufacturer="manufacturer", + model="model", + ) + entry_2 = device_registry.async_update_device(entry_2.id, labels={"label2"}) + entry_1_and_2 = device_registry.async_get_or_create( + config_entry_id="789", + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:89")}, + identifiers={("bridgeid", "0789")}, + manufacturer="manufacturer", + model="model", + ) + entry_1_and_2 = device_registry.async_update_device( + entry_1_and_2.id, labels={"label1", "label2"} + ) + + entries = dr.async_entries_for_label(device_registry, "label1") + assert len(entries) == 2 + assert entries == [entry_1, entry_1_and_2] + + entries = dr.async_entries_for_label(device_registry, "label2") + assert len(entries) == 2 + assert entries == [entry_2, entry_1_and_2] + + assert not dr.async_entries_for_label(device_registry, "unknown") + assert not dr.async_entries_for_label(device_registry, "") diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index e3b91c46e184f6..fc62da43079a87 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -265,6 +265,9 @@ async def test_loading_saving_data( entity_registry.async_update_entity_options( orig_entry2.entity_id, "light", {"minimum_brightness": 20} ) + entity_registry.async_update_entity( + orig_entry2.entity_id, labels={"label1", "label2"} + ) orig_entry2 = entity_registry.async_get(orig_entry2.entity_id) assert len(entity_registry.entities) == 2 @@ -292,6 +295,7 @@ async def test_loading_saving_data( assert new_entry2.icon == "hass:user-icon" assert new_entry2.hidden_by == er.RegistryEntryHider.INTEGRATION assert new_entry2.has_entity_name is True + assert new_entry2.labels == {"label1", "label2"} assert new_entry2.name == "User Name" assert new_entry2.options == {"light": {"minimum_brightness": 20}} assert new_entry2.original_device_class == "mock-device-class" @@ -1527,3 +1531,70 @@ def test_migrate_entity_to_new_platform( new_unique_id=new_unique_id, new_config_entry_id=new_config_entry.entry_id, ) + + +async def test_removing_labels(entity_registry: er.EntityRegistry) -> None: + """Make sure we can clear labels.""" + entry = entity_registry.async_get_or_create( + domain="light", + platform="hue", + unique_id="5678", + ) + entry = entity_registry.async_update_entity( + entry.entity_id, labels={"label1", "label2"} + ) + + entity_registry.async_clear_label_id("label1") + entry_cleared_label1 = entity_registry.async_get(entry.entity_id) + + entity_registry.async_clear_label_id("label2") + entry_cleared_label2 = entity_registry.async_get(entry.entity_id) + + assert entry_cleared_label1 + assert entry_cleared_label2 + assert entry != entry_cleared_label1 + assert entry != entry_cleared_label2 + assert entry_cleared_label1 != entry_cleared_label2 + assert entry.labels == {"label1", "label2"} + assert entry_cleared_label1.labels == {"label2"} + assert not entry_cleared_label2.labels + + +async def test_entries_for_label(entity_registry: er.EntityRegistry) -> None: + """Test getting entity entries by label.""" + entity_registry.async_get_or_create( + domain="light", + platform="hue", + unique_id="000", + ) + entry = entity_registry.async_get_or_create( + domain="light", + platform="hue", + unique_id="123", + ) + label_1 = entity_registry.async_update_entity(entry.entity_id, labels={"label1"}) + entry = entity_registry.async_get_or_create( + domain="light", + platform="hue", + unique_id="456", + ) + label_2 = entity_registry.async_update_entity(entry.entity_id, labels={"label2"}) + entry = entity_registry.async_get_or_create( + domain="light", + platform="hue", + unique_id="789", + ) + label_1_and_2 = entity_registry.async_update_entity( + entry.entity_id, labels={"label1", "label2"} + ) + + entries = er.async_entries_for_label(entity_registry, "label1") + assert len(entries) == 2 + assert entries == [label_1, label_1_and_2] + + entries = er.async_entries_for_label(entity_registry, "label2") + assert len(entries) == 2 + assert entries == [label_2, label_1_and_2] + + assert not er.async_entries_for_label(entity_registry, "unknown") + assert not er.async_entries_for_label(entity_registry, "") diff --git a/tests/helpers/test_label_registry.py b/tests/helpers/test_label_registry.py new file mode 100644 index 00000000000000..80067b6f105057 --- /dev/null +++ b/tests/helpers/test_label_registry.py @@ -0,0 +1,457 @@ +"""Tests for the Label Registry.""" +import re +from typing import Any + +import pytest + +from homeassistant.core import HomeAssistant +from homeassistant.helpers import ( + device_registry as dr, + entity_registry as er, + label_registry as lr, +) +from homeassistant.helpers.label_registry import ( + EVENT_LABEL_REGISTRY_UPDATED, + STORAGE_KEY, + STORAGE_VERSION_MAJOR, + LabelRegistry, + async_get, + async_load, +) + +from tests.common import async_capture_events, flush_store + + +async def test_list_labels( + hass: HomeAssistant, label_registry: lr.LabelRegistry +) -> None: + """Make sure that we can read label.""" + labels = label_registry.async_list_labels() + assert len(list(labels)) == len(label_registry.labels) + + +async def test_create_label( + hass: HomeAssistant, label_registry: lr.LabelRegistry +) -> None: + """Make sure that we can create labels.""" + update_events = async_capture_events(hass, EVENT_LABEL_REGISTRY_UPDATED) + label = label_registry.async_create( + name="My Label", + color="#FF0000", + icon="mdi:test", + description="This label is for testing", + ) + + assert label.label_id == "my_label" + assert label.name == "My Label" + assert label.color == "#FF0000" + assert label.icon == "mdi:test" + assert label.description == "This label is for testing" + + assert len(label_registry.labels) == 1 + + await hass.async_block_till_done() + + assert len(update_events) == 1 + assert update_events[0].data == { + "action": "create", + "label_id": label.label_id, + } + + +async def test_create_label_with_name_already_in_use( + hass: HomeAssistant, label_registry: lr.LabelRegistry +) -> None: + """Make sure that we can't create an label with a name already in use.""" + update_events = async_capture_events(hass, EVENT_LABEL_REGISTRY_UPDATED) + label_registry.async_create("mock") + + with pytest.raises( + ValueError, match=re.escape("The name mock (mock) is already in use") + ): + label_registry.async_create("mock") + + await hass.async_block_till_done() + + assert len(label_registry.labels) == 1 + assert len(update_events) == 1 + + +async def test_create_label_with_id_already_in_use( + hass: HomeAssistant, label_registry: lr.LabelRegistry +) -> None: + """Make sure that we can't create an label with a name already in use.""" + label = label_registry.async_create("Label") + + updated_label = label_registry.async_update(label.label_id, name="Renamed Label") + assert updated_label.label_id == label.label_id + + second_label = label_registry.async_create("Label") + assert label.label_id != second_label.label_id + assert second_label.label_id == "label_2" + + +async def test_delete_label( + hass: HomeAssistant, label_registry: lr.LabelRegistry +) -> None: + """Make sure that we can delete an label.""" + update_events = async_capture_events(hass, EVENT_LABEL_REGISTRY_UPDATED) + label = label_registry.async_create("Label") + assert len(label_registry.labels) == 1 + + label_registry.async_delete(label.label_id) + + assert not label_registry.labels + + await hass.async_block_till_done() + + assert len(update_events) == 2 + assert update_events[0].data == { + "action": "create", + "label_id": label.label_id, + } + assert update_events[1].data == { + "action": "remove", + "label_id": label.label_id, + } + + +async def test_delete_non_existing_label( + hass: HomeAssistant, label_registry: lr.LabelRegistry +) -> None: + """Make sure that we can't delete an label that doesn't exist.""" + label_registry.async_create("mock") + + with pytest.raises(KeyError): + label_registry.async_delete("") + + assert len(label_registry.labels) == 1 + + +async def test_update_label( + hass: HomeAssistant, label_registry: lr.LabelRegistry +) -> None: + """Make sure that we can update labels.""" + update_events = async_capture_events(hass, EVENT_LABEL_REGISTRY_UPDATED) + label = label_registry.async_create("Mock") + + assert len(label_registry.labels) == 1 + assert label.label_id == "mock" + assert label.name == "Mock" + assert label.color is None + assert label.icon is None + assert label.description is None + + updated_label = label_registry.async_update( + label.label_id, + name="Updated", + color="#FFFFFF", + icon="mdi:update", + description="Updated description", + ) + + assert updated_label != label + assert updated_label.label_id == "mock" + assert updated_label.name == "Updated" + assert updated_label.color == "#FFFFFF" + assert updated_label.icon == "mdi:update" + assert updated_label.description == "Updated description" + + assert len(label_registry.labels) == 1 + + await hass.async_block_till_done() + + assert len(update_events) == 2 + assert update_events[0].data == { + "action": "create", + "label_id": label.label_id, + } + assert update_events[1].data == { + "action": "update", + "label_id": label.label_id, + } + + +async def test_update_label_with_same_data( + hass: HomeAssistant, label_registry: lr.LabelRegistry +) -> None: + """Make sure that we can reapply the same data to the label and it won't update.""" + update_events = async_capture_events(hass, EVENT_LABEL_REGISTRY_UPDATED) + label = label_registry.async_create( + "mock", + color="#FFFFFF", + icon="mdi:test", + description="Description", + ) + + udpated_label = label_registry.async_update( + label_id=label.label_id, + name="mock", + color="#FFFFFF", + icon="mdi:test", + description="Description", + ) + assert label == udpated_label + + await hass.async_block_till_done() + + # No update event + assert len(update_events) == 1 + assert update_events[0].data == { + "action": "create", + "label_id": label.label_id, + } + + +async def test_update_label_with_same_name_change_case( + hass: HomeAssistant, label_registry: lr.LabelRegistry +) -> None: + """Make sure that we can reapply the same name with a different case to the label.""" + label = label_registry.async_create("mock") + + updated_label = label_registry.async_update(label.label_id, name="Mock") + + assert updated_label.name == "Mock" + assert updated_label.label_id == label.label_id + assert updated_label.normalized_name == label.normalized_name + assert len(label_registry.labels) == 1 + + +async def test_update_label_with_name_already_in_use( + hass: HomeAssistant, label_registry: lr.LabelRegistry +) -> None: + """Make sure that we can't update an label with a name already in use.""" + label1 = label_registry.async_create("mock1") + label2 = label_registry.async_create("mock2") + + with pytest.raises( + ValueError, match=re.escape("The name mock2 (mock2) is already in use") + ): + label_registry.async_update(label1.label_id, name="mock2") + + assert label1.name == "mock1" + assert label2.name == "mock2" + assert len(label_registry.labels) == 2 + + +async def test_update_label_with_normalized_name_already_in_use( + hass: HomeAssistant, label_registry: lr.LabelRegistry +) -> None: + """Make sure that we can't update an label with a normalized name already in use.""" + label1 = label_registry.async_create("mock1") + label2 = label_registry.async_create("M O C K 2") + + with pytest.raises( + ValueError, match=re.escape("The name mock2 (mock2) is already in use") + ): + label_registry.async_update(label1.label_id, name="mock2") + + assert label1.name == "mock1" + assert label2.name == "M O C K 2" + assert len(label_registry.labels) == 2 + + +async def test_load_labels( + hass: HomeAssistant, label_registry: lr.LabelRegistry +) -> None: + """Make sure that we can load/save data correctly.""" + label1 = label_registry.async_create( + "Label One", + color="#FF000", + icon="mdi:one", + description="This label is label one", + ) + label2 = label_registry.async_create( + "Label Two", + color="#000FF", + icon="mdi:two", + description="This label is label two", + ) + + assert len(label_registry.labels) == 2 + + registry2 = LabelRegistry(hass) + await flush_store(label_registry._store) + await registry2.async_load() + + assert len(registry2.labels) == 2 + assert list(label_registry.labels) == list(registry2.labels) + + label1_registry2 = registry2.async_get_or_create("Label One") + assert label1_registry2.label_id == label1.label_id + assert label1_registry2.name == label1.name + assert label1_registry2.color == label1.color + assert label1_registry2.description == label1.description + assert label1_registry2.icon == label1.icon + assert label1_registry2.normalized_name == label1.normalized_name + + label2_registry2 = registry2.async_get_or_create("Label Two") + assert label2_registry2.name == label2.name + assert label2_registry2.color == label2.color + assert label2_registry2.description == label2.description + assert label2_registry2.icon == label2.icon + assert label2_registry2.normalized_name == label2.normalized_name + + +@pytest.mark.parametrize("load_registries", [False]) +async def test_loading_label_from_storage( + hass: HomeAssistant, hass_storage: Any +) -> None: + """Test loading stored labels on start.""" + hass_storage[STORAGE_KEY] = { + "version": STORAGE_VERSION_MAJOR, + "data": { + "labels": [ + { + "color": "#FFFFFF", + "description": None, + "icon": "mdi:test", + "label_id": "one", + "name": "One", + } + ] + }, + } + + await async_load(hass) + registry = async_get(hass) + + assert len(registry.labels) == 1 + + +async def test_getting_label( + hass: HomeAssistant, label_registry: lr.LabelRegistry +) -> None: + """Make sure we can get the labels by name.""" + label = label_registry.async_get_or_create("Mock1") + label2 = label_registry.async_get_or_create("mock1") + label3 = label_registry.async_get_or_create("mock 1") + + assert label == label2 + assert label == label3 + assert label2 == label3 + + get_label = label_registry.async_get_label_by_name("M o c k 1") + assert get_label == label + + get_label = label_registry.async_get_label(label.label_id) + assert get_label == label + + +async def test_async_get_label_by_name_not_found( + hass: HomeAssistant, label_registry: lr.LabelRegistry +) -> None: + """Make sure we return None for non-existent labels.""" + label_registry.async_create("Mock1") + + assert len(label_registry.labels) == 1 + + assert label_registry.async_get_label_by_name("non_exist") is None + + +async def test_labels_removed_from_devices( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + label_registry: lr.LabelRegistry, +) -> None: + """Tests if label gets removed from devices when the label is removed.""" + label1 = label_registry.async_create("label1") + label2 = label_registry.async_create("label2") + assert len(label_registry.labels) == 2 + + entry = device_registry.async_get_or_create( + config_entry_id="123", + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:23")}, + identifiers={("bridgeid", "0123")}, + manufacturer="manufacturer", + model="model", + ) + device_registry.async_update_device(entry.id, labels={label1.label_id}) + entry = device_registry.async_get_or_create( + config_entry_id="456", + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:56")}, + identifiers={("bridgeid", "0456")}, + manufacturer="manufacturer", + model="model", + ) + device_registry.async_update_device(entry.id, labels={label2.label_id}) + entry = device_registry.async_get_or_create( + config_entry_id="789", + connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:89")}, + identifiers={("bridgeid", "0789")}, + manufacturer="manufacturer", + model="model", + ) + device_registry.async_update_device( + entry.id, labels={label1.label_id, label2.label_id} + ) + + entries = dr.async_entries_for_label(device_registry, label1.label_id) + assert len(entries) == 2 + entries = dr.async_entries_for_label(device_registry, label2.label_id) + assert len(entries) == 2 + + label_registry.async_delete(label1.label_id) + + entries = dr.async_entries_for_label(device_registry, label1.label_id) + assert len(entries) == 0 + entries = dr.async_entries_for_label(device_registry, label2.label_id) + assert len(entries) == 2 + + label_registry.async_delete(label2.label_id) + + entries = dr.async_entries_for_label(device_registry, label1.label_id) + assert len(entries) == 0 + entries = dr.async_entries_for_label(device_registry, label2.label_id) + assert len(entries) == 0 + + +async def test_labels_removed_from_entities( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + label_registry: lr.LabelRegistry, +) -> None: + """Tests if label gets removed from entity when the label is removed.""" + label1 = label_registry.async_create("label1") + label2 = label_registry.async_create("label2") + assert len(label_registry.labels) == 2 + + entry = entity_registry.async_get_or_create( + domain="light", + platform="hue", + unique_id="123", + ) + entity_registry.async_update_entity(entry.entity_id, labels={label1.label_id}) + entry = entity_registry.async_get_or_create( + domain="light", + platform="hue", + unique_id="456", + ) + entity_registry.async_update_entity(entry.entity_id, labels={label2.label_id}) + entry = entity_registry.async_get_or_create( + domain="light", + platform="hue", + unique_id="789", + ) + entity_registry.async_update_entity( + entry.entity_id, labels={label1.label_id, label2.label_id} + ) + + entries = er.async_entries_for_label(entity_registry, label1.label_id) + assert len(entries) == 2 + entries = er.async_entries_for_label(entity_registry, label2.label_id) + assert len(entries) == 2 + + label_registry.async_delete(label1.label_id) + + entries = er.async_entries_for_label(entity_registry, label1.label_id) + assert len(entries) == 0 + entries = er.async_entries_for_label(entity_registry, label2.label_id) + assert len(entries) == 2 + + label_registry.async_delete(label2.label_id) + + entries = er.async_entries_for_label(entity_registry, label1.label_id) + assert len(entries) == 0 + entries = er.async_entries_for_label(entity_registry, label2.label_id) + assert len(entries) == 0 diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 8e8123ac7af747..58ea31391f3794 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -223,6 +223,88 @@ def area_mock(hass): ) +@pytest.fixture +def label_mock(hass: HomeAssistant) -> None: + """Mock including label info.""" + hass.states.async_set("light.Bowl", STATE_ON) + hass.states.async_set("light.Ceiling", STATE_OFF) + hass.states.async_set("light.Kitchen", STATE_OFF) + + device_has_label1 = dr.DeviceEntry(labels={"label1"}) + device_has_label2 = dr.DeviceEntry(labels={"label2"}) + device_has_labels = dr.DeviceEntry(labels={"label1", "label2"}) + device_no_labels = dr.DeviceEntry(id="device-no-labels") + + mock_device_registry( + hass, + { + device_has_label1.id: device_has_label1, + device_has_label2.id: device_has_label2, + device_has_labels.id: device_has_labels, + device_no_labels.id: device_no_labels, + }, + ) + + entity_with_my_label = er.RegistryEntry( + entity_id="light.with_my_label", + unique_id="with_my_label", + platform="test", + labels={"my-label"}, + ) + hidden_entity_with_my_label = er.RegistryEntry( + entity_id="light.hidden_with_my_label", + unique_id="hidden_with_my_label", + platform="test", + labels={"my-label"}, + hidden_by=er.RegistryEntryHider.USER, + ) + config_entity_with_my_label = er.RegistryEntry( + entity_id="light.config_with_my_label", + unique_id="config_with_my_label", + platform="test", + labels={"my-label"}, + entity_category=EntityCategory.CONFIG, + ) + entity_with_label1_from_device = er.RegistryEntry( + entity_id="light.with_label1_from_device", + unique_id="with_label1_from_device", + platform="test", + device_id=device_has_label1.id, + ) + entity_with_label1_and_label2_from_device = er.RegistryEntry( + entity_id="light.with_label1_and_label2_from_device", + unique_id="with_label1_and_label2_from_device", + platform="test", + labels={"label1"}, + device_id=device_has_label2.id, + ) + entity_with_labels_from_device = er.RegistryEntry( + entity_id="light.with_labels_from_device", + unique_id="with_labels_from_device", + platform="test", + device_id=device_has_labels.id, + ) + entity_with_no_labels = er.RegistryEntry( + entity_id="light.no_labels", + unique_id="no_labels", + platform="test", + device_id=device_no_labels.id, + ) + + mock_registry( + hass, + { + config_entity_with_my_label.entity_id: config_entity_with_my_label, + entity_with_label1_and_label2_from_device.entity_id: entity_with_label1_and_label2_from_device, + entity_with_label1_from_device.entity_id: entity_with_label1_from_device, + entity_with_labels_from_device.entity_id: entity_with_labels_from_device, + entity_with_my_label.entity_id: entity_with_my_label, + entity_with_no_labels.entity_id: entity_with_no_labels, + hidden_entity_with_my_label.entity_id: hidden_entity_with_my_label, + }, + ) + + async def test_call_from_config(hass: HomeAssistant) -> None: """Test the sync wrapper of service.async_call_from_config.""" calls = async_mock_service(hass, "test_domain", "test_service") @@ -542,6 +624,39 @@ async def test_extract_entity_ids_from_devices(hass: HomeAssistant, area_mock) - ) +async def test_extract_entity_ids_from_labels( + hass: HomeAssistant, label_mock: None +) -> None: + """Test extract_entity_ids method with labels.""" + call = ServiceCall("light", "turn_on", {"label_id": "my-label"}) + + assert { + "light.with_my_label", + } == await service.async_extract_entity_ids(hass, call) + + call = ServiceCall("light", "turn_on", {"label_id": "label1"}) + + assert { + "light.with_label1_from_device", + "light.with_labels_from_device", + "light.with_label1_and_label2_from_device", + } == await service.async_extract_entity_ids(hass, call) + + call = ServiceCall("light", "turn_on", {"label_id": ["label2"]}) + + assert { + "light.with_labels_from_device", + "light.with_label1_and_label2_from_device", + } == await service.async_extract_entity_ids(hass, call) + + assert ( + await service.async_extract_entity_ids( + hass, ServiceCall("light", "turn_on", {"label_id": ENTITY_MATCH_NONE}) + ) + == set() + ) + + async def test_async_get_all_descriptions(hass: HomeAssistant) -> None: """Test async_get_all_descriptions.""" group = hass.components.group @@ -1199,6 +1314,45 @@ async def test_extract_from_service_area_id(hass: HomeAssistant, area_mock) -> N ] +async def test_extract_from_service_label_id( + hass: HomeAssistant, label_mock: None +) -> None: + """Test the extraction using label ID as reference.""" + entities = [ + MockEntity(name="with_my_label", entity_id="light.with_my_label"), + MockEntity(name="no_labels", entity_id="light.no_labels"), + MockEntity( + name="with_labels_from_device", entity_id="light.with_labels_from_device" + ), + ] + + call = ServiceCall("light", "turn_on", {"label_id": "my-label"}) + extracted = await service.async_extract_entities(hass, entities, call) + assert len(extracted) == 1 + assert extracted[0].entity_id == "light.with_my_label" + + call = ServiceCall("light", "turn_on", {"label_id": ["my-label", "label1"]}) + extracted = await service.async_extract_entities(hass, entities, call) + assert len(extracted) == 2 + assert sorted(ent.entity_id for ent in extracted) == [ + "light.with_labels_from_device", + "light.with_my_label", + ] + + call = ServiceCall( + "light", + "turn_on", + {"label_id": ["my-label", "label1"], "device_id": "device-no-labels"}, + ) + extracted = await service.async_extract_entities(hass, entities, call) + assert len(extracted) == 3 + assert sorted(ent.entity_id for ent in extracted) == [ + "light.no_labels", + "light.with_labels_from_device", + "light.with_my_label", + ] + + async def test_entity_service_call_warn_referenced( hass: HomeAssistant, caplog: pytest.LogCaptureFixture ) -> None: @@ -1210,12 +1364,14 @@ async def test_entity_service_call_warn_referenced( "area_id": "non-existent-area", "entity_id": "non.existent", "device_id": "non-existent-device", + "label_id": "non-existent-label", }, ) await service.entity_service_call(hass, {}, "", call) assert ( "Unable to find referenced areas non-existent-area, devices" - " non-existent-device, entities non.existent" in caplog.text + " non-existent-device, entities non.existent, labels non-existent-label" + in caplog.text ) @@ -1230,13 +1386,15 @@ async def test_async_extract_entities_warn_referenced( "area_id": "non-existent-area", "entity_id": "non.existent", "device_id": "non-existent-device", + "label_id": "non-existent-label", }, ) extracted = await service.async_extract_entities(hass, {}, call) assert len(extracted) == 0 assert ( "Unable to find referenced areas non-existent-area, devices" - " non-existent-device, entities non.existent" in caplog.text + " non-existent-device, entities non.existent, labels non-existent-label" + in caplog.text ) diff --git a/tests/syrupy.py b/tests/syrupy.py index af34cb628fc1ce..f95068a4158d7a 100644 --- a/tests/syrupy.py +++ b/tests/syrupy.py @@ -29,6 +29,7 @@ device_registry as dr, entity_registry as er, issue_registry as ir, + label_registry as lr, ) @@ -60,6 +61,10 @@ class EntityRegistryEntrySnapshot(dict): """Tiny wrapper to represent an entity registry entry in snapshots.""" +class LabelRegistryEntrySnapshot(dict): + """Tiny wrapper to represent an label registry entry in snapshots.""" + + class FlowResultSnapshot(dict): """Tiny wrapper to represent a flow result in snapshots.""" @@ -103,6 +108,8 @@ def _serialize( serializable_data = cls._serializable_entity_registry_entry(data) elif isinstance(data, ir.IssueEntry): serializable_data = cls._serializable_issue_registry_entry(data) + elif isinstance(data, lr.LabelEntry): + serializable_data = cls._serializable_issue_registry_entry(data) elif isinstance(data, dict) and "flow_id" in data and "handler" in data: serializable_data = cls._serializable_flow_result(data) elif isinstance(data, vol.Schema): @@ -189,6 +196,13 @@ def _serializable_issue_registry_entry( """Prepare a Home Assistant issue registry entry for serialization.""" return IssueRegistryItemSnapshot(data.to_json() | {"created": ANY}) + @classmethod + def _serializable_label_registry_entry( + cls, data: lr.LabelEntry + ) -> SerializableData: + """Prepare a Home Assistant label registry entry for serialization.""" + return LabelRegistryEntrySnapshot(dataclasses.asdict(data)) + @classmethod def _serializable_state(cls, data: State) -> SerializableData: """Prepare a Home Assistant State for serialization."""