diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml index b7efaf7869..e6a73822e9 100644 --- a/api/Avalonia.nupkg.xml +++ b/api/Avalonia.nupkg.xml @@ -25,6 +25,60 @@ baseline/Avalonia/lib/net10.0/Avalonia.Base.dll current/Avalonia/lib/net10.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Data.Core.Plugins.BindingPlugins + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.DataValidationBase + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.ExceptionValidationPlugin + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IDataValidationPlugin + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IndeiValidationPlugin + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IPropertyAccessorPlugin + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IStreamPlugin + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.PropertyAccessorBase + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.PropertyError + baseline/Avalonia/lib/net10.0/Avalonia.Base.dll + current/Avalonia/lib/net10.0/Avalonia.Base.dll + CP0001 T:Avalonia.Diagnostics.AppliedStyle @@ -253,6 +307,60 @@ baseline/Avalonia/lib/net8.0/Avalonia.Base.dll current/Avalonia/lib/net8.0/Avalonia.Base.dll + + CP0001 + T:Avalonia.Data.Core.Plugins.BindingPlugins + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.DataValidationBase + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.ExceptionValidationPlugin + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IDataValidationPlugin + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IndeiValidationPlugin + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IPropertyAccessorPlugin + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.IStreamPlugin + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.PropertyAccessorBase + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + + + CP0001 + T:Avalonia.Data.Core.Plugins.PropertyError + baseline/Avalonia/lib/net8.0/Avalonia.Base.dll + current/Avalonia/lib/net8.0/Avalonia.Base.dll + CP0001 T:Avalonia.Diagnostics.AppliedStyle diff --git a/src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs b/src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs index 520c345ad5..50e137eac1 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs +++ b/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. /// [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] - public static class BindingPlugins + internal static class BindingPlugins { internal static readonly List s_propertyAccessors = new() { @@ -17,7 +17,6 @@ namespace Avalonia.Data.Core.Plugins internal static readonly List s_dataValidators = new() { - new DataAnnotationsValidationPlugin(), new IndeiValidationPlugin(), new ExceptionValidationPlugin(), }; diff --git a/src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs b/src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs index 6bef9f69f6..85ebf6ff78 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/DataValidationBase.cs +++ b/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 /// s. /// - public abstract class DataValidationBase : PropertyAccessorBase, IObserver + internal abstract class DataValidationBase : PropertyAccessorBase, IObserver { private readonly IPropertyAccessor _inner; diff --git a/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs index 899b542f80..25c77e3282 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs @@ -7,7 +7,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Validates properties that report errors by throwing exceptions. /// - public class ExceptionValidationPlugin : IDataValidationPlugin + internal class ExceptionValidationPlugin : IDataValidationPlugin { /// public bool Match(WeakReference reference, string memberName) => true; diff --git a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs index 5d2f06d2c6..339c8a656d 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs @@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Defines how data validation is observed by an . /// - public interface IDataValidationPlugin + internal interface IDataValidationPlugin { /// /// Checks whether this plugin can handle data validation on the specified object. diff --git a/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs index 8941e86598..8db19bbffa 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs @@ -3,8 +3,7 @@ using System; namespace Avalonia.Data.Core.Plugins { /// - /// Defines an accessor to a property on an object returned by a - /// + /// Defines an accessor to a property on an object. /// public interface IPropertyAccessor : IDisposable { diff --git a/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs index e2d7312ac4..fb24969c7e 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs @@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Defines how a member is read, written and observed by a binding. /// - public interface IPropertyAccessorPlugin + internal interface IPropertyAccessorPlugin { /// /// Checks whether this plugin can handle accessing the properties of the specified object. diff --git a/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs index b741cfaca2..da86543507 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs @@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Defines a plugin that handles the '^' stream binding operator. /// - public interface IStreamPlugin + internal interface IStreamPlugin { /// /// Checks whether this plugin handles the specified value. diff --git a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs index 385d96a7b8..a06514b5da 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs @@ -9,7 +9,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Validates properties on objects that implement . /// - public class IndeiValidationPlugin : IDataValidationPlugin + internal class IndeiValidationPlugin : IDataValidationPlugin { private static readonly WeakEvent ErrorsChangedWeakEvent = WeakEvent.Register( diff --git a/src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs b/src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs index df36347e93..ad4e46ca08 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs @@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Defines a default base implementation for a . /// - public abstract class PropertyAccessorBase : IPropertyAccessor + internal abstract class PropertyAccessorBase : IPropertyAccessor { private Action? _listener; diff --git a/src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs b/src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs index 7143a4269a..dae52e8541 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs @@ -5,7 +5,7 @@ namespace Avalonia.Data.Core.Plugins /// /// An that represents an error. /// - public class PropertyError : IPropertyAccessor + internal class PropertyError : IPropertyAccessor { private readonly BindingNotification _error; diff --git a/src/Avalonia.Base/VisualTree/VisualExtensions.cs b/src/Avalonia.Base/VisualTree/VisualExtensions.cs index 55176a6502..b202a2e4b7 100644 --- a/src/Avalonia.Base/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Base/VisualTree/VisualExtensions.cs @@ -147,6 +147,19 @@ namespace Avalonia.VisualTree /// If given visual should be included in search. /// First ancestor of given type. public static T? FindAncestorOfType(this Visual? visual, bool includeSelf = false) where T : class + { + return FindAncestorOfType(visual, includeSelf, predicate: null); + } + + /// + /// Finds first ancestor of given type that matches a predicate. + /// + /// Ancestor type. + /// The visual. + /// If given visual should be included in search. + /// The predicate that the ancestor must match. + /// First ancestor of given type. + public static T? FindAncestorOfType(this Visual? visual, bool includeSelf, Predicate? predicate) where T : class { if (visual is null) { @@ -159,7 +172,10 @@ namespace Avalonia.VisualTree { if (parent is T result) { - return result; + if (predicate == null || predicate(result)) + { + return result; + } } parent = parent.VisualParent; @@ -176,18 +192,31 @@ namespace Avalonia.VisualTree /// If given visual should be included in search. /// First descendant of given type. public static T? FindDescendantOfType(this Visual? visual, bool includeSelf = false) where T : class + { + return FindDescendantOfType(visual, includeSelf, predicate: null); + } + + /// + /// Finds first descendant of given type that matches given predicate. + /// + /// Descendant type. + /// The visual. + /// If given visual should be included in search. + /// The predicate that the descendant must match. + /// First descendant of given type that matches given predicate. + public static T? FindDescendantOfType(this Visual? visual, bool includeSelf, Predicate? 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(visual); + return FindDescendantOfTypeCore(visual, predicate); } /// @@ -485,7 +514,7 @@ namespace Avalonia.VisualTree .Select(x => x.Element!); } - private static T? FindDescendantOfTypeCore(Visual visual) where T : class + private static T? FindDescendantOfTypeCore(Visual visual, Predicate? predicate) where T : class { var visualChildren = visual.VisualChildren; var visualChildrenCount = visualChildren.Count; @@ -496,10 +525,13 @@ namespace Avalonia.VisualTree if (child is T result) { - return result; + if (predicate == null || predicate(result)) + { + return result; + } } - var childResult = FindDescendantOfTypeCore(child); + var childResult = FindDescendantOfTypeCore(child, predicate); if (!(childResult is null)) { diff --git a/src/Avalonia.Controls/AppBuilder.cs b/src/Avalonia.Controls/AppBuilder.cs index 9d6bcff813..9259be8594 100644 --- a/src/Avalonia.Controls/AppBuilder.cs +++ b/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; } + /// + /// Adds support for validation using System.ComponentModel.DataAnnotations. + /// + [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] + public AppBuilder WithDataAnnotationsValidation() + { + if (!BindingPlugins.DataValidators.Any(x => x is DataAnnotationsValidationPlugin)) + BindingPlugins.DataValidators.Insert(0, new DataAnnotationsValidationPlugin()); + return Self; + } + /// /// Registers an action that is executed with the current font manager. /// diff --git a/src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs b/src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs index 8cdee8e3fe..9e28080cb5 100644 --- a/src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs +++ b/src/Avalonia.Controls/Automation/AutomationElementIdentifiers.cs @@ -31,6 +31,12 @@ namespace Avalonia.Automation /// public static AutomationProperty HelpTextProperty { get; } = new AutomationProperty(); + /// + /// Identifies the itemStatus automation property. The class name property value is returned + /// by the method. + /// + public static AutomationProperty ItemStatusProperty { get; } = new AutomationProperty(); + /// /// Identifies the landmark type automation property. The class name property value is returned /// by the method. diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs index 56111bdc83..b32b60118c 100644 --- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -319,6 +319,41 @@ namespace Avalonia.Automation.Peers /// public int GetHeadingLevel() => GetHeadingLevelCore(); + + /// + /// Gets the item type that is associated with this automation peer. + /// + /// + /// + /// + /// Windows + /// UIA_ItemTypePropertyId + /// + /// + /// macOS + /// No mapping. + /// + /// + /// + public string? GetItemType() => GetItemTypeCore(); + + /// + /// Gets the item status that is associated with this automation peer. + /// + /// + /// + /// + /// Windows + /// UIA_ItemStatusPropertyId + /// + /// + /// macOS + /// No mapping. + /// + /// + /// + public string? GetItemStatus() => GetItemStatusCore(); + /// /// Gets the that is the parent of this . /// @@ -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(); diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs index c59ba6b148..cdab4911f2 100644 --- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs +++ b/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); + } } diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index 396f82a622..dc9adc0fac 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/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(SelectionMode.AlwaysSelected); ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); AffectsMeasure(TabStripPlacementProperty); - SelectedItemProperty.Changed.AddClassHandler((x, e) => x.UpdateSelectedContent()); + SelectedItemProperty.Changed.AddClassHandler((x, _) => x.UpdateSelectedContent()); AutomationProperties.ControlTypeOverrideProperty.OverrideDefaultValue(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 diff --git a/src/Windows/Avalonia.Win32.Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32.Automation/AutomationNode.cs index 8562807452..78f562decf 100644 --- a/src/Windows/Avalonia.Win32.Automation/AutomationNode.cs +++ b/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()), diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.DataValidation.cs b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.DataValidation.cs index 8dd75fedf0..04aa7b8de2 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.DataValidation.cs +++ b/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, diff --git a/tests/Avalonia.Base.UnitTests/VisualExtensionsTests.cs b/tests/Avalonia.Base.UnitTests/VisualExtensionsTests.cs index 4863c0fa61..270702f178 100644 --- a/tests/Avalonia.Base.UnitTests/VisualExtensionsTests.cs +++ b/tests/Avalonia.Base.UnitTests/VisualExtensionsTests.cs @@ -22,6 +22,23 @@ namespace Avalonia.Base.UnitTests Assert.Equal(root, target.FindAncestorOfType()); } + [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(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