summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs')
-rw-r--r--Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs2649
1 files changed, 2649 insertions, 0 deletions
diff --git a/Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs b/Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs
new file mode 100644
index 00000000..c520e929
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs
@@ -0,0 +1,2649 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using NUnit.Framework;
+using CategoryAttribute=NUnit.Framework.CategoryAttribute;
+using DescriptionAttribute=NUnit.Framework.DescriptionAttribute;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class BindingUnitTests
+ : BindingBaseUnitTests
+ {
+ class Logger
+ : LogListener
+ {
+ public IReadOnlyList<string> Messages
+ {
+ get { return messages; }
+ }
+
+ public override void Warning (string category, string message)
+ {
+ messages.Add ("[" + category + "] " + message);
+ }
+
+ readonly List<string> messages = new List<string>();
+ }
+
+ Logger log;
+
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup ();
+ log = new Logger();
+
+ Device.PlatformServices = new MockPlatformServices ();
+ Log.Listeners.Add (log);
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ Log.Listeners.Remove (log);
+ }
+
+ protected override BindingBase CreateBinding (BindingMode mode, string stringFormat = null)
+ {
+ return new Binding ("Text", mode, stringFormat: stringFormat);
+ }
+
+ [Test]
+ public void Ctor()
+ {
+ const string path = "Foo";
+
+ var binding = new Binding (path, BindingMode.OneWayToSource);
+ Assert.AreEqual (path, binding.Path);
+ Assert.AreEqual (BindingMode.OneWayToSource, binding.Mode);
+ }
+
+ [Test]
+ public void InvalidCtor()
+ {
+ Assert.Throws<ArgumentNullException> (() => new Binding (null),
+ "Allowed null Path");
+
+ Assert.Throws<ArgumentException> (() => new Binding (String.Empty),
+ "Allowed empty path");
+
+ Assert.Throws<ArgumentException> (() => new Binding (" "),
+ "Allowed whitespace path");
+
+ Assert.Throws<ArgumentException> (() => new Binding ("Path", (BindingMode)Int32.MaxValue),
+ "Allowed invalid value for BindingMode");
+ }
+
+ [Test]
+ [Description ("You should get an exception when trying to change a binding after it's been applied")]
+ public void ChangeBindingAfterApply()
+ {
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
+
+ var binding = new Binding { Path = "Text" };
+
+ var vm = new MockViewModel { Text = "Bar" };
+ var bo = new MockBindable { BindingContext = vm };
+ bo.SetBinding (property, binding);
+
+ Assert.That (() => binding.Path = "path", Throws.InvalidOperationException);
+ Assert.That (() => binding.Converter = null, Throws.InvalidOperationException);
+ Assert.That (() => binding.ConverterParameter = new object(), Throws.InvalidOperationException);
+ }
+
+ [Test]
+ public void NullPathIsSelf()
+ {
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
+
+ var binding = new Binding();
+
+ var bo = new MockBindable { BindingContext = "Foo" };
+ bo.SetBinding (property, binding);
+
+ Assert.That (bo.GetValue (property), Is.EqualTo ("Foo"));
+ }
+
+ class DoubleViewModel
+ : MockViewModel
+ {
+ public double Value
+ {
+ get;
+ set;
+ }
+ }
+
+ [Test]
+ public void StringFormatNonStringType()
+ {
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
+
+ var binding = new Binding ("Value", stringFormat: "{0:P2}");
+
+ var vm = new DoubleViewModel { Value = 0.95 };
+ var bo = new MockBindable { BindingContext = vm };
+ bo.SetBinding (property, binding);
+
+ if (System.Threading.Thread.CurrentThread.CurrentCulture.Name == "tr-TR")
+ Assert.That (bo.GetValue (property), Is.EqualTo ("%95,00"));
+ else
+ Assert.That (bo.GetValue (property), Is.EqualTo ("95.00 %"));
+ }
+
+ [Test]
+ public void ReuseBindingInstance()
+ {
+ var vm = new MockViewModel();
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = vm;
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null);
+ var binding = new Binding ("Text");
+ bindable.SetBinding (property, binding);
+
+ var bindable2 = new MockBindable();
+ bindable2.BindingContext = new MockViewModel();
+ Assert.Throws<InvalidOperationException> (() => bindable2.SetBinding (property, binding),
+ "Binding allowed reapplication with a different context");
+ }
+
+ class ComplexPropertyNamesViewModel
+ : MockViewModel
+ {
+ public string Foo_Bar
+ {
+ get;
+ set;
+ }
+
+ public string @if
+ {
+ get;
+ set;
+ }
+
+ /*public string P̀ः०‿
+ {
+ get;
+ set;
+ }*/
+
+ public string _UnderscoreStart
+ {
+ get;
+ set;
+ }
+ }
+
+ [Category ("[Binding] Complex paths")]
+ [TestCase ("Foo_Bar")]
+ [TestCase ("if")]
+ //TODO FIXME [TestCase ("P̀ः०‿")]
+ [TestCase ("_UnderscoreStart")]
+ public void ComplexPropertyNames (string propertyName)
+ {
+ var vm = new ComplexPropertyNamesViewModel();
+ vm.GetType().GetProperty (propertyName).SetValue (vm, "Value");
+
+ var binding = new Binding (propertyName);
+
+ var bindable = new MockBindable { BindingContext = vm };
+ bindable.SetBinding (MockBindable.TextProperty, binding);
+
+ Assert.That (bindable.Text, Is.EqualTo ("Value"));
+ }
+
+ [Test, Category ("[Binding] Simple paths")]
+ public void ValueSetOnOneWayWithSimplePathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new MockViewModel {
+ Text = value
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null, propertyDefault);
+
+ var binding = new Binding ("Text", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Text,
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Simple paths")]
+ public void ValueSetOnOneWayToSourceWithSimplePathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new MockViewModel();
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text,
+ defaultValue: value, defaultBindingMode: propertyDefault);
+
+ var binding = new Binding ("Text", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property changed");
+ Assert.AreEqual (value, viewmodel.Text,
+ "BindingContext property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Simple paths")]
+ public void ValueSetOnTwoWayWithSimplePathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new MockViewModel {
+ Text = value
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding ("Text", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Text,
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test]
+ [Category ("[Binding] Complex paths")]
+ public void ValueSetOnOneWayWithComplexPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Text = value
+ }
+ }
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null, propertyDefault);
+
+ var binding = new Binding ("Model.Model.Text", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Model.Model.Text,
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Complex paths")]
+ public void ValueSetOnOneWayToSourceWithComplexPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ }
+ }
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text,
+ defaultValue: value, defaultBindingMode: propertyDefault);
+
+ var binding = new Binding ("Model.Model.Text", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property changed");
+ Assert.AreEqual (value, viewmodel.Model.Model.Text,
+ "BindingContext property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Complex paths")]
+ public void ValueSetOnTwoWayWithComplexPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Text = value
+ }
+ }
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding ("Model.Model.Text", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Model.Model.Text,
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ class Outer {
+ public Outer (Inner inner)
+ {
+ PropertyWithPublicSetter = inner;
+ PropertyWithPrivateSetter = inner;
+ }
+
+ public Inner PropertyWithPublicSetter { get; set; }
+ public Inner PropertyWithPrivateSetter { get; private set; }
+ }
+
+ class Inner {
+ public Inner (string property)
+ {
+ GetSetProperty = property;
+ }
+
+ public string GetSetProperty { get; set; }
+ }
+
+ [Test, Category ("[Binding] Complex paths")]
+ public void BindingWithNoPublicSetterOnParent (
+ [Values (true, false)] bool setContextFirst,
+ [Values (BindingMode.OneWay, BindingMode.TwoWay)] BindingMode bindingmode,
+ [Values (true, false)] bool usePrivateSetter)
+ {
+ var value = "FooBar";
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Text, "default value", BindingMode.Default);
+ var binding = new Binding (usePrivateSetter? "PropertyWithPrivateSetter.GetSetProperty": "PropertyWithPublicSetter.GetSetProperty", bindingmode);
+ var viewmodel = new Outer (new Inner (value));
+ var bindable = new MockBindable ();
+
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.PropertyWithPublicSetter.GetSetProperty);
+ Assert.AreEqual (value, bindable.GetValue (property));
+
+ if (bindingmode == BindingMode.TwoWay) {
+ var updatedValue = "Qux";
+ bindable.SetValue (property, updatedValue);
+ Assert.AreEqual (updatedValue, bindable.GetValue (property));
+ Assert.AreEqual (updatedValue, viewmodel.PropertyWithPublicSetter.GetSetProperty);
+ }
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnOneWayWithIndexedPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+ };
+ viewmodel.Model.Model[1] = value;
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null, propertyDefault);
+
+ var binding = new Binding ("Model.Model[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Model.Model[1],
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnOneWayWithSelfIndexedPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel();
+ viewmodel[1] = value;
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null, propertyDefault);
+
+ var binding = new Binding (".[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel[1],
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnOneWayWithIndexedPathArrayBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Bar";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Array = new [] { "Foo", "Bar" }
+ }
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null, propertyDefault);
+
+ var binding = new Binding ("Model.Array[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Model.Array[1],
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnOneWayWithIndexedSelfPathArrayBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "bar";
+ string[] context = new[] { "foo", value };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null, propertyDefault);
+
+ var binding = new Binding (".[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = context;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = context;
+ }
+
+ Assert.AreEqual (value, context[1],
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnOneWayToSourceWithIndexedPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, defaultValue: value, defaultBindingMode: propertyDefault);
+
+ var binding = new Binding ("Model.Model[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property changed");
+ Assert.AreEqual (value, viewmodel.Model.Model[1],
+ "BindingContext property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnTwoWayWithIndexedPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+ };
+ viewmodel.Model.Model[1] = value;
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding ("Model.Model[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Model.Model[1],
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnTwoWayWithIndexedArrayPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Array = new string[2]
+ }
+ };
+ viewmodel.Model.Array[1] = value;
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding ("Model.Array[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Model.Array[1],
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnTwoWayWithIndexedArraySelfPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ string[] viewmodel = new [] { "bar", value };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding (".[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel[1],
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Self paths")]
+ public void ValueSetOnOneWayWithSelfPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null, propertyDefault);
+
+ var binding = new Binding (".", bindingMode);
+
+ const string value = "value";
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = value;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = value;
+ }
+
+ Assert.AreEqual (value, bindable.BindingContext,
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Self paths")]
+ public void ValueNotSetOnOneWayToSourceWithSelfPathBinding (
+ [Values (true, false)] bool isDefault)
+ {
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ const string value = "Foo";
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, defaultValue: value, defaultBindingMode: propertyDefault);
+
+ var binding = new Binding (".", bindingMode);
+
+ var bindable = new MockBindable();
+ Assert.IsNull (bindable.BindingContext);
+
+ bindable.SetBinding (property, binding);
+
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property changed");
+ Assert.IsNull (bindable.BindingContext,
+ "BindingContext changed with self-path binding");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Self paths")]
+ public void ValueSetOnTwoWayWithSelfPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding (".", bindingMode);
+
+ const string value = "Foo";
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = value;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = value;
+ }
+
+ Assert.AreEqual (value, bindable.BindingContext,
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Simple paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithSimplePathOnOneWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new MockViewModel {
+ Text = "Foo"
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Text", bindingMode));
+
+ viewmodel.Text = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Bindable did not update on binding context property change");
+ Assert.AreEqual (newvalue, viewmodel.Text,
+ "Source property changed when it shouldn't");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Simple paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithSimplePathOnOneWayToSourceBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new MockViewModel {
+ Text = "Foo"
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Text", bindingMode));
+
+ string original = (string)bindable.GetValue (property);
+ const string value = "value";
+ viewmodel.Text = value;
+ Assert.AreEqual (original, bindable.GetValue (property),
+ "Target updated from Source on OneWayToSource");
+
+ bindable.SetValue (property, newvalue);
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Bindable did not update on binding context property change");
+ Assert.AreEqual (newvalue, viewmodel.Text,
+ "Source property changed when it shouldn't");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Simple paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithSimplePathOnTwoWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new MockViewModel {
+ Text = "Foo"
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Text", bindingMode));
+
+ viewmodel.Text = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Target property did not update change");
+ Assert.AreEqual (newvalue, viewmodel.Text,
+ "Source property changed from what it was set to");
+
+ const string newvalue2 = "New Value in the other direction";
+
+ bindable.SetValue (property, newvalue2);
+ Assert.AreEqual (newvalue2, viewmodel.Text,
+ "Source property did not update with Target's change");
+ Assert.AreEqual (newvalue2, bindable.GetValue (property),
+ "Target property changed from what it was set to");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Complex paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithComplexPathOnOneWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Text = "Foo"
+ }
+ }
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Model.Model.Text", bindingMode));
+
+ viewmodel.Model.Model.Text = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Bindable did not update on binding context property change");
+ Assert.AreEqual (newvalue, viewmodel.Model.Model.Text,
+ "Source property changed when it shouldn't");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Complex paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithComplexPathOnOneWayToSourceBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Text = "Foo"
+ }
+ }
+ };;
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Model.Model.Text", bindingMode));
+
+ string original = (string)bindable.GetValue (property);
+ const string value = "value";
+ viewmodel.Model.Model.Text = value;
+ Assert.AreEqual (original, bindable.GetValue (property),
+ "Target updated from Source on OneWayToSource");
+
+ bindable.SetValue (property, newvalue);
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Bindable did not update on binding context property change");
+ Assert.AreEqual (newvalue, viewmodel.Model.Model.Text,
+ "Source property changed when it shouldn't");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Complex paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithComplexPathOnTwoWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Text = "Foo"
+ }
+ }
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Model.Model.Text", bindingMode));
+
+ viewmodel.Model.Model.Text = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Target property did not update change");
+ Assert.AreEqual (newvalue, viewmodel.Model.Model.Text,
+ "Source property changed from what it was set to");
+
+ const string newvalue2 = "New Value in the other direction";
+
+ bindable.SetValue (property, newvalue2);
+ Assert.AreEqual (newvalue2, viewmodel.Model.Model.Text,
+ "Source property did not update with Target's change");
+ Assert.AreEqual (newvalue2, bindable.GetValue (property),
+ "Target property changed from what it was set to");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Indexed paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithIndexedPathOnOneWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+ };
+ viewmodel.Model.Model[1] = "Foo";
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Model.Model[1]", bindingMode));
+
+ viewmodel.Model.Model[1] = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Bindable did not update on binding context property change");
+ Assert.AreEqual (newvalue, viewmodel.Model.Model[1],
+ "Source property changed when it shouldn't");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Indexed paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithIndexedPathOnOneWayToSourceBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+ };
+ viewmodel.Model.Model[1] = "Foo";
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Model.Model[1]", bindingMode));
+
+ string original = (string)bindable.GetValue (property);
+ const string value = "value";
+ viewmodel.Model.Model[1] = value;
+ Assert.AreEqual (original, bindable.GetValue (property),
+ "Target updated from Source on OneWayToSource");
+
+ bindable.SetValue (property, newvalue);
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Bindable did not update on binding context property change");
+ Assert.AreEqual (newvalue, viewmodel.Model.Model[1],
+ "Source property changed when it shouldn't");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Indexed paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithIndexedPathOnTwoWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+ };
+ viewmodel.Model.Model[1] = "Foo";
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Model.Model[1]", bindingMode));
+
+ viewmodel.Model.Model[1] = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Target property did not update change");
+ Assert.AreEqual (newvalue, viewmodel.Model.Model[1],
+ "Source property changed from what it was set to");
+
+ const string newvalue2 = "New Value in the other direction";
+
+ bindable.SetValue (property, newvalue2);
+ Assert.AreEqual (newvalue2, viewmodel.Model.Model[1],
+ "Source property did not update with Target's change");
+ Assert.AreEqual (newvalue2, bindable.GetValue (property),
+ "Target property changed from what it was set to");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Indexed paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithIndexedArrayPathOnTwoWayBinding (bool isDefault)
+ {
+ var viewmodel = new ComplexMockViewModel {
+ Array = new string[2]
+ };
+ viewmodel.Array[1] = "Foo";
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Array[1]", bindingMode));
+
+ const string newvalue2 = "New Value in the other direction";
+
+ bindable.SetValue (property, newvalue2);
+ Assert.AreEqual (newvalue2, viewmodel.Array[1],
+ "Source property did not update with Target's change");
+ Assert.AreEqual (newvalue2, bindable.GetValue (property),
+ "Target property changed from what it was set to");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Self paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithSelfPathOnOneWayBinding (bool isDefault)
+ {
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ const string value = "foo";
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = value;
+ bindable.SetBinding (property, new Binding (".", bindingMode));
+
+ const string newvalue = "value";
+ bindable.SetValue (property, newvalue);
+ Assert.AreEqual (value, bindable.BindingContext,
+ "Source was updated from Target on OneWay binding");
+
+ bindable.BindingContext = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Bindable did not update on binding context property change");
+ Assert.AreEqual (newvalue, bindable.BindingContext,
+ "Source property changed when it shouldn't");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Self paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueDoesNotUpdateWithSelfPathOnOneWayToSourceBinding (bool isDefault)
+ {
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding (".", bindingMode);
+
+ var bindable = new MockBindable();
+ bindable.SetBinding (property, binding);
+
+ const string newvalue = "new value";
+
+ string original = (string)bindable.GetValue (property);
+ bindable.BindingContext = newvalue;
+ Assert.AreEqual (original, bindable.GetValue (property),
+ "Target updated from Source on OneWayToSource with self path");
+
+ const string newvalue2 = "new value 2";
+ bindable.SetValue (property, newvalue2);
+ Assert.AreEqual (newvalue2, bindable.GetValue (property),
+ "Target property changed on OneWayToSource with self path");
+ Assert.AreEqual (newvalue, bindable.BindingContext,
+ "Source property changed on OneWayToSource with self path");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Self paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithSelfPathOnTwoWayBinding (bool isDefault)
+ {
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding (".", bindingMode);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = "value";
+ bindable.SetBinding (property, binding);
+
+ const string newvalue = "New Value";
+ bindable.BindingContext = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Target property did not update change");
+ Assert.AreEqual (newvalue, bindable.BindingContext,
+ "Source property changed from what it was set to");
+
+ const string newvalue2 = "New Value in the other direction";
+
+ bindable.SetValue (property, newvalue2);
+ Assert.AreEqual (newvalue, bindable.BindingContext,
+ "Self-path Source changed with Target's change");
+ Assert.AreEqual (newvalue2, bindable.GetValue (property),
+ "Target property changed from what it was set to");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithOldContextDoesNotUpdateWithOneWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new MockViewModel {
+ Text = "Foo"
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding ("Text", bindingMode);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+
+ bindable.BindingContext = new MockViewModel();
+ Assert.AreEqual (null, bindable.GetValue (property));
+
+ viewmodel.Text = newvalue;
+ Assert.AreEqual (null, bindable.GetValue (property),
+ "Target updated from old Source property change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithOldContextDoesNotUpdateWithTwoWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new MockViewModel {
+ Text = "Foo"
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding ("Text", bindingMode);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+
+ bindable.BindingContext = new MockViewModel();
+ Assert.AreEqual (null, bindable.GetValue (property));
+
+ viewmodel.Text = newvalue;
+ Assert.AreEqual (null, bindable.GetValue (property),
+ "Target updated from old Source property change");
+
+ string original = viewmodel.Text;
+
+ bindable.SetValue (property, newvalue);
+ Assert.AreEqual (original, viewmodel.Text,
+ "Source updated from old Target property change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithOldContextDoesNotUpdateWithOneWayToSourceBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new MockViewModel {
+ Text = "Foo"
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding ("Text", bindingMode);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+
+ bindable.BindingContext = new MockViewModel();
+ Assert.AreEqual (property.DefaultValue, bindable.GetValue (property));
+
+ viewmodel.Text = newvalue;
+ Assert.AreEqual (property.DefaultValue, bindable.GetValue (property),
+ "Target updated from old Source property change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test]
+ public void BindingStaysOnUpdateValueFromBinding()
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new MockViewModel {
+ Text = "Foo"
+ };
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null);
+
+ var binding = new Binding ("Text");
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+
+ viewmodel.Text = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property));
+
+ const string newValue2 = "new value 2";
+ viewmodel.Text = newValue2;
+ Assert.AreEqual (newValue2, bindable.GetValue (property));
+
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test]
+ public void OneWayToSourceContextSetToNull()
+ {
+ var binding = new Binding ("Text", BindingMode.OneWayToSource);
+
+ MockBindable bindable = new MockBindable {
+ BindingContext = new MockViewModel()
+ };
+ bindable.SetBinding (MockBindable.TextProperty, binding);
+
+ Assert.That (() => bindable.BindingContext = null, Throws.Nothing);
+ }
+
+ [Category ("[Binding] Simple paths")]
+ [TestCase (BindingMode.OneWay)]
+ [TestCase (BindingMode.OneWayToSource)]
+ [TestCase (BindingMode.TwoWay)]
+ public void SourceAndTargetAreWeakWeakSimplePath (BindingMode mode)
+ {
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", BindingMode.OneWay);
+
+ var binding = new Binding ("Text", mode);
+
+ WeakReference weakViewModel = null, weakBindable = null;
+
+ int i = 0;
+ Action create = null;
+ create = () => {
+ if (i++ < 1024) {
+ create();
+ return;
+ }
+
+ MockBindable bindable = new MockBindable();
+ weakBindable = new WeakReference (bindable);
+
+ MockViewModel viewmodel = new MockViewModel();
+ weakViewModel = new WeakReference (viewmodel);
+
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+
+ Assume.That (() => bindable.BindingContext = null, Throws.Nothing);
+ };
+
+ create();
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+
+ if (mode == BindingMode.TwoWay || mode == BindingMode.OneWay)
+ Assert.IsFalse (weakViewModel.IsAlive, "ViewModel wasn't collected");
+
+ if (mode == BindingMode.TwoWay || mode == BindingMode.OneWayToSource)
+ Assert.IsFalse (weakBindable.IsAlive, "Bindable wasn't collected");
+ }
+
+ internal class ComplexMockViewModel
+ : MockViewModel
+ {
+ public ComplexMockViewModel Model
+ {
+ get { return model; }
+ set
+ {
+ if (model == value)
+ return;
+
+ model = value;
+ OnPropertyChanged ("Model");
+ }
+ }
+
+ internal int count;
+ public int QueryCount
+ {
+ get { return count++; }
+ }
+
+ [IndexerName ("Indexer")]
+ public string this [int v]
+ {
+ get { return values[v]; }
+ set
+ {
+ if (values[v] == value)
+ return;
+
+ values[v] = value;
+ OnPropertyChanged ("Indexer[" + v + "]");
+ }
+ }
+
+ public string[] Array
+ {
+ get;
+ set;
+ }
+
+ public object DoStuff()
+ {
+ return null;
+ }
+
+ public object DoStuff (object argument)
+ {
+ return null;
+ }
+
+ string[] values = new string[5];
+ ComplexMockViewModel model;
+ }
+
+ [Category ("[Binding] Complex paths")]
+ [TestCase (BindingMode.OneWay)]
+ [TestCase (BindingMode.OneWayToSource)]
+ [TestCase (BindingMode.TwoWay)]
+ public void SourceAndTargetAreWeakComplexPath (BindingMode mode)
+ {
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value");
+
+ var binding = new Binding ("Model.Model[1]");
+
+ WeakReference weakViewModel = null, weakBindable = null;
+
+ HackAroundMonoSucking (0, property, binding, out weakViewModel, out weakBindable);
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+
+ if (mode == BindingMode.TwoWay || mode == BindingMode.OneWay)
+ Assert.IsFalse (weakViewModel.IsAlive, "ViewModel wasn't collected");
+
+ if (mode == BindingMode.TwoWay || mode == BindingMode.OneWayToSource)
+ Assert.IsFalse (weakBindable.IsAlive, "Bindable wasn't collected");
+ }
+
+ // Mono doesn't handle the GC properly until the stack frame where the object is created is popped.
+ // This means calling another method and not just using lambda as works in real .NET
+ void HackAroundMonoSucking (int i, BindableProperty property, Binding binding, out WeakReference weakViewModel, out WeakReference weakBindable)
+ {
+ if (i++ < 1024) {
+ HackAroundMonoSucking (i, property, binding, out weakViewModel, out weakBindable);
+ return;
+ }
+
+ MockBindable bindable = new MockBindable();
+
+ weakBindable = new WeakReference (bindable);
+
+ ComplexMockViewModel viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+ };
+
+ weakViewModel = new WeakReference (viewmodel);
+
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+
+ bindable.BindingContext = null;
+ }
+
+ class TestConverter<TSource,TTarget>
+ : IValueConverter
+ {
+ public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ Assert.AreEqual (typeof (TTarget), targetType);
+ return System.Convert.ChangeType (value, targetType, CultureInfo.CurrentUICulture);
+ }
+
+ public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ Assert.AreEqual (typeof (TSource), targetType);
+ return System.Convert.ChangeType (value, targetType, CultureInfo.CurrentUICulture);
+ }
+ }
+
+ [Test]
+ public void ValueConverter()
+ {
+ var converter = new TestConverter<string, int>();
+
+ var vm = new MockViewModel { Text = "1" };
+ var property = BindableProperty.Create<MockBindable, int> (w=>w.TargetInt, 0);
+
+ var bindable = new MockBindable();
+ bindable.SetBinding (property, new Binding ("Text", converter: converter));
+ bindable.BindingContext = vm;
+
+ Assert.AreEqual (1, bindable.GetValue (property));
+
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test]
+ public void ValueConverterBack()
+ {
+ var converter = new TestConverter<string, int>();
+
+ var vm = new MockViewModel();
+ var property = BindableProperty.Create<MockBindable, int> (w=>w.TargetInt, 1, BindingMode.OneWayToSource);
+ var bindable = new MockBindable();
+ bindable.SetBinding (property, new Binding ("Text", converter: converter));
+ bindable.BindingContext = vm;
+
+ Assert.AreEqual ("1", vm.Text);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+
+ class TestConverterParameter : IValueConverter
+ {
+ public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return parameter;
+ }
+
+ public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return parameter;
+ }
+ }
+
+ [Test]
+ public void ValueConverterParameter ()
+ {
+ var converter = new TestConverterParameter ();
+
+ var vm = new MockViewModel ();
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "Bar", BindingMode.OneWayToSource);
+ var bindable = new MockBindable();
+ bindable.SetBinding (property, new Binding ("Text", converter: converter, converterParameter: "Foo"));
+ bindable.BindingContext = vm;
+
+ Assert.AreEqual ("Foo", vm.Text);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ class TestConverterCulture : IValueConverter
+ {
+ public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return culture.ToString ();
+ }
+
+ public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return culture.ToString ();
+ }
+ }
+
+ #if !WINDOWS_PHONE
+ [Test]
+ [SetUICulture ("pt-PT")]
+ public void ValueConverterCulture ()
+ {
+ var converter = new TestConverterCulture ();
+ var vm = new MockViewModel ();
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "Bar", BindingMode.OneWayToSource);
+ var bindable = new MockBindable();
+ bindable.SetBinding (property, new Binding ("Text", converter: converter));
+ bindable.BindingContext = vm;
+
+ Assert.AreEqual ("pt-PT", vm.Text);
+ }
+ #endif
+
+ [Test]
+ public void SelfBindingConverter()
+ {
+ var converter = new TestConverter<int, string> ();
+
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Text, "0");
+ var bindable = new MockBindable ();
+ bindable.BindingContext = 1;
+ bindable.SetBinding (property, new Binding (Binding.SelfPath, converter:converter));
+ Assert.AreEqual ("1", bindable.GetValue (property));
+
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ internal class MultiplePropertyViewModel
+ : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ int done;
+ public int Done
+ {
+ get { return done; }
+ set
+ {
+ done = value;
+ OnPropertyChanged();
+ OnPropertyChanged ("Progress");
+ }
+ }
+
+ int total = 100;
+ public int Total
+ {
+ get { return total; }
+ set
+ {
+ if (total == value)
+ return;
+
+ total = value;
+ OnPropertyChanged();
+ OnPropertyChanged ("Progress");
+ }
+ }
+
+ public float Progress
+ {
+ get { return (float)done / total; }
+ }
+
+ protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null)
+ {
+ PropertyChangedEventHandler handler = PropertyChanged;
+ if (handler != null)
+ handler (this, new PropertyChangedEventArgs (propertyName));
+ }
+ }
+
+ internal class MultiplePropertyBindable
+ : BindableObject
+ {
+ public static readonly BindableProperty ValueProperty =
+ BindableProperty.Create<MultiplePropertyBindable, float> (b => b.Value, 0f);
+
+ public float Value
+ {
+ get { return (float)GetValue (ValueProperty); }
+ set { SetValue (ValueProperty, value); }
+ }
+
+ public static readonly BindableProperty DoneProperty =
+ BindableProperty.Create<MultiplePropertyBindable, int> (b => b.Done, 0);
+
+ public int Done
+ {
+ get { return (int)GetValue (DoneProperty); }
+ set { SetValue (DoneProperty, value); }
+ }
+ }
+
+ [Test]
+ public void MultiplePropertyUpdates()
+ {
+ var mpvm = new MultiplePropertyViewModel();
+
+ var bindable = new MultiplePropertyBindable();
+ bindable.SetBinding (MultiplePropertyBindable.ValueProperty, new Binding ("Progress", BindingMode.OneWay));
+ bindable.SetBinding (MultiplePropertyBindable.DoneProperty, new Binding ("Done", BindingMode.OneWayToSource));
+ bindable.BindingContext = mpvm;
+
+ bindable.Done = 5;
+
+ Assert.AreEqual (5, mpvm.Done);
+ Assert.AreEqual (0.05f, mpvm.Progress);
+ Assert.AreEqual (5, bindable.Done);
+ Assert.AreEqual (0.05f, bindable.Value);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Complex paths")]
+ [Description ("When part of a complex path can not be evaluated during an update, bindables should return to their default value.")]
+ public void NullInPathUsesDefaultValue()
+ {
+ var vm = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ };
+
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Text, "foo bar");
+
+ var bindable = new MockBindable();
+ bindable.SetBinding (property, new Binding ("Model.Text", BindingMode.OneWay));
+ bindable.BindingContext = vm;
+
+ vm.Model = null;
+
+ Assert.AreEqual (property.DefaultValue, bindable.GetValue (property));
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Complex paths")]
+ [Description ("When part of a complex path can not be evaluated during an update, bindables should return to their default value.")]
+ public void NullContextUsesDefaultValue()
+ {
+ var vm = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Text = "vm value"
+ }
+ };
+
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Text, "foo bar");
+
+ var bindable = new MockBindable();
+ bindable.SetBinding (property, new Binding ("Model.Text", BindingMode.OneWay));
+ bindable.BindingContext = vm;
+
+ Assume.That (bindable.GetValue (property), Is.EqualTo (vm.Model.Text));
+
+ bindable.BindingContext = null;
+
+ Assert.AreEqual (property.DefaultValue, bindable.GetValue (property));
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test]
+ [Description ("OneWay bindings should not double apply on source updates.")]
+ public void OneWayBindingsDontDoubleApplyOnSourceUpdates()
+ {
+ var vm = new ComplexMockViewModel();
+
+ var bindable = new MockBindable();
+ bindable.SetBinding (MultiplePropertyBindable.DoneProperty, new Binding ("QueryCount", BindingMode.OneWay));
+ bindable.BindingContext = vm;
+
+ Assert.AreEqual (1, vm.count);
+
+ bindable.BindingContext = null;
+
+ Assert.AreEqual (1, vm.count, "Source property was queried on an unset");
+
+ bindable.BindingContext = vm;
+
+ Assert.AreEqual (2, vm.count, "Source property was queried multiple times on a reapply");
+ }
+
+ [Test]
+ [Description ("When there are multiple bindings, an update in one should not cause the other to udpate.")]
+ public void BindingsShouldNotTriggerOtherBindings()
+ {
+ var vm = new ComplexMockViewModel();
+
+ var bindable = new MockBindable();
+ bindable.SetBinding (MultiplePropertyBindable.DoneProperty, new Binding ("QueryCount", BindingMode.OneWay));
+ bindable.SetBinding (MockBindable.TextProperty, new Binding ("Text", BindingMode.OneWay));
+ bindable.BindingContext = vm;
+
+ Assert.AreEqual (1, vm.count);
+
+ vm.Text = "update";
+
+ Assert.AreEqual (1, vm.count, "Source property was queried due to a different binding update.");
+ }
+
+ internal class DerivedViewModel
+ : MockViewModel
+ {
+ public override string Text
+ {
+ get { return base.Text + "2"; }
+ set { base.Text = value; }
+ }
+ }
+
+ [Test]
+ [Description ("The most derived version of a property should always be called.")]
+ public void MostDerviedPropertyOnContextSwitchOfSimilarType()
+ {
+ var vm = new MockViewModel { Text = "text" };
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = vm;
+ bindable.SetBinding (MockBindable.TextProperty, new Binding ("Text"));
+
+ Assert.AreEqual (vm.Text, bindable.GetValue (MockBindable.TextProperty));
+
+ bindable.BindingContext = vm = new DerivedViewModel { Text = "text" };
+
+ Assert.AreEqual (vm.Text, bindable.GetValue (MockBindable.TextProperty));
+ }
+
+ internal class EmptyViewModel
+ {
+ }
+
+ internal class DifferentViewModel
+ : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ string text = "foo";
+
+ public string Text
+ {
+ get { return text; }
+ }
+
+ public string Text2
+ {
+ set { text = value; }
+ }
+
+ public string PrivateSetter
+ {
+ get;
+ private set;
+ }
+ }
+
+ [Test]
+ [Description ("Paths should not distinguish types, a context change to a completely different type should work.")]
+ public void DifferentContextTypeAccessedCorrectlyWithSamePath()
+ {
+ var vm = new MockViewModel { Text = "text" };
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = vm;
+ bindable.SetBinding (MockBindable.TextProperty, new Binding ("Text"));
+
+ Assert.AreEqual (vm.Text, bindable.GetValue (MockBindable.TextProperty));
+
+ var dvm = new DifferentViewModel();
+ bindable.BindingContext = dvm;
+
+ Assert.AreEqual (dvm.Text, bindable.GetValue (MockBindable.TextProperty));
+ }
+
+ [Test]
+ public void PropertyChangeBindingsOccurThroughMainThread()
+ {
+ var vm = new MockViewModel { Text = "text" };
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = vm;
+ bindable.SetBinding (MockBindable.TextProperty, new Binding ("Text"));
+
+ bool mainThread = false;
+ Device.PlatformServices = new MockPlatformServices (invokeOnMainThread: a => mainThread = true);
+
+ vm.Text = "updated";
+
+ Assert.IsTrue (mainThread, "Binding did not occur on main thread");
+ Assert.AreNotEqual (vm.Text, bindable.GetValue (MockBindable.TextProperty), "Binding was applied anyway through other means");
+ }
+
+ [Test]
+ public void Clone()
+ {
+ object param = new object();
+ var binding = new Binding (".", converter: new TestConverter<string, int>(), converterParameter: param, stringFormat: "{0}");
+ var clone = (Binding)binding.Clone();
+
+ Assert.AreSame (binding.Converter, clone.Converter);
+ Assert.AreSame (binding.ConverterParameter, clone.ConverterParameter);
+ Assert.AreEqual (binding.Mode, clone.Mode);
+ Assert.AreEqual (binding.Path, clone.Path);
+ Assert.AreEqual (binding.StringFormat, clone.StringFormat);
+ }
+
+ [Test]
+ public void PropertyMissingOneWay()
+ {
+ var bindable = new MockBindable { BindingContext = new MockViewModel() };
+ bindable.Text = "foo";
+
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("Monkeys", BindingMode.OneWay)), Throws.Nothing);
+ Assert.That (log.Messages.Count, Is.EqualTo (1), "An error was not logged");
+ Assert.That (bindable.Text, Is.EqualTo (MockBindable.TextProperty.DefaultValue));
+ }
+
+ [Test]
+ public void PropertyMissingOneWayToSource()
+ {
+ var bindable = new MockBindable { BindingContext = new MockViewModel() };
+ bindable.Text = "foo";
+
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("Monkeys", BindingMode.OneWayToSource)), Throws.Nothing);
+ Assert.That (log.Messages.Count, Is.EqualTo (1), "An error was not logged");
+ Assert.That (bindable.Text, Is.EqualTo (bindable.Text));
+ }
+
+ [Test]
+ public void PropertyMissingTwoWay()
+ {
+ var bindable = new MockBindable { BindingContext = new MockViewModel() };
+ bindable.Text = "foo";
+
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("Monkeys")), Throws.Nothing);
+ // The first error is for the initial binding, the second is for reflecting the update back to the default value
+ Assert.That (log.Messages.Count, Is.EqualTo (2), "An error was not logged");
+ Assert.That (bindable.Text, Is.EqualTo (MockBindable.TextProperty.DefaultValue));
+ }
+
+ [Test]
+ public void GetterMissingTwoWay()
+ {
+ var bindable = new MockBindable { BindingContext = new DifferentViewModel() };
+ bindable.Text = "foo";
+
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("Text2")), Throws.Nothing);
+ Assert.That (bindable.Text, Is.EqualTo (MockBindable.TextProperty.DefaultValue));
+ Assert.That (log.Messages.Count, Is.EqualTo (1), "An error was not logged");
+ Assert.That (log.Messages[0], Is.StringContaining (String.Format (BindingExpression.PropertyNotFoundErrorMessage,
+ "Text2",
+ "Xamarin.Forms.Core.UnitTests.BindingUnitTests+DifferentViewModel",
+ "Xamarin.Forms.Core.UnitTests.MockBindable",
+ "Text")));
+
+ Assert.That (((DifferentViewModel) bindable.BindingContext).Text, Is.EqualTo (MockBindable.TextProperty.DefaultValue));
+ }
+
+ [Test]
+ public void BindingAppliesAfterGetterPreviouslyMissing()
+ {
+ var bindable = new MockBindable { BindingContext = new EmptyViewModel() };
+ bindable.SetBinding (MockBindable.TextProperty, new Binding ("Text"));
+
+ bindable.BindingContext = new MockViewModel { Text = "Foo" };
+ Assert.That (bindable.Text, Is.EqualTo ("Foo"));
+
+ Assert.That (log.Messages.Count, Is.Not.GreaterThan (1), "Too many errors were logged");
+ Assert.That (log.Messages[0], Is.StringContaining (String.Format (BindingExpression.PropertyNotFoundErrorMessage,
+ "Text",
+ "Xamarin.Forms.Core.UnitTests.BindingUnitTests+EmptyViewModel",
+ "Xamarin.Forms.Core.UnitTests.MockBindable",
+ "Text")));
+ }
+
+ [Test]
+ public void SetterMissingTwoWay()
+ {
+ var bindable = new MockBindable { BindingContext = new DifferentViewModel() };
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("Text")), Throws.Nothing);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (1), "An error was not logged");
+ Assert.That (log.Messages[0], Is.StringContaining (String.Format (BindingExpression.PropertyNotFoundErrorMessage,
+ "Text",
+ "Xamarin.Forms.Core.UnitTests.BindingUnitTests+DifferentViewModel",
+ "Xamarin.Forms.Core.UnitTests.MockBindable",
+ "Text")));
+
+ Assert.That (() => bindable.SetValueCore (MockBindable.TextProperty, "foo"), Throws.Nothing);
+ }
+
+ [Test]
+ public void PrivateSetterTwoWay()
+ {
+ var bindable = new MockBindable { BindingContext = new DifferentViewModel() };
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("PrivateSetter")), Throws.Nothing);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (1), "An error was not logged");
+ Assert.That (log.Messages[0], Is.StringContaining (String.Format (BindingExpression.PropertyNotFoundErrorMessage,
+ "PrivateSetter",
+ "Xamarin.Forms.Core.UnitTests.BindingUnitTests+DifferentViewModel",
+ "Xamarin.Forms.Core.UnitTests.MockBindable",
+ "Text")));
+
+ Assert.That (() => bindable.SetValueCore (MockBindable.TextProperty, "foo"), Throws.Nothing);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (2), "An error was not logged");
+ Assert.That (log.Messages[1], Is.StringContaining (String.Format (BindingExpression.PropertyNotFoundErrorMessage,
+ "PrivateSetter",
+ "Xamarin.Forms.Core.UnitTests.BindingUnitTests+DifferentViewModel",
+ "Xamarin.Forms.Core.UnitTests.MockBindable",
+ "Text")));
+ }
+
+ [Test]
+ public void PropertyNotFound()
+ {
+ var bindable = new MockBindable { BindingContext = new MockViewModel() };
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("MissingProperty")), Throws.Nothing);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (1), "An error was not logged");
+ Assert.That (log.Messages[0], Is.StringContaining (String.Format (BindingExpression.PropertyNotFoundErrorMessage,
+ "MissingProperty",
+ "Xamarin.Forms.Core.UnitTests.MockViewModel",
+ "Xamarin.Forms.Core.UnitTests.MockBindable",
+ "Text")));
+ }
+
+ [Test]
+ [Description ("When binding with a multi-part path and part is null, no error should be thrown or logged")]
+ public void ChainedPartNull()
+ {
+ var bindable = new MockBindable { BindingContext = new ComplexMockViewModel() };
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("Model.Text")), Throws.Nothing);
+ Assert.That (log.Messages.Count, Is.EqualTo (0), "An error was logged");
+ }
+
+ [Test]
+ public void PropertyNotFoundChained()
+ {
+ var bindable = new MockBindable {
+ BindingContext = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+
+ };
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("Model.MissingProperty")), Throws.Nothing);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (1), "An error was not logged");
+ Assert.That (log.Messages[0], Is.StringContaining (String.Format (BindingExpression.PropertyNotFoundErrorMessage,
+ "MissingProperty",
+ "Xamarin.Forms.Core.UnitTests.BindingUnitTests+ComplexMockViewModel",
+ "Xamarin.Forms.Core.UnitTests.MockBindable",
+ "Text")));
+
+ Assert.That (bindable.Text, Is.EqualTo (MockBindable.TextProperty.DefaultValue));
+ }
+
+ [Test]
+ public void CreateBindingNull()
+ {
+ Assert.That (() => Binding.Create<MockViewModel> (null), Throws.InstanceOf<ArgumentNullException>());
+ }
+
+ [Test]
+ public void CreateBindingSimple()
+ {
+ Binding binding = Binding.Create<MockViewModel> (mvm => mvm.Text);
+ Assert.IsNotNull (binding);
+ Assert.AreEqual ("Text", binding.Path);
+ }
+
+ [Test]
+ public void CreateBindingComplex()
+ {
+ Binding binding = Binding.Create<ComplexMockViewModel> (vm => vm.Model.Model.Text);
+ Assert.IsNotNull (binding);
+ Assert.AreEqual ("Model.Model.Text", binding.Path);
+ }
+
+ [Test]
+ public void CreateBindingIndexed()
+ {
+ Binding binding = Binding.Create<ComplexMockViewModel> (vm => vm.Model.Model[5]);
+ Assert.IsNotNull (binding);
+ Assert.AreEqual ("Model.Model[5]", binding.Path);
+ }
+
+ [Test]
+ public void CreateBindingIndexedNonConstant()
+ {
+ int x = 5;
+ Assert.That (
+ () => Binding.Create<ComplexMockViewModel> (vm => vm.Model.Model[x]),
+ Throws.ArgumentException);
+ }
+
+ internal class ReferenceTypeIndexerViewModel
+ : MockViewModel
+ {
+ public string this [string value]
+ {
+ get { return value; }
+ }
+ }
+
+ [Test]
+ public void CreateBindingNullToIndexer()
+ {
+ Assert.That (
+ () => Binding.Create<ReferenceTypeIndexerViewModel> (vm => vm[null]),
+ Throws.Nothing);
+ }
+
+ [Test]
+ public void CreateBindingWithMethod()
+ {
+ Assert.That (
+ () => Binding.Create<ComplexMockViewModel> (vm => vm.DoStuff()),
+ Throws.ArgumentException);
+ }
+
+ [Test]
+ [Description ("Indexers are seen as methods, we don't want to get them confused with real methods.")]
+ public void CreateBindingWithMethodArgument()
+ {
+ Assert.That (
+ () => Binding.Create<ComplexMockViewModel> (vm => vm.DoStuff (null)),
+ Throws.ArgumentException);
+ }
+
+ object Method (MockViewModel vm)
+ {
+ return vm.Text;
+ }
+
+ [Test]
+ public void CreateBindingMethod()
+ {
+ Func<MockViewModel, object> func = vm => vm.Text;
+ Assert.That (() => Binding.Create<MockViewModel> (vm => func (vm)),
+ Throws.ArgumentException);
+
+ Assert.That (() => Binding.Create<MockViewModel> (vm => Method (vm)),
+ Throws.ArgumentException);
+ }
+
+ [Test]
+ public void CreateBindingPrivateIndexer()
+ {
+ Assert.That (() => Binding.Create<InternalIndexerViewModel> (vm => vm[5]),
+ Throws.ArgumentException);
+ }
+
+ class InternalIndexerViewModel
+ {
+ internal int this [int x]
+ {
+ get { return x; }
+ }
+ }
+
+ [Test]
+ public void CreateBindingInvalidExpression()
+ {
+ Assert.That (() => Binding.Create<MockViewModel> (vm => vm.Text + vm.Text),
+ Throws.ArgumentException);
+
+ Assert.That (() => Binding.Create<MockViewModel> (vm => 5),
+ Throws.ArgumentException);
+
+ Assert.That (() => Binding.Create<MockViewModel> (vm => null),
+ Throws.ArgumentException);
+ }
+
+ [Test]
+ public void SetBindingContextBeforeContextBindingAndInnerBindings ()
+ {
+ var label = new Label ();
+ var view = new StackLayout { Children = {label} };
+
+ view.BindingContext = new {item0 = "Foo", item1 = "Bar"};
+ label.SetBinding (BindableObject.BindingContextProperty, "item0");
+ label.SetBinding (Label.TextProperty, Binding.SelfPath);
+
+ Assert.AreEqual ("Foo", label.Text);
+ }
+
+ [Test]
+ public void SetBindingContextAndInnerBindingBeforeContextBinding ()
+ {
+ var label = new Label ();
+ var view = new StackLayout { Children = {label} };
+
+ view.BindingContext = new {item0 = "Foo", item1 = "Bar"};
+ label.SetBinding (Label.TextProperty, Binding.SelfPath);
+ label.SetBinding (BindableObject.BindingContextProperty, "item0");
+
+ Assert.AreEqual ("Foo", label.Text);
+ }
+
+ [Test]
+ public void SetBindingContextAfterContextBindingAndInnerBindings ()
+ {
+ var label = new Label ();
+ var view = new StackLayout { Children = {label} };
+
+ label.SetBinding (BindableObject.BindingContextProperty, "item0");
+ label.SetBinding (Label.TextProperty, Binding.SelfPath);
+ view.BindingContext = new {item0 = "Foo", item1 = "Bar"};
+
+ Assert.AreEqual ("Foo", label.Text);
+ }
+
+ [Test]
+ public void SetBindingContextAfterInnerBindingsAndContextBinding ()
+ {
+ var label = new Label ();
+ var view = new StackLayout { Children = {label} };
+
+ label.SetBinding (Label.TextProperty, Binding.SelfPath);
+ label.SetBinding (BindableObject.BindingContextProperty, "item0");
+ view.BindingContext = new {item0 = "Foo", item1 = "Bar"};
+
+ Assert.AreEqual ("Foo", label.Text);
+ }
+
+ [Test]
+ public void Convert ()
+ {
+ var slider = new Slider ();
+ var vm = new MockViewModel { Text = "0.5" };
+ slider.BindingContext = vm;
+ slider.SetBinding (Slider.ValueProperty, "Text", BindingMode.TwoWay);
+
+ Assert.That (slider.Value, Is.EqualTo (0.5));
+
+ slider.Value = 0.9;
+
+ Assert.That (vm.Text, Is.EqualTo ("0.9"));
+ }
+
+ #if !WINDOWS_PHONE
+ [Test]
+ [SetCulture ("pt-PT")]
+ [SetUICulture ("pt-PT")]
+ public void ConvertIsCultureInvariant ()
+ {
+ var slider = new Slider ();
+ var vm = new MockViewModel { Text = "0.5" };
+ slider.BindingContext = vm;
+ slider.SetBinding (Slider.ValueProperty, "Text", BindingMode.TwoWay);
+
+ Assert.That (slider.Value, Is.EqualTo (0.5));
+
+ slider.Value = 0.9;
+
+ Assert.That (vm.Text, Is.EqualTo ("0.9"));
+ }
+ #endif
+
+ [Test]
+ public void FailToConvert ()
+ {
+ var slider = new Slider ();
+ slider.BindingContext = new ComplexMockViewModel { Model = new ComplexMockViewModel() };
+
+ Assert.That (() => {
+ slider.SetBinding (Slider.ValueProperty, "Model");
+ }, Throws.Nothing);
+
+ Assert.That (slider.Value, Is.EqualTo (Slider.ValueProperty.DefaultValue));
+ }
+
+ class NullViewModel : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public string Foo
+ {
+ get;
+ set;
+ }
+
+ public string Bar
+ {
+ get;
+ set;
+ }
+
+ public void SignalAllPropertiesChanged (bool useNull)
+ {
+ var changed = PropertyChanged;
+ if (changed != null)
+ changed (this, new PropertyChangedEventArgs ((useNull) ? null : String.Empty));
+ }
+ }
+
+ class MockBindable2 : MockBindable
+ {
+ public static readonly BindableProperty Text2Property = BindableProperty.Create<MockBindable2, string> (
+ b => b.Text2, "default", BindingMode.TwoWay);
+
+ public string Text2
+ {
+ get { return (string)GetValue (Text2Property); }
+ set { SetValue (Text2Property, value); }
+ }
+ }
+
+ [TestCase (true)]
+ [TestCase (false)]
+ public void NullPropertyUpdatesAllBindings (bool useStringEmpty)
+ {
+ var vm = new NullViewModel();
+ var bindable = new MockBindable2();
+ bindable.BindingContext = vm;
+ bindable.SetBinding (MockBindable.TextProperty, "Foo");
+ bindable.SetBinding (MockBindable2.Text2Property, "Bar");
+
+ vm.Foo = "Foo";
+ vm.Bar = "Bar";
+ Assert.That (() => vm.SignalAllPropertiesChanged (useNull: !useStringEmpty), Throws.Nothing);
+
+ Assert.That (bindable.Text, Is.EqualTo ("Foo"));
+ Assert.That (bindable.Text2, Is.EqualTo ("Bar"));
+ }
+
+ [TestCase]
+ public void BindingSourceOverContext ()
+ {
+ var label = new Label ();
+ label.BindingContext = "bindingcontext";
+ label.SetBinding (Label.TextProperty, Binding.SelfPath);
+ Assert.AreEqual ("bindingcontext", label.Text);
+
+ label.SetBinding (Label.TextProperty, new Binding (Binding.SelfPath, source: "bindingsource"));
+ Assert.AreEqual ("bindingsource", label.Text);
+ }
+
+ class TestViewModel : INotifyPropertyChanged
+ {
+ event PropertyChangedEventHandler PropertyChanged;
+
+ event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
+ {
+ add { PropertyChanged += value; }
+ remove { PropertyChanged -= value; }
+ }
+
+ public string Foo { get; set; }
+
+ public int InvocationListSize ()
+ {
+ if (PropertyChanged == null)
+ return 0;
+ return PropertyChanged.GetInvocationList ().Length;
+ }
+
+ public virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
+ }
+ }
+
+ [Test]
+ public void BindingUnsubscribesForDeadTarget ()
+ {
+ TestViewModel viewmodel = new TestViewModel();
+
+ int i = 0;
+ Action create = null;
+ create = () => {
+ if (i++ < 1024) {
+ create();
+ return;
+ }
+
+ var button = new Button ();
+ button.SetBinding (Button.TextProperty, "Foo");
+ button.BindingContext = viewmodel;
+ };
+
+ create();
+
+ Assume.That (viewmodel.InvocationListSize (), Is.EqualTo (1));
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect ();
+
+ viewmodel.OnPropertyChanged ("Foo");
+
+ Assert.AreEqual (0, viewmodel.InvocationListSize ());
+ }
+
+ [Test]
+ public void BindingCreatesSingleSubscription ()
+ {
+ TestViewModel viewmodel = new TestViewModel();
+
+ var button = new Button ();
+ button.SetBinding (Button.TextProperty, "Foo");
+ button.BindingContext = viewmodel;
+
+ Assert.That (viewmodel.InvocationListSize (), Is.EqualTo (1));
+ }
+
+ public class IndexedViewModel : INotifyPropertyChanged
+ {
+ Dictionary<string, object> dict = new Dictionary<string, object> ();
+
+ public object this [string index]
+ {
+ get { return dict[index]; }
+ set
+ {
+ dict[index] = value;
+ OnPropertyChanged ("Item[" + index + "]");
+
+ }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
+ }
+ }
+
+ [Test]
+ public void IndexedViewModelPropertyChanged ()
+ {
+ var label = new Label ();
+ var viewModel = new IndexedViewModel ();
+ //viewModel["Foo"] = "Bar";
+
+ label.BindingContext = new {
+ Data = viewModel
+ };
+ label.SetBinding (Label.TextProperty, "Data[Foo]");
+
+
+ Assert.AreEqual (null, label.Text);
+
+ viewModel["Foo"] = "Baz";
+
+ Assert.AreEqual ("Baz", label.Text);
+ }
+ }
+}