summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Core.UnitTests
diff options
context:
space:
mode:
authorStephane Delcroix <stephane@delcroix.org>2016-11-15 20:39:48 +0100
committerJason Smith <jason.smith@xamarin.com>2016-11-15 11:39:48 -0800
commita6bbed029c64d2d64b74eeb67e27a099abf70664 (patch)
tree551c3924c055e2d39592b3f1c726cca46924dd73 /Xamarin.Forms.Core.UnitTests
parent14e21dcebd4a706aaa5eed384b142957d84df002 (diff)
downloadxamarin-forms-a6bbed029c64d2d64b74eeb67e27a099abf70664.tar.gz
xamarin-forms-a6bbed029c64d2d64b74eeb67e27a099abf70664.tar.bz2
xamarin-forms-a6bbed029c64d2d64b74eeb67e27a099abf70664.zip
[XamlC] TypedBindings, some tests, a compiler, ... (#489)
Diffstat (limited to 'Xamarin.Forms.Core.UnitTests')
-rw-r--r--Xamarin.Forms.Core.UnitTests/BindingBaseUnitTests.cs546
-rw-r--r--Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs701
-rw-r--r--Xamarin.Forms.Core.UnitTests/MockViewModel.cs22
-rw-r--r--Xamarin.Forms.Core.UnitTests/TypedBindingUnitTests.cs1526
-rw-r--r--Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj1
5 files changed, 2145 insertions, 651 deletions
diff --git a/Xamarin.Forms.Core.UnitTests/BindingBaseUnitTests.cs b/Xamarin.Forms.Core.UnitTests/BindingBaseUnitTests.cs
index dd0d2244..84addb7a 100644
--- a/Xamarin.Forms.Core.UnitTests/BindingBaseUnitTests.cs
+++ b/Xamarin.Forms.Core.UnitTests/BindingBaseUnitTests.cs
@@ -1,12 +1,80 @@
using System;
+using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
+using System.Runtime.CompilerServices;
using NUnit.Framework;
namespace Xamarin.Forms.Core.UnitTests
{
public abstract class BindingBaseUnitTests : BaseTestFixture
{
- protected abstract BindingBase CreateBinding (BindingMode mode, string stringFormat = null);
+ internal 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>();
+ }
+
+ internal Logger log;
+
+ protected abstract BindingBase CreateBinding (BindingMode mode = BindingMode.Default, string stringFormat = null);
+
+ 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;
+ }
[Test]
public void CloneMode()
@@ -20,8 +88,7 @@ namespace Xamarin.Forms.Core.UnitTests
[Test]
public void StringFormat()
{
- var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
-
+ var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable));
var binding = CreateBinding (BindingMode.Default, "Foo {0}");
var vm = new MockViewModel { Text = "Bar" };
@@ -34,8 +101,7 @@ namespace Xamarin.Forms.Core.UnitTests
[Test]
public void StringFormatOnUpdate()
{
- var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
-
+ var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable));
var binding = CreateBinding (BindingMode.Default, "Foo {0}");
var vm = new MockViewModel { Text = "Bar" };
@@ -51,8 +117,7 @@ namespace Xamarin.Forms.Core.UnitTests
[Description ("StringFormat should not be applied to OneWayToSource bindings")]
public void StringFormatOneWayToSource()
{
- var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
-
+ var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable));
var binding = CreateBinding (BindingMode.OneWayToSource, "Foo {0}");
var vm = new MockViewModel { Text = "Bar" };
@@ -68,8 +133,7 @@ namespace Xamarin.Forms.Core.UnitTests
[Description ("StringFormat should only be applied from from source in TwoWay bindings")]
public void StringFormatTwoWay()
{
- var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
-
+ var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable));
var binding = CreateBinding (BindingMode.TwoWay, "Foo {0}");
var vm = new MockViewModel { Text = "Bar" };
@@ -86,8 +150,7 @@ namespace Xamarin.Forms.Core.UnitTests
[Description ("You should get an exception when trying to change a binding after it's been applied")]
public void ChangeAfterApply()
{
- var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
-
+ var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable));
var binding = CreateBinding (BindingMode.OneWay);
var vm = new MockViewModel { Text = "Bar" };
@@ -97,6 +160,466 @@ namespace Xamarin.Forms.Core.UnitTests
Assert.That (() => binding.Mode = BindingMode.OneWayToSource, Throws.InvalidOperationException);
Assert.That (() => binding.StringFormat = "{0}", Throws.InvalidOperationException);
}
+
+ [Test]
+ public void StringFormatNonStringType()
+ {
+ var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable));
+ var binding = new Binding("Value", stringFormat: "{0:P2}");
+
+ var vm = new { Value = 0.95d };
+ 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("Foo", typeof(string), typeof(MockBindable));
+ 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");
+ }
+
+ [Test, Category("[Binding] Set Value")]
+ public void ValueSetOnOneWay(
+ [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("Text", typeof(string), typeof(MockBindable), null, propertyDefault);
+ var binding = CreateBinding(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] Set Value")]
+ public void ValueSetOnOneWayToSource(
+ [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("Text", typeof(string), typeof(MockBindable), defaultValue: value, defaultBindingMode: propertyDefault);
+ var binding = CreateBinding(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] Set Value")]
+ public void ValueSetOnTwoWay(
+ [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("Text", typeof(string), typeof(MockBindable), defaultValue: "default value", defaultBindingMode: propertyDefault);
+ var binding = CreateBinding(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] Update Value")]
+ public void ValueUpdatedWithSimplePathOnOneWayBinding(
+ [Values(true, false)]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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = CreateBinding(bindingMode);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding(property, binding);
+
+ 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());
+ }
+
+ [Test, Category("[Binding] Update Value")]
+ public void ValueUpdatedWithSimplePathOnOneWayToSourceBinding(
+ [Values(true, false)]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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = CreateBinding(bindingMode);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding(property, binding);
+
+ 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());
+ }
+
+ [Test, Category("[Binding] Update Value")]
+ public void ValueUpdatedWithSimplePathOnTwoWayBinding(
+ [Values(true, false)]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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = CreateBinding(bindingMode);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding(property, binding);
+
+ 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());
+ }
+
+ [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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = CreateBinding(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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = CreateBinding(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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = CreateBinding(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("Text", typeof(string), typeof(MockBindable), null);
+ var binding = CreateBinding(BindingMode.Default);
+
+ 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("Text", typeof(string), typeof(MockBindable), "default value", BindingMode.OneWay);
+ var binding = CreateBinding(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");
+ }
+
+ [Test]
+ public void PropertyChangeBindingsOccurThroughMainThread()
+ {
+ var vm = new MockViewModel { Text = "text" };
+
+ var bindable = new MockBindable();
+ var binding = CreateBinding();
+ bindable.BindingContext = vm;
+ bindable.SetBinding(MockBindable.TextProperty, binding);
+
+ 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");
+ }
}
[TestFixture]
@@ -223,5 +746,6 @@ namespace Xamarin.Forms.Core.UnitTests
Assert.That (() => BindingBase.TryGetSynchronizedCollection (null, out context),
Throws.InstanceOf<ArgumentNullException>());
}
+
}
}
diff --git a/Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs b/Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs
index c28adda9..548e4ad5 100644
--- a/Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs
+++ b/Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Globalization;
using System.ComponentModel;
using System.Linq;
@@ -16,24 +15,6 @@ namespace Xamarin.Forms.Core.UnitTests
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()
{
@@ -52,7 +33,7 @@ namespace Xamarin.Forms.Core.UnitTests
Log.Listeners.Remove (log);
}
- protected override BindingBase CreateBinding (BindingMode mode, string stringFormat = null)
+ protected override BindingBase CreateBinding(BindingMode mode = BindingMode.Default, string stringFormat = null)
{
return new Binding ("Text", mode, stringFormat: stringFormat);
}
@@ -87,9 +68,8 @@ namespace Xamarin.Forms.Core.UnitTests
[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 property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable));
+ var binding = (Binding)CreateBinding(BindingMode.Default, "Foo {0}");
var vm = new MockViewModel { Text = "Bar" };
var bo = new MockBindable { BindingContext = vm };
@@ -103,59 +83,13 @@ namespace Xamarin.Forms.Core.UnitTests
[Test]
public void NullPathIsSelf()
{
- var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
-
+ var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable));
var binding = new Binding();
var bo = new MockBindable { BindingContext = "Foo" };
- bo.SetBinding (property, binding);
-
- Assert.That (bo.GetValue (property), Is.EqualTo ("Foo"));
- }
+ bo.SetBinding(property, binding);
- 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");
+ Assert.That(bo.GetValue(property), Is.EqualTo("Foo"));
}
class ComplexPropertyNamesViewModel
@@ -204,118 +138,6 @@ namespace Xamarin.Forms.Core.UnitTests
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 (
@@ -338,9 +160,8 @@ namespace Xamarin.Forms.Core.UnitTests
bindingMode = BindingMode.Default;
}
- var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null, propertyDefault);
-
- var binding = new Binding ("Model.Model.Text", bindingMode);
+ var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable), null, propertyDefault);
+ var binding = new Binding("Model.Model.Text", bindingMode);
var bindable = new MockBindable();
if (setContextFirst) {
@@ -379,10 +200,8 @@ namespace Xamarin.Forms.Core.UnitTests
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 property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable), value, propertyDefault);
+ var binding = new Binding("Model.Model.Text", bindingMode);
var bindable = new MockBindable();
if (setContextFirst) {
@@ -422,9 +241,8 @@ namespace Xamarin.Forms.Core.UnitTests
bindingMode = BindingMode.Default;
}
- var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
-
- var binding = new Binding ("Model.Model.Text", bindingMode);
+ var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = new Binding("Model.Model.Text", bindingMode);
var bindable = new MockBindable();
if (setContextFirst) {
@@ -470,10 +288,10 @@ namespace Xamarin.Forms.Core.UnitTests
[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 ();
+ var property = BindableProperty.Create("Text", typeof(string), typeof(MockBindable), "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;
@@ -884,142 +702,32 @@ namespace Xamarin.Forms.Core.UnitTests
bindingMode = BindingMode.Default;
}
- var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+ var property = BindableProperty.Create("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
- var binding = new Binding (".", bindingMode);
+ var binding = new Binding(".", bindingMode);
const string value = "Foo";
var bindable = new MockBindable();
if (setContextFirst) {
bindable.BindingContext = value;
- bindable.SetBinding (property, binding);
+ bindable.SetBinding(property, binding);
} else {
- bindable.SetBinding (property, binding);
+ bindable.SetBinding(property, binding);
bindable.BindingContext = value;
}
- Assert.AreEqual (value, bindable.BindingContext,
+ Assert.AreEqual(value, bindable.BindingContext,
"BindingContext property changed");
- Assert.AreEqual (value, bindable.GetValue (property),
+ Assert.AreEqual(value, bindable.GetValue(property),
"Target property did not change");
- Assert.That (log.Messages.Count, Is.EqualTo (0),
+ 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)
+ [Category("[Binding] Complex paths")]
+ [TestCase(true)]
+ [TestCase(false)]
+ public void ValueUpdatedWithComplexPathOnOneWayBinding(bool isDefault)
{
const string newvalue = "New Value";
var viewmodel = new ComplexMockViewModel {
@@ -1373,316 +1081,68 @@ namespace Xamarin.Forms.Core.UnitTests
bindingMode = BindingMode.Default;
}
- var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+ var property = BindableProperty.Create<MockBindable, string>(w => w.Text, "default value", propertyDefault);
- var binding = new Binding (".", bindingMode);
+ var binding = new Binding(".", bindingMode);
var bindable = new MockBindable();
bindable.BindingContext = "value";
- bindable.SetBinding (property, binding);
+ bindable.SetBinding(property, binding);
const string newvalue = "New Value";
bindable.BindingContext = newvalue;
- Assert.AreEqual (newvalue, bindable.GetValue (property),
+ Assert.AreEqual(newvalue, bindable.GetValue(property),
"Target property did not update change");
- Assert.AreEqual (newvalue, bindable.BindingContext,
+ 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,
+ bindable.SetValue(property, newvalue2);
+ Assert.AreEqual(newvalue, bindable.BindingContext,
"Self-path Source changed with Target's change");
- Assert.AreEqual (newvalue2, bindable.GetValue (property),
+ 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),
+ 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)
+ [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", BindingMode.OneWay);
+ var property = BindableProperty.Create<MockBindable, string>(w => w.Text, "default value");
- var binding = new Binding ("Text", mode);
+ var binding = new Binding("Model.Model[1]");
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();
+ 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");
- }
+ Assert.IsFalse(weakViewModel.IsAlive, "ViewModel 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");
+ 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)
+ 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);
+ HackAroundMonoSucking(i, property, binding, out weakViewModel, out weakBindable);
return;
}
MockBindable bindable = new MockBindable();
- weakBindable = new WeakReference (bindable);
+ weakBindable = new WeakReference(bindable);
ComplexMockViewModel viewmodel = new ComplexMockViewModel {
Model = new ComplexMockViewModel {
@@ -1719,16 +1179,18 @@ namespace Xamarin.Forms.Core.UnitTests
{
var converter = new TestConverter<string, int>();
- var vm = new MockViewModel { Text = "1" };
- var property = BindableProperty.Create<MockBindable, int> (w=>w.TargetInt, 0);
+ var vm = new MockViewModel ("1");
+ var property = BindableProperty.Create("TargetInt", typeof(int), typeof(MockBindable), 0);
+ var binding = CreateBinding();
+ ((Binding)binding).Converter = converter;
var bindable = new MockBindable();
- bindable.SetBinding (property, new Binding ("Text", converter: converter));
+ bindable.SetBinding(property, binding);
bindable.BindingContext = vm;
- Assert.AreEqual (1, bindable.GetValue (property));
+ Assert.AreEqual(1, bindable.GetValue(property));
- Assert.That (log.Messages.Count, Is.EqualTo (0),
+ Assert.That(log.Messages.Count, Is.EqualTo(0),
"An error was logged: " + log.Messages.FirstOrDefault());
}
@@ -2035,71 +1497,50 @@ namespace Xamarin.Forms.Core.UnitTests
string text = "foo";
- public string Text
- {
+ public string Text {
get { return text; }
}
- public string Text2
- {
+ public string Text2 {
set { text = value; }
}
- public string PrivateSetter
- {
+ public string PrivateSetter {
get;
private set;
}
}
[Test]
- [Description ("Paths should not distinguish types, a context change to a completely different type should work.")]
+ [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"));
+ bindable.SetBinding(MockBindable.TextProperty, new Binding("Text"));
- Assert.AreEqual (vm.Text, bindable.GetValue (MockBindable.TextProperty));
+ 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");
+ Assert.AreEqual(dvm.Text, bindable.GetValue(MockBindable.TextProperty));
}
[Test]
public void Clone()
{
object param = new object();
- var binding = new Binding (".", converter: new TestConverter<string, int>(), converterParameter: param, stringFormat: "{0}");
+ 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);
+ 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]
@@ -2247,7 +1688,7 @@ namespace Xamarin.Forms.Core.UnitTests
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.BindingBaseUnitTests+ComplexMockViewModel",
"Xamarin.Forms.Core.UnitTests.MockBindable",
"Text")));
@@ -2673,4 +2114,4 @@ namespace Xamarin.Forms.Core.UnitTests
Assert.AreEqual ("Baz", label.Text);
}
}
-}
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/MockViewModel.cs b/Xamarin.Forms.Core.UnitTests/MockViewModel.cs
index da2f6f96..bdb3f7ef 100644
--- a/Xamarin.Forms.Core.UnitTests/MockViewModel.cs
+++ b/Xamarin.Forms.Core.UnitTests/MockViewModel.cs
@@ -3,28 +3,30 @@ using System.Runtime.CompilerServices;
namespace Xamarin.Forms.Core.UnitTests
{
- internal class MockViewModel
- : INotifyPropertyChanged
+ class MockViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
- string text;
+ public MockViewModel(string text=null)
+ {
+ _text = text;
+ }
+
+ string _text;
public virtual string Text {
- get { return text; }
+ get { return _text; }
set {
- if (text == value)
+ if (_text == value)
return;
- text = value;
+ _text = value;
OnPropertyChanged ("Text");
}
}
protected void OnPropertyChanged ([CallerMemberName] string propertyName = null)
{
- PropertyChangedEventHandler handler = PropertyChanged;
- if (handler != null)
- handler (this, new PropertyChangedEventArgs (propertyName));
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs (propertyName));
}
}
-}
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/TypedBindingUnitTests.cs b/Xamarin.Forms.Core.UnitTests/TypedBindingUnitTests.cs
new file mode 100644
index 00000000..65a78276
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/TypedBindingUnitTests.cs
@@ -0,0 +1,1526 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using System.Globalization;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using CategoryAttribute = NUnit.Framework.CategoryAttribute;
+using DescriptionAttribute = NUnit.Framework.DescriptionAttribute;
+using System.Threading.Tasks;
+using Xamarin.Forms.Internals;
+using System.Diagnostics;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class TypedBindingUnitTests : BindingBaseUnitTests
+ {
+ [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 = BindingMode.Default, string stringFormat = null)
+ {
+ return new TypedBinding<MockViewModel, string>(
+ getter: mvm => mvm.Text,
+ setter: (mvm, s) => mvm.Text = s,
+ handlers: new [] {
+ new Tuple<Func<MockViewModel, object>, string> (mvm=>mvm, "Text")
+ })
+ {
+ Mode = mode,
+ StringFormat= stringFormat
+ };
+ }
+
+ [Test]
+ public void InvalidCtor()
+ {
+ Assert.Throws<ArgumentNullException>(() => new TypedBinding<MockViewModel, string>(null, (mvm, s) => mvm.Text = s, null), "Allowed null getter");
+ }
+
+ [Test, NUnit.Framework.Category("[Binding] Set Value")]
+ 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("Foo", typeof(string), typeof(MockBindable), null, propertyDefault);
+ var binding = new TypedBinding<ComplexMockViewModel, string>(
+ cmvm => cmvm.Model.Model.Text,
+ (cmvm, s) => cmvm.Model.Model.Text = s, new [] {
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model.Model, "Text")
+ }){Mode = 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 {
+ Text = value
+ }
+ }
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable), value, propertyDefault);
+ var binding = new TypedBinding<ComplexMockViewModel, string>(
+ cmvm => cmvm.Model.Model.Text,
+ (cmvm, s) => cmvm.Model.Model.Text = s, new [] {
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model.Model, "Text")
+ }){Mode = 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("Foo", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = new TypedBinding<ComplexMockViewModel, string>(
+ cmvm => cmvm.Model.Model.Text,
+ (cmvm, s) => cmvm.Model.Model.Text = s, new [] {
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model.Model, "Text")
+ }){Mode = 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());
+ }
+
+ [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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = new TypedBinding<ComplexMockViewModel, string>(
+ cmvm => cmvm.Model.Model.Text,
+ (cmvm, s) => cmvm.Model.Model.Text = s, new [] {
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model.Model, "Text")
+ }){Mode = bindingMode};
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding(property, binding);
+
+ 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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = new TypedBinding<ComplexMockViewModel, string>(
+ cmvm => cmvm.Model.Model.Text,
+ (cmvm, s) => cmvm.Model.Model.Text = s, new [] {
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model.Model, "Text")
+ }){Mode = bindingMode};
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding(property, binding);
+
+ 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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = new TypedBinding<ComplexMockViewModel, string>(
+ cmvm => cmvm.Model.Model.Text,
+ (cmvm, s) => cmvm.Model.Model.Text = s, new [] {
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model.Model, "Text")
+ }){Mode = bindingMode};
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding(property, binding);
+
+ 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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = new TypedBinding<ComplexMockViewModel, string>(
+ cmvm => cmvm.Model.Model[1],
+ (cmvm, s) => cmvm.Model.Model[1] = s, new [] {
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model.Model, "Indexer[1]")
+ }){Mode = bindingMode};
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding(property, binding);
+
+ 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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = new TypedBinding<ComplexMockViewModel, string>(
+ cmvm => cmvm.Model.Model [1],
+ (cmvm, s) => cmvm.Model.Model [1] = s, new [] {
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model.Model, "Indexer[1]")
+ }){Mode = bindingMode};
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding(property, binding);
+
+ 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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = new TypedBinding<ComplexMockViewModel, string>(
+ cmvm => cmvm.Model.Model [1],
+ (cmvm, s) => cmvm.Model.Model [1] = s, new [] {
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model.Model, "Indexer[1]")
+ }){Mode = bindingMode};
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding(property, binding);
+
+ 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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = new TypedBinding<ComplexMockViewModel, string>(
+ cmvm => cmvm.Array [1],
+ (cmvm, s) => cmvm.Array [1] = s,
+ null){Mode = bindingMode};
+
+ 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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = new TypedBinding<string, string>(
+ cmvm => cmvm,
+ (cmvm, s) => cmvm = s,null){Mode = bindingMode};
+ const string value = "foo";
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = value;
+ bindable.SetBinding(property, binding);
+
+ 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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = new TypedBinding<string, string>(
+ cmvm => cmvm, (cmvm, s) => cmvm = s, null){Mode = 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("Text", typeof(string), typeof(MockBindable), "default value", propertyDefault);
+ var binding = new TypedBinding<string, string>(
+ cmvm => cmvm, (cmvm, s) => cmvm = s, null){Mode = 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());
+ }
+
+ [Category("[Binding] Complex paths")]
+ [TestCase(BindingMode.OneWay)]
+ [TestCase(BindingMode.OneWayToSource)]
+ [TestCase(BindingMode.TwoWay)]
+ public void SourceAndTargetAreWeakComplexPath(BindingMode mode)
+ {
+ var property = BindableProperty.Create("Text", typeof(string), typeof(MockBindable), "default value");
+
+ var binding = new TypedBinding<ComplexMockViewModel, string>(
+ cmvm => cmvm.Model.Model [1],
+ (cmvm, s) => cmvm.Model.Model [1] = s, new [] {
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model.Model, "Indexer[1]")
+ }){Mode = 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);
+
+ ComplexMockViewModel viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+ };
+
+ weakViewModel = new WeakReference(viewmodel);
+
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding(property, binding);
+
+ bindable.BindingContext = null;
+ };
+
+ create();
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+
+ 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");
+ }
+
+ 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("1");
+ var property = BindableProperty.Create("TargetInt", typeof(int), typeof(MockBindable), 0);
+ var binding = new TypedBinding<MockViewModel, string>(
+ getter: mvm => mvm.Text,
+ setter: (mvm, s) => mvm.Text = s,
+ handlers: new [] {
+ new Tuple<Func<MockViewModel, object>, string> (mvm=>mvm, "Text")
+ }){Converter = converter};
+
+ var bindable = new MockBindable();
+ bindable.SetBinding(property, binding);
+ 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("TargetInt", typeof(int), typeof(MockBindable), 1, BindingMode.OneWayToSource);
+ var binding = new TypedBinding<MockViewModel, string>(
+ getter: mvm => mvm.Text,
+ setter: (mvm, s) => mvm.Text = s,
+ handlers: new [] {
+ new Tuple<Func<MockViewModel, object>, string> (mvm=>mvm, "Text")
+ }){Converter = converter};
+
+ var bindable = new MockBindable();
+ bindable.SetBinding(property, binding);
+ 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("Text", typeof(string), typeof(MockBindable), "Bar", BindingMode.OneWayToSource);
+ var binding = new TypedBinding<MockViewModel, string>(
+ getter: mvm => mvm.Text,
+ setter: (mvm, s) => mvm.Text = s,
+ handlers: new [] {
+ new Tuple<Func<MockViewModel, object>, string> (mvm=>mvm, "Text")
+ }){Converter = converter, ConverterParameter = "Foo"};
+
+ var bindable = new MockBindable();
+ bindable.SetBinding(property, binding);
+ 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("Text", typeof(string), typeof(MockBindable), "Bar", BindingMode.OneWayToSource);
+ var binding = new TypedBinding<MockViewModel, string>(
+ getter: mvm => mvm.Text,
+ setter: (mvm, s) => mvm.Text = s,
+ handlers: new [] {
+ new Tuple<Func<MockViewModel, object>, string> (mvm=>mvm, "Text")
+ }){Converter = converter};
+ var bindable = new MockBindable();
+ bindable.SetBinding(property, binding);
+ bindable.BindingContext = vm;
+
+ Assert.AreEqual("pt-PT", vm.Text);
+ }
+#endif
+
+ [Test]
+ public void SelfBindingConverter()
+ {
+ var converter = new TestConverter<int, string>();
+
+ var property = BindableProperty.Create("Text", typeof(string), typeof(MockBindable), "0");
+ var binding = new TypedBinding<int, int>(
+ mvm => mvm, (mvm, s) => mvm = s, null){Converter = converter};
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = 1;
+ bindable.SetBinding(property, binding);
+ 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 ("Value", typeof(float), typeof(MultiplePropertyBindable), 0f);
+
+ public float Value {
+ get { return (float)GetValue(ValueProperty); }
+ set { SetValue(ValueProperty, value); }
+ }
+
+ public static readonly BindableProperty DoneProperty = BindableProperty.Create("Done", typeof(int), typeof(MultiplePropertyBindable), 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();
+ var progressBinding = new TypedBinding<MultiplePropertyViewModel, float>(vm => vm.Progress, null, new [] {
+ new Tuple<Func<MultiplePropertyViewModel, object>, string> (vm=>vm, "Progress"),
+ }){Mode = BindingMode.OneWay};
+ var doneBinding = new TypedBinding<MultiplePropertyViewModel, int>(vm => vm.Done, (vm,d)=>vm.Done=d, new [] {
+ new Tuple<Func<MultiplePropertyViewModel, object>, string> (vm=>vm, "Done"),
+ }){Mode = BindingMode.OneWayToSource};
+
+ bindable.SetBinding(MultiplePropertyBindable.ValueProperty, progressBinding);
+ bindable.SetBinding(MultiplePropertyBindable.DoneProperty, doneBinding);
+ 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("Text", typeof(string), typeof(MockBindable), "foo bar");
+
+ var bindable = new MockBindable();
+ var binding = new TypedBinding<ComplexMockViewModel, string>(cvm => cvm.Model.Text, (cvm, t) => cvm.Model.Text = t, new [] {
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cvm=>cvm, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cvm=>cvm.Model, "Text")
+ }){Mode = BindingMode.OneWay};
+ bindable.SetBinding(property, binding);
+ 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("Text", typeof(string), typeof(MockBindable), "foo bar");
+ var binding = new TypedBinding<ComplexMockViewModel, string>(cvm => cvm.Model.Text, (cvm, t) => cvm.Model.Text = t, new [] {
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cvm=>cvm, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cvm=>cvm.Model, "Text")
+ }){Mode = BindingMode.OneWay};
+ var bindable = new MockBindable();
+ bindable.SetBinding(property, binding);
+ 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();
+ var binding = new TypedBinding<ComplexMockViewModel, int>(cmvm => cmvm.QueryCount, null, null){Mode = BindingMode.OneWay};
+ bindable.SetBinding(MultiplePropertyBindable.DoneProperty,binding);
+ 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();
+ var qcbinding = new TypedBinding<ComplexMockViewModel, int>(cmvm => cmvm.QueryCount, null, null){Mode = BindingMode.OneWay};
+ var textBinding = new TypedBinding<ComplexMockViewModel, string>(cmvm => cmvm.Text, null, null){Mode = BindingMode.OneWay};
+ bindable.SetBinding(MultiplePropertyBindable.DoneProperty, qcbinding);
+ bindable.SetBinding(MockBindable.TextProperty, textBinding);
+ 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;
+ var binding = new TypedBinding<MockViewModel, string>(mvm => mvm.Text, (mvm, s) => mvm.Text = s, new [] {
+ new Tuple<Func<MockViewModel, object>, string>(mvm=>mvm, "Text")
+ });
+ bindable.SetBinding(MockBindable.TextProperty, binding);
+
+ Assert.AreEqual(vm.Text, bindable.GetValue(MockBindable.TextProperty));
+
+ bindable.BindingContext = vm = new DerivedViewModel { Text = "text" };
+
+ Assert.AreEqual(vm.Text, bindable.GetValue(MockBindable.TextProperty));
+ }
+
+ [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() };
+ var binding = new TypedBinding<ComplexMockViewModel, string>(
+ cmvm => cmvm.Model.Text,
+ (cmvm, s) => cmvm.Model.Text = s, new [] {
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm, "Model"),
+ new Tuple<Func<ComplexMockViewModel, object>, string>(cmvm=>cmvm.Model, "Text"),
+ });
+
+ Assert.That(() => bindable.SetBinding(MockBindable.TextProperty, binding), Throws.Nothing);
+ Assert.That(log.Messages.Count, Is.EqualTo(0), "An error was logged");
+ }
+
+ [Test]
+ public void SetBindingContextBeforeContextBindingAndInnerBindings()
+ {
+ var label = new Label();
+ var view = new StackLayout { Children = { label } };
+
+ view.BindingContext = new Tuple<string, string>("Foo", "Bar");
+ var bindingItem1 = new TypedBinding<Tuple<string, string>, string>(s => s.Item1, null, null);
+ var bindingSelf = new TypedBinding<string, string>(s => s, null, null);
+ label.SetBinding(BindableObject.BindingContextProperty, bindingItem1);
+ label.SetBinding(Label.TextProperty, bindingSelf);
+
+ Assert.AreEqual("Foo", label.Text);
+ }
+
+ [Test]
+ public void SetBindingContextAndInnerBindingBeforeContextBinding()
+ {
+ var label = new Label();
+ var view = new StackLayout { Children = { label } };
+
+ view.BindingContext = new Tuple<string, string>("Foo", "Bar");
+ var bindingItem1 = new TypedBinding<Tuple<string, string>, string>(s => s.Item1, null, null);
+ var bindingSelf = new TypedBinding<string, string>(s => s, null, null);
+ label.SetBinding(Label.TextProperty, bindingSelf);
+ label.SetBinding(BindableObject.BindingContextProperty, bindingItem1);
+
+ Assert.AreEqual("Foo", label.Text);
+ }
+
+ [Test]
+ public void SetBindingContextAfterContextBindingAndInnerBindings()
+ {
+ var label = new Label();
+ var view = new StackLayout { Children = { label } };
+ var bindingItem1 = new TypedBinding<Tuple<string, string>, string>(s => s.Item1, null, null);
+ var bindingSelf = new TypedBinding<string, string>(s => s, null, null);
+
+ label.SetBinding(BindableObject.BindingContextProperty, bindingItem1);
+ label.SetBinding(Label.TextProperty, bindingSelf);
+ view.BindingContext = new Tuple<string, string>("Foo", "Bar");
+
+ Assert.AreEqual("Foo", label.Text);
+ }
+
+ [Test]
+ public void SetBindingContextAfterInnerBindingsAndContextBinding()
+ {
+ var label = new Label();
+ var view = new StackLayout { Children = { label } };
+ var bindingItem1 = new TypedBinding<Tuple<string, string>, string>(s => s.Item1, null, null);
+ var bindingSelf = new TypedBinding<string, string>(s => s, null, null);
+
+ label.SetBinding(Label.TextProperty, bindingItem1);
+ label.SetBinding(BindableObject.BindingContextProperty, bindingItem1);
+ view.BindingContext = new Tuple<string, string>("Foo", "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, new TypedBinding<MockViewModel, string>(mvm => mvm.Text, (mvm, s) => mvm.Text = s, null){Mode = 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, new TypedBinding<MockViewModel, string>(mvm => mvm.Text, (mvm, s) => mvm.Text = s, null){Mode = 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, new TypedBinding<ComplexMockViewModel, ComplexMockViewModel>(mvm => mvm.Model, null, null));
+ }, Throws.Nothing);
+
+ Assert.That(slider.Value, Is.EqualTo(Slider.ValueProperty.DefaultValue));
+ Assert.That(log.Messages.Count, Is.EqualTo(1), "No error logged");
+ }
+
+ class NullViewModel : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public string Foo {
+ get;
+ set;
+ }
+
+ public string Bar {
+ get;
+ set;
+ }
+
+ public void SignalAllPropertiesChanged(bool useNull)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs((useNull) ? null : String.Empty));
+ }
+ }
+
+ class MockBindable2 : MockBindable
+ {
+ public static readonly BindableProperty Text2Property = BindableProperty.Create("Text2", typeof(string), typeof(MockBindable2), "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, new TypedBinding<NullViewModel,string>(nvm => nvm.Foo, null, new [] {
+ new Tuple<Func<NullViewModel, object>, string>(nvm=>nvm,"Foo")
+ }));
+ bindable.SetBinding(MockBindable2.Text2Property, new TypedBinding<NullViewModel, string>(nvm => nvm.Bar, null, new [] {
+ new Tuple<Func<NullViewModel, object>, string>(nvm=>nvm,"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";
+ var bindingSelf = new TypedBinding<string, string>(s => s, null, null);
+ label.SetBinding(Label.TextProperty, bindingSelf);
+ Assert.AreEqual("bindingcontext", label.Text);
+
+ var bindingSelfSource= new TypedBinding<string, string>(s => s, null, null){Source = "bindingsource"};
+ label.SetBinding(Label.TextProperty, bindingSelfSource);
+ 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()
+ {
+ var viewmodel = new TestViewModel();
+
+ int i = 0;
+ Action create = null;
+ create = () => {
+ if (i++ < 1024) {
+ create();
+ return;
+ }
+
+ var button = new Button();
+ button.SetBinding(Button.TextProperty, new TypedBinding<TestViewModel,string>(vm => vm.Foo, (vm, s) => vm.Foo = s, new [] {
+ new Tuple<Func<TestViewModel, object>, string>(vm=>vm,"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 BindingDoesNotStayAliveForDeadTarget()
+ {
+ var viewModel = new TestViewModel();
+ WeakReference bindingRef = null;
+
+ int i = 0;
+ Action create = null;
+ create = () => {
+ if (i++ < 1024) {
+ create();
+ return;
+ }
+
+ var binding = new TypedBinding<TestViewModel, string>(vm => vm.Foo, (vm, s) => vm.Foo = s, new [] {
+ new Tuple<Func<TestViewModel, object>, string>(vm=>vm,"Foo")
+ });
+ var button = new Button();
+ button.SetBinding(Button.TextProperty, binding);
+ button.BindingContext = viewModel;
+
+ bindingRef = new WeakReference(binding);
+ binding = null;
+ };
+
+ create();
+
+ Assume.That(viewModel.InvocationListSize(), Is.EqualTo(1));
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+
+ Assert.IsFalse(bindingRef.IsAlive, "Binding should not be alive!");
+ }
+
+ [Test]
+ public void BindingCreatesSingleSubscription()
+ {
+ TestViewModel viewmodel = new TestViewModel();
+ var binding = new TypedBinding<TestViewModel, string>(vm => vm.Foo, (vm, s) => vm.Foo = s, new [] {
+ new Tuple<Func<TestViewModel, object>, string>(vm=>vm,"Foo")
+ });
+
+ var button = new Button();
+ button.SetBinding(Button.TextProperty, binding);
+ button.BindingContext = viewmodel;
+
+ Assert.That(viewmodel.InvocationListSize(), Is.EqualTo(1));
+ }
+
+ public class IndexedViewModel : INotifyPropertyChanged
+ {
+ Dictionary<string, object> dict = new Dictionary<string, object>();
+
+ [IndexerName("Item")]
+ 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();
+
+ var binding = new TypedBinding<Tuple<IndexedViewModel, object>, object>(
+ vm => vm.Item1["Foo"],
+ (vm, s) => vm.Item1 ["Foo"] = s,
+ new [] {
+ new Tuple<Func<Tuple<IndexedViewModel, object>, object>, string>(vm=>vm, "Item1"),
+ new Tuple<Func<Tuple<IndexedViewModel, object>, object>, string>(vm=>vm.Item1, "Item[Foo]"),
+ });
+
+ label.BindingContext = new Tuple<IndexedViewModel, object>(viewModel, new object());
+ label.SetBinding(Label.TextProperty, binding);
+ Assert.AreEqual(null, label.Text);
+
+ viewModel ["Foo"] = "Baz";
+
+ Assert.AreEqual("Baz", label.Text);
+ }
+
+ [Test]
+ [Ignore]
+ public void SpeedTestApply()
+ {
+
+ var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable));
+ var vm0 = new MockViewModel { Text = "Foo" };
+ var vm1 = new MockViewModel { Text = "Bar" };
+ var bindable = new MockBindable();
+
+ var it = 100000;
+
+ BindingBase binding = new TypedBinding<MockViewModel, string>(
+ getter: mvm => mvm.Text,
+ setter: (mvm, s) => mvm.Text = s,
+ handlers: new [] {
+ new Tuple<Func<MockViewModel, object>, string> (mvm=>mvm, "Text")
+ });
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ var swtb = Stopwatch.StartNew();
+ for (var i = 0; i < it; i++) {
+ binding.Apply(i % 2 == 0 ? vm0 : vm1, bindable, property);
+ binding.Unapply();
+ }
+ swtb.Stop();
+ Assert.AreEqual("Bar", bindable.GetValue(property));
+
+ binding = new TypedBinding<MockViewModel, string>(
+ getter: mvm => mvm.Text,
+ setter: (mvm, s) => mvm.Text = s,
+ handlers: null);
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ var swtbh = Stopwatch.StartNew();
+ for (var i = 0; i < it; i++) {
+ binding.Apply(i % 2 == 0 ? vm0 : vm1, bindable, property);
+ binding.Unapply();
+ }
+ swtbh.Stop();
+ Assert.AreEqual("Bar", bindable.GetValue(property));
+
+ binding = new Binding("Text");
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ var swb = Stopwatch.StartNew();
+ for (var i = 0; i < it; i++) {
+ binding.Apply(i % 2 == 0 ? vm0 : vm1, bindable, property);
+ binding.Unapply();
+ }
+ swb.Stop();
+ Assert.AreEqual("Bar", bindable.GetValue(property));
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ var swsv = Stopwatch.StartNew();
+ for (var i = 0; i < it; i++)
+ bindable.SetValue(property, (i % 2 == 0 ? vm0 : vm1).Text);
+ swsv.Stop();
+ Assert.AreEqual("Bar", bindable.GetValue(property));
+
+ Assert.Fail($"Applying {it} Typedbindings\t\t\t: {swtb.ElapsedMilliseconds}ms.\nApplying {it} Typedbindings (without INPC)\t: {swtbh.ElapsedMilliseconds}ms.\nApplying {it} Bindings\t\t\t: {swb.ElapsedMilliseconds}ms.\nSetting {it} values\t\t\t\t: {swsv.ElapsedMilliseconds}ms.");
+ }
+
+ [Test]
+ [Ignore]
+ public void SpeedTestSetBC()
+ {
+
+ var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable));
+ var vm0 = new MockViewModel { Text = "Foo" };
+ var vm1 = new MockViewModel { Text = "Bar" };
+ var bindable = new MockBindable();
+
+ var it = 100000;
+
+ BindingBase binding = new TypedBinding<MockViewModel, string>(
+ getter: mvm => mvm.Text,
+ setter: (mvm, s) => mvm.Text = s,
+ handlers: new [] {
+ new Tuple<Func<MockViewModel, object>, string> (mvm=>mvm, "Text")
+ });
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ bindable.SetBinding(property, binding);
+ var swtb = Stopwatch.StartNew();
+ for (var i = 0; i < it; i++)
+ bindable.BindingContext = i % 2 == 0 ? vm0 : vm1;
+ swtb.Stop();
+ //Assert.AreEqual("Bar", bindable.GetValue(property));
+
+ binding = new TypedBinding<MockViewModel, string>(
+ getter: mvm => mvm.Text,
+ setter: (mvm, s) => mvm.Text = s,
+ handlers: null);
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ bindable.SetBinding(property, binding);
+ var swtbh = Stopwatch.StartNew();
+ for (var i = 0; i < it; i++)
+ bindable.BindingContext = i % 2 == 0 ? vm0 : vm1;
+ swtbh.Stop();
+ Assert.AreEqual("Bar", bindable.GetValue(property));
+
+ binding = new Binding("Text");
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ bindable.SetBinding(property, binding);
+ var swb = Stopwatch.StartNew();
+ for (var i = 0; i < it; i++)
+ bindable.BindingContext = i % 2 == 0 ? vm0 : vm1;
+ swb.Stop();
+ Assert.AreEqual("Bar", bindable.GetValue(property));
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ bindable.SetBinding(property, binding);
+ var swsv = Stopwatch.StartNew();
+ for (var i = 0; i < it; i++)
+ bindable.SetValue(property, (i % 2 == 0 ? vm0 : vm1).Text);
+ swsv.Stop();
+ Assert.AreEqual("Bar", bindable.GetValue(property));
+
+ Assert.Fail($"Setting BC for {it} Typedbindings\t\t\t: {swtb.ElapsedMilliseconds}ms.\nSetting BC for {it} Typedbindings (without INPC)\t: {swtbh.ElapsedMilliseconds}ms.\nSetting BC for {it} Bindings\t\t\t\t: {swb.ElapsedMilliseconds}ms.\nSetting {it} values\t\t\t\t\t: {swsv.ElapsedMilliseconds}ms.");
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj b/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj
index e4a36d1a..243cbce3 100644
--- a/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj
+++ b/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj
@@ -180,6 +180,7 @@
<Compile Include="PinchGestureRecognizerTests.cs" />
<Compile Include="AppLinkEntryTests.cs" />
<Compile Include="NativeBindingTests.cs" />
+ <Compile Include="TypedBindingUnitTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj">