Add XCUI new editor sandbox phase 1
This commit is contained in:
257
tests/NewEditor/test_xcui_asset_document_source.cpp
Normal file
257
tests/NewEditor/test_xcui_asset_document_source.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "XCUIBackend/XCUIAssetDocumentSource.h"
|
||||
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Containers/String.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
using XCEngine::Containers::String;
|
||||
using XCEngine::Editor::XCUIBackend::XCUIAssetDocumentSource;
|
||||
using XCEngine::Resources::ResourceManager;
|
||||
|
||||
String ToContainersString(const std::string& value) {
|
||||
return String(value.c_str());
|
||||
}
|
||||
|
||||
void WriteTextFile(const fs::path& path, const std::string& contents) {
|
||||
fs::create_directories(path.parent_path());
|
||||
std::ofstream output(path, std::ios::binary | std::ios::trunc);
|
||||
output << contents;
|
||||
}
|
||||
|
||||
std::string BuildMinimalViewDocument(const std::string& themeReference) {
|
||||
return
|
||||
"<View name=\"Test\" theme=\"" + themeReference + "\">\n"
|
||||
" <Column id=\"root\">\n"
|
||||
" <Text text=\"hello\" />\n"
|
||||
" </Column>\n"
|
||||
"</View>\n";
|
||||
}
|
||||
|
||||
std::string BuildMinimalThemeDocument() {
|
||||
return
|
||||
"<Theme name=\"TestTheme\">\n"
|
||||
" <Token name=\"color.text.primary\" type=\"color\" value=\"#FFFFFF\" />\n"
|
||||
"</Theme>\n";
|
||||
}
|
||||
|
||||
class ScopedCurrentPath {
|
||||
public:
|
||||
explicit ScopedCurrentPath(const fs::path& newPath) {
|
||||
m_originalPath = fs::current_path();
|
||||
fs::create_directories(newPath);
|
||||
fs::current_path(newPath);
|
||||
}
|
||||
|
||||
~ScopedCurrentPath() {
|
||||
if (!m_originalPath.empty()) {
|
||||
fs::current_path(m_originalPath);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
fs::path m_originalPath = {};
|
||||
};
|
||||
|
||||
class XCUIAssetDocumentSourceTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
m_originalResourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
ResourceManager::Get().SetResourceRoot(String());
|
||||
|
||||
m_tempRoot = fs::temp_directory_path() /
|
||||
fs::path("xcui_asset_document_source_tests");
|
||||
m_tempRoot /= fs::path(::testing::UnitTest::GetInstance()->current_test_info()->name());
|
||||
fs::remove_all(m_tempRoot);
|
||||
fs::create_directories(m_tempRoot);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
ResourceManager::Get().UnloadAll();
|
||||
ResourceManager::Get().SetResourceRoot(m_originalResourceRoot);
|
||||
|
||||
std::error_code ec;
|
||||
fs::remove_all(m_tempRoot, ec);
|
||||
}
|
||||
|
||||
fs::path CreateRepositorySubdir(const std::string& relativePath) const {
|
||||
const fs::path path = m_tempRoot / fs::path(relativePath);
|
||||
fs::create_directories(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
void WriteProjectDocuments(const XCUIAssetDocumentSource::PathSet& paths) const {
|
||||
WriteTextFile(
|
||||
m_tempRoot / fs::path(paths.view.primaryRelativePath),
|
||||
BuildMinimalViewDocument("Theme.xctheme"));
|
||||
WriteTextFile(
|
||||
m_tempRoot / fs::path(paths.theme.primaryRelativePath),
|
||||
BuildMinimalThemeDocument());
|
||||
}
|
||||
|
||||
void WriteLegacyDocuments(const XCUIAssetDocumentSource::PathSet& paths) const {
|
||||
WriteTextFile(
|
||||
m_tempRoot / fs::path(paths.view.legacyRelativePath),
|
||||
BuildMinimalViewDocument(
|
||||
fs::path(paths.theme.legacyRelativePath).filename().generic_string()));
|
||||
WriteTextFile(
|
||||
m_tempRoot / fs::path(paths.theme.legacyRelativePath),
|
||||
BuildMinimalThemeDocument());
|
||||
}
|
||||
|
||||
fs::path m_tempRoot = {};
|
||||
|
||||
private:
|
||||
String m_originalResourceRoot = {};
|
||||
};
|
||||
|
||||
TEST_F(XCUIAssetDocumentSourceTest, DemoAndLayoutLabPathSetsUseExpectedPaths) {
|
||||
const XCUIAssetDocumentSource::PathSet demoPaths =
|
||||
XCUIAssetDocumentSource::MakeDemoPathSet();
|
||||
EXPECT_EQ(demoPaths.setName, "Demo");
|
||||
EXPECT_EQ(demoPaths.view.primaryRelativePath, "Assets/XCUI/NewEditor/Demo/View.xcui");
|
||||
EXPECT_EQ(demoPaths.theme.primaryRelativePath, "Assets/XCUI/NewEditor/Demo/Theme.xctheme");
|
||||
EXPECT_EQ(demoPaths.view.legacyRelativePath, "new_editor/resources/xcui_demo_view.xcui");
|
||||
EXPECT_EQ(demoPaths.theme.legacyRelativePath, "new_editor/resources/xcui_demo_theme.xctheme");
|
||||
|
||||
const XCUIAssetDocumentSource::PathSet layoutLabPaths =
|
||||
XCUIAssetDocumentSource::MakeLayoutLabPathSet();
|
||||
EXPECT_EQ(layoutLabPaths.setName, "LayoutLab");
|
||||
EXPECT_EQ(layoutLabPaths.view.primaryRelativePath, "Assets/XCUI/NewEditor/LayoutLab/View.xcui");
|
||||
EXPECT_EQ(layoutLabPaths.theme.primaryRelativePath, "Assets/XCUI/NewEditor/LayoutLab/Theme.xctheme");
|
||||
EXPECT_EQ(layoutLabPaths.view.legacyRelativePath, "new_editor/resources/xcui_layout_lab_view.xcui");
|
||||
EXPECT_EQ(layoutLabPaths.theme.legacyRelativePath, "new_editor/resources/xcui_layout_lab_theme.xctheme");
|
||||
}
|
||||
|
||||
TEST_F(XCUIAssetDocumentSourceTest, MakePathSetSanitizesNamesAndBuildsLegacySnakeCase) {
|
||||
const XCUIAssetDocumentSource::PathSet paths =
|
||||
XCUIAssetDocumentSource::MakePathSet(" Layout Lab! Debug-42 ");
|
||||
|
||||
EXPECT_EQ(paths.setName, "LayoutLabDebug-42");
|
||||
EXPECT_EQ(
|
||||
paths.view.primaryRelativePath,
|
||||
"Assets/XCUI/NewEditor/LayoutLabDebug-42/View.xcui");
|
||||
EXPECT_EQ(
|
||||
paths.theme.legacyRelativePath,
|
||||
"new_editor/resources/xcui_layout_lab_debug_42_theme.xctheme");
|
||||
}
|
||||
|
||||
TEST_F(XCUIAssetDocumentSourceTest, CollectCandidatePathsPrefersProjectAssetsBeforeLegacyMirror) {
|
||||
const XCUIAssetDocumentSource::PathSet paths =
|
||||
XCUIAssetDocumentSource::MakePathSet("Worker1CandidateOrder");
|
||||
WriteProjectDocuments(paths);
|
||||
WriteLegacyDocuments(paths);
|
||||
|
||||
const std::vector<XCUIAssetDocumentSource::ResolutionCandidate> candidates =
|
||||
XCUIAssetDocumentSource::CollectCandidatePaths(paths.view, m_tempRoot, fs::path());
|
||||
|
||||
ASSERT_EQ(candidates.size(), 2u);
|
||||
EXPECT_EQ(candidates[0].origin, XCUIAssetDocumentSource::PathOrigin::ProjectAssets);
|
||||
EXPECT_EQ(candidates[0].resolvedPath, fs::path(m_tempRoot / paths.view.primaryRelativePath).lexically_normal());
|
||||
EXPECT_EQ(candidates[1].origin, XCUIAssetDocumentSource::PathOrigin::LegacyMirror);
|
||||
EXPECT_EQ(candidates[1].resolvedPath, fs::path(m_tempRoot / paths.view.legacyRelativePath).lexically_normal());
|
||||
}
|
||||
|
||||
TEST_F(XCUIAssetDocumentSourceTest, DiagnoseRepositoryRootReportsProjectAssetAncestor) {
|
||||
const XCUIAssetDocumentSource::PathSet paths =
|
||||
XCUIAssetDocumentSource::MakeDemoPathSet();
|
||||
WriteProjectDocuments(paths);
|
||||
|
||||
const fs::path searchRoot = CreateRepositorySubdir("tools/worker1/deep");
|
||||
const XCUIAssetDocumentSource::RepositoryDiscovery discovery =
|
||||
XCUIAssetDocumentSource::DiagnoseRepositoryRoot(paths, { searchRoot }, false);
|
||||
|
||||
EXPECT_EQ(discovery.repositoryRoot, m_tempRoot.lexically_normal());
|
||||
ASSERT_EQ(discovery.probes.size(), 1u);
|
||||
EXPECT_TRUE(discovery.probes[0].matched);
|
||||
EXPECT_EQ(discovery.probes[0].searchRoot, searchRoot.lexically_normal());
|
||||
EXPECT_EQ(discovery.probes[0].matchedRelativePath, paths.view.primaryRelativePath);
|
||||
EXPECT_NE(discovery.statusMessage.find(paths.view.primaryRelativePath), std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(XCUIAssetDocumentSourceTest, DiagnoseRepositoryRootReportsLegacyMirrorAncestor) {
|
||||
const XCUIAssetDocumentSource::PathSet paths =
|
||||
XCUIAssetDocumentSource::MakeLayoutLabPathSet();
|
||||
WriteLegacyDocuments(paths);
|
||||
|
||||
const fs::path searchRoot = CreateRepositorySubdir("sandbox/runtime/session");
|
||||
const XCUIAssetDocumentSource::RepositoryDiscovery discovery =
|
||||
XCUIAssetDocumentSource::DiagnoseRepositoryRoot(paths, { searchRoot }, false);
|
||||
|
||||
EXPECT_EQ(discovery.repositoryRoot, m_tempRoot.lexically_normal());
|
||||
ASSERT_EQ(discovery.probes.size(), 1u);
|
||||
EXPECT_TRUE(discovery.probes[0].matched);
|
||||
EXPECT_EQ(discovery.probes[0].matchedRelativePath, paths.view.legacyRelativePath);
|
||||
EXPECT_NE(discovery.statusMessage.find(paths.view.legacyRelativePath), std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(XCUIAssetDocumentSourceTest, ReloadUsesLegacyFallbackAndTracksSourceChanges) {
|
||||
const XCUIAssetDocumentSource::PathSet paths =
|
||||
XCUIAssetDocumentSource::MakePathSet("Worker1HotReloadRegression");
|
||||
WriteLegacyDocuments(paths);
|
||||
|
||||
const fs::path sandboxPath = CreateRepositorySubdir("runtime/worker1");
|
||||
ScopedCurrentPath scopedCurrentPath(sandboxPath);
|
||||
|
||||
XCUIAssetDocumentSource source(paths);
|
||||
ASSERT_TRUE(source.Reload());
|
||||
|
||||
const XCUIAssetDocumentSource::LoadState& initialState = source.GetState();
|
||||
EXPECT_TRUE(initialState.succeeded);
|
||||
EXPECT_TRUE(initialState.usedLegacyFallback);
|
||||
EXPECT_TRUE(initialState.changeTrackingReady);
|
||||
EXPECT_TRUE(initialState.missingTrackedSourcePaths.empty());
|
||||
EXPECT_EQ(initialState.repositoryRoot, m_tempRoot.lexically_normal());
|
||||
EXPECT_EQ(initialState.view.pathOrigin, XCUIAssetDocumentSource::PathOrigin::LegacyMirror);
|
||||
EXPECT_EQ(initialState.theme.pathOrigin, XCUIAssetDocumentSource::PathOrigin::LegacyMirror);
|
||||
EXPECT_FALSE(initialState.trackedSourcePaths.empty());
|
||||
EXPECT_NE(initialState.trackingStatusMessage.find("Tracking "), std::string::npos);
|
||||
EXPECT_FALSE(source.HasTrackedChanges());
|
||||
|
||||
const fs::path themePath = m_tempRoot / fs::path(paths.theme.legacyRelativePath);
|
||||
fs::last_write_time(
|
||||
themePath,
|
||||
fs::last_write_time(themePath) + std::chrono::seconds(2));
|
||||
|
||||
EXPECT_TRUE(source.HasTrackedChanges());
|
||||
ASSERT_TRUE(source.ReloadIfChanged());
|
||||
|
||||
const XCUIAssetDocumentSource::LoadState& reloadedState = source.GetState();
|
||||
EXPECT_EQ(
|
||||
reloadedState.view.backend,
|
||||
XCUIAssetDocumentSource::LoadBackend::CompilerFallback);
|
||||
EXPECT_EQ(
|
||||
reloadedState.theme.backend,
|
||||
XCUIAssetDocumentSource::LoadBackend::CompilerFallback);
|
||||
EXPECT_TRUE(reloadedState.changeTrackingReady);
|
||||
}
|
||||
|
||||
TEST_F(XCUIAssetDocumentSourceTest, ReloadFailureIncludesRepositoryDiscoveryDiagnostic) {
|
||||
const XCUIAssetDocumentSource::PathSet paths =
|
||||
XCUIAssetDocumentSource::MakePathSet("Worker1MissingDocuments");
|
||||
const fs::path sandboxPath = CreateRepositorySubdir("runtime/missing");
|
||||
ScopedCurrentPath scopedCurrentPath(sandboxPath);
|
||||
|
||||
XCUIAssetDocumentSource source(paths);
|
||||
EXPECT_FALSE(source.Reload());
|
||||
|
||||
const XCUIAssetDocumentSource::LoadState& state = source.GetState();
|
||||
EXPECT_FALSE(state.succeeded);
|
||||
EXPECT_TRUE(state.repositoryRoot.empty());
|
||||
EXPECT_NE(state.repositoryDiscovery.statusMessage.find("Repository root not found"), std::string::npos);
|
||||
EXPECT_NE(state.errorMessage.find(paths.view.primaryRelativePath), std::string::npos);
|
||||
EXPECT_NE(state.errorMessage.find("Repository root not found"), std::string::npos);
|
||||
EXPECT_TRUE(state.view.candidatePaths.empty());
|
||||
EXPECT_TRUE(state.view.attemptMessages.empty());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user