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 (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 // The leaf node has changed. If the binding mode is not OneWayToSource, publish the
// value to the target. // value to the target.
if (_mode != BindingMode.OneWayToSource) 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); 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] [Fact]
public void Can_Use_UpdateTarget_To_Update_From_Non_INPC_Data() 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); 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] [Fact]
public void OneWayToSource_Binding_Updates_Source_When_Target_Changes() 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, RelativeSource? relativeSource,
Optional<TIn> source, Optional<TIn> source,
object? targetNullValue, object? targetNullValue,
string? stringFormat,
UpdateSourceTrigger updateSourceTrigger) UpdateSourceTrigger updateSourceTrigger)
{ {
var target = new TargetClass { DataContext = dataContext }; var target = new TargetClass { DataContext = dataContext };
@ -66,6 +67,7 @@ public abstract partial class BindingExpressionTests
mode: mode, mode: mode,
targetNullValue: targetNullValue, targetNullValue: targetNullValue,
targetTypeConverter: TargetTypeConverter.GetReflectionConverter(), targetTypeConverter: TargetTypeConverter.GetReflectionConverter(),
stringFormat: stringFormat,
updateSourceTrigger: updateSourceTrigger); updateSourceTrigger: updateSourceTrigger);
target.GetValueStore().AddBinding(targetProperty, bindingExpression); target.GetValueStore().AddBinding(targetProperty, bindingExpression);
@ -87,6 +89,7 @@ public abstract partial class BindingExpressionTests
RelativeSource? relativeSource, RelativeSource? relativeSource,
Optional<TIn> source, Optional<TIn> source,
object? targetNullValue, object? targetNullValue,
string? stringFormat,
UpdateSourceTrigger updateSourceTrigger) UpdateSourceTrigger updateSourceTrigger)
{ {
var target = new TargetClass { DataContext = dataContext }; var target = new TargetClass { DataContext = dataContext };
@ -112,6 +115,7 @@ public abstract partial class BindingExpressionTests
mode: mode, mode: mode,
targetNullValue: targetNullValue, targetNullValue: targetNullValue,
targetTypeConverter: TargetTypeConverter.GetReflectionConverter(), targetTypeConverter: TargetTypeConverter.GetReflectionConverter(),
stringFormat: stringFormat,
updateSourceTrigger: updateSourceTrigger); updateSourceTrigger: updateSourceTrigger);
target.GetValueStore().AddBinding(targetProperty, bindingExpression); target.GetValueStore().AddBinding(targetProperty, bindingExpression);
return (target, bindingExpression); return (target, bindingExpression);
@ -129,7 +133,8 @@ public abstract partial class BindingExpressionTests
BindingMode mode = BindingMode.OneWay, BindingMode mode = BindingMode.OneWay,
RelativeSource? relativeSource = null, RelativeSource? relativeSource = null,
Optional<TIn> source = default, Optional<TIn> source = default,
object? targetNullValue = null) object? targetNullValue = null,
string? stringFormat = null)
where TIn : class? where TIn : class?
{ {
var (target, _) = CreateTargetAndExpression( var (target, _) = CreateTargetAndExpression(
@ -143,7 +148,8 @@ public abstract partial class BindingExpressionTests
mode, mode,
relativeSource, relativeSource,
source, source,
targetNullValue); targetNullValue,
stringFormat);
return target; return target;
} }
@ -158,6 +164,7 @@ public abstract partial class BindingExpressionTests
BindingMode mode = BindingMode.OneWay, BindingMode mode = BindingMode.OneWay,
RelativeSource? relativeSource = null, RelativeSource? relativeSource = null,
object? targetNullValue = null, object? targetNullValue = null,
string? stringFormat = null,
UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.PropertyChanged) UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.PropertyChanged)
where TIn : class? where TIn : class?
{ {
@ -173,6 +180,7 @@ public abstract partial class BindingExpressionTests
relativeSource, relativeSource,
source, source,
targetNullValue, targetNullValue,
stringFormat,
updateSourceTrigger); updateSourceTrigger);
return target; return target;
} }
@ -189,6 +197,7 @@ public abstract partial class BindingExpressionTests
RelativeSource? relativeSource = null, RelativeSource? relativeSource = null,
Optional<TIn> source = default, Optional<TIn> source = default,
object? targetNullValue = null, object? targetNullValue = null,
string? stringFormat = null,
UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.PropertyChanged) UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.PropertyChanged)
where TIn : class? where TIn : class?
{ {
@ -213,6 +222,7 @@ public abstract partial class BindingExpressionTests
relativeSource, relativeSource,
source, source,
targetNullValue, targetNullValue,
stringFormat,
updateSourceTrigger); updateSourceTrigger);
} }
@ -228,6 +238,7 @@ public abstract partial class BindingExpressionTests
RelativeSource? relativeSource, RelativeSource? relativeSource,
Optional<TIn> source, Optional<TIn> source,
object? targetNullValue, object? targetNullValue,
string? stringFormat,
UpdateSourceTrigger updateSourceTrigger) UpdateSourceTrigger updateSourceTrigger)
where TIn : class?; where TIn : class?;

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

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

Loading…
Cancel
Save