Put ESP Screens in English and keep layouts when entity IDs change (0.2.40)
Everything a user sees or reads is now English: both boards' labels, status replies and logs, the ESP Screens editor and Alerts cheatsheet, and the README and guides (docs/TILES.md, CALIBRATING.md, ACCEPTANCE.md and TROUBLESHOOTING.md replace the Dutch file names). Labels follow Home Assistant's own English names (Heat/Cool modes, Heating/Cooling actions, Away, Returning to dock, weather conditions), dates use English abbreviations and the top bar writes numbers the English way. Docs use HA 2026.9's menu names (Settings -> Apps, App store, Settings -> Tools). Unused legacy files (home-like.yaml, buttons.yaml, a.out) are removed. Firmware 0.2.34 names its entities in English. Home Assistant registers a renamed ESPHome entity under a new entity ID, so the inbox text.<screen>_tegelinstellingen becomes text.<screen>_tile_settings and the layout stored under the old ID would be lost. The manager now recognises the screen by its device, during a run or after a restart, and moves its layout and update history along; the Update button and the nightly round follow the screen through the rename. Screens that still run 0.2.33 or older keep working: both languages are matched. Main is the only release branch from now on. Tested on a Home Assistant Yellow (HA 2026.9.1) with both bench boards: see docs/TEST_RESULTS_0240.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+85
-85
@@ -1,4 +1,4 @@
|
||||
"""Top bar (app 0.2.38 / firmware 0.2.32): validation, Dutch text, icons, visibility and delivery."""
|
||||
"""Top bar (app 0.2.38 / firmware 0.2.32): validation, text, icons, visibility and delivery."""
|
||||
import asyncio
|
||||
import importlib.util
|
||||
import json
|
||||
@@ -28,10 +28,10 @@ def entity(eid, content='state', icon='auto', show='always'):
|
||||
|
||||
class ValidationTests(unittest.TestCase):
|
||||
def test_items_fill_defaults_and_keep_order(self):
|
||||
header = validate_header({'items': [{'type': 'clock'}, {'type': 'entity', 'entity': 'binary_sensor.voordeur'}, {'type': 'analog'}]})
|
||||
self.assertEqual(header['items'], [{'type': 'clock'}, entity('binary_sensor.voordeur'), {'type': 'analog'}])
|
||||
header = validate_header({'items': [{'type': 'clock'}, {'type': 'entity', 'entity': 'binary_sensor.front_door'}, {'type': 'analog'}]})
|
||||
self.assertEqual(header['items'], [{'type': 'clock'}, entity('binary_sensor.front_door'), {'type': 'analog'}])
|
||||
# Top-bar-only domains: a phone's tracker, a lock, the alarm, the home zone.
|
||||
for eid in ('device_tracker.telefoon', 'lock.voordeur', 'alarm_control_panel.huis', 'zone.home', 'event.deurbel'):
|
||||
for eid in ('device_tracker.phone', 'lock.front_door', 'alarm_control_panel.house', 'zone.home', 'event.doorbell'):
|
||||
validate_header({'items': [entity(eid)]})
|
||||
|
||||
def test_rejects_what_the_firmware_cannot_draw(self):
|
||||
@@ -56,22 +56,22 @@ class ValidationTests(unittest.TestCase):
|
||||
validate_header({'items': [entity('person.max'), entity('person.max', content='last_changed')]})
|
||||
|
||||
def test_layout_keeps_header_and_old_layouts_keep_their_clock(self):
|
||||
layout = validate_layout({'title': 'Woonkamer', 'tiles': [], 'header': {'items': [{'type': 'date'}]}})
|
||||
layout = validate_layout({'title': 'Living room', 'tiles': [], 'header': {'items': [{'type': 'date'}]}})
|
||||
self.assertEqual(header_items(layout), [{'type': 'date'}])
|
||||
self.assertEqual(header_items(validate_layout({'title': 'Thuis', 'tiles': []})), [{'type': 'clock'}])
|
||||
without_clock = validate_layout({'title': 'Thuis', 'tiles': [], 'settings': {'show_clock': False}})
|
||||
self.assertEqual(header_items(validate_layout({'title': 'Home', 'tiles': []})), [{'type': 'clock'}])
|
||||
without_clock = validate_layout({'title': 'Home', 'tiles': [], 'settings': {'show_clock': False}})
|
||||
self.assertEqual(header_items(without_clock), [])
|
||||
|
||||
class TextTests(unittest.TestCase):
|
||||
def test_numbers_as_home_assistant_writes_them_in_dutch(self):
|
||||
self.assertEqual(header_bar.number_text(21.34, 1), '21,3')
|
||||
self.assertEqual(header_bar.number_text(1249.0, 0), '1.249')
|
||||
self.assertEqual(header_bar.number_text(1249.04, 1), '1.249,0')
|
||||
self.assertEqual(header_bar.number_text(234.038566388889), '234,039')
|
||||
self.assertEqual(header_bar.number_text(23.5), '23,5')
|
||||
self.assertEqual(header_bar.number_text(-3.5, 1), '-3,5')
|
||||
self.assertEqual(header_bar.number_text(-0.04, 1), '0,0')
|
||||
self.assertEqual(header_bar.with_unit('21,3', '°C'), '21,3 °C')
|
||||
def test_numbers_as_home_assistant_writes_them(self):
|
||||
self.assertEqual(header_bar.number_text(21.34, 1), '21.3')
|
||||
self.assertEqual(header_bar.number_text(1249.0, 0), '1,249')
|
||||
self.assertEqual(header_bar.number_text(1249.04, 1), '1,249.0')
|
||||
self.assertEqual(header_bar.number_text(234.038566388889), '234.039')
|
||||
self.assertEqual(header_bar.number_text(23.5), '23.5')
|
||||
self.assertEqual(header_bar.number_text(-3.5, 1), '-3.5')
|
||||
self.assertEqual(header_bar.number_text(-0.04, 1), '0.0')
|
||||
self.assertEqual(header_bar.with_unit('21.3', '°C'), '21.3 °C')
|
||||
self.assertEqual(header_bar.with_unit('65', '%'), '65%')
|
||||
self.assertEqual(header_bar.with_unit('18', '°'), '18°')
|
||||
self.assertEqual(header_bar.with_unit('4', None), '4')
|
||||
@@ -81,63 +81,63 @@ class TextTests(unittest.TestCase):
|
||||
registry = {'sensor.t': {'options': {'sensor': {'suggested_display_precision': 1}}},
|
||||
'sensor.p': {'options': {'sensor': {'suggested_display_precision': 2, 'display_precision': 0}}}}
|
||||
cases = [
|
||||
('sensor.t', state('21.34', unit_of_measurement='°C', device_class='temperature'), ('21,3 °C', None)),
|
||||
('sensor.p', state('1249.4', unit_of_measurement='W'), ('1.249 W', None)),
|
||||
('sensor.t', state('21.34', unit_of_measurement='°C', device_class='temperature'), ('21.3 °C', None)),
|
||||
('sensor.p', state('1249.4', unit_of_measurement='W'), ('1,249 W', None)),
|
||||
('sensor.h', state('unavailable', unit_of_measurement='%'), ('—', None)),
|
||||
('binary_sensor.deur', state('on', device_class='door'), ('Open', None)),
|
||||
('binary_sensor.deur', state('off', device_class='door'), ('Dicht', None)),
|
||||
('binary_sensor.rook', state('on', device_class='smoke'), ('Rook', None)),
|
||||
('binary_sensor.iets', state('off'), ('Uit', None)),
|
||||
('alarm_control_panel.huis', state('armed_away'), ('Afwezig', None)),
|
||||
('alarm_control_panel.huis', state('disarmed'), ('Uit', None)),
|
||||
('alarm_control_panel.huis', state('triggered'), ('Alarm', None)),
|
||||
('lock.voordeur', state('locked'), ('Op slot', None)),
|
||||
('person.max', state('not_home'), ('Weg', None)),
|
||||
('person.max', state('Kantoor'), ('Kantoor', None)),
|
||||
('binary_sensor.door', state('on', device_class='door'), ('Open', None)),
|
||||
('binary_sensor.door', state('off', device_class='door'), ('Closed', None)),
|
||||
('binary_sensor.smoke', state('on', device_class='smoke'), ('Smoke', None)),
|
||||
('binary_sensor.something', state('off'), ('Off', None)),
|
||||
('alarm_control_panel.house', state('armed_away'), ('Armed away', None)),
|
||||
('alarm_control_panel.house', state('disarmed'), ('Disarmed', None)),
|
||||
('alarm_control_panel.house', state('triggered'), ('Triggered', None)),
|
||||
('lock.front_door', state('locked'), ('Locked', None)),
|
||||
('person.max', state('not_home'), ('Away', None)),
|
||||
('person.max', state('Office'), ('Office', None)),
|
||||
('zone.home', state('4'), ('4', None)),
|
||||
('weather.thuis', state('rainy', temperature=17.6, temperature_unit='°C'), ('18 °C', None)),
|
||||
('climate.woonkamer', state('heat', current_temperature=20.46), ('20,5 °C', None)),
|
||||
('weather.home', state('rainy', temperature=17.6, temperature_unit='°C'), ('18 °C', None)),
|
||||
('climate.living_room', state('heat', current_temperature=20.46), ('20.5 °C', None)),
|
||||
('sun.sun', state('above_horizon', next_setting='2026-09-14T17:57:31+00:00'), ('19:57', None)),
|
||||
('input_datetime.wekker', state('07:30:00', has_date=False, has_time=True), ('07:30', None)),
|
||||
('input_datetime.alarm', state('07:30:00', has_date=False, has_time=True), ('07:30', None)),
|
||||
('sensor.backup', state('2026-09-15T03:34:32+00:00', device_class='timestamp'), (None, 1789443272)),
|
||||
('scene.film', state('2026-09-14T15:32:14+00:00'), (None, 1789399934)),
|
||||
('script.alles_uit', state('off', last_triggered='2026-09-14T15:32:14+00:00'), (None, 1789399934)),
|
||||
('script.all_off', state('off', last_triggered='2026-09-14T15:32:14+00:00'), (None, 1789399934)),
|
||||
]
|
||||
for eid, value, expected in cases:
|
||||
self.assertEqual(header_bar.value(eid, value, registry.get(eid), {'temperature': '°C'}, tz), expected, eid)
|
||||
|
||||
def test_text_keeps_to_the_font_and_its_length(self):
|
||||
self.assertEqual(header_bar.clean_text('Café “Zon” 25 m³'), 'Café Zon 25 m³')
|
||||
self.assertEqual(header_bar.clean_text('Café “Sun” 25 m³'), 'Café Sun 25 m³')
|
||||
self.assertEqual(header_bar.clean_text('Ångström ✓'), 'Angström')
|
||||
self.assertLessEqual(len(header_bar.clean_text('é' * 60).encode()), header_bar.TEXT_BYTES)
|
||||
|
||||
def test_active_colour_and_icon(self):
|
||||
door_open, door_closed = state('on', device_class='door'), state('off', device_class='door')
|
||||
self.assertTrue(header_bar.active('binary_sensor.deur', door_open))
|
||||
self.assertFalse(header_bar.active('binary_sensor.deur', door_closed))
|
||||
self.assertTrue(header_bar.active('binary_sensor.door', door_open))
|
||||
self.assertFalse(header_bar.active('binary_sensor.door', door_closed))
|
||||
self.assertFalse(header_bar.active('sensor.p', state('0.0')))
|
||||
self.assertTrue(header_bar.active('sensor.p', state('12')))
|
||||
self.assertTrue(header_bar.active('person.max', state('home')))
|
||||
self.assertFalse(header_bar.active('person.max', state('Kantoor')))
|
||||
self.assertFalse(header_bar.active('lock.voordeur', state('locked')))
|
||||
self.assertFalse(header_bar.active('person.max', state('Office')))
|
||||
self.assertFalse(header_bar.active('lock.front_door', state('locked')))
|
||||
self.assertFalse(header_bar.active('sensor.x', state('unavailable')))
|
||||
self.assertEqual(header_bar.accent('binary_sensor.deur', door_open), header_bar.AMBER)
|
||||
self.assertIsNone(header_bar.accent('binary_sensor.deur', door_closed))
|
||||
self.assertEqual(header_bar.accent('binary_sensor.lek', state('on', device_class='moisture')), header_bar.RED)
|
||||
self.assertEqual(header_bar.accent('alarm_control_panel.huis', state('armed_home')), header_bar.GREEN)
|
||||
self.assertEqual(header_bar.accent('alarm_control_panel.huis', state('arming')), header_bar.ORANGE)
|
||||
self.assertEqual(header_bar.accent('alarm_control_panel.huis', state('triggered')), header_bar.RED)
|
||||
self.assertIsNone(header_bar.accent('alarm_control_panel.huis', state('disarmed')))
|
||||
self.assertEqual(header_bar.accent('lock.voordeur', state('unlocked')), header_bar.RED)
|
||||
self.assertEqual(header_bar.accent('binary_sensor.door', door_open), header_bar.AMBER)
|
||||
self.assertIsNone(header_bar.accent('binary_sensor.door', door_closed))
|
||||
self.assertEqual(header_bar.accent('binary_sensor.leak', state('on', device_class='moisture')), header_bar.RED)
|
||||
self.assertEqual(header_bar.accent('alarm_control_panel.house', state('armed_home')), header_bar.GREEN)
|
||||
self.assertEqual(header_bar.accent('alarm_control_panel.house', state('arming')), header_bar.ORANGE)
|
||||
self.assertEqual(header_bar.accent('alarm_control_panel.house', state('triggered')), header_bar.RED)
|
||||
self.assertIsNone(header_bar.accent('alarm_control_panel.house', state('disarmed')))
|
||||
self.assertEqual(header_bar.accent('lock.front_door', state('unlocked')), header_bar.RED)
|
||||
self.assertEqual(header_bar.accent('person.max', state('home')), header_bar.GREEN)
|
||||
glyph = tile_icons.GLYPHS
|
||||
self.assertEqual(header_bar.auto_icon('binary_sensor.deur', door_open), glyph['door-open'])
|
||||
self.assertEqual(header_bar.auto_icon('binary_sensor.deur', door_closed), glyph['door-closed'])
|
||||
self.assertEqual(header_bar.auto_icon('binary_sensor.door', door_open), glyph['door-open'])
|
||||
self.assertEqual(header_bar.auto_icon('binary_sensor.door', door_closed), glyph['door-closed'])
|
||||
self.assertEqual(header_bar.auto_icon('sensor.t', state('21', device_class='temperature')), glyph['thermometer'])
|
||||
self.assertEqual(header_bar.auto_icon('sensor.t', state('21', device_class='temperature', icon='mdi:fire')), glyph['fire'])
|
||||
self.assertEqual(header_bar.auto_icon('weather.thuis', state('rainy')), glyph['weather-rainy'])
|
||||
self.assertEqual(header_bar.auto_icon('lock.voordeur', state('unlocked')), glyph['lock-open-variant'])
|
||||
self.assertEqual(header_bar.auto_icon('counter.bezoekers', None), glyph['gauge'])
|
||||
self.assertEqual(header_bar.auto_icon('weather.home', state('rainy')), glyph['weather-rainy'])
|
||||
self.assertEqual(header_bar.auto_icon('lock.front_door', state('unlocked')), glyph['lock-open-variant'])
|
||||
self.assertEqual(header_bar.auto_icon('counter.visitors', None), glyph['gauge'])
|
||||
|
||||
def test_every_icon_name_is_in_the_fonts(self):
|
||||
names = set(header_bar.SENSOR_ICONS.values()) | set(header_bar.DOMAIN_ICONS.values())
|
||||
@@ -148,45 +148,45 @@ class TextTests(unittest.TestCase):
|
||||
|
||||
class MessageTests(unittest.TestCase):
|
||||
def test_message_leaves_hidden_items_out_and_keeps_builtins(self):
|
||||
states = {'binary_sensor.deur': state('off', device_class='door'), 'person.max': state('home', last_changed='2026-09-14T15:00:00+00:00'),
|
||||
states = {'binary_sensor.door': state('off', device_class='door'), 'person.max': state('home', last_changed='2026-09-14T15:00:00+00:00'),
|
||||
'sensor.t': state('21.3', unit_of_measurement='°C', device_class='temperature')}
|
||||
layout = {'title': 'Hal', 'tiles': [], 'header': {'items': [
|
||||
entity('binary_sensor.deur', show='active'), entity('person.max', content='last_changed'),
|
||||
layout = {'title': 'Hallway', 'tiles': [], 'header': {'items': [
|
||||
entity('binary_sensor.door', show='active'), entity('person.max', content='last_changed'),
|
||||
entity('sensor.t', icon='none'), {'type': 'analog'}, {'type': 'clock'}]}}
|
||||
message = header_bar.message(layout, states)
|
||||
self.assertEqual(message['op'], 'header')
|
||||
self.assertEqual(message['items'], [
|
||||
{'k': 'ago', 'i': tile_icons.GLYPHS['account'], 'e': 1789398000, 'c': header_bar.GREEN},
|
||||
{'k': 'text', 't': '21,3 °C'}, {'k': 'analog'}, {'k': 'clock'}])
|
||||
states['binary_sensor.deur'] = state('on', device_class='door')
|
||||
{'k': 'text', 't': '21.3 °C'}, {'k': 'analog'}, {'k': 'clock'}])
|
||||
states['binary_sensor.door'] = state('on', device_class='door')
|
||||
opened = header_bar.message(layout, states)['items'][0]
|
||||
self.assertEqual(opened, {'k': 'text', 'i': tile_icons.GLYPHS['door-open'], 't': 'Open', 'c': header_bar.AMBER})
|
||||
preview = header_bar.preview(layout['header'], {**states, 'binary_sensor.deur': state('off', device_class='door')})
|
||||
preview = header_bar.preview(layout['header'], {**states, 'binary_sensor.door': state('off', device_class='door')})
|
||||
self.assertEqual([p['shown'] for p in preview], [False, True, True, True, True])
|
||||
self.assertEqual(preview[0]['name'], 'binary_sensor.deur')
|
||||
self.assertEqual(preview[0]['name'], 'binary_sensor.door')
|
||||
|
||||
def test_old_layout_sends_the_clock_of_show_clock(self):
|
||||
self.assertEqual(header_bar.message({'title': 'Thuis', 'tiles': []}, {})['items'], [{'k': 'clock'}])
|
||||
self.assertEqual(header_bar.message({'title': 'Thuis', 'tiles': [], 'settings': {'show_clock': False}}, {})['items'], [])
|
||||
self.assertEqual(header_bar.message({'title': 'Home', 'tiles': []}, {})['items'], [{'k': 'clock'}])
|
||||
self.assertEqual(header_bar.message({'title': 'Home', 'tiles': [], 'settings': {'show_clock': False}}, {})['items'], [])
|
||||
|
||||
def test_suggestions_prefer_the_screens_room_and_skip_diagnostics(self):
|
||||
states = {'sensor.plug_temp': state('30', device_class='temperature'), 'sensor.woonkamer_temp': state('21', device_class='temperature'),
|
||||
'sensor.zolder_temp': state('18', device_class='temperature'), 'zone.home': state('3'), 'sun.sun': state('above_horizon')}
|
||||
entities = [{'id': 'sensor.plug_temp', 'name': 'Plug', 'area': 'Woonkamer'}, {'id': 'sensor.woonkamer_temp', 'name': 'Woonkamer', 'area': 'Woonkamer'},
|
||||
{'id': 'sensor.zolder_temp', 'name': 'Zolder', 'area': 'Zolder'}, {'id': 'zone.home', 'name': 'Thuis', 'area': ''},
|
||||
{'id': 'sun.sun', 'name': 'Zon', 'area': ''}]
|
||||
picks = header_bar.suggestions({'name': 'Woonkamer'}, entities, states, {'sensor.plug_temp': {'entity_category': 'diagnostic'}})
|
||||
states = {'sensor.plug_temp': state('30', device_class='temperature'), 'sensor.living_room_temp': state('21', device_class='temperature'),
|
||||
'sensor.attic_temp': state('18', device_class='temperature'), 'zone.home': state('3'), 'sun.sun': state('above_horizon')}
|
||||
entities = [{'id': 'sensor.plug_temp', 'name': 'Plug', 'area': 'Living room'}, {'id': 'sensor.living_room_temp', 'name': 'Living room', 'area': 'Living room'},
|
||||
{'id': 'sensor.attic_temp', 'name': 'Attic', 'area': 'Attic'}, {'id': 'zone.home', 'name': 'Home', 'area': ''},
|
||||
{'id': 'sun.sun', 'name': 'Sun', 'area': ''}]
|
||||
picks = header_bar.suggestions({'name': 'Living room'}, entities, states, {'sensor.plug_temp': {'entity_category': 'diagnostic'}})
|
||||
self.assertEqual([(p['label'], p['item']['entity']) for p in picks],
|
||||
[('Temperatuur', 'sensor.woonkamer_temp'), ('Aantal thuis', 'zone.home'), ('Zon op en onder', 'sun.sun')])
|
||||
[('Temperature', 'sensor.living_room_temp'), ('People home', 'zone.home'), ('Sunrise and sunset', 'sun.sun')])
|
||||
for pick in picks:
|
||||
validate_header({'items': [pick['item']]})
|
||||
|
||||
def test_discovery_offers_top_bar_domains_outside_the_tile_picker(self):
|
||||
registry = [{'entity_id': 'lock.voordeur'}, {'entity_id': 'light.hal'}, {'entity_id': 'update.core'}]
|
||||
states = {'lock.voordeur': state('locked'), 'light.hal': state('on'), 'update.core': state('off'), 'zone.home': state('2')}
|
||||
registry = [{'entity_id': 'lock.front_door'}, {'entity_id': 'light.hall'}, {'entity_id': 'update.core'}]
|
||||
states = {'lock.front_door': state('locked'), 'light.hall': state('on'), 'update.core': state('off'), 'zone.home': state('2')}
|
||||
_, entities = discover(registry, states, [], [])
|
||||
flags = {e['id']: e.get('tile', True) for e in entities}
|
||||
self.assertEqual(flags, {'lock.voordeur': False, 'light.hal': True, 'zone.home': False})
|
||||
self.assertEqual(flags, {'lock.front_door': False, 'light.hall': True, 'zone.home': False})
|
||||
|
||||
class ParityTests(unittest.TestCase):
|
||||
def test_text_font_carries_exactly_the_glyphs_the_add_on_sends(self):
|
||||
@@ -198,8 +198,8 @@ class ParityTests(unittest.TestCase):
|
||||
self.assertEqual({a or b for a, b in glyphs}, set(header_bar.GLYPHS), name)
|
||||
|
||||
def test_editor_and_firmware_share_words_and_spacing(self):
|
||||
for phrase in ('Zojuist', ' min geleden', ' uur geleden', 'Gisteren', ' dagen geleden', '1 week geleden', ' weken geleden',
|
||||
'1 maand geleden', ' maanden geleden', ' jaar geleden', 'Over ', 'Morgen'):
|
||||
for phrase in ('Just now', ' min ago', ' hour ago', ' hours ago', 'Yesterday', ' days ago', '1 week ago', ' weeks ago',
|
||||
'1 month ago', ' months ago', ' year ago', ' years ago', 'In ', 'Tomorrow'):
|
||||
self.assertIn(phrase, FIRMWARE)
|
||||
self.assertIn(phrase, EDITOR)
|
||||
for threshold in ('3600', '86400', '172800', '604800', '2592000', '31536000'):
|
||||
@@ -232,10 +232,10 @@ class ManagerTests(unittest.IsolatedAsyncioTestCase):
|
||||
def manager(self, path, firmware='0.2.32'):
|
||||
class HA:
|
||||
online = True
|
||||
registry = [{'entity_id': 'text.screen', 'platform': 'esphome', 'original_name': 'Tegelinstellingen', 'device_id': 'd'},
|
||||
{'entity_id': 'sensor.fw', 'platform': 'esphome', 'original_name': 'Schermfirmware', 'device_id': 'd'},
|
||||
registry = [{'entity_id': 'text.screen', 'platform': 'esphome', 'original_name': 'Tile settings', 'device_id': 'd'},
|
||||
{'entity_id': 'sensor.fw', 'platform': 'esphome', 'original_name': 'Screen firmware', 'device_id': 'd'},
|
||||
{'entity_id': 'sensor.t', 'options': {'sensor': {'suggested_display_precision': 1}}},
|
||||
{'entity_id': 'binary_sensor.deur'}, {'entity_id': 'light.a'}]
|
||||
{'entity_id': 'binary_sensor.door'}, {'entity_id': 'light.a'}]
|
||||
devices, areas = [], []
|
||||
time_zone, units = None, {'temperature': '°C'}
|
||||
def __init__(self):
|
||||
@@ -243,7 +243,7 @@ class ManagerTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.changed = asyncio.Event()
|
||||
self.states = {'text.screen': state('Ready'), 'sensor.fw': state(firmware), 'light.a': state('on'),
|
||||
'sensor.t': state('21.34', unit_of_measurement='°C', device_class='temperature'),
|
||||
'binary_sensor.deur': state('off', device_class='door')}
|
||||
'binary_sensor.door': state('off', device_class='door')}
|
||||
async def send(self, inbox, message, action=None):
|
||||
self.messages.append(message)
|
||||
return Manager(HA(), path)
|
||||
@@ -251,21 +251,21 @@ class ManagerTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_header_follows_layout_only_on_new_firmware(self):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
m = self.manager(Path(temp) / 'screens.json')
|
||||
layout = {'title': 'Hal', 'tiles': [{'entity': 'light.a', 'name': ''}], 'settings': {'show_clock': True},
|
||||
'header': {'items': [entity('sensor.t'), entity('binary_sensor.deur', show='active')]}}
|
||||
layout = {'title': 'Hallway', 'tiles': [{'entity': 'light.a', 'name': ''}], 'settings': {'show_clock': True},
|
||||
'header': {'items': [entity('sensor.t'), entity('binary_sensor.door', show='active')]}}
|
||||
m.save('text.screen', layout)
|
||||
saved = m.layouts['text.screen']
|
||||
self.assertFalse(saved['settings']['show_clock'], 'older firmware hides its clock when the bar has none')
|
||||
await m.sync_one('text.screen', saved)
|
||||
self.assertEqual([msg['op'] for msg in m.ha.messages], ['layout', 'header', 'state'])
|
||||
self.assertEqual(m.ha.messages[1]['items'], [{'k': 'text', 'i': tile_icons.GLYPHS['thermometer'], 't': '21,3 °C'}])
|
||||
self.assertEqual(m.ha.messages[1]['items'], [{'k': 'text', 'i': tile_icons.GLYPHS['thermometer'], 't': '21.3 °C'}])
|
||||
await m.sync_one('text.screen', saved)
|
||||
self.assertEqual(len(m.ha.messages), 3, 'an unchanged bar is not sent again')
|
||||
m.ha.states['binary_sensor.deur'] = state('on', device_class='door')
|
||||
m.ha.states['binary_sensor.door'] = state('on', device_class='door')
|
||||
await m.sync_one('text.screen', saved)
|
||||
self.assertEqual(len(m.ha.messages), 4)
|
||||
self.assertEqual(m.ha.messages[-1]['items'][1]['t'], 'Open')
|
||||
self.assertIn('binary_sensor.deur', m.watched_entities())
|
||||
self.assertIn('binary_sensor.door', m.watched_entities())
|
||||
old = self.manager(Path(temp) / 'old.json', firmware='0.2.31')
|
||||
old.save('text.screen', layout)
|
||||
await old.sync_one('text.screen', old.layouts['text.screen'])
|
||||
@@ -275,11 +275,11 @@ class ManagerTests(unittest.IsolatedAsyncioTestCase):
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
m = self.manager(Path(temp) / 'screens.json')
|
||||
header = {'items': [{'type': 'date'}, {'type': 'clock'}]}
|
||||
m.save('text.screen', {'title': 'Hal', 'tiles': [], 'header': header})
|
||||
m.save('text.screen', {'title': 'Andere naam', 'tiles': []})
|
||||
m.save('text.screen', {'title': 'Hallway', 'tiles': [], 'header': header})
|
||||
m.save('text.screen', {'title': 'Different name', 'tiles': []})
|
||||
self.assertEqual(m.layouts['text.screen']['header'], header)
|
||||
with self.assertRaises(ValueError):
|
||||
m.save('text.screen', {'title': 'Hal', 'tiles': [], 'header': {'items': [entity('sensor.bestaat_niet')]}})
|
||||
m.save('text.screen', {'title': 'Hallway', 'tiles': [], 'header': {'items': [entity('sensor.does_not_exist')]}})
|
||||
self.assertEqual(m.layouts['text.screen']['header'], header)
|
||||
|
||||
async def test_preview_endpoint_formats_unsaved_items(self):
|
||||
@@ -291,11 +291,11 @@ class ManagerTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(inventory['header']['max_items'], 6)
|
||||
self.assertEqual(inventory['header']['min_firmware'], '0.2.32')
|
||||
headers = {'X-Screen-CSRF': inventory['csrf']}
|
||||
items = [entity('binary_sensor.deur', show='active'), {'type': 'clock'}]
|
||||
items = [entity('binary_sensor.door', show='active'), {'type': 'clock'}]
|
||||
response = await client.post('/api/header-preview', json={'header': {'items': items}}, headers=headers)
|
||||
data = await response.json()
|
||||
self.assertEqual([p['shown'] for p in data['items']], [False, True])
|
||||
self.assertEqual(data['items'][0]['t'], 'Dicht')
|
||||
self.assertEqual(data['items'][0]['t'], 'Closed')
|
||||
bad = await client.post('/api/header-preview', json={'header': {'items': [{'type': 'nope'}]}}, headers=headers)
|
||||
self.assertEqual(bad.status, 400)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user