feat: split tests into per-suite files

This commit is contained in:
2026-10-19 00:45:29 +02:00
parent 92c1daf396
commit 3a04c1884a
11 changed files with 2425 additions and 2128 deletions
+218
View File
@@ -0,0 +1,218 @@
#include <filesystem>
#include <string>
#include <destrum/ObjectModel/Component.h>
#include <destrum/ObjectModel/GameObject.h>
#include <destrum/Scene/Scene.h>
#include <destrum/Scene/SceneManager.h>
#include <destrum/Serialization/ComponentFactory.h>
#include <destrum/Serialization/ComponentRegistry.h>
#include <destrum/Serialization/GlmJson.h>
#include <destrum/Serialization/SceneSerializer.h>
#include <destrum/Components/UI/Canvas.h>
#include <destrum/Components/UI/RectTransform.h>
#include <destrum/Components/UI/UIImage.h>
#include <destrum/Components/UI/UIText.h>
#include <destrum/Graphics/Managers/Renderer2D.h>
#include <destrum/Graphics/Resources/Font.h>
#include <components/CrosshairUI.h>
#include <components/FirstPersonController.h>
#include <components/WinGoal.h>
#include <components/WinScreenUI.h>
#include "TestUtils.h"
using namespace test;
namespace {
void testCanvasLayout()
{
Scene& scene = SceneManager::GetInstance().CreateScene("canvas");
GameObject* hud = scene.CreateGameObject("hud");
auto* canvas = hud->AddComponent<Canvas>();
const auto addChild = [&](const std::string& name, GameObject& parent) {
GameObject* child = scene.CreateGameObject(name);
child->GetTransform().SetParent(&parent.GetTransform(), false);
return child;
};
GameObject* panel = addChild("panel", *hud);
auto* panelRect = panel->AddComponent<RectTransform>();
panelRect->SetSize({100.0f, 50.0f});
panelRect->SetPosition({10.0f, 0.0f});
panel->AddComponent<UIImage>(glm::vec4{1.0f, 0.0f, 0.0f, 1.0f});
GameObject* fill = addChild("fill", *panel);
fill->AddComponent<UIImage>(glm::vec4{0.0f, 1.0f, 0.0f, 1.0f})->SetShape(UIImageShape::Ellipse);
GameObject* corner = addChild("corner", *hud);
auto* cornerRect = corner->AddComponent<RectTransform>();
cornerRect->SetAnchor({1.0f, 1.0f});
cornerRect->SetPivot({1.0f, 1.0f});
cornerRect->SetPosition({-20.0f, -20.0f});
cornerRect->SetSize({40.0f, 40.0f});
corner->AddComponent<UIImage>();
GameObject* hidden = addChild("hidden", *hud);
auto* hiddenRect = hidden->AddComponent<RectTransform>();
hiddenRect->SetSize({20.0f, 20.0f});
hidden->AddComponent<UIImage>();
hidden->SetActive(false);
GameObject* nested = addChild("nested", *hud);
nested->AddComponent<Canvas>();
nested->AddComponent<UIImage>();
scene.CommitPendingAdditions();
auto& r2d = Renderer2D::GetInstance();
r2d.Clear();
canvas->Draw({1920.0f, 1080.0f});
const auto& shapes = r2d.GetShapes();
check(shapes.size() == 3, "a canvas must draw its active children and skip inactive ones and nested canvases");
check(shapes[0].type == Shape2DType::Rect && shapes[0].center == glm::vec2{970.0f, 540.0f} &&
shapes[0].halfSize == glm::vec2{50.0f, 25.0f},
"a centered rect must be placed around its anchor, offset by its position");
check(shapes[1].type == Shape2DType::Ellipse && shapes[1].center == shapes[0].center &&
shapes[1].halfSize == shapes[0].halfSize,
"an object without a RectTransform must fill its parent and draw after it");
check(shapes[2].center == glm::vec2{1880.0f, 1040.0f},
"anchors and pivot must place a rect relative to the screen corner");
check(panelRect->GetScreenRect().min == glm::vec2{920.0f, 515.0f},
"the canvas must store where it drew each rect");
check(hiddenRect->GetScreenRect().size == glm::vec2{20.0f, 20.0f},
"hidden objects must still be laid out so the editor can show them");
r2d.Clear();
canvas->Draw({3840.0f, 2160.0f});
check(r2d.GetShapes().size() == 3 && r2d.GetShapes()[0].halfSize == glm::vec2{100.0f, 50.0f} &&
r2d.GetShapes()[0].center == glm::vec2{1940.0f, 1080.0f},
"sizes and offsets must scale with the screen height");
canvas->SetScaleMode(CanvasScaleMode::ConstantPixelSize);
r2d.Clear();
canvas->Draw({3840.0f, 2160.0f});
check(r2d.GetShapes()[0].halfSize == glm::vec2{50.0f, 25.0f}, "a constant pixel size canvas must not scale");
check(Canvas::FindFor(*fill) == canvas && Canvas::FindFor(*nested) == nested->GetComponent<Canvas>(),
"FindFor must return the closest canvas on the object or its parents");
check(cornerRect->GetParentRect().size == glm::vec2{3840.0f, 2160.0f},
"the canvas must store the parent rect each rect was laid out in");
cornerRect->ApplyAnchorPreset({0.0f, -1.0f});
check(cornerRect->GetAnchorMin() == glm::vec2{0.0f, 0.0f} && cornerRect->GetAnchorMax() == glm::vec2{0.0f, 1.0f} &&
cornerRect->GetPivot() == glm::vec2{0.0f, 0.5f} && cornerRect->GetPosition() == glm::vec2{0.0f} &&
cornerRect->GetSize() == glm::vec2{40.0f, 0.0f},
"an anchor preset must pin or stretch each axis and keep the size on pinned axes");
panelRect->Stretch();
check(panelRect->ComputeRect(UIRect{.min = {10.0f, 20.0f}, .size = {30.0f, 40.0f}}, 2.0f).size == glm::vec2{30.0f, 40.0f},
"a stretched rect must fill its parent");
r2d.Clear();
SceneManager::GetInstance().Destroy();
}
void testUITextPlacement()
{
Scene& scene = SceneManager::GetInstance().CreateScene("ui text");
auto* text = scene.CreateGameObject("text")->AddComponent<UIText>("hi", 20.0f);
const UIRect rect{.min = {100.0f, 100.0f}, .size = {200.0f, 100.0f}};
check(text->PlaceText(rect, {50.0f, 20.0f}) == glm::vec2{175.0f, 140.0f}, "text must be centered by default");
text->SetAlignment(UITextAlign::End, UITextAlign::Start);
check(text->PlaceText(rect, {50.0f, 20.0f}) == glm::vec2{250.0f, 100.0f}, "text must follow its alignment");
SceneManager::GetInstance().Destroy();
}
void testHudRoundTrip()
{
RegisterEngineComponents();
ComponentFactory::Register<CrosshairUI>("CrosshairUI");
ComponentFactory::Register<WinScreenUI>("WinScreenUI");
ComponentFactory::Register<WinGoal>("WinGoal");
ComponentFactory::Register<FirstPersonController>("FirstPersonController");
Scene& scene = SceneManager::GetInstance().CreateScene("hud");
GameObject* player = scene.CreateGameObject("player");
player->AddComponent<FirstPersonController>();
GameObject* goal = scene.CreateGameObject("goal");
auto* winGoal = goal->AddComponent<WinGoal>();
GameObject* hud = scene.CreateGameObject("hud");
hud->AddComponent<Canvas>()->SetReferenceHeight(720.0f);
GameObject* crosshair = scene.CreateGameObject("crosshair");
crosshair->GetTransform().SetParent(&hud->GetTransform(), false);
auto* crosshairRect = crosshair->AddComponent<RectTransform>();
crosshairRect->SetAnchors({0.25f, 0.5f}, {0.75f, 0.5f});
crosshair->AddComponent<UIImage>()->SetOutline(3.0f);
GameObject* screen = scene.CreateGameObject("screen");
screen->GetTransform().SetParent(&hud->GetTransform(), false);
auto* label = screen->AddComponent<UIText>("YOU WIN", 64.0f);
label->SetAlignment(UITextAlign::Start, UITextAlign::End);
auto* crosshairUi = hud->AddComponent<CrosshairUI>();
crosshairUi->SetPlayer(*player);
crosshairUi->SetCrosshair(*crosshair);
auto* winScreenUi = hud->AddComponent<WinScreenUI>();
winScreenUi->SetGoal(*goal);
winScreenUi->SetScreen(*screen);
scene.CommitPendingAdditions();
crosshairUi->LateUpdate(0.0f);
winScreenUi->LateUpdate(0.0f);
check(!crosshair->IsActive(), "the crosshair must hide while the player isn't possessed");
check(!screen->IsActive(), "the win screen must hide until the goal is reached");
winGoal->Activate(*player);
winScreenUi->LateUpdate(0.0f);
check(screen->IsActive(), "the win screen must show once the goal is reached");
const auto path = std::filesystem::temp_directory_path() / "destrum_hud_scene.json";
check(SceneSerializer::Save(scene, path), "the hud scene must save");
Scene& copy = SceneManager::GetInstance().CreateScene("hud copy");
check(SceneSerializer::Load(copy, path), "the hud scene must load");
std::filesystem::remove(path);
copy.CommitPendingAdditions();
GameObject* loadedHud = findByName(copy, "hud");
GameObject* loadedCrosshair = findByName(copy, "crosshair");
GameObject* loadedScreen = findByName(copy, "screen");
check(loadedHud != nullptr && loadedCrosshair != nullptr && loadedScreen != nullptr,
"the hud objects must survive a round-trip");
check(loadedCrosshair->GetTransform().GetParent() == &loadedHud->GetTransform(),
"ui objects must stay parented to their canvas");
const auto* loadedCanvas = loadedHud->GetComponent<Canvas>();
check(loadedCanvas != nullptr && loadedCanvas->GetReferenceHeight() == 720.0f, "canvas settings must round-trip");
const auto* loadedRect = loadedCrosshair->GetComponent<RectTransform>();
check(loadedRect != nullptr && loadedRect->GetAnchorMin() == glm::vec2{0.25f, 0.5f} &&
loadedRect->GetAnchorMax() == glm::vec2{0.75f, 0.5f},
"rect transforms must round-trip");
const auto* loadedImage = loadedCrosshair->GetComponent<UIImage>();
check(loadedImage != nullptr && loadedImage->GetOutline() == 3.0f, "ui images must round-trip");
const auto* loadedLabel = loadedScreen->GetComponent<UIText>();
check(loadedLabel != nullptr && loadedLabel->GetText() == "YOU WIN" && loadedLabel->GetFontSize() == 64.0f &&
loadedLabel->GetHorizontalAlign() == UITextAlign::Start &&
loadedLabel->GetVerticalAlign() == UITextAlign::End,
"ui text must round-trip");
const auto* loadedCrosshairUi = loadedHud->GetComponent<CrosshairUI>();
const auto* loadedWinScreenUi = loadedHud->GetComponent<WinScreenUI>();
check(loadedCrosshairUi != nullptr && loadedCrosshairUi->GetPlayer().Get(copy) == findByName(copy, "player") &&
loadedCrosshairUi->GetCrosshair().Get(copy) == loadedCrosshair,
"crosshair references must point at the loaded objects");
check(loadedWinScreenUi != nullptr && loadedWinScreenUi->GetGoal().Get(copy) == findByName(copy, "goal") &&
loadedWinScreenUi->GetScreen().Get(copy) == loadedScreen,
"win screen references must point at the loaded objects");
SceneManager::GetInstance().Destroy();
}
}
void RunUiTests()
{
testCanvasLayout();
testUITextPlacement();
testHudRoundTrip();
}