Browse Source

Merge branch 'master' into fix/border-inner-rounded-clip

pull/20647/head
Wiesław Šoltés 1 month ago
committed by GitHub
parent
commit
72976b77fc
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 108
      api/Avalonia.nupkg.xml
  2. 3
      src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs
  3. 2
      src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs
  4. 2
      src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
  5. 2
      src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
  6. 3
      src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs
  7. 2
      src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs
  8. 2
      src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs
  9. 2
      src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
  10. 2
      src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs
  11. 2
      src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs
  12. 40
      src/Avalonia.Base/VisualTree/VisualExtensions.cs
  13. 13
      src/Avalonia.Controls/AppBuilder.cs
  14. 6
      src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs
  15. 37
      src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
  16. 9
      src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
  17. 25
      src/Avalonia.Controls/TabControl.cs
  18. 3
      src/Windows/Avalonia.Win32.Automation/AutomationNode.cs
  19. 5
      tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.DataValidation.cs
  20. 43
      tests/Avalonia.Base.UnitTests/VisualExtensionsTests.cs
  21. 77
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs

108
api/Avalonia.nupkg.xml

@ -25,6 +25,60 @@
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.BindingPlugins</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.DataValidationBase</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.ExceptionValidationPlugin</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IDataValidationPlugin</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IndeiValidationPlugin</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IPropertyAccessorPlugin</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IStreamPlugin</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.PropertyAccessorBase</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.PropertyError</Target>
<Left>baseline/Avalonia/lib/net10.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net10.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Diagnostics.AppliedStyle</Target>
@ -253,6 +307,60 @@
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.BindingPlugins</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.DataValidationBase</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.ExceptionValidationPlugin</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IDataValidationPlugin</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IndeiValidationPlugin</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IPropertyAccessorPlugin</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.IStreamPlugin</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.PropertyAccessorBase</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Data.Core.Plugins.PropertyError</Target>
<Left>baseline/Avalonia/lib/net8.0/Avalonia.Base.dll</Left>
<Right>current/Avalonia/lib/net8.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0001</DiagnosticId>
<Target>T:Avalonia.Diagnostics.AppliedStyle</Target>

3
src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs

@ -7,7 +7,7 @@ namespace Avalonia.Data.Core.Plugins
/// Holds a registry of plugins used for bindings.
/// </summary>
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
public static class BindingPlugins
internal static class BindingPlugins
{
internal static readonly List<IPropertyAccessorPlugin> s_propertyAccessors = new()
{
@ -17,7 +17,6 @@ namespace Avalonia.Data.Core.Plugins
internal static readonly List<IDataValidationPlugin> s_dataValidators = new()
{
new DataAnnotationsValidationPlugin(),
new IndeiValidationPlugin(),
new ExceptionValidationPlugin(),
};

2
src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs

@ -11,7 +11,7 @@ namespace Avalonia.Data.Core.Plugins
/// and convert any values received from the inner property accessor into
/// <see cref="BindingNotification"/>s.
/// </remarks>
public abstract class DataValidationBase : PropertyAccessorBase, IObserver<object?>
internal abstract class DataValidationBase : PropertyAccessorBase, IObserver<object?>
{
private readonly IPropertyAccessor _inner;

2
src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs

@ -7,7 +7,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// Validates properties that report errors by throwing exceptions.
/// </summary>
public class ExceptionValidationPlugin : IDataValidationPlugin
internal class ExceptionValidationPlugin : IDataValidationPlugin
{
/// <inheritdoc/>
public bool Match(WeakReference<object?> reference, string memberName) => true;

2
src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs

@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// Defines how data validation is observed by an <see cref="BindingExpression"/>.
/// </summary>
public interface IDataValidationPlugin
internal interface IDataValidationPlugin
{
/// <summary>
/// Checks whether this plugin can handle data validation on the specified object.

3
src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs

@ -3,8 +3,7 @@ using System;
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Defines an accessor to a property on an object returned by a
/// <see cref="IPropertyAccessorPlugin"/>
/// Defines an accessor to a property on an object.
/// </summary>
public interface IPropertyAccessor : IDisposable
{

2
src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs

@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// Defines how a member is read, written and observed by a binding.
/// </summary>
public interface IPropertyAccessorPlugin
internal interface IPropertyAccessorPlugin
{
/// <summary>
/// Checks whether this plugin can handle accessing the properties of the specified object.

2
src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs

@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// Defines a plugin that handles the '^' stream binding operator.
/// </summary>
public interface IStreamPlugin
internal interface IStreamPlugin
{
/// <summary>
/// Checks whether this plugin handles the specified value.

2
src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs

@ -9,7 +9,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// Validates properties on objects that implement <see cref="INotifyDataErrorInfo"/>.
/// </summary>
public class IndeiValidationPlugin : IDataValidationPlugin
internal class IndeiValidationPlugin : IDataValidationPlugin
{
private static readonly WeakEvent<INotifyDataErrorInfo, DataErrorsChangedEventArgs>
ErrorsChangedWeakEvent = WeakEvent.Register<INotifyDataErrorInfo, DataErrorsChangedEventArgs>(

2
src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs

@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// Defines a default base implementation for a <see cref="IPropertyAccessor"/>.
/// </summary>
public abstract class PropertyAccessorBase : IPropertyAccessor
internal abstract class PropertyAccessorBase : IPropertyAccessor
{
private Action<object?>? _listener;

2
src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs

@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins
/// <summary>
/// An <see cref="IPropertyAccessor"/> that represents an error.
/// </summary>
public class PropertyError : IPropertyAccessor
internal class PropertyError : IPropertyAccessor
{
private readonly BindingNotification _error;

40
src/Avalonia.Base/VisualTree/VisualExtensions.cs

@ -147,6 +147,19 @@ namespace Avalonia.VisualTree
/// <param name="includeSelf">If given visual should be included in search.</param>
/// <returns>First ancestor of given type.</returns>
public static T? FindAncestorOfType<T>(this Visual? visual, bool includeSelf = false) where T : class
{
return FindAncestorOfType<T>(visual, includeSelf, predicate: null);
}
/// <summary>
/// Finds first ancestor of given type that matches a predicate.
/// </summary>
/// <typeparam name="T">Ancestor type.</typeparam>
/// <param name="visual">The visual.</param>
/// <param name="includeSelf">If given visual should be included in search.</param>
/// <param name="predicate">The predicate that the ancestor must match.</param>
/// <returns>First ancestor of given type.</returns>
public static T? FindAncestorOfType<T>(this Visual? visual, bool includeSelf, Predicate<T>? predicate) where T : class
{
if (visual is null)
{
@ -158,9 +171,12 @@ namespace Avalonia.VisualTree
while (parent != null)
{
if (parent is T result)
{
if (predicate == null || predicate(result))
{
return result;
}
}
parent = parent.VisualParent;
}
@ -176,18 +192,31 @@ namespace Avalonia.VisualTree
/// <param name="includeSelf">If given visual should be included in search.</param>
/// <returns>First descendant of given type.</returns>
public static T? FindDescendantOfType<T>(this Visual? visual, bool includeSelf = false) where T : class
{
return FindDescendantOfType<T>(visual, includeSelf, predicate: null);
}
/// <summary>
/// Finds first descendant of given type that matches given predicate.
/// </summary>
/// <typeparam name="T">Descendant type.</typeparam>
/// <param name="visual">The visual.</param>
/// <param name="includeSelf">If given visual should be included in search.</param>
/// <param name="predicate">The predicate that the descendant must match.</param>
/// <returns>First descendant of given type that matches given predicate.</returns>
public static T? FindDescendantOfType<T>(this Visual? visual, bool includeSelf, Predicate<T>? predicate) where T : class
{
if (visual is null)
{
return null;
}
if (includeSelf && visual is T result)
if (includeSelf && visual is T result && (predicate == null || predicate(result)))
{
return result;
}
return FindDescendantOfTypeCore<T>(visual);
return FindDescendantOfTypeCore<T>(visual, predicate);
}
/// <summary>
@ -485,7 +514,7 @@ namespace Avalonia.VisualTree
.Select(x => x.Element!);
}
private static T? FindDescendantOfTypeCore<T>(Visual visual) where T : class
private static T? FindDescendantOfTypeCore<T>(Visual visual, Predicate<T>? predicate) where T : class
{
var visualChildren = visual.VisualChildren;
var visualChildrenCount = visualChildren.Count;
@ -495,11 +524,14 @@ namespace Avalonia.VisualTree
Visual child = visualChildren[i];
if (child is T result)
{
if (predicate == null || predicate(result))
{
return result;
}
}
var childResult = FindDescendantOfTypeCore<T>(child);
var childResult = FindDescendantOfTypeCore<T>(child, predicate);
if (!(childResult is null))
{

13
src/Avalonia.Controls/AppBuilder.cs

@ -1,10 +1,12 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Data.Core.Plugins;
namespace Avalonia
{
@ -265,6 +267,17 @@ namespace Avalonia
return Self;
}
/// <summary>
/// Adds support for validation using <c>System.ComponentModel.DataAnnotations</c>.
/// </summary>
[RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)]
public AppBuilder WithDataAnnotationsValidation()
{
if (!BindingPlugins.DataValidators.Any(x => x is DataAnnotationsValidationPlugin))
BindingPlugins.DataValidators.Insert(0, new DataAnnotationsValidationPlugin());
return Self;
}
/// <summary>
/// Registers an action that is executed with the current font manager.
/// </summary>

6
src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs

@ -31,6 +31,12 @@ namespace Avalonia.Automation
/// </summary>
public static AutomationProperty HelpTextProperty { get; } = new AutomationProperty();
/// <summary>
/// Identifies the itemStatus automation property. The class name property value is returned
/// by the <see cref="AutomationPeer.GetItemStatus"/> method.
/// </summary>
public static AutomationProperty ItemStatusProperty { get; } = new AutomationProperty();
/// <summary>
/// Identifies the landmark type automation property. The class name property value is returned
/// by the <see cref="AutomationPeer.GetLandmarkType"/> method.

37
src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs

@ -319,6 +319,41 @@ namespace Avalonia.Automation.Peers
/// </remarks>
public int GetHeadingLevel() => GetHeadingLevelCore();
/// <summary>
/// Gets the item type that is associated with this automation peer.
/// </summary>
/// <remarks>
/// <list type="table">
/// <item>
/// <term>Windows</term>
/// <description><c>UIA_ItemTypePropertyId</c></description>
/// </item>
/// <item>
/// <term>macOS</term>
/// <description>No mapping.</description>
/// </item>
/// </list>
/// </remarks>
public string? GetItemType() => GetItemTypeCore();
/// <summary>
/// Gets the item status that is associated with this automation peer.
/// </summary>
/// <remarks>
/// <list type="table">
/// <item>
/// <term>Windows</term>
/// <description><c>UIA_ItemStatusPropertyId</c></description>
/// </item>
/// <item>
/// <term>macOS</term>
/// <description>No mapping.</description>
/// </item>
/// </list>
/// </remarks>
public string? GetItemStatus() => GetItemStatusCore();
/// <summary>
/// Gets the <see cref="AutomationPeer"/> that is the parent of this <see cref="AutomationPeer"/>.
/// </summary>
@ -562,6 +597,8 @@ namespace Avalonia.Automation.Peers
protected virtual string? GetHelpTextCore() => null;
protected virtual AutomationLandmarkType? GetLandmarkTypeCore() => null;
protected virtual int GetHeadingLevelCore() => 0;
protected virtual string? GetItemTypeCore() => null;
protected virtual string? GetItemStatusCore() => null;
protected abstract AutomationPeer? GetParentCore();
protected abstract bool HasKeyboardFocusCore();
protected abstract bool IsContentElementCore();

9
src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs

@ -199,6 +199,8 @@ namespace Avalonia.Automation.Peers
protected override string? GetAutomationIdCore() => AutomationProperties.GetAutomationId(Owner) ?? Owner.Name;
protected override Rect GetBoundingRectangleCore() => GetBounds(Owner);
protected override string GetClassNameCore() => Owner.GetType().Name;
protected override string? GetItemStatusCore() => AutomationProperties.GetItemStatus(Owner);
protected override string? GetItemTypeCore() => AutomationProperties.GetItemType(Owner);
protected override bool HasKeyboardFocusCore() => Owner.IsFocused;
protected override bool IsContentElementCore() => true;
protected override bool IsControlElementCore() => true;
@ -281,6 +283,13 @@ namespace Avalonia.Automation.Peers
{
InvalidateParent();
}
else if (e.Property == AutomationProperties.ItemStatusProperty)
{
RaisePropertyChangedEvent(
AutomationElementIdentifiers.ItemStatusProperty,
e.OldValue,
e.NewValue);
}
}

25
src/Avalonia.Controls/TabControl.cs

@ -1,4 +1,4 @@
using System.Linq;
using System.Diagnostics;
using Avalonia.Collections;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Presenters;
@ -7,7 +7,6 @@ using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
using Avalonia.Automation;
using Avalonia.Controls.Metadata;
using Avalonia.Reactive;
@ -76,7 +75,7 @@ namespace Avalonia.Controls
SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected);
ItemsPanelProperty.OverrideDefaultValue<TabControl>(DefaultPanel);
AffectsMeasure<TabControl>(TabStripPlacementProperty);
SelectedItemProperty.Changed.AddClassHandler<TabControl>((x, e) => x.UpdateSelectedContent());
SelectedItemProperty.Changed.AddClassHandler<TabControl>((x, _) => x.UpdateSelectedContent());
AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue<TabControl>(AutomationControlType.Tab);
}
@ -231,7 +230,25 @@ namespace Avalonia.Controls
}
_selectedItemSubscriptions = new CompositeDisposable(
container.GetObservable(ContentControl.ContentProperty).Subscribe(v => SelectedContent = v),
container.GetObservable(ContentControl.ContentProperty).Subscribe(content =>
{
var contentElement = content as StyledElement;
var contentDataContext = contentElement?.DataContext;
SelectedContent = content;
// When the ContentPresenter (ContentPart) displays content that is a Control, it doesn't
// set its DataContext to that of the Control's. If the content doesn't set a DataContext,
// then it gets inherited from the TabControl. Work around this issue by setting the
// DataContext of the ContentPart to the content's original DataContext (inherited from
// container).
if (contentElement is not null &&
contentElement.DataContext != contentDataContext &&
ContentPart is not null)
{
Debug.Assert(!contentElement.IsSet(DataContextProperty));
ContentPart.DataContext = contentDataContext;
}
}),
container.GetObservable(ContentControl.ContentTemplateProperty).Subscribe(v => SelectedContentTemplate = SelectContentTemplate(v)));
// Note how we fall back to our own ContentTemplate if the container doesn't specify one

3
src/Windows/Avalonia.Win32.Automation/AutomationNode.cs

@ -39,6 +39,7 @@ namespace Avalonia.Win32.Automation
{ AutomationElementIdentifiers.HelpTextProperty, UiaPropertyId.HelpText },
{ AutomationElementIdentifiers.LandmarkTypeProperty, UiaPropertyId.LandmarkType },
{ AutomationElementIdentifiers.HeadingLevelProperty, UiaPropertyId.HeadingLevel },
{ AutomationElementIdentifiers.ItemStatusProperty, UiaPropertyId.ItemStatus },
{ ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty, UiaPropertyId.ExpandCollapseExpandCollapseState },
{ RangeValuePatternIdentifiers.IsReadOnlyProperty, UiaPropertyId.RangeValueIsReadOnly},
{ RangeValuePatternIdentifiers.MaximumProperty, UiaPropertyId.RangeValueMaximum },
@ -135,6 +136,8 @@ namespace Avalonia.Win32.Automation
UiaPropertyId.IsEnabled => InvokeSync(() => Peer.IsEnabled()),
UiaPropertyId.IsKeyboardFocusable => InvokeSync(() => Peer.IsKeyboardFocusable()),
UiaPropertyId.IsOffscreen => InvokeSync(() => Peer.IsOffscreen()),
UiaPropertyId.ItemType => InvokeSync(() => Peer.GetItemType()),
UiaPropertyId.ItemStatus => InvokeSync(() => Peer.GetItemStatus()),
UiaPropertyId.LocalizedControlType => InvokeSync(() => Peer.GetLocalizedControlType()),
UiaPropertyId.Name => InvokeSync(() => Peer.GetName()),
UiaPropertyId.HelpText => InvokeSync(() => Peer.GetHelpText()),

5
tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.DataValidation.cs

@ -2,7 +2,9 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Avalonia.Data;
using Avalonia.Data.Core.Plugins;
using Avalonia.UnitTests;
using Xunit;
@ -273,6 +275,9 @@ public partial class BindingExpressionTests
[Fact]
public void Updates_Data_Validation_For_Required_DataAnnotation()
{
if (!BindingPlugins.DataValidators.Any(x => x is DataAnnotationsValidationPlugin))
BindingPlugins.DataValidators.Insert(0, new DataAnnotationsValidationPlugin());
var data = new DataAnnotationsViewModel();
var target = CreateTargetWithSource(
data,

43
tests/Avalonia.Base.UnitTests/VisualExtensionsTests.cs

@ -22,6 +22,23 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(root, target.FindAncestorOfType<TestRoot>());
}
[Fact]
public void FindAncestorOfType_Finds_Visible_Parent()
{
StackPanel target;
var root = new TestRoot
{
Child = new TestRoot
{
Child = target = new StackPanel(),
IsVisible = false
}
};
Assert.Equal(root, target.FindAncestorOfType<TestRoot>(false, v => v.IsVisible));
}
[Fact]
public void FindAncestorOfType_Finds_Ancestor_Of_Nested_Child()
{
@ -85,6 +102,32 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(target, root.FindDescendantOfType<Button>());
}
[Fact]
public void FindDescendantOfType_Finds_Nested_Visible_Child()
{
Button target;
var root = new TestRoot
{
Child = new StackPanel
{
Children =
{
new StackPanel
{
Children =
{
new Button { IsVisible = false },
(target = new Button())
}
}
}
}
};
Assert.Equal(target, root.FindDescendantOfType<Button>(false, v => v.IsVisible));
}
[Fact]
public void FindCommonVisualAncestor_First_Is_Parent_Of_Second()
{

77
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@ -9,6 +9,8 @@ using Avalonia.Controls.Selection;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Harfbuzz;
using Avalonia.Headless;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.LogicalTree;
@ -259,51 +261,48 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void DataContexts_Should_Be_Correctly_Set()
{
using var app = Start();
var items = new object[]
{
"Foo",
new Item("Bar"),
new TextBlock { Text = "Baz" },
new TabItem { Content = "Qux" },
new TabItem { Content = new TextBlock { Text = "Bob" } }
new TabItem { Content = new TextBlock { Text = "Bob" } },
new TabItem { DataContext = "Rob", Content = new TextBlock { Text = "Bob" } },
};
var target = new TabControl
{
Template = TabControlTemplate(),
DataContext = "Base",
DataTemplates =
{
new FuncDataTemplate<Item>((x, __) => new Button { Content = x })
},
ItemsSource = items,
};
ApplyTemplate(target);
var root = CreateRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
target.ContentPart!.UpdateChild();
var dataContext = ((TextBlock)target.ContentPart.Child!).DataContext;
var dataContext = ((TextBlock)target.ContentPart!.Child!).DataContext;
Assert.Equal(items[0], dataContext);
target.SelectedIndex = 1;
target.ContentPart.UpdateChild();
dataContext = ((Button)target.ContentPart.Child).DataContext;
Assert.Equal(items[1], dataContext);
target.SelectedIndex = 2;
target.ContentPart.UpdateChild();
dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
Assert.Equal("Base", dataContext);
target.SelectedIndex = 3;
target.ContentPart.UpdateChild();
dataContext = ((TextBlock)target.ContentPart.Child).DataContext;
Assert.Equal("Qux", dataContext);
target.SelectedIndex = 4;
target.ContentPart.UpdateChild();
dataContext = target.ContentPart.DataContext;
Assert.Equal("Base", dataContext);
target.SelectedIndex = 5;
dataContext = target.ContentPart.Child.DataContext;
Assert.Equal("Rob", dataContext);
}
/// <summary>
@ -843,6 +842,45 @@ namespace Avalonia.Controls.UnitTests
}.RegisterInNameScope(scope));
}
private static ControlTheme CreateTabControlControlTheme()
{
return new ControlTheme(typeof(TabControl))
{
Setters =
{
new Setter(TabControl.TemplateProperty, TabControlTemplate()),
},
};
}
private static ControlTheme CreateTabItemControlTheme()
{
return new ControlTheme(typeof(TabItem))
{
Setters =
{
new Setter(TabItem.TemplateProperty, TabItemTemplate()),
},
};
}
private static TestRoot CreateRoot(Control child)
{
return new TestRoot
{
Resources =
{
{ typeof(TabControl), CreateTabControlControlTheme() },
{ typeof(TabItem), CreateTabItemControlTheme() },
},
DataTemplates =
{
new FuncDataTemplate<Item>((x, _) => new Button { Content = x.Value })
},
Child = child,
};
}
private class TestTopLevel : TopLevel
{
private readonly ILayoutManager _layoutManager;
@ -892,6 +930,19 @@ namespace Avalonia.Controls.UnitTests
target.ContentPart!.ApplyTemplate();
}
private IDisposable Start()
{
return UnitTestApplication.Start(
TestServices.MockThreadingInterface.With(
fontManagerImpl: new HeadlessFontManagerStub(),
keyboardDevice: () => new KeyboardDevice(),
keyboardNavigation: () => new KeyboardNavigationHandler(),
inputManager: new InputManager(),
renderInterface: new HeadlessPlatformRenderInterface(),
textShaperImpl: new HarfBuzzTextShaper(),
assetLoader: new StandardAssetLoader()));
}
private class Item
{
public Item(string value)

Loading…
Cancel
Save