diff options
author | Stephane Delcroix <stephane@delcroix.org> | 2016-11-15 20:39:48 +0100 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-11-15 11:39:48 -0800 |
commit | a6bbed029c64d2d64b74eeb67e27a099abf70664 (patch) | |
tree | 551c3924c055e2d39592b3f1c726cca46924dd73 /Xamarin.Forms.Core.UnitTests/BindingBaseUnitTests.cs | |
parent | 14e21dcebd4a706aaa5eed384b142957d84df002 (diff) | |
download | xamarin-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.cs | 546 |
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>()); } + } } |