diff --git a/src/app/gui/widget_auto_register.cc b/src/app/gui/widget_auto_register.cc new file mode 100644 index 00000000..014b08ac --- /dev/null +++ b/src/app/gui/widget_auto_register.cc @@ -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 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 + diff --git a/src/app/gui/widget_auto_register.h b/src/app/gui/widget_auto_register.h new file mode 100644 index 00000000..872a6481 --- /dev/null +++ b/src/app/gui/widget_auto_register.h @@ -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_ + diff --git a/test/imgui/dungeon_editor_tests.cc b/test/imgui/dungeon_editor_tests.cc new file mode 100644 index 00000000..c8d8158d --- /dev/null +++ b/test/imgui/dungeon_editor_tests.cc @@ -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(); + + t->TestFunc = [](ImGuiTestContext* ctx) { + DungeonEditorTestVars& vars = ctx->GetVars(); + + // 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(); + + t->TestFunc = [](ImGuiTestContext* ctx) { + DungeonEditorTestVars& vars = ctx->GetVars(); + + 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(); + + 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(); + + t->TestFunc = [](ImGuiTestContext* ctx) { + DungeonEditorTestVars& vars = ctx->GetVars(); + + 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