#include "app/gui/widgets/tile_selector_widget.h" #include #include #include "app/gfx/core/bitmap.h" #include "app/gui/canvas/canvas.h" #include "imgui/imgui.h" #include "testing.h" namespace yaze { namespace test { using ::testing::Eq; using ::testing::NotNull; /** * @brief Test fixture for TileSelectorWidget tests. * * Creates and destroys ImGui context for tests that need it. * Tests that call ImGui functions (like Render) require the context, * while pure logic tests (like TileOrigin calculations) do not. */ class TileSelectorWidgetTest : public ::testing::Test { protected: void SetUp() override { // Create ImGui context for tests that need it (e.g., Render tests) // This is required because Canvas and TileSelectorWidget use ImGui functions imgui_context_ = ImGui::CreateContext(); ImGui::SetCurrentContext(imgui_context_); // Initialize minimal ImGui IO for testing ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2(1920, 1080); io.DeltaTime = 1.0f / 60.0f; // Create a test canvas canvas_ = std::make_unique("TestCanvas", ImVec2(512, 512), gui::CanvasGridSize::k16x16); // Create a test config config_.tile_size = 16; config_.display_scale = 2.0f; config_.tiles_per_row = 8; config_.total_tiles = 64; // 8x8 grid config_.draw_offset = {2.0f, 0.0f}; config_.show_tile_ids = false; config_.highlight_color = {1.0f, 0.85f, 0.35f, 1.0f}; } void TearDown() override { // Clean up canvas before destroying ImGui context canvas_.reset(); // Destroy ImGui context if (imgui_context_) { ImGui::DestroyContext(imgui_context_); imgui_context_ = nullptr; } } ImGuiContext* imgui_context_ = nullptr; std::unique_ptr canvas_; gui::TileSelectorWidget::Config config_; }; // Test basic construction TEST_F(TileSelectorWidgetTest, Construction) { gui::TileSelectorWidget widget("test_widget"); EXPECT_EQ(widget.GetSelectedTileID(), 0); } // Test construction with config TEST_F(TileSelectorWidgetTest, ConstructionWithConfig) { gui::TileSelectorWidget widget("test_widget", config_); EXPECT_EQ(widget.GetSelectedTileID(), 0); } // Test canvas attachment TEST_F(TileSelectorWidgetTest, AttachCanvas) { gui::TileSelectorWidget widget("test_widget"); widget.AttachCanvas(canvas_.get()); // No crash means success } // Test tile count setting TEST_F(TileSelectorWidgetTest, SetTileCount) { gui::TileSelectorWidget widget("test_widget", config_); widget.SetTileCount(128); // Verify selection is clamped when tile count changes widget.SetSelectedTile(100); EXPECT_EQ(widget.GetSelectedTileID(), 100); // Setting tile count lower should clamp selection widget.SetTileCount(50); EXPECT_EQ(widget.GetSelectedTileID(), 0); // Should reset to 0 } // Test selected tile setting TEST_F(TileSelectorWidgetTest, SetSelectedTile) { gui::TileSelectorWidget widget("test_widget", config_); widget.SetTileCount(64); widget.SetSelectedTile(10); EXPECT_EQ(widget.GetSelectedTileID(), 10); widget.SetSelectedTile(63); EXPECT_EQ(widget.GetSelectedTileID(), 63); // Out of bounds should be ignored widget.SetSelectedTile(64); EXPECT_EQ(widget.GetSelectedTileID(), 63); // Should remain unchanged widget.SetSelectedTile(-1); EXPECT_EQ(widget.GetSelectedTileID(), 63); // Should remain unchanged } // Test tile origin calculation TEST_F(TileSelectorWidgetTest, TileOrigin) { gui::TileSelectorWidget widget("test_widget", config_); widget.SetTileCount(64); // Test first tile (0,0) auto origin = widget.TileOrigin(0); EXPECT_FLOAT_EQ(origin.x, config_.draw_offset.x); EXPECT_FLOAT_EQ(origin.y, config_.draw_offset.y); // Test tile at (1,0) origin = widget.TileOrigin(1); float expected_x = config_.draw_offset.x + (config_.tile_size * config_.display_scale); EXPECT_FLOAT_EQ(origin.x, expected_x); EXPECT_FLOAT_EQ(origin.y, config_.draw_offset.y); // Test tile at (0,1) - first tile of second row origin = widget.TileOrigin(8); expected_x = config_.draw_offset.x; float expected_y = config_.draw_offset.y + (config_.tile_size * config_.display_scale); EXPECT_FLOAT_EQ(origin.x, expected_x); EXPECT_FLOAT_EQ(origin.y, expected_y); // Test invalid tile ID origin = widget.TileOrigin(64); EXPECT_FLOAT_EQ(origin.x, -1.0f); EXPECT_FLOAT_EQ(origin.y, -1.0f); } // Test render without atlas (should not crash) // NOTE: This test requires a full ImGui frame context which is complex to set up // in a unit test without SDL/renderer backends. We test the early return path // where canvas_ is nullptr instead. TEST_F(TileSelectorWidgetTest, RenderWithoutCanvas) { gui::TileSelectorWidget widget("test_widget", config_); // Do NOT attach canvas - this tests the early return path gfx::Bitmap atlas; auto result = widget.Render(atlas, false); // With no canvas attached, Render should return early with default result EXPECT_FALSE(result.tile_clicked); EXPECT_FALSE(result.tile_double_clicked); EXPECT_FALSE(result.selection_changed); EXPECT_EQ(result.selected_tile, -1); } // Test programmatic selection for AI/automation TEST_F(TileSelectorWidgetTest, ProgrammaticSelection) { gui::TileSelectorWidget widget("test_widget", config_); widget.AttachCanvas(canvas_.get()); widget.SetTileCount(64); // Simulate AI/automation selecting tiles programmatically for (int i = 0; i < 64; ++i) { widget.SetSelectedTile(i); EXPECT_EQ(widget.GetSelectedTileID(), i); auto origin = widget.TileOrigin(i); int expected_col = i % config_.tiles_per_row; int expected_row = i / config_.tiles_per_row; float expected_x = config_.draw_offset.x + expected_col * config_.tile_size * config_.display_scale; float expected_y = config_.draw_offset.y + expected_row * config_.tile_size * config_.display_scale; EXPECT_FLOAT_EQ(origin.x, expected_x); EXPECT_FLOAT_EQ(origin.y, expected_y); } } // Test scroll to tile TEST_F(TileSelectorWidgetTest, ScrollToTile) { gui::TileSelectorWidget widget("test_widget", config_); widget.AttachCanvas(canvas_.get()); widget.SetTileCount(64); // Scroll to various tiles (should not crash) widget.ScrollToTile(0); widget.ScrollToTile(10); widget.ScrollToTile(63); // Invalid tile should not crash widget.ScrollToTile(-1); widget.ScrollToTile(64); } // Test different configs TEST_F(TileSelectorWidgetTest, DifferentConfigs) { // Test with 16x16 grid gui::TileSelectorWidget::Config large_config; large_config.tile_size = 8; large_config.display_scale = 1.0f; large_config.tiles_per_row = 16; large_config.total_tiles = 256; large_config.draw_offset = {0.0f, 0.0f}; gui::TileSelectorWidget large_widget("large_widget", large_config); large_widget.SetTileCount(256); for (int i = 0; i < 256; ++i) { large_widget.SetSelectedTile(i); EXPECT_EQ(large_widget.GetSelectedTileID(), i); } } } // namespace test } // namespace yaze