From de794fb03ae1e08ddbdde580f4a89d5ea86ca6d6 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 29 Sep 2026 16:16:31 +0200 Subject: [PATCH] Surface esp32 framework.advanced.flash_chip on the framework form --- script/sync_components.py | 1 + .../test_sync_components_esp32_visibility.py | 43 +++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/script/sync_components.py b/script/sync_components.py index b5e308565..e0eaee656 100755 --- a/script/sync_components.py +++ b/script/sync_components.py @@ -900,6 +900,7 @@ class Visibility(StrEnum): _ESP32_ADVANCED_VISIBLE: dict[str, bool | str] = { "sram1_as_iram": True, "minimum_chip_revision": "0.0", + "flash_chip": "generic", } # --------------------------------------------------------------------------- diff --git a/tests/test_sync_components_esp32_visibility.py b/tests/test_sync_components_esp32_visibility.py index c1abac9c5..c17e8c8c0 100644 --- a/tests/test_sync_components_esp32_visibility.py +++ b/tests/test_sync_components_esp32_visibility.py @@ -1,9 +1,9 @@ """ Pin the esp32 ``framework.advanced`` and ota ``allow_partition_access`` visibility. -``sram1_as_iram`` and ``minimum_chip_revision`` stay surfaced under the Advanced -disclosure with their siblings hidden; ``allow_partition_access`` stays core and -esp32-gated. +``sram1_as_iram``, ``minimum_chip_revision`` and ``flash_chip`` stay surfaced under +the Advanced disclosure with their siblings hidden; ``allow_partition_access`` +stays core and esp32-gated. """ from __future__ import annotations @@ -12,13 +12,23 @@ import orjson import pytest +from esphome.const import __version__ as esphome_version +from esphome_device_builder.helpers.version_compat import version_at_least from script.sync_components import ( # type: ignore[import-not-found] _OUTPUT_BODIES_DIR, + _OUTPUT_INDEX_FILE, _esp32_variant_gate, _surface_esp32_advanced_fields, ) +# ``framework.advanced.flash_chip`` first ships in esphome 2026.10.0. +_FLASH_CHIP_SINCE = "2026.10.0b1" +_INSTALLED_HAS_FLASH_CHIP = version_at_least(esphome_version, _FLASH_CHIP_SINCE) +_CATALOG_HAS_FLASH_CHIP = version_at_least( + orjson.loads(_OUTPUT_INDEX_FILE.read_bytes())["esphome_schema_version"], _FLASH_CHIP_SINCE +) + def _load(component_id: str) -> dict[str, Any]: return orjson.loads((_OUTPUT_BODIES_DIR / f"{component_id}.json").read_bytes()) @@ -72,6 +82,19 @@ def test_surface_unhides_curated_fields_and_group_keeps_siblings_hidden() -> Non assert adc is not None and adc["hidden"] is True # untouched +@pytest.mark.skipif(not _INSTALLED_HAS_FLASH_CHIP, reason="installed esphome lacks flash_chip") +def test_surface_unhides_flash_chip_without_variant_gate() -> None: + """``flash_chip`` surfaces ungated: ``generic`` validates on every variant.""" + framework = _framework_with_advanced(child_keys=("flash_chip", "adc_oneshot_in_iram")) + _surface_esp32_advanced_fields(framework) + advanced = framework["config_entries"][0] + assert advanced["hidden"] is False and advanced["advanced"] is False + flash_chip = _find(advanced["config_entries"], "flash_chip") + assert flash_chip is not None + assert flash_chip["hidden"] is False and flash_chip["advanced"] is False + assert "depends_on" not in flash_chip + + def test_surface_no_op_without_promotable_child() -> None: """A framework whose advanced group has no allow-listed child is left hidden.""" framework = _framework_with_advanced(child_keys=("adc_oneshot_in_iram",)) @@ -98,6 +121,14 @@ def test_esp32_catalog_surfaces_curated_fields_under_advanced() -> None: min_rev = _find(advanced["config_entries"], "minimum_chip_revision") assert min_rev is not None assert {o["value"] for o in min_rev["options"]} >= {"0.0", "3.0"} + flash_chip = _find(advanced["config_entries"], "flash_chip") + if _CATALOG_HAS_FLASH_CHIP: + assert flash_chip is not None + assert not flash_chip.get("hidden") and not flash_chip.get("advanced") + assert "depends_on" not in flash_chip # valid on every variant + assert {o["value"] for o in flash_chip["options"]} >= {"generic", "mxic_opi"} + else: + assert flash_chip is None # the field predates the synced schema # Scope guard: a sibling expert knob stays hidden (yaml_only). adc = _find(advanced["config_entries"], "adc_oneshot_in_iram") assert adc is not None and adc.get("hidden") is True @@ -121,6 +152,12 @@ def test_variant_gate_derived_from_esphome() -> None: assert _esp32_variant_gate("disable_fatfs") is None +@pytest.mark.skipif(not _INSTALLED_HAS_FLASH_CHIP, reason="installed esphome lacks flash_chip") +def test_variant_gate_flash_chip_ungated() -> None: + """``flash_chip: generic`` validates on every variant, so no gate is derived.""" + assert _esp32_variant_gate("flash_chip", "generic") is None + + def test_variant_gate_fails_loud_when_underivable() -> None: """A key no variant's base config accepts raises, rather than silently ungating.""" with pytest.raises(RuntimeError):