@@ -634,6 +634,61 @@ public async Task CanTriggerUpdatesOnCascadingValuesFromServiceProvider()
634
634
await cascadingValueSource . NotifyChangedAsync ( new MyParamType ( "Nobody is listening, but this shouldn't be an error" ) ) ;
635
635
}
636
636
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
+
637
692
[ Fact ]
638
693
public async Task AfterSupplyingValueThroughNotifyChanged_InitialValueFactoryIsNotUsed ( )
639
694
{
@@ -772,6 +827,40 @@ public void CanUseTryAddPatternForCascadingValuesInServiceCollection_CascadingVa
772
827
Assert . Equal ( 2 , services . Count ( ) ) ;
773
828
}
774
829
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
+
775
864
private class SingleDeliveryValue ( string text )
776
865
{
777
866
public string Text => text ;
@@ -861,6 +950,43 @@ public void AttemptIllegalAccessToLastParameterView()
861
950
}
862
951
}
863
952
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
+
864
990
class SingleDeliveryParameterConsumerComponent : AutoRenderComponent
865
991
{
866
992
public int NumSetParametersCalls { get ; private set ; }
0 commit comments