263 lines
10 KiB
C++
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
|