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

246 lines
8.3 KiB
C++

#include "Features/Project/ProjectPanel.h"
#include "Rendering/Assets/BuiltInIcons.h"
#include "Composition/EditorPanelIds.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_panel_" + 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 = {};
};
UIEditorPanelContentHostFrame MakeProjectHostFrame() {
UIEditorPanelContentHostFrame frame = {};
UIEditorPanelContentHostPanelState panelState = {};
panelState.panelId = std::string(kProjectPanelId);
panelState.kind = UIEditorPanelPresentationKind::HostedContent;
panelState.mounted = true;
panelState.bounds = ::XCEngine::UI::UIRect(0.0f, 0.0f, 640.0f, 360.0f);
frame.panelStates.push_back(std::move(panelState));
return frame;
}
::XCEngine::UI::UIInputEvent MakePointerButtonDown(
float x,
float y,
::XCEngine::UI::UIPointerButton button) {
::XCEngine::UI::UIInputEvent event = {};
event.type = ::XCEngine::UI::UIInputEventType::PointerButtonDown;
event.position = ::XCEngine::UI::UIPoint(x, y);
event.pointerButton = button;
return event;
}
TEST(ProjectPanelTests, CreateFolderCommandCreatesDirectoryAndQueuesRename) {
TemporaryRepo repo = {};
ProjectPanel panel = {};
panel.Initialize(repo.Root());
const UIEditorHostCommandEvaluationResult evaluation =
panel.EvaluateAssetCommand("assets.create_folder");
EXPECT_TRUE(evaluation.executable);
const UIEditorHostCommandDispatchResult dispatch =
panel.DispatchAssetCommand("assets.create_folder");
EXPECT_TRUE(dispatch.commandExecuted);
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/New Folder"));
const auto& events = panel.GetFrameEvents();
ASSERT_EQ(events.size(), 2u);
EXPECT_EQ(events[0].kind, ProjectPanel::EventKind::AssetSelected);
EXPECT_EQ(events[0].source, ProjectPanel::EventSource::Command);
EXPECT_EQ(events[0].itemId, "Assets/New Folder");
EXPECT_EQ(events[1].kind, ProjectPanel::EventKind::RenameRequested);
EXPECT_EQ(events[1].source, ProjectPanel::EventSource::Command);
EXPECT_EQ(events[1].itemId, "Assets/New Folder");
}
TEST(ProjectPanelTests, CreateMaterialCommandCreatesFileAndQueuesRename) {
TemporaryRepo repo = {};
ProjectPanel panel = {};
panel.Initialize(repo.Root());
const UIEditorHostCommandEvaluationResult evaluation =
panel.EvaluateAssetCommand("assets.create_material");
EXPECT_TRUE(evaluation.executable);
const UIEditorHostCommandDispatchResult dispatch =
panel.DispatchAssetCommand("assets.create_material");
EXPECT_TRUE(dispatch.commandExecuted);
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/New Material.mat"));
const auto& events = panel.GetFrameEvents();
ASSERT_EQ(events.size(), 2u);
EXPECT_EQ(events[0].kind, ProjectPanel::EventKind::AssetSelected);
EXPECT_EQ(events[0].itemId, "Assets/New Material.mat");
EXPECT_EQ(events[1].kind, ProjectPanel::EventKind::RenameRequested);
EXPECT_EQ(events[1].itemId, "Assets/New Material.mat");
}
TEST(ProjectPanelTests, BackgroundContextMenuCreateFolderUsesCurrentFolder) {
TemporaryRepo repo = {};
ProjectPanel panel = {};
panel.Initialize(repo.Root());
const UIEditorPanelContentHostFrame hostFrame = MakeProjectHostFrame();
panel.Update(
hostFrame,
{ MakePointerButtonDown(520.0f, 180.0f, ::XCEngine::UI::UIPointerButton::Right) },
true,
true);
panel.Update(
hostFrame,
{ MakePointerButtonDown(520.0f, 194.0f, ::XCEngine::UI::UIPointerButton::Left) },
true,
true);
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/New Folder"));
const UIEditorHostCommandEvaluationResult renameEvaluation =
panel.EvaluateEditCommand("edit.rename");
EXPECT_TRUE(renameEvaluation.executable);
EXPECT_EQ(renameEvaluation.message, "Rename project item 'New Folder'.");
const auto& events = panel.GetFrameEvents();
ASSERT_FALSE(events.empty());
EXPECT_EQ(events.back().kind, ProjectPanel::EventKind::RenameRequested);
EXPECT_EQ(events.back().itemId, "Assets/New Folder");
}
TEST(ProjectPanelTests, FolderContextMenuCreateFolderUsesFolderTarget) {
TemporaryRepo repo = {};
ASSERT_TRUE(std::filesystem::create_directories(repo.Root() / "project/Assets/FolderA"));
ProjectPanel panel = {};
panel.Initialize(repo.Root());
const UIEditorPanelContentHostFrame hostFrame = MakeProjectHostFrame();
panel.Update(
hostFrame,
{ MakePointerButtonDown(300.0f, 80.0f, ::XCEngine::UI::UIPointerButton::Right) },
true,
true);
panel.Update(
hostFrame,
{ MakePointerButtonDown(320.0f, 120.0f, ::XCEngine::UI::UIPointerButton::Left) },
true,
true);
EXPECT_TRUE(std::filesystem::exists(repo.Root() / "project/Assets/FolderA/New Folder"));
}
TEST(ProjectPanelTests, InjectedRuntimeSelectionDrivesRenameWithoutPanelSelectionSync) {
TemporaryRepo repo = {};
ASSERT_TRUE(repo.CreateDirectory("project/Assets/Scripts"));
ASSERT_TRUE(repo.WriteFile("project/Assets/Scripts/Player.cs"));
ASSERT_TRUE(repo.WriteFile("project/Assets/Scripts/Player.cs.meta"));
EditorProjectRuntime runtime = {};
ASSERT_TRUE(runtime.Initialize(repo.Root()));
ProjectPanel panel = {};
panel.SetProjectRuntime(&runtime);
ASSERT_TRUE(runtime.NavigateToFolder("Assets/Scripts"));
ASSERT_TRUE(runtime.SetSelection("Assets/Scripts/Player.cs"));
const UIEditorHostCommandDispatchResult dispatch =
panel.DispatchEditCommand("edit.rename");
EXPECT_TRUE(dispatch.commandExecuted);
const auto& events = panel.GetFrameEvents();
ASSERT_EQ(events.size(), 1u);
EXPECT_EQ(events[0].kind, ProjectPanel::EventKind::RenameRequested);
EXPECT_EQ(events[0].source, ProjectPanel::EventSource::GridPrimary);
EXPECT_EQ(events[0].itemId, "Assets/Scripts/Player.cs");
}
TEST(ProjectPanelTests, InjectedRuntimeCurrentFolderDrivesRenameFallbackWithoutTreeSync) {
TemporaryRepo repo = {};
ASSERT_TRUE(repo.CreateDirectory("project/Assets/FolderA"));
EditorProjectRuntime runtime = {};
ASSERT_TRUE(runtime.Initialize(repo.Root()));
ProjectPanel panel = {};
panel.SetProjectRuntime(&runtime);
ASSERT_TRUE(runtime.NavigateToFolder("Assets/FolderA"));
const UIEditorHostCommandEvaluationResult evaluation =
panel.EvaluateEditCommand("edit.rename");
EXPECT_TRUE(evaluation.executable);
EXPECT_EQ(evaluation.message, "Rename project item 'FolderA'.");
}
TEST(ProjectPanelTests, BuiltInIconsCanBeConfiguredBeforeRuntimeInitialization) {
TemporaryRepo repo = {};
ProjectPanel panel = {};
BuiltInIcons icons = {};
panel.SetBuiltInIcons(&icons);
panel.Initialize(repo.Root());
const UIEditorHostCommandEvaluationResult evaluation =
panel.EvaluateAssetCommand("assets.create_folder");
EXPECT_TRUE(evaluation.executable);
}
} // namespace
} // namespace XCEngine::UI::Editor::App