@ -18,12 +18,15 @@
@@ -18,12 +18,15 @@
using System.Collections.Generic ;
using System.Linq ;
using System.Threading.Tasks ;
using Avalonia.Controls ;
using Avalonia.Controls.Primitives ;
using Avalonia.Headless ;
using Avalonia.Headless.NUnit ;
using Avalonia.Input ;
using Avalonia.Threading ;
using Avalonia.VisualTree ;
using AwesomeAssertions ;
@ -290,6 +293,107 @@ public class SharpTreeViewTests
@@ -290,6 +293,107 @@ public class SharpTreeViewTests
RenderedRows ( tree ) . Should ( ) . Equal ( "A" , "C" , "B" , "B1" ) ;
}
/// <summary>Builds <paramref name="rootCount"/> top-level rows in a viewport too short to show
/// them all, with one expandable node, so an expansion has somewhere to scroll.</summary>
static ( Window window , SharpTreeView tree , ScrollViewer scrollViewer , TestNode [ ] nodes ) ShortViewport (
int rootCount , int expandableIndex , int childCount )
{
var nodes = Enumerable . Range ( 0 , rootCount )
. Select ( i = > i = = expandableIndex
? new TestNode ( $"N{i}" , Enumerable . Range ( 0 , childCount )
. Select ( c = > new TestNode ( $"N{i}.{c}" ) ) . ToArray ( ) )
: new TestNode ( $"N{i}" ) )
. ToArray ( ) ;
var root = new TestNode ( "root" , nodes ) ;
var tree = new SharpTreeView { ShowRoot = false , Root = root } ;
var window = new Window { Content = tree , Width = 3 0 0 , Height = 1 8 0 } ;
window . Show ( ) ;
Dispatcher . UIThread . RunJobs ( ) ;
return ( window , tree , tree . GetVisualDescendants ( ) . OfType < ScrollViewer > ( ) . First ( ) , nodes ) ;
}
/// <summary>Expands a node the way a user does, with Right on its focused row.</summary>
static void PressRightOn ( Window window , SharpTreeView tree , SharpTreeNode node )
{
tree . SelectedItem = node ;
Dispatcher . UIThread . RunJobs ( ) ;
tree . ContainerFromItem ( node ) ? . Focus ( ) ;
Dispatcher . UIThread . RunJobs ( ) ;
window . KeyPress ( Key . Right , RawInputModifiers . None , PhysicalKey . ArrowRight , null ) ;
Dispatcher . UIThread . RunJobs ( ) ;
}
[AvaloniaTest]
public void Expanding_A_Node_Scrolls_Children_That_Do_Not_Fit_Into_View ( )
{
var ( window , tree , scrollViewer , nodes ) = ShortViewport ( rootCount : 1 5 , expandableIndex : 5 , childCount : 5 ) ;
var parent = nodes [ 5 ] ;
scrollViewer . Offset . Y . Should ( ) . Be ( 0 , "nothing has moved the viewport yet" ) ;
PressRightOn ( window , tree , parent ) ;
tree . IsNodeFullyVisible ( parent . Children [ ^ 1 ] )
. Should ( ) . BeTrue ( "the expansion reveals children below the viewport, so the view scrolls to show them" ) ;
tree . IsNodeFullyVisible ( parent )
. Should ( ) . BeTrue ( "the scroll is bounded by the expanded node: it never leaves the viewport" ) ;
}
[AvaloniaTest]
public void Expanding_A_Node_Whose_Children_Already_Fit_Leaves_The_Viewport_Alone ( )
{
var ( window , tree , scrollViewer , nodes ) = ShortViewport ( rootCount : 1 5 , expandableIndex : 1 , childCount : 2 ) ;
var parent = nodes [ 1 ] ;
PressRightOn ( window , tree , parent ) ;
scrollViewer . Offset . Y . Should ( ) . Be ( 0 , "the children fit below the node, so there is nothing to scroll to" ) ;
tree . IsNodeFullyVisible ( parent . Children [ ^ 1 ] ) . Should ( ) . BeTrue ( ) ;
}
[AvaloniaTest]
public void Expanding_A_Node_With_More_Children_Than_Fit_Keeps_The_Node_Visible ( )
{
var ( window , tree , _ , nodes ) = ShortViewport ( rootCount : 1 5 , expandableIndex : 5 , childCount : 3 0 ) ;
var parent = nodes [ 5 ] ;
PressRightOn ( window , tree , parent ) ;
tree . IsNodeFullyVisible ( parent )
. Should ( ) . BeTrue ( "showing every child would push the node off the top, so the scroll stops at the node" ) ;
tree . IsNodeFullyVisible ( parent . Children [ 0 ] )
. Should ( ) . BeTrue ( "as many children as fit are shown below it" ) ;
}
[AvaloniaTest]
public async Task Clicking_The_Expander_Scrolls_The_Children_Into_View ( )
{
// The mouse path never passes through SharpTreeView: the row template's toggle writes
// IsExpanded straight to the node, so the reveal hangs off the toggle's Click.
var ( window , tree , _ , nodes ) = ShortViewport ( rootCount : 1 5 , expandableIndex : 5 , childCount : 5 ) ;
var parent = nodes [ 5 ] ;
await window . ClickAsync ( ( ) = > tree . ContainerFromItem ( parent ) ? . GetVisualDescendants ( )
. OfType < ToggleButton > ( ) . FirstOrDefault ( b = > b . Name = = "PART_Expander" ) ) ;
Dispatcher . UIThread . RunJobs ( ) ;
parent . IsExpanded . Should ( ) . BeTrue ( "precondition: the click toggled the node open" ) ;
tree . IsNodeFullyVisible ( parent . Children [ ^ 1 ] )
. Should ( ) . BeTrue ( "a click on the expander reveals the children, just as the keyboard does" ) ;
}
[AvaloniaTest]
public void Expanding_A_Node_In_Code_Does_Not_Move_The_Viewport ( )
{
// Revealing a node expands its ancestors first (ScrollIntoNodeView, TreeSelectionBinder)
// and positions the viewport itself afterwards; a scroll per ancestor would fight that.
var ( _ , _ , scrollViewer , nodes ) = ShortViewport ( rootCount : 1 5 , expandableIndex : 5 , childCount : 5 ) ;
nodes [ 5 ] . IsExpanded = true ;
Dispatcher . UIThread . RunJobs ( ) ;
scrollViewer . Offset . Y . Should ( ) . Be ( 0 , "only a user gesture reveals the children" ) ;
}
static List < string > RenderedRows ( SharpTreeView tree )
{
var rows = new List < string > ( ) ;