diff options
author | Stephane Delcroix <stephane@delcroix.org> | 2016-09-08 20:39:05 +0200 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-09-08 11:39:05 -0700 |
commit | 85426c5d9495eb1d55b3128bf97e50c68a73b53f (patch) | |
tree | 2f81e5868ce61eb90d15c6c51a354603b8395627 /Xamarin.Forms.Core.UnitTests | |
parent | 11326e1c182b3ff5c3d82c6ef7d09c193bc19891 (diff) | |
download | xamarin-forms-85426c5d9495eb1d55b3128bf97e50c68a73b53f.tar.gz xamarin-forms-85426c5d9495eb1d55b3128bf97e50c68a73b53f.tar.bz2 xamarin-forms-85426c5d9495eb1d55b3128bf97e50c68a73b53f.zip |
Native Bindings (#278)
* [C, I, A, W] Support Native Bindings
* fix tabs
Diffstat (limited to 'Xamarin.Forms.Core.UnitTests')
-rw-r--r-- | Xamarin.Forms.Core.UnitTests/NativeBindingTests.cs | 487 | ||||
-rw-r--r-- | Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj | 1 |
2 files changed, 488 insertions, 0 deletions
diff --git a/Xamarin.Forms.Core.UnitTests/NativeBindingTests.cs b/Xamarin.Forms.Core.UnitTests/NativeBindingTests.cs new file mode 100644 index 00000000..e7c7f9a8 --- /dev/null +++ b/Xamarin.Forms.Core.UnitTests/NativeBindingTests.cs @@ -0,0 +1,487 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using System.ComponentModel; +using System.Globalization; + +namespace Xamarin.Forms.Core.UnitTests +{ + public class MockNativeView + { + public IList<MockNativeView> SubViews { get; set; } + public string Foo { get; set; } + public int Bar { get; set; } + public string Baz { get; set; } + + public void FireBazChanged() + { + BazChanged?.Invoke(this, new TappedEventArgs(null)); + } + + public event EventHandler<TappedEventArgs> BazChanged; + + public event EventHandler SelectedColorChanged; + + MockNativeColor _selectedColor; + public MockNativeColor SelectedColor + { + get { return _selectedColor; } + set + { + if (_selectedColor == value) + return; + _selectedColor = value; + SelectedColorChanged?.Invoke(this, EventArgs.Empty); + } + } + + } + + class MockNativeViewWrapper : View + { + public MockNativeView NativeView { get; } + + public MockNativeViewWrapper(MockNativeView nativeView) + { + NativeView = nativeView; + nativeView.TransferbindablePropertiesToWrapper(this); + } + + protected override void OnBindingContextChanged() + { + NativeView.SetBindingContext(BindingContext, nv => nv.SubViews); + base.OnBindingContextChanged(); + } + + } + + public class MockNativeColor + { + + public MockNativeColor(Color color) + { + FormsColor = color; + } + + public Color FormsColor + { + get; + set; + } + } + + public static class MockNativeViewExtensions + { + public static View ToView(this MockNativeView nativeView) + { + return new MockNativeViewWrapper(nativeView); + } + + public static void SetBinding(this MockNativeView target, string targetProperty, BindingBase binding, string updateSourceEventName = null) + { + NativeBindingHelpers.SetBinding(target, targetProperty, binding, updateSourceEventName); + } + + internal static void SetBinding(this MockNativeView target, string targetProperty, BindingBase binding, INotifyPropertyChanged propertyChanged) + { + NativeBindingHelpers.SetBinding(target, targetProperty, binding, propertyChanged); + } + + public static void SetBinding(this MockNativeView target, BindableProperty targetProperty, BindingBase binding) + { + NativeBindingHelpers.SetBinding(target, targetProperty, binding); + } + + public static void SetValue(this MockNativeView target, BindableProperty targetProperty, object value) + { + NativeBindingHelpers.SetValue(target, targetProperty, value); + } + + public static void SetBindingContext(this MockNativeView target, object bindingContext, Func<MockNativeView, IEnumerable<MockNativeView>> getChild = null) + { + NativeBindingHelpers.SetBindingContext(target, bindingContext, getChild); + } + + internal static void TransferbindablePropertiesToWrapper(this MockNativeView target, MockNativeViewWrapper wrapper) + { + NativeBindingHelpers.TransferBindablePropertiesToWrapper(target, wrapper); + } + } + + class MockCustomColorConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is Color) + return new MockNativeColor((Color)value); + return value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is MockNativeColor) + return ((MockNativeColor)value).FormsColor; + return value; + } + } + + class MockINPC : INotifyPropertyChanged + { + public void FireINPC(object sender, string propertyName) + { + PropertyChanged?.Invoke(sender, new PropertyChangedEventArgs(propertyName)); + } + + public event PropertyChangedEventHandler PropertyChanged; + } + + class MockVMForNativeBinding : INotifyPropertyChanged + { + string fFoo; + public string FFoo { + get { return fFoo; } + set { + fFoo = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("FFoo")); + } + } + + int bBar; + public int BBar { + get { return bBar; } + set { + bBar = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("BBar")); + } + } + + Color cColor; + public Color CColor + { + get { return cColor; } + set + { + if (cColor == value) + return; + cColor = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("CColor")); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + } + + [TestFixture] + public class NativeBindingTests + { + [SetUp] + public void SetUp() + { + Device.PlatformServices = new MockPlatformServices(); + + //this should collect the ConditionalWeakTable + GC.Collect(); + } + + [Test] + public void SetOneWayBinding() + { + var nativeView = new MockNativeView(); + Assert.AreEqual(null, nativeView.Foo); + Assert.AreEqual(0, nativeView.Bar); + + nativeView.SetBinding("Foo", new Binding("FFoo", mode:BindingMode.OneWay)); + nativeView.SetBinding("Bar", new Binding("BBar", mode:BindingMode.OneWay)); + Assert.AreEqual(null, nativeView.Foo); + Assert.AreEqual(0, nativeView.Bar); + + nativeView.SetBindingContext(new { FFoo = "Foo", BBar = 42 }); + Assert.AreEqual("Foo", nativeView.Foo); + Assert.AreEqual(42, nativeView.Bar); + } + + [Test] + public void AttachedPropertiesAreTransferredFromTheBackpack() + { + var nativeView = new MockNativeView(); + nativeView.SetValue(Grid.ColumnProperty, 3); + nativeView.SetBinding(Grid.RowProperty, new Binding("foo")); + + var view = nativeView.ToView(); + view.BindingContext = new { foo = 42 }; + Assert.AreEqual(3, view.GetValue(Grid.ColumnProperty)); + Assert.AreEqual(42, view.GetValue(Grid.RowProperty)); + } + + [Test] + public void Set2WayBindings() + { + var nativeView = new MockNativeView(); + Assert.AreEqual(null, nativeView.Foo); + Assert.AreEqual(0, nativeView.Bar); + + var vm = new MockVMForNativeBinding(); + nativeView.SetBindingContext(vm); + var inpc = new MockINPC(); + nativeView.SetBinding("Foo", new Binding("FFoo", mode:BindingMode.TwoWay), inpc); + nativeView.SetBinding("Bar", new Binding("BBar", mode:BindingMode.TwoWay), inpc); + Assert.AreEqual(null, nativeView.Foo); + Assert.AreEqual(0, nativeView.Bar); + Assert.AreEqual(null, vm.FFoo); + Assert.AreEqual(0, vm.BBar); + + nativeView.Foo = "oof"; + inpc.FireINPC(nativeView, "Foo"); + nativeView.Bar = -42; + inpc.FireINPC(nativeView, "Bar"); + Assert.AreEqual("oof", nativeView.Foo); + Assert.AreEqual(-42, nativeView.Bar); + Assert.AreEqual("oof", vm.FFoo); + Assert.AreEqual(-42, vm.BBar); + + vm.FFoo = "foo"; + vm.BBar = 42; + Assert.AreEqual("foo", nativeView.Foo); + Assert.AreEqual(42, nativeView.Bar); + Assert.AreEqual("foo", vm.FFoo); + Assert.AreEqual(42, vm.BBar); + } + + [Test] + public void Set2WayBindingsWithUpdateSourceEvent() + { + var nativeView = new MockNativeView(); + Assert.AreEqual(null, nativeView.Baz); + + var vm = new MockVMForNativeBinding(); + nativeView.SetBindingContext(vm); + + nativeView.SetBinding("Baz", new Binding("FFoo", mode: BindingMode.TwoWay), "BazChanged"); + Assert.AreEqual(null, nativeView.Baz); + Assert.AreEqual(null, vm.FFoo); + + nativeView.Baz = "oof"; + nativeView.FireBazChanged(); + Assert.AreEqual("oof", nativeView.Baz); + Assert.AreEqual("oof", vm.FFoo); + + vm.FFoo = "foo"; + Assert.AreEqual("foo", nativeView.Baz); + Assert.AreEqual("foo", vm.FFoo); + } + + [Test] + public void Set2WayBindingsWithUpdateSourceEventInBindingObject() + { + var nativeView = new MockNativeView(); + Assert.AreEqual(null, nativeView.Baz); + + var vm = new MockVMForNativeBinding(); + nativeView.SetBindingContext(vm); + + nativeView.SetBinding("Baz", new Binding("FFoo", mode: BindingMode.TwoWay) { UpdateSourceEventName = "BazChanged"}); + Assert.AreEqual(null, nativeView.Baz); + Assert.AreEqual(null, vm.FFoo); + + nativeView.Baz = "oof"; + nativeView.FireBazChanged(); + Assert.AreEqual("oof", nativeView.Baz); + Assert.AreEqual("oof", vm.FFoo); + + vm.FFoo = "foo"; + Assert.AreEqual("foo", nativeView.Baz); + Assert.AreEqual("foo", vm.FFoo); + } + + [Test] + public void NativeViewsAreCollected() + { + WeakReference wr = null; + + int i = 0; + Action create = null; + create = () => { + if (i++ < 1024) { + create(); + return; + } + + var nativeView = new MockNativeView(); + nativeView.SetBinding("fooBar", new Binding("Foo", BindingMode.TwoWay)); + nativeView.SetBinding("Baz", new Binding("Qux", BindingMode.TwoWay), "BazChanged"); + + wr = new WeakReference(nativeView); + nativeView = null; + + }; + + create(); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + Assert.False(wr.IsAlive); + } + + [Test] + public void ProxiesAreCollected() + { + WeakReference wr = null; + + int i = 0; + Action create = null; + create = () => { + if (i++ < 1024) { + create(); + return; + } + + var nativeView = new MockNativeView(); + nativeView.SetBinding("fooBar", new Binding("Foo", BindingMode.TwoWay)); + nativeView.SetBinding("Baz", new Binding("Qux", BindingMode.TwoWay), "BazChanged"); + + NativeBindingHelpers.BindableObjectProxy<MockNativeView> proxy; + if (!NativeBindingHelpers.BindableObjectProxy<MockNativeView>.BindableObjectProxies.TryGetValue(nativeView, out proxy)) + Assert.Fail(); + + wr = new WeakReference(proxy); + nativeView = null; + }; + + create(); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + Assert.False(wr.IsAlive); + } + + [Test] + public void SetBindingContextToSubviews() + { + var nativeView = new MockNativeView { SubViews = new List<MockNativeView> ()}; + var nativeViewChild = new MockNativeView(); + + nativeViewChild.SetBinding("Foo", new Binding("FFoo", mode: BindingMode.OneWay)); + nativeViewChild.SetBinding("Bar", new Binding("BBar", mode: BindingMode.OneWay)); + + nativeView.SubViews.Add(nativeViewChild); + + var vm = new MockVMForNativeBinding(); + nativeView.SetBindingContext(vm, v => v.SubViews); + + Assert.AreEqual(null, nativeViewChild.Foo); + Assert.AreEqual(0, nativeViewChild.Bar); + + nativeView.SetBindingContext(new { FFoo = "Foo", BBar = 42 }, v => v.SubViews); + Assert.AreEqual("Foo", nativeViewChild.Foo); + Assert.AreEqual(42, nativeViewChild.Bar); + } + + [Test] + public void ThrowsNeedsConverter() + { + var nativeView = new MockNativeView(); + Assert.AreEqual(null, nativeView.Foo); + Assert.AreEqual(0, nativeView.Bar); + var vm = new MockVMForNativeBinding(); + nativeView.SetBinding("SelectedColor", new Binding("CColor")); + Assert.Throws<ArgumentException>(() => nativeView.SetBindingContext(vm)); + } + + [Test] + public void TestConverterDoesNotThrow() + { + var nativeView = new MockNativeView(); + Assert.AreEqual(null, nativeView.Foo); + Assert.AreEqual(0, nativeView.Bar); + var vm = new MockVMForNativeBinding(); + var converter = new MockCustomColorConverter(); + nativeView.SetBinding("SelectedColor", new Binding("CColor", converter: converter)); + Assert.DoesNotThrow(() => nativeView.SetBindingContext(vm)); + } + + [Test] + public void TestConverterWorks() + { + var nativeView = new MockNativeView(); + Assert.AreEqual(null, nativeView.Foo); + Assert.AreEqual(0, nativeView.Bar); + var vm = new MockVMForNativeBinding(); + vm.CColor = Color.Red; + var converter = new MockCustomColorConverter(); + nativeView.SetBinding("SelectedColor", new Binding("CColor", converter: converter)); + nativeView.SetBindingContext(vm); + Assert.AreEqual(vm.CColor, nativeView.SelectedColor.FormsColor); + } + + [Test] + public void TestConverter2WayWorks() + { + var nativeView = new MockNativeView(); + Assert.AreEqual(null, nativeView.Foo); + Assert.AreEqual(0, nativeView.Bar); + var inpc = new MockINPC(); + var vm = new MockVMForNativeBinding(); + vm.CColor = Color.Red; + var converter = new MockCustomColorConverter(); + nativeView.SetBinding("SelectedColor", new Binding("CColor", BindingMode.TwoWay, converter), inpc); + nativeView.SetBindingContext(vm); + Assert.AreEqual(vm.CColor, nativeView.SelectedColor.FormsColor); + + var newFormsColor = Color.Blue; + var newColor = new MockNativeColor(newFormsColor); + nativeView.SelectedColor = newColor; + inpc.FireINPC(nativeView, nameof(nativeView.SelectedColor)); + + Assert.AreEqual(newFormsColor, vm.CColor); + + } + + [Test] + public void Binding2WayWithConvertersDoNotLoop() + { + var nativeView = new MockNativeView(); + int count = 0; + + nativeView.SelectedColorChanged += (o, e) => { + if (++count > 5) + Assert.Fail("Probable loop detected"); + }; + + var vm = new MockVMForNativeBinding { CColor = Color.Red }; + + nativeView.SetBinding("SelectedColor", new Binding("CColor", BindingMode.TwoWay, new MockCustomColorConverter()), "SelectedColorChanged"); + nativeView.SetBindingContext(vm); + + Assert.AreEqual(count, 1); + } + + [Test] + public void ThrowsOnMissingProperty() + { + var nativeView = new MockNativeView(); + nativeView.SetBinding("Qux", new Binding("Foo")); + Assert.Throws<InvalidOperationException>(()=>nativeView.SetBindingContext(new { Foo = 42 })); + } + + [Test] + public void ThrowsOnMissingEvent() + { + var nativeView = new MockNativeView(); + Assert.Throws<ArgumentException>(()=>nativeView.SetBinding("Foo", new Binding("Foo", BindingMode.TwoWay), "missingEvent")); + } + + [Test] + public void OneWayToSourceAppliedOnSetBC() + { + var nativeView = new MockNativeView { Foo = "foobar" }; + nativeView.SetBinding("Foo", new Binding("FFoo", BindingMode.OneWayToSource)); + var vm = new MockVMForNativeBinding { FFoo = "qux" }; + nativeView.SetBindingContext(vm); + Assert.AreEqual("foobar", vm.FFoo); + } + } +}
\ No newline at end of file diff --git a/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj b/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj index 74544605..2c7490d1 100644 --- a/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj +++ b/Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj @@ -178,6 +178,7 @@ <Compile Include="TriggerTests.cs" /> <Compile Include="PinchGestureRecognizerTests.cs" /> <Compile Include="AppLinkEntryTests.cs" /> + <Compile Include="NativeBindingTests.cs" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj"> |