Files
esphome/tests/component_tests/lvgl/test_validation.py

178 lines
7.0 KiB
Python

"""Tests for LVGL final_validation display metadata checks."""
from __future__ import annotations
import pytest
from esphome.components.const import BYTE_ORDER_BIG, BYTE_ORDER_LITTLE, CONF_BYTE_ORDER
from esphome.components.display import add_metadata
from esphome.components.lvgl import final_validation
from esphome.config import Config
from esphome.config_validation import Invalid
from esphome.const import KEY_CORE, KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM
from esphome.core import CORE, ID
from esphome.final_validate import full_config
@pytest.fixture(autouse=True)
def _setup_core():
"""Ensure CORE.data has enough context for final_validation."""
CORE.data[KEY_CORE] = {
KEY_TARGET_PLATFORM: "host",
KEY_TARGET_FRAMEWORK: "",
}
full_config.set(Config())
yield
CORE.reset()
def _register_displays(*display_ids: str) -> None:
"""Register display IDs in full_config so get_path_for_id works."""
fc = full_config.get()
display_list = [{"id": ID(d, True)} for d in display_ids]
fc["display"] = display_list
for i, disp_id in enumerate(display_ids):
fc.declare_ids.append((ID(disp_id, True), ["display", i, "id"]))
def _make_lvgl_config(
display_ids: list[str],
byte_order: str | None = None,
) -> dict:
"""Build a minimal LVGL config dict for final_validation."""
_register_displays(*display_ids)
config = {
"displays": [ID(d, True) for d in display_ids],
"log_level": "WARN",
"color_depth": 16,
"transparency_key": 0x000400,
"draw_rounding": 2,
"buffer_size": 0,
}
if byte_order is not None:
config[CONF_BYTE_ORDER] = byte_order
return config
class TestByteOrderAutoConfig:
"""Test that LVGL auto-configures byte_order from display metadata."""
def test_inherits_big_endian_from_display(self) -> None:
"""LVGL should inherit big_endian from display metadata."""
add_metadata(ID("my_disp"), 320, 240, byte_order=BYTE_ORDER_BIG)
configs = [_make_lvgl_config(["my_disp"])]
final_validation(configs)
assert configs[0][CONF_BYTE_ORDER] == BYTE_ORDER_BIG
def test_inherits_little_endian_from_display(self) -> None:
"""LVGL should inherit little_endian from display metadata."""
add_metadata(ID("my_disp"), 320, 240, byte_order=BYTE_ORDER_LITTLE)
configs = [_make_lvgl_config(["my_disp"])]
final_validation(configs)
assert configs[0][CONF_BYTE_ORDER] == BYTE_ORDER_LITTLE
def test_defaults_to_big_endian_when_no_metadata(self) -> None:
"""LVGL should default to big_endian when display has no metadata."""
configs = [_make_lvgl_config(["my_disp"])]
final_validation(configs)
assert configs[0][CONF_BYTE_ORDER] == BYTE_ORDER_BIG
class TestByteOrderExplicitMismatchError:
"""Test that LVGL rejects explicit byte_order mismatch with display."""
def test_raises_on_mismatch(self) -> None:
"""Explicit LVGL byte_order different from display should raise."""
add_metadata(ID("my_disp"), 320, 240, byte_order=BYTE_ORDER_LITTLE)
configs = [_make_lvgl_config(["my_disp"], byte_order=BYTE_ORDER_BIG)]
with pytest.raises(
Invalid, match="LVGL byte order must match the display byte order"
):
final_validation(configs)
def test_no_error_when_matching(self) -> None:
"""Explicit LVGL byte_order matching display should pass."""
add_metadata(ID("my_disp"), 320, 240, byte_order=BYTE_ORDER_BIG)
configs = [_make_lvgl_config(["my_disp"], byte_order=BYTE_ORDER_BIG)]
final_validation(configs)
class TestByteOrderMultipleDisplays:
"""Test byte_order validation with multiple displays."""
def test_consistent_displays_inherit(self) -> None:
"""All displays with same byte_order should set LVGL byte_order."""
add_metadata(ID("disp_a"), 320, 240, byte_order=BYTE_ORDER_LITTLE)
add_metadata(ID("disp_b"), 128, 64, byte_order=BYTE_ORDER_LITTLE)
configs = [_make_lvgl_config(["disp_a", "disp_b"])]
final_validation(configs)
assert configs[0][CONF_BYTE_ORDER] == BYTE_ORDER_LITTLE
def test_inconsistent_displays_raises(self) -> None:
"""Displays with different byte_order should raise an error."""
add_metadata(ID("disp_a"), 320, 240, byte_order=BYTE_ORDER_BIG)
add_metadata(ID("disp_b"), 128, 64, byte_order=BYTE_ORDER_LITTLE)
configs = [_make_lvgl_config(["disp_a", "disp_b"])]
with pytest.raises(Invalid, match="same byte_order"):
final_validation(configs)
class TestHasWriterCheck:
"""Test that LVGL rejects displays with has_writer set."""
def test_display_with_writer_raises(self) -> None:
"""Display with lambda/pages/auto_clear should be rejected."""
add_metadata(ID("my_disp"), 320, 240, has_writer=True)
configs = [_make_lvgl_config(["my_disp"])]
with pytest.raises(Invalid, match="not compatible with LVGL"):
final_validation(configs)
def test_display_without_writer_passes(self) -> None:
"""Display without writer should pass."""
add_metadata(ID("my_disp"), 320, 240, has_writer=False)
configs = [_make_lvgl_config(["my_disp"])]
final_validation(configs)
class TestRotationCheck:
"""Test that LVGL rejects displays with non-zero rotation."""
def test_display_with_rotation_raises(self) -> None:
"""Display with rotation should be rejected."""
add_metadata(ID("my_disp"), 320, 240, rotation=90)
configs = [_make_lvgl_config(["my_disp"])]
with pytest.raises(Invalid, match="rotation.*not compatible with LVGL"):
final_validation(configs)
def test_display_without_rotation_passes(self) -> None:
"""Display with rotation=0 should pass."""
add_metadata(ID("my_disp"), 320, 240, rotation=0)
configs = [_make_lvgl_config(["my_disp"])]
final_validation(configs)
class TestDrawRoundingMerge:
"""Test that display draw_rounding is merged into LVGL config."""
def test_display_draw_rounding_overrides_lower(self) -> None:
"""Display draw_rounding higher than LVGL default should win."""
add_metadata(ID("my_disp"), 320, 240, draw_rounding=8)
configs = [_make_lvgl_config(["my_disp"])]
final_validation(configs)
assert configs[0]["draw_rounding"] == 8
def test_display_draw_rounding_does_not_lower(self) -> None:
"""Display draw_rounding lower than LVGL config should not reduce it."""
add_metadata(ID("my_disp"), 320, 240, draw_rounding=1)
configs = [_make_lvgl_config(["my_disp"])]
configs[0]["draw_rounding"] = 4
final_validation(configs)
assert configs[0]["draw_rounding"] == 4
def test_zero_draw_rounding_no_change(self) -> None:
"""Display with draw_rounding=0 should not affect LVGL config."""
add_metadata(ID("my_disp"), 320, 240, draw_rounding=0)
configs = [_make_lvgl_config(["my_disp"])]
final_validation(configs)
assert configs[0]["draw_rounding"] == 2