// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Linq; using System.Reactive.Linq; using System.Reactive.Subjects; using Moq; using Xunit; namespace Avalonia.Base.UnitTests { public class PriorityValueTests { private static readonly AvaloniaProperty TestProperty = new StyledProperty( "Test", typeof(PriorityValueTests), new StyledPropertyMetadata()); [Fact] public void Initial_Value_Should_Be_UnsetValue() { var target = new PriorityValue(null, TestProperty, typeof(string)); Assert.Same(AvaloniaProperty.UnsetValue, target.Value); } [Fact] public void First_Binding_Sets_Value() { var target = new PriorityValue(null, TestProperty, typeof(string)); target.Add(Single("foo"), 0); Assert.Equal("foo", target.Value); } [Fact] public void Changing_Binding_Should_Set_Value() { var target = new PriorityValue(null, TestProperty, typeof(string)); var subject = new BehaviorSubject("foo"); target.Add(subject, 0); Assert.Equal("foo", target.Value); subject.OnNext("bar"); Assert.Equal("bar", target.Value); } [Fact] public void Setting_Direct_Value_Should_Override_Binding() { var target = new PriorityValue(null, TestProperty, typeof(string)); target.Add(Single("foo"), 0); target.SetValue("bar", 0); Assert.Equal("bar", target.Value); } [Fact] public void Binding_Firing_Should_Override_Direct_Value() { var target = new PriorityValue(null, TestProperty, typeof(string)); var source = new BehaviorSubject("initial"); target.Add(source, 0); Assert.Equal("initial", target.Value); target.SetValue("first", 0); Assert.Equal("first", target.Value); source.OnNext("second"); Assert.Equal("second", target.Value); } [Fact] public void Earlier_Binding_Firing_Should_Not_Override_Later() { var target = new PriorityValue(null, TestProperty, typeof(string)); var nonActive = new BehaviorSubject("na"); var source = new BehaviorSubject("initial"); target.Add(nonActive, 1); target.Add(source, 1); Assert.Equal("initial", target.Value); target.SetValue("first", 1); Assert.Equal("first", target.Value); nonActive.OnNext("second"); Assert.Equal("first", target.Value); } [Fact] public void Binding_Completing_Should_Revert_To_Direct_Value() { var target = new PriorityValue(null, TestProperty, typeof(string)); var source = new BehaviorSubject("initial"); target.Add(source, 0); Assert.Equal("initial", target.Value); target.SetValue("first", 0); Assert.Equal("first", target.Value); source.OnNext("second"); Assert.Equal("second", target.Value); source.OnCompleted(); Assert.Equal("first", target.Value); } [Fact] public void Binding_With_Lower_Priority_Has_Precedence() { var target = new PriorityValue(null, TestProperty, typeof(string)); target.Add(Single("foo"), 1); target.Add(Single("bar"), 0); target.Add(Single("baz"), 1); Assert.Equal("bar", target.Value); } [Fact] public void Later_Binding_With_Same_Priority_Should_Take_Precedence() { var target = new PriorityValue(null, TestProperty, typeof(string)); target.Add(Single("foo"), 1); target.Add(Single("bar"), 0); target.Add(Single("baz"), 0); target.Add(Single("qux"), 1); Assert.Equal("baz", target.Value); } [Fact] public void Changing_Binding_With_Lower_Priority_Should_Set_Not_Value() { var target = new PriorityValue(null, TestProperty, typeof(string)); var subject = new BehaviorSubject("bar"); target.Add(Single("foo"), 0); target.Add(subject, 1); Assert.Equal("foo", target.Value); subject.OnNext("baz"); Assert.Equal("foo", target.Value); } [Fact] public void UnsetValue_Should_Fall_Back_To_Next_Binding() { var target = new PriorityValue(null, TestProperty, typeof(string)); var subject = new BehaviorSubject("bar"); target.Add(subject, 0); target.Add(Single("foo"), 1); Assert.Equal("bar", target.Value); subject.OnNext(AvaloniaProperty.UnsetValue); Assert.Equal("foo", target.Value); } [Fact] public void Adding_Value_Should_Call_OnNext() { var owner = new Mock(); var target = new PriorityValue(owner.Object, TestProperty, typeof(string)); target.Add(Single("foo"), 0); owner.Verify(x => x.Changed(target.Property, target.ValuePriority, AvaloniaProperty.UnsetValue, "foo")); } [Fact] public void Changing_Value_Should_Call_OnNext() { var owner = new Mock(); var target = new PriorityValue(owner.Object, TestProperty, typeof(string)); var subject = new BehaviorSubject("foo"); target.Add(subject, 0); subject.OnNext("bar"); owner.Verify(x => x.Changed(target.Property, target.ValuePriority, "foo", "bar")); } [Fact] public void Disposing_A_Binding_Should_Revert_To_Next_Value() { var target = new PriorityValue(null, TestProperty, typeof(string)); target.Add(Single("foo"), 0); var disposable = target.Add(Single("bar"), 0); Assert.Equal("bar", target.Value); disposable.Dispose(); Assert.Equal("foo", target.Value); } [Fact] public void Disposing_A_Binding_Should_Remove_BindingEntry() { var target = new PriorityValue(null, TestProperty, typeof(string)); target.Add(Single("foo"), 0); var disposable = target.Add(Single("bar"), 0); Assert.Equal(2, target.GetBindings().Count()); disposable.Dispose(); Assert.Single(target.GetBindings()); } [Fact] public void Completing_A_Binding_Should_Revert_To_Previous_Binding() { var target = new PriorityValue(null, TestProperty, typeof(string)); var source = new BehaviorSubject("bar"); target.Add(Single("foo"), 0); target.Add(source, 0); Assert.Equal("bar", target.Value); source.OnCompleted(); Assert.Equal("foo", target.Value); } [Fact] public void Completing_A_Binding_Should_Revert_To_Lower_Priority() { var target = new PriorityValue(null, TestProperty, typeof(string)); var source = new BehaviorSubject("bar"); target.Add(Single("foo"), 1); target.Add(source, 0); Assert.Equal("bar", target.Value); source.OnCompleted(); Assert.Equal("foo", target.Value); } [Fact] public void Completing_A_Binding_Should_Remove_BindingEntry() { var target = new PriorityValue(null, TestProperty, typeof(string)); var subject = new BehaviorSubject("bar"); target.Add(Single("foo"), 0); target.Add(subject, 0); Assert.Equal(2, target.GetBindings().Count()); subject.OnCompleted(); Assert.Single(target.GetBindings()); } [Fact] public void Direct_Value_Should_Be_Coerced() { var target = new PriorityValue(null, TestProperty, typeof(int), x => Math.Min((int)x, 10)); target.SetValue(5, 0); Assert.Equal(5, target.Value); target.SetValue(15, 0); Assert.Equal(10, target.Value); } [Fact] public void Bound_Value_Should_Be_Coerced() { var target = new PriorityValue(null, TestProperty, typeof(int), x => Math.Min((int)x, 10)); var source = new Subject(); target.Add(source, 0); source.OnNext(5); Assert.Equal(5, target.Value); source.OnNext(15); Assert.Equal(10, target.Value); } [Fact] public void Revalidate_Should_ReCoerce_Value() { var max = 10; var target = new PriorityValue(null, TestProperty, typeof(int), x => Math.Min((int)x, max)); var source = new Subject(); target.Add(source, 0); source.OnNext(5); Assert.Equal(5, target.Value); source.OnNext(15); Assert.Equal(10, target.Value); max = 12; target.Revalidate(); Assert.Equal(12, target.Value); } /// /// Returns an observable that returns a single value but does not complete. /// /// The type of the observable. /// The value. /// The observable. private IObservable Single(T value) { return Observable.Never().StartWith(value); } } }