mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 13:27:14 +00:00
[lvgl] Ensure that on_value events fire on checked change (#16119)
This commit is contained in:
83
tests/component_tests/lvgl/config/widget_state_test.yaml
Normal file
83
tests/component_tests/lvgl/config/widget_state_test.yaml
Normal file
@@ -0,0 +1,83 @@
|
||||
esphome:
|
||||
name: test
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
spi:
|
||||
- id: spi_bus
|
||||
clk_pin: GPIO18
|
||||
mosi_pin: GPIO23
|
||||
|
||||
display:
|
||||
- platform: mipi_spi
|
||||
spi_id: spi_bus
|
||||
model: st7789v
|
||||
id: tft_display
|
||||
dimensions:
|
||||
width: 240
|
||||
height: 320
|
||||
cs_pin: GPIO22
|
||||
dc_pin: GPIO21
|
||||
auto_clear_enabled: false
|
||||
invert_colors: false
|
||||
update_interval: never
|
||||
|
||||
lvgl:
|
||||
id: lvgl_id
|
||||
displays: tft_display
|
||||
pages:
|
||||
- id: main_page
|
||||
widgets:
|
||||
# Widget with multiple static states; one true, one false.
|
||||
- button:
|
||||
id: btn_static
|
||||
state:
|
||||
checked: true
|
||||
disabled: false
|
||||
|
||||
# Widget with a templated (lambda) state.
|
||||
- button:
|
||||
id: btn_lambda
|
||||
state:
|
||||
pressed: !lambda return true;
|
||||
|
||||
# Button referenced by enable/disable actions; the on_click handler
|
||||
# exercises both branches of the obj_disable/obj_enable code path.
|
||||
- button:
|
||||
id: btn_actions
|
||||
on_click:
|
||||
- lvgl.widget.disable: btn_actions
|
||||
- lvgl.widget.enable: btn_actions
|
||||
|
||||
# Button matrix with two buttons; matrix_btn_a is targeted by
|
||||
# lvgl.widget.disable/enable actions to exercise the
|
||||
# MatrixButton.set_state code path.
|
||||
- buttonmatrix:
|
||||
id: matrix
|
||||
rows:
|
||||
- buttons:
|
||||
- id: matrix_btn_a
|
||||
text: A
|
||||
control:
|
||||
checkable: true
|
||||
- id: matrix_btn_b
|
||||
text: B
|
||||
control:
|
||||
checkable: true
|
||||
on_click:
|
||||
- lvgl.widget.disable: matrix_btn_a
|
||||
- lvgl.widget.enable: matrix_btn_a
|
||||
|
||||
# Switch derived from an LVGL switch widget – exercises
|
||||
# set_state(LV_STATE.CHECKED, v) inside the control lambda.
|
||||
- switch:
|
||||
id: switch_widget
|
||||
|
||||
switch:
|
||||
- platform: lvgl
|
||||
id: lvgl_switch
|
||||
name: LVGL Switch
|
||||
widget: switch_widget
|
||||
167
tests/component_tests/lvgl/test_widget_state.py
Normal file
167
tests/component_tests/lvgl/test_widget_state.py
Normal file
@@ -0,0 +1,167 @@
|
||||
"""Tests for LVGL widget state code generation.
|
||||
|
||||
These tests cover the change from the old ``add_state``/``clear_state`` helpers
|
||||
on :class:`Widget` (and on :class:`MatrixButton`) to a single ``set_state``
|
||||
method that delegates to the new C++ helpers
|
||||
``LvglComponent::lv_obj_set_state_value`` and
|
||||
``LvglComponent::lv_buttonmatrix_set_button_ctrl_value``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.__main__ import generate_cpp_contents
|
||||
from esphome.config import read_config
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def main_cpp(request: pytest.FixtureRequest) -> str:
|
||||
"""Generate the C++ output for the shared widget-state YAML config once
|
||||
per module.
|
||||
|
||||
Module-scoped so the (relatively expensive) codegen runs a single time;
|
||||
the function-scoped fixtures from ``conftest.py`` (e.g. ``generate_main``)
|
||||
can't be requested from a higher-scoped fixture, so the small amount of
|
||||
setup is inlined here. The captured string is independent of
|
||||
``CORE.reset()`` calls that the per-test autouse fixtures perform after
|
||||
this fixture has produced its value.
|
||||
"""
|
||||
config_path = Path(request.fspath).parent / "config" / "widget_state_test.yaml"
|
||||
original_path = CORE.config_path
|
||||
try:
|
||||
CORE.config_path = config_path
|
||||
CORE.config = read_config({})
|
||||
generate_cpp_contents(CORE.config)
|
||||
return CORE.cpp_global_section + CORE.cpp_main_section
|
||||
finally:
|
||||
CORE.config_path = original_path
|
||||
CORE.reset()
|
||||
|
||||
|
||||
def test_static_state_emits_set_state_value(main_cpp: str) -> None:
|
||||
"""A widget with ``state: { checked: true, disabled: false }`` should
|
||||
generate one ``lv_obj_set_state_value`` call per entry, with the
|
||||
appropriate boolean argument.
|
||||
"""
|
||||
assert (
|
||||
"LvglComponent::lv_obj_set_state_value(btn_static, LV_STATE_CHECKED, true)"
|
||||
in main_cpp
|
||||
)
|
||||
assert (
|
||||
"LvglComponent::lv_obj_set_state_value(btn_static, LV_STATE_DISABLED, false)"
|
||||
in main_cpp
|
||||
)
|
||||
|
||||
|
||||
def test_lambda_state_emits_set_state_value_with_lambda(main_cpp: str) -> None:
|
||||
"""A widget with ``state: { pressed: !lambda return true; }`` should
|
||||
generate ``lv_obj_set_state_value(..., LV_STATE_PRESSED, <expr>)`` where
|
||||
``<expr>`` is the lambda's return value (cast or inlined), not a static
|
||||
bool.
|
||||
"""
|
||||
# The set_state call is emitted for the templated state.
|
||||
assert (
|
||||
"LvglComponent::lv_obj_set_state_value(btn_lambda, LV_STATE_PRESSED,"
|
||||
in main_cpp
|
||||
)
|
||||
# And it must NOT have collapsed the lambda to a literal true/false.
|
||||
assert (
|
||||
"LvglComponent::lv_obj_set_state_value(btn_lambda, LV_STATE_PRESSED, true)"
|
||||
not in main_cpp
|
||||
)
|
||||
# The legacy if/else over add_state/remove_state is gone.
|
||||
assert "lv_obj_add_state(btn_lambda, LV_STATE_PRESSED)" not in main_cpp
|
||||
assert "lv_obj_remove_state(btn_lambda, LV_STATE_PRESSED)" not in main_cpp
|
||||
|
||||
|
||||
def test_widget_disable_action_uses_set_state_value(main_cpp: str) -> None:
|
||||
"""``lvgl.widget.disable: btn_actions`` should emit a
|
||||
``set_state_value(..., LV_STATE_DISABLED, true)`` call rather than the
|
||||
legacy ``lv_obj_add_state``.
|
||||
"""
|
||||
assert (
|
||||
"LvglComponent::lv_obj_set_state_value(btn_actions, LV_STATE_DISABLED, true)"
|
||||
in main_cpp
|
||||
)
|
||||
# No leftover legacy add_state for the DISABLED state of this widget.
|
||||
assert "lv_obj_add_state(btn_actions, LV_STATE_DISABLED)" not in main_cpp
|
||||
|
||||
|
||||
def test_widget_enable_action_uses_set_state_value(main_cpp: str) -> None:
|
||||
"""``lvgl.widget.enable: btn_actions`` should emit a
|
||||
``set_state_value(..., LV_STATE_DISABLED, false)`` call rather than the
|
||||
legacy ``lv_obj_remove_state``.
|
||||
"""
|
||||
assert (
|
||||
"LvglComponent::lv_obj_set_state_value(btn_actions, LV_STATE_DISABLED, false)"
|
||||
in main_cpp
|
||||
)
|
||||
assert "lv_obj_remove_state(btn_actions, LV_STATE_DISABLED)" not in main_cpp
|
||||
|
||||
|
||||
def test_buttonmatrix_disable_action_uses_helper(main_cpp: str) -> None:
|
||||
"""``lvgl.widget.disable: matrix_btn_a`` should route through the new
|
||||
``lv_buttonmatrix_set_button_ctrl_value`` helper for button index 0
|
||||
with the ``DISABLED`` control bit set to ``true``, instead of the
|
||||
legacy ``lv_buttonmatrix_set_button_ctrl``.
|
||||
|
||||
The button matrix obj is the compound's ``obj`` member and the index
|
||||
is the position of the button in the row layout.
|
||||
"""
|
||||
assert (
|
||||
"LvglComponent::lv_buttonmatrix_set_button_ctrl_value(matrix->obj, 0, "
|
||||
"LV_BUTTONMATRIX_CTRL_DISABLED, true)"
|
||||
) in main_cpp
|
||||
|
||||
|
||||
def test_buttonmatrix_enable_action_uses_helper(main_cpp: str) -> None:
|
||||
"""``lvgl.widget.enable: matrix_btn_a`` should route through the new
|
||||
``lv_buttonmatrix_set_button_ctrl_value`` helper for button index 0
|
||||
with the ``DISABLED`` control bit set to ``false``, instead of the
|
||||
legacy ``lv_buttonmatrix_clear_button_ctrl``.
|
||||
"""
|
||||
assert (
|
||||
"LvglComponent::lv_buttonmatrix_set_button_ctrl_value(matrix->obj, 0, "
|
||||
"LV_BUTTONMATRIX_CTRL_DISABLED, false)"
|
||||
) in main_cpp
|
||||
# The legacy clear_button_ctrl path is gone for the matrix button enable
|
||||
# action.
|
||||
assert (
|
||||
"lv_buttonmatrix_clear_button_ctrl(matrix->obj, 0, LV_BUTTONMATRIX_CTRL_DISABLED)"
|
||||
not in main_cpp
|
||||
)
|
||||
|
||||
|
||||
def test_lvgl_switch_control_calls_set_state_value(main_cpp: str) -> None:
|
||||
"""The LVGL switch platform installs a control lambda that mirrors the
|
||||
switch's bool value into ``LV_STATE_CHECKED`` via
|
||||
``lv_obj_set_state_value`` (replacing the previous if/else over
|
||||
``add_state``/``clear_state`` plus an explicit ``send_event`` of
|
||||
``lv_api_event``).
|
||||
"""
|
||||
# The control lambda calls the new helper with the bool ``v`` parameter.
|
||||
assert (
|
||||
"LvglComponent::lv_obj_set_state_value(switch_widget, LV_STATE_CHECKED, v)"
|
||||
in main_cpp
|
||||
)
|
||||
# The deprecated lv_api_event symbol must no longer appear anywhere.
|
||||
assert "lv_api_event" not in main_cpp
|
||||
|
||||
|
||||
def test_default_state_does_not_emit_set_state_value(main_cpp: str) -> None:
|
||||
"""A widget without a ``state:`` block must not generate any
|
||||
``lv_obj_set_state_value`` calls for it. (Sanity-check that the
|
||||
new code path is opt-in driven by the YAML.)
|
||||
"""
|
||||
assert (
|
||||
"LvglComponent::lv_obj_set_state_value(switch_widget, LV_STATE_DISABLED"
|
||||
not in main_cpp
|
||||
)
|
||||
assert (
|
||||
"LvglComponent::lv_obj_set_state_value(btn_static, LV_STATE_PRESSED"
|
||||
not in main_cpp
|
||||
)
|
||||
Reference in New Issue
Block a user