mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Add binary sensor platform to Habitica integration (#129613)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
5f36062ef3
commit
9253fa4471
@ -30,6 +30,7 @@ CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
|
||||
|
||||
PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.CALENDAR,
|
||||
Platform.SENSOR,
|
||||
|
85
homeassistant/components/habitica/binary_sensor.py
Normal file
85
homeassistant/components/habitica/binary_sensor.py
Normal file
@ -0,0 +1,85 @@
|
||||
"""Binary sensor platform for Habitica integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from enum import StrEnum
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import ASSETS_URL
|
||||
from .entity import HabiticaBase
|
||||
from .types import HabiticaConfigEntry
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class HabiticaBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||
"""Habitica Binary Sensor Description."""
|
||||
|
||||
value_fn: Callable[[dict[str, Any]], bool | None]
|
||||
entity_picture: Callable[[dict[str, Any]], str | None]
|
||||
|
||||
|
||||
class HabiticaBinarySensor(StrEnum):
|
||||
"""Habitica Entities."""
|
||||
|
||||
PENDING_QUEST = "pending_quest"
|
||||
|
||||
|
||||
def get_scroll_image_for_pending_quest_invitation(user: dict[str, Any]) -> str | None:
|
||||
"""Entity picture for pending quest invitation."""
|
||||
if user["party"]["quest"].get("key") and user["party"]["quest"]["RSVPNeeded"]:
|
||||
return f"inventory_quest_scroll_{user["party"]["quest"]["key"]}.png"
|
||||
return None
|
||||
|
||||
|
||||
BINARY_SENSOR_DESCRIPTIONS: tuple[HabiticaBinarySensorEntityDescription, ...] = (
|
||||
HabiticaBinarySensorEntityDescription(
|
||||
key=HabiticaBinarySensor.PENDING_QUEST,
|
||||
translation_key=HabiticaBinarySensor.PENDING_QUEST,
|
||||
value_fn=lambda user: user["party"]["quest"]["RSVPNeeded"],
|
||||
entity_picture=get_scroll_image_for_pending_quest_invitation,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: HabiticaConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the habitica binary sensors."""
|
||||
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
HabiticaBinarySensorEntity(coordinator, description)
|
||||
for description in BINARY_SENSOR_DESCRIPTIONS
|
||||
)
|
||||
|
||||
|
||||
class HabiticaBinarySensorEntity(HabiticaBase, BinarySensorEntity):
|
||||
"""Representation of a Habitica binary sensor."""
|
||||
|
||||
entity_description: HabiticaBinarySensorEntityDescription
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
"""If the binary sensor is on."""
|
||||
return self.entity_description.value_fn(self.coordinator.data.user)
|
||||
|
||||
@property
|
||||
def entity_picture(self) -> str | None:
|
||||
"""Return the entity picture to use in the frontend, if any."""
|
||||
if entity_picture := self.entity_description.entity_picture(
|
||||
self.coordinator.data.user
|
||||
):
|
||||
return f"{ASSETS_URL}{entity_picture}"
|
||||
return None
|
@ -135,6 +135,14 @@
|
||||
"on": "mdi:sleep"
|
||||
}
|
||||
}
|
||||
},
|
||||
"binary_sensor": {
|
||||
"pending_quest": {
|
||||
"default": "mdi:script-outline",
|
||||
"state": {
|
||||
"on": "mdi:script-text-outline"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
@ -38,6 +38,11 @@
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"pending_quest": {
|
||||
"name": "Pending quest invitation"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"run_cron": {
|
||||
"name": "Start my day"
|
||||
|
64
tests/components/habitica/fixtures/quest_invitation_off.json
Normal file
64
tests/components/habitica/fixtures/quest_invitation_off.json
Normal file
@ -0,0 +1,64 @@
|
||||
{
|
||||
"data": {
|
||||
"api_user": "test-api-user",
|
||||
"profile": { "name": "test-user" },
|
||||
"stats": {
|
||||
"buffs": {
|
||||
"str": 0,
|
||||
"int": 0,
|
||||
"per": 0,
|
||||
"con": 0,
|
||||
"stealth": 0,
|
||||
"streaks": false,
|
||||
"seafoam": false,
|
||||
"shinySeed": false,
|
||||
"snowball": false,
|
||||
"spookySparkles": false
|
||||
},
|
||||
"hp": 0,
|
||||
"mp": 50.89999999999998,
|
||||
"exp": 737,
|
||||
"gp": 137.62587214609795,
|
||||
"lvl": 38,
|
||||
"class": "wizard",
|
||||
"maxHealth": 50,
|
||||
"maxMP": 166,
|
||||
"toNextLevel": 880,
|
||||
"points": 5
|
||||
},
|
||||
"preferences": {
|
||||
"sleep": false,
|
||||
"automaticAllocation": true,
|
||||
"disableClasses": false
|
||||
},
|
||||
"flags": {
|
||||
"classSelected": true
|
||||
},
|
||||
"tasksOrder": {
|
||||
"rewards": ["5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b"],
|
||||
"todos": [
|
||||
"88de7cd9-af2b-49ce-9afd-bf941d87336b",
|
||||
"2f6fcabc-f670-4ec3-ba65-817e8deea490",
|
||||
"1aa3137e-ef72-4d1f-91ee-41933602f438",
|
||||
"86ea2475-d1b5-4020-bdcc-c188c7996afa"
|
||||
],
|
||||
"dailys": [
|
||||
"f21fa608-cfc6-4413-9fc7-0eb1b48ca43a",
|
||||
"bc1d1855-b2b8-4663-98ff-62e7b763dfc4",
|
||||
"e97659e0-2c42-4599-a7bb-00282adc410d",
|
||||
"564b9ac9-c53d-4638-9e7f-1cd96fe19baa",
|
||||
"f2c85972-1a19-4426-bc6d-ce3337b9d99f",
|
||||
"2c6d136c-a1c3-4bef-b7c4-fa980784b1e1"
|
||||
],
|
||||
"habits": ["1d147de6-5c02-4740-8e2f-71d3015a37f4"]
|
||||
},
|
||||
"party": {
|
||||
"quest": {
|
||||
"RSVPNeeded": false,
|
||||
"key": null
|
||||
}
|
||||
},
|
||||
"needsCron": true,
|
||||
"lastCron": "2024-09-21T22:01:55.586Z"
|
||||
}
|
||||
}
|
@ -52,6 +52,12 @@
|
||||
],
|
||||
"habits": ["1d147de6-5c02-4740-8e2f-71d3015a37f4"]
|
||||
},
|
||||
"party": {
|
||||
"quest": {
|
||||
"RSVPNeeded": true,
|
||||
"key": "dustbunnies"
|
||||
}
|
||||
},
|
||||
"needsCron": true,
|
||||
"lastCron": "2024-09-21T22:01:55.586Z"
|
||||
}
|
||||
|
48
tests/components/habitica/snapshots/test_binary_sensor.ambr
Normal file
48
tests/components/habitica/snapshots/test_binary_sensor.ambr
Normal file
@ -0,0 +1,48 @@
|
||||
# serializer version: 1
|
||||
# name: test_binary_sensors[binary_sensor.test_user_pending_quest_invitation-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'binary_sensor.test_user_pending_quest_invitation',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Pending quest invitation',
|
||||
'platform': 'habitica',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': <HabiticaBinarySensor.PENDING_QUEST: 'pending_quest'>,
|
||||
'unique_id': '00000000-0000-0000-0000-000000000000_pending_quest',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_binary_sensors[binary_sensor.test_user_pending_quest_invitation-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'entity_picture': 'https://habitica-assets.s3.amazonaws.com/mobileApp/images/inventory_quest_scroll_dustbunnies.png',
|
||||
'friendly_name': 'test-user Pending quest invitation',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.test_user_pending_quest_invitation',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
80
tests/components/habitica/test_binary_sensor.py
Normal file
80
tests/components/habitica/test_binary_sensor.py
Normal file
@ -0,0 +1,80 @@
|
||||
"""Tests for the Habitica binary sensor platform."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.habitica.const import ASSETS_URL, DEFAULT_URL, DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import STATE_OFF, STATE_ON, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry, load_json_object_fixture, snapshot_platform
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def binary_sensor_only() -> Generator[None]:
|
||||
"""Enable only the binarty sensor platform."""
|
||||
with patch(
|
||||
"homeassistant.components.habitica.PLATFORMS",
|
||||
[Platform.BINARY_SENSOR],
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_habitica")
|
||||
async def test_binary_sensors(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test setup of the Habitica binary sensor platform."""
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("fixture", "entity_state", "entity_picture"),
|
||||
[
|
||||
("user", STATE_ON, f"{ASSETS_URL}inventory_quest_scroll_dustbunnies.png"),
|
||||
("quest_invitation_off", STATE_OFF, None),
|
||||
],
|
||||
)
|
||||
async def test_pending_quest_states(
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
fixture: str,
|
||||
entity_state: str,
|
||||
entity_picture: str | None,
|
||||
) -> None:
|
||||
"""Test states of pending quest sensor."""
|
||||
|
||||
aioclient_mock.get(
|
||||
f"{DEFAULT_URL}/api/v3/user",
|
||||
json=load_json_object_fixture(f"{fixture}.json", DOMAIN),
|
||||
)
|
||||
aioclient_mock.get(f"{DEFAULT_URL}/api/v3/tasks/user", json={"data": []})
|
||||
|
||||
config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
assert (
|
||||
state := hass.states.get("binary_sensor.test_user_pending_quest_invitation")
|
||||
)
|
||||
assert state.state == entity_state
|
||||
assert state.attributes.get("entity_picture") == entity_picture
|
Loading…
x
Reference in New Issue
Block a user