A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

362 lines
13 KiB

using Avalonia.Collections;
using Avalonia.Controls.Presenters;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests.Presenters;
public sealed class TableViewLayoutHelperTests : ScopedTestBase
{
[Fact]
public void UpdateActualWidths_With_No_Columns_Returns_False()
{
var columns = new AvaloniaList<TableViewColumn>();
Assert.False(TableViewLayoutHelper.UpdateActualWidths(columns, 100, false, 1.0));
}
[Fact]
public void UpdateActualWidths_Single_Star_Column_Gets_Full_Width()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = new GridLength(1, GridUnitType.Star) },
};
Assert.True(TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1.0));
Assert.Equal(200, columns[0].ActualWidth);
}
[Fact]
public void UpdateActualWidths_Distributes_Star_Columns_Proportionally()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = new GridLength(1, GridUnitType.Star) },
new() { Width = new GridLength(3, GridUnitType.Star) },
};
TableViewLayoutHelper.UpdateActualWidths(columns, 400, false, 1.0);
Assert.Equal(100, columns[0].ActualWidth);
Assert.Equal(300, columns[1].ActualWidth);
}
[Fact]
public void UpdateActualWidths_Pixel_Column_Uses_Its_Fixed_Width()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = new GridLength(80) },
};
TableViewLayoutHelper.UpdateActualWidths(columns, 500, false, 1.0);
Assert.Equal(80, columns[0].ActualWidth);
}
[Fact]
public void UpdateActualWidths_Pixel_Plus_Star_Subtracts_Fixed_From_Star_Budget()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = new GridLength(50) },
new() { Width = new GridLength(1, GridUnitType.Star) },
};
TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1.0);
Assert.Equal(50, columns[0].ActualWidth);
Assert.Equal(150, columns[1].ActualWidth);
}
[Fact]
public void UpdateActualWidths_Star_Clamped_To_Zero_When_Pixel_Exceeds_Available()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = new GridLength(300) },
new() { Width = new GridLength(1, GridUnitType.Star) },
};
TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1.0);
Assert.Equal(300, columns[0].ActualWidth);
Assert.Equal(0, columns[1].ActualWidth);
}
[Fact]
public void UpdateActualWidths_Treats_Auto_As_One_Star()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = GridLength.Auto },
new() { Width = new GridLength(1, GridUnitType.Star) },
};
TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1.0);
Assert.Equal(100, columns[0].ActualWidth);
Assert.Equal(100, columns[1].ActualWidth);
}
[Theory]
[InlineData(GridUnitType.Pixel)]
[InlineData(GridUnitType.Star)]
[InlineData(GridUnitType.Auto)]
public void UpdateActualWidths_Zeros_Hidden_Columns_And_Preserves_Configured_Widths(GridUnitType unit)
{
var hidden = new TableViewColumn { Width = new GridLength(2, unit) };
var columns = new AvaloniaList<TableViewColumn>
{
hidden,
new() { Width = new GridLength(1, GridUnitType.Star) },
};
TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1);
var actualWidth = hidden.ActualWidth;
hidden.IsVisible = false;
TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1);
Assert.Equal(new GridLength(2, unit), hidden.Width);
Assert.Equal(0, hidden.ActualWidth);
Assert.Equal(200, columns[1].ActualWidth);
hidden.IsVisible = true;
TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1);
Assert.Equal(actualWidth, hidden.ActualWidth);
}
[Fact]
public void UpdateActualWidths_Falls_Back_To_1000_For_Infinite_Width()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = new GridLength(1, GridUnitType.Star) },
new() { Width = new GridLength(1, GridUnitType.Star) },
};
TableViewLayoutHelper.UpdateActualWidths(columns, double.PositiveInfinity, false, 1.0);
Assert.Equal(500, columns[0].ActualWidth);
Assert.Equal(500, columns[1].ActualWidth);
}
[Fact]
public void UpdateActualWidths_Returns_False_When_No_Change()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = new GridLength(1, GridUnitType.Star) },
new() { Width = new GridLength(50) },
};
Assert.True(TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1.0));
Assert.False(TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1.0));
}
[Fact]
public void UpdateActualWidths_Returns_True_When_Width_Changes()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = new GridLength(1, GridUnitType.Star) },
};
Assert.True(TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1.0));
Assert.True(TableViewLayoutHelper.UpdateActualWidths(columns, 300, false, 1.0));
Assert.Equal(300, columns[0].ActualWidth);
}
[Fact]
public void UpdateActualWidths_Spreads_Rounding_Across_Star_Columns()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = new GridLength(2, GridUnitType.Star) },
new() { Width = new GridLength(3, GridUnitType.Star) },
new() { Width = new GridLength(2, GridUnitType.Star) },
};
TableViewLayoutHelper.UpdateActualWidths(columns, 100, useLayoutRounding: true, layoutScale: 1);
// Weights 2:3:2 over 100 give 28.57 / 42.86 / 28.57. Rounding each column independently
// would produce 29 / 43 / 29 = 101; spreading the remainder keeps the total at 100.
Assert.Equal(29, columns[0].ActualWidth);
Assert.Equal(42, columns[1].ActualWidth);
Assert.Equal(29, columns[2].ActualWidth);
Assert.Equal(100, columns[0].ActualWidth + columns[1].ActualWidth + columns[2].ActualWidth);
}
[Fact]
public void UpdateActualWidths_Spreads_Rounding_With_Intertwined_Fixed_Columns()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = new GridLength(50) },
new() { Width = new GridLength(1, GridUnitType.Star) },
new() { Width = new GridLength(30) },
new() { Width = new GridLength(3, GridUnitType.Star) },
new() { Width = new GridLength(2, GridUnitType.Star) },
};
TableViewLayoutHelper.UpdateActualWidths(columns, 201, useLayoutRounding: true, layoutScale: 1);
// Fixed columns keep their exact size (80 total). The remaining 121px star budget is split
// 1:3:2 (20.17 / 60.5 / 40.33). Rounding each independently would drop to 200; spreading the
// remainder gives the extra pixel to the 3* column so everything fills exactly 201.
Assert.Equal(50, columns[0].ActualWidth);
Assert.Equal(20, columns[1].ActualWidth);
Assert.Equal(30, columns[2].ActualWidth);
Assert.Equal(61, columns[3].ActualWidth);
Assert.Equal(40, columns[4].ActualWidth);
Assert.Equal(
201,
columns[0].ActualWidth + columns[1].ActualWidth + columns[2].ActualWidth +
columns[3].ActualWidth + columns[4].ActualWidth);
}
[Fact]
public void UpdateActualWidths_Spreads_Rounding_At_Fractional_Layout_Scale()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = new GridLength(3, GridUnitType.Star) },
new() { Width = new GridLength(1, GridUnitType.Star) },
new() { Width = new GridLength(3, GridUnitType.Star) },
};
TableViewLayoutHelper.UpdateActualWidths(columns, 100, useLayoutRounding: true, layoutScale: 2);
// At 2x scale widths snap to half-pixels (whole device pixels). Weights 3:1:3 give
// 42.86 / 14.29 / 42.86; rounding each independently would drift the total to 100.5,
// while spreading the remainder keeps it at 100.
Assert.Equal(43, columns[0].ActualWidth);
Assert.Equal(14, columns[1].ActualWidth);
Assert.Equal(43, columns[2].ActualWidth);
Assert.Equal(100, columns[0].ActualWidth + columns[1].ActualWidth + columns[2].ActualWidth);
}
[Fact]
public void UpdateActualWidths_Ignores_Hidden_Columns()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = new GridLength(3, GridUnitType.Star) },
new() { Width = new GridLength(100), IsVisible = false },
new() { Width = new GridLength(1, GridUnitType.Star) },
new() { Width = new GridLength(5, GridUnitType.Star), IsVisible = false },
new() { Width = new GridLength(3, GridUnitType.Star) },
};
TableViewLayoutHelper.UpdateActualWidths(columns, 100, useLayoutRounding: true, layoutScale: 2);
Assert.Equal(43, columns[0].ActualWidth);
Assert.Equal(0, columns[1].ActualWidth);
Assert.Equal(14, columns[2].ActualWidth);
Assert.Equal(0, columns[3].ActualWidth);
Assert.Equal(43, columns[4].ActualWidth);
Assert.Equal(100, columns[0].ActualWidth + columns[2].ActualWidth + columns[4].ActualWidth);
}
[Fact]
public void UpdateActualWidths_Rounds_Fixed_Column_Width_And_Star_Absorbs_Remainder()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = new GridLength(50.4) },
new() { Width = new GridLength(1, GridUnitType.Star) },
new() { Width = new GridLength(2, GridUnitType.Star) },
};
TableViewLayoutHelper.UpdateActualWidths(columns, 200, useLayoutRounding: true, layoutScale: 1);
// The fixed column rounds to a whole pixel (50); the remaining 150px star budget is split
// 1:2 between the star columns.
Assert.Equal(50, columns[0].ActualWidth);
Assert.Equal(50, columns[1].ActualWidth);
Assert.Equal(100, columns[2].ActualWidth);
}
[Fact]
public void UpdateActualWidths_Sets_All_Hidden_Widths_To_Zero()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { IsVisible = false },
new() { IsVisible = false },
};
Assert.True(TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1));
Assert.All(columns, column => Assert.Equal(0, column.ActualWidth));
Assert.False(TableViewLayoutHelper.NeedsActualWidths(columns));
Assert.False(TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1));
}
[Fact]
public void NeedsActualWidths_Returns_False_For_Empty_Columns()
{
var columns = new AvaloniaList<TableViewColumn>();
Assert.False(TableViewLayoutHelper.NeedsActualWidths(columns));
}
[Fact]
public void NeedsActualWidths_Returns_True_When_First_Column_Has_NaN_Width()
{
var columns = new AvaloniaList<TableViewColumn> { new(), new() };
Assert.True(TableViewLayoutHelper.NeedsActualWidths(columns));
}
[Fact]
public void NeedsActualWidths_Returns_False_After_Widths_Are_Computed()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = new GridLength(1, GridUnitType.Star) },
new() { Width = new GridLength(1, GridUnitType.Star) },
};
TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1.0);
Assert.False(TableViewLayoutHelper.NeedsActualWidths(columns));
}
[Fact]
public void NeedsActualWidths_Detects_Reset_When_First_Column_Is_Hidden()
{
var columns = new AvaloniaList<TableViewColumn> { new(), new() };
TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1);
columns[0].IsVisible = false;
TableViewLayoutHelper.ResetActualWidths(columns);
Assert.True(double.IsNaN(columns[0].ActualWidth));
Assert.True(TableViewLayoutHelper.NeedsActualWidths(columns));
TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1);
Assert.Equal(0, columns[0].ActualWidth);
Assert.Equal(200, columns[1].ActualWidth);
Assert.False(TableViewLayoutHelper.NeedsActualWidths(columns));
}
[Fact]
public void ResetActualWidths_Sets_All_Widths_To_NaN()
{
var columns = new AvaloniaList<TableViewColumn>
{
new() { Width = new GridLength(50) },
new() { Width = new GridLength(1, GridUnitType.Star) },
};
TableViewLayoutHelper.UpdateActualWidths(columns, 200, false, 1.0);
TableViewLayoutHelper.ResetActualWidths(columns);
Assert.True(double.IsNaN(columns[0].ActualWidth));
Assert.True(double.IsNaN(columns[1].ActualWidth));
}
}