Files
EVEngine/test/editor_prefab.cpp
T

143 lines
6.8 KiB
C++

#include "editor/EditorPrefab.h"
#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
using namespace eve::editor;
namespace {
void applyAll(SceneTargetBase& scene, const std::vector<DomainOperation>& operations) {
for (const DomainOperation& operation : operations) REQUIRE(scene.applyDomainOperation(operation).isAccepted());
}
SceneDocumentTarget sourceScene() {
SceneDocumentTarget scene("source");
auto root = scene.makeCreate({ObjectId("vehicle"), {}, "Vehicle", {1.0, 2.0, 3.0}});
REQUIRE(root.value);
REQUIRE(scene.applyDomainOperation(*root.value).isAccepted());
auto wheel = scene.makeCreate({ObjectId("wheel"), ObjectId("vehicle"), "Wheel",
{0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.8, 0.8, 0.8}});
REQUIRE(wheel.value);
REQUIRE(scene.applyDomainOperation(*wheel.value).isAccepted());
return scene;
}
} // namespace
TEST_CASE("editor.prefab.capture_and_instantiate_preserve_stable_mapping") {
ScenePrefabService prefabs;
SceneDocumentTarget source = sourceScene();
auto captured = prefabs.capture(AssetGuid("asset://prefabs/vehicle"), source, ObjectId("vehicle"));
REQUIRE(captured.value);
CHECK_EQ(captured.value->objects.size(), size_t{2});
SceneDocumentTarget level("level");
auto parent = level.makeCreate({ObjectId("garage"), {}, "Garage", {}});
REQUIRE(parent.value);
REQUIRE(level.applyDomainOperation(*parent.value).isAccepted());
auto instance = prefabs.instantiate(*captured.value, "vehicle-01", ObjectId("garage"), level);
REQUIRE(instance.value);
CHECK_EQ(instance.value->sourceRevision, captured.value->revision);
CHECK_EQ(instance.value->operations.size(), size_t{2});
applyAll(level, instance.value->operations);
CHECK_EQ(level.sceneObject(ObjectId("vehicle-01/vehicle")).value->parent, ObjectId("garage"));
CHECK_EQ(level.sceneObject(ObjectId("vehicle-01/wheel")).value->parent,
ObjectId("vehicle-01/vehicle"));
const auto collision = prefabs.instantiate(*captured.value, "vehicle-01", ObjectId("garage"), level);
CHECK_EQ(static_cast<int>(collision.status), static_cast<int>(EditorStatus::Conflict));
}
TEST_CASE("editor.prefab_apply_and_revert_overrides_are_revisioned_and_reversible") {
ScenePrefabService prefabs;
SceneDocumentTarget source = sourceScene();
auto captured = prefabs.capture(AssetGuid("vehicle"), source, ObjectId("vehicle"));
REQUIRE(captured.value);
SceneDocumentTarget level("level-overrides");
auto instance = prefabs.instantiate(*captured.value, "instance", {}, level);
REQUIRE(instance.value);
applyAll(level, instance.value->operations);
auto rename = level.makeRename(ObjectId("instance/wheel"), "Big Wheel");
REQUIRE(rename.value);
REQUIRE(level.applyDomainOperation(*rename.value).isAccepted());
SceneTransformValue changed{2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.5, 1.5, 1.5};
auto transform = level.makeSetTransform(ObjectId("instance/wheel"), changed);
REQUIRE(transform.value);
REQUIRE(level.applyDomainOperation(*transform.value).isAccepted());
auto badges = prefabs.inspectOverrides(*captured.value, "instance", {}, level);
REQUIRE(badges.value);
CHECK_EQ(badges.value->size(), size_t{2});
CHECK_EQ(badges.value->at(0).instanceObject, ObjectId("instance/wheel"));
auto applied = prefabs.applyOverrides(*captured.value, "instance", {}, level);
REQUIRE(applied.value);
CHECK_EQ(applied.value->revision, captured.value->revision + 1);
CHECK_EQ(applied.value->objects[1].name, "Big Wheel");
CHECK_EQ(applied.value->objects[1].transform, changed);
auto revert = prefabs.revertOverrides(*captured.value, "instance", {}, level);
REQUIRE(revert.value);
CHECK_EQ(revert.value->size(), size_t{2});
applyAll(level, *revert.value);
CHECK_EQ(level.sceneObject(ObjectId("instance/wheel")).value->name, "Wheel");
CHECK_EQ(level.readTransform(ObjectId("instance/wheel")).value,
captured.value->objects[1].transform);
}
TEST_CASE("editor.prefab_nested_dependencies_detect_cycles_and_refresh_revision_pins") {
ScenePrefabService prefabs;
auto parent = prefabs.capture(AssetGuid("parent"), sourceScene(), ObjectId("vehicle"));
auto child = prefabs.capture(AssetGuid("wheel-prefab"), sourceScene(), ObjectId("wheel"));
REQUIRE(parent.value); REQUIRE(child.value);
child.value->revision = 3;
parent.value->objects[1].nestedPrefab = child.value->asset;
parent.value->objects[1].nestedRevision = 1;
const auto resolver = [&](const AssetGuid& asset) {
if (asset == child.value->asset) return EditorResult<PrefabAssetSnapshot>::applied(*child.value);
return EditorResult<PrefabAssetSnapshot>::error(EditorStatus::NotFound,
RuleId("test.missing"), "missing");
};
const auto dependencies = prefabs.inspectDependencies(*parent.value, resolver);
CHECK_EQ(static_cast<int>(dependencies.status), static_cast<int>(EditorStatus::Applied));
REQUIRE(!dependencies.diagnostics.empty());
auto refreshed = prefabs.refreshNestedRevisions(*parent.value, resolver);
REQUIRE(refreshed.value);
CHECK_EQ(refreshed.value->revision, parent.value->revision + 1);
CHECK_EQ(refreshed.value->objects[1].nestedRevision, 3U);
child.value->objects[0].nestedPrefab = parent.value->asset;
child.value->objects[0].nestedRevision = parent.value->revision;
const auto cyclicResolver = [&](const AssetGuid& asset) {
if (asset == child.value->asset) return EditorResult<PrefabAssetSnapshot>::applied(*child.value);
if (asset == parent.value->asset) return EditorResult<PrefabAssetSnapshot>::applied(*parent.value);
return EditorResult<PrefabAssetSnapshot>::error(EditorStatus::NotFound,
RuleId("test.missing"), "missing");
};
CHECK_EQ(static_cast<int>(prefabs.inspectDependencies(*parent.value, cyclicResolver).status),
static_cast<int>(EditorStatus::Rejected));
}
TEST_CASE("editor.prefab_snapshot_round_trip_rejects_invalid_hierarchy") {
ScenePrefabService prefabs;
SceneDocumentTarget source = sourceScene();
auto captured = prefabs.capture(AssetGuid("vehicle"), source, ObjectId("vehicle"));
REQUIRE(captured.value);
const EditorValue snapshot = prefabs.snapshotValue(*captured.value);
auto restored = prefabs.loadSnapshot(snapshot);
REQUIRE(restored.value);
CHECK_EQ(prefabs.snapshotValue(*restored.value), snapshot);
EditorValue broken = snapshot;
auto* object = broken.getIf<EditorValue::Object>();
REQUIRE(object);
auto* rows = object->at("objects").getIf<EditorValue::Array>();
REQUIRE(rows);
auto* wheel = rows->at(1).getIf<EditorValue::Object>();
REQUIRE(wheel);
(*wheel)["parentSourceId"] = "missing";
CHECK_EQ(static_cast<int>(prefabs.loadSnapshot(broken).status), static_cast<int>(EditorStatus::Rejected));
}