@@ -2240,35 +2240,52 @@ public async Task When_LargeExtent_And_Very_Large_List_Scroll_To_End_And_Back_Ha
2240
2240
2241
2241
var scroll = list . FindFirstDescendant < ScrollViewer > ( ) ;
2242
2242
Assert . IsNotNull ( scroll ) ;
2243
- dataContextChanged . Should ( ) . BeLessThan ( 10 , $ "dataContextChanged { dataContextChanged } ") ;
2244
-
2245
- ScrollTo ( list , ElementHeight ) ;
2246
-
2247
- await WindowHelper . WaitForIdle ( ) ;
2248
-
2249
- materialized . Should ( ) . BeLessThan ( 12 , $ "materialized { materialized } ") ;
2250
- dataContextChanged . Should ( ) . BeLessThan ( 11 , $ "dataContextChanged { dataContextChanged } ") ;
2251
2243
2252
- ScrollTo ( list , ElementHeight * 3 ) ;
2253
-
2254
- await WindowHelper . WaitForIdle ( ) ;
2244
+ int expectedMaterialized = 0 , expectedDCChanged = 0 ;
2245
+ int [ ] previouslyMaterializedItems = [ ] ;
2246
+ async Task ScrollAndValidate ( string context , double ? scrollTo )
2247
+ {
2248
+ if ( scrollTo is { } voffset )
2249
+ {
2250
+ ScrollTo ( list , voffset ) ;
2251
+ }
2252
+ await WindowHelper . WaitForIdle ( ) ;
2255
2253
2256
- materialized . Should ( ) . BeLessThan ( 14 , $ "materialized { materialized } ") ;
2257
- dataContextChanged . Should ( ) . BeLessThan ( 13 , $ "dataContextChanged { dataContextChanged } ") ;
2254
+ #if HAS_UNO && ! ( __IOS__ || __ANDROID__ )
2255
+ var evpScaling = ( list . ItemsPanelRoot as IVirtualizingPanel ) . GetLayouter ( ) . CacheLength * VirtualizingPanelLayout . ExtendedViewportScaling ;
2256
+ #else
2257
+ var evpScaling = 0.5 ;
2258
+ #endif
2258
2259
2259
- ScrollTo ( list , scroll . ExtentHeight / 2 ) ; // Scroll to middle
2260
+ var offset = scroll . VerticalOffset ;
2261
+ var max = scroll . ExtentHeight ;
2262
+ var vp = scroll . ViewportHeight ;
2260
2263
2261
- await WindowHelper . WaitForIdle ( ) ;
2264
+ var evpStart = Math . Clamp ( offset - vp * evpScaling , 0 , max ) ;
2265
+ var evpEnd = Math . Clamp ( offset + vp + vp * evpScaling , 0 , max ) ;
2262
2266
2263
- materialized . Should ( ) . BeLessThan ( 14 , $ "materialized { materialized } ") ;
2264
- dataContextChanged . Should ( ) . BeLessThan ( 25 , $ "dataContextChanged { dataContextChanged } ") ;
2267
+ var firstIndex = ( int ) Math . Round ( evpStart / ElementHeight , 0 , MidpointRounding . ToNegativeInfinity ) ;
2268
+ var lastIndex = ( int ) Math . Round ( evpEnd / ElementHeight , 0 , MidpointRounding . ToPositiveInfinity ) - 1 ;
2269
+ var itemsInEVP = Enumerable . Range ( firstIndex , lastIndex - firstIndex + 1 ) . ToArray ( ) ;
2270
+ var newItemsInEVP = itemsInEVP . Except ( previouslyMaterializedItems ) . ToArray ( ) ;
2265
2271
2266
- ScrollTo ( list , scroll . ExtentHeight / 4 ) ; // Scroll to Quarter
2272
+ // materialized starts with +1 extra, since we use it to determine whether the DataTemplate itself is a self-container
2273
+ // Math.Max to count the historical highest, since "materialization" doesnt "unhappen" (we dont count tear-down).
2274
+ expectedMaterialized = Math . Max ( expectedMaterialized , 1 + itemsInEVP . Length ) ;
2275
+ // dc-changed counts the total items prepared and "re-entrancy"(out of effective-viewport and back in).
2276
+ // we just need to add the new items since last time
2277
+ expectedDCChanged += newItemsInEVP . Length ;
2278
+ previouslyMaterializedItems = itemsInEVP ;
2267
2279
2268
- await WindowHelper . WaitForIdle ( ) ;
2280
+ materialized . Should ( ) . BeLessOrEqualTo ( expectedMaterialized , $ "[{ context } ] materialized { materialized } ") ;
2281
+ dataContextChanged . Should ( ) . BeLessOrEqualTo ( expectedDCChanged , $ "[{ context } ] dataContextChanged { dataContextChanged } ") ;
2282
+ }
2269
2283
2270
- materialized . Should ( ) . BeLessThan ( 14 , $ "materialized { materialized } ") ;
2271
- dataContextChanged . Should ( ) . BeLessThan ( 35 , $ "dataContextChanged { dataContextChanged } ") ;
2284
+ await ScrollAndValidate ( "initial state" , null ) ;
2285
+ await ScrollAndValidate ( "scrolled past element#0" , ElementHeight ) ;
2286
+ await ScrollAndValidate ( "scrolled past element#2" , ElementHeight * 3 ) ;
2287
+ await ScrollAndValidate ( "scrolled to 1/2" , scroll . ExtentHeight / 2 ) ;
2288
+ await ScrollAndValidate ( "scrolled back to 1/4" , scroll . ExtentHeight / 4 ) ;
2272
2289
}
2273
2290
#endif
2274
2291
@@ -4865,6 +4882,34 @@ void AddItem(string item, bool select = false)
4865
4882
4866
4883
Assert . AreEqual ( sv . ScrollableHeight , sv . VerticalOffset , "ListView is not scrolled to the end." ) ;
4867
4884
}
4885
+
4886
+ [ TestMethod ]
4887
+ public async Task When_SelectionChanged_DuringRefresh ( )
4888
+ {
4889
+ var source = new ObservableCollection < string > ( Enumerable . Range ( 0 , 4 ) . Select ( x => $ "Item { x } ") ) ;
4890
+ var sut = new ListView
4891
+ {
4892
+ Height = source . Count * 29 * 1.5 , // give ample room
4893
+ ItemsSource = source ,
4894
+ ItemTemplate = FixedSizeItemTemplate , // height=29
4895
+ } ;
4896
+
4897
+ await UITestHelper . Load ( sut , x => x . IsLoaded ) ;
4898
+
4899
+ Assert . IsTrue ( Enumerable . Range ( 0 , 4 ) . All ( x => sut . ContainerFromIndex ( x ) is { } ) , "All containers should be materialized." ) ;
4900
+
4901
+ source . Move ( 1 , 2 ) ; // swap: 0[1]23 -> 02[1]3
4902
+ sut . SelectedItem = source [ 2 ] ; // select "Item 1" (at index 2)
4903
+
4904
+ await UITestHelper . WaitForIdle ( ) ;
4905
+
4906
+ var tree = sut . TreeGraph ( ) ;
4907
+ Assert . IsTrue ( Enumerable . Range ( 0 , 4 ) . All ( x => sut . ContainerFromIndex ( x ) is { } ) , "All containers should be materialized." ) ;
4908
+
4909
+ #if ! ( __ANDROID__ || __IOS__ || __MACOS__ )
4910
+ Assert . AreEqual ( 4 , sut . ItemsPanelRoot . Children . OfType < ListViewItem > ( ) . Count ( ) , "There should be only 4 materialized container." ) ;
4911
+ #endif
4912
+ }
4868
4913
}
4869
4914
4870
4915
public partial class Given_ListViewBase // data class, data-context, view-model, template-selector
0 commit comments