private void RunElementTrackingTestRoutine(Action < ObservableCollection <ObservableCollection <string> > /* data */, TestScrollingSurface[] /* scrollers */, ItemsRepeater /* rootRepeater */> testRoutine) { // Base setup for our element tracking tests. // We have 4 fake scrollers in series, initially in a non scrollable configuration. // Under them we have a group repeater tree (2 groups with 3 items each). // The group UI is a StackPanel with a TextBlock "header" and an inner ItemsRepeater. RunOnUIThread.Execute(() => { int groupCount = 2; int itemsPerGroup = 3; var data = new ObservableCollection <ObservableCollection <string> >( Enumerable.Range(0, groupCount).Select(i => new ObservableCollection <string>( Enumerable.Range(0, itemsPerGroup).Select(j => string.Format("Item #{0}.{1}", i, j))))); var itemElements = Enumerable.Range(0, groupCount).Select(i => Enumerable.Range(0, itemsPerGroup).Select(j => new Border { Tag = data[i][j], Width = 50, Height = 50, Background = new SolidColorBrush(Colors.Red) }).ToList()).ToArray(); var headerElements = Enumerable.Range(0, groupCount).Select(i => new TextBlock { Text = "Header #" + i }).ToList(); var groupRepeaters = Enumerable.Range(0, groupCount).Select(i => new ItemsRepeater { Tag = "ItemsRepeater #" + i, ItemsSource = data[i], ItemTemplate = MockElementFactory.CreateElementFactory(itemElements[i]), Layout = new TestGridLayout { Orientation = Orientation.Horizontal, MinItemWidth = 50, MinItemHeight = 50, MinRowSpacing = 10, MinColumnSpacing = 10 } }).ToList(); var groupElement = Enumerable.Range(0, groupCount).Select(i => { var panel = new StackPanel(); panel.Tag = "Group #" + i; panel.Children.Add(headerElements[i]); panel.Children.Add(groupRepeaters[i]); return(panel); }).ToList(); var rootRepeater = new ItemsRepeater { Tag = "Root ItemsRepeater", ItemsSource = data, ItemTemplate = MockElementFactory.CreateElementFactory(groupElement), Layout = new TestStackLayout { Orientation = Orientation.Vertical } }; var scrollers = new TestScrollingSurface[4]; for (int i = 0; i < scrollers.Length; ++i) { scrollers[i] = new TestScrollingSurface() { Tag = "S" + i, Content = i > 0 ? (UIElement)scrollers[i - 1] : rootRepeater }; } var resetScrollers = (Action)(() => { foreach (var scroller in scrollers) { scroller.IsHorizontallyScrollable = false; scroller.IsVerticallyScrollable = false; scroller.RegisterAnchorCandidateFunc = null; scroller.UnregisterAnchorCandidateFunc = null; scroller.GetRelativeViewportFunc = null; } }); var outerScroller = scrollers.Last(); outerScroller.Width = 200.0; outerScroller.Height = 2000.0; Content = outerScroller; Content.UpdateLayout(); testRoutine(data, scrollers, rootRepeater); }); }
// [TestMethod] Temporarily disabled for bug 18866003 public void ValidateLoadUnload() { /* * if (!PlatformConfiguration.IsOsVersionGreaterThan(OSVersion.Redstone2)) * { * Log.Warning("Skipping: Test has instability on rs1 and rs2. Tracked by bug 18919096"); * return; * } */ // In this test, we will repeatedly put a repeater in and out // of the visual tree, under the same or a different parent. // And we will validate that the subscriptions and unsubscriptions to // the IRepeaterScrollingSurface events is done in sync. TestScrollingSurface scroller1 = null; TestScrollingSurface scroller2 = null; ItemsRepeater repeater = null; WeakReference repeaterWeakRef = null; var renderingEvent = new ManualResetEvent(false); var unorderedLoadEvent = false; var loadCounter = 0; var unloadCounter = 0; int scroller1SubscriberCount = 0; int scroller2SubscriberCount = 0; RunOnUIThread.Execute(() => { CompositionTarget.Rendering += (sender, args) => { renderingEvent.Set(); }; var host = new Grid(); scroller1 = new TestScrollingSurface() { Tag = "Scroller 1" }; scroller2 = new TestScrollingSurface() { Tag = "Scroller 2" }; repeater = new ItemsRepeater(); repeaterWeakRef = new WeakReference(repeater); repeater.Loaded += delegate { Log.Comment("ItemsRepeater Loaded in " + ((FrameworkElement)repeater.Parent).Tag); unorderedLoadEvent |= (++loadCounter > unloadCounter + 1); }; repeater.Unloaded += delegate { Log.Comment("ItemsRepeater Unloaded"); unorderedLoadEvent |= (++unloadCounter > loadCounter); }; // Subscribers count should never go above 1 or under 0. var validateSubscriberCount = new Action(() => { Verify.IsLessThanOrEqual(scroller1SubscriberCount, 1); Verify.IsGreaterThanOrEqual(scroller1SubscriberCount, 0); Verify.IsLessThanOrEqual(scroller2SubscriberCount, 1); Verify.IsGreaterThanOrEqual(scroller2SubscriberCount, 0); }); scroller1.ConfigurationChangedAddFunc = () => { ++scroller1SubscriberCount; validateSubscriberCount(); }; scroller2.ConfigurationChangedAddFunc = () => { ++scroller2SubscriberCount; validateSubscriberCount(); }; scroller1.ConfigurationChangedRemoveFunc = () => { --scroller1SubscriberCount; validateSubscriberCount(); }; scroller2.ConfigurationChangedRemoveFunc = () => { --scroller2SubscriberCount; validateSubscriberCount(); }; scroller1.Content = repeater; host.Children.Add(scroller1); host.Children.Add(scroller2); Content = host; }); IdleSynchronizer.Wait(); Verify.IsTrue(renderingEvent.WaitOne(), "Waiting for rendering event"); renderingEvent.Reset(); Log.Comment("Putting repeater in and out of scroller 1 until we observe two out-of-sync loaded/unloaded events."); for (int i = 0; i < 2; ++i) { while (!unorderedLoadEvent) { RunOnUIThread.Execute(() => { // Validate subscription count for events + reset. scroller1.Content = null; scroller1.Content = repeater; }); // For this issue to repro, we need to wait in such a way // that we don't tick the UI thread. We can't use IdleSynchronizer here. Task.Delay(16 * 3).Wait(); } unorderedLoadEvent = false; Log.Comment("Detected an unordered load/unload event."); } IdleSynchronizer.Wait(); Verify.IsTrue(renderingEvent.WaitOne(), "Waiting for rendering event"); renderingEvent.Reset(); RunOnUIThread.Execute(() => { Verify.AreEqual(1, scroller1SubscriberCount); Verify.AreEqual(0, scroller2SubscriberCount); Log.Comment("Moving repeater from scroller 1 to scroller 2."); scroller1.Content = null; scroller2.Content = repeater; }); IdleSynchronizer.Wait(); Verify.IsTrue(renderingEvent.WaitOne(), "Waiting for rendering event"); RunOnUIThread.Execute(() => { Verify.AreEqual(0, scroller1SubscriberCount); Verify.AreEqual(1, scroller2SubscriberCount); Log.Comment("Moving repeater out of scroller 2."); scroller2.Content = null; repeater = null; }); Log.Comment("Waiting for repeater to get GCed."); for (int i = 0; i < 5 && repeaterWeakRef.IsAlive; ++i) { GC.Collect(); GC.WaitForPendingFinalizers(); IdleSynchronizer.Wait(); } Verify.IsFalse(repeaterWeakRef.IsAlive); renderingEvent.Reset(); RunOnUIThread.Execute(() => { Verify.AreEqual(0, scroller1SubscriberCount); Verify.AreEqual(0, scroller2SubscriberCount); Log.Comment("Scroller raise IRepeaterScrollSurface.PostArrange. Make sure no one is subscribing to it."); scroller1.InvalidateArrange(); scroller2.InvalidateArrange(); Content.UpdateLayout(); }); IdleSynchronizer.Wait(); Verify.IsTrue(renderingEvent.WaitOne(), "Waiting for rendering event"); }