Add missing XCUI core UI tests
This commit is contained in:
30
tests/Core/UI/CMakeLists.txt
Normal file
30
tests/Core/UI/CMakeLists.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
# ============================================================
|
||||
# UI Core Tests
|
||||
# ============================================================
|
||||
|
||||
set(UI_TEST_SOURCES
|
||||
test_ui_core.cpp
|
||||
)
|
||||
|
||||
add_executable(core_ui_tests ${UI_TEST_SOURCES})
|
||||
|
||||
if(MSVC)
|
||||
set_target_properties(core_ui_tests PROPERTIES
|
||||
LINK_FLAGS "/NODEFAULTLIB:libcpmt.lib /NODEFAULTLIB:libcmt.lib"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(core_ui_tests
|
||||
PRIVATE
|
||||
XCEngine
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
target_include_directories(core_ui_tests PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
${CMAKE_SOURCE_DIR}/tests/Fixtures
|
||||
)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(core_ui_tests)
|
||||
197
tests/Core/UI/test_ui_core.cpp
Normal file
197
tests/Core/UI/test_ui_core.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCEngine/UI/Core/UIContext.h>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::UI::HasAnyDirtyFlags;
|
||||
using XCEngine::UI::IUIViewModel;
|
||||
using XCEngine::UI::RevisionedViewModelBase;
|
||||
using XCEngine::UI::UIBuildContext;
|
||||
using XCEngine::UI::UIBuildElementDesc;
|
||||
using XCEngine::UI::UIContext;
|
||||
using XCEngine::UI::UIDirtyFlags;
|
||||
using XCEngine::UI::UIElementChangeKind;
|
||||
using XCEngine::UI::UIElementId;
|
||||
using XCEngine::UI::UIElementNode;
|
||||
using XCEngine::UI::UIElementTree;
|
||||
|
||||
class TestViewModel : public RevisionedViewModelBase {
|
||||
public:
|
||||
void Touch() {
|
||||
MarkViewModelChanged();
|
||||
}
|
||||
};
|
||||
|
||||
UIBuildElementDesc MakeElement(
|
||||
UIElementId id,
|
||||
const char* typeName,
|
||||
std::uint64_t localStateRevision = 0,
|
||||
const IUIViewModel* viewModel = nullptr,
|
||||
std::uint64_t structuralRevision = 0) {
|
||||
UIBuildElementDesc desc = {};
|
||||
desc.id = id;
|
||||
desc.typeName = typeName;
|
||||
desc.localStateRevision = localStateRevision;
|
||||
desc.viewModel = viewModel;
|
||||
desc.structuralRevision = structuralRevision;
|
||||
return desc;
|
||||
}
|
||||
|
||||
void BuildBasicTree(UIBuildContext& buildContext) {
|
||||
auto root = buildContext.PushElement(MakeElement(1, "Root"));
|
||||
EXPECT_TRUE(static_cast<bool>(root));
|
||||
EXPECT_TRUE(buildContext.AddLeaf(MakeElement(2, "Label")));
|
||||
auto panel = buildContext.PushElement(MakeElement(3, "Panel"));
|
||||
EXPECT_TRUE(static_cast<bool>(panel));
|
||||
EXPECT_TRUE(buildContext.AddLeaf(MakeElement(4, "Button")));
|
||||
}
|
||||
|
||||
const UIElementNode& RequireNode(const UIElementTree& tree, UIElementId id) {
|
||||
const UIElementNode* node = tree.FindNode(id);
|
||||
EXPECT_NE(node, nullptr);
|
||||
return *node;
|
||||
}
|
||||
|
||||
TEST(UICoreTest, RebuildCreatesStableParentChildTree) {
|
||||
UIContext context = {};
|
||||
const auto result = context.Rebuild(BuildBasicTree);
|
||||
|
||||
EXPECT_TRUE(result.succeeded);
|
||||
EXPECT_TRUE(result.treeChanged);
|
||||
EXPECT_EQ(result.generation, 1u);
|
||||
EXPECT_EQ(context.GetElementTree().GetRootId(), 1u);
|
||||
EXPECT_EQ(context.GetElementTree().GetNodeCount(), 4u);
|
||||
EXPECT_TRUE(result.HasChange(1));
|
||||
EXPECT_TRUE(result.HasChange(4));
|
||||
|
||||
const UIElementNode& root = RequireNode(context.GetElementTree(), 1);
|
||||
ASSERT_EQ(root.childIds.size(), 2u);
|
||||
EXPECT_EQ(root.childIds[0], 2u);
|
||||
EXPECT_EQ(root.childIds[1], 3u);
|
||||
EXPECT_EQ(root.depth, 0u);
|
||||
|
||||
const UIElementNode& panel = RequireNode(context.GetElementTree(), 3);
|
||||
ASSERT_EQ(panel.childIds.size(), 1u);
|
||||
EXPECT_EQ(panel.childIds[0], 4u);
|
||||
EXPECT_EQ(panel.depth, 1u);
|
||||
|
||||
ASSERT_EQ(result.dirtyRootIds.size(), 1u);
|
||||
EXPECT_EQ(result.dirtyRootIds[0], 1u);
|
||||
}
|
||||
|
||||
TEST(UICoreTest, RebuildSkipsUnchangedTreeAfterDirtyFlagsAreCleared) {
|
||||
UIContext context = {};
|
||||
const auto initial = context.Rebuild(BuildBasicTree);
|
||||
ASSERT_TRUE(initial.succeeded);
|
||||
context.GetElementTree().ClearAllDirtyFlags();
|
||||
|
||||
const auto result = context.Rebuild(BuildBasicTree);
|
||||
|
||||
EXPECT_TRUE(result.succeeded);
|
||||
EXPECT_FALSE(result.treeChanged);
|
||||
EXPECT_TRUE(result.changes.empty());
|
||||
EXPECT_TRUE(result.dirtyRootIds.empty());
|
||||
}
|
||||
|
||||
TEST(UICoreTest, LocalStateChangeOnlyInvalidatesTheChangedLeaf) {
|
||||
UIContext context = {};
|
||||
ASSERT_TRUE(context.Rebuild(BuildBasicTree).succeeded);
|
||||
context.GetElementTree().ClearAllDirtyFlags();
|
||||
|
||||
const auto result = context.Rebuild([](UIBuildContext& buildContext) {
|
||||
auto root = buildContext.PushElement(MakeElement(1, "Root"));
|
||||
EXPECT_TRUE(static_cast<bool>(root));
|
||||
EXPECT_TRUE(buildContext.AddLeaf(MakeElement(2, "Label", 1)));
|
||||
auto panel = buildContext.PushElement(MakeElement(3, "Panel"));
|
||||
EXPECT_TRUE(static_cast<bool>(panel));
|
||||
EXPECT_TRUE(buildContext.AddLeaf(MakeElement(4, "Button")));
|
||||
});
|
||||
|
||||
EXPECT_TRUE(result.succeeded);
|
||||
EXPECT_TRUE(result.treeChanged);
|
||||
ASSERT_EQ(result.changes.size(), 1u);
|
||||
ASSERT_NE(result.FindChange(2), nullptr);
|
||||
EXPECT_EQ(result.FindChange(2)->kind, UIElementChangeKind::Updated);
|
||||
EXPECT_TRUE(HasAnyDirtyFlags(result.FindChange(2)->dirtyFlags, UIDirtyFlags::LocalState));
|
||||
EXPECT_TRUE(HasAnyDirtyFlags(result.FindChange(2)->dirtyFlags, UIDirtyFlags::Paint));
|
||||
|
||||
ASSERT_EQ(result.dirtyRootIds.size(), 1u);
|
||||
EXPECT_EQ(result.dirtyRootIds[0], 2u);
|
||||
|
||||
const UIElementNode& leaf = RequireNode(context.GetElementTree(), 2);
|
||||
EXPECT_TRUE(HasAnyDirtyFlags(leaf.dirtyFlags, UIDirtyFlags::LocalState));
|
||||
EXPECT_FALSE(RequireNode(context.GetElementTree(), 1).IsDirty());
|
||||
}
|
||||
|
||||
TEST(UICoreTest, ViewModelRevisionChangeInvalidatesBoundElement) {
|
||||
TestViewModel viewModel = {};
|
||||
UIContext context = {};
|
||||
ASSERT_TRUE(context.Rebuild([&](UIBuildContext& buildContext) {
|
||||
auto root = buildContext.PushElement(MakeElement(1, "Root"));
|
||||
EXPECT_TRUE(static_cast<bool>(root));
|
||||
EXPECT_TRUE(buildContext.AddLeaf(MakeElement(2, "Inspector", 0, &viewModel)));
|
||||
}).succeeded);
|
||||
context.GetElementTree().ClearAllDirtyFlags();
|
||||
|
||||
viewModel.Touch();
|
||||
const auto result = context.Rebuild([&](UIBuildContext& buildContext) {
|
||||
auto root = buildContext.PushElement(MakeElement(1, "Root"));
|
||||
EXPECT_TRUE(static_cast<bool>(root));
|
||||
EXPECT_TRUE(buildContext.AddLeaf(MakeElement(2, "Inspector", 0, &viewModel)));
|
||||
});
|
||||
|
||||
EXPECT_TRUE(result.succeeded);
|
||||
ASSERT_NE(result.FindChange(2), nullptr);
|
||||
EXPECT_TRUE(HasAnyDirtyFlags(result.FindChange(2)->dirtyFlags, UIDirtyFlags::ViewModel));
|
||||
EXPECT_TRUE(HasAnyDirtyFlags(result.FindChange(2)->dirtyFlags, UIDirtyFlags::Paint));
|
||||
ASSERT_EQ(result.dirtyRootIds.size(), 1u);
|
||||
EXPECT_EQ(result.dirtyRootIds[0], 2u);
|
||||
}
|
||||
|
||||
TEST(UICoreTest, StructuralChangesBubbleLayoutInvalidationToAncestors) {
|
||||
UIContext context = {};
|
||||
ASSERT_TRUE(context.Rebuild([](UIBuildContext& buildContext) {
|
||||
auto root = buildContext.PushElement(MakeElement(1, "Root"));
|
||||
EXPECT_TRUE(static_cast<bool>(root));
|
||||
auto panel = buildContext.PushElement(MakeElement(2, "Panel"));
|
||||
EXPECT_TRUE(static_cast<bool>(panel));
|
||||
EXPECT_TRUE(buildContext.AddLeaf(MakeElement(3, "Text")));
|
||||
}).succeeded);
|
||||
context.GetElementTree().ClearAllDirtyFlags();
|
||||
|
||||
const auto result = context.Rebuild([](UIBuildContext& buildContext) {
|
||||
auto root = buildContext.PushElement(MakeElement(1, "Root"));
|
||||
EXPECT_TRUE(static_cast<bool>(root));
|
||||
auto panel = buildContext.PushElement(MakeElement(2, "Panel"));
|
||||
EXPECT_TRUE(static_cast<bool>(panel));
|
||||
EXPECT_TRUE(buildContext.AddLeaf(MakeElement(3, "Text")));
|
||||
EXPECT_TRUE(buildContext.AddLeaf(MakeElement(4, "Icon")));
|
||||
});
|
||||
|
||||
EXPECT_TRUE(result.succeeded);
|
||||
EXPECT_TRUE(result.HasChange(4));
|
||||
ASSERT_NE(result.FindChange(2), nullptr);
|
||||
EXPECT_TRUE(HasAnyDirtyFlags(result.FindChange(2)->dirtyFlags, UIDirtyFlags::Structure));
|
||||
|
||||
const UIElementNode& root = RequireNode(context.GetElementTree(), 1);
|
||||
const UIElementNode& panel = RequireNode(context.GetElementTree(), 2);
|
||||
EXPECT_TRUE(HasAnyDirtyFlags(root.dirtyFlags, UIDirtyFlags::Layout));
|
||||
EXPECT_TRUE(HasAnyDirtyFlags(panel.dirtyFlags, UIDirtyFlags::Structure));
|
||||
|
||||
ASSERT_EQ(result.dirtyRootIds.size(), 1u);
|
||||
EXPECT_EQ(result.dirtyRootIds[0], 1u);
|
||||
}
|
||||
|
||||
TEST(UICoreTest, RebuildFailsWhenElementScopesRemainOpen) {
|
||||
UIContext context = {};
|
||||
const auto result = context.Rebuild([](UIBuildContext& buildContext) {
|
||||
EXPECT_TRUE(buildContext.BeginElement(MakeElement(1, "Root")));
|
||||
});
|
||||
|
||||
EXPECT_FALSE(result.succeeded);
|
||||
EXPECT_FALSE(result.errorMessage.empty());
|
||||
EXPECT_EQ(context.GetElementTree().GetNodeCount(), 0u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user