Files
EVEngine/test/scene_editing_commands.cpp

92 lines
4.3 KiB
C++

#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include "editing/EditingValueJson.h"
#include "scene/editing/SceneTarget.h"
#include <map>
namespace {
using namespace eve::editing;
using namespace eve::scene_editing;
struct Commands final : IEditingCommandRegistry {
std::map<std::string, EditingCommandPlanner> planners;
Result<void> registerPlannedCommand(EditingCommandDescriptor descriptor, EditingCommandPlanner planner) override {
planners.emplace(descriptor.id.value(), std::move(planner));
return applied<void>();
}
Result<std::size_t> unregisterOwner(const std::string&) override {
const auto count = planners.size();
planners.clear();
return applied<std::size_t>(count);
}
Result<CommandPlan> plan(IEditableTarget& target, const std::string& command, Value payload) {
CommandRequest request;
request.payload = std::move(payload);
return planners.at(command)(target, request);
}
};
TEST_CASE("scene.editing.commands_hierarchy_plans_are_reversible_and_backend_neutral") {
Commands commands;
REQUIRE(registerEditingCommands(commands).ok());
for (const auto* id :
{"scene.object.create.v1", "scene.object.delete.v1", "scene.object.rename.v1", "scene.object.reparent.v1"})
REQUIRE(commands.planners.contains(id));
SceneDocumentTarget document("document");
RuntimeWorldTarget runtime("runtime");
for (SceneTargetBase* target :
{static_cast<SceneTargetBase*>(&document), static_cast<SceneTargetBase*>(&runtime)}) {
auto create =
commands.plan(*target, "scene.object.create.v1",
Value::Object{{"object", "cube"}, {"name", "Cube"}, {"position", Value::Array{1, 2, 3}}});
REQUIRE(create.ok());
REQUIRE(!target->sceneObject(ObjectId("cube")).ok());
REQUIRE_EQ(create.value().operations.size(), std::size_t(1));
REQUIRE(target->applyDomainOperation(create.value().operations.front()).ok());
REQUIRE_EQ(target->readTransform(ObjectId("cube")).value().y, 2.0);
auto rename =
commands.plan(*target, "scene.object.rename.v1", Value::Object{{"object", "cube"}, {"name", "Renamed"}});
REQUIRE(rename.ok());
auto op = rename.value().operations.front();
REQUIRE(target->applyDomainOperation(op).ok());
if (!op.inverseType.empty()) op.type = op.inverseType;
op.payload = op.inverse;
REQUIRE(target->applyDomainOperation(op).ok());
REQUIRE_EQ(target->sceneObject(ObjectId("cube")).value().name, std::string("Cube"));
auto cycle =
commands.plan(*target, "scene.object.reparent.v1", Value::Object{{"object", "cube"}, {"parent", "cube"}});
REQUIRE(!cycle.ok());
auto remove = commands.plan(*target, "scene.object.delete.v1", Value::Object{{"object", "cube"}});
REQUIRE(remove.ok());
REQUIRE(target->applyDomainOperation(remove.value().operations.front()).ok());
REQUIRE(target->sceneChildren({}).empty());
}
}
TEST_CASE("scene.editing.snapshot_restore_is_validated_atomic_and_reversible") {
SceneDocumentTarget target("scene");
auto create = target.makeCreate({ObjectId("root"), {}, "Root", {}});
REQUIRE(create.ok());
REQUIRE(target.applyDomainOperation(create.value()).ok());
const auto before = target.snapshotValue();
auto decoded = editorValueFromJson(editorValueToJson(before));
REQUIRE(decoded.ok());
auto restore = target.makeRestore(decoded.value());
REQUIRE(restore.ok());
REQUIRE(target.applyDomainOperation(restore.value()).ok());
REQUIRE_EQ(editorValueToJson(before), editorValueToJson(target.snapshotValue()));
auto invalid = decoded.value();
(*invalid.getIf<Value::Object>())["schemaVersion"] = 99;
const auto revision = target.revision();
REQUIRE(!target.makeRestore(invalid).ok());
REQUIRE_EQ(target.revision(), revision);
invalid = decoded.value();
auto& objects = *invalid.getIf<Value::Object>()->at("objects").getIf<Value::Array>();
(*objects.front().getIf<Value::Object>())["parent"] = "root";
REQUIRE(!target.makeRestore(invalid).ok());
REQUIRE_EQ(editorValueToJson(before), editorValueToJson(target.snapshotValue()));
}
} // namespace