@@ -431,6 +431,154 @@ public async Task PersistenceFails_MultipleComponentsUseInvalidKeyTypes(object c
431431 Assert . Contains ( sink . Writes , w => w is { LogLevel : LogLevel . Error } && w . EventId == new EventId ( 1000 , "PersistenceCallbackError" ) ) ;
432432 }
433433
434+ [ Fact ]
435+ public async Task PersistAsync_CanPersistValueTypes_IntProperty ( )
436+ {
437+ // Arrange
438+ var ( logger , sink ) = CreateTestLogger ( ) ;
439+ var state = new Dictionary < string , byte [ ] > ( ) ;
440+ var store = new TestStore ( state ) ;
441+ var persistenceManager = new ComponentStatePersistenceManager (
442+ logger ,
443+ new ServiceCollection ( ) . BuildServiceProvider ( ) ) ;
444+
445+ var renderer = new TestRenderer ( ) ;
446+ var component = new ValueTypeTestComponent { IntValue = 42 } ;
447+ var componentStates = CreateComponentState ( renderer , [ ( component , null ) ] , null ) ;
448+ var componentState = componentStates . First ( ) ;
449+
450+ // Create the provider and subscribe the component
451+ var provider = new SupplyParameterFromPersistentComponentStateValueProvider ( persistenceManager . State ) ;
452+ var cascadingParameterInfo = CreateCascadingParameterInfo ( nameof ( ValueTypeTestComponent . IntValue ) , typeof ( int ) ) ;
453+ provider . Subscribe ( componentState , cascadingParameterInfo ) ;
454+
455+ // Act
456+ await persistenceManager . PersistStateAsync ( store , renderer ) ;
457+
458+ // Assert - Check if there were any errors in the persistence
459+ var errors = sink . Writes . Where ( w => w . LogLevel == LogLevel . Error ) . ToList ( ) ;
460+ if ( errors . Any ( ) )
461+ {
462+ var errorMessage = string . Join ( "; " , errors . Select ( e => e . State ? . ToString ( ) ) ) ;
463+ throw new InvalidOperationException ( $ "Persistence failed with errors: { errorMessage } ") ;
464+ }
465+
466+ Assert . NotEmpty ( store . State ) ;
467+
468+ // Verify the value was persisted correctly
469+ var newState = new PersistentComponentState ( new Dictionary < string , byte [ ] > ( ) , [ ] ) ;
470+ newState . InitializeExistingState ( store . State ) ;
471+
472+ var key = SupplyParameterFromPersistentComponentStateValueProvider . ComputeKey ( componentState , cascadingParameterInfo . PropertyName ) ;
473+ Assert . True ( newState . TryTakeFromJson < int > ( key , out var retrievedValue ) ) ;
474+ Assert . Equal ( 42 , retrievedValue ) ;
475+ }
476+
477+ [ Fact ]
478+ public async Task PersistAsync_CanPersistValueTypes_NullableIntProperty ( )
479+ {
480+ // Arrange
481+ var state = new Dictionary < string , byte [ ] > ( ) ;
482+ var store = new TestStore ( state ) ;
483+ var persistenceManager = new ComponentStatePersistenceManager (
484+ NullLogger < ComponentStatePersistenceManager > . Instance ,
485+ new ServiceCollection ( ) . BuildServiceProvider ( ) ) ;
486+
487+ var renderer = new TestRenderer ( ) ;
488+ var component = new ValueTypeTestComponent { NullableIntValue = 123 } ;
489+ var componentStates = CreateComponentState ( renderer , [ ( component , null ) ] , null ) ;
490+ var componentState = componentStates . First ( ) ;
491+
492+ // Create the provider and subscribe the component
493+ var provider = new SupplyParameterFromPersistentComponentStateValueProvider ( persistenceManager . State ) ;
494+ var cascadingParameterInfo = CreateCascadingParameterInfo ( nameof ( ValueTypeTestComponent . NullableIntValue ) , typeof ( int ? ) ) ;
495+ provider . Subscribe ( componentState , cascadingParameterInfo ) ;
496+
497+ // Act
498+ await persistenceManager . PersistStateAsync ( store , renderer ) ;
499+
500+ // Assert
501+ Assert . NotEmpty ( store . State ) ;
502+
503+ // Verify the value was persisted correctly
504+ var newState = new PersistentComponentState ( new Dictionary < string , byte [ ] > ( ) , [ ] ) ;
505+ newState . InitializeExistingState ( store . State ) ;
506+
507+ var key = SupplyParameterFromPersistentComponentStateValueProvider . ComputeKey ( componentState , cascadingParameterInfo . PropertyName ) ;
508+ Assert . True ( newState . TryTakeFromJson < int ? > ( key , out var retrievedValue ) ) ;
509+ Assert . Equal ( 123 , retrievedValue ) ;
510+ }
511+
512+ [ Fact ]
513+ public async Task PersistAsync_CanPersistValueTypes_TupleProperty ( )
514+ {
515+ // Arrange
516+ var state = new Dictionary < string , byte [ ] > ( ) ;
517+ var store = new TestStore ( state ) ;
518+ var persistenceManager = new ComponentStatePersistenceManager (
519+ NullLogger < ComponentStatePersistenceManager > . Instance ,
520+ new ServiceCollection ( ) . BuildServiceProvider ( ) ) ;
521+
522+ var renderer = new TestRenderer ( ) ;
523+ var component = new ValueTypeTestComponent { TupleValue = ( "test" , 456 ) } ;
524+ var componentStates = CreateComponentState ( renderer , [ ( component , null ) ] , null ) ;
525+ var componentState = componentStates . First ( ) ;
526+
527+ // Create the provider and subscribe the component
528+ var provider = new SupplyParameterFromPersistentComponentStateValueProvider ( persistenceManager . State ) ;
529+ var cascadingParameterInfo = CreateCascadingParameterInfo ( nameof ( ValueTypeTestComponent . TupleValue ) , typeof ( ( string , int ) ) ) ;
530+ provider . Subscribe ( componentState , cascadingParameterInfo ) ;
531+
532+ // Act
533+ await persistenceManager . PersistStateAsync ( store , renderer ) ;
534+
535+ // Assert
536+ Assert . NotEmpty ( store . State ) ;
537+
538+ // Verify the value was persisted correctly
539+ var newState = new PersistentComponentState ( new Dictionary < string , byte [ ] > ( ) , [ ] ) ;
540+ newState . InitializeExistingState ( store . State ) ;
541+
542+ var key = SupplyParameterFromPersistentComponentStateValueProvider . ComputeKey ( componentState , cascadingParameterInfo . PropertyName ) ;
543+ Assert . True ( newState . TryTakeFromJson < ( string , int ) > ( key , out var retrievedValue ) ) ;
544+ Assert . Equal ( ( "test" , 456 ) , retrievedValue ) ;
545+ }
546+
547+ [ Fact ]
548+ public async Task PersistAsync_CanPersistValueTypes_NullableTupleProperty ( )
549+ {
550+ // Arrange
551+ var state = new Dictionary < string , byte [ ] > ( ) ;
552+ var store = new TestStore ( state ) ;
553+ var persistenceManager = new ComponentStatePersistenceManager (
554+ NullLogger < ComponentStatePersistenceManager > . Instance ,
555+ new ServiceCollection ( ) . BuildServiceProvider ( ) ) ;
556+
557+ var renderer = new TestRenderer ( ) ;
558+ var component = new ValueTypeTestComponent { NullableTupleValue = ( "test2" , 789 ) } ;
559+ var componentStates = CreateComponentState ( renderer , [ ( component , null ) ] , null ) ;
560+ var componentState = componentStates . First ( ) ;
561+
562+ // Create the provider and subscribe the component
563+ var provider = new SupplyParameterFromPersistentComponentStateValueProvider ( persistenceManager . State ) ;
564+ var cascadingParameterInfo = CreateCascadingParameterInfo ( nameof ( ValueTypeTestComponent . NullableTupleValue ) , typeof ( ( string , int ) ? ) ) ;
565+ provider . Subscribe ( componentState , cascadingParameterInfo ) ;
566+
567+ // Act
568+ await persistenceManager . PersistStateAsync ( store , renderer ) ;
569+
570+ // Assert
571+ Assert . NotEmpty ( store . State ) ;
572+
573+ // Verify the value was persisted correctly
574+ var newState = new PersistentComponentState ( new Dictionary < string , byte [ ] > ( ) , [ ] ) ;
575+ newState . InitializeExistingState ( store . State ) ;
576+
577+ var key = SupplyParameterFromPersistentComponentStateValueProvider . ComputeKey ( componentState , cascadingParameterInfo . PropertyName ) ;
578+ Assert . True ( newState . TryTakeFromJson < ( string , int ) ? > ( key , out var retrievedValue ) ) ;
579+ Assert . Equal ( ( "test2" , 789 ) , retrievedValue ) ;
580+ }
581+
434582 private static void InitializeState ( PersistentComponentState state , List < ( ComponentState componentState , string propertyName , string value ) > items )
435583 {
436584 var dictionary = new Dictionary < string , byte [ ] > ( ) ;
@@ -452,7 +600,7 @@ private static CascadingParameterInfo CreateCascadingParameterInfo(string proper
452600
453601 private static List < ComponentState > CreateComponentState (
454602 TestRenderer renderer ,
455- List < ( TestComponent , object ) > components ,
603+ List < ( IComponent , object ) > components ,
456604 ParentComponent parentComponent = null )
457605 {
458606 var i = 1 ;
@@ -464,7 +612,20 @@ private static List<ComponentState> CreateComponentState(
464612 var componentState = new ComponentState ( renderer , i ++ , component , parentComponentState ) ;
465613 if ( currentRenderTree != null && key != null )
466614 {
467- currentRenderTree . OpenComponent < TestComponent > ( 0 ) ;
615+ // Open component based on the actual component type
616+ if ( component is TestComponent )
617+ {
618+ currentRenderTree . OpenComponent < TestComponent > ( 0 ) ;
619+ }
620+ else if ( component is ValueTypeTestComponent )
621+ {
622+ currentRenderTree . OpenComponent < ValueTypeTestComponent > ( 0 ) ;
623+ }
624+ else
625+ {
626+ currentRenderTree . OpenComponent < IComponent > ( 0 ) ;
627+ }
628+
468629 var frames = currentRenderTree . GetFrames ( ) ;
469630 frames . Array [ frames . Count - 1 ] . ComponentStateField = componentState ;
470631 if ( key != null )
@@ -497,6 +658,24 @@ private class TestComponent : IComponent
497658 public Task SetParametersAsync ( ParameterView parameters ) => throw new NotImplementedException ( ) ;
498659 }
499660
661+ private class ValueTypeTestComponent : IComponent
662+ {
663+ [ SupplyParameterFromPersistentComponentState ]
664+ public int IntValue { get ; set ; }
665+
666+ [ SupplyParameterFromPersistentComponentState ]
667+ public int ? NullableIntValue { get ; set ; }
668+
669+ [ SupplyParameterFromPersistentComponentState ]
670+ public ( string , int ) TupleValue { get ; set ; }
671+
672+ [ SupplyParameterFromPersistentComponentState ]
673+ public ( string , int ) ? NullableTupleValue { get ; set ; }
674+
675+ public void Attach ( RenderHandle renderHandle ) => throw new NotImplementedException ( ) ;
676+ public Task SetParametersAsync ( ParameterView parameters ) => throw new NotImplementedException ( ) ;
677+ }
678+
500679 private class TestStore ( Dictionary < string , byte [ ] > initialState ) : IPersistentComponentStateStore
501680 {
502681 public IDictionary < string , byte [ ] > State { get ; set ; } = initialState ;
0 commit comments