@@ -634,6 +634,61 @@ public async Task CanTriggerUpdatesOnCascadingValuesFromServiceProvider()
634634 await cascadingValueSource . NotifyChangedAsync ( new MyParamType ( "Nobody is listening, but this shouldn't be an error" ) ) ;
635635 }
636636
637+ [ Fact ]
638+ public async Task CanAddSubscriberDuringChangeNotification ( )
639+ {
640+ // Arrange
641+ var services = new ServiceCollection ( ) ;
642+ var paramValue = new MyParamType ( "Initial value" ) ;
643+ var cascadingValueSource = new CascadingValueSource < MyParamType > ( paramValue , isFixed : false ) ;
644+ services . AddCascadingValue ( _ => cascadingValueSource ) ;
645+ var renderer = new TestRenderer ( services . BuildServiceProvider ( ) ) ;
646+ var component = new ConditionallyRenderSubscriberComponent ( )
647+ {
648+ RenderWhenEqualTo = "Final value" ,
649+ } ;
650+
651+ // Act/Assert: Initial render
652+ var componentId = await renderer . Dispatcher . InvokeAsync ( ( ) => renderer . AssignRootComponentId ( component ) ) ;
653+ renderer . RenderRootComponent ( componentId ) ;
654+ var firstBatch = renderer . Batches . Single ( ) ;
655+ var diff = firstBatch . DiffsByComponentId [ componentId ] . Single ( ) ;
656+ Assert . Collection ( diff . Edits ,
657+ edit =>
658+ {
659+ Assert . Equal ( RenderTreeEditType . PrependFrame , edit . Type ) ;
660+ AssertFrame . Text (
661+ firstBatch . ReferenceFrames [ edit . ReferenceFrameIndex ] ,
662+ "CascadingParameter=Initial value" ) ;
663+ } ) ;
664+ Assert . Equal ( 1 , component . NumRenders ) ;
665+
666+ // Act: Second render
667+ paramValue . ChangeValue ( "Final value" ) ;
668+ await cascadingValueSource . NotifyChangedAsync ( ) ;
669+ var secondBatch = renderer . Batches [ 1 ] ;
670+ var diff2 = secondBatch . DiffsByComponentId [ componentId ] . Single ( ) ;
671+
672+ // Assert: Subscriber can get added during change notification and receive the cascading value
673+ AssertFrame . Text (
674+ secondBatch . ReferenceFrames [ diff2 . Edits [ 0 ] . ReferenceFrameIndex ] ,
675+ "CascadingParameter=Final value" ) ;
676+ Assert . Equal ( 2 , component . NumRenders ) ;
677+
678+ // Assert: Subscriber can get added during change notification and receive the cascading value
679+ var nestedComponent = FindComponent < SimpleSubscriberComponent > ( secondBatch , out var nestedComponentId ) ;
680+ var nestedComponentDiff = secondBatch . DiffsByComponentId [ nestedComponentId ] . Single ( ) ;
681+ Assert . Collection ( nestedComponentDiff . Edits ,
682+ edit =>
683+ {
684+ Assert . Equal ( RenderTreeEditType . PrependFrame , edit . Type ) ;
685+ AssertFrame . Text (
686+ secondBatch . ReferenceFrames [ edit . ReferenceFrameIndex ] ,
687+ "CascadingParameter=Final value" ) ;
688+ } ) ;
689+ Assert . Equal ( 1 , nestedComponent . NumRenders ) ;
690+ }
691+
637692 [ Fact ]
638693 public async Task AfterSupplyingValueThroughNotifyChanged_InitialValueFactoryIsNotUsed ( )
639694 {
@@ -772,6 +827,40 @@ public void CanUseTryAddPatternForCascadingValuesInServiceCollection_CascadingVa
772827 Assert . Equal ( 2 , services . Count ( ) ) ;
773828 }
774829
830+ [ Theory ]
831+ [ InlineData ( 0 ) ]
832+ [ InlineData ( 1 ) ]
833+ [ InlineData ( CascadingValueSource < MyParamType > . ComponentStateBuffer . Capacity - 1 ) ]
834+ [ InlineData ( CascadingValueSource < MyParamType > . ComponentStateBuffer . Capacity ) ]
835+ [ InlineData ( CascadingValueSource < MyParamType > . ComponentStateBuffer . Capacity + 1 ) ]
836+ [ InlineData ( CascadingValueSource < MyParamType > . ComponentStateBuffer . Capacity * 2 ) ]
837+ public async Task CanHaveManySubscribers ( int numSubscribers )
838+ {
839+ // Arrange
840+ var services = new ServiceCollection ( ) ;
841+ var paramValue = new MyParamType ( "Initial value" ) ;
842+ var cascadingValueSource = new CascadingValueSource < MyParamType > ( paramValue , isFixed : false ) ;
843+ services . AddCascadingValue ( _ => cascadingValueSource ) ;
844+ var renderer = new TestRenderer ( services . BuildServiceProvider ( ) ) ;
845+ var components = Enumerable . Range ( 0 , numSubscribers ) . Select ( _ => new SimpleSubscriberComponent ( ) ) . ToArray ( ) ;
846+
847+ // Act/Assert: Initial render
848+ foreach ( var component in components )
849+ {
850+ await renderer . Dispatcher . InvokeAsync ( ( ) => renderer . AssignRootComponentId ( component ) ) ;
851+ component . TriggerRender ( ) ;
852+ Assert . Equal ( 1 , component . NumRenders ) ;
853+ }
854+
855+ // Act/Assert: All components re-render when the cascading value changes
856+ paramValue . ChangeValue ( "Final value" ) ;
857+ await cascadingValueSource . NotifyChangedAsync ( ) ;
858+ foreach ( var component in components )
859+ {
860+ Assert . Equal ( 2 , component . NumRenders ) ;
861+ }
862+ }
863+
775864 private class SingleDeliveryValue ( string text )
776865 {
777866 public string Text => text ;
@@ -861,6 +950,43 @@ public void AttemptIllegalAccessToLastParameterView()
861950 }
862951 }
863952
953+ class ConditionallyRenderSubscriberComponent : AutoRenderComponent
954+ {
955+ public int NumRenders { get ; private set ; }
956+
957+ public SimpleSubscriberComponent NestedSubscriber { get ; private set ; }
958+
959+ [ Parameter ] public string RenderWhenEqualTo { get ; set ; }
960+
961+ [ CascadingParameter ] MyParamType CascadingParameter { get ; set ; }
962+
963+ protected override void BuildRenderTree ( RenderTreeBuilder builder )
964+ {
965+ NumRenders ++ ;
966+ builder . AddContent ( 0 , $ "CascadingParameter={ CascadingParameter } ") ;
967+
968+ if ( string . Equals ( RenderWhenEqualTo , CascadingParameter . ToString ( ) , StringComparison . OrdinalIgnoreCase ) )
969+ {
970+ builder . OpenComponent < SimpleSubscriberComponent > ( 1 ) ;
971+ builder . AddComponentReferenceCapture ( 2 , component => NestedSubscriber = component as SimpleSubscriberComponent ) ;
972+ builder . CloseComponent ( ) ;
973+ }
974+ }
975+ }
976+
977+ class SimpleSubscriberComponent : AutoRenderComponent
978+ {
979+ public int NumRenders { get ; private set ; }
980+
981+ [ CascadingParameter ] MyParamType CascadingParameter { get ; set ; }
982+
983+ protected override void BuildRenderTree ( RenderTreeBuilder builder )
984+ {
985+ NumRenders ++ ;
986+ builder . AddContent ( 0 , $ "CascadingParameter={ CascadingParameter } ") ;
987+ }
988+ }
989+
864990 class SingleDeliveryParameterConsumerComponent : AutoRenderComponent
865991 {
866992 public int NumSetParametersCalls { get ; private set ; }
0 commit comments