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