From ee59001f723c28ea62e34f820f6674d5f5da9bec Mon Sep 17 00:00:00 2001 From: Mattheinrichs Date: Wed, 8 Jul 2026 16:05:28 -0500 Subject: [PATCH] Add navigation destination text entity to Tessie integration (#169155) --- homeassistant/components/tessie/__init__.py | 1 + homeassistant/components/tessie/icons.json | 5 ++ homeassistant/components/tessie/strings.json | 5 ++ homeassistant/components/tessie/text.py | 42 +++++++++++ .../tessie/snapshots/test_text.ambr | 60 +++++++++++++++ tests/components/tessie/test_text.py | 75 +++++++++++++++++++ 6 files changed, 188 insertions(+) create mode 100644 homeassistant/components/tessie/text.py create mode 100644 tests/components/tessie/snapshots/test_text.ambr create mode 100644 tests/components/tessie/test_text.py diff --git a/homeassistant/components/tessie/__init__.py b/homeassistant/components/tessie/__init__.py index 7237255375fa..30042482cb1b 100644 --- a/homeassistant/components/tessie/__init__.py +++ b/homeassistant/components/tessie/__init__.py @@ -50,6 +50,7 @@ PLATFORMS = [ Platform.SELECT, Platform.SENSOR, Platform.SWITCH, + Platform.TEXT, Platform.UPDATE, ] diff --git a/homeassistant/components/tessie/icons.json b/homeassistant/components/tessie/icons.json index b90af3ddff03..4ba74f3eb330 100644 --- a/homeassistant/components/tessie/icons.json +++ b/homeassistant/components/tessie/icons.json @@ -291,6 +291,11 @@ "vehicle_state_valet_mode": { "default": "mdi:bow-tie" } + }, + "text": { + "navigation_destination": { + "default": "mdi:map-marker" + } } } } diff --git a/homeassistant/components/tessie/strings.json b/homeassistant/components/tessie/strings.json index 9be7124a85c8..36cce2ef326c 100644 --- a/homeassistant/components/tessie/strings.json +++ b/homeassistant/components/tessie/strings.json @@ -612,6 +612,11 @@ "name": "Valet mode" } }, + "text": { + "navigation_destination": { + "name": "Navigation destination" + } + }, "update": { "update": { "name": "[%key:component::update::title%]" diff --git a/homeassistant/components/tessie/text.py b/homeassistant/components/tessie/text.py new file mode 100644 index 000000000000..0168c579a48b --- /dev/null +++ b/homeassistant/components/tessie/text.py @@ -0,0 +1,42 @@ +"""Text platform for Tessie integration.""" + +from typing import override + +from homeassistant.components.text import TextEntity, TextMode +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback + +from . import TessieConfigEntry +from .entity import TessieEntity +from .models import TessieVehicleData + +PARALLEL_UPDATES = 0 + + +async def async_setup_entry( + hass: HomeAssistant, + entry: TessieConfigEntry, + async_add_entities: AddConfigEntryEntitiesCallback, +) -> None: + """Set up the Tessie Text platform from a config entry.""" + async_add_entities( + TessieNavigationTextEntity(vehicle) for vehicle in entry.runtime_data.vehicles + ) + + +class TessieNavigationTextEntity(TessieEntity, TextEntity): + """Text entity to send a navigation destination to the vehicle.""" + + _attr_mode = TextMode.TEXT + _attr_native_max = 255 + _attr_native_min = 1 + _attr_native_value: str | None = None + + def __init__(self, vehicle: TessieVehicleData) -> None: + """Initialize the navigation text entity.""" + super().__init__(vehicle, "navigation_destination") + + @override + async def async_set_value(self, value: str) -> None: + """Send a navigation destination to the vehicle.""" + await self.run(self.api.navigation_request(value)) diff --git a/tests/components/tessie/snapshots/test_text.ambr b/tests/components/tessie/snapshots/test_text.ambr new file mode 100644 index 000000000000..4064adbbbf86 --- /dev/null +++ b/tests/components/tessie/snapshots/test_text.ambr @@ -0,0 +1,60 @@ +# serializer version: 1 +# name: test_text_entities[text.test_navigation_destination-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': dict({ + : 255, + : 1, + : , + : None, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'text', + 'entity_category': None, + 'entity_id': 'text.test_navigation_destination', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Navigation destination', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Navigation destination', + 'platform': 'tessie', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'navigation_destination', + 'unique_id': 'VINVINVIN-navigation_destination', + 'unit_of_measurement': None, + }) +# --- +# name: test_text_entities[text.test_navigation_destination-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Test Navigation destination', + : 255, + : 1, + : , + : None, + }), + 'context': , + 'entity_id': 'text.test_navigation_destination', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- diff --git a/tests/components/tessie/test_text.py b/tests/components/tessie/test_text.py new file mode 100644 index 000000000000..e19ae0e32be6 --- /dev/null +++ b/tests/components/tessie/test_text.py @@ -0,0 +1,75 @@ +"""Test the Tessie text platform.""" + +from unittest.mock import patch + +import pytest +from syrupy.assertion import SnapshotAssertion + +from homeassistant.components.text import ( + ATTR_VALUE, + DOMAIN as TEXT_DOMAIN, + SERVICE_SET_VALUE, +) +from homeassistant.const import ATTR_ENTITY_ID, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import entity_registry as er + +from .common import ERROR_UNKNOWN, assert_entities, setup_platform + +NAVIGATION_ENTITY_ID = "text.test_navigation_destination" + + +async def test_text_entities( + hass: HomeAssistant, + snapshot: SnapshotAssertion, + entity_registry: er.EntityRegistry, +) -> None: + """Test that the navigation text entity is set up correctly.""" + entry = await setup_platform(hass, [Platform.TEXT]) + assert_entities(hass, entry.entry_id, entity_registry, snapshot) + + +async def test_set_navigation_destination(hass: HomeAssistant) -> None: + """Test sending a navigation destination to the vehicle.""" + await setup_platform(hass, [Platform.TEXT]) + + with patch( + "tesla_fleet_api.tessie.Vehicle.navigation_request", + ) as mock_nav: + await hass.services.async_call( + TEXT_DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: NAVIGATION_ENTITY_ID, + ATTR_VALUE: "1 Infinite Loop, Cupertino, CA", + }, + blocking=True, + ) + mock_nav.assert_called_once_with("1 Infinite Loop, Cupertino, CA") + + +async def test_set_navigation_destination_error(hass: HomeAssistant) -> None: + """Test that a transport error is translated to HomeAssistantError.""" + await setup_platform(hass, [Platform.TEXT]) + + with ( + patch( + "tesla_fleet_api.tessie.Vehicle.navigation_request", + side_effect=ERROR_UNKNOWN, + ) as mock_nav, + pytest.raises(HomeAssistantError) as error, + ): + await hass.services.async_call( + TEXT_DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: NAVIGATION_ENTITY_ID, + ATTR_VALUE: "Times Square, New York", + }, + blocking=True, + ) + + mock_nav.assert_called_once() + assert error.value.translation_domain == "tessie" + assert error.value.translation_key == "cannot_connect"