Files
UnrealEngine/Engine/Source/Developer/ToolMenus/Private/Tests/ToolMenusTestUtilities.cpp
2025-05-18 13:04:45 +08:00

290 lines
9.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Tests/ToolMenusTestUtilities.h"
#include "Misc/UObjectTestUtils.h"
namespace UE::ToolMenus
{
FName FMenuEntry::TypeName = "MenuEntry";
FName FMenuWildcardEntry::TypeName = "WildcardMenuEntry";
FName FMenu::TypeName = "Menu";
bool FToolMenuAutomationTestAdapter::Matches(const FMenuEntryBase* InExpectedEntry, const FToolMenuEntry& InActualEntry)
{
// Empty name matches anything
if (InExpectedEntry->Name.IsNone())
{
return true;
}
if (!TestInstance.TestEqual("Entry Name", InActualEntry.Name.ToString(), InExpectedEntry->Name.ToString()))
{
return false;
}
return true;
}
bool FToolMenuAutomationTestAdapter::Matches(const FMenuEntry& InExpectedEntry, const FToolMenuEntry& InActualEntry)
{
if (!Matches(static_cast<const FMenuEntryBase*>(&InExpectedEntry), InActualEntry))
{
return false;
}
if (InExpectedEntry.Type.IsSet() && !UE::CoreUObject::TestEqual(FString::Printf(TEXT("Entry (\"%s\") MultiBlockType"), *InActualEntry.Name.ToString()), InActualEntry.Type, InExpectedEntry.Type.GetValue(), TestInstance))
{
return false;
}
return true;
}
bool FToolMenuAutomationTestAdapter::Matches(const FMenuSection& InExpectedSection, const FToolMenuSection& InActualSection, const bool bInTestChildren)
{
bool bAllSucceeded = true;
if (!InExpectedSection.Name.IsNone()
&& !TestInstance.TestEqual("Section Name", InActualSection.Name, InExpectedSection.Name))
{
bAllSucceeded = false;
}
if (!bInTestChildren)
{
return true;
}
// If we don't require all entries, we default to failure, and only succeed if we find all entries in the (expected) section
if (!Parameters.EntryMatchParameters.bActualHasAllExpectedItems)
{
bAllSucceeded = false;
}
// If we require an exact match, we require an exact count
const bool bRequireMatchingEntryCount = Parameters.EntryMatchParameters.bActualHasAllExpectedItems && Parameters.EntryMatchParameters.bExpectedHasAllActualItems;
if (bRequireMatchingEntryCount
&& !TestInstance.TestEqual(
FString::Printf(TEXT("Entry Count (for section \"%s\")"), *InActualSection.Name.ToString()),
InActualSection.Blocks.Num(),
InExpectedSection.Entries.Num()))
{
// ...we default to failure but continue checking for matches before returning
bAllSucceeded = false;
}
// Actual should have at least the expected item count
if (Parameters.EntryMatchParameters.bActualHasAllExpectedItems
&& !TestInstance.TestGreaterEqual(
FString::Printf(TEXT("Entry Count (for section \"%s\")"), *InActualSection.Name.ToString()),
InActualSection.Blocks.Num(),
InExpectedSection.Entries.Num()))
{
// ...we default to failure but continue checking for matches before returning
bAllSucceeded = false;
}
// Expected should have at least the actual item count
if (Parameters.EntryMatchParameters.bExpectedHasAllActualItems
&& !TestInstance.TestGreaterEqual(
FString::Printf(TEXT("Entry Count (for section \"%s\")"), *InActualSection.Name.ToString()),
InExpectedSection.Entries.Num(),
InActualSection.Blocks.Num()))
{
// ...we default to failure but continue checking for matches before returning
bAllSucceeded = false;
}
// If we don't require all entries to be specified in the expected section, keep track of the amount we expect to find and only succeed if we find them all
const int32 ExpectedEntryCount = InExpectedSection.Entries.Num();
int32 FoundEntryCount = 0;
// Track the last found index, to validate order if required
int32 LastFoundEntryIdx = INDEX_NONE;
bool bFoundEntriesInOrder = true;
for (int32 ExpectedEntryIdx = 0; ExpectedEntryIdx < InExpectedSection.Entries.Num(); ++ExpectedEntryIdx)
{
const TSharedRef<FMenuEntryBase>& ExpectedEntry = InExpectedSection.Entries[ExpectedEntryIdx];
for (int32 ActualEntryIdx = 0; ActualEntryIdx < InActualSection.Blocks.Num(); ++ActualEntryIdx)
{
if (!TestValidIndex(FString::Printf(TEXT("Entry (for section \"%s\")"), *InActualSection.Name.ToString()), ActualEntryIdx, InActualSection.Blocks))
{
bAllSucceeded = false;
continue;
}
const FToolMenuEntry& ActualEntry = InActualSection.Blocks[ActualEntryIdx];
// "None" matches anything, so we're a bit looser about matching requirements
const bool bMatchAny = ExpectedEntry->Name.IsNone();
if (bMatchAny || ExpectedEntry->Name == ActualEntry.Name)
{
if (!bMatchAny && ExpectedEntry->GetTypeName() == FMenuEntry::TypeName)
{
if (!Matches(StaticCastSharedRef<FMenuEntry>(ExpectedEntry).Get(), ActualEntry))
{
bAllSucceeded = false;
}
}
if (!bMatchAny && !Matches(&ExpectedEntry.Get(), ActualEntry))
{
bAllSucceeded = false;
}
else
{
// We matched, increment found
++FoundEntryCount;
if (Parameters.EntryMatchParameters.bActualHasExpectedOrder)
{
if (ActualEntryIdx < LastFoundEntryIdx)
{
bFoundEntriesInOrder = false;
}
LastFoundEntryIdx = ActualEntryIdx;
}
break;
}
}
}
}
if (!bAllSucceeded)
{
const bool bFoundExpectedSections = (!Parameters.EntryMatchParameters.bActualHasAllExpectedItems && FoundEntryCount > 0)
|| FoundEntryCount >= ExpectedEntryCount;
bAllSucceeded = bFoundExpectedSections;
}
// Finally, check if we found all entries in order if required
bAllSucceeded = bAllSucceeded && (!Parameters.EntryMatchParameters.bActualHasExpectedOrder || bFoundEntriesInOrder);
return bAllSucceeded;
}
bool FToolMenuAutomationTestAdapter::Matches(const FMenu& InExpectedMenu, const UToolMenu& InActualMenu)
{
bool bAllSucceeded = true;
if (!InExpectedMenu.Name.IsNone()
&& !TestInstance.TestEqual("ToolMenu Name", InActualMenu.MenuName, InExpectedMenu.Name))
{
bAllSucceeded = false;
}
// If we don't require all sections, we default to failure, and only succeed if we find all sections in the (expected) menu
if (!Parameters.SectionMatchParameters.bActualHasAllExpectedItems)
{
bAllSucceeded = false;
}
// If we require an exact match, we require an exact count
const bool bRequireMatchingSectionCount = Parameters.SectionMatchParameters.bActualHasAllExpectedItems && Parameters.SectionMatchParameters.bExpectedHasAllActualItems;
if (bRequireMatchingSectionCount
&& !TestInstance.TestEqual(
FString::Printf(TEXT("Section Count (for menu \"%s\")"), *InActualMenu.MenuName.ToString()),
InActualMenu.Sections.Num(),
InExpectedMenu.Sections.Num()))
{
// ...we default to failure but continue checking for matches before returning
bAllSucceeded = false;
}
// Actual should have at least the expected item count
if (Parameters.SectionMatchParameters.bActualHasAllExpectedItems
&& !TestInstance.TestGreaterEqual(
FString::Printf(TEXT("Section Count (for menu \"%s\")"), *InActualMenu.MenuName.ToString()),
InActualMenu.Sections.Num(),
InExpectedMenu.Sections.Num()))
{
// ...we default to failure but continue checking for matches before returning
bAllSucceeded = false;
}
// Expected should have at least the actual item count
if (Parameters.SectionMatchParameters.bExpectedHasAllActualItems
&& !TestInstance.TestGreaterEqual(
FString::Printf(TEXT("Section Count (for menu \"%s\")"), *InExpectedMenu.Name.ToString()),
InExpectedMenu.Sections.Num(),
InActualMenu.Sections.Num()))
{
// ...we default to failure but continue checking for matches before returning
bAllSucceeded = false;
}
// If we don't require all sections to be specified in the expected menu, keep track of the amount we expect to find and only succeed if we find them all
const int32 ExpectedSectionCount = InExpectedMenu.Sections.Num();
int32 FoundSectionCount = 0;
// Track the last found index, to validate order if required
int32 LastFoundSectionIdx = INDEX_NONE;
bool bFoundEntriesInOrder = true;
for (int32 ExpectedSectionIdx = 0; ExpectedSectionIdx < InExpectedMenu.Sections.Num(); ++ExpectedSectionIdx)
{
const TSharedRef<FMenuSection>& ExpectedSection = InExpectedMenu.Sections[ExpectedSectionIdx];
for (int32 ActualSectionIdx = 0; ActualSectionIdx < InActualMenu.Sections.Num(); ++ActualSectionIdx)
{
if (!TestValidIndex(FString::Printf(TEXT("Section (for menu \"%s\")"), *InActualMenu.MenuName.ToString()), ActualSectionIdx, InActualMenu.Sections))
{
bAllSucceeded = false;
continue;
}
const FToolMenuSection& ActualSection = InActualMenu.Sections[ActualSectionIdx];
// "None" matches anything, so we're a bit looser about matching requirements
const bool bMatchAny = ExpectedSection->Name.IsNone();
// "None" matches anything
if (bMatchAny || ExpectedSection->Name == ActualSection.Name)
{
if (!bMatchAny && !Matches(*ExpectedSection, ActualSection, Parameters.SectionMatchParameters.bMatchChildrenIfAnyExpected && ExpectedSection->Entries.Num() > 0))
{
bAllSucceeded = false;
}
else
{
// We matched, increment found
++FoundSectionCount;
if (Parameters.SectionMatchParameters.bActualHasExpectedOrder)
{
if (ActualSectionIdx < LastFoundSectionIdx)
{
bFoundEntriesInOrder = false;
}
LastFoundSectionIdx = ActualSectionIdx;
}
break;
}
}
}
}
if (!bAllSucceeded)
{
const bool bFoundExpectedSections = (!Parameters.SectionMatchParameters.bActualHasAllExpectedItems && FoundSectionCount > 0)
|| FoundSectionCount >= ExpectedSectionCount;
bAllSucceeded = bFoundExpectedSections;
}
// Finally, check if we found all entries in order if required
bAllSucceeded = bAllSucceeded && (!Parameters.SectionMatchParameters.bActualHasExpectedOrder || bFoundEntriesInOrder);
return bAllSucceeded;
}
}