Add XCUI expansion state and coverage tests

This commit is contained in:
2026-04-05 07:29:27 +08:00
parent 646e5855ce
commit 511e94fd30
18 changed files with 1213 additions and 53 deletions

View File

@@ -131,6 +131,18 @@ bool DrawDataContainsText(
return false;
}
const XCEngine::UI::Runtime::UISystemPresentedLayer* FindPresentedLayerById(
const XCEngine::UI::Runtime::UISystemFrameResult& frame,
XCEngine::UI::Runtime::UIScreenLayerId layerId) {
for (const XCEngine::UI::Runtime::UISystemPresentedLayer& layer : frame.layers) {
if (layer.layerId == layerId) {
return &layer;
}
}
return nullptr;
}
class RecordingScriptRuntime : public IScriptRuntime {
public:
explicit RecordingScriptRuntime(std::vector<std::string>* events)
@@ -430,4 +442,146 @@ TEST_F(SceneRuntimeTest, StopClearsUiRuntimeState) {
EXPECT_TRUE(runtime.GetLastUIFrame().layers.empty());
}
TEST_F(SceneRuntimeTest, LayeredSceneUiRoutesInputOnlyToTopInteractivePresentedLayer) {
Scene* runtimeScene = CreateScene("RuntimeScene");
runtime.Start(runtimeScene);
runtime.SetUIViewportRect(XCEngine::UI::UIRect(0.0f, 0.0f, 1280.0f, 720.0f));
runtime.SetUIFocused(true);
TempFileScope gameplayView("xcui_scene_runtime_gameplay", ".xcui", BuildViewMarkup("Gameplay Layer"));
TempFileScope overlayView("xcui_scene_runtime_overlay", ".xcui", BuildViewMarkup("Overlay Layer"));
XCEngine::UI::Runtime::UIScreenLayerOptions gameplayOptions = {};
gameplayOptions.debugName = "gameplay";
gameplayOptions.acceptsInput = true;
gameplayOptions.blocksLayersBelow = false;
XCEngine::UI::Runtime::UIScreenLayerOptions overlayOptions = {};
overlayOptions.debugName = "overlay";
overlayOptions.acceptsInput = true;
overlayOptions.blocksLayersBelow = false;
const auto gameplayLayerId = runtime.GetUIScreenStackController().PushScreen(
BuildScreenAsset(gameplayView.Path(), "runtime.gameplay"),
gameplayOptions);
const auto overlayLayerId = runtime.GetUIScreenStackController().PushScreen(
BuildScreenAsset(overlayView.Path(), "runtime.overlay"),
overlayOptions);
ASSERT_NE(gameplayLayerId, 0u);
ASSERT_NE(overlayLayerId, 0u);
XCEngine::UI::UIInputEvent textEvent = {};
textEvent.type = XCEngine::UI::UIInputEventType::Character;
textEvent.character = 'I';
runtime.QueueUIInputEvent(textEvent);
runtime.Update(0.016f);
const auto& frame = runtime.GetLastUIFrame();
ASSERT_EQ(frame.presentedLayerCount, 2u);
ASSERT_EQ(frame.skippedLayerCount, 0u);
ASSERT_EQ(frame.layers.size(), 2u);
const auto* gameplayLayer = FindPresentedLayerById(frame, gameplayLayerId);
const auto* overlayLayer = FindPresentedLayerById(frame, overlayLayerId);
ASSERT_NE(gameplayLayer, nullptr);
ASSERT_NE(overlayLayer, nullptr);
EXPECT_EQ(gameplayLayer->stats.inputEventCount, 0u);
EXPECT_EQ(overlayLayer->stats.inputEventCount, 1u);
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Gameplay Layer"));
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Overlay Layer"));
}
TEST_F(SceneRuntimeTest, BlockingLayerSkipsLowerLayersAndOwnsQueuedInput) {
Scene* runtimeScene = CreateScene("RuntimeScene");
runtime.Start(runtimeScene);
runtime.SetUIViewportRect(XCEngine::UI::UIRect(0.0f, 0.0f, 1280.0f, 720.0f));
runtime.SetUIFocused(true);
TempFileScope gameplayView("xcui_scene_runtime_blocked_gameplay", ".xcui", BuildViewMarkup("Blocked Gameplay"));
TempFileScope modalView("xcui_scene_runtime_modal", ".xcui", BuildViewMarkup("Pause Modal"));
XCEngine::UI::Runtime::UIScreenLayerOptions gameplayOptions = {};
gameplayOptions.debugName = "gameplay";
gameplayOptions.acceptsInput = true;
gameplayOptions.blocksLayersBelow = false;
const auto gameplayLayerId = runtime.GetUIScreenStackController().PushScreen(
BuildScreenAsset(gameplayView.Path(), "runtime.blocked.gameplay"),
gameplayOptions);
const auto modalLayerId = runtime.GetUIScreenStackController().PushModal(
BuildScreenAsset(modalView.Path(), "runtime.pause.modal"),
"pause-modal");
ASSERT_NE(gameplayLayerId, 0u);
ASSERT_NE(modalLayerId, 0u);
XCEngine::UI::UIInputEvent textEvent = {};
textEvent.type = XCEngine::UI::UIInputEventType::Character;
textEvent.character = 'P';
runtime.QueueUIInputEvent(textEvent);
runtime.Update(0.016f);
const auto& frame = runtime.GetLastUIFrame();
ASSERT_EQ(frame.presentedLayerCount, 1u);
ASSERT_EQ(frame.skippedLayerCount, 1u);
ASSERT_EQ(frame.layers.size(), 1u);
const auto* modalLayer = FindPresentedLayerById(frame, modalLayerId);
ASSERT_NE(modalLayer, nullptr);
EXPECT_EQ(modalLayer->stats.inputEventCount, 1u);
EXPECT_EQ(FindPresentedLayerById(frame, gameplayLayerId), nullptr);
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Pause Modal"));
EXPECT_FALSE(DrawDataContainsText(frame.drawData, "Blocked Gameplay"));
}
TEST_F(SceneRuntimeTest, HiddenTopLayerDoesNotStealInputFromVisibleUnderlyingLayer) {
Scene* runtimeScene = CreateScene("RuntimeScene");
runtime.Start(runtimeScene);
runtime.SetUIViewportRect(XCEngine::UI::UIRect(0.0f, 0.0f, 1280.0f, 720.0f));
runtime.SetUIFocused(true);
TempFileScope gameplayView("xcui_scene_runtime_visible_gameplay", ".xcui", BuildViewMarkup("Visible Gameplay"));
TempFileScope hiddenOverlayView("xcui_scene_runtime_hidden_overlay", ".xcui", BuildViewMarkup("Hidden Overlay"));
XCEngine::UI::Runtime::UIScreenLayerOptions gameplayOptions = {};
gameplayOptions.debugName = "gameplay";
gameplayOptions.acceptsInput = true;
gameplayOptions.blocksLayersBelow = false;
XCEngine::UI::Runtime::UIScreenLayerOptions hiddenOverlayOptions = {};
hiddenOverlayOptions.debugName = "hidden-overlay";
hiddenOverlayOptions.visible = false;
hiddenOverlayOptions.acceptsInput = true;
hiddenOverlayOptions.blocksLayersBelow = false;
const auto gameplayLayerId = runtime.GetUIScreenStackController().PushScreen(
BuildScreenAsset(gameplayView.Path(), "runtime.visible.gameplay"),
gameplayOptions);
const auto hiddenOverlayLayerId = runtime.GetUIScreenStackController().PushScreen(
BuildScreenAsset(hiddenOverlayView.Path(), "runtime.hidden.overlay"),
hiddenOverlayOptions);
ASSERT_NE(gameplayLayerId, 0u);
ASSERT_NE(hiddenOverlayLayerId, 0u);
XCEngine::UI::UIInputEvent textEvent = {};
textEvent.type = XCEngine::UI::UIInputEventType::Character;
textEvent.character = 'W';
runtime.QueueUIInputEvent(textEvent);
runtime.Update(0.016f);
const auto& frame = runtime.GetLastUIFrame();
ASSERT_EQ(frame.presentedLayerCount, 1u);
ASSERT_EQ(frame.skippedLayerCount, 1u);
ASSERT_EQ(frame.layers.size(), 1u);
const auto* gameplayLayer = FindPresentedLayerById(frame, gameplayLayerId);
ASSERT_NE(gameplayLayer, nullptr);
EXPECT_EQ(gameplayLayer->stats.inputEventCount, 1u);
EXPECT_EQ(FindPresentedLayerById(frame, hiddenOverlayLayerId), nullptr);
EXPECT_TRUE(DrawDataContainsText(frame.drawData, "Visible Gameplay"));
EXPECT_FALSE(DrawDataContainsText(frame.drawData, "Hidden Overlay"));
}
} // namespace