Files
XCEngine/tests/UI/Editor/unit/test_project_browser_model.cpp

263 lines
10 KiB
C++

#include "Features/Project/ProjectBrowserModel.h"
#include <gtest/gtest.h>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <string>
namespace XCEngine::UI::Editor::App {
namespace {
class TemporaryRepo final {
public:
TemporaryRepo() {
const auto uniqueSuffix =
std::chrono::steady_clock::now().time_since_epoch().count();
m_root =
std::filesystem::temp_directory_path() /
("xcengine_project_browser_model_" + std::to_string(uniqueSuffix));
}
~TemporaryRepo() {
std::error_code errorCode = {};
std::filesystem::remove_all(m_root, errorCode);
}
const std::filesystem::path& Root() const {
return m_root;
}
bool CreateDirectory(const std::filesystem::path& relativePath) const {
std::error_code errorCode = {};
std::filesystem::create_directories(m_root / relativePath, errorCode);
return !errorCode;
}
bool WriteFile(
const std::filesystem::path& relativePath,
std::string_view contents = "test") const {
const std::filesystem::path absolutePath = m_root / relativePath;
std::error_code errorCode = {};
std::filesystem::create_directories(absolutePath.parent_path(), errorCode);
if (errorCode) {
return false;
}
std::ofstream stream(absolutePath, std::ios::binary);
if (!stream.is_open()) {
return false;
}
stream << contents;
return stream.good();
}
private:
std::filesystem::path m_root = {};
};
TEST(ProjectBrowserModelTests, ReparentFolderMovesFolderMetaAndRemapsCurrentFolder) {
TemporaryRepo repo = {};
ASSERT_TRUE(repo.CreateDirectory("project/Assets/A/Child"));
ASSERT_TRUE(repo.CreateDirectory("project/Assets/B"));
ASSERT_TRUE(repo.WriteFile("project/Assets/A.meta"));
ASSERT_TRUE(repo.WriteFile("project/Assets/A/Child.meta"));
ASSERT_TRUE(repo.WriteFile("project/Assets/B.meta"));
ProjectBrowserModel model = {};
model.Initialize(repo.Root());
ASSERT_TRUE(model.NavigateToFolder("Assets/A/Child"));
std::string movedFolderId = {};
ASSERT_TRUE(model.ReparentFolder("Assets/A", "Assets/B", &movedFolderId));
EXPECT_EQ(movedFolderId, "Assets/B/A");
EXPECT_EQ(model.GetCurrentFolderId(), "Assets/B/A/Child");
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/B/A"));
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/B/A.meta"));
EXPECT_FALSE(std::filesystem::exists(repo.Root() / "project/Assets/A"));
EXPECT_FALSE(std::filesystem::exists(repo.Root() / "project/Assets/A.meta"));
}
TEST(ProjectBrowserModelTests, MoveFolderToRootMovesFolderMetaAndRemapsCurrentFolder) {
TemporaryRepo repo = {};
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Parent/Nested"));
ASSERT_TRUE(repo.WriteFile("project/Assets/Parent.meta"));
ASSERT_TRUE(repo.WriteFile("project/Assets/Parent/Nested.meta"));
ProjectBrowserModel model = {};
model.Initialize(repo.Root());
ASSERT_TRUE(model.NavigateToFolder("Assets/Parent/Nested"));
std::string movedFolderId = {};
ASSERT_TRUE(model.MoveFolderToRoot("Assets/Parent/Nested", &movedFolderId));
EXPECT_EQ(movedFolderId, "Assets/Nested");
EXPECT_EQ(model.GetCurrentFolderId(), "Assets/Nested");
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/Nested"));
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/Nested.meta"));
EXPECT_FALSE(std::filesystem::exists(repo.Root() / "project/Assets/Parent/Nested"));
EXPECT_FALSE(std::filesystem::exists(repo.Root() / "project/Assets/Parent/Nested.meta"));
}
TEST(ProjectBrowserModelTests, CreateFolderCreatesUniqueDirectoryUnderTargetFolder) {
TemporaryRepo repo = {};
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Scenes"));
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Scenes/New Folder"));
ProjectBrowserModel model = {};
model.Initialize(repo.Root());
std::string createdFolderId = {};
ASSERT_TRUE(model.CreateFolder("Assets/Scenes", "New Folder", &createdFolderId));
EXPECT_EQ(createdFolderId, "Assets/Scenes/New Folder 1");
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/Scenes/New Folder 1"));
}
TEST(ProjectBrowserModelTests, CreateMaterialCreatesUniqueMaterialFileAndExposesRelativePath) {
TemporaryRepo repo = {};
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Materials"));
ASSERT_TRUE(repo.WriteFile("project/Assets/Materials/New Material.mat"));
ProjectBrowserModel model = {};
model.Initialize(repo.Root());
std::string createdItemId = {};
ASSERT_TRUE(model.CreateMaterial("Assets/Materials", "New Material", &createdItemId));
EXPECT_EQ(createdItemId, "Assets/Materials/New Material 1.mat");
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/Materials/New Material 1.mat"));
EXPECT_EQ(
model.BuildProjectRelativePath(createdItemId),
"Assets/Materials/New Material 1.mat");
ASSERT_TRUE(model.NavigateToFolder("Assets/Materials"));
const ProjectBrowserModel::AssetEntry* createdEntry =
model.FindAssetEntry(createdItemId);
ASSERT_NE(createdEntry, nullptr);
EXPECT_EQ(createdEntry->kind, ProjectBrowserModel::ItemKind::Material);
EXPECT_EQ(createdEntry->displayName, "New Material 1");
EXPECT_EQ(createdEntry->nameWithExtension, "New Material 1.mat");
}
TEST(ProjectBrowserModelTests, CanMoveItemToFolderRejectsDescendantFolderTargets) {
TemporaryRepo repo = {};
ASSERT_TRUE(repo.CreateDirectory("project/Assets/FolderA/Nested"));
ASSERT_TRUE(repo.CreateDirectory("project/Assets/FolderB"));
ProjectBrowserModel model = {};
model.Initialize(repo.Root());
EXPECT_FALSE(model.CanMoveItemToFolder("Assets/FolderA", "Assets/FolderA/Nested"));
EXPECT_TRUE(model.CanMoveItemToFolder("Assets/FolderA", "Assets/FolderB"));
}
TEST(ProjectBrowserModelTests, MoveItemToFolderMovesFileMetaAndRefreshesCurrentListing) {
TemporaryRepo repo = {};
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Scripts"));
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Archive"));
ASSERT_TRUE(repo.WriteFile("project/Assets/Scripts/Player.cs"));
ASSERT_TRUE(repo.WriteFile("project/Assets/Scripts/Player.cs.meta"));
ProjectBrowserModel model = {};
model.Initialize(repo.Root());
ASSERT_TRUE(model.NavigateToFolder("Assets/Scripts"));
std::string movedItemId = {};
ASSERT_TRUE(model.MoveItemToFolder(
"Assets/Scripts/Player.cs",
"Assets/Archive",
&movedItemId));
EXPECT_EQ(movedItemId, "Assets/Archive/Player.cs");
EXPECT_EQ(model.GetCurrentFolderId(), "Assets/Scripts");
EXPECT_FALSE(std::filesystem::exists(repo.Root() / "project/Assets/Scripts/Player.cs"));
EXPECT_FALSE(std::filesystem::exists(repo.Root() / "project/Assets/Scripts/Player.cs.meta"));
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/Archive/Player.cs"));
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/Archive/Player.cs.meta"));
EXPECT_EQ(model.FindAssetEntry("Assets/Scripts/Player.cs"), nullptr);
ASSERT_TRUE(model.NavigateToFolder("Assets/Archive"));
EXPECT_NE(model.FindAssetEntry("Assets/Archive/Player.cs"), nullptr);
}
TEST(ProjectBrowserModelTests, RenameFilePreservesExtensionAndUpdatesCurrentListing) {
TemporaryRepo repo = {};
ASSERT_TRUE(repo.WriteFile("project/Assets/Scenes/Main.xc"));
ASSERT_TRUE(repo.WriteFile("project/Assets/Scenes/Main.xc.meta"));
ProjectBrowserModel model = {};
model.Initialize(repo.Root());
ASSERT_TRUE(model.NavigateToFolder("Assets/Scenes"));
std::string renamedItemId = {};
ASSERT_TRUE(model.RenameItem("Assets/Scenes/Main.xc", "Gameplay", &renamedItemId));
EXPECT_EQ(renamedItemId, "Assets/Scenes/Gameplay.xc");
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/Scenes/Gameplay.xc"));
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/Scenes/Gameplay.xc.meta"));
EXPECT_FALSE(std::filesystem::exists(repo.Root() / "project/Assets/Scenes/Main.xc"));
EXPECT_FALSE(std::filesystem::exists(repo.Root() / "project/Assets/Scenes/Main.xc.meta"));
const ProjectBrowserModel::AssetEntry* renamedEntry =
model.FindAssetEntry("Assets/Scenes/Gameplay.xc");
ASSERT_NE(renamedEntry, nullptr);
EXPECT_EQ(renamedEntry->displayName, "Gameplay");
EXPECT_EQ(renamedEntry->nameWithExtension, "Gameplay.xc");
}
TEST(ProjectBrowserModelTests, DeleteFolderRemovesMetaAndFallsBackCurrentFolder) {
TemporaryRepo repo = {};
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Parent/Nested"));
ASSERT_TRUE(repo.WriteFile("project/Assets/Parent.meta"));
ASSERT_TRUE(repo.WriteFile("project/Assets/Parent/Nested.meta"));
ProjectBrowserModel model = {};
model.Initialize(repo.Root());
ASSERT_TRUE(model.NavigateToFolder("Assets/Parent/Nested"));
ASSERT_TRUE(model.DeleteItem("Assets/Parent"));
EXPECT_EQ(model.GetCurrentFolderId(), "Assets");
EXPECT_FALSE(std::filesystem::exists(repo.Root() / "project/Assets/Parent"));
EXPECT_FALSE(std::filesystem::exists(repo.Root() / "project/Assets/Parent.meta"));
}
TEST(ProjectBrowserModelTests, AssetEntriesExposeKindAndOpenCapability) {
TemporaryRepo repo = {};
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Scenes"));
ASSERT_TRUE(repo.WriteFile("project/Assets/Scenes/Main.xc"));
ASSERT_TRUE(repo.WriteFile("project/Assets/mesh.fbx"));
ASSERT_TRUE(repo.WriteFile("project/Assets/readme.txt"));
ProjectBrowserModel model = {};
model.Initialize(repo.Root());
const ProjectBrowserModel::AssetEntry* sceneEntry =
model.FindAssetEntry("Assets/mesh.fbx");
ASSERT_NE(sceneEntry, nullptr);
EXPECT_EQ(sceneEntry->kind, ProjectBrowserModel::ItemKind::Model);
EXPECT_FALSE(sceneEntry->canOpen);
const ProjectBrowserModel::AssetEntry* fileEntry =
model.FindAssetEntry("Assets/readme.txt");
ASSERT_NE(fileEntry, nullptr);
EXPECT_EQ(fileEntry->kind, ProjectBrowserModel::ItemKind::File);
EXPECT_FALSE(fileEntry->canOpen);
ASSERT_TRUE(model.NavigateToFolder("Assets/Scenes"));
const ProjectBrowserModel::AssetEntry* openedSceneEntry =
model.FindAssetEntry("Assets/Scenes/Main.xc");
ASSERT_NE(openedSceneEntry, nullptr);
EXPECT_EQ(openedSceneEntry->kind, ProjectBrowserModel::ItemKind::Scene);
EXPECT_TRUE(openedSceneEntry->canOpen);
}
} // namespace
} // namespace XCEngine::UI::Editor::App