diff --git a/homeassistant/components/lifx/services.py b/homeassistant/components/lifx/services.py index 2e5da5f11489..a522e1364203 100644 --- a/homeassistant/components/lifx/services.py +++ b/homeassistant/components/lifx/services.py @@ -115,8 +115,13 @@ LIFX_EFFECT_SCHEMA = { LIFX_EFFECT_PULSE_SCHEMA = cv.make_entity_service_schema( { **LIFX_EFFECT_SCHEMA, - probatio.Exclusive(ATTR_BRIGHTNESS, ATTR_BRIGHTNESS): VALID_BRIGHTNESS, - probatio.Exclusive(ATTR_BRIGHTNESS_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_PCT, + # A brightness of zero would pulse to black + probatio.Exclusive(ATTR_BRIGHTNESS, ATTR_BRIGHTNESS): probatio.All( + VALID_BRIGHTNESS, probatio.Clamp(min=1) + ), + probatio.Exclusive(ATTR_BRIGHTNESS_PCT, ATTR_BRIGHTNESS): probatio.All( + VALID_BRIGHTNESS_PCT, probatio.Clamp(min=1) + ), probatio.Exclusive(ATTR_COLOR_NAME, COLOR_GROUP): cv.string, probatio.Exclusive(ATTR_RGB_COLOR, COLOR_GROUP): probatio.All( probatio.Coerce(tuple), probatio.ExactSequence((cv.byte, cv.byte, cv.byte)) @@ -152,11 +157,12 @@ LIFX_EFFECT_COLORLOOP_SCHEMA = cv.make_entity_service_schema( **LIFX_EFFECT_SCHEMA, probatio.Exclusive(ATTR_BRIGHTNESS, ATTR_BRIGHTNESS): VALID_BRIGHTNESS, probatio.Exclusive(ATTR_BRIGHTNESS_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_PCT, + # A saturation of zero switches the bulb to color temperature mode ATTR_SATURATION_MAX: probatio.All( - probatio.Coerce(int), probatio.Clamp(min=0, max=100) + probatio.Coerce(int), probatio.Clamp(min=1, max=100) ), ATTR_SATURATION_MIN: probatio.All( - probatio.Coerce(int), probatio.Clamp(min=0, max=100) + probatio.Coerce(int), probatio.Clamp(min=1, max=100) ), ATTR_PERIOD: probatio.All(probatio.Coerce(float), probatio.Clamp(min=0.05)), ATTR_CHANGE: probatio.All( diff --git a/homeassistant/components/lifx/services.yaml b/homeassistant/components/lifx/services.yaml index 38deb806bd71..bf72102f7c9e 100644 --- a/homeassistant/components/lifx/services.yaml +++ b/homeassistant/components/lifx/services.yaml @@ -186,7 +186,6 @@ effect_move: - left theme: example: exciting - default: exciting selector: select: mode: dropdown diff --git a/tests/components/lifx/test_light.py b/tests/components/lifx/test_light.py index 89ba9769a1b9..2bfa1567c5a7 100644 --- a/tests/components/lifx/test_light.py +++ b/tests/components/lifx/test_light.py @@ -341,6 +341,33 @@ async def test_effect_pulse_reads_color_before_merging( assert effect.color == HSBK(120.0, 1.0, 128 / 255, 3500) +@pytest.mark.parametrize( + ("brightness", "expected"), + [ + pytest.param({ATTR_BRIGHTNESS: 0}, 1 / 255, id="brightness"), + pytest.param({ATTR_BRIGHTNESS_PCT: 0}, 0.01, id="brightness_pct"), + ], +) +async def test_effect_pulse_clamps_brightness_above_zero( + hass: HomeAssistant, + mock_effect_conductor: MagicMock, + brightness: dict[str, int], + expected: float, +) -> None: + """Test a zero brightness is raised, as it would pulse to black.""" + await async_setup_lifx_entry(hass, create_mock_light()) + + await hass.services.async_call( + DOMAIN, + SERVICE_EFFECT_PULSE, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_HS_COLOR: (120.0, 50.0), **brightness}, + blocking=True, + ) + + effect, _ = mock_effect_conductor.start.await_args.args + assert effect.color == HSBK(120.0, 0.5, expected, 3500) + + async def test_effect_colorloop_uses_public_effect_defaults( hass: HomeAssistant, mock_effect_conductor: MagicMock ) -> None: @@ -390,6 +417,24 @@ async def test_effect_colorloop_orders_saturation_bounds( assert effect.saturation_max == 0.8 +async def test_effect_colorloop_clamps_saturation_above_zero( + hass: HomeAssistant, mock_effect_conductor: MagicMock +) -> None: + """Test a zero saturation is raised, as it switches the bulb to white.""" + await async_setup_lifx_entry(hass, create_mock_light()) + + await hass.services.async_call( + DOMAIN, + SERVICE_EFFECT_COLORLOOP, + {ATTR_ENTITY_ID: ENTITY_ID, ATTR_SATURATION_MIN: 0, ATTR_SATURATION_MAX: 0}, + blocking=True, + ) + + effect = mock_effect_conductor.start.await_args.args[0] + assert effect.saturation_min == 0.01 + assert effect.saturation_max == 0.01 + + async def test_effect_colorloop_accepts_absolute_brightness( hass: HomeAssistant, mock_effect_conductor: MagicMock ) -> None: @@ -1670,6 +1715,26 @@ def test_theme_selector_options_match_the_library(service: str) -> None: assert options == sorted(ThemeLibrary.get_available_themes()) +@pytest.mark.parametrize( + ("service", "default"), + [ + pytest.param(SERVICE_EFFECT_MOVE, None, id="move"), + pytest.param(SERVICE_PAINT_THEME, "exciting", id="paint_theme"), + ], +) +def test_theme_field_default_matches_the_action( + service: str, default: str | None +) -> None: + """Test the UI pre-fills the theme the action applies when none is given. + + Move applies no theme without one, so a default would suggest a color + change the action does not otherwise make. + """ + services = load_yaml_dict(f"{lifx.__path__[0]}/services.yaml") + + assert services[service]["fields"][ATTR_THEME].get("default") == default + + @pytest.mark.parametrize( ("service", "factory", "palette"), [