2026-07-15 19:17:47 +02:00
// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
# include "Libraries/DirectiveUtilInputFunctionLibrary.h"
# include "Types/DirectiveUtilInputTypes.h"
# include "Types/DirectiveUtilTypes.h"
# include "InputMappingContext.h"
2026-08-15 21:06:43 +02:00
# include "EnhancedPlayerInput.h"
2026-07-15 19:17:47 +02:00
# include "EnhancedInputSubsystems.h"
# include "Engine/GameInstance.h"
# include "Engine/LocalPlayer.h"
# include "Engine/World.h"
# include "Engine/Engine.h"
# include "GameFramework/PlayerController.h"
# include "Misc/AutomationTest.h"
2026-08-15 21:06:43 +02:00
IMPLEMENT_SIMPLE_AUTOMATION_TEST ( FDirectiveUtilInputActivePathTest , " DirectiveUtilities.InputActivePathTests " , EAutomationTestFlags : : EditorContext | EAutomationTestFlags : : ClientContext | EAutomationTestFlags : : EngineFilter )
2026-07-15 19:17:47 +02:00
bool FDirectiveUtilInputActivePathTest : : RunTest ( const FString & Parameters )
{
AddExpectedMessagePlain ( TEXT ( " LocalPlayer not found. Cannot set input mapping contexts. " ) , ELogVerbosity : : Warning , EAutomationExpectedMessageFlags : : Contains , - 1 ) ;
AddExpectedMessagePlain ( TEXT ( " EnhancedInput subsystem not found. " ) , ELogVerbosity : : Warning , EAutomationExpectedMessageFlags : : Contains , - 1 ) ;
UGameInstance * GameInstance = NewObject < UGameInstance > ( GEngine ) ;
if ( ! GameInstance )
{
2026-08-15 21:06:43 +02:00
AddError ( TEXT ( " Could not create a game instance for Enhanced Input active-path assertions. " ) ) ;
return false ;
2026-07-15 19:17:47 +02:00
}
GameInstance - > AddToRoot ( ) ;
GameInstance - > InitializeStandalone ( ) ;
UWorld * World = GameInstance - > GetWorld ( ) ;
UClass * LocalPlayerClass = GEngine - > LocalPlayerClass ? GEngine - > LocalPlayerClass . Get ( ) : ULocalPlayer : : StaticClass ( ) ;
ULocalPlayer * LocalPlayer = NewObject < ULocalPlayer > ( GEngine , LocalPlayerClass ) ;
if ( World & & LocalPlayer )
{
GameInstance - > AddLocalPlayer ( LocalPlayer , FPlatformMisc : : GetPlatformUserForUserIndex ( 0 ) ) ;
}
if ( ! World | | ! LocalPlayer )
{
2026-08-15 21:06:43 +02:00
AddError ( TEXT ( " Local player unavailable in the Enhanced Input test harness. " ) ) ;
2026-07-15 19:17:47 +02:00
GameInstance - > Shutdown ( ) ;
GameInstance - > RemoveFromRoot ( ) ;
2026-08-15 21:06:43 +02:00
return false ;
2026-07-15 19:17:47 +02:00
}
APlayerController * PlayerController = World - > SpawnActor < APlayerController > ( ) ;
if ( ! PlayerController )
{
2026-08-15 21:06:43 +02:00
AddError ( TEXT ( " Could not spawn a player controller for Enhanced Input active-path assertions. " ) ) ;
2026-07-15 19:17:47 +02:00
GameInstance - > Shutdown ( ) ;
GameInstance - > RemoveFromRoot ( ) ;
2026-08-15 21:06:43 +02:00
return false ;
2026-07-15 19:17:47 +02:00
}
PlayerController - > SetPlayer ( LocalPlayer ) ;
2026-08-15 21:06:43 +02:00
PlayerController - > InitInputSystem ( ) ;
PlayerController - > PlayerInput = NewObject < UEnhancedPlayerInput > ( PlayerController ) ;
2026-07-15 19:17:47 +02:00
UEnhancedInputLocalPlayerSubsystem * Subsystem = UDirectiveUtilInputFunctionLibrary : : GetEnhancedInputSubsystem ( PlayerController ) ;
if ( ! Subsystem )
{
2026-08-15 21:06:43 +02:00
AddError ( TEXT ( " Enhanced Input subsystem unavailable for the synthetic local player. " ) ) ;
2026-07-15 19:17:47 +02:00
GameInstance - > Shutdown ( ) ;
GameInstance - > RemoveFromRoot ( ) ;
2026-08-15 21:06:43 +02:00
return false ;
2026-07-15 19:17:47 +02:00
}
TestNotNull ( " GetEnhancedInputSubsystem returns the subsystem for a live local player " , Subsystem ) ;
2026-08-15 21:06:43 +02:00
auto ApplyPendingMappings = [ Subsystem ]
{
FModifyContextOptions Options ;
Options . bForceImmediately = true ;
Subsystem - > RequestRebuildControlMappings ( Options ) ;
} ;
2026-07-15 19:17:47 +02:00
UInputMappingContext * ContextA = NewObject < UInputMappingContext > ( GetTransientPackage ( ) ) ;
UInputMappingContext * ContextB = NewObject < UInputMappingContext > ( GetTransientPackage ( ) ) ;
ContextA - > AddToRoot ( ) ;
ContextB - > AddToRoot ( ) ;
const TSoftObjectPtr < UInputMappingContext > SoftA ( ContextA ) ;
const TSoftObjectPtr < UInputMappingContext > SoftB ( ContextB ) ;
FDirectiveUtilEnhancedInputContextData DataA ;
DataA . InputContext = SoftA ;
DataA . Priority = 0 ;
TArray < FDirectiveUtilEnhancedInputContextData > ToAdd ;
ToAdd . Add ( DataA ) ;
TestEqual ( " AddInputMappingContexts succeeds for a live controller " ,
UDirectiveUtilInputFunctionLibrary : : AddInputMappingContexts ( PlayerController , ToAdd , false ) ,
EDirectiveUtilSuccessStatus : : Success ) ;
2026-08-15 21:06:43 +02:00
ApplyPendingMappings ( ) ;
2026-07-15 19:17:47 +02:00
TestTrue ( " Added context is reported active " ,
UDirectiveUtilInputFunctionLibrary : : IsInputMappingContextActive ( PlayerController , SoftA ) ) ;
TestEqual ( " SwapInputMappingContexts succeeds " ,
UDirectiveUtilInputFunctionLibrary : : SwapInputMappingContexts ( PlayerController , SoftA , SoftB , 0 , false ) ,
EDirectiveUtilSuccessStatus : : Success ) ;
2026-08-15 21:06:43 +02:00
ApplyPendingMappings ( ) ;
2026-07-15 19:17:47 +02:00
TestFalse ( " Swapped-out context is inactive " ,
UDirectiveUtilInputFunctionLibrary : : IsInputMappingContextActive ( PlayerController , SoftA ) ) ;
TestTrue ( " Swapped-in context is active " ,
UDirectiveUtilInputFunctionLibrary : : IsInputMappingContextActive ( PlayerController , SoftB ) ) ;
TArray < TSoftObjectPtr < UInputMappingContext > > ToRemove ;
ToRemove . Add ( SoftB ) ;
TestEqual ( " RemoveInputMappingContexts succeeds " ,
UDirectiveUtilInputFunctionLibrary : : RemoveInputMappingContexts ( PlayerController , ToRemove ) ,
EDirectiveUtilSuccessStatus : : Success ) ;
2026-08-15 21:06:43 +02:00
ApplyPendingMappings ( ) ;
2026-07-15 19:17:47 +02:00
TestFalse ( " Removed context is inactive " ,
UDirectiveUtilInputFunctionLibrary : : IsInputMappingContextActive ( PlayerController , SoftB ) ) ;
2026-08-15 21:06:43 +02:00
TestEqual ( " Swap adds the new context when the previous context is inactive " ,
UDirectiveUtilInputFunctionLibrary : : SwapInputMappingContexts ( PlayerController , SoftA , SoftB , 7 , true ) ,
EDirectiveUtilSuccessStatus : : Success ) ;
ApplyPendingMappings ( ) ;
TestTrue ( " Fallback swap context is active " ,
UDirectiveUtilInputFunctionLibrary : : IsInputMappingContextActive ( PlayerController , SoftB ) ) ;
UDirectiveUtilInputFunctionLibrary : : RemoveInputMappingContexts ( PlayerController , { SoftB } ) ;
ApplyPendingMappings ( ) ;
2026-07-15 19:17:47 +02:00
UDirectiveUtilInputFunctionLibrary : : AddInputMappingContexts ( PlayerController , ToAdd , false ) ;
TestEqual ( " ClearAllInputMappingContexts succeeds " ,
UDirectiveUtilInputFunctionLibrary : : ClearAllInputMappingContexts ( PlayerController ) ,
EDirectiveUtilSuccessStatus : : Success ) ;
2026-08-15 21:06:43 +02:00
ApplyPendingMappings ( ) ;
2026-07-15 19:17:47 +02:00
TestFalse ( " Context is inactive after clear-all " ,
UDirectiveUtilInputFunctionLibrary : : IsInputMappingContextActive ( PlayerController , SoftA ) ) ;
2026-08-15 21:06:43 +02:00
AddExpectedMessagePlain ( TEXT ( " input mapping contexts could not be loaded " ) , ELogVerbosity : : Warning , EAutomationExpectedMessageFlags : : Contains , - 1 ) ;
2026-07-15 19:17:47 +02:00
UDirectiveUtilInputFunctionLibrary : : AddInputMappingContexts ( PlayerController , ToAdd , false ) ;
2026-08-15 21:06:43 +02:00
ApplyPendingMappings ( ) ;
2026-07-15 19:17:47 +02:00
FDirectiveUtilEnhancedInputContextData UnresolvableData ;
UnresolvableData . Priority = 0 ;
TArray < FDirectiveUtilEnhancedInputContextData > UnresolvableToAdd ;
UnresolvableToAdd . Add ( UnresolvableData ) ;
TestEqual ( " AddInputMappingContexts fails when no context loads " ,
UDirectiveUtilInputFunctionLibrary : : AddInputMappingContexts ( PlayerController , UnresolvableToAdd , true ) ,
EDirectiveUtilSuccessStatus : : Failure ) ;
TestTrue ( " Previously active context survives a failed clearing add " ,
UDirectiveUtilInputFunctionLibrary : : IsInputMappingContextActive ( PlayerController , SoftA ) ) ;
ContextA - > RemoveFromRoot ( ) ;
ContextB - > RemoveFromRoot ( ) ;
GameInstance - > Shutdown ( ) ;
GameInstance - > RemoveFromRoot ( ) ;
return true ;
}