| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108 |
- using System;
- using System.ComponentModel;
- using System.Reactive.Linq;
- using System.Reactive.Subjects;
- using System.Threading;
- using System.Threading.Tasks;
- using Avalonia.Controls;
- using Avalonia.Data;
- using Avalonia.Logging;
- using Avalonia.Platform;
- using Avalonia.Threading;
- using Avalonia.UnitTests;
- using Microsoft.Reactive.Testing;
- using Moq;
- using Xunit;
- #nullable enable
- namespace Avalonia.Base.UnitTests
- {
- public class AvaloniaObjectTests_Binding
- {
- [Fact]
- public void Bind_Sets_Current_Value()
- {
- var target = new Class1();
- var source = new BehaviorSubject<BindingValue<string>>("initial");
- var property = Class1.FooProperty;
- target.Bind(property, source);
- Assert.Equal("initial", target.GetValue(property));
- }
- [Fact]
- public void Bind_Raises_PropertyChanged()
- {
- var target = new Class1();
- var source = new Subject<BindingValue<string>>();
- var raised = 0;
- target.PropertyChanged += (s, e) =>
- {
- Assert.Equal(Class1.FooProperty, e.Property);
- Assert.Equal("foodefault", (string?)e.OldValue);
- Assert.Equal("newvalue", (string?)e.NewValue);
- Assert.Equal(BindingPriority.LocalValue, e.Priority);
- ++raised;
- };
- target.Bind(Class1.FooProperty, source);
- source.OnNext("newvalue");
- Assert.Equal(1, raised);
- }
- [Fact]
- public void PropertyChanged_Not_Raised_When_Value_Unchanged()
- {
- var target = new Class1();
- var source = new Subject<BindingValue<string>>();
- var raised = 0;
- target.PropertyChanged += (s, e) => ++raised;
- target.Bind(Class1.FooProperty, source);
- source.OnNext("newvalue");
- source.OnNext("newvalue");
- Assert.Equal(1, raised);
- }
- [Fact]
- public void Setting_LocalValue_Overrides_Binding_Until_Binding_Produces_Next_Value()
- {
- var target = new Class1();
- var source = new Subject<BindingValue<string>>();
- var property = Class1.FooProperty;
- target.Bind(property, source);
- source.OnNext("foo");
- Assert.Equal("foo", target.GetValue(property));
- target.SetValue(property, "bar");
- Assert.Equal("bar", target.GetValue(property));
- source.OnNext("baz");
- Assert.Equal("baz", target.GetValue(property));
- }
- [Fact]
- public void Completing_LocalValue_Binding_Reverts_To_Default_Value_Even_When_Local_Value_Set_Earlier()
- {
- var target = new Class1();
- var source = new Subject<BindingValue<string>>();
- var property = Class1.FooProperty;
- target.Bind(property, source);
- source.OnNext("foo");
- target.SetValue(property, "bar");
- source.OnNext("baz");
- source.OnCompleted();
- Assert.Equal("foodefault", target.GetValue(property));
- }
- [Fact]
- public void Disposing_LocalValue_Binding_Should_Not_Revert_To_Set_LocalValue()
- {
- var target = new Class1();
- var source = new BehaviorSubject<BindingValue<string>>("bar");
- target.SetValue(Class1.FooProperty, "foo");
- var sub = target.Bind(Class1.FooProperty, source);
- Assert.Equal("bar", target.GetValue(Class1.FooProperty));
- sub.Dispose();
- Assert.Equal("foodefault", target.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void LocalValue_Binding_Should_Override_Style_Binding()
- {
- var target = new Class1();
- var source1 = new BehaviorSubject<BindingValue<string>>("foo");
- var source2 = new BehaviorSubject<BindingValue<string>>("bar");
- target.Bind(Class1.FooProperty, source1, BindingPriority.Style);
- Assert.Equal("foo", target.GetValue(Class1.FooProperty));
- target.Bind(Class1.FooProperty, source2, BindingPriority.LocalValue);
- Assert.Equal("bar", target.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void Style_Binding_Should_NotOverride_LocalValue_Binding()
- {
- var target = new Class1();
- var source1 = new BehaviorSubject<BindingValue<string>>("foo");
- var source2 = new BehaviorSubject<BindingValue<string>>("bar");
- target.Bind(Class1.FooProperty, source1, BindingPriority.LocalValue);
- Assert.Equal("foo", target.GetValue(Class1.FooProperty));
- target.Bind(Class1.FooProperty, source2, BindingPriority.Style);
- Assert.Equal("foo", target.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void Completing_Animation_Binding_Reverts_To_Set_LocalValue()
- {
- var target = new Class1();
- var source = new Subject<BindingValue<string>>();
- var property = Class1.FooProperty;
- target.SetValue(property, "foo");
- target.Bind(property, source, BindingPriority.Animation);
- source.OnNext("bar");
- source.OnCompleted();
- Assert.Equal("foo", target.GetValue(property));
- }
- [Fact]
- public void Completing_LocalValue_Binding_Raises_PropertyChanged()
- {
- var target = new Class1();
- var source = new BehaviorSubject<BindingValue<string>>("foo");
- var property = Class1.FooProperty;
- var raised = 0;
- target.Bind(property, source);
- Assert.Equal("foo", target.GetValue(property));
- target.PropertyChanged += (s, e) =>
- {
- Assert.Equal(BindingPriority.Unset, e.Priority);
- Assert.Equal(property, e.Property);
- Assert.Equal("foo", e.OldValue as string);
- Assert.Equal("foodefault", e.NewValue as string);
- ++raised;
- };
- source.OnCompleted();
- Assert.Equal("foodefault", target.GetValue(property));
- Assert.Equal(1, raised);
- }
- [Fact]
- public void Completing_Style_Binding_Raises_PropertyChanged()
- {
- var target = new Class1();
- var source = new BehaviorSubject<BindingValue<string>>("foo");
- var property = Class1.FooProperty;
- var raised = 0;
- target.Bind(property, source, BindingPriority.Style);
- Assert.Equal("foo", target.GetValue(property));
- target.PropertyChanged += (s, e) =>
- {
- Assert.Equal(BindingPriority.Unset, e.Priority);
- Assert.Equal(property, e.Property);
- Assert.Equal("foo", e.OldValue as string);
- Assert.Equal("foodefault", e.NewValue as string);
- ++raised;
- };
- source.OnCompleted();
- Assert.Equal("foodefault", target.GetValue(property));
- Assert.Equal(1, raised);
- }
- [Fact]
- public void Completing_LocalValue_Binding_With_Style_Binding_Raises_PropertyChanged()
- {
- var target = new Class1();
- var source = new BehaviorSubject<BindingValue<string>>("foo");
- var property = Class1.FooProperty;
- var raised = 0;
- target.Bind(property, new BehaviorSubject<BindingValue<string>>("bar"), BindingPriority.Style);
- target.Bind(property, source);
- Assert.Equal("foo", target.GetValue(property));
- target.PropertyChanged += (s, e) =>
- {
- Assert.Equal(BindingPriority.Style, e.Priority);
- Assert.Equal(property, e.Property);
- Assert.Equal("foo", e.OldValue as string);
- Assert.Equal("bar", e.NewValue as string);
- ++raised;
- };
- source.OnCompleted();
- Assert.Equal("bar", target.GetValue(property));
- Assert.Equal(1, raised);
- }
- [Fact]
- public void Disposing_LocalValue_Binding_Raises_PropertyChanged()
- {
- var target = new Class1();
- var source = new BehaviorSubject<BindingValue<string>>("foo");
- var property = Class1.FooProperty;
- var raised = 0;
- var sub = target.Bind(property, source);
- Assert.Equal("foo", target.GetValue(property));
- target.PropertyChanged += (s, e) =>
- {
- Assert.Equal(BindingPriority.Unset, e.Priority);
- Assert.Equal(property, e.Property);
- Assert.Equal("foo", e.OldValue as string);
- Assert.Equal("foodefault", e.NewValue as string);
- ++raised;
- };
- sub.Dispose();
- Assert.Equal("foodefault", target.GetValue(property));
- Assert.Equal(1, raised);
- }
- [Fact]
- public void Setting_Style_Value_Overrides_Binding_Permanently()
- {
- var target = new Class1();
- var source = new Subject<string>();
- target.Bind(Class1.FooProperty, source, BindingPriority.Style);
- source.OnNext("foo");
- Assert.Equal("foo", target.GetValue(Class1.FooProperty));
- target.SetValue(Class1.FooProperty, "bar", BindingPriority.Style);
- Assert.Equal("bar", target.GetValue(Class1.FooProperty));
- source.OnNext("baz");
- Assert.Equal("bar", target.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void Second_LocalValue_Binding_Unsubscribes_First()
- {
- var property = Class1.FooProperty;
- var target = new Class1();
- var source1 = new Subject<BindingValue<string>>();
- var source2 = new Subject<BindingValue<string>>();
- target.Bind(property, source1, BindingPriority.LocalValue);
- target.Bind(property, source2, BindingPriority.LocalValue);
- source1.OnNext("foo");
- Assert.Equal("foodefault", target.GetValue(property));
- source2.OnNext("bar");
- Assert.Equal("bar", target.GetValue(property));
- source1.OnNext("baz");
- Assert.Equal("bar", target.GetValue(property));
- }
- [Fact]
- public void Completing_Second_LocalValue_Binding_Doesnt_Revert_To_First()
- {
- var property = Class1.FooProperty;
- var target = new Class1();
- var source1 = new Subject<BindingValue<string>>();
- var source2 = new Subject<BindingValue<string>>();
- target.Bind(property, source1, BindingPriority.LocalValue);
- target.Bind(property, source2, BindingPriority.LocalValue);
- source1.OnNext("foo");
- source2.OnNext("bar");
- source1.OnNext("baz");
- source2.OnCompleted();
- Assert.Equal("foodefault", target.GetValue(property));
- }
- [Fact]
- public void Completing_StyleTrigger_Binding_Reverts_To_StyleBinding()
- {
- var property = Class1.FooProperty;
- var target = new Class1();
- var source1 = new Subject<BindingValue<string>>();
- var source2 = new Subject<BindingValue<string>>();
- target.Bind(property, source1, BindingPriority.Style);
- target.Bind(property, source2, BindingPriority.StyleTrigger);
- source1.OnNext("foo");
- source2.OnNext("bar");
- source2.OnCompleted();
- source1.OnNext("baz");
- Assert.Equal("baz", target.GetValue(property));
- }
- [Fact]
- public void Bind_NonGeneric_Sets_Current_Value()
- {
- Class1 target = new Class1();
- Class1 source = new Class1();
- source.SetValue(Class1.FooProperty, "initial");
- target.Bind((AvaloniaProperty)Class1.FooProperty, source.GetObservable(Class1.FooProperty));
- Assert.Equal("initial", target.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void Bind_NonGeneric_Can_Set_Null_On_Reference_Type()
- {
- var target = new Class1();
- var source = new BehaviorSubject<object?>(null);
- var property = Class1.FooProperty;
- target.Bind(property, source);
- Assert.Null(target.GetValue(property));
- }
- [Fact]
- public void LocalValue_Bind_NonGeneric_To_ValueType_Accepts_UnsetValue()
- {
- var target = new Class1();
- var source = new Subject<object>();
- target.Bind(Class1.QuxProperty, source);
- source.OnNext(6.7);
- source.OnNext(AvaloniaProperty.UnsetValue);
- Assert.Equal(5.6, target.GetValue(Class1.QuxProperty));
- Assert.False(target.IsSet(Class1.QuxProperty));
- }
- [Fact]
- public void Style_Bind_NonGeneric_To_ValueType_Accepts_UnsetValue()
- {
- var target = new Class1();
- var source = new Subject<object>();
- target.Bind(Class1.QuxProperty, source, BindingPriority.Style);
- source.OnNext(6.7);
- source.OnNext(AvaloniaProperty.UnsetValue);
- Assert.Equal(5.6, target.GetValue(Class1.QuxProperty));
- Assert.False(target.IsSet(Class1.QuxProperty));
- }
- [Fact]
- public void LocalValue_Bind_NonGeneric_To_ValueType_Accepts_DoNothing()
- {
- var target = new Class1();
- var source = new Subject<object>();
- target.Bind(Class1.QuxProperty, source);
- source.OnNext(6.7);
- source.OnNext(BindingOperations.DoNothing);
- Assert.Equal(6.7, target.GetValue(Class1.QuxProperty));
- }
- [Fact]
- public void Style_Bind_NonGeneric_To_ValueType_Accepts_DoNothing()
- {
- var target = new Class1();
- var source = new Subject<object>();
- target.Bind(Class1.QuxProperty, source, BindingPriority.Style);
- source.OnNext(6.7);
- source.OnNext(BindingOperations.DoNothing);
- Assert.Equal(6.7, target.GetValue(Class1.QuxProperty));
- }
- [Fact]
- public void OneTime_Binding_Ignores_UnsetValue()
- {
- var target = new Class1();
- var source = new Subject<object>();
- target.Bind(Class1.QuxProperty, new TestOneTimeBinding(source));
- source.OnNext(AvaloniaProperty.UnsetValue);
- Assert.Equal(5.6, target.GetValue(Class1.QuxProperty));
- source.OnNext(6.7);
- Assert.Equal(6.7, target.GetValue(Class1.QuxProperty));
- }
- [Fact]
- public void OneTime_Binding_Ignores_Binding_Errors()
- {
- var target = new Class1();
- var source = new Subject<object>();
- target.Bind(Class1.QuxProperty, new TestOneTimeBinding(source));
- source.OnNext(new BindingNotification(new Exception(), BindingErrorType.Error));
- Assert.Equal(5.6, target.GetValue(Class1.QuxProperty));
- source.OnNext(6.7);
- Assert.Equal(6.7, target.GetValue(Class1.QuxProperty));
- }
- [Fact]
- public void Bind_Does_Not_Throw_Exception_For_Unregistered_Property()
- {
- Class1 target = new Class1();
- target.Bind(Class2.BarProperty, Observable.Never<BindingValue<string>>().StartWith("foo"));
- Assert.Equal("foo", target.GetValue(Class2.BarProperty));
- }
- [Fact]
- public void Bind_Sets_Subsequent_Value()
- {
- Class1 target = new Class1();
- Class1 source = new Class1();
- source.SetValue(Class1.FooProperty, "initial");
- target.Bind(Class1.FooProperty, source.GetObservable(Class1.FooProperty));
- source.SetValue(Class1.FooProperty, "subsequent");
- Assert.Equal("subsequent", target.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void Bind_Ignores_Invalid_Value_Type()
- {
- Class1 target = new Class1();
- target.Bind((AvaloniaProperty)Class1.FooProperty, Observable.Return((object)123));
- Assert.Equal("foodefault", target.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void Observable_Is_Unsubscribed_When_Subscription_Disposed()
- {
- var scheduler = new TestScheduler();
- var source = scheduler.CreateColdObservable<BindingValue<string>>();
- var target = new Class1();
- var subscription = target.Bind(Class1.FooProperty, source);
- Assert.Equal(1, source.Subscriptions.Count);
- Assert.Equal(Subscription.Infinite, source.Subscriptions[0].Unsubscribe);
- subscription.Dispose();
- Assert.Equal(1, source.Subscriptions.Count);
- Assert.Equal(0, source.Subscriptions[0].Unsubscribe);
- }
- [Fact]
- public void Two_Way_Separate_Binding_Works()
- {
- Class1 obj1 = new Class1();
- Class1 obj2 = new Class1();
- obj1.SetValue(Class1.FooProperty, "initial1");
- obj2.SetValue(Class1.FooProperty, "initial2");
- obj1.Bind(Class1.FooProperty, obj2.GetObservable(Class1.FooProperty));
- obj2.Bind(Class1.FooProperty, obj1.GetObservable(Class1.FooProperty));
- Assert.Equal("initial2", obj1.GetValue(Class1.FooProperty));
- Assert.Equal("initial2", obj2.GetValue(Class1.FooProperty));
- obj1.SetValue(Class1.FooProperty, "first");
- Assert.Equal("first", obj1.GetValue(Class1.FooProperty));
- Assert.Equal("first", obj2.GetValue(Class1.FooProperty));
- obj2.SetValue(Class1.FooProperty, "second");
- Assert.Equal("second", obj1.GetValue(Class1.FooProperty));
- Assert.Equal("second", obj2.GetValue(Class1.FooProperty));
- obj1.SetValue(Class1.FooProperty, "third");
- Assert.Equal("third", obj1.GetValue(Class1.FooProperty));
- Assert.Equal("third", obj2.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void Two_Way_Binding_With_Priority_Works()
- {
- Class1 obj1 = new Class1();
- Class1 obj2 = new Class1();
- obj1.SetValue(Class1.FooProperty, "initial1", BindingPriority.Style);
- obj2.SetValue(Class1.FooProperty, "initial2", BindingPriority.Style);
- obj1.Bind(Class1.FooProperty, obj2.GetObservable(Class1.FooProperty), BindingPriority.Style);
- obj2.Bind(Class1.FooProperty, obj1.GetObservable(Class1.FooProperty), BindingPriority.Style);
- Assert.Equal("initial2", obj1.GetValue(Class1.FooProperty));
- Assert.Equal("initial2", obj2.GetValue(Class1.FooProperty));
- obj1.SetValue(Class1.FooProperty, "first", BindingPriority.Style);
- Assert.Equal("first", obj1.GetValue(Class1.FooProperty));
- Assert.Equal("first", obj2.GetValue(Class1.FooProperty));
- obj2.SetValue(Class1.FooProperty, "second", BindingPriority.Style);
- Assert.Equal("first", obj1.GetValue(Class1.FooProperty));
- Assert.Equal("second", obj2.GetValue(Class1.FooProperty));
- obj1.SetValue(Class1.FooProperty, "third", BindingPriority.Style);
- Assert.Equal("third", obj1.GetValue(Class1.FooProperty));
- Assert.Equal("second", obj2.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void Local_Binding_Overwrites_Local_Value()
- {
- var target = new Class1();
- var binding = new Subject<BindingValue<string>>();
- target.Bind(Class1.FooProperty, binding);
- binding.OnNext("first");
- Assert.Equal("first", target.GetValue(Class1.FooProperty));
- target.SetValue(Class1.FooProperty, "second");
- Assert.Equal("second", target.GetValue(Class1.FooProperty));
- binding.OnNext("third");
- Assert.Equal("third", target.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void StyleBinding_Overrides_Default_Value()
- {
- Class1 target = new Class1();
- target.Bind(Class1.FooProperty, Single("stylevalue"), BindingPriority.Style);
- Assert.Equal("stylevalue", target.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void this_Operator_Returns_Value_Property()
- {
- Class1 target = new Class1();
- target.SetValue(Class1.FooProperty, "newvalue");
- Assert.Equal("newvalue", target[Class1.FooProperty]);
- }
- [Fact]
- public void this_Operator_Sets_Value_Property()
- {
- Class1 target = new Class1();
- target[Class1.FooProperty] = "newvalue";
- Assert.Equal("newvalue", target.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void this_Operator_Doesnt_Accept_Observable()
- {
- Class1 target = new Class1();
- Assert.Throws<ArgumentException>(() =>
- {
- target[Class1.FooProperty] = Observable.Return("newvalue");
- });
- }
- [Fact]
- public void this_Operator_Binds_One_Way()
- {
- Class1 target1 = new Class1();
- Class2 target2 = new Class2();
- IndexerDescriptor binding = Class2.BarProperty.Bind().WithMode(BindingMode.OneWay);
- target1.SetValue(Class1.FooProperty, "first");
- target2[binding] = target1[!Class1.FooProperty];
- target1.SetValue(Class1.FooProperty, "second");
- Assert.Equal("second", target2.GetValue(Class2.BarProperty));
- }
- [Fact]
- public void this_Operator_Binds_Two_Way()
- {
- Class1 target1 = new Class1();
- Class1 target2 = new Class1();
- target1.SetValue(Class1.FooProperty, "first");
- target2[!Class1.FooProperty] = target1[!!Class1.FooProperty];
- Assert.Equal("first", target2.GetValue(Class1.FooProperty));
- target1.SetValue(Class1.FooProperty, "second");
- Assert.Equal("second", target2.GetValue(Class1.FooProperty));
- target2.SetValue(Class1.FooProperty, "third");
- Assert.Equal("third", target1.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void this_Operator_Binds_One_Time()
- {
- Class1 target1 = new Class1();
- Class1 target2 = new Class1();
- target1.SetValue(Class1.FooProperty, "first");
- target2[!Class1.FooProperty] = target1[Class1.FooProperty.Bind().WithMode(BindingMode.OneTime)];
- target1.SetValue(Class1.FooProperty, "second");
- Assert.Equal("first", target2.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void Binding_Error_Reverts_To_Default_Value()
- {
- var target = new Class1();
- var source = new Subject<BindingValue<string>>();
- target.Bind(Class1.FooProperty, source);
- source.OnNext("initial");
- source.OnNext(BindingValue<string>.BindingError(new InvalidOperationException("Foo")));
- Assert.Equal("foodefault", target.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void Binding_Error_With_FallbackValue_Causes_Target_Update()
- {
- var target = new Class1();
- var source = new Subject<BindingValue<string>>();
- target.Bind(Class1.FooProperty, source);
- source.OnNext("initial");
- source.OnNext(BindingValue<string>.BindingError(new InvalidOperationException("Foo"), "bar"));
- Assert.Equal("bar", target.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void DataValidationError_Does_Not_Cause_Target_Update()
- {
- var target = new Class1();
- var source = new Subject<BindingValue<string>>();
- target.Bind(Class1.FooProperty, source);
- source.OnNext("initial");
- source.OnNext(BindingValue<string>.DataValidationError(new InvalidOperationException("Foo")));
- Assert.Equal("initial", target.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void DataValidationError_With_FallbackValue_Causes_Target_Update()
- {
- var target = new Class1();
- var source = new Subject<BindingValue<string>>();
- target.Bind(Class1.FooProperty, source);
- source.OnNext("initial");
- source.OnNext(BindingValue<string>.DataValidationError(new InvalidOperationException("Foo"), "bar"));
- Assert.Equal("bar", target.GetValue(Class1.FooProperty));
- }
- [Fact]
- public void Bind_Logs_Binding_Error()
- {
- var target = new Class1();
- var source = new Subject<BindingValue<double>>();
- var called = false;
- var expectedMessageTemplate = "Error in binding to {Target}.{Property}: {Message}";
- LogCallback checkLogMessage = (level, area, src, mt, pv) =>
- {
- if (level == LogEventLevel.Warning &&
- area == LogArea.Binding &&
- mt == expectedMessageTemplate)
- {
- called = true;
- }
- };
- using (TestLogSink.Start(checkLogMessage))
- {
- target.Bind(Class1.QuxProperty, source);
- source.OnNext(6.7);
- source.OnNext(BindingValue<double>.BindingError(new InvalidOperationException("Foo")));
- Assert.Equal(5.6, target.GetValue(Class1.QuxProperty));
- Assert.True(called);
- }
- }
- [Fact]
- public void Untyped_LocalValue_Binding_Logs_Invalid_Value_Type()
- {
- var target = new Class1();
- var source = new Subject<object?>();
- var called = false;
- var expectedMessageTemplate = "Error in binding to {Target}.{Property}: expected {ExpectedType}, got {Value} ({ValueType})";
- LogCallback checkLogMessage = (level, area, src, mt, pv) =>
- {
- if (level == LogEventLevel.Warning &&
- area == LogArea.Binding &&
- mt == expectedMessageTemplate &&
- src == target &&
- pv[0].GetType() == typeof(Class1) &&
- (AvaloniaProperty)pv[1] == Class1.QuxProperty &&
- (Type)pv[2] == typeof(double) &&
- (string)pv[3] == "foo" &&
- (Type)pv[4] == typeof(string))
- {
- called = true;
- }
- };
- using (TestLogSink.Start(checkLogMessage))
- {
- target.Bind(Class1.QuxProperty, source);
- source.OnNext(1.2);
- source.OnNext("foo");
- Assert.Equal(5.6, target.GetValue(Class1.QuxProperty));
- Assert.True(called);
- }
- }
- [Fact]
- public void Untyped_Style_Binding_Logs_Invalid_Value_Type()
- {
- var target = new Class1();
- var source = new Subject<object?>();
- var called = false;
- var expectedMessageTemplate = "Error in binding to {Target}.{Property}: expected {ExpectedType}, got {Value} ({ValueType})";
- LogCallback checkLogMessage = (level, area, src, mt, pv) =>
- {
- if (level == LogEventLevel.Warning &&
- area == LogArea.Binding &&
- mt == expectedMessageTemplate &&
- src == target &&
- pv[0].GetType() == typeof(Class1) &&
- (AvaloniaProperty)pv[1] == Class1.QuxProperty &&
- (Type)pv[2] == typeof(double) &&
- (string)pv[3] == "foo" &&
- (Type)pv[4] == typeof(string))
- {
- called = true;
- }
- };
- using (TestLogSink.Start(checkLogMessage))
- {
- target.Bind(Class1.QuxProperty, source, BindingPriority.Style);
- source.OnNext(1.2);
- source.OnNext("foo");
- Assert.Equal(5.6, target.GetValue(Class1.QuxProperty));
- Assert.True(called);
- }
- }
- [Fact]
- public async Task Bind_With_Scheduler_Executes_On_Scheduler()
- {
- var target = new Class1();
- var source = new Subject<double>();
- var currentThreadId = Thread.CurrentThread.ManagedThreadId;
- var threadingInterfaceMock = new Mock<IPlatformThreadingInterface>();
- threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread)
- .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId);
- var services = new TestServices(
- scheduler: AvaloniaScheduler.Instance,
- threadingInterface: threadingInterfaceMock.Object);
- using (UnitTestApplication.Start(services))
- {
- target.Bind(Class1.QuxProperty, source);
- await Task.Run(() => source.OnNext(6.7));
- }
- }
- [Fact]
- public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
- {
- var viewModel = new TestStackOverflowViewModel()
- {
- Value = 50
- };
- var target = new Class1();
- target.Bind(Class1.DoubleValueProperty,
- new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel });
- var child = new Class1();
- child[!!Class1.DoubleValueProperty] = target[!!Class1.DoubleValueProperty];
- Assert.Equal(1, viewModel.SetterInvokedCount);
- // Issues #855 and #824 were causing a StackOverflowException at this point.
- target.DoubleValue = 51.001;
- Assert.Equal(2, viewModel.SetterInvokedCount);
- double expected = 51;
- Assert.Equal(expected, viewModel.Value);
- Assert.Equal(expected, target.DoubleValue);
- Assert.Equal(expected, child.DoubleValue);
- }
- [Fact]
- public void IsAnimating_On_Property_With_No_Value_Returns_False()
- {
- var target = new Class1();
- Assert.False(target.IsAnimating(Class1.FooProperty));
- }
- [Fact]
- public void IsAnimating_On_Property_With_Animation_Value_Returns_True()
- {
- var target = new Class1();
- var source = new BehaviorSubject<BindingValue<string>>("foo");
- target.Bind(Class1.FooProperty, source, BindingPriority.Animation);
- Assert.True(target.IsAnimating(Class1.FooProperty));
- }
- [Fact]
- public void IsAnimating_On_Property_With_Non_Animation_Binding_Returns_False()
- {
- var target = new Class1();
- var source = new Subject<string>();
- target.Bind(Class1.FooProperty, source, BindingPriority.LocalValue);
- Assert.False(target.IsAnimating(Class1.FooProperty));
- }
- [Fact]
- public void IsAnimating_On_Property_With_Animation_Binding_Returns_True()
- {
- var target = new Class1();
- var source = new BehaviorSubject<string>("foo");
- target.Bind(Class1.FooProperty, source, BindingPriority.Animation);
- Assert.True(target.IsAnimating(Class1.FooProperty));
- }
- [Fact]
- public void IsAnimating_On_Property_With_Local_Value_And_Animation_Binding_Returns_True()
- {
- var target = new Class1();
- var source = new BehaviorSubject<string>("foo");
- target.SetValue(Class1.FooProperty, "bar");
- target.Bind(Class1.FooProperty, source, BindingPriority.Animation);
- Assert.True(target.IsAnimating(Class1.FooProperty));
- }
- [Fact]
- public void IsAnimating_Returns_True_When_Animated_Value_Is_Same_As_Local_Value()
- {
- var target = new Class1();
- var source = new BehaviorSubject<string>("foo");
- target.SetValue(Class1.FooProperty, "foo");
- target.Bind(Class1.FooProperty, source, BindingPriority.Animation);
- Assert.True(target.IsAnimating(Class1.FooProperty));
- }
- [Fact]
- public void TwoWay_Binding_Should_Not_Call_Setter_On_Creation()
- {
- var target = new Class1();
- var source = new TestTwoWayBindingViewModel();
- target.Bind(Class1.DoubleValueProperty, new Binding(nameof(source.Value), BindingMode.TwoWay) { Source = source });
- Assert.False(source.SetterCalled);
- }
- [Fact]
- public void TwoWay_Binding_Should_Not_Call_Setter_On_Creation_Indexer()
- {
- var target = new Class1();
- var source = new TestTwoWayBindingViewModel();
- target.Bind(Class1.DoubleValueProperty, new Binding("[0]", BindingMode.TwoWay) { Source = source });
- Assert.False(source.SetterCalled);
- }
- [Fact]
- public void TwoWay_Binding_Should_Not_Fail_With_Null_DataContext()
- {
- var target = new TextBlock();
- target.DataContext = null;
- target.Bind(TextBlock.TextProperty, new Binding("Missing", BindingMode.TwoWay));
- }
- [Fact]
- public void TwoWay_Binding_Should_Not_Fail_With_Null_DataContext_Indexer()
- {
- var target = new TextBlock();
- target.DataContext = null;
- target.Bind(TextBlock.TextProperty, new Binding("[0]", BindingMode.TwoWay));
- }
- [Fact]
- public void Disposing_Completed_Binding_Does_Not_Throw()
- {
- var target = new Class1();
- var source = new Subject<BindingValue<string>>();
- var subscription = target.Bind(Class1.FooProperty, source);
- source.OnCompleted();
- subscription.Dispose();
- }
- /// <summary>
- /// Returns an observable that returns a single value but does not complete.
- /// </summary>
- /// <typeparam name="T">The type of the observable.</typeparam>
- /// <param name="value">The value.</param>
- /// <returns>The observable.</returns>
- private IObservable<BindingValue<T>> Single<T>(T value)
- {
- return Observable.Never<BindingValue<T>>().StartWith(value);
- }
- private class Class1 : AvaloniaObject
- {
- public static readonly StyledProperty<string> FooProperty =
- AvaloniaProperty.Register<Class1, string>("Foo", "foodefault");
- public static readonly StyledProperty<double> QuxProperty =
- AvaloniaProperty.Register<Class1, double>("Qux", 5.6);
- public static readonly StyledProperty<double> DoubleValueProperty =
- AvaloniaProperty.Register<Class1, double>(nameof(DoubleValue));
- public double DoubleValue
- {
- get { return GetValue(DoubleValueProperty); }
- set { SetValue(DoubleValueProperty, value); }
- }
- }
- private class Class2 : Class1
- {
- public static readonly StyledProperty<string> BarProperty =
- AvaloniaProperty.Register<Class2, string>("Bar", "bardefault");
- }
- private class TestOneTimeBinding : IBinding
- {
- private IObservable<object> _source;
- public TestOneTimeBinding(IObservable<object> source)
- {
- _source = source;
- }
- public InstancedBinding Initiate(
- IAvaloniaObject target,
- AvaloniaProperty? targetProperty,
- object? anchor = null,
- bool enableDataValidation = false)
- {
- return InstancedBinding.OneTime(_source);
- }
- }
- private class TestStackOverflowViewModel : INotifyPropertyChanged
- {
- public int SetterInvokedCount { get; private set; }
- public const int MaxInvokedCount = 1000;
- private double _value;
- public event PropertyChangedEventHandler? PropertyChanged;
- public double Value
- {
- get { return _value; }
- set
- {
- if (_value != value)
- {
- SetterInvokedCount++;
- if (SetterInvokedCount < MaxInvokedCount)
- {
- _value = (int)value;
- if (_value > 75)
- _value = 75;
- if (_value < 25)
- _value = 25;
- }
- else
- {
- _value = value;
- }
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
- }
- }
- }
- }
- private class TestTwoWayBindingViewModel
- {
- private double _value;
- public double Value
- {
- get => _value;
- set
- {
- _value = value;
- SetterCalled = true;
- }
- }
- public double this[int index]
- {
- get => _value;
- set
- {
- _value = value;
- SetterCalled = true;
- }
- }
- public bool SetterCalled { get; private set; }
- }
- }
- }
|