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