Browse Source

Fix bindings without property path (#16729)

* Added failing binding tests

* Fix bindings without property path

---------

Co-authored-by: Benedikt Stebner <Gillibald@users.noreply.github.com>
release/11.2.0-beta2
Julien Lebosquain 2 years ago
committed by GitHub
parent
commit
20f77a3490
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  2. 10
      tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.GetValue.cs
  3. 25
      tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.Mode.cs
  4. 15
      tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs
  5. 6
      tests/Avalonia.Markup.UnitTests/Data/BindingTests_Logging.cs

5
src/Avalonia.Base/Data/Core/BindingExpression.cs

@ -231,6 +231,11 @@ internal partial class BindingExpression : UntypedBindingExpressionBase, IDescri
if (nodeIndex == _nodes.Count - 1)
{
// If the binding source is a data context without any path and is currently null, treat it as an invalid
// value. This allows bindings to DataContext and DataContext.Property to share the same behavior.
if (value is null && _nodes[nodeIndex] is DataContextNodeBase)
value = AvaloniaProperty.UnsetValue;
// The leaf node has changed. If the binding mode is not OneWayToSource, publish the
// value to the target.
if (_mode != BindingMode.OneWayToSource)

10
tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.GetValue.cs

@ -94,6 +94,16 @@ public abstract partial class BindingExpressionTests
GC.KeepAlive(data);
}
[Fact]
public void TargetNullValue_Should_Not_Be_Used_When_Source_Is_Data_Context_And_Null()
{
var target = CreateTarget<string?, string?>(
o => o,
targetNullValue: "bar");
Assert.Equal(null, target.String);
}
[Fact]
public void Can_Use_UpdateTarget_To_Update_From_Non_INPC_Data()
{

25
tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.Mode.cs

@ -69,6 +69,31 @@ public partial class BindingExpressionTests
Assert.Equal(0.5, target.Double);
}
[Fact]
public void OneTime_Binding_Waits_For_DataContext_Without_Property_Path()
{
var target = CreateTarget<string?, string?>(
x => x,
mode: BindingMode.OneTime);
target.DataContext = "foo";
Assert.Equal("foo", target.String);
}
[Fact]
public void OneTime_Binding_Waits_For_DataContext_Without_Property_Path_With_StringFormat()
{
var target = CreateTarget<string?, string?>(
x => x,
mode: BindingMode.OneTime,
stringFormat: "bar: {0}");
target.DataContext = "foo";
Assert.Equal("bar: foo", target.String);
}
[Fact]
public void OneWayToSource_Binding_Updates_Source_When_Target_Changes()
{

15
tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs

@ -33,6 +33,7 @@ public abstract partial class BindingExpressionTests
RelativeSource? relativeSource,
Optional<TIn> source,
object? targetNullValue,
string? stringFormat,
UpdateSourceTrigger updateSourceTrigger)
{
var target = new TargetClass { DataContext = dataContext };
@ -66,6 +67,7 @@ public abstract partial class BindingExpressionTests
mode: mode,
targetNullValue: targetNullValue,
targetTypeConverter: TargetTypeConverter.GetReflectionConverter(),
stringFormat: stringFormat,
updateSourceTrigger: updateSourceTrigger);
target.GetValueStore().AddBinding(targetProperty, bindingExpression);
@ -87,6 +89,7 @@ public abstract partial class BindingExpressionTests
RelativeSource? relativeSource,
Optional<TIn> source,
object? targetNullValue,
string? stringFormat,
UpdateSourceTrigger updateSourceTrigger)
{
var target = new TargetClass { DataContext = dataContext };
@ -112,6 +115,7 @@ public abstract partial class BindingExpressionTests
mode: mode,
targetNullValue: targetNullValue,
targetTypeConverter: TargetTypeConverter.GetReflectionConverter(),
stringFormat: stringFormat,
updateSourceTrigger: updateSourceTrigger);
target.GetValueStore().AddBinding(targetProperty, bindingExpression);
return (target, bindingExpression);
@ -129,7 +133,8 @@ public abstract partial class BindingExpressionTests
BindingMode mode = BindingMode.OneWay,
RelativeSource? relativeSource = null,
Optional<TIn> source = default,
object? targetNullValue = null)
object? targetNullValue = null,
string? stringFormat = null)
where TIn : class?
{
var (target, _) = CreateTargetAndExpression(
@ -143,7 +148,8 @@ public abstract partial class BindingExpressionTests
mode,
relativeSource,
source,
targetNullValue);
targetNullValue,
stringFormat);
return target;
}
@ -158,6 +164,7 @@ public abstract partial class BindingExpressionTests
BindingMode mode = BindingMode.OneWay,
RelativeSource? relativeSource = null,
object? targetNullValue = null,
string? stringFormat = null,
UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.PropertyChanged)
where TIn : class?
{
@ -173,6 +180,7 @@ public abstract partial class BindingExpressionTests
relativeSource,
source,
targetNullValue,
stringFormat,
updateSourceTrigger);
return target;
}
@ -189,6 +197,7 @@ public abstract partial class BindingExpressionTests
RelativeSource? relativeSource = null,
Optional<TIn> source = default,
object? targetNullValue = null,
string? stringFormat = null,
UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.PropertyChanged)
where TIn : class?
{
@ -213,6 +222,7 @@ public abstract partial class BindingExpressionTests
relativeSource,
source,
targetNullValue,
stringFormat,
updateSourceTrigger);
}
@ -228,6 +238,7 @@ public abstract partial class BindingExpressionTests
RelativeSource? relativeSource,
Optional<TIn> source,
object? targetNullValue,
string? stringFormat,
UpdateSourceTrigger updateSourceTrigger)
where TIn : class?;

6
tests/Avalonia.Markup.UnitTests/Data/BindingTests_Logging.cs

@ -305,8 +305,8 @@ namespace Avalonia.Markup.UnitTests.Data
[InlineData(false)]
public void Should_Log_Invalid_TargetNullValue(bool rooted)
{
var target = new Decorator { };
var binding = new Binding() { TargetNullValue = "foo" };
var target = new Decorator { DataContext = new { Bar = (string?) null } };
var binding = new Binding("Bar") { TargetNullValue = "foo" };
if (rooted)
new TestRoot(target);
@ -314,7 +314,7 @@ namespace Avalonia.Markup.UnitTests.Data
// An invalid target null value is invalid whether the control is rooted or not.
using (AssertLog(
target,
"",
binding.Path,
"Could not convert TargetNullValue 'foo' to 'System.Double'.",
level: LogEventLevel.Error,
property: Visual.OpacityProperty))

Loading…
Cancel
Save