feat(xcui): add tab strip and workspace compose foundations
This commit is contained in:
@@ -2,6 +2,8 @@ set(CORE_UI_TEST_SOURCES
|
||||
${CMAKE_SOURCE_DIR}/tests/UI/Core/unit/test_ui_shortcut_scope.cpp
|
||||
${CMAKE_SOURCE_DIR}/tests/UI/Core/unit/test_ui_splitter_layout.cpp
|
||||
${CMAKE_SOURCE_DIR}/tests/UI/Core/unit/test_ui_splitter_interaction.cpp
|
||||
${CMAKE_SOURCE_DIR}/tests/UI/Core/unit/test_ui_tab_strip_layout.cpp
|
||||
${CMAKE_SOURCE_DIR}/tests/UI/Core/unit/test_ui_tab_strip_model.cpp
|
||||
# Migration bridge: legacy XCUI unit coverage still lives under tests/Core/UI
|
||||
# until it is moved into tests/UI/Core/unit without changing behavior.
|
||||
${CMAKE_SOURCE_DIR}/tests/Core/UI/test_ui_core.cpp
|
||||
|
||||
72
tests/UI/Core/unit/test_ui_tab_strip_layout.cpp
Normal file
72
tests/UI/Core/unit/test_ui_tab_strip_layout.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/UI/Layout/UITabStripLayout.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::UISize;
|
||||
using XCEngine::UI::Layout::ArrangeUITabStrip;
|
||||
using XCEngine::UI::Layout::MeasureUITabStrip;
|
||||
using XCEngine::UI::Layout::MeasureUITabStripHeaderWidth;
|
||||
using XCEngine::UI::Layout::UITabStripMeasureItem;
|
||||
using XCEngine::UI::Layout::UITabStripMetrics;
|
||||
|
||||
void ExpectRect(
|
||||
const UIRect& rect,
|
||||
float x,
|
||||
float y,
|
||||
float width,
|
||||
float height) {
|
||||
EXPECT_FLOAT_EQ(rect.x, x);
|
||||
EXPECT_FLOAT_EQ(rect.y, y);
|
||||
EXPECT_FLOAT_EQ(rect.width, width);
|
||||
EXPECT_FLOAT_EQ(rect.height, height);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(UITabStripLayoutTest, MeasureUsesTallestContentAndWidestHeaderBudget) {
|
||||
const UITabStripMetrics metrics = { 32.0f, 80.0f, 10.0f, 2.0f };
|
||||
const auto measured = MeasureUITabStrip(
|
||||
{
|
||||
UITabStripMeasureItem{ 36.0f, UISize(220.0f, 140.0f), UISize(120.0f, 80.0f) },
|
||||
UITabStripMeasureItem{ 96.0f, UISize(180.0f, 200.0f), UISize(160.0f, 90.0f) }
|
||||
},
|
||||
metrics);
|
||||
|
||||
const float desiredHeaderWidth =
|
||||
MeasureUITabStripHeaderWidth(36.0f, metrics) +
|
||||
MeasureUITabStripHeaderWidth(96.0f, metrics) +
|
||||
metrics.tabGap;
|
||||
const float minimumHeaderWidth = metrics.tabMinWidth * 2.0f + metrics.tabGap;
|
||||
|
||||
EXPECT_FLOAT_EQ(measured.desiredSize.width, 220.0f);
|
||||
EXPECT_FLOAT_EQ(measured.desiredSize.height, metrics.headerHeight + 200.0f);
|
||||
EXPECT_FLOAT_EQ(measured.minimumSize.width, minimumHeaderWidth);
|
||||
EXPECT_FLOAT_EQ(measured.minimumSize.height, metrics.headerHeight + 90.0f);
|
||||
}
|
||||
|
||||
TEST(UITabStripLayoutTest, MeasureWithoutItemsReturnsZeroSize) {
|
||||
const auto measured = MeasureUITabStrip({}, UITabStripMetrics{});
|
||||
|
||||
EXPECT_FLOAT_EQ(measured.desiredSize.width, 0.0f);
|
||||
EXPECT_FLOAT_EQ(measured.desiredSize.height, 0.0f);
|
||||
EXPECT_FLOAT_EQ(measured.minimumSize.width, 0.0f);
|
||||
EXPECT_FLOAT_EQ(measured.minimumSize.height, 0.0f);
|
||||
}
|
||||
|
||||
TEST(UITabStripLayoutTest, ArrangeScalesHeadersToAvailableWidthAndReservesContentArea) {
|
||||
const UITabStripMetrics metrics = { 30.0f, 72.0f, 12.0f, 4.0f };
|
||||
const auto arranged = ArrangeUITabStrip(
|
||||
UIRect(10.0f, 20.0f, 180.0f, 120.0f),
|
||||
{ 120.0f, 100.0f },
|
||||
metrics);
|
||||
|
||||
ExpectRect(arranged.headerRect, 10.0f, 20.0f, 180.0f, 30.0f);
|
||||
ExpectRect(arranged.contentRect, 10.0f, 50.0f, 180.0f, 90.0f);
|
||||
ASSERT_EQ(arranged.tabHeaderRects.size(), 2u);
|
||||
EXPECT_NEAR(arranged.tabHeaderRects[0].width + arranged.tabHeaderRects[1].width, 176.0f, 0.001f);
|
||||
ExpectRect(arranged.tabHeaderRects[0], 10.0f, 20.0f, 96.0f, 30.0f);
|
||||
ExpectRect(arranged.tabHeaderRects[1], 110.0f, 20.0f, 80.0f, 30.0f);
|
||||
}
|
||||
53
tests/UI/Core/unit/test_ui_tab_strip_model.cpp
Normal file
53
tests/UI/Core/unit/test_ui_tab_strip_model.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/UI/Widgets/UITabStripModel.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::UI::Widgets::UITabStripModel;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(UITabStripModelTest, SetItemCountInitializesAndClampsSelection) {
|
||||
UITabStripModel model = {};
|
||||
|
||||
EXPECT_TRUE(model.SetItemCount(3u));
|
||||
EXPECT_TRUE(model.HasSelection());
|
||||
EXPECT_EQ(model.GetSelectedIndex(), 0u);
|
||||
|
||||
EXPECT_TRUE(model.SetSelectedIndex(2u));
|
||||
EXPECT_EQ(model.GetSelectedIndex(), 2u);
|
||||
|
||||
EXPECT_TRUE(model.SetItemCount(2u));
|
||||
EXPECT_EQ(model.GetSelectedIndex(), 1u);
|
||||
}
|
||||
|
||||
TEST(UITabStripModelTest, NextPreviousFirstAndLastStayWithinBounds) {
|
||||
UITabStripModel model = {};
|
||||
ASSERT_TRUE(model.SetItemCount(3u));
|
||||
|
||||
EXPECT_TRUE(model.SelectLast());
|
||||
EXPECT_EQ(model.GetSelectedIndex(), 2u);
|
||||
EXPECT_FALSE(model.SelectNext());
|
||||
EXPECT_EQ(model.GetSelectedIndex(), 2u);
|
||||
|
||||
EXPECT_TRUE(model.SelectPrevious());
|
||||
EXPECT_EQ(model.GetSelectedIndex(), 1u);
|
||||
EXPECT_TRUE(model.SelectFirst());
|
||||
EXPECT_EQ(model.GetSelectedIndex(), 0u);
|
||||
EXPECT_FALSE(model.SelectPrevious());
|
||||
EXPECT_EQ(model.GetSelectedIndex(), 0u);
|
||||
}
|
||||
|
||||
TEST(UITabStripModelTest, RejectsOutOfRangeSelectionAndClearsWhenEmpty) {
|
||||
UITabStripModel model = {};
|
||||
ASSERT_TRUE(model.SetItemCount(2u));
|
||||
|
||||
EXPECT_FALSE(model.SetSelectedIndex(3u));
|
||||
EXPECT_EQ(model.GetSelectedIndex(), 0u);
|
||||
|
||||
EXPECT_TRUE(model.SetItemCount(0u));
|
||||
EXPECT_FALSE(model.HasSelection());
|
||||
EXPECT_EQ(model.GetSelectedIndex(), UITabStripModel::InvalidIndex);
|
||||
EXPECT_FALSE(model.SetSelectedIndex(0u));
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
add_subdirectory(splitter_resize)
|
||||
add_subdirectory(tab_strip_selection)
|
||||
add_subdirectory(workspace_compose)
|
||||
|
||||
add_custom_target(editor_ui_layout_integration_tests
|
||||
DEPENDS
|
||||
editor_ui_layout_splitter_resize_validation
|
||||
editor_ui_layout_tab_strip_selection_validation
|
||||
editor_ui_layout_workspace_compose_validation
|
||||
)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
set(EDITOR_UI_LAYOUT_TAB_STRIP_SELECTION_RESOURCES
|
||||
View.xcui
|
||||
${CMAKE_SOURCE_DIR}/tests/UI/Editor/integration/shared/themes/editor_validation.xctheme
|
||||
)
|
||||
|
||||
add_executable(editor_ui_layout_tab_strip_selection_validation WIN32
|
||||
main.cpp
|
||||
${EDITOR_UI_LAYOUT_TAB_STRIP_SELECTION_RESOURCES}
|
||||
)
|
||||
|
||||
target_include_directories(editor_ui_layout_tab_strip_selection_validation PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/tests/UI/Editor/integration/shared/src
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
)
|
||||
|
||||
target_compile_definitions(editor_ui_layout_tab_strip_selection_validation PRIVATE
|
||||
UNICODE
|
||||
_UNICODE
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(editor_ui_layout_tab_strip_selection_validation PRIVATE /utf-8 /FS)
|
||||
set_property(TARGET editor_ui_layout_tab_strip_selection_validation PROPERTY
|
||||
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
endif()
|
||||
|
||||
target_link_libraries(editor_ui_layout_tab_strip_selection_validation PRIVATE
|
||||
editor_ui_integration_host
|
||||
)
|
||||
|
||||
set_target_properties(editor_ui_layout_tab_strip_selection_validation PROPERTIES
|
||||
OUTPUT_NAME "XCUIEditorLayoutTabStripSelectionValidation"
|
||||
)
|
||||
|
||||
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES View.xcui)
|
||||
@@ -0,0 +1,46 @@
|
||||
<View
|
||||
name="EditorTabStripSelectionValidation"
|
||||
theme="../../shared/themes/editor_validation.xctheme">
|
||||
<Column width="fill" height="fill" padding="20" gap="12">
|
||||
<Card
|
||||
title="功能:TabStrip 选择切换"
|
||||
subtitle="只验证 tab 头部点击、键盘导航,以及只渲染 selected tab 内容"
|
||||
tone="accent"
|
||||
height="156">
|
||||
<Column gap="6">
|
||||
<Text text="1. 点击 Scene / Console / Inspector 任一 tab:下方内容区应立即切换,旧内容不应继续显示。" />
|
||||
<Text text="2. 先点击一个 tab 让它获得 focus,再按 Left / Right / Home / End:selected tab 应变化。" />
|
||||
<Text text="3. 右下角 Result 正常应显示 Tab selected 或 Tab navigated;Focused 应落在当前 tab。" />
|
||||
<Text text="4. 这个场景只检查 TabStrip 基础能力,不检查 editor 业务面板。" />
|
||||
</Column>
|
||||
</Card>
|
||||
|
||||
<TabStrip
|
||||
id="editor-workspace-tabs"
|
||||
tabHeaderHeight="34"
|
||||
tabMinWidth="96"
|
||||
height="fill">
|
||||
<Tab id="tab-scene" label="Scene" selected="true">
|
||||
<Card title="Scene Tab Content" subtitle="selected = Scene" height="fill">
|
||||
<Column gap="8">
|
||||
<Text text="这里应该只显示 Scene 的内容占位。" />
|
||||
</Column>
|
||||
</Card>
|
||||
</Tab>
|
||||
<Tab id="tab-console" label="Console">
|
||||
<Card title="Console Tab Content" subtitle="selected = Console" height="fill">
|
||||
<Column gap="8">
|
||||
<Text text="切换到 Console 后,Scene 内容应消失。" />
|
||||
</Column>
|
||||
</Card>
|
||||
</Tab>
|
||||
<Tab id="tab-inspector" label="Inspector">
|
||||
<Card title="Inspector Tab Content" subtitle="selected = Inspector" height="fill">
|
||||
<Column gap="8">
|
||||
<Text text="按 Home / End 时,也应只保留当前 selected 内容。" />
|
||||
</Column>
|
||||
</Card>
|
||||
</Tab>
|
||||
</TabStrip>
|
||||
</Column>
|
||||
</View>
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "Application.h"
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) {
|
||||
return XCEngine::Tests::EditorUI::RunEditorUIValidationApp(
|
||||
hInstance,
|
||||
nCmdShow,
|
||||
"editor.layout.tab_strip_selection");
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
set(EDITOR_UI_LAYOUT_WORKSPACE_COMPOSE_RESOURCES
|
||||
View.xcui
|
||||
${CMAKE_SOURCE_DIR}/tests/UI/Editor/integration/shared/themes/editor_validation.xctheme
|
||||
)
|
||||
|
||||
add_executable(editor_ui_layout_workspace_compose_validation WIN32
|
||||
main.cpp
|
||||
${EDITOR_UI_LAYOUT_WORKSPACE_COMPOSE_RESOURCES}
|
||||
)
|
||||
|
||||
target_include_directories(editor_ui_layout_workspace_compose_validation PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/tests/UI/Editor/integration/shared/src
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
)
|
||||
|
||||
target_compile_definitions(editor_ui_layout_workspace_compose_validation PRIVATE
|
||||
UNICODE
|
||||
_UNICODE
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(editor_ui_layout_workspace_compose_validation PRIVATE /utf-8 /FS)
|
||||
set_property(TARGET editor_ui_layout_workspace_compose_validation PROPERTY
|
||||
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
endif()
|
||||
|
||||
target_link_libraries(editor_ui_layout_workspace_compose_validation PRIVATE
|
||||
editor_ui_integration_host
|
||||
)
|
||||
|
||||
set_target_properties(editor_ui_layout_workspace_compose_validation PROPERTIES
|
||||
OUTPUT_NAME "XCUIEditorLayoutWorkspaceComposeValidation"
|
||||
)
|
||||
|
||||
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES View.xcui)
|
||||
@@ -0,0 +1,94 @@
|
||||
<View
|
||||
name="EditorWorkspaceComposeValidation"
|
||||
theme="../../shared/themes/editor_validation.xctheme">
|
||||
<Column width="fill" height="fill" padding="20" gap="12">
|
||||
<Card
|
||||
title="功能:Workspace compose"
|
||||
subtitle="只检查 editor 工作区的 split + tab + placeholder 组合,不检查任何业务面板"
|
||||
tone="accent"
|
||||
height="156">
|
||||
<Column gap="6">
|
||||
<Text text="1. 先看布局:左、中、右、下四个区域应边界清晰,没有重叠、穿透或错位。" />
|
||||
<Text text="2. 拖拽 workspace-left-right 和 workspace-top-bottom:各区域尺寸应实时变化,并被最小尺寸 clamp 住。" />
|
||||
<Text text="3. 点击中间的 Document A / B / C:只应显示当前 selected tab 的 placeholder 内容。" />
|
||||
<Text text="4. 这个场景只验证工作区组合基础,不代表 Hierarchy / Inspector / Console 已开始实现。" />
|
||||
</Column>
|
||||
</Card>
|
||||
|
||||
<Splitter
|
||||
id="workspace-top-bottom"
|
||||
axis="vertical"
|
||||
splitRatio="0.76"
|
||||
splitterSize="10"
|
||||
splitterHitSize="18"
|
||||
primaryMin="320"
|
||||
secondaryMin="120"
|
||||
height="fill">
|
||||
<Splitter
|
||||
id="workspace-left-right"
|
||||
axis="horizontal"
|
||||
splitRatio="0.24"
|
||||
splitterSize="10"
|
||||
splitterHitSize="18"
|
||||
primaryMin="160"
|
||||
secondaryMin="420"
|
||||
height="fill">
|
||||
<Card id="workspace-left-slot" title="Navigation Slot" subtitle="placeholder panel host" height="fill">
|
||||
<Column gap="8">
|
||||
<Text text="这里是左侧 placeholder slot,只检查 pane compose。" />
|
||||
</Column>
|
||||
</Card>
|
||||
|
||||
<Splitter
|
||||
id="workspace-center-right"
|
||||
axis="horizontal"
|
||||
splitRatio="0.70"
|
||||
splitterSize="10"
|
||||
splitterHitSize="18"
|
||||
primaryMin="260"
|
||||
secondaryMin="180"
|
||||
height="fill">
|
||||
<TabStrip
|
||||
id="workspace-document-tabs"
|
||||
tabHeaderHeight="34"
|
||||
tabMinWidth="112"
|
||||
height="fill">
|
||||
<Tab id="tab-document-a" label="Document A" selected="true">
|
||||
<Card title="Primary Document Slot" subtitle="selected = Document A" height="fill">
|
||||
<Column gap="8">
|
||||
<Text text="这里应只显示 Document A 的 placeholder 内容。" />
|
||||
</Column>
|
||||
</Card>
|
||||
</Tab>
|
||||
<Tab id="tab-document-b" label="Document B">
|
||||
<Card title="Secondary Document Slot" subtitle="selected = Document B" height="fill">
|
||||
<Column gap="8">
|
||||
<Text text="切换到 Document B 后,A 的内容应消失。" />
|
||||
</Column>
|
||||
</Card>
|
||||
</Tab>
|
||||
<Tab id="tab-document-c" label="Document C">
|
||||
<Card title="Tertiary Document Slot" subtitle="selected = Document C" height="fill">
|
||||
<Column gap="8">
|
||||
<Text text="这里只是第三个 placeholder,不代表真实面板业务。" />
|
||||
</Column>
|
||||
</Card>
|
||||
</Tab>
|
||||
</TabStrip>
|
||||
|
||||
<Card id="workspace-right-slot" title="Details Slot" subtitle="placeholder panel host" height="fill">
|
||||
<Column gap="8">
|
||||
<Text text="这里是右侧 placeholder slot,只检查嵌套 split 稳定性。" />
|
||||
</Column>
|
||||
</Card>
|
||||
</Splitter>
|
||||
</Splitter>
|
||||
|
||||
<Card id="workspace-bottom-slot" title="Output Slot" subtitle="placeholder panel host" height="fill">
|
||||
<Column gap="8">
|
||||
<Text text="这里是底部 placeholder slot,用来检查上下 split compose。" />
|
||||
</Column>
|
||||
</Card>
|
||||
</Splitter>
|
||||
</Column>
|
||||
</View>
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "Application.h"
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow) {
|
||||
return XCEngine::Tests::EditorUI::RunEditorUIValidationApp(
|
||||
hInstance,
|
||||
nCmdShow,
|
||||
"editor.layout.workspace_compose");
|
||||
}
|
||||
@@ -24,8 +24,8 @@ fs::path RepoRelative(const char* relativePath) {
|
||||
return (RepoRootPath() / relativePath).lexically_normal();
|
||||
}
|
||||
|
||||
const std::array<EditorValidationScenario, 4>& GetEditorValidationScenarios() {
|
||||
static const std::array<EditorValidationScenario, 4> scenarios = { {
|
||||
const std::array<EditorValidationScenario, 6>& GetEditorValidationScenarios() {
|
||||
static const std::array<EditorValidationScenario, 6> scenarios = { {
|
||||
{
|
||||
"editor.input.keyboard_focus",
|
||||
UIValidationDomain::Editor,
|
||||
@@ -61,6 +61,24 @@ const std::array<EditorValidationScenario, 4>& GetEditorValidationScenarios() {
|
||||
RepoRelative("tests/UI/Editor/integration/layout/splitter_resize/View.xcui"),
|
||||
RepoRelative("tests/UI/Editor/integration/shared/themes/editor_validation.xctheme"),
|
||||
RepoRelative("tests/UI/Editor/integration/layout/splitter_resize/captures")
|
||||
},
|
||||
{
|
||||
"editor.layout.tab_strip_selection",
|
||||
UIValidationDomain::Editor,
|
||||
"layout",
|
||||
"Editor Layout | TabStrip Selection",
|
||||
RepoRelative("tests/UI/Editor/integration/layout/tab_strip_selection/View.xcui"),
|
||||
RepoRelative("tests/UI/Editor/integration/shared/themes/editor_validation.xctheme"),
|
||||
RepoRelative("tests/UI/Editor/integration/layout/tab_strip_selection/captures")
|
||||
},
|
||||
{
|
||||
"editor.layout.workspace_compose",
|
||||
UIValidationDomain::Editor,
|
||||
"layout",
|
||||
"Editor Layout | Workspace Compose",
|
||||
RepoRelative("tests/UI/Editor/integration/layout/workspace_compose/View.xcui"),
|
||||
RepoRelative("tests/UI/Editor/integration/shared/themes/editor_validation.xctheme"),
|
||||
RepoRelative("tests/UI/Editor/integration/layout/workspace_compose/captures")
|
||||
}
|
||||
} };
|
||||
return scenarios;
|
||||
|
||||
@@ -2,6 +2,7 @@ set(EDITOR_UI_UNIT_TEST_SOURCES
|
||||
test_input_modifier_tracker.cpp
|
||||
test_editor_validation_registry.cpp
|
||||
test_structured_editor_shell.cpp
|
||||
test_ui_editor_workspace_model.cpp
|
||||
# Migration bridge: editor-facing XCUI primitive tests still reuse the
|
||||
# legacy source location until they are relocated under tests/UI/Editor/unit.
|
||||
${CMAKE_SOURCE_DIR}/tests/Core/UI/test_ui_editor_collection_primitives.cpp
|
||||
|
||||
@@ -17,19 +17,27 @@ TEST(EditorValidationRegistryTest, KnownEditorValidationScenariosResolveToExisti
|
||||
const auto* keyboardScenario = FindEditorValidationScenario("editor.input.keyboard_focus");
|
||||
const auto* shortcutScenario = FindEditorValidationScenario("editor.input.shortcut_scope");
|
||||
const auto* splitterScenario = FindEditorValidationScenario("editor.layout.splitter_resize");
|
||||
const auto* tabStripScenario = FindEditorValidationScenario("editor.layout.tab_strip_selection");
|
||||
const auto* workspaceScenario = FindEditorValidationScenario("editor.layout.workspace_compose");
|
||||
|
||||
ASSERT_NE(pointerScenario, nullptr);
|
||||
ASSERT_NE(keyboardScenario, nullptr);
|
||||
ASSERT_NE(shortcutScenario, nullptr);
|
||||
ASSERT_NE(splitterScenario, nullptr);
|
||||
ASSERT_NE(tabStripScenario, nullptr);
|
||||
ASSERT_NE(workspaceScenario, nullptr);
|
||||
EXPECT_EQ(pointerScenario->domain, UIValidationDomain::Editor);
|
||||
EXPECT_EQ(keyboardScenario->domain, UIValidationDomain::Editor);
|
||||
EXPECT_EQ(shortcutScenario->domain, UIValidationDomain::Editor);
|
||||
EXPECT_EQ(splitterScenario->domain, UIValidationDomain::Editor);
|
||||
EXPECT_EQ(tabStripScenario->domain, UIValidationDomain::Editor);
|
||||
EXPECT_EQ(workspaceScenario->domain, UIValidationDomain::Editor);
|
||||
EXPECT_EQ(pointerScenario->categoryId, "input");
|
||||
EXPECT_EQ(keyboardScenario->categoryId, "input");
|
||||
EXPECT_EQ(shortcutScenario->categoryId, "input");
|
||||
EXPECT_EQ(splitterScenario->categoryId, "layout");
|
||||
EXPECT_EQ(tabStripScenario->categoryId, "layout");
|
||||
EXPECT_EQ(workspaceScenario->categoryId, "layout");
|
||||
EXPECT_TRUE(std::filesystem::exists(pointerScenario->documentPath));
|
||||
EXPECT_TRUE(std::filesystem::exists(pointerScenario->themePath));
|
||||
EXPECT_TRUE(std::filesystem::exists(keyboardScenario->documentPath));
|
||||
@@ -38,6 +46,10 @@ TEST(EditorValidationRegistryTest, KnownEditorValidationScenariosResolveToExisti
|
||||
EXPECT_TRUE(std::filesystem::exists(shortcutScenario->themePath));
|
||||
EXPECT_TRUE(std::filesystem::exists(splitterScenario->documentPath));
|
||||
EXPECT_TRUE(std::filesystem::exists(splitterScenario->themePath));
|
||||
EXPECT_TRUE(std::filesystem::exists(tabStripScenario->documentPath));
|
||||
EXPECT_TRUE(std::filesystem::exists(tabStripScenario->themePath));
|
||||
EXPECT_TRUE(std::filesystem::exists(workspaceScenario->documentPath));
|
||||
EXPECT_TRUE(std::filesystem::exists(workspaceScenario->themePath));
|
||||
}
|
||||
|
||||
TEST(EditorValidationRegistryTest, DefaultScenarioPointsToKeyboardFocusBatch) {
|
||||
|
||||
143
tests/UI/Editor/unit/test_ui_editor_workspace_model.cpp
Normal file
143
tests/UI/Editor/unit/test_ui_editor_workspace_model.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCNewEditor/Editor/UIEditorWorkspaceModel.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::NewEditor::BuildUIEditorWorkspacePanel;
|
||||
using XCEngine::NewEditor::BuildUIEditorWorkspaceSplit;
|
||||
using XCEngine::NewEditor::BuildUIEditorWorkspaceTabStack;
|
||||
using XCEngine::NewEditor::CollectUIEditorWorkspaceVisiblePanels;
|
||||
using XCEngine::NewEditor::ContainsUIEditorWorkspacePanel;
|
||||
using XCEngine::NewEditor::FindUIEditorWorkspaceActivePanel;
|
||||
using XCEngine::NewEditor::TryActivateUIEditorWorkspacePanel;
|
||||
using XCEngine::NewEditor::UIEditorWorkspaceModel;
|
||||
using XCEngine::NewEditor::UIEditorWorkspaceNodeKind;
|
||||
using XCEngine::NewEditor::UIEditorWorkspaceSplitAxis;
|
||||
using XCEngine::NewEditor::UIEditorWorkspaceValidationCode;
|
||||
using XCEngine::NewEditor::ValidateUIEditorWorkspace;
|
||||
|
||||
std::vector<std::string> CollectVisiblePanelIds(const UIEditorWorkspaceModel& workspace) {
|
||||
const auto panels = CollectUIEditorWorkspaceVisiblePanels(workspace);
|
||||
std::vector<std::string> ids = {};
|
||||
ids.reserve(panels.size());
|
||||
for (const auto& panel : panels) {
|
||||
ids.push_back(panel.panelId);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(UIEditorWorkspaceModelTest, ValidationRejectsSplitWithoutExactlyTwoChildren) {
|
||||
UIEditorWorkspaceModel workspace = {};
|
||||
workspace.root.kind = UIEditorWorkspaceNodeKind::Split;
|
||||
workspace.root.nodeId = "root-split";
|
||||
workspace.root.splitAxis = UIEditorWorkspaceSplitAxis::Horizontal;
|
||||
workspace.root.splitRatio = 0.5f;
|
||||
workspace.root.children.push_back(
|
||||
BuildUIEditorWorkspacePanel("panel-a-node", "panel-a", "Panel A", true));
|
||||
|
||||
const auto result = ValidateUIEditorWorkspace(workspace);
|
||||
EXPECT_EQ(result.code, UIEditorWorkspaceValidationCode::InvalidSplitChildCount);
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceModelTest, ValidationRejectsTabStackWithNestedSplitChild) {
|
||||
UIEditorWorkspaceModel workspace = {};
|
||||
workspace.root = BuildUIEditorWorkspaceTabStack(
|
||||
"root-tabs",
|
||||
{
|
||||
BuildUIEditorWorkspacePanel("panel-a-node", "panel-a", "Panel A", true),
|
||||
BuildUIEditorWorkspaceSplit(
|
||||
"nested-split",
|
||||
UIEditorWorkspaceSplitAxis::Horizontal,
|
||||
0.5f,
|
||||
BuildUIEditorWorkspacePanel("panel-b-node", "panel-b", "Panel B", true),
|
||||
BuildUIEditorWorkspacePanel("panel-c-node", "panel-c", "Panel C", true))
|
||||
},
|
||||
0u);
|
||||
|
||||
const auto result = ValidateUIEditorWorkspace(workspace);
|
||||
EXPECT_EQ(result.code, UIEditorWorkspaceValidationCode::NonPanelTabChild);
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceModelTest, VisiblePanelsOnlyIncludeSelectedTabsAcrossSplitTree) {
|
||||
UIEditorWorkspaceModel workspace = {};
|
||||
workspace.root = BuildUIEditorWorkspaceSplit(
|
||||
"root-split",
|
||||
UIEditorWorkspaceSplitAxis::Horizontal,
|
||||
0.68f,
|
||||
BuildUIEditorWorkspacePanel("left-panel-node", "left-panel", "Left Panel", true),
|
||||
BuildUIEditorWorkspaceSplit(
|
||||
"right-split",
|
||||
UIEditorWorkspaceSplitAxis::Vertical,
|
||||
0.74f,
|
||||
BuildUIEditorWorkspaceTabStack(
|
||||
"document-tabs",
|
||||
{
|
||||
BuildUIEditorWorkspacePanel("doc-a-node", "doc-a", "Document A", true),
|
||||
BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B", true)
|
||||
},
|
||||
1u),
|
||||
BuildUIEditorWorkspacePanel("bottom-panel-node", "bottom-panel", "Bottom Panel", true)));
|
||||
workspace.activePanelId = "doc-b";
|
||||
|
||||
const auto validation = ValidateUIEditorWorkspace(workspace);
|
||||
ASSERT_TRUE(validation.IsValid()) << validation.message;
|
||||
|
||||
const auto visibleIds = CollectVisiblePanelIds(workspace);
|
||||
ASSERT_EQ(visibleIds.size(), 3u);
|
||||
EXPECT_EQ(visibleIds[0], "left-panel");
|
||||
EXPECT_EQ(visibleIds[1], "doc-b");
|
||||
EXPECT_EQ(visibleIds[2], "bottom-panel");
|
||||
|
||||
const auto* activePanel = FindUIEditorWorkspaceActivePanel(workspace);
|
||||
ASSERT_NE(activePanel, nullptr);
|
||||
EXPECT_EQ(activePanel->panelId, "doc-b");
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceModelTest, ActivatingHiddenPanelSelectsContainingTabAndUpdatesActivePanel) {
|
||||
UIEditorWorkspaceModel workspace = {};
|
||||
workspace.root = BuildUIEditorWorkspaceSplit(
|
||||
"root-split",
|
||||
UIEditorWorkspaceSplitAxis::Horizontal,
|
||||
0.62f,
|
||||
BuildUIEditorWorkspaceTabStack(
|
||||
"document-tabs",
|
||||
{
|
||||
BuildUIEditorWorkspacePanel("doc-a-node", "doc-a", "Document A", true),
|
||||
BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B", true),
|
||||
BuildUIEditorWorkspacePanel("doc-c-node", "doc-c", "Document C", true)
|
||||
},
|
||||
0u),
|
||||
BuildUIEditorWorkspacePanel("details-node", "details", "Details", true));
|
||||
|
||||
ASSERT_TRUE(ContainsUIEditorWorkspacePanel(workspace, "doc-b"));
|
||||
ASSERT_TRUE(TryActivateUIEditorWorkspacePanel(workspace, "doc-b"));
|
||||
EXPECT_EQ(workspace.activePanelId, "doc-b");
|
||||
ASSERT_EQ(workspace.root.children.front().selectedTabIndex, 1u);
|
||||
|
||||
const auto visibleIds = CollectVisiblePanelIds(workspace);
|
||||
ASSERT_EQ(visibleIds.size(), 2u);
|
||||
EXPECT_EQ(visibleIds[0], "doc-b");
|
||||
EXPECT_EQ(visibleIds[1], "details");
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceModelTest, ValidationRejectsActivePanelHiddenByCurrentTabSelection) {
|
||||
UIEditorWorkspaceModel workspace = {};
|
||||
workspace.root = BuildUIEditorWorkspaceTabStack(
|
||||
"document-tabs",
|
||||
{
|
||||
BuildUIEditorWorkspacePanel("doc-a-node", "doc-a", "Document A", true),
|
||||
BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B", true)
|
||||
},
|
||||
0u);
|
||||
workspace.activePanelId = "doc-b";
|
||||
|
||||
const auto result = ValidateUIEditorWorkspace(workspace);
|
||||
EXPECT_EQ(result.code, UIEditorWorkspaceValidationCode::InvalidActivePanelId);
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
set(RUNTIME_UI_TEST_SOURCES
|
||||
${CMAKE_SOURCE_DIR}/tests/UI/Runtime/unit/test_ui_runtime_shortcut_scope.cpp
|
||||
${CMAKE_SOURCE_DIR}/tests/UI/Runtime/unit/test_ui_runtime_splitter_validation.cpp
|
||||
${CMAKE_SOURCE_DIR}/tests/UI/Runtime/unit/test_ui_runtime_tab_strip.cpp
|
||||
${CMAKE_SOURCE_DIR}/tests/Core/UI/test_ui_runtime.cpp
|
||||
)
|
||||
|
||||
add_executable(runtime_ui_tests ${RUNTIME_UI_TEST_SOURCES})
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(runtime_ui_tests PRIVATE /FS)
|
||||
set_target_properties(runtime_ui_tests PROPERTIES
|
||||
LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib"
|
||||
)
|
||||
|
||||
245
tests/UI/Runtime/unit/test_ui_runtime_tab_strip.cpp
Normal file
245
tests/UI/Runtime/unit/test_ui_runtime_tab_strip.cpp
Normal file
@@ -0,0 +1,245 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/Input/InputTypes.h>
|
||||
#include <XCEngine/UI/Runtime/UIScreenDocumentHost.h>
|
||||
#include <XCEngine/UI/Runtime/UIScreenPlayer.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Input::KeyCode;
|
||||
using XCEngine::UI::UIPoint;
|
||||
using XCEngine::UI::UIRect;
|
||||
using XCEngine::UI::UIInputEvent;
|
||||
using XCEngine::UI::UIInputEventType;
|
||||
using XCEngine::UI::UIPointerButton;
|
||||
using XCEngine::UI::UIDrawCommand;
|
||||
using XCEngine::UI::UIDrawCommandType;
|
||||
using XCEngine::UI::UIDrawData;
|
||||
using XCEngine::UI::UIDrawList;
|
||||
using XCEngine::UI::Runtime::UIScreenAsset;
|
||||
using XCEngine::UI::Runtime::UIScreenFrameInput;
|
||||
using XCEngine::UI::Runtime::UIScreenPlayer;
|
||||
using XCEngine::UI::Runtime::UIDocumentScreenHost;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
~TempFileScope() {
|
||||
std::error_code ec;
|
||||
fs::remove(m_path, ec);
|
||||
}
|
||||
|
||||
const fs::path& Path() const {
|
||||
return m_path;
|
||||
}
|
||||
|
||||
private:
|
||||
fs::path m_path = {};
|
||||
};
|
||||
|
||||
UIScreenAsset BuildScreenAsset(const fs::path& viewPath, const char* screenId) {
|
||||
UIScreenAsset screen = {};
|
||||
screen.screenId = screenId;
|
||||
screen.documentPath = viewPath.string();
|
||||
return screen;
|
||||
}
|
||||
|
||||
UIScreenFrameInput BuildInputState(std::uint64_t frameIndex) {
|
||||
UIScreenFrameInput input = {};
|
||||
input.viewportRect = UIRect(0.0f, 0.0f, 960.0f, 640.0f);
|
||||
input.frameIndex = frameIndex;
|
||||
input.focused = true;
|
||||
return input;
|
||||
}
|
||||
|
||||
std::string BuildTabStripMarkup() {
|
||||
return
|
||||
"<View name=\"Runtime TabStrip Test\">\n"
|
||||
" <Column padding=\"18\" gap=\"10\">\n"
|
||||
" <TabStrip id=\"workspace-tabs\" tabHeaderHeight=\"34\" tabMinWidth=\"84\">\n"
|
||||
" <Tab id=\"tab-inspector\" label=\"Inspector\" selected=\"true\">\n"
|
||||
" <Card title=\"Inspector Content\">\n"
|
||||
" <Text text=\"Selected: Inspector\" />\n"
|
||||
" </Card>\n"
|
||||
" </Tab>\n"
|
||||
" <Tab id=\"tab-console\" label=\"Console\">\n"
|
||||
" <Card title=\"Console Content\">\n"
|
||||
" <Text text=\"Selected: Console\" />\n"
|
||||
" </Card>\n"
|
||||
" </Tab>\n"
|
||||
" <Tab id=\"tab-profiler\" label=\"Profiler\">\n"
|
||||
" <Card title=\"Profiler Content\">\n"
|
||||
" <Text text=\"Selected: Profiler\" />\n"
|
||||
" </Card>\n"
|
||||
" </Tab>\n"
|
||||
" </TabStrip>\n"
|
||||
" </Column>\n"
|
||||
"</View>\n";
|
||||
}
|
||||
|
||||
bool DrawDataContainsText(
|
||||
const UIDrawData& drawData,
|
||||
const std::string& text) {
|
||||
for (const UIDrawList& drawList : drawData.GetDrawLists()) {
|
||||
for (const UIDrawCommand& command : drawList.GetCommands()) {
|
||||
if (command.type == UIDrawCommandType::Text && command.text == text) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const UIDrawCommand* FindTextCommand(
|
||||
const UIDrawData& drawData,
|
||||
const std::string& text) {
|
||||
for (const UIDrawList& drawList : drawData.GetDrawLists()) {
|
||||
for (const UIDrawCommand& command : drawList.GetCommands()) {
|
||||
if (command.type == UIDrawCommandType::Text && command.text == text) {
|
||||
return &command;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UIInputEvent MakePointerButtonEvent(
|
||||
UIInputEventType type,
|
||||
const UIPoint& position) {
|
||||
UIInputEvent event = {};
|
||||
event.type = type;
|
||||
event.pointerButton = UIPointerButton::Left;
|
||||
event.position = position;
|
||||
return event;
|
||||
}
|
||||
|
||||
UIInputEvent MakeKeyDownEvent(KeyCode keyCode) {
|
||||
UIInputEvent event = {};
|
||||
event.type = UIInputEventType::KeyDown;
|
||||
event.keyCode = static_cast<std::int32_t>(keyCode);
|
||||
return event;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(UIRuntimeTabStripValidationTest, EmptyTabStripProducesExplicitFrameError) {
|
||||
TempFileScope viewFile(
|
||||
"xcui_runtime_invalid_tab_strip",
|
||||
".xcui",
|
||||
"<View name=\"Invalid TabStrip Test\">\n"
|
||||
" <Column padding=\"16\" gap=\"10\">\n"
|
||||
" <TabStrip id=\"broken-tabs\" />\n"
|
||||
" </Column>\n"
|
||||
"</View>\n");
|
||||
|
||||
UIDocumentScreenHost host = {};
|
||||
UIScreenPlayer player(host);
|
||||
|
||||
ASSERT_TRUE(player.Load(BuildScreenAsset(viewFile.Path(), "runtime.invalid_tab_strip")));
|
||||
|
||||
const auto& frame = player.Update(BuildInputState(1u));
|
||||
EXPECT_NE(frame.errorMessage.find("broken-tabs"), std::string::npos);
|
||||
EXPECT_NE(frame.errorMessage.find("at least 1 Tab child"), std::string::npos);
|
||||
}
|
||||
|
||||
TEST(UIRuntimeTabStripTest, PointerSelectingTabSwitchesVisibleContentAndPersists) {
|
||||
TempFileScope viewFile("xcui_runtime_tab_strip_pointer", ".xcui", BuildTabStripMarkup());
|
||||
UIDocumentScreenHost host = {};
|
||||
UIScreenPlayer player(host);
|
||||
|
||||
ASSERT_TRUE(player.Load(BuildScreenAsset(viewFile.Path(), "runtime.tab_strip.pointer")));
|
||||
|
||||
const auto& initialFrame = player.Update(BuildInputState(1u));
|
||||
EXPECT_TRUE(DrawDataContainsText(initialFrame.drawData, "Inspector Content"));
|
||||
EXPECT_FALSE(DrawDataContainsText(initialFrame.drawData, "Console Content"));
|
||||
const UIDrawCommand* consoleTab = FindTextCommand(initialFrame.drawData, "Console");
|
||||
ASSERT_NE(consoleTab, nullptr);
|
||||
|
||||
UIScreenFrameInput selectInput = BuildInputState(2u);
|
||||
selectInput.events.push_back(MakePointerButtonEvent(UIInputEventType::PointerButtonDown, consoleTab->position));
|
||||
selectInput.events.push_back(MakePointerButtonEvent(UIInputEventType::PointerButtonUp, consoleTab->position));
|
||||
const auto& selectedFrame = player.Update(selectInput);
|
||||
EXPECT_FALSE(DrawDataContainsText(selectedFrame.drawData, "Inspector Content"));
|
||||
EXPECT_TRUE(DrawDataContainsText(selectedFrame.drawData, "Console Content"));
|
||||
|
||||
const auto& debugAfterSelect = host.GetInputDebugSnapshot();
|
||||
EXPECT_EQ(debugAfterSelect.lastResult, "Tab selected");
|
||||
EXPECT_NE(debugAfterSelect.focusedStateKey.find("/workspace-tabs/tab-console"), std::string::npos);
|
||||
|
||||
const auto& persistedFrame = player.Update(BuildInputState(3u));
|
||||
EXPECT_TRUE(DrawDataContainsText(persistedFrame.drawData, "Console Content"));
|
||||
EXPECT_FALSE(DrawDataContainsText(persistedFrame.drawData, "Inspector Content"));
|
||||
}
|
||||
|
||||
TEST(UIRuntimeTabStripTest, KeyboardNavigationUpdatesSelectionAndFocus) {
|
||||
TempFileScope viewFile("xcui_runtime_tab_strip_keyboard", ".xcui", BuildTabStripMarkup());
|
||||
UIDocumentScreenHost host = {};
|
||||
UIScreenPlayer player(host);
|
||||
|
||||
ASSERT_TRUE(player.Load(BuildScreenAsset(viewFile.Path(), "runtime.tab_strip.keyboard")));
|
||||
|
||||
const auto& initialFrame = player.Update(BuildInputState(1u));
|
||||
const UIDrawCommand* inspectorTab = FindTextCommand(initialFrame.drawData, "Inspector");
|
||||
ASSERT_NE(inspectorTab, nullptr);
|
||||
|
||||
UIScreenFrameInput focusInput = BuildInputState(2u);
|
||||
focusInput.events.push_back(MakePointerButtonEvent(UIInputEventType::PointerButtonDown, inspectorTab->position));
|
||||
focusInput.events.push_back(MakePointerButtonEvent(UIInputEventType::PointerButtonUp, inspectorTab->position));
|
||||
player.Update(focusInput);
|
||||
|
||||
UIScreenFrameInput rightInput = BuildInputState(3u);
|
||||
rightInput.events.push_back(MakeKeyDownEvent(KeyCode::Right));
|
||||
const auto& consoleFrame = player.Update(rightInput);
|
||||
EXPECT_TRUE(DrawDataContainsText(consoleFrame.drawData, "Console Content"));
|
||||
EXPECT_FALSE(DrawDataContainsText(consoleFrame.drawData, "Inspector Content"));
|
||||
|
||||
const auto& afterRight = host.GetInputDebugSnapshot();
|
||||
EXPECT_EQ(afterRight.lastResult, "Tab navigated");
|
||||
EXPECT_NE(afterRight.focusedStateKey.find("/workspace-tabs/tab-console"), std::string::npos);
|
||||
|
||||
UIScreenFrameInput leftInput = BuildInputState(4u);
|
||||
leftInput.events.push_back(MakeKeyDownEvent(KeyCode::Left));
|
||||
const auto& inspectorFrameAfterLeft = player.Update(leftInput);
|
||||
EXPECT_TRUE(DrawDataContainsText(inspectorFrameAfterLeft.drawData, "Inspector Content"));
|
||||
EXPECT_FALSE(DrawDataContainsText(inspectorFrameAfterLeft.drawData, "Console Content"));
|
||||
|
||||
const auto& afterLeft = host.GetInputDebugSnapshot();
|
||||
EXPECT_EQ(afterLeft.lastResult, "Tab navigated");
|
||||
EXPECT_NE(afterLeft.focusedStateKey.find("/workspace-tabs/tab-inspector"), std::string::npos);
|
||||
|
||||
UIScreenFrameInput endInput = BuildInputState(5u);
|
||||
endInput.events.push_back(MakeKeyDownEvent(KeyCode::End));
|
||||
const auto& profilerFrame = player.Update(endInput);
|
||||
EXPECT_TRUE(DrawDataContainsText(profilerFrame.drawData, "Profiler Content"));
|
||||
EXPECT_FALSE(DrawDataContainsText(profilerFrame.drawData, "Inspector Content"));
|
||||
|
||||
const auto& afterEnd = host.GetInputDebugSnapshot();
|
||||
EXPECT_EQ(afterEnd.lastResult, "Tab navigated");
|
||||
EXPECT_NE(afterEnd.focusedStateKey.find("/workspace-tabs/tab-profiler"), std::string::npos);
|
||||
|
||||
UIScreenFrameInput homeInput = BuildInputState(6u);
|
||||
homeInput.events.push_back(MakeKeyDownEvent(KeyCode::Home));
|
||||
const auto& inspectorFrame = player.Update(homeInput);
|
||||
EXPECT_TRUE(DrawDataContainsText(inspectorFrame.drawData, "Inspector Content"));
|
||||
EXPECT_FALSE(DrawDataContainsText(inspectorFrame.drawData, "Profiler Content"));
|
||||
|
||||
const auto& afterHome = host.GetInputDebugSnapshot();
|
||||
EXPECT_EQ(afterHome.lastResult, "Tab navigated");
|
||||
EXPECT_NE(afterHome.focusedStateKey.find("/workspace-tabs/tab-inspector"), std::string::npos);
|
||||
}
|
||||
@@ -121,6 +121,8 @@ Runtime 的集成测试结构与 Editor 保持同一规范,但宿主职责必
|
||||
- `editor.input.pointer_states`
|
||||
- `editor.input.shortcut_scope`
|
||||
- `editor.layout.splitter_resize`
|
||||
- `editor.layout.tab_strip_selection`
|
||||
- `editor.layout.workspace_compose`
|
||||
|
||||
这些场景只用于验证 XCUI 模块能力,不代表开始复刻完整 editor 面板。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user