208 lines
9.3 KiB
C++
208 lines
9.3 KiB
C++
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
|
|
|
|
#include "Misc/AutomationTest.h"
|
|
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "EdGraph/EdGraphNode.h"
|
|
#include "EdGraph/EdGraphPin.h"
|
|
#include "EdGraphSchema_K2.h"
|
|
#include "Engine/Blueprint.h"
|
|
#include "K2Node_CallArrayFunction.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "Kismet2/CompilerResultsLog.h"
|
|
#include "Libraries/DirectiveUtilArrayFunctionLibrary.h"
|
|
#include "Tests/DirectiveUtilTestObject.h"
|
|
|
|
namespace DirectiveUtilArrayNodeTest
|
|
{
|
|
struct FDependentPin
|
|
{
|
|
FName Name;
|
|
EPinContainerType ContainerType;
|
|
};
|
|
|
|
struct FArrayNodeCase
|
|
{
|
|
FName FunctionName;
|
|
TArray<FDependentPin> DependentPins;
|
|
};
|
|
|
|
FEdGraphPinType MakeArrayType(
|
|
const FName Category,
|
|
const FName SubCategory = NAME_None,
|
|
UObject* SubCategoryObject = nullptr)
|
|
{
|
|
FEdGraphPinType PinType;
|
|
PinType.PinCategory = Category;
|
|
PinType.PinSubCategory = SubCategory;
|
|
PinType.PinSubCategoryObject = SubCategoryObject;
|
|
PinType.ContainerType = EPinContainerType::Array;
|
|
return PinType;
|
|
}
|
|
|
|
UEdGraph* MakeGraph()
|
|
{
|
|
UBlueprint* Blueprint = NewObject<UBlueprint>();
|
|
Blueprint->GeneratedClass = UObject::StaticClass();
|
|
Blueprint->SkeletonGeneratedClass = UObject::StaticClass();
|
|
UEdGraph* Graph = NewObject<UEdGraph>(Blueprint);
|
|
Graph->Schema = UEdGraphSchema_K2::StaticClass();
|
|
Blueprint->UbergraphPages.Add(Graph);
|
|
return Graph;
|
|
}
|
|
|
|
UEdGraphPin* AddArrayOutput(UEdGraph& Graph, const FName Name, const FEdGraphPinType& PinType)
|
|
{
|
|
UEdGraphNode* SourceNode = NewObject<UEdGraphNode>(&Graph);
|
|
Graph.AddNode(SourceNode);
|
|
return SourceNode->CreatePin(EGPD_Output, PinType, Name);
|
|
}
|
|
|
|
UK2Node_CallArrayFunction* AddFunctionNode(UEdGraph& Graph, const FName FunctionName)
|
|
{
|
|
UK2Node_CallArrayFunction* Node = NewObject<UK2Node_CallArrayFunction>(&Graph);
|
|
Graph.AddNode(Node);
|
|
const UFunction* Function = UDirectiveUtilArrayFunctionLibrary::StaticClass()->FindFunctionByName(FunctionName);
|
|
Node->FunctionReference.SetExternalMember(FunctionName, UDirectiveUtilArrayFunctionLibrary::StaticClass());
|
|
Node->bDefaultsToPureFunc = Function && Function->HasAnyFunctionFlags(FUNC_BlueprintPure);
|
|
Node->AllocateDefaultPins();
|
|
return Node;
|
|
}
|
|
|
|
void Connect(UEdGraphPin& OutputPin, UK2Node& Node, UEdGraphPin& InputPin)
|
|
{
|
|
OutputPin.MakeLinkTo(&InputPin);
|
|
Node.PinConnectionListChanged(&InputPin);
|
|
}
|
|
|
|
void Disconnect(UEdGraphPin& OutputPin, UK2Node& Node, UEdGraphPin& InputPin)
|
|
{
|
|
OutputPin.BreakLinkTo(&InputPin);
|
|
Node.PinConnectionListChanged(&InputPin);
|
|
}
|
|
|
|
bool HasElementType(
|
|
const UEdGraphPin& Pin,
|
|
const FEdGraphPinType& ArrayType,
|
|
const EPinContainerType ContainerType)
|
|
{
|
|
return Pin.PinType.ContainerType == ContainerType
|
|
&& Pin.PinType.PinCategory == ArrayType.PinCategory
|
|
&& Pin.PinType.PinSubCategory == ArrayType.PinSubCategory
|
|
&& Pin.PinType.PinSubCategoryObject == ArrayType.PinSubCategoryObject;
|
|
}
|
|
}
|
|
|
|
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
|
FDirectiveUtilArrayNodeWildcardTest,
|
|
"DirectiveUtilities.ArrayNodeWildcardTests",
|
|
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
|
|
|
bool FDirectiveUtilArrayNodeWildcardTest::RunTest(const FString& Parameters)
|
|
{
|
|
using namespace DirectiveUtilArrayNodeTest;
|
|
|
|
const TArray<FArrayNodeCase> NodeCases = {
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_NextIndex), {}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_PreviousIndex), {}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_RemoveDuplicates), {}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_AppendOptimized), {{TEXT("SourceArray"), EPinContainerType::Array}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_InsertOptimized), {{TEXT("SourceArray"), EPinContainerType::Array}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_GetValidFirstItemCopy), {{TEXT("OutItem"), EPinContainerType::None}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_GetValidLastItemCopy), {{TEXT("OutItem"), EPinContainerType::None}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_GetValidItemFromIndexCopy), {{TEXT("OutItem"), EPinContainerType::None}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_GetRandomItem), {{TEXT("OutItem"), EPinContainerType::None}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_LastValue), {{TEXT("OutItem"), EPinContainerType::None}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_Pop), {{TEXT("OutItem"), EPinContainerType::None}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_PopFirst), {{TEXT("OutItem"), EPinContainerType::None}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_RemoveAtSwap), {}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_RemoveAtIndices), {}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_RemoveAllOccurrences), {{TEXT("Item"), EPinContainerType::None}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_Slice), {{TEXT("OutArray"), EPinContainerType::Array}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_Rotate), {}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_GetDistinct), {{TEXT("OutArray"), EPinContainerType::Array}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_CountOccurrences), {{TEXT("ItemToCount"), EPinContainerType::None}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_GetMostCommon), {{TEXT("OutItem"), EPinContainerType::None}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_Sample), {{TEXT("OutArray"), EPinContainerType::Array}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_SampleFromStream), {{TEXT("OutArray"), EPinContainerType::Array}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_SampleWeighted), {{TEXT("OutArray"), EPinContainerType::Array}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_SampleWeightedFromStream), {{TEXT("OutArray"), EPinContainerType::Array}}},
|
|
{GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_GetPage), {{TEXT("OutArray"), EPinContainerType::Array}}}
|
|
};
|
|
|
|
const TArray<FEdGraphPinType> ArrayTypes = {
|
|
MakeArrayType(UEdGraphSchema_K2::PC_Boolean),
|
|
MakeArrayType(UEdGraphSchema_K2::PC_String),
|
|
MakeArrayType(UEdGraphSchema_K2::PC_Object, NAME_None, UDirectiveUtilTestObject::StaticClass()),
|
|
MakeArrayType(UEdGraphSchema_K2::PC_Struct, NAME_None, FDirectiveUtilCollisionValue::StaticStruct())
|
|
};
|
|
|
|
for (const FArrayNodeCase& NodeCase : NodeCases)
|
|
{
|
|
UEdGraph* Graph = MakeGraph();
|
|
UK2Node_CallArrayFunction* Node = AddFunctionNode(*Graph, NodeCase.FunctionName);
|
|
UEdGraphPin* TargetArray = Node->FindPin(TEXT("TargetArray"));
|
|
TestNotNull(*FString::Printf(TEXT("%s should have a TargetArray pin"), *NodeCase.FunctionName.ToString()), TargetArray);
|
|
if (!TargetArray)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TestTrue(
|
|
*FString::Printf(TEXT("%s should begin as a wildcard array"), *NodeCase.FunctionName.ToString()),
|
|
TargetArray->PinType.IsArray() && TargetArray->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard);
|
|
if (NodeCase.FunctionName == GET_FUNCTION_NAME_CHECKED(UDirectiveUtilArrayFunctionLibrary, Array_RemoveAtIndices))
|
|
{
|
|
const UEdGraphPin* IndicesPin = Node->FindPin(TEXT("Indices"));
|
|
TestNotNull(TEXT("RemoveAtIndices should have an Indices pin"), IndicesPin);
|
|
if (IndicesPin)
|
|
{
|
|
TestTrue(
|
|
TEXT("RemoveAtIndices should keep Indices as an integer array"),
|
|
IndicesPin->PinType.IsArray()
|
|
&& IndicesPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Int);
|
|
}
|
|
}
|
|
FCompilerResultsLog ModuleValidationLog;
|
|
FBlueprintEditorUtils::ValidateEditorOnlyNodes(Node, ModuleValidationLog);
|
|
TestEqual(
|
|
*FString::Printf(TEXT("%s should be valid in a runtime Blueprint"), *NodeCase.FunctionName.ToString()),
|
|
ModuleValidationLog.NumWarnings,
|
|
0);
|
|
|
|
for (int32 TypeIndex = 0; TypeIndex < ArrayTypes.Num(); ++TypeIndex)
|
|
{
|
|
const FEdGraphPinType& ArrayType = ArrayTypes[TypeIndex];
|
|
UEdGraphPin* ArrayOutput = AddArrayOutput(
|
|
*Graph,
|
|
*FString::Printf(TEXT("ArrayOutput%d"), TypeIndex),
|
|
ArrayType);
|
|
Connect(*ArrayOutput, *Node, *TargetArray);
|
|
TestTrue(
|
|
*FString::Printf(TEXT("%s should resolve TargetArray type %d"), *NodeCase.FunctionName.ToString(), TypeIndex),
|
|
HasElementType(*TargetArray, ArrayType, EPinContainerType::Array));
|
|
|
|
for (const FDependentPin& ExpectedPin : NodeCase.DependentPins)
|
|
{
|
|
const UEdGraphPin* DependentPin = Node->FindPin(ExpectedPin.Name);
|
|
TestNotNull(
|
|
*FString::Printf(TEXT("%s should have a %s pin"), *NodeCase.FunctionName.ToString(), *ExpectedPin.Name.ToString()),
|
|
DependentPin);
|
|
if (DependentPin)
|
|
{
|
|
TestTrue(
|
|
*FString::Printf(TEXT("%s should resolve %s type %d"), *NodeCase.FunctionName.ToString(), *ExpectedPin.Name.ToString(), TypeIndex),
|
|
HasElementType(*DependentPin, ArrayType, ExpectedPin.ContainerType));
|
|
}
|
|
}
|
|
|
|
Disconnect(*ArrayOutput, *Node, *TargetArray);
|
|
TestTrue(
|
|
*FString::Printf(TEXT("%s should reset after type %d"), *NodeCase.FunctionName.ToString(), TypeIndex),
|
|
TargetArray->PinType.IsArray() && TargetArray->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|