#include "Features/Hierarchy/HierarchyModel.h" #include "Scene/EditorSceneBridge.h" #include #include #include #include #include #include #include namespace XCEngine::UI::Editor::App { namespace { using ::XCEngine::Components::GameObject; using ::XCEngine::Components::Scene; using ::XCEngine::Components::SceneManager; class ScopedSceneManagerReset final { public: ScopedSceneManagerReset() { Reset(); } ~ScopedSceneManagerReset() { Reset(); } private: static void Reset() { SceneManager& manager = SceneManager::Get(); const auto scenes = manager.GetAllScenes(); for (Scene* scene : scenes) { manager.UnloadScene(scene); } } }; class TemporaryProjectRoot final { public: TemporaryProjectRoot() { const auto uniqueSuffix = std::chrono::steady_clock::now().time_since_epoch().count(); m_root = std::filesystem::temp_directory_path() / ("xcui_hierarchy_scene_bridge_" + std::to_string(uniqueSuffix)); } ~TemporaryProjectRoot() { std::error_code errorCode = {}; std::filesystem::remove_all(m_root, errorCode); } const std::filesystem::path& Root() const { return m_root; } private: std::filesystem::path m_root = {}; }; TEST(HierarchySceneBindingTests, BuildFromSceneUsesRealGameObjectIds) { ScopedSceneManagerReset reset = {}; Scene* scene = SceneManager::Get().CreateScene("Main"); ASSERT_NE(scene, nullptr); SceneManager::Get().SetActiveScene(scene); GameObject* root = scene->CreateGameObject("Root"); ASSERT_NE(root, nullptr); GameObject* child = scene->CreateGameObject("Child", root); ASSERT_NE(child, nullptr); const HierarchyModel model = HierarchyModel::BuildFromScene(scene); const HierarchyNode* rootNode = model.FindNode(MakeEditorGameObjectItemId(root->GetID())); ASSERT_NE(rootNode, nullptr); EXPECT_EQ(rootNode->label, "Root"); ASSERT_EQ(rootNode->children.size(), 1u); EXPECT_EQ( rootNode->children.front().nodeId, MakeEditorGameObjectItemId(child->GetID())); EXPECT_EQ(rootNode->children.front().label, "Child"); } TEST(HierarchySceneBindingTests, DuplicateGameObjectClonesHierarchyIntoScene) { ScopedSceneManagerReset reset = {}; Scene* scene = SceneManager::Get().CreateScene("Main"); ASSERT_NE(scene, nullptr); SceneManager::Get().SetActiveScene(scene); GameObject* root = scene->CreateGameObject("Root"); ASSERT_NE(root, nullptr); GameObject* child = scene->CreateGameObject("Child", root); ASSERT_NE(child, nullptr); const std::string duplicateId = DuplicateEditorGameObject(MakeEditorGameObjectItemId(root->GetID())); ASSERT_FALSE(duplicateId.empty()); const HierarchyModel model = HierarchyModel::BuildFromScene(scene); const HierarchyNode* duplicateNode = model.FindNode(duplicateId); ASSERT_NE(duplicateNode, nullptr); EXPECT_EQ(duplicateNode->label, "Root"); ASSERT_EQ(duplicateNode->children.size(), 1u); EXPECT_EQ(duplicateNode->children.front().label, "Child"); const auto roots = scene->GetRootGameObjects(); EXPECT_EQ(roots.size(), 2u); } TEST(HierarchySceneBindingTests, RenameGameObjectUpdatesRealSceneAndProjection) { ScopedSceneManagerReset reset = {}; Scene* scene = SceneManager::Get().CreateScene("Main"); ASSERT_NE(scene, nullptr); SceneManager::Get().SetActiveScene(scene); GameObject* gameObject = scene->CreateGameObject("Camera"); ASSERT_NE(gameObject, nullptr); const std::string itemId = MakeEditorGameObjectItemId(gameObject->GetID()); ASSERT_TRUE(RenameEditorGameObject(itemId, "PlayerCamera")); EXPECT_EQ(gameObject->GetName(), "PlayerCamera"); const HierarchyModel model = HierarchyModel::BuildFromScene(scene); const HierarchyNode* node = model.FindNode(itemId); ASSERT_NE(node, nullptr); EXPECT_EQ(node->label, "PlayerCamera"); } TEST(HierarchySceneBindingTests, ReparentAndMoveToRootOperateOnRealScene) { ScopedSceneManagerReset reset = {}; Scene* scene = SceneManager::Get().CreateScene("Main"); ASSERT_NE(scene, nullptr); SceneManager::Get().SetActiveScene(scene); GameObject* parentA = scene->CreateGameObject("ParentA"); ASSERT_NE(parentA, nullptr); GameObject* parentB = scene->CreateGameObject("ParentB"); ASSERT_NE(parentB, nullptr); GameObject* child = scene->CreateGameObject("Child", parentA); ASSERT_NE(child, nullptr); ASSERT_TRUE(ReparentEditorGameObject( MakeEditorGameObjectItemId(child->GetID()), MakeEditorGameObjectItemId(parentB->GetID()))); EXPECT_EQ(child->GetParent(), parentB); HierarchyModel model = HierarchyModel::BuildFromScene(scene); const HierarchyNode* parentBNode = model.FindNode(MakeEditorGameObjectItemId(parentB->GetID())); ASSERT_NE(parentBNode, nullptr); ASSERT_EQ(parentBNode->children.size(), 1u); EXPECT_EQ(parentBNode->children.front().label, "Child"); ASSERT_TRUE(MoveEditorGameObjectToRoot( MakeEditorGameObjectItemId(child->GetID()))); EXPECT_EQ(child->GetParent(), nullptr); model = HierarchyModel::BuildFromScene(scene); const auto roots = scene->GetRootGameObjects(); EXPECT_EQ(roots.size(), 3u); } TEST(HierarchySceneBindingTests, EnsureStartupSceneLoadsMainSceneAndSetsActive) { ScopedSceneManagerReset reset = {}; TemporaryProjectRoot projectRoot = {}; const std::filesystem::path scenePath = projectRoot.Root() / "Assets" / "Scenes" / "Main.xc"; std::filesystem::create_directories(scenePath.parent_path()); { Scene scene("Main"); scene.CreateGameObject("Camera"); scene.Save(scenePath.string()); } const EditorStartupSceneResult result = EnsureEditorStartupScene(projectRoot.Root()); EXPECT_TRUE(result.ready); EXPECT_TRUE(result.loadedFromDisk); ASSERT_NE(GetActiveEditorScene(), nullptr); EXPECT_EQ(GetActiveEditorScene()->GetName(), "Main"); const HierarchyModel model = HierarchyModel::BuildFromScene(GetActiveEditorScene()); EXPECT_FALSE(model.Empty()); SceneManager& sceneManager = SceneManager::Get(); const auto scenes = sceneManager.GetAllScenes(); for (Scene* scene : scenes) { sceneManager.UnloadScene(scene); } ::XCEngine::Resources::ResourceManager::Get().Shutdown(); } } // namespace } // namespace XCEngine::UI::Editor::App