feat: Introduce automatic widget registration for ImGui with test engine integration
This commit is contained in:
85
src/app/gui/widget_auto_register.cc
Normal file
85
src/app/gui/widget_auto_register.cc
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#include "app/gui/widget_auto_register.h"
|
||||||
|
|
||||||
|
#include "imgui/imgui_internal.h"
|
||||||
|
#include "absl/strings/string_view.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace gui {
|
||||||
|
|
||||||
|
// Thread-local storage for the current auto-registration scope
|
||||||
|
thread_local std::vector<std::string> g_auto_scope_stack_;
|
||||||
|
|
||||||
|
AutoWidgetScope::AutoWidgetScope(const std::string& name)
|
||||||
|
: scope_(name), name_(name) {
|
||||||
|
g_auto_scope_stack_.push_back(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoRegisterLastItem(const std::string& widget_type,
|
||||||
|
const std::string& explicit_label,
|
||||||
|
const std::string& description) {
|
||||||
|
ImGuiContext* ctx = ImGui::GetCurrentContext();
|
||||||
|
if (!ctx || !ctx->CurrentWindow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the last item's ID
|
||||||
|
ImGuiID imgui_id = ctx->LastItemData.ID;
|
||||||
|
if (imgui_id == 0) {
|
||||||
|
return; // No valid item to register
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract label
|
||||||
|
std::string label = explicit_label;
|
||||||
|
if (label.empty()) {
|
||||||
|
// Try to get label from ImGui
|
||||||
|
const char* imgui_label = ImGui::GetItemLabel();
|
||||||
|
if (imgui_label && imgui_label[0] != '\0') {
|
||||||
|
label = imgui_label;
|
||||||
|
} else {
|
||||||
|
// Fallback to widget type + ID
|
||||||
|
label = absl::StrCat(widget_type, "_", imgui_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build full hierarchical path
|
||||||
|
std::string full_path;
|
||||||
|
if (!g_auto_scope_stack_.empty()) {
|
||||||
|
full_path = absl::StrJoin(g_auto_scope_stack_, "/");
|
||||||
|
full_path += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add widget type and normalized label
|
||||||
|
std::string normalized_label = WidgetIdRegistry::NormalizeLabel(label);
|
||||||
|
full_path += absl::StrCat(widget_type, ":", normalized_label);
|
||||||
|
|
||||||
|
// Capture metadata from ImGui's last item
|
||||||
|
WidgetIdRegistry::WidgetMetadata metadata;
|
||||||
|
metadata.label = label;
|
||||||
|
|
||||||
|
// Get window name
|
||||||
|
if (ctx->CurrentWindow) {
|
||||||
|
metadata.window_name = std::string(ctx->CurrentWindow->Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture visibility and enabled state
|
||||||
|
const ImGuiLastItemData& last = ctx->LastItemData;
|
||||||
|
metadata.visible = (last.StatusFlags & ImGuiItemStatusFlags_Visible) != 0;
|
||||||
|
metadata.enabled = (last.ItemFlags & ImGuiItemFlags_Disabled) == 0;
|
||||||
|
|
||||||
|
// Capture bounding rectangle
|
||||||
|
WidgetIdRegistry::WidgetBounds bounds;
|
||||||
|
bounds.min_x = last.Rect.Min.x;
|
||||||
|
bounds.min_y = last.Rect.Min.y;
|
||||||
|
bounds.max_x = last.Rect.Max.x;
|
||||||
|
bounds.max_y = last.Rect.Max.y;
|
||||||
|
bounds.valid = true;
|
||||||
|
metadata.bounds = bounds;
|
||||||
|
|
||||||
|
// Register with the global registry
|
||||||
|
WidgetIdRegistry::Instance().RegisterWidget(
|
||||||
|
full_path, widget_type, imgui_id, description, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gui
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
268
src/app/gui/widget_auto_register.h
Normal file
268
src/app/gui/widget_auto_register.h
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
#ifndef YAZE_APP_GUI_WIDGET_AUTO_REGISTER_H_
|
||||||
|
#define YAZE_APP_GUI_WIDGET_AUTO_REGISTER_H_
|
||||||
|
|
||||||
|
#include "imgui/imgui.h"
|
||||||
|
#include "app/gui/widget_id_registry.h"
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file widget_auto_register.h
|
||||||
|
* @brief Automatic widget registration helpers for ImGui Test Engine integration
|
||||||
|
*
|
||||||
|
* This file provides inline wrappers and RAII helpers that automatically
|
||||||
|
* register ImGui widgets with the WidgetIdRegistry for test automation.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* {
|
||||||
|
* gui::AutoWidgetScope scope("Dungeon/Canvas");
|
||||||
|
* if (gui::AutoButton("Save##DungeonSave")) {
|
||||||
|
* // Button clicked
|
||||||
|
* }
|
||||||
|
* gui::AutoInputText("RoomName", buffer, sizeof(buffer));
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* All widgets created within this scope will be automatically registered
|
||||||
|
* with their full hierarchical paths for test harness discovery.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace gui {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class AutoWidgetScope
|
||||||
|
* @brief RAII scope that enables automatic widget registration
|
||||||
|
*
|
||||||
|
* Creates a widget ID scope and enables auto-registration for all widgets
|
||||||
|
* created within this scope. Combines WidgetIdScope with automatic metadata
|
||||||
|
* capture.
|
||||||
|
*/
|
||||||
|
class AutoWidgetScope {
|
||||||
|
public:
|
||||||
|
explicit AutoWidgetScope(const std::string& name);
|
||||||
|
~AutoWidgetScope() = default;
|
||||||
|
|
||||||
|
// Get current scope path
|
||||||
|
std::string GetPath() const { return scope_.GetFullPath(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
WidgetIdScope scope_;
|
||||||
|
std::string name_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Automatically register the last ImGui item
|
||||||
|
*
|
||||||
|
* Call this after any ImGui widget creation to automatically register it.
|
||||||
|
* Captures widget type, bounds, visibility, and enabled state.
|
||||||
|
*
|
||||||
|
* @param widget_type Type of widget ("button", "input", "checkbox", etc.)
|
||||||
|
* @param explicit_label Optional explicit label (uses ImGui::GetItemLabel() if empty)
|
||||||
|
* @param description Optional description for the test harness
|
||||||
|
*/
|
||||||
|
void AutoRegisterLastItem(const std::string& widget_type,
|
||||||
|
const std::string& explicit_label = "",
|
||||||
|
const std::string& description = "");
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Automatic Registration Wrappers for Common ImGui Widgets
|
||||||
|
// ============================================================================
|
||||||
|
// These wrappers call the standard ImGui functions and automatically register
|
||||||
|
// the widget with the WidgetIdRegistry for test automation.
|
||||||
|
//
|
||||||
|
// They preserve the exact same API and return values as ImGui, so they can be
|
||||||
|
// drop-in replacements in existing code.
|
||||||
|
|
||||||
|
inline bool AutoButton(const char* label, const ImVec2& size = ImVec2(0, 0)) {
|
||||||
|
bool clicked = ImGui::Button(label, size);
|
||||||
|
AutoRegisterLastItem("button", label);
|
||||||
|
return clicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoSmallButton(const char* label) {
|
||||||
|
bool clicked = ImGui::SmallButton(label);
|
||||||
|
AutoRegisterLastItem("button", label, "Small button");
|
||||||
|
return clicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoCheckbox(const char* label, bool* v) {
|
||||||
|
bool changed = ImGui::Checkbox(label, v);
|
||||||
|
AutoRegisterLastItem("checkbox", label);
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoRadioButton(const char* label, bool active) {
|
||||||
|
bool clicked = ImGui::RadioButton(label, active);
|
||||||
|
AutoRegisterLastItem("radio", label);
|
||||||
|
return clicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoRadioButton(const char* label, int* v, int v_button) {
|
||||||
|
bool clicked = ImGui::RadioButton(label, v, v_button);
|
||||||
|
AutoRegisterLastItem("radio", label);
|
||||||
|
return clicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoInputText(const char* label, char* buf, size_t buf_size,
|
||||||
|
ImGuiInputTextFlags flags = 0,
|
||||||
|
ImGuiInputTextCallback callback = nullptr,
|
||||||
|
void* user_data = nullptr) {
|
||||||
|
bool changed = ImGui::InputText(label, buf, buf_size, flags, callback, user_data);
|
||||||
|
AutoRegisterLastItem("input", label);
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoInputTextMultiline(const char* label, char* buf, size_t buf_size,
|
||||||
|
const ImVec2& size = ImVec2(0, 0),
|
||||||
|
ImGuiInputTextFlags flags = 0,
|
||||||
|
ImGuiInputTextCallback callback = nullptr,
|
||||||
|
void* user_data = nullptr) {
|
||||||
|
bool changed = ImGui::InputTextMultiline(label, buf, buf_size, size, flags, callback, user_data);
|
||||||
|
AutoRegisterLastItem("textarea", label);
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoInputInt(const char* label, int* v, int step = 1, int step_fast = 100,
|
||||||
|
ImGuiInputTextFlags flags = 0) {
|
||||||
|
bool changed = ImGui::InputInt(label, v, step, step_fast, flags);
|
||||||
|
AutoRegisterLastItem("input_int", label);
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoInputFloat(const char* label, float* v, float step = 0.0f,
|
||||||
|
float step_fast = 0.0f, const char* format = "%.3f",
|
||||||
|
ImGuiInputTextFlags flags = 0) {
|
||||||
|
bool changed = ImGui::InputFloat(label, v, step, step_fast, format, flags);
|
||||||
|
AutoRegisterLastItem("input_float", label);
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoSliderInt(const char* label, int* v, int v_min, int v_max,
|
||||||
|
const char* format = "%d", ImGuiSliderFlags flags = 0) {
|
||||||
|
bool changed = ImGui::SliderInt(label, v, v_min, v_max, format, flags);
|
||||||
|
AutoRegisterLastItem("slider", label);
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoSliderFloat(const char* label, float* v, float v_min, float v_max,
|
||||||
|
const char* format = "%.3f", ImGuiSliderFlags flags = 0) {
|
||||||
|
bool changed = ImGui::SliderFloat(label, v, v_min, v_max, format, flags);
|
||||||
|
AutoRegisterLastItem("slider", label);
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoCombo(const char* label, int* current_item, const char* const items[],
|
||||||
|
int items_count, int popup_max_height_in_items = -1) {
|
||||||
|
bool changed = ImGui::Combo(label, current_item, items, items_count, popup_max_height_in_items);
|
||||||
|
AutoRegisterLastItem("combo", label);
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoSelectable(const char* label, bool selected = false,
|
||||||
|
ImGuiSelectableFlags flags = 0,
|
||||||
|
const ImVec2& size = ImVec2(0, 0)) {
|
||||||
|
bool clicked = ImGui::Selectable(label, selected, flags, size);
|
||||||
|
AutoRegisterLastItem("selectable", label);
|
||||||
|
return clicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoSelectable(const char* label, bool* p_selected,
|
||||||
|
ImGuiSelectableFlags flags = 0,
|
||||||
|
const ImVec2& size = ImVec2(0, 0)) {
|
||||||
|
bool clicked = ImGui::Selectable(label, p_selected, flags, size);
|
||||||
|
AutoRegisterLastItem("selectable", label);
|
||||||
|
return clicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoMenuItem(const char* label, const char* shortcut = nullptr,
|
||||||
|
bool selected = false, bool enabled = true) {
|
||||||
|
bool activated = ImGui::MenuItem(label, shortcut, selected, enabled);
|
||||||
|
AutoRegisterLastItem("menuitem", label);
|
||||||
|
return activated;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoMenuItem(const char* label, const char* shortcut, bool* p_selected,
|
||||||
|
bool enabled = true) {
|
||||||
|
bool activated = ImGui::MenuItem(label, shortcut, p_selected, enabled);
|
||||||
|
AutoRegisterLastItem("menuitem", label);
|
||||||
|
return activated;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoBeginMenu(const char* label, bool enabled = true) {
|
||||||
|
bool opened = ImGui::BeginMenu(label, enabled);
|
||||||
|
if (opened) {
|
||||||
|
AutoRegisterLastItem("menu", label, "Submenu");
|
||||||
|
}
|
||||||
|
return opened;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoBeginTabItem(const char* label, bool* p_open = nullptr,
|
||||||
|
ImGuiTabItemFlags flags = 0) {
|
||||||
|
bool selected = ImGui::BeginTabItem(label, p_open, flags);
|
||||||
|
if (selected) {
|
||||||
|
AutoRegisterLastItem("tab", label);
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoTreeNode(const char* label) {
|
||||||
|
bool opened = ImGui::TreeNode(label);
|
||||||
|
if (opened) {
|
||||||
|
AutoRegisterLastItem("treenode", label);
|
||||||
|
}
|
||||||
|
return opened;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoTreeNodeEx(const char* label, ImGuiTreeNodeFlags flags = 0) {
|
||||||
|
bool opened = ImGui::TreeNodeEx(label, flags);
|
||||||
|
if (opened) {
|
||||||
|
AutoRegisterLastItem("treenode", label);
|
||||||
|
}
|
||||||
|
return opened;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AutoCollapsingHeader(const char* label, ImGuiTreeNodeFlags flags = 0) {
|
||||||
|
bool opened = ImGui::CollapsingHeader(label, flags);
|
||||||
|
if (opened) {
|
||||||
|
AutoRegisterLastItem("collapsing", label);
|
||||||
|
}
|
||||||
|
return opened;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Canvas-specific registration helpers
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register a canvas widget after BeginChild or similar
|
||||||
|
*
|
||||||
|
* Canvases typically use BeginChild which doesn't have a return value,
|
||||||
|
* so we provide a separate registration helper.
|
||||||
|
*
|
||||||
|
* @param canvas_name Name of the canvas (should match BeginChild name)
|
||||||
|
* @param description Optional description of the canvas purpose
|
||||||
|
*/
|
||||||
|
inline void RegisterCanvas(const char* canvas_name, const std::string& description = "") {
|
||||||
|
// Get the child window ID
|
||||||
|
ImGuiContext* ctx = ImGui::GetCurrentContext();
|
||||||
|
if (ctx && ctx->CurrentWindow) {
|
||||||
|
ImGuiID canvas_id = ImGui::GetID(canvas_name);
|
||||||
|
AutoRegisterLastItem("canvas", canvas_name, description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register a table after BeginTable
|
||||||
|
*
|
||||||
|
* @param table_name Name of the table (should match BeginTable name)
|
||||||
|
* @param description Optional description
|
||||||
|
*/
|
||||||
|
inline void RegisterTable(const char* table_name, const std::string& description = "") {
|
||||||
|
ImGuiID table_id = ImGui::GetID(table_name);
|
||||||
|
AutoRegisterLastItem("table", table_name, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gui
|
||||||
|
} // namespace yaze
|
||||||
|
|
||||||
|
#endif // YAZE_APP_GUI_WIDGET_AUTO_REGISTER_H_
|
||||||
|
|
||||||
417
test/imgui/dungeon_editor_tests.cc
Normal file
417
test/imgui/dungeon_editor_tests.cc
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
#include "imgui.h"
|
||||||
|
|
||||||
|
#ifdef IMGUI_ENABLE_TEST_ENGINE
|
||||||
|
#include "imgui_test_engine/imgui_te_context.h"
|
||||||
|
#include "imgui_test_engine/imgui_te_engine.h"
|
||||||
|
#include "imgui_test_engine/imgui_te_ui.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "app/editor/dungeon/dungeon_editor.h"
|
||||||
|
#include "app/gui/widget_id_registry.h"
|
||||||
|
#include "app/rom.h"
|
||||||
|
|
||||||
|
namespace yaze {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
#ifdef IMGUI_ENABLE_TEST_ENGINE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file dungeon_editor_tests.cc
|
||||||
|
* @brief Comprehensive ImGui Test Engine tests for the Dungeon Editor
|
||||||
|
*
|
||||||
|
* These tests cover:
|
||||||
|
* - Canvas rendering and visibility
|
||||||
|
* - Room selection and loading
|
||||||
|
* - Object placement and manipulation
|
||||||
|
* - Property editing
|
||||||
|
* - Layer management
|
||||||
|
* - Graphics and palette loading
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Test Variables and Fixtures
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
struct DungeonEditorTestVars {
|
||||||
|
editor::DungeonEditor* editor = nullptr;
|
||||||
|
Rom* rom = nullptr;
|
||||||
|
bool rom_loaded = false;
|
||||||
|
int selected_room_id = 0;
|
||||||
|
bool canvas_visible = false;
|
||||||
|
ImVec2 canvas_size = ImVec2(0, 0);
|
||||||
|
int object_count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Canvas Rendering Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void RegisterDungeonCanvasTests(ImGuiTestEngine* engine) {
|
||||||
|
// Test: Canvas should be visible after room selection
|
||||||
|
ImGuiTest* t =
|
||||||
|
IM_REGISTER_TEST(engine, "dungeon_editor", "canvas_visibility");
|
||||||
|
t->SetVarsDataType<DungeonEditorTestVars>();
|
||||||
|
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
DungeonEditorTestVars& vars = ctx->GetVars<DungeonEditorTestVars>();
|
||||||
|
|
||||||
|
// Wait for the dungeon editor window to be available
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
|
||||||
|
// Verify canvas is present
|
||||||
|
ctx->ItemInfo("Dungeon/Canvas/canvas:DungeonCanvas");
|
||||||
|
IM_CHECK(ctx->ItemInfo("Dungeon/Canvas/canvas:DungeonCanvas").ID != 0);
|
||||||
|
|
||||||
|
// Canvas should be visible
|
||||||
|
ImGuiWindow* canvas_window = ctx->GetWindowByRef("Dungeon/Canvas");
|
||||||
|
IM_CHECK(canvas_window != nullptr);
|
||||||
|
IM_CHECK(canvas_window->Active);
|
||||||
|
|
||||||
|
vars.canvas_visible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test: Canvas should render after loading ROM
|
||||||
|
t = IM_REGISTER_TEST(engine, "dungeon_editor", "canvas_rendering_after_load");
|
||||||
|
t->SetVarsDataType<DungeonEditorTestVars>();
|
||||||
|
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
DungeonEditorTestVars& vars = ctx->GetVars<DungeonEditorTestVars>();
|
||||||
|
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
|
||||||
|
// Click "Load ROM" button if available
|
||||||
|
if (ctx->ItemExists("File/button:LoadROM")) {
|
||||||
|
ctx->ItemClick("File/button:LoadROM");
|
||||||
|
ctx->Yield(); // Wait for ROM to load
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify canvas renders something (not blank)
|
||||||
|
// We can check if the canvas texture was created
|
||||||
|
auto canvas_info = ctx->ItemInfo("Dungeon/Canvas/canvas:DungeonCanvas");
|
||||||
|
IM_CHECK(canvas_info.RectFull.GetSize().x > 0 &&
|
||||||
|
canvas_info.RectFull.GetSize().y > 0);
|
||||||
|
|
||||||
|
// Check that the canvas has non-zero size
|
||||||
|
vars.canvas_size = canvas_info.RectFull.GetSize();
|
||||||
|
IM_CHECK(vars.canvas_size.x > 0);
|
||||||
|
IM_CHECK(vars.canvas_size.y > 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test: Canvas should update when room changes
|
||||||
|
t = IM_REGISTER_TEST(engine, "dungeon_editor",
|
||||||
|
"canvas_updates_on_room_change");
|
||||||
|
t->SetVarsDataType<DungeonEditorTestVars>();
|
||||||
|
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
|
||||||
|
// Select room 0
|
||||||
|
ctx->ItemClick("Dungeon/RoomSelector/selectable:Room_0");
|
||||||
|
ctx->Yield();
|
||||||
|
|
||||||
|
// Capture initial canvas state
|
||||||
|
ImVec2 size1 =
|
||||||
|
ctx->ItemInfo("Dungeon/Canvas/canvas:DungeonCanvas").RectFull.GetSize();
|
||||||
|
|
||||||
|
// Select room 1
|
||||||
|
ctx->ItemClick("Dungeon/RoomSelector/selectable:Room_1");
|
||||||
|
ctx->Yield();
|
||||||
|
|
||||||
|
// Canvas should still be valid (may have different content, but same size)
|
||||||
|
ImVec2 size2 =
|
||||||
|
ctx->ItemInfo("Dungeon/Canvas/canvas:DungeonCanvas").RectFull.GetSize();
|
||||||
|
IM_CHECK(size2.x > 0 && size2.y > 0);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Room Selection Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void RegisterDungeonRoomSelectorTests(ImGuiTestEngine* engine) {
|
||||||
|
// Test: Room selector should be visible
|
||||||
|
ImGuiTest* t =
|
||||||
|
IM_REGISTER_TEST(engine, "dungeon_editor", "room_selector_visible");
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
|
||||||
|
// Verify room selector table exists
|
||||||
|
ctx->ItemInfo("Dungeon/RoomSelector/table:RoomList");
|
||||||
|
IM_CHECK(ctx->ItemInfo("Dungeon/RoomSelector/table:RoomList").ID != 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test: Clicking room should change selection
|
||||||
|
t = IM_REGISTER_TEST(engine, "dungeon_editor", "room_selection_click");
|
||||||
|
t->SetVarsDataType<DungeonEditorTestVars>();
|
||||||
|
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
DungeonEditorTestVars& vars = ctx->GetVars<DungeonEditorTestVars>();
|
||||||
|
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
|
||||||
|
// Click on room 5
|
||||||
|
ctx->ItemClick("Dungeon/RoomSelector/selectable:Room_5");
|
||||||
|
vars.selected_room_id = 5;
|
||||||
|
|
||||||
|
// Verify the room is now selected (visual feedback should exist)
|
||||||
|
// We can check if the canvas updates or if selection state changes
|
||||||
|
ctx->Yield();
|
||||||
|
|
||||||
|
// Success if we got here without errors
|
||||||
|
IM_CHECK(vars.selected_room_id == 5);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test: Multiple room tabs should work
|
||||||
|
t = IM_REGISTER_TEST(engine, "dungeon_editor", "room_tabs_switching");
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
|
||||||
|
// Open room 0
|
||||||
|
ctx->ItemClick("Dungeon/RoomSelector/selectable:Room_0");
|
||||||
|
ctx->Yield();
|
||||||
|
|
||||||
|
// Open room 10 (should create a new tab)
|
||||||
|
ctx->ItemClick("Dungeon/RoomSelector/selectable:Room_10");
|
||||||
|
ctx->Yield();
|
||||||
|
|
||||||
|
// Switch back to room 0 tab
|
||||||
|
if (ctx->ItemExists("Dungeon/Canvas/tab:Room_0")) {
|
||||||
|
ctx->ItemClick("Dungeon/Canvas/tab:Room_0");
|
||||||
|
ctx->Yield();
|
||||||
|
|
||||||
|
// Verify we're on room 0
|
||||||
|
IM_CHECK(ctx->ItemInfo("Dungeon/Canvas/tab:Room_0").StatusFlags &
|
||||||
|
ImGuiItemStatusFlags_Opened);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Object Editor Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void RegisterDungeonObjectEditorTests(ImGuiTestEngine* engine) {
|
||||||
|
// Test: Object selector should be visible
|
||||||
|
ImGuiTest* t =
|
||||||
|
IM_REGISTER_TEST(engine, "dungeon_editor", "object_selector_visible");
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
|
||||||
|
// Verify object selector exists
|
||||||
|
IM_CHECK(ctx->ItemExists("Dungeon/ObjectSelector"));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test: Selecting object should enable placement mode
|
||||||
|
t = IM_REGISTER_TEST(engine, "dungeon_editor", "object_selection");
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
|
||||||
|
// Click on an object in the selector
|
||||||
|
if (ctx->ItemExists("Dungeon/ObjectSelector/selectable:Object_0")) {
|
||||||
|
ctx->ItemClick("Dungeon/ObjectSelector/selectable:Object_0");
|
||||||
|
ctx->Yield();
|
||||||
|
|
||||||
|
// Object should be selected (visual feedback should exist)
|
||||||
|
// Success if no errors
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test: Object property panel should show when object selected
|
||||||
|
t = IM_REGISTER_TEST(engine, "dungeon_editor", "object_property_panel");
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
|
||||||
|
// Place or select an object
|
||||||
|
ctx->ItemClick("Dungeon/Canvas/canvas:DungeonCanvas",
|
||||||
|
ImGuiMouseButton_Left);
|
||||||
|
ctx->Yield();
|
||||||
|
|
||||||
|
// Property panel should appear
|
||||||
|
// (This might be conditional on having objects in the room)
|
||||||
|
if (ctx->ItemExists("Dungeon/ObjectEditor/input_int:ObjectID")) {
|
||||||
|
// Can edit object ID
|
||||||
|
ctx->ItemInputValue("Dungeon/ObjectEditor/input_int:ObjectID", 42);
|
||||||
|
ctx->Yield();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Layer Management Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void RegisterDungeonLayerTests(ImGuiTestEngine* engine) {
|
||||||
|
// Test: Layer controls should be visible
|
||||||
|
ImGuiTest* t =
|
||||||
|
IM_REGISTER_TEST(engine, "dungeon_editor", "layer_controls_visible");
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
|
||||||
|
// Verify layer checkboxes exist
|
||||||
|
if (ctx->ItemExists("Dungeon/ObjectEditor/checkbox:ShowBG1")) {
|
||||||
|
IM_CHECK(ctx->ItemInfo("Dungeon/ObjectEditor/checkbox:ShowBG1").ID != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx->ItemExists("Dungeon/ObjectEditor/checkbox:ShowBG2")) {
|
||||||
|
IM_CHECK(ctx->ItemInfo("Dungeon/ObjectEditor/checkbox:ShowBG2").ID != 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test: Toggling layer visibility
|
||||||
|
t = IM_REGISTER_TEST(engine, "dungeon_editor", "layer_toggle");
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
|
||||||
|
// Toggle BG1 layer
|
||||||
|
if (ctx->ItemExists("Dungeon/ObjectEditor/checkbox:ShowBG1")) {
|
||||||
|
ctx->ItemClick("Dungeon/ObjectEditor/checkbox:ShowBG1");
|
||||||
|
ctx->Yield();
|
||||||
|
|
||||||
|
// Toggle it back
|
||||||
|
ctx->ItemClick("Dungeon/ObjectEditor/checkbox:ShowBG1");
|
||||||
|
ctx->Yield();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Palette and Graphics Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void RegisterDungeonGraphicsTests(ImGuiTestEngine* engine) {
|
||||||
|
// Test: Palette editor should open
|
||||||
|
ImGuiTest* t = IM_REGISTER_TEST(engine, "dungeon_editor", "palette_editor");
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
|
||||||
|
// Click "Palette Editor" button if available
|
||||||
|
if (ctx->ItemExists("Dungeon/Toolset/button:PaletteEditor")) {
|
||||||
|
ctx->ItemClick("Dungeon/Toolset/button:PaletteEditor");
|
||||||
|
ctx->Yield();
|
||||||
|
|
||||||
|
// Palette window should open
|
||||||
|
IM_CHECK(ctx->WindowInfo("Palette Editor").Window != nullptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test: Graphics should load for selected room
|
||||||
|
t = IM_REGISTER_TEST(engine, "dungeon_editor", "graphics_loading");
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
|
||||||
|
// Select a room
|
||||||
|
ctx->ItemClick("Dungeon/RoomSelector/selectable:Room_0");
|
||||||
|
ctx->Yield(2); // Wait for graphics to load
|
||||||
|
|
||||||
|
// Canvas should have valid content
|
||||||
|
auto canvas_info = ctx->ItemInfo("Dungeon/Canvas/canvas:DungeonCanvas");
|
||||||
|
IM_CHECK(canvas_info.RectFull.GetWidth() > 0);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Integration Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void RegisterDungeonIntegrationTests(ImGuiTestEngine* engine) {
|
||||||
|
// Test: Full workflow - load ROM, select room, place object
|
||||||
|
ImGuiTest* t =
|
||||||
|
IM_REGISTER_TEST(engine, "dungeon_editor", "full_edit_workflow");
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
|
||||||
|
// 1. Load ROM (if needed)
|
||||||
|
ctx->Yield(2);
|
||||||
|
|
||||||
|
// 2. Select a room
|
||||||
|
ctx->ItemClick("Dungeon/RoomSelector/selectable:Room_5");
|
||||||
|
ctx->Yield(2);
|
||||||
|
|
||||||
|
// 3. Select an object type
|
||||||
|
if (ctx->ItemExists("Dungeon/ObjectSelector/selectable:Object_1")) {
|
||||||
|
ctx->ItemClick("Dungeon/ObjectSelector/selectable:Object_1");
|
||||||
|
ctx->Yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Click on canvas to place object
|
||||||
|
ctx->ItemClick("Dungeon/Canvas/canvas:DungeonCanvas",
|
||||||
|
ImGuiMouseButton_Left);
|
||||||
|
ctx->Yield();
|
||||||
|
|
||||||
|
// 5. Verify object was placed (property panel should appear)
|
||||||
|
// This is a basic workflow test - success if no crashes
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Widget Discovery Tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void RegisterDungeonWidgetDiscoveryTests(ImGuiTestEngine* engine) {
|
||||||
|
// Test: Widget registry should capture all dungeon editor widgets
|
||||||
|
ImGuiTest* t =
|
||||||
|
IM_REGISTER_TEST(engine, "dungeon_editor", "widget_registry_complete");
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
|
||||||
|
// Yield a few frames to let widgets register
|
||||||
|
ctx->Yield(3);
|
||||||
|
|
||||||
|
// Query the widget registry
|
||||||
|
auto& registry = gui::WidgetIdRegistry::Instance();
|
||||||
|
auto all_widgets = registry.GetAllWidgets();
|
||||||
|
|
||||||
|
// Should have multiple widgets registered
|
||||||
|
IM_CHECK(all_widgets.size() > 10);
|
||||||
|
|
||||||
|
// Essential widgets should be present
|
||||||
|
IM_CHECK(registry.GetWidgetId("Dungeon/RoomSelector/table:RoomList") != 0);
|
||||||
|
IM_CHECK(registry.GetWidgetId("Dungeon/Canvas/canvas:DungeonCanvas") != 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test: Export widget catalog
|
||||||
|
t = IM_REGISTER_TEST(engine, "dungeon_editor", "widget_catalog_export");
|
||||||
|
t->TestFunc = [](ImGuiTestContext* ctx) {
|
||||||
|
ctx->SetRef("Dungeon Editor");
|
||||||
|
ctx->Yield(2);
|
||||||
|
|
||||||
|
auto& registry = gui::WidgetIdRegistry::Instance();
|
||||||
|
|
||||||
|
// Export to JSON
|
||||||
|
std::string json_catalog = registry.ExportCatalog("json");
|
||||||
|
IM_CHECK(!json_catalog.empty());
|
||||||
|
IM_CHECK(json_catalog.find("\"widgets\"") != std::string::npos);
|
||||||
|
|
||||||
|
// Export to YAML
|
||||||
|
std::string yaml_catalog = registry.ExportCatalog("yaml");
|
||||||
|
IM_CHECK(!yaml_catalog.empty());
|
||||||
|
IM_CHECK(yaml_catalog.find("widgets:") != std::string::npos);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Registration Function
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register all dungeon editor tests with the ImGui Test Engine
|
||||||
|
*
|
||||||
|
* Call this function during application initialization to register all
|
||||||
|
* automated tests for the dungeon editor.
|
||||||
|
*
|
||||||
|
* @param engine The ImGuiTestEngine instance
|
||||||
|
*/
|
||||||
|
void RegisterDungeonEditorTests(ImGuiTestEngine* engine) {
|
||||||
|
RegisterDungeonCanvasTests(engine);
|
||||||
|
RegisterDungeonRoomSelectorTests(engine);
|
||||||
|
RegisterDungeonObjectEditorTests(engine);
|
||||||
|
RegisterDungeonLayerTests(engine);
|
||||||
|
RegisterDungeonGraphicsTests(engine);
|
||||||
|
RegisterDungeonIntegrationTests(engine);
|
||||||
|
RegisterDungeonWidgetDiscoveryTests(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // IMGUI_ENABLE_TEST_ENGINE
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
} // namespace yaze
|
||||||
Reference in New Issue
Block a user