mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 15:10:51 +00:00
168 lines
6.4 KiB
Python
168 lines
6.4 KiB
Python
"""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
|
|
)
|