diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index 9577381d92b..b4d01ef52e0 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -471,25 +471,37 @@ select_option = vol.All( @SELECTORS.register("select") class SelectSelector(Selector): - """Selector for an single-choice input select.""" + """Selector for an single or multi-choice input select.""" selector_type = "select" CONFIG_SCHEMA = vol.Schema( { - vol.Required("options"): vol.All( - vol.Any([str], [select_option]), vol.Length(min=1) - ) + vol.Required("options"): vol.All(vol.Any([str], [select_option])), + vol.Optional("multiple", default=False): cv.boolean, + vol.Optional("custom_value", default=False): cv.boolean, + vol.Optional("mode"): vol.In(("list", "dropdown")), } ) def __call__(self, data: Any) -> Any: """Validate the passed selection.""" - if isinstance(self.config["options"][0], str): - options = self.config["options"] - else: - options = [option["value"] for option in self.config["options"]] - return vol.In(options)(vol.Schema(str)(data)) + options = [] + if self.config["options"]: + if isinstance(self.config["options"][0], str): + options = self.config["options"] + else: + options = [option["value"] for option in self.config["options"]] + + parent_schema = vol.In(options) + if self.config["custom_value"]: + parent_schema = vol.Any(parent_schema, str) + + if not self.config["multiple"]: + return parent_schema(vol.Schema(str)(data)) + if not isinstance(data, list): + raise vol.Invalid("Value should be a list") + return [parent_schema(vol.Schema(str)(val)) for val in data] @SELECTORS.register("text") diff --git a/tests/helpers/test_selector.py b/tests/helpers/test_selector.py index 9e68af05487..bef95b056b4 100644 --- a/tests/helpers/test_selector.py +++ b/tests/helpers/test_selector.py @@ -246,7 +246,7 @@ def test_number_selector_schema(schema, valid_selections, invalid_selections): ), ) def test_number_selector_schema_error(schema): - """Test select selector.""" + """Test number selector.""" with pytest.raises(vol.Invalid): selector.validate_selector({"number": schema}) @@ -349,7 +349,7 @@ def test_text_selector_schema(schema, valid_selections, invalid_selections): ( {"options": ["red", "green", "blue"]}, ("red", "green", "blue"), - ("cat", 0, None), + ("cat", 0, None, ["red"]), ), ( { @@ -359,7 +359,36 @@ def test_text_selector_schema(schema, valid_selections, invalid_selections): ] }, ("red", "green"), - ("cat", 0, None), + ("cat", 0, None, ["red"]), + ), + ( + {"options": ["red", "green", "blue"], "multiple": True}, + (["red"], ["green", "blue"], []), + ("cat", 0, None, "red"), + ), + ( + { + "options": ["red", "green", "blue"], + "multiple": True, + "custom_value": True, + }, + (["red"], ["green", "blue"], ["red", "cat"], []), + ("cat", 0, None, "red"), + ), + ( + {"options": ["red", "green", "blue"], "custom_value": True}, + ("red", "green", "blue", "cat"), + (0, None, ["red"]), + ), + ( + {"options": [], "custom_value": True}, + ("red", "cat"), + (0, None, ["red"]), + ), + ( + {"options": [], "custom_value": True, "multiple": True}, + (["red"], ["green", "blue"], []), + (0, None, "red"), ), ), ) @@ -373,7 +402,6 @@ def test_select_selector_schema(schema, valid_selections, invalid_selections): ( {}, # Must have options {"options": {"hello": "World"}}, # Options must be a list - {"options": []}, # Must have at least option # Options must be strings or value / label pairs {"options": [{"hello": "World"}]}, # Options must all be of the same type