feat(xcui): add editor layout persistence validation
This commit is contained in:
@@ -6,6 +6,7 @@ set(EDITOR_UI_UNIT_TEST_SOURCES
|
||||
test_ui_editor_collection_primitives.cpp
|
||||
test_ui_editor_panel_chrome.cpp
|
||||
test_ui_editor_workspace_controller.cpp
|
||||
test_ui_editor_workspace_layout_persistence.cpp
|
||||
test_ui_editor_workspace_model.cpp
|
||||
test_ui_editor_workspace_session.cpp
|
||||
)
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <XCNewEditor/Editor/UIEditorWorkspaceController.h>
|
||||
#include <XCNewEditor/Editor/UIEditorWorkspaceLayoutPersistence.h>
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::NewEditor::AreUIEditorWorkspaceLayoutSnapshotsEquivalent;
|
||||
using XCEngine::NewEditor::BuildDefaultUIEditorWorkspaceController;
|
||||
using XCEngine::NewEditor::BuildDefaultUIEditorWorkspaceSession;
|
||||
using XCEngine::NewEditor::BuildUIEditorWorkspaceLayoutSnapshot;
|
||||
using XCEngine::NewEditor::BuildUIEditorWorkspacePanel;
|
||||
using XCEngine::NewEditor::BuildUIEditorWorkspaceSplit;
|
||||
using XCEngine::NewEditor::BuildUIEditorWorkspaceTabStack;
|
||||
using XCEngine::NewEditor::DeserializeUIEditorWorkspaceLayoutSnapshot;
|
||||
using XCEngine::NewEditor::SerializeUIEditorWorkspaceLayoutSnapshot;
|
||||
using XCEngine::NewEditor::TryCloseUIEditorWorkspacePanel;
|
||||
using XCEngine::NewEditor::TryHideUIEditorWorkspacePanel;
|
||||
using XCEngine::NewEditor::UIEditorPanelRegistry;
|
||||
using XCEngine::NewEditor::UIEditorWorkspaceController;
|
||||
using XCEngine::NewEditor::UIEditorWorkspaceLayoutLoadCode;
|
||||
using XCEngine::NewEditor::UIEditorWorkspaceLayoutOperationStatus;
|
||||
using XCEngine::NewEditor::UIEditorWorkspaceCommandKind;
|
||||
using XCEngine::NewEditor::UIEditorWorkspaceCommandStatus;
|
||||
using XCEngine::NewEditor::UIEditorWorkspaceModel;
|
||||
using XCEngine::NewEditor::UIEditorWorkspaceSession;
|
||||
using XCEngine::NewEditor::UIEditorWorkspaceSplitAxis;
|
||||
|
||||
UIEditorPanelRegistry BuildPanelRegistry() {
|
||||
UIEditorPanelRegistry registry = {};
|
||||
registry.panels = {
|
||||
{ "doc-a", "Document A", {}, true, true, true },
|
||||
{ "doc-b", "Document B", {}, true, true, true },
|
||||
{ "details", "Details", {}, true, true, true }
|
||||
};
|
||||
return registry;
|
||||
}
|
||||
|
||||
UIEditorWorkspaceModel BuildWorkspace() {
|
||||
UIEditorWorkspaceModel workspace = {};
|
||||
workspace.root = BuildUIEditorWorkspaceSplit(
|
||||
"root-split",
|
||||
UIEditorWorkspaceSplitAxis::Horizontal,
|
||||
0.66f,
|
||||
BuildUIEditorWorkspaceTabStack(
|
||||
"document-tabs",
|
||||
{
|
||||
BuildUIEditorWorkspacePanel("doc-a-node", "doc-a", "Document A", true),
|
||||
BuildUIEditorWorkspacePanel("doc-b-node", "doc-b", "Document B", true)
|
||||
},
|
||||
0u),
|
||||
BuildUIEditorWorkspacePanel("details-node", "details", "Details", true));
|
||||
workspace.activePanelId = "doc-a";
|
||||
return workspace;
|
||||
}
|
||||
|
||||
std::string ReplaceFirst(
|
||||
std::string source,
|
||||
std::string_view from,
|
||||
std::string_view to) {
|
||||
const std::size_t index = source.find(from);
|
||||
EXPECT_NE(index, std::string::npos);
|
||||
if (index == std::string::npos) {
|
||||
return source;
|
||||
}
|
||||
|
||||
source.replace(index, from.size(), to);
|
||||
return source;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST(UIEditorWorkspaceLayoutPersistenceTest, SerializeAndDeserializeRoundTripPreservesWorkspaceAndSession) {
|
||||
const UIEditorPanelRegistry registry = BuildPanelRegistry();
|
||||
UIEditorWorkspaceModel workspace = BuildWorkspace();
|
||||
UIEditorWorkspaceSession session =
|
||||
BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
|
||||
ASSERT_TRUE(TryHideUIEditorWorkspacePanel(registry, workspace, session, "doc-a"));
|
||||
ASSERT_TRUE(TryCloseUIEditorWorkspacePanel(registry, workspace, session, "doc-b"));
|
||||
|
||||
const auto snapshot = BuildUIEditorWorkspaceLayoutSnapshot(workspace, session);
|
||||
const std::string serialized = SerializeUIEditorWorkspaceLayoutSnapshot(snapshot);
|
||||
const auto loadResult =
|
||||
DeserializeUIEditorWorkspaceLayoutSnapshot(registry, serialized);
|
||||
|
||||
ASSERT_TRUE(loadResult.IsValid()) << loadResult.message;
|
||||
EXPECT_TRUE(
|
||||
AreUIEditorWorkspaceLayoutSnapshotsEquivalent(loadResult.snapshot, snapshot));
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceLayoutPersistenceTest, DeserializeRejectsInvalidSelectedTabIndex) {
|
||||
const UIEditorPanelRegistry registry = BuildPanelRegistry();
|
||||
const UIEditorWorkspaceModel workspace = BuildWorkspace();
|
||||
const UIEditorWorkspaceSession session =
|
||||
BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
|
||||
const std::string invalidSerialized = ReplaceFirst(
|
||||
SerializeUIEditorWorkspaceLayoutSnapshot(
|
||||
BuildUIEditorWorkspaceLayoutSnapshot(workspace, session)),
|
||||
"node_tabstack \"document-tabs\" 0 2",
|
||||
"node_tabstack \"document-tabs\" 5 2");
|
||||
|
||||
const auto loadResult =
|
||||
DeserializeUIEditorWorkspaceLayoutSnapshot(registry, invalidSerialized);
|
||||
|
||||
EXPECT_EQ(loadResult.code, UIEditorWorkspaceLayoutLoadCode::InvalidWorkspace);
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceLayoutPersistenceTest, DeserializeRejectsMissingSessionRecord) {
|
||||
const UIEditorPanelRegistry registry = BuildPanelRegistry();
|
||||
const UIEditorWorkspaceModel workspace = BuildWorkspace();
|
||||
const UIEditorWorkspaceSession session =
|
||||
BuildDefaultUIEditorWorkspaceSession(registry, workspace);
|
||||
|
||||
const std::string invalidSerialized = ReplaceFirst(
|
||||
SerializeUIEditorWorkspaceLayoutSnapshot(
|
||||
BuildUIEditorWorkspaceLayoutSnapshot(workspace, session)),
|
||||
"session \"details\" 1 1\n",
|
||||
"");
|
||||
|
||||
const auto loadResult =
|
||||
DeserializeUIEditorWorkspaceLayoutSnapshot(registry, invalidSerialized);
|
||||
|
||||
EXPECT_EQ(loadResult.code, UIEditorWorkspaceLayoutLoadCode::InvalidWorkspaceSession);
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceLayoutPersistenceTest, RestoreSerializedLayoutRestoresSavedStateAfterFurtherMutations) {
|
||||
const UIEditorPanelRegistry registry = BuildPanelRegistry();
|
||||
UIEditorWorkspaceController controller =
|
||||
BuildDefaultUIEditorWorkspaceController(registry, BuildWorkspace());
|
||||
|
||||
EXPECT_EQ(
|
||||
controller.Dispatch({ UIEditorWorkspaceCommandKind::HidePanel, "doc-a" }).status,
|
||||
UIEditorWorkspaceCommandStatus::Changed);
|
||||
|
||||
const std::string savedLayout =
|
||||
SerializeUIEditorWorkspaceLayoutSnapshot(controller.CaptureLayoutSnapshot());
|
||||
|
||||
EXPECT_EQ(
|
||||
controller.Dispatch({ UIEditorWorkspaceCommandKind::ClosePanel, "doc-b" }).status,
|
||||
UIEditorWorkspaceCommandStatus::Changed);
|
||||
EXPECT_EQ(controller.GetWorkspace().activePanelId, "details");
|
||||
|
||||
const auto restoreResult = controller.RestoreSerializedLayout(savedLayout);
|
||||
EXPECT_EQ(restoreResult.status, UIEditorWorkspaceLayoutOperationStatus::Changed);
|
||||
EXPECT_EQ(controller.GetWorkspace().activePanelId, "doc-b");
|
||||
ASSERT_EQ(restoreResult.visiblePanelIds.size(), 2u);
|
||||
EXPECT_EQ(restoreResult.visiblePanelIds[0], "doc-b");
|
||||
EXPECT_EQ(restoreResult.visiblePanelIds[1], "details");
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceLayoutPersistenceTest, RestoreSerializedLayoutRejectsInvalidPayloadWithoutMutatingCurrentState) {
|
||||
const UIEditorPanelRegistry registry = BuildPanelRegistry();
|
||||
UIEditorWorkspaceController controller =
|
||||
BuildDefaultUIEditorWorkspaceController(registry, BuildWorkspace());
|
||||
|
||||
EXPECT_EQ(
|
||||
controller.Dispatch({ UIEditorWorkspaceCommandKind::ActivatePanel, "details" }).status,
|
||||
UIEditorWorkspaceCommandStatus::Changed);
|
||||
const auto before = controller.CaptureLayoutSnapshot();
|
||||
|
||||
const std::string invalidSerialized = ReplaceFirst(
|
||||
SerializeUIEditorWorkspaceLayoutSnapshot(before),
|
||||
"active \"details\"",
|
||||
"active \"missing\"");
|
||||
|
||||
const auto restoreResult = controller.RestoreSerializedLayout(invalidSerialized);
|
||||
EXPECT_EQ(restoreResult.status, UIEditorWorkspaceLayoutOperationStatus::Rejected);
|
||||
|
||||
const auto after = controller.CaptureLayoutSnapshot();
|
||||
EXPECT_TRUE(AreUIEditorWorkspaceLayoutSnapshotsEquivalent(after, before));
|
||||
}
|
||||
|
||||
TEST(UIEditorWorkspaceLayoutPersistenceTest, RestoreLayoutSnapshotReturnsNoOpWhenStateAlreadyMatchesSnapshot) {
|
||||
UIEditorWorkspaceController controller =
|
||||
BuildDefaultUIEditorWorkspaceController(BuildPanelRegistry(), BuildWorkspace());
|
||||
|
||||
const auto result = controller.RestoreLayoutSnapshot(controller.CaptureLayoutSnapshot());
|
||||
|
||||
EXPECT_EQ(result.status, UIEditorWorkspaceLayoutOperationStatus::NoOp);
|
||||
}
|
||||
Reference in New Issue
Block a user