mirror of
https://github.com/esphome/esphome.git
synced 2026-06-24 13:09:12 +00:00
178 lines
7.0 KiB
Python
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
|