Browse Source

Merge branch 'master' into fixes/avalonia-collections-nullability

pull/7082/head
Max Katz 5 years ago
committed by GitHub
parent
commit
47153bffd7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      samples/ControlCatalog/Pages/DataGridPage.xaml
  2. 19
      src/Avalonia.Base/Collections/AvaloniaList.cs
  3. 27
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  4. 3
      src/Avalonia.Controls.DataGrid/Themes/Default.xaml
  5. 3
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  6. 74
      src/Avalonia.Controls/Border.cs
  7. 55
      src/Avalonia.Controls/TextBox.cs
  8. 52
      src/Avalonia.Controls/Utils/BorderRenderHelper.cs
  9. 152
      tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs
  10. 48
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

9
samples/ControlCatalog/Pages/DataGridPage.xaml

@ -4,6 +4,12 @@
x:Class="ControlCatalog.Pages.DataGridPage">
<UserControl.Resources>
<local:GDPValueConverter x:Key="GDPConverter" />
<DataTemplate x:Key="Demo.DataTemplates.CountryHeader">
<StackPanel Orientation="Horizontal" Spacing="5">
<PathIcon Height="12" Data="M 255 116 A 1 1 0 0 0 254 117 L 254 130 A 1 1 0 0 0 255 131 A 1 1 0 0 0 256 130 L 256 123.87109 C 256.1125 123.90694 256.2187 123.94195 256.33984 123.97852 C 257.18636 124.23404 258.19155 124.5 259 124.5 C 259.80845 124.5 260.52133 124.2168 261.17773 123.9668 C 261.83414 123.7168 262.43408 123.5 263 123.5 C 263.56592 123.5 264.5612 123.73404 265.37109 123.97852 C 266.18098 124.22299 266.82227 124.4668 266.82227 124.4668 A 0.50005 0.50005 0 0 0 267.5 124 L 267.5 118 A 0.50005 0.50005 0 0 0 267.17773 117.5332 C 267.17773 117.5332 266.50667 117.27701 265.66016 117.02148 C 264.81364 116.76596 263.80845 116.5 263 116.5 C 262.19155 116.5 261.47867 116.7832 260.82227 117.0332 C 260.16586 117.2832 259.56592 117.5 259 117.5 C 258.43408 117.5 257.4388 117.26596 256.62891 117.02148 C 256.39123 116.94974 256.17716 116.87994 255.98047 116.81445 A 1 1 0 0 0 255 116 z M 263 117.5 C 263.56592 117.5 264.5612 117.73404 265.37109 117.97852 C 266.00097 118.16865 266.29646 118.28239 266.5 118.35742 L 266.5 120.29297 C 266.25708 120.21012 265.97978 120.11797 265.66016 120.02148 C 264.81364 119.76596 263.80845 119.5 263 119.5 C 262.19155 119.5 261.47867 119.7832 260.82227 120.0332 C 260.16586 120.2832 259.56592 120.5 259 120.5 C 258.43408 120.5 257.4388 120.26596 256.62891 120.02148 C 256.39971 119.9523 256.19148 119.88388 256 119.82031 L 256 117.87109 C 256.1125 117.90694 256.2187 117.94195 256.33984 117.97852 C 257.18636 118.23404 258.19155 118.5 259 118.5 C 259.80845 118.5 260.52133 118.2168 261.17773 117.9668 C 261.83414 117.7168 262.43408 117.5 263 117.5 z M 263 120.5 C 263.56592 120.5 264.5612 120.73404 265.37109 120.97852 C 265.8714 121.12954 266.2398 121.25641 266.5 121.34961 L 266.5 123.30469 C 266.22286 123.20649 266.12863 123.1629 265.66016 123.02148 C 264.81364 122.76596 263.80845 122.5 263 122.5 C 262.19155 122.5 261.47867 122.7832 260.82227 123.0332 C 260.16586 123.2832 259.56592 123.5 259 123.5 C 258.43408 123.5 257.4388 123.26596 256.62891 123.02148 C 256.39971 122.9523 256.19148 122.88388 256 122.82031 L 256 120.87109 C 256.1125 120.90694 256.2187 120.94195 256.33984 120.97852 C 257.18636 121.23404 258.19155 121.5 259 121.5 C 259.80845 121.5 260.52133 121.2168 261.17773 120.9668 C 261.83414 120.7168 262.43408 120.5 263 120.5 z" />
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<UserControl.Styles>
<Style Selector="DataGridCell.gdp">
@ -28,7 +34,8 @@
DockPanel.Dock="Top"/>
<DataGrid Name="dataGrid1" Margin="12" CanUserResizeColumns="True" CanUserReorderColumns="True" CanUserSortColumns="True" HeadersVisibility="All">
<DataGrid.Columns>
<DataGridTextColumn Header="Country" Binding="{Binding Name}" Width="6*" MinWidth="400" />
<!-- Using HeaderTemplate -->
<DataGridTextColumn Header="Country" HeaderTemplate="{StaticResource Demo.DataTemplates.CountryHeader}" Binding="{Binding Name}" Width="6*" MinWidth="400" />
<!-- CompiledBinding example of usage. -->
<DataGridTextColumn Header="Region" Binding="{CompiledBinding Region}" Width="4*" x:DataType="local:Country" />
<DataGridTextColumn Header="Population" Binding="{Binding Population}" Width="3*" />

19
src/Avalonia.Base/Collections/AvaloniaList.cs

@ -503,11 +503,24 @@ namespace Avalonia.Collections
{
_ = items ?? throw new ArgumentNullException(nameof(items));
foreach (var i in items)
var hItems = new HashSet<T>(items);
int counter = 0;
for (int i = _inner.Count - 1; i >= 0; --i)
{
// TODO: Optimize to only send as many notifications as necessary.
Remove(i);
if (hItems.Contains(_inner[i]))
{
counter += 1;
}
else if(counter > 0)
{
RemoveRange(i + 1, counter);
counter = 0;
}
}
if (counter > 0)
RemoveRange(0, counter);
}
/// <summary>

27
src/Avalonia.Controls.DataGrid/DataGridColumn.cs

@ -12,6 +12,7 @@ using System;
using System.ComponentModel;
using System.Linq;
using System.Diagnostics;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Markup.Xaml.MarkupExtensions;
@ -29,6 +30,7 @@ namespace Avalonia.Controls
private bool _settingWidthInternally;
private int _displayIndexWithFiller;
private object _header;
private IDataTemplate _headerTemplate;
private DataGridColumnHeader _headerCell;
private IControl _editingElement;
private ICellEditBinding _editBinding;
@ -416,6 +418,28 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Gets or sets an <see cref="IDataTemplate"/> for the <see cref="Header"/>
/// </summary>
public IDataTemplate HeaderTemplate
{
get
{
return _headerTemplate;
}
set
{
if (_headerTemplate != value)
{
_headerTemplate = value;
if (_headerCell != null)
{
_headerCell.ContentTemplate = value;
}
}
}
}
public bool IsAutoGenerated
{
get;
@ -850,7 +874,8 @@ namespace Avalonia.Controls
var result = new DataGridColumnHeader
{
OwningColumn = this,
Content = _header
Content = _header,
ContentTemplate = _headerTemplate
};
//result.EnsureStyle(null);

3
src/Avalonia.Controls.DataGrid/Themes/Default.xaml

@ -47,7 +47,8 @@
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}">
<ContentPresenter Content="{TemplateBinding Content}"/>
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
<Path Name="SortIcon"
Grid.Column="1"

3
src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml

@ -193,7 +193,8 @@
Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Content="{TemplateBinding Content}" />
<ContentPresenter Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
<Path Name="SortIcon"
Grid.Column="1"

74
src/Avalonia.Controls/Border.cs

@ -1,3 +1,5 @@
using Avalonia.Collections;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Utils;
using Avalonia.Layout;
using Avalonia.Media;
@ -41,7 +43,31 @@ namespace Avalonia.Controls
/// </summary>
public static readonly StyledProperty<BoxShadows> BoxShadowProperty =
AvaloniaProperty.Register<Border, BoxShadows>(nameof(BoxShadow));
/// <summary>
/// Defines the <see cref="BorderDashOffset"/> property.
/// </summary>
public static readonly StyledProperty<double> BorderDashOffsetProperty =
AvaloniaProperty.Register<Border, double>(nameof(BorderDashOffset));
/// <summary>
/// Defines the <see cref="BorderDashArray"/> property.
/// </summary>
public static readonly StyledProperty<AvaloniaList<double>?> BorderDashArrayProperty =
AvaloniaProperty.Register<Border, AvaloniaList<double>?>(nameof(BorderDashArray));
/// <summary>
/// Defines the <see cref="BorderLineCap"/> property.
/// </summary>
public static readonly StyledProperty<PenLineCap> BorderLineCapProperty =
AvaloniaProperty.Register<Border, PenLineCap>(nameof(BorderLineCap), PenLineCap.Flat);
/// <summary>
/// Defines the <see cref="BorderLineJoin"/> property.
/// </summary>
public static readonly StyledProperty<PenLineJoin> BorderLineJoinProperty =
AvaloniaProperty.Register<Border, PenLineJoin>(nameof(BorderLineJoin), PenLineJoin.Miter);
private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper();
/// <summary>
@ -54,6 +80,10 @@ namespace Avalonia.Controls
BorderBrushProperty,
BorderThicknessProperty,
CornerRadiusProperty,
BorderDashArrayProperty,
BorderLineCapProperty,
BorderLineJoinProperty,
BorderDashOffsetProperty,
BoxShadowProperty);
AffectsMeasure<Border>(BorderThicknessProperty);
}
@ -76,6 +106,15 @@ namespace Avalonia.Controls
set { SetValue(BorderBrushProperty, value); }
}
/// <summary>
/// Gets or sets a collection of <see cref="double"/> values that indicate the pattern of dashes and gaps that is used to outline shapes.
/// </summary>
public AvaloniaList<double>? BorderDashArray
{
get { return GetValue(BorderDashArrayProperty); }
set { SetValue(BorderDashArrayProperty, value); }
}
/// <summary>
/// Gets or sets the thickness of the border.
/// </summary>
@ -85,6 +124,33 @@ namespace Avalonia.Controls
set { SetValue(BorderThicknessProperty, value); }
}
/// <summary>
/// Gets or sets a value that specifies the distance within the dash pattern where a dash begins.
/// </summary>
public double BorderDashOffset
{
get { return GetValue(BorderDashOffsetProperty); }
set { SetValue(BorderDashOffsetProperty, value); }
}
/// <summary>
/// Gets or sets a <see cref="PenLineCap"/> enumeration value that describes the shape at the ends of a line.
/// </summary>
public PenLineCap BorderLineCap
{
get { return GetValue(BorderLineCapProperty); }
set { SetValue(BorderLineCapProperty, value); }
}
/// <summary>
/// Gets or sets a <see cref="PenLineJoin"/> enumeration value that specifies the type of join that is used at the vertices of a Shape.
/// </summary>
public PenLineJoin BorderLineJoin
{
get { return GetValue(BorderLineJoinProperty); }
set { SetValue(BorderLineJoinProperty, value); }
}
/// <summary>
/// Gets or sets the radius of the border rounded corners.
/// </summary>
@ -93,7 +159,7 @@ namespace Avalonia.Controls
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
/// <summary>
/// Gets or sets the box shadow effect parameters
/// </summary>
@ -102,7 +168,7 @@ namespace Avalonia.Controls
get => GetValue(BoxShadowProperty);
set => SetValue(BoxShadowProperty, value);
}
/// <summary>
/// Renders the control.
/// </summary>
@ -110,7 +176,7 @@ namespace Avalonia.Controls
public override void Render(DrawingContext context)
{
_borderRenderHelper.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush,
BoxShadow);
BoxShadow, BorderDashOffset, BorderLineCap, BorderLineJoin, BorderDashArray);
}
/// <summary>

55
src/Avalonia.Controls/TextBox.cs

@ -642,15 +642,31 @@ namespace Avalonia.Controls
if (!string.IsNullOrEmpty(input))
{
DeleteSelection();
caretIndex = CaretIndex;
text = Text ?? string.Empty;
SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
CaretIndex += input.Length;
ClearSelection();
if (IsUndoEnabled)
var oldText = _text;
_ignoreTextChanges = true;
try
{
_undoRedoHelper.DiscardRedo();
DeleteSelection(false);
caretIndex = CaretIndex;
text = Text ?? string.Empty;
SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
CaretIndex += input.Length;
ClearSelection();
if (IsUndoEnabled)
{
_undoRedoHelper.DiscardRedo();
}
if (_text != oldText)
{
RaisePropertyChanged(TextProperty, oldText, _text);
}
}
finally
{
_ignoreTextChanges = false;
}
}
}
@ -1285,7 +1301,7 @@ namespace Avalonia.Controls
CaretIndex = SelectionEnd;
}
private bool DeleteSelection()
private bool DeleteSelection(bool raiseTextChanged = true)
{
if (!IsReadOnly)
{
@ -1297,7 +1313,7 @@ namespace Avalonia.Controls
var start = Math.Min(selectionStart, selectionEnd);
var end = Math.Max(selectionStart, selectionEnd);
var text = Text;
SetTextInternal(text.Substring(0, start) + text.Substring(end));
SetTextInternal(text.Substring(0, start) + text.Substring(end), raiseTextChanged);
CaretIndex = start;
ClearSelection();
return true;
@ -1348,16 +1364,23 @@ namespace Avalonia.Controls
return i;
}
private void SetTextInternal(string value)
private void SetTextInternal(string value, bool raiseTextChanged = true)
{
try
if (raiseTextChanged)
{
_ignoreTextChanges = true;
SetAndRaise(TextProperty, ref _text, value);
try
{
_ignoreTextChanges = true;
SetAndRaise(TextProperty, ref _text, value);
}
finally
{
_ignoreTextChanges = false;
}
}
finally
else
{
_ignoreTextChanges = false;
_text = value;
}
}

52
src/Avalonia.Controls/Utils/BorderRenderHelper.cs

@ -1,8 +1,10 @@
using System;
using Avalonia.Collections;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
using Avalonia.Utilities;
using JetBrains.Annotations;
namespace Avalonia.Controls.Utils
{
@ -15,8 +17,13 @@ namespace Avalonia.Controls.Utils
private Size _size;
private Thickness _borderThickness;
private CornerRadius _cornerRadius;
private AvaloniaList<double> _borderDashArray;
private double _borderDashOffset;
private PenLineCap _borderLineCap;
private PenLineJoin _borderJoin;
private bool _initialized;
void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius)
{
_backendSupportsIndividualCorners ??= AvaloniaLocator.Current.GetService<IPlatformRenderInterface>()
@ -60,7 +67,8 @@ namespace Avalonia.Controls.Utils
if (boundRect.Width != 0 && innerRect.Height != 0)
{
var borderGeometryKeypoints = new BorderGeometryKeypoints(boundRect, borderThickness, cornerRadius, false);
var borderGeometryKeypoints =
new BorderGeometryKeypoints(boundRect, borderThickness, cornerRadius, false);
var borderGeometry = new StreamGeometry();
using (var ctx = borderGeometry.Open())
@ -84,17 +92,22 @@ namespace Avalonia.Controls.Utils
public void Render(DrawingContext context,
Size finalSize, Thickness borderThickness, CornerRadius cornerRadius,
IBrush background, IBrush borderBrush, BoxShadows boxShadows)
IBrush background, IBrush borderBrush, BoxShadows boxShadows, double borderDashOffset = 0,
PenLineCap borderLineCap = PenLineCap.Flat, PenLineJoin borderLineJoin = PenLineJoin.Miter,
AvaloniaList<double> borderDashArray = null)
{
if (_size != finalSize
|| _borderThickness != borderThickness
|| _cornerRadius != cornerRadius
|| !_initialized)
Update(finalSize, borderThickness, cornerRadius);
RenderCore(context, background, borderBrush, boxShadows);
RenderCore(context, background, borderBrush, boxShadows, borderDashOffset, borderLineCap, borderLineJoin,
borderDashArray);
}
void RenderCore(DrawingContext context, IBrush background, IBrush borderBrush, BoxShadows boxShadows)
void RenderCore(DrawingContext context, IBrush background, IBrush borderBrush, BoxShadows boxShadows,
double borderDashOffset, PenLineCap borderLineCap, PenLineJoin borderLineJoin,
AvaloniaList<double> borderDashArray)
{
if (_useComplexRendering)
{
@ -115,11 +128,25 @@ namespace Avalonia.Controls.Utils
var borderThickness = _borderThickness.Top;
IPen pen = null;
ImmutableDashStyle? dashStyle = null;
if (borderDashArray != null && borderDashArray.Count > 0)
{
dashStyle = new ImmutableDashStyle(borderDashArray, borderDashOffset);
}
if (borderBrush != null && borderThickness > 0)
{
pen = new ImmutablePen(borderBrush.ToImmutable(), borderThickness);
pen = new ImmutablePen(
borderBrush.ToImmutable(),
borderThickness,
dashStyle,
borderLineCap,
borderLineJoin);
}
var rect = new Rect(_size);
if (!MathUtilities.IsZero(borderThickness))
rect = rect.Deflate(borderThickness * 0.5);
@ -130,7 +157,8 @@ namespace Avalonia.Controls.Utils
}
}
private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, BorderGeometryKeypoints keypoints)
private static void CreateGeometry(StreamGeometryContext context, Rect boundRect,
BorderGeometryKeypoints keypoints)
{
context.BeginFigure(keypoints.TopLeft, true);
@ -184,7 +212,8 @@ namespace Avalonia.Controls.Utils
private class BorderGeometryKeypoints
{
internal BorderGeometryKeypoints(Rect boundRect, Thickness borderThickness, CornerRadius cornerRadius, bool inner)
internal BorderGeometryKeypoints(Rect boundRect, Thickness borderThickness, CornerRadius cornerRadius,
bool inner)
{
var left = 0.5 * borderThickness.Left;
var top = 0.5 * borderThickness.Top;
@ -206,10 +235,13 @@ namespace Avalonia.Controls.Utils
topLeftX = Math.Max(0, cornerRadius.TopLeft - left) + boundRect.TopLeft.X;
topRightX = boundRect.Width - Math.Max(0, cornerRadius.TopRight - top) + boundRect.TopLeft.X;
rightTopY = Math.Max(0, cornerRadius.TopRight - right) + boundRect.TopLeft.Y;
rightBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomRight - bottom) + boundRect.TopLeft.Y;
bottomRightX = boundRect.Width - Math.Max(0, cornerRadius.BottomRight - right) + boundRect.TopLeft.X;
rightBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomRight - bottom) +
boundRect.TopLeft.Y;
bottomRightX = boundRect.Width - Math.Max(0, cornerRadius.BottomRight - right) +
boundRect.TopLeft.X;
bottomLeftX = Math.Max(0, cornerRadius.BottomLeft - left) + boundRect.TopLeft.X;
leftBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomLeft - bottom) + boundRect.TopLeft.Y;
leftBottomY = boundRect.Height - Math.Max(0, cornerRadius.BottomLeft - bottom) +
boundRect.TopLeft.Y;
}
else
{

152
tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs

@ -357,5 +357,157 @@ namespace Avalonia.Base.UnitTests.Collections
Assert.Equal(target, result);
}
[Fact]
public void RemoveAll_Should_Send_Single_Notification_For_Sequential_Range()
{
var target = new AvaloniaList<string>(Enumerable.Range(0, 10).Select(x => $"Item {x}"));
var toRemove = new[] { "Item 5", "Item 6", "Item 7" };
var raised = 0;
target.CollectionChanged += (s, e) =>
{
Assert.Equal(NotifyCollectionChangedAction.Remove, e.Action);
Assert.Equal(5, e.OldStartingIndex);
Assert.Equal(toRemove, e.OldItems);
++raised;
};
target.RemoveAll(toRemove);
Assert.Equal(1, raised);
}
[Fact]
public void RemoveAll_Should_Send_Single_Notification_For_Sequential_Range_With_Duplicate_Source_Items()
{
var items = Enumerable.Range(0, 20).Select(x => $"Item {x / 2}");
var target = new AvaloniaList<string>(items);
var toRemove = new[] { "Item 5", "Item 6", "Item 7" };
var raised = 0;
target.CollectionChanged += (s, e) =>
{
Assert.Equal(NotifyCollectionChangedAction.Remove, e.Action);
Assert.Equal(10, e.OldStartingIndex);
Assert.Equal(new[]
{
"Item 5",
"Item 5",
"Item 6",
"Item 6",
"Item 7",
"Item 7",
}, e.OldItems);
++raised;
};
target.RemoveAll(toRemove);
Assert.Equal(1, raised);
}
[Fact]
public void RemoveAll_Should_Send_Multiple_Notifications_For_Non_Sequential_Range()
{
var target = new AvaloniaList<string>(Enumerable.Range(0, 10).Select(x => $"Item {x}"));
var raised = 0;
var toRemove = new[]
{
new[] { "Item 2", "Item 3" },
new[] { "Item 5", "Item 6" }
};
target.CollectionChanged += (s, e) =>
{
Assert.Equal(NotifyCollectionChangedAction.Remove, e.Action);
if (raised == 0)
{
Assert.Equal(5, e.OldStartingIndex);
Assert.Equal(toRemove[1], e.OldItems);
}
else
{
Assert.Equal(2, e.OldStartingIndex);
Assert.Equal(toRemove[0], e.OldItems);
}
++raised;
};
target.RemoveAll(toRemove[0].Concat(toRemove[1]));
Assert.Equal(2, raised);
}
[Fact]
public void RemoveAll_Should_Send_Multiple_Notifications_For_Sequential_Range_With_Nonsequential_Duplicate_Source_Items()
{
var items = Enumerable.Range(0, 10).Select(x => $"Item {x}");
var target = new AvaloniaList<string>(items.Concat(items));
var raised = 0;
var toRemove = new[] { "Item 5", "Item 6", "Item 7" };
target.CollectionChanged += (s, e) =>
{
Assert.Equal(NotifyCollectionChangedAction.Remove, e.Action);
if (raised == 0)
{
Assert.Equal(15, e.OldStartingIndex);
Assert.Equal(toRemove, e.OldItems);
}
else
{
Assert.Equal(5, e.OldStartingIndex);
Assert.Equal(toRemove, e.OldItems);
}
++raised;
};
target.RemoveAll(toRemove);
Assert.Equal(2, raised);
}
[Fact]
public void RemoveAll_Should_Not_Send_Notification_For_Items_Not_Present()
{
var target = new AvaloniaList<string>(Enumerable.Range(0, 10).Select(x => $"Item {x}"));
var toRemove = new[] { "Item 5", "Item 6", "Item 7", "Not present" };
var raised = 0;
target.CollectionChanged += (s, e) =>
{
Assert.Equal(NotifyCollectionChangedAction.Remove, e.Action);
Assert.Equal(5, e.OldStartingIndex);
Assert.Equal(toRemove.Take(3).ToArray(), e.OldItems);
++raised;
};
target.RemoveAll(toRemove);
Assert.Equal(1, raised);
}
[Fact]
public void RemoveAll_Should_Handle_Empty_List()
{
var target = new AvaloniaList<string>();
var toRemove = new[] { "Item 5", "Item 6", "Item 7" };
var raised = 0;
target.CollectionChanged += (s, e) =>
{
++raised;
};
target.RemoveAll(toRemove);
Assert.Equal(0, raised);
}
}
}

48
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -784,6 +784,54 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Setting_SelectedText_Should_Fire_Single_Text_Changed_Notification()
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
Template = CreateTemplate(),
Text = "0123",
AcceptsReturn = true,
AcceptsTab = true,
SelectionStart = 1,
SelectionEnd = 3,
};
var values = new List<string>();
target.GetObservable(TextBox.TextProperty).Subscribe(x => values.Add(x));
target.SelectedText = "A";
Assert.Equal(new[] { "0123", "0A3" }, values);
}
}
[Fact]
public void Entering_Text_With_SelectedText_Should_Fire_Single_Text_Changed_Notification()
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
Template = CreateTemplate(),
Text = "0123",
AcceptsReturn = true,
AcceptsTab = true,
SelectionStart = 1,
SelectionEnd = 3,
};
var values = new List<string>();
target.GetObservable(TextBox.TextProperty).Subscribe(x => values.Add(x));
RaiseTextEvent(target, "A");
Assert.Equal(new[] { "0123", "0A3" }, values);
}
}
private static TestServices FocusServices => TestServices.MockThreadingInterface.With(
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice(),

Loading…
Cancel
Save