complete milestone 1

This commit is contained in:
2026-01-16 22:17:12 +01:00
parent 8de36aa975
commit baa77d4c95
8 changed files with 524 additions and 78 deletions

View File

@@ -2,11 +2,27 @@
#include "ui_inspector.h"
#include <fstream>
#include <sstream>
#include <filesystem>
namespace mosis::testing {
using json = nlohmann::json;
// Helper to normalize RmlUi URLs for comparison
// RmlUi uses '|' instead of ':' for Windows drive letters
static std::string NormalizeUrl(const std::string& url) {
std::string normalized = url;
// Replace | with : (for Windows drive letters)
for (char& c : normalized) {
if (c == '|') c = ':';
}
// Normalize backslashes to forward slashes
for (char& c : normalized) {
if (c == '\\') c = '/';
}
return normalized;
}
UIInspector::UIInspector(Rml::Context* context)
: m_context(context)
{
@@ -24,10 +40,16 @@ json UIInspector::ElementToJson(Rml::Element* element) const {
j["id"] = id;
}
// Classes
// Classes - split into array
std::string class_str = element->GetAttribute<Rml::String>("class", "");
if (!class_str.empty()) {
j["classes"] = class_str;
json classes_arr = json::array();
std::istringstream iss(class_str);
std::string cls;
while (iss >> cls) {
classes_arr.push_back(cls);
}
j["classes"] = classes_arr;
}
// Bounds
@@ -75,32 +97,63 @@ json UIInspector::ElementToJson(Rml::Element* element) const {
json UIInspector::DumpHierarchy() const {
json result;
// Get current timestamp
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&time_t), "%Y-%m-%dT%H:%M:%S");
result["timestamp"] = ss.str();
// Resolution
auto dimensions = m_context->GetDimensions();
result["resolution"] = {
{"width", dimensions.x},
{"height", dimensions.y}
};
// Current screen (document URL)
std::string screen = "";
if (m_context->GetNumDocuments() > 0) {
auto* doc = m_context->GetDocument(0);
// Current screen - use override if set, otherwise detect from documents
std::string screen = m_current_screen;
Rml::ElementDocument* main_doc = nullptr;
// Normalize the current screen path for comparison
std::string normalized_current = NormalizeUrl(m_current_screen);
// Find the document matching the current screen, or first non-debugger document
for (int i = 0; i < m_context->GetNumDocuments(); i++) {
auto* doc = m_context->GetDocument(i);
if (doc) {
screen = doc->GetSourceURL();
std::string url = doc->GetSourceURL();
// Skip debugger documents
if (url.find("__rmlui") == std::string::npos) {
// Normalize the document URL for comparison
std::string normalized_url = NormalizeUrl(url);
// If we have a current screen override, match it
if (!m_current_screen.empty()) {
if (normalized_url.find(normalized_current) != std::string::npos ||
normalized_current.find(normalized_url) != std::string::npos) {
main_doc = doc;
break;
}
} else if (!main_doc) {
// No override, use first non-debugger document
screen = url;
main_doc = doc;
}
}
}
}
result["screen"] = screen;
// Elements - dump all documents
// Elements - dump the main document body (expected format for HierarchyReader)
if (main_doc) {
result["elements"] = ElementToJson(main_doc);
} else {
result["elements"] = json::object();
}
// Also include all documents for more detailed inspection
result["documents"] = json::array();
for (int i = 0; i < m_context->GetNumDocuments(); i++) {
auto* doc = m_context->GetDocument(i);
@@ -108,24 +161,51 @@ json UIInspector::DumpHierarchy() const {
json doc_json;
doc_json["url"] = doc->GetSourceURL();
doc_json["title"] = doc->GetTitle();
// Dump document root (ElementDocument inherits from Element)
doc_json["body"] = ElementToJson(doc);
result["documents"].push_back(doc_json);
}
}
return result;
}
bool UIInspector::SaveHierarchy(const std::string& path) const {
json hierarchy = DumpHierarchy();
std::ofstream file(path);
if (!file) return false;
file << hierarchy.dump(2);
std::string content = hierarchy.dump(2);
// Use temporary file + rename for atomic writes
std::string temp_path = path + ".tmp";
{
std::ofstream file(temp_path, std::ios::binary | std::ios::trunc);
if (!file) {
return false;
}
file.write(content.c_str(), content.size());
file.flush();
if (!file.good()) {
file.close();
return false;
}
file.close();
}
// Delete the old file first to avoid rename conflicts on Windows
std::error_code ec;
std::filesystem::remove(path, ec); // Ignore errors if file doesn't exist
// Rename temp file to final path
std::filesystem::rename(temp_path, path, ec);
if (ec) {
// Fallback: copy and delete if rename fails
std::filesystem::copy_file(temp_path, path,
std::filesystem::copy_options::overwrite_existing, ec);
std::filesystem::remove(temp_path, ec);
}
return true;
}

View File

@@ -12,9 +12,12 @@ public:
UIInspector(Rml::Context* context);
~UIInspector() = default;
// Set current screen URL override (to handle stale document detection)
void SetCurrentScreen(const std::string& screenUrl) { m_current_screen = screenUrl; }
// Dump entire UI hierarchy to JSON
nlohmann::json DumpHierarchy() const;
// Save hierarchy to file
bool SaveHierarchy(const std::string& path) const;
@@ -42,6 +45,7 @@ private:
Rml::Element* FindElementRecursive(Rml::Element* root, const std::string& selector) const;
Rml::Context* m_context;
std::string m_current_screen; // Override for screen URL detection
};
} // namespace mosis::testing