فهرست منبع

Merge pull request #4211 from AvaloniaUI/fixes/logicalnotnode-bindingnotification

Handle BindingNotifications in LogicalNotNode.
Steven Kirk 5 سال پیش
والد
کامیت
e4972f1246

+ 26 - 10
src/Avalonia.Base/Data/Core/LogicalNotNode.cs

@@ -12,8 +12,19 @@ namespace Avalonia.Data.Core
             base.NextValueChanged(Negate(value));
         }
 
-        private static object Negate(object v)
+        private static object Negate(object value)
         {
+            var notification = value as BindingNotification;
+            var v = BindingNotification.ExtractValue(value);
+
+            BindingNotification GenerateError(Exception e)
+            {
+                notification ??= new BindingNotification(AvaloniaProperty.UnsetValue);
+                notification.AddError(e, BindingErrorType.Error);
+                notification.ClearValue();
+                return notification;
+            }
+
             if (v != AvaloniaProperty.UnsetValue)
             {
                 var s = v as string;
@@ -28,9 +39,7 @@ namespace Avalonia.Data.Core
                     }
                     else
                     {
-                        return new BindingNotification(
-                            new InvalidCastException($"Unable to convert '{s}' to bool."), 
-                            BindingErrorType.Error);
+                        return GenerateError(new InvalidCastException($"Unable to convert '{s}' to bool."));
                     }
                 }
                 else
@@ -38,24 +47,31 @@ namespace Avalonia.Data.Core
                     try
                     {
                         var boolean = Convert.ToBoolean(v, CultureInfo.InvariantCulture);
-                        return !boolean;
+
+                        if (notification is object)
+                        {
+                            notification.SetValue(!boolean);
+                            return notification;
+                        }
+                        else
+                        {
+                            return !boolean;
+                        }
                     }
                     catch (InvalidCastException)
                     {
                         // The error message here is "Unable to cast object of type 'System.Object'
                         // to type 'System.IConvertible'" which is kinda useless so provide our own.
-                        return new BindingNotification(
-                            new InvalidCastException($"Unable to convert '{v}' to bool."),
-                            BindingErrorType.Error);
+                        return GenerateError(new InvalidCastException($"Unable to convert '{v}' to bool."));
                     }
                     catch (Exception e)
                     {
-                        return new BindingNotification(e, BindingErrorType.Error);
+                        return GenerateError(e);
                     }
                 }
             }
 
-            return AvaloniaProperty.UnsetValue;
+            return notification ?? AvaloniaProperty.UnsetValue;
         }
 
         public object Transform(object value)

+ 77 - 1
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Negation.cs

@@ -1,4 +1,6 @@
 using System;
+using System.Collections;
+using System.ComponentModel;
 using System.Reactive.Linq;
 using System.Threading.Tasks;
 using Avalonia.Data;
@@ -89,6 +91,69 @@ namespace Avalonia.Markup.UnitTests.Parsers
             GC.KeepAlive(data);
         }
 
+        [Fact]
+        public async Task Should_Negate_BindingNotification_Value()
+        {
+            var data = new { Foo = true };
+            var target = ExpressionObserverBuilder.Build(data, "!Foo", enableDataValidation: true);
+            var result = await target.Take(1);
+
+            Assert.Equal(new BindingNotification(false), result);
+
+            GC.KeepAlive(data);
+        }
+
+        [Fact]
+        public async Task Should_Pass_Through_BindingNotification_Error()
+        {
+            var data = new { };
+            var target = ExpressionObserverBuilder.Build(data, "!Foo", enableDataValidation: true);
+            var result = await target.Take(1);
+
+            Assert.Equal(
+                new BindingNotification(
+                    new MissingMemberException("Could not find a matching property accessor for 'Foo' on '{ }'"),
+                    BindingErrorType.Error),
+                result);
+
+            GC.KeepAlive(data);
+        }
+
+        [Fact]
+        public async Task Should_Negate_BindingNotification_Error_FallbackValue()
+        {
+            var data = new Test { DataValidationError = "Test error" };
+            var target = ExpressionObserverBuilder.Build(data, "!Foo", enableDataValidation: true);
+            var result = await target.Take(1);
+
+            Assert.Equal(
+                new BindingNotification(
+                    new DataValidationException("Test error"),
+                    BindingErrorType.DataValidationError,
+                    true),
+                result);
+
+            GC.KeepAlive(data);
+        }
+
+        [Fact]
+        public async Task Should_Add_Error_To_BindingNotification_For_FallbackValue_Not_Convertible_To_Boolean()
+        {
+            var data = new Test { Bar = new object(), DataValidationError = "Test error" };
+            var target = ExpressionObserverBuilder.Build(data, "!Bar", enableDataValidation: true);
+            var result = await target.Take(1);
+
+            Assert.Equal(
+                new BindingNotification(
+                    new AggregateException(
+                        new DataValidationException("Test error"),
+                        new InvalidCastException($"Unable to convert 'System.Object' to bool.")),
+                    BindingErrorType.Error),
+                result);
+
+            GC.KeepAlive(data);
+        }
+
         [Fact]
         public void SetValue_Should_Return_False_For_Invalid_Value()
         {
@@ -101,9 +166,20 @@ namespace Avalonia.Markup.UnitTests.Parsers
             GC.KeepAlive(data);
         }
 
-        private class Test
+        private class Test : INotifyDataErrorInfo
         {
             public bool Foo { get; set; }
+            public object Bar { get; set; }
+
+            public string DataValidationError { get; set; }
+            public bool HasErrors => !string.IsNullOrWhiteSpace(DataValidationError);
+
+            public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
+
+            public IEnumerable GetErrors(string propertyName)
+            {
+                return DataValidationError is object ? new[] { DataValidationError } : null;
+            }
         }
     }
 }