Files
XCEngine/tests/Core/UI/test_ui_runtime.cpp

144 lines
5.1 KiB
C++
Raw Normal View History

#include <gtest/gtest.h>
#include <XCEngine/UI/Runtime/UIScreenDocumentHost.h>
#include <XCEngine/UI/Runtime/UIScreenPlayer.h>
#include <XCEngine/UI/Runtime/UISystem.h>
2026-04-05 05:44:07 +08:00
#include <chrono>
#include <filesystem>
#include <fstream>
2026-04-05 05:44:07 +08:00
#include <string>
namespace {
using XCEngine::UI::Runtime::UIScreenAsset;
using XCEngine::UI::Runtime::UIScreenFrameInput;
using XCEngine::UI::Runtime::UIScreenPlayer;
using XCEngine::UI::Runtime::UIDocumentScreenHost;
using XCEngine::UI::Runtime::UISystem;
2026-04-05 05:44:07 +08:00
namespace fs = std::filesystem;
2026-04-05 05:44:07 +08:00
class TempFileScope {
public:
TempFileScope(std::string stem, std::string extension, std::string contents) {
const auto uniqueId = std::to_string(
std::chrono::steady_clock::now().time_since_epoch().count());
m_path = fs::temp_directory_path() / (std::move(stem) + "_" + uniqueId + std::move(extension));
std::ofstream output(m_path, std::ios::binary | std::ios::trunc);
output << contents;
}
2026-04-05 05:44:07 +08:00
~TempFileScope() {
std::error_code ec;
fs::remove(m_path, ec);
}
2026-04-05 05:44:07 +08:00
const fs::path& Path() const {
return m_path;
}
2026-04-05 05:44:07 +08:00
private:
fs::path m_path = {};
};
2026-04-05 05:44:07 +08:00
std::string BuildViewMarkup(const char* heroTitle, const char* overlayText = nullptr) {
std::string markup =
"<View name=\"Runtime Screen\">\n"
" <Column id=\"root\" padding=\"18\" gap=\"10\">\n"
" <Card id=\"hero\" title=\"" + std::string(heroTitle) + "\" subtitle=\"Shared XCUI runtime layer\" />\n"
" <Text id=\"status\" text=\"Ready for play\" />\n"
" <Row id=\"actions\" gap=\"12\">\n"
" <Button id=\"start\" text=\"Start\" />\n"
" <Button id=\"options\" text=\"Options\" />\n"
" </Row>\n";
if (overlayText != nullptr) {
markup += " <Card id=\"overlay\" title=\"" + std::string(overlayText) + "\" tone=\"accent\" />\n";
}
2026-04-05 05:44:07 +08:00
markup +=
" </Column>\n"
"</View>\n";
return markup;
}
2026-04-05 05:44:07 +08:00
UIScreenAsset BuildScreenAsset(const fs::path& viewPath, const char* screenId) {
UIScreenAsset screen = {};
screen.screenId = screenId;
screen.documentPath = viewPath.string();
return screen;
}
2026-04-05 05:44:07 +08:00
bool DrawDataContainsText(
const XCEngine::UI::UIDrawData& drawData,
const std::string& text) {
for (const XCEngine::UI::UIDrawList& drawList : drawData.GetDrawLists()) {
for (const XCEngine::UI::UIDrawCommand& command : drawList.GetCommands()) {
if (command.type == XCEngine::UI::UIDrawCommandType::Text &&
command.text == text) {
return true;
}
}
}
return false;
}
2026-04-05 05:44:07 +08:00
UIScreenFrameInput BuildInputState(std::uint64_t frameIndex = 1u) {
UIScreenFrameInput input = {};
2026-04-05 05:44:07 +08:00
input.viewportRect = XCEngine::UI::UIRect(0.0f, 0.0f, 800.0f, 480.0f);
input.frameIndex = frameIndex;
input.focused = true;
return input;
}
2026-04-05 05:44:07 +08:00
} // namespace
2026-04-05 05:44:07 +08:00
TEST(UIRuntimeTest, ScreenPlayerBuildsDrawDataFromDocumentTree) {
TempFileScope viewFile("xcui_runtime_screen", ".xcui", BuildViewMarkup("Runtime HUD"));
UIDocumentScreenHost host = {};
UIScreenPlayer player(host);
2026-04-05 05:44:07 +08:00
ASSERT_TRUE(player.Load(BuildScreenAsset(viewFile.Path(), "runtime.main_menu")));
2026-04-05 05:44:07 +08:00
const auto& frame = player.Update(BuildInputState());
EXPECT_TRUE(frame.stats.documentLoaded);
EXPECT_EQ(frame.stats.nodeCount, 7u);
EXPECT_EQ(frame.stats.drawListCount, frame.drawData.GetDrawListCount());
EXPECT_EQ(frame.stats.commandCount, frame.drawData.GetTotalCommandCount());
EXPECT_GE(frame.stats.textCommandCount, 5u);
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Runtime HUD"));
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Ready for play"));
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Start"));
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Options"));
EXPECT_EQ(player.GetPresentedFrameCount(), 1u);
}
2026-04-05 05:44:07 +08:00
TEST(UIRuntimeTest, UISystemForwardsActiveScreenToPlayer) {
TempFileScope baseView("xcui_runtime_base", ".xcui", BuildViewMarkup("Base Screen"));
TempFileScope overlayView("xcui_runtime_overlay", ".xcui", BuildViewMarkup("Overlay Screen", "Modal Dialog"));
2026-04-05 05:44:07 +08:00
UIDocumentScreenHost host = {};
UISystem system(host);
2026-04-05 05:44:07 +08:00
const auto baseLayer = system.PushScreen(
BuildScreenAsset(baseView.Path(), "runtime.base"));
ASSERT_NE(baseLayer, 0u);
2026-04-05 05:44:07 +08:00
XCEngine::UI::Runtime::UIScreenLayerOptions overlayOptions = {};
overlayOptions.debugName = "overlay";
overlayOptions.blocksLayersBelow = true;
const auto overlayLayer = system.PushScreen(
BuildScreenAsset(overlayView.Path(), "runtime.overlay"),
overlayOptions);
ASSERT_NE(overlayLayer, 0u);
2026-04-05 05:44:07 +08:00
const auto& frame = system.Update(BuildInputState(3u));
EXPECT_EQ(frame.presentedLayerCount, 1u);
EXPECT_EQ(frame.skippedLayerCount, 1u);
2026-04-05 05:44:07 +08:00
EXPECT_EQ(frame.layers.size(), 1u);
EXPECT_EQ(frame.layers.front().layerId, overlayLayer);
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Overlay Screen"));
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Modal Dialog"));
EXPECT_FALSE(DrawDataContainsText(frame.drawData, "Base Screen"));
}