Browse Source

Handle BindingNotifications in LogicalNotNode.

Fixes handling binding notifications in `LogicalNotNote`. Previously if a `!` was placed in a binding that produced a `BindingNotification`, then `LogicalNotNode` tried to convert the `BindingNotification` to `bool` and failed, throwing an exception. Handle this properly.
pull/4211/head
Steven Kirk 6 years ago
parent
commit
ed43a5753a
  1. 36
      src/Avalonia.Base/Data/Core/LogicalNotNode.cs
  2. 78
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Negation.cs

36
src/Avalonia.Base/Data/Core/LogicalNotNode.cs

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

78
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Negation.cs

@ -1,4 +1,6 @@
using System; using System;
using System.Collections;
using System.ComponentModel;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Data; using Avalonia.Data;
@ -89,6 +91,69 @@ namespace Avalonia.Markup.UnitTests.Parsers
GC.KeepAlive(data); 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] [Fact]
public void SetValue_Should_Return_False_For_Invalid_Value() public void SetValue_Should_Return_False_For_Invalid_Value()
{ {
@ -101,9 +166,20 @@ namespace Avalonia.Markup.UnitTests.Parsers
GC.KeepAlive(data); GC.KeepAlive(data);
} }
private class Test private class Test : INotifyDataErrorInfo
{ {
public bool Foo { get; set; } 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;
}
} }
} }
} }

Loading…
Cancel
Save