Consolidate companion components

This commit is contained in:
mikecarper committed 2026-09-17 13:41:39 -07:00
1 parent aab8f5038a
commit ebee99af81
32 files changed
+286 -287

No files matched your search

File renamed without changes.
@@ -1,6 +1,6 @@
#include <Arduino.h>
#include <helpers/CompanionJohn.h>
#include <helpers/bible/JohnData.generated.h>
#include <helpers/CompanionReader.h>
#include <helpers/bible/ReaderData.generated.h>
#include <cassert>
#include <cstdio>
#include <string>
@@ -10,23 +10,23 @@ using namespace mesh::bible;
int main(int argc, char**) {
Reference ref = {};
for (const char* cmd : {"get John 3:16", " GET\tJoHn 3:16 \t", "get john 03:16"}) {
for (const char* cmd : {"get reader 3:16", " GET\treader 3:16 \t", "get reader 03:16"}) {
assert(parse(cmd, ref) == ParseResult::Valid && ref.chapter == 3 && ref.verse == 16);
}
for (const char* cmd : {"get John", "get John 0:1", "get John 22:1",
"get John 1:52", "get John 3:0", "get John -3:16", "get John 3:16-18",
"get John 3:16 junk", "get John 9999999999:1", "get John 1:9999999999",
"get John 3:", "get John 3:16\x1b", "get John 3 :16", "get John :16"}) {
for (const char* cmd : {"get reader", "get reader 0:1", "get reader 22:1",
"get reader 1:52", "get reader 3:0", "get reader -3:16", "get reader 3:16-18",
"get reader 3:16 junk", "get reader 9999999999:1", "get reader 1:9999999999",
"get reader 3:", "get reader 3:16\x1b", "get reader 3 :16", "get reader :16"}) {
assert(parse(cmd, ref) == ParseResult::Invalid);
}
for (const char* cmd : {"get name", "forget John 3:16", "get johnny 3:16",
"get 1 John 3:16", "getJohn 3:16", "", "get J", "get", "g"}) {
for (const char* cmd : {"get name", "forget reader 3:16", "get readerny 3:16",
"get 1 reader 3:16", "getreader 3:16", "", "get J", "get", "g"}) {
assert(parse(cmd, ref) == ParseResult::NoMatch);
Stream output;
assert(!mesh::handleJohnCommand(cmd, output) && output.text.empty());
assert(!mesh::handleReaderCommand(cmd, output) && output.text.empty());
}
assert(parse(nullptr, ref) == ParseResult::NoMatch);
const Corpus& corpus = generated::johnCorpus;
const Corpus& corpus = generated::readerCorpus;
struct Guarded { uint32_t pre; char scratch[kBlockSize]; uint32_t post; } buf = {};
buf.pre = 0x11223344; buf.post = 0xaabbccdd;
unsigned count = 0;
@@ -36,9 +36,9 @@ int main(int argc, char**) {
assert(lookup(corpus, {chapter, verse}, buf.scratch, kBlockSize, text) == LookupResult::Found);
assert(buf.pre == 0x11223344 && buf.post == 0xaabbccdd);
char cmd[32];
snprintf(cmd, sizeof(cmd), "get John %u:%u", chapter, verse);
snprintf(cmd, sizeof(cmd), "get reader %u:%u", chapter, verse);
Stream output;
assert(mesh::handleJohnCommand(cmd, output));
assert(mesh::handleReaderCommand(cmd, output));
const std::string expected = "\r\n" + std::string(text) + "\r\n";
assert(output.text.find(expected) != std::string::npos);
for (const unsigned char* c = reinterpret_cast<const unsigned char*>(text); *c; ++c)
@@ -88,7 +88,7 @@ int main(int argc, char**) {
assert(text == nullptr && buf.pre == 0x11223344 && buf.post == 0xaabbccdd);
}
Stream invalid;
assert(mesh::handleJohnCommand("get John 3:99", invalid));
assert(mesh::handleReaderCommand("get reader 3:99", invalid));
assert(invalid.text.find("ERROR") != std::string::npos);
if (argc == 1) std::puts("All 879 verses, terminal output, parser and corruption checks passed");
}
@@ -1,5 +1,5 @@
#include <helpers/CompanionJohn.h>
#include <helpers/bible/JohnBookmarkFiles.h>
#include <helpers/CompanionReader.h>
#include <helpers/bible/ReaderBookmarkFiles.h>
#include <helpers/ui/UIScreen.h>
#include <cassert>
#include <cstdio>
@@ -41,18 +41,18 @@ struct Files {
struct FakeMesh {
Files files;
int saves = 0;
bool loadJohnBookmark(Position& p) { return loadReaderBookmark(files, p); }
bool saveJohnBookmark(Position p) { ++saves; return saveReaderBookmark(files, p); }
bool loadReaderBookmark(Position& p) { return mesh::bible::loadReaderBookmark(files, p); }
bool saveReaderBookmark(Position p) { ++saves; return mesh::bible::saveReaderBookmark(files, p); }
} the_mesh;
class UITask {
public:
bool closed = false;
std::string alert;
void closeJohnReader() { closed = true; }
void closeReader() { closed = true; }
void showAlert(const char* text, int) { alert = text; }
};
#include "../../../examples/companion_radio/ui-new/JohnReaderScreen.h"
#include "../../../examples/companion_radio/ui-new/ReaderScreen.h"
class Display : public DisplayDriver {
int x = 0, y = 0;
@@ -141,25 +141,25 @@ int main(int argc, char** argv) {
if (argc > 2) assert(std::freopen(argv[2], "w", stdout));
Display display(128, 64);
UITask task;
JohnReaderScreen first(&task, &display);
ReaderScreen first(&task, &display);
first.open(); first.render(display);
std::puts("FRAME 128 64 John 1:1");
std::puts("FRAME 128 64 reader 1:1");
display.dump();
assert(the_mesh.saveJohnBookmark(pos(91))); // John 3:16
JohnReaderScreen verse(&task, &display);
assert(the_mesh.saveReaderBookmark(pos(91))); // reader 3:16
ReaderScreen verse(&task, &display);
verse.open();
for (int part = 0; part < (UI_SMALL_MESSAGE_FONT ? 1 : 2); ++part) {
display.clear(); verse.render(display);
std::printf("FRAME 128 64 John 3:16 part %d\n", part + 1);
std::printf("FRAME 128 64 reader 3:16 part %d\n", part + 1);
display.dump();
verse.handleInput(KEY_NEXT);
}
#if UI_SMALL_MESSAGE_FONT
Display tiny(72, 40);
assert(the_mesh.saveJohnBookmark(pos(91)));
JohnReaderScreen tiny_verse(&task, &tiny);
assert(the_mesh.saveReaderBookmark(pos(91)));
ReaderScreen tiny_verse(&task, &tiny);
tiny_verse.open(); tiny_verse.render(tiny);
std::puts("FRAME 72 40 John 3:16 on a tiny screen (5px)");
std::puts("FRAME 72 40 reader 3:16 on a tiny screen (5px)");
tiny.dump();
tiny.resize(40, 72);
tiny_verse.render(tiny);
@@ -199,9 +199,9 @@ int main(int argc, char** argv) {
auto measure = [&expected_text](const char* line) { return expected_text.getTextWidth(line); };
UITask task;
the_mesh = FakeMesh{};
JohnReaderScreen screen(&task, &display);
ReaderScreen screen(&task, &display);
screen.open();
assert(screen.flush() && the_mesh.saves == 0); // don't create John 1:1
assert(screen.flush() && the_mesh.saves == 0); // don't create reader 1:1
#if UI_BUTTON_READER_HINT
display.clear(); screen.render(display);
Display footer(display.width(), display.height());
@@ -237,7 +237,7 @@ int main(int argc, char** argv) {
char scratch[kBlockSize];
for (uint16_t verse = 0; verse < kVerseCount; ++verse) {
const char* text;
assert(mesh::readJohnVerse(referenceAt(verse), scratch, sizeof(scratch), text) == LookupResult::Found);
assert(mesh::readReaderVerse(referenceAt(verse), scratch, sizeof(scratch), text) == LookupResult::Found);
memmove(scratch, text, strlen(text) + 1);
std::string rebuilt;
uint16_t offset = 0;
@@ -304,27 +304,27 @@ int main(int argc, char** argv) {
touch_gesture(x, screen.readerTouchBar()->top, x, screen.readerTouchBar()->top);
};
tap(2); assert(screen.flush());
Position saved; assert(the_mesh.loadJohnBookmark(saved) && saved.verse == 1);
Position saved; assert(the_mesh.loadReaderBookmark(saved) && saved.verse == 1);
tap(1); assert(screen.flush());
assert(!the_mesh.loadJohnBookmark(saved) && saved.atStart());
assert(!the_mesh.loadReaderBookmark(saved) && saved.atStart());
tap(3); assert(screen.flush());
assert(the_mesh.loadJohnBookmark(saved) && referenceAt(saved.verse).chapter == 2);
assert(the_mesh.loadReaderBookmark(saved) && referenceAt(saved.verse).chapter == 2);
tap(0); assert(screen.flush());
assert(!the_mesh.loadJohnBookmark(saved) && saved.atStart());
assert(!the_mesh.loadReaderBookmark(saved) && saved.atStart());
tap(4); assert(task.closed);
task.closed = false;
touch_gesture(120,70,120,70); assert(screen.flush());
assert(the_mesh.loadJohnBookmark(saved) && saved.verse == 1);
assert(the_mesh.loadReaderBookmark(saved) && saved.verse == 1);
touch_gesture(40,70,40,70); assert(screen.flush());
assert(!the_mesh.loadJohnBookmark(saved) && saved.atStart());
assert(!the_mesh.loadReaderBookmark(saved) && saved.atStart());
touch_gesture(120,70,40,70); assert(screen.flush());
assert(the_mesh.loadJohnBookmark(saved) && saved.verse == 1);
assert(the_mesh.loadReaderBookmark(saved) && saved.verse == 1);
touch_gesture(40,70,120,70); assert(screen.flush());
assert(!the_mesh.loadJohnBookmark(saved) && saved.atStart());
assert(!the_mesh.loadReaderBookmark(saved) && saved.atStart());
touch_gesture(80,100,80,50); assert(screen.flush());
assert(the_mesh.loadJohnBookmark(saved) && referenceAt(saved.verse).chapter == 2);
assert(the_mesh.loadReaderBookmark(saved) && referenceAt(saved.verse).chapter == 2);
touch_gesture(80,50,80,100); assert(screen.flush());
assert(!the_mesh.loadJohnBookmark(saved) && saved.atStart());
assert(!the_mesh.loadReaderBookmark(saved) && saved.atStart());
touch_gesture(80,0,80,0); assert(task.closed);
#endif
}
@@ -337,7 +337,7 @@ int main(int argc, char** argv) {
the_mesh = FakeMesh{};
Display chapters_display(128, 64);
UITask chapters_task;
JohnReaderScreen chapters(&chapters_task, &chapters_display);
ReaderScreen chapters(&chapters_task, &chapters_display);
chapters.open();
assert(chapters.handleInput(KEY_UP));
assert(chapters.flush() && the_mesh.saves == 0);
@@ -345,7 +345,7 @@ int main(int argc, char** argv) {
assert(chapters.handleInput(KEY_DOWN));
assert(chapters.flush());
Position saved;
assert(the_mesh.loadJohnBookmark(saved));
assert(the_mesh.loadReaderBookmark(saved));
assert(referenceAt(saved.verse).chapter == chapter && saved.offset == 0);
assert(referenceAt(saved.verse).verse == 1);
}
@@ -353,66 +353,66 @@ int main(int argc, char** argv) {
assert(chapters.handleInput(KEY_DOWN));
assert(chapters.flush() && the_mesh.saves == writes);
// Moving backwards from the middle of a chapter reaches the preceding one.
assert(the_mesh.saveJohnBookmark(pos(91, 12))); // 3:16, with a stale page offset
JohnReaderScreen middle(&chapters_task, &chapters_display);
assert(the_mesh.saveReaderBookmark(pos(91, 12))); // 3:16, with a stale page offset
ReaderScreen middle(&chapters_task, &chapters_display);
middle.open();
assert(middle.handleInput(KEY_UP) && middle.flush());
Position chapter_saved;
assert(the_mesh.loadJohnBookmark(chapter_saved));
assert(the_mesh.loadReaderBookmark(chapter_saved));
assert(referenceAt(chapter_saved.verse).chapter == 2);
assert(referenceAt(chapter_saved.verse).verse == 1 && chapter_saved.offset == 0);
assert(middle.handleInput(KEY_UP) && middle.flush());
assert(!the_mesh.loadJohnBookmark(chapter_saved) && chapter_saved.atStart());
assert(!the_mesh.loadReaderBookmark(chapter_saved) && chapter_saved.atStart());
// Resume within a split verse; independent screen and store lifecycle.
the_mesh = FakeMesh{};
Display display(64,32);
UITask task;
JohnReaderScreen first(&task, &display);
ReaderScreen first(&task, &display);
first.open(); first.handleInput(KEY_NEXT);
display.clear(); first.render(display); const auto body = display.body();
first.poll(); assert(the_mesh.saves == 0);
test_now += 2000; first.poll(); assert(the_mesh.saves == 1);
first.poll(); assert(the_mesh.saves == 1);
Position saved;
assert(the_mesh.loadJohnBookmark(saved) && saved.verse == 0 && saved.offset > 0);
JohnReaderScreen rebooted(&task, &display);
assert(the_mesh.loadReaderBookmark(saved) && saved.verse == 0 && saved.offset > 0);
ReaderScreen rebooted(&task, &display);
rebooted.open(); display.clear(); rebooted.render(display);
assert(display.body() == body);
rebooted.handleInput(KEY_PREV); assert(rebooted.flush());
assert(!the_mesh.files.exists("/john.pos") && !the_mesh.files.exists("/john.pos.bak"));
assert(!the_mesh.loadJohnBookmark(saved) && saved.atStart());
assert(!the_mesh.files.exists("/reader.pos") && !the_mesh.files.exists("/reader.pos.bak"));
assert(!the_mesh.loadReaderBookmark(saved) && saved.atStart());
#if UI_SMALL_MESSAGE_FONT
// A bookmark from the old 6-pixel-wide font resumes on the new page that
// contains those words; it does not advance to another verse or lose them.
char scratch[kBlockSize];
const char* text;
assert(mesh::readJohnVerse({3, 16}, scratch, sizeof(scratch), text) == LookupResult::Found);
assert(mesh::readReaderVerse({3, 16}, scratch, sizeof(scratch), text) == LookupResult::Found);
const auto legacy_measure = [](const char* line) { return strlen(line) * 6; };
const auto legacy = readerPage(text, 0, 128, 5, legacy_measure);
assert(text[legacy.next]);
the_mesh = FakeMesh{};
assert(the_mesh.saveJohnBookmark(pos(91, legacy.next)));
assert(the_mesh.saveReaderBookmark(pos(91, legacy.next)));
Display migrated_display(128,64);
mesh::ui::SmallMessageText compact(migrated_display);
const auto compact_measure = [&compact](const char* line) { return compact.getTextWidth(line); };
const auto compact_page = readerPage(text, legacy.next, 128,
compact.lineCount(12, migrated_display.bodyBottom()), compact_measure);
assert(compact_page.start <= legacy.next && compact_page.next > legacy.next);
JohnReaderScreen migrated(&task, &migrated_display);
ReaderScreen migrated(&task, &migrated_display);
migrated.open(); migrated.render(migrated_display); assert(migrated.flush());
assert(the_mesh.loadJohnBookmark(saved) && saved == pos(91, compact_page.start));
assert(compact_page.parts == 1); // John 3:16 now fits in one V4 screen
assert(the_mesh.loadReaderBookmark(saved) && saved == pos(91, compact_page.start));
assert(compact_page.parts == 1); // reader 3:16 now fits in one V4 screen
// Reflow an already-open reader when panel geometry crosses the 5px/6px
// threshold, and when a tiny panel rotates into the stacked-header layout.
for (const auto& dimensions : {std::make_pair(72,40), {128,64}, {40,72}, {64,128}, {32,128}, {160,80}, {80,160}, {159,80}}) {
assert(the_mesh.saveJohnBookmark(pos(91, legacy.next)));
assert(the_mesh.saveReaderBookmark(pos(91, legacy.next)));
Display changed(72,40);
JohnReaderScreen active(&task, &changed);
ReaderScreen active(&task, &changed);
active.open(); active.render(changed); assert(active.flush());
Position before; assert(the_mesh.loadJohnBookmark(before));
Position before; assert(the_mesh.loadReaderBookmark(before));
changed.resize(dimensions.first, dimensions.second);
mesh::ui::SmallMessageText small_font(changed);
const bool small = changed.useSmallMessageFont();
@@ -424,10 +424,10 @@ int main(int argc, char** argv) {
new_measure);
assert(new_page.start <= before.offset && new_page.next > before.offset);
active.render(changed); assert(active.flush());
Position after; assert(the_mesh.loadJohnBookmark(after));
Position after; assert(the_mesh.loadReaderBookmark(after));
assert(after == pos(91, new_page.start));
const auto rendered = changed.body();
JohnReaderScreen reopened(&task, &changed);
ReaderScreen reopened(&task, &changed);
reopened.open(); changed.clear(); reopened.render(changed);
assert(changed.body() == rendered);
}
@@ -459,12 +459,12 @@ int main(int argc, char** argv) {
}
// A reset between renames recovers the previous record, not the .tmp.
Files interrupted = original;
assert(interrupted.rename("/john.pos", "/john.pos.bak"));
assert(interrupted.rename("/reader.pos", "/reader.pos.bak"));
assert(loadReaderBookmark(interrupted, saved) && saved == pos(4, 12));
assert(saveReaderBookmark(interrupted, pos(20)));
assert(saveReaderBookmark(interrupted, pos(0)) && interrupted.data.empty());
Files damaged;
damaged.data["/john.pos"] = {1, 2, 3};
damaged.data["/reader.pos"] = {1, 2, 3};
assert(!loadReaderBookmark(damaged, saved) && saved.atStart());
assert(saveReaderBookmark(damaged, pos(10)));
assert(loadReaderBookmark(damaged, saved) && saved == pos(10));
+5 -5
View File
@@ -159,20 +159,20 @@ int main() {
displayPowerPrefs().inbox = DisplayInboxMode::Pending;
assert(!messages.hasMessages());
#if COMPANION_FEATURE_JOHN
#if COMPANION_FEATURE_READER
// Receiving and downloading messages must leave an open reader alone.
Screen reader;
task.john_reader = task.curr = &reader;
task.reader = task.curr = &reader;
for (bool connected : {false, true}) {
task.connected = connected;
for (int i = 0; i < 2; ++i) {
mesh.receive("while reading");
assert(task.isJohnReaderActive());
assert(task.isReaderActive());
}
for (int remaining : {1, 0}) {
mesh.download();
assert(task.getMsgCount() == remaining);
assert(task.isJohnReaderActive());
assert(task.isReaderActive());
}
}
#endif
@@ -284,7 +284,7 @@ int main() {
with tempfile.TemporaryDirectory(prefix="mesh-inbox-test-") as directory:
executable = Path(directory) / "inbox.exe"
result = subprocess.run([
compiler, "-std=c++17", f"-DCOMPANION_FEATURE_JOHN={reader_enabled}",
compiler, "-std=c++17", f"-DCOMPANION_FEATURE_READER={reader_enabled}",
"-I" + str(ROOT / "src"), "-I" + str(ROOT / "test/mocks"),
"-x", "c++", "-", str(ROOT / "src/helpers/ui/MomentaryButton.cpp"),
str(ROOT / "src/helpers/ui/DisplayDriver.cpp"), "-o", str(executable),
+27 -27
View File
@@ -14,8 +14,8 @@ from test_message_navigation import PREAMBLE
from test_replay_reset_integration import extract_braced
ROOT = Path(__file__).resolve().parents[1]
FIXTURE = ROOT / "test/fixtures/companion_john"
spec = importlib.util.spec_from_file_location("pack_john", ROOT / "tools/bible/pack_john.py")
FIXTURE = ROOT / "test/fixtures/companion_reader"
spec = importlib.util.spec_from_file_location("pack_reader", ROOT / "tools/bible/pack_reader.py")
packer = importlib.util.module_from_spec(spec)
spec.loader.exec_module(packer)
@@ -48,10 +48,10 @@ class CompanionReaderTest(unittest.TestCase):
def test_ascii_typography_preserves_words_case_and_pinned_source(self):
original = copy.deepcopy(self.document)
sample = '\u201cJohn\u2019s\u00a0Word\u201d\u2014\u2018Test\u2019\u2013Yes!'
self.assertEqual(packer.ascii_verse(sample, 64), b'"John\'s Word"-\'Test\'-Yes!')
self.assertEqual(packer.ascii_verse('Already ASCII: John 3:16.', 64),
b'Already ASCII: John 3:16.')
sample = '\u201creader\u2019s\u00a0Word\u201d\u2014\u2018Test\u2019\u2013Yes!'
self.assertEqual(packer.ascii_verse(sample, 64), b'"reader\'s Word"-\'Test\'-Yes!')
self.assertEqual(packer.ascii_verse('Already ASCII: reader 3:16.', 64),
b'Already ASCII: reader 3:16.')
# Bounds apply after conversion, not to the original multibyte form.
self.assertEqual(packer.ascii_verse('\u201cHi\u201d', 4), b'"Hi"')
with self.assertRaises(ValueError):
@@ -92,8 +92,8 @@ class CompanionReaderTest(unittest.TestCase):
str(ROOT / "src/helpers/ota/OtaTinf.c"), "-o", str(obj)])
self.run_checked([cxx, "-std=c++17", "-Os", "-Wall", "-Wextra", "-Werror",
*flags, "-I" + str(ROOT / "src"), "-I" + str(FIXTURE),
str(FIXTURE / "test_john.cpp"),
str(ROOT / "src/helpers/CompanionJohn.cpp"), str(obj), "-o", str(binary)])
str(FIXTURE / "test_reader_lookup.cpp"),
str(ROOT / "src/helpers/CompanionReader.cpp"), str(obj), "-o", str(binary)])
result = self.run_checked([str(binary), "--dump"])
actual = dict(line.split("\t", 1) for line in result.splitlines())
self.assertEqual(actual, self.expected_verses)
@@ -105,28 +105,28 @@ class CompanionReaderTest(unittest.TestCase):
for flags in ([], ["-DESP32_PLATFORM=1"], ["-DNRF52_PLATFORM=1"],
["-DESP32_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1"],
["-DESP32_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1",
"-DENABLE_USB_INTERFACE=1", "-DCOMPANION_FEATURE_JOHN=0"]):
"-DENABLE_USB_INTERFACE=1", "-DCOMPANION_FEATURE_READER=0"]):
result = self.run_checked([cxx, "-E", "-P", *flags,
str(ROOT / "src/helpers/CompanionJohn.cpp")])
self.assertNotIn("johnData", result)
self.assertNotIn("handleJohnCommand", result)
str(ROOT / "src/helpers/CompanionReader.cpp")])
self.assertNotIn("readerData", result)
self.assertNotIn("handleReaderCommand", result)
def test_terminal_dispatch_is_separate_from_bounded_api_replies(self):
source = (ROOT / "examples/companion_radio/MyMesh.cpp").read_text(encoding="utf-8")
start = source.index("void MyMesh::handleTerminalCommand(")
end = source.index("\nvoid MyMesh::enterCLIRescue()", start)
terminal = source[start:end]
self.assertEqual(source.count("mesh::handleJohnCommand("), 1)
self.assertLess(terminal.index("mesh::handleJohnCommand("),
self.assertEqual(source.count("mesh::handleReaderCommand("), 1)
self.assertLess(terminal.index("mesh::handleReaderCommand("),
terminal.index("char local_reply[160]"))
self.assertIn("get John <chapter>:<verse> (World English Bible, offline)", terminal)
self.assertEqual(terminal.count("#if COMPANION_FEATURE_JOHN"), 2)
self.assertIn("get reader <chapter>:<verse> (World English Bible, offline)", terminal)
self.assertEqual(terminal.count("#if COMPANION_FEATURE_READER"), 2)
def test_reader_navigation_and_bookmark_recovery(self):
cc, cxx = shutil.which("gcc"), shutil.which("g++")
if not cc or not cxx:
self.skipTest("host GCC and G++ required")
with tempfile.TemporaryDirectory(prefix="meshcore-john-reader-") as directory:
with tempfile.TemporaryDirectory(prefix="meshcore-reader-reader-") as directory:
directory = Path(directory)
flags = ["-DNRF52_PLATFORM=1", "-DCOMPANION_RADIO_FULL=1", "-DENABLE_USB_INTERFACE=1"]
obj, binary = directory / "tinf.o", directory / "reader.exe"
@@ -139,21 +139,21 @@ class CompanionReaderTest(unittest.TestCase):
f"-DUI_BUTTON_READER_HINT={button_hint}",
f"-DUI_READER_TOUCH_BAR={touch_bar}",
"-I" + str(ROOT / "src"), "-I" + str(FIXTURE),
str(FIXTURE / "test_reader.cpp"), str(ROOT / "src/helpers/CompanionJohn.cpp"),
str(FIXTURE / "test_reader_screen.cpp"), str(ROOT / "src/helpers/CompanionReader.cpp"),
str(obj), "-o", str(binary)])
self.run_checked([str(binary)])
def test_reader_button_routing_and_profile_gate(self):
ui = (ROOT / "examples/companion_radio/ui-new/UITask.cpp").read_text(encoding="utf-8")
long_press = ui[ui.index("char UITask::handleLongPress("):]
self.assertLess(long_press.index("isJohnReaderActive()"), long_press.index("enterCLIRescue()"))
self.assertLess(long_press.index("isReaderActive()"), long_press.index("enterCLIRescue()"))
self.assertLess(long_press.index("isRadioPage()"), long_press.index("enterCLIRescue()"))
self.assertIn("#if COMPANION_FEATURE_JOHN\n#include \"JohnReaderScreen.h\"", ui)
self.assertIn("#if COMPANION_FEATURE_READER\n#include \"ReaderScreen.h\"", ui)
self.assertLess(ui.index("#define UI_BUTTON_READER_HINT 1"),
ui.index('#include "JohnReaderScreen.h"'))
ui.index('#include "ReaderScreen.h"'))
# Queue downloads preserve the current screen unconditionally now;
# test_companion_inbox exercises this with the reader enabled.
self.assertIn("else if (isJohnReaderActive())", ui)
self.assertIn("else if (isReaderActive())", ui)
self.assertIn("c = handleLongPress(KEY_ENTER);", ui)
self.assertIn("handleDoubleClick(KEY_PREV)", ui)
@@ -166,7 +166,7 @@ struct Board {
bool isUsbHostConnected() const { return host; }
};
struct Interfaces { bool takePairingRequest() { return false; } };
struct JohnReaderScreen : Screen { int saves = 0; void flush() { ++saves; } };
struct ReaderScreen : Screen { int saves = 0; void flush() { ++saves; } };
'''
members = r'''
Board* _board;
@@ -189,12 +189,12 @@ int main() {
displayPowerPrefs().usb = {DisplayMode::On, 5};
Display display;
Screen home;
JohnReaderScreen reader;
ReaderScreen reader;
Board board;
Interfaces interfaces;
UITask task(display);
task._board = &board; task._interfaceManager = &interfaces;
task.curr = task.home = &home; task.john_reader = &reader;
task.curr = task.home = &home; task.reader = &reader;
task.servicePairingState();
assert(!display.isOn() && reader.saves == 0);
task.curr = &reader;
@@ -204,7 +204,7 @@ int main() {
assert(display.isOn() && reader.saves == 0);
g_mock_millis = 5000; task.servicePairingState();
assert(!display.isOn());
const int expected_saves = COMPANION_FEATURE_JOHN ? 1 : 0;
const int expected_saves = COMPANION_FEATURE_READER ? 1 : 0;
assert(reader.saves == expected_saves);
task.servicePairingState();
assert(reader.saves == expected_saves); // No repeated writes while dark.
@@ -233,7 +233,7 @@ int main() {
for enabled in (0, 1):
with self.subTest(reader_enabled=enabled):
binary = directory / "power.exe"
self.run_checked([compiler, "-std=c++17", f"-DCOMPANION_FEATURE_JOHN={enabled}",
self.run_checked([compiler, "-std=c++17", f"-DCOMPANION_FEATURE_READER={enabled}",
"-I" + str(ROOT / "src"), "-I" + str(ROOT / "test/mocks"),
str(directory / "power.cpp"),
str(ROOT / "src/helpers/ui/MomentaryButton.cpp"),
+2 -2
View File
@@ -22,14 +22,14 @@ class IndicatorMessagesProfileTest(unittest.TestCase):
# Compile the real gate: HAS_TOUCH must not overwrite the explicit
# physical-button footer setting with zero.
start = ui.index("#ifndef UI_BUTTON_READER_HINT")
gate = ui[start:ui.index("#if COMPANION_FEATURE_JOHN", start)]
gate = ui[start:ui.index("#if COMPANION_FEATURE_READER", start)]
result = subprocess.run(["c++", "-E", "-P", "-x", "c++", "-"],
input="#define HAS_TOUCH 1\n#define UI_BUTTON_READER_HINT 1\n" + gate +
"\n#if UI_BUTTON_READER_HINT != 1\n#error reader hint disabled\n#endif\n",
text=True, capture_output=True)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertIn("static_cast<MsgPreviewScreen*>(msg_preview)->readerTouchBar()", ui)
self.assertIn("static_cast<JohnReaderScreen*>(john_reader)->readerTouchBar()", ui)
self.assertIn("static_cast<ReaderScreen*>(reader)->readerTouchBar()", ui)
def test_indicator_enables_messages_home_page(self):
profile = PROFILE.read_text()
+6 -6
View File
@@ -117,15 +117,15 @@ public:
UIScreen* curr=nullptr;
UIScreen* msg_preview=nullptr;
UIScreen* home=nullptr;
UIScreen* john_reader=nullptr;
UIScreen* reader=nullptr;
uint32_t _auto_off=0,_next_refresh=0;
int buzzer_changes=0;
int getMsgCount() const { return 0; }
explicit UITask(DisplayDriver& display) : _display(&display) {}
bool isJohnReaderActive() const { return john_reader && curr==john_reader; }
bool isReaderActive() const { return reader && curr==reader; }
void gotoHomeScreen() { curr=home; }
void toggleBuzzer() { ++buzzer_changes; }
void showJohnReader() { curr=john_reader; }
void showReader() { curr=reader; }
void showAlert(const char*,int) {}
char handleLongPress(char c) { return c; }
char handleMultiClick(char,bool);
@@ -159,7 +159,7 @@ int main() {
HomeScreen home(&task);
Screen group;
MsgPreviewScreen messages(&task);
task.home=&home; task.msg_preview=task.curr=&messages; task.john_reader=&group;
task.home=&home; task.msg_preview=task.curr=&messages; task.reader=&group;
messages.addPreview(1,"Alice","public old",0,"Public");
messages.addPreview(1,"Bob","public new",0,"Public");
messages.addPreview(1,"Carol","second",2,"Second");
@@ -289,7 +289,7 @@ int main() {
g_mock_millis+=25; task.pollButton();
g_mock_millis+=MOMENTARY_BUTTON_MULTI_CLICK_MS; task.pollButton();
assert(home.key==0);
#if COMPANION_FEATURE_JOHN
#if COMPANION_FEATURE_READER
task.curr=&group;
gesture(task,1); assert(group.key==KEY_NEXT && group.events==1);
gesture(task,2); assert(group.key==KEY_PREV && group.events==2);
@@ -391,7 +391,7 @@ class MessageNavigationTest(unittest.TestCase):
binary = Path(temp) / "navigation"
result = subprocess.run([
"c++", "-std=c++17", signedness,
f"-DCOMPANION_FEATURE_JOHN={enabled}",
f"-DCOMPANION_FEATURE_READER={enabled}",
*flags,
"-I" + str(ROOT / "src"), "-I" + str(ROOT / "test/mocks"),
"-fsanitize=address,undefined", "-fno-pie", "-no-pie",
+1 -1
View File
@@ -223,7 +223,7 @@ class ReaderTouchCoordinatesTest(unittest.TestCase):
source = (ROOT / "examples/companion_radio/ui-new/UITask.cpp").read_text()
self.assertIn("layout.header_divider_y + 1", source)
self.assertIn("if (!_display->isOn()) reader_touch_bar = nullptr;", source)
reader = (ROOT / "examples/companion_radio/ui-new/JohnReaderScreen.h").read_text()
reader = (ROOT / "examples/companion_radio/ui-new/ReaderScreen.h").read_text()
self.assertIn("makeReaderTouchBar(hint_text, bottom, top - 1)", reader)