Refactor editor product runtime state architecture
This commit is contained in:
@@ -67,7 +67,6 @@ set(EDITOR_UI_UNIT_TEST_SOURCES
|
||||
set(EDITOR_APP_CORE_TEST_SOURCES
|
||||
test_editor_host_command_bridge.cpp
|
||||
test_editor_project_runtime.cpp
|
||||
test_editor_runtime_coordinator.cpp
|
||||
test_editor_scene_runtime_backend.cpp
|
||||
test_editor_shell_asset_validation.cpp
|
||||
test_editor_window_frame_orchestrator.cpp
|
||||
@@ -132,6 +131,7 @@ if(TARGET XCEditorCore)
|
||||
|
||||
target_include_directories(editor_app_core_tests
|
||||
PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/editor/src
|
||||
${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Composition
|
||||
${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Core
|
||||
${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Features
|
||||
@@ -179,6 +179,7 @@ if(TARGET XCEditorCore)
|
||||
|
||||
target_include_directories(editor_app_feature_tests
|
||||
PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/editor/src
|
||||
${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Composition
|
||||
${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Core
|
||||
${XCENGINE_EDITOR_UI_TESTS_EDITOR_ROOT}/app/Features
|
||||
@@ -249,3 +250,74 @@ endif()
|
||||
gtest_discover_tests(editor_windowing_phase1_tests
|
||||
DISCOVERY_MODE POST_BUILD
|
||||
)
|
||||
|
||||
add_executable(editor_runtime_state_tests
|
||||
test_editor_runtime_command_service.cpp
|
||||
test_editor_store.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(editor_runtime_state_tests
|
||||
PRIVATE
|
||||
XCEditorCore
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
target_include_directories(editor_runtime_state_tests
|
||||
PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/editor/src
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(editor_runtime_state_tests PRIVATE /utf-8 /FS)
|
||||
set_target_properties(editor_runtime_state_tests PROPERTIES
|
||||
MSVC_DEBUG_INFORMATION_FORMAT "$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>"
|
||||
COMPILE_PDB_NAME "editor_runtime_state_tests-compile"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/Debug"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/Release"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/MinSizeRel"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/RelWithDebInfo"
|
||||
)
|
||||
set_property(TARGET editor_runtime_state_tests PROPERTY
|
||||
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
endif()
|
||||
|
||||
gtest_discover_tests(editor_runtime_state_tests
|
||||
DISCOVERY_MODE POST_BUILD
|
||||
)
|
||||
|
||||
add_executable(editor_product_registry_tests
|
||||
test_editor_product_registry.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(editor_product_registry_tests
|
||||
PRIVATE
|
||||
XCEditorCore
|
||||
GTest::gtest_main
|
||||
)
|
||||
|
||||
target_include_directories(editor_product_registry_tests
|
||||
PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/editor/src
|
||||
${CMAKE_SOURCE_DIR}/engine/include
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_compile_options(editor_product_registry_tests PRIVATE /utf-8 /FS)
|
||||
set_target_properties(editor_product_registry_tests PROPERTIES
|
||||
MSVC_DEBUG_INFORMATION_FORMAT "$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>"
|
||||
COMPILE_PDB_NAME "editor_product_registry_tests-compile"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_DEBUG "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/Debug"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_RELEASE "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/Release"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/MinSizeRel"
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_CURRENT_BINARY_DIR}/compile-pdb/RelWithDebInfo"
|
||||
)
|
||||
set_property(TARGET editor_product_registry_tests PROPERTY
|
||||
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
endif()
|
||||
|
||||
gtest_discover_tests(editor_product_registry_tests
|
||||
DISCOVERY_MODE POST_BUILD
|
||||
)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Panels/EditorPanelIds.h"
|
||||
#include "Commands/EditorEditCommandRoute.h"
|
||||
#include "Commands/EditorHostCommandBridge.h"
|
||||
#include "State/EditorCommandFocusService.h"
|
||||
#include "Product/Commands/EditorHostCommandBridge.h"
|
||||
#include "Product/Core/Commands/EditorEditCommandRoute.h"
|
||||
#include "Product/Features/Workspace/Hierarchy/HierarchyWorkspaceFeature.h"
|
||||
#include "Product/Features/Workspace/Project/ProjectWorkspaceFeature.h"
|
||||
#include "Product/State/EditorCommandFocusService.h"
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceController.h>
|
||||
|
||||
#include <utility>
|
||||
@@ -293,7 +294,8 @@ TEST(EditorHostCommandBridgeTest, InspectorEditCommandsDelegateToBoundInspectorR
|
||||
|
||||
TEST(EditorHostCommandBridgeTest, ActivePanelRouteIsUsedAsFallbackWhenNoExplicitCommandFocusExists) {
|
||||
UIEditorWorkspaceController controller =
|
||||
MakeWorkspaceController(XCEngine::UI::Editor::App::kHierarchyPanelId);
|
||||
MakeWorkspaceController(
|
||||
XCEngine::UI::Editor::App::kHierarchyWorkspaceFeaturePanelId);
|
||||
|
||||
StubEditCommandRoute hierarchyRoute = {};
|
||||
hierarchyRoute.evaluationResult.executable = true;
|
||||
@@ -310,7 +312,8 @@ TEST(EditorHostCommandBridgeTest, ActivePanelRouteIsUsedAsFallbackWhenNoExplicit
|
||||
|
||||
TEST(EditorHostCommandBridgeTest, ExplicitCommandFocusOverridesActivePanelFallback) {
|
||||
UIEditorWorkspaceController controller =
|
||||
MakeWorkspaceController(XCEngine::UI::Editor::App::kProjectPanelId);
|
||||
MakeWorkspaceController(
|
||||
XCEngine::UI::Editor::App::kProjectWorkspaceFeaturePanelId);
|
||||
|
||||
EditorCommandFocusService commandFocus = {};
|
||||
commandFocus.ClaimFocus(EditorActionRoute::Scene);
|
||||
|
||||
195
tests/UI/Editor/unit/test_editor_product_registry.cpp
Normal file
195
tests/UI/Editor/unit/test_editor_product_registry.cpp
Normal file
@@ -0,0 +1,195 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Product/Registry/EditorProductRegistry.h"
|
||||
#include "Product/Runtime/Shell/EditorShellAssetBuilder.h"
|
||||
|
||||
#include <XCEditor/Shell/UIEditorShellAsset.h>
|
||||
#include <XCEditor/Foundation/UIEditorShortcutManager.h>
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceModel.h>
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceValidation.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <string_view>
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
namespace {
|
||||
|
||||
EditorRuntimePaths TestRuntimePaths() {
|
||||
return EditorRuntimePaths{
|
||||
.workspaceRoot = ".",
|
||||
.executableRoot = ".",
|
||||
.resourceRoot = "editor/resources",
|
||||
.projectRoot = "project",
|
||||
.captureRoot = "captures",
|
||||
};
|
||||
}
|
||||
|
||||
const UIEditorMenuDescriptor* FindMenu(
|
||||
const UIEditorMenuModel& model,
|
||||
std::string_view menuId) {
|
||||
for (const UIEditorMenuDescriptor& menu : model.menus) {
|
||||
if (menu.menuId == menuId) {
|
||||
return &menu;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const UIEditorMenuItemDescriptor* FindMenuItem(
|
||||
const std::vector<UIEditorMenuItemDescriptor>& items,
|
||||
std::string_view itemId) {
|
||||
for (const UIEditorMenuItemDescriptor& item : items) {
|
||||
if (item.itemId == itemId) {
|
||||
return &item;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const UIEditorCommandDescriptor* FindWorkspacePanelCommand(
|
||||
const UIEditorCommandRegistry& registry,
|
||||
std::string_view panelId) {
|
||||
for (const UIEditorCommandDescriptor& command : registry.commands) {
|
||||
if (command.kind == UIEditorCommandKind::Workspace &&
|
||||
command.workspaceCommand.panelId == panelId) {
|
||||
return &command;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TEST(EditorProductRegistryTests, DefaultWorkspaceMetadataValidates) {
|
||||
const EditorProductRegistryValidationResult validation =
|
||||
ValidateEditorProductRegistry();
|
||||
EXPECT_TRUE(validation.IsValid()) << validation.message;
|
||||
|
||||
const UIEditorPanelRegistry panelRegistry = BuildEditorWorkspacePanelRegistry();
|
||||
ASSERT_EQ(panelRegistry.panels.size(), GetEditorWorkspaceFeatures().size());
|
||||
|
||||
const UIEditorWorkspaceModel workspace = BuildDefaultEditorWorkspaceModel();
|
||||
const UIEditorWorkspaceValidationResult workspaceValidation =
|
||||
ValidateUIEditorWorkspace(workspace);
|
||||
EXPECT_TRUE(workspaceValidation.IsValid()) << workspaceValidation.message;
|
||||
EXPECT_EQ(workspace.activePanelId, "scene");
|
||||
}
|
||||
|
||||
TEST(EditorProductRegistryTests, WorkspaceCommandsAndViewMenuStayInSync) {
|
||||
UIEditorCommandRegistry commandRegistry = {};
|
||||
commandRegistry.commands = BuildEditorWorkspaceShellCommands();
|
||||
const UIEditorCommandRegistryValidationResult commandValidation =
|
||||
ValidateUIEditorCommandRegistry(commandRegistry);
|
||||
EXPECT_TRUE(commandValidation.IsValid()) << commandValidation.message;
|
||||
|
||||
const UIEditorMenuDescriptor viewMenu = BuildEditorWorkspaceViewMenu();
|
||||
const UIEditorMenuModel menuModel = { { viewMenu } };
|
||||
const UIEditorMenuModelValidationResult menuValidation =
|
||||
ValidateUIEditorMenuModel(menuModel, commandRegistry);
|
||||
EXPECT_TRUE(menuValidation.IsValid()) << menuValidation.message;
|
||||
|
||||
const UIEditorCommandDescriptor* resetLayoutCommand =
|
||||
FindUIEditorCommandDescriptor(commandRegistry, kEditorResetLayoutCommandId);
|
||||
ASSERT_NE(resetLayoutCommand, nullptr);
|
||||
EXPECT_EQ(
|
||||
resetLayoutCommand->workspaceCommand.kind,
|
||||
UIEditorWorkspaceCommandKind::ResetWorkspace);
|
||||
EXPECT_EQ(
|
||||
resetLayoutCommand->workspaceCommand.panelSource,
|
||||
UIEditorCommandPanelSource::None);
|
||||
|
||||
const UIEditorMenuItemDescriptor* panelsSubmenu =
|
||||
FindMenuItem(viewMenu.items, "view-panels");
|
||||
ASSERT_NE(panelsSubmenu, nullptr);
|
||||
ASSERT_EQ(panelsSubmenu->kind, UIEditorMenuItemKind::Submenu);
|
||||
ASSERT_EQ(panelsSubmenu->children.size(), GetEditorWorkspaceFeatures().size());
|
||||
|
||||
for (const EditorWorkspaceFeatureDescriptor& feature : GetEditorWorkspaceFeatures()) {
|
||||
const UIEditorCommandDescriptor* command =
|
||||
FindWorkspacePanelCommand(commandRegistry, feature.panelId);
|
||||
ASSERT_NE(command, nullptr);
|
||||
EXPECT_EQ(command->displayName, feature.defaultTitle);
|
||||
EXPECT_EQ(
|
||||
command->workspaceCommand.kind,
|
||||
UIEditorWorkspaceCommandKind::ActivatePanel);
|
||||
EXPECT_EQ(
|
||||
command->workspaceCommand.panelSource,
|
||||
UIEditorCommandPanelSource::FixedPanelId);
|
||||
|
||||
const UIEditorMenuItemDescriptor* menuItem =
|
||||
FindMenuItem(
|
||||
panelsSubmenu->children,
|
||||
std::string("view-panel-") + std::string(feature.panelId));
|
||||
ASSERT_NE(menuItem, nullptr);
|
||||
EXPECT_EQ(menuItem->label, feature.defaultTitle);
|
||||
EXPECT_EQ(menuItem->commandId, command->commandId);
|
||||
EXPECT_EQ(
|
||||
menuItem->checkedState.source,
|
||||
UIEditorMenuCheckedStateSource::PanelActive);
|
||||
EXPECT_EQ(menuItem->checkedState.panelId, feature.panelId);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EditorProductRegistryTests, ShellAssetUsesRegistryWorkspacePreset) {
|
||||
const EditorShellCommandPreset shellPreset = BuildEditorShellCommandPreset();
|
||||
const UIEditorCommandRegistryValidationResult shellCommandValidation =
|
||||
ValidateUIEditorCommandRegistry(shellPreset.commandRegistry);
|
||||
EXPECT_TRUE(shellCommandValidation.IsValid()) << shellCommandValidation.message;
|
||||
const UIEditorMenuModelValidationResult shellMenuValidation =
|
||||
ValidateUIEditorMenuModel(
|
||||
shellPreset.menuModel,
|
||||
shellPreset.commandRegistry);
|
||||
EXPECT_TRUE(shellMenuValidation.IsValid()) << shellMenuValidation.message;
|
||||
UIEditorShortcutManager shortcutManager(shellPreset.commandRegistry);
|
||||
for (const ::XCEngine::UI::UIShortcutBinding& binding : shellPreset.shortcutBindings) {
|
||||
shortcutManager.RegisterBinding(binding);
|
||||
}
|
||||
const UIEditorShortcutManagerValidationResult shortcutValidation =
|
||||
shortcutManager.ValidateConfiguration();
|
||||
EXPECT_TRUE(shortcutValidation.IsValid()) << shortcutValidation.message;
|
||||
|
||||
const EditorShellAsset shell = BuildEditorApplicationShellAsset(TestRuntimePaths());
|
||||
const EditorShellAssetValidationResult assetValidation =
|
||||
ValidateEditorShellAsset(shell);
|
||||
EXPECT_TRUE(assetValidation.IsValid()) << assetValidation.message;
|
||||
|
||||
const UIEditorPanelRegistry registryPanels = BuildEditorWorkspacePanelRegistry();
|
||||
EXPECT_EQ(shell.panelRegistry.panels.size(), registryPanels.panels.size());
|
||||
EXPECT_TRUE(
|
||||
AreUIEditorWorkspaceModelsEquivalent(
|
||||
shell.workspace,
|
||||
BuildDefaultEditorWorkspaceModel()));
|
||||
|
||||
const UIEditorMenuDescriptor* assetViewMenu =
|
||||
FindMenu(shell.shellDefinition.menuModel, "view");
|
||||
ASSERT_NE(assetViewMenu, nullptr);
|
||||
const UIEditorMenuDescriptor registryViewMenu = BuildEditorWorkspaceViewMenu();
|
||||
ASSERT_EQ(assetViewMenu->items.size(), registryViewMenu.items.size());
|
||||
ASSERT_NE(FindMenuItem(assetViewMenu->items, "view-panels"), nullptr);
|
||||
|
||||
for (const UIEditorCommandDescriptor& expected : shellPreset.commandRegistry.commands) {
|
||||
const UIEditorCommandDescriptor* actual =
|
||||
FindUIEditorCommandDescriptor(
|
||||
shell.shortcutAsset.commandRegistry,
|
||||
expected.commandId);
|
||||
ASSERT_NE(actual, nullptr);
|
||||
EXPECT_EQ(actual->displayName, expected.displayName);
|
||||
EXPECT_EQ(actual->kind, expected.kind);
|
||||
EXPECT_EQ(actual->workspaceCommand.kind, expected.workspaceCommand.kind);
|
||||
EXPECT_EQ(
|
||||
actual->workspaceCommand.panelSource,
|
||||
expected.workspaceCommand.panelSource);
|
||||
EXPECT_EQ(actual->workspaceCommand.panelId, expected.workspaceCommand.panelId);
|
||||
}
|
||||
|
||||
EXPECT_EQ(
|
||||
shell.shellDefinition.menuModel.menus.size(),
|
||||
shellPreset.menuModel.menus.size());
|
||||
EXPECT_EQ(
|
||||
shell.shortcutAsset.bindings.size(),
|
||||
shellPreset.shortcutBindings.size());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Project/EditorProjectRuntime.h"
|
||||
#include "State/EditorSelectionService.h"
|
||||
#include "Product/Services/Project/EditorProjectRuntime.h"
|
||||
#include "Product/Runtime/Store/EditorStore.h"
|
||||
#include "Product/State/EditorSelectionService.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -7,6 +8,7 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
namespace {
|
||||
@@ -185,5 +187,63 @@ TEST(EditorProjectRuntimeTests, BoundSelectionServiceBecomesTheSingleProjectSele
|
||||
EXPECT_FALSE(runtime.HasSelection());
|
||||
}
|
||||
|
||||
TEST(EditorProjectRuntimeTests, BoundStorePreservesPersistedCurrentFolderDuringInitialize) {
|
||||
TemporaryRepo repo = {};
|
||||
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Scenes"));
|
||||
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Scripts"));
|
||||
|
||||
Product::EditorStore store = {};
|
||||
Product::EditorState initialState = {};
|
||||
initialState.session.currentProjectFolderId = "Assets/Scenes";
|
||||
store.Reset(std::move(initialState));
|
||||
|
||||
EditorProjectRuntime runtime = {};
|
||||
runtime.BindStore(&store);
|
||||
ASSERT_TRUE(runtime.Initialize(repo.Root() / "project"));
|
||||
|
||||
EXPECT_EQ(store.GetState().session.currentProjectFolderId, "Assets/Scenes");
|
||||
EXPECT_EQ(runtime.GetBrowserModel().GetCurrentFolderId(), "Assets/Scenes");
|
||||
}
|
||||
|
||||
TEST(EditorProjectRuntimeTests, BoundStoreCanonicalizesInvalidPersistedCurrentFolderDuringInitialize) {
|
||||
TemporaryRepo repo = {};
|
||||
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Scenes"));
|
||||
|
||||
Product::EditorStore store = {};
|
||||
Product::EditorState initialState = {};
|
||||
initialState.session.currentProjectFolderId = "Assets/Missing";
|
||||
store.Reset(std::move(initialState));
|
||||
|
||||
EditorProjectRuntime runtime = {};
|
||||
runtime.BindStore(&store);
|
||||
ASSERT_TRUE(runtime.Initialize(repo.Root() / "project"));
|
||||
|
||||
EXPECT_EQ(store.GetState().session.currentProjectFolderId, "Assets");
|
||||
EXPECT_EQ(runtime.GetBrowserModel().GetCurrentFolderId(), "Assets");
|
||||
}
|
||||
|
||||
TEST(EditorProjectRuntimeTests, BoundStoreRemainsTheAuthoritativeCurrentFolderSourceAtRuntime) {
|
||||
TemporaryRepo repo = {};
|
||||
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Scenes"));
|
||||
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Scripts"));
|
||||
|
||||
Product::EditorStore store = {};
|
||||
store.Reset({});
|
||||
|
||||
EditorProjectRuntime runtime = {};
|
||||
runtime.BindStore(&store);
|
||||
ASSERT_TRUE(runtime.Initialize(repo.Root() / "project"));
|
||||
|
||||
ASSERT_TRUE(runtime.NavigateToFolder("Assets/Scenes"));
|
||||
EXPECT_EQ(store.GetState().session.currentProjectFolderId, "Assets/Scenes");
|
||||
EXPECT_EQ(runtime.GetBrowserModel().GetCurrentFolderId(), "Assets/Scenes");
|
||||
|
||||
const Product::EditorStore::DispatchResult dispatchResult =
|
||||
store.Dispatch(Product::EditorCommand::SetCurrentProjectFolder("Assets/Scripts"));
|
||||
ASSERT_TRUE(dispatchResult.handled);
|
||||
ASSERT_TRUE(dispatchResult.stateChanged);
|
||||
EXPECT_EQ(runtime.GetBrowserModel().GetCurrentFolderId(), "Assets/Scripts");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
|
||||
217
tests/UI/Editor/unit/test_editor_runtime_command_service.cpp
Normal file
217
tests/UI/Editor/unit/test_editor_runtime_command_service.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Product/Core/Scene/EditorSceneBackend.h"
|
||||
#include "Product/Runtime/Store/EditorStore.h"
|
||||
#include "Product/Services/Runtime/EditorRuntimeCommandService.h"
|
||||
#include "Product/Services/Scene/EditorSceneRuntime.h"
|
||||
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::Components::Scene;
|
||||
|
||||
class FakeEditorSceneBackend;
|
||||
|
||||
class FakeEditorScenePlaySession final : public EditorScenePlaySession {
|
||||
public:
|
||||
explicit FakeEditorScenePlaySession(FakeEditorSceneBackend& backend)
|
||||
: m_backend(backend) {}
|
||||
|
||||
~FakeEditorScenePlaySession() override;
|
||||
|
||||
Scene* GetRuntimeScene() const override;
|
||||
|
||||
private:
|
||||
FakeEditorSceneBackend& m_backend;
|
||||
};
|
||||
|
||||
class FakeEditorSceneBackend final : public EditorSceneBackend {
|
||||
public:
|
||||
FakeEditorSceneBackend() {
|
||||
editScene = std::make_unique<Scene>("Edit");
|
||||
activeScene = editScene.get();
|
||||
}
|
||||
|
||||
EditorStartupSceneResult EnsureStartupScene(
|
||||
const std::filesystem::path&) override {
|
||||
return EditorStartupSceneResult{
|
||||
.ready = true,
|
||||
.loadedFromDisk = true,
|
||||
.scenePath = std::filesystem::path("D:/Project/Assets/Scenes/Main.xc"),
|
||||
.sceneName = "Edit"
|
||||
};
|
||||
}
|
||||
|
||||
EditorSceneHierarchySnapshot BuildHierarchySnapshot() const override {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool NewScene(std::string_view sceneName) override {
|
||||
editScene = std::make_unique<Scene>(
|
||||
sceneName.empty() ? std::string("Untitled") : std::string(sceneName));
|
||||
activeScene = editScene.get();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenSceneAsset(const std::filesystem::path& scenePath) override {
|
||||
lastOpenedScenePath = scenePath;
|
||||
if (!openSceneResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
editScene = std::make_unique<Scene>(openedSceneName);
|
||||
activeScene = editScene.get();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SaveActiveScene(const std::filesystem::path&) override {
|
||||
return true;
|
||||
}
|
||||
|
||||
Scene* GetActiveScene() const override {
|
||||
return activeScene;
|
||||
}
|
||||
|
||||
std::unique_ptr<EditorScenePlaySession> BeginPlaySession() override {
|
||||
++beginPlaySessionCallCount;
|
||||
runtimeScene = std::make_unique<Scene>("Runtime");
|
||||
activeScene = runtimeScene.get();
|
||||
return std::make_unique<FakeEditorScenePlaySession>(*this);
|
||||
}
|
||||
|
||||
std::optional<EditorSceneObjectSnapshot> GetObjectSnapshot(
|
||||
std::string_view) const override {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool AddComponent(std::string_view, std::string_view) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RenameGameObject(std::string_view, std::string_view) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DeleteGameObject(std::string_view) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string DuplicateGameObject(std::string_view) override {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ReparentGameObject(std::string_view, std::string_view) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveGameObjectBefore(std::string_view, std::string_view) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveGameObjectAfter(std::string_view, std::string_view) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveGameObjectToRoot(std::string_view) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
void EndPlaySession() {
|
||||
++endPlaySessionCallCount;
|
||||
runtimeScene.reset();
|
||||
activeScene = editScene.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<Scene> editScene = {};
|
||||
std::unique_ptr<Scene> runtimeScene = {};
|
||||
Scene* activeScene = nullptr;
|
||||
std::filesystem::path lastOpenedScenePath = {};
|
||||
int beginPlaySessionCallCount = 0;
|
||||
int endPlaySessionCallCount = 0;
|
||||
bool openSceneResult = true;
|
||||
std::string openedSceneName = "Opened";
|
||||
};
|
||||
|
||||
FakeEditorScenePlaySession::~FakeEditorScenePlaySession() {
|
||||
m_backend.EndPlaySession();
|
||||
}
|
||||
|
||||
Scene* FakeEditorScenePlaySession::GetRuntimeScene() const {
|
||||
return m_backend.runtimeScene.get();
|
||||
}
|
||||
|
||||
struct RuntimeCommandServiceHarness {
|
||||
RuntimeCommandServiceHarness() {
|
||||
auto backend = std::make_unique<FakeEditorSceneBackend>();
|
||||
backendPtr = backend.get();
|
||||
sceneRuntime.SetBackend(std::move(backend));
|
||||
|
||||
Product::EditorState initialState = {};
|
||||
initialState.session.projectRoot = "D:/Project";
|
||||
store.Reset(std::move(initialState));
|
||||
|
||||
const EditorStartupSceneResult startupScene =
|
||||
sceneRuntime.Initialize("D:/Project");
|
||||
EXPECT_TRUE(startupScene.ready);
|
||||
|
||||
EditorRuntimePaths runtimePaths = {};
|
||||
runtimePaths.projectRoot = "D:/Project";
|
||||
service.Initialize(store, sceneRuntime, runtimePaths, startupScene);
|
||||
}
|
||||
|
||||
Product::EditorStore store = {};
|
||||
EditorSceneRuntime sceneRuntime = {};
|
||||
FakeEditorSceneBackend* backendPtr = nullptr;
|
||||
EditorRuntimeCommandService service = {};
|
||||
};
|
||||
|
||||
TEST(EditorRuntimeCommandServiceTests, InitializationProjectsStartupSceneIntoStoreSession) {
|
||||
RuntimeCommandServiceHarness harness = {};
|
||||
|
||||
EXPECT_EQ(
|
||||
harness.store.GetState().session.currentScenePath,
|
||||
std::filesystem::path("D:/Project/Assets/Scenes/Main.xc"));
|
||||
EXPECT_EQ(harness.store.GetState().session.currentSceneName, "Edit");
|
||||
EXPECT_FALSE(harness.store.GetState().session.sceneDocumentDirty);
|
||||
EXPECT_EQ(harness.store.GetState().session.runtimeMode, EditorRuntimeMode::Edit);
|
||||
}
|
||||
|
||||
TEST(EditorRuntimeCommandServiceTests, FileAndRunCommandsMutateStoreSessionState) {
|
||||
RuntimeCommandServiceHarness harness = {};
|
||||
ASSERT_NE(harness.backendPtr, nullptr);
|
||||
|
||||
int notificationCount = 0;
|
||||
harness.store.Subscribe(
|
||||
[&](const Product::EditorState&) {
|
||||
++notificationCount;
|
||||
});
|
||||
|
||||
const UIEditorHostCommandDispatchResult newSceneResult =
|
||||
harness.service.DispatchFileCommand("file.new_scene");
|
||||
ASSERT_TRUE(newSceneResult.commandExecuted);
|
||||
EXPECT_TRUE(harness.store.GetState().session.currentScenePath.empty());
|
||||
EXPECT_EQ(harness.store.GetState().session.currentSceneName, "Untitled");
|
||||
EXPECT_TRUE(harness.store.GetState().session.sceneDocumentDirty);
|
||||
|
||||
const UIEditorHostCommandDispatchResult playResult =
|
||||
harness.service.DispatchRunCommand("run.play");
|
||||
ASSERT_TRUE(playResult.commandExecuted);
|
||||
EXPECT_EQ(harness.store.GetState().session.runtimeMode, EditorRuntimeMode::Play);
|
||||
|
||||
const UIEditorHostCommandDispatchResult stopResult =
|
||||
harness.service.DispatchRunCommand("run.stop");
|
||||
ASSERT_TRUE(stopResult.commandExecuted);
|
||||
EXPECT_EQ(harness.store.GetState().session.runtimeMode, EditorRuntimeMode::Edit);
|
||||
EXPECT_GE(notificationCount, 3);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
@@ -1,309 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Project/EditorProjectRuntime.h"
|
||||
#include "Runtime/EditorRuntimeCoordinator.h"
|
||||
#include "Scene/EditorSceneBackend.h"
|
||||
#include "Scene/EditorSceneRuntime.h"
|
||||
#include "State/EditorSession.h"
|
||||
|
||||
#include <XCEngine/Scene/Scene.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::Components::Scene;
|
||||
|
||||
class FakeEditorSceneBackend;
|
||||
|
||||
class FakeEditorScenePlaySession final : public EditorScenePlaySession {
|
||||
public:
|
||||
explicit FakeEditorScenePlaySession(FakeEditorSceneBackend& backend)
|
||||
: m_backend(backend) {}
|
||||
|
||||
~FakeEditorScenePlaySession() override;
|
||||
|
||||
Scene* GetRuntimeScene() const override;
|
||||
|
||||
private:
|
||||
FakeEditorSceneBackend& m_backend;
|
||||
};
|
||||
|
||||
class FakeEditorSceneBackend final : public EditorSceneBackend {
|
||||
public:
|
||||
FakeEditorSceneBackend() {
|
||||
editScene = std::make_unique<Scene>("Edit");
|
||||
activeScene = editScene.get();
|
||||
}
|
||||
|
||||
EditorStartupSceneResult EnsureStartupScene(
|
||||
const std::filesystem::path&) override {
|
||||
return EditorStartupSceneResult{
|
||||
.ready = true,
|
||||
.loadedFromDisk = true,
|
||||
.scenePath = std::filesystem::path("D:/Project/Assets/Scenes/Main.xc"),
|
||||
.sceneName = "Edit"
|
||||
};
|
||||
}
|
||||
|
||||
EditorSceneHierarchySnapshot BuildHierarchySnapshot() const override {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool NewScene(std::string_view sceneName) override {
|
||||
editScene = std::make_unique<Scene>(
|
||||
sceneName.empty() ? std::string("Untitled") : std::string(sceneName));
|
||||
activeScene = editScene.get();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenSceneAsset(const std::filesystem::path& scenePath) override {
|
||||
lastOpenedScenePath = scenePath;
|
||||
if (!openSceneResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
editScene = std::make_unique<Scene>(openedSceneName);
|
||||
activeScene = editScene.get();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SaveActiveScene(const std::filesystem::path&) override {
|
||||
++saveActiveSceneCallCount;
|
||||
savedActiveSceneWasEditScene = activeScene == editScene.get();
|
||||
return true;
|
||||
}
|
||||
|
||||
Scene* GetActiveScene() const override {
|
||||
return activeScene;
|
||||
}
|
||||
|
||||
std::unique_ptr<EditorScenePlaySession> BeginPlaySession() override {
|
||||
++beginPlaySessionCallCount;
|
||||
if (failBeginPlaySession) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
runtimeScene = std::make_unique<Scene>("Runtime");
|
||||
activeScene = runtimeScene.get();
|
||||
return std::make_unique<FakeEditorScenePlaySession>(*this);
|
||||
}
|
||||
|
||||
std::optional<EditorSceneObjectSnapshot> GetObjectSnapshot(
|
||||
std::string_view) const override {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool AddComponent(std::string_view, std::string_view) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RenameGameObject(std::string_view, std::string_view) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DeleteGameObject(std::string_view) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string DuplicateGameObject(std::string_view) override {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ReparentGameObject(std::string_view, std::string_view) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveGameObjectBefore(std::string_view, std::string_view) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveGameObjectAfter(std::string_view, std::string_view) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MoveGameObjectToRoot(std::string_view) override {
|
||||
return false;
|
||||
}
|
||||
|
||||
void EndPlaySession() {
|
||||
++endPlaySessionCallCount;
|
||||
runtimeScene.reset();
|
||||
activeScene = editScene.get();
|
||||
}
|
||||
|
||||
Scene* EditScene() const {
|
||||
return editScene.get();
|
||||
}
|
||||
|
||||
Scene* RuntimeScene() const {
|
||||
return runtimeScene.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<Scene> editScene = {};
|
||||
std::unique_ptr<Scene> runtimeScene = {};
|
||||
Scene* activeScene = nullptr;
|
||||
std::filesystem::path lastOpenedScenePath = {};
|
||||
int beginPlaySessionCallCount = 0;
|
||||
int endPlaySessionCallCount = 0;
|
||||
int saveActiveSceneCallCount = 0;
|
||||
bool savedActiveSceneWasEditScene = false;
|
||||
bool failBeginPlaySession = false;
|
||||
bool openSceneResult = true;
|
||||
std::string openedSceneName = "Opened";
|
||||
};
|
||||
|
||||
FakeEditorScenePlaySession::~FakeEditorScenePlaySession() {
|
||||
m_backend.EndPlaySession();
|
||||
}
|
||||
|
||||
Scene* FakeEditorScenePlaySession::GetRuntimeScene() const {
|
||||
return m_backend.RuntimeScene();
|
||||
}
|
||||
|
||||
struct RuntimeCoordinatorHarness {
|
||||
RuntimeCoordinatorHarness(
|
||||
EditorRuntimePaths runtimePaths = {}) {
|
||||
auto backend = std::make_unique<FakeEditorSceneBackend>();
|
||||
backendPtr = backend.get();
|
||||
sceneRuntime.SetBackend(std::move(backend));
|
||||
const EditorStartupSceneResult startupScene =
|
||||
sceneRuntime.Initialize("D:/Project");
|
||||
EXPECT_TRUE(startupScene.ready);
|
||||
coordinator.Initialize(
|
||||
session,
|
||||
sceneRuntime,
|
||||
projectRuntime,
|
||||
runtimePaths,
|
||||
startupScene);
|
||||
}
|
||||
|
||||
EditorSession session = {};
|
||||
EditorProjectRuntime projectRuntime = {};
|
||||
EditorSceneRuntime sceneRuntime = {};
|
||||
FakeEditorSceneBackend* backendPtr = nullptr;
|
||||
EditorRuntimeCoordinator coordinator = {};
|
||||
};
|
||||
|
||||
TEST(EditorRuntimeCoordinatorTests, InitializeProjectsStartupSceneDocumentStateToSession) {
|
||||
RuntimeCoordinatorHarness harness = {};
|
||||
|
||||
EXPECT_EQ(
|
||||
harness.session.currentScenePath,
|
||||
std::filesystem::path("D:/Project/Assets/Scenes/Main.xc"));
|
||||
EXPECT_EQ(harness.session.currentSceneName, "Edit");
|
||||
EXPECT_FALSE(harness.session.sceneDocumentDirty);
|
||||
EXPECT_EQ(harness.session.runtimeMode, EditorRuntimeMode::Edit);
|
||||
}
|
||||
|
||||
TEST(EditorRuntimeCoordinatorTests, ScriptRebuildEvaluationIsNoLongerHardcodedStub) {
|
||||
EditorRuntimePaths runtimePaths = {};
|
||||
runtimePaths.projectRoot = "D:/Project";
|
||||
RuntimeCoordinatorHarness harness(runtimePaths);
|
||||
|
||||
const UIEditorHostCommandEvaluationResult evaluation =
|
||||
harness.coordinator.EvaluateScriptCommand("scripts.rebuild");
|
||||
EXPECT_NE(
|
||||
evaluation.message,
|
||||
"Script rebuild is owned by the runtime coordinator, but no in-process script assembly builder is bound.");
|
||||
}
|
||||
|
||||
TEST(EditorRuntimeCoordinatorTests, NewSceneProjectsUnsavedDirtyDocumentStateToSession) {
|
||||
RuntimeCoordinatorHarness harness = {};
|
||||
|
||||
const UIEditorHostCommandDispatchResult newSceneResult =
|
||||
harness.coordinator.DispatchFileCommand("file.new_scene");
|
||||
EXPECT_TRUE(newSceneResult.commandExecuted);
|
||||
EXPECT_TRUE(harness.session.currentScenePath.empty());
|
||||
EXPECT_EQ(harness.session.currentSceneName, "Untitled");
|
||||
EXPECT_TRUE(harness.session.sceneDocumentDirty);
|
||||
}
|
||||
|
||||
TEST(EditorRuntimeCoordinatorTests, OpenSceneProjectsCoordinatorOwnedDocumentStateToSession) {
|
||||
RuntimeCoordinatorHarness harness = {};
|
||||
ASSERT_NE(harness.backendPtr, nullptr);
|
||||
harness.backendPtr->openedSceneName = "Opened Scene";
|
||||
|
||||
const std::filesystem::path scenePath =
|
||||
"D:/Project/Assets/Scenes/Secondary.xc";
|
||||
ASSERT_TRUE(harness.coordinator.RequestOpenSceneAsset(scenePath));
|
||||
EXPECT_EQ(harness.backendPtr->lastOpenedScenePath, scenePath);
|
||||
EXPECT_EQ(harness.session.currentScenePath, scenePath);
|
||||
EXPECT_EQ(harness.session.currentSceneName, "Opened Scene");
|
||||
EXPECT_FALSE(harness.session.sceneDocumentDirty);
|
||||
}
|
||||
|
||||
TEST(EditorRuntimeCoordinatorTests, PlayModeRunsRuntimeSceneAndRestoresEditSceneOnStop) {
|
||||
RuntimeCoordinatorHarness harness = {};
|
||||
ASSERT_NE(harness.backendPtr, nullptr);
|
||||
ASSERT_EQ(harness.sceneRuntime.GetActiveScene(), harness.backendPtr->EditScene());
|
||||
|
||||
const UIEditorHostCommandDispatchResult playResult =
|
||||
harness.coordinator.DispatchRunCommand("run.play");
|
||||
EXPECT_TRUE(playResult.commandExecuted);
|
||||
EXPECT_EQ(harness.session.runtimeMode, EditorRuntimeMode::Play);
|
||||
EXPECT_EQ(harness.backendPtr->beginPlaySessionCallCount, 1);
|
||||
EXPECT_EQ(harness.sceneRuntime.GetActiveScene(), harness.backendPtr->RuntimeScene());
|
||||
EXPECT_NE(harness.sceneRuntime.GetActiveScene(), harness.backendPtr->EditScene());
|
||||
|
||||
const UIEditorHostCommandDispatchResult stopResult =
|
||||
harness.coordinator.DispatchRunCommand("run.stop");
|
||||
EXPECT_TRUE(stopResult.commandExecuted);
|
||||
EXPECT_EQ(harness.session.runtimeMode, EditorRuntimeMode::Edit);
|
||||
EXPECT_EQ(harness.backendPtr->endPlaySessionCallCount, 1);
|
||||
EXPECT_EQ(harness.sceneRuntime.GetActiveScene(), harness.backendPtr->EditScene());
|
||||
}
|
||||
|
||||
TEST(EditorRuntimeCoordinatorTests, StepFromEditCreatesPausedPlaySession) {
|
||||
RuntimeCoordinatorHarness harness = {};
|
||||
ASSERT_NE(harness.backendPtr, nullptr);
|
||||
|
||||
const UIEditorHostCommandDispatchResult stepResult =
|
||||
harness.coordinator.DispatchRunCommand("run.step");
|
||||
EXPECT_TRUE(stepResult.commandExecuted);
|
||||
EXPECT_EQ(harness.session.runtimeMode, EditorRuntimeMode::Paused);
|
||||
EXPECT_EQ(harness.backendPtr->beginPlaySessionCallCount, 1);
|
||||
EXPECT_EQ(harness.sceneRuntime.GetActiveScene(), harness.backendPtr->RuntimeScene());
|
||||
|
||||
const UIEditorHostCommandDispatchResult stopResult =
|
||||
harness.coordinator.DispatchRunCommand("run.stop");
|
||||
EXPECT_TRUE(stopResult.commandExecuted);
|
||||
EXPECT_EQ(harness.backendPtr->endPlaySessionCallCount, 1);
|
||||
EXPECT_EQ(harness.sceneRuntime.GetActiveScene(), harness.backendPtr->EditScene());
|
||||
}
|
||||
|
||||
TEST(EditorRuntimeCoordinatorTests, SaveAfterStoppingPlayTargetsRestoredEditScene) {
|
||||
RuntimeCoordinatorHarness harness = {};
|
||||
ASSERT_NE(harness.backendPtr, nullptr);
|
||||
|
||||
ASSERT_TRUE(harness.coordinator.DispatchRunCommand("run.play").commandExecuted);
|
||||
ASSERT_TRUE(harness.coordinator.DispatchRunCommand("run.stop").commandExecuted);
|
||||
|
||||
const UIEditorHostCommandDispatchResult saveResult =
|
||||
harness.coordinator.DispatchFileCommand("file.save_scene");
|
||||
EXPECT_TRUE(saveResult.commandExecuted);
|
||||
EXPECT_EQ(harness.backendPtr->saveActiveSceneCallCount, 1);
|
||||
EXPECT_TRUE(harness.backendPtr->savedActiveSceneWasEditScene);
|
||||
}
|
||||
|
||||
TEST(EditorRuntimeCoordinatorTests, FailedPlaySessionLeavesEditorInEditMode) {
|
||||
RuntimeCoordinatorHarness harness = {};
|
||||
ASSERT_NE(harness.backendPtr, nullptr);
|
||||
harness.backendPtr->failBeginPlaySession = true;
|
||||
|
||||
const UIEditorHostCommandDispatchResult playResult =
|
||||
harness.coordinator.DispatchRunCommand("run.play");
|
||||
EXPECT_FALSE(playResult.commandExecuted);
|
||||
EXPECT_EQ(harness.session.runtimeMode, EditorRuntimeMode::Edit);
|
||||
EXPECT_EQ(harness.backendPtr->beginPlaySessionCallCount, 1);
|
||||
EXPECT_EQ(harness.backendPtr->endPlaySessionCallCount, 0);
|
||||
EXPECT_EQ(harness.sceneRuntime.GetActiveScene(), harness.backendPtr->EditScene());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace XCEngine::UI::Editor::App
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "Scene/EditorSceneBackend.h"
|
||||
#include "Scene/EditorSceneRuntime.h"
|
||||
#include "Product/Core/Scene/EditorSceneBackend.h"
|
||||
#include "Product/Services/Scene/EditorSceneRuntime.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "EditorWorkspacePanelRegistry.h"
|
||||
#include "EditorShellAssetBuilder.h"
|
||||
#include "Panels/EditorPanelIds.h"
|
||||
#include "Product/EditorProductManifest.h"
|
||||
#include "Product/Features/Workspace/Game/GameWorkspaceFeature.h"
|
||||
#include "Product/Registry/EditorProductRegistry.h"
|
||||
#include "Product/Runtime/Shell/EditorShellAssetBuilder.h"
|
||||
|
||||
#include <XCEditor/Shell/UIEditorShellAsset.h>
|
||||
|
||||
@@ -13,15 +12,12 @@ namespace {
|
||||
|
||||
using XCEngine::Input::KeyCode;
|
||||
using XCEngine::UI::Editor::App::BuildEditorApplicationShellAsset;
|
||||
using XCEngine::UI::Editor::App::EditorProductManifestValidationCode;
|
||||
using XCEngine::UI::Editor::App::EditorProductViewportKind;
|
||||
using XCEngine::UI::Editor::App::EditorRuntimePaths;
|
||||
using XCEngine::UI::Editor::App::EditorWorkspacePanelCompositionValidationCode;
|
||||
using XCEngine::UI::Editor::App::FindEditorProductPanel;
|
||||
using XCEngine::UI::Editor::App::GetEditorProductPanels;
|
||||
using XCEngine::UI::Editor::App::kGamePanelId;
|
||||
using XCEngine::UI::Editor::App::ValidateEditorProductManifest;
|
||||
using XCEngine::UI::Editor::App::ValidateEditorWorkspacePanelComposition;
|
||||
using XCEngine::UI::Editor::App::FindEditorWorkspaceFeature;
|
||||
using XCEngine::UI::Editor::App::GetEditorWorkspaceFeatures;
|
||||
using XCEngine::UI::Editor::App::kGameWorkspaceFeaturePanelId;
|
||||
using XCEngine::UI::Editor::App::ValidateEditorProductRegistry;
|
||||
using XCEngine::UI::Editor::EditorShellAssetValidationCode;
|
||||
using XCEngine::UI::Editor::FindUIEditorPanelDescriptor;
|
||||
using XCEngine::UI::Editor::UIEditorCommandPanelSource;
|
||||
@@ -86,23 +82,14 @@ TEST(EditorShellAssetValidationTest, DefaultShellAssetPassesValidation) {
|
||||
shellAsset.panelRegistry.panels.front().presentationKind);
|
||||
}
|
||||
|
||||
TEST(EditorShellAssetValidationTest, ProductManifestIsPureDataAndCompositionIsComplete) {
|
||||
const auto manifestValidation = ValidateEditorProductManifest();
|
||||
EXPECT_EQ(manifestValidation.code, EditorProductManifestValidationCode::None)
|
||||
<< manifestValidation.message;
|
||||
ASSERT_TRUE(manifestValidation.IsValid());
|
||||
|
||||
const auto compositionValidation = ValidateEditorWorkspacePanelComposition();
|
||||
EXPECT_EQ(
|
||||
compositionValidation.code,
|
||||
EditorWorkspacePanelCompositionValidationCode::None)
|
||||
<< compositionValidation.message;
|
||||
ASSERT_TRUE(compositionValidation.IsValid());
|
||||
TEST(EditorShellAssetValidationTest, ProductRegistryAndCompositionAreComplete) {
|
||||
const auto registryValidation = ValidateEditorProductRegistry();
|
||||
ASSERT_TRUE(registryValidation.IsValid()) << registryValidation.message;
|
||||
|
||||
const auto shellAsset = BuildEditorApplicationShellAsset(TestRuntimePaths());
|
||||
ASSERT_EQ(shellAsset.panelRegistry.panels.size(), GetEditorProductPanels().size());
|
||||
ASSERT_EQ(shellAsset.panelRegistry.panels.size(), GetEditorWorkspaceFeatures().size());
|
||||
|
||||
const auto* gamePanel = FindEditorProductPanel(kGamePanelId);
|
||||
const auto* gamePanel = FindEditorWorkspaceFeature(kGameWorkspaceFeaturePanelId);
|
||||
ASSERT_NE(gamePanel, nullptr);
|
||||
EXPECT_EQ(gamePanel->viewportKind, EditorProductViewportKind::Game);
|
||||
EXPECT_TRUE(gamePanel->viewportPlaceholderStatus.empty());
|
||||
|
||||
100
tests/UI/Editor/unit/test_editor_store.cpp
Normal file
100
tests/UI/Editor/unit/test_editor_store.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Product/Runtime/Store/EditorStore.h"
|
||||
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceModel.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine::UI::Editor::Product {
|
||||
namespace {
|
||||
|
||||
TEST(EditorStoreTests, SetSceneDocumentStateNotifiesObserversWhenSessionChanges) {
|
||||
EditorStore store = {};
|
||||
EditorState initialState = {};
|
||||
initialState.session.projectRoot = "D:/Project";
|
||||
store.Reset(std::move(initialState));
|
||||
|
||||
int notificationCount = 0;
|
||||
EditorState observedState = {};
|
||||
store.Subscribe(
|
||||
[&](const EditorState& state) {
|
||||
++notificationCount;
|
||||
observedState = state;
|
||||
});
|
||||
|
||||
const EditorStore::DispatchResult result = store.Dispatch(
|
||||
EditorCommand::SetSceneDocumentState(
|
||||
"D:/Project/Assets/Scenes/Main.xc",
|
||||
"Main",
|
||||
true));
|
||||
|
||||
EXPECT_TRUE(result.handled);
|
||||
EXPECT_TRUE(result.stateChanged);
|
||||
EXPECT_EQ(notificationCount, 1);
|
||||
EXPECT_EQ(
|
||||
observedState.session.currentScenePath,
|
||||
std::filesystem::path("D:/Project/Assets/Scenes/Main.xc"));
|
||||
EXPECT_EQ(observedState.session.currentSceneName, "Main");
|
||||
EXPECT_TRUE(observedState.session.sceneDocumentDirty);
|
||||
}
|
||||
|
||||
TEST(EditorStoreTests, SetCurrentProjectFolderSkipsNotificationsWhenSessionDoesNotChange) {
|
||||
EditorStore store = {};
|
||||
store.Reset({});
|
||||
|
||||
int notificationCount = 0;
|
||||
store.Subscribe(
|
||||
[&](const EditorState&) {
|
||||
++notificationCount;
|
||||
});
|
||||
|
||||
const EditorStore::DispatchResult result = store.Dispatch(
|
||||
EditorCommand::SetCurrentProjectFolder("Assets"));
|
||||
|
||||
EXPECT_TRUE(result.handled);
|
||||
EXPECT_FALSE(result.stateChanged);
|
||||
EXPECT_EQ(notificationCount, 0);
|
||||
}
|
||||
|
||||
TEST(EditorStoreTests, SetWindowWorkspaceStateCommitsAuthoritativeWindowDraft) {
|
||||
EditorStore store = {};
|
||||
EditorState initialState = {};
|
||||
initialState.windowWorkspace.primaryWindowId = "main";
|
||||
initialState.windowWorkspace.activeWindowId = "main";
|
||||
|
||||
UIEditorWindowWorkspaceState mainWindow = {};
|
||||
mainWindow.windowId = "main";
|
||||
initialState.windowWorkspace.windows.push_back(mainWindow);
|
||||
store.Reset(std::move(initialState));
|
||||
|
||||
UIEditorWorkspaceModel nextWorkspace = {};
|
||||
nextWorkspace.root = BuildUIEditorWorkspaceSingleTabStack(
|
||||
"root-tabs",
|
||||
"scene",
|
||||
"Scene",
|
||||
true);
|
||||
nextWorkspace.activePanelId = "scene";
|
||||
UIEditorWorkspaceSession nextSession = {};
|
||||
|
||||
int notificationCount = 0;
|
||||
store.Subscribe(
|
||||
[&](const EditorState&) {
|
||||
++notificationCount;
|
||||
});
|
||||
|
||||
const EditorStore::DispatchResult result = store.Dispatch(
|
||||
EditorCommand::SetWindowWorkspaceState("main", nextWorkspace, nextSession));
|
||||
|
||||
ASSERT_TRUE(result.handled);
|
||||
ASSERT_TRUE(result.stateChanged);
|
||||
EXPECT_EQ(notificationCount, 1);
|
||||
const UIEditorWindowWorkspaceState* const storedWindow =
|
||||
store.FindWindowWorkspaceState("main");
|
||||
ASSERT_NE(storedWindow, nullptr);
|
||||
EXPECT_EQ(storedWindow->workspace.activePanelId, "scene");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace XCEngine::UI::Editor::Product
|
||||
@@ -1,46 +1,18 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Frame/EditorWindowFrameOrchestrator.h"
|
||||
#include "Product/Runtime/Diagnostics/EditorFrameStatusController.h"
|
||||
#include "Product/Runtime/Windowing/Frame/EditorWindowFrameOrchestrator.h"
|
||||
|
||||
#include <XCEditor/Shell/UIEditorShellInteraction.h>
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceController.h>
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
namespace XCEngine::UI::Editor::App {
|
||||
namespace {
|
||||
|
||||
class FakeShellDefinitionProvider final : public EditorShellDefinitionProvider {
|
||||
public:
|
||||
UIEditorShellInteractionDefinition BuildShellDefinition(
|
||||
const UIEditorWorkspaceController&,
|
||||
std::string_view,
|
||||
std::string_view,
|
||||
EditorShellVariant) const override {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
class FakeFrameStatusService final : public EditorFrameStatusService {
|
||||
public:
|
||||
void SetStatus(std::string, std::string) override {}
|
||||
void SetReadyStatus() override {}
|
||||
std::string ComposeStatusText() const override { return {}; }
|
||||
void UpdateStatusFromShellResult(
|
||||
const UIEditorWorkspaceController&,
|
||||
const UIEditorShellInteractionResult&) override {}
|
||||
std::string DescribeWorkspaceState(
|
||||
const UIEditorWorkspaceController&,
|
||||
const UIEditorShellInteractionState&) const override {
|
||||
return {};
|
||||
}
|
||||
std::vector<WorkspaceTraceEntry> SyncWorkspacePanelFrameEvents(
|
||||
const std::vector<EditorWorkspacePanelFrameEvent>&) override {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
class FakeWorkspaceShellRuntime final : public EditorWorkspaceShellRuntime {
|
||||
public:
|
||||
void Initialize(
|
||||
@@ -60,8 +32,6 @@ public:
|
||||
void SetViewportSurfacePresentationEnabled(bool) override {}
|
||||
|
||||
void Update(
|
||||
const EditorShellDefinitionProvider&,
|
||||
EditorFrameStatusService&,
|
||||
UIEditorWorkspaceController&,
|
||||
const ::XCEngine::UI::UIRect&,
|
||||
const std::vector<::XCEngine::UI::UIInputEvent>&,
|
||||
@@ -135,8 +105,7 @@ public:
|
||||
|
||||
TEST(EditorWindowFrameOrchestratorTests, UtilityWindowRequestsFlowThroughFrameTransferRequests) {
|
||||
EditorWindowFrameOrchestrator orchestrator = {};
|
||||
FakeShellDefinitionProvider shellDefinitionProvider = {};
|
||||
FakeFrameStatusService frameStatusService = {};
|
||||
EditorFrameStatusController frameStatusController = {};
|
||||
FakeWorkspaceShellRuntime shellRuntime = {};
|
||||
UIEditorWorkspaceController workspaceController = {};
|
||||
::XCEngine::UI::UIDrawData drawData = {};
|
||||
@@ -152,8 +121,7 @@ TEST(EditorWindowFrameOrchestratorTests, UtilityWindowRequestsFlowThroughFrameTr
|
||||
|
||||
const EditorWindowFrameTransferRequests transferRequests =
|
||||
orchestrator.UpdateAndAppend(
|
||||
shellDefinitionProvider,
|
||||
frameStatusService,
|
||||
frameStatusController,
|
||||
&consumeUtilityWindowRequest,
|
||||
workspaceController,
|
||||
shellRuntime,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <XCEditor/Shell/UIEditorShellCapturePolicy.h>
|
||||
#include <XCEditor/Workspace/UIEditorWorkspaceCompose.h>
|
||||
|
||||
#include "EditorWindowPointerCapture.h"
|
||||
#include "Product/Framework/Windowing/EditorWindowPointerCapture.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "Game/GameViewportFeature.h"
|
||||
#include "Panels/EditorPanelIds.h"
|
||||
#include "State/EditorCommandFocusService.h"
|
||||
#include "Viewport/GameViewportRenderService.h"
|
||||
#include "Product/Features/Workspace/Game/GameViewportFeature.h"
|
||||
#include "Product/Features/Workspace/Game/GameWorkspaceFeature.h"
|
||||
#include "Product/Rendering/Viewport/GameViewportRenderService.h"
|
||||
#include "Product/State/EditorCommandFocusService.h"
|
||||
|
||||
#include <XCEditor/Viewport/UIEditorViewportInputBridge.h>
|
||||
|
||||
@@ -39,7 +39,7 @@ UIEditorWorkspaceComposeState BuildGameComposeState(
|
||||
const UIEditorViewportInputBridgeState& inputBridgeState) {
|
||||
UIEditorWorkspaceComposeState composeState = {};
|
||||
UIEditorWorkspacePanelPresentationState panelState = {};
|
||||
panelState.panelId = std::string(kGamePanelId);
|
||||
panelState.panelId = std::string(kGameWorkspaceFeaturePanelId);
|
||||
panelState.viewportShellState.inputBridgeState = inputBridgeState;
|
||||
composeState.panelStates.push_back(std::move(panelState));
|
||||
return composeState;
|
||||
@@ -51,7 +51,7 @@ UIEditorWorkspaceComposeFrame BuildGameComposeFrame(
|
||||
const UISize& requestedViewportSize) {
|
||||
UIEditorWorkspaceComposeFrame composeFrame = {};
|
||||
UIEditorWorkspaceViewportComposeFrame viewportFrame = {};
|
||||
viewportFrame.panelId = std::string(kGamePanelId);
|
||||
viewportFrame.panelId = std::string(kGameWorkspaceFeaturePanelId);
|
||||
viewportFrame.viewportShellFrame.inputFrame = inputFrame;
|
||||
viewportFrame.viewportShellFrame.requestedViewportSize = requestedViewportSize;
|
||||
viewportFrame.viewportShellFrame.slotLayout.inputRect = inputRect;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "Hierarchy/HierarchyModel.h"
|
||||
#include "Scene/EngineEditorSceneBackend.h"
|
||||
#include "Product/Features/Workspace/Hierarchy/HierarchyModel.h"
|
||||
#include "Product/Services/Scene/EngineEditorSceneBackend.h"
|
||||
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "Inspector/Components/IInspectorComponentEditor.h"
|
||||
#include "Inspector/Components/InspectorComponentEditorRegistry.h"
|
||||
#include "Inspector/InspectorFieldValueApplier.h"
|
||||
#include "Inspector/InspectorPresentationModel.h"
|
||||
#include "Inspector/InspectorSubject.h"
|
||||
#include "Project/EditorProjectRuntime.h"
|
||||
#include "Scene/EditorSceneRuntime.h"
|
||||
#include "Scene/EngineEditorSceneBackend.h"
|
||||
#include "Product/Features/Workspace/Inspector/Components/IInspectorComponentEditor.h"
|
||||
#include "Product/Features/Workspace/Inspector/Components/InspectorComponentEditorRegistry.h"
|
||||
#include "Product/Features/Workspace/Inspector/InspectorFieldValueApplier.h"
|
||||
#include "Product/Features/Workspace/Inspector/InspectorPresentationModel.h"
|
||||
#include "Product/Features/Workspace/Inspector/InspectorSubject.h"
|
||||
#include "Product/Services/Project/EditorProjectRuntime.h"
|
||||
#include "Product/Services/Scene/EditorSceneRuntime.h"
|
||||
#include "Product/Services/Scene/EngineEditorSceneBackend.h"
|
||||
|
||||
#include <XCEngine/Components/CameraComponent.h>
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "Project/ProjectBrowserModel.h"
|
||||
#include "Product/Services/Project/ProjectBrowserModel.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#include "Project/ProjectPanel.h"
|
||||
#include "Assets/EditorIconService.h"
|
||||
#include "SystemInteractionService.h"
|
||||
|
||||
#include "Panels/EditorPanelIds.h"
|
||||
#include "Product/Core/Assets/EditorIconService.h"
|
||||
#include "Product/Features/Workspace/Project/ProjectPanel.h"
|
||||
#include "Product/Features/Workspace/Project/ProjectWorkspaceFeature.h"
|
||||
#include "Product/Framework/System/SystemInteractionService.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
@@ -117,7 +116,7 @@ private:
|
||||
|
||||
UIEditorHostedPanelDispatchEntry MakeProjectDispatchEntry() {
|
||||
UIEditorHostedPanelDispatchEntry entry = {};
|
||||
entry.panelId = std::string(kProjectPanelId);
|
||||
entry.panelId = std::string(kProjectWorkspaceFeaturePanelId);
|
||||
entry.presentationKind = UIEditorPanelPresentationKind::HostedContent;
|
||||
entry.mounted = true;
|
||||
entry.bounds = ::XCEngine::UI::UIRect(0.0f, 0.0f, 640.0f, 360.0f);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "Viewport/SceneViewportRenderPlan.h"
|
||||
#include "Product/Rendering/Viewport/SceneViewportRenderPlan.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#include "Scene/EditorSceneRuntime.h"
|
||||
#include "Scene/EngineEditorSceneBackend.h"
|
||||
#include "Scene/SceneViewportController.h"
|
||||
#include "Scene/SceneViewportSession.h"
|
||||
#include "Inspector/InspectorSubject.h"
|
||||
#include "Project/EditorProjectRuntime.h"
|
||||
#include "Viewport/SceneViewportRenderService.h"
|
||||
#include "Viewport/ViewportHostService.h"
|
||||
#include "Viewport/ViewportRenderTargets.h"
|
||||
#include "Viewport/ViewportRenderTargetUtils.h"
|
||||
#include "State/EditorSelectionService.h"
|
||||
|
||||
#include "Panels/EditorPanelIds.h"
|
||||
#include "Product/Features/Workspace/Inspector/InspectorSubject.h"
|
||||
#include "Product/Features/Workspace/Scene/SceneViewportController.h"
|
||||
#include "Product/Features/Workspace/Scene/SceneViewportSession.h"
|
||||
#include "Product/Features/Workspace/Scene/SceneWorkspaceFeature.h"
|
||||
#include "Product/Rendering/Viewport/SceneViewportRenderService.h"
|
||||
#include "Product/Rendering/Viewport/ViewportHostService.h"
|
||||
#include "Product/Rendering/Viewport/ViewportRenderTargets.h"
|
||||
#include "Product/Rendering/Viewport/ViewportRenderTargetUtils.h"
|
||||
#include "Product/Services/Project/EditorProjectRuntime.h"
|
||||
#include "Product/Services/Scene/EditorSceneRuntime.h"
|
||||
#include "Product/Services/Scene/EngineEditorSceneBackend.h"
|
||||
#include "Product/State/EditorSelectionService.h"
|
||||
#include <XCEditor/Viewport/UIEditorViewportInputBridge.h>
|
||||
#include <XCEditor/Viewport/UIEditorViewportSlot.h>
|
||||
|
||||
@@ -185,7 +184,8 @@ UIEditorWorkspaceComposeState BuildSceneComposeState(
|
||||
const UIEditorViewportInputBridgeState& inputBridgeState) {
|
||||
UIEditorWorkspaceComposeState composeState = {};
|
||||
UIEditorWorkspacePanelPresentationState panelState = {};
|
||||
panelState.panelId = std::string(::XCEngine::UI::Editor::App::kScenePanelId);
|
||||
panelState.panelId = std::string(
|
||||
::XCEngine::UI::Editor::App::kSceneWorkspaceFeaturePanelId);
|
||||
panelState.viewportShellState.inputBridgeState = inputBridgeState;
|
||||
composeState.panelStates.push_back(std::move(panelState));
|
||||
return composeState;
|
||||
@@ -197,7 +197,8 @@ UIEditorWorkspaceComposeFrame BuildSceneComposeFrame(
|
||||
const UISize& requestedViewportSize) {
|
||||
UIEditorWorkspaceComposeFrame composeFrame = {};
|
||||
UIEditorWorkspaceViewportComposeFrame viewportFrame = {};
|
||||
viewportFrame.panelId = std::string(::XCEngine::UI::Editor::App::kScenePanelId);
|
||||
viewportFrame.panelId = std::string(
|
||||
::XCEngine::UI::Editor::App::kSceneWorkspaceFeaturePanelId);
|
||||
viewportFrame.viewportShellFrame.inputFrame = inputFrame;
|
||||
viewportFrame.viewportShellFrame.requestedViewportSize = requestedViewportSize;
|
||||
viewportFrame.viewportShellFrame.slotLayout.inputRect = inputRect;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "Viewport/ViewportObjectIdPicker.h"
|
||||
#include "Product/Rendering/Viewport/ViewportObjectIdPicker.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user