Files
homeassistant_espscreen/tests/test_easy_package.py
T
Max Gramser d76461d774 Build every screen from one shared core plus a small file per board, the same firmware as before (0.2.84)
The two board profiles (5,000 lines each, 87 % the same text) become packages/core.yaml and one file per
board under packages/boards/: the hardware, a table of 84 sizes and font sizes, 18 hooks with the lines of C++
only that board adds inside a shared lambda, and the board's own sections. packages/cyd.yaml and
packages/guition.yaml keep their names and places (a screen installed from ESP Screens builds as before);
home-like-2432s028.yaml and guition-4848s040.yaml become the same entries for a checkout.

The firmware stays 0.2.70: the C++ ESPHome generates for each board, through the checkout profile and
through the GitHub package route, was compared line by line with the old profiles' (docs/TEST_RESULTS_0284.md).
tools/check_packages.py replaces tools/generate_packages.py; tools/profiles.py gives the tests and tools the
three files as one; docs/PROFILES.md says how the files fit together and how a board is added.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 22:57:14 +02:00

98 lines
5.6 KiB
Python

"""The packages a screen builds from: one shared core plus a board file, runtime tiles only (app 0.2.84+)."""
import importlib.util
import re
import subprocess
import sys
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'tools'))
import profiles # noqa: E402
BOARDS = {'cyd': 'home-like-2432s028.yaml', 'guition': 'guition-4848s040.yaml'}
# The old manual profile bound tiles to fixed entities in the YAML. ESP Screens sends the tiles now, so
# none of this may come back: the fixed subscriptions, its tap handlers and scripts, its own vacuum card.
GONE = ['ha_state_tile1', 'tile1_brightness', 'tile6_climate_humidity', 'tile6_cover_state', 'smartdisplay_action',
'publish_action', 'do_tile_action', 'open_vacuum_overlay', 'vacuum_action', 'vacuum_fan_speed', 'vacuum_overlay',
'vacspd_quiet', 'vacuum_actions', 'DYNAMIC_TILES', 'TILE_COUNT', 'DIRECT_ACTIONS', 'TILE1_ENTITY',
'light_controls::subscribe']
def load_checker():
spec = importlib.util.spec_from_file_location('check_packages', ROOT / 'tools/check_packages.py')
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
class PackageTests(unittest.TestCase):
def test_the_packages_fit_together(self):
result = subprocess.run([sys.executable, str(ROOT / 'tools/check_packages.py')], capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
def test_every_entry_is_the_shared_core_plus_its_board(self):
checker = load_checker()
for board, profile in BOARDS.items():
for name in (profile, f'packages/{board}.yaml'):
self.assertEqual(sorted(checker.included(ROOT / name)), sorted(['packages/core.yaml', str(profiles.BOARDS[board].relative_to(ROOT))]), name)
def test_the_published_entry_takes_everything_from_github(self):
for board in BOARDS:
package = (ROOT / 'packages' / f'{board}.yaml').read_text()
self.assertNotIn('!secret', package)
self.assertNotIn('type: local', package)
self.assertIn('url: https://github.com/MaxGramser/homeassistant_espscreen.git', package)
self.assertIn('FONT_DIR: "https://raw.githubusercontent.com/MaxGramser/homeassistant_espscreen/main/fonts"', package)
# The fonts of the shared core come from that place; a checkout takes them from fonts/.
self.assertIn('file: "${FONT_DIR}/Roboto-500.ttf"', profiles.CORE.read_text())
self.assertIn('FONT_DIR: "fonts"', (ROOT / BOARDS[board]).read_text())
def test_no_secret_or_local_path_under_packages(self):
for path in sorted((ROOT / 'packages').rglob('*.yaml')):
text = '\n'.join(line for line in path.read_text().split('\n') if not line.lstrip().startswith('#'))
self.assertNotIn('!secret', text, path.name)
self.assertNotIn('type: local', text, path.name)
for section in ('sensor', 'binary_sensor', 'text_sensor'):
block = re.search(r'^' + section + r':\n.*?(?=^[a-zA-Z_]+:|\Z)', text, re.M | re.S)
self.assertFalse(block and re.search(r'^ - platform: homeassistant$', block[0], re.M), f'{path.name}: a {section} subscribes to Home Assistant')
def test_the_manual_profile_is_gone_from_profiles_and_packages(self):
for board, name in BOARDS.items():
for entry in (name, f'packages/{board}.yaml'):
text = '\n'.join(line for line in profiles.text(entry).split('\n') if not line.lstrip().startswith('#'))
for key in GONE:
self.assertNotIn(key, text, f'{entry} still carries {key}')
def test_runtime_tiles_keep_what_they_bind_and_open(self):
for board in BOARDS:
package = profiles.text(f'packages/{board}.yaml')
for key in ['runtime_tiles::bind(9, id(tile10)', 'runtime_tiles::enabled = true;', 'id: open_value_overlay',
'id: ui_refresh', 'runtime_tiles::render(id(lbl_room));', 'id: climate_detail_overlay',
'id: color_detail_overlay']:
self.assertIn(key, package, board)
# The CYD keeps its resistive calibration on the screen itself; the Guition's GT911 needs none.
self.assertIn('screen_calibration::setup(', profiles.resolved('packages/cyd.yaml'))
self.assertNotIn('screen_calibration::setup(', profiles.resolved('packages/guition.yaml'))
def test_the_core_carries_no_board_number_and_every_board_the_same_names(self):
core = profiles.CORE.read_text()
for needle in ('esp32:', 'platform: xpt2046', 'platform: gt911', 'platform: st7701s', 'platform: mipi_spi', 'psram:',
'set_raw_correction', 'camera_image', 'GPIO'):
self.assertNotIn(needle, core, f'{needle} is a board\'s, not the core\'s')
names = {board: set(profiles.substitutions_of(path)) for board, path in profiles.BOARDS.items()}
shared = set.intersection(*names.values())
self.assertGreater(len(shared), 100)
for board, defined in names.items():
own = defined - shared
self.assertTrue(all(re.match(r'(TOUCH_AFFINE_|TOUCH_CAL_|EDGE_SWIPE_|ALERT_\w*IMAGE|CAMERA_)', n) for n in own), f'{board}: {sorted(own)}')
def test_the_checker_refuses_a_fixed_home_assistant_subscription(self):
source = (ROOT / 'tools/check_packages.py').read_text()
self.assertIn("re.search(r'^ - platform: homeassistant$', match[0], re.M)", source)
self.assertIn("for section in ('sensor', 'binary_sensor', 'text_sensor'):", source)
if __name__ == '__main__':
unittest.main()