Files
homeassistant_espscreen/tests/test_settings_view.py
T
Max Gramser 4ff0d44312 Send one alert to every screen, teach Claude about ESP Screens, and add a Settings page (0.2.45)
- The app listens for the Home Assistant events esp_screens_show_alert and
  esp_screens_dismiss_alert and calls each screen's show_alert/dismiss_alert action,
  screens added later included. Fields are typed like Home Assistant's ESPHome action
  schema; an unusable value is left empty instead of losing the alert.
- Settings → Claude installs an ESP Screens skill for Claude Code in Home Assistant
  (/homeassistant/.claude/skills/esp-screens) or downloads it as a zip for claude.ai.
  It is generated from the alert reference, so fields, colors and icons match the firmware.
- New screen, Firmware & USB, firmware updates, Alerts and Claude move to a Settings view;
  the header keeps one button. Unselected screens in My screens get a light grey card.

App only; firmware stays 0.2.38.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 14:52:27 +02:00

53 lines
2.7 KiB
Python

"""Settings (app 0.2.45): New screen, Firmware & USB, firmware updates, Alerts and Claude moved off the main
page into a view of their own, so the header keeps one button and My screens keeps the editor."""
from pathlib import Path
import re
import unittest
STATIC = Path(__file__).resolve().parents[1] / 'screen_manager/app/static'
def between(text, start, end):
return text.split(start, 1)[1].split(end, 1)[0]
class SettingsView(unittest.TestCase):
def setUp(self):
self.html = (STATIC / 'index.html').read_text()
self.script = (STATIC / 'app.js').read_text()
self.css = (STATIC / 'style.css').read_text()
def test_header_keeps_only_the_settings_button(self):
header = between(self.html, '<header>', '</header>')
self.assertEqual(re.findall(r'<button id="([\w-]+)"', header), ['open-settings'])
def test_every_tool_lives_in_the_settings_view_once(self):
settings = between(self.html, '<section id="settings-view"', '</main>')
home = between(self.html, '<div id="home-view">', '<section id="settings-view"')
for element in ('new-screen', 'open-firmware', 'updates', 'update-all', 'auto-update', 'open-alerts',
'claude-install', 'claude-status', 'claude-path', 'claude-download', 'close-settings'):
self.assertEqual(self.html.count(f'id="{element}"'), 1, element)
self.assertIn(f'id="{element}"', settings, element)
self.assertNotIn(f'id="{element}"', home, element)
# The first-run button stays where a new user starts.
self.assertIn('id="start"', home)
self.assertIn('href="api/claude-skill.zip"', settings)
def test_the_view_switches_by_hash_and_renders_what_it_shows(self):
for marker in ('function showView', 'location.hash === "#settings"', 'window.addEventListener("hashchange", showView)',
'$("#home-view").hidden = settings', 'function renderClaude', 'api("claude-skill", { method: "POST" })',
'if (full) renderClaude();', '$("#new-screen").onclick = $("#start").onclick = openInstaller'):
self.assertIn(marker, self.script, marker)
self.assertNotIn('Not flashed yet? Firmware & USB', self.script, 'texts point to Settings')
for marker in ('.settings-groups', '.settings-group', '.claude-skill', '.screen:hover:not(.selected)'):
self.assertIn(marker, self.css, marker)
def test_unselected_screens_get_a_grey_card(self):
rule = between(self.css, '\n.screen {', '}')
self.assertIn('background: #e9ecf1', rule)
self.assertIn('background: white', between(self.css, '\n.screen.selected {', '}'))
if __name__ == '__main__':
unittest.main()