summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Core.UnitTests/BindingBaseUnitTests.cs
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/BindingBaseUnitTests.cs
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/BindingBaseUnitTests.cs')
-rw-r--r--Xamarin.Forms.Core.UnitTests/BindingBaseUnitTests.cs546
1 files changed, 535 insertions, 11 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>());
}
+
}
}