Build each screen in one language: the screen texts from the translations, European letters in the fonts, and Language & region in the app
The firmware takes its texts from screen_manager/translations/<code>.json at build time (components/smart_display: one table in main.cpp, keys in screen_text_keys.h), and the fonts carry every letter European languages write with. The app keeps Settings -> Language & region (language, clock, numbers) for every screen, writes LANGUAGE into the screens' YAML and sends the clock and the number format in the layout message; the per-screen 24-hour clock goes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
// GENERATED by tools/i18n.py (`python3 tools/i18n.py header`): the English texts for the C++ tests, which
|
||||
// build a header without ESPHome and so without the table a screen's build writes into main.cpp.
|
||||
#pragma once
|
||||
#include "components/smart_display/screen_text.h"
|
||||
|
||||
static_assert(screen_text::KEYS_HASH == 0x725A92EEu && screen_text::KEY_COUNT == 302,
|
||||
"screen_text_keys.h does not match screen_manager/translations/en.json: run tools/i18n.py header");
|
||||
const char *const screen_text::TABLE[] = {
|
||||
".",
|
||||
",",
|
||||
"Just now",
|
||||
"{n} min ago",
|
||||
"1 hour ago | {n} hours ago",
|
||||
"Yesterday",
|
||||
"1 day ago | {n} days ago",
|
||||
"1 week ago | {n} weeks ago",
|
||||
"1 month ago | {n} months ago",
|
||||
"1 year ago | {n} years ago",
|
||||
"In {n} min",
|
||||
"In 1 hour | In {n} hours",
|
||||
"Tomorrow",
|
||||
"In 1 day | In {n} days",
|
||||
"AM",
|
||||
"PM",
|
||||
"Sunday",
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
"Sun",
|
||||
"Mon",
|
||||
"Tue",
|
||||
"Wed",
|
||||
"Thu",
|
||||
"Fri",
|
||||
"Sat",
|
||||
"Su",
|
||||
"Mo",
|
||||
"Tu",
|
||||
"We",
|
||||
"Th",
|
||||
"Fr",
|
||||
"Sa",
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
"{weekday} {day} {month}",
|
||||
"{weekday} {day} {month}",
|
||||
"{day} {month}",
|
||||
"{weekday} {time}",
|
||||
"{n} s",
|
||||
"{n} min",
|
||||
"{n} h",
|
||||
"{h} h {m} min",
|
||||
"{n} d",
|
||||
"{d} d {h} h",
|
||||
"Connecting to Home Assistant",
|
||||
"Waiting for ESP Screens",
|
||||
"Loading tiles...",
|
||||
"HA not connected",
|
||||
"ESP Screens not active",
|
||||
"Choose tiles in HA",
|
||||
"Home",
|
||||
"No network",
|
||||
"Settings",
|
||||
"Brightness",
|
||||
"Night",
|
||||
"Screen",
|
||||
"This screen",
|
||||
"Dark mode",
|
||||
"Auto standby",
|
||||
"Standby after",
|
||||
"Standby brightness",
|
||||
"Night mode",
|
||||
"Starts",
|
||||
"Ends",
|
||||
"Night brightness",
|
||||
"Back to page 1",
|
||||
"After",
|
||||
"Also on standby",
|
||||
"Swipe between pages",
|
||||
"Page buttons",
|
||||
"Rotation",
|
||||
"Address",
|
||||
"Firmware",
|
||||
"Home Assistant",
|
||||
"Restart",
|
||||
"Tap again to restart",
|
||||
"{n} sec",
|
||||
"{n} min",
|
||||
"{n} h",
|
||||
"{h} h {m}",
|
||||
"Tap to open",
|
||||
"Page {n}",
|
||||
"Unavailable",
|
||||
"Refused",
|
||||
"Command sent...",
|
||||
"Confirmed by Home Assistant",
|
||||
"Notification",
|
||||
"OK",
|
||||
"Color",
|
||||
"Color temperature",
|
||||
"Brightness",
|
||||
"Waiting...",
|
||||
"Effect",
|
||||
"Nothing to choose",
|
||||
"Loading...",
|
||||
"Not connected",
|
||||
"Target",
|
||||
"Mode",
|
||||
"Fan",
|
||||
"Swing",
|
||||
"Now {value}",
|
||||
"Check the robot in HA",
|
||||
"Automatic suction power",
|
||||
"The robot chooses suction and water",
|
||||
"Suction and water as set per room",
|
||||
"Quiet",
|
||||
"Normal",
|
||||
"Turbo",
|
||||
"Max",
|
||||
"Pause cleaning",
|
||||
"Pause",
|
||||
"Resume",
|
||||
"Start cleaning",
|
||||
"Clean",
|
||||
"Dock",
|
||||
"Position",
|
||||
"Tilt",
|
||||
"Tilt {n}%",
|
||||
"Feels like {n}\302\260",
|
||||
"No daily forecast from Home Assistant",
|
||||
"Coming days",
|
||||
"No data",
|
||||
"average",
|
||||
"average of {duration}",
|
||||
"Now",
|
||||
"1 hour",
|
||||
"24 hours",
|
||||
"1 week",
|
||||
"High {value}",
|
||||
"Low {value}",
|
||||
"once | {n} times",
|
||||
"Loading history...",
|
||||
"No history in this period",
|
||||
"No history available",
|
||||
"Muted",
|
||||
"Not playing",
|
||||
"Loading",
|
||||
"Running",
|
||||
"Paused",
|
||||
"Stopped",
|
||||
"Pause",
|
||||
"Start",
|
||||
"Cancel",
|
||||
"Paused {time}",
|
||||
"Sun",
|
||||
"Sunrise {time}",
|
||||
"Sunset {time}",
|
||||
"rise {time}",
|
||||
"set {time}",
|
||||
"Running...",
|
||||
"Never run",
|
||||
"Last {time}",
|
||||
"Yesterday {time}",
|
||||
"Last {date}",
|
||||
"No image from this camera",
|
||||
"Loading image",
|
||||
"Live",
|
||||
"Recording",
|
||||
"Tap to view",
|
||||
"No image yet",
|
||||
"Activate",
|
||||
"Run",
|
||||
"Press",
|
||||
"Follow the calibration wizard",
|
||||
"GT911 touch check",
|
||||
"Hold each tap for a moment",
|
||||
"Tap the crosshair\012Point {point}/5 - tap {tap}/3",
|
||||
"Tap gently, a bit longer",
|
||||
"Tap the same crosshair 3 times",
|
||||
"Measurement rejected; try again",
|
||||
"Save failed; try again",
|
||||
"On",
|
||||
"Off",
|
||||
"Off",
|
||||
"Heat",
|
||||
"Cool",
|
||||
"Heat/Cool",
|
||||
"Auto",
|
||||
"Dry",
|
||||
"Fan only",
|
||||
"Heating",
|
||||
"Cooling",
|
||||
"Idle",
|
||||
"Off",
|
||||
"Drying",
|
||||
"Fan",
|
||||
"Preheating",
|
||||
"Defrosting",
|
||||
"Open",
|
||||
"Closed",
|
||||
"Opening",
|
||||
"Closing",
|
||||
"Playing",
|
||||
"Paused",
|
||||
"Idle",
|
||||
"Standby",
|
||||
"Home",
|
||||
"Away",
|
||||
"Above the horizon",
|
||||
"Below the horizon",
|
||||
"Docked",
|
||||
"Cleaning",
|
||||
"Paused",
|
||||
"Returning to dock",
|
||||
"Idle",
|
||||
"Sunny",
|
||||
"Clear, night",
|
||||
"Cloudy",
|
||||
"Partly cloudy",
|
||||
"Rainy",
|
||||
"Pouring",
|
||||
"Snowy",
|
||||
"Snowy, rainy",
|
||||
"Fog",
|
||||
"Hail",
|
||||
"Lightning",
|
||||
"Lightning, rainy",
|
||||
"Windy",
|
||||
"Exceptional",
|
||||
"Low",
|
||||
"Normal",
|
||||
"Charging",
|
||||
"Not charging",
|
||||
"Danger",
|
||||
"Safe",
|
||||
"Cold",
|
||||
"Normal",
|
||||
"Connected",
|
||||
"Disconnected",
|
||||
"Open",
|
||||
"Closed",
|
||||
"Open",
|
||||
"Closed",
|
||||
"Danger",
|
||||
"Safe",
|
||||
"Hot",
|
||||
"Normal",
|
||||
"Light",
|
||||
"Dark",
|
||||
"Open",
|
||||
"Locked",
|
||||
"Wet",
|
||||
"Dry",
|
||||
"Motion",
|
||||
"No motion",
|
||||
"Moving",
|
||||
"Still",
|
||||
"Occupied",
|
||||
"Clear",
|
||||
"Open",
|
||||
"Closed",
|
||||
"Plugged in",
|
||||
"Unplugged",
|
||||
"On",
|
||||
"Off",
|
||||
"Home",
|
||||
"Away",
|
||||
"Problem",
|
||||
"OK",
|
||||
"Active",
|
||||
"Inactive",
|
||||
"Unsafe",
|
||||
"Safe",
|
||||
"Smoke",
|
||||
"No smoke",
|
||||
"Sound",
|
||||
"Silent",
|
||||
"Tampering",
|
||||
"OK",
|
||||
"Update",
|
||||
"Up to date",
|
||||
"Vibration",
|
||||
"Still",
|
||||
"Open",
|
||||
"Closed"
|
||||
};
|
||||
const char *const screen_text::LANGUAGE = "en";
|
||||
int screen_text::plural_index(int n) { return n == 1 ? 0 : 1; }
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
#include "components/smart_display/runtime_model.h"
|
||||
#include "components/smart_display/cyd_ui.h"
|
||||
#include <cassert>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
#define THEME_TEST
|
||||
#include "components/smart_display/alert_overlay.h"
|
||||
#include <cassert>
|
||||
|
||||
@@ -4,6 +4,7 @@ The screen maps the device class itself (`tile_controls::binary_state_text`), fr
|
||||
state message has carried since app 0.2.23: nothing new on the wire, so it works with older apps too. These tests keep
|
||||
its table equal to the add-on's `header_bar.BINARY_STATES`, which the top bar and the history card use.
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import unittest
|
||||
@@ -22,9 +23,17 @@ TILES = (ROOT / 'components/smart_display/runtime_tiles.h').read_text()
|
||||
PROFILES = ('guition-4848s040.yaml', 'home-like-2432s028.yaml', 'packages/cyd.yaml', 'packages/guition.yaml')
|
||||
|
||||
|
||||
ENGLISH = json.loads((ROOT / 'screen_manager/translations/en.json').read_text(encoding='utf-8'))
|
||||
|
||||
|
||||
def firmware_words():
|
||||
"""The firmware's table (app 0.2.90: keys into screen.ha.binary, in the screen's language) in English."""
|
||||
block = re.search(r'constexpr BinaryWords BINARY_WORDS\[\] = \{(.*?)\n\};', CONTROLS, re.S)
|
||||
return {device_class: (on, off) for device_class, on, off in re.findall(r'\{"(\w+)", "([^"]*)", "([^"]*)"\}', block.group(1))}
|
||||
words = ENGLISH['screen']['ha']['binary']
|
||||
found = {}
|
||||
for device_class, on, off in re.findall(r'\{"(\w+)", screen_text::txt::ha_binary_(\w+), screen_text::txt::ha_binary_(\w+)\}', block.group(1)):
|
||||
found[device_class] = (words[on], words[off])
|
||||
return found
|
||||
|
||||
|
||||
class BinaryWords(unittest.TestCase):
|
||||
@@ -41,7 +50,8 @@ class BinaryWords(unittest.TestCase):
|
||||
for attrs in ({}, {'device_class': 'future_class'}):
|
||||
self.assertEqual(header_bar.value('binary_sensor.x', {'state': 'on', 'attributes': attrs}), ('On', None))
|
||||
self.assertEqual(history_card.state_label('binary_sensor', 'off', attrs), 'Off')
|
||||
self.assertIn('return on ? "On" : "Off";', CONTROLS)
|
||||
self.assertIn('return screen_text::tr(on ? screen_text::txt::ha_on : screen_text::txt::ha_off);', CONTROLS)
|
||||
self.assertEqual((ENGLISH['screen']['ha']['on'], ENGLISH['screen']['ha']['off']), ('On', 'Off'))
|
||||
|
||||
def test_the_state_message_already_carries_the_class(self):
|
||||
tile = {'entity': 'binary_sensor.front_door', 'name': ''}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
#include "../components/smart_display/calibration_math.h"
|
||||
#include <cassert>
|
||||
int main() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
// clang++ -std=c++17 -Wall -Wextra -Werror -I. tests/test_camera_view.cpp -o /tmp/test_camera_view && /tmp/test_camera_view
|
||||
#include "../components/smart_display/camera_view.h"
|
||||
#include <cassert>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
#include "../components/smart_display/chunk_transport.h"
|
||||
#include <cassert>
|
||||
int main() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
#include "../components/smart_display/cyd_ui.h"
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
// c++ -std=c++17 -Wall -Wextra -pedantic tests/test_effects_page.cpp -o /tmp/test_effects_page && /tmp/test_effects_page
|
||||
// The model of a light's effects page (firmware 0.2.70+): what it can show, how a number maps to its slider, the texts.
|
||||
#define EFFECTS_PAGE_TEST
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
#include "components/smart_display/header_bar.h"
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
|
||||
@@ -18,6 +18,8 @@ import tile_icons # noqa: E402
|
||||
from core import FIRMWARE_VERSION, HEADER_MIN_FIRMWARE, discover, header_items, validate_header, validate_layout # noqa: E402
|
||||
|
||||
FIRMWARE = (ROOT / 'components/smart_display/header_bar.h').read_text()
|
||||
# The firmware's words live in the translations since app 0.2.90 (screen.time in English, the language of reference).
|
||||
FIRMWARE += json.dumps(json.loads((ROOT / 'screen_manager/translations/en.json').read_text(encoding='utf-8'))['screen']['time'])
|
||||
TILES = (ROOT / 'components/smart_display/runtime_tiles.h').read_text()
|
||||
EDITOR = (ROOT / 'web/src/model/topbar.ts').read_text()
|
||||
PROFILES = ('guition-4848s040.yaml', 'home-like-2432s028.yaml')
|
||||
@@ -109,8 +111,10 @@ class TextTests(unittest.TestCase):
|
||||
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é “Sun” 25 m³'), 'Café Sun 25 m³')
|
||||
self.assertEqual(header_bar.clean_text('Ångström ✓'), 'Angström')
|
||||
# Every letter European languages write with stays (app 0.2.90); anything else folds to its base letter or goes.
|
||||
self.assertEqual(header_bar.clean_text('Café “Sun” 25 m³'), 'Café “Sun” 25 m³')
|
||||
self.assertEqual(header_bar.clean_text('Ångström ✓'), 'Ångströ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):
|
||||
@@ -228,10 +232,10 @@ class ParityTests(unittest.TestCase):
|
||||
# Firmware 0.2.73+: until the first layout the text and a spinner stand in the middle and the name stays empty;
|
||||
# the first layout deletes them, so no spinner turns behind the tiles.
|
||||
render = TILES.split('inline void render(lv_obj_t *room) {', 1)[1].split('\n}', 1)[0]
|
||||
self.assertIn('if (!model.configured) boot_status(lv_obj_get_parent(room), !ha_connected() ? '
|
||||
'"Connecting to Home Assistant" : "Waiting for ESP Screens");', render)
|
||||
self.assertIn('if (!model.configured) boot_status(lv_obj_get_parent(room), tr(!ha_connected() ? '
|
||||
'txt::status_connecting : txt::status_waiting));', render)
|
||||
self.assertIn('else if (boot_panel) { lv_obj_delete(boot_panel);', render)
|
||||
self.assertIn('label(room, !model.configured ? "" :', render)
|
||||
self.assertIn('label(room, !model.configured ? std::string() :', render)
|
||||
boot = TILES.split('inline void boot_status(', 1)[1].split('\n}', 1)[0]
|
||||
self.assertIn('boot_spinner = spinner_create(boot_panel,', boot)
|
||||
# Under the tiles, the cards and an alert, in the name's place.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
// clang++ -std=c++17 -Wall -Wextra -Werror -I. tests/test_history_view.cpp -o /tmp/test_history_view && /tmp/test_history_view
|
||||
#include "../components/smart_display/history_view.h"
|
||||
#include <cassert>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
#define LIGHT_CONTROLS_TEST
|
||||
#include "../components/smart_display/light_controls.h"
|
||||
#include <cassert>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
// clang++ -std=c++17 -Wall -Wextra -Werror -I. tests/test_media_card.cpp -o /tmp/test_media_card && /tmp/test_media_card
|
||||
#include "../components/smart_display/media_card.h"
|
||||
#include <cassert>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
#include "../components/smart_display/runtime_model.h"
|
||||
#include <cassert>
|
||||
int main() {
|
||||
|
||||
@@ -47,14 +47,14 @@ ENTITY_IDS = {
|
||||
'standby_seconds': 'number.office_1_standby_after', 'standby_brightness': 'number.office_1_standby_brightness',
|
||||
'night_enabled': 'switch.office_1_night_mode', 'night_start': 'time.office_1_night_starts',
|
||||
'night_end': 'time.office_1_night_ends', 'night_brightness': 'number.office_1_night_brightness',
|
||||
'clock_24h': 'switch.office_1_24_hour_clock', 'auto_home': 'switch.office_1_back_to_page_1',
|
||||
'auto_home': 'switch.office_1_back_to_page_1',
|
||||
'auto_home_seconds': 'number.office_1_back_to_page_1_after', 'home_on_standby': 'switch.office_1_back_to_page_1_on_standby',
|
||||
'swipe_pages': 'switch.office_1_swipe_between_pages', 'rotation': 'select.office_1_rotation',
|
||||
'dark_mode': 'switch.office_1_dark_mode', 'page_buttons': 'switch.office_1_page_buttons',
|
||||
}
|
||||
STATES = {'brightness': '80.0', 'standby_enabled': 'on', 'standby_seconds': '600.0', 'standby_brightness': '20.0',
|
||||
'night_enabled': 'on', 'night_start': '22:30:00', 'night_end': '07:00:00', 'night_brightness': '5.0',
|
||||
'clock_24h': 'on', 'auto_home': 'on', 'auto_home_seconds': '120.0', 'home_on_standby': 'off',
|
||||
'auto_home': 'on', 'auto_home_seconds': '120.0', 'home_on_standby': 'off',
|
||||
'swipe_pages': 'on', 'rotation': '90°', 'dark_mode': 'off', 'page_buttons': 'on'}
|
||||
|
||||
|
||||
@@ -109,7 +109,8 @@ def fake_ha(firmware=FIRMWARE_VERSION, owned=True, guition=True):
|
||||
|
||||
class CoreSettings(unittest.TestCase):
|
||||
def test_every_setting_the_editor_offers_has_an_entity(self):
|
||||
self.assertEqual(set(SETTING_ENTITIES), set(SETTING_RULES) - {'show_clock'})
|
||||
# 12 or 24 hours is Settings -> Language & region's, for every screen at once (app 0.2.90).
|
||||
self.assertEqual(set(SETTING_ENTITIES), set(SETTING_RULES) - {'show_clock', 'clock_24h'})
|
||||
self.assertEqual(SETTING_ENTITIES_MIN_FIRMWARE, '0.2.49')
|
||||
self.assertLessEqual(tuple(map(int, SETTING_ENTITIES_MIN_FIRMWARE.split('.'))), tuple(map(int, FIRMWARE_VERSION.split('.'))))
|
||||
|
||||
@@ -179,7 +180,7 @@ class OwnedSettings(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertNotIn('settings', m.layouts['text.screen'], 'a screen that owns its settings has none stored')
|
||||
view = m.settings_view(m.screen('text.screen'))
|
||||
self.assertEqual((view['owner'], view['unavailable']), ('screen', []))
|
||||
self.assertEqual(view['keys'], [key for key in SETTING_RULES if key != 'show_clock'])
|
||||
self.assertEqual(view['keys'], [key for key in SETTING_RULES if key not in ('show_clock', 'clock_24h')])
|
||||
self.assertEqual({key: view['values'][key] for key in ('brightness', 'night_start', 'rotation', 'home_on_standby')},
|
||||
{'brightness': 80, 'night_start': 1350, 'rotation': 90, 'home_on_standby': False})
|
||||
m.ha.states[ENTITY_IDS['night_end']] = {'state': 'unavailable'}
|
||||
@@ -225,12 +226,11 @@ class OwnedSettings(unittest.IsolatedAsyncioTestCase):
|
||||
await m.change_settings('text.screen', {'brightness': 10})
|
||||
self.assertEqual(m.ha.calls, [('number.set_value', {'entity_id': ENTITY_IDS['brightness'], 'value': 10})])
|
||||
m.ha.calls.clear()
|
||||
await m.change_settings('text.screen', {'night_start': 1320, 'rotation': 270, 'clock_24h': False})
|
||||
await m.change_settings('text.screen', {'night_start': 1320, 'rotation': 270})
|
||||
self.assertEqual(m.ha.calls, [('time.set_value', {'entity_id': ENTITY_IDS['night_start'], 'time': '22:00:00'}),
|
||||
('switch.turn_off', {'entity_id': ENTITY_IDS['clock_24h']}),
|
||||
('select.select_option', {'entity_id': ENTITY_IDS['rotation'], 'option': '270°'})])
|
||||
m.ha.calls.clear()
|
||||
for bad in ({'standby_brightness': 95}, {'show_clock': False}, {'beep': True}, {}, None, {'brightness': '50'}):
|
||||
for bad in ({'standby_brightness': 95}, {'show_clock': False}, {'clock_24h': False}, {'beep': True}, {}, None, {'brightness': '50'}):
|
||||
with self.assertRaises(ValueError):
|
||||
await m.change_settings('text.screen', bad)
|
||||
m.ha.states[ENTITY_IDS['night_end']] = {'state': 'unavailable'}
|
||||
@@ -569,9 +569,12 @@ class Editor(unittest.TestCase):
|
||||
def test_the_rows_are_the_settings_of_the_screen_page(self):
|
||||
keys = re.findall(r'\{ key: "(\w+)", label: "[^"]+", kind: "(\w+)"', self.script)
|
||||
self.assertEqual([key for key, _ in keys], [key for key in SETTING_RULES if key != 'show_clock'][:0] or [key for key, _ in keys])
|
||||
self.assertEqual({key for key, _ in keys}, set(SETTING_RULES) - {'show_clock'})
|
||||
# 12 or 24 hours is Settings -> Language & region's, for every screen at once (app 0.2.90).
|
||||
self.assertEqual({key for key, _ in keys}, set(SETTING_RULES) - {'show_clock', 'clock_24h'})
|
||||
# Labels and steps as on the screen: the same rows, the same -/+ steps, the same duration ladder.
|
||||
page_labels = set(re.findall(r'(?:number|toggle|duration|moment|choice)\("([^"]+)"', SCREEN_PAGE))
|
||||
# The page's labels are keys into the translations since app 0.2.90; English is the reference.
|
||||
english = json.loads((ROOT / 'screen_manager/translations/en.json').read_text(encoding='utf-8'))['screen']['settings']
|
||||
page_labels = {english[key] for key in re.findall(r'(?:number|toggle|duration|moment|choice)\(screen_text::txt::settings_(\w+)', SCREEN_PAGE)}
|
||||
groups = self.script[self.script.index('const SETTING_GROUPS = ['):self.script.index('export type SettingRow')]
|
||||
script_labels = set(re.findall(r'label: "([^"]+)"', groups))
|
||||
self.assertEqual(page_labels, script_labels)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
#include "components/smart_display/screen_settings.h"
|
||||
#include <cassert>
|
||||
int main() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#define SETTINGS_SCREEN_TEST
|
||||
#include "screen_text_en.h"
|
||||
#include "../components/smart_display/settings_screen.h"
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
@@ -8,7 +9,7 @@ using namespace settings_screen;
|
||||
// The row of a page by its label, so a test says what it means.
|
||||
static const Row &row_named(const Page &page, const std::string &label) {
|
||||
for (uint8_t i = 0; i < page.count; ++i)
|
||||
if (label == page.rows[i].label) return page.rows[i];
|
||||
if (label == label_text(page.rows[i])) return page.rows[i];
|
||||
assert(false);
|
||||
return page.rows[0];
|
||||
}
|
||||
@@ -17,7 +18,7 @@ int main() {
|
||||
// ---- the duration ladder: seconds low down, quarters of an hour up top, and symmetric ----
|
||||
assert(ladder_step(60) == 30 && ladder_step(600) == 60 && ladder_step(1800) == 300);
|
||||
assert(ladder_step(3600) == 900 && ladder_step(36000) == 1800);
|
||||
Row timeout = duration("After", nullptr, nullptr, 30, 3600);
|
||||
Row timeout = duration(screen_text::txt::settings_after, nullptr, nullptr, 30, 3600);
|
||||
assert(stepped(timeout, 120, 1) == 150 && stepped(timeout, 120, -1) == 90);
|
||||
assert(stepped(timeout, 300, 1) == 360 && stepped(timeout, 360, -1) == 300);
|
||||
assert(stepped(timeout, 300, -1) == 270 && stepped(timeout, 270, 1) == 300);
|
||||
@@ -25,9 +26,9 @@ int main() {
|
||||
assert(at_end(timeout, 30, -1) && at_end(timeout, 3600, 1) && !at_end(timeout, 120, -1));
|
||||
|
||||
// ---- a number stops at its ends, a moment walks around midnight ----
|
||||
Row percent = number("Brightness", nullptr, nullptr, 5, 100, 5, "%");
|
||||
Row percent = number(screen_text::txt::settings_brightness, nullptr, nullptr, 5, 100, 5, "%");
|
||||
assert(stepped(percent, 100, 1) == 100 && stepped(percent, 5, -1) == 5 && stepped(percent, 95, 1) == 100);
|
||||
Row start = moment("Starts", nullptr, nullptr);
|
||||
Row start = moment(screen_text::txt::settings_starts, nullptr, nullptr);
|
||||
assert(stepped(start, 1320, 1) == 1335 && stepped(start, 1425, 1) == 0 && stepped(start, 0, -1) == 1425);
|
||||
assert(!at_end(start, 0, -1));
|
||||
// Holding the key walks whole hours, and first lands on the hour it is in.
|
||||
@@ -64,7 +65,7 @@ int main() {
|
||||
assert(screen_settings::current.standby_brightness == 30); // never above the normal brightness
|
||||
assert(screen_settings::current.valid());
|
||||
// Dark mode (firmware 0.2.54+) sits right under Brightness, a switch of its own outside the frozen block.
|
||||
assert(light.count == 5 && light.rows[1].kind == Kind::toggle && std::string(light.rows[1].label) == "Dark mode");
|
||||
assert(light.count == 5 && light.rows[1].kind == Kind::toggle && std::string(label_text(light.rows[1])) == "Dark mode");
|
||||
const Row &dark = row_named(light, "Dark mode");
|
||||
assert(value_text(dark) == "Off");
|
||||
dark.write(1);
|
||||
@@ -84,10 +85,11 @@ int main() {
|
||||
buttons.write(1);
|
||||
assert(page_buttons == 1);
|
||||
}
|
||||
const Row &clock = row_named(screen, "Clock");
|
||||
assert(clock.option_count == 2 && value_text(clock) == "24 hour");
|
||||
clock.write(0);
|
||||
assert(screen_settings::current.clock_24h == 0 && value_text(clock) == "12 hour");
|
||||
// 12 or 24 hours is chosen for every screen at once in ESP Screens (app 0.2.90): the page has no row for it, and
|
||||
// the value still arrives through set() and the layout message.
|
||||
for (uint8_t i = 0; i < screen.count; ++i) assert(std::string(label_text(screen.rows[i])) != "Clock");
|
||||
set("clock_24h", 0);
|
||||
assert(screen_settings::current.clock_24h == 0);
|
||||
const Row &home = row_named(screen, "Back to page 1");
|
||||
home.write(0);
|
||||
assert(auto_home == 0 && value_text(home) == "Off");
|
||||
@@ -107,17 +109,17 @@ int main() {
|
||||
assert(PAGE_COUNT == 5);
|
||||
for (uint8_t p = 0; p < PAGE_COUNT; ++p) {
|
||||
const Page &page = pages[p];
|
||||
assert(page.title && page.count && page.count <= 8);
|
||||
assert(page.title != NO_TEXT && *screen_text::tr(page.title) && page.count && page.count <= 8);
|
||||
for (uint8_t i = 0; i < page.count; ++i) {
|
||||
const Row &row = page.rows[i];
|
||||
assert(row.label && *row.label);
|
||||
assert(row.label != NO_TEXT && *label_text(row));
|
||||
if (row.kind == Kind::page) assert(row.opens > 0 && row.opens < PAGE_COUNT && *row.icon);
|
||||
if (row.kind == Kind::toggle || row.kind == Kind::choice || row.kind == Kind::number ||
|
||||
row.kind == Kind::duration || row.kind == Kind::moment)
|
||||
assert(row.read && row.write);
|
||||
if (row.kind == Kind::number) assert(row.step > 0 && row.high > row.low);
|
||||
if (row.kind == Kind::duration) assert(row.high > row.low);
|
||||
if (row.kind == Kind::choice) assert(row.options && row.option_count);
|
||||
if (row.kind == Kind::choice) assert((row.options || row.option_keys != NO_TEXT) && row.option_count);
|
||||
if (row.kind == Kind::info) assert(row.text);
|
||||
if (row.kind == Kind::action) assert(row.run && *row.icon);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
// The colour table (firmware 0.2.54): the light look stays the design it was, the dark look reads well beside a bed.
|
||||
// clang++ -std=c++17 -Wall -Wextra -Werror -I. tests/test_theme.cpp -o /tmp/test_theme && /tmp/test_theme
|
||||
#define THEME_TEST
|
||||
|
||||
+1
-1
@@ -151,7 +151,7 @@ class Setting(unittest.TestCase):
|
||||
self.assertEqual(DARK_MODE_MIN_FIRMWARE, '0.2.54')
|
||||
self.assertLessEqual(tuple(map(int, DARK_MODE_MIN_FIRMWARE.split('.'))), tuple(map(int, FIRMWARE_VERSION.split('.'))))
|
||||
page = (COMPONENT / 'settings_screen.h').read_text()
|
||||
self.assertIn('toggle("Dark mode", []() -> int32_t { return dark_mode; },', page)
|
||||
self.assertIn('toggle(screen_text::txt::settings_dark_mode, []() -> int32_t { return dark_mode; },', page)
|
||||
self.assertIn('else if (key == "dark_mode") reported = dark_mode = flag(value);', page)
|
||||
editor = (ROOT / 'web/src/store.ts').read_text()
|
||||
self.assertIn('{ key: "dark_mode", label: "Dark mode", kind: "toggle" },', editor)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
// c++ -std=c++17 -Wall -Wextra -pedantic tests/test_tile_controls.cpp -o /tmp/test_tile_controls && /tmp/test_tile_controls
|
||||
#define THEME_TEST
|
||||
#include "../components/smart_display/tile_controls.h"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
#include "components/smart_display/tile_icon.h"
|
||||
#include <cassert>
|
||||
int main() {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "screen_text_en.h"
|
||||
#define THEME_TEST
|
||||
#include "components/smart_display/tile_palette.h"
|
||||
#include <cassert>
|
||||
|
||||
Reference in New Issue
Block a user