diff --git a/homeassistant/components/fressnapf_tracker/__init__.py b/homeassistant/components/fressnapf_tracker/__init__.py index 76d378360d6b25..930011f366d570 100644 --- a/homeassistant/components/fressnapf_tracker/__init__.py +++ b/homeassistant/components/fressnapf_tracker/__init__.py @@ -12,7 +12,7 @@ FressnapfTrackerDataUpdateCoordinator, ) -PLATFORMS: list[Platform] = [Platform.DEVICE_TRACKER] +PLATFORMS: list[Platform] = [Platform.DEVICE_TRACKER, Platform.SENSOR] async def async_setup_entry( diff --git a/homeassistant/components/fressnapf_tracker/entity.py b/homeassistant/components/fressnapf_tracker/entity.py index 04bc324fcdcee6..d8751a725094d4 100644 --- a/homeassistant/components/fressnapf_tracker/entity.py +++ b/homeassistant/components/fressnapf_tracker/entity.py @@ -1,6 +1,7 @@ """fressnapf_tracker class.""" from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.entity import EntityDescription from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import FressnapfTrackerDataUpdateCoordinator @@ -25,3 +26,17 @@ def __init__(self, coordinator: FressnapfTrackerDataUpdateCoordinator) -> None: manufacturer="Fressnapf", serial_number=str(self.id), ) + + +class FressnapfTrackerEntity(FressnapfTrackerBaseEntity): + """Entity for fressnapf_tracker.""" + + def __init__( + self, + coordinator: FressnapfTrackerDataUpdateCoordinator, + entity_description: EntityDescription, + ) -> None: + """Initialize the entity.""" + super().__init__(coordinator) + self.entity_description = entity_description + self._attr_unique_id = f"{self.id}_{entity_description.key}" diff --git a/homeassistant/components/fressnapf_tracker/quality_scale.yaml b/homeassistant/components/fressnapf_tracker/quality_scale.yaml index d3ac359c903bc0..a4bdab08c031a4 100644 --- a/homeassistant/components/fressnapf_tracker/quality_scale.yaml +++ b/homeassistant/components/fressnapf_tracker/quality_scale.yaml @@ -53,9 +53,7 @@ rules: entity-category: todo entity-device-class: todo entity-disabled-by-default: todo - entity-translations: - status: exempt - comment: No entities to translate + entity-translations: done exception-translations: todo icon-translations: todo reconfiguration-flow: done diff --git a/homeassistant/components/fressnapf_tracker/sensor.py b/homeassistant/components/fressnapf_tracker/sensor.py new file mode 100644 index 00000000000000..38ccb9b04f9fd9 --- /dev/null +++ b/homeassistant/components/fressnapf_tracker/sensor.py @@ -0,0 +1,63 @@ +"""Sensor platform for fressnapf_tracker.""" + +from collections.abc import Callable +from dataclasses import dataclass + +from fressnapftracker import Tracker + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.const import PERCENTAGE, EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from . import FressnapfTrackerConfigEntry +from .entity import FressnapfTrackerEntity + + +@dataclass(frozen=True, kw_only=True) +class FressnapfTrackerSensorDescription(SensorEntityDescription): + """Class describing Fressnapf Tracker sensor entities.""" + + value_fn: Callable[[Tracker], int] + + +SENSOR_ENTITY_DESCRIPTIONS: tuple[FressnapfTrackerSensorDescription, ...] = ( + FressnapfTrackerSensorDescription( + key="battery", + state_class=SensorStateClass.MEASUREMENT, + device_class=SensorDeviceClass.BATTERY, + native_unit_of_measurement=PERCENTAGE, + entity_category=EntityCategory.DIAGNOSTIC, + value_fn=lambda data: data.battery, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: FressnapfTrackerConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up the Fressnapf Tracker sensors.""" + + async_add_entities( + FressnapfTrackerSensor(coordinator, sensor_description) + for sensor_description in SENSOR_ENTITY_DESCRIPTIONS + for coordinator in entry.runtime_data + ) + + +class FressnapfTrackerSensor(FressnapfTrackerEntity, SensorEntity): + """fressnapf_tracker sensor for general information.""" + + entity_description: FressnapfTrackerSensorDescription + + @property + def native_value(self) -> int: + """Return the state of the resources if it has been received yet.""" + return self.entity_description.value_fn(self.coordinator.data) diff --git a/tests/components/fressnapf_tracker/snapshots/test_device_tracker.ambr b/tests/components/fressnapf_tracker/snapshots/test_device_tracker.ambr index 01b8f2ac76cb96..bd55751bbf1f28 100644 --- a/tests/components/fressnapf_tracker/snapshots/test_device_tracker.ambr +++ b/tests/components/fressnapf_tracker/snapshots/test_device_tracker.ambr @@ -1,35 +1,4 @@ # serializer version: 1 -# name: test_state_entity_device_snapshots[Fluffy-entry] - DeviceRegistryEntrySnapshot({ - 'area_id': None, - 'config_entries': , - 'config_entries_subentries': , - 'configuration_url': None, - 'connections': set({ - }), - 'disabled_by': None, - 'entry_type': None, - 'hw_version': None, - 'id': , - 'identifiers': set({ - tuple( - 'fressnapf_tracker', - 'ABC123456', - ), - }), - 'labels': set({ - }), - 'manufacturer': 'Fressnapf', - 'model': 'GPS Tracker 2.0', - 'model_id': None, - 'name': 'Fluffy', - 'name_by_user': None, - 'primary_config_entry': , - 'serial_number': 'ABC123456', - 'sw_version': None, - 'via_device_id': None, - }) -# --- # name: test_state_entity_device_snapshots[device_tracker.fluffy-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/fressnapf_tracker/snapshots/test_init.ambr b/tests/components/fressnapf_tracker/snapshots/test_init.ambr new file mode 100644 index 00000000000000..59d50fa4ce13b5 --- /dev/null +++ b/tests/components/fressnapf_tracker/snapshots/test_init.ambr @@ -0,0 +1,32 @@ +# serializer version: 1 +# name: test_state_entity_device_snapshots[Fluffy-entry] + DeviceRegistryEntrySnapshot({ + 'area_id': None, + 'config_entries': , + 'config_entries_subentries': , + 'configuration_url': None, + 'connections': set({ + }), + 'disabled_by': None, + 'entry_type': None, + 'hw_version': None, + 'id': , + 'identifiers': set({ + tuple( + 'fressnapf_tracker', + 'ABC123456', + ), + }), + 'labels': set({ + }), + 'manufacturer': 'Fressnapf', + 'model': 'GPS Tracker 2.0', + 'model_id': None, + 'name': 'Fluffy', + 'name_by_user': None, + 'primary_config_entry': , + 'serial_number': 'ABC123456', + 'sw_version': None, + 'via_device_id': None, + }) +# --- diff --git a/tests/components/fressnapf_tracker/snapshots/test_sensor.ambr b/tests/components/fressnapf_tracker/snapshots/test_sensor.ambr new file mode 100644 index 00000000000000..042a34fb76d804 --- /dev/null +++ b/tests/components/fressnapf_tracker/snapshots/test_sensor.ambr @@ -0,0 +1,54 @@ +# serializer version: 1 +# name: test_state_entity_device_snapshots[sensor.fluffy_battery-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': , + 'entity_id': 'sensor.fluffy_battery', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Battery', + 'platform': 'fressnapf_tracker', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': None, + 'unique_id': 'ABC123456_battery', + 'unit_of_measurement': '%', + }) +# --- +# name: test_state_entity_device_snapshots[sensor.fluffy_battery-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'battery', + 'friendly_name': 'Fluffy Battery', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.fluffy_battery', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '85', + }) +# --- diff --git a/tests/components/fressnapf_tracker/test_device_tracker.py b/tests/components/fressnapf_tracker/test_device_tracker.py index 0c0f9b5921d5e2..b1c5e8ca1c6779 100644 --- a/tests/components/fressnapf_tracker/test_device_tracker.py +++ b/tests/components/fressnapf_tracker/test_device_tracker.py @@ -1,38 +1,39 @@ """Test the Fressnapf Tracker device tracker platform.""" -from unittest.mock import AsyncMock, MagicMock +from collections.abc import AsyncGenerator +from unittest.mock import AsyncMock, MagicMock, patch from fressnapftracker import Tracker import pytest from syrupy.assertion import SnapshotAssertion -from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.const import STATE_UNAVAILABLE, Platform from homeassistant.core import HomeAssistant -from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers import entity_registry as er from tests.common import MockConfigEntry, snapshot_platform +@pytest.fixture(autouse=True) +async def platforms() -> AsyncGenerator[None]: + """Return the platforms to be loaded for this test.""" + with patch( + "homeassistant.components.fressnapf_tracker.PLATFORMS", + [Platform.DEVICE_TRACKER], + ): + yield + + @pytest.mark.usefixtures("init_integration") async def test_state_entity_device_snapshots( hass: HomeAssistant, entity_registry: er.EntityRegistry, - device_registry: dr.DeviceRegistry, mock_config_entry: MockConfigEntry, snapshot: SnapshotAssertion, ) -> None: """Test device tracker entity is created correctly.""" await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) - device_entries = dr.async_entries_for_config_entry( - device_registry, mock_config_entry.entry_id - ) - assert device_entries - for device_entry in device_entries: - assert device_entry == snapshot(name=f"{device_entry.name}-entry"), ( - f"device entry snapshot failed for {device_entry.name}" - ) - @pytest.mark.usefixtures("mock_auth_client") async def test_device_tracker_no_position( diff --git a/tests/components/fressnapf_tracker/test_init.py b/tests/components/fressnapf_tracker/test_init.py index d69a5180f9f6ce..1de77405886ee1 100644 --- a/tests/components/fressnapf_tracker/test_init.py +++ b/tests/components/fressnapf_tracker/test_init.py @@ -3,9 +3,11 @@ from unittest.mock import AsyncMock, MagicMock import pytest +from syrupy.assertion import SnapshotAssertion from homeassistant.config_entries import ConfigEntryState from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr from tests.common import MockConfigEntry @@ -59,3 +61,20 @@ async def test_setup_entry_api_error( await hass.async_block_till_done() assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY + + +@pytest.mark.usefixtures("init_integration") +async def test_state_entity_device_snapshots( + device_registry: dr.DeviceRegistry, + mock_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, +) -> None: + """Test sensor entity is created correctly.""" + device_entries = dr.async_entries_for_config_entry( + device_registry, mock_config_entry.entry_id + ) + assert device_entries + for device_entry in device_entries: + assert device_entry == snapshot(name=f"{device_entry.name}-entry"), ( + f"device entry snapshot failed for {device_entry.name}" + ) diff --git a/tests/components/fressnapf_tracker/test_sensor.py b/tests/components/fressnapf_tracker/test_sensor.py new file mode 100644 index 00000000000000..61a4ecccdc7db1 --- /dev/null +++ b/tests/components/fressnapf_tracker/test_sensor.py @@ -0,0 +1,33 @@ +"""Test the Fressnapf Tracker sensor platform.""" + +from collections.abc import AsyncGenerator +from unittest.mock import patch + +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from tests.common import MockConfigEntry, snapshot_platform + + +@pytest.fixture(autouse=True) +async def platforms() -> AsyncGenerator[None]: + """Return the platforms to be loaded for this test.""" + with patch( + "homeassistant.components.fressnapf_tracker.PLATFORMS", [Platform.SENSOR] + ): + yield + + +@pytest.mark.usefixtures("init_integration") +async def test_state_entity_device_snapshots( + hass: HomeAssistant, + entity_registry: er.EntityRegistry, + mock_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, +) -> None: + """Test sensor entity is created correctly.""" + await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)