Split options flow into Basic and Advanced steps

Users have complained about the overwhelming number of options (28+)
in the configuration UI. This change splits the options into two steps:

**Basic Settings (9 options):**
- lights, min/max brightness, min/max color temp
- sleep brightness, sleep color temp, transition, interval

**Advanced Settings (20+ options):**
- All other options (sunrise/sunset times, offsets, brightness modes,
  take_over_control, detect_non_ha_changes, etc.)

The flow now works as:
1. User configures basic options
2. Menu appears: "Configure advanced options" or "Save and finish"
3. If advanced chosen, show advanced options form
4. Save combined options

Changes:
- Add BASIC_OPTIONS constant to const.py
- Refactor OptionsFlowHandler with async_step_init, async_step_advanced,
  async_step_finish methods
- Update strings.json with menu_options and advanced step
- Update all 34 translation files with translated menu/titles
- Update tests for multi-step flow

Note for Weblate: After merging, run "Rescan all translation files"
to pick up the new structure. Existing translations are preserved.
This commit is contained in:
Bas Nijholt 2025-11-27 13:45:25 -08:00
commit f94fb6a85e
No known key found for this signature in database
38 changed files with 1003 additions and 375 deletions

View file

@ -1,6 +1,7 @@
"""Test Adaptive Lighting config flow."""
from homeassistant.components.adaptive_lighting.const import (
BASIC_OPTIONS,
CONF_SUNRISE_TIME,
CONF_SUNSET_TIME,
DEFAULT_NAME,
@ -16,6 +17,12 @@ from tests.common import MockConfigEntry
DEFAULT_DATA = {key: default for key, default, _ in VALIDATION_TUPLES}
# Split DEFAULT_DATA into basic and advanced options
BASIC_DATA = {key: value for key, value in DEFAULT_DATA.items() if key in BASIC_OPTIONS}
ADVANCED_DATA = {
key: value for key, value in DEFAULT_DATA.items() if key not in BASIC_OPTIONS
}
async def test_flow_manual_configuration(hass):
"""Test that config flow works."""
@ -53,7 +60,7 @@ async def test_import_success(hass):
async def test_options(hass):
"""Test updating options."""
"""Test updating options with multi-step flow."""
entry = MockConfigEntry(
domain=DOMAIN,
title=DEFAULT_NAME,
@ -64,24 +71,45 @@ async def test_options(hass):
await hass.config_entries.async_setup(entry.entry_id)
# Step 1: Init with basic options
result = await hass.config_entries.options.async_init(entry.entry_id)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "init"
data = DEFAULT_DATA.copy()
data[CONF_SUNRISE_TIME] = NONE_STR
data[CONF_SUNSET_TIME] = NONE_STR
# Submit basic options - this should show the menu
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input=data,
user_input=BASIC_DATA.copy(),
)
assert result["type"] == FlowResultType.MENU
assert result["step_id"] == "init"
# Choose to go to advanced options
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={"next_step_id": "advanced"},
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "advanced"
# Submit advanced options
advanced_data = ADVANCED_DATA.copy()
advanced_data[CONF_SUNRISE_TIME] = NONE_STR
advanced_data[CONF_SUNSET_TIME] = NONE_STR
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input=advanced_data,
)
assert result["type"] == FlowResultType.CREATE_ENTRY
for key, value in data.items():
# Verify all data is saved (basic + advanced)
expected_data = {**BASIC_DATA, **advanced_data}
for key, value in expected_data.items():
assert result["data"][key] == value
async def test_incorrect_options(hass):
"""Test updating incorrect options."""
async def test_options_finish_without_advanced(hass):
"""Test updating options and finishing without advanced step."""
entry = MockConfigEntry(
domain=DOMAIN,
title=DEFAULT_NAME,
@ -92,14 +120,70 @@ async def test_incorrect_options(hass):
await hass.config_entries.async_setup(entry.entry_id)
# Step 1: Init with basic options
result = await hass.config_entries.options.async_init(entry.entry_id)
data = DEFAULT_DATA.copy()
data[CONF_SUNRISE_TIME] = "yolo"
data[CONF_SUNSET_TIME] = "yolo"
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "init"
# Submit basic options - this should show the menu
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input=data,
user_input=BASIC_DATA.copy(),
)
assert result["type"] == FlowResultType.MENU
# Choose to finish (skip advanced options)
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={"next_step_id": "finish"},
)
assert result["type"] == FlowResultType.CREATE_ENTRY
# Verify only basic data is saved
for key, value in BASIC_DATA.items():
assert result["data"][key] == value
async def test_incorrect_options(hass):
"""Test updating incorrect options in advanced step."""
entry = MockConfigEntry(
domain=DOMAIN,
title=DEFAULT_NAME,
data={CONF_NAME: DEFAULT_NAME},
options={},
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
# Step 1: Init with basic options
result = await hass.config_entries.options.async_init(entry.entry_id)
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input=BASIC_DATA.copy(),
)
assert result["type"] == FlowResultType.MENU
# Choose to go to advanced options
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={"next_step_id": "advanced"},
)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "advanced"
# Submit invalid advanced options
advanced_data = ADVANCED_DATA.copy()
advanced_data[CONF_SUNRISE_TIME] = "yolo"
advanced_data[CONF_SUNSET_TIME] = "yolo"
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input=advanced_data,
)
# Should show form again with errors
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "advanced"
assert result["errors"] == {"base": "option_error"}
async def test_import_twice(hass):