// ----------------------------------------------------------------------- // // Copyright 2013 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex.Base.UnitTests { using System; using System.Linq; using System.Reactive.Linq; using System.Reactive.Subjects; using Xunit; public class PerspexObjectTests { public PerspexObjectTests() { // Ensure properties are registered. PerspexProperty p; p = Class1.FooProperty; p = Class2.BarProperty; } [Fact] public void GetProperties_Returns_Registered_Properties() { string[] names = PerspexObject.GetProperties(typeof(Class1)).Select(x => x.Name).ToArray(); Assert.Equal(new[] { "Foo", "Baz", "Qux" }, names); } [Fact] public void GetProperties_Returns_Registered_Properties_For_Base_Types() { string[] names = PerspexObject.GetProperties(typeof(Class2)).Select(x => x.Name).ToArray(); Assert.Equal(new[] { "Bar", "Flob", "Foo", "Baz", "Qux" }, names); } [Fact] public void GetValue_Returns_Default_Value() { Class1 target = new Class1(); Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); } [Fact] public void GetValue_Returns_Overridden_Default_Value() { Class2 target = new Class2(); Assert.Equal("foooverride", target.GetValue(Class1.FooProperty)); } [Fact] public void GetValue_Returns_Set_Value() { Class1 target = new Class1(); target.SetValue(Class1.FooProperty, "newvalue"); Assert.Equal("newvalue", target.GetValue(Class1.FooProperty)); } [Fact] public void GetValue_Returns_Inherited_Value() { Class1 parent = new Class1(); Class2 child = new Class2 { Parent = parent }; parent.SetValue(Class1.BazProperty, "changed"); Assert.Equal("changed", child.GetValue(Class1.BazProperty)); } [Fact] public void ClearValue_Clears_Value() { Class1 target = new Class1(); target.SetValue(Class1.FooProperty, "newvalue"); target.ClearValue(Class1.FooProperty); Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); } [Fact] public void SetValue_Sets_Value() { Class1 target = new Class1(); target.SetValue(Class1.FooProperty, "newvalue"); Assert.Equal("newvalue", target.GetValue(Class1.FooProperty)); } [Fact] public void SetValue_Raises_PropertyChanged() { Class1 target = new Class1(); bool raised = false; target.PropertyChanged += (s, e) => { raised = s == target && e.Property == Class1.FooProperty && (string)e.OldValue == "foodefault" && (string)e.NewValue == "newvalue"; }; target.SetValue(Class1.FooProperty, "newvalue"); Assert.True(raised); } [Fact] public void SetValue_Doesnt_Raise_PropertyChanged_If_Value_Not_Changed() { Class1 target = new Class1(); bool raised = false; target.SetValue(Class1.FooProperty, "bar"); target.PropertyChanged += (s, e) => { raised = true; }; target.SetValue(Class1.FooProperty, "bar"); Assert.False(raised); } [Fact] public void SetValue_Doesnt_Raise_PropertyChanged_If_Value_Not_Changed_From_Default() { Class1 target = new Class1(); bool raised = false; target.PropertyChanged += (s, e) => { raised = true; }; target.SetValue(Class1.FooProperty, "foodefault"); Assert.False(raised); } [Fact] public void SetValue_Throws_Exception_For_Unregistered_Property() { Class1 target = new Class1(); Assert.Throws(() => { target.SetValue(Class2.BarProperty, "invalid"); }); } [Fact] public void SetValue_Throws_Exception_For_Invalid_Value_Type() { Class1 target = new Class1(); Assert.Throws(() => { target.SetValue(Class1.FooProperty, 123); }); } [Fact] public void SetValue_Of_Integer_On_Double_Property_Works() { Class2 target = new Class2(); target.SetValue(Class2.FlobProperty, 4); } [Fact] public void SetValue_Causes_Coercion() { Class1 target = new Class1(); target.SetValue(Class1.QuxProperty, 5); Assert.Equal(5, target.GetValue(Class1.QuxProperty)); target.SetValue(Class1.QuxProperty, -5); Assert.Equal(0, target.GetValue(Class1.QuxProperty)); target.SetValue(Class1.QuxProperty, 15); Assert.Equal(10, target.GetValue(Class1.QuxProperty)); } [Fact] public void SetValue_Respects_Priority() { Class1 target = new Class1(); target.SetValue(Class1.FooProperty, "one", BindingPriority.TemplatedParent); Assert.Equal("one", target.GetValue(Class1.FooProperty)); target.SetValue(Class1.FooProperty, "two", BindingPriority.Style); Assert.Equal("one", target.GetValue(Class1.FooProperty)); target.SetValue(Class1.FooProperty, "three", BindingPriority.StyleTrigger); Assert.Equal("three", target.GetValue(Class1.FooProperty)); } [Fact] public void CoerceValue_Causes_Recoercion() { Class1 target = new Class1(); target.SetValue(Class1.QuxProperty, 7); Assert.Equal(7, target.GetValue(Class1.QuxProperty)); target.MaxQux = 5; target.CoerceValue(Class1.QuxProperty); } [Fact] public void GetObservable_Returns_Initial_Value() { Class1 target = new Class1(); int raised = 0; target.GetObservable(Class1.FooProperty).Subscribe(x => { if (x == "foodefault") { ++raised; } }); Assert.Equal(1, raised); } [Fact] public void GetObservable_Returns_Property_Change() { Class1 target = new Class1(); bool raised = false; target.GetObservable(Class1.FooProperty).Subscribe(x => raised = x == "newvalue"); raised = false; target.SetValue(Class1.FooProperty, "newvalue"); Assert.True(raised); } [Fact] public void GetObservable_Returns_Property_Change_Only_For_Correct_Property() { Class2 target = new Class2(); bool raised = false; target.GetObservable(Class1.FooProperty).Subscribe(x => raised = true); raised = false; target.SetValue(Class2.BarProperty, "newvalue"); Assert.False(raised); } [Fact] public void GetObservable_Dispose_Stops_Property_Changes() { Class1 target = new Class1(); bool raised = false; target.GetObservable(Class1.FooProperty) .Subscribe(x => raised = true) .Dispose(); raised = false; target.SetValue(Class1.FooProperty, "newvalue"); Assert.False(raised); } [Fact] public void Setting_InheritanceParent_Raises_PropertyChanged_When_Value_Changed_In_Parent() { bool raised = false; Class1 parent = new Class1(); parent.SetValue(Class1.BazProperty, "changed"); Class2 child = new Class2(); child.PropertyChanged += (s, e) => raised = s == child && e.Property == Class1.BazProperty && (string)e.OldValue == "bazdefault" && (string)e.NewValue == "changed"; child.Parent = parent; Assert.True(raised); } [Fact] public void Setting_InheritanceParent_Doesnt_Raise_PropertyChanged_When_Local_Value_Set() { bool raised = false; Class1 parent = new Class1(); parent.SetValue(Class1.BazProperty, "changed"); Class2 child = new Class2(); child.SetValue(Class1.BazProperty, "localvalue"); child.PropertyChanged += (s, e) => raised = true; child.Parent = parent; Assert.False(raised); } [Fact] public void Setting_Value_In_InheritanceParent_Raises_PropertyChanged() { bool raised = false; Class1 parent = new Class1(); Class2 child = new Class2(); child.PropertyChanged += (s, e) => raised = s == child && e.Property == Class1.BazProperty && (string)e.OldValue == "bazdefault" && (string)e.NewValue == "changed"; child.Parent = parent; parent.SetValue(Class1.BazProperty, "changed"); Assert.True(raised); } [Fact] public void Bind_Sets_Current_Value() { Class1 target = new Class1(); Class1 source = new Class1(); source.SetValue(Class1.FooProperty, "initial"); target.Bind(Class1.FooProperty, source.GetObservable(Class1.FooProperty)); Assert.Equal("initial", target.GetValue(Class1.FooProperty)); } [Fact] public void Bind_NonGeneric_Sets_Current_Value() { Class1 target = new Class1(); Class1 source = new Class1(); source.SetValue(Class1.FooProperty, "initial"); target.Bind((PerspexProperty)Class1.FooProperty, source.GetObservable(Class1.FooProperty)); Assert.Equal("initial", target.GetValue(Class1.FooProperty)); } [Fact] public void Bind_Throws_Exception_For_Unregistered_Property() { Class1 target = new Class1(); Assert.Throws(() => { target.Bind(Class2.BarProperty, Observable.Return("foo")); }); } [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_Throws_Exception_For_Invalid_Value_Type() { Class1 target = new Class1(); Assert.Throws(() => { target.Bind((PerspexProperty)Class1.FooProperty, Observable.Return((object)123)); }); } [Fact] public void Two_Way_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("second", 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("third", obj2.GetValue(Class1.FooProperty)); } [Fact] public void BindTwoWay_Gets_Initial_Value_From_Source() { Class1 source = new Class1(); Class1 target = new Class1(); source.SetValue(Class1.FooProperty, "initial"); target.BindTwoWay(Class1.FooProperty, source, Class1.FooProperty); Assert.Equal("initial", target.GetValue(Class1.FooProperty)); } [Fact] public void BindTwoWay_Updates_Values() { Class1 source = new Class1(); Class1 target = new Class1(); source.SetValue(Class1.FooProperty, "first"); target.BindTwoWay(Class1.FooProperty, source, Class1.FooProperty); Assert.Equal("first", target.GetValue(Class1.FooProperty)); source.SetValue(Class1.FooProperty, "second"); Assert.Equal("second", target.GetValue(Class1.FooProperty)); target.SetValue(Class1.FooProperty, "third"); Assert.Equal("third", source.GetValue(Class1.FooProperty)); } [Fact] public void Setting_UnsetValue_Reverts_To_Default_Value() { Class1 target = new Class1(); target.SetValue(Class1.FooProperty, "newvalue"); target.SetValue(Class1.FooProperty, PerspexProperty.UnsetValue); Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); } [Fact] public void Local_Binding_Overwrites_Local_Value() { var target = new Class1(); var binding = new Subject(); 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, this.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(() => { target[Class1.FooProperty] = Observable.Return("newvalue"); }); } [Fact] public void this_Operator_Binds_One_Way() { Class1 target1 = new Class1(); Class1 target2 = new Class1(); Binding binding = Class1.FooProperty.Bind().WithMode(BindingMode.OneWay); target1.SetValue(Class1.FooProperty, "first"); target2[binding] = target1[!Class1.FooProperty]; target1.SetValue(Class1.FooProperty, "second"); Assert.Equal("second", target2.GetValue(Class1.FooProperty)); } [Fact] public void this_Operator_Binds_Two_Way() { Class1 target1 = new Class1(); Class1 target2 = new Class1(); Binding binding = Class1.FooProperty.Bind().WithMode(BindingMode.TwoWay); target1.SetValue(Class1.FooProperty, "first"); target2[binding] = 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(); Binding binding = Class1.FooProperty.Bind().WithMode(BindingMode.OneTime); target1.SetValue(Class1.FooProperty, "first"); target2[binding] = target1[!Class1.FooProperty]; target1.SetValue(Class1.FooProperty, "second"); Assert.Equal("first", target2.GetValue(Class1.FooProperty)); } /// /// 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); } private class Class1 : PerspexObject { public static readonly PerspexProperty FooProperty = PerspexProperty.Register("Foo", "foodefault"); public static readonly PerspexProperty BazProperty = PerspexProperty.Register("Baz", "bazdefault", true); public static readonly PerspexProperty QuxProperty = PerspexProperty.Register("Qux", coerce: Coerce); public int MaxQux { get; set; } public Class1() { this.MaxQux = 10; } private static int Coerce(PerspexObject instance, int value) { return Math.Min(Math.Max(value, 0), ((Class1)instance).MaxQux); } } private class Class2 : Class1 { public static readonly PerspexProperty BarProperty = PerspexProperty.Register("Bar", "bardefault"); public static readonly PerspexProperty FlobProperty = PerspexProperty.Register("Flob"); static Class2() { FooProperty.OverrideDefaultValue(typeof(Class2), "foooverride"); } public Class1 Parent { get { return (Class1)this.InheritanceParent; } set { this.InheritanceParent = value; } } } } }