From 4a93b4a4b4adecbf586cedacaab76fef8f2510d0 Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Tue, 2 Apr 2024 10:43:14 +0200 Subject: [PATCH] Add floor selector (#114614) --- homeassistant/helpers/selector.py | 42 +++++++++++++++++++ tests/helpers/test_selector.py | 67 +++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index 938cc6a9246..c4db601fac6 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -844,6 +844,48 @@ class EntitySelector(Selector[EntitySelectorConfig]): return cast(list, vol.Schema([validate])(data)) # Output is a list +class FloorSelectorConfig(TypedDict, total=False): + """Class to represent an floor selector config.""" + + entity: EntityFilterSelectorConfig | list[EntityFilterSelectorConfig] + device: DeviceFilterSelectorConfig | list[DeviceFilterSelectorConfig] + multiple: bool + + +@SELECTORS.register("floor") +class FloorSelector(Selector[AreaSelectorConfig]): + """Selector of a single or list of floors.""" + + selector_type = "floor" + + CONFIG_SCHEMA = vol.Schema( + { + vol.Optional("entity"): vol.All( + cv.ensure_list, + [ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA], + ), + vol.Optional("device"): vol.All( + cv.ensure_list, + [DEVICE_FILTER_SELECTOR_CONFIG_SCHEMA], + ), + vol.Optional("multiple", default=False): cv.boolean, + } + ) + + def __init__(self, config: FloorSelectorConfig | None = None) -> None: + """Instantiate a selector.""" + super().__init__(config) + + def __call__(self, data: Any) -> str | list[str]: + """Validate the passed selection.""" + if not self.config["multiple"]: + floor_id: str = vol.Schema(str)(data) + return floor_id + if not isinstance(data, list): + raise vol.Invalid("Value should be a list") + return [vol.Schema(str)(val) for val in data] + + class IconSelectorConfig(TypedDict, total=False): """Class to represent an icon selector config.""" diff --git a/tests/helpers/test_selector.py b/tests/helpers/test_selector.py index 0dc7e570fc5..8864edc7386 100644 --- a/tests/helpers/test_selector.py +++ b/tests/helpers/test_selector.py @@ -1158,3 +1158,70 @@ def test_qr_code_selector_schema(schema, valid_selections, invalid_selections) - def test_label_selector_schema(schema, valid_selections, invalid_selections) -> None: """Test label selector.""" _test_selector("label", schema, valid_selections, invalid_selections) + + +@pytest.mark.parametrize( + ("schema", "valid_selections", "invalid_selections"), + [ + ({}, ("abc123",), (None,)), + ({"entity": {}}, ("abc123",), (None,)), + ({"entity": {"domain": "light"}}, ("abc123",), (None,)), + ( + {"entity": {"domain": "binary_sensor", "device_class": "motion"}}, + ("abc123",), + (None,), + ), + ( + { + "entity": { + "domain": "binary_sensor", + "device_class": "motion", + "integration": "demo", + } + }, + ("abc123",), + (None,), + ), + ( + { + "entity": [ + {"domain": "light"}, + {"domain": "binary_sensor", "device_class": "motion"}, + ] + }, + ("abc123",), + (None,), + ), + ( + {"device": {"integration": "demo", "model": "mock-model"}}, + ("abc123",), + (None,), + ), + ( + { + "device": [ + {"integration": "demo", "model": "mock-model"}, + {"integration": "other-demo", "model": "other-mock-model"}, + ] + }, + ("abc123",), + (None,), + ), + ( + { + "entity": {"domain": "binary_sensor", "device_class": "motion"}, + "device": {"integration": "demo", "model": "mock-model"}, + }, + ("abc123",), + (None,), + ), + ( + {"multiple": True}, + ((["abc123", "def456"],)), + (None, "abc123", ["abc123", None]), + ), + ], +) +def test_floor_selector_schema(schema, valid_selections, invalid_selections) -> None: + """Test floor selector.""" + _test_selector("floor", schema, valid_selections, invalid_selections)