mirror of
https://github.com/home-assistant/core.git
synced 2025-12-20 02:48:57 +00:00
Add support for choose selector (#159412)
This commit is contained in:
@@ -418,6 +418,88 @@ class BooleanSelector(Selector[BooleanSelectorConfig]):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def reject_nested_choose_selector(config: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""Reject nested choose selectors."""
|
||||||
|
for choice in config.get("choices", {}).values():
|
||||||
|
if isinstance(choice["selector"], dict):
|
||||||
|
selector_type, _ = _get_selector_type_and_class(choice["selector"])
|
||||||
|
if selector_type == "choose":
|
||||||
|
raise vol.Invalid("Nested choose selectors are not allowed")
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
class ChooseSelectorChoiceConfig(TypedDict, total=False):
|
||||||
|
"""Class to represent a choose selector choice config."""
|
||||||
|
|
||||||
|
selector: Required[Selector | dict[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
|
class ChooseSelectorConfig(BaseSelectorConfig):
|
||||||
|
"""Class to represent a choose selector config."""
|
||||||
|
|
||||||
|
choices: Required[dict[str, ChooseSelectorChoiceConfig]]
|
||||||
|
translation_key: str
|
||||||
|
|
||||||
|
|
||||||
|
@SELECTORS.register("choose")
|
||||||
|
class ChooseSelector(Selector[ChooseSelectorConfig]):
|
||||||
|
"""Selector allowing to choose one of several selectors."""
|
||||||
|
|
||||||
|
selector_type = "choose"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.All(
|
||||||
|
make_selector_config_schema(
|
||||||
|
{
|
||||||
|
vol.Required("choices"): {
|
||||||
|
str: {
|
||||||
|
vol.Required("selector"): vol.Any(Selector, validate_selector),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
vol.Optional("translation_key"): cv.string,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
reject_nested_choose_selector,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, config: ChooseSelectorConfig | None = None) -> None:
|
||||||
|
"""Instantiate a selector."""
|
||||||
|
super().__init__(config)
|
||||||
|
|
||||||
|
def serialize(self) -> dict[str, dict[str, ChooseSelectorConfig]]:
|
||||||
|
"""Serialize ChooseSelectorConfig for voluptuous_serialize."""
|
||||||
|
_config = deepcopy(self.config)
|
||||||
|
if "choices" in _config:
|
||||||
|
for choice in _config["choices"].values():
|
||||||
|
if isinstance(choice["selector"], Selector):
|
||||||
|
choice["selector"] = choice["selector"].serialize()["selector"]
|
||||||
|
return {"selector": {self.selector_type: _config}}
|
||||||
|
|
||||||
|
def __call__(self, data: Any) -> Any:
|
||||||
|
"""Validate the passed selection."""
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
for choice in self.config["choices"].values():
|
||||||
|
try:
|
||||||
|
validated = selector(choice["selector"])(data) # type: ignore[operator]
|
||||||
|
except (vol.Invalid, vol.MultipleInvalid):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return validated
|
||||||
|
|
||||||
|
raise vol.Invalid("Value does not match any choice selector")
|
||||||
|
|
||||||
|
if "active_choice" not in data:
|
||||||
|
raise vol.Invalid("Missing active_choice key")
|
||||||
|
if data["active_choice"] not in data:
|
||||||
|
raise vol.Invalid("Missing value for active choice")
|
||||||
|
|
||||||
|
choices = self.config.get("choices", {})
|
||||||
|
if data["active_choice"] not in choices:
|
||||||
|
raise vol.Invalid("Invalid active_choice key")
|
||||||
|
return selector(choices[data["active_choice"]]["selector"])( # type: ignore[operator]
|
||||||
|
data[data["active_choice"]]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ColorRGBSelectorConfig(BaseSelectorConfig):
|
class ColorRGBSelectorConfig(BaseSelectorConfig):
|
||||||
"""Class to represent a color RGB selector config."""
|
"""Class to represent a color RGB selector config."""
|
||||||
|
|
||||||
@@ -1223,14 +1305,10 @@ class ObjectSelector(Selector[ObjectSelectorConfig]):
|
|||||||
_config = deepcopy(self.config)
|
_config = deepcopy(self.config)
|
||||||
if "fields" in _config:
|
if "fields" in _config:
|
||||||
for field_items in _config["fields"].values():
|
for field_items in _config["fields"].values():
|
||||||
if isinstance(field_items["selector"], ObjectSelector):
|
if isinstance(field_items["selector"], Selector):
|
||||||
field_items["selector"] = field_items["selector"].serialize()
|
field_items["selector"] = field_items["selector"].serialize()[
|
||||||
elif isinstance(field_items["selector"], Selector):
|
"selector"
|
||||||
field_items["selector"] = {
|
]
|
||||||
field_items["selector"].selector_type: field_items[
|
|
||||||
"selector"
|
|
||||||
].config
|
|
||||||
}
|
|
||||||
return {"selector": {self.selector_type: _config}}
|
return {"selector": {self.selector_type: _config}}
|
||||||
|
|
||||||
def __call__(self, data: Any) -> Any:
|
def __call__(self, data: Any) -> Any:
|
||||||
|
|||||||
@@ -323,6 +323,10 @@ def gen_strings_schema(config: Config, integration: Integration) -> vol.Schema:
|
|||||||
),
|
),
|
||||||
slug_validator=vol.Any("_", cv.slug),
|
slug_validator=vol.Any("_", cv.slug),
|
||||||
),
|
),
|
||||||
|
vol.Optional("choices"): cv.schema_with_slug_keys(
|
||||||
|
translation_value_validator,
|
||||||
|
slug_validator=translation_key_validator,
|
||||||
|
),
|
||||||
vol.Optional("options"): gen_data_entry_schema(
|
vol.Optional("options"): gen_data_entry_schema(
|
||||||
config=config,
|
config=config,
|
||||||
integration=integration,
|
integration=integration,
|
||||||
|
|||||||
@@ -1,4 +1,163 @@
|
|||||||
# serializer version: 1
|
# serializer version: 1
|
||||||
|
# name: test_choose_selector_serialize
|
||||||
|
dict({
|
||||||
|
'selector': dict({
|
||||||
|
'choose': dict({
|
||||||
|
'choices': dict({
|
||||||
|
'entity_choice': dict({
|
||||||
|
'selector': dict({
|
||||||
|
'entity': dict({
|
||||||
|
'domain': list([
|
||||||
|
'light',
|
||||||
|
]),
|
||||||
|
'multiple': False,
|
||||||
|
'reorder': False,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'number_choice': dict({
|
||||||
|
'selector': dict({
|
||||||
|
'number': dict({
|
||||||
|
'max': 100.0,
|
||||||
|
'min': 0.0,
|
||||||
|
'mode': 'slider',
|
||||||
|
'step': 1.0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'object_choice': dict({
|
||||||
|
'selector': dict({
|
||||||
|
'object': dict({
|
||||||
|
'description_field': 'percentage',
|
||||||
|
'fields': dict({
|
||||||
|
'name': dict({
|
||||||
|
'required': True,
|
||||||
|
'selector': dict({
|
||||||
|
'text': dict({
|
||||||
|
'multiline': False,
|
||||||
|
'multiple': False,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'percentage': dict({
|
||||||
|
'selector': dict({
|
||||||
|
'number': dict({
|
||||||
|
'mode': 'box',
|
||||||
|
'step': 1.0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'label_field': 'name',
|
||||||
|
'multiple': False,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'text_choice': dict({
|
||||||
|
'selector': dict({
|
||||||
|
'text': dict({
|
||||||
|
'multiline': True,
|
||||||
|
'multiple': False,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_choose_selector_serialize.1
|
||||||
|
dict({
|
||||||
|
'selector': dict({
|
||||||
|
'choose': dict({
|
||||||
|
'choices': dict({
|
||||||
|
'number_choice': dict({
|
||||||
|
'selector': dict({
|
||||||
|
'number': dict({
|
||||||
|
'max': 100.0,
|
||||||
|
'min': 0.0,
|
||||||
|
'mode': 'slider',
|
||||||
|
'step': 1.0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'object_choice': dict({
|
||||||
|
'selector': dict({
|
||||||
|
'object': dict({
|
||||||
|
'description_field': 'percentage',
|
||||||
|
'fields': dict({
|
||||||
|
'name': dict({
|
||||||
|
'required': True,
|
||||||
|
'selector': dict({
|
||||||
|
'text': dict({
|
||||||
|
'multiline': False,
|
||||||
|
'multiple': False,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'object': dict({
|
||||||
|
'selector': dict({
|
||||||
|
'object': dict({
|
||||||
|
'fields': dict({
|
||||||
|
'choose': dict({
|
||||||
|
'required': True,
|
||||||
|
'selector': dict({
|
||||||
|
'choose': dict({
|
||||||
|
'choices': dict({
|
||||||
|
'number_choice': dict({
|
||||||
|
'selector': dict({
|
||||||
|
'number': dict({
|
||||||
|
'max': 100.0,
|
||||||
|
'min': 0.0,
|
||||||
|
'mode': 'slider',
|
||||||
|
'step': 1.0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'text_choice': dict({
|
||||||
|
'selector': dict({
|
||||||
|
'text': dict({
|
||||||
|
'multiline': True,
|
||||||
|
'multiple': False,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'multiple': False,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'percentage': dict({
|
||||||
|
'selector': dict({
|
||||||
|
'number': dict({
|
||||||
|
'mode': 'box',
|
||||||
|
'step': 1.0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'label_field': 'name',
|
||||||
|
'multiple': False,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'text_choice': dict({
|
||||||
|
'selector': dict({
|
||||||
|
'text': dict({
|
||||||
|
'multiline': True,
|
||||||
|
'multiple': False,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_nested_object_selectors
|
# name: test_nested_object_selectors
|
||||||
dict({
|
dict({
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
@@ -16,64 +175,60 @@
|
|||||||
}),
|
}),
|
||||||
'object': dict({
|
'object': dict({
|
||||||
'selector': dict({
|
'selector': dict({
|
||||||
'selector': dict({
|
'object': dict({
|
||||||
'object': dict({
|
'description_field': 'other_name',
|
||||||
'description_field': 'other_name',
|
'fields': dict({
|
||||||
'fields': dict({
|
'new_object': dict({
|
||||||
'new_object': dict({
|
'required': True,
|
||||||
'required': True,
|
'selector': dict({
|
||||||
'selector': dict({
|
'object': dict({
|
||||||
'selector': dict({
|
'description_field': 'description',
|
||||||
'object': dict({
|
'fields': dict({
|
||||||
'description_field': 'description',
|
'description': dict({
|
||||||
'fields': dict({
|
'required': True,
|
||||||
'description': dict({
|
'selector': dict({
|
||||||
'required': True,
|
'text': dict({
|
||||||
'selector': dict({
|
'multiline': False,
|
||||||
'text': dict({
|
'multiple': False,
|
||||||
'multiline': False,
|
}),
|
||||||
'multiple': False,
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
'title': dict({
|
||||||
}),
|
'required': True,
|
||||||
'title': dict({
|
'selector': dict({
|
||||||
'required': True,
|
'text': dict({
|
||||||
'selector': dict({
|
'multiline': False,
|
||||||
'text': dict({
|
'multiple': False,
|
||||||
'multiline': False,
|
|
||||||
'multiple': False,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'label_field': 'title',
|
|
||||||
'multiple': False,
|
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
'label_field': 'title',
|
||||||
}),
|
'multiple': False,
|
||||||
'no_name': dict({
|
}),
|
||||||
'required': True,
|
}),
|
||||||
'selector': dict({
|
}),
|
||||||
'text': dict({
|
'no_name': dict({
|
||||||
'multiline': False,
|
'required': True,
|
||||||
'multiple': False,
|
'selector': dict({
|
||||||
}),
|
'text': dict({
|
||||||
}),
|
'multiline': False,
|
||||||
}),
|
'multiple': False,
|
||||||
'other_name': dict({
|
}),
|
||||||
'required': True,
|
}),
|
||||||
'selector': dict({
|
}),
|
||||||
'text': dict({
|
'other_name': dict({
|
||||||
'multiline': False,
|
'required': True,
|
||||||
'multiple': False,
|
'selector': dict({
|
||||||
}),
|
'text': dict({
|
||||||
|
'multiline': False,
|
||||||
|
'multiple': False,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'label_field': 'no_name',
|
|
||||||
'multiple': False,
|
|
||||||
}),
|
}),
|
||||||
|
'label_field': 'no_name',
|
||||||
|
'multiple': False,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -513,6 +513,259 @@ def test_boolean_selector_schema(schema, valid_selections, invalid_selections) -
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("schema", "valid_selections", "invalid_selections"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"choices": {
|
||||||
|
"text_choice": {"selector": {"text": {}}},
|
||||||
|
"number_choice": {"selector": {"number": {"min": 0, "max": 100}}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(
|
||||||
|
# Direct value matching text selector
|
||||||
|
"some text",
|
||||||
|
# Direct value matching number selector
|
||||||
|
42,
|
||||||
|
# Explicit choice with active_choice key
|
||||||
|
{"active_choice": "text_choice", "text_choice": "hello world"},
|
||||||
|
{"active_choice": "number_choice", "number_choice": 50},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
# None doesn't match any selector
|
||||||
|
None,
|
||||||
|
# Missing active_choice key
|
||||||
|
{"text_choice": "hello"},
|
||||||
|
# Invalid active_choice key
|
||||||
|
{"active_choice": "invalid", "invalid": "value"},
|
||||||
|
# Missing value for active choice
|
||||||
|
{"active_choice": "text_choice"},
|
||||||
|
# Wrong value type for number selector
|
||||||
|
{"active_choice": "number_choice", "number_choice": "not a number"},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"choices": {
|
||||||
|
"entity": {"selector": {"entity": {}}},
|
||||||
|
"device": {"selector": {"device": {}}},
|
||||||
|
"text": {"selector": {"text": {}}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(
|
||||||
|
# Direct value matching entity selector
|
||||||
|
"sensor.abc123",
|
||||||
|
FAKE_UUID,
|
||||||
|
# Explicit choice
|
||||||
|
{"active_choice": "entity", "entity": "light.bedroom"},
|
||||||
|
{"active_choice": "device", "device": "device123"},
|
||||||
|
{"active_choice": "text", "text": "some text"},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
None,
|
||||||
|
# List doesn't match any selector
|
||||||
|
["sensor.abc", "light.def"],
|
||||||
|
# Missing active_choice key
|
||||||
|
{"entity": "sensor.abc"},
|
||||||
|
# Invalid active_choice
|
||||||
|
{"active_choice": "area", "area": "area123"},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_choose_selector_schema(schema, valid_selections, invalid_selections) -> None:
|
||||||
|
"""Test choose selector."""
|
||||||
|
|
||||||
|
def get_selected_value(data):
|
||||||
|
"""Get the selected value from the input."""
|
||||||
|
if isinstance(data, dict) and "active_choice" in data:
|
||||||
|
return data[data["active_choice"]]
|
||||||
|
return data
|
||||||
|
|
||||||
|
_test_selector(
|
||||||
|
"choose", schema, valid_selections, invalid_selections, get_selected_value
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("schema", "raises"),
|
||||||
|
[
|
||||||
|
# Valid schemas
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"choices": {
|
||||||
|
"text": {"selector": {"text": {}}},
|
||||||
|
"number": {"selector": {"number": {}}},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
does_not_raise(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"choices": {
|
||||||
|
"text": {"selector": selector.TextSelector()},
|
||||||
|
"number": {"selector": selector.NumberSelector()},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
does_not_raise(),
|
||||||
|
),
|
||||||
|
# Invalid schemas
|
||||||
|
(
|
||||||
|
{}, # Missing required 'choices' key
|
||||||
|
pytest.raises(vol.Invalid),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"choices": {} # Empty choices dict
|
||||||
|
},
|
||||||
|
does_not_raise(), # Empty dict is technically valid
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"choices": {
|
||||||
|
"text": {} # Missing required 'selector' key in choice
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pytest.raises(vol.Invalid),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"choices": {
|
||||||
|
"invalid": {"selector": {"not_exist": {}}} # Invalid selector type
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pytest.raises(vol.Invalid),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"choices": "not a dict" # choices should be a dict
|
||||||
|
},
|
||||||
|
pytest.raises(vol.Invalid),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"choices": {
|
||||||
|
"invalid": {
|
||||||
|
"selector": {
|
||||||
|
"choose": {
|
||||||
|
"choices": {
|
||||||
|
"text": {"selector": {"text": {}}},
|
||||||
|
"number": {"selector": {"number": {}}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} # Nested choose is not allowed
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pytest.raises(vol.Invalid),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_choose_selector_validate_schema(
|
||||||
|
schema: dict, raises: AbstractContextManager
|
||||||
|
) -> None:
|
||||||
|
"""Test choose selector schema validation."""
|
||||||
|
with raises:
|
||||||
|
selector.validate_selector({"choose": schema})
|
||||||
|
|
||||||
|
|
||||||
|
def test_choose_selector_serialize(snapshot: SnapshotAssertion) -> None:
|
||||||
|
"""Test choose selector serialization."""
|
||||||
|
# Test with dict-based selectors
|
||||||
|
choose_selector = selector.ChooseSelector(
|
||||||
|
{
|
||||||
|
"choices": {
|
||||||
|
"text_choice": {"selector": {"text": {"multiline": True}}},
|
||||||
|
"number_choice": {"selector": {"number": {"min": 0, "max": 100}}},
|
||||||
|
"entity_choice": {"selector": {"entity": {"domain": "light"}}},
|
||||||
|
"object_choice": {
|
||||||
|
"selector": {
|
||||||
|
"object": {
|
||||||
|
"fields": {
|
||||||
|
"name": {
|
||||||
|
"required": True,
|
||||||
|
"selector": {"text": {}},
|
||||||
|
},
|
||||||
|
"percentage": {
|
||||||
|
"selector": {"number": {}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"multiple": False,
|
||||||
|
"label_field": "name",
|
||||||
|
"description_field": "percentage",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert choose_selector.serialize() == snapshot
|
||||||
|
|
||||||
|
# Test with Selector object instances
|
||||||
|
choose_selector_objects = selector.ChooseSelector(
|
||||||
|
{
|
||||||
|
"choices": {
|
||||||
|
"text_choice": {"selector": selector.TextSelector({"multiline": True})},
|
||||||
|
"number_choice": {
|
||||||
|
"selector": selector.NumberSelector({"min": 0, "max": 100})
|
||||||
|
},
|
||||||
|
"object_choice": {
|
||||||
|
"selector": selector.ObjectSelector(
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"name": {
|
||||||
|
"required": True,
|
||||||
|
"selector": selector.TextSelector({}),
|
||||||
|
},
|
||||||
|
"percentage": {
|
||||||
|
"selector": selector.NumberSelector({}),
|
||||||
|
},
|
||||||
|
"object": {
|
||||||
|
"selector": selector.ObjectSelector(
|
||||||
|
{
|
||||||
|
"fields": {
|
||||||
|
"choose": {
|
||||||
|
"required": True,
|
||||||
|
"selector": selector.ChooseSelector(
|
||||||
|
{
|
||||||
|
"choices": {
|
||||||
|
"text_choice": {
|
||||||
|
"selector": selector.TextSelector(
|
||||||
|
{
|
||||||
|
"multiline": True
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"number_choice": {
|
||||||
|
"selector": selector.NumberSelector(
|
||||||
|
{
|
||||||
|
"min": 0,
|
||||||
|
"max": 100,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"multiple": False,
|
||||||
|
"label_field": "name",
|
||||||
|
"description_field": "percentage",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert choose_selector_objects.serialize() == snapshot
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("schema", "valid_selections", "invalid_selections"),
|
("schema", "valid_selections", "invalid_selections"),
|
||||||
[
|
[
|
||||||
|
|||||||
Reference in New Issue
Block a user