mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-11-11 14:47:39 +00:00
Retain the upstream 1.0.1 publication, web navigation/serial diagnostics and P4 keyboard fixes alongside the CM0/Cardputer integration. Resolve the ESP library dependency conflict by retaining core_hostlink, core_sstv and core_walkie. Align the configuration readiness test with the current session wrapper and complete advertised AutoInterface access editing and port validation. Preserve keyboard focus on clipped controls by scrolling them into view instead of skipping them. Make compact UI fixtures tolerate asynchronous redraws and print the exact missing/unreachable control when they fail. Validation: native Linux suite 335/335 before the final focus fix; focused native keyboard checks and compact suite after it; web contracts 25/25 and device-page build; Python I2C/version checks; ESP stack hygiene and Reticulum scope guards; staged whitespace checks and GitNexus scope audit. No device reinstallation or full ESP firmware rebuild was performed for this source integration.
138 lines
5.7 KiB
C++
138 lines
5.7 KiB
C++
#include "app/input_event.h"
|
|
#include "ui/app_runtime.h"
|
|
#include "ui/app_screen.h"
|
|
#include "ui/screens/geocaching/geocaching_page_shell.h"
|
|
#include "ui/shell_ui_runner.h"
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <cstdlib>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <thread>
|
|
#include <unistd.h>
|
|
#include <vector>
|
|
|
|
using namespace std::chrono_literals;
|
|
lv_obj_t* label(lv_obj_t* parent, const char* text)
|
|
{
|
|
if (lv_obj_has_flag(parent, LV_OBJ_FLAG_HIDDEN)) return nullptr;
|
|
if (lv_obj_check_type(parent, &lv_label_class) && !std::strcmp(lv_label_get_text(parent), text)) return parent;
|
|
for (uint32_t i = 0; i < lv_obj_get_child_count(parent); ++i)
|
|
if (auto* found = label(lv_obj_get_child(parent, i), text)) return found;
|
|
return nullptr;
|
|
}
|
|
void fields(lv_obj_t* parent, std::vector<lv_obj_t*>& out)
|
|
{
|
|
if (lv_obj_check_type(parent, &lv_textarea_class))
|
|
{
|
|
out.push_back(parent);
|
|
return;
|
|
}
|
|
for (uint32_t i = 0; i < lv_obj_get_child_count(parent); ++i) fields(lv_obj_get_child(parent, i), out);
|
|
}
|
|
int main()
|
|
{
|
|
const auto root = std::filesystem::temp_directory_path() / ("trailmate-geocaching-page-" + std::to_string(getpid()));
|
|
std::filesystem::create_directories(root);
|
|
setenv("TRAIL_MATE_SETTINGS_ROOT", root.c_str(), 1);
|
|
setenv("TRAIL_MATE_CACHE_ROOT", root.c_str(), 1);
|
|
setenv("TRAIL_MATE_SD_ROOT", (root / "sd").c_str(), 1);
|
|
setenv("TRAIL_MATE_RUNTIME_MODE", "offline", 1);
|
|
setenv("TRAIL_MATE_LORA_DISABLE", "1", 1);
|
|
setenv("TRAIL_MATE_GPS_DEVICE", "/dev/trailmate-test-missing", 1);
|
|
trailmate::cardputer_zero::linux_ui::ShellSession shell;
|
|
trailmate::cardputer_zero::linux_ui::CanvasLvglHost host(shell);
|
|
assert(shell.begin());
|
|
const auto tick = [&](auto duration)
|
|
{
|
|
const auto end = std::chrono::steady_clock::now() + duration;
|
|
do
|
|
{
|
|
host.tick();
|
|
std::this_thread::sleep_for(5ms);
|
|
} while (std::chrono::steady_clock::now() < end);
|
|
};
|
|
tick(3300ms);
|
|
using Key = trailmate::cardputer_zero::app::InputKey;
|
|
const auto key = [&](Key code, char value = 0)
|
|
{
|
|
shell.enqueueInputs({{code, value ? std::string(1, value) : std::string{}, value}});
|
|
tick(150ms);
|
|
};
|
|
for (int i = 0; i < 7; ++i) key(Key::Right);
|
|
key(Key::Enter);
|
|
tick(650ms);
|
|
assert(label(lv_screen_active(), "Geocaching"));
|
|
assert(ui_get_active_app() && !std::strcmp(ui_get_active_app()->stable_id(), "geocaching"));
|
|
const auto click = [&](const char* text)
|
|
{
|
|
auto* text_object = label(lv_screen_active(), text);
|
|
const auto ready = std::chrono::steady_clock::now() + 5s;
|
|
while (!text_object && std::chrono::steady_clock::now() < ready)
|
|
{
|
|
tick(20ms);
|
|
text_object = label(lv_screen_active(), text);
|
|
}
|
|
if (!text_object) std::fprintf(stderr, "Missing Geocaching control: %s\n", text);
|
|
assert(text_object);
|
|
auto* button = lv_obj_get_parent(text_object);
|
|
assert(lv_obj_check_type(button, &lv_button_class) && !lv_obj_has_state(button, LV_STATE_DISABLED));
|
|
for (unsigned step = 0; step < 40 && lv_group_get_focused(lv_group_get_default()) != button; ++step) key(Key::Tab);
|
|
assert(lv_group_get_focused(lv_group_get_default()) == button);
|
|
key(Key::Enter);
|
|
tick(650ms);
|
|
};
|
|
click("My caches");
|
|
click("New");
|
|
std::vector<lv_obj_t*> text_fields;
|
|
fields(lv_screen_active(), text_fields);
|
|
assert(text_fields.size() == 5);
|
|
for (const char ch : std::string("UI draft")) key(Key::Character, ch);
|
|
assert(!std::strcmp(lv_textarea_get_text(text_fields[0]), "UI draft"));
|
|
key(Key::Left);
|
|
key(Key::Character, '!');
|
|
assert(!std::strcmp(lv_textarea_get_text(text_fields[0]), "UI draf!t"));
|
|
key(Key::Backspace);
|
|
key(Key::Right);
|
|
assert(!std::strcmp(lv_textarea_get_text(text_fields[0]), "UI draft"));
|
|
lv_textarea_set_text(text_fields[1], "Native page persistence");
|
|
lv_group_focus_obj(text_fields[1]);
|
|
lv_textarea_set_cursor_pos(text_fields[1], LV_TEXTAREA_CURSOR_LAST);
|
|
trailmate::cardputer_zero::app::InputEvent committed;
|
|
committed.key = Key::TextEdit;
|
|
committed.edit.focus = shell.textInputState().focus;
|
|
committed.edit.commit = " \xe4\xb8\xad\xe6\x96\x87\xf0\x9f\x99\x82";
|
|
committed.edit.preedit = "pending composition";
|
|
shell.enqueueInputs({committed});
|
|
tick(150ms);
|
|
const std::string description = "Native page persistence" + committed.edit.commit;
|
|
assert(description == lv_textarea_get_text(text_fields[1]));
|
|
lv_textarea_set_text(text_fields[2], "Look here");
|
|
click("Save");
|
|
assert(label(lv_screen_active(), "UI draft"));
|
|
click("UI draft");
|
|
text_fields.clear();
|
|
fields(lv_screen_active(), text_fields);
|
|
assert(text_fields.size() == 5 && !std::strcmp(lv_textarea_get_text(text_fields[0]), "UI draft"));
|
|
assert(description == lv_textarea_get_text(text_fields[1]));
|
|
lv_textarea_set_text(text_fields[0], "Edited UI draft");
|
|
click("Save");
|
|
assert(label(lv_screen_active(), "Edited UI draft"));
|
|
click("Edited UI draft");
|
|
click("Delete local cache");
|
|
click("Confirm local delete");
|
|
const auto deleted_deadline = std::chrono::steady_clock::now() + 5s;
|
|
while (label(lv_screen_active(), "Edited UI draft") && std::chrono::steady_clock::now() < deleted_deadline)
|
|
tick(20ms);
|
|
assert(!label(lv_screen_active(), "Edited UI draft"));
|
|
key(Key::Home);
|
|
tick(650ms);
|
|
assert(!ui_get_active_app());
|
|
key(Key::Enter);
|
|
tick(650ms);
|
|
assert(ui_get_active_app() && !std::strcmp(ui_get_active_app()->stable_id(), "geocaching"));
|
|
// Leave the page open: the host must detach it before destroying LVGL.
|
|
std::filesystem::remove_all(root);
|
|
}
|