using System; using System.Collections.Generic; using System.Globalization; using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Data.Converters; using Avalonia.Data.Core; using Avalonia.UnitTests; using Xunit; #nullable enable namespace Avalonia.Base.UnitTests.Data.Core { public class BindingExpressionTests_SetValue { [Fact] public void Should_Set_Simple_Property_Value() { var data = new Person { Name = "Frank" }; var target = BindingExpression.Create(data, o => o.Name); using (target.ToObservable().Subscribe(_ => { })) { target.WriteValueToSource("Kups"); } Assert.Equal("Kups", data.Name); } [Fact] public void Should_Set_Attached_Property_Value() { var data = new AvaloniaObject(); var target = BindingExpression.Create(data, o => o[DockPanel.DockProperty]); using (target.ToObservable().Subscribe(_ => { })) { target.WriteValueToSource(Dock.Right); } Assert.Equal(Dock.Right, data[DockPanel.DockProperty]); } [Fact] public void Should_Set_Indexed_Value() { var data = new { Foo = new[] { "foo" } }; var target = BindingExpression.Create(data, o => o.Foo[0]); using (target.ToObservable().Subscribe(_ => { })) { target.WriteValueToSource("bar"); } Assert.Equal("bar", data.Foo[0]); GC.KeepAlive(data); } [Fact] public void Should_Set_Value_On_Simple_Property_Chain() { var data = new Person { Pet = new Dog { Name = "Fido" } }; var target = BindingExpression.Create(data, o => o.Pet!.Name); using (target.ToObservable().Subscribe(_ => { })) { target.WriteValueToSource("Rover"); } Assert.Equal("Rover", data.Pet.Name); } [Fact] public void Should_Not_Try_To_Set_Value_On_Broken_Chain() { var data = new Person { Pet = new Dog { Name = "Fido" } }; var target = BindingExpression.Create(data, o => o.Pet!.Name); // Ensure the UntypedBindingExpression's subscriptions are kept active. using (target.ToObservable()!.OfType().Subscribe(x => { })) { data.Pet = null; Assert.False(target.WriteValueToSource("Rover")); } } [Fact] public void SetValue_Should_Return_False_For_Missing_Property() { var data = new Person { Pet = new Cat() }; var target = BindingExpression.Create(data, o => (o.Pet as Dog)!.IsBarky); using (target.ToObservable().Subscribe(_ => { })) { Assert.False(target.WriteValueToSource("baz")); } GC.KeepAlive(data); } [Fact] public void SetValue_Should_Notify_New_Value_With_Inpc() { var data = new Person(); var target = BindingExpression.Create(data, o => o.Name); var result = new List(); target.ToObservable().Subscribe(result.Add); target.WriteValueToSource("Frank"); Assert.Equal(new[] { null, "Frank" }, result); GC.KeepAlive(data); } [Fact] public void SetValue_Should_Notify_New_Value_Without_Inpc() { var data = new Snail(); var target = BindingExpression.Create(data, o => o.Name); var result = new List(); target.ToObservable().Subscribe(result.Add); target.WriteValueToSource("Frank"); Assert.Equal(new[] { null, "Frank" }, result); GC.KeepAlive(data); } [Fact] public void SetValue_Should_Return_False_For_Missing_Object() { var data = new Person(); var target = BindingExpression.Create(data, o => (o.Pet as Dog)!.Name); using (target.ToObservable().Subscribe(_ => { })) { Assert.False(target.WriteValueToSource("Fido")); } GC.KeepAlive(data); } [Fact] public void SetValue_Should_Use_Converter() { var data = new Person { Name = "Frank" }; var target = BindingExpression.Create( data, o => o.Name, converter: new CaseConverter()); using (target.ToObservable().Subscribe(_ => { })) { Assert.True(target.WriteValueToSource("Kups")); } Assert.Equal("kups", data.Name); GC.KeepAlive(data); } [Fact] public void TwoWay_Binding_Should_Not_Write_Unchanged_Value_Back_To_Property_With_Converter() { var data = new Cat(); var control = new Control(); var target = BindingExpression.Create( data, o => o.WhiskerCount, converter: new CaseConverter(), mode: BindingMode.TwoWay); var instance = new InstancedBinding( target, BindingMode.TwoWay, BindingPriority.LocalValue); BindingOperations.Apply(control, Visual.OpacityProperty, instance); Assert.Equal(4, control.Opacity); Assert.Equal(9, data.Lives); data.WhiskerCount = 3; Assert.Equal(3, control.Opacity); Assert.Equal(8, data.Lives); GC.KeepAlive(data); } private interface IAnimal { string? Name { get; } } private class Person : NotifyingBase { private string? _name; private IAnimal? _pet; public string? Name { get => _name; set { _name = value; RaisePropertyChanged(nameof(Name)); } } public IAnimal? Pet { get => _pet; set { _pet = value; RaisePropertyChanged(nameof(Pet)); } } } private class Animal : NotifyingBase, IAnimal { private string? _name; public string? Name { get => _name; set { _name = value; RaisePropertyChanged(nameof(Name)); } } } private class Dog : Animal { public bool IsBarky { get; set; } } private class Cat : Animal { private int _whiskerCount= 4; public int WhiskerCount { get => _whiskerCount; set { _whiskerCount = value; RaisePropertyChanged(nameof(WhiskerCount)); --Lives; } } public int Lives { get; private set; } = 9; } private class Snail : IAnimal { public string? Name { get; set; } } private class CaseConverter : IValueConverter { public static readonly CaseConverter Instance = new(); public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { return value?.ToString()?.ToUpper(); } public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { return value?.ToString()?.ToLower(); } } } }