diff --git a/.github/workflows/install_dependencies/action.yml b/.github/workflows/install_dependencies/action.yml index df0bee8a..3041698b 100644 --- a/.github/workflows/install_dependencies/action.yml +++ b/.github/workflows/install_dependencies/action.yml @@ -36,6 +36,8 @@ runs: run: | echo "::warning::### WARNING! Deprecation warnings muted with option '--use-pep517' please address this at some point in pytest.yaml. ###" pip install -r core/requirements.txt --use-pep517 + # because they decided to pull codecov the package from PyPI... + sed -i '/codecov/d' core/requirements_test.txt pip install -r core/requirements_test.txt --use-pep517 pip install -e core/ --use-pep517 pip install ulid-transform # this is in Adaptive-lighting's manifest.json diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index cea75602..394acef3 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: python-version: ["3.10"] - core-version: ["2023.2.5", "2023.3.6", "2023.4.0", "dev"] + core-version: ["2023.2.5", "2023.3.6", "2023.4.6", "dev"] steps: - name: Check out code from GitHub uses: actions/checkout@v3 diff --git a/custom_components/adaptive_lighting/__init__.py b/custom_components/adaptive_lighting/__init__.py index dc928a6b..07a0a758 100755 --- a/custom_components/adaptive_lighting/__init__.py +++ b/custom_components/adaptive_lighting/__init__.py @@ -6,7 +6,6 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_SOURCE from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.reload import async_setup_reload_service import voluptuous as vol from .const import ( @@ -36,10 +35,13 @@ CONFIG_SCHEMA = vol.Schema( ) +async def reload_configuration_yaml(event: dict, hass: HomeAssistant): + """Reload configuration.yaml.""" + await hass.services.async_call("homeassistant", "check_config", {}) + + async def async_setup(hass: HomeAssistant, config: dict[str, Any]): """Import integration from config.""" - # This will reload any changes the user made to any YAML configurations. - await async_setup_reload_service(hass, DOMAIN, PLATFORMS) if DOMAIN in config: for entry in config[DOMAIN]: @@ -55,6 +57,10 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry): """Set up the component.""" data = hass.data.setdefault(DOMAIN, {}) + # This will reload any changes the user made to any YAML configurations. + # Called during 'quick reload' or hass.reload_config_entry + hass.bus.async_listen("hass.config.entry_updated", reload_configuration_yaml) + undo_listener = config_entry.add_update_listener(async_update_options) data[config_entry.entry_id] = {UNDO_UPDATE_LISTENER: undo_listener} for platform in PLATFORMS: diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index d6e96a18..ebdf3c27 100644 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -156,12 +156,13 @@ from .const import ( ) _SUPPORT_OPTS = { - "brightness": SUPPORT_BRIGHTNESS, - "color_temp": SUPPORT_COLOR_TEMP, - "color": SUPPORT_COLOR, - "transition": SUPPORT_TRANSITION, + COLOR_MODE_BRIGHTNESS: SUPPORT_BRIGHTNESS, + COLOR_MODE_COLOR_TEMP: SUPPORT_COLOR_TEMP, + CONST_COLOR: SUPPORT_COLOR, + ATTR_TRANSITION: SUPPORT_TRANSITION, } + VALID_COLOR_MODES = { COLOR_MODE_BRIGHTNESS: ATTR_BRIGHTNESS, COLOR_MODE_COLOR_TEMP: ATTR_COLOR_TEMP_KELVIN, @@ -643,16 +644,18 @@ def _expand_light_groups(hass: HomeAssistant, lights: list[str]) -> list[str]: def _supported_to_attributes(supported): supported_attributes = {} supports_colors = False - for mode, attr in VALID_COLOR_MODES.items(): - if mode not in supported: - continue - supported_attributes[attr] = True - if ( - not supports_colors - and mode != COLOR_MODE_BRIGHTNESS - and mode != COLOR_MODE_COLOR_TEMP - ): - supports_colors = True + for mode in supported: + attr = VALID_COLOR_MODES.get(mode) + if attr: + supported_attributes[attr] = True + if attr in COLOR_ATTRS: + supports_colors = True + # ATTR_SUPPORTED_FEATURES only + elif mode in _SUPPORT_OPTS: + supported_attributes[mode] = True + if CONST_COLOR in supported_attributes: + supports_colors = True + supported_attributes.pop(CONST_COLOR) return supported_attributes, supports_colors diff --git a/tests/test_switch.py b/tests/test_switch.py index 039465e9..ec2d9f40 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -3,9 +3,10 @@ import asyncio from copy import deepcopy import datetime +import itertools import logging from random import randint -from unittest.mock import patch +from unittest.mock import MagicMock, patch from homeassistant.components.adaptive_lighting.const import ( ADAPT_BRIGHTNESS_SWITCH, @@ -27,6 +28,7 @@ from homeassistant.components.adaptive_lighting.const import ( CONF_TRANSITION, CONF_TURN_ON_LIGHTS, CONF_USE_DEFAULTS, + CONST_COLOR, DEFAULT_MAX_BRIGHTNESS, DEFAULT_NAME, DEFAULT_SLEEP_BRIGHTNESS, @@ -39,7 +41,10 @@ from homeassistant.components.adaptive_lighting.const import ( UNDO_UPDATE_LISTENER, ) from homeassistant.components.adaptive_lighting.switch import ( + _SUPPORT_OPTS, + VALID_COLOR_MODES, _attributes_have_changed, + _supported_features, color_difference_redmean, create_context, is_our_context, @@ -49,9 +54,13 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, ATTR_COLOR_TEMP_KELVIN, + ATTR_MAX_COLOR_TEMP_KELVIN, + ATTR_MIN_COLOR_TEMP_KELVIN, ATTR_RGB_COLOR, + ATTR_SUPPORTED_COLOR_MODES, ATTR_TRANSITION, ATTR_XY_COLOR, + COLOR_MODE_BRIGHTNESS, ) from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN from homeassistant.components.light import SERVICE_TURN_OFF @@ -519,6 +528,79 @@ async def test_turn_on_off_listener_not_tracking_untracked_lights(hass): assert light not in switch.turn_on_off_listener.lights +def test_supported_features(hass): # noqa: C901 + """Test the supported features of a light.""" + + possible_legacy_features = {} + MAX_COMBINATIONS = 4 # maximum number of elements that can be combined + for i in range(1, min(MAX_COMBINATIONS, len(_SUPPORT_OPTS)) + 1): + for combination in itertools.combinations(_SUPPORT_OPTS.keys(), i): + key = "_".join(combination) + value = [v for k, v in _SUPPORT_OPTS.items() if k in combination] + possible_legacy_features[key] = value + + possible_color_modes = {} + for i in range(1, len(VALID_COLOR_MODES) + 1): + for combination in itertools.combinations(VALID_COLOR_MODES.keys(), i): + key = "_".join(combination) + value = [v for k, v in VALID_COLOR_MODES.items() if k in combination] + possible_color_modes[key] = value + + # create a mock HomeAssistant object + hass = MagicMock() + + # iterate over possible legacy features + for feature_key, feature_values in possible_legacy_features.items(): + # _LOGGER.debug(feature_values) + # set the attributes of the mock state object to the possible legacy feature values + state_attrs = {ATTR_SUPPORTED_FEATURES: sum(feature_values)} + hass.states.get.return_value.attributes = state_attrs + + # iterate over possible color modes + for mode_key, mode_values in possible_color_modes.items(): + # _LOGGER.debug(mode_values) + # set the attributes of the mock state object to the possible color mode values + state_attrs[ATTR_SUPPORTED_COLOR_MODES] = set(mode_values) + hass.states.get.return_value.attributes = state_attrs + + # Handle both the new and the old _supported_features. + result = _supported_features(hass, ENTITY_LIGHT) + supported, supports_colors = ( + result if isinstance(result, tuple) else (result, None) + ) + expected_supported = {} if supports_colors is not None else set() + for mode, attr in VALID_COLOR_MODES.items(): + if mode in mode_values: + if supports_colors is None: + expected_supported.add(mode) + else: + expected_supported[attr] = True + if supports_colors is True: + expected_supported[COLOR_MODE_BRIGHTNESS] = True + for opt, value in _SUPPORT_OPTS.items(): + if value in feature_values: + if supports_colors is None: + expected_supported.add(opt) + else: + if supports_colors is True: + expected_supported[COLOR_MODE_BRIGHTNESS] = True + if opt in VALID_COLOR_MODES: + expected_supported[VALID_COLOR_MODES[opt]] = True + elif opt != CONST_COLOR: + expected_supported[opt] = True + if ATTR_MIN_COLOR_TEMP_KELVIN in supported: + supported.pop(ATTR_MIN_COLOR_TEMP_KELVIN) + if ATTR_MAX_COLOR_TEMP_KELVIN in supported: + supported.pop(ATTR_MAX_COLOR_TEMP_KELVIN) + assert supported == expected_supported, ( + f"\nExpected supported: {expected_supported}\n" + f"Actual supported: {supported}\n" + f"feature_values: {feature_values}\n" + f"mode_values: {mode_values}\n" + f"supports_colors: {supports_colors}\n" + ) + + @pytest.mark.dependency(depends=GLOBAL_TEST_DEPENDENCIES) async def test_manual_control(hass): """Test the 'manual control' tracking."""