33
44using System ;
55using System . Collections . Generic ;
6+ using System . Linq ;
67using System . Text ;
78using System . Text . Json ;
89using Microsoft . AspNetCore . Components . Infrastructure ;
@@ -431,6 +432,146 @@ public async Task PersistenceFails_MultipleComponentsUseInvalidKeyTypes(object c
431432 Assert . Contains ( sink . Writes , w => w is { LogLevel : LogLevel . Error } && w . EventId == new EventId ( 1000 , "PersistenceCallbackError" ) ) ;
432433 }
433434
435+ [ Fact ]
436+ public async Task PersistAsync_CanPersistValueTypes_IntProperty ( )
437+ {
438+ // Arrange
439+ var state = new Dictionary < string , byte [ ] > ( ) ;
440+ var store = new TestStore ( state ) ;
441+ var persistenceManager = new ComponentStatePersistenceManager (
442+ NullLogger < ComponentStatePersistenceManager > . Instance ,
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
459+ Assert . NotEmpty ( store . State ) ;
460+
461+ // Verify the value was persisted correctly
462+ var newState = new PersistentComponentState ( new Dictionary < string , byte [ ] > ( ) , [ ] ) ;
463+ newState . InitializeExistingState ( store . State ) ;
464+
465+ var key = SupplyParameterFromPersistentComponentStateValueProvider . ComputeKey ( componentState , cascadingParameterInfo . PropertyName ) ;
466+ Assert . True ( newState . TryTakeFromJson < int > ( key , out var retrievedValue ) ) ;
467+ Assert . Equal ( 42 , retrievedValue ) ;
468+ }
469+
470+ [ Fact ]
471+ public async Task PersistAsync_CanPersistValueTypes_NullableIntProperty ( )
472+ {
473+ // Arrange
474+ var state = new Dictionary < string , byte [ ] > ( ) ;
475+ var store = new TestStore ( state ) ;
476+ var persistenceManager = new ComponentStatePersistenceManager (
477+ NullLogger < ComponentStatePersistenceManager > . Instance ,
478+ new ServiceCollection ( ) . BuildServiceProvider ( ) ) ;
479+
480+ var renderer = new TestRenderer ( ) ;
481+ var component = new ValueTypeTestComponent { NullableIntValue = 123 } ;
482+ var componentStates = CreateComponentState ( renderer , [ ( component , null ) ] , null ) ;
483+ var componentState = componentStates . First ( ) ;
484+
485+ // Create the provider and subscribe the component
486+ var provider = new SupplyParameterFromPersistentComponentStateValueProvider ( persistenceManager . State ) ;
487+ var cascadingParameterInfo = CreateCascadingParameterInfo ( nameof ( ValueTypeTestComponent . NullableIntValue ) , typeof ( int ? ) ) ;
488+ provider . Subscribe ( componentState , cascadingParameterInfo ) ;
489+
490+ // Act
491+ await persistenceManager . PersistStateAsync ( store , renderer ) ;
492+
493+ // Assert
494+ Assert . NotEmpty ( store . State ) ;
495+
496+ // Verify the value was persisted correctly
497+ var newState = new PersistentComponentState ( new Dictionary < string , byte [ ] > ( ) , [ ] ) ;
498+ newState . InitializeExistingState ( store . State ) ;
499+
500+ var key = SupplyParameterFromPersistentComponentStateValueProvider . ComputeKey ( componentState , cascadingParameterInfo . PropertyName ) ;
501+ Assert . True ( newState . TryTakeFromJson < int ? > ( key , out var retrievedValue ) ) ;
502+ Assert . Equal ( 123 , retrievedValue ) ;
503+ }
504+
505+ [ Fact ]
506+ public async Task PersistAsync_CanPersistValueTypes_TupleProperty ( )
507+ {
508+ // Arrange
509+ var state = new Dictionary < string , byte [ ] > ( ) ;
510+ var store = new TestStore ( state ) ;
511+ var persistenceManager = new ComponentStatePersistenceManager (
512+ NullLogger < ComponentStatePersistenceManager > . Instance ,
513+ new ServiceCollection ( ) . BuildServiceProvider ( ) ) ;
514+
515+ var renderer = new TestRenderer ( ) ;
516+ var component = new ValueTypeTestComponent { TupleValue = ( "test" , 456 ) } ;
517+ var componentStates = CreateComponentState ( renderer , [ ( component , null ) ] , null ) ;
518+ var componentState = componentStates . First ( ) ;
519+
520+ // Create the provider and subscribe the component
521+ var provider = new SupplyParameterFromPersistentComponentStateValueProvider ( persistenceManager . State ) ;
522+ var cascadingParameterInfo = CreateCascadingParameterInfo ( nameof ( ValueTypeTestComponent . TupleValue ) , typeof ( ( string , int ) ) ) ;
523+ provider . Subscribe ( componentState , cascadingParameterInfo ) ;
524+
525+ // Act
526+ await persistenceManager . PersistStateAsync ( store , renderer ) ;
527+
528+ // Assert
529+ Assert . NotEmpty ( store . State ) ;
530+
531+ // Verify the value was persisted correctly
532+ var newState = new PersistentComponentState ( new Dictionary < string , byte [ ] > ( ) , [ ] ) ;
533+ newState . InitializeExistingState ( store . State ) ;
534+
535+ var key = SupplyParameterFromPersistentComponentStateValueProvider . ComputeKey ( componentState , cascadingParameterInfo . PropertyName ) ;
536+ Assert . True ( newState . TryTakeFromJson < ( string , int ) > ( key , out var retrievedValue ) ) ;
537+ Assert . Equal ( ( "test" , 456 ) , retrievedValue ) ;
538+ }
539+
540+ [ Fact ]
541+ public async Task PersistAsync_CanPersistValueTypes_NullableTupleProperty ( )
542+ {
543+ // Arrange
544+ var state = new Dictionary < string , byte [ ] > ( ) ;
545+ var store = new TestStore ( state ) ;
546+ var persistenceManager = new ComponentStatePersistenceManager (
547+ NullLogger < ComponentStatePersistenceManager > . Instance ,
548+ new ServiceCollection ( ) . BuildServiceProvider ( ) ) ;
549+
550+ var renderer = new TestRenderer ( ) ;
551+ var component = new ValueTypeTestComponent { NullableTupleValue = ( "test2" , 789 ) } ;
552+ var componentStates = CreateComponentState ( renderer , [ ( component , null ) ] , null ) ;
553+ var componentState = componentStates . First ( ) ;
554+
555+ // Create the provider and subscribe the component
556+ var provider = new SupplyParameterFromPersistentComponentStateValueProvider ( persistenceManager . State ) ;
557+ var cascadingParameterInfo = CreateCascadingParameterInfo ( nameof ( ValueTypeTestComponent . NullableTupleValue ) , typeof ( ( string , int ) ? ) ) ;
558+ provider . Subscribe ( componentState , cascadingParameterInfo ) ;
559+
560+ // Act
561+ await persistenceManager . PersistStateAsync ( store , renderer ) ;
562+
563+ // Assert
564+ Assert . NotEmpty ( store . State ) ;
565+
566+ // Verify the value was persisted correctly
567+ var newState = new PersistentComponentState ( new Dictionary < string , byte [ ] > ( ) , [ ] ) ;
568+ newState . InitializeExistingState ( store . State ) ;
569+
570+ var key = SupplyParameterFromPersistentComponentStateValueProvider . ComputeKey ( componentState , cascadingParameterInfo . PropertyName ) ;
571+ Assert . True ( newState . TryTakeFromJson < ( string , int ) ? > ( key , out var retrievedValue ) ) ;
572+ Assert . Equal ( ( "test2" , 789 ) , retrievedValue ) ;
573+ }
574+
434575 private static void InitializeState ( PersistentComponentState state , List < ( ComponentState componentState , string propertyName , string value ) > items )
435576 {
436577 var dictionary = new Dictionary < string , byte [ ] > ( ) ;
@@ -452,7 +593,7 @@ private static CascadingParameterInfo CreateCascadingParameterInfo(string proper
452593
453594 private static List < ComponentState > CreateComponentState (
454595 TestRenderer renderer ,
455- List < ( TestComponent , object ) > components ,
596+ List < ( IComponent , object ) > components ,
456597 ParentComponent parentComponent = null )
457598 {
458599 var i = 1 ;
@@ -464,7 +605,20 @@ private static List<ComponentState> CreateComponentState(
464605 var componentState = new ComponentState ( renderer , i ++ , component , parentComponentState ) ;
465606 if ( currentRenderTree != null && key != null )
466607 {
467- currentRenderTree . OpenComponent < TestComponent > ( 0 ) ;
608+ // Open component based on the actual component type
609+ if ( component is TestComponent )
610+ {
611+ currentRenderTree . OpenComponent < TestComponent > ( 0 ) ;
612+ }
613+ else if ( component is ValueTypeTestComponent )
614+ {
615+ currentRenderTree . OpenComponent < ValueTypeTestComponent > ( 0 ) ;
616+ }
617+ else
618+ {
619+ currentRenderTree . OpenComponent < IComponent > ( 0 ) ;
620+ }
621+
468622 var frames = currentRenderTree . GetFrames ( ) ;
469623 frames . Array [ frames . Count - 1 ] . ComponentStateField = componentState ;
470624 if ( key != null )
@@ -497,6 +651,24 @@ private class TestComponent : IComponent
497651 public Task SetParametersAsync ( ParameterView parameters ) => throw new NotImplementedException ( ) ;
498652 }
499653
654+ private class ValueTypeTestComponent : IComponent
655+ {
656+ [ SupplyParameterFromPersistentComponentState ]
657+ public int IntValue { get ; set ; }
658+
659+ [ SupplyParameterFromPersistentComponentState ]
660+ public int ? NullableIntValue { get ; set ; }
661+
662+ [ SupplyParameterFromPersistentComponentState ]
663+ public ( string , int ) TupleValue { get ; set ; }
664+
665+ [ SupplyParameterFromPersistentComponentState ]
666+ public ( string , int ) ? NullableTupleValue { get ; set ; }
667+
668+ public void Attach ( RenderHandle renderHandle ) => throw new NotImplementedException ( ) ;
669+ public Task SetParametersAsync ( ParameterView parameters ) => throw new NotImplementedException ( ) ;
670+ }
671+
500672 private class TestStore ( Dictionary < string , byte [ ] > initialState ) : IPersistentComponentStateStore
501673 {
502674 public IDictionary < string , byte [ ] > State { get ; set ; } = initialState ;
0 commit comments