#include "dialogue/editing/DialogueGraph.h" #include "dialogue/ConversationAuthoring.h" #include "zeroerr/assert.h" #include "zeroerr/unittest.h" using namespace eve::dialogue_editing; using namespace eve::editing; namespace { GraphDocumentData conversationGraph(DialogueGraphDomain& domain) { auto line = domain.makeNode(GraphNodeId("greeting"), "line"); auto choice = domain.makeNode(GraphNodeId("answer"), "choice"); auto route = domain.makeRouteNode(GraphNodeId("answer.yes"), "Yes"); auto end = domain.makeNode(GraphNodeId("done"), "end"); REQUIRE(line.ok()); REQUIRE(choice.ok()); REQUIRE(route.ok()); REQUIRE(end.ok()); auto* lineProperties = line.value().properties.getIf(); REQUIRE(lineProperties); (*lineProperties)["speaker"] = "guide"; (*lineProperties)["text"] = "Continue?"; (*lineProperties)["i18nKey"] = "dialogue.guide.continue"; (*lineProperties)["voice"] = "asset://voice/continue.ogg"; GraphDocument graph; REQUIRE(graph.createNode(line.value()).ok()); REQUIRE(graph.createNode(choice.value()).ok()); REQUIRE(graph.createNode(route.value()).ok()); REQUIRE(graph.createNode(end.value()).ok()); const auto connect = [&](const char* edge, const char* from, const char* to) { const auto* output = graph.findPin(GraphPinId(from)); const auto* input = graph.findPin(GraphPinId(to)); REQUIRE(output); REQUIRE(input); REQUIRE(graph.connect({StableId(edge), output->id, input->id}, domain.canConnect(*output, *input)).ok()); }; connect("line-choice", "greeting.out", "answer.in"); connect("choice-route", "answer.out", "answer.yes.in"); connect("route-end", "answer.yes.out", "done.in"); EditorValue::Array parameters{"has_met_guide"}; REQUIRE(graph.setParameters(EditorValue::Object{{"id", "intro"}, {"version", int64_t{2}}, {"entry", "greeting"}, {"parameters", std::move(parameters)}}).ok()); return graph.snapshot(domain.domain()); } } // namespace TEST_CASE("editor.dialogue.graph_compiles_nodes_routes_and_localization_metadata") { DialogueGraphDomain domain; const GraphDocumentData graph = conversationGraph(domain); const DialogueGraphCompileResult compiled = domain.compile(graph); CHECK_EQ(static_cast(compiled.status), static_cast(EditorStatus::Applied)); CHECK_EQ(compiled.documentRevision, graph.revision); const auto* definition = compiled.definition.getIf(); REQUIRE(definition); CHECK_EQ(*definition->at("id").getIf(), "intro"); CHECK_EQ(*definition->at("version").getIf(), int64_t{2}); CHECK_EQ(definition->at("nodes").getIf()->size(), size_t{3}); } TEST_CASE("editor.dialogue.graph_rejects_bypassed_or_incomplete_routes") { DialogueGraphDomain domain; GraphDocumentData graph = conversationGraph(domain); graph.edges.erase(graph.edges.begin() + 1); const auto incomplete = domain.compile(graph); CHECK_EQ(static_cast(incomplete.status), static_cast(EditorStatus::Failed)); REQUIRE(!incomplete.diagnostics.empty()); graph = conversationGraph(domain); graph.schemaVersion = 3; CHECK_EQ(static_cast(domain.compile(graph).status), static_cast(EditorStatus::Unsupported)); graph = conversationGraph(domain); auto* parameters = graph.parameters.getIf(); REQUIRE(parameters); (*parameters)["entry"] = "answer.yes"; CHECK_EQ(static_cast(domain.compile(graph).status), static_cast(EditorStatus::Failed)); } TEST_CASE("editor.dialogue.graph_builds_real_conversation_document") { DialogueGraphDomain domain; const GraphDocumentData graph = conversationGraph(domain); DialogueGraphRuntimeBuilder builder; auto built = builder.build(graph); REQUIRE(built.ok()); auto* document = built.value(); REQUIRE(document); CHECK_EQ(document->getId(), "intro"); CHECK_EQ(document->getEntry(), "greeting"); CHECK_EQ(document->getNodeCount(), 3); CHECK_EQ(document->getField("greeting", "next"), "answer"); CHECK_EQ(document->getRouteLabel("answer", 0), "Yes"); CHECK_EQ(document->getRouteTarget("answer", 0), "done"); CHECK(document->validate()); delete document; }