Browse Source

Fix OneWayToSource bindings with read-only properties. (#14513)

* Added more OneWayToSource tests.

One of whom is failing.

* Don't public value for OneWayToSource bindings.

Looks to have been a brainfart. This allows `OneWayToSource` bindings to read-only properties.
Steven Kirk 1 year ago
parent
commit
f6fe68eddb

+ 0 - 3
src/Avalonia.Base/Data/Core/BindingExpression.cs

@@ -375,9 +375,6 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri
                 TryGetTarget(out var target) &&
                 TryGetTarget(out var target) &&
                 TargetProperty is not null)
                 TargetProperty is not null)
             {
             {
-                if (_mode is BindingMode.OneWayToSource)
-                    PublishValue(target.GetValue(TargetProperty));
-
                 var trigger = UpdateSourceTrigger;
                 var trigger = UpdateSourceTrigger;
 
 
                 if (trigger is UpdateSourceTrigger.PropertyChanged)
                 if (trigger is UpdateSourceTrigger.PropertyChanged)

+ 67 - 0
tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.Mode.cs

@@ -1,5 +1,6 @@
 using Avalonia.Data;
 using Avalonia.Data;
 using Xunit;
 using Xunit;
+using Xunit.Sdk;
 
 
 #nullable enable
 #nullable enable
 
 
@@ -67,4 +68,70 @@ public partial class BindingExpressionTests
         data2.DoubleValue = 0.2;
         data2.DoubleValue = 0.2;
         Assert.Equal(0.5, target.Double);
         Assert.Equal(0.5, target.Double);
     }
     }
+
+    [Fact]
+    public void OneWayToSource_Binding_Updates_Source_When_Target_Changes()
+    {
+        var data = new ViewModel();
+        var target = CreateTarget<ViewModel, string?>(
+            x => x.StringValue,
+            dataContext: data,
+            mode: BindingMode.OneWayToSource);
+
+        Assert.Null(data.StringValue);
+
+        target.String = "foo";
+
+        Assert.Equal("foo", data.StringValue);
+    }
+
+    [Fact]
+    public void OneWayToSource_Binding_Does_Not_Update_Target_When_Source_Changes()
+    {
+        var data = new ViewModel();
+        var target = CreateTarget<ViewModel, string?>(
+            x => x.StringValue,
+            dataContext: data,
+            mode: BindingMode.OneWayToSource);
+
+        target.String = "foo";
+        Assert.Equal("foo", data.StringValue);
+
+        data.StringValue = "bar";
+        Assert.Equal("foo", target.String);
+    }
+
+    [Fact]
+    public void OneWayToSource_Binding_Updates_Source_When_DataContext_Changes()
+    {
+        var data1 = new ViewModel();
+        var data2 = new ViewModel();
+        var target = CreateTarget<ViewModel, string?>(
+            x => x.StringValue,
+            dataContext: data1,
+            mode: BindingMode.OneWayToSource);
+
+        target.String = "foo";
+        Assert.Equal("foo", data1.StringValue);
+
+        target.DataContext = data2;
+        Assert.Equal("foo", data2.StringValue);
+    }
+
+    [Fact]
+    public void Can_Bind_Readonly_Property_OneWayToSource()
+    {
+        var data = new ViewModel();
+        var target = CreateTarget<ViewModel, string?>(
+            x => x.StringValue,
+            dataContext: data,
+            mode: BindingMode.OneWayToSource,
+            targetProperty: TargetClass.ReadOnlyStringProperty);
+
+        Assert.Equal("readonly", data.StringValue);
+
+        target.SetReadOnlyString("foo");
+
+        Assert.Equal("foo", data.StringValue);
+    }
 }
 }

+ 14 - 0
tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs

@@ -343,6 +343,12 @@ public abstract partial class BindingExpressionTests
             AvaloniaProperty.Register<TargetClass, object?>("Object");
             AvaloniaProperty.Register<TargetClass, object?>("Object");
         public static readonly StyledProperty<string?> StringProperty =
         public static readonly StyledProperty<string?> StringProperty =
             AvaloniaProperty.Register<TargetClass, string?>("String");
             AvaloniaProperty.Register<TargetClass, string?>("String");
+        public static readonly DirectProperty<TargetClass, string?> ReadOnlyStringProperty =
+            AvaloniaProperty.RegisterDirect<TargetClass, string?>(
+                nameof(ReadOnlyString),
+                o => o.ReadOnlyString);
+
+        private string? _readOnlyString = "readonly";
 
 
         static TargetClass()
         static TargetClass()
         {
         {
@@ -379,10 +385,18 @@ public abstract partial class BindingExpressionTests
             set => SetValue(StringProperty, value);
             set => SetValue(StringProperty, value);
         }
         }
 
 
+        public string? ReadOnlyString
+        {
+            get => _readOnlyString;
+            private set => SetAndRaise(ReadOnlyStringProperty, ref _readOnlyString, value);
+        }
+
         public Dictionary<AvaloniaProperty, BindingNotification> BindingNotifications { get; } = new();
         public Dictionary<AvaloniaProperty, BindingNotification> BindingNotifications { get; } = new();
 
 
         public override string ToString() => nameof(TargetClass);
         public override string ToString() => nameof(TargetClass);
 
 
+        public void SetReadOnlyString(string? value) => ReadOnlyString = value;
+
         protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error)
         protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error)
         {
         {
             base.UpdateDataValidation(property, state, error);
             base.UpdateDataValidation(property, state, error);