Files
ProjectEleri/Plugins/SUDS/Source/SUDSTest/Private/TestParsing.cpp

1189 lines
47 KiB
C++

#include "SUDSMessageLogger.h"
#include "SUDSScript.h"
#include "Misc/AutomationTest.h"
#include "SUDSScriptImporter.h"
#include "SUDSScriptNode.h"
#include "TestUtils.h"
UE_DISABLE_OPTIMIZATION
const FString SimpleParsingInput = R"RAWSUD(
===
# Nothing in header but a comment
===
# A comment in body
Player: Excuse me?
NPC: Well, hello there. This is a test.
* A test?
NPC: Yes, a test. This is some indented continuation text.
Player: Oh I see, thank you.
NPC: You're welcome.
* Another option
NPC: This is another option with an embedded choice.
* How far can this go?
NPC: Theoretically forever but who knows?
* This is an extra question
NPC: That should have been added to the previous choice
* Another question?
NPC: Yep, this one too
* A third level of questions?
NPC: Yes, really!
* Wow
NPC: IKR
* Continuation with no response, just fallthrough
Player: This is a level 2 fallthrough
Player: Well, that's all for now. This should appear for all paths as a fall-through.
This, in fact, is a multi-line piece of text
Which is joined to the previous text node with the line breaks
NPC: Bye!
)RAWSUD";
const FString GotoParsingInput = R"RAWSUD(
:start
:alsostart
Player: This is the start
:choice
* Go to end
NPC: How rude, bye then
[goto end]
* Nested option
NPC: Some nested text with <Bounce>formatting</>
* Go to goodbye
Player: Gotta go!
[go to goodbye]
* Skip
[goto secondchoice]
* This is a <Bounce>mistake</>
NPC: Oh no
[goto this_is_an_error]
:secondchoice
NPC: Yep, this one too
* Go back to choice
NPC: Okay!
[goto choice]
* Return to the start
NPC: Gotcha
[goto start]
* Alternative start, also with no text before
[goto alsostart]
:goodbye
NPC: Bye!
)RAWSUD";
const FString SetVariableParsingInput = R"RAWSUD(
===
# Set some vars in header
# Text var with an existing localised ID
[set SpeakerName.Player "Protagonist"] @12345@
# Text var no localised ID
[set ValetName "Bob"]
[set SomeFloat 12.5]
[set SomeName `AName`]
[set EmbeddedQuoteString "Hello this has some \"Embedded Quotes\""]
===
Player: Hello
[set SomeInt 99]
NPC: Wotcha
# Test that inserting a set node in between text and choice doesn't break link
[set SomeGender masculine]
* Choice 1
[set SomeBoolean True]
NPC: Truth
* Choice 2
NPC: Surprise
[set ValetName "Kate"]
[set SomeGender feminine]
Player: Well
)RAWSUD";
const FString ConditionalParsingInput = R"RAWSUD(
===
# Nothing in header but a comment
===
# A comment in body
Player: Excuse me?
NPC: Well, hello there. This is a test.
* A test?
NPC: Yes, a test. This is some indented continuation text.
Player: Oh I see, thank you.
[if $polite == 1]
NPC: You're welcome.
[else]
NPC: Too right.
[endif]
* Another option
NPC: This is another option with an embedded choice.
* How far can this go?
NPC: Theoretically forever but who knows?
[if $extraq]
* This is an extra question
NPC: That should have been added to the previous choice
* Another question?
NPC: Yep, this one too
[endif]
Player: Well, that's all for now. This should appear for all paths as a fall-through.
This, in fact, is a multi-line piece of text
Which is joined to the previous text node with the line breaks
NPC: Bye!
)RAWSUD";
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestSimpleParsing,
"SUDSTest.TestSimpleParsing",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestSimpleParsing::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(SimpleParsingInput), SimpleParsingInput.Len(), "SimpleParsingInput", &Logger, true));
// Test the content of the parsing
auto RootNode = Importer.GetNode(0);
if (!TestNotNull("Root node should exist", RootNode))
return false;
TestEqual("Root node type", RootNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Root node speaker", RootNode->Identifier, "Player");
TestEqual("Root node text", RootNode->Text, "Excuse me?");
TestEqual("Root node path", RootNode->ChoicePath, "/");
TestEqual("Root node edges", RootNode->Edges.Num(), 1);
auto NextNode = Importer.GetNode(RootNode->Edges[0].TargetNodeIdx);
if (!TestNotNull("Next node should exist", NextNode))
return false;
TestEqual("Second node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Second node speaker", NextNode->Identifier, "NPC");
TestEqual("Second node text", NextNode->Text, "Well, hello there. This is a test.");
TestEqual("Second node path", RootNode->ChoicePath, "/");
TestEqual("Second node edges", NextNode->Edges.Num(), 1);
NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (!TestNotNull("Third node should exist", NextNode))
return false;
TestEqual("Third node type", NextNode->NodeType, ESUDSParsedNodeType::Choice);
// Choice itself is still at root, only the edges (individual choices) introduce new path levels
TestEqual("Third node path", NextNode->ChoicePath, "/");
TestEqual("Third node edges", NextNode->Edges.Num(), 2);
auto Choice1Node = NextNode;
const FSUDSParsedNode* FallthroughNode = nullptr;
if (NextNode->Edges.Num() >= 2)
{
TestEqual("Choice 1 node edge 0 text", Choice1Node->Edges[0].Text, "A test?");
TestEqual("Choice 1 node edge 1 text", Choice1Node->Edges[1].Text, "Another option");
// Follow choice 1
NextNode = Importer.GetNode(Choice1Node->Edges[0].TargetNodeIdx);
if (TestNotNull("Next node should exist", NextNode))
{
TestEqual("Choice 1 1st text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Choice 1 1st text node speaker", NextNode->Identifier, "NPC");
TestEqual("Choice 1 1st text node text", NextNode->Text, "Yes, a test. This is some indented continuation text.");
TestEqual("Choice 1 1st text node path", NextNode->ChoicePath, "/C001/");
TestEqual("Choice 1 1st text node edges", NextNode->Edges.Num(), 1);
NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Next node should exist", NextNode))
{
TestEqual("Choice 1 2nd text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Choice 1 2nd text node speaker", NextNode->Identifier, "Player");
TestEqual("Choice 1 2nd text node text", NextNode->Text, "Oh I see, thank you.");
TestEqual("Choice 1 2nd text node path", NextNode->ChoicePath, "/C001/");
TestEqual("Choice 1 2nd text node edges", NextNode->Edges.Num(), 1);
NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Next node should exist", NextNode))
{
TestEqual("Choice 1 3rd text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Choice 1 3rd text node speaker", NextNode->Identifier, "NPC");
TestEqual("Choice 1 3rd text node text", NextNode->Text, "You're welcome.");
TestEqual("Choice 1 3rd text node path", NextNode->ChoicePath, "/C001/");
// Should fall through, all the way to the end and not to "level 2 fallthrough" since that's deeper level
TestEqual("Choice 1 3rd text node edges", NextNode->Edges.Num(), 1);
if (NextNode->Edges.Num() >= 1)
{
auto LinkedNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Choice 1 3rd text linked node", LinkedNode))
{
TestTrue("Choice 1 3rd text target node", LinkedNode->Text.StartsWith("Well, that's all for now"));
FallthroughNode = LinkedNode;
}
}
}
}
}
// Follow choice 2
NextNode = Importer.GetNode(Choice1Node->Edges[1].TargetNodeIdx);
if (!TestNotNull("Next node should exist", NextNode))
return false;
TestEqual("Choice 2 1st text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Choice 2 1st text node speaker", NextNode->Identifier, "NPC");
TestEqual("Choice 2 1st text node text", NextNode->Text, "This is another option with an embedded choice.");
TestEqual("Choice 2 2nd text node path", NextNode->ChoicePath, "/C002/");
TestEqual("Choice 2 1st text node edges", NextNode->Edges.Num(), 1);
NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (!TestNotNull("Next node should exist", NextNode))
return false;
// This is nested choice node
TestEqual("Choice 2 1st text node type", NextNode->NodeType, ESUDSParsedNodeType::Choice);
TestEqual("Choice 2 nested choice edges", NextNode->Edges.Num(), 3);
auto NestedChoiceNode = NextNode;
if (NestedChoiceNode->Edges.Num() >= 3)
{
TestEqual("Nested choice edge text 0", NestedChoiceNode->Edges[0].Text, "How far can this go?");
NextNode = Importer.GetNode(NestedChoiceNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Next node should exist", NextNode))
{
TestEqual("Nested Choice 1st text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Nested Choice 1st text node speaker", NextNode->Identifier, "NPC");
TestEqual("Nested Choice 1st text node text", NextNode->Text, "Theoretically forever but who knows?");
// Choice edges are assigned unique numbers in ascending order, but nested
// This helps with fallthrough
TestEqual("Nested Choice 1st text node path", NextNode->ChoicePath, "/C002/C003/");
if (TestEqual("Nested Choice 1st text node edges", NextNode->Edges.Num(), 1))
{
// Should fall through
auto LinkedNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Nested Choice 1st linked node", LinkedNode))
{
TestEqual("Nested Choice 1st text target node", LinkedNode->Text, "This is a level 2 fallthrough");
}
}
}
TestEqual("Nested choice edge text 1", NestedChoiceNode->Edges[1].Text, "This is an extra question");
NextNode = Importer.GetNode(NestedChoiceNode->Edges[1].TargetNodeIdx);
if (TestNotNull("Next node should exist", NextNode))
{
TestEqual("Nested Choice 2nd text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Nested Choice 2nd text node speaker", NextNode->Identifier, "NPC");
TestEqual("Nested Choice 2nd text node text", NextNode->Text, "That should have been added to the previous choice");
TestEqual("Nested Choice 2nd text node path", NextNode->ChoicePath, "/C002/C004/");
TestEqual("Nested Choice 2nd text node edges", NextNode->Edges.Num(), 1);
if (TestEqual("Nested Choice 2nd text node edges", NextNode->Edges.Num(), 1))
{
// Should fall through
auto LinkedNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Nested Choice 2nd linked node", LinkedNode))
{
TestEqual("Nested Choice 2nd text target node", LinkedNode->Text, "This is a level 2 fallthrough");
}
}
}
TestEqual("Nested choice edge text 2", NestedChoiceNode->Edges[2].Text, "Another question?");
NextNode = Importer.GetNode(NestedChoiceNode->Edges[2].TargetNodeIdx);
if (TestNotNull("Next node should exist", NextNode))
{
TestEqual("Nested Choice 3rd text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Nested Choice 3rd text node speaker", NextNode->Identifier, "NPC");
TestEqual("Nested Choice 3rd text node text", NextNode->Text, "Yep, this one too");
TestEqual("Nested Choice 3rd text node path", NextNode->ChoicePath, "/C002/C005/");
if (TestEqual("Nested Choice 3rd text node edges", NextNode->Edges.Num(), 1))
{
// Double nested
auto DoubleChoiceNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Double Nested Choice node", DoubleChoiceNode))
{
TestEqual("Double Nested Choice node type", DoubleChoiceNode->NodeType, ESUDSParsedNodeType::Choice);
if (TestEqual("Double Nested Choice node edges", DoubleChoiceNode->Edges.Num(), 3))
{
TestEqual("Double Nested choice edge text 0", DoubleChoiceNode->Edges[0].Text, "A third level of questions?");
NextNode = Importer.GetNode(DoubleChoiceNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Double Nested Choice node option 0", NextNode))
{
TestEqual("Double Nested Choice option 0 text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Double Nested Choice option 0 text node speaker", NextNode->Identifier, "NPC");
TestEqual("Double Nested Choice option 0 text node text", NextNode->Text, "Yes, really!");
// Should fall through
if (TestEqual("Double Nested Choice option 0 text edge", NextNode->Edges.Num(), 1))
NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Double Nested Choice option 0 text linked node", NextNode))
{
TestEqual("Double Nested Choice option 0 text linked node", NextNode->Text, "This is a level 2 fallthrough");
}
}
TestEqual("Double Nested choice edge text 1", DoubleChoiceNode->Edges[1].Text, "Wow");
NextNode = Importer.GetNode(DoubleChoiceNode->Edges[1].TargetNodeIdx);
if (TestNotNull("Double Nested Choice node option 1", NextNode))
{
TestEqual("Double Nested Choice option 1 text node type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Double Nested Choice option 1 text node speaker", NextNode->Identifier, "NPC");
TestEqual("Double Nested Choice option 1 text node text", NextNode->Text, "IKR");
// Should fall through
if (TestEqual("Double Nested Choice option 1 text edge", NextNode->Edges.Num(), 1))
NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Double Nested Choice option 0 text linked node", NextNode))
{
TestEqual("Double Nested Choice option 0 text linked node", NextNode->Text, "This is a level 2 fallthrough");
}
}
// Last Should fall through
TestEqual("Double Nested choice edge text 2", DoubleChoiceNode->Edges[2].Text, "Continuation with no response, just fallthrough");
auto LinkedNode = Importer.GetNode(DoubleChoiceNode->Edges[2].TargetNodeIdx);
if (TestNotNull("Nested Choice 3rd linked node", LinkedNode))
{
TestEqual("Nested Choice 3rd text target node", LinkedNode->Text, "This is a level 2 fallthrough");
}
}
}
}
}
}
if (TestNotNull("Should have found fallthrough node", FallthroughNode))
{
// Test the final fallthrough
TestEqual("Fallthrough node type", FallthroughNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Fallthrough node speaker", FallthroughNode->Identifier, "Player");
TestEqual("Fallthrough node text", FallthroughNode->Text, "Well, that's all for now. This should appear for all paths as a fall-through.\nThis, in fact, is a multi-line piece of text\nWhich is joined to the previous text node with the line breaks");
if (TestEqual("Fallthrough node edge count", FallthroughNode->Edges.Num(), 1))
{
NextNode = Importer.GetNode(FallthroughNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Fallthrough node next node not null", NextNode))
{
TestEqual("Fallthrough node 2 type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Fallthrough node 2 speaker", NextNode->Identifier, "NPC");
TestEqual("Fallthrough node 2 text", NextNode->Text, "Bye!");
// Should have no further edges since is at end
TestEqual("Fallthrough node 2 edge count", NextNode->Edges.Num(), 0);
}
}
}
}
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestGotoParsing,
"SUDSTest.TestGotoParsing",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestGotoParsing::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(GotoParsingInput), GotoParsingInput.Len(), "GotoParsingInput", &Logger, true));
// Test the content of the parsing
auto RootNode = Importer.GetNode(0);
if (!TestNotNull("Root node should exist", RootNode))
return false;
TestEqual("Root node type", RootNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Root node speaker", RootNode->Identifier, "Player");
TestEqual("Root node text", RootNode->Text, "This is the start");
TestEqual("Root node edges", RootNode->Edges.Num(), 1);
auto NextNode = Importer.GetNode(RootNode->Edges[0].TargetNodeIdx);
if (!TestNotNull("Next node should exist", NextNode))
return false;
TestEqual("Choice node type", NextNode->NodeType, ESUDSParsedNodeType::Choice);
if (TestEqual("Choice node edges", NextNode->Edges.Num(), 2))
{
auto ChoiceNode = NextNode;
TestEqual("Choice 1 text", ChoiceNode->Edges[0].Text, "Go to end");
NextNode = Importer.GetNode(ChoiceNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Next node should not be null", NextNode))
{
TestEqual("Goto End node text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Goto End node text speaker", NextNode->Identifier, "NPC");
TestEqual("Goto End node text text", NextNode->Text, "How rude, bye then");
if (TestEqual("Goto End node text edges", NextNode->Edges.Num(), 1))
{
NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Goto node should not be null", NextNode))
{
TestEqual("Goto End node type", NextNode->NodeType, ESUDSParsedNodeType::Goto);
TestEqual("Goto End node label", NextNode->Identifier, FSUDSScriptImporter::EndGotoLabel);
}
}
}
TestEqual("Choice 2 text", ChoiceNode->Edges[1].Text, "Nested option");
NextNode = Importer.GetNode(ChoiceNode->Edges[1].TargetNodeIdx);
if (TestNotNull("Next node should not be null", NextNode))
{
TestEqual("Choice 2 node text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Choice 2 node text speaker", NextNode->Identifier, "NPC");
TestEqual("Choice 2 node text text", NextNode->Text, "Some nested text with <Bounce>formatting</>");
if (TestEqual("Choice 2 node text edges", NextNode->Edges.Num(), 1))
{
NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Nested choice node should not be null", NextNode))
{
TestEqual("Goto End node type", NextNode->NodeType, ESUDSParsedNodeType::Choice);
auto NestedChoice = NextNode;
if (TestEqual("Nested Choice node edges", NextNode->Edges.Num(), 3))
{
TestEqual("Nested choice 0 text", NestedChoice->Edges[0].Text, "Go to goodbye");
NextNode = Importer.GetNode(NestedChoice->Edges[0].TargetNodeIdx);
if (TestNotNull("Should not be null", NextNode))
{
TestEqual("Nested choice 0 text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Nested choice 0 text speaker", NextNode->Identifier, "Player");
TestEqual("Nested choice 0 text text", NextNode->Text, "Gotta go!");
if (TestEqual("Nested Choice textnode edges", NextNode->Edges.Num(), 1))
{
NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Should not be null", NextNode))
{
// This is the goto
TestEqual("Nested choice 0 goto type", NextNode->NodeType, ESUDSParsedNodeType::Goto);
TestEqual("Nested choice 0 goto label", NextNode->Identifier, "goodbye");
const int DestIdx = Importer.GetGotoTargetNodeIndex(NextNode->Identifier);
TestNotEqual("Label should be valid", DestIdx, -1);
NextNode = Importer.GetNode(DestIdx);
if (TestNotNull("Goto dest node", NextNode))
{
TestEqual("Goto dest text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Goto dest text speaker", NextNode->Identifier, "NPC");
TestEqual("Goto dest text text", NextNode->Text, "Bye!");
}
}
}
}
TestEqual("Nested choice 1 text", NestedChoice->Edges[1].Text, "Skip");
NextNode = Importer.GetNode(NestedChoice->Edges[1].TargetNodeIdx);
if (TestNotNull("Should not be null", NextNode))
{
// This one goes straight to goto
TestEqual("Nested choice 1 goto type", NextNode->NodeType, ESUDSParsedNodeType::Goto);
TestEqual("Nested choice 1 goto label", NextNode->Identifier, "secondchoice");
const int DestIdx = Importer.GetGotoTargetNodeIndex(NextNode->Identifier);
TestNotEqual("Label should be valid", DestIdx, -1);
NextNode = Importer.GetNode(DestIdx);
if (TestNotNull("Goto dest node", NextNode))
{
TestEqual("Goto dest text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Goto dest text speaker", NextNode->Identifier, "NPC");
TestEqual("Goto dest text text", NextNode->Text, "Yep, this one too");
}
}
TestEqual("Nested choice 2 text", NestedChoice->Edges[2].Text, "This is a <Bounce>mistake</>");
NextNode = Importer.GetNode(NestedChoice->Edges[2].TargetNodeIdx);
if (TestNotNull("Should not be null", NextNode))
{
TestEqual("Nested choice 2 text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Nested choice 2 text speaker", NextNode->Identifier, "NPC");
TestEqual("Nested choice 2 text text", NextNode->Text, "Oh no");
if (TestEqual("Nested Choice text node edges", NextNode->Edges.Num(), 1))
{
NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (TestNotNull("Should not be null", NextNode))
{
// This is the goto
TestEqual("Nested choice 0 goto type", NextNode->NodeType, ESUDSParsedNodeType::Goto);
TestEqual("Nested choice 0 goto label", NextNode->Identifier, "this_is_an_error");
TestEqual("Label should go nowhere", Importer.GetGotoTargetNodeIndex(NextNode->Identifier), -1);
}
}
}
}
}
}
}
// Pick up the latter part (only reachable by goto)
NextNode = Importer.GetNode(Importer.GetGotoTargetNodeIndex("secondchoice"));
if (TestNotNull("End choice node should not be null", NextNode))
{
TestEqual("End choice node text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("End choice node text speaker", NextNode->Identifier, "NPC");
TestEqual("End choice node text text", NextNode->Text, "Yep, this one too");
if (TestEqual("End choice node text edges", NextNode->Edges.Num(), 1))
{
NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (TestNotNull("", NextNode))
{
TestEqual("End choice node text type", NextNode->NodeType, ESUDSParsedNodeType::Choice);
if (TestEqual("End choice node edges", NextNode->Edges.Num(), 3))
{
auto EndChoiceNode = NextNode;
TestEqual("End choice 0 text", EndChoiceNode->Edges[0].Text, "Go back to choice");
NextNode = Importer.GetNode(EndChoiceNode->Edges[0].TargetNodeIdx);
if (TestNotNull("", NextNode))
{
TestEqual("Node text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Node text speaker", NextNode->Identifier, "NPC");
TestEqual("Node text text", NextNode->Text, "Okay!");
if (TestEqual("Next node edges", NextNode->Edges.Num(), 1))
{
NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (TestNotNull("", NextNode))
{
TestEqual("Should be goto node", NextNode->NodeType, ESUDSParsedNodeType::Goto);
TestEqual("Goto label", NextNode->Identifier, "choice");
// This should lead back to a choice node, ie letting the previous text node use the same choices
// without repeating the text (loop with context)
const int DestIdx = Importer.GetGotoTargetNodeIndex(NextNode->Identifier);
TestNotEqual("Label should be valid", DestIdx, -1);
NextNode = Importer.GetNode(DestIdx);
if (TestNotNull("Goto dest node", NextNode))
{
TestEqual("Goto dest choice type", NextNode->NodeType, ESUDSParsedNodeType::Choice);
// Just make sure it's the right choice node
if (TestEqual("Goto dest choice edges", NextNode->Edges.Num(), 2))
{
TestEqual("Goto dest choice check edge", NextNode->Edges[0].Text, "Go to end");
}
}
}
}
NextNode = Importer.GetNode(EndChoiceNode->Edges[1].TargetNodeIdx);
if (TestNotNull("", NextNode))
{
TestEqual("Node text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Node text speaker", NextNode->Identifier, "NPC");
TestEqual("Node text text", NextNode->Text, "Gotcha");
if (TestEqual("Next node edges", NextNode->Edges.Num(), 1))
{
NextNode = Importer.GetNode(NextNode->Edges[0].TargetNodeIdx);
if (TestNotNull("", NextNode))
{
TestEqual("Should be goto node", NextNode->NodeType, ESUDSParsedNodeType::Goto);
TestEqual("Goto label", NextNode->Identifier, "start");
// This should lead back to a choice node, ie letting the previous text node use the same choices
// without repeating the text (loop with context)
const int DestIdx = Importer.GetGotoTargetNodeIndex(NextNode->Identifier);
TestNotEqual("Label should be valid", DestIdx, -1);
NextNode = Importer.GetNode(DestIdx);
if (TestNotNull("Goto dest node", NextNode))
{
// Just make sure it's the right node
TestEqual("Goto dest text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Node text speaker", NextNode->Identifier, "Player");
TestEqual("Node text text", NextNode->Text, "This is the start");
}
}
}
}
NextNode = Importer.GetNode(EndChoiceNode->Edges[2].TargetNodeIdx);
if (TestNotNull("", NextNode))
{
// Straight to goto
TestEqual("Should be goto node", NextNode->NodeType, ESUDSParsedNodeType::Goto);
TestEqual("Goto label", NextNode->Identifier, "alsostart");
// This should lead back to a choice node, ie letting the previous text node use the same choices
// without repeating the text (loop with context)
const int DestIdx = Importer.GetGotoTargetNodeIndex(NextNode->Identifier);
TestNotEqual("Label should be valid", DestIdx, -1);
NextNode = Importer.GetNode(DestIdx);
if (TestNotNull("Goto dest node", NextNode))
{
// Just make sure it's the right node
TestEqual("Goto dest text type", NextNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Node text speaker", NextNode->Identifier, "Player");
TestEqual("Node text text", NextNode->Text, "This is the start");
}
}
}
}
}
}
}
// Test that start & autostart point to the same place
const int StartNodeIdx = Importer.GetGotoTargetNodeIndex("start");
const auto StartNode = Importer.GetNode(StartNodeIdx);
if (TestNotNull("Second choice node should not be null", StartNode))
{
TestEqual("Start node text type", StartNode->NodeType, ESUDSParsedNodeType::Text);
TestEqual("Start node text speaker", StartNode->Identifier, "Player");
TestEqual("Start node text text", StartNode->Text, "This is the start");
}
const int AlsoStartNodeIdx = Importer.GetGotoTargetNodeIndex("alsostart");
TestEqual("start and alsostart should reference the same node", AlsoStartNodeIdx, StartNodeIdx);
}
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestSetVariableParsing,
"SUDSTest.TestSetVariableParsing",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestSetVariableParsing::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(SetVariableParsingInput), SetVariableParsingInput.Len(), "SetVariableParsingInput", &Logger, true));
// Test the content of the parsing
// Header nodes first
auto NextNode = Importer.GetHeaderNode(0);
TestParsedSetLiteral(this, "Header node 1", NextNode, "SpeakerName.Player", "Protagonist");
TestGetParsedNextNode(this, "Header node 1 next", NextNode, Importer, true, &NextNode);
TestParsedSetLiteral(this, "Header node 2", NextNode, "ValetName", "Bob");
TestGetParsedNextNode(this, "Header node 2 next", NextNode, Importer, true, &NextNode);
TestParsedSetLiteral(this, "Header node 3", NextNode, "SomeFloat", 12.5f);
TestGetParsedNextNode(this, "Header node 3 next", NextNode, Importer, true, &NextNode);
TestParsedSetLiteral(this, "Header node 4", NextNode, "SomeName", FName("AName"));
TestGetParsedNextNode(this, "Header node 4 next", NextNode, Importer, true, &NextNode);
TestParsedSetLiteral(this, "Header node 5", NextNode, "EmbeddedQuoteString", "Hello this has some \"Embedded Quotes\"");
// Now body nodes
NextNode = Importer.GetNode(0);
TestParsedText(this, "Root node", NextNode, "Player", "Hello");
TestGetParsedNextNode(this, "Node 1 next", NextNode, Importer, false, &NextNode);
TestParsedSetLiteral(this, "Node 2", NextNode, "SomeInt", 99);
TestGetParsedNextNode(this, "Node 2 next", NextNode, Importer, false, &NextNode);
TestParsedText(this, "Node 3 text", NextNode, "NPC", "Wotcha");
TestGetParsedNextNode(this, "Node 3 next", NextNode, Importer, false, &NextNode);
TestParsedSetLiteral(this, "Node 4", NextNode, "SomeGender", ETextGender::Masculine);
TestGetParsedNextNode(this, "Node 4 next", NextNode, Importer, false, &NextNode);
TestParsedChoice(this, "Node 5 choice", NextNode, 2);
auto ChoiceNode = NextNode;
TestParsedChoiceEdge(this, "Choice 1 edge", ChoiceNode, 0, "Choice 1", Importer, &NextNode);
TestParsedSetLiteral(this, "Choice 1 set", NextNode, "SomeBoolean", true);
TestGetParsedNextNode(this, "Choice 1 next", NextNode, Importer, false, &NextNode);
TestParsedText(this, "Choice 1 text", NextNode, "NPC", "Truth");
TestParsedChoiceEdge(this, "Choice 2 edge", ChoiceNode, 1, "Choice 2", Importer, &NextNode);
TestParsedText(this, "Choice 2 text", NextNode, "NPC", "Surprise");
TestGetParsedNextNode(this, "Choice 2 next", NextNode, Importer, false, &NextNode);
TestParsedSetLiteral(this, "Choice 2 set", NextNode, "ValetName", "Kate");
TestGetParsedNextNode(this, "Choice 2 next 2", NextNode, Importer, false, &NextNode);
TestParsedSetLiteral(this, "Choice 2 set 2", NextNode, "SomeGender", ETextGender::Feminine);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestConversionToRuntime,
"SUDSTest.TestConversionToRuntime",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestConversionToRuntime::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(GotoParsingInput), GotoParsingInput.Len(), "GotoParsingInput", &Logger, true));
auto Asset = NewObject<USUDSScript>(GetTransientPackage(), "Test");
const ScopedStringTableHolder StringTableHolder;
Importer.PopulateAsset(Asset, StringTableHolder.StringTable);
auto StartNode = Asset->GetFirstNode();
if (!TestNotNull("Start node should be true", StartNode))
{
return false;
}
TestTextNode(this, "Start node", StartNode, "Player", "This is the start");
auto NextNode = StartNode;
if (!TestEqual("Start node edges", NextNode->GetEdgeCount(), 1))
{
return false;
}
auto pEdge = NextNode->GetEdge(0);
if (!TestNotNull("Start node edge", pEdge))
{
return false;
}
NextNode = pEdge->GetTargetNode().Get();
if (TestChoiceNode(this, "Start node next", NextNode, 2))
{
auto ChoiceNode = NextNode;
if (TestChoiceEdge(this, "Choice 1 text", ChoiceNode, 0, "Go to end", &NextNode))
{
TestTextNode(this, "Goto End node text type", NextNode, "NPC", "How rude, bye then");
// This is a goto end so should just have one edge
auto GotoEdge = NextNode->GetEdge(0);
if (TestNotNull("Goto end node", GotoEdge))
{
TestFalse("Goto end should be null", GotoEdge->GetTargetNode().IsValid());
}
}
if (TestChoiceEdge(this, "Choice 2 text", ChoiceNode, 1, "Nested option", &NextNode))
{
TestTextNode(this, "Choice 2 node text type", NextNode, "NPC", "Some nested text with <Bounce>formatting</>");
if (TestEdge(this, "Choice 2 node text edges", NextNode, 0, &NextNode))
{
if (TestChoiceNode(this, "Goto End node type", NextNode, 3))
{
auto NestedChoice = NextNode;
if (TestChoiceEdge(this, "Nested choice 0 text", NestedChoice, 0, "Go to goodbye", &NextNode))
{
TestTextNode(this, "Nested choice 0 text type", NextNode, "Player", "Gotta go!");
if (TestEdge(this, "Nested Choice textnode edges", NextNode, 0, &NextNode))
{
// This was the goto which should have led to goodbye
TestTextNode(this, "Check goto goodbye", NextNode, "NPC", "Bye!");
}
}
if (TestChoiceEdge(this, "Nested choice 1 text", NestedChoice, 1, "Skip", &NextNode))
{
// This will go directly to secondchoice node
TestTextNode(this, "Nested choice 1 goto", NextNode, "NPC", "Yep, this one too");
}
if (TestChoiceEdge(this, "Nested choice 2 text", NestedChoice, 2, "This is a <Bounce>mistake</>", &NextNode))
{
// there's a text node then a failed goto (nowhere)
TestTextNode(this, "Nested choice 2 node", NextNode, "NPC", "Oh no");
auto GotoEdge = NextNode->GetEdge(0);
if (TestNotNull("Nested choice 2 node edge", GotoEdge))
{
TestFalse("Nested choice 2 node goes nowhere", GotoEdge->GetTargetNode().IsValid());
}
}
}
}
}
// Pick up the latter part (only reachable by goto)
NextNode = Asset->GetNodeByLabel("secondchoice");
if (TestTextNode(this, "secondchoice node", NextNode, "NPC", "Yep, this one too"))
{
if (TestEdge(this, "secondchoice node text edges", NextNode, 0, &NextNode))
{
if (TestChoiceNode(this, "secondchoice next choice", NextNode, 3))
{
auto EndChoiceNode = NextNode;
if (TestChoiceEdge(this, "End choice 0 edge", EndChoiceNode, 0, "Go back to choice", &NextNode))
{
TestTextNode(this, "Node text", NextNode, "NPC", "Okay!");
if (TestEdge(this, "Next node edges", NextNode, 0, &NextNode))
{
// Should go back to :choice node
TestChoiceNode(this, "Check choice goto", NextNode, 2);
TestChoiceEdge(this, "Check choice goto", NextNode, 0, "Go to end", &NextNode);
}
}
if (TestChoiceEdge(this, "End choice 1 edge", EndChoiceNode, 1, "Return to the start", &NextNode))
{
TestTextNode(this, "Node text", NextNode, "NPC", "Gotcha");
if (TestEdge(this, "Next node edges", NextNode, 0, &NextNode))
{
// Should go back to :start
TestTextNode(this, "Check goto text", NextNode, "Player", "This is the start");
}
}
if (TestChoiceEdge(this, "End choice 2 edge", EndChoiceNode, 2, "Alternative start, also with no text before", &NextNode))
{
// Should go directly back to :alsostart, same as :start
TestTextNode(this, "Check goto text", NextNode, "Player", "This is the start");
}
}
}
}
}
// check goto labels
auto GotoNode = Asset->GetNodeByLabel("start");
TestTextNode(this, "Goto Start", GotoNode, "Player", "This is the start");
GotoNode = Asset->GetNodeByLabel("alsostart");
TestTextNode(this, "Goto alsostart", GotoNode, "Player", "This is the start");
GotoNode = Asset->GetNodeByLabel("choice");
TestChoiceNode(this, "Goto choice", GotoNode, 2);
GotoNode = Asset->GetNodeByLabel("secondchoice");
TestTextNode(this, "Goto secondchoice", GotoNode, "NPC", "Yep, this one too");
GotoNode = Asset->GetNodeByLabel("goodbye");
TestTextNode(this, "Goto goodbye", GotoNode, "NPC", "Bye!");
// Test speakers
TestEqual("Num speakers", Asset->GetSpeakers().Num(), 2);
TestTrue("Speaker 1", Asset->GetSpeakers().Contains("Player"));
TestTrue("Speaker 2", Asset->GetSpeakers().Contains("NPC"));
return true;
}
const FString PartiallyLocalisedInput = R"RAWSUD(
Vagabond: Well met, fellow!
* Er, hi?
Vagabond: Verily, 'tis wondrous to see such a fine fellow on the road this morn!
[goto FriendlyChat]
* Jog on, mate @0001@
Vagabond: Well, really! Good day then sir! @0002@
[goto end]
:FriendlyChat
Vagabond: Mayhaps we could travel together a while, and share a tale or two? @0007@
What do you say?
)RAWSUD";
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestPartiallyLocalised,
"SUDSTest.TestPartiallyLocalised",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestPartiallyLocalised::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(PartiallyLocalisedInput), PartiallyLocalisedInput.Len(), "PartiallyLocalisedInput", &Logger, true));
// Test the content of the parsing
auto NextNode = Importer.GetNode(0);
TestParsedText(this, "Start node", NextNode, "Vagabond", "Well met, fellow!");
// Test that we generated unique textIDs for inserted lines that are unique
// Should be after the last explicit one
TestFalse("TextID should be populated", NextNode->TextID.IsEmpty());
TestEqual("TextID should be correct", NextNode->TextID, "@0008@");
TestGetParsedNextNode(this, "Next", NextNode, Importer, false, &NextNode);
if (TestParsedChoice(this, "First choice", NextNode, 2))
{
TestFalse("TextID should be populated", NextNode->Edges[0].TextID.IsEmpty());
TestEqual("TextID should be correct", NextNode->Edges[0].TextID, "@0009@");
TestFalse("TextID should be populated", NextNode->Edges[1].TextID.IsEmpty());
TestEqual("TextID should be correct", NextNode->Edges[1].TextID, "@0001@");
TestParsedChoiceEdge(this, "First choice", NextNode, 0, "Er, hi?", Importer, &NextNode);
TestParsedText(this, "Next node", NextNode, "Vagabond", "Verily, 'tis wondrous to see such a fine fellow on the road this morn!");
TestFalse("TextID should be populated", NextNode->TextID.IsEmpty());
TestEqual("TextID should be correct", NextNode->TextID, "@000a@");
TestGetParsedNextNode(this, "Next", NextNode, Importer, false, &NextNode);
TestParsedGoto(this, "Goto", NextNode, Importer, &NextNode);
TestParsedText(this, "Next node", NextNode, "Vagabond", "Mayhaps we could travel together a while, and share a tale or two?\nWhat do you say?");
TestEqual("TextID should be correct", NextNode->TextID, "@0007@");
}
NextNode = Importer.GetNode(0);
TestGetParsedNextNode(this, "Next", NextNode, Importer, false, &NextNode);
if (TestParsedChoice(this, "First choice", NextNode, 2))
{
TestEqual("TextID should be correct", NextNode->Edges[1].TextID, "@0001@");
TestParsedChoiceEdge(this, "First choice", NextNode, 1, "Jog on, mate", Importer, &NextNode);
TestParsedText(this, "Next node", NextNode, "Vagabond", "Well, really! Good day then sir!");
TestEqual("TextID should be correct", NextNode->TextID, "@0002@");
}
return true;
}
const FString ProblemChoiceInput = R"RAWSUD(
NPC: Well, hello there. This is a test.
:choice
* A test?
Player: Yes!
* This is a mistake; goto goes direct to another choice
[goto choice]
NPC: Bye!
)RAWSUD";
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestParseChoiceProblem,
"SUDSTest.TestParseChoiceProblem",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestParseChoiceProblem::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestFalse("Import should fail", Importer.ImportFromBuffer(GetData(ProblemChoiceInput), ProblemChoiceInput.Len(), "ProblemChoiceInput", &Logger, true));
if (TestTrue("Logger should have registered errors", Logger.HasErrors()))
{
FText ErrMsg = Logger.GetErrorMessages()[0]->ToText();
TestTrue("Error should contain 'Choices MUST show another speaker line'", ErrMsg.ToString().Contains("Choices MUST show another speaker line"));
}
return true;
}
const FString TrailingEventInput = R"RAWSUD(
NPC: Well, hello there. This is a test.
* Choice
[event SomeEvent 1]
)RAWSUD";
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestTrailingEventProblem,
"SUDSTest.TestTrailingEventProblem",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestTrailingEventProblem::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(TrailingEventInput), TrailingEventInput.Len(), "TrailingEventInput", &Logger, true));
return true;
}
const FString ConditionalChoicesNestedAndSiblingsInput = R"RAWSUD(
Player: Hello
[if {TopCondition} == 0]
* Top level choice 1
[goto outro]
[endif]
[if {TopCondition} == 1]
* Top level choice 2
[goto outro]
[if {NestedCondition1} == 1]
* Nested condition choice 1
[goto outro]
[endif]
[if {NestedCondition2} == 0]
* Nested condition choice 2
[goto outro]
[endif]
[if {NestedCondition3} == 1]
* Nested condition choice 3
[goto outro]
[endif]
[endif]
:outro
Player: Bye
)RAWSUD";
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestConditionalChoicesNestedAndSiblings,
"SUDSTest.TestConditionalChoicesNestedAndSiblings",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestConditionalChoicesNestedAndSiblings::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(ConditionalChoicesNestedAndSiblingsInput), ConditionalChoicesNestedAndSiblingsInput.Len(), "ConditionalChoicesNestedAndSiblingsInput", &Logger, true));
auto Asset = NewObject<USUDSScript>(GetTransientPackage(), "Test");
const ScopedStringTableHolder StringTableHolder;
Importer.PopulateAsset(Asset, StringTableHolder.StringTable);
auto StartNode = Asset->GetFirstNode();
if (!TestNotNull("Start node should be true", StartNode))
{
return false;
}
TestTextNode(this, "Start node", StartNode, "Player", "Hello");
auto NextNode = StartNode;
if (!TestEqual("Start node edges", NextNode->GetEdgeCount(), 1))
{
return false;
}
auto pEdge = NextNode->GetEdge(0);
if (!TestNotNull("Start node edge", pEdge))
{
return false;
}
/* Node structure:
T -> C1
C1 -> S1 -> (if TopCondition == 0) C2 --> Top level choice 1
C1 -> S2 -> (if TopCondition == 1) C3 -> Top Level choice 2
|-> S3 -> (if NestedCondition1 == 1) -> C4 -> Nested condition choice 1
|-> S4 -> (if NestedCondition2 == 0) -> C5 -> Nested condition choice 2
|-> S5 -> (if NestedCondition3 == 0) -> C5 -> Nested condition choice 3
*/
NextNode = pEdge->GetTargetNode().Get();
if (TestChoiceNode(this, "Start node next", NextNode, 2))
{
auto ChoiceNode = NextNode;
// Top condition == 0 subtree
if (TestChoiceEdge(this, "Select Path 1", ChoiceNode, 0, "", &NextNode))
{
if (TestSelectNode(this, "Select 1", NextNode, 1))
{
if (TestSelectEdge(this, "Select 1 Edge", NextNode, 0, "{TopCondition} == 0", &NextNode))
{
if (TestChoiceNode(this, "Choice 1 Leaf", NextNode, 1))
{
TestChoiceEdge(this, "Choice 1 Leaf Edge", NextNode, 0, "Top level choice 1", &NextNode);
}
}
}
}
// Top condition == 1 subtree
if (TestChoiceEdge(this, "Select Path 2", ChoiceNode, 1, "", &NextNode))
{
if (TestSelectNode(this, "Select 2", NextNode, 1))
{
if (TestSelectEdge(this, "Select 2 Edge", NextNode, 0, "{TopCondition} == 1", &NextNode))
{
// This choice node holds the non-nested condition choice, plus 3 more nested selects
// Because each condition is a sibling they become separate select subtrees
if (TestChoiceNode(this, "Choice 2 Mid", NextNode, 4))
{
auto ChoiceNode2 = NextNode;
TestChoiceEdge(this, "Choice 2 Leaf Edge 1", ChoiceNode2, 0, "Top level choice 2", &NextNode);
if (TestChoiceEdge(this, "Choice 2 To Select Edge 1", ChoiceNode2, 1, "", &NextNode))
{
if (TestSelectNode(this, "Select 2", NextNode, 1))
{
if (TestSelectEdge(this, "Select 2 Edge", NextNode, 0, "{NestedCondition1} == 1", &NextNode))
{
if (TestChoiceNode(this, "Choice 2 Leaf", NextNode, 1))
{
TestChoiceEdge(this, "Choice 2 Leaf Edge", NextNode, 0, "Nested condition choice 1", &NextNode);
}
}
}
}
if (TestChoiceEdge(this, "Choice 2 To Select Edge 2", ChoiceNode2, 2, "", &NextNode))
{
if (TestSelectNode(this, "Select 3", NextNode, 1))
{
if (TestSelectEdge(this, "Select 3 Edge", NextNode, 0, "{NestedCondition2} == 0", &NextNode))
{
if (TestChoiceNode(this, "Choice 3 Leaf", NextNode, 1))
{
TestChoiceEdge(this, "Choice 3 Leaf Edge", NextNode, 0, "Nested condition choice 2", &NextNode);
}
}
}
}
if (TestChoiceEdge(this, "Choice 2 To Select Edge 3", ChoiceNode2, 3, "", &NextNode))
{
if (TestSelectNode(this, "Select 4", NextNode, 1))
{
if (TestSelectEdge(this, "Select 4 Edge", NextNode, 0, "{NestedCondition3} == 1", &NextNode))
{
if (TestChoiceNode(this, "Choice 4 Leaf", NextNode, 1))
{
TestChoiceEdge(this, "Choice 4 Leaf Edge", NextNode, 0, "Nested condition choice 3", &NextNode);
}
}
}
}
}
}
}
}
}
return true;
}
// The deliberate mistake here is that the speaker lines are not indented underneath the choices,
// which when combined with the conditionals confuses the parser so that it leaves all but the
// first choice orphaned and unreachable. Import should report this to the user
const FString ErrorsReportedForBadChoiceIndentsInput = R"RAWSUD(
npc: hello
:chatchoices
[if {npc_hasQuests}]
* Any news?
player: Any news?
[endif]
[if {npc_canTrade}]
* Trade
player: What do you have for sale?
[endif]
[if {npc_hasLore}]
* How are you?
player: How are things?
[endif]
* Nevermind
npc: Alright. Come back anytime.
)RAWSUD";
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FTestErrorsReportedForBadChoiceIndents,
"SUDSTest.TestErrorsReportedForBadChoiceIndents",
EAutomationTestFlags::EditorContext |
EAutomationTestFlags::ClientContext |
EAutomationTestFlags::ProductFilter)
bool FTestErrorsReportedForBadChoiceIndents::RunTest(const FString& Parameters)
{
FSUDSMessageLogger Logger(false);
FSUDSScriptImporter Importer;
TestTrue("Import should succeed", Importer.ImportFromBuffer(GetData(ErrorsReportedForBadChoiceIndentsInput), ErrorsReportedForBadChoiceIndentsInput.Len(), "ErrorsReportedForBadChoiceIndentsInput", &Logger, true));
TestTrue("Warnings should have been logged", Logger.HasWarnings());
return true;
}
UE_ENABLE_OPTIMIZATION