summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Core.UnitTests
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Core.UnitTests')
-rw-r--r--Xamarin.Forms.Core.UnitTests/AbsoluteLayoutTests.cs265
-rw-r--r--Xamarin.Forms.Core.UnitTests/AnimatableKeyTests.cs72
-rw-r--r--Xamarin.Forms.Core.UnitTests/BaseTestFixture.cs32
-rw-r--r--Xamarin.Forms.Core.UnitTests/BehaviorTest.cs110
-rw-r--r--Xamarin.Forms.Core.UnitTests/BindableObjectExtensionTests.cs72
-rw-r--r--Xamarin.Forms.Core.UnitTests/BindableObjectUnitTests.cs1326
-rw-r--r--Xamarin.Forms.Core.UnitTests/BindablePropertyUnitTests.cs135
-rw-r--r--Xamarin.Forms.Core.UnitTests/BindingBaseUnitTests.cs227
-rw-r--r--Xamarin.Forms.Core.UnitTests/BindingExpressionTests.cs83
-rw-r--r--Xamarin.Forms.Core.UnitTests/BindingTests.cs75
-rw-r--r--Xamarin.Forms.Core.UnitTests/BindingTypeConverterTests.cs27
-rw-r--r--Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs2649
-rw-r--r--Xamarin.Forms.Core.UnitTests/BoxViewUnitTests.cs42
-rw-r--r--Xamarin.Forms.Core.UnitTests/ButtonUnitTest.cs181
-rw-r--r--Xamarin.Forms.Core.UnitTests/CarouselPageTests.cs29
-rw-r--r--Xamarin.Forms.Core.UnitTests/CellTests.cs190
-rw-r--r--Xamarin.Forms.Core.UnitTests/ColorUnitTests.cs268
-rw-r--r--Xamarin.Forms.Core.UnitTests/CommandSourceTests.cs184
-rw-r--r--Xamarin.Forms.Core.UnitTests/CommandTests.cs155
-rw-r--r--Xamarin.Forms.Core.UnitTests/ContentFormUnitTests.cs68
-rw-r--r--Xamarin.Forms.Core.UnitTests/ContentViewUnitTest.cs393
-rw-r--r--Xamarin.Forms.Core.UnitTests/ContraintTypeConverterTests.cs16
-rw-r--r--Xamarin.Forms.Core.UnitTests/ControlTemplateTests.cs181
-rw-r--r--Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs79
-rw-r--r--Xamarin.Forms.Core.UnitTests/DataTemplateTests.cs119
-rw-r--r--Xamarin.Forms.Core.UnitTests/DataTriggerTests.cs146
-rw-r--r--Xamarin.Forms.Core.UnitTests/DatePickerUnitTest.cs178
-rw-r--r--Xamarin.Forms.Core.UnitTests/DependencyServiceTests.cs128
-rw-r--r--Xamarin.Forms.Core.UnitTests/DistanceTests.cs197
-rw-r--r--Xamarin.Forms.Core.UnitTests/DynamicBindingContextTests.cs314
-rw-r--r--Xamarin.Forms.Core.UnitTests/DynamicResourceTests.cs153
-rw-r--r--Xamarin.Forms.Core.UnitTests/EasingTests.cs32
-rw-r--r--Xamarin.Forms.Core.UnitTests/EditorTests.cs34
-rw-r--r--Xamarin.Forms.Core.UnitTests/EffectTests.cs121
-rw-r--r--Xamarin.Forms.Core.UnitTests/ElementTests.cs176
-rw-r--r--Xamarin.Forms.Core.UnitTests/EntryCellTests.cs82
-rw-r--r--Xamarin.Forms.Core.UnitTests/EntryUnitTests.cs55
-rw-r--r--Xamarin.Forms.Core.UnitTests/EventTriggerTest.cs73
-rw-r--r--Xamarin.Forms.Core.UnitTests/FluentTests.cs8
-rw-r--r--Xamarin.Forms.Core.UnitTests/FontUnitTests.cs129
-rw-r--r--Xamarin.Forms.Core.UnitTests/FormattedStringTests.cs114
-rw-r--r--Xamarin.Forms.Core.UnitTests/FrameUnitTests.cs291
-rw-r--r--Xamarin.Forms.Core.UnitTests/GeocoderUnitTests.cs42
-rw-r--r--Xamarin.Forms.Core.UnitTests/GridLengthTypeConverterTests.cs53
-rw-r--r--Xamarin.Forms.Core.UnitTests/GridTests.cs1569
-rw-r--r--Xamarin.Forms.Core.UnitTests/GroupViewUnitTests.cs292
-rw-r--r--Xamarin.Forms.Core.UnitTests/ImageSourceTests.cs151
-rw-r--r--Xamarin.Forms.Core.UnitTests/ImageTests.cs261
-rw-r--r--Xamarin.Forms.Core.UnitTests/Images/crimson.jpgbin0 -> 79109 bytes
-rw-r--r--Xamarin.Forms.Core.UnitTests/KeyboardTests.cs52
-rw-r--r--Xamarin.Forms.Core.UnitTests/LabelTests.cs296
-rw-r--r--Xamarin.Forms.Core.UnitTests/LayoutOptionsUnitTests.cs22
-rw-r--r--Xamarin.Forms.Core.UnitTests/ListProxyTests.cs426
-rw-r--r--Xamarin.Forms.Core.UnitTests/ListViewTests.cs1490
-rw-r--r--Xamarin.Forms.Core.UnitTests/MapSpanTests.cs44
-rw-r--r--Xamarin.Forms.Core.UnitTests/MapTests.cs147
-rw-r--r--Xamarin.Forms.Core.UnitTests/MarginTests.cs134
-rw-r--r--Xamarin.Forms.Core.UnitTests/MasterDetailFormUnitTests.cs415
-rw-r--r--Xamarin.Forms.Core.UnitTests/MenuItemTests.cs75
-rw-r--r--Xamarin.Forms.Core.UnitTests/MessagingCenterTests.cs191
-rw-r--r--Xamarin.Forms.Core.UnitTests/MockPlatformServices.cs270
-rw-r--r--Xamarin.Forms.Core.UnitTests/MockViewModel.cs30
-rw-r--r--Xamarin.Forms.Core.UnitTests/MotionTests.cs118
-rw-r--r--Xamarin.Forms.Core.UnitTests/MultiPageTests.cs805
-rw-r--r--Xamarin.Forms.Core.UnitTests/MultiTriggerTests.cs134
-rw-r--r--Xamarin.Forms.Core.UnitTests/NavigationMenuUnitTests.cs191
-rw-r--r--Xamarin.Forms.Core.UnitTests/NavigationModelTests.cs304
-rw-r--r--Xamarin.Forms.Core.UnitTests/NavigationProxyTests.cs188
-rw-r--r--Xamarin.Forms.Core.UnitTests/NavigationUnitTest.cs393
-rw-r--r--Xamarin.Forms.Core.UnitTests/NotifiedPropertiesTests.cs214
-rw-r--r--Xamarin.Forms.Core.UnitTests/NotifyCollectionChangedEventArgsExtensionsTests.cs73
-rw-r--r--Xamarin.Forms.Core.UnitTests/ObservableWrapperTests.cs402
-rw-r--r--Xamarin.Forms.Core.UnitTests/OpenGLViewUnitTests.cs20
-rw-r--r--Xamarin.Forms.Core.UnitTests/PageTests.cs498
-rw-r--r--Xamarin.Forms.Core.UnitTests/PanGestureRecognizerUnitTests.cs99
-rw-r--r--Xamarin.Forms.Core.UnitTests/PickerTests.cs59
-rw-r--r--Xamarin.Forms.Core.UnitTests/PinTests.cs105
-rw-r--r--Xamarin.Forms.Core.UnitTests/PinchGestureRecognizerTests.cs75
-rw-r--r--Xamarin.Forms.Core.UnitTests/PointTests.cs121
-rw-r--r--Xamarin.Forms.Core.UnitTests/PositionTests.cs89
-rw-r--r--Xamarin.Forms.Core.UnitTests/ProgressBarTests.cs50
-rw-r--r--Xamarin.Forms.Core.UnitTests/Properties/AssemblyInfo.cs27
-rw-r--r--Xamarin.Forms.Core.UnitTests/RectangleUnitTests.cs208
-rw-r--r--Xamarin.Forms.Core.UnitTests/RegistrarUnitTests.cs149
-rw-r--r--Xamarin.Forms.Core.UnitTests/RelativeLayoutTests.cs516
-rw-r--r--Xamarin.Forms.Core.UnitTests/ResourceDictionaryTests.cs256
-rw-r--r--Xamarin.Forms.Core.UnitTests/ScrollViewUnitTests.cs403
-rw-r--r--Xamarin.Forms.Core.UnitTests/SearchBarUnitTests.cs112
-rw-r--r--Xamarin.Forms.Core.UnitTests/SizeTests.cs93
-rw-r--r--Xamarin.Forms.Core.UnitTests/SliderUnitTests.cs112
-rw-r--r--Xamarin.Forms.Core.UnitTests/StackLayoutUnitTests.cs619
-rw-r--r--Xamarin.Forms.Core.UnitTests/StepperUnitTests.cs176
-rw-r--r--Xamarin.Forms.Core.UnitTests/StyleTests.cs670
-rw-r--r--Xamarin.Forms.Core.UnitTests/SwitchCellTests.cs62
-rw-r--r--Xamarin.Forms.Core.UnitTests/SwitchUnitTests.cs48
-rw-r--r--Xamarin.Forms.Core.UnitTests/TabbedFormUnitTests.cs32
-rw-r--r--Xamarin.Forms.Core.UnitTests/TableModelTests.cs87
-rw-r--r--Xamarin.Forms.Core.UnitTests/TableRootUnitTests.cs103
-rw-r--r--Xamarin.Forms.Core.UnitTests/TableSectionTests.cs225
-rw-r--r--Xamarin.Forms.Core.UnitTests/TableViewUnitTests.cs84
-rw-r--r--Xamarin.Forms.Core.UnitTests/TapGestureRecognizerTests.cs33
-rw-r--r--Xamarin.Forms.Core.UnitTests/TemplatedItemsListTests.cs1073
-rw-r--r--Xamarin.Forms.Core.UnitTests/TextCellTests.cs129
-rw-r--r--Xamarin.Forms.Core.UnitTests/ThicknessTests.cs132
-rw-r--r--Xamarin.Forms.Core.UnitTests/TimePickerUnitTest.cs43
-rw-r--r--Xamarin.Forms.Core.UnitTests/ToolbarItemTests.cs11
-rw-r--r--Xamarin.Forms.Core.UnitTests/ToolbarTrackerTests.cs194
-rw-r--r--Xamarin.Forms.Core.UnitTests/ToolbarUnitTests.cs144
-rw-r--r--Xamarin.Forms.Core.UnitTests/TriggerTests.cs116
-rw-r--r--Xamarin.Forms.Core.UnitTests/TypeUnitTests.cs23
-rw-r--r--Xamarin.Forms.Core.UnitTests/UnitPlatform.cs40
-rw-r--r--Xamarin.Forms.Core.UnitTests/UriImageSourceTests.cs145
-rw-r--r--Xamarin.Forms.Core.UnitTests/ViewCellTests.cs65
-rw-r--r--Xamarin.Forms.Core.UnitTests/ViewUnitTests.cs868
-rw-r--r--Xamarin.Forms.Core.UnitTests/WebViewUnitTests.cs93
-rw-r--r--Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj206
-rw-r--r--Xamarin.Forms.Core.UnitTests/packages.config4
117 files changed, 26408 insertions, 0 deletions
diff --git a/Xamarin.Forms.Core.UnitTests/AbsoluteLayoutTests.cs b/Xamarin.Forms.Core.UnitTests/AbsoluteLayoutTests.cs
new file mode 100644
index 00000000..3faec22e
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/AbsoluteLayoutTests.cs
@@ -0,0 +1,265 @@
+using System;
+using NUnit.Framework;
+using NUnit.Framework.Constraints;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class AbsoluteLayoutTests : BaseTestFixture
+ {
+
+ [Test]
+ public void Constructor ()
+ {
+ var abs = new AbsoluteLayout {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ Assert.That (abs.Children, Is.Empty);
+
+ var sizeReq = abs.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity);
+ Assert.AreEqual (Size.Zero, sizeReq.Request);
+ Assert.AreEqual (Size.Zero, sizeReq.Minimum);
+ }
+
+ [Test]
+ public void AbsolutePositionAndSize ()
+ {
+ var abs = new AbsoluteLayout {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ var child = new View {IsPlatformEnabled = true};
+
+ abs.Children.Add (child, new Rectangle (10, 20, 30, 40));
+
+ abs.Layout (new Rectangle (0, 0, 100, 100));
+
+ Assert.AreEqual (new Rectangle (10, 20, 30, 40), child.Bounds);
+ }
+
+ [Test]
+ public void AbsolutePositionRelativeSize ()
+ {
+ var abs = new AbsoluteLayout {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ var child = new View {IsPlatformEnabled = true};
+
+
+ abs.Children.Add (child, new Rectangle (10, 20, 0.4, 0.5), AbsoluteLayoutFlags.SizeProportional);
+
+ abs.Layout (new Rectangle (0, 0, 100, 100));
+
+ Assert.That (child.X, Is.EqualTo (10));
+ Assert.That (child.Y, Is.EqualTo (20));
+ Assert.That (child.Width, Is.EqualTo (40).Within (0.0001));
+ Assert.That (child.Height, Is.EqualTo (50).Within (0.0001));
+ }
+
+ [TestCase (30, 40, 0.2, 0.3)]
+ [TestCase (35, 45, 0.5, 0.5)]
+ [TestCase (35, 45, 0, 0)]
+ [TestCase (35, 45, 1, 1)]
+ public void RelativePositionAbsoluteSize (double width, double height, double relX, double relY)
+ {
+ var abs = new AbsoluteLayout {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ var child = new View {IsPlatformEnabled = true};
+
+ abs.Children.Add (child, new Rectangle (relX, relY, width, height), AbsoluteLayoutFlags.PositionProportional);
+
+ abs.Layout (new Rectangle (0, 0, 100, 100));
+
+ double expectedX = Math.Round ((100 - width) * relX);
+ double expectedY = Math.Round ((100 - height) * relY);
+ Assert.That (child.X, Is.EqualTo (expectedX).Within (0.0001));
+ Assert.That (child.Y, Is.EqualTo (expectedY).Within (0.0001));
+ Assert.That (child.Width, Is.EqualTo (width));
+ Assert.That (child.Height, Is.EqualTo (height));
+ }
+
+ [Test]
+ public void RelativePositionRelativeSize ([Values (0.0, 0.2, 0.5, 1.0)] double relX, [Values (0.0, 0.2, 0.5, 1.0)] double relY, [Values (0.0, 0.2, 0.5, 1.0)] double relHeight, [Values (0.0, 0.2, 0.5, 1.0)] double relWidth)
+ {
+ var abs = new AbsoluteLayout {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ var child = new View {
+ IsPlatformEnabled = true
+ };
+ abs.Children.Add (child, new Rectangle(relX, relY, relWidth, relHeight), AbsoluteLayoutFlags.All);
+ abs.Layout (new Rectangle(0, 0, 100, 100));
+
+ double expectedWidth = Math.Round (100 * relWidth);
+ double expectedHeight = Math.Round (100 * relHeight);
+ double expectedX = Math.Round ((100 - expectedWidth) * relX);
+ double expectedY = Math.Round ((100 - expectedHeight) * relY);
+ Assert.That (child.X, Is.EqualTo (expectedX).Within (0.0001));
+ Assert.That (child.Y, Is.EqualTo (expectedY).Within (0.0001));
+ Assert.That (child.Width, Is.EqualTo (expectedWidth).Within (0.0001));
+ Assert.That (child.Height, Is.EqualTo (expectedHeight).Within (0.0001));
+ }
+
+ [Test]
+ public void SizeRequestWithNormalChild ()
+ {
+ var abs = new AbsoluteLayout {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ var child = new View ();
+
+ // ChildSizeReq == 100x20
+ abs.Children.Add (child, new Rectangle (10, 20, 30, 40));
+
+ var sizeReq = abs.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity);
+
+ Assert.AreEqual (new Size (40, 60), sizeReq.Request);
+ Assert.AreEqual (new Size (40, 60), sizeReq.Minimum);
+ }
+
+ [Test]
+ public void SizeRequestWithRelativePositionChild ()
+ {
+ var abs = new AbsoluteLayout {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ var child = new View ();
+
+ // ChildSizeReq == 100x20
+ abs.Children.Add (child, new Rectangle(0.5, 0.5, 30, 40), AbsoluteLayoutFlags.PositionProportional);
+
+ var sizeReq = abs.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity);
+
+ Assert.AreEqual (new Size (30, 40), sizeReq.Request);
+ Assert.AreEqual (new Size (30, 40), sizeReq.Minimum);
+ }
+
+ [Test]
+ public void SizeRequestWithRelativeChild ()
+ {
+ var abs = new AbsoluteLayout {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ var child = new View {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ // ChildSizeReq == 100x20
+ abs.Children.Add (child, new Rectangle(0.5, 0.5, 0.5, 0.5), AbsoluteLayoutFlags.All);
+
+ var sizeReq = abs.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity);
+
+ Assert.AreEqual (new Size (200, 40), sizeReq.Request);
+ Assert.AreEqual (new Size (0, 0), sizeReq.Minimum);
+ }
+
+ [Test]
+ public void SizeRequestWithRelativeSizeChild ()
+ {
+ var abs = new AbsoluteLayout {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ var child = new View {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ // ChildSizeReq == 100x20
+ abs.Children.Add (child, new Rectangle(10, 20, 0.5, 0.5), AbsoluteLayoutFlags.SizeProportional);
+
+ var sizeReq = abs.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity);
+
+ Assert.AreEqual (new Size (210, 60), sizeReq.Request);
+ Assert.AreEqual (new Size (10, 20), sizeReq.Minimum);
+ }
+
+ [Test]
+ public void MeasureInvalidatedFiresWhenFlagsChanged ()
+ {
+ var abs = new AbsoluteLayout {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ var child = new View {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ abs.Children.Add (child, new Rectangle (1, 1, 100, 100));
+
+ bool fired = false;
+ abs.MeasureInvalidated += (sender, args) => fired = true;
+
+ AbsoluteLayout.SetLayoutFlags (child, AbsoluteLayoutFlags.PositionProportional);
+
+ Assert.True (fired);
+ }
+
+ [Test]
+ public void MeasureInvalidatedFiresWhenBoundsChanged ()
+ {
+ var abs = new AbsoluteLayout {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ var child = new View {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ abs.Children.Add (child, new Rectangle (1, 1, 100, 100));
+
+ bool fired = false;
+ abs.MeasureInvalidated += (sender, args) => fired = true;
+
+ AbsoluteLayout.SetLayoutBounds (child, new Rectangle (2, 2, 200, 200));
+
+ Assert.True (fired);
+ }
+
+ [Test]
+ public void TestBoundsTypeConverter ()
+ {
+ var converter = new BoundsTypeConverter ();
+
+ Assert.IsTrue (converter.CanConvertFrom (typeof(string)));
+ Assert.AreEqual (new Rectangle (3, 4, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize), converter.ConvertFromInvariantString ("3, 4"));
+ Assert.AreEqual (new Rectangle (3, 4, 20, 30), converter.ConvertFromInvariantString ("3, 4, 20, 30"));
+ Assert.AreEqual (new Rectangle (3, 4, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize), converter.ConvertFromInvariantString ("3, 4, AutoSize, AutoSize"));
+ Assert.AreEqual (new Rectangle (3, 4, AbsoluteLayout.AutoSize, 30), converter.ConvertFromInvariantString ("3, 4, AutoSize, 30"));
+ Assert.AreEqual (new Rectangle (3, 4, 20, AbsoluteLayout.AutoSize), converter.ConvertFromInvariantString ("3, 4, 20, AutoSize"));
+ }
+
+ [Test]
+ public void TurkeyTestBoundsTypeConverter ()
+ {
+ var converter = new BoundsTypeConverter ();
+
+ var autoSize = "AutoSize";
+
+ Assert.AreEqual (new Rectangle (3.3, 4.4, AbsoluteLayout.AutoSize, AbsoluteLayout.AutoSize), converter.ConvertFromInvariantString ("3.3, 4.4, " + autoSize + ", AutoSize"));
+ Assert.AreEqual (new Rectangle (3.3, 4.4, 5.5, 6.6), converter.ConvertFromInvariantString ("3.3, 4.4, 5.5, 6.6"));
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/AnimatableKeyTests.cs b/Xamarin.Forms.Core.UnitTests/AnimatableKeyTests.cs
new file mode 100644
index 00000000..385ab1fc
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/AnimatableKeyTests.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class AnimatableKeyTests
+ {
+ class FakeAnimatable : IAnimatable
+ {
+ public void BatchBegin ()
+ {
+
+ }
+
+ public void BatchCommit ()
+ {
+
+ }
+ }
+
+ [Test]
+ public void KeysWithDifferentHandlesAreNotEqual ()
+ {
+ var animatable = new FakeAnimatable();
+
+ var key1 = new AnimatableKey(animatable, "handle1");
+ var key2 = new AnimatableKey(animatable, "handle2");
+
+ Assert.AreNotEqual (key1, key2);
+ }
+
+ [Test]
+ public void KeysWithDifferentAnimatablesAreNotEqual ()
+ {
+ var animatable1 = new FakeAnimatable();
+ var animatable2 = new FakeAnimatable();
+
+ var key1 = new AnimatableKey(animatable1, "handle");
+ var key2 = new AnimatableKey(animatable2, "handle");
+
+ Assert.AreNotEqual (key1, key2);
+ }
+
+ [Test]
+ public void KeysWithSameAnimatableAndHandleAreEqual ()
+ {
+ var animatable = new FakeAnimatable();
+
+ var key1 = new AnimatableKey(animatable, "handle");
+ var key2 = new AnimatableKey(animatable, "handle");
+
+ Assert.AreEqual (key1, key2);
+ }
+
+ [Test]
+ public void ThrowsWhenKeysWithSameAnimatableAdded ()
+ {
+ var animatable = new FakeAnimatable();
+
+ var key1 = new AnimatableKey(animatable, "handle");
+ var key2 = new AnimatableKey(animatable, "handle");
+
+ var dict = new Dictionary<AnimatableKey, object> { { key1, new object () } };
+
+ Assert.Throws<ArgumentException> (() => {
+ dict.Add (key2, new object ());
+ });
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/BaseTestFixture.cs b/Xamarin.Forms.Core.UnitTests/BaseTestFixture.cs
new file mode 100644
index 00000000..d3a57ea3
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/BaseTestFixture.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Globalization;
+using System.Threading;
+
+using NUnit.Framework;
+using NUnit.Framework.Constraints;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ public class BaseTestFixture
+ {
+ [SetUp]
+ public virtual void Setup ()
+ {
+#if !WINDOWS_PHONE
+ var culture = Environment.GetEnvironmentVariable ("UNIT_TEST_CULTURE");
+
+ if (!string.IsNullOrEmpty (culture)) {
+ var thead = Thread.CurrentThread;
+ thead.CurrentCulture = new CultureInfo (culture);
+ }
+#endif
+ Console.WriteLine ("Current culture: {0}", Thread.CurrentThread.CurrentCulture.Name);
+ }
+
+ [TearDown]
+ public virtual void TearDown ()
+ {
+
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/BehaviorTest.cs b/Xamarin.Forms.Core.UnitTests/BehaviorTest.cs
new file mode 100644
index 00000000..78e0cfb0
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/BehaviorTest.cs
@@ -0,0 +1,110 @@
+using System;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ internal class MockBehavior<T> : Behavior<T> where T:BindableObject
+ {
+ public bool attached;
+ public bool detached;
+
+ protected override void OnAttachedTo (BindableObject bindable)
+ {
+ base.OnAttachedTo (bindable);
+ attached = true;
+ AssociatedObject = bindable;
+ }
+
+ protected override void OnDetachingFrom (BindableObject bindable)
+ {
+ detached = true;
+ base.OnDetachingFrom (bindable);
+ AssociatedObject = null;
+ }
+
+ public BindableObject AssociatedObject {get;set;}
+ }
+
+ [TestFixture]
+ public class BehaviorTest : BaseTestFixture
+ {
+ [Test]
+ public void AttachAndDetach ()
+ {
+ var behavior = new MockBehavior<MockBindable> ();
+ var bindable = new MockBindable ();
+
+ Assert.False (behavior.attached);
+ Assert.False (behavior.detached);
+ Assert.Null (behavior.AssociatedObject);
+
+ ((IAttachedObject)behavior).AttachTo (bindable);
+
+ Assert.True (behavior.attached);
+ Assert.False (behavior.detached);
+ Assert.AreSame (bindable, behavior.AssociatedObject);
+
+ ((IAttachedObject)behavior).DetachFrom (bindable);
+
+ Assert.True (behavior.attached);
+ Assert.True (behavior.detached);
+ Assert.Null (behavior.AssociatedObject);
+ }
+
+ [Test]
+ public void AttachToTypeCompatibleWithTargetType ()
+ {
+ var behavior = new MockBehavior<MockBindable> ();
+ var bindable = new View ();
+
+ Assert.Throws<InvalidOperationException> (() => ((IAttachedObject)behavior).AttachTo (bindable));
+ }
+
+ [Test]
+ public void BehaviorsInCollectionAreAttachedWhenCollectionIsAttached ()
+ {
+ var behavior = new MockBehavior<MockBindable> ();
+ var collection = new AttachedCollection<Behavior> ();
+ var bindable = new MockBindable ();
+ collection.Add (behavior);
+ Assert.Null (behavior.AssociatedObject);
+
+ ((IAttachedObject)collection).AttachTo (bindable);
+ Assert.AreSame (bindable, behavior.AssociatedObject);
+
+ ((IAttachedObject)collection).DetachFrom (bindable);
+ Assert.Null (behavior.AssociatedObject);
+ }
+
+ [Test]
+ public void BehaviorsAddedToAttachedCollectionAreAttached ()
+ {
+ var behavior = new MockBehavior<MockBindable> ();
+ var collection = new AttachedCollection<Behavior> ();
+ var bindable = new MockBindable ();
+ ((IAttachedObject)collection).AttachTo (bindable);
+ Assert.Null (behavior.AssociatedObject);
+
+ collection.Add (behavior);
+ Assert.AreSame (bindable, behavior.AssociatedObject);
+
+ collection.Remove (behavior);
+ Assert.Null (behavior.AssociatedObject);
+ }
+
+ [Test]
+ public void TestBehaviorsAttachedDP ()
+ {
+ var behavior = new MockBehavior<MockBindable> ();
+ var bindable = new MockBindable ();
+ var collection = bindable.Behaviors;
+ Assert.Null (behavior.AssociatedObject);
+
+ collection.Add (behavior);
+ Assert.AreSame (bindable, behavior.AssociatedObject);
+
+ collection.Remove (behavior);
+ Assert.Null (behavior.AssociatedObject);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/BindableObjectExtensionTests.cs b/Xamarin.Forms.Core.UnitTests/BindableObjectExtensionTests.cs
new file mode 100644
index 00000000..06a24c7f
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/BindableObjectExtensionTests.cs
@@ -0,0 +1,72 @@
+using System;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class BindableObjectExtensionTests : BaseTestFixture
+ {
+ [Test]
+ public void SetBindingNull()
+ {
+ Assert.That (() => BindableObjectExtensions.SetBinding (null, MockBindable.TextProperty, "Name"),
+ Throws.InstanceOf<ArgumentNullException>());
+ Assert.That (() => BindableObjectExtensions.SetBinding (new MockBindable(), null, "Name"),
+ Throws.InstanceOf<ArgumentNullException>());
+ Assert.That (() => BindableObjectExtensions.SetBinding (new MockBindable(), MockBindable.TextProperty, null),
+ Throws.InstanceOf<ArgumentNullException>());
+
+ Assert.That (() => BindableObjectExtensions.SetBinding<MockViewModel> (null, MockBindable.TextProperty, vm => vm.Text),
+ Throws.InstanceOf<ArgumentNullException>());
+ Assert.That (() => BindableObjectExtensions.SetBinding<MockViewModel> (new MockBindable(), null, vm => vm.Text),
+ Throws.InstanceOf<ArgumentNullException>());
+ Assert.That (() => BindableObjectExtensions.SetBinding<MockViewModel> (new MockBindable(), MockBindable.TextProperty, null),
+ Throws.InstanceOf<ArgumentNullException>());
+ }
+
+ [Test]
+ public void Issue2643()
+ {
+ Label labelTempoDiStampa = new Label();
+ labelTempoDiStampa.BindingContext = new { Name = "1", Company = "Xamarin" };
+ labelTempoDiStampa.SetBinding (Label.TextProperty, "Name", stringFormat: "Hi: {0}");
+
+ Assert.That (labelTempoDiStampa.Text, Is.EqualTo ("Hi: 1"));
+ }
+
+ class Bz27229ViewModel
+ {
+ public object Member { get; private set; }
+ public Bz27229ViewModel ()
+ {
+ Member = new Generic<Label> (new Label {Text="foo"});
+ }
+ }
+
+ class Generic<TResult>
+ {
+ public Generic (TResult result)
+ {
+ Result = result;
+ }
+
+ public TResult Result { get; set; }
+ }
+
+ [Test]
+ public void Bz27229 ()
+ {
+ var totalCheckTime = new TextCell { Text = "Total Check Time" };
+ totalCheckTime.BindingContext = new Bz27229ViewModel ();
+ totalCheckTime.SetBinding(TextCell.DetailProperty,"Member.Result.Text");
+ Assert.AreEqual ("foo", totalCheckTime.Detail);
+
+ totalCheckTime = new TextCell { Text = "Total Check Time" };
+ totalCheckTime.BindingContext = new Bz27229ViewModel ();
+ totalCheckTime.SetBinding<Bz27229ViewModel>(TextCell.DetailProperty, vm =>
+ ((Generic<Label>)vm.Member).Result.Text);
+
+ Assert.AreEqual ("foo", totalCheckTime.Detail);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/BindableObjectUnitTests.cs b/Xamarin.Forms.Core.UnitTests/BindableObjectUnitTests.cs
new file mode 100644
index 00000000..7c4bac7a
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/BindableObjectUnitTests.cs
@@ -0,0 +1,1326 @@
+using System;
+using System.Globalization;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TypeConverter (typeof(ToBarConverter))]
+ internal class Bar
+ {
+
+ }
+
+ internal class Baz
+ {
+
+ }
+
+ internal class MockBindable
+ : VisualElement
+ {
+ public static readonly BindableProperty TextProperty = BindableProperty.Create<MockBindable, string> (
+ b => b.Text, "default", BindingMode.TwoWay);
+
+ public string Text
+ {
+ get { return (string)GetValue (TextProperty); }
+ set { SetValue (TextProperty, value); }
+ }
+
+ public string Foo { get; set; }
+
+ public int TargetInt { get; set; }
+
+ public static readonly BindableProperty BarProperty =
+ BindableProperty.Create<MockBindable, Bar> (w => w.Bar, default(Bar));
+
+ public Bar Bar {
+ get { return (Bar)GetValue (BarProperty); }
+ set { SetValue (BarProperty, value); }
+ }
+
+ public static readonly BindableProperty BazProperty =
+ BindableProperty.Create<MockBindable, Baz> (w => w.Baz, default(Baz));
+
+ [TypeConverter (typeof (ToBazConverter))]
+ public Baz Baz {
+ get { return (Baz)GetValue (BazProperty); }
+ set { SetValue (BazProperty, value); }
+ }
+
+ public static readonly BindableProperty QuxProperty =
+ BindableProperty.Create<MockBindable, Baz> (w => w.Qux, default(Baz));
+
+ public Baz Qux {
+ get { return (Baz)GetValue (QuxProperty); }
+ set { SetValue (QuxProperty, value); }
+ }
+ }
+
+ internal class ToBarConverter : TypeConverter
+ {
+ public override object ConvertFrom (System.Globalization.CultureInfo culture, object value)
+ {
+ return new Bar ();
+ }
+ }
+
+ internal class ToBazConverter : TypeConverter
+ {
+ public override object ConvertFrom (System.Globalization.CultureInfo culture, object value)
+ {
+ return new Baz ();
+ }
+ }
+
+ [TestFixture]
+ public class BindableObjectUnitTests : BaseTestFixture
+ {
+ [SetUp]
+ public void Setup()
+ {
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ Device.PlatformServices = null;
+ }
+
+ [Test]
+ public void BindingContext()
+ {
+ var mock = new MockBindable();
+ Assert.IsNull (mock.BindingContext);
+
+ object obj = new object();
+ mock.BindingContext = obj;
+ Assert.AreSame (obj, mock.BindingContext);
+ }
+
+ [Test]
+ public void BindingContextChangedEvent()
+ {
+ var mock = new MockBindable();
+ mock.BindingContextChanged += (sender, args) => Assert.Pass();
+
+ mock.BindingContext = new object();
+
+ Assert.Fail ("The BindingContextChanged event was not fired.");
+ }
+
+ [Test]
+ [Description ("When the BindingContext changes, any bindings should be immediately applied.")]
+ public void BindingContextChangedBindingsApplied()
+ {
+ var mock = new MockBindable();
+ mock.SetBinding (MockBindable.TextProperty, new Binding ("."));
+ mock.BindingContext = "Test";
+
+ Assert.AreEqual ("Test", mock.GetValue (MockBindable.TextProperty));
+
+ mock.BindingContext = "Testing";
+
+ Assert.AreEqual ("Testing", mock.GetValue (MockBindable.TextProperty),
+ "Bindings were not reapplied to the new binding context");
+ }
+
+ [Test]
+ [Description ("When the BindingContext changes, the new context needs to listen for updates.")]
+ public void BindingContextChangedBindingsListening()
+ {
+ var mock = new MockBindable();
+ mock.SetBinding (MockBindable.TextProperty, new Binding ("Text"));
+
+ var vm = new MockViewModel();
+ mock.BindingContext = vm;
+
+ mock.BindingContext = (vm = new MockViewModel());
+
+ vm.Text = "test";
+
+ Assert.AreEqual ("test", mock.GetValue (MockBindable.TextProperty),
+ "The new ViewModel was not being listened to after being set");
+ }
+
+ [Test]
+ [Description ("When an INPC implementer is unset as the BindingContext, its changes shouldn't be listened to any further.")]
+ public void BindingContextUnsetStopsListening()
+ {
+ var mock = new MockBindable();
+ mock.SetBinding (MockBindable.TextProperty, new Binding ("Text"));
+
+ var vm = new MockViewModel();
+ mock.BindingContext = vm;
+
+ mock.BindingContext = null;
+
+ vm.Text = "test";
+
+ Assert.IsNull (mock.GetValue (Entry.TextProperty), "ViewModel was still being listened to after set to null");
+ }
+
+ [Test]
+ public void PropertyChanging()
+ {
+ var mock = new MockBindable();
+ mock.PropertyChanging += (sender, args) => {
+ Assert.AreEqual (MockBindable.TextProperty.PropertyName, args.PropertyName);
+ Assert.AreEqual (MockBindable.TextProperty.DefaultValue, mock.GetValue (MockBindable.TextProperty));
+ Assert.Pass();
+ };
+
+ mock.SetValue (MockBindable.TextProperty, "foo");
+
+ Assert.Fail ("The PropertyChanging event was not fired.");
+ }
+
+ [Test]
+ public void PropertyChangingSameValue()
+ {
+ const string value = "foo";
+
+ var mock = new MockBindable();
+ mock.SetValue (MockBindable.TextProperty, value);
+ mock.PropertyChanging += (s,e) => Assert.Fail();
+
+ mock.SetValue (MockBindable.TextProperty, value);
+
+ Assert.Pass();
+ }
+
+ [Test]
+ public void PropertyChangingDefaultValue()
+ {
+ var prop = BindableProperty.Create<MockBindable, string> (w => w.Foo, "DefaultValue");
+
+ var mock = new MockBindable();
+ mock.PropertyChanging += (s,e) => Assert.Fail();
+ mock.SetValue (prop, prop.DefaultValue);
+
+ Assert.Pass();
+ }
+
+ [Test]
+ public void PropertyChanged()
+ {
+ const string value = "foo";
+
+ var mock = new MockBindable();
+ mock.PropertyChanged += (sender, args) => {
+ Assert.AreEqual (MockBindable.TextProperty.PropertyName, args.PropertyName);
+ Assert.AreEqual (value, mock.GetValue (MockBindable.TextProperty));
+ Assert.Pass();
+ };
+
+ mock.SetValue (MockBindable.TextProperty, value);
+
+ Assert.Fail ("The PropertyChanged event was not fired.");
+ }
+
+ [Test]
+ public void PropertyChangedSameValue()
+ {
+ const string value = "foo";
+
+ var mock = new MockBindable();
+ mock.SetValue (MockBindable.TextProperty, value);
+ mock.PropertyChanged += (s,e) => Assert.Fail();
+
+ mock.SetValue (MockBindable.TextProperty, value);
+
+ Assert.Pass();
+ }
+
+ [Test]
+ public void PropertyChangedDefaultValue()
+ {
+ var prop = BindableProperty.Create<MockBindable, string> (w => w.Foo, "DefaultValue");
+
+ var mock = new MockBindable();
+ mock.PropertyChanged += (s,e) => Assert.Fail();
+
+ mock.SetValue (prop, prop.DefaultValue);
+
+ Assert.Pass();
+ }
+
+ [Test]
+ public void GetSetValue()
+ {
+ const string value = "foo";
+ var mock = new MockBindable();
+ mock.SetValue (MockBindable.TextProperty, value);
+
+ Assert.AreEqual (value, mock.GetValue (MockBindable.TextProperty));
+ }
+
+ [Test]
+ public void GetValueDefault()
+ {
+ var nulldefault = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
+ TestGetValueDefault (nulldefault);
+
+ var foodefault = BindableProperty.Create<MockBindable, string> (w => w.Foo, "Foo");
+ TestGetValueDefault (foodefault);
+ }
+
+ void TestGetValueDefault (BindableProperty property)
+ {
+ var mock = new MockBindable();
+ object value = mock.GetValue (property);
+ Assert.AreEqual (property.DefaultValue, value);
+ }
+
+ [Test]
+ public void SetValueInvalid()
+ {
+ var mock = new MockBindable();
+ Assert.Throws<ArgumentNullException> (() => mock.SetValue ((BindableProperty)null, "null"));
+ }
+
+ [Test]
+ public void GetValueInvalid()
+ {
+ var mock = new MockBindable();
+ Assert.Throws<ArgumentNullException> (() => mock.GetValue (null));
+ }
+
+ [Test]
+ public void ClearValueInvalid()
+ {
+ var mock = new MockBindable();
+ Assert.Throws<ArgumentNullException> (() => mock.ClearValue ((BindableProperty)null));
+ }
+
+ [Test]
+ public void ClearValue()
+ {
+ const string value = "foo";
+ var mock = new MockBindable();
+ mock.SetValue (MockBindable.TextProperty, value);
+ Assert.AreEqual (value, mock.GetValue (MockBindable.TextProperty));
+
+ mock.ClearValue (MockBindable.TextProperty);
+ TestGetValueDefault (MockBindable.TextProperty);
+ }
+
+ [Test]
+ public void ClearValueTriggersINPC ()
+ {
+ var bindable = new MockBindable ();
+ bool changingfired = false;
+ bool changedfired = false;
+ bool changingdelegatefired = false;
+ bool changeddelegatefired = false;
+ var property = BindableProperty.Create ("Foo", typeof(string), typeof(MockBindable), "foo",
+ propertyChanged: (b, o, n) => changeddelegatefired = true,
+ propertyChanging: (b, o, n) => changingdelegatefired = true
+ );
+ bindable.PropertyChanged += (sender, e) => { changedfired |= e.PropertyName == "Foo"; };
+ bindable.PropertyChanging += (sender, e) => { changingfired |= e.PropertyName == "Foo"; };
+
+ bindable.SetValue (property, "foobar");
+ changingfired = changedfired = changeddelegatefired = changingdelegatefired = false;
+
+ bindable.ClearValue (property);
+ Assert.True (changingfired);
+ Assert.True (changedfired);
+ Assert.True (changingdelegatefired);
+ Assert.True (changeddelegatefired);
+ }
+
+ [Test]
+ public void ClearValueDoesNotTriggersINPCOnSameValues ()
+ {
+ var bindable = new MockBindable ();
+ bool changingfired = false;
+ bool changedfired = false;
+ bool changingdelegatefired = false;
+ bool changeddelegatefired = false;
+ var property = BindableProperty.Create ("Foo", typeof(string), typeof(MockBindable), "foo",
+ propertyChanged: (b, o, n) => changeddelegatefired = true,
+ propertyChanging: (b, o, n) => changingdelegatefired = true
+ );
+ bindable.PropertyChanged += (sender, e) => { changedfired |= e.PropertyName == "Foo"; };
+ bindable.PropertyChanging += (sender, e) => { changingfired |= e.PropertyName == "Foo"; };
+
+ bindable.SetValue (property, "foobar");
+ bindable.SetValue (property, "foo");
+ changingfired = changedfired = changeddelegatefired = changingdelegatefired = false;
+
+ bindable.ClearValue (property);
+ Assert.False (changingfired);
+ Assert.False (changedfired);
+ Assert.False (changingdelegatefired);
+ Assert.False (changeddelegatefired);
+ }
+
+ [Test]
+ public void SetBindingInvalid()
+ {
+ var mock = new MockBindable();
+ Assert.Throws<ArgumentNullException> (() => mock.SetBinding (null, new Binding (".")));
+ Assert.Throws<ArgumentNullException> (() => mock.SetBinding (MockBindable.TextProperty, null));
+ }
+
+ [Test]
+ public void RemoveUnaddedBinding()
+ {
+ var mock = new MockBindable();
+ Assert.That (() => mock.RemoveBinding (MockBindable.TextProperty), Throws.Nothing);
+ }
+
+ [Test]
+ public void RemoveBindingInvalid()
+ {
+ var mock = new MockBindable();
+ Assert.Throws<ArgumentNullException> (() => mock.RemoveBinding (null));
+ }
+
+ [Test]
+ public void RemovedBindingDoesNotUpdate()
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new MockViewModel {
+ Text = "Foo"
+ };
+
+ var binding = new Binding ("Text");
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (MockBindable.TextProperty, binding);
+
+ string original = (string)bindable.GetValue (MockBindable.TextProperty);
+
+ bindable.RemoveBinding (MockBindable.TextProperty);
+
+ viewmodel.Text = newvalue;
+ Assert.AreEqual (original, bindable.GetValue (MockBindable.TextProperty),
+ "Property updated from a removed binding");
+ }
+
+ [Test]
+ public void CoerceValue()
+ {
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null,
+ coerceValue: (bo, o) => o.ToUpper());
+
+ const string value = "value";
+ var mock = new MockBindable();
+ mock.SetValue (property, value);
+ Assert.AreEqual (value.ToUpper(), mock.GetValue (property));
+ }
+
+ [Test]
+ public void ValidateValue()
+ {
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null,
+ validateValue: (b, v) => false);
+
+ var mock = new MockBindable();
+ Assert.Throws<ArgumentException> (() => mock.SetValue (property, null));
+ }
+
+ [Test]
+ public void BindablePropertyChanged()
+ {
+ bool changed = false;
+
+ string oldv = "bar";
+ string newv = "foo";
+
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, oldv,
+ propertyChanged: (b, ov, nv) => {
+ Assert.AreSame (oldv, ov);
+ Assert.AreSame (newv, nv);
+ changed = true;
+ });
+
+ var mock = new MockBindable();
+ mock.SetValue (property, newv);
+
+ Assert.IsTrue (changed, "PropertyChanged was not called");
+ }
+
+ [Test]
+ public void RecursiveChange ()
+ {
+ bool changedA1 = false, changedA2 = false, changedB1 = false, changedB2 = false;
+
+ var mock = new MockBindable ();
+ mock.PropertyChanged += (sender, args) => {
+ if (!changedA1) {
+ Assert.AreEqual ("1", mock.GetValue (MockBindable.TextProperty));
+ Assert.IsFalse (changedA2);
+ Assert.IsFalse (changedB1);
+ Assert.IsFalse (changedB2);
+ mock.SetValue (MockBindable.TextProperty, "2");
+ changedA1 = true;
+ } else {
+ Assert.AreEqual ("2", mock.GetValue (MockBindable.TextProperty));
+ Assert.IsFalse (changedA2);
+ Assert.IsTrue (changedB1);
+ Assert.IsFalse (changedB2);
+ changedA2 = true;
+ }
+ };
+ mock.PropertyChanged += (sender, args) => {
+ if (!changedB1) {
+ Assert.AreEqual ("1", mock.GetValue (MockBindable.TextProperty));
+ Assert.IsTrue (changedA1);
+ Assert.IsFalse (changedA2);
+ Assert.IsFalse (changedB2);
+ changedB1 = true;
+ } else {
+ Assert.AreEqual ("2", mock.GetValue (MockBindable.TextProperty));
+ Assert.IsTrue (changedA1);
+ Assert.IsTrue (changedA2);
+ Assert.IsFalse (changedB2);
+ changedB2 = true;
+ }
+ };
+ mock.SetValue (MockBindable.TextProperty, "1");
+ Assert.AreEqual ("2", mock.GetValue (MockBindable.TextProperty));
+ Assert.IsTrue (changedA1);
+ Assert.IsTrue (changedA2);
+ Assert.IsTrue (changedB1);
+ Assert.IsTrue (changedB2);
+ }
+
+ [Test]
+ public void RaiseOnEqual()
+ {
+ string foo = "foo";
+ var mock = new MockBindable();
+ mock.SetValue (MockBindable.TextProperty, foo);
+
+ bool changing = false;
+ mock.PropertyChanging += (o, e) => {
+ Assert.That (e.PropertyName, Is.EqualTo (MockBindable.TextProperty.PropertyName));
+ changing = true;
+ };
+
+ bool changed = true;
+ mock.PropertyChanged += (o, e) => {
+ Assert.That (e.PropertyName, Is.EqualTo (MockBindable.TextProperty.PropertyName));
+ changed = true;
+ };
+
+ mock.SetValueCore (MockBindable.TextProperty, foo,
+ BindableObject.SetValueFlags.ClearOneWayBindings | BindableObject.SetValueFlags.ClearDynamicResource | BindableObject.SetValueFlags.RaiseOnEqual);
+
+ Assert.That (changing, Is.True, "PropertyChanging event did not fire");
+ Assert.That (changed, Is.True, "PropertyChanged event did not fire");
+ }
+
+ [Test]
+ public void BindingContextGetter ()
+ {
+ var label = new Label ();
+ var view = new StackLayout { Children = {label} };
+
+ view.BindingContext = new {item0 = "Foo", item1 = "Bar"};
+ label.SetBinding (BindableObject.BindingContextProperty, "item0");
+ label.SetBinding (Label.TextProperty, Binding.SelfPath);
+
+ Assert.AreSame (label.BindingContext, label.GetValue (BindableObject.BindingContextProperty));
+ }
+
+ [Test]
+ public void BoundBindingContextUpdate ()
+ {
+ var label = new Label ();
+ var view = new StackLayout { Children = {label} };
+ var vm = new MockViewModel { Text = "FooBar" };
+
+ view.BindingContext = vm;
+ label.SetBinding (BindableObject.BindingContextProperty, "Text");
+ label.SetBinding (Label.TextProperty, Binding.SelfPath);
+
+ Assert.AreEqual ("FooBar", label.BindingContext);
+
+ vm.Text = "Baz";
+ Assert.AreEqual ("Baz", label.BindingContext);
+ }
+
+ [Test]
+ public void BoundBindingContextChange ()
+ {
+ var label = new Label ();
+ var view = new StackLayout { Children = {label} };
+
+ view.BindingContext = new MockViewModel { Text = "FooBar" };;
+ label.SetBinding (BindableObject.BindingContextProperty, "Text");
+ label.SetBinding (Label.TextProperty, Binding.SelfPath);
+
+ Assert.AreEqual ("FooBar", label.BindingContext);
+
+ view.BindingContext = new MockViewModel { Text = "Baz" };;
+ Assert.AreEqual ("Baz", label.BindingContext);
+ }
+
+ [Test]
+ public void TestReadOnly ()
+ {
+ var bindablePropertyKey = BindableProperty.CreateReadOnly<MockBindable, string> (w => w.Foo, "DefaultValue");
+ var bindableProperty = bindablePropertyKey.BindableProperty;
+
+ var bindable = new MockBindable ();
+ Assert.AreEqual ("DefaultValue", bindable.GetValue (bindableProperty));
+
+ bindable.SetValue (bindablePropertyKey, "Bar");
+ Assert.AreEqual ("Bar", bindable.GetValue (bindableProperty));
+
+ Assert.Throws<InvalidOperationException> (() => bindable.SetValue (bindableProperty, "Baz"));
+ Assert.AreEqual ("Bar", bindable.GetValue (bindableProperty));
+
+ Assert.Throws<InvalidOperationException> (() => bindable.ClearValue (bindableProperty));
+
+ bindable.ClearValue (bindablePropertyKey);
+ Assert.AreEqual ("DefaultValue", bindable.GetValue (bindableProperty));
+ }
+
+ [Test]
+ public void TestBindingTwoWayOnReadOnly ()
+ {
+ var bindablePropertyKey = BindableProperty.CreateReadOnly<MockBindable, string> (w => w.Foo, "DefaultValue", BindingMode.OneWayToSource);
+ var bindableProperty = bindablePropertyKey.BindableProperty;
+
+ var bindable = new MockBindable ();
+ var vm = new MockViewModel ();
+
+ bindable.SetBinding (bindableProperty, new Binding ("Text", BindingMode.TwoWay));
+ Assert.DoesNotThrow (() => bindable.BindingContext = vm);
+
+ Assert.AreEqual ("DefaultValue", bindable.GetValue (bindableProperty));
+ }
+
+ [Test]
+ public void DefaultValueCreator ()
+ {
+ int invoked = 0;
+ object defaultValue = new object ();
+ var bindableProperty = BindableProperty.Create ("Foo", typeof(object), typeof(MockBindable), defaultValue, defaultValueCreator: o => {
+ invoked++;
+ return new object ();
+ });
+ var bindable = new MockBindable ();
+
+ Assert.AreSame (defaultValue, bindableProperty.DefaultValue);
+ var newvalue = bindable.GetValue (bindableProperty);
+ Assert.AreNotSame (defaultValue, newvalue);
+ Assert.NotNull (newvalue);
+ Assert.AreEqual (1, invoked);
+ }
+
+ [Test]
+ public void DefaultValueCreatorIsInvokedOnlyAtFirstTime ()
+ {
+ int invoked = 0;
+ var bindableProperty = BindableProperty.Create ("Foo", typeof(object), typeof(MockBindable), null, defaultValueCreator: o => {
+ invoked++;
+ return new object ();
+ });
+ var bindable = new MockBindable ();
+
+ var value0 = bindable.GetValue (bindableProperty);
+ var value1 = bindable.GetValue (bindableProperty);
+ Assert.NotNull (value0);
+ Assert.NotNull (value1);
+ Assert.AreSame (value0, value1);
+ Assert.AreEqual (1, invoked);
+ }
+
+ [Test]
+ public void DefaultValueCreatorNotSharedAccrossInstances ()
+ {
+ int invoked = 0;
+ var bindableProperty = BindableProperty.Create ("Foo", typeof(object), typeof(MockBindable), null, defaultValueCreator: o=> {
+ invoked++;
+ return new object ();
+ });
+ var bindable0 = new MockBindable ();
+ var bindable1 = new MockBindable ();
+ var value0 = bindable0.GetValue (bindableProperty);
+ var value1 = bindable1.GetValue (bindableProperty);
+
+ Assert.AreNotSame (value0, value1);
+ Assert.AreEqual (2, invoked);
+ }
+
+ [Test]
+ public void DefaultValueCreatorInvokedAfterClearValue ()
+ {
+ int invoked = 0;
+ var bindableProperty = BindableProperty.Create ("Foo", typeof(object), typeof(MockBindable), null, defaultValueCreator: o => {
+ invoked++;
+ return new object ();
+ });
+ var bindable = new MockBindable ();
+
+ Assert.AreEqual (0, invoked);
+
+ var value0 = bindable.GetValue (bindableProperty);
+ Assert.NotNull (value0);
+ Assert.AreEqual (1, invoked);
+ bindable.ClearValue (bindableProperty);
+
+ var value1 = bindable.GetValue (bindableProperty);
+ Assert.NotNull (value1);
+ Assert.AreEqual (2, invoked);
+ Assert.AreNotSame (value0, value1);
+ }
+
+ [Test]
+ public void DefaultValueCreatorOnlyInvokedOnGetValue ()
+ {
+ int invoked = 0;
+ var bindableProperty = BindableProperty.Create ("Foo", typeof(object), typeof(MockBindable), null, defaultValueCreator: o => {
+ invoked++;
+ return new object ();
+ });
+ var bindable = new MockBindable ();
+
+ Assert.AreEqual (0, invoked);
+
+ var newvalue = bindable.GetValue (bindableProperty);
+ Assert.NotNull (newvalue);
+ Assert.AreEqual (1, invoked);
+ }
+
+ [Test]
+ public void DefaultValueCreatorDoesNotTriggerINPC ()
+ {
+ int invoked = 0;
+ int propertychanged = 0;
+ int changedfired = 0;
+ var bindableProperty = BindableProperty.Create ("Foo", typeof(object), typeof(MockBindable), null,
+ propertyChanged: (bindable,oldvalue,newvalue) =>{
+ propertychanged ++;
+ },
+ defaultValueCreator: o => {
+ invoked++;
+ return new object ();
+ });
+
+ var bp = new MockBindable ();
+ bp.PropertyChanged += (sender, e) => {
+ if (e.PropertyName == "Foo")
+ changedfired++;
+ };
+ var value0 = bp.GetValue (bindableProperty);
+ Assert.NotNull (value0);
+ Assert.AreEqual (1, invoked);
+ Assert.AreEqual (0, propertychanged);
+ Assert.AreEqual (0, changedfired);
+
+ }
+
+ [Test]
+ public void StyleValueIsOverridenByValue ()
+ {
+ var label = new Label ();
+ label.SetValue (Label.TextProperty, "Foo", true);
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetValue (Label.TextProperty, "Bar");
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleBindingIsOverridenByValue ()
+ {
+ var label = new Label ();
+ label.SetBinding (Label.TextProperty, new Binding ("foo"), true);
+ label.BindingContext = new {foo = "Foo"};
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetValue (Label.TextProperty, "Bar");
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleDynResourceIsOverridenByValue ()
+ {
+ var label = new Label ();
+ label.SetDynamicResource (Label.TextProperty, "foo", true);
+ label.Resources = new ResourceDictionary {
+ {"foo", "Foo"}
+ };
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetValue (Label.TextProperty, "Bar");
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleValueIsOverridenByBinding ()
+ {
+ var label = new Label ();
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetValue (Label.TextProperty, "Foo", true);
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetBinding (Label.TextProperty, "bar");
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleBindingIsOverridenByBinding ()
+ {
+ var label = new Label ();
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetBinding (Label.TextProperty, new Binding ("foo"), true);
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetBinding (Label.TextProperty, "bar");
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleDynResourceIsOverridenByBinding ()
+ {
+ var label = new Label ();
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetDynamicResource (Label.TextProperty, "foo", true);
+ label.Resources = new ResourceDictionary {
+ {"foo", "Foo"},
+ };
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetBinding (Label.TextProperty, "bar");
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleValueIsOverridenByDynResource ()
+ {
+ var label = new Label ();
+ label.Resources = new ResourceDictionary {
+ {"foo", "Foo"},
+ {"bar", "Bar"}
+ };
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetValue (Label.TextProperty, "Foo", true);
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetDynamicResource (Label.TextProperty, "bar");
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleBindingIsOverridenByDynResource ()
+ {
+ var label = new Label ();
+ label.Resources = new ResourceDictionary {
+ {"foo", "Foo"},
+ {"bar", "Bar"}
+ };
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetBinding (Label.TextProperty, new Binding ("foo"), true);
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetDynamicResource (Label.TextProperty, "bar");
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleDynResourceIsOverridenByDynResource ()
+ {
+ var label = new Label ();
+ label.Resources = new ResourceDictionary {
+ {"foo", "Foo"},
+ {"bar", "Bar"}
+ };
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetDynamicResource (Label.TextProperty, "foo", true);
+
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetDynamicResource (Label.TextProperty, "bar");
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void ValueIsPreservedOnStyleValue ()
+ {
+ var label = new Label ();
+ label.SetValue (Label.TextProperty, "Foo");
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetValue (Label.TextProperty, "Bar", true);
+ Assert.AreEqual ("Foo", label.Text);
+ }
+ [Test]
+ public void BindingIsPreservedOnStyleValue ()
+ {
+ var label = new Label ();
+ label.SetBinding (Label.TextProperty, new Binding ("foo"));
+ label.BindingContext = new {foo = "Foo"};
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetValue (Label.TextProperty, "Bar", true);
+ Assert.AreEqual ("Foo", label.Text);
+ }
+ [Test]
+ public void DynResourceIsPreservedOnStyleValue ()
+ {
+ var label = new Label ();
+ label.SetDynamicResource (Label.TextProperty, "foo");
+ label.Resources = new ResourceDictionary {
+ {"foo", "Foo"}
+ };
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetValue (Label.TextProperty, "Bar", true);
+ Assert.AreEqual ("Foo", label.Text);
+ }
+
+ [Test]
+ public void ValueIsPreservedOnStyleBinding ()
+ {
+ var label = new Label ();
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetValue (Label.TextProperty, "Foo");
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetBinding (Label.TextProperty, new Binding ("bar"), true);
+ Assert.AreEqual ("Foo", label.Text);
+ }
+
+ [Test]
+ public void BindingIsPreservedOnStyleBinding ()
+ {
+ var label = new Label ();
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetBinding (Label.TextProperty, new Binding ("foo"));
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetBinding (Label.TextProperty, new Binding ("bar"), true);
+ Assert.AreEqual ("Foo", label.Text);
+ }
+
+ [Test]
+ public void DynResourceIsPreservedOnStyleBinding ()
+ {
+ var label = new Label ();
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetDynamicResource (Label.TextProperty, "foo");
+ label.Resources = new ResourceDictionary {
+ {"foo", "Foo"},
+ };
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetBinding (Label.TextProperty, new Binding ("bar"), true);
+ Assert.AreEqual ("Foo", label.Text);
+ }
+
+ [Test]
+ public void ValueIsPreservedOnStyleDynResource ()
+ {
+ var label = new Label ();
+ label.Resources = new ResourceDictionary {
+ {"foo", "Foo"},
+ {"bar", "Bar"}
+ };
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetValue (Label.TextProperty, "Foo");
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetDynamicResource (Label.TextProperty, "bar", true);
+ Assert.AreEqual ("Foo", label.Text);
+ }
+
+ [Test]
+ public void BindingIsPreservedOnStyleDynResource ()
+ {
+ var label = new Label ();
+ label.Resources = new ResourceDictionary {
+ {"foo", "Foo"},
+ {"bar", "Bar"}
+ };
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetBinding (Label.TextProperty, new Binding ("foo"));
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetDynamicResource (Label.TextProperty, "bar", true);
+ Assert.AreEqual ("Foo", label.Text);
+ }
+
+ [Test]
+ public void DynResourceIsPreservedOnStyleDynResource ()
+ {
+ var label = new Label ();
+ label.Resources = new ResourceDictionary {
+ {"foo", "Foo"},
+ {"bar", "Bar"}
+ };
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetDynamicResource (Label.TextProperty, "foo");
+
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetDynamicResource (Label.TextProperty, "bar", true);
+ Assert.AreEqual ("Foo", label.Text);
+ }
+
+ [Test]
+ public void StyleValueIsOverridenByStyleValue ()
+ {
+ var label = new Label ();
+ label.SetValue (Label.TextProperty, "Foo", true);
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetValue (Label.TextProperty, "Bar", true);
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleBindingIsOverridenByStyleValue ()
+ {
+ var label = new Label ();
+ label.SetBinding (Label.TextProperty, new Binding ("foo"), true);
+ label.BindingContext = new {foo = "Foo"};
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetValue (Label.TextProperty, "Bar", true);
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleDynResourceIsOverridenByStyleValue ()
+ {
+ var label = new Label ();
+ label.SetDynamicResource (Label.TextProperty, "foo", true);
+ label.Resources = new ResourceDictionary {
+ {"foo", "Foo"}
+ };
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetValue (Label.TextProperty, "Bar", true);
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleValueIsOverridenByStyleBinding ()
+ {
+ var label = new Label ();
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetValue (Label.TextProperty, "Foo", true);
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetBinding (Label.TextProperty, new Binding("bar"), true);
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleBindingIsOverridenByStyleBinding ()
+ {
+ var label = new Label ();
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetBinding (Label.TextProperty, new Binding ("foo"), true);
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetBinding (Label.TextProperty, new Binding("bar"), true);
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleDynResourceIsOverridenByStyleBinding ()
+ {
+ var label = new Label ();
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetDynamicResource (Label.TextProperty, "foo", true);
+ label.Resources = new ResourceDictionary {
+ {"foo", "Foo"},
+ };
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetBinding (Label.TextProperty, new Binding("bar"), true);
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleValueIsOverridenByStyleDynResource ()
+ {
+ var label = new Label ();
+ label.Resources = new ResourceDictionary {
+ {"foo", "Foo"},
+ {"bar", "Bar"}
+ };
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetValue (Label.TextProperty, "Foo", true);
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetDynamicResource (Label.TextProperty, "bar", true);
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleBindingIsOverridenByStyleDynResource ()
+ {
+ var label = new Label ();
+ label.Resources = new ResourceDictionary {
+ {"foo", "Foo"},
+ {"bar", "Bar"}
+ };
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetBinding (Label.TextProperty, new Binding ("foo"), true);
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetDynamicResource (Label.TextProperty, "bar", true);
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void StyleDynResourceIsOverridenByStyleDynResource ()
+ {
+ var label = new Label ();
+ label.Resources = new ResourceDictionary {
+ {"foo", "Foo"},
+ {"bar", "Bar"}
+ };
+ label.BindingContext = new {foo = "Foo", bar = "Bar"};
+ label.SetDynamicResource (Label.TextProperty, "foo", true);
+
+ Assert.AreEqual ("Foo", label.Text);
+
+ label.SetDynamicResource (Label.TextProperty, "bar", true);
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void SetValueCoreImplicitelyCastBasicType ()
+ {
+ var prop = BindableProperty.Create ("Foo", typeof(int), typeof(MockBindable), 0);
+ var bindable = new MockBindable ();
+
+ Assert.DoesNotThrow (() => bindable.SetValue (prop, (object)(short)42));
+ Assert.AreEqual (42, bindable.GetValue (prop));
+
+ bindable.SetValue (prop, (object)(long)-42);
+ Assert.AreNotEqual (-42, bindable.GetValue (prop));
+ }
+
+ class CastFromString
+ {
+ public string Result { get; private set; }
+ public static implicit operator CastFromString (string source)
+ {
+ var o = new CastFromString ();
+ o.Result = source;
+ return o;
+ }
+ }
+
+ [Test]
+ public void SetValueCoreInvokesOpImplicitOnPropertyType ()
+ {
+ var prop = BindableProperty.Create ("Foo", typeof(CastFromString), typeof(MockBindable), null);
+ var bindable = new MockBindable ();
+
+ Assert.Null (bindable.GetValue (prop));
+ bindable.SetValue (prop, "foo");
+
+ Assert.AreEqual ("foo", ((CastFromString)bindable.GetValue (prop)).Result);
+ }
+
+ class CastToString
+ {
+ string Result { get; set; }
+
+ public CastToString (string result)
+ {
+ Result = result;
+ }
+
+ public static implicit operator string (CastToString source)
+ {
+ return source.Result;
+ }
+
+ public override string ToString ()
+ {
+ throw new InvalidOperationException ();
+ }
+ }
+
+ [Test]
+ public void SetValueCoreInvokesOpImplicitOnValue ()
+ {
+ var prop = BindableProperty.Create ("Foo", typeof(string), typeof(MockBindable), null);
+ var bindable = new MockBindable ();
+
+ Assert.Null (bindable.GetValue (prop));
+ bindable.SetValue (prop, new CastToString ("foo"));
+
+ Assert.AreEqual ("foo", bindable.GetValue (prop));
+ }
+
+ [Test]
+ public void DefaultValueCreatorCalledForChangeDelegates ()
+ {
+ int changedOld = -1;
+ int changedNew = -1;
+
+ int changingOld = -1;
+ int changingNew = -1;
+ var prop = BindableProperty.Create ("Foo", typeof (int), typeof (MockBindable), 0, defaultValueCreator: b => 10,
+ propertyChanged: (b, value, newValue) => {
+ changedOld = (int) value;
+ changedNew = (int) newValue;
+ },
+ propertyChanging: (b, value, newValue) => {
+ changingOld = (int) value;
+ changingNew = (int) newValue;
+ });
+
+ var bindable = new MockBindable ();
+
+
+ var defaultValue = (int) bindable.GetValue (prop);
+
+ Assert.AreEqual (10, defaultValue);
+
+ bindable.SetValue (prop, 5);
+
+ bindable.ClearValue (prop);
+
+ Assert.AreEqual (5, changedOld);
+ Assert.AreEqual (5, changingOld);
+ Assert.AreEqual (10, changedNew);
+ Assert.AreEqual (10, changingNew);
+ }
+
+ [Test]
+ public void GetValuesDefaults()
+ {
+ var prop = BindableProperty.Create ("Foo", typeof(int), typeof(MockBindable), 0);
+ var prop1 = BindableProperty.Create ("Foo1", typeof(int), typeof(MockBindable), 1);
+ var prop2 = BindableProperty.Create ("Foo2", typeof(int), typeof(MockBindable), 2);
+ var bindable = new MockBindable ();
+
+
+ object[] values = bindable.GetValues (prop, prop1, prop2);
+ Assert.That (values.Length == 3);
+ Assert.That (values[0], Is.EqualTo (0));
+ Assert.That (values[1], Is.EqualTo (1));
+ Assert.That (values[2], Is.EqualTo (2));
+ }
+
+ [Test]
+ public void GetValues()
+ {
+ var prop = BindableProperty.Create ("Foo", typeof(int), typeof(MockBindable), 0);
+ var prop1 = BindableProperty.Create ("Foo1", typeof(int), typeof(MockBindable), 1);
+ var prop2 = BindableProperty.Create ("Foo2", typeof(int), typeof(MockBindable), 2);
+ var bindable = new MockBindable ();
+ bindable.SetValue (prop, 3);
+ bindable.SetValue (prop2, 5);
+
+
+ object[] values = bindable.GetValues (prop, prop1, prop2);
+ Assert.That (values.Length == 3);
+ Assert.That (values[0], Is.EqualTo (3));
+ Assert.That (values[1], Is.EqualTo (1));
+ Assert.That (values[2], Is.EqualTo (5));
+ }
+
+ class BindingContextConverter
+ : IValueConverter
+ {
+ public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return new MockViewModel { Text = value + "Converted" };
+ }
+
+ public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ [Test]
+ //https://bugzilla.xamarin.com/show_bug.cgi?id=24485
+ public void BindingContextBoundThroughConverter()
+ {
+ var bindable = new MockBindable();
+ bindable.BindingContext = "test";
+ bindable.SetBinding (BindableObject.BindingContextProperty, new Binding (".", converter: new BindingContextConverter()));
+ bindable.SetBinding (MockBindable.TextProperty, "Text");
+
+ Assert.That (() => bindable.Text, Is.EqualTo ("testConverted"));
+ }
+
+ public class VMLocator
+ {
+ public event EventHandler Invoked;
+ public int Count;
+ public object VM {
+ get {
+ Count++;
+ var eh = Invoked;
+ if (eh != null)
+ eh (this, EventArgs.Empty);
+ return new object ();
+ }
+ }
+ }
+
+ [Test]
+ //https://bugzilla.xamarin.com/show_bug.cgi?id=27299
+ public void BindingOnBindingContextDoesntReapplyBindingContextBinding ()
+ {
+ var bindable = new MockBindable ();
+ var locator = new VMLocator ();
+ Assert.AreEqual (0, locator.Count);
+ locator.Invoked += (sender, e) => Assert.IsTrue (locator.Count <= 1);
+ bindable.SetBinding (BindableObject.BindingContextProperty, new Binding ("VM", source: locator));
+ Assert.IsTrue (locator.Count == 1);
+ }
+
+ [Test]
+ public void BindingsEditableAfterUnapplied()
+ {
+ var bindable = new MockBindable();
+
+ var binding = new Binding (".");
+ bindable.SetBinding (MockBindable.TextProperty, binding);
+ bindable.BindingContext = "foo";
+
+ Assume.That (bindable.Text, Is.EqualTo (bindable.BindingContext));
+
+ bindable.RemoveBinding (MockBindable.TextProperty);
+
+ Assert.That (() => binding.Path = "foo", Throws.Nothing);
+ }
+
+ [Test]
+ // https://bugzilla.xamarin.com/show_bug.cgi?id=24054
+ public void BindingsAppliedUnappliedWithNullContext()
+ {
+ var bindable = new MockBindable();
+
+ var binding = new Binding (".");
+ bindable.SetBinding (MockBindable.TextProperty, binding);
+ bindable.BindingContext = "foo";
+
+ Assume.That (bindable.Text, Is.EqualTo (bindable.BindingContext));
+
+ bindable.BindingContext = null;
+
+ Assume.That (bindable.Text, Is.EqualTo (bindable.BindingContext));
+
+ bindable.BindingContext = "bar";
+
+ Assume.That (bindable.Text, Is.EqualTo (bindable.BindingContext));
+
+ bindable.RemoveBinding (MockBindable.TextProperty);
+
+ Assert.That (() => binding.Path = "Foo", Throws.Nothing);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/BindablePropertyUnitTests.cs b/Xamarin.Forms.Core.UnitTests/BindablePropertyUnitTests.cs
new file mode 100644
index 00000000..1d006a6d
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/BindablePropertyUnitTests.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Linq;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class BindablePropertyUnitTests : BaseTestFixture
+ {
+ [Test]
+ public void Create()
+ {
+ const BindingMode mode = BindingMode.OneWayToSource;
+ const string dvalue = "default";
+ BindableProperty.CoerceValueDelegate<string> coerce = (bindable, value) => value;
+ BindableProperty.ValidateValueDelegate<string> validate = (b, v) => true;
+ BindableProperty.BindingPropertyChangedDelegate<string> changed = (b, ov, nv) => { };
+ BindableProperty.BindingPropertyChangingDelegate<string> changing = (b, ov, nv) => { };
+
+ var prop = BindableProperty.Create<Button, string> (b => b.Text, dvalue, mode, validate, changed, changing, coerce);
+ Assert.AreEqual ("Text", prop.PropertyName);
+ Assert.AreEqual (typeof (Button), prop.DeclaringType);
+ Assert.AreEqual (typeof (string), prop.ReturnType);
+ Assert.AreEqual (dvalue, prop.DefaultValue);
+ Assert.AreEqual (mode, prop.DefaultBindingMode);
+ }
+
+ [Test]
+ public void CreateWithDefaultMode ()
+ {
+ const BindingMode mode = BindingMode.Default;
+ var prop = BindableProperty.Create<Button, string> (b => b.Text, null, defaultBindingMode: mode);
+ Assert.AreEqual (BindingMode.OneWay, prop.DefaultBindingMode);
+ }
+
+ [Test]
+ public void CreateCasted()
+ {
+ var prop = BindableProperty.Create<Cell, bool> (c => c.IsEnabled, true);
+
+ Assert.AreEqual ("IsEnabled", prop.PropertyName);
+ Assert.AreEqual (typeof (Cell), prop.DeclaringType);
+ Assert.AreEqual (typeof (bool), prop.ReturnType);
+ }
+
+ [Test]
+ public void CreateNonGeneric()
+ {
+ const BindingMode mode = BindingMode.OneWayToSource;
+ const string dvalue = "default";
+ BindableProperty.CoerceValueDelegate coerce = (bindable, value) => value;
+ BindableProperty.ValidateValueDelegate validate = (b, v) => true;
+ BindableProperty.BindingPropertyChangedDelegate changed = (b, ov, nv) => { };
+ BindableProperty.BindingPropertyChangingDelegate changing = (b, ov, nv) => { };
+
+ var prop = BindableProperty.Create ("Text", typeof(string), typeof(Button), dvalue, mode, validate, changed, changing, coerce);
+ Assert.AreEqual ("Text", prop.PropertyName);
+ Assert.AreEqual (typeof (Button), prop.DeclaringType);
+ Assert.AreEqual (typeof (string), prop.ReturnType);
+ Assert.AreEqual (dvalue, prop.DefaultValue);
+ Assert.AreEqual (mode, prop.DefaultBindingMode);
+ }
+
+ class GenericView<T> : View
+ {
+ public string Text
+ {
+ get;
+ set;
+ }
+ }
+
+ [Test]
+ public void CreateForGeneric()
+ {
+ const BindingMode mode = BindingMode.OneWayToSource;
+ const string dvalue = "default";
+ BindableProperty.CoerceValueDelegate coerce = (bindable, value) => value;
+ BindableProperty.ValidateValueDelegate validate = (b, v) => true;
+ BindableProperty.BindingPropertyChangedDelegate changed = (b, ov, nv) => { };
+ BindableProperty.BindingPropertyChangingDelegate changing = (b, ov, nv) => { };
+
+ var prop = BindableProperty.Create ("Text", typeof(string), typeof(GenericView<>), dvalue, mode, validate, changed, changing, coerce);
+ Assert.AreEqual ("Text", prop.PropertyName);
+ Assert.AreEqual (typeof (GenericView<>), prop.DeclaringType);
+ Assert.AreEqual (typeof (string), prop.ReturnType);
+ Assert.AreEqual (dvalue, prop.DefaultValue);
+ Assert.AreEqual (mode, prop.DefaultBindingMode);
+ }
+
+ [Test]
+ public void ChangingBeforeChanged ()
+ {
+ bool changingfired = false;
+ bool changedfired = false;
+ BindableProperty.BindingPropertyChangedDelegate<string> changed = (b, ov, nv) => {
+ Assert.True (changingfired);
+ changedfired = true;
+ };
+ BindableProperty.BindingPropertyChangingDelegate<string> changing = (b, ov, nv) => {
+ Assert.False (changedfired);
+ changingfired = true;
+ };
+
+ var prop = BindableProperty.Create<Button, string> (b => b.Text, "Foo",
+ propertyChanging: changing,
+ propertyChanged: changed);
+
+ Assert.False (changingfired);
+ Assert.False (changedfired);
+
+ (new View ()).SetValue (prop, "Bar");
+
+ Assert.True (changingfired);
+ Assert.True (changedfired);
+ }
+
+ [Test]
+ public void NullableProperty ()
+ {
+ var prop = BindableProperty.Create ("foo", typeof(DateTime?), typeof(MockBindable), null);
+ Assert.AreEqual (typeof(DateTime?), prop.ReturnType);
+
+ var bindable = new MockBindable ();
+ Assert.AreEqual (null, bindable.GetValue (prop));
+
+ var now = DateTime.Now;
+ bindable.SetValue (prop, now);
+ Assert.AreEqual (now, bindable.GetValue (prop));
+
+ bindable.SetValue (prop, null);
+ Assert.AreEqual (null, bindable.GetValue (prop));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/BindingBaseUnitTests.cs b/Xamarin.Forms.Core.UnitTests/BindingBaseUnitTests.cs
new file mode 100644
index 00000000..dd0d2244
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/BindingBaseUnitTests.cs
@@ -0,0 +1,227 @@
+using System;
+using System.Linq;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ public abstract class BindingBaseUnitTests : BaseTestFixture
+ {
+ protected abstract BindingBase CreateBinding (BindingMode mode, string stringFormat = null);
+
+ [Test]
+ public void CloneMode()
+ {
+ var binding = CreateBinding (BindingMode.Default);
+ var clone = binding.Clone();
+
+ Assert.AreEqual (binding.Mode, clone.Mode);
+ }
+
+ [Test]
+ public void StringFormat()
+ {
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
+
+ var binding = CreateBinding (BindingMode.Default, "Foo {0}");
+
+ var vm = new MockViewModel { Text = "Bar" };
+ var bo = new MockBindable { BindingContext = vm };
+ bo.SetBinding (property, binding);
+
+ Assert.That (bo.GetValue (property), Is.EqualTo ("Foo Bar"));
+ }
+
+ [Test]
+ public void StringFormatOnUpdate()
+ {
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
+
+ var binding = CreateBinding (BindingMode.Default, "Foo {0}");
+
+ var vm = new MockViewModel { Text = "Bar" };
+ var bo = new MockBindable { BindingContext = vm };
+ bo.SetBinding (property, binding);
+
+ vm.Text = "Baz";
+
+ Assert.That (bo.GetValue (property), Is.EqualTo ("Foo Baz"));
+ }
+
+ [Test]
+ [Description ("StringFormat should not be applied to OneWayToSource bindings")]
+ public void StringFormatOneWayToSource()
+ {
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
+
+ var binding = CreateBinding (BindingMode.OneWayToSource, "Foo {0}");
+
+ var vm = new MockViewModel { Text = "Bar" };
+ var bo = new MockBindable { BindingContext = vm };
+ bo.SetBinding (property, binding);
+
+ bo.SetValue (property, "Bar");
+
+ Assert.That (vm.Text, Is.EqualTo ("Bar"));
+ }
+
+ [Test]
+ [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 binding = CreateBinding (BindingMode.TwoWay, "Foo {0}");
+
+ var vm = new MockViewModel { Text = "Bar" };
+ var bo = new MockBindable { BindingContext = vm };
+ bo.SetBinding (property, binding);
+
+ bo.SetValue (property, "Baz");
+
+ Assert.That (vm.Text, Is.EqualTo ("Baz"));
+ Assert.That (bo.GetValue (property), Is.EqualTo ("Foo Baz"));
+ }
+
+ [Test]
+ [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 binding = CreateBinding (BindingMode.OneWay);
+
+ var vm = new MockViewModel { Text = "Bar" };
+ var bo = new MockBindable { BindingContext = vm };
+ bo.SetBinding (property, binding);
+
+ Assert.That (() => binding.Mode = BindingMode.OneWayToSource, Throws.InvalidOperationException);
+ Assert.That (() => binding.StringFormat = "{0}", Throws.InvalidOperationException);
+ }
+ }
+
+ [TestFixture]
+ public class BindingBaseTests : BaseTestFixture
+ {
+ [Test]
+ public void EnableCollectionSynchronizationInvalid()
+ {
+ Assert.That (() => BindingBase.EnableCollectionSynchronization (null, new object(),
+ (collection, context, method, access) => { }), Throws.InstanceOf<ArgumentNullException>());
+ Assert.That (() => BindingBase.EnableCollectionSynchronization (new string[0], new object(),
+ null), Throws.InstanceOf<ArgumentNullException>());
+ Assert.That (() => BindingBase.EnableCollectionSynchronization (new string[0], null,
+ (collection, context, method, access) => { }), Throws.Nothing);
+ }
+
+ [Test]
+ public void EnableCollectionSynchronization()
+ {
+ string[] stuff = new[] {"foo", "bar"};
+ object context = new object();
+ CollectionSynchronizationCallback callback = (collection, o, method, access) => { };
+
+ BindingBase.EnableCollectionSynchronization (stuff, context, callback);
+
+ CollectionSynchronizationContext syncContext;
+ Assert.IsTrue (BindingBase.TryGetSynchronizedCollection (stuff, out syncContext));
+ Assert.That (syncContext, Is.Not.Null);
+ Assert.AreSame (syncContext.Callback, callback);
+ Assert.That (syncContext.ContextReference, Is.Not.Null);
+ Assert.That (syncContext.ContextReference.Target, Is.SameAs (context));
+ }
+
+ [Test]
+ public void DisableCollectionSynchronization()
+ {
+ string[] stuff = new[] {"foo", "bar"};
+ object context = new object();
+ CollectionSynchronizationCallback callback = (collection, o, method, access) => { };
+
+ BindingBase.EnableCollectionSynchronization (stuff, context, callback);
+
+ BindingBase.DisableCollectionSynchronization (stuff);
+
+ CollectionSynchronizationContext syncContext;
+ Assert.IsFalse (BindingBase.TryGetSynchronizedCollection (stuff, out syncContext));
+ Assert.IsNull (syncContext);
+ }
+
+ [Test]
+ public void CollectionAndContextAreHeldWeakly()
+ {
+ WeakReference weakCollection = null, weakContext = null;
+
+ int i = 0;
+ Action create = null;
+ create = () => {
+ if (i++ < 1024) {
+ create();
+ return;
+ }
+
+ string[] collection = new[] {"foo", "bar"};
+ weakCollection = new WeakReference (collection);
+
+ object context = new object();
+ weakContext = new WeakReference (context);
+
+ BindingBase.EnableCollectionSynchronization (collection, context, (enumerable, o, method, access) => { });
+ };
+
+ create();
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+
+ Assert.IsFalse (weakCollection.IsAlive);
+ Assert.IsFalse (weakContext.IsAlive);
+ }
+
+ [Test]
+ public void CollectionAndContextAreHeldWeaklyClosingOverCollection()
+ {
+ WeakReference weakCollection = null, weakContext = null;
+
+ int i = 0;
+ Action create = null;
+ create = () => {
+ if (i++ < 1024) {
+ create();
+ return;
+ }
+
+ string[] collection = new[] {"foo", "bar"};
+ weakCollection = new WeakReference (collection);
+
+ object context = new object();
+ weakContext = new WeakReference (context);
+
+ BindingBase.EnableCollectionSynchronization (collection, context, (enumerable, o, method, access) => {
+ collection[0] = "baz";
+ });
+ };
+
+ create();
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+
+ Assert.IsFalse (weakCollection.IsAlive);
+ Assert.IsFalse (weakContext.IsAlive);
+ }
+
+ [Test]
+ public void DisableCollectionSynchronizationInvalid()
+ {
+ Assert.That (() => BindingBase.DisableCollectionSynchronization (null), Throws.InstanceOf<ArgumentNullException>());
+ }
+
+ [Test]
+ public void TryGetSynchronizedCollectionInvalid()
+ {
+ CollectionSynchronizationContext context;
+ Assert.That (() => BindingBase.TryGetSynchronizedCollection (null, out context),
+ Throws.InstanceOf<ArgumentNullException>());
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/BindingExpressionTests.cs b/Xamarin.Forms.Core.UnitTests/BindingExpressionTests.cs
new file mode 100644
index 00000000..b3f0198d
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/BindingExpressionTests.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class BindingExpressionTests : BaseTestFixture
+ {
+ [Test]
+ public void Ctor()
+ {
+ string path = "Foo.Bar";
+ var binding = new Binding (path);
+
+ var be = new BindingExpression (binding, path);
+
+ Assert.AreSame (binding, be.Binding);
+ Assert.AreEqual (path, be.Path);
+ }
+
+ [Test]
+ public void CtorInvalid()
+ {
+ string path = "Foo.Bar";
+ var binding = new Binding (path);
+
+ Assert.Throws<ArgumentNullException> (() => new BindingExpression (binding, null),
+ "Allowed the path to eb null");
+
+ Assert.Throws<ArgumentNullException> (() => new BindingExpression (null, path),
+ "Allowed the binding to be null");
+ }
+
+ [Test]
+ public void ApplyNull()
+ {
+ const string path = "Foo.Bar";
+ var binding = new Binding (path);
+ var be = new BindingExpression (binding, path);
+ Assert.DoesNotThrow (() => be.Apply (null, new MockBindable(), TextCell.TextProperty));
+ }
+
+ // We only throw on invalid path features, if they give an invalid property
+ // name, it won't have compiled in the first place or they misstyped.
+ [TestCase ("Foo.")]
+ [TestCase ("Foo[]")]
+ [TestCase ("Foo.Bar[]")]
+ [TestCase ("Foo[1")]
+ public void InvalidPaths (string path)
+ {
+ var fex = Assert.Throws<FormatException> (() => {
+ var binding = new Binding (path);
+ new BindingExpression (binding, path);
+ });
+
+ Assert.IsFalse (String.IsNullOrWhiteSpace (fex.Message),
+ "FormatException did not contain an explanation");
+ }
+
+ [Test]
+ public void ValidPaths (
+ [Values (
+ ".", "[1]", "[1 ]", ".[1]", ". [1]",
+ "Foo", "Foo.Bar", "Foo. Bar", "Foo.Bar[1]",
+ "Foo.Bar [1]")]
+ string path,
+ [Values (true, false)] bool spaceBefore,
+ [Values (true, false)] bool spaceAfter)
+ {
+ if (spaceBefore)
+ path = " " + path;
+ if (spaceAfter)
+ path = path + " ";
+
+ var binding = new Binding (path);
+ Assert.DoesNotThrow (() => new BindingExpression (binding, path));
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/BindingTests.cs b/Xamarin.Forms.Core.UnitTests/BindingTests.cs
new file mode 100644
index 00000000..d586a6dd
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/BindingTests.cs
@@ -0,0 +1,75 @@
+using NUnit.Framework;
+using System.Collections.Generic;
+using System;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ internal class BindingSystemTests : BaseTestFixture
+ {
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ }
+
+ class BindableViewCell : ViewCell
+ {
+ public static readonly BindableProperty NameProperty =
+ BindableProperty.Create<BindableViewCell, string> (w => w.Name, "");
+
+ public Label NameLabel { get; set; }
+
+ public string Name
+ {
+ get { return (string) GetValue (NameProperty); }
+ set { SetValue (NameProperty, value); }
+ }
+
+ public BindableViewCell ()
+ {
+ NameLabel = new Label {BindingContext = this};
+ NameLabel.SetBinding (Label.TextProperty, new Binding ("Name"));
+ View = NameLabel;
+ }
+ }
+
+ [Test]
+ public void RecursiveSettingInSystem ()
+ {
+ var tempObjects = new[] {
+ new {Name = "Test1"},
+ new {Name = "Test2"}
+ };
+
+ var template = new DataTemplate (typeof (BindableViewCell)) {
+ Bindings = { {BindableViewCell.NameProperty, new Binding ("Name")} }
+ };
+
+ var cell1 = (Cell)template.CreateContent ();
+ cell1.BindingContext = tempObjects[0];
+ cell1.Parent = new ListView ();
+
+ var cell2 = (Cell)template.CreateContent ();
+ cell2.BindingContext = tempObjects[1];
+ cell2.Parent = new ListView ();
+
+ var viewCell1 = (BindableViewCell) cell1;
+ var viewCell2 = (BindableViewCell) cell2;
+
+ Assert.AreEqual ("Test1", viewCell1.Name);
+ Assert.AreEqual ("Test2", viewCell2.Name);
+
+ Assert.AreEqual ("Test1", viewCell1.NameLabel.Text);
+ Assert.AreEqual ("Test2", viewCell2.NameLabel.Text);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/BindingTypeConverterTests.cs b/Xamarin.Forms.Core.UnitTests/BindingTypeConverterTests.cs
new file mode 100644
index 00000000..e6d97d24
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/BindingTypeConverterTests.cs
@@ -0,0 +1,27 @@
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class BindingTypeConverterTests : BaseTestFixture
+ {
+ [Test]
+ public void CanConvertFrom()
+ {
+ var c = new BindingTypeConverter();
+ Assert.That (c.CanConvertFrom (typeof (string)), Is.True);
+ Assert.That (c.CanConvertFrom (typeof (int)), Is.False);
+ }
+
+ [Test]
+ public void Convert()
+ {
+ var c = new BindingTypeConverter();
+ var binding = c.ConvertFromInvariantString ("Path");
+
+ Assert.That (binding, Is.InstanceOf<Binding>());
+ Assert.That (((Binding) binding).Path, Is.EqualTo ("Path"));
+ Assert.That (((Binding) binding).Mode, Is.EqualTo (BindingMode.Default));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs b/Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs
new file mode 100644
index 00000000..c520e929
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/BindingUnitTests.cs
@@ -0,0 +1,2649 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using NUnit.Framework;
+using CategoryAttribute=NUnit.Framework.CategoryAttribute;
+using DescriptionAttribute=NUnit.Framework.DescriptionAttribute;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class BindingUnitTests
+ : BindingBaseUnitTests
+ {
+ class Logger
+ : LogListener
+ {
+ public IReadOnlyList<string> Messages
+ {
+ get { return messages; }
+ }
+
+ public override void Warning (string category, string message)
+ {
+ messages.Add ("[" + category + "] " + message);
+ }
+
+ readonly List<string> messages = new List<string>();
+ }
+
+ Logger log;
+
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup ();
+ log = new Logger();
+
+ Device.PlatformServices = new MockPlatformServices ();
+ Log.Listeners.Add (log);
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ Log.Listeners.Remove (log);
+ }
+
+ protected override BindingBase CreateBinding (BindingMode mode, string stringFormat = null)
+ {
+ return new Binding ("Text", mode, stringFormat: stringFormat);
+ }
+
+ [Test]
+ public void Ctor()
+ {
+ const string path = "Foo";
+
+ var binding = new Binding (path, BindingMode.OneWayToSource);
+ Assert.AreEqual (path, binding.Path);
+ Assert.AreEqual (BindingMode.OneWayToSource, binding.Mode);
+ }
+
+ [Test]
+ public void InvalidCtor()
+ {
+ Assert.Throws<ArgumentNullException> (() => new Binding (null),
+ "Allowed null Path");
+
+ Assert.Throws<ArgumentException> (() => new Binding (String.Empty),
+ "Allowed empty path");
+
+ Assert.Throws<ArgumentException> (() => new Binding (" "),
+ "Allowed whitespace path");
+
+ Assert.Throws<ArgumentException> (() => new Binding ("Path", (BindingMode)Int32.MaxValue),
+ "Allowed invalid value for BindingMode");
+ }
+
+ [Test]
+ [Description ("You should get an exception when trying to change a binding after it's been applied")]
+ public void ChangeBindingAfterApply()
+ {
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
+
+ var binding = new Binding { Path = "Text" };
+
+ var vm = new MockViewModel { Text = "Bar" };
+ var bo = new MockBindable { BindingContext = vm };
+ bo.SetBinding (property, binding);
+
+ Assert.That (() => binding.Path = "path", Throws.InvalidOperationException);
+ Assert.That (() => binding.Converter = null, Throws.InvalidOperationException);
+ Assert.That (() => binding.ConverterParameter = new object(), Throws.InvalidOperationException);
+ }
+
+ [Test]
+ public void NullPathIsSelf()
+ {
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
+
+ var binding = new Binding();
+
+ var bo = new MockBindable { BindingContext = "Foo" };
+ bo.SetBinding (property, binding);
+
+ Assert.That (bo.GetValue (property), Is.EqualTo ("Foo"));
+ }
+
+ class DoubleViewModel
+ : MockViewModel
+ {
+ public double Value
+ {
+ get;
+ set;
+ }
+ }
+
+ [Test]
+ public void StringFormatNonStringType()
+ {
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Foo, null);
+
+ var binding = new Binding ("Value", stringFormat: "{0:P2}");
+
+ var vm = new DoubleViewModel { Value = 0.95 };
+ var bo = new MockBindable { BindingContext = vm };
+ bo.SetBinding (property, binding);
+
+ if (System.Threading.Thread.CurrentThread.CurrentCulture.Name == "tr-TR")
+ Assert.That (bo.GetValue (property), Is.EqualTo ("%95,00"));
+ else
+ Assert.That (bo.GetValue (property), Is.EqualTo ("95.00 %"));
+ }
+
+ [Test]
+ public void ReuseBindingInstance()
+ {
+ var vm = new MockViewModel();
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = vm;
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null);
+ var binding = new Binding ("Text");
+ bindable.SetBinding (property, binding);
+
+ var bindable2 = new MockBindable();
+ bindable2.BindingContext = new MockViewModel();
+ Assert.Throws<InvalidOperationException> (() => bindable2.SetBinding (property, binding),
+ "Binding allowed reapplication with a different context");
+ }
+
+ class ComplexPropertyNamesViewModel
+ : MockViewModel
+ {
+ public string Foo_Bar
+ {
+ get;
+ set;
+ }
+
+ public string @if
+ {
+ get;
+ set;
+ }
+
+ /*public string P̀ः०‿
+ {
+ get;
+ set;
+ }*/
+
+ public string _UnderscoreStart
+ {
+ get;
+ set;
+ }
+ }
+
+ [Category ("[Binding] Complex paths")]
+ [TestCase ("Foo_Bar")]
+ [TestCase ("if")]
+ //TODO FIXME [TestCase ("P̀ः०‿")]
+ [TestCase ("_UnderscoreStart")]
+ public void ComplexPropertyNames (string propertyName)
+ {
+ var vm = new ComplexPropertyNamesViewModel();
+ vm.GetType().GetProperty (propertyName).SetValue (vm, "Value");
+
+ var binding = new Binding (propertyName);
+
+ var bindable = new MockBindable { BindingContext = vm };
+ bindable.SetBinding (MockBindable.TextProperty, binding);
+
+ Assert.That (bindable.Text, Is.EqualTo ("Value"));
+ }
+
+ [Test, Category ("[Binding] Simple paths")]
+ public void ValueSetOnOneWayWithSimplePathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new MockViewModel {
+ Text = value
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null, propertyDefault);
+
+ var binding = new Binding ("Text", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Text,
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Simple paths")]
+ public void ValueSetOnOneWayToSourceWithSimplePathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new MockViewModel();
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text,
+ defaultValue: value, defaultBindingMode: propertyDefault);
+
+ var binding = new Binding ("Text", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property changed");
+ Assert.AreEqual (value, viewmodel.Text,
+ "BindingContext property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Simple paths")]
+ public void ValueSetOnTwoWayWithSimplePathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new MockViewModel {
+ Text = value
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding ("Text", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Text,
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test]
+ [Category ("[Binding] Complex paths")]
+ public void ValueSetOnOneWayWithComplexPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Text = value
+ }
+ }
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null, propertyDefault);
+
+ var binding = new Binding ("Model.Model.Text", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Model.Model.Text,
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Complex paths")]
+ public void ValueSetOnOneWayToSourceWithComplexPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ }
+ }
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text,
+ defaultValue: value, defaultBindingMode: propertyDefault);
+
+ var binding = new Binding ("Model.Model.Text", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property changed");
+ Assert.AreEqual (value, viewmodel.Model.Model.Text,
+ "BindingContext property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Complex paths")]
+ public void ValueSetOnTwoWayWithComplexPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Text = value
+ }
+ }
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding ("Model.Model.Text", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Model.Model.Text,
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ class Outer {
+ public Outer (Inner inner)
+ {
+ PropertyWithPublicSetter = inner;
+ PropertyWithPrivateSetter = inner;
+ }
+
+ public Inner PropertyWithPublicSetter { get; set; }
+ public Inner PropertyWithPrivateSetter { get; private set; }
+ }
+
+ class Inner {
+ public Inner (string property)
+ {
+ GetSetProperty = property;
+ }
+
+ public string GetSetProperty { get; set; }
+ }
+
+ [Test, Category ("[Binding] Complex paths")]
+ public void BindingWithNoPublicSetterOnParent (
+ [Values (true, false)] bool setContextFirst,
+ [Values (BindingMode.OneWay, BindingMode.TwoWay)] BindingMode bindingmode,
+ [Values (true, false)] bool usePrivateSetter)
+ {
+ var value = "FooBar";
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Text, "default value", BindingMode.Default);
+ var binding = new Binding (usePrivateSetter? "PropertyWithPrivateSetter.GetSetProperty": "PropertyWithPublicSetter.GetSetProperty", bindingmode);
+ var viewmodel = new Outer (new Inner (value));
+ var bindable = new MockBindable ();
+
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.PropertyWithPublicSetter.GetSetProperty);
+ Assert.AreEqual (value, bindable.GetValue (property));
+
+ if (bindingmode == BindingMode.TwoWay) {
+ var updatedValue = "Qux";
+ bindable.SetValue (property, updatedValue);
+ Assert.AreEqual (updatedValue, bindable.GetValue (property));
+ Assert.AreEqual (updatedValue, viewmodel.PropertyWithPublicSetter.GetSetProperty);
+ }
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnOneWayWithIndexedPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+ };
+ viewmodel.Model.Model[1] = value;
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null, propertyDefault);
+
+ var binding = new Binding ("Model.Model[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Model.Model[1],
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnOneWayWithSelfIndexedPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel();
+ viewmodel[1] = value;
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null, propertyDefault);
+
+ var binding = new Binding (".[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel[1],
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnOneWayWithIndexedPathArrayBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Bar";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Array = new [] { "Foo", "Bar" }
+ }
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null, propertyDefault);
+
+ var binding = new Binding ("Model.Array[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Model.Array[1],
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnOneWayWithIndexedSelfPathArrayBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "bar";
+ string[] context = new[] { "foo", value };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null, propertyDefault);
+
+ var binding = new Binding (".[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = context;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = context;
+ }
+
+ Assert.AreEqual (value, context[1],
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnOneWayToSourceWithIndexedPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, defaultValue: value, defaultBindingMode: propertyDefault);
+
+ var binding = new Binding ("Model.Model[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property changed");
+ Assert.AreEqual (value, viewmodel.Model.Model[1],
+ "BindingContext property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnTwoWayWithIndexedPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+ };
+ viewmodel.Model.Model[1] = value;
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding ("Model.Model[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Model.Model[1],
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnTwoWayWithIndexedArrayPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Array = new string[2]
+ }
+ };
+ viewmodel.Model.Array[1] = value;
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding ("Model.Array[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel.Model.Array[1],
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Indexed paths")]
+ public void ValueSetOnTwoWayWithIndexedArraySelfPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ const string value = "Foo";
+ string[] viewmodel = new [] { "bar", value };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding (".[1]", bindingMode);
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = viewmodel;
+ }
+
+ Assert.AreEqual (value, viewmodel[1],
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Self paths")]
+ public void ValueSetOnOneWayWithSelfPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null, propertyDefault);
+
+ var binding = new Binding (".", bindingMode);
+
+ const string value = "value";
+
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = value;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = value;
+ }
+
+ Assert.AreEqual (value, bindable.BindingContext,
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Self paths")]
+ public void ValueNotSetOnOneWayToSourceWithSelfPathBinding (
+ [Values (true, false)] bool isDefault)
+ {
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ const string value = "Foo";
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, defaultValue: value, defaultBindingMode: propertyDefault);
+
+ var binding = new Binding (".", bindingMode);
+
+ var bindable = new MockBindable();
+ Assert.IsNull (bindable.BindingContext);
+
+ bindable.SetBinding (property, binding);
+
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property changed");
+ Assert.IsNull (bindable.BindingContext,
+ "BindingContext changed with self-path binding");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Self paths")]
+ public void ValueSetOnTwoWayWithSelfPathBinding (
+ [Values (true, false)] bool setContextFirst,
+ [Values (true, false)] bool isDefault)
+ {
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding (".", bindingMode);
+
+ const string value = "Foo";
+ var bindable = new MockBindable();
+ if (setContextFirst) {
+ bindable.BindingContext = value;
+ bindable.SetBinding (property, binding);
+ } else {
+ bindable.SetBinding (property, binding);
+ bindable.BindingContext = value;
+ }
+
+ Assert.AreEqual (value, bindable.BindingContext,
+ "BindingContext property changed");
+ Assert.AreEqual (value, bindable.GetValue (property),
+ "Target property did not change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Simple paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithSimplePathOnOneWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new MockViewModel {
+ Text = "Foo"
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Text", bindingMode));
+
+ viewmodel.Text = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Bindable did not update on binding context property change");
+ Assert.AreEqual (newvalue, viewmodel.Text,
+ "Source property changed when it shouldn't");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Simple paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithSimplePathOnOneWayToSourceBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new MockViewModel {
+ Text = "Foo"
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Text", bindingMode));
+
+ string original = (string)bindable.GetValue (property);
+ const string value = "value";
+ viewmodel.Text = value;
+ Assert.AreEqual (original, bindable.GetValue (property),
+ "Target updated from Source on OneWayToSource");
+
+ bindable.SetValue (property, newvalue);
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Bindable did not update on binding context property change");
+ Assert.AreEqual (newvalue, viewmodel.Text,
+ "Source property changed when it shouldn't");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Simple paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithSimplePathOnTwoWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new MockViewModel {
+ Text = "Foo"
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Text", bindingMode));
+
+ viewmodel.Text = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Target property did not update change");
+ Assert.AreEqual (newvalue, viewmodel.Text,
+ "Source property changed from what it was set to");
+
+ const string newvalue2 = "New Value in the other direction";
+
+ bindable.SetValue (property, newvalue2);
+ Assert.AreEqual (newvalue2, viewmodel.Text,
+ "Source property did not update with Target's change");
+ Assert.AreEqual (newvalue2, bindable.GetValue (property),
+ "Target property changed from what it was set to");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Complex paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithComplexPathOnOneWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Text = "Foo"
+ }
+ }
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Model.Model.Text", bindingMode));
+
+ viewmodel.Model.Model.Text = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Bindable did not update on binding context property change");
+ Assert.AreEqual (newvalue, viewmodel.Model.Model.Text,
+ "Source property changed when it shouldn't");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Complex paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithComplexPathOnOneWayToSourceBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Text = "Foo"
+ }
+ }
+ };;
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Model.Model.Text", bindingMode));
+
+ string original = (string)bindable.GetValue (property);
+ const string value = "value";
+ viewmodel.Model.Model.Text = value;
+ Assert.AreEqual (original, bindable.GetValue (property),
+ "Target updated from Source on OneWayToSource");
+
+ bindable.SetValue (property, newvalue);
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Bindable did not update on binding context property change");
+ Assert.AreEqual (newvalue, viewmodel.Model.Model.Text,
+ "Source property changed when it shouldn't");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Complex paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithComplexPathOnTwoWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Text = "Foo"
+ }
+ }
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Model.Model.Text", bindingMode));
+
+ viewmodel.Model.Model.Text = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Target property did not update change");
+ Assert.AreEqual (newvalue, viewmodel.Model.Model.Text,
+ "Source property changed from what it was set to");
+
+ const string newvalue2 = "New Value in the other direction";
+
+ bindable.SetValue (property, newvalue2);
+ Assert.AreEqual (newvalue2, viewmodel.Model.Model.Text,
+ "Source property did not update with Target's change");
+ Assert.AreEqual (newvalue2, bindable.GetValue (property),
+ "Target property changed from what it was set to");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Indexed paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithIndexedPathOnOneWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+ };
+ viewmodel.Model.Model[1] = "Foo";
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Model.Model[1]", bindingMode));
+
+ viewmodel.Model.Model[1] = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Bindable did not update on binding context property change");
+ Assert.AreEqual (newvalue, viewmodel.Model.Model[1],
+ "Source property changed when it shouldn't");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Indexed paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithIndexedPathOnOneWayToSourceBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+ };
+ viewmodel.Model.Model[1] = "Foo";
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Model.Model[1]", bindingMode));
+
+ string original = (string)bindable.GetValue (property);
+ const string value = "value";
+ viewmodel.Model.Model[1] = value;
+ Assert.AreEqual (original, bindable.GetValue (property),
+ "Target updated from Source on OneWayToSource");
+
+ bindable.SetValue (property, newvalue);
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Bindable did not update on binding context property change");
+ Assert.AreEqual (newvalue, viewmodel.Model.Model[1],
+ "Source property changed when it shouldn't");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Indexed paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithIndexedPathOnTwoWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+ };
+ viewmodel.Model.Model[1] = "Foo";
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Model.Model[1]", bindingMode));
+
+ viewmodel.Model.Model[1] = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Target property did not update change");
+ Assert.AreEqual (newvalue, viewmodel.Model.Model[1],
+ "Source property changed from what it was set to");
+
+ const string newvalue2 = "New Value in the other direction";
+
+ bindable.SetValue (property, newvalue2);
+ Assert.AreEqual (newvalue2, viewmodel.Model.Model[1],
+ "Source property did not update with Target's change");
+ Assert.AreEqual (newvalue2, bindable.GetValue (property),
+ "Target property changed from what it was set to");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Indexed paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithIndexedArrayPathOnTwoWayBinding (bool isDefault)
+ {
+ var viewmodel = new ComplexMockViewModel {
+ Array = new string[2]
+ };
+ viewmodel.Array[1] = "Foo";
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, new Binding ("Array[1]", bindingMode));
+
+ const string newvalue2 = "New Value in the other direction";
+
+ bindable.SetValue (property, newvalue2);
+ Assert.AreEqual (newvalue2, viewmodel.Array[1],
+ "Source property did not update with Target's change");
+ Assert.AreEqual (newvalue2, bindable.GetValue (property),
+ "Target property changed from what it was set to");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Self paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithSelfPathOnOneWayBinding (bool isDefault)
+ {
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ const string value = "foo";
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = value;
+ bindable.SetBinding (property, new Binding (".", bindingMode));
+
+ const string newvalue = "value";
+ bindable.SetValue (property, newvalue);
+ Assert.AreEqual (value, bindable.BindingContext,
+ "Source was updated from Target on OneWay binding");
+
+ bindable.BindingContext = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Bindable did not update on binding context property change");
+ Assert.AreEqual (newvalue, bindable.BindingContext,
+ "Source property changed when it shouldn't");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Self paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueDoesNotUpdateWithSelfPathOnOneWayToSourceBinding (bool isDefault)
+ {
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding (".", bindingMode);
+
+ var bindable = new MockBindable();
+ bindable.SetBinding (property, binding);
+
+ const string newvalue = "new value";
+
+ string original = (string)bindable.GetValue (property);
+ bindable.BindingContext = newvalue;
+ Assert.AreEqual (original, bindable.GetValue (property),
+ "Target updated from Source on OneWayToSource with self path");
+
+ const string newvalue2 = "new value 2";
+ bindable.SetValue (property, newvalue2);
+ Assert.AreEqual (newvalue2, bindable.GetValue (property),
+ "Target property changed on OneWayToSource with self path");
+ Assert.AreEqual (newvalue, bindable.BindingContext,
+ "Source property changed on OneWayToSource with self path");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Category ("[Binding] Self paths")]
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithSelfPathOnTwoWayBinding (bool isDefault)
+ {
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding (".", bindingMode);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = "value";
+ bindable.SetBinding (property, binding);
+
+ const string newvalue = "New Value";
+ bindable.BindingContext = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property),
+ "Target property did not update change");
+ Assert.AreEqual (newvalue, bindable.BindingContext,
+ "Source property changed from what it was set to");
+
+ const string newvalue2 = "New Value in the other direction";
+
+ bindable.SetValue (property, newvalue2);
+ Assert.AreEqual (newvalue, bindable.BindingContext,
+ "Self-path Source changed with Target's change");
+ Assert.AreEqual (newvalue2, bindable.GetValue (property),
+ "Target property changed from what it was set to");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithOldContextDoesNotUpdateWithOneWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new MockViewModel {
+ Text = "Foo"
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding ("Text", bindingMode);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+
+ bindable.BindingContext = new MockViewModel();
+ Assert.AreEqual (null, bindable.GetValue (property));
+
+ viewmodel.Text = newvalue;
+ Assert.AreEqual (null, bindable.GetValue (property),
+ "Target updated from old Source property change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithOldContextDoesNotUpdateWithTwoWayBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new MockViewModel {
+ Text = "Foo"
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.TwoWay;
+ if (isDefault) {
+ propertyDefault = BindingMode.TwoWay;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding ("Text", bindingMode);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+
+ bindable.BindingContext = new MockViewModel();
+ Assert.AreEqual (null, bindable.GetValue (property));
+
+ viewmodel.Text = newvalue;
+ Assert.AreEqual (null, bindable.GetValue (property),
+ "Target updated from old Source property change");
+
+ string original = viewmodel.Text;
+
+ bindable.SetValue (property, newvalue);
+ Assert.AreEqual (original, viewmodel.Text,
+ "Source updated from old Target property change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [TestCase (true)]
+ [TestCase (false)]
+ public void ValueUpdatedWithOldContextDoesNotUpdateWithOneWayToSourceBinding (bool isDefault)
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new MockViewModel {
+ Text = "Foo"
+ };
+
+ BindingMode propertyDefault = BindingMode.OneWay;
+ BindingMode bindingMode = BindingMode.OneWayToSource;
+ if (isDefault) {
+ propertyDefault = BindingMode.OneWayToSource;
+ bindingMode = BindingMode.Default;
+ }
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", propertyDefault);
+
+ var binding = new Binding ("Text", bindingMode);
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+
+ bindable.BindingContext = new MockViewModel();
+ Assert.AreEqual (property.DefaultValue, bindable.GetValue (property));
+
+ viewmodel.Text = newvalue;
+ Assert.AreEqual (property.DefaultValue, bindable.GetValue (property),
+ "Target updated from old Source property change");
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test]
+ public void BindingStaysOnUpdateValueFromBinding()
+ {
+ const string newvalue = "New Value";
+ var viewmodel = new MockViewModel {
+ Text = "Foo"
+ };
+
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, null);
+
+ var binding = new Binding ("Text");
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+
+ viewmodel.Text = newvalue;
+ Assert.AreEqual (newvalue, bindable.GetValue (property));
+
+ const string newValue2 = "new value 2";
+ viewmodel.Text = newValue2;
+ Assert.AreEqual (newValue2, bindable.GetValue (property));
+
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test]
+ public void OneWayToSourceContextSetToNull()
+ {
+ var binding = new Binding ("Text", BindingMode.OneWayToSource);
+
+ MockBindable bindable = new MockBindable {
+ BindingContext = new MockViewModel()
+ };
+ bindable.SetBinding (MockBindable.TextProperty, binding);
+
+ Assert.That (() => bindable.BindingContext = null, Throws.Nothing);
+ }
+
+ [Category ("[Binding] Simple paths")]
+ [TestCase (BindingMode.OneWay)]
+ [TestCase (BindingMode.OneWayToSource)]
+ [TestCase (BindingMode.TwoWay)]
+ public void SourceAndTargetAreWeakWeakSimplePath (BindingMode mode)
+ {
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value", BindingMode.OneWay);
+
+ var binding = new Binding ("Text", mode);
+
+ WeakReference weakViewModel = null, weakBindable = null;
+
+ int i = 0;
+ Action create = null;
+ create = () => {
+ if (i++ < 1024) {
+ create();
+ return;
+ }
+
+ MockBindable bindable = new MockBindable();
+ weakBindable = new WeakReference (bindable);
+
+ MockViewModel viewmodel = new MockViewModel();
+ weakViewModel = new WeakReference (viewmodel);
+
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+
+ Assume.That (() => bindable.BindingContext = null, Throws.Nothing);
+ };
+
+ create();
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+
+ if (mode == BindingMode.TwoWay || mode == BindingMode.OneWay)
+ Assert.IsFalse (weakViewModel.IsAlive, "ViewModel wasn't collected");
+
+ if (mode == BindingMode.TwoWay || mode == BindingMode.OneWayToSource)
+ Assert.IsFalse (weakBindable.IsAlive, "Bindable wasn't collected");
+ }
+
+ internal class ComplexMockViewModel
+ : MockViewModel
+ {
+ public ComplexMockViewModel Model
+ {
+ get { return model; }
+ set
+ {
+ if (model == value)
+ return;
+
+ model = value;
+ OnPropertyChanged ("Model");
+ }
+ }
+
+ internal int count;
+ public int QueryCount
+ {
+ get { return count++; }
+ }
+
+ [IndexerName ("Indexer")]
+ public string this [int v]
+ {
+ get { return values[v]; }
+ set
+ {
+ if (values[v] == value)
+ return;
+
+ values[v] = value;
+ OnPropertyChanged ("Indexer[" + v + "]");
+ }
+ }
+
+ public string[] Array
+ {
+ get;
+ set;
+ }
+
+ public object DoStuff()
+ {
+ return null;
+ }
+
+ public object DoStuff (object argument)
+ {
+ return null;
+ }
+
+ string[] values = new string[5];
+ ComplexMockViewModel model;
+ }
+
+ [Category ("[Binding] Complex paths")]
+ [TestCase (BindingMode.OneWay)]
+ [TestCase (BindingMode.OneWayToSource)]
+ [TestCase (BindingMode.TwoWay)]
+ public void SourceAndTargetAreWeakComplexPath (BindingMode mode)
+ {
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "default value");
+
+ var binding = new Binding ("Model.Model[1]");
+
+ WeakReference weakViewModel = null, weakBindable = null;
+
+ HackAroundMonoSucking (0, property, binding, out weakViewModel, out weakBindable);
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+
+ if (mode == BindingMode.TwoWay || mode == BindingMode.OneWay)
+ Assert.IsFalse (weakViewModel.IsAlive, "ViewModel wasn't collected");
+
+ if (mode == BindingMode.TwoWay || mode == BindingMode.OneWayToSource)
+ Assert.IsFalse (weakBindable.IsAlive, "Bindable wasn't collected");
+ }
+
+ // Mono doesn't handle the GC properly until the stack frame where the object is created is popped.
+ // This means calling another method and not just using lambda as works in real .NET
+ void HackAroundMonoSucking (int i, BindableProperty property, Binding binding, out WeakReference weakViewModel, out WeakReference weakBindable)
+ {
+ if (i++ < 1024) {
+ HackAroundMonoSucking (i, property, binding, out weakViewModel, out weakBindable);
+ return;
+ }
+
+ MockBindable bindable = new MockBindable();
+
+ weakBindable = new WeakReference (bindable);
+
+ ComplexMockViewModel viewmodel = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+ };
+
+ weakViewModel = new WeakReference (viewmodel);
+
+ bindable.BindingContext = viewmodel;
+ bindable.SetBinding (property, binding);
+
+ bindable.BindingContext = null;
+ }
+
+ class TestConverter<TSource,TTarget>
+ : IValueConverter
+ {
+ public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ Assert.AreEqual (typeof (TTarget), targetType);
+ return System.Convert.ChangeType (value, targetType, CultureInfo.CurrentUICulture);
+ }
+
+ public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ Assert.AreEqual (typeof (TSource), targetType);
+ return System.Convert.ChangeType (value, targetType, CultureInfo.CurrentUICulture);
+ }
+ }
+
+ [Test]
+ public void ValueConverter()
+ {
+ var converter = new TestConverter<string, int>();
+
+ var vm = new MockViewModel { Text = "1" };
+ var property = BindableProperty.Create<MockBindable, int> (w=>w.TargetInt, 0);
+
+ var bindable = new MockBindable();
+ bindable.SetBinding (property, new Binding ("Text", converter: converter));
+ bindable.BindingContext = vm;
+
+ Assert.AreEqual (1, bindable.GetValue (property));
+
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test]
+ public void ValueConverterBack()
+ {
+ var converter = new TestConverter<string, int>();
+
+ var vm = new MockViewModel();
+ var property = BindableProperty.Create<MockBindable, int> (w=>w.TargetInt, 1, BindingMode.OneWayToSource);
+ var bindable = new MockBindable();
+ bindable.SetBinding (property, new Binding ("Text", converter: converter));
+ bindable.BindingContext = vm;
+
+ Assert.AreEqual ("1", vm.Text);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+
+ class TestConverterParameter : IValueConverter
+ {
+ public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return parameter;
+ }
+
+ public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return parameter;
+ }
+ }
+
+ [Test]
+ public void ValueConverterParameter ()
+ {
+ var converter = new TestConverterParameter ();
+
+ var vm = new MockViewModel ();
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "Bar", BindingMode.OneWayToSource);
+ var bindable = new MockBindable();
+ bindable.SetBinding (property, new Binding ("Text", converter: converter, converterParameter: "Foo"));
+ bindable.BindingContext = vm;
+
+ Assert.AreEqual ("Foo", vm.Text);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ class TestConverterCulture : IValueConverter
+ {
+ public object Convert (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return culture.ToString ();
+ }
+
+ public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return culture.ToString ();
+ }
+ }
+
+ #if !WINDOWS_PHONE
+ [Test]
+ [SetUICulture ("pt-PT")]
+ public void ValueConverterCulture ()
+ {
+ var converter = new TestConverterCulture ();
+ var vm = new MockViewModel ();
+ var property = BindableProperty.Create<MockBindable, string> (w=>w.Text, "Bar", BindingMode.OneWayToSource);
+ var bindable = new MockBindable();
+ bindable.SetBinding (property, new Binding ("Text", converter: converter));
+ bindable.BindingContext = vm;
+
+ Assert.AreEqual ("pt-PT", vm.Text);
+ }
+ #endif
+
+ [Test]
+ public void SelfBindingConverter()
+ {
+ var converter = new TestConverter<int, string> ();
+
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Text, "0");
+ var bindable = new MockBindable ();
+ bindable.BindingContext = 1;
+ bindable.SetBinding (property, new Binding (Binding.SelfPath, converter:converter));
+ Assert.AreEqual ("1", bindable.GetValue (property));
+
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ internal class MultiplePropertyViewModel
+ : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ int done;
+ public int Done
+ {
+ get { return done; }
+ set
+ {
+ done = value;
+ OnPropertyChanged();
+ OnPropertyChanged ("Progress");
+ }
+ }
+
+ int total = 100;
+ public int Total
+ {
+ get { return total; }
+ set
+ {
+ if (total == value)
+ return;
+
+ total = value;
+ OnPropertyChanged();
+ OnPropertyChanged ("Progress");
+ }
+ }
+
+ public float Progress
+ {
+ get { return (float)done / total; }
+ }
+
+ protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null)
+ {
+ PropertyChangedEventHandler handler = PropertyChanged;
+ if (handler != null)
+ handler (this, new PropertyChangedEventArgs (propertyName));
+ }
+ }
+
+ internal class MultiplePropertyBindable
+ : BindableObject
+ {
+ public static readonly BindableProperty ValueProperty =
+ BindableProperty.Create<MultiplePropertyBindable, float> (b => b.Value, 0f);
+
+ public float Value
+ {
+ get { return (float)GetValue (ValueProperty); }
+ set { SetValue (ValueProperty, value); }
+ }
+
+ public static readonly BindableProperty DoneProperty =
+ BindableProperty.Create<MultiplePropertyBindable, int> (b => b.Done, 0);
+
+ public int Done
+ {
+ get { return (int)GetValue (DoneProperty); }
+ set { SetValue (DoneProperty, value); }
+ }
+ }
+
+ [Test]
+ public void MultiplePropertyUpdates()
+ {
+ var mpvm = new MultiplePropertyViewModel();
+
+ var bindable = new MultiplePropertyBindable();
+ bindable.SetBinding (MultiplePropertyBindable.ValueProperty, new Binding ("Progress", BindingMode.OneWay));
+ bindable.SetBinding (MultiplePropertyBindable.DoneProperty, new Binding ("Done", BindingMode.OneWayToSource));
+ bindable.BindingContext = mpvm;
+
+ bindable.Done = 5;
+
+ Assert.AreEqual (5, mpvm.Done);
+ Assert.AreEqual (0.05f, mpvm.Progress);
+ Assert.AreEqual (5, bindable.Done);
+ Assert.AreEqual (0.05f, bindable.Value);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Complex paths")]
+ [Description ("When part of a complex path can not be evaluated during an update, bindables should return to their default value.")]
+ public void NullInPathUsesDefaultValue()
+ {
+ var vm = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ };
+
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Text, "foo bar");
+
+ var bindable = new MockBindable();
+ bindable.SetBinding (property, new Binding ("Model.Text", BindingMode.OneWay));
+ bindable.BindingContext = vm;
+
+ vm.Model = null;
+
+ Assert.AreEqual (property.DefaultValue, bindable.GetValue (property));
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test, Category ("[Binding] Complex paths")]
+ [Description ("When part of a complex path can not be evaluated during an update, bindables should return to their default value.")]
+ public void NullContextUsesDefaultValue()
+ {
+ var vm = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel {
+ Text = "vm value"
+ }
+ };
+
+ var property = BindableProperty.Create<MockBindable, string> (w => w.Text, "foo bar");
+
+ var bindable = new MockBindable();
+ bindable.SetBinding (property, new Binding ("Model.Text", BindingMode.OneWay));
+ bindable.BindingContext = vm;
+
+ Assume.That (bindable.GetValue (property), Is.EqualTo (vm.Model.Text));
+
+ bindable.BindingContext = null;
+
+ Assert.AreEqual (property.DefaultValue, bindable.GetValue (property));
+ Assert.That (log.Messages.Count, Is.EqualTo (0),
+ "An error was logged: " + log.Messages.FirstOrDefault());
+ }
+
+ [Test]
+ [Description ("OneWay bindings should not double apply on source updates.")]
+ public void OneWayBindingsDontDoubleApplyOnSourceUpdates()
+ {
+ var vm = new ComplexMockViewModel();
+
+ var bindable = new MockBindable();
+ bindable.SetBinding (MultiplePropertyBindable.DoneProperty, new Binding ("QueryCount", BindingMode.OneWay));
+ bindable.BindingContext = vm;
+
+ Assert.AreEqual (1, vm.count);
+
+ bindable.BindingContext = null;
+
+ Assert.AreEqual (1, vm.count, "Source property was queried on an unset");
+
+ bindable.BindingContext = vm;
+
+ Assert.AreEqual (2, vm.count, "Source property was queried multiple times on a reapply");
+ }
+
+ [Test]
+ [Description ("When there are multiple bindings, an update in one should not cause the other to udpate.")]
+ public void BindingsShouldNotTriggerOtherBindings()
+ {
+ var vm = new ComplexMockViewModel();
+
+ var bindable = new MockBindable();
+ bindable.SetBinding (MultiplePropertyBindable.DoneProperty, new Binding ("QueryCount", BindingMode.OneWay));
+ bindable.SetBinding (MockBindable.TextProperty, new Binding ("Text", BindingMode.OneWay));
+ bindable.BindingContext = vm;
+
+ Assert.AreEqual (1, vm.count);
+
+ vm.Text = "update";
+
+ Assert.AreEqual (1, vm.count, "Source property was queried due to a different binding update.");
+ }
+
+ internal class DerivedViewModel
+ : MockViewModel
+ {
+ public override string Text
+ {
+ get { return base.Text + "2"; }
+ set { base.Text = value; }
+ }
+ }
+
+ [Test]
+ [Description ("The most derived version of a property should always be called.")]
+ public void MostDerviedPropertyOnContextSwitchOfSimilarType()
+ {
+ var vm = new MockViewModel { Text = "text" };
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = vm;
+ bindable.SetBinding (MockBindable.TextProperty, new Binding ("Text"));
+
+ Assert.AreEqual (vm.Text, bindable.GetValue (MockBindable.TextProperty));
+
+ bindable.BindingContext = vm = new DerivedViewModel { Text = "text" };
+
+ Assert.AreEqual (vm.Text, bindable.GetValue (MockBindable.TextProperty));
+ }
+
+ internal class EmptyViewModel
+ {
+ }
+
+ internal class DifferentViewModel
+ : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ string text = "foo";
+
+ public string Text
+ {
+ get { return text; }
+ }
+
+ public string Text2
+ {
+ set { text = value; }
+ }
+
+ public string PrivateSetter
+ {
+ get;
+ private set;
+ }
+ }
+
+ [Test]
+ [Description ("Paths should not distinguish types, a context change to a completely different type should work.")]
+ public void DifferentContextTypeAccessedCorrectlyWithSamePath()
+ {
+ var vm = new MockViewModel { Text = "text" };
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = vm;
+ bindable.SetBinding (MockBindable.TextProperty, new Binding ("Text"));
+
+ Assert.AreEqual (vm.Text, bindable.GetValue (MockBindable.TextProperty));
+
+ var dvm = new DifferentViewModel();
+ bindable.BindingContext = dvm;
+
+ Assert.AreEqual (dvm.Text, bindable.GetValue (MockBindable.TextProperty));
+ }
+
+ [Test]
+ public void PropertyChangeBindingsOccurThroughMainThread()
+ {
+ var vm = new MockViewModel { Text = "text" };
+
+ var bindable = new MockBindable();
+ bindable.BindingContext = vm;
+ bindable.SetBinding (MockBindable.TextProperty, new Binding ("Text"));
+
+ bool mainThread = false;
+ Device.PlatformServices = new MockPlatformServices (invokeOnMainThread: a => mainThread = true);
+
+ vm.Text = "updated";
+
+ Assert.IsTrue (mainThread, "Binding did not occur on main thread");
+ Assert.AreNotEqual (vm.Text, bindable.GetValue (MockBindable.TextProperty), "Binding was applied anyway through other means");
+ }
+
+ [Test]
+ public void Clone()
+ {
+ object param = new object();
+ var binding = new Binding (".", converter: new TestConverter<string, int>(), converterParameter: param, stringFormat: "{0}");
+ var clone = (Binding)binding.Clone();
+
+ Assert.AreSame (binding.Converter, clone.Converter);
+ Assert.AreSame (binding.ConverterParameter, clone.ConverterParameter);
+ Assert.AreEqual (binding.Mode, clone.Mode);
+ Assert.AreEqual (binding.Path, clone.Path);
+ Assert.AreEqual (binding.StringFormat, clone.StringFormat);
+ }
+
+ [Test]
+ public void PropertyMissingOneWay()
+ {
+ var bindable = new MockBindable { BindingContext = new MockViewModel() };
+ bindable.Text = "foo";
+
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("Monkeys", BindingMode.OneWay)), Throws.Nothing);
+ Assert.That (log.Messages.Count, Is.EqualTo (1), "An error was not logged");
+ Assert.That (bindable.Text, Is.EqualTo (MockBindable.TextProperty.DefaultValue));
+ }
+
+ [Test]
+ public void PropertyMissingOneWayToSource()
+ {
+ var bindable = new MockBindable { BindingContext = new MockViewModel() };
+ bindable.Text = "foo";
+
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("Monkeys", BindingMode.OneWayToSource)), Throws.Nothing);
+ Assert.That (log.Messages.Count, Is.EqualTo (1), "An error was not logged");
+ Assert.That (bindable.Text, Is.EqualTo (bindable.Text));
+ }
+
+ [Test]
+ public void PropertyMissingTwoWay()
+ {
+ var bindable = new MockBindable { BindingContext = new MockViewModel() };
+ bindable.Text = "foo";
+
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("Monkeys")), Throws.Nothing);
+ // The first error is for the initial binding, the second is for reflecting the update back to the default value
+ Assert.That (log.Messages.Count, Is.EqualTo (2), "An error was not logged");
+ Assert.That (bindable.Text, Is.EqualTo (MockBindable.TextProperty.DefaultValue));
+ }
+
+ [Test]
+ public void GetterMissingTwoWay()
+ {
+ var bindable = new MockBindable { BindingContext = new DifferentViewModel() };
+ bindable.Text = "foo";
+
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("Text2")), Throws.Nothing);
+ Assert.That (bindable.Text, Is.EqualTo (MockBindable.TextProperty.DefaultValue));
+ Assert.That (log.Messages.Count, Is.EqualTo (1), "An error was not logged");
+ Assert.That (log.Messages[0], Is.StringContaining (String.Format (BindingExpression.PropertyNotFoundErrorMessage,
+ "Text2",
+ "Xamarin.Forms.Core.UnitTests.BindingUnitTests+DifferentViewModel",
+ "Xamarin.Forms.Core.UnitTests.MockBindable",
+ "Text")));
+
+ Assert.That (((DifferentViewModel) bindable.BindingContext).Text, Is.EqualTo (MockBindable.TextProperty.DefaultValue));
+ }
+
+ [Test]
+ public void BindingAppliesAfterGetterPreviouslyMissing()
+ {
+ var bindable = new MockBindable { BindingContext = new EmptyViewModel() };
+ bindable.SetBinding (MockBindable.TextProperty, new Binding ("Text"));
+
+ bindable.BindingContext = new MockViewModel { Text = "Foo" };
+ Assert.That (bindable.Text, Is.EqualTo ("Foo"));
+
+ Assert.That (log.Messages.Count, Is.Not.GreaterThan (1), "Too many errors were logged");
+ Assert.That (log.Messages[0], Is.StringContaining (String.Format (BindingExpression.PropertyNotFoundErrorMessage,
+ "Text",
+ "Xamarin.Forms.Core.UnitTests.BindingUnitTests+EmptyViewModel",
+ "Xamarin.Forms.Core.UnitTests.MockBindable",
+ "Text")));
+ }
+
+ [Test]
+ public void SetterMissingTwoWay()
+ {
+ var bindable = new MockBindable { BindingContext = new DifferentViewModel() };
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("Text")), Throws.Nothing);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (1), "An error was not logged");
+ Assert.That (log.Messages[0], Is.StringContaining (String.Format (BindingExpression.PropertyNotFoundErrorMessage,
+ "Text",
+ "Xamarin.Forms.Core.UnitTests.BindingUnitTests+DifferentViewModel",
+ "Xamarin.Forms.Core.UnitTests.MockBindable",
+ "Text")));
+
+ Assert.That (() => bindable.SetValueCore (MockBindable.TextProperty, "foo"), Throws.Nothing);
+ }
+
+ [Test]
+ public void PrivateSetterTwoWay()
+ {
+ var bindable = new MockBindable { BindingContext = new DifferentViewModel() };
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("PrivateSetter")), Throws.Nothing);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (1), "An error was not logged");
+ Assert.That (log.Messages[0], Is.StringContaining (String.Format (BindingExpression.PropertyNotFoundErrorMessage,
+ "PrivateSetter",
+ "Xamarin.Forms.Core.UnitTests.BindingUnitTests+DifferentViewModel",
+ "Xamarin.Forms.Core.UnitTests.MockBindable",
+ "Text")));
+
+ Assert.That (() => bindable.SetValueCore (MockBindable.TextProperty, "foo"), Throws.Nothing);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (2), "An error was not logged");
+ Assert.That (log.Messages[1], Is.StringContaining (String.Format (BindingExpression.PropertyNotFoundErrorMessage,
+ "PrivateSetter",
+ "Xamarin.Forms.Core.UnitTests.BindingUnitTests+DifferentViewModel",
+ "Xamarin.Forms.Core.UnitTests.MockBindable",
+ "Text")));
+ }
+
+ [Test]
+ public void PropertyNotFound()
+ {
+ var bindable = new MockBindable { BindingContext = new MockViewModel() };
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("MissingProperty")), Throws.Nothing);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (1), "An error was not logged");
+ Assert.That (log.Messages[0], Is.StringContaining (String.Format (BindingExpression.PropertyNotFoundErrorMessage,
+ "MissingProperty",
+ "Xamarin.Forms.Core.UnitTests.MockViewModel",
+ "Xamarin.Forms.Core.UnitTests.MockBindable",
+ "Text")));
+ }
+
+ [Test]
+ [Description ("When binding with a multi-part path and part is null, no error should be thrown or logged")]
+ public void ChainedPartNull()
+ {
+ var bindable = new MockBindable { BindingContext = new ComplexMockViewModel() };
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("Model.Text")), Throws.Nothing);
+ Assert.That (log.Messages.Count, Is.EqualTo (0), "An error was logged");
+ }
+
+ [Test]
+ public void PropertyNotFoundChained()
+ {
+ var bindable = new MockBindable {
+ BindingContext = new ComplexMockViewModel {
+ Model = new ComplexMockViewModel()
+ }
+
+ };
+ Assert.That (() => bindable.SetBinding (MockBindable.TextProperty, new Binding ("Model.MissingProperty")), Throws.Nothing);
+
+ Assert.That (log.Messages.Count, Is.EqualTo (1), "An error was not logged");
+ Assert.That (log.Messages[0], Is.StringContaining (String.Format (BindingExpression.PropertyNotFoundErrorMessage,
+ "MissingProperty",
+ "Xamarin.Forms.Core.UnitTests.BindingUnitTests+ComplexMockViewModel",
+ "Xamarin.Forms.Core.UnitTests.MockBindable",
+ "Text")));
+
+ Assert.That (bindable.Text, Is.EqualTo (MockBindable.TextProperty.DefaultValue));
+ }
+
+ [Test]
+ public void CreateBindingNull()
+ {
+ Assert.That (() => Binding.Create<MockViewModel> (null), Throws.InstanceOf<ArgumentNullException>());
+ }
+
+ [Test]
+ public void CreateBindingSimple()
+ {
+ Binding binding = Binding.Create<MockViewModel> (mvm => mvm.Text);
+ Assert.IsNotNull (binding);
+ Assert.AreEqual ("Text", binding.Path);
+ }
+
+ [Test]
+ public void CreateBindingComplex()
+ {
+ Binding binding = Binding.Create<ComplexMockViewModel> (vm => vm.Model.Model.Text);
+ Assert.IsNotNull (binding);
+ Assert.AreEqual ("Model.Model.Text", binding.Path);
+ }
+
+ [Test]
+ public void CreateBindingIndexed()
+ {
+ Binding binding = Binding.Create<ComplexMockViewModel> (vm => vm.Model.Model[5]);
+ Assert.IsNotNull (binding);
+ Assert.AreEqual ("Model.Model[5]", binding.Path);
+ }
+
+ [Test]
+ public void CreateBindingIndexedNonConstant()
+ {
+ int x = 5;
+ Assert.That (
+ () => Binding.Create<ComplexMockViewModel> (vm => vm.Model.Model[x]),
+ Throws.ArgumentException);
+ }
+
+ internal class ReferenceTypeIndexerViewModel
+ : MockViewModel
+ {
+ public string this [string value]
+ {
+ get { return value; }
+ }
+ }
+
+ [Test]
+ public void CreateBindingNullToIndexer()
+ {
+ Assert.That (
+ () => Binding.Create<ReferenceTypeIndexerViewModel> (vm => vm[null]),
+ Throws.Nothing);
+ }
+
+ [Test]
+ public void CreateBindingWithMethod()
+ {
+ Assert.That (
+ () => Binding.Create<ComplexMockViewModel> (vm => vm.DoStuff()),
+ Throws.ArgumentException);
+ }
+
+ [Test]
+ [Description ("Indexers are seen as methods, we don't want to get them confused with real methods.")]
+ public void CreateBindingWithMethodArgument()
+ {
+ Assert.That (
+ () => Binding.Create<ComplexMockViewModel> (vm => vm.DoStuff (null)),
+ Throws.ArgumentException);
+ }
+
+ object Method (MockViewModel vm)
+ {
+ return vm.Text;
+ }
+
+ [Test]
+ public void CreateBindingMethod()
+ {
+ Func<MockViewModel, object> func = vm => vm.Text;
+ Assert.That (() => Binding.Create<MockViewModel> (vm => func (vm)),
+ Throws.ArgumentException);
+
+ Assert.That (() => Binding.Create<MockViewModel> (vm => Method (vm)),
+ Throws.ArgumentException);
+ }
+
+ [Test]
+ public void CreateBindingPrivateIndexer()
+ {
+ Assert.That (() => Binding.Create<InternalIndexerViewModel> (vm => vm[5]),
+ Throws.ArgumentException);
+ }
+
+ class InternalIndexerViewModel
+ {
+ internal int this [int x]
+ {
+ get { return x; }
+ }
+ }
+
+ [Test]
+ public void CreateBindingInvalidExpression()
+ {
+ Assert.That (() => Binding.Create<MockViewModel> (vm => vm.Text + vm.Text),
+ Throws.ArgumentException);
+
+ Assert.That (() => Binding.Create<MockViewModel> (vm => 5),
+ Throws.ArgumentException);
+
+ Assert.That (() => Binding.Create<MockViewModel> (vm => null),
+ Throws.ArgumentException);
+ }
+
+ [Test]
+ public void SetBindingContextBeforeContextBindingAndInnerBindings ()
+ {
+ var label = new Label ();
+ var view = new StackLayout { Children = {label} };
+
+ view.BindingContext = new {item0 = "Foo", item1 = "Bar"};
+ label.SetBinding (BindableObject.BindingContextProperty, "item0");
+ label.SetBinding (Label.TextProperty, Binding.SelfPath);
+
+ Assert.AreEqual ("Foo", label.Text);
+ }
+
+ [Test]
+ public void SetBindingContextAndInnerBindingBeforeContextBinding ()
+ {
+ var label = new Label ();
+ var view = new StackLayout { Children = {label} };
+
+ view.BindingContext = new {item0 = "Foo", item1 = "Bar"};
+ label.SetBinding (Label.TextProperty, Binding.SelfPath);
+ label.SetBinding (BindableObject.BindingContextProperty, "item0");
+
+ Assert.AreEqual ("Foo", label.Text);
+ }
+
+ [Test]
+ public void SetBindingContextAfterContextBindingAndInnerBindings ()
+ {
+ var label = new Label ();
+ var view = new StackLayout { Children = {label} };
+
+ label.SetBinding (BindableObject.BindingContextProperty, "item0");
+ label.SetBinding (Label.TextProperty, Binding.SelfPath);
+ view.BindingContext = new {item0 = "Foo", item1 = "Bar"};
+
+ Assert.AreEqual ("Foo", label.Text);
+ }
+
+ [Test]
+ public void SetBindingContextAfterInnerBindingsAndContextBinding ()
+ {
+ var label = new Label ();
+ var view = new StackLayout { Children = {label} };
+
+ label.SetBinding (Label.TextProperty, Binding.SelfPath);
+ label.SetBinding (BindableObject.BindingContextProperty, "item0");
+ view.BindingContext = new {item0 = "Foo", item1 = "Bar"};
+
+ Assert.AreEqual ("Foo", label.Text);
+ }
+
+ [Test]
+ public void Convert ()
+ {
+ var slider = new Slider ();
+ var vm = new MockViewModel { Text = "0.5" };
+ slider.BindingContext = vm;
+ slider.SetBinding (Slider.ValueProperty, "Text", BindingMode.TwoWay);
+
+ Assert.That (slider.Value, Is.EqualTo (0.5));
+
+ slider.Value = 0.9;
+
+ Assert.That (vm.Text, Is.EqualTo ("0.9"));
+ }
+
+ #if !WINDOWS_PHONE
+ [Test]
+ [SetCulture ("pt-PT")]
+ [SetUICulture ("pt-PT")]
+ public void ConvertIsCultureInvariant ()
+ {
+ var slider = new Slider ();
+ var vm = new MockViewModel { Text = "0.5" };
+ slider.BindingContext = vm;
+ slider.SetBinding (Slider.ValueProperty, "Text", BindingMode.TwoWay);
+
+ Assert.That (slider.Value, Is.EqualTo (0.5));
+
+ slider.Value = 0.9;
+
+ Assert.That (vm.Text, Is.EqualTo ("0.9"));
+ }
+ #endif
+
+ [Test]
+ public void FailToConvert ()
+ {
+ var slider = new Slider ();
+ slider.BindingContext = new ComplexMockViewModel { Model = new ComplexMockViewModel() };
+
+ Assert.That (() => {
+ slider.SetBinding (Slider.ValueProperty, "Model");
+ }, Throws.Nothing);
+
+ Assert.That (slider.Value, Is.EqualTo (Slider.ValueProperty.DefaultValue));
+ }
+
+ class NullViewModel : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public string Foo
+ {
+ get;
+ set;
+ }
+
+ public string Bar
+ {
+ get;
+ set;
+ }
+
+ public void SignalAllPropertiesChanged (bool useNull)
+ {
+ var changed = PropertyChanged;
+ if (changed != null)
+ changed (this, new PropertyChangedEventArgs ((useNull) ? null : String.Empty));
+ }
+ }
+
+ class MockBindable2 : MockBindable
+ {
+ public static readonly BindableProperty Text2Property = BindableProperty.Create<MockBindable2, string> (
+ b => b.Text2, "default", BindingMode.TwoWay);
+
+ public string Text2
+ {
+ get { return (string)GetValue (Text2Property); }
+ set { SetValue (Text2Property, value); }
+ }
+ }
+
+ [TestCase (true)]
+ [TestCase (false)]
+ public void NullPropertyUpdatesAllBindings (bool useStringEmpty)
+ {
+ var vm = new NullViewModel();
+ var bindable = new MockBindable2();
+ bindable.BindingContext = vm;
+ bindable.SetBinding (MockBindable.TextProperty, "Foo");
+ bindable.SetBinding (MockBindable2.Text2Property, "Bar");
+
+ vm.Foo = "Foo";
+ vm.Bar = "Bar";
+ Assert.That (() => vm.SignalAllPropertiesChanged (useNull: !useStringEmpty), Throws.Nothing);
+
+ Assert.That (bindable.Text, Is.EqualTo ("Foo"));
+ Assert.That (bindable.Text2, Is.EqualTo ("Bar"));
+ }
+
+ [TestCase]
+ public void BindingSourceOverContext ()
+ {
+ var label = new Label ();
+ label.BindingContext = "bindingcontext";
+ label.SetBinding (Label.TextProperty, Binding.SelfPath);
+ Assert.AreEqual ("bindingcontext", label.Text);
+
+ label.SetBinding (Label.TextProperty, new Binding (Binding.SelfPath, source: "bindingsource"));
+ Assert.AreEqual ("bindingsource", label.Text);
+ }
+
+ class TestViewModel : INotifyPropertyChanged
+ {
+ event PropertyChangedEventHandler PropertyChanged;
+
+ event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
+ {
+ add { PropertyChanged += value; }
+ remove { PropertyChanged -= value; }
+ }
+
+ public string Foo { get; set; }
+
+ public int InvocationListSize ()
+ {
+ if (PropertyChanged == null)
+ return 0;
+ return PropertyChanged.GetInvocationList ().Length;
+ }
+
+ public virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
+ }
+ }
+
+ [Test]
+ public void BindingUnsubscribesForDeadTarget ()
+ {
+ TestViewModel viewmodel = new TestViewModel();
+
+ int i = 0;
+ Action create = null;
+ create = () => {
+ if (i++ < 1024) {
+ create();
+ return;
+ }
+
+ var button = new Button ();
+ button.SetBinding (Button.TextProperty, "Foo");
+ button.BindingContext = viewmodel;
+ };
+
+ create();
+
+ Assume.That (viewmodel.InvocationListSize (), Is.EqualTo (1));
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect ();
+
+ viewmodel.OnPropertyChanged ("Foo");
+
+ Assert.AreEqual (0, viewmodel.InvocationListSize ());
+ }
+
+ [Test]
+ public void BindingCreatesSingleSubscription ()
+ {
+ TestViewModel viewmodel = new TestViewModel();
+
+ var button = new Button ();
+ button.SetBinding (Button.TextProperty, "Foo");
+ button.BindingContext = viewmodel;
+
+ Assert.That (viewmodel.InvocationListSize (), Is.EqualTo (1));
+ }
+
+ public class IndexedViewModel : INotifyPropertyChanged
+ {
+ Dictionary<string, object> dict = new Dictionary<string, object> ();
+
+ public object this [string index]
+ {
+ get { return dict[index]; }
+ set
+ {
+ dict[index] = value;
+ OnPropertyChanged ("Item[" + index + "]");
+
+ }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
+ }
+ }
+
+ [Test]
+ public void IndexedViewModelPropertyChanged ()
+ {
+ var label = new Label ();
+ var viewModel = new IndexedViewModel ();
+ //viewModel["Foo"] = "Bar";
+
+ label.BindingContext = new {
+ Data = viewModel
+ };
+ label.SetBinding (Label.TextProperty, "Data[Foo]");
+
+
+ Assert.AreEqual (null, label.Text);
+
+ viewModel["Foo"] = "Baz";
+
+ Assert.AreEqual ("Baz", label.Text);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/BoxViewUnitTests.cs b/Xamarin.Forms.Core.UnitTests/BoxViewUnitTests.cs
new file mode 100644
index 00000000..6d26af45
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/BoxViewUnitTests.cs
@@ -0,0 +1,42 @@
+using System;
+using NUnit.Framework;
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class BoxViewUnitTests : BaseTestFixture
+ {
+ [Test]
+ public void TestConstructor ()
+ {
+ var box = new BoxView {
+ Color = new Color (0.2, 0.3, 0.4),
+ WidthRequest=20,
+ HeightRequest=30,
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ Assert.AreEqual (new Color (0.2, 0.3, 0.4), box.Color);
+ var request = box.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity).Request;
+ Assert.AreEqual (20, request.Width);
+ Assert.AreEqual (30, request.Height);
+ }
+
+ [Test]
+ public void DefaultSize ()
+ {
+ var box = new BoxView {
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ var request = box.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity).Request;
+ Assert.AreEqual (40, request.Width);
+ Assert.AreEqual (40, request.Height);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/ButtonUnitTest.cs b/Xamarin.Forms.Core.UnitTests/ButtonUnitTest.cs
new file mode 100644
index 00000000..29ad2d0e
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/ButtonUnitTest.cs
@@ -0,0 +1,181 @@
+using System;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class ButtonUnitTest
+ : CommandSourceTests<Button>
+ {
+ [SetUp]
+ public override void Setup ()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [TearDown]
+ public override void TearDown ()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ }
+
+ [Test]
+ public void MeasureInvalidatedOnTextChange ()
+ {
+ var button = new Button ();
+
+ bool fired = false;
+ button.MeasureInvalidated += (sender, args) => fired = true;
+
+ button.Text = "foo";
+ Assert.True (fired);
+ }
+
+ [Test]
+ public void TestTappedEvent ()
+ {
+ var view = new Button ();
+
+ bool activated = false;
+ view.Clicked += (sender, e) => activated = true;
+
+ ((IButtonController) view).SendClicked ();
+
+ Assert.True (activated);
+ }
+
+ protected override Button CreateSource()
+ {
+ return new Button();
+ }
+
+ protected override void Activate (Button source)
+ {
+ ((IButtonController) source).SendClicked();
+ }
+
+ protected override BindableProperty IsEnabledProperty
+ {
+ get { return Button.IsEnabledProperty; }
+ }
+
+ protected override BindableProperty CommandProperty
+ {
+ get { return Button.CommandProperty; }
+ }
+
+ protected override BindableProperty CommandParameterProperty
+ {
+ get { return Button.CommandParameterProperty; }
+ }
+
+
+ [Test]
+ public void TestBindingContextPropagation ()
+ {
+ var context = new object ();
+ var button = new Button ();
+ button.BindingContext = context;
+ var source = new FileImageSource ();
+ button.Image = source;
+ Assert.AreSame (context, source.BindingContext);
+
+ button = new Button ();
+ source = new FileImageSource ();
+ button.Image = source;
+ button.BindingContext = context;
+ Assert.AreSame (context, source.BindingContext);
+ }
+
+ [Test]
+ public void TestImageSourcePropertiesChangedTriggerResize ()
+ {
+ var source = new FileImageSource ();
+ var button = new Button { Image = source };
+ bool fired = false;
+ button.MeasureInvalidated += (sender, e) => fired = true;
+ Assert.Null (source.File);
+ source.File = "foo.png";
+ Assert.NotNull (source.File);
+ Assert.True (fired);
+ }
+
+ [Test]
+ public void AssignToFontStructUpdatesFontFamily (
+ [Values (NamedSize.Default, NamedSize.Large, NamedSize.Medium, NamedSize.Small, NamedSize.Micro)] NamedSize size,
+ [Values (FontAttributes.None, FontAttributes.Bold, FontAttributes.Italic, FontAttributes.Bold | FontAttributes.Italic)] FontAttributes attributes)
+ {
+ var button = new Button {Platform = new UnitPlatform ()};
+ double startSize = button.FontSize;
+ var startAttributes = button.FontAttributes;
+
+ bool firedSizeChanged = false;
+ bool firedAttributesChanged = false;
+ button.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == Label.FontSizeProperty.PropertyName)
+ firedSizeChanged = true;
+ if (args.PropertyName == Label.FontAttributesProperty.PropertyName)
+ firedAttributesChanged = true;
+ };
+
+ button.Font = Font.OfSize ("Testing123", size).WithAttributes (attributes);
+
+ Assert.AreEqual (Device.GetNamedSize (size, typeof (Label), true), button.FontSize);
+ Assert.AreEqual (attributes, button.FontAttributes);
+ Assert.AreEqual (startSize != button.FontSize, firedSizeChanged);
+ Assert.AreEqual (startAttributes != button.FontAttributes, firedAttributesChanged);
+ }
+
+ [Test]
+ public void AssignToFontFamilyUpdatesFont ()
+ {
+ var button = new Button {Platform = new UnitPlatform ()};
+
+ button.FontFamily = "CrazyFont";
+ Assert.AreEqual (button.Font, Font.OfSize ("CrazyFont", button.FontSize));
+ }
+
+ [Test]
+ public void AssignToFontSizeUpdatesFont ()
+ {
+ var button = new Button {Platform = new UnitPlatform ()};
+
+ button.FontSize = 1000;
+ Assert.AreEqual (button.Font, Font.SystemFontOfSize (1000));
+ }
+
+ [Test]
+ public void AssignToFontAttributesUpdatesFont ()
+ {
+ var button = new Button {Platform = new UnitPlatform ()};
+
+ button.FontAttributes = FontAttributes.Italic | FontAttributes.Bold;
+ Assert.AreEqual (button.Font, Font.SystemFontOfSize (button.FontSize, FontAttributes.Bold | FontAttributes.Italic));
+ }
+
+ [Test]
+ public void CommandCanExecuteUpdatesEnabled ()
+ {
+ var button = new Button ();
+
+ bool result = false;
+
+ var bindingContext = new {
+ Command = new Command (() => { }, () => result)
+ };
+
+ button.SetBinding (Button.CommandProperty, "Command");
+ button.BindingContext = bindingContext;
+
+ Assert.False (button.IsEnabled);
+
+ result = true;
+
+ bindingContext.Command.ChangeCanExecute ();
+
+ Assert.True (button.IsEnabled);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/CarouselPageTests.cs b/Xamarin.Forms.Core.UnitTests/CarouselPageTests.cs
new file mode 100644
index 00000000..553d67ce
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/CarouselPageTests.cs
@@ -0,0 +1,29 @@
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ public class CarouselPageTests : MultiPageTests<ContentPage>
+ {
+ protected override MultiPage<ContentPage> CreateMultiPage()
+ {
+ return new CarouselPage();
+ }
+
+ protected override ContentPage CreateContainedPage()
+ {
+ return new ContentPage { Content = new View() };
+ }
+
+ protected override int GetIndex (ContentPage page)
+ {
+ return CarouselPage.GetIndex (page);
+ }
+
+ [Test]
+ public void TestConstructor()
+ {
+ var page = new CarouselPage();
+ Assert.That (page.Children, Is.Empty);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/CellTests.cs b/Xamarin.Forms.Core.UnitTests/CellTests.cs
new file mode 100644
index 00000000..0006b4cf
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/CellTests.cs
@@ -0,0 +1,190 @@
+using NUnit.Framework;
+using System;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class CellTests : BaseTestFixture
+ {
+ internal class TestCell : Cell
+ {
+ public bool OnAppearingSent { get; set; }
+ public bool OnDisappearingSent { get; set; }
+
+ protected override void OnAppearing ()
+ {
+ base.OnAppearing ();
+ OnAppearingSent = true;
+ }
+
+ protected override void OnDisappearing ()
+ {
+ base.OnDisappearing ();
+ OnDisappearingSent = true;
+ }
+ }
+
+ [Test]
+ public void Selected ()
+ {
+ var cell = new TestCell ();
+
+ bool tapped = false;
+ cell.Tapped += (sender, args) => tapped = true;
+
+ cell.OnTapped();
+ Assert.IsTrue (tapped);
+ }
+
+ [Test]
+ public void AppearingEvent ()
+ {
+ var cell = new TestCell ();
+
+ bool emitted = false;
+ cell.Appearing += (sender, args) => emitted = true;
+
+ cell.SendAppearing ();
+ Assert.True (emitted);
+ Assert.True (cell.OnAppearingSent);
+ Assert.False (cell.OnDisappearingSent);
+ }
+
+ [Test]
+ public void DisappearingEvent ()
+ {
+ var cell = new TestCell ();
+
+ bool emitted = false;
+ cell.Disappearing += (sender, args) => emitted = true;
+
+ cell.SendDisappearing ();
+ Assert.True (emitted);
+ Assert.False (cell.OnAppearingSent);
+ Assert.True (cell.OnDisappearingSent);
+ }
+
+ [Test]
+ public void TestBindingContextPropagationOnImageCell ()
+ {
+ var context = new object ();
+ var cell = new ImageCell ();
+ cell.BindingContext = context;
+ var source = new FileImageSource ();
+ cell.ImageSource = source;
+ Assert.AreSame (context, source.BindingContext);
+
+ cell = new ImageCell ();
+ source = new FileImageSource ();
+ cell.ImageSource = source;
+ cell.BindingContext = context;
+ Assert.AreSame (context, source.BindingContext);
+ }
+
+ [Test]
+ public void HasContextActions()
+ {
+ bool changed = false;
+
+ var cell = new TextCell();
+ cell.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "HasContextActions")
+ changed = true;
+ };
+
+ Assert.That (cell.HasContextActions, Is.False);
+ Assert.That (changed, Is.False);
+
+ var collection = cell.ContextActions;
+
+ Assert.That (cell.HasContextActions, Is.False);
+ Assert.That (changed, Is.False);
+
+ collection.Add (new MenuItem());
+
+ Assert.That (cell.HasContextActions, Is.True);
+ Assert.That (changed, Is.True);
+ }
+
+ [Test]
+ public void MenuItemsGetBindingContext()
+ {
+ var cell = new TextCell {
+ ContextActions = {
+ new MenuItem ()
+ }
+ };
+
+ object bc = new object ();
+
+ cell.BindingContext = bc;
+ Assert.That (cell.ContextActions [0].BindingContext, Is.SameAs (bc));
+
+ cell = new TextCell { BindingContext = new object () };
+ cell.ContextActions.Add (new MenuItem ());
+
+ Assert.That (cell.ContextActions [0].BindingContext, Is.SameAs (cell.BindingContext));
+ }
+
+ [Test]
+ public void RenderHeightINPCFromParent()
+ {
+ var lv = new ListView();
+ var cell = new TextCell();
+ cell.Parent = lv;
+
+ int changing = 0, changed = 0;
+ cell.PropertyChanging += (sender, args) => {
+ if (args.PropertyName == "RenderHeight")
+ changing++;
+ };
+
+ cell.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "RenderHeight")
+ changed++;
+ };
+
+ lv.RowHeight = 5;
+
+ Assume.That (cell.RenderHeight, Is.EqualTo (5));
+
+ Assert.That (changing, Is.EqualTo (1));
+ Assert.That (changed, Is.EqualTo (1));
+ }
+
+ [Test]
+ public async void ForceUpdateSizeCallsAreRateLimited()
+ {
+ var lv = new ListView { HasUnevenRows = true };
+ var cell = new ViewCell { Parent = lv };
+
+ int numberOfCalls = 0;
+ cell.ForceUpdateSizeRequested += (object sender, System.EventArgs e) => { numberOfCalls++; };
+
+ cell.ForceUpdateSize ();
+ cell.ForceUpdateSize ();
+ cell.ForceUpdateSize ();
+ cell.ForceUpdateSize ();
+
+ await System.Threading.Tasks.Task.Delay (TimeSpan.FromMilliseconds (150));
+
+ Assert.AreEqual (1, numberOfCalls);
+ }
+
+ [Test]
+ public async void ForceUpdateSizeWillNotBeCalledIfParentIsNotAListViewWithUnevenRows ()
+ {
+ var lv = new ListView { HasUnevenRows = false };
+ var cell = new ViewCell { Parent = lv };
+
+ int numberOfCalls = 0;
+ cell.ForceUpdateSizeRequested += (object sender, System.EventArgs e) => { numberOfCalls++; };
+
+ cell.ForceUpdateSize ();
+
+ await System.Threading.Tasks.Task.Delay (TimeSpan.FromMilliseconds (16));
+
+ Assert.AreEqual (0, numberOfCalls);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/ColorUnitTests.cs b/Xamarin.Forms.Core.UnitTests/ColorUnitTests.cs
new file mode 100644
index 00000000..b29a9680
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/ColorUnitTests.cs
@@ -0,0 +1,268 @@
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class ColorUnitTests : BaseTestFixture
+ {
+ [Test]
+ public void TestHSLPostSetEquality ()
+ {
+ var color = new Color (1, 0.5, 0.2);
+ var color2 = color;
+
+ color2 = color.WithLuminosity (.2);
+ Assert.False (color == color2);
+ }
+
+ [Test]
+ public void TestHSLPostSetInequality ()
+ {
+ var color = new Color (1, 0.5, 0.2);
+ var color2 = color;
+
+ color2 = color.WithLuminosity (.2);
+
+ Assert.True (color != color2);
+ }
+
+ [Test]
+ public void TestHSLSetToDefaultValue ()
+ {
+ var color = new Color (0.2, 0.5, 0.8);
+
+ // saturation is initialized to 0, make sure we still update
+ color = color.WithSaturation (0);
+
+ Assert.AreEqual (color.R, color.G);
+ Assert.AreEqual (color.R, color.B);
+ }
+
+ [Test]
+ public void TestHSLModifiers ()
+ {
+ var color = Color.Default;
+ Assert.Throws<InvalidOperationException> (()=> color.WithHue (.1));
+ Assert.Throws<InvalidOperationException> (()=> color.WithLuminosity (.1));
+ Assert.Throws<InvalidOperationException> (()=> color.WithSaturation (.1));
+
+ color = Color.FromHsla (.8, .6, .2);
+ Assert.AreEqual (Color.FromHsla (.1, .6, .2), color.WithHue (.1));
+ Assert.AreEqual (Color.FromHsla (.8, .1, .2), color.WithSaturation (.1));
+ Assert.AreEqual (Color.FromHsla (.8, .6, .1), color.WithLuminosity (.1));
+ }
+
+ [Test]
+ public void TestMultiplyAlpha ()
+ {
+ var color = new Color (1, 1, 1, 1);
+ color = color.MultiplyAlpha (0.25);
+ Assert.AreEqual (.25, color.A);
+
+ color = Color.Default;
+ Assert.Throws<InvalidOperationException>(()=>color = color.MultiplyAlpha (0.25));
+
+ color = Color.FromHsla (1, 1, 1, 1);
+ color = color.MultiplyAlpha (0.25);
+ Assert.AreEqual (.25, color.A);
+ }
+
+ [Test]
+ public void TestClamping ()
+ {
+ var color = new Color (2, 2, 2, 2);
+
+ Assert.AreEqual (1, color.R);
+ Assert.AreEqual (1, color.G);
+ Assert.AreEqual (1, color.B);
+ Assert.AreEqual (1, color.A);
+
+ color = new Color (-1, -1, -1, -1);
+
+ Assert.AreEqual (0, color.R);
+ Assert.AreEqual (0, color.G);
+ Assert.AreEqual (0, color.B);
+ Assert.AreEqual (0, color.A);
+ }
+
+ [Test]
+ public void TestRGBToHSL ()
+ {
+ var color = new Color (.5, .1, .1);
+
+ Assert.That (color.Hue, Is.EqualTo (1).Within (0.001));
+ Assert.That (color.Saturation, Is.EqualTo (0.662).Within (0.01));
+ Assert.That (color.Luminosity, Is.EqualTo (0.302).Within (0.01));
+ }
+
+ [Test]
+ public void TestHSLToRGB ()
+ {
+ var color = Color.FromHsla (0, .662, .302);
+
+ Assert.That (color.R, Is.EqualTo (0.5).Within (0.01));
+ Assert.That (color.G, Is.EqualTo (0.1).Within (0.01));
+ Assert.That (color.B, Is.EqualTo (0.1).Within (0.01));
+ }
+
+ [Test]
+ public void TestColorFromValue ()
+ {
+ var color = new Color (0.2);
+
+ Assert.AreEqual (new Color (0.2, 0.2, 0.2, 1), color);
+ }
+
+ [Test]
+ public void TestAddLuminosity ()
+ {
+ var color = new Color (0.2);
+ var brighter = color.AddLuminosity (0.2);
+ Assert.That (brighter.Luminosity, Is.EqualTo (color.Luminosity + 0.2).Within (0.001));
+
+ color = Color.Default;
+ Assert.Throws<InvalidOperationException> (() => color.AddLuminosity (0.2));
+ }
+
+ [Test]
+ public void TestZeroLuminosity ()
+ {
+ var color = new Color (0.1, 0.2, 0.3);
+ color = color.AddLuminosity (-1);
+
+ Assert.AreEqual (0, color.Luminosity);
+ Assert.AreEqual (0, color.R);
+ Assert.AreEqual (0, color.G);
+ Assert.AreEqual (0, color.B);
+ }
+
+ [Test]
+ public void TestHashCode ()
+ {
+ var color1 = new Color (0.1);
+ var color2 = new Color (0.1);
+
+ Assert.True (color1.GetHashCode () == color2.GetHashCode ());
+ color2 = Color.FromHsla (color2.Hue, color2.Saturation, .5);
+
+ Assert.False (color1.GetHashCode () == color2.GetHashCode ());
+ }
+
+ [Test]
+ public void TestHashCodeNamedColors ()
+ {
+ Color red = Color.Red; //R=1, G=0, B=0, A=1
+ int hashRed = red.GetHashCode();
+
+ Color blue = Color.Blue; //R=0, G=0, B=1, A=1
+ int hashBlue = blue.GetHashCode();
+
+ Assert.False (hashRed == hashBlue);
+ }
+
+ [Test]
+ public void TestHashCodeAll ()
+ {
+ Dictionary<int,Color> colorsAndHashes = new Dictionary<int,Color> ();
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Transparent.GetHashCode (), Color.Transparent));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Aqua.GetHashCode (), Color.Aqua));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Black.GetHashCode (), Color.Black));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Blue.GetHashCode (), Color.Blue));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Fuchsia.GetHashCode (), Color.Fuchsia));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Gray.GetHashCode (), Color.Gray));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Green.GetHashCode (), Color.Green));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Lime.GetHashCode (), Color.Lime));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Maroon.GetHashCode (), Color.Maroon));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Navy.GetHashCode (), Color.Navy));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Olive.GetHashCode (), Color.Olive));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Purple.GetHashCode (), Color.Purple));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Pink.GetHashCode (), Color.Pink));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Red.GetHashCode (), Color.Red));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Silver.GetHashCode (), Color.Silver));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Teal.GetHashCode (), Color.Teal));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.White.GetHashCode (), Color.White));
+ Assert.DoesNotThrow (() => colorsAndHashes.Add (Color.Yellow.GetHashCode (), Color.Yellow));
+ }
+
+ [Test]
+ public void TestSetHue ()
+ {
+ var color = new Color (0.2, 0.5, 0.7);
+ color = Color.FromHsla (.2, color.Saturation, color.Luminosity);
+
+ Assert.That (color.R, Is.EqualTo (0.6).Within (0.001));
+ Assert.That (color.G, Is.EqualTo (0.7).Within (0.001));
+ Assert.That (color.B, Is.EqualTo (0.2).Within (0.001));
+ }
+
+ [Test]
+ public void ZeroLuminToRGB ()
+ {
+ var color = new Color (0);
+ Assert.AreEqual (0, color.Luminosity);
+ Assert.AreEqual (0, color.Hue);
+ Assert.AreEqual (0, color.Saturation);
+ }
+
+ [Test]
+ public void TestToString ()
+ {
+ var color = new Color (1, 1, 1, 0.5);
+ Assert.AreEqual ("[Color: A=0.5, R=1, G=1, B=1, Hue=0, Saturation=0, Luminosity=1]", color.ToString ());
+ }
+
+ [Test]
+ public void TestFromHex ()
+ {
+ var color = Color.FromRgb(138, 43, 226);
+ Assert.AreEqual(color, Color.FromHex("8a2be2"));
+
+ Assert.AreEqual (Color.FromRgba (138, 43, 226, 128), Color.FromHex ("#808a2be2"));
+ Assert.AreEqual (Color.FromHex ("#aabbcc"), Color.FromHex ("#abc"));
+ Assert.AreEqual (Color.FromHex ("#aabbccdd"), Color.FromHex ("#abcd"));
+ }
+
+ [Test]
+ public void FromRGBDouble ()
+ {
+ var color = Color.FromRgb (0.2, 0.3, 0.4);
+
+ Assert.AreEqual (new Color (0.2, 0.3, 0.4), color);
+ }
+
+ [Test]
+ public void FromRGBADouble ()
+ {
+ var color = Color.FromRgba (0.2, 0.3, 0.4, 0.5);
+
+ Assert.AreEqual (new Color (0.2, 0.3, 0.4, 0.5), color);
+ }
+
+ [Test]
+ public void TestColorTypeConverter ()
+ {
+ var converter = new ColorTypeConverter ();
+ Assert.True (converter.CanConvertFrom (typeof(string)));
+ Assert.AreEqual (Color.Blue, converter.ConvertFromInvariantString ("Color.Blue"));
+ Assert.AreEqual (Color.Blue, converter.ConvertFromInvariantString ("Blue"));
+ Assert.AreEqual (Color.Blue, converter.ConvertFromInvariantString ("#0000ff"));
+ Assert.AreEqual (Color.Default, converter.ConvertFromInvariantString ("Color.Default"));
+ Assert.AreEqual (Color.Accent, converter.ConvertFromInvariantString ("Accent"));
+ var hotpink = Color.FromHex ("#FF69B4");
+ Color.Accent = hotpink;
+ Assert.AreEqual (Color.Accent, converter.ConvertFromInvariantString ("Accent"));
+ Assert.Throws<InvalidOperationException> (() => converter.ConvertFromInvariantString (""));
+ }
+
+ [Test]
+ public void TestDefault ()
+ {
+ Assert.AreEqual (Color.Default, default(Color));
+ Assert.AreEqual (Color.Default, new Color ());
+ }
+
+
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/CommandSourceTests.cs b/Xamarin.Forms.Core.UnitTests/CommandSourceTests.cs
new file mode 100644
index 00000000..f544a297
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/CommandSourceTests.cs
@@ -0,0 +1,184 @@
+using System;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ public abstract class CommandSourceTests<T> : BaseTestFixture
+ where T : BindableObject
+ {
+ [Test]
+ public void TestCommand ()
+ {
+ var source = CreateSource();
+
+ bool executed = false;
+ source.SetValue (CommandProperty, new Command (o => {
+ executed = true;
+ Assert.AreEqual (source, o);
+ }));
+
+ source.SetValue (CommandParameterProperty, source);
+
+ Activate (source);
+
+ Assert.True (executed);
+ }
+
+ [Test]
+ public void CommandCanExecuteModifiesEnabled ([Values(true, false)] bool initial)
+ {
+ bool canExecute = initial;
+ Command command;
+ var source = CreateSource();
+ source.SetValue (CommandProperty, command = new Command (() => { }, () => canExecute));
+
+ Assert.AreEqual (canExecute, source.GetValue (IsEnabledProperty));
+
+ canExecute = !initial;
+ command.ChangeCanExecute ();
+
+ Assert.AreEqual (canExecute, source.GetValue (IsEnabledProperty));
+ }
+
+ [Test]
+ public void ReenabledAfterCommandRemoved()
+ {
+ var source = CreateSource();
+ source.SetValue (CommandProperty, new Command (() => { }, () => false));
+
+ Assert.That (source.GetValue (IsEnabledProperty), Is.False);
+
+ source.SetValue (CommandProperty, null);
+
+ Assert.That (source.GetValue (IsEnabledProperty), Is.True);
+ }
+
+ [Test]
+ public void CommandUnhooksOnNull ()
+ {
+ bool canExecute = false;
+ Command command;
+ var source = CreateSource();
+
+ bool raised = false;
+ source.SetValue (CommandProperty, command = new Command (() => { }, () => {
+ raised = true;
+ return canExecute;
+ }));
+
+ raised = false;
+ source.SetValue (CommandProperty, null);
+
+ canExecute = true;
+ command.ChangeCanExecute ();
+
+ Assert.False (raised);
+ }
+
+ [Test]
+ public void CommandCanExecuteInvokedOnCommandSet ()
+ {
+ bool fired = false;
+ Func<bool> canExecute = () => {
+ fired = true;
+ return true;
+ };
+
+ Assert.IsFalse (fired);
+ var source = CreateSource();
+ source.SetValue (CommandProperty, new Command (() => { }, canExecute));
+
+ Assert.True (fired);
+ }
+
+ [Test]
+ public void CommandCanExecuteInvokedOnCommandParameterSet ()
+ {
+ bool fired;
+ Func<bool> canExecute = () => {
+ fired = true;
+ return true;
+ };
+
+ var source = CreateSource();
+ source.SetValue (CommandProperty, new Command (() => { }, canExecute));
+
+ fired = false;
+ Assert.IsFalse (fired);
+ source.SetValue (CommandParameterProperty, new object ());
+ Assert.True (fired);
+ }
+
+ [Test]
+ public void CommandCanExecuteInvokedOnChange()
+ {
+ bool fired;
+ Func<bool> canExecute = () => {
+ fired = true;
+ return true;
+ };
+
+ var cmd = new Command (() => { }, canExecute);
+ var source = CreateSource();
+ source.SetValue (CommandProperty, cmd);
+
+ fired = false;
+
+ cmd.ChangeCanExecute();
+
+ Assert.That (fired, Is.True, "CanExecute was not called when the event was raised");
+ }
+
+ class BoolViewModel
+ : MockViewModel
+ {
+ bool toggle;
+
+ public bool Toggle
+ {
+ get { return toggle; }
+ set
+ {
+ if (toggle == value)
+ return;
+
+ toggle = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ [Test]
+ public void EnabledUpdatesDoNotRemoveBindings()
+ {
+ var vm = new BoolViewModel { Toggle = true };
+ var source = CreateSource();
+ source.BindingContext = vm;
+ source.SetBinding (IsEnabledProperty, "Toggle");
+
+ Assert.That (source.GetValue (IsEnabledProperty), Is.True);
+
+ source.SetValue (CommandProperty, new Command (() => { }));
+
+ Assert.That (source.GetIsBound (IsEnabledProperty), Is.True);
+ }
+
+ protected abstract T CreateSource();
+ protected abstract void Activate (T source);
+
+ protected abstract BindableProperty IsEnabledProperty
+ {
+ get;
+ }
+
+ protected abstract BindableProperty CommandProperty
+ {
+ get;
+ }
+
+ protected abstract BindableProperty CommandParameterProperty
+ {
+ get;
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/CommandTests.cs b/Xamarin.Forms.Core.UnitTests/CommandTests.cs
new file mode 100644
index 00000000..1182fe40
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/CommandTests.cs
@@ -0,0 +1,155 @@
+using System;
+using NUnit.Framework;
+
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class CommandTests : BaseTestFixture
+ {
+ [Test]
+ public void Constructor ()
+ {
+ var cmd = new Command (() => { });
+ Assert.True (cmd.CanExecute (null));
+ }
+
+ [Test]
+ public void ThrowsWithNullConstructor ()
+ {
+ Assert.Throws<ArgumentNullException> (() => new Command ((Action)null));
+ }
+
+ [Test]
+ public void ThrowsWithNullParameterizedConstructor ()
+ {
+ Assert.Throws<ArgumentNullException> (() => new Command ((Action<object>)null));
+ }
+
+ [Test]
+ public void ThrowsWithNullCanExecute ()
+ {
+ Assert.Throws<ArgumentNullException> (() => new Command (() => { }, null));
+ }
+
+ [Test]
+ public void ThrowsWithNullParameterizedCanExecute ()
+ {
+ Assert.Throws<ArgumentNullException> (() => new Command (o => { }, null));
+ }
+
+ [Test]
+ public void ThrowsWithNullExecuteValidCanExecute ()
+ {
+ Assert.Throws<ArgumentNullException> (() => new Command (null, () => true));
+ }
+
+ [Test]
+ public void Execute ()
+ {
+ bool executed = false;
+ var cmd = new Command (() => executed = true);
+
+ cmd.Execute (null);
+ Assert.True (executed);
+ }
+
+ [Test]
+ public void ExecuteParameterized ()
+ {
+ object executed = null;
+ var cmd = new Command (o => executed = o);
+
+ var expected = new object ();
+ cmd.Execute (expected);
+
+ Assert.AreEqual (expected, executed);
+ }
+
+ [Test]
+ public void ExecuteWithCanExecute ()
+ {
+ bool executed = false;
+ var cmd = new Command (() => executed = true, () => true);
+
+ cmd.Execute (null);
+ Assert.True (executed);
+ }
+
+ [Test]
+ public void CanExecute ([Values (true, false)] bool expected)
+ {
+ bool canExecuteRan = false;
+ var cmd = new Command (() => { }, () => {
+ canExecuteRan = true;
+ return expected;
+ });
+
+ Assert.AreEqual(expected, cmd.CanExecute (null));
+ Assert.True (canExecuteRan);
+ }
+
+ [Test]
+ public void ChangeCanExecute ()
+ {
+ bool signaled = false;
+ var cmd = new Command (() => { });
+
+ cmd.CanExecuteChanged += (sender, args) => signaled = true;
+
+ cmd.ChangeCanExecute ();
+ Assert.True (signaled);
+ }
+
+ [Test]
+ public void GenericThrowsWithNullExecute ()
+ {
+ Assert.Throws<ArgumentNullException> (() => new Command<string> (null));
+ }
+
+ [Test]
+ public void GenericThrowsWithNullExecuteAndCanExecuteValid ()
+ {
+ Assert.Throws<ArgumentNullException> (() => new Command<string> (null, s => true));
+ }
+
+ [Test]
+ public void GenericThrowsWithValidExecuteAndCanExecuteNull ()
+ {
+ Assert.Throws<ArgumentNullException> (() => new Command<string> (s => { }, null));
+ }
+
+ [Test]
+ public void GenericExecute ()
+ {
+ string result = null;
+ var cmd = new Command<string> (s => result = s);
+
+ cmd.Execute ("Foo");
+ Assert.AreEqual ("Foo", result);
+ }
+
+ [Test]
+ public void GenericExecuteWithCanExecute ()
+ {
+ string result = null;
+ var cmd = new Command<string> (s => result = s, s => true);
+
+ cmd.Execute ("Foo");
+ Assert.AreEqual ("Foo", result);
+ }
+
+ [Test]
+ public void GenericCanExecute ([Values (true, false)] bool expected)
+ {
+ string result = null;
+ var cmd = new Command<string> (s => { }, s => {
+ result = s;
+ return expected;
+ });
+
+ Assert.AreEqual (expected, cmd.CanExecute ("Foo"));
+ Assert.AreEqual ("Foo", result);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/ContentFormUnitTests.cs b/Xamarin.Forms.Core.UnitTests/ContentFormUnitTests.cs
new file mode 100644
index 00000000..5b6f3e60
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/ContentFormUnitTests.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Linq;
+using NUnit.Framework;
+
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class ContentPageUnitTests : BaseTestFixture
+ {
+ [Test]
+ public void PropagateBindingContextBefore()
+ {
+ var stack = new StackLayout();
+
+ var content = new ContentPage();
+ content.Content = stack;
+
+ object context = new object();
+ content.BindingContext = context;
+
+ Assert.AreSame (context, stack.BindingContext);
+ }
+
+ [Test]
+ public void PropagateBindingContextAfter()
+ {
+ var stack = new StackLayout();
+
+ var content = new ContentPage();
+
+ object context = new object();
+ content.BindingContext = context;
+
+ content.Content = stack;
+
+ Assert.AreSame (context, stack.BindingContext);
+ }
+
+ [Test]
+ public void PropagateToolbarItemBindingContextPreAdd ()
+ {
+ var page = new ContentPage ();
+ object context = "hello";
+
+ var toolbarItem = new ToolbarItem ();
+ page.ToolbarItems.Add (toolbarItem);
+
+ page.BindingContext = context;
+
+ Assert.AreEqual (context, toolbarItem.BindingContext);
+ }
+
+ [Test]
+ public void PropagateToolbarItemBindingContextPostAdd ()
+ {
+ var page = new ContentPage ();
+ object context = "hello";
+
+ var toolbarItem = new ToolbarItem ();
+ page.BindingContext = context;
+
+ page.ToolbarItems.Add (toolbarItem);
+
+ Assert.AreEqual (context, toolbarItem.BindingContext);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/ContentViewUnitTest.cs b/Xamarin.Forms.Core.UnitTests/ContentViewUnitTest.cs
new file mode 100644
index 00000000..9d7a12e0
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/ContentViewUnitTest.cs
@@ -0,0 +1,393 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class ContentViewUnitTests : BaseTestFixture
+ {
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ }
+
+ [Test]
+ public void TestConstructor ()
+ {
+ var contentView = new ContentView ();
+
+ Assert.Null (contentView.Content);
+ Assert.AreEqual (Color.Default, contentView.BackgroundColor);
+ Assert.AreEqual (new Thickness (0), contentView.Padding);
+ }
+
+ [Test]
+ public void TestSetChild ()
+ {
+ var contentView = new ContentView ();
+
+ var child1 = new Label ();
+
+ bool added = false;
+
+ contentView.ChildAdded += (sender, e) => added = true;
+
+ contentView.Content = child1;
+
+ Assert.True (added);
+ Assert.AreEqual (child1, contentView.Content);
+
+ added = false;
+ contentView.Content = child1;
+
+ Assert.False (added);
+ }
+
+ [Test]
+ public void TestReplaceChild ()
+ {
+ var contentView = new ContentView ();
+
+ var child1 = new Label ();
+ var child2 = new Label ();
+
+ contentView.Content = child1;
+
+ bool removed = false;
+ bool added = false;
+
+ contentView.ChildRemoved += (sender, e) => removed = true;
+ contentView.ChildAdded += (sender, e) => added = true;
+
+ contentView.Content = child2;
+
+ Assert.True (removed);
+ Assert.True (added);
+ Assert.AreEqual (child2, contentView.Content);
+ }
+
+ [Test]
+ public void TestFrameLayout ()
+ {
+ View child;
+
+ var contentView = new ContentView {
+ Padding = new Thickness (10),
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 200,
+ IsPlatformEnabled = true
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ Assert.AreEqual (new Size (120, 220), contentView.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity).Request);
+
+ contentView.Layout (new Rectangle (0, 0, 300, 300));
+
+ Assert.AreEqual (new Rectangle (10, 10, 280, 280), child.Bounds);
+ }
+
+ [Test]
+ public void WidthRequest ()
+ {
+ View child;
+
+ var contentView = new ContentView {
+ Padding = new Thickness (10),
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 200,
+ IsPlatformEnabled = true
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform (),
+ WidthRequest = 20
+ };
+
+ Assert.AreEqual (new Size (40, 220), contentView.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity).Request);
+ }
+
+ [Test]
+ public void HeightRequest ()
+ {
+ View child;
+
+ var contentView = new ContentView {
+ Padding = new Thickness (10),
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 200,
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform (),
+ HeightRequest = 20
+ };
+
+ Assert.AreEqual (new Size (120, 40), contentView.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity).Request);
+ }
+
+ [Test]
+ public void LayoutVerticallyCenter()
+ {
+ View child;
+
+ var contentView = new ContentView {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 100,
+ IsPlatformEnabled = true,
+ VerticalOptions = LayoutOptions.Center
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ contentView.Layout (new Rectangle(0,0, 200, 200));
+
+ Assert.AreEqual (new Rectangle (0, 50, 200, 100), child.Bounds);
+ }
+
+ [Test]
+ public void LayoutVerticallyBegin()
+ {
+ View child;
+
+ var contentView = new ContentView {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 100,
+ IsPlatformEnabled = true,
+ VerticalOptions = LayoutOptions.Start
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ contentView.Layout (new Rectangle(0,0, 200, 200));
+
+ Assert.AreEqual (new Rectangle (0, 0, 200, 100), child.Bounds);
+ }
+
+ [Test]
+ public void LayoutVerticallyEnd()
+ {
+ View child;
+
+ var contentView = new ContentView {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 100,
+ IsPlatformEnabled = true,
+ VerticalOptions = LayoutOptions.End
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ contentView.Layout (new Rectangle(0,0, 200, 200));
+
+ Assert.AreEqual (new Rectangle (0, 100, 200, 100), child.Bounds);
+ }
+
+ [Test]
+ public void LayoutHorizontallyCenter()
+ {
+ View child;
+
+ var contentView = new ContentView {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 100,
+ IsPlatformEnabled = true,
+ HorizontalOptions = LayoutOptions.Center
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ contentView.Layout (new Rectangle(0,0, 200, 200));
+
+ Assert.AreEqual (new Rectangle (50, 0, 100, 200), child.Bounds);
+ }
+
+ [Test]
+ public void LayoutHorizontallyBegin()
+ {
+ View child;
+
+ var contentView = new ContentView {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 100,
+ IsPlatformEnabled = true,
+ HorizontalOptions = LayoutOptions.Start
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ contentView.Layout (new Rectangle(0,0, 200, 200));
+
+ Assert.AreEqual (new Rectangle (0, 0, 100, 200), child.Bounds);
+ }
+
+ [Test]
+ public void LayoutHorizontallyEnd()
+ {
+ View child;
+
+ var contentView = new ContentView {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 100,
+ IsPlatformEnabled = true,
+ HorizontalOptions = LayoutOptions.End
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ contentView.Layout (new Rectangle(0,0, 200, 200));
+
+ Assert.AreEqual (new Rectangle (100, 0, 100, 200), child.Bounds);
+ }
+
+ [Test]
+ public void NullTemplateDirectlyHosts ()
+ {
+ // order of setting properties carefully picked to emulate running on real backend
+ var platform = new UnitPlatform ();
+
+ var contentView = new ContentView ();
+ var child = new View ();
+
+ contentView.Content = child;
+ contentView.Platform = platform;
+
+ Assert.AreEqual (child, contentView.LogicalChildren[0]);
+ }
+
+ class SimpleTemplate : StackLayout
+ {
+ public SimpleTemplate ()
+ {
+ Children.Add (new Label ());
+ Children.Add (new ContentPresenter ());
+ }
+ }
+
+
+ [Test]
+ public void TemplateInflates ()
+ {
+ var platform = new UnitPlatform ();
+
+ var contentView = new ContentView ();
+
+ contentView.ControlTemplate = new ControlTemplate (typeof (SimpleTemplate));
+ contentView.Platform = platform;
+
+ Assert.That (contentView.LogicalChildren[0], Is.TypeOf<SimpleTemplate> ());
+ }
+
+ [Test]
+ public void PacksContent ()
+ {
+ var platform = new UnitPlatform ();
+
+ var contentView = new ContentView ();
+ var child = new View ();
+
+ contentView.ControlTemplate = new ControlTemplate (typeof (SimpleTemplate));
+ contentView.Content = child;
+ contentView.Platform = platform;
+
+ Assume.That (contentView.LogicalChildren[0], Is.TypeOf<SimpleTemplate> ());
+ Assert.That (contentView.Descendants (), Contains.Item (child));
+ }
+
+ [Test]
+ public void DoesNotInheritBindingContextToTemplate ()
+ {
+ var platform = new UnitPlatform ();
+
+ var contentView = new ContentView ();
+ var child = new View ();
+
+ contentView.ControlTemplate = new ControlTemplate (typeof (SimpleTemplate));
+ contentView.Content = child;
+ contentView.Platform = platform;
+
+ var bc = "Test";
+ contentView.BindingContext = bc;
+
+ Assert.AreNotEqual (bc, contentView.LogicalChildren[0].BindingContext);
+ Assert.IsNull (contentView.LogicalChildren[0].BindingContext);
+ }
+
+ [Test]
+ public void ContentDoesGetBindingContext ()
+ {
+ var platform = new UnitPlatform ();
+
+ var contentView = new ContentView ();
+ var child = new View ();
+
+ contentView.ControlTemplate = new ControlTemplate (typeof (SimpleTemplate));
+ contentView.Content = child;
+ contentView.Platform = platform;
+
+ var bc = "Test";
+ contentView.BindingContext = bc;
+
+ Assert.AreEqual (bc, child.BindingContext);
+ }
+
+ [Test]
+ public void ContentParentIsNotInsideTempalte ()
+ {
+ var platform = new UnitPlatform ();
+
+ var contentView = new ContentView ();
+ var child = new View ();
+
+ contentView.ControlTemplate = new ControlTemplate (typeof (SimpleTemplate));
+ contentView.Content = child;
+ contentView.Platform = platform;
+
+ Assert.AreEqual (contentView, child.Parent);
+ }
+
+ [Test]
+ public void NonTemplatedContentInheritsBindingContext ()
+ {
+ var platform = new UnitPlatform ();
+
+ var contentView = new ContentView ();
+ var child = new View ();
+
+ contentView.Content = child;
+ contentView.Platform = platform;
+ contentView.BindingContext = "Foo";
+
+ Assert.AreEqual ("Foo", child.BindingContext);
+ }
+ }
+
+}
diff --git a/Xamarin.Forms.Core.UnitTests/ContraintTypeConverterTests.cs b/Xamarin.Forms.Core.UnitTests/ContraintTypeConverterTests.cs
new file mode 100644
index 00000000..e34f9a4a
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/ContraintTypeConverterTests.cs
@@ -0,0 +1,16 @@
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class ContraintTypeConverterTests : BaseTestFixture
+ {
+ [Test]
+ public void ConvertFrom ()
+ {
+ var converter = new ConstraintTypeConverter ();
+ Assert.AreEqual (Constraint.Constant (1.0).Compute (null), ((Constraint)converter.ConvertFromInvariantString ("1.0")).Compute (null));
+ Assert.AreEqual (Constraint.Constant (1.3).Compute (null), ((Constraint)converter.ConvertFromInvariantString ("1.3")).Compute (null));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/ControlTemplateTests.cs b/Xamarin.Forms.Core.UnitTests/ControlTemplateTests.cs
new file mode 100644
index 00000000..ffced2d2
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/ControlTemplateTests.cs
@@ -0,0 +1,181 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using NUnit.Framework;
+using Xamarin.Forms;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class ControlTemplateTests : BaseTestFixture
+ {
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ }
+
+ public class ContentControl : StackLayout
+ {
+ public ContentControl ()
+ {
+ var label = new Label ();
+ label.SetBinding (Label.TextProperty, new TemplateBinding ("Name"));
+
+ Children.Add (label);
+ Children.Add (new ContentPresenter ());
+ }
+ }
+
+ public class PresenterWrapper : ContentView
+ {
+ public PresenterWrapper ()
+ {
+ Content = new ContentPresenter ();
+ }
+ }
+
+ public class TestView : ContentView
+ {
+ public static readonly BindableProperty NameProperty =
+ BindableProperty.Create (nameof (Name), typeof (string), typeof (TestView), default(string));
+
+ public string Name
+ {
+ get { return (string)GetValue (NameProperty); }
+ set { SetValue (NameProperty, value); }
+ }
+
+ public TestView ()
+ {
+ ControlTemplate = new ControlTemplate(typeof (ContentControl));
+ }
+ }
+
+ [Test]
+ public void ResettingControlTemplateNullsPresenterContent ()
+ {
+ var testView = new TestView {
+ Platform = new UnitPlatform (),
+ ControlTemplate = new ControlTemplate (typeof (PresenterWrapper))
+ };
+
+ var label = new Label ();
+ testView.Content = label;
+ var originalPresenter = (ContentPresenter)testView.LogicalChildren[0].LogicalChildren[0];
+
+ Assert.AreEqual (label, originalPresenter.Content);
+
+ testView.ControlTemplate = new ControlTemplate (typeof (PresenterWrapper));
+
+ Assert.IsNull (originalPresenter.Content);
+ }
+
+ [Test]
+ public void NestedTemplateBindings ()
+ {
+ var testView = new TestView ();
+ var label = (Label)testView.LogicalChildren[0].LogicalChildren[0];
+
+ testView.Platform = new UnitPlatform ();
+ Assert.IsNull (label.Text);
+
+ testView.Name = "Bar";
+ Assert.AreEqual ("Bar", label.Text);
+ }
+
+ [Test]
+ public void ParentControlTemplateDoesNotClearChildTemplate ()
+ {
+ var parentView = new TestView ();
+ var childView = new TestView ();
+ parentView.Platform = new UnitPlatform ();
+
+ parentView.Content = childView;
+ childView.Content = new Button ();
+ var childPresenter = (ContentPresenter)childView.LogicalChildren[0].LogicalChildren[1];
+
+ parentView.ControlTemplate = new ControlTemplate (typeof (ContentControl));
+ Assert.IsNotNull (childPresenter.Content);
+ }
+
+ [Test]
+ public void NullConstructor ()
+ {
+ Assert.Throws<ArgumentNullException> (() => new ControlTemplate (null));
+ }
+
+ class TestPage : ContentPage
+ {
+ public static readonly BindableProperty NameProperty =
+ BindableProperty.Create (nameof (Name), typeof (string), typeof (TestPage), null);
+
+ public string Name
+ {
+ get { return (string)GetValue (NameProperty); }
+ set { SetValue (NameProperty, value); }
+ }
+ }
+
+ class TestContent : ContentView
+ {
+ public TestContent ()
+ {
+ Content = new Entry ();
+ Content.SetBinding (Entry.TextProperty, new TemplateBinding ("Name", BindingMode.TwoWay));
+ }
+ }
+
+ class ViewModel : INotifyPropertyChanged
+ {
+ string name;
+
+ public string Name
+ {
+ get { return name; }
+ set
+ {
+ if (name == value)
+ return;
+ name = value;
+ OnPropertyChanged ();
+ }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
+ }
+ }
+
+ [Test]
+ public void DoubleTwoWayBindingWorks ()
+ {
+ var page = new TestPage ();
+ page.Platform = new UnitPlatform ();
+ var viewModel = new ViewModel {
+ Name = "Jason"
+ };
+ page.BindingContext = viewModel;
+
+ page.ControlTemplate = new ControlTemplate (typeof (TestContent));
+ page.SetBinding (TestPage.NameProperty, "Name");
+
+ var entry = ((ContentView)page.LogicalChildren[0]).Content as Entry;
+ ((IElementController)entry).SetValueFromRenderer (Entry.TextProperty, "Bar");
+ viewModel.Name = "Raz";
+
+ Assert.AreEqual ("Raz", entry.Text);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs b/Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs
new file mode 100644
index 00000000..85779956
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/DataTemplateSelectorTests.cs
@@ -0,0 +1,79 @@
+using System;
+using NUnit.Framework;
+using Xamarin.Forms;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class DataTemplateSelectorTests : BaseTestFixture
+ {
+ class TemplateOne : DataTemplate
+ {
+ public TemplateOne () : base (typeof (ViewCell))
+ {
+
+ }
+ }
+
+ class TemplateTwo : DataTemplate
+ {
+ public TemplateTwo () : base (typeof (EntryCell))
+ {
+
+ }
+ }
+
+ class TestDTS : DataTemplateSelector
+ {
+ public TestDTS ()
+ {
+ templateOne = new TemplateOne ();
+ templateTwo = new TemplateTwo ();
+ }
+
+ protected override DataTemplate OnSelectTemplate (object item, BindableObject container)
+ {
+ if (item is double)
+ return templateOne;
+ if (item is byte)
+ return new TestDTS ();
+ return templateTwo;
+ }
+
+ readonly DataTemplate templateOne;
+ readonly DataTemplate templateTwo;
+ }
+
+ [Test]
+ public void Constructor ()
+ {
+ var dts = new TestDTS ();
+ }
+
+ [Test]
+ public void ReturnsCorrectType ()
+ {
+ var dts = new TestDTS ();
+ Assert.IsInstanceOf<TemplateOne> (dts.SelectTemplate (1d, null));
+ Assert.IsInstanceOf<TemplateTwo> (dts.SelectTemplate ("test", null));
+ }
+
+ [Test]
+ public void ListViewSupport ()
+ {
+ var listView = new ListView(ListViewCachingStrategy.RecycleElement);
+ listView.ItemsSource = new object[] { 0d, "test" };
+
+ listView.ItemTemplate = new TestDTS ();
+ Assert.IsInstanceOf<ViewCell> (listView.TemplatedItems[0]);
+ Assert.IsInstanceOf<EntryCell> (listView.TemplatedItems[1]);
+ }
+
+ [Test]
+ public void NestingThrowsException ()
+ {
+ var dts = new TestDTS ();
+ Assert.Throws<NotSupportedException> (() => dts.SelectTemplate ((byte)0, null));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/DataTemplateTests.cs b/Xamarin.Forms.Core.UnitTests/DataTemplateTests.cs
new file mode 100644
index 00000000..24bf6a06
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/DataTemplateTests.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class DataTemplateTests : BaseTestFixture
+ {
+ [Test]
+ public void CtorInvalid()
+ {
+ Assert.Throws<ArgumentNullException> (() => new DataTemplate ((Func<object>)null),
+ "Allowed null creator delegate");
+
+ Assert.Throws<ArgumentNullException> (() => new DataTemplate ((Type)null),
+ "Allowed null type");
+ }
+
+ [Test]
+ public void CreateContent()
+ {
+ var template = new DataTemplate (() => new MockBindable());
+ object obj = template.CreateContent();
+
+ Assert.IsNotNull (obj);
+ Assert.That (obj, Is.InstanceOf<MockBindable>());
+ }
+
+ [Test]
+ public void CreateContentType()
+ {
+ var template = new DataTemplate (typeof (MockBindable));
+ object obj = template.CreateContent();
+
+ Assert.IsNotNull (obj);
+ Assert.That (obj, Is.InstanceOf<MockBindable>());
+ }
+
+ [Test]
+ public void CreateContentValues()
+ {
+ var template = new DataTemplate (typeof (MockBindable)) {
+ Values = { { MockBindable.TextProperty, "value" } }
+ };
+
+ MockBindable bindable = (MockBindable)template.CreateContent();
+ Assert.That (bindable.GetValue (MockBindable.TextProperty), Is.EqualTo ("value"));
+ }
+
+ [Test]
+ public void CreateContentBindings()
+ {
+ var template = new DataTemplate (() => new MockBindable()) {
+ Bindings = { { MockBindable.TextProperty, new Binding (".") } }
+ };
+
+ MockBindable bindable = (MockBindable)template.CreateContent();
+ bindable.BindingContext = "text";
+ Assert.That (bindable.GetValue (MockBindable.TextProperty), Is.EqualTo ("text"));
+ }
+
+ [Test]
+ public void SetBindingInvalid()
+ {
+ var template = new DataTemplate (typeof (MockBindable));
+ Assert.That (() => template.SetBinding (null, new Binding (".")), Throws.InstanceOf<ArgumentNullException>());
+ Assert.That (() => template.SetBinding (MockBindable.TextProperty, null), Throws.InstanceOf<ArgumentNullException>());
+ }
+
+ [Test]
+ public void SetBindingOverridesValue()
+ {
+ var template = new DataTemplate (typeof (MockBindable));
+ template.SetValue (MockBindable.TextProperty, "value");
+ template.SetBinding (MockBindable.TextProperty, new Binding ("."));
+
+ MockBindable bindable = (MockBindable) template.CreateContent();
+ Assume.That (bindable.GetValue (MockBindable.TextProperty), Is.EqualTo (bindable.BindingContext));
+
+ bindable.BindingContext = "binding";
+ Assert.That (bindable.GetValue (MockBindable.TextProperty), Is.EqualTo ("binding"));
+ }
+
+ [Test]
+ public void SetValueOverridesBinding()
+ {
+ var template = new DataTemplate (typeof (MockBindable));
+ template.SetBinding (MockBindable.TextProperty, new Binding ("."));
+ template.SetValue (MockBindable.TextProperty, "value");
+
+ MockBindable bindable = (MockBindable) template.CreateContent();
+ Assert.That (bindable.GetValue (MockBindable.TextProperty), Is.EqualTo ("value"));
+ bindable.BindingContext = "binding";
+ Assert.That (bindable.GetValue (MockBindable.TextProperty), Is.EqualTo ("value"));
+ }
+
+ [Test]
+ public void SetValueInvalid()
+ {
+ var template = new DataTemplate (typeof (MockBindable));
+ Assert.That (() => template.SetValue (null, "string"), Throws.InstanceOf<ArgumentNullException>());
+ }
+
+ [Test]
+ public void SetValueAndBinding ()
+ {
+ var template = new DataTemplate (typeof (TextCell)) {
+ Bindings = {
+ {TextCell.TextProperty, new Binding ("Text")}
+ },
+ Values = {
+ {TextCell.TextProperty, "Text"}
+ }
+ };
+ Assert.That (() => template.CreateContent (), Throws.InstanceOf<InvalidOperationException> ());
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/DataTriggerTests.cs b/Xamarin.Forms.Core.UnitTests/DataTriggerTests.cs
new file mode 100644
index 00000000..585bebcf
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/DataTriggerTests.cs
@@ -0,0 +1,146 @@
+using NUnit.Framework;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class DataTriggerTests : BaseTestFixture
+ {
+ class MockElement : VisualElement
+ {
+ }
+
+ [Test]
+ public void SettersAppliedOnAttachIfConditionIsTrue ()
+ {
+ var setterbp = BindableProperty.Create ("bar", typeof(string), typeof(BindableObject), null);
+ var element = new MockElement ();
+ var datatrigger = new DataTrigger (typeof(VisualElement)) {
+ Binding = new Binding ("foo"),
+ Value = "foobar",
+ Setters = {
+ new Setter { Property = setterbp, Value = "qux" },
+ }
+ };
+
+ element.SetValue (setterbp, "default");
+ element.BindingContext = new {foo = "foobar"};
+ Assert.AreEqual ("default", element.GetValue (setterbp));
+ element.Triggers.Add (datatrigger);
+ Assert.AreEqual ("qux", element.GetValue (setterbp));
+ }
+
+ [Test]
+ public void SettersUnappliedOnDetach ()
+ {
+ var setterbp = BindableProperty.Create ("bar", typeof(string), typeof(BindableObject), null);
+ var element = new MockElement ();
+ var datatrigger = new DataTrigger (typeof(VisualElement)) {
+ Binding = new Binding ("foo"),
+ Value = "foobar",
+ Setters = {
+ new Setter { Property = setterbp, Value = "qux" },
+ }
+ };
+
+ element.SetValue (setterbp, "default");
+ element.Triggers.Add (datatrigger);
+
+ Assert.AreEqual ("default", element.GetValue (setterbp));
+ element.BindingContext = new {foo = "foobar"};
+ Assert.AreEqual ("qux", element.GetValue (setterbp));
+ element.Triggers.Remove (datatrigger);
+ Assert.AreEqual ("default", element.GetValue (setterbp));
+ }
+
+ [Test]
+ public void SettersAppliedOnConditionChanged ()
+ {
+ var setterbp = BindableProperty.Create ("bar", typeof(string), typeof(BindableObject), null);
+ var element = new MockElement ();
+ var trigger = new DataTrigger (typeof (VisualElement)){
+ Binding = new Binding ("foo"),
+ Value = "foobar",
+ Setters = {
+ new Setter { Property = setterbp, Value = "qux" },
+ }
+ };
+
+ element.SetValue (setterbp, "default");
+ element.Triggers.Add (trigger);
+
+ Assert.AreEqual ("default", element.GetValue (setterbp));
+ element.BindingContext = new {foo = "foobar"};
+ Assert.AreEqual ("qux", element.GetValue (setterbp));
+ element.BindingContext = new {foo = ""};
+ Assert.AreEqual ("default", element.GetValue (setterbp));
+ }
+
+ [Test]
+ public void TriggersAppliedOnMultipleElements ()
+ {
+ var setterbp = BindableProperty.Create ("bar", typeof(string), typeof(BindableObject), null);
+ var trigger = new DataTrigger (typeof(VisualElement)) {
+ Binding = new Binding ("foo"),
+ Value = "foobar",
+ Setters = {
+ new Setter { Property = setterbp, Value = "qux" },
+ }
+ };
+ var element0 = new MockElement { Triggers = { trigger } };
+ var element1 = new MockElement { Triggers = { trigger } };
+
+ element0.BindingContext = element1.BindingContext = new {foo = "foobar"};
+ Assert.AreEqual ("qux", element0.GetValue (setterbp));
+ Assert.AreEqual ("qux", element1.GetValue (setterbp));
+ }
+
+ [Test]
+ //https://bugzilla.xamarin.com/show_bug.cgi?id=30074
+ public void AllTriggersUnappliedBeforeApplying ()
+ {
+ var boxview = new BoxView {
+ Triggers = {
+ new DataTrigger (typeof(BoxView)) {
+ Binding = new Binding ("."),
+ Value = "Complete",
+ Setters = {
+ new Setter { Property = BoxView.ColorProperty, Value = Color.Green },
+ new Setter { Property = VisualElement.OpacityProperty, Value = .5 },
+ }
+ },
+ new DataTrigger (typeof(BoxView)) {
+ Binding = new Binding ("."),
+ Value = "MissingInfo",
+ Setters = {
+ new Setter { Property = BoxView.ColorProperty, Value = Color.Yellow },
+ }
+ },
+ new DataTrigger (typeof(BoxView)) {
+ Binding = new Binding ("."),
+ Value = "Error",
+ Setters = {
+ new Setter { Property = BoxView.ColorProperty, Value = Color.Red },
+ }
+ },
+ }
+ };
+
+ boxview.BindingContext = "Complete";
+ Assert.AreEqual (Color.Green, boxview.Color);
+ Assert.AreEqual (.5, boxview.Opacity);
+
+ boxview.BindingContext = "MissingInfo";
+ Assert.AreEqual (Color.Yellow, boxview.Color);
+ Assert.AreEqual (1, boxview.Opacity);
+
+ boxview.BindingContext = "Error";
+ Assert.AreEqual (Color.Red, boxview.Color);
+ Assert.AreEqual (1, boxview.Opacity);
+
+ boxview.BindingContext = "Complete";
+ Assert.AreEqual (Color.Green, boxview.Color);
+ Assert.AreEqual (.5, boxview.Opacity);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/DatePickerUnitTest.cs b/Xamarin.Forms.Core.UnitTests/DatePickerUnitTest.cs
new file mode 100644
index 00000000..97bbf410
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/DatePickerUnitTest.cs
@@ -0,0 +1,178 @@
+using System;
+
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class DatePickerUnitTest : BaseTestFixture
+ {
+ [Test]
+ public void TestMinimumDateException ()
+ {
+ DatePicker picker = new DatePicker ();
+
+ picker.MinimumDate = new DateTime (1950, 1, 1);
+
+ Assert.AreEqual (new DateTime (1950, 1, 1), picker.MinimumDate);
+
+ Assert.That (() => picker.MinimumDate = new DateTime (2200, 1, 1), Throws.ArgumentException);
+ }
+
+ [Test]
+ public void TestMaximumDateException ()
+ {
+ DatePicker picker = new DatePicker ();
+
+ picker.MaximumDate = new DateTime (2050, 1, 1);
+
+ Assert.AreEqual (new DateTime (2050, 1, 1), picker.MaximumDate);
+
+ Assert.That (() => picker.MaximumDate = new DateTime (1800, 1, 1), Throws.ArgumentException);
+ }
+
+ [Test]
+ public void TestMaximumDateClamping ()
+ {
+ DatePicker picker = new DatePicker ();
+
+ picker.Date = new DateTime (2050, 1, 1);
+
+ Assert.AreEqual (new DateTime (2050, 1, 1), picker.Date);
+
+ bool dateChanged = false;
+ bool maximumDateChanged = false;
+ picker.PropertyChanged += (sender, e) => {
+ switch (e.PropertyName) {
+ case "MaximumDate":
+ maximumDateChanged = true;
+ break;
+ case "Date":
+ dateChanged = true;
+ Assert.IsFalse (maximumDateChanged);
+ break;
+ }
+ };
+
+ var newDate = new DateTime (2000, 1, 1);
+ picker.MaximumDate = newDate;
+
+ Assert.IsTrue (maximumDateChanged);
+ Assert.IsTrue (dateChanged);
+
+ Assert.AreEqual (newDate, picker.MaximumDate);
+ Assert.AreEqual (newDate, picker.Date);
+ Assert.AreEqual (picker.MaximumDate, picker.Date);
+ }
+
+ [Test]
+ public void TestMinimumDateClamping ()
+ {
+ DatePicker picker = new DatePicker ();
+
+ picker.Date = new DateTime (1950, 1, 1);
+
+ Assert.AreEqual (new DateTime (1950, 1, 1), picker.Date);
+
+ bool dateChanged = false;
+ bool minimumDateChanged = false;
+ picker.PropertyChanged += (sender, e) => {
+ switch (e.PropertyName) {
+ case "MinimumDate":
+ minimumDateChanged = true;
+ break;
+ case "Date":
+ dateChanged = true;
+ Assert.IsFalse (minimumDateChanged);
+ break;
+ }
+ };
+
+ var newDate = new DateTime (2000, 1, 1);
+ picker.MinimumDate = newDate;
+
+ Assert.IsTrue (minimumDateChanged);
+ Assert.IsTrue (dateChanged);
+
+ Assert.AreEqual (newDate, picker.MinimumDate);
+ Assert.AreEqual (newDate, picker.Date);
+ Assert.AreEqual (picker.MinimumDate, picker.Date);
+ }
+
+ [Test]
+ public void TestDateClamping ()
+ {
+ DatePicker picker = new DatePicker ();
+
+ picker.Date = new DateTime (1500, 1, 1);
+
+ Assert.AreEqual (picker.MinimumDate, picker.Date);
+
+ picker.Date = new DateTime (2500, 1, 1);
+
+ Assert.AreEqual (picker.MaximumDate, picker.Date);
+ }
+
+ [Test]
+ public void TestDateSelected ()
+ {
+ var picker = new DatePicker ();
+
+ bool selected = false;
+ picker.DateSelected += (sender, arg) => selected = true;
+
+ // we can be fairly sure it wont ever be 2008 again
+ picker.Date = new DateTime (2008, 5, 5);
+
+ Assert.True (selected);
+ }
+
+ static object[] DateTimes = {
+ new object[] { new DateTime (2006, 12, 20), new DateTime (2011, 11, 30) },
+ new object[] { new DateTime (1900, 1, 1), new DateTime (1999, 01, 15) }, // Minimum Date
+ new object[] { new DateTime (2006, 12, 20), new DateTime (2100, 12, 31) } // Maximum Date
+ };
+
+ [Test, TestCaseSource("DateTimes")]
+ public void DatePickerSelectedEventArgs (DateTime initialDate, DateTime finalDate)
+ {
+ var datePicker = new DatePicker ();
+ datePicker.Date = initialDate;
+
+ DatePicker pickerFromSender = null;
+ DateTime oldDate = new DateTime ();
+ DateTime newDate = new DateTime ();
+
+ datePicker.DateSelected += (s, e) => {
+ pickerFromSender = (DatePicker)s;
+ oldDate = e.OldDate;
+ newDate = e.NewDate;
+ };
+
+ datePicker.Date = finalDate;
+
+ Assert.AreEqual (datePicker, pickerFromSender);
+ Assert.AreEqual (initialDate, oldDate);
+ Assert.AreEqual (finalDate, newDate);
+ }
+
+ [Test]
+ //https://bugzilla.xamarin.com/show_bug.cgi?id=32144
+ public void SetNullValueDoesNotThrow ()
+ {
+ var datePicker = new DatePicker ();
+ Assert.DoesNotThrow (() => datePicker.SetValue (DatePicker.DateProperty, null));
+ Assert.AreEqual (DatePicker.DateProperty.DefaultValue, datePicker.Date);
+ }
+
+ [Test]
+ public void SetNullableDateTime ()
+ {
+ var datePicker = new DatePicker ();
+ var dateTime = new DateTime (2015, 7, 21);
+ DateTime? nullableDateTime = dateTime;
+ datePicker.SetValue (DatePicker.DateProperty, nullableDateTime);
+ Assert.AreEqual (dateTime, datePicker.Date);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/DependencyServiceTests.cs b/Xamarin.Forms.Core.UnitTests/DependencyServiceTests.cs
new file mode 100644
index 00000000..30e38b62
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/DependencyServiceTests.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using Xamarin.Forms;
+using Xamarin.Forms.Core.UnitTests;
+
+[assembly: Dependency(typeof (DependencyTestImpl))]
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ public interface IDependencyTest
+ {
+ bool Works { get; }
+ }
+
+ public interface IDependencyTestRegister
+ {
+ bool Works { get; }
+ }
+
+ public interface IUnsatisfied
+ {
+ bool Broken { get; }
+ }
+
+ public class DependencyTestImpl : IDependencyTest
+ {
+ public bool Works { get { return true; } }
+ }
+
+ public class DependencyTestRegisterImpl : IDependencyTestRegister
+ {
+ public bool Works { get { return true; } }
+ }
+
+ public class DependencyTestRegisterImpl2 : IDependencyTestRegister
+ {
+ public bool Works { get { return false; } }
+ }
+
+ public class DependencyServiceTests : BaseTestFixture
+ {
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ }
+
+ [Test]
+ public void GetGlobalInstance ()
+ {
+ var global = DependencyService.Get<IDependencyTest> ();
+
+ Assert.NotNull (global);
+
+ var secondFetch = DependencyService.Get<IDependencyTest> ();
+
+ Assert.True (ReferenceEquals (global, secondFetch));
+ }
+
+ [Test]
+ public void NewInstanceIsNotGlobalInstance ()
+ {
+ var global = DependencyService.Get<IDependencyTest> ();
+
+ Assert.NotNull (global);
+
+ var secondFetch = DependencyService.Get<IDependencyTest> (DependencyFetchTarget.NewInstance);
+
+ Assert.False (ReferenceEquals (global, secondFetch));
+ }
+
+ [Test]
+ public void NewInstanceIsAlwaysNew ()
+ {
+ var firstFetch = DependencyService.Get<IDependencyTest> (DependencyFetchTarget.NewInstance);
+
+ Assert.NotNull (firstFetch);
+
+ var secondFetch = DependencyService.Get<IDependencyTest> (DependencyFetchTarget.NewInstance);
+
+ Assert.False (ReferenceEquals (firstFetch, secondFetch));
+ }
+
+ [Test]
+ public void UnsatisfiedReturnsNull ()
+ {
+ Assert.Null (DependencyService.Get<IUnsatisfied> ());
+ }
+
+ [Test]
+ public void RegisterTypeImplementation ()
+ {
+ DependencyService.Register<DependencyTestRegisterImpl> ();
+ var global = DependencyService.Get<DependencyTestRegisterImpl> ();
+ Assert.NotNull (global);
+ }
+
+
+ [Test]
+ public void RegisterInterfaceAndImplementations ()
+ {
+ DependencyService.Register<IDependencyTestRegister, DependencyTestRegisterImpl2> ();
+ var global = DependencyService.Get<IDependencyTestRegister> ();
+ Assert.IsInstanceOf<DependencyTestRegisterImpl2> (global);
+ }
+
+ [Test]
+ public void RegisterInterfaceAndOverrideImplementations ()
+ {
+ DependencyService.Register<IDependencyTestRegister, DependencyTestRegisterImpl> ();
+ DependencyService.Register<IDependencyTestRegister, DependencyTestRegisterImpl2> ();
+ var global = DependencyService.Get<IDependencyTestRegister> ();
+ Assert.IsInstanceOf<DependencyTestRegisterImpl2> (global);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/DistanceTests.cs b/Xamarin.Forms.Core.UnitTests/DistanceTests.cs
new file mode 100644
index 00000000..829bf957
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/DistanceTests.cs
@@ -0,0 +1,197 @@
+using System;
+using NUnit.Framework;
+using Xamarin.Forms.Maps;
+
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class DistanceTests : BaseTestFixture
+ {
+ [Test]
+ public void Constructor ()
+ {
+ var distance = new Distance (25);
+ Assert.AreEqual (25, distance.Meters);
+ }
+
+ [Test]
+ public void ConstructFromKilometers()
+ {
+ const double EPSILON = 0.001;
+
+ Distance distance = Distance.FromKilometers(2);
+
+ Assert.True(Math.Abs(distance.Kilometers - 2) < EPSILON);
+ Assert.True(Math.Abs(distance.Meters - 2000) < EPSILON);
+ Assert.True(Math.Abs(distance.Miles - 1.24274) < EPSILON);
+ }
+
+ [Test]
+ public void ConstructFromMeters()
+ {
+ const double EPSILON = 0.001;
+
+ Distance distance = Distance.FromMeters(10560);
+
+ Assert.True(Math.Abs(distance.Meters - 10560) < EPSILON);
+ Assert.True(Math.Abs(distance.Miles - 6.5616798) < EPSILON);
+ Assert.True(Math.Abs(distance.Kilometers - 10.56) < EPSILON);
+ }
+
+ [Test]
+ public void ConstructFromMiles()
+ {
+ const double EPSILON = 0.001;
+
+ // Reached the limit of double precision using the number
+ // of miles of the earth's circumference
+ const double EPSILON_FOR_LARGE_MILES_TO_METERS = 16;
+
+ // Reached the limit of double precision
+ const double EPSILON_FOR_LARGE_MILES_TO_KM = 0.1;
+
+ Distance distance = Distance.FromMiles(3963.1676);
+
+ Assert.True(Math.Abs(distance.Miles - 3963.1676) < EPSILON);
+ Assert.True(Math.Abs(distance.Meters - 6378099.99805) < EPSILON_FOR_LARGE_MILES_TO_METERS);
+ Assert.True(Math.Abs(distance.Kilometers - 6378.09999805) < EPSILON_FOR_LARGE_MILES_TO_KM);
+ }
+
+ [Test]
+ public void EqualityOp([Range(5, 9)] double x, [Range(5, 9)] double y)
+ {
+ bool result = Distance.FromMeters(x) == Distance.FromMeters(y);
+
+ if (x == y)
+ Assert.True(result);
+ else
+ Assert.False(result);
+ }
+
+ [Test]
+ public void Equals([Range(3, 7)] double x, [Range(3, 7)] double y)
+ {
+ bool result = Distance.FromMiles(x).Equals(Distance.FromMiles(y));
+ if (x == y)
+ Assert.True(result);
+ else
+ Assert.False(result);
+ }
+
+ [Test]
+ public void EqualsNull()
+ {
+ Assert.False(Distance.FromMeters(5).Equals(null));
+ }
+
+ [Test]
+ public void GettingAndSettingKilometers()
+ {
+ const double EPSILON = 0.001;
+
+ Distance distance = Distance.FromKilometers(1891);
+ Assert.True(Math.Abs(distance.Kilometers - 1891) < EPSILON);
+ }
+
+ [Test]
+ public void GettingAndSettingMeters()
+ {
+ const double EPSILON = 0.001;
+
+ Distance distance = Distance.FromMeters(123434);
+ Assert.True(Math.Abs(distance.Meters - 123434) < EPSILON);
+ }
+
+ [Test]
+ public void GettingAndSettingMiles()
+ {
+ const double EPSILON = 0.001;
+
+ Distance distance = Distance.FromMiles(515);
+ Assert.True(Math.Abs(distance.Miles - 515) < EPSILON);
+ }
+
+ [Test]
+ public void HashCode([Range(4, 5)] double x, [Range(4, 5)] double y)
+ {
+ Distance distance1 = Distance.FromMiles(x);
+ Distance distance2 = Distance.FromMiles(y);
+
+ bool result = distance1.GetHashCode() == distance2.GetHashCode();
+
+ if (x == y)
+ Assert.True(result);
+ else
+ Assert.False(result);
+ }
+
+ [Test]
+ public void InequalityOp([Range(5, 9)] double x, [Range(5, 9)] double y)
+ {
+ bool result = Distance.FromMeters(x) != Distance.FromMeters(y);
+
+ if (x != y)
+ Assert.True(result);
+ else
+ Assert.False(result);
+ }
+
+ [Test]
+ public void ObjectInitializerKilometers()
+ {
+ const double EPSILON = 0.001;
+
+ Distance distance = Distance.FromKilometers(10);
+ Assert.True(Math.Abs(distance.Meters - 10000) < EPSILON);
+ }
+
+ [Test]
+ public void ObjectInitializerMeters()
+ {
+ const double EPSILON = 0.001;
+
+ Distance distance = Distance.FromMeters(1057);
+ Assert.True(Math.Abs(distance.Kilometers - 1.057) < EPSILON);
+ }
+
+ [Test]
+ public void ObjectInitializerMiles()
+ {
+ const double EPSILON = 0.001;
+
+ Distance distance = Distance.FromMiles(100);
+ Assert.True(Math.Abs(distance.Meters - 160934.4) < EPSILON);
+ }
+
+ [Test]
+ public void ClampFromMeters ()
+ {
+ var distance = Distance.FromMeters (-1);
+
+ Assert.AreEqual (0, distance.Meters);
+ }
+
+ [Test]
+ public void ClampFromMiles ()
+ {
+ var distance = Distance.FromMiles (-1);
+
+ Assert.AreEqual (0, distance.Meters);
+ }
+
+ [Test]
+ public void ClampFromKilometers ()
+ {
+ var distance = Distance.FromKilometers (-1);
+
+ Assert.AreEqual (0, distance.Meters);
+ }
+
+ [Test]
+ public void Equals ()
+ {
+ Assert.True (Distance.FromMiles (2).Equals ((object) Distance.FromMiles (2)));
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/DynamicBindingContextTests.cs b/Xamarin.Forms.Core.UnitTests/DynamicBindingContextTests.cs
new file mode 100644
index 00000000..53618909
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/DynamicBindingContextTests.cs
@@ -0,0 +1,314 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class DynamicBindingContextTests
+ {
+ [Test]
+ public void BindingTwoWayToDynamicModel ()
+ {
+ var view = new MockBindable ();
+ var model = new DynamicModel
+ {
+ Properties =
+ {
+ { "Title", "Foo" },
+ }
+ };
+
+ view.SetBinding (MockBindable.TextProperty, "Title");
+ view.BindingContext = model;
+
+ Assert.AreEqual ("Foo", view.Text);
+
+ view.Text = "Bar";
+
+ Assert.AreEqual ("Bar", model.Properties["Title"]);
+ }
+
+ // This whole class and inner types is just a very simple
+ // dictionary-based dynamic model that proves that the
+ // approach works. It implements just the bare minimum of
+ // the base types to get our bindings to work properly and
+ // pass the tests.
+ class DynamicModel : IReflectableType
+ {
+ public DynamicModel ()
+ {
+ Properties = new Dictionary<string, object> ();
+ }
+
+ public TypeInfo GetTypeInfo ()
+ {
+ return new DynamicTypeInfo (Properties);
+ }
+
+ public IDictionary<string, object> Properties { get; private set; }
+
+ class DynamicTypeInfo : TypeDelegator
+ {
+ IDictionary<string, object> properties;
+
+ public DynamicTypeInfo (IDictionary<string, object> properties)
+ : base (typeof(object))
+ {
+ this.properties = properties;
+ }
+
+ public override PropertyInfo GetDeclaredProperty (string name)
+ {
+ if (!properties.ContainsKey (name))
+ return null;
+
+ return new DynamicPropertyInfo (properties, name);
+ }
+
+ internal class DynamicPropertyInfo : PropertyInfo
+ {
+ IDictionary<string, object> properties;
+ string name;
+
+ public DynamicPropertyInfo (IDictionary<string, object> properties, string name)
+ {
+ this.properties = properties;
+ this.name = name;
+ }
+
+ public override bool CanRead
+ {
+ get { return true; }
+ }
+
+ public override bool CanWrite
+ {
+ get { return true; }
+ }
+
+ public override MethodInfo GetGetMethod (bool nonPublic)
+ {
+ return new DynamicPropertyGetterInfo (this, properties);
+ }
+
+ public override MethodInfo GetSetMethod (bool nonPublic)
+ {
+ return new DynamicPropertySetterInfo (this, properties);
+ }
+
+ public override Type PropertyType
+ {
+ get { return properties[name].GetType (); }
+ }
+
+ public override string Name
+ {
+ get { return name; }
+ }
+
+ public override PropertyAttributes Attributes
+ {
+ get { return PropertyAttributes.None; }
+ }
+
+ public override MethodInfo[] GetAccessors (bool nonPublic)
+ {
+ return new[] { GetGetMethod (nonPublic), GetSetMethod (nonPublic) };
+ }
+
+ public override ParameterInfo[] GetIndexParameters ()
+ {
+ return new ParameterInfo[0];
+ }
+
+ public override object GetValue (object obj, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override void SetValue (object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, System.Globalization.CultureInfo culture)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override Type DeclaringType
+ {
+ get { throw new NotImplementedException (); }
+ }
+
+ public override object[] GetCustomAttributes (Type attributeType, bool inherit)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override object[] GetCustomAttributes (bool inherit)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override bool IsDefined (Type attributeType, bool inherit)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override Type ReflectedType
+ {
+ get { throw new NotImplementedException (); }
+ }
+ }
+
+ internal class DynamicPropertyGetterInfo : DynamicPropertyMethodInfo
+ {
+ public DynamicPropertyGetterInfo (PropertyInfo property, IDictionary<string, object> properties)
+ : base (property, properties)
+ {
+ }
+
+ public override object Invoke (object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
+ {
+ return Properties[Property.Name];
+ }
+
+ public override ParameterInfo[] GetParameters ()
+ {
+ return new[] { new DynamicParameterInfo (Property, Property.PropertyType, "value") };
+ }
+
+ public override Type ReturnType
+ {
+ get { return Property.PropertyType; }
+ }
+ }
+
+ internal class DynamicPropertySetterInfo : DynamicPropertyMethodInfo
+ {
+ public DynamicPropertySetterInfo (PropertyInfo property, IDictionary<string, object> properties)
+ : base (property, properties)
+ {
+ }
+
+ public override object Invoke (object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture)
+ {
+ Properties[Property.Name] = parameters[0];
+ return null;
+ }
+
+ public override ParameterInfo[] GetParameters ()
+ {
+ return new[] {
+ new DynamicParameterInfo (Property, typeof(DynamicTypeInfo), "this"),
+ new DynamicParameterInfo (Property, Property.PropertyType, "value")
+ };
+ }
+
+ public override Type ReturnType
+ {
+ get { return typeof(void); }
+ }
+ }
+
+ internal abstract class DynamicPropertyMethodInfo : MethodInfo
+ {
+ public DynamicPropertyMethodInfo (PropertyInfo property, IDictionary<string, object> properties)
+ {
+ Property = property;
+ Properties = properties;
+ }
+
+ protected PropertyInfo Property { get; private set; }
+
+ protected IDictionary<string, object> Properties { get; private set; }
+
+ public override MethodInfo GetBaseDefinition ()
+ {
+ return null;
+ }
+
+ public override ICustomAttributeProvider ReturnTypeCustomAttributes
+ {
+ get { return null; }
+ }
+
+ public override MethodAttributes Attributes
+ {
+ get { return MethodAttributes.Public; }
+ }
+
+ public override MethodImplAttributes GetMethodImplementationFlags ()
+ {
+ return MethodImplAttributes.IL;
+ }
+
+ public override ParameterInfo[] GetParameters ()
+ {
+ return new ParameterInfo[0];
+ }
+
+ public override RuntimeMethodHandle MethodHandle
+ {
+ get { throw new NotImplementedException (); }
+ }
+
+ public override Type DeclaringType
+ {
+ get { return typeof(DynamicModel); }
+ }
+
+ public override object[] GetCustomAttributes (Type attributeType, bool inherit)
+ {
+ return new object[0];
+ }
+
+ public override object[] GetCustomAttributes (bool inherit)
+ {
+ return new object[0];
+ }
+
+ public override bool IsDefined (Type attributeType, bool inherit)
+ {
+ return false;
+ }
+
+ public override string Name
+ {
+ get { return Property.Name; }
+ }
+
+ public override Type ReflectedType
+ {
+ get { return typeof(DynamicModel); }
+ }
+ }
+
+ internal class DynamicParameterInfo : ParameterInfo
+ {
+ MemberInfo member;
+ Type type;
+
+ public DynamicParameterInfo (MemberInfo member, Type type, string name)
+ {
+ this.member = member;
+ this.type = type;
+ }
+
+ public override MemberInfo Member
+ {
+ get { return member; }
+ }
+
+ public override Type ParameterType
+ {
+ get { return type; }
+ }
+ }
+ }
+
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/DynamicResourceTests.cs b/Xamarin.Forms.Core.UnitTests/DynamicResourceTests.cs
new file mode 100644
index 00000000..faea9963
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/DynamicResourceTests.cs
@@ -0,0 +1,153 @@
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class DynamicResourceTests : BaseTestFixture
+ {
+ [SetUp]
+ public override void Setup ()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [Test]
+ public void TestDynamicResource ()
+ {
+ var label = new Label ();
+ label.SetDynamicResource (Label.TextProperty, "foo");
+ var layout = new StackLayout {
+ Children = {
+ label
+ }
+ };
+
+ Assert.AreEqual (Label.TextProperty.DefaultValue, label.Text);
+
+ layout.Resources = new ResourceDictionary {
+ { "foo", "FOO" }
+ };
+ Assert.AreEqual ("FOO", label.Text);
+ }
+
+ [Test]
+ public void SetResourceTriggerSetValue ()
+ {
+ var label = new Label ();
+ label.SetDynamicResource (Label.TextProperty, "foo");
+ Assert.AreEqual (Label.TextProperty.DefaultValue, label.Text);
+ label.Resources = new ResourceDictionary {
+ {"foo", "FOO"}
+ };
+ Assert.AreEqual ("FOO", label.Text);
+ }
+
+ [Test]
+ public void SetResourceOnParentTriggerSetValue ()
+ {
+ var label = new Label ();
+ var layout = new StackLayout { Children = {label}};
+ label.SetDynamicResource (Label.TextProperty, "foo");
+ Assert.AreEqual (Label.TextProperty.DefaultValue, label.Text);
+ layout.Resources = new ResourceDictionary {
+ {"foo", "FOO"}
+ };
+ Assert.AreEqual ("FOO", label.Text);
+ }
+
+ [Test]
+ public void SettingResourceTriggersValueChanged ()
+ {
+ var label = new Label ();
+ label.SetDynamicResource (Label.TextProperty, "foo");
+ Assert.AreEqual (Label.TextProperty.DefaultValue, label.Text);
+ label.Resources = new ResourceDictionary ();
+ label.Resources.Add ("foo", "FOO");
+ Assert.AreEqual ("FOO", label.Text);
+ }
+
+ [Test]
+ public void AddingAResourceDictionaryTriggersValueChangedForExistingValues ()
+ {
+ var label = new Label ();
+ label.SetDynamicResource (Label.TextProperty, "foo");
+ Assert.AreEqual (Label.TextProperty.DefaultValue, label.Text);
+ var rd = new ResourceDictionary { {"foo","FOO"}};
+ label.Resources = rd;
+ Assert.AreEqual ("FOO", label.Text);
+ }
+
+ [Test]
+ public void ValueChangedTriggeredOnSubscribeIfKeyAlreadyExists ()
+ {
+ var label = new Label ();
+ label.Resources = new ResourceDictionary { {"foo","FOO"}};
+ Assert.AreEqual (Label.TextProperty.DefaultValue, label.Text);
+ label.SetDynamicResource (Label.TextProperty, "foo");
+ Assert.AreEqual ("FOO", label.Text);
+ }
+
+ [Test]
+ public void RemoveDynamicResourceStopsUpdating ()
+ {
+ var label = new Label ();
+ label.Resources = new ResourceDictionary { {"foo","FOO"}};
+ Assert.AreEqual (Label.TextProperty.DefaultValue, label.Text);
+ label.SetDynamicResource (Label.TextProperty, "foo");
+ Assert.AreEqual ("FOO", label.Text);
+ label.RemoveDynamicResource (Label.TextProperty);
+ label.Resources ["foo"] = "BAR";
+ Assert.AreEqual ("FOO", label.Text);
+ }
+
+ [Test]
+ public void ReparentResubscribe ()
+ {
+ var layout0 = new ContentView { Resources = new ResourceDictionary {{"foo","FOO"}}};
+ var layout1 = new ContentView { Resources = new ResourceDictionary {{"foo","BAR"}}};
+
+ var label = new Label ();
+ label.SetDynamicResource (Label.TextProperty, "foo");
+ Assert.AreEqual (Label.TextProperty.DefaultValue, label.Text);
+
+ layout0.Content = label;
+ Assert.AreEqual ("FOO", label.Text);
+
+ layout0.Content = null;
+ layout1.Content = label;
+ Assert.AreEqual ("BAR", label.Text);
+ }
+
+ [Test]
+ public void ClearedResourcesDoesNotClearValues ()
+ {
+ var layout0 = new ContentView { Resources = new ResourceDictionary {{"foo","FOO"}}};
+ var label = new Label ();
+ label.SetDynamicResource (Label.TextProperty, "foo");
+ layout0.Content = label;
+
+ Assert.AreEqual ("FOO", label.Text);
+
+ layout0.Resources.Clear ();
+ Assert.AreEqual ("FOO", label.Text);
+ }
+
+ [Test]
+ //Issue 2608
+ public void ResourcesCanBeChanged ()
+ {
+ var label = new Label ();
+ label.BindingContext = new MockViewModel ();
+ label.SetBinding (Label.TextProperty, "Text", BindingMode.TwoWay);
+ label.SetDynamicResource (Label.TextProperty, "foo");
+ label.Resources = new ResourceDictionary { {"foo","FOO"}};
+
+ Assert.AreEqual ("FOO", label.Text);
+
+ label.Resources ["foo"] = "BAR";
+
+ Assert.AreEqual ("BAR", label.Text);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/EasingTests.cs b/Xamarin.Forms.Core.UnitTests/EasingTests.cs
new file mode 100644
index 00000000..be491ed8
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/EasingTests.cs
@@ -0,0 +1,32 @@
+using System;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class EasingTests : BaseTestFixture
+ {
+ [Test]
+ public void Linear ([Range (0, 10)] double input)
+ {
+ Assert.AreEqual (input, Easing.Linear.Ease (input));
+ }
+
+ [Test]
+ public void AllRunFromZeroToOne ([Values (0.0, 1.0)] double val)
+ {
+ const double epsilon = 0.001;
+ Assert.True (Math.Abs (val - Easing.Linear.Ease (val)) < epsilon);
+ Assert.True (Math.Abs (val - Easing.BounceIn.Ease (val)) < epsilon);
+ Assert.True (Math.Abs (val - Easing.BounceOut.Ease (val)) < epsilon);
+ Assert.True (Math.Abs (val - Easing.CubicIn.Ease (val)) < epsilon);
+ Assert.True (Math.Abs (val - Easing.CubicInOut.Ease (val)) < epsilon);
+ Assert.True (Math.Abs (val - Easing.CubicOut.Ease (val)) < epsilon);
+ Assert.True (Math.Abs (val - Easing.SinIn.Ease (val)) < epsilon);
+ Assert.True (Math.Abs (val - Easing.SinInOut.Ease (val)) < epsilon);
+ Assert.True (Math.Abs (val - Easing.SinOut.Ease (val)) < epsilon);
+ Assert.True (Math.Abs (val - Easing.SpringIn.Ease (val)) < epsilon);
+ Assert.True (Math.Abs (val - Easing.SpringOut.Ease (val)) < epsilon);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/EditorTests.cs b/Xamarin.Forms.Core.UnitTests/EditorTests.cs
new file mode 100644
index 00000000..c16f1d2a
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/EditorTests.cs
@@ -0,0 +1,34 @@
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class EditorTests : BaseTestFixture
+ {
+ [TestCase ("Hi", "My text has changed")]
+ [TestCase (null, "My text has changed")]
+ [TestCase ("Hi", null)]
+ public void EditorTextChangedEventArgs (string initialText, string finalText)
+ {
+ var editor = new Editor {
+ Text = initialText
+ };
+
+ Editor editorFromSender = null;
+ string oldText = null;
+ string newText = null;
+
+ editor.TextChanged += (s, e) => {
+ editorFromSender = (Editor)s;
+ oldText = e.OldTextValue;
+ newText = e.NewTextValue;
+ };
+
+ editor.Text = finalText;
+
+ Assert.AreEqual (editor, editorFromSender);
+ Assert.AreEqual (initialText, oldText);
+ Assert.AreEqual (finalText, newText);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/EffectTests.cs b/Xamarin.Forms.Core.UnitTests/EffectTests.cs
new file mode 100644
index 00000000..66399bc8
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/EffectTests.cs
@@ -0,0 +1,121 @@
+using System;
+using NUnit.Framework;
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class EffectTests : BaseTestFixture
+ {
+ [Test]
+ public void ResolveSetsId ()
+ {
+ string id = "Unknown";
+ var effect = Effect.Resolve (id);
+ Assert.AreEqual (id, effect.ResolveId);
+ }
+
+ [Test]
+ public void UnknownIdReturnsNullEffect ()
+ {
+ var effect = Effect.Resolve ("Foo");
+ Assert.IsInstanceOf<NullEffect> (effect);
+ }
+
+ [Test]
+ public void SendAttachedSetsFlag ()
+ {
+ var effect = Effect.Resolve ("Foo");
+ effect.SendAttached ();
+ Assert.True (effect.IsAttached);
+ }
+
+ [Test]
+ public void SendDetachedUnsetsFlag ()
+ {
+ var effect = Effect.Resolve ("Foo");
+ effect.SendAttached ();
+ effect.SendDetached ();
+ Assert.False (effect.IsAttached);
+ }
+
+ [Test]
+ public void EffectLifecyclePreProvider ()
+ {
+ var effect = new CustomEffect ();
+ var element = new Label ();
+
+ element.Effects.Add (effect);
+ ((IVisualElementController)element).EffectControlProvider = new EffectControlProvider ();
+
+ Assert.True (effect.IsAttached);
+ Assert.True (effect.OnAttachedCalled);
+ Assert.True (effect.Registered);
+ Assert.False (effect.OnDetachedCalled);
+
+ element.Effects.Remove (effect);
+ Assert.True (effect.OnDetachedCalled);
+ }
+
+ [Test]
+ public void EffectLifecyclePostProvider ()
+ {
+ var effect = new CustomEffect ();
+ var element = new Label ();
+
+ ((IVisualElementController)element).EffectControlProvider = new EffectControlProvider ();
+ element.Effects.Add (effect);
+
+ Assert.True (effect.IsAttached);
+ Assert.True (effect.OnAttachedCalled);
+ Assert.True (effect.Registered);
+ Assert.False (effect.OnDetachedCalled);
+
+ element.Effects.Remove (effect);
+ Assert.True (effect.OnDetachedCalled);
+ }
+
+ [Test]
+ public void EffectsClearDetachesEffect ()
+ {
+ var effect = new CustomEffect ();
+ var element = new Label ();
+
+ ((IVisualElementController)element).EffectControlProvider = new EffectControlProvider ();
+ element.Effects.Add (effect);
+
+ element.Effects.Clear ();
+
+ Assert.True (effect.OnDetachedCalled);
+ }
+
+ class EffectControlProvider : IEffectControlProvider
+ {
+ public void RegisterEffect (Effect effect)
+ {
+ var e = effect as CustomEffect;
+ if (e != null)
+ e.Registered = true;
+ }
+ }
+
+ class CustomEffect : Effect
+ {
+ public bool OnAttachedCalled;
+ public bool OnDetachedCalled;
+ public bool Registered;
+
+ protected override void OnAttached ()
+ {
+ OnAttachedCalled = true;
+ }
+
+ protected override void OnDetached ()
+ {
+ OnDetachedCalled = true;
+ }
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/ElementTests.cs b/Xamarin.Forms.Core.UnitTests/ElementTests.cs
new file mode 100644
index 00000000..6bffe338
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/ElementTests.cs
@@ -0,0 +1,176 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ public class TestElement
+ : Element
+ {
+ public TestElement ()
+ {
+ internalChildren.CollectionChanged += OnChildrenChanged;
+ }
+
+ void OnChildrenChanged (object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if (e.NewItems != null) {
+ foreach (Element element in e.NewItems)
+ OnChildAdded (element);
+ }
+
+ if (e.OldItems != null) {
+ foreach (Element element in e.OldItems)
+ OnChildRemoved (element);
+ }
+ }
+
+ public IList<Element> Children
+ {
+ get { return internalChildren; }
+ }
+
+ internal override ReadOnlyCollection<Element> LogicalChildren
+ {
+ get { return new ReadOnlyCollection<Element> (internalChildren); }
+ }
+
+ readonly ObservableCollection<Element> internalChildren = new ObservableCollection<Element> ();
+ }
+
+ [TestFixture]
+ public class ElementTests
+ : BaseTestFixture
+ {
+ [Test]
+ public void DescendantAddedLevel1 ()
+ {
+ var root = new TestElement();
+
+ var child = new TestElement();
+
+ bool added = false;
+ root.DescendantAdded += (sender, args) => {
+ Assert.That (args.Element, Is.SameAs (child));
+ added = true;
+ };
+
+ root.Children.Add (child);
+ }
+
+ [Test]
+ public void DescendantAddedLevel2 ()
+ {
+ var root = new TestElement();
+
+ var child = new TestElement();
+ root.Children.Add (child);
+
+ var child2 = new TestElement();
+
+ bool added = false;
+ root.DescendantAdded += (sender, args) => {
+ Assert.That (args.Element, Is.SameAs (child2));
+ added = true;
+ };
+
+ child.Children.Add (child2);
+ }
+
+ [Test]
+ public void DescendantAddedExistingChildren ()
+ {
+ var root = new TestElement();
+
+ var tier2 = new TestElement();
+
+ var child = new TestElement {
+ Children = {
+ tier2
+ }
+ };
+
+ bool tier1added = false;
+ bool tier2added = false;
+ root.DescendantAdded += (sender, args) => {
+ if (!tier1added)
+ tier1added = ReferenceEquals (child, args.Element);
+ if (!tier2added)
+ tier2added = ReferenceEquals (tier2, args.Element);
+ };
+
+ root.Children.Add (child);
+
+ Assert.That (tier1added, Is.True);
+ Assert.That (tier2added, Is.True);
+ }
+
+ [Test]
+ public void DescendantRemovedLevel1 ()
+ {
+ var root = new TestElement();
+
+ var child = new TestElement();
+ root.Children.Add (child);
+
+ bool removed = false;
+ root.DescendantRemoved += (sender, args) => {
+ Assert.That (args.Element, Is.SameAs (child));
+ removed = true;
+ };
+
+ root.Children.Remove (child);
+ }
+
+ [Test]
+ public void DescendantRemovedLevel2 ()
+ {
+ var root = new TestElement();
+
+ var child = new TestElement();
+ root.Children.Add (child);
+
+ var child2 = new TestElement();
+ child.Children.Add (child2);
+
+ bool removed = false;
+ root.DescendantRemoved += (sender, args) => {
+ Assert.That (args.Element, Is.SameAs (child2));
+ removed = true;
+ };
+
+ child.Children.Remove (child2);
+ }
+
+ [Test]
+ public void DescendantRemovedWithChildren ()
+ {
+ var root = new TestElement();
+
+ var tier2 = new TestElement();
+
+ var child = new TestElement {
+ Children = {
+ tier2
+ }
+ };
+
+ root.Children.Add (child);
+
+ bool tier1removed = false;
+ bool tier2removed = false;
+ root.DescendantRemoved += (sender, args) => {
+ if (!tier1removed)
+ tier1removed = ReferenceEquals (child, args.Element);
+ if (!tier2removed)
+ tier2removed = ReferenceEquals (tier2, args.Element);
+ };
+
+ root.Children.Remove (child);
+
+ Assert.That (tier1removed, Is.True);
+ Assert.That (tier2removed, Is.True);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/EntryCellTests.cs b/Xamarin.Forms.Core.UnitTests/EntryCellTests.cs
new file mode 100644
index 00000000..34b7b663
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/EntryCellTests.cs
@@ -0,0 +1,82 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class EntryCellTests : BaseTestFixture
+ {
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [Test]
+ public void ChangingHorizontalTextAlignmentFiresXAlignChanged ()
+ {
+ var entryCell = new EntryCell { HorizontalTextAlignment = TextAlignment.Center };
+
+ var xAlignFired = false;
+ var horizontalTextAlignmentFired = false;
+
+ entryCell.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "XAlign") {
+ xAlignFired = true;
+ } else if (args.PropertyName == EntryCell.HorizontalTextAlignmentProperty.PropertyName) {
+ horizontalTextAlignmentFired = true;
+ }
+ };
+
+ entryCell.HorizontalTextAlignment = TextAlignment.End;
+
+ Assert.True(xAlignFired);
+ Assert.True(horizontalTextAlignmentFired);
+ }
+
+ [Test]
+ public void EntryCellXAlignBindingMatchesHorizontalTextAlignmentBinding ()
+ {
+ var vm = new ViewModel ();
+ vm.Alignment = TextAlignment.Center;
+
+ var entryCellXAlign = new EntryCell () { BindingContext = vm };
+ entryCellXAlign.SetBinding (EntryCell.XAlignProperty, new Binding ("Alignment"));
+
+ var entryCellHorizontalTextAlignment = new EntryCell () { BindingContext = vm };
+ entryCellHorizontalTextAlignment.SetBinding (EntryCell.HorizontalTextAlignmentProperty, new Binding ("Alignment"));
+
+ Assert.AreEqual (TextAlignment.Center, entryCellXAlign.XAlign);
+ Assert.AreEqual (TextAlignment.Center, entryCellHorizontalTextAlignment.HorizontalTextAlignment);
+
+ vm.Alignment = TextAlignment.End;
+
+ Assert.AreEqual (TextAlignment.End, entryCellXAlign.XAlign);
+ Assert.AreEqual (TextAlignment.End, entryCellHorizontalTextAlignment.HorizontalTextAlignment);
+ }
+
+ sealed class ViewModel : INotifyPropertyChanged
+ {
+ TextAlignment alignment;
+
+ public TextAlignment Alignment
+ {
+ get { return alignment; }
+ set
+ {
+ alignment = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ void OnPropertyChanged ([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/EntryUnitTests.cs b/Xamarin.Forms.Core.UnitTests/EntryUnitTests.cs
new file mode 100644
index 00000000..83f71b9a
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/EntryUnitTests.cs
@@ -0,0 +1,55 @@
+using System.Diagnostics;
+
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class EntryUnitTests : BaseTestFixture
+ {
+ [Test]
+ public void ValueChangedFromSetValue()
+ {
+ var entry = new Entry();
+
+ const string value = "Foo";
+
+ bool signaled = false;
+ entry.TextChanged += (sender, args) => {
+ signaled = true;
+ Assert.AreEqual (value, args.NewTextValue);
+ };
+
+ entry.SetValue (Entry.TextProperty, value);
+
+ Assert.IsTrue (signaled, "ValueChanged did not fire");
+ }
+
+ [TestCase (null, "foo")]
+ [TestCase ("foo", "bar")]
+ [TestCase ("foo", null)]
+ public void ValueChangedArgs (string initial, string final)
+ {
+ var entry = new Entry {
+ Text = initial
+ };
+
+ string oldValue = null;
+ string newValue = null;
+
+ Entry entryFromSender = null;
+
+ entry.TextChanged += (s, e) => {
+ entryFromSender = (Entry)s;
+ oldValue = e.OldTextValue;
+ newValue = e.NewTextValue;
+ };
+
+ entry.Text = final;
+
+ Assert.AreEqual (entry, entryFromSender);
+ Assert.AreEqual (initial, oldValue);
+ Assert.AreEqual (final, newValue);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/EventTriggerTest.cs b/Xamarin.Forms.Core.UnitTests/EventTriggerTest.cs
new file mode 100644
index 00000000..89034a03
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/EventTriggerTest.cs
@@ -0,0 +1,73 @@
+using System;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ internal class MockTriggerAction : TriggerAction<BindableObject>
+ {
+ public bool Invoked { get; set;}
+
+ protected override void Invoke (BindableObject sender)
+ {
+ Invoked = true;
+ }
+ }
+
+ internal class MockBindableWithEvent : VisualElement
+ {
+ public void FireEvent ()
+ {
+ if (MockEvent != null)
+ MockEvent (this, EventArgs.Empty);
+ }
+
+ public void FireEvent2 ()
+ {
+ if (MockEvent2 != null)
+ MockEvent2 (this, EventArgs.Empty);
+ }
+
+ public event EventHandler MockEvent;
+ public event EventHandler MockEvent2;
+ }
+
+ [TestFixture]
+ public class EventTriggerTest : BaseTestFixture
+ {
+ [Test]
+ public void TestTriggerActionInvoked ()
+ {
+ var bindable = new MockBindableWithEvent ();
+ var triggeraction = new MockTriggerAction ();
+ var eventtrigger = new EventTrigger () { Event = "MockEvent", Actions = { triggeraction } };
+ var collection = bindable.Triggers;
+ collection.Add (eventtrigger);
+
+ Assert.False (triggeraction.Invoked);
+ bindable.FireEvent ();
+ Assert.True (triggeraction.Invoked);
+ }
+
+ [Test]
+ public void TestChangeEventOnEventTrigger ()
+ {
+ var bindable = new MockBindableWithEvent ();
+ var triggeraction = new MockTriggerAction ();
+ var eventtrigger = new EventTrigger { Event = "MockEvent", Actions = { triggeraction } };
+ var collection = bindable.Triggers;
+ collection.Add (eventtrigger);
+
+ triggeraction.Invoked = false;
+ Assert.False (triggeraction.Invoked);
+ bindable.FireEvent ();
+ Assert.True (triggeraction.Invoked);
+
+ triggeraction.Invoked = false;
+ Assert.False (triggeraction.Invoked);
+ bindable.FireEvent2 ();
+ Assert.False (triggeraction.Invoked);
+
+ Assert.Throws<InvalidOperationException>(()=> eventtrigger.Event = "MockEvent2");
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/FluentTests.cs b/Xamarin.Forms.Core.UnitTests/FluentTests.cs
new file mode 100644
index 00000000..b8780adb
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/FluentTests.cs
@@ -0,0 +1,8 @@
+using System.Linq;
+using NUnit.Framework;
+
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+
+}
diff --git a/Xamarin.Forms.Core.UnitTests/FontUnitTests.cs b/Xamarin.Forms.Core.UnitTests/FontUnitTests.cs
new file mode 100644
index 00000000..dcf972f9
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/FontUnitTests.cs
@@ -0,0 +1,129 @@
+using System.Globalization;
+
+using NUnit.Framework;
+
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class FontUnitTests : BaseTestFixture
+ {
+ [Test]
+ public void TestFontForSize ()
+ {
+ var font = Font.OfSize ("Foo", 12);
+ Assert.AreEqual ("Foo", font.FontFamily);
+ Assert.AreEqual (12, font.FontSize);
+ Assert.AreEqual ((NamedSize)0, font.NamedSize);
+ }
+
+ [Test]
+ public void TestFontForSizeDouble ()
+ {
+ var font = Font.OfSize ("Foo", 12.7);
+ Assert.AreEqual ("Foo", font.FontFamily);
+ Assert.AreEqual (12.7, font.FontSize);
+ Assert.AreEqual ((NamedSize)0, font.NamedSize);
+ }
+
+ [Test]
+ public void TestFontForNamedSize ()
+ {
+ var font = Font.OfSize ("Foo", NamedSize.Large);
+ Assert.AreEqual ("Foo", font.FontFamily);
+ Assert.AreEqual (0, font.FontSize);
+ Assert.AreEqual (NamedSize.Large, font.NamedSize);
+ }
+
+ [Test]
+ public void TestSystemFontOfSize ()
+ {
+ var font = Font.SystemFontOfSize (12);
+ Assert.AreEqual (null, font.FontFamily);
+ Assert.AreEqual (12, font.FontSize);
+ Assert.AreEqual ((NamedSize)0, font.NamedSize);
+
+ font = Font.SystemFontOfSize (NamedSize.Medium);
+ Assert.AreEqual (null, font.FontFamily);
+ Assert.AreEqual (0, font.FontSize);
+ Assert.AreEqual (NamedSize.Medium, font.NamedSize);
+ }
+
+ [Test]
+ public void CultureTestSystemFontOfSizeDouble ()
+ {
+ var font = Font.SystemFontOfSize (12.7);
+ Assert.AreEqual (null, font.FontFamily);
+ Assert.AreEqual (12.7, font.FontSize);
+ Assert.AreEqual ((NamedSize)0, font.NamedSize);
+
+ font = Font.SystemFontOfSize (NamedSize.Medium);
+ Assert.AreEqual (null, font.FontFamily);
+ Assert.AreEqual (0, font.FontSize);
+ Assert.AreEqual (NamedSize.Medium, font.NamedSize);
+ }
+
+ [Test]
+ public void TestEquality ()
+ {
+ var font1 = Font.SystemFontOfSize (12);
+ var font2 = Font.SystemFontOfSize (12);
+
+ Assert.True (font1 == font2);
+ Assert.False (font1 != font2);
+
+ font2 = Font.SystemFontOfSize (13);
+
+ Assert.False (font1 == font2);
+ Assert.True (font1 != font2);
+ }
+
+ [Test]
+ public void TestHashCode ()
+ {
+ var font1 = Font.SystemFontOfSize (12);
+ var font2 = Font.SystemFontOfSize (12);
+
+ Assert.True (font1.GetHashCode () == font2.GetHashCode ());
+
+ font2 = Font.SystemFontOfSize (13);
+
+ Assert.False (font1.GetHashCode () == font2.GetHashCode ());
+ }
+
+ [Test]
+ public void TestEquals ()
+ {
+ var font = Font.SystemFontOfSize (12);
+
+ Assert.False (font.Equals (null));
+ Assert.True (font.Equals (font));
+ Assert.False (font.Equals ("Font"));
+ Assert.True (font.Equals (Font.SystemFontOfSize (12)));
+ }
+
+ [Test]
+ public void TestFontConverter ()
+ {
+ var converter = new FontTypeConverter ();
+ Assert.True (converter.CanConvertFrom (typeof(string)));
+ Assert.AreEqual (Font.SystemFontOfSize (NamedSize.Medium), converter.ConvertFromInvariantString ("Medium"));
+ Assert.AreEqual (Font.SystemFontOfSize (42), converter.ConvertFromInvariantString ("42"));
+ Assert.AreEqual (Font.OfSize ("Foo", NamedSize.Micro), converter.ConvertFromInvariantString ("Foo, Micro"));
+ Assert.AreEqual (Font.OfSize ("Foo", 42), converter.ConvertFromInvariantString ("Foo, 42"));
+ Assert.AreEqual (Font.OfSize ("Foo", 12.7), converter.ConvertFromInvariantString ("Foo, 12.7"));
+ Assert.AreEqual (Font.SystemFontOfSize (NamedSize.Large, FontAttributes.Bold), converter.ConvertFromInvariantString ("Bold, Large"));
+ Assert.AreEqual (Font.SystemFontOfSize (42, FontAttributes.Bold), converter.ConvertFromInvariantString ("Bold, 42"));
+ Assert.AreEqual (Font.OfSize ("Foo", NamedSize.Medium), converter.ConvertFromInvariantString ("Foo"));
+ Assert.AreEqual (Font.OfSize ("Foo", NamedSize.Large).WithAttributes (FontAttributes.Bold), converter.ConvertFromInvariantString ("Foo, Bold, Large"));
+ Assert.AreEqual (Font.OfSize ("Foo", NamedSize.Large).WithAttributes (FontAttributes.Italic), converter.ConvertFromInvariantString ("Foo, Italic, Large"));
+ Assert.AreEqual (Font.OfSize ("Foo", NamedSize.Large).WithAttributes (FontAttributes.Bold | FontAttributes.Italic), converter.ConvertFromInvariantString ("Foo, Bold, Italic, Large"));
+ Assert.AreEqual (Font.OfSize ("Foo", 12).WithAttributes (FontAttributes.Bold), converter.ConvertFromInvariantString ("Foo, Bold, 12"));
+ Assert.AreEqual (Font.OfSize ("Foo", 12.7).WithAttributes (FontAttributes.Bold), converter.ConvertFromInvariantString ("Foo, Bold, 12.7"));
+ Assert.AreEqual (Font.OfSize ("Foo", 12).WithAttributes (FontAttributes.Italic), converter.ConvertFromInvariantString ("Foo, Italic, 12"));
+ Assert.AreEqual (Font.OfSize ("Foo", 12.7).WithAttributes (FontAttributes.Italic), converter.ConvertFromInvariantString ("Foo, Italic, 12.7"));
+ Assert.AreEqual (Font.OfSize ("Foo", 12).WithAttributes (FontAttributes.Bold | FontAttributes.Italic), converter.ConvertFromInvariantString ("Foo, Bold, Italic, 12"));
+ Assert.AreEqual (Font.OfSize ("Foo", 12.7).WithAttributes (FontAttributes.Bold | FontAttributes.Italic), converter.ConvertFromInvariantString ("Foo, Bold, Italic, 12.7"));
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/FormattedStringTests.cs b/Xamarin.Forms.Core.UnitTests/FormattedStringTests.cs
new file mode 100644
index 00000000..7c247bda
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/FormattedStringTests.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.ObjectModel;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class FormattedStringTests : BaseTestFixture
+ {
+ [SetUp]
+ public override void Setup ()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [TearDown]
+ public override void TearDown ()
+ {
+ base.Setup ();
+ Device.PlatformServices = null;
+ }
+
+ [Test]
+ public void NullSpansNotAllowed()
+ {
+ var fs = new FormattedString();
+ Assert.That (() => fs.Spans.Add (null), Throws.InstanceOf<ArgumentNullException>());
+
+ fs = new FormattedString();
+ fs.Spans.Add (new Span());
+
+ Assert.That (() => {
+ fs.Spans[0] = null;
+ }, Throws.InstanceOf<ArgumentNullException>());
+ }
+
+ [Test]
+ public void SpanChangeTriggersSpansPropertyChange()
+ {
+ var span = new Span();
+ var fs = new FormattedString();
+ fs.Spans.Add (span);
+
+ bool spansChanged = false;
+ fs.PropertyChanged += (s, e) => {
+ if (e.PropertyName == "Spans")
+ spansChanged = true;
+ };
+
+ span.Text = "New text";
+
+ Assert.That (spansChanged, Is.True);
+ }
+
+ [Test]
+ public void SpanChangesUnsubscribes()
+ {
+ var span = new Span();
+ var fs = new FormattedString();
+ fs.Spans.Add (span);
+ fs.Spans.Remove (span);
+
+ bool spansChanged = false;
+ fs.PropertyChanged += (s, e) => {
+ if (e.PropertyName == "Spans")
+ spansChanged = true;
+ };
+
+ span.Text = "New text";
+
+ Assert.That (spansChanged, Is.False);
+ }
+
+ [Test]
+ public void AddingSpanTriggersSpansPropertyChange()
+ {
+ var span = new Span();
+ var fs = new FormattedString();
+
+ bool spansChanged = false;
+ fs.PropertyChanged += (s, e) => {
+ if (e.PropertyName == "Spans")
+ spansChanged = true;
+ };
+
+ fs.Spans.Add (span);
+
+ Assert.That (spansChanged, Is.True);
+ }
+
+ [Test]
+ public void ImplicitStringConversion()
+ {
+ string original = "fubar";
+ FormattedString fs = original;
+ Assert.That (fs, Is.Not.Null);
+ Assert.That (fs.Spans.Count, Is.EqualTo (1));
+ Assert.That (fs.Spans[0], Is.Not.Null);
+ Assert.That (fs.Spans[0].Text, Is.EqualTo (original));
+ }
+
+ [Test]
+ public void ImplicitStringConversionNull()
+ {
+ string original = null;
+ FormattedString fs = original;
+ Assert.That (fs, Is.Not.Null);
+ Assert.That (fs.Spans.Count, Is.EqualTo (1));
+ Assert.That (fs.Spans[0], Is.Not.Null);
+ Assert.That (fs.Spans[0].Text, Is.EqualTo (original));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/FrameUnitTests.cs b/Xamarin.Forms.Core.UnitTests/FrameUnitTests.cs
new file mode 100644
index 00000000..e7bf5974
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/FrameUnitTests.cs
@@ -0,0 +1,291 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class FrameUnitTests : BaseTestFixture
+ {
+ [Test]
+ public void TestConstructor ()
+ {
+ Frame frame = new Frame ();
+
+ Assert.Null (frame.Content);
+ Assert.AreEqual (new Thickness (20, 20, 20, 20), frame.Padding);
+ }
+
+ [Test]
+ public void TestPackWithoutChild ()
+ {
+ Frame frame = new Frame ();
+
+ var parent = new NaiveLayout ();
+
+ bool thrown = false;
+ try {
+ parent.Children.Add (frame);
+ } catch {
+ thrown = true;
+ }
+
+ Assert.False (thrown);
+ }
+
+ [Test]
+ public void TestPackWithChild ()
+ {
+ Frame frame = new Frame {
+ Content = new View ()
+ };
+
+ var parent = new NaiveLayout ();
+
+ bool thrown = false;
+ try {
+ parent.Children.Add (frame);
+ } catch {
+ thrown = true;
+ }
+
+ Assert.False (thrown);
+ }
+
+ [Test]
+ public void TestSetChild ()
+ {
+ Frame frame = new Frame ();
+
+ var child1 = new Label ();
+
+ bool added = false;
+
+ frame.ChildAdded += (sender, e) => added = true;
+
+ frame.Content = child1;
+
+ Assert.True (added);
+ Assert.AreEqual (child1, frame.Content);
+
+ added = false;
+ frame.Content = child1;
+
+ Assert.False (added);
+ }
+
+ [Test]
+ public void TestReplaceChild ()
+ {
+ Frame frame = new Frame ();
+
+ var child1 = new Label ();
+ var child2 = new Label ();
+
+ frame.Content = child1;
+
+ bool removed = false;
+ bool added = false;
+
+ frame.ChildRemoved += (sender, e) => removed = true;
+ frame.ChildAdded += (sender, e) => added = true;
+
+ frame.Content = child2;
+
+ Assert.True (removed);
+ Assert.True (added);
+ Assert.AreEqual (child2, frame.Content);
+ }
+
+ [Test]
+ public void TestFrameLayout ()
+ {
+ View child;
+
+ var frame = new Frame {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 200,
+ IsPlatformEnabled = true
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ Assert.AreEqual (new Size (140, 240), frame.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity).Request);
+
+ frame.Layout (new Rectangle (0, 0, 300, 300));
+
+ Assert.AreEqual (new Rectangle (20, 20, 260, 260), child.Bounds);
+ }
+
+ [Test]
+ public void TestDoesNotThrowOnSetNullChild ()
+ {
+ Assert.DoesNotThrow (() => new Frame {Content = null});
+ }
+
+ [Test]
+ public void WidthRequest ()
+ {
+ var frame = new Frame {
+ Content = new View {
+ WidthRequest = 100,
+ HeightRequest = 200,
+ IsPlatformEnabled = true
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform (),
+ WidthRequest = 20
+ };
+
+ Assert.AreEqual (new Size (60, 240), frame.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity).Request);
+ }
+
+ [Test]
+ public void HeightRequest ()
+ {
+ var frame = new Frame {
+ Content = new View {
+ WidthRequest = 100,
+ HeightRequest = 200,
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform (),
+ HeightRequest = 20
+ };
+
+ Assert.AreEqual (new Size (140, 60), frame.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity).Request);
+ }
+
+ [Test]
+ public void LayoutVerticallyCenter ()
+ {
+ View child;
+
+ var frame = new Frame {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 100,
+ IsPlatformEnabled = true,
+ VerticalOptions = LayoutOptions.Center
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ frame.Layout (new Rectangle (0, 0, 200, 200));
+
+ Assert.AreEqual (new Rectangle (20, 50, 160, 100), child.Bounds);
+ }
+
+ [Test]
+ public void LayoutVerticallyBegin()
+ {
+ View child;
+
+ var frame = new Frame {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 100,
+ IsPlatformEnabled = true,
+ VerticalOptions = LayoutOptions.Start
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ frame.Layout (new Rectangle (0, 0, 200, 200));
+
+ Assert.AreEqual (new Rectangle (20, 20, 160, 100), child.Bounds);
+ }
+
+ [Test]
+ public void LayoutVerticallyEnd()
+ {
+ View child;
+
+ var frame = new Frame {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 100,
+ IsPlatformEnabled = true,
+ VerticalOptions = LayoutOptions.End
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ frame.Layout (new Rectangle (0, 0, 200, 200));
+
+ Assert.AreEqual (new Rectangle (20, 80, 160, 100), child.Bounds);
+ }
+
+ [Test]
+ public void LayoutHorizontallyCenter()
+ {
+ View child;
+
+ var frame = new Frame {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 100,
+ IsPlatformEnabled = true,
+ HorizontalOptions = LayoutOptions.Center
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ frame.Layout (new Rectangle (0, 0, 200, 200));
+
+ Assert.AreEqual (new Rectangle (50, 20, 100, 160), child.Bounds);
+ }
+
+ [Test]
+ public void LayoutHorizontallyBegin()
+ {
+ View child;
+
+ var frame = new Frame {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 100,
+ IsPlatformEnabled = true,
+ HorizontalOptions = LayoutOptions.Start
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ frame.Layout (new Rectangle (0, 0, 200, 200));
+
+ Assert.AreEqual (new Rectangle (20, 20, 100, 160), child.Bounds);
+ }
+
+ [Test]
+ public void LayoutHorizontallyEnd()
+ {
+ View child;
+
+ var frame = new Frame {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 100,
+ IsPlatformEnabled = true,
+ HorizontalOptions = LayoutOptions.End
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ frame.Layout (new Rectangle (0, 0, 200, 200));
+
+ Assert.AreEqual (new Rectangle (80, 20, 100, 160), child.Bounds);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/GeocoderUnitTests.cs b/Xamarin.Forms.Core.UnitTests/GeocoderUnitTests.cs
new file mode 100644
index 00000000..414c49de
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/GeocoderUnitTests.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using Xamarin.Forms.Maps;
+
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class GeocoderUnitTests : BaseTestFixture
+ {
+ [Test]
+ public async void AddressesForPosition ()
+ {
+ Geocoder.GetAddressesForPositionFuncAsync = GetAddressesForPositionFuncAsync;
+ var geocoder = new Geocoder ();
+ var result = await geocoder.GetAddressesForPositionAsync (new Position (1, 2));
+ Assert.AreEqual (new String[] { "abc", "def" }, result);
+ }
+
+ async Task<IEnumerable<string>> GetAddressesForPositionFuncAsync (Position position)
+ {
+ Assert.AreEqual (new Position (1, 2), position);
+ return new string[] {"abc", "def"};
+ }
+
+ [Test]
+ public async void PositionsForAddress () {
+ Geocoder.GetPositionsForAddressAsyncFunc = GetPositionsForAddressAsyncFunc ;
+ var geocoder = new Geocoder ();
+ var result = await geocoder.GetPositionsForAddressAsync ("quux");
+ Assert.AreEqual (new Position [] { new Position (1, 2), new Position (3, 4) }, result);
+ }
+
+ async Task<IEnumerable<Position>> GetPositionsForAddressAsyncFunc (string address)
+ {
+ Assert.AreEqual ("quux", address);
+ return new Position[] {new Position (1, 2), new Position (3, 4)};
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/GridLengthTypeConverterTests.cs b/Xamarin.Forms.Core.UnitTests/GridLengthTypeConverterTests.cs
new file mode 100644
index 00000000..4c1f21f7
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/GridLengthTypeConverterTests.cs
@@ -0,0 +1,53 @@
+using System;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class GridLengthTypeConverterTests : BaseTestFixture
+ {
+ [Test]
+ public void TestAbsolute ()
+ {
+ var converter = new GridLengthTypeConverter ();
+
+ Assert.AreEqual (new GridLength (42), converter.ConvertFromInvariantString ("42"));
+ Assert.AreEqual (new GridLength (42.2), converter.ConvertFromInvariantString ("42.2"));
+
+ Assert.Throws<FormatException> (() => converter.ConvertFromInvariantString ("foo"));
+ }
+
+ [Test]
+ public void TestAuto ()
+ {
+ var converter = new GridLengthTypeConverter ();
+
+ Assert.AreEqual (GridLength.Auto, converter.ConvertFromInvariantString ("auto"));
+ Assert.AreEqual (GridLength.Auto, converter.ConvertFromInvariantString (" AuTo "));
+ }
+
+ [Test]
+ public void TestStar ()
+ {
+ var converter = new GridLengthTypeConverter ();
+
+ Assert.AreEqual (new GridLength (1, GridUnitType.Star), converter.ConvertFromInvariantString ("*"));
+ Assert.AreEqual (new GridLength (42, GridUnitType.Star), converter.ConvertFromInvariantString ("42*"));
+
+ }
+
+ [Test]
+ public void TestValue ()
+ {
+ var converter = new GridLengthTypeConverter ();
+ Assert.AreEqual (new GridLength(3.3), converter.ConvertFromInvariantString("3.3"));
+ }
+
+ [Test]
+ public void TestValueStar ()
+ {
+ var converter = new GridLengthTypeConverter ();
+ Assert.AreEqual (new GridLength(32.3, GridUnitType.Star), converter.ConvertFromInvariantString("32.3*"));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/GridTests.cs b/Xamarin.Forms.Core.UnitTests/GridTests.cs
new file mode 100644
index 00000000..203d3b64
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/GridTests.cs
@@ -0,0 +1,1569 @@
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class GridTests : BaseTestFixture
+ {
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ }
+
+ [Test]
+ public void ThrowsOnNullAdd ()
+ {
+ var layout = new Grid ();
+
+ Assert.Throws<ArgumentNullException> (() => layout.Children.Add (null));
+ }
+
+ [Test]
+ public void ThrowsOnNullRemove ()
+ {
+ var layout = new Grid ();
+
+ Assert.Throws<ArgumentNullException> (() => layout.Children.Remove (null));
+ }
+
+ [Test]
+ public void TestBasicVerticalLayout ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label3 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.Children.AddVertical (new View[] {
+ label1,
+ label2,
+ label3
+ });
+
+ layout.Layout (new Rectangle (0, 0, 912, 912));
+
+ Assert.AreEqual (912, layout.Width);
+ Assert.AreEqual (912, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 912, 300), label1.Bounds);
+ Assert.AreEqual (new Rectangle (0, 306, 912, 300), label2.Bounds);
+ Assert.AreEqual (new Rectangle (0, 612, 912, 300), label3.Bounds);
+ }
+
+ [Test]
+ public void TestBasicHorizontalLayout ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label3 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.Children.AddHorizontal (new View[] {
+ label1,
+ label2,
+ label3
+ });
+
+ layout.Layout (new Rectangle (0, 0, 912, 912));
+
+ Assert.AreEqual (912, layout.Width);
+ Assert.AreEqual (912, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 300, 912), label1.Bounds);
+ Assert.AreEqual (new Rectangle (306, 0, 300, 912), label2.Bounds);
+ Assert.AreEqual (new Rectangle (612, 0, 300, 912), label3.Bounds);
+ }
+
+ [Test]
+ public void TestVerticalExpandStart ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition { Height = new GridLength (1, GridUnitType.Star) },
+ new RowDefinition { Height = GridLength.Auto},
+ };
+ layout.Children.Add (label1, 0, 0);
+ layout.Children.Add (label2, 0, 1);
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (1000, layout.Width);
+ Assert.AreEqual (1000, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 1000, 1000 - 20 - layout.RowSpacing), label1.Bounds);
+ Assert.AreEqual (new Rectangle (0, 1000 - 20, 1000, 20), label2.Bounds);
+ }
+
+ [Test]
+ public void TestHorizontalExpandStart ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label { Platform = platform, IsPlatformEnabled = true };
+ var label2 = new Label { Platform = platform, IsPlatformEnabled = true };
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition { Width = new GridLength (1, GridUnitType.Star) },
+ new ColumnDefinition { Width = GridLength.Auto },
+ };
+ layout.Children.Add (label1, 0, 0);
+ layout.Children.Add (label2, 1, 0);
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (1000, layout.Width);
+ Assert.AreEqual (1000, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 1000 - 106, 1000), label1.Bounds);
+ Assert.AreEqual (new Rectangle (1000 - 100, 0, 100, 1000), label2.Bounds);
+ }
+
+ [Test]
+ public void TestVerticalExpandEnd ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition { Height = GridLength.Auto},
+ new RowDefinition { Height = new GridLength (1, GridUnitType.Star) },
+ };
+ layout.Children.Add (label1, 0, 0);
+ layout.Children.Add (label2, 0, 1);
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (1000, layout.Width);
+ Assert.AreEqual (1000, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 1000, 20), label1.Bounds);
+ Assert.AreEqual (new Rectangle (0, 26, 1000, 1000 - 26), label2.Bounds);
+ }
+
+ [Test]
+ public void TestHorizontalExpandEnd ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label { Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition { Width = GridLength.Auto },
+ new ColumnDefinition { Width = new GridLength (1, GridUnitType.Star) },
+ };
+
+ layout.Children.Add (label1, 0, 0);
+ layout.Children.Add (label2, 1, 0);
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (1000, layout.Width);
+ Assert.AreEqual (1000, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 100, 1000), label1.Bounds);
+ Assert.AreEqual (new Rectangle (106, 0, 1000 - 106, 1000), label2.Bounds);
+ }
+
+ [Test]
+ public void TestVerticalExpandMiddle ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label3 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition { Height = GridLength.Auto},
+ new RowDefinition { Height = new GridLength (1, GridUnitType.Star) },
+ new RowDefinition { Height = GridLength.Auto}
+ };
+ layout.Children.Add (label1, 0, 0);
+ layout.Children.Add (label2, 0, 1);
+ layout.Children.Add (label3, 0, 2);
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (1000, layout.Width);
+ Assert.AreEqual (1000, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 1000, 20), label1.Bounds);
+ Assert.AreEqual (new Rectangle (0, 26, 1000, 1000 - 52), label2.Bounds);
+ Assert.AreEqual (new Rectangle (0, 980, 1000, 20), label3.Bounds);
+ }
+
+ [Test]
+ public void TestHorizontalExpandMiddle ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label3 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition { Width = GridLength.Auto },
+ new ColumnDefinition { Width = new GridLength (1, GridUnitType.Star) },
+ new ColumnDefinition { Width = GridLength.Auto },
+ };
+
+ layout.Children.Add (label1, 0, 0);
+ layout.Children.Add (label2, 1, 0);
+ layout.Children.Add (label3, 2, 0);
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (1000, layout.Width);
+ Assert.AreEqual (1000, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 100, 1000), label1.Bounds);
+ Assert.AreEqual (new Rectangle (106, 0, 1000 - 212, 1000), label2.Bounds);
+ Assert.AreEqual (new Rectangle (900, 0, 100, 1000), label3.Bounds);
+ }
+
+ [Test]
+ public void TestTableNoExpand ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label3 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label4 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.Children.Add (label1, 0, 0);
+ layout.Children.Add (label2, 1, 0);
+ layout.Children.Add (label3, 0, 1);
+ layout.Children.Add (label4, 1, 1);
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition { Width = GridLength.Auto },
+ new ColumnDefinition { Width = GridLength.Auto },
+ };
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition { Height = GridLength.Auto},
+ new RowDefinition { Height = GridLength.Auto}
+ };
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (1000, layout.Width);
+ Assert.AreEqual (1000, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 100, 20), label1.Bounds);
+ Assert.AreEqual (new Rectangle (106, 0, 100, 20), label2.Bounds);
+ Assert.AreEqual (new Rectangle (0, 26, 100, 20), label3.Bounds);
+ Assert.AreEqual (new Rectangle (106, 26, 100, 20), label4.Bounds);
+ }
+
+ [Test]
+ public void TestTableExpand ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label3 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label4 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition { Width = GridLength.Auto },
+ new ColumnDefinition { Width = new GridLength (1, GridUnitType.Star) },
+ };
+
+ layout.Children.Add (label1, 0, 0);
+ layout.Children.Add (label2, 1, 0);
+ layout.Children.Add (label3, 0, 1);
+ layout.Children.Add (label4, 1, 1);
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (1000, layout.Width);
+ Assert.AreEqual (1000, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 100, 497), label1.Bounds);
+ Assert.AreEqual (new Rectangle (106, 0, 894, 497), label2.Bounds);
+ Assert.AreEqual (new Rectangle (0, 503, 100, 497), label3.Bounds);
+ Assert.AreEqual (new Rectangle (106, 503, 894, 497), label4.Bounds);
+ }
+
+ [Test]
+ public void TestTableSpan ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label3 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.Children.Add (label1, 0, 2, 0, 1);
+ layout.Children.Add (label2, 0, 1, 1, 2);
+ layout.Children.Add (label3, 1, 2, 1, 2);
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition { Width = GridLength.Auto },
+ new ColumnDefinition { Width = GridLength.Auto },
+ };
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition { Height = GridLength.Auto},
+ new RowDefinition { Height = GridLength.Auto}
+ };
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (1000, layout.Width);
+ Assert.AreEqual (1000, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 206, 20), label1.Bounds);
+ Assert.AreEqual (new Rectangle (0, 26, 100, 20), label2.Bounds);
+ Assert.AreEqual (new Rectangle (106, 26, 100, 20), label3.Bounds);
+ }
+
+ [Test]
+ public void TestTableExpandedSpan ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label3 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition { Width = new GridLength (1, GridUnitType.Star) },
+ new ColumnDefinition { Width = new GridLength (1, GridUnitType.Star) },
+ };
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition { Height = GridLength.Auto},
+ new RowDefinition { Height = GridLength.Auto}
+ };
+
+ layout.Children.Add (label1, 0, 2, 0, 1);
+ layout.Children.Add (label2, 0, 1, 1, 2);
+ layout.Children.Add (label3, 1, 2, 1, 2);
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (1000, layout.Width);
+ Assert.AreEqual (1000, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 1000, 20), label1.Bounds);
+ Assert.AreEqual (new Rectangle (0, 26, 497, 20), label2.Bounds);
+ Assert.AreEqual (new Rectangle (503, 26, 497, 20), label3.Bounds);
+ }
+
+ [Test]
+ public void TestInvalidSet ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ bool thrown = false;
+
+ try {
+ layout.Children.Add (label1, 2, 1, 0, 1);
+ } catch (ArgumentOutOfRangeException) {
+ thrown = true;
+ }
+
+ Assert.True (thrown);
+ }
+
+ [Test]
+ public void TestCentering ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true, HorizontalOptions = LayoutOptions.Center, VerticalOptions = LayoutOptions.Center };
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition () {Width = new GridLength (1, GridUnitType.Star)},
+ };
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition () {Height = new GridLength (1,GridUnitType.Star)},
+ };
+
+ layout.Children.Add (label1);
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (new Rectangle (450, 490, 100, 20), label1.Bounds);
+ }
+
+ [Test]
+ public void TestStart ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label { Platform = platform, IsPlatformEnabled = true, HorizontalOptions = LayoutOptions.Start, VerticalOptions = LayoutOptions.StartAndExpand };
+
+ layout.Children.AddVertical (label1);
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition () {Width = new GridLength (1, GridUnitType.Star)},
+ };
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition () {Height = new GridLength (1,GridUnitType.Star)},
+ };
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (new Rectangle (0, 0, 100, 20), label1.Bounds);
+ }
+
+ [Test]
+ public void TestEnd ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label { Platform = platform, IsPlatformEnabled = true, HorizontalOptions = LayoutOptions.End, VerticalOptions = LayoutOptions.EndAndExpand };
+
+ layout.Children.AddVertical (label1);
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition () {Width = new GridLength (1, GridUnitType.Star)},
+ };
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition () {Height = new GridLength (1,GridUnitType.Star)},
+ };
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (new Rectangle (900, 980, 100, 20), label1.Bounds);
+ }
+
+ [Test]
+ public void TestDefaultRowSpacing ()
+ {
+ var layout = new Grid ();
+
+ bool preferredSizeChanged = false;
+ layout.MeasureInvalidated += (sender, args) => {
+ preferredSizeChanged = true;
+ };
+
+ layout.RowSpacing = layout.RowSpacing;
+
+ Assert.False (preferredSizeChanged);
+
+ layout.RowSpacing = 10;
+
+ Assert.True (preferredSizeChanged);
+ }
+
+ [Test]
+ public void TestDefaultColumnSpacing ()
+ {
+ var layout = new Grid ();
+
+ bool preferredSizeChanged = false;
+ layout.MeasureInvalidated += (sender, args) => {
+ preferredSizeChanged = true;
+ };
+
+ layout.ColumnSpacing = layout.ColumnSpacing;
+
+ Assert.False (preferredSizeChanged);
+
+ layout.ColumnSpacing = 10;
+
+ Assert.True (preferredSizeChanged);
+ }
+
+ [Test]
+ public void TestAddCell ()
+ {
+ var layout = new Grid ();
+ bool preferredSizeChanged = false;
+ layout.MeasureInvalidated += (sender, args) => preferredSizeChanged = true;
+
+ Assert.False (preferredSizeChanged);
+
+ layout.Children.Add (new Label (), 0, 0);
+
+ Assert.True (preferredSizeChanged);
+ }
+
+ [Test]
+ public void TestMoveCell ()
+ {
+ var layout = new Grid ();
+ var label = new Label ();
+ layout.Children.Add (label, 0, 0);
+
+ bool preferredSizeChanged = false;
+ layout.MeasureInvalidated += (sender, args) => {
+ preferredSizeChanged = true;
+ };
+
+ Assert.False (preferredSizeChanged);
+ Grid.SetRow (label, 2);
+ Assert.True (preferredSizeChanged);
+
+ preferredSizeChanged = false;
+ Assert.False (preferredSizeChanged);
+ Grid.SetColumn (label, 2);
+ Assert.True (preferredSizeChanged);
+
+ preferredSizeChanged = false;
+ Assert.False (preferredSizeChanged);
+ Grid.SetRowSpan (label, 2);
+ Assert.True (preferredSizeChanged);
+
+ preferredSizeChanged = false;
+ Assert.False (preferredSizeChanged);
+ Grid.SetColumnSpan (label, 2);
+ Assert.True (preferredSizeChanged);
+ }
+
+ [Test]
+ public void TestInvalidBottomAdd ()
+ {
+ var layout = new Grid ();
+
+ Assert.Throws<ArgumentOutOfRangeException> (() => layout.Children.Add (new View (), 0, 1, 1, 0));
+ }
+
+ [Test]
+ public void TestZeroSizeConstraints ()
+ {
+ var layout = new Grid {Platform = new UnitPlatform ()};
+
+ Assert.AreEqual (new Size (0, 0), layout.GetSizeRequest (0, 0).Request);
+ Assert.AreEqual (new Size (0, 0), layout.GetSizeRequest (0, 10).Request);
+ Assert.AreEqual (new Size (0, 0), layout.GetSizeRequest (10, 0).Request);
+ }
+
+ [Test]
+ public void TestSizeRequest ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid {Platform = platform, IsPlatformEnabled = true};
+ layout.Children.AddVertical (new[] {
+ new View {Platform = platform, IsPlatformEnabled = true},
+ new View {Platform = platform, IsPlatformEnabled = true},
+ new View {Platform = platform, IsPlatformEnabled = true}
+ });
+
+ var result = layout.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity).Request;
+ Assert.AreEqual (new Size (100, 72), result);
+ }
+
+ [Test]
+ public void TestLimitedSizeRequest ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid {Platform = platform, IsPlatformEnabled = true};
+ layout.Children.AddVertical (new[] {
+ new View {Platform = platform, IsPlatformEnabled = true},
+ new View {Platform = platform, IsPlatformEnabled = true},
+ new View {Platform = platform, IsPlatformEnabled = true}
+ });
+
+ var result = layout.GetSizeRequest (10, 10).Request;
+ Assert.AreEqual (new Size (100, 72), result);
+ }
+
+ [Test]
+ public void TestLimitedWidthSizeRequest ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid {Platform = platform, IsPlatformEnabled = true};
+ layout.Children.AddVertical (new[] {
+ new View {Platform = platform, IsPlatformEnabled = true},
+ new View {Platform = platform, IsPlatformEnabled = true},
+ new View {Platform = platform, IsPlatformEnabled = true}
+ });
+
+ var result = layout.GetSizeRequest (10, double.PositiveInfinity).Request;
+ Assert.AreEqual (new Size (100, 72), result);
+ }
+
+ [Test]
+ public void TestLimitedHeightSizeRequest ()
+ {
+
+ var platform = new UnitPlatform ();
+ var layout = new Grid {Platform = platform, IsPlatformEnabled = true};
+ layout.Children.AddVertical (new[] {
+ new View {Platform = platform, IsPlatformEnabled = true},
+ new View {Platform = platform, IsPlatformEnabled = true},
+ new View {Platform = platform, IsPlatformEnabled = true}
+ });
+
+ var result = layout.GetSizeRequest (double.PositiveInfinity, 10).Request;
+ Assert.AreEqual (new Size (100, 72), result);
+ }
+
+ [Test]
+ public void IgnoresInvisibleChildren ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid {Platform = platform};
+
+ var label1 = new Label { Platform = platform, IsVisible = false, IsPlatformEnabled = true, VerticalOptions = LayoutOptions.FillAndExpand };
+ var label2 = new Label { Platform = platform, IsPlatformEnabled = true};
+
+ layout.Children.AddVertical (label1);
+ layout.Children.AddVertical (label2);
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition { Width = GridLength.Auto },
+ };
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition { Height = GridLength.Auto},
+ new RowDefinition { Height = GridLength.Auto},
+ };
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (1000, layout.Width);
+ Assert.AreEqual (1000, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, -1, -1), label1.Bounds);
+ Assert.AreEqual (new Rectangle (0, 6, 100, 20), label2.Bounds);
+ }
+
+ [Test]
+ public void TestSizeRequestWithPadding ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid {Platform = platform, IsPlatformEnabled = true, Padding = new Thickness(20, 10, 15, 5)};
+ layout.Children.AddVertical (new[] {
+ new View {Platform = platform, IsPlatformEnabled = true},
+ new View {Platform = platform, IsPlatformEnabled = true},
+ new View {Platform = platform, IsPlatformEnabled = true}
+ });
+
+ var result = layout.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity).Request;
+ Assert.AreEqual (new Size (135, 87), result);
+ }
+
+ [Test]
+ public void InvalidCallsToStaticMethods ()
+ {
+ Assert.Throws<ArgumentException> (() => Grid.SetRow (new Label (), -1));
+ Assert.Throws<ArgumentException> (() => Grid.SetColumn (new Label (), -1));
+ Assert.Throws<ArgumentException> (() => Grid.SetRowSpan (new Label (), 0));
+ Assert.Throws<ArgumentException> (() => Grid.SetColumnSpan (new Label (), 0));
+ }
+
+ [Test]
+ public void TestAddedBP ()
+ {
+ var platform = new UnitPlatform ();
+
+ var labela0 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var labela1 = new Label { Platform = platform, IsPlatformEnabled = true };
+ Grid.SetColumn (labela1, 1);
+ var labelb1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ Grid.SetRow (labelb1, 1);
+ Grid.SetColumn (labelb1, 1);
+ var labelc = new Label {Platform = platform, IsPlatformEnabled = true};
+ Grid.SetRow (labelc, 2);
+ Grid.SetColumnSpan (labelc, 2);
+
+ var layout = new Grid {
+ Platform = platform,
+ Children = {
+ labela0,
+ labela1,
+ labelb1,
+ labelc
+ }
+ };
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition { Width = GridLength.Auto },
+ new ColumnDefinition { Width = GridLength.Auto },
+ };
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition { Height = GridLength.Auto},
+ new RowDefinition { Height = GridLength.Auto},
+ new RowDefinition { Height = GridLength.Auto},
+ };
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (1000, layout.Width);
+ Assert.AreEqual (1000, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 100, 20), labela0.Bounds);
+ Assert.AreEqual (new Rectangle (106, 0, 100, 20), labela1.Bounds);
+ Assert.AreEqual (new Rectangle (106, 26, 100, 20), labelb1.Bounds);
+ Assert.AreEqual (new Rectangle (0, 52, 206, 20), labelc.Bounds);
+ }
+
+ [Test]
+ public void Remove ()
+ {
+ var platform = new UnitPlatform();
+
+ var labela0 = new Label { Platform = platform, IsPlatformEnabled = true };
+ var labela1 = new Label { Platform = platform, IsPlatformEnabled = true };
+ Grid.SetColumn(labela1, 1);
+ var labelb1 = new Label { Platform = platform, IsPlatformEnabled = true };
+ Grid.SetRow(labelb1, 1);
+ Grid.SetColumn(labelb1, 1);
+ var labelc = new Label { Platform = platform, IsPlatformEnabled = true };
+ Grid.SetRow(labelc, 2);
+ Grid.SetColumnSpan(labelc, 2);
+
+ var layout = new Grid {
+ Platform = platform,
+ Children = {
+ labela0,
+ labela1,
+ labelb1,
+ labelc
+ }
+ };
+
+ layout.Children.Remove (labela0);
+ Assert.False (layout.LogicalChildren.Contains (labela0));
+ }
+
+ [Test]
+ public void TestAbsoluteLayout ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label3 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition {Width = new GridLength (150)},
+ new ColumnDefinition {Width = new GridLength (150)},
+ new ColumnDefinition {Width = new GridLength (150)},
+ };
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition {Height = new GridLength (30)},
+ new RowDefinition {Height = new GridLength (30)},
+ new RowDefinition {Height = new GridLength (30)},
+ };
+ layout.Children.Add (label1, 0, 0);
+ layout.Children.Add (label2, 1, 1);
+ layout.Children.Add (label3, 2, 2);
+
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (1000, layout.Width);
+ Assert.AreEqual (1000, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 150, 30), label1.Bounds);
+ Assert.AreEqual (new Rectangle (156, 36, 150, 30), label2.Bounds);
+ Assert.AreEqual (new Rectangle (312, 72, 150, 30), label3.Bounds);
+ }
+
+ [Test]
+ public void TestAbsoluteLayoutWithSpans ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label3 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition {Width = new GridLength (150)},
+ new ColumnDefinition {Width = new GridLength (150)},
+ new ColumnDefinition {Width = new GridLength (150)},
+ };
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition {Height = new GridLength (30)},
+ new RowDefinition {Height = new GridLength (30)},
+ new RowDefinition {Height = new GridLength (30)},
+ };
+ layout.Children.Add (label1, 0, 2, 0, 1);
+ layout.Children.Add (label2, 2, 3, 0, 2);
+ layout.Children.Add (label3, 1, 2);
+
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (1000, layout.Width);
+ Assert.AreEqual (1000, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 306, 30), label1.Bounds);
+ Assert.AreEqual (new Rectangle (312, 0, 150, 66), label2.Bounds);
+ Assert.AreEqual (new Rectangle (156, 72, 150, 30), label3.Bounds);
+ }
+
+ [Test]
+ public void TestStarLayout ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label3 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition {Width = new GridLength (1, GridUnitType.Star)},
+ new ColumnDefinition {Width = new GridLength (1, GridUnitType.Star)},
+ new ColumnDefinition {Width = new GridLength (1, GridUnitType.Star)},
+ };
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition {Height = new GridLength (1, GridUnitType.Star)},
+ new RowDefinition {Height = new GridLength (1, GridUnitType.Star)},
+ new RowDefinition {Height = new GridLength (1, GridUnitType.Star)},
+ };
+ layout.Children.Add (label1, 0, 0);
+ layout.Children.Add (label2, 1, 1);
+ layout.Children.Add (label3, 2, 2);
+
+ var request = layout.GetSizeRequest (1002, 462);
+ Assert.AreEqual (312, request.Request.Width);
+ Assert.AreEqual (72, request.Request.Height);
+
+ layout.Layout (new Rectangle (0, 0, 1002, 462));
+ Assert.AreEqual (1002, layout.Width);
+ Assert.AreEqual (462, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 330, 150), label1.Bounds);
+ Assert.AreEqual (new Rectangle (336, 156, 330, 150), label2.Bounds);
+ Assert.AreEqual (new Rectangle (672, 312, 330, 150), label3.Bounds);
+ }
+
+ [Test]
+ public void TestStarLayoutWithSpans ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label3 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition {Width = new GridLength (1, GridUnitType.Star)},
+ new ColumnDefinition {Width = new GridLength (1, GridUnitType.Star)},
+ new ColumnDefinition {Width = new GridLength (1, GridUnitType.Star)},
+ };
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition {Height = new GridLength (1, GridUnitType.Star)},
+ new RowDefinition {Height = new GridLength (1, GridUnitType.Star)},
+ new RowDefinition {Height = new GridLength (1, GridUnitType.Star)},
+ };
+ layout.Children.Add (label1, 0, 2, 0, 1);
+ layout.Children.Add (label2, 2, 3, 0, 2);
+ layout.Children.Add (label3, 1, 2);
+
+ layout.Layout (new Rectangle (0, 0, 1002, 462));
+
+ Assert.AreEqual (1002, layout.Width);
+ Assert.AreEqual (462, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 666, 150), label1.Bounds);
+ Assert.AreEqual (new Rectangle (672, 0, 330, 306), label2.Bounds);
+ Assert.AreEqual (new Rectangle (336, 312, 330, 150), label3.Bounds);
+ }
+
+ [Test]
+ public void TestAutoLayout ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label3 = new Label {Platform = platform, IsPlatformEnabled = true};
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition {Width = GridLength.Auto},
+ new ColumnDefinition {Width = GridLength.Auto},
+ new ColumnDefinition {Width = GridLength.Auto},
+ };
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition {Height = GridLength.Auto},
+ new RowDefinition {Height = GridLength.Auto},
+ new RowDefinition {Height = GridLength.Auto},
+ };
+ layout.Children.Add (label1, 0, 0);
+ layout.Children.Add (label2, 1, 1);
+ layout.Children.Add (label3, 2, 2);
+
+
+ layout.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (1000, layout.Width);
+ Assert.AreEqual (1000, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 100, 20), label1.Bounds);
+ Assert.AreEqual (new Rectangle (106, 26, 100, 20), label2.Bounds);
+ Assert.AreEqual (new Rectangle (212, 52, 100, 20), label3.Bounds);
+ }
+
+ [Test]
+ public void TestAutoLayoutWithSpans ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label { Platform = platform, IsPlatformEnabled = true, WidthRequest = 150, Text = "label1" };
+ var label2 = new Label { Platform = platform, IsPlatformEnabled = true, HeightRequest = 50, Text = "label2" };
+ var label3 = new Label { Platform = platform, IsPlatformEnabled = true, Text = "label3" };
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition {Width = GridLength.Auto},
+ new ColumnDefinition {Width = GridLength.Auto},
+ new ColumnDefinition {Width = GridLength.Auto},
+ };
+ layout.RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition {Height = GridLength.Auto},
+ new RowDefinition {Height = GridLength.Auto},
+ new RowDefinition {Height = GridLength.Auto},
+ };
+ layout.Children.Add (label1, 0, 2, 0, 1);
+ layout.Children.Add (label2, 2, 3, 0, 2);
+ layout.Children.Add (label3, 1, 2);
+
+ layout.Layout (new Rectangle (0, 0, 1002, 462));
+
+ Assert.AreEqual (1002, layout.Width);
+ Assert.AreEqual (462, layout.Height);
+
+ Assert.AreEqual (new Rectangle (0, 0, 150, 20), label1.Bounds);
+ Assert.AreEqual (new Rectangle (156, 0, 100, 50), label2.Bounds);
+ Assert.AreEqual (new Rectangle (50, 56, 100, 20), label3.Bounds);
+ }
+
+ [Test]
+ public void AutoLayoutWithComplexSpans ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label3 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label4 = new Label {Platform = platform, IsPlatformEnabled = true, WidthRequest = 206};
+ var label5 = new Label {Platform = platform, IsPlatformEnabled = true, WidthRequest = 312};
+ var label6 = new Label {Platform = platform, IsPlatformEnabled = true, WidthRequest = 312};
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition {Width = GridLength.Auto},
+ new ColumnDefinition {Width = GridLength.Auto},
+ new ColumnDefinition {Width = GridLength.Auto},
+ new ColumnDefinition {Width = GridLength.Auto},
+ new ColumnDefinition {Width = GridLength.Auto},
+ };
+
+ layout.Children.Add (label1, 0, 0);
+ layout.Children.Add (label2, 1, 0);
+ layout.Children.Add (label3, 4, 0);
+ layout.Children.Add (label4, 2, 4, 0, 1);
+ layout.Children.Add (label5, 0, 3, 0, 1);
+ layout.Children.Add (label6, 2, 6, 0, 1);
+
+ layout.Layout (new Rectangle (0, 0, 1000, 500));
+
+ Assert.AreEqual (100, layout.ColumnDefinitions [0].ActualWidth);
+ Assert.AreEqual (100, layout.ColumnDefinitions [1].ActualWidth);
+ Assert.AreEqual (100, layout.ColumnDefinitions [2].ActualWidth);
+ Assert.AreEqual (100, layout.ColumnDefinitions [3].ActualWidth);
+ Assert.AreEqual (100, layout.ColumnDefinitions [4].ActualWidth);
+ }
+
+ [Test]
+ public void AutoLayoutExpandColumns ()
+ {
+ var platform = new UnitPlatform ();
+ var layout = new Grid ();
+ layout.Platform = platform;
+
+ var label1 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label2 = new Label {Platform = platform, IsPlatformEnabled = true};
+ var label3 = new Label {Platform = platform, IsPlatformEnabled = true, WidthRequest = 300};
+
+ layout.ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition { Width = GridLength.Auto },
+ new ColumnDefinition { Width = GridLength.Auto },
+ };
+
+ layout.Children.Add (label1, 0, 0);
+ layout.Children.Add (label2, 1, 0);
+ layout.Children.Add (label3, 0, 2, 0, 1);
+
+ layout.Layout (new Rectangle (0, 0, 1000, 500));
+
+ Assert.AreEqual (100, layout.ColumnDefinitions [0].ActualWidth);
+ Assert.AreEqual (194, layout.ColumnDefinitions [1].ActualWidth);
+ }
+
+ [Test]
+ public void GridHasDefaultDefinitions ()
+ {
+ var grid = new Grid ();
+ Assert.NotNull (grid.ColumnDefinitions);
+ Assert.NotNull (grid.RowDefinitions);
+ }
+
+ [Test]
+ public void DefaultDefinitionsArentSharedAccrossInstances ()
+ {
+ var grid0 = new Grid ();
+ var coldefs = grid0.ColumnDefinitions;
+ var rowdefs = grid0.RowDefinitions;
+
+ var grid1 = new Grid ();
+ Assert.AreNotSame (grid0, grid1);
+ Assert.AreNotSame (coldefs, grid1.ColumnDefinitions);
+ Assert.AreNotSame (rowdefs, grid1.RowDefinitions);
+ }
+
+ [Test]
+ public void ChildrenLayoutRespectAlignment ()
+ {
+ var platform = new UnitPlatform ();
+ var grid = new Grid {
+ ColumnDefinitions = { new ColumnDefinition { Width = new GridLength (300) } },
+ RowDefinitions = { new RowDefinition { Height = new GridLength (100) } },
+ Platform = platform,
+ };
+ var label = new Label {
+ Platform = platform,
+ IsPlatformEnabled = true,
+ VerticalOptions = LayoutOptions.Center,
+ HorizontalOptions = LayoutOptions.End,
+ };
+
+ grid.Children.Add (label);
+ grid.Layout (new Rectangle (0, 0, 500, 500));
+
+ Assert.AreEqual (new Rectangle (200, 40, 100, 20), label.Bounds);
+ }
+
+ [Test]
+ public void BothChildrenPropertiesUseTheSameBackendStore ()
+ {
+ var view = new View ();
+ var grid = new Grid ();
+ Assert.AreEqual (0, grid.Children.Count);
+ (grid as Layout<View>).Children.Add (view);
+ Assert.AreEqual (1, grid.Children.Count);
+ Assert.AreEqual (1, (grid as Layout<View>).Children.Count);
+ Assert.AreSame (view, (grid as Layout<View>).Children.First ());
+ Assert.AreSame (view, grid.Children.First ());
+ }
+
+ [Test]
+ //Issue 1384
+ public void ImageInAutoCellIsProperlyConstrained ()
+ {
+ var platform = new UnitPlatform ();
+
+ var content = new Image {
+ Aspect= Aspect.AspectFit,
+ Platform = platform,
+ IsPlatformEnabled = true
+ };
+ var grid = new Grid {
+ Platform = platform,
+ IsPlatformEnabled = true,
+ BackgroundColor = Color.Red,
+ VerticalOptions=LayoutOptions.Start,
+ Children = {
+ content
+ },
+ RowDefinitions = { new RowDefinition { Height = GridLength.Auto} },
+ ColumnDefinitions = { new ColumnDefinition { Width = GridLength.Auto } }
+ };
+ var view = new ContentView {
+ Platform = platform,
+ IsPlatformEnabled = true,
+ Content = grid,
+ };
+ view.Layout (new Rectangle (0, 0, 100, 100));
+ Assert.AreEqual (100, grid.Width);
+ Assert.AreEqual (20, grid.Height);
+
+ view.Layout (new Rectangle (0, 0, 50, 50));
+ Assert.AreEqual (50, grid.Width);
+ Assert.AreEqual (10, grid.Height);
+ }
+
+ [Test]
+ //Issue 1384
+ public void ImageInStarCellIsProperlyConstrained ()
+ {
+ var platform = new UnitPlatform ();
+
+ var content = new Image {
+ Aspect= Aspect.AspectFit,
+ Platform = platform,
+ MinimumHeightRequest = 10,
+ MinimumWidthRequest = 50,
+ IsPlatformEnabled = true
+ };
+ var grid = new Grid {
+ Platform = platform,
+ IsPlatformEnabled = true,
+ BackgroundColor = Color.Red,
+ VerticalOptions=LayoutOptions.Start,
+ Children = {
+ content
+ }
+ };
+ var view = new ContentView {
+ Platform = platform,
+ IsPlatformEnabled = true,
+ Content = grid,
+ };
+ view.Layout (new Rectangle (0, 0, 100, 100));
+ Assert.AreEqual (100, grid.Width);
+ Assert.AreEqual (20, grid.Height);
+
+ view.Layout (new Rectangle (0, 0, 50, 50));
+ Assert.AreEqual (50, grid.Width);
+ Assert.AreEqual (10, grid.Height);
+ }
+
+ [Test]
+ public void SizeRequestForStar ()
+ {
+ var platform = new UnitPlatform ();
+
+ var grid = new Grid{
+ RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition {Height = new GridLength (1, GridUnitType.Star)},
+ new RowDefinition {Height = GridLength.Auto},
+ },
+ ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition {Width = new GridLength (1, GridUnitType.Star)},
+ new ColumnDefinition {Width = GridLength.Auto},
+ }
+ };
+ grid.Children.Add (new Label {BackgroundColor = Color.Lime, Text="Foo", Platform = platform, IsPlatformEnabled = true});
+ grid.Children.Add (new Label {Text = "Bar", Platform = platform, IsPlatformEnabled = true},0,1);
+ grid.Children.Add (new Label {Text="Baz",XAlign = TextAlignment.End, Platform = platform, IsPlatformEnabled = true},1,0);
+ grid.Children.Add (new Label {Text="Qux", XAlign = TextAlignment.End, Platform = platform, IsPlatformEnabled = true},1,1);
+
+ var request = grid.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity);
+ Assert.AreEqual (206, request.Request.Width);
+ Assert.AreEqual (46, request.Request.Height);
+
+ Assert.AreEqual (106, request.Minimum.Width);
+ Assert.AreEqual (26, request.Minimum.Height);
+ //
+ }
+
+ [Test]
+ //Issue 1497
+ public void StarRowsShouldOccupyTheSpace ()
+ {
+ var platform = new UnitPlatform ();
+ var label = new Label {
+ Platform = platform,
+ IsPlatformEnabled = true,
+ };
+ var Button = new Button {
+ HorizontalOptions = LayoutOptions.FillAndExpand,
+ VerticalOptions = LayoutOptions.EndAndExpand,
+ Platform = platform,
+ IsPlatformEnabled = true,
+ };
+ var grid = new Grid {
+ RowDefinitions = new RowDefinitionCollection {
+ new RowDefinition { Height = GridLength.Auto },
+ new RowDefinition { Height = new GridLength (1, GridUnitType.Star) },
+ },
+ ColumnDefinitions = new ColumnDefinitionCollection {
+ new ColumnDefinition {Width = new GridLength(1, GridUnitType.Star)},
+ },
+ Platform = platform,
+ IsPlatformEnabled = true,
+ };
+
+ grid.Children.Add (label);
+ grid.Children.Add (Button, 0, 1);
+
+ grid.Layout (new Rectangle (0, 0, 300, 300));
+ Assert.AreEqual (new Rectangle (0, 280, 300, 20), Button.Bounds);
+ }
+
+ [Test]
+ public void StarColumnsWithSpansDoNotExpandAutos ()
+ {
+ var grid = new Grid {
+ RowDefinitions = {
+ new RowDefinition {Height = GridLength.Auto},
+ new RowDefinition {Height = GridLength.Auto},
+ },
+ ColumnDefinitions = {
+ new ColumnDefinition {Width = new GridLength (1, GridUnitType.Auto)},
+ new ColumnDefinition {Width = new GridLength (1, GridUnitType.Auto)},
+ new ColumnDefinition {Width = new GridLength (1, GridUnitType.Star)}
+ },
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ var spanBox = new BoxView {WidthRequest = 70, HeightRequest = 20, IsPlatformEnabled = true};
+ var box1 = new BoxView {WidthRequest = 20, HeightRequest = 20, IsPlatformEnabled = true};
+ var box2 = new BoxView {WidthRequest = 20, HeightRequest = 20, IsPlatformEnabled = true};
+ var box3 = new BoxView {WidthRequest = 20, HeightRequest = 20, IsPlatformEnabled = true};
+
+ grid.Children.Add (spanBox, 0, 3, 0, 1);
+ grid.Children.Add (box1, 0, 1);
+ grid.Children.Add (box2, 1, 1);
+ grid.Children.Add (box3, 2, 1);
+
+ grid.Layout (new Rectangle(0, 0, 300, 46));
+
+ Assert.AreEqual (new Rectangle (0, 0, 300, 20), spanBox.Bounds);
+ Assert.AreEqual (new Rectangle (0, 26, 20, 20), box1.Bounds);
+ Assert.AreEqual (new Rectangle (26, 26, 20, 20), box2.Bounds);
+ Assert.AreEqual (new Rectangle (52, 26, 248, 20), box3.Bounds);
+ }
+
+ static SizeRequest GetResizableSize (VisualElement view, double widthconstraint, double heightconstraint)
+ {
+ if (!(view is Editor))
+ return new SizeRequest(new Size (100, 20));
+ if (widthconstraint < 100)
+ return new SizeRequest(new Size (widthconstraint, 2000/widthconstraint));
+ if (heightconstraint < 20)
+ return new SizeRequest(new Size (2000/heightconstraint, heightconstraint));
+ return new SizeRequest(new Size (100, 20));
+ }
+
+ [Test]
+ //Issue 1893
+ public void EditorSpanningOnMultipleAutoRows ()
+ {
+ var grid0 = new Grid {
+ ColumnDefinitions = {
+ new ColumnDefinition { Width = GridLength.Auto },
+ new ColumnDefinition { Width = new GridLength (1, GridUnitType.Star) },
+ },
+ RowDefinitions = {
+ new RowDefinition { Height = GridLength.Auto },
+ new RowDefinition { Height = GridLength.Auto },
+ },
+ Platform = new UnitPlatform (GetResizableSize),
+ IsPlatformEnabled = true,
+ };
+
+ var label0 = new Label { IsPlatformEnabled = true };
+ var editor0 = new Editor { IsPlatformEnabled = true };
+ grid0.Children.Add (label0, 0, 0);
+ grid0.Children.Add (editor0, 1, 2, 0, 2);
+
+ grid0.Layout (new Rectangle (0, 0, 156, 200));
+ Assert.AreEqual (new Rectangle (106, 0, 50, 40), editor0.Bounds);
+
+ var grid1 = new Grid {
+ ColumnDefinitions = {
+ new ColumnDefinition { Width = GridLength.Auto },
+ new ColumnDefinition { Width = new GridLength (1, GridUnitType.Star) },
+ },
+ RowDefinitions = {
+ new RowDefinition { Height = GridLength.Auto },
+ },
+ Platform = new UnitPlatform (GetResizableSize),
+ IsPlatformEnabled = true,
+ };
+
+ var label1 = new Label { IsPlatformEnabled = true };
+ var editor1 = new Editor { IsPlatformEnabled = true };
+ grid1.Children.Add (label1, 0, 0);
+ grid1.Children.Add (editor1, 1, 0);
+
+ grid1.Layout (new Rectangle (0, 0, 156, 200));
+ Assert.AreEqual (new Rectangle (106, 0, 50, 40), editor1.Bounds);
+ }
+
+ [Test]
+ public void WidthBoundRequestRespected ()
+ {
+ var grid = new Grid {
+ ColumnDefinitions = {
+ new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) },
+ new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }
+ },
+ RowDefinitions = {
+ new RowDefinition { Height = GridLength.Auto },
+ new RowDefinition { Height = GridLength.Auto },
+ },
+ Platform = new UnitPlatform (GetResizableSize),
+ IsPlatformEnabled = true,
+ RowSpacing = 0,
+ ColumnSpacing = 0,
+ };
+
+ var topLabel = new Editor {IsPlatformEnabled = true};
+ var leftLabel = new Label {IsPlatformEnabled = true, WidthRequest = 10};
+ var rightLabel = new Label {IsPlatformEnabled = true, WidthRequest = 10};
+
+ grid.Children.Add (topLabel, 0, 2, 0, 1);
+ grid.Children.Add (leftLabel, 0, 1);
+ grid.Children.Add (rightLabel, 1, 1);
+
+ var unboundRequest = grid.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity);
+ var widthBoundRequest = grid.GetSizeRequest (50, double.PositiveInfinity);
+
+ Assert.AreEqual (new SizeRequest (new Size (20, 120), new Size (0, 120)), unboundRequest);
+ Assert.AreEqual (new SizeRequest (new Size (50, 60), new Size (0, 60)), widthBoundRequest);
+ }
+
+ [Test]
+ //https://bugzilla.xamarin.com/show_bug.cgi?id=31608
+ public void ColAndRowDefinitionsAreActuallyBindable ()
+ {
+ var rowdef = new RowDefinition ();
+ rowdef.SetBinding (RowDefinition.HeightProperty, "Height");
+ var grid = new Grid {
+ RowDefinitions = new RowDefinitionCollection { rowdef },
+ };
+ Assert.AreEqual (RowDefinition.HeightProperty.DefaultValue, rowdef.Height);
+ grid.BindingContext = new {Height = 32};
+ Assert.AreEqual (new GridLength(32), rowdef.Height);
+ }
+
+ [Test]
+ //https://bugzilla.xamarin.com/show_bug.cgi?id=31967
+ public void ChangingRowHeightViaBindingTriggersRedraw ()
+ {
+ var rowdef = new RowDefinition ();
+ rowdef.SetBinding (RowDefinition.HeightProperty, "Height");
+ var grid = new Grid {
+// RowDefinitions = new RowDefinitionCollection {
+// new RowDefinition { Height = GridLength.Auto },
+// rowdef
+// },
+ RowSpacing = 0,
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true,
+ };
+ grid.RowDefinitions.Add (new RowDefinition { Height = GridLength.Auto });
+ grid.RowDefinitions.Add (rowdef);
+
+ var label0 = new Label { IsPlatformEnabled = true };
+ Grid.SetRow (label0, 0);
+ var label1 = new Label { IsPlatformEnabled = true };
+ Grid.SetRow (label1, 1);
+
+ grid.BindingContext = new {Height = 0};
+ grid.Children.Add (label0);
+ grid.Children.Add (label1);
+
+ Assert.AreEqual (new SizeRequest (new Size (100, 20), new Size (0, 20)), grid.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity));
+ grid.BindingContext = new {Height = 42};
+ Assert.AreEqual (new SizeRequest (new Size (100, 62), new Size (0, 62)), grid.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity));
+ }
+
+ [Test]
+ public void InvalidationBlockedForAbsoluteCell ()
+ {
+ var grid = new Grid () {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true,
+ RowDefinitions = {
+ new RowDefinition { Height = new GridLength (100, GridUnitType.Absolute) }
+ },
+ ColumnDefinitions = {
+ new ColumnDefinition { Width = new GridLength (200, GridUnitType.Absolute) }
+ }
+ };
+
+ var label = new Label { IsPlatformEnabled = true };
+ grid.Children.Add (label);
+
+ bool invalidated = false;
+ grid.MeasureInvalidated += (sender, args) => {
+ invalidated = true;
+ };
+
+ label.Text = "Testing";
+
+ Assert.False (invalidated);
+ }
+
+ // because the constraint is internal, we need this
+ public enum HackLayoutConstraint
+ {
+ None = LayoutConstraint.None,
+ VerticallyFixed = LayoutConstraint.VerticallyFixed,
+ HorizontallyFixed = LayoutConstraint.HorizontallyFixed,
+ Fixed = LayoutConstraint.Fixed
+ }
+
+ [TestCase (HackLayoutConstraint.None, GridUnitType.Absolute, GridUnitType.Absolute, ExpectedResult = true)]
+ [TestCase (HackLayoutConstraint.None, GridUnitType.Star, GridUnitType.Absolute, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.None, GridUnitType.Absolute, GridUnitType.Star, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.None, GridUnitType.Auto, GridUnitType.Absolute, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.None, GridUnitType.Absolute, GridUnitType.Auto, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.None, GridUnitType.Star, GridUnitType.Star, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.None, GridUnitType.Auto, GridUnitType.Star, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.None, GridUnitType.Star, GridUnitType.Auto, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.None, GridUnitType.Auto, GridUnitType.Auto, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.VerticallyFixed, GridUnitType.Absolute, GridUnitType.Absolute, ExpectedResult = true)]
+ [TestCase (HackLayoutConstraint.VerticallyFixed, GridUnitType.Star, GridUnitType.Absolute, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.VerticallyFixed, GridUnitType.Absolute, GridUnitType.Star, ExpectedResult = true)]
+ [TestCase (HackLayoutConstraint.VerticallyFixed, GridUnitType.Auto, GridUnitType.Absolute, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.VerticallyFixed, GridUnitType.Absolute, GridUnitType.Auto, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.VerticallyFixed, GridUnitType.Star, GridUnitType.Star, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.VerticallyFixed, GridUnitType.Auto, GridUnitType.Star, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.VerticallyFixed, GridUnitType.Star, GridUnitType.Auto, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.VerticallyFixed, GridUnitType.Auto, GridUnitType.Auto, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.HorizontallyFixed, GridUnitType.Absolute, GridUnitType.Absolute, ExpectedResult = true)]
+ [TestCase (HackLayoutConstraint.HorizontallyFixed, GridUnitType.Star, GridUnitType.Absolute, ExpectedResult = true)]
+ [TestCase (HackLayoutConstraint.HorizontallyFixed, GridUnitType.Absolute, GridUnitType.Star, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.HorizontallyFixed, GridUnitType.Auto, GridUnitType.Absolute, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.HorizontallyFixed, GridUnitType.Absolute, GridUnitType.Auto, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.HorizontallyFixed, GridUnitType.Star, GridUnitType.Star, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.HorizontallyFixed, GridUnitType.Auto, GridUnitType.Star, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.HorizontallyFixed, GridUnitType.Star, GridUnitType.Auto, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.HorizontallyFixed, GridUnitType.Auto, GridUnitType.Auto, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.Fixed, GridUnitType.Absolute, GridUnitType.Absolute, ExpectedResult = true)]
+ [TestCase (HackLayoutConstraint.Fixed, GridUnitType.Star, GridUnitType.Absolute, ExpectedResult = true)]
+ [TestCase (HackLayoutConstraint.Fixed, GridUnitType.Absolute, GridUnitType.Star, ExpectedResult = true)]
+ [TestCase (HackLayoutConstraint.Fixed, GridUnitType.Auto, GridUnitType.Absolute, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.Fixed, GridUnitType.Absolute, GridUnitType.Auto, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.Fixed, GridUnitType.Star, GridUnitType.Star, ExpectedResult = true)]
+ [TestCase (HackLayoutConstraint.Fixed, GridUnitType.Auto, GridUnitType.Star, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.Fixed, GridUnitType.Star, GridUnitType.Auto, ExpectedResult = false)]
+ [TestCase (HackLayoutConstraint.Fixed, GridUnitType.Auto, GridUnitType.Auto, ExpectedResult = false)]
+ public bool InvalidationPropogationTests (HackLayoutConstraint gridConstraint, GridUnitType horizontalType, GridUnitType verticalType)
+ {
+ var grid = new Grid {
+ ComputedConstraint = (LayoutConstraint) gridConstraint,
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true,
+ RowDefinitions = {
+ new RowDefinition { Height = new GridLength (1, verticalType) }
+ },
+ ColumnDefinitions = {
+ new ColumnDefinition { Width = new GridLength (1, horizontalType) }
+ }
+ };
+
+ var label = new Label { IsPlatformEnabled = true };
+ grid.Children.Add (label);
+
+ bool invalidated = false;
+ grid.MeasureInvalidated += (sender, args) => {
+ invalidated = true;
+ };
+
+ label.Text = "Testing";
+
+ return !invalidated;
+ }
+ }
+
+ [TestFixture]
+ public class GridMeasureTests : BaseTestFixture
+ {
+ static List<Action> delayActions = new List<Action> ();
+
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices (invokeOnMainThread: a => { delayActions.Add (a); });
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ }
+
+ [Test]
+ public void NestedInvalidateMeasureDoesNotCrash ()
+ {
+ var grid = new Grid {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true
+ };
+
+ var child = new Label {
+ IsPlatformEnabled = true
+ };
+ grid.Children.Add (child);
+
+ var child2 = new Label {
+ IsPlatformEnabled = true
+ };
+ grid.Children.Add (child2);
+
+ bool fire = true;
+ child.SizeChanged += (sender, args) => {
+ if (fire)
+ child.InvalidateMeasure (InvalidationTrigger.Undefined);
+ fire = false;
+ };
+
+ grid.Layout (new Rectangle (0, 0, 100, 100));
+
+ foreach (var delayAction in delayActions) {
+ delayAction ();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/GroupViewUnitTests.cs b/Xamarin.Forms.Core.UnitTests/GroupViewUnitTests.cs
new file mode 100644
index 00000000..61e38cf1
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/GroupViewUnitTests.cs
@@ -0,0 +1,292 @@
+using System;
+using System.Collections;
+using System.Linq;
+using NUnit.Framework;
+
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ internal class NaiveLayout : Layout<View>
+ {
+ protected override void LayoutChildren (double x, double y, double width, double height)
+ {
+ foreach (var child in LogicalChildren.Cast<View>()) {
+ var result = new Rectangle (x, y, 0, 0);
+ var request = child.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity);
+ result.Width = request.Request.Width;
+ result.Height = request.Request.Height;
+
+ child.Layout (result);
+ }
+ }
+ }
+
+ [TestFixture]
+ public class LayoutUnitTests : BaseTestFixture
+ {
+ [Test]
+ public void TestRaiseChild ()
+ {
+ var view = new NaiveLayout ();
+
+ var child1 = new View ();
+ var child2 = new View ();
+ var child3 = new View ();
+
+ view.Children.Add (child1);
+ view.Children.Add (child2);
+ view.Children.Add (child3);
+
+ bool reordered = false;
+ view.ChildrenReordered += (sender, args) => reordered = true;
+
+ view.RaiseChild (child1);
+
+ Assert.AreEqual (child1, view.LogicalChildren [2]);
+ Assert.True (reordered);
+
+ view.RaiseChild (child2);
+ Assert.AreEqual (child2, view.LogicalChildren [2]);
+ }
+
+ [Test]
+ public void TestRaiseUnownedChild ()
+ {
+ var view = new NaiveLayout ();
+
+ var child1 = new View ();
+ var child2 = new View ();
+ var child3 = new View ();
+
+ view.Children.Add (child1);
+ view.Children.Add (child3);
+
+ bool reordered = false;
+ view.ChildrenReordered += (sender, args) => reordered = true;
+
+ view.RaiseChild (child2);
+
+ Assert.False (reordered);
+ }
+
+ [Test]
+ public void TestLowerChild ()
+ {
+ var view = new NaiveLayout ();
+
+ var child1 = new View ();
+ var child2 = new View ();
+ var child3 = new View ();
+
+ view.Children.Add (child1);
+ view.Children.Add (child2);
+ view.Children.Add (child3);
+
+ bool reordered = false;
+ view.ChildrenReordered += (sender, args) => reordered = true;
+
+ view.LowerChild (child3);
+
+ Assert.AreEqual (child3, view.LogicalChildren [0]);
+ Assert.True (reordered);
+
+ view.LowerChild (child2);
+ Assert.AreEqual (child2, view.LogicalChildren [0]);
+ }
+
+ [Test]
+ public void TestLowerUnownedChild ()
+ {
+ var view = new NaiveLayout ();
+
+ var child1 = new View ();
+ var child2 = new View ();
+ var child3 = new View ();
+
+ view.Children.Add (child1);
+ view.Children.Add (child3);
+
+ bool reordered = false;
+ view.ChildrenReordered += (sender, args) => reordered = true;
+
+ view.LowerChild (child2);
+
+ Assert.False (reordered);
+ }
+
+ [Test]
+ public void TestAdd ()
+ {
+ var view = new NaiveLayout ();
+ var child1 = new View ();
+
+ bool added = false;
+ view.ChildAdded += (sender, args) => added = true;
+
+ view.Children.Add (child1);
+
+ Assert.True (added);
+ Assert.AreEqual (child1, view.LogicalChildren [0]);
+ }
+
+ [Test]
+ public void TestDoubleAdd ()
+ {
+ var view = new NaiveLayout ();
+ var child1 = new View ();
+ view.Children.Add (child1);
+
+ bool added = false;
+ view.ChildAdded += (sender, args) => added = true;
+
+ view.Children.Add (child1);
+
+ Assert.False (added);
+ Assert.AreEqual (child1, view.LogicalChildren [0]);
+ }
+
+ [Test]
+ public void TestRemove ()
+ {
+ var view = new NaiveLayout ();
+ var child1 = new View ();
+
+ view.Children.Add (child1);
+
+ bool removed = false;
+ view.ChildRemoved += (sender, args) => removed = true;
+
+ view.Children.Remove (child1);
+
+ Assert.True (removed);
+ Assert.False (view.LogicalChildren.Any ());
+ }
+
+ [Test]
+ public void TestGenericEnumerator ()
+ {
+ var view = new NaiveLayout ();
+
+ var children = new[] {
+ new View (),
+ new View (),
+ new View ()
+ };
+
+ foreach (var child in children)
+ view.Children.Add (child);
+
+ int i = 0;
+ foreach (var child in view.LogicalChildren) {
+ Assert.AreEqual (children[i], child);
+ i++;
+ }
+ }
+
+ [Test]
+ public void TestEnumerator ()
+ {
+ var view = new NaiveLayout ();
+
+ var children = new [] {
+ new View (),
+ new View (),
+ new View ()
+ };
+
+ foreach (var child in children)
+ view.Children.Add (child);
+
+ int i = 0;
+ var enumerator = (view.LogicalChildren as IEnumerable).GetEnumerator ();
+ while (enumerator.MoveNext ()) {
+ Assert.AreEqual (children [i], enumerator.Current as View);
+ i++;
+ }
+ }
+
+ [Test]
+ public void TestInitializerSyntax ()
+ {
+ View view1, view2;
+ var group = new NaiveLayout {
+ Children = {
+ (view1 = new View ()),
+ (view2 = new View ())
+ }
+ };
+
+ Assert.AreEqual (2, group.LogicalChildren.Count);
+ Assert.IsTrue (group.LogicalChildren.Contains (view1));
+ Assert.IsTrue (group.LogicalChildren.Contains (view2));
+ Assert.AreEqual (view1, group.LogicalChildren[0]);
+ }
+
+ [Test]
+ public void TestChildren ()
+ {
+ View view1, view2;
+ var group = new NaiveLayout {
+ Children = {
+ (view1 = new View ()),
+ (view2 = new View ())
+ }
+ };
+
+ Assert.AreEqual (2, group.Children.Count);
+ Assert.IsTrue (group.Children.Contains (view1));
+ Assert.IsTrue (group.Children.Contains (view2));
+ Assert.AreEqual (view1, group.Children[0]);
+ }
+
+ [Test]
+ public void TestDefaultLayout ()
+ {
+ View view;
+ var group = new NaiveLayout {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true,
+ Children = {
+ (view = new View {
+ WidthRequest = 50,
+ HeightRequest = 20,
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ })
+ }
+ };
+
+ group.Layout (new Rectangle (0, 0, 400, 400));
+
+ Assert.AreEqual (new Rectangle (0, 0, 50, 20), view.Bounds);
+ }
+
+ [Test]
+ public void ThrowsInvalidOperationOnSelfAdd ()
+ {
+ var group = new NaiveLayout ();
+ Assert.Throws<InvalidOperationException> (() => group.Children.Add (group));
+ }
+
+ [Test]
+ public void ReorderChildrenDoesNotRaiseChildAddedOrRemoved ()
+ {
+ var child1 = new BoxView ();
+ var child2 = new BoxView ();
+ var layout = new NaiveLayout {
+ Children = {child1, child2}
+ };
+
+ var added = false;
+ var removed = false;
+
+ layout.ChildAdded += (sender, args) => added = true;
+ layout.ChildRemoved += (sender, args) => removed = true;
+
+ layout.RaiseChild (child1);
+
+ Assert.False (added);
+ Assert.False (removed);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/ImageSourceTests.cs b/Xamarin.Forms.Core.UnitTests/ImageSourceTests.cs
new file mode 100644
index 00000000..95aa5131
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/ImageSourceTests.cs
@@ -0,0 +1,151 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using NUnit.Framework;
+using System.IO;
+using System.Threading.Tasks;
+using System.Threading;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class ImageSourceTests : BaseTestFixture
+ {
+ [SetUp]
+ public override void Setup ()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [Test]
+ public void TestConstructors ()
+ {
+ var filesource = new FileImageSource { File = "File.png" };
+ Assert.AreEqual ("File.png", filesource.File);
+
+ Func<CancellationToken, Task<Stream>> stream = token => new Task<Stream> (() => new FileStream ("Foo", System.IO.FileMode.Open), token);
+ var streamsource = new StreamImageSource { Stream = stream };
+ Assert.AreEqual (stream, streamsource.Stream);
+ }
+
+ [Test]
+ public void TestHelpers ()
+ {
+ var imagesource = ImageSource.FromFile ("File.png");
+ Assert.That (imagesource, Is.TypeOf<FileImageSource> ());
+ Assert.AreEqual ("File.png", ((FileImageSource)imagesource).File);
+
+ Func<Stream> stream = () => new System.IO.FileStream ("Foo", System.IO.FileMode.Open);
+ var streamsource = ImageSource.FromStream (stream);
+ Assert.That (streamsource, Is.TypeOf<StreamImageSource> ());
+
+ var urisource = ImageSource.FromUri (new Uri ("http://xamarin.com/img.png"));
+ Assert.That (urisource, Is.TypeOf<UriImageSource> ());
+ Assert.AreEqual ("http://xamarin.com/img.png", ((UriImageSource)(urisource)).Uri.AbsoluteUri);
+ }
+
+ [Test]
+ public void TestImplicitFileConversion ()
+ {
+ var image = new Image { Source = "File.png" };
+ Assert.IsTrue (image.Source != null);
+ Assert.That (image.Source, Is.InstanceOf<FileImageSource> ());
+ Assert.AreEqual ("File.png", ((FileImageSource)(image.Source)).File);
+ }
+
+ [Test]
+ public void TestImplicitUriConversion ()
+ {
+ var image = new Image { Source = new Uri ("http://xamarin.com/img.png") };
+ Assert.IsTrue (image.Source != null);
+ Assert.That (image.Source, Is.InstanceOf<UriImageSource> ());
+ Assert.AreEqual ("http://xamarin.com/img.png", ((UriImageSource)(image.Source)).Uri.AbsoluteUri);
+ }
+
+ [Test]
+ public void TestImplicitStringUriConversion ()
+ {
+ var image = new Image { Source = "http://xamarin.com/img.png" };
+ Assert.IsTrue (image.Source != null);
+ Assert.That (image.Source, Is.InstanceOf<UriImageSource> ());
+ Assert.AreEqual ("http://xamarin.com/img.png", ((UriImageSource)(image.Source)).Uri.AbsoluteUri);
+ }
+
+ [Test]
+ public void TestSetStringValue ()
+ {
+ var image = new Image ();
+ image.SetValue (Image.SourceProperty, "foo.png");
+ Assert.IsNotNull (image.Source);
+ Assert.That (image.Source, Is.InstanceOf<FileImageSource> ());
+ Assert.AreEqual ("foo.png", ((FileImageSource)(image.Source)).File);
+ }
+
+ [Test]
+ public void TextBindToStringValue ()
+ {
+ var image = new Image ();
+ image.SetBinding (Image.SourceProperty, ".");
+ Assert.IsNull (image.Source);
+ image.BindingContext = "foo.png";
+ Assert.IsNotNull (image.Source);
+ Assert.That (image.Source, Is.InstanceOf<FileImageSource> ());
+ Assert.AreEqual ("foo.png", ((FileImageSource)(image.Source)).File);
+ }
+
+ [Test]
+ public void TextBindToStringUriValue ()
+ {
+ var image = new Image ();
+ image.SetBinding (Image.SourceProperty, ".");
+ Assert.IsNull (image.Source);
+ image.BindingContext = "http://xamarin.com/img.png";
+ Assert.IsNotNull (image.Source);
+ Assert.That (image.Source, Is.InstanceOf<UriImageSource> ());
+ Assert.AreEqual ("http://xamarin.com/img.png", ((UriImageSource)(image.Source)).Uri.AbsoluteUri);
+ }
+
+ [Test]
+ public void TextBindToUriValue ()
+ {
+ var image = new Image ();
+ image.SetBinding (Image.SourceProperty, ".");
+ Assert.IsNull (image.Source);
+ image.BindingContext = new Uri("http://xamarin.com/img.png");
+ Assert.IsNotNull (image.Source);
+ Assert.That (image.Source, Is.InstanceOf<UriImageSource> ());
+ Assert.AreEqual ("http://xamarin.com/img.png", ((UriImageSource)(image.Source)).Uri.AbsoluteUri);
+ }
+
+ class MockImageSource : ImageSource
+ {
+ }
+
+ [Test]
+ public void TestBindingContextPropagation ()
+ {
+ var context = new object ();
+ var image = new Image ();
+ image.BindingContext = context;
+ var source = new MockImageSource ();
+ image.Source = source;
+ Assert.AreSame (context, source.BindingContext);
+
+ image = new Image ();
+ source = new MockImageSource ();
+ image.Source = source;
+ image.BindingContext = context;
+ Assert.AreSame (context, source.BindingContext);
+ }
+
+ [Test]
+ public void ImplicitCastOnAbsolutePathsShouldCreateAFileImageSource ()
+ {
+ var path = "/private/var/mobile/Containers/Data/Application/B1E5AB19-F815-4B4A-AB97-BD4571D53743/Documents/temp/IMG_20140603_150614_preview.jpg";
+ var image = new Image { Source = path };
+ Assert.That (image.Source, Is.TypeOf<FileImageSource> ());
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/ImageTests.cs b/Xamarin.Forms.Core.UnitTests/ImageTests.cs
new file mode 100644
index 00000000..1e2546db
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/ImageTests.cs
@@ -0,0 +1,261 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using NUnit.Framework;
+using System.IO;
+using System.Threading.Tasks;
+using System.Threading;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class ImageTests : BaseTestFixture
+ {
+ [SetUp]
+ public override void Setup ()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices (getStreamAsync: GetStreamAsync);
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ }
+
+ [Test]
+ public void TestSizing ()
+ {
+ var image = new Image {Source = ImageSource.FromFile ("File.png"), Platform = new UnitPlatform (), IsPlatformEnabled = true};
+
+ var result = image.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity);
+
+ Assert.AreEqual (100, result.Request.Width);
+ Assert.AreEqual (20, result.Request.Height);
+ }
+
+ [Test]
+ public void TestAspectSizingWithConstrainedHeight ()
+ {
+ var image = new Image {Source = ImageSource.FromFile ("File.png"), Platform = new UnitPlatform (), IsPlatformEnabled = true};
+
+ var result = image.GetSizeRequest (double.PositiveInfinity, 10);
+
+ Assert.AreEqual (50, result.Request.Width);
+ Assert.AreEqual (10, result.Request.Height);
+ }
+
+ [Test]
+ public void TestAspectSizingWithConstrainedWidth ()
+ {
+ var image = new Image {Source = ImageSource.FromFile ("File.png"), Platform = new UnitPlatform (), IsPlatformEnabled = true};
+
+ var result = image.GetSizeRequest (25, double.PositiveInfinity);
+
+ Assert.AreEqual (25, result.Request.Width);
+ Assert.AreEqual (5, result.Request.Height);
+ }
+
+ [Test]
+ public void TestAspectFillSizingWithConstrainedHeight ()
+ {
+ var image = new Image {Source = ImageSource.FromFile ("File.png"), Platform = new UnitPlatform (), IsPlatformEnabled = true};
+
+ image.Aspect = Aspect.AspectFill;
+ var result = image.GetSizeRequest (double.PositiveInfinity, 10);
+
+ Assert.AreEqual (50, result.Request.Width);
+ Assert.AreEqual (10, result.Request.Height);
+ }
+
+ [Test]
+ public void TestAspectFillSizingWithConstrainedWidth ()
+ {
+ var image = new Image {Source = ImageSource.FromFile ("File.png"), Platform = new UnitPlatform (), IsPlatformEnabled = true};
+
+ image.Aspect = Aspect.AspectFill;
+ var result = image.GetSizeRequest (25, double.PositiveInfinity);
+
+ Assert.AreEqual (25, result.Request.Width);
+ Assert.AreEqual (5, result.Request.Height);
+ }
+
+ [Test]
+ public void TestFillSizingWithConstrainedHeight ()
+ {
+ var image = new Image {Source = ImageSource.FromFile ("File.png"), Platform = new UnitPlatform (), IsPlatformEnabled = true};
+
+ image.Aspect = Aspect.AspectFill;
+ var result = image.GetSizeRequest (double.PositiveInfinity, 10);
+
+ Assert.AreEqual (50, result.Request.Width);
+ Assert.AreEqual (10, result.Request.Height);
+ }
+
+ [Test]
+ public void TestFillSizingWithConstrainedWidth ()
+ {
+ var image = new Image {Source = ImageSource.FromFile ("File.png"), Platform = new UnitPlatform (), IsPlatformEnabled = true};
+
+ image.Aspect = Aspect.AspectFill;
+ var result = image.GetSizeRequest (25, double.PositiveInfinity);
+
+ Assert.AreEqual (25, result.Request.Width);
+ Assert.AreEqual (5, result.Request.Height);
+ }
+
+ [Test]
+ public void TestSizeChanged ()
+ {
+ var image = new Image { Source = "File0.png" };
+ Assert.AreEqual ("File0.png", ((FileImageSource)image.Source).File);
+
+ var preferredSizeChanged = false;
+ image.MeasureInvalidated += (sender, args) => preferredSizeChanged = true;
+
+ image.Source = "File1.png";
+ Assert.AreEqual ("File1.png", ((FileImageSource)image.Source).File);
+ Assert.True (preferredSizeChanged);
+ }
+
+ [Test]
+ public void TestSource ()
+ {
+ var image = new Image ();
+
+ Assert.IsNull (image.Source);
+
+ bool signaled = false;
+ image.PropertyChanged += (sender, e) => {
+ if (e.PropertyName == "Source")
+ signaled = true;
+ };
+
+ var source = ImageSource.FromFile ("File.png");
+ image.Source = source;
+
+ Assert.AreEqual (source, image.Source);
+ Assert.True (signaled);
+ }
+
+ [Test]
+ public void TestSourceDoubleSet ()
+ {
+ var image = new Image {Source = ImageSource.FromFile ("File.png")};
+
+ bool signaled = false;
+ image.PropertyChanged += (sender, e) => {
+ if (e.PropertyName == "Source")
+ signaled = true;
+ };
+
+ image.Source = image.Source;
+
+ Assert.False (signaled);
+ }
+
+ [Test]
+ public void TestFileImageSourceChanged ()
+ {
+ var source = (FileImageSource)ImageSource.FromFile ("File.png");
+
+ bool signaled = false;
+ source.SourceChanged += (sender, e) => {
+ signaled = true;
+ };
+
+ source.File = "Other.png";
+ Assert.AreEqual ("Other.png", source.File);
+
+ Assert.True (signaled);
+ }
+
+ [Test]
+ public void TestFileImageSourcePropertiesChangedTriggerResize ()
+ {
+ var source = new FileImageSource ();
+ var image = new Image { Source = source };
+ bool fired = false;
+ image.MeasureInvalidated += (sender, e) => fired = true;
+ Assert.Null (source.File);
+ source.File = "foo.png";
+ Assert.NotNull (source.File);
+ Assert.True (fired);
+ }
+
+ [Test]
+ public void TestStreamImageSourcePropertiesChangedTriggerResize ()
+ {
+ var source = new StreamImageSource ();
+ var image = new Image { Source = source };
+ bool fired = false;
+ image.MeasureInvalidated += (sender, e) => fired = true;
+ Assert.Null (source.Stream);
+ source.Stream = token => Task.FromResult<Stream> (null);
+ Assert.NotNull (source.Stream);
+ Assert.True (fired);
+ }
+
+ [Test]
+ public void TestImageSourceToNullCancelsLoading ()
+ {
+ var image = new Image ();
+ var mockImageRenderer = new MockImageRenderer (image);
+ var loader = new UriImageSource { Uri = new Uri ("http://www.public-domain-image.com/free-images/miscellaneous/big-high-border-fence.jpg") };
+ image.Source = loader;
+ Assert.IsTrue (image.IsLoading);
+ image.Source = null;
+ Assert.IsFalse (image.IsLoading);
+ Assert.IsTrue (cancelled);
+ }
+
+ static bool cancelled;
+
+ static async Task<Stream> GetStreamAsync (Uri uri, CancellationToken cancellationToken)
+ {
+ try {
+ await Task.Delay (5000, cancellationToken);
+ } catch (TaskCanceledException ex) {
+ cancelled = true;
+ throw ex;
+ }
+
+ if (cancellationToken.IsCancellationRequested) {
+ cancelled = true;
+ throw new TaskCanceledException ();
+ }
+
+ var stream = typeof(ImageTests).Assembly.GetManifestResourceStream (uri.LocalPath.Substring (1));
+ return stream;
+ }
+
+ class MockImageRenderer
+ {
+ public MockImageRenderer (Image element)
+ {
+ Element = element;
+ Element.PropertyChanged += ( sender, e) => {
+ if (e.PropertyName == nameof (Image.Source))
+ Load ();
+ };
+ }
+
+ public Image Element { get; set; }
+
+ public async void Load ()
+ {
+ if (initialLoad && Element.Source != null) {
+ initialLoad = false;
+ ((IElementController)Element).SetValueFromRenderer (Image.IsLoadingPropertyKey, true);
+ await (Element.Source as UriImageSource).GetStreamAsync ();
+ ((IElementController)Element).SetValueFromRenderer (Image.IsLoadingPropertyKey, false);
+ }
+ }
+
+ bool initialLoad = true;
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/Images/crimson.jpg b/Xamarin.Forms.Core.UnitTests/Images/crimson.jpg
new file mode 100644
index 00000000..3db7bb21
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/Images/crimson.jpg
Binary files differ
diff --git a/Xamarin.Forms.Core.UnitTests/KeyboardTests.cs b/Xamarin.Forms.Core.UnitTests/KeyboardTests.cs
new file mode 100644
index 00000000..773f0903
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/KeyboardTests.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ internal class KeyboardTests : BaseTestFixture
+ {
+ [Test]
+ public void KeyboardTypesAreCorrect ()
+ {
+ Assert.True (Keyboard.Chat is ChatKeyboard);
+ Assert.True (Keyboard.Email is EmailKeyboard);
+ Assert.True (Keyboard.Numeric is NumericKeyboard);
+ Assert.True (Keyboard.Telephone is TelephoneKeyboard);
+ Assert.True (Keyboard.Text is TextKeyboard);
+ Assert.True (Keyboard.Url is UrlKeyboard);
+ }
+ }
+
+ [TestFixture]
+ internal class KeyboardTypeConverterTests : BaseTestFixture
+ {
+ [Test]
+ public void ConversionConvert ()
+ {
+
+ var converter = new KeyboardTypeConverter ();
+ Assert.True (converter.CanConvertFrom (typeof(string)));
+ foreach (var kvp in new Dictionary<string, Keyboard> {
+ {"Keyboard.Default", Keyboard.Default},
+ {"Keyboard.Email", Keyboard.Email},
+ {"Keyboard.Text", Keyboard.Text},
+ {"Keyboard.Url", Keyboard.Url},
+ {"Keyboard.Telephone", Keyboard.Telephone},
+ {"Keyboard.Chat", Keyboard.Chat},
+ })
+ Assert.AreSame (kvp.Value, converter.ConvertFromInvariantString (kvp.Key));
+ }
+
+ [Test]
+ public void ConversionFail ()
+ {
+ var converter = new KeyboardTypeConverter ();
+ Assert.Throws<InvalidOperationException> (() => converter.ConvertFromInvariantString ("Foo"));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/LabelTests.cs b/Xamarin.Forms.Core.UnitTests/LabelTests.cs
new file mode 100644
index 00000000..f56f792d
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/LabelTests.cs
@@ -0,0 +1,296 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using NUnit.Framework;
+using NUnit.Framework.Constraints;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class LabelTests : BaseTestFixture
+ {
+ [SetUp]
+ public override void Setup ()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [TearDown]
+ public override void TearDown ()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ }
+
+ [Test]
+ public void TextAndAttributedTextMutuallyExclusive ()
+ {
+ var label = new Label ();
+ Assert.IsNull (label.Text);
+ Assert.IsNull (label.FormattedText);
+
+ label.Text = "Foo";
+ Assert.AreEqual ("Foo", label.Text);
+ Assert.IsNull (label.FormattedText);
+
+ var fs = new FormattedString ();
+ label.FormattedText = fs;
+ Assert.IsNull (label.Text);
+ Assert.AreSame (fs, label.FormattedText);
+
+ label.Text = "Foo";
+ Assert.AreEqual ("Foo", label.Text);
+ Assert.IsNull (label.FormattedText);
+ }
+
+ [Test]
+ public void AssignToFontStructUpdatesFontFamily (
+ [Values (NamedSize.Default, NamedSize.Large, NamedSize.Medium, NamedSize.Small, NamedSize.Micro)] NamedSize size,
+ [Values (FontAttributes.None, FontAttributes.Bold, FontAttributes.Italic, FontAttributes.Bold | FontAttributes.Italic)] FontAttributes attributes)
+ {
+ var label = new Label {Platform = new UnitPlatform ()};
+ double startSize = label.FontSize;
+ var startAttributes = label.FontAttributes;
+
+ bool firedSizeChanged = false;
+ bool firedAttributesChanged = false;
+ label.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == Label.FontSizeProperty.PropertyName)
+ firedSizeChanged = true;
+ if (args.PropertyName == Label.FontAttributesProperty.PropertyName)
+ firedAttributesChanged = true;
+ };
+
+ label.Font = Font.OfSize ("Testing123", size).WithAttributes (attributes);
+
+ Assert.AreEqual (Device.GetNamedSize (size, typeof (Label), true), label.FontSize);
+ Assert.AreEqual (attributes, label.FontAttributes);
+ Assert.AreEqual (startSize != label.FontSize, firedSizeChanged);
+ Assert.AreEqual (startAttributes != label.FontAttributes, firedAttributesChanged);
+ }
+
+ [Test]
+ public void AssignToFontFamilyUpdatesFont ()
+ {
+ var label = new Label {Platform = new UnitPlatform ()};
+
+ label.FontFamily = "CrazyFont";
+ Assert.AreEqual (label.Font, Font.OfSize ("CrazyFont", label.FontSize));
+ }
+
+ [Test]
+ public void AssignToFontSizeUpdatesFont ()
+ {
+ var label = new Label {Platform = new UnitPlatform ()};
+
+ label.FontSize = 1000;
+ Assert.AreEqual (label.Font, Font.SystemFontOfSize (1000));
+ }
+
+ [Test]
+ public void AssignedToFontSizeUpdatesFontDouble ()
+ {
+ var label = new Label {Platform = new UnitPlatform ()};
+
+ label.FontSize = 10.7;
+ Assert.AreEqual (label.Font, Font.SystemFontOfSize (10.7));
+ }
+
+ [Test]
+ public void AssignedToFontSizeDouble ()
+ {
+ var label = new Label {Platform = new UnitPlatform ()};
+
+ label.FontSize = 10.7;
+ Assert.AreEqual (label.FontSize, 10.7);
+ }
+
+
+ [Test]
+ public void AssignToFontAttributesUpdatesFont ()
+ {
+ var label = new Label {Platform = new UnitPlatform ()};
+
+ label.FontAttributes = FontAttributes.Italic | FontAttributes.Bold;
+ Assert.AreEqual (label.Font, Font.SystemFontOfSize (label.FontSize, FontAttributes.Bold | FontAttributes.Italic));
+ }
+
+ [Test]
+ public void LabelResizesWhenFontChanges ()
+ {
+ var label = new Label {Platform = new UnitPlatform ((ve, w, h) => {
+ var l = (Label) ve;
+ return new SizeRequest(new Size(l.Font.FontSize, l.Font.FontSize));
+ }), IsPlatformEnabled = true};
+
+ Assert.AreEqual (label.Font.FontSize, label.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity).Request.Width);
+
+ bool fired = false;
+
+ label.MeasureInvalidated += (sender, args) => {
+ Assert.AreEqual (25, label.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity).Request.Width);
+ fired = true;
+ };
+
+
+ label.FontSize = 25;
+
+ Assert.True (fired);
+ }
+
+ [Test]
+ public void FontSizeConverterTests ()
+ {
+ var converter = new FontSizeConverter ();
+ Assert.AreEqual (12, converter.ConvertFromInvariantString ("12"));
+ Assert.AreEqual (10.7, converter.ConvertFromInvariantString ("10.7"));
+ }
+
+ [Test]
+ public void FontSizeCanBeSetFromStyle ()
+ {
+ var label = new Label ();
+
+ Assert.AreEqual (10.0, label.FontSize);
+
+ label.SetValue (Label.FontSizeProperty, 1.0, true);
+ Assert.AreEqual (1.0, label.FontSize);
+ }
+
+ [Test]
+ public void ManuallySetFontSizeNotOverridenByStyle ()
+ {
+ var label = new Label ();
+
+ Assert.AreEqual (10.0, label.FontSize);
+
+ label.SetValue (Label.FontSizeProperty, 2.0, false);
+ Assert.AreEqual (2.0, label.FontSize);
+
+ label.SetValue (Label.FontSizeProperty, 1.0, true);
+ Assert.AreEqual (2.0, label.FontSize);
+ }
+
+ [Test]
+ public void ChangingHorizontalTextAlignmentFiresXAlignChanged ()
+ {
+ var label = new Label () { HorizontalTextAlignment = TextAlignment.Center };
+
+ var xAlignFired = false;
+ var horizontalTextAlignmentFired = false;
+
+ label.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "XAlign") {
+ xAlignFired = true;
+ } else if (args.PropertyName == Label.HorizontalTextAlignmentProperty.PropertyName) {
+ horizontalTextAlignmentFired = true;
+ }
+ };
+
+ label.HorizontalTextAlignment = TextAlignment.End;
+
+ Assert.True(xAlignFired);
+ Assert.True(horizontalTextAlignmentFired);
+ }
+
+ [Test]
+ public void ChangingVerticalTextAlignmentFiresYAlignChanged ()
+ {
+ var label = new Label () { VerticalTextAlignment = TextAlignment.Center };
+
+ var yAlignFired = false;
+ var verticalTextAlignmentFired = false;
+
+ label.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "YAlign") {
+ yAlignFired = true;
+ } else if (args.PropertyName == Label.VerticalTextAlignmentProperty.PropertyName) {
+ verticalTextAlignmentFired = true;
+ }
+ };
+
+ label.VerticalTextAlignment = TextAlignment.End;
+
+ Assert.True (yAlignFired);
+ Assert.True (verticalTextAlignmentFired);
+ }
+
+ [Test]
+ public void EntryCellXAlignBindingMatchesHorizontalTextAlignmentBinding ()
+ {
+ var vm = new ViewModel ();
+ vm.HorizontalAlignment = TextAlignment.Center;
+
+ var labelXAlign = new Label () { BindingContext = vm };
+ labelXAlign.SetBinding (Label.XAlignProperty, new Binding ("HorizontalAlignment"));
+
+ var labelHorizontalTextAlignment = new Label () { BindingContext = vm };
+ labelHorizontalTextAlignment.SetBinding (Label.HorizontalTextAlignmentProperty, new Binding ("HorizontalAlignment"));
+
+ Assert.AreEqual (TextAlignment.Center, labelXAlign.XAlign);
+ Assert.AreEqual (TextAlignment.Center, labelHorizontalTextAlignment.HorizontalTextAlignment);
+
+ vm.HorizontalAlignment = TextAlignment.End;
+
+ Assert.AreEqual (TextAlignment.End, labelXAlign.XAlign);
+ Assert.AreEqual (TextAlignment.End, labelHorizontalTextAlignment.HorizontalTextAlignment);
+ }
+
+ [Test]
+ public void EntryCellYAlignBindingMatchesVerticalTextAlignmentBinding ()
+ {
+ var vm = new ViewModel ();
+ vm.VerticalAlignment = TextAlignment.Center;
+
+ var labelYAlign = new Label () { BindingContext = vm };
+ labelYAlign.SetBinding (Label.YAlignProperty, new Binding ("VerticalAlignment"));
+
+ var labelVerticalTextAlignment = new Label () { BindingContext = vm };
+ labelVerticalTextAlignment.SetBinding (Label.VerticalTextAlignmentProperty, new Binding ("VerticalAlignment"));
+
+ Assert.AreEqual (TextAlignment.Center, labelYAlign.YAlign);
+ Assert.AreEqual (TextAlignment.Center, labelVerticalTextAlignment.VerticalTextAlignment);
+
+ vm.VerticalAlignment = TextAlignment.End;
+
+ Assert.AreEqual (TextAlignment.End, labelYAlign.YAlign);
+ Assert.AreEqual (TextAlignment.End, labelVerticalTextAlignment.VerticalTextAlignment);
+ }
+
+ sealed class ViewModel : INotifyPropertyChanged
+ {
+ TextAlignment horizontalAlignment;
+ TextAlignment verticalAlignment;
+
+ public TextAlignment HorizontalAlignment
+ {
+ get { return horizontalAlignment; }
+ set
+ {
+ horizontalAlignment = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public TextAlignment VerticalAlignment
+ {
+ get { return verticalAlignment; }
+ set
+ {
+ verticalAlignment = value;
+ OnPropertyChanged();
+ }
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ void OnPropertyChanged ([CallerMemberName] string propertyName = null)
+ {
+ PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
+ }
+ }
+ }
+}
+
diff --git a/Xamarin.Forms.Core.UnitTests/LayoutOptionsUnitTests.cs b/Xamarin.Forms.Core.UnitTests/LayoutOptionsUnitTests.cs
new file mode 100644
index 00000000..476cf73c
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/LayoutOptionsUnitTests.cs
@@ -0,0 +1,22 @@
+using System;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class LayoutOptionsUnitTests : BaseTestFixture
+ {
+ [Test]
+ public void TestTypeConverter ()
+ {
+ var converter = new LayoutOptionsConverter ();
+ Assert.True (converter.CanConvertFrom (typeof(string)));
+ Assert.AreEqual (LayoutOptions.Center, converter.ConvertFromInvariantString ("LayoutOptions.Center"));
+ Assert.AreEqual (LayoutOptions.Center, converter.ConvertFromInvariantString ("Center"));
+ Assert.AreNotEqual (LayoutOptions.CenterAndExpand, converter.ConvertFromInvariantString ("Center"));
+ Assert.Throws<InvalidOperationException> (() => converter.ConvertFromInvariantString ("foo"));
+ Assert.Throws<InvalidOperationException> (() => converter.ConvertFromInvariantString ("foo.bar"));
+ Assert.Throws<InvalidOperationException> (() => converter.ConvertFromInvariantString ("foo.bar.baz"));
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/ListProxyTests.cs b/Xamarin.Forms.Core.UnitTests/ListProxyTests.cs
new file mode 100644
index 00000000..6fa85d2c
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/ListProxyTests.cs
@@ -0,0 +1,426 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class ListProxyTests : BaseTestFixture
+ {
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ }
+
+ [Test]
+ public void ListCount()
+ {
+ var list = new List<string> { "foo", "bar" };
+ var proxy = new ListProxy (list);
+
+ Assert.AreEqual (list.Count, proxy.Count);
+ list.Add ("baz");
+ Assert.AreEqual (list.Count, proxy.Count);
+ }
+
+ [Test]
+ public void CollectionCount()
+ {
+ var list = new Collection<string> { "foo", "bar" };
+ var proxy = new ListProxy (list);
+
+ Assert.AreEqual (list.Count, proxy.Count);
+ list.Add ("baz");
+ Assert.AreEqual (list.Count, proxy.Count);
+ }
+
+ [Test]
+ [Description ("Count should ensure that the window is created if neccessary")]
+ public void EnumerableInitialCount()
+ {
+ var enumerable = Enumerable.Range (0, 100);
+ var proxy = new ListProxy (enumerable, 10);
+
+ Assert.AreEqual (10, proxy.Count);
+ }
+
+ [Test]
+ public void EnumerableCount()
+ {
+ var enumerable = Enumerable.Range (0, 100);
+ var proxy = new ListProxy (enumerable, 10);
+
+ int changed = 0;
+ proxy.CountChanged += (o, e) => changed++;
+
+ var enumerator = proxy.GetEnumerator();
+ enumerator.MoveNext();
+
+ Assert.AreEqual (10, proxy.Count);
+ Assert.AreEqual (1, changed);
+
+ enumerator.MoveNext();
+
+ Assert.AreEqual (10, proxy.Count);
+ Assert.AreEqual (1, changed);
+
+ while (enumerator.MoveNext()) {
+ }
+
+ enumerator.Dispose();
+
+ Assert.AreEqual (100, proxy.Count);
+ Assert.AreEqual (19, changed);
+
+ using (enumerator = proxy.GetEnumerator()) {
+
+ Assert.AreEqual (100, proxy.Count);
+
+ while (enumerator.MoveNext())
+ Assert.AreEqual (100, proxy.Count);
+
+ Assert.AreEqual (100, proxy.Count);
+ }
+
+ Assert.AreEqual (19, changed);
+ }
+
+ [Test]
+ public void InsideWindowSize()
+ {
+ var numbers = Enumerable.Range (0, 100);
+ var proxy = new ListProxy (numbers, 10);
+
+ int i = (int)proxy[5];
+ Assert.That (i, Is.EqualTo (5));
+ }
+
+ [Test]
+ public void IndexOutsideWindowSize()
+ {
+ var numbers = Enumerable.Range (0, 100);
+ var proxy = new ListProxy (numbers, 10);
+
+ int i = (int)proxy[50];
+ Assert.That (i, Is.EqualTo (50));
+ }
+
+ [Test]
+ public void IndexInsideToOutsideWindowSize()
+ {
+ var numbers = Enumerable.Range (0, 100);
+ var proxy = new ListProxy (numbers, 10);
+
+ int i = (int)proxy[5];
+ Assert.That (i, Is.EqualTo (5));
+
+ i = (int)proxy[50];
+ Assert.That (i, Is.EqualTo (50));
+ }
+
+ [Test]
+ public void IndexOutsideToPreWindowSize()
+ {
+ var numbers = Enumerable.Range (0, 100);
+ var proxy = new ListProxy (numbers, 10);
+
+ int i = (int)proxy[50];
+ Assert.That (i, Is.EqualTo (50));
+
+ i = (int)proxy[5];
+ Assert.That (i, Is.EqualTo (5));
+ }
+
+ [Test]
+ public void EnumerableIndexOutOfRange()
+ {
+ var numbers = Enumerable.Range (0, 100);
+ var proxy = new ListProxy (numbers);
+
+ Assert.That (() => proxy[100], Throws.InstanceOf<ArgumentOutOfRangeException>());
+ }
+
+ class IntCollection
+ : ICollection
+ {
+ readonly List<int> ints;
+
+ public IntCollection (IEnumerable<int> ints)
+ {
+ this.ints = ints.ToList();
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return ints.GetEnumerator();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public void CopyTo (Array array, int index)
+ {
+ throw new NotImplementedException();
+ }
+
+ public int Count { get { return ints.Count; }}
+
+ public object SyncRoot
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public bool IsSynchronized
+ {
+ get { throw new NotImplementedException(); }
+ }
+
+ public bool IsReadOnly { get { return true; } }
+ }
+
+ [Test]
+ public void CollectionIndexOutOfRange()
+ {
+ var numbers = new IntCollection (Enumerable.Range (0, 100));
+ var proxy = new ListProxy (numbers);
+
+ Assert.That (() => proxy[100], Throws.InstanceOf<ArgumentOutOfRangeException>());
+ }
+
+ [Test]
+ public void ListIndexOutOfRange()
+ {
+ var numbers = Enumerable.Range (0, 100).ToList();
+ var proxy = new ListProxy (numbers);
+
+ Assert.That (() => proxy[100], Throws.InstanceOf<ArgumentOutOfRangeException>());
+ }
+
+ [Test]
+ public void CollectionChangedWhileEnumerating()
+ {
+ var c = new ObservableCollection<string> { "foo", "bar" };
+ var p = new ListProxy (c);
+
+ IEnumerator<object> e = p.GetEnumerator();
+ Assert.IsTrue (e.MoveNext(), "Initial MoveNext() failed, test can't continue");
+
+ c.Add ("baz");
+
+ Assert.That (() => e.MoveNext(), Throws.InvalidOperationException,
+ "MoveNext did not throw an exception when the underlying collection had changed");
+ }
+
+ [Test]
+ public void SynchronizedCollectionAccess()
+ {
+ var collection = new ObservableCollection<string> { "foo" };
+ var context = new object();
+
+ var list = new ListProxy (collection);
+
+ bool executed = false;
+ BindingBase.EnableCollectionSynchronization (collection, context, (enumerable, o, method, access) => {
+ executed = true;
+ Assert.AreSame (collection, enumerable);
+ Assert.AreSame (context, o);
+ Assert.IsNotNull (method);
+ Assert.IsFalse (access);
+
+ lock (enumerable)
+ method();
+ });
+
+ object value = list[0];
+
+ Assert.IsTrue (executed, "Callback was not executed");
+ }
+
+ [Test]
+ public void SynchronizedCollectionAdd()
+ {
+ bool invoked = false;
+ Device.PlatformServices = new MockPlatformServices (invokeOnMainThread: action => {
+ invoked = true;
+ action();
+ });
+
+ var collection = new ObservableCollection<string> { "foo" };
+ var context = new object();
+
+ var list = new ListProxy (collection);
+
+ Assert.IsFalse (invoked, "An invoke shouldn't be executed just setting up ListProxy");
+
+ bool executed = false;
+ BindingBase.EnableCollectionSynchronization (collection, context, (enumerable, o, method, access) => {
+ executed = true;
+ Assert.AreSame (collection, enumerable);
+ Assert.AreSame (context, o);
+ Assert.IsNotNull (method);
+ Assert.IsFalse (access);
+
+ lock (enumerable)
+ method();
+ });
+
+ var mre = new ManualResetEvent (false);
+
+ Task.Factory.StartNew (() => {
+ lock (collection)
+ collection.Add ("foo");
+
+ mre.Set();
+ }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
+
+ mre.WaitOne (5000);
+
+ Assert.IsTrue (executed, "Callback was not executed");
+ Assert.IsTrue (invoked, "Callback was not executed on the UI thread");
+ }
+
+ [Test]
+ public void ClearEnumerable()
+ {
+ var proxy = new ListProxy (Enumerable.Range (0, 100));
+ var enumerator = proxy.GetEnumerator();
+ enumerator.MoveNext();
+ enumerator.MoveNext();
+
+ proxy.Clear();
+
+ Assert.AreEqual (100, proxy.Count);
+ Assert.That (() => enumerator.MoveNext(), Throws.InvalidOperationException);
+ }
+
+ [Test]
+ public void ClearCollection()
+ {
+ var proxy = new ListProxy (new IntCollection (Enumerable.Range (0, 100)));
+ var enumerator = proxy.GetEnumerator();
+ enumerator.MoveNext();
+ enumerator.MoveNext();
+
+ proxy.Clear();
+
+ Assert.AreEqual (100, proxy.Count);
+ Assert.That (() => enumerator.MoveNext(), Throws.InvalidOperationException);
+ }
+
+ [Test]
+ public void ClearList()
+ {
+ var proxy = new ListProxy (Enumerable.Range (0, 100).ToList());
+ var enumerator = proxy.GetEnumerator();
+ enumerator.MoveNext();
+ enumerator.MoveNext();
+
+ proxy.Clear();
+
+ Assert.AreEqual (100, proxy.Count);
+ Assert.That (() => enumerator.MoveNext(), Throws.InvalidOperationException);
+ }
+
+ [Test]
+ public void IndexOfValueTypeNonList()
+ {
+ var proxy = new ListProxy (Enumerable.Range (0, 100));
+ Assert.AreEqual (1, proxy.IndexOf (1));
+ }
+
+ [Test]
+ public void EnumeratorForEnumerable()
+ {
+ var proxy = new ListProxy (Enumerable.Range (0, 2));
+
+ var enumerator = proxy.GetEnumerator();
+ Assert.That (enumerator.Current, Is.Null);
+ Assert.That (enumerator.MoveNext(), Is.True);
+ Assert.That (enumerator.Current, Is.EqualTo (0));
+ Assert.That (enumerator.MoveNext(), Is.True);
+ Assert.That (enumerator.Current, Is.EqualTo (1));
+ Assert.That (enumerator.MoveNext(), Is.False);
+ }
+
+ [Test]
+ public void ProxyIsWeaklyHeldByINotifyCollectionChanged()
+ {
+ ObservableCollection<string> collection = new ObservableCollection<string>();
+
+ WeakReference weakProxy = null;
+
+ int i = 0;
+ Action create = null;
+ create = () => {
+ if (i++ < 1024) {
+ create();
+ return;
+ }
+
+ var proxy = new ListProxy (collection);
+ weakProxy = new WeakReference (proxy);
+ };
+
+ create();
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+
+ Assert.That (weakProxy.IsAlive, Is.False);
+ }
+
+ [Test]
+ public void IEnumerableAddDoesNotReport0()
+ {
+ var custom = new CustomINCC();
+ custom.Add ("test");
+ custom.Add ("test2");
+
+ var proxy = new ListProxy (custom);
+ Assert.That (proxy.Count, Is.EqualTo (2));
+
+ custom.Add ("testing");
+ Assert.That (proxy.Count, Is.EqualTo (3));
+ }
+
+ class CustomINCC : IEnumerable<string>, INotifyCollectionChanged
+ {
+ public event NotifyCollectionChangedEventHandler CollectionChanged;
+ List<string> Items = new List<string> ();
+
+ public void Add (string s)
+ {
+ Items.Add(s);
+ if (CollectionChanged != null)
+ CollectionChanged (this, new NotifyCollectionChangedEventArgs (NotifyCollectionChangedAction.Add, s));
+ }
+
+ public IEnumerator<string> GetEnumerator ()
+ {
+ return Items.GetEnumerator ();
+ }
+ IEnumerator IEnumerable.GetEnumerator ()
+ {
+ return Items.GetEnumerator ();
+ }
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/ListViewTests.cs b/Xamarin.Forms.Core.UnitTests/ListViewTests.cs
new file mode 100644
index 00000000..d5672f77
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/ListViewTests.cs
@@ -0,0 +1,1490 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Input;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class ListViewTests : BaseTestFixture
+ {
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ Device.Info = null;
+ }
+
+ [SetUp]
+ public override void Setup ()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ Device.Info = new TestDeviceInfo ();
+ }
+
+ [Test]
+ public void TestConstructor ()
+ {
+ var listView = new ListView ();
+
+ Assert.Null (listView.ItemsSource);
+ Assert.Null (listView.ItemTemplate);
+ Assert.AreEqual (LayoutOptions.FillAndExpand, listView.HorizontalOptions);
+ Assert.AreEqual (LayoutOptions.FillAndExpand, listView.VerticalOptions);
+ }
+
+ internal class ListItem
+ {
+ public string Name { get; set; }
+ public string Description { get; set; }
+ }
+
+ [Test]
+ public void TestTemplating ()
+ {
+ var cellTemplate = new DataTemplate (typeof (TextCell));
+ cellTemplate.SetBinding (TextCell.TextProperty, new Binding ("Name"));
+ cellTemplate.SetBinding (TextCell.DetailProperty, new Binding ("Description"));
+
+ var listView = new ListView {
+ ItemsSource = new[] {
+ new ListItem {Name = "Foo", Description = "Bar"},
+ new ListItem {Name = "Baz", Description = "Raz"}
+ },
+ ItemTemplate = cellTemplate
+ };
+
+ var cell = (Cell)listView.ItemTemplate.CreateContent ();
+
+ var textCell = (TextCell)cell;
+ cell.BindingContext = listView.ItemsSource.OfType<ListItem> ().First ();
+
+ Assert.AreEqual ("Foo", textCell.Text);
+ Assert.AreEqual ("Bar", textCell.Detail);
+ }
+
+ [Test]
+ public void TemplateNullObject()
+ {
+ var listView = new ListView {
+ ItemsSource = new object[] {
+ null
+ }
+ };
+
+ Cell cell = listView.TemplatedItems[0];
+
+ Assert.That (cell, Is.Not.Null);
+ Assert.That (cell, Is.InstanceOf<TextCell>());
+ Assert.That (((TextCell) cell).Text, Is.Null);
+ }
+
+ [Test]
+ [Description ("Setting GroupDisplayBinding or GroupHeaderTemplate when the other is set should set the other one to null.")]
+ public void SettingGroupHeaderTemplateSetsDisplayBindingToNull()
+ {
+ var listView = new ListView {
+ GroupDisplayBinding = new Binding ("Path")
+ };
+
+ listView.GroupHeaderTemplate = new DataTemplate (typeof (TextCell));
+
+ Assert.That (listView.GroupDisplayBinding, Is.Null);
+ }
+
+ [Test]
+ [Description ("Setting GroupDisplayBinding or GroupHeaderTemplate when the other is set should set the other one to null.")]
+ public void SettingGroupDisplayBindingSetsHeaderTemplateToNull()
+ {
+ var listView = new ListView {
+ GroupHeaderTemplate = new DataTemplate (typeof (TextCell))
+ };
+
+ listView.GroupDisplayBinding = new Binding ("Path");
+
+ Assert.That (listView.GroupHeaderTemplate, Is.Null);
+ }
+
+ [Test]
+ [Description ("You should be able to set ItemsSource without having set the other properties first without issue")]
+ public void SettingItemsSourceWithoutBindingsOrItemsSource()
+ {
+ var listView = new ListView {
+ IsGroupingEnabled = true
+ };
+
+ Assert.That (() => listView.ItemsSource = new[] { new[] { new object() } }, Throws.Nothing);
+ }
+
+ [Test]
+ public void DefaultGroupHeaderTemplates()
+ {
+ var items = new[] { new[] { new object() } };
+
+ var listView = new ListView {
+ IsGroupingEnabled = true,
+ ItemsSource = items
+ };
+
+ var til = (TemplatedItemsList<ItemsView<Cell>, Cell>)((IList)listView.TemplatedItems)[0];
+ Cell cell = til.HeaderContent;
+
+ Assert.That (cell, Is.Not.Null);
+ Assert.That (cell, Is.InstanceOf<TextCell>());
+ Assert.That (((TextCell) cell).Text, Is.EqualTo (items[0].ToString()));
+ }
+
+ [Test]
+ [Description ("Tapping a different item (row) that is equal to the current item selection should still raise ItemSelected")]
+ public void NotifyRowTappedDifferentIndex()
+ {
+ string item = "item";
+
+ var listView = new ListView {
+ ItemsSource = new[] {
+ item,
+ item
+ }
+ };
+
+ listView.NotifyRowTapped (0);
+
+ bool raised = false;
+ listView.ItemSelected += (sender, arg) => raised = true;
+
+ listView.NotifyRowTapped (1);
+ Assert.That (raised, Is.True, "ItemSelected was not raised");
+ }
+
+ [Test]
+ public void DoesNotCrashWhenAddingToSource ()
+ {
+ var items = new ObservableCollection<string> {
+ "Foo",
+ "Bar",
+ "Baz"
+ };
+
+ var listView = new ListView {
+ ItemsSource = items,
+ ItemTemplate = new DataTemplate(typeof(TextCell))
+ };
+
+ Assert.DoesNotThrow (() => items.Add ("Blah"));
+ }
+
+ [Test]
+ public void DoesNotThrowWhenMovingInSource ()
+ {
+ var items = new ObservableCollection<string> {
+ "Foo",
+ "Bar",
+ "Baz"
+ };
+
+ var listView = new ListView {
+ ItemsSource = items,
+ ItemTemplate = new DataTemplate (typeof (TextCell))
+ };
+
+ Assert.DoesNotThrow (() => items.Move (0, 1));
+ }
+
+ [Test]
+ [Description ("A cell being tapped from the UI should raise both tapped events, but not change ItemSelected")]
+ public void NotifyTappedSameItem()
+ {
+ int cellTapped = 0;
+ int itemTapped = 0;
+ int itemSelected = 0;
+
+ var listView = new ListView {
+ ItemsSource = new[] { "item" },
+ ItemTemplate = new DataTemplate (() => {
+ var cell = new TextCell();
+ cell.Tapped += (s, e) => {
+ cellTapped++;
+ };
+ return cell;
+ })
+ };
+
+ listView.ItemTapped += (sender, arg) => itemTapped++;
+ listView.ItemSelected += (sender, arg) => itemSelected++;
+
+ listView.NotifyRowTapped (0);
+
+ Assert.That (cellTapped, Is.EqualTo (1), "Cell.Tapped was not raised");
+ Assert.That (itemTapped, Is.EqualTo (1), "ListView.ItemTapped was not raised");
+ Assert.That (itemSelected, Is.EqualTo (1), "ListView.ItemSelected was not raised");
+
+ listView.NotifyRowTapped (0);
+
+ Assert.That (cellTapped, Is.EqualTo (2), "Cell.Tapped was not raised a second time");
+ Assert.That (itemTapped, Is.EqualTo (2), "ListView.ItemTapped was not raised a second time");
+ Assert.That (itemSelected, Is.EqualTo (1), "ListView.ItemSelected was raised a second time");
+ }
+
+ [Test]
+ public void ScrollTo()
+ {
+ var listView = new ListView {
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform()
+ };
+
+ object item = new object();
+
+ bool requested = false;
+ listView.ScrollToRequested += (sender, args) => {
+ requested = true;
+
+ Assert.That (args.Item, Is.SameAs (item));
+ Assert.That (args.Group, Is.Null);
+ Assert.That (args.Position, Is.EqualTo (ScrollToPosition.Center));
+ Assert.That (args.ShouldAnimate, Is.EqualTo (true));
+ };
+
+ listView.ScrollTo (item, ScrollToPosition.Center, animated: true);
+ Assert.That (requested, Is.True);
+ }
+
+ [Test]
+ public void ScrollToDelayed()
+ {
+ var listView = new ListView();
+
+ object item = new object();
+
+ bool requested = false;
+ listView.ScrollToRequested += (sender, args) => {
+ requested = true;
+
+ Assert.That (args.Item, Is.SameAs (item));
+ Assert.That (args.Group, Is.Null);
+ Assert.That (args.Position, Is.EqualTo (ScrollToPosition.Center));
+ Assert.That (args.ShouldAnimate, Is.EqualTo (true));
+ };
+
+ listView.ScrollTo (item, ScrollToPosition.Center, animated: true);
+ Assert.That (requested, Is.False);
+
+ listView.IsPlatformEnabled = true;
+ listView.Platform = new UnitPlatform();
+
+ Assert.That (requested, Is.True);
+ }
+
+ [Test]
+ public void ScrollToGroup()
+ {
+ // Fake a renderer so we pass along messages right away
+ var listView = new ListView {
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform(),
+ IsGroupingEnabled = true
+ };
+
+ object item = new object();
+ object group = new object();
+
+ bool requested = false;
+ listView.ScrollToRequested += (sender, args) => {
+ requested = true;
+
+ Assert.That (args.Item, Is.SameAs (item));
+ Assert.That (args.Group, Is.SameAs (group));
+ Assert.That (args.Position, Is.EqualTo (ScrollToPosition.Center));
+ Assert.That (args.ShouldAnimate, Is.EqualTo (true));
+ };
+
+ listView.ScrollTo (item, group, ScrollToPosition.Center, animated: true);
+ Assert.That (requested, Is.True);
+ }
+
+ [Test]
+ public void ScrollToInvalid()
+ {
+ var listView = new ListView {
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform()
+ };
+
+ Assert.That (() => listView.ScrollTo (new object(), (ScrollToPosition) 500, true), Throws.ArgumentException);
+ Assert.That (() => listView.ScrollTo (new object(), new object(), ScrollToPosition.Start, true), Throws.InvalidOperationException);
+
+ listView.IsGroupingEnabled = true;
+ Assert.That (() => listView.ScrollTo (new object(), new object(), (ScrollToPosition) 500, true), Throws.ArgumentException);
+ }
+
+ [Test]
+ public void GetSizeRequest ()
+ {
+ var listView = new ListView {
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform (),
+ HasUnevenRows = false,
+ RowHeight = 50,
+ ItemsSource = Enumerable.Range (0, 20).ToList ()
+ };
+
+
+ var sizeRequest = listView.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity);
+ Assert.AreEqual (40, sizeRequest.Minimum.Width);
+ Assert.AreEqual (40, sizeRequest.Minimum.Height);
+ Assert.AreEqual (50, sizeRequest.Request.Width);
+ Assert.AreEqual (50 * 20, sizeRequest.Request.Height);
+ }
+
+ [Test]
+ public void GetSizeRequestUneven ()
+ {
+ var listView = new ListView {
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform (),
+ HasUnevenRows = true,
+ RowHeight = 50,
+ ItemsSource = Enumerable.Range (0, 20).ToList ()
+ };
+
+
+ var sizeRequest = listView.GetSizeRequest (double.PositiveInfinity, double.PositiveInfinity);
+ Assert.AreEqual (40, sizeRequest.Minimum.Width);
+ Assert.AreEqual (40, sizeRequest.Minimum.Height);
+ Assert.AreEqual (50, sizeRequest.Request.Width);
+ Assert.AreEqual (100, sizeRequest.Request.Height);
+ }
+
+ public class ListItemValue : IComparable<ListItemValue>
+ {
+ public string Name { get; private set; }
+
+ public ListItemValue (string name)
+ {
+ Name = name;
+ }
+
+ int IComparable<ListItemValue>.CompareTo (ListItemValue value)
+ {
+ return Name.CompareTo (value.Name);
+ }
+
+ public string Label
+ {
+ get { return Name[0].ToString (); }
+ }
+ }
+
+ public class ListItemCollection : ObservableCollection<ListItemValue>
+ {
+ public string Title { get; private set; }
+
+ public ListItemCollection (string title)
+ {
+ Title = title;
+ }
+
+ public static List<ListItemValue> GetSortedData ()
+ {
+ var items = ListItems;
+ items.Sort ();
+ return items;
+ }
+
+ // Data used to populate our list.
+ static readonly List<ListItemValue> ListItems = new List<ListItemValue> () {
+ new ListItemValue ("Babbage"),
+ new ListItemValue ("Boole"),
+ new ListItemValue ("Berners-Lee"),
+ new ListItemValue ("Atanasoff"),
+ new ListItemValue ("Allen"),
+ new ListItemValue ("Cormack"),
+ new ListItemValue ("Cray"),
+ new ListItemValue ("Dijkstra"),
+ new ListItemValue ("Dix"),
+ new ListItemValue ("Dewey"),
+ new ListItemValue ("Erdos"),
+ };
+ }
+
+ public class TestCell : TextCell
+ {
+ public static int NumberOfCells = 0;
+
+ public TestCell ()
+ {
+ Interlocked.Increment (ref NumberOfCells);
+ }
+
+ ~TestCell ()
+ {
+ Interlocked.Decrement (ref NumberOfCells);
+ }
+ }
+
+ ObservableCollection<ListItemCollection> SetupList ()
+ {
+ var allListItemGroups = new ObservableCollection<ListItemCollection> ();
+
+ foreach (var item in ListItemCollection.GetSortedData ()) {
+ // Attempt to find any existing groups where theg group title matches the first char of our ListItem's name.
+ var listItemGroup = allListItemGroups.FirstOrDefault (g => g.Title == item.Label);
+
+ // If the list group does not exist, we create it.
+ if (listItemGroup == null) {
+ listItemGroup = new ListItemCollection (item.Label) { item };
+ allListItemGroups.Add (listItemGroup);
+ } else {
+ // If the group does exist, we simply add the demo to the existing group.
+ listItemGroup.Add (item);
+ }
+ }
+ return allListItemGroups;
+ }
+
+ [Test]
+ public void UncollectableHeaderReferences ()
+ {
+ var list = new ListView {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true,
+ ItemTemplate = new DataTemplate (typeof (TextCell)) {
+ Bindings = {
+ {TextCell.TextProperty, new Binding ("Name")}
+ }
+ },
+ GroupHeaderTemplate = new DataTemplate (typeof (TestCell)) {
+ Bindings = {
+ {TextCell.TextProperty, new Binding ("Title")}
+ }
+ },
+ IsGroupingEnabled = true,
+ ItemsSource = SetupList (),
+ };
+
+ Assert.AreEqual (5, TestCell.NumberOfCells);
+
+ var newList1 = SetupList ();
+ var newList2 = SetupList ();
+
+ for (var i = 0; i < 400; i++) {
+ list.ItemsSource = i % 2 > 0 ? newList1 : newList2;
+
+ // grab a header just so we can be sure its reailized
+ var header = list.TemplatedItems.GetGroup (0).HeaderContent;
+ }
+
+ GC.Collect ();
+ GC.WaitForPendingFinalizers ();
+
+ // use less or equal because mono will keep the last header var alive no matter what
+ Assert.True (TestCell.NumberOfCells <= 6);
+
+ var keepAlive = list.ToString ();
+ }
+
+ [Test]
+ public void CollectionChangedMultipleFires ()
+ {
+ var source = new ObservableCollection<string> {
+ "Foo",
+ "Bar"
+ };
+
+ var list = new ListView {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true,
+ ItemsSource = source,
+ ItemTemplate = new DataTemplate (typeof (TextCell))
+ };
+
+ int fireCount = 0;
+ list.TemplatedItems.CollectionChanged += (sender, args) => {
+ fireCount++;
+ };
+
+ source.Add ("Baz");
+
+ Assert.AreEqual (1, fireCount);
+ }
+
+ [Test]
+ public void GroupedCollectionChangedMultipleFires ()
+ {
+ var source = new ObservableCollection<ObservableCollection <string>> {
+ new ObservableCollection<string> {"Foo"},
+ new ObservableCollection<string> {"Bar"}
+ };
+
+ var list = new ListView {
+ Platform = new UnitPlatform (),
+ IsPlatformEnabled = true,
+ IsGroupingEnabled = true,
+ ItemsSource = source,
+ ItemTemplate = new DataTemplate (typeof (TextCell)) {
+ Bindings = {
+ {TextCell.TextProperty, new Binding (".") }
+ }
+ }
+ };
+
+ int fireCount = 0;
+ list.TemplatedItems.GroupedCollectionChanged += (sender, args) => {
+ fireCount++;
+ };
+
+ source[0].Add ("Baz");
+
+ Assert.AreEqual (1, fireCount);
+ }
+
+ [Test]
+ public void HeaderAsView()
+ {
+ var label = new Label { Text = "header" };
+ var lv = new ListView {
+ Header = label
+ };
+
+ IListViewController controller = lv;
+ Assert.That (controller.HeaderElement, Is.SameAs (label));
+ }
+
+ [Test]
+ public void HeaderTemplated()
+ {
+ var lv = new ListView {
+ Header = "header",
+ HeaderTemplate = new DataTemplate(typeof(Label)) {
+ Bindings = {
+ { Label.TextProperty, new Binding (".") }
+ }
+ }
+ };
+
+ IListViewController controller = lv;
+ Assert.That (controller.HeaderElement, Is.Not.Null);
+ Assert.That (controller.HeaderElement, Is.InstanceOf<Label>());
+ Assert.That (((Label) controller.HeaderElement).Text, Is.EqualTo (lv.Header));
+ }
+
+ [Test]
+ public void HeaderTemplateThrowsIfCell ()
+ {
+ var lv = new ListView ();
+
+ Assert.Throws<ArgumentException> (() => lv.HeaderTemplate = new DataTemplate (typeof (TextCell)));
+ }
+
+ [Test]
+ public void FooterTemplateThrowsIfCell ()
+ {
+ var lv = new ListView ();
+
+ Assert.Throws<ArgumentException> (() => lv.FooterTemplate = new DataTemplate (typeof (TextCell)));
+ }
+
+ [Test]
+ public void HeaderObjectTemplatedChanged()
+ {
+ var lv = new ListView {
+ Header = "header",
+ HeaderTemplate = new DataTemplate (typeof (Label)) {
+ Bindings = {
+ { Label.TextProperty, new Binding (".") }
+ }
+ }
+ };
+
+ bool changed = false, changing = false;
+ lv.PropertyChanging += (sender, args) => {
+ if (args.PropertyName == "HeaderElement")
+ changing = true;
+ };
+ lv.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "HeaderElement")
+ changed = true;
+ };
+
+ lv.Header = "newheader";
+
+ Assert.That (changing, Is.False);
+ Assert.That (changed, Is.False);
+
+ IListViewController controller = lv;
+ Assert.That (controller.HeaderElement, Is.Not.Null);
+ Assert.That (controller.HeaderElement, Is.InstanceOf<Label>());
+ Assert.That (((Label) controller.HeaderElement).Text, Is.EqualTo (lv.Header));
+ }
+
+ [Test]
+ public void HeaderViewChanged()
+ {
+ var lv = new ListView {
+ Header = new Label { Text = "header" }
+ };
+
+ bool changed = false, changing = false;
+ lv.PropertyChanging += (sender, args) => {
+ if (args.PropertyName == "HeaderElement")
+ changing = true;
+ };
+ lv.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "HeaderElement")
+ changed = true;
+ };
+
+ Label label = new Label { Text = "header" };
+ lv.Header = label;
+
+ Assert.That (changing, Is.True);
+ Assert.That (changed, Is.True);
+
+ IListViewController controller = lv;
+ Assert.That (controller.HeaderElement, Is.SameAs (label));
+ }
+
+
+ [Test]
+ public void HeaderTemplateChanged()
+ {
+ var lv = new ListView {
+ Header = "header",
+ HeaderTemplate = new DataTemplate(typeof(Label)) {
+ Bindings = {
+ { Label.TextProperty, new Binding (".") }
+ }
+ }
+ };
+
+ bool changed = false, changing = false;
+ lv.PropertyChanging += (sender, args) => {
+ if (args.PropertyName == "HeaderElement")
+ changing = true;
+ };
+ lv.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "HeaderElement")
+ changed = true;
+ };
+
+ lv.HeaderTemplate = new DataTemplate (typeof (Entry)) {
+ Bindings = {
+ { Entry.TextProperty, new Binding (".") }
+ }
+ };
+
+ Assert.That (changing, Is.True);
+ Assert.That (changed, Is.True);
+
+ IListViewController controller = lv;
+ Assert.That (controller.HeaderElement, Is.Not.Null);
+ Assert.That (controller.HeaderElement, Is.InstanceOf<Entry>());
+ Assert.That (((Entry) controller.HeaderElement).Text, Is.EqualTo (lv.Header));
+ }
+
+ [Test]
+ public void HeaderTemplateChangedNoObject()
+ {
+ var lv = new ListView {
+ HeaderTemplate = new DataTemplate(typeof(Label)) {
+ Bindings = {
+ { Label.TextProperty, new Binding (".") }
+ }
+ }
+ };
+
+ bool changed = false, changing = false;
+ lv.PropertyChanging += (sender, args) => {
+ if (args.PropertyName == "HeaderElement")
+ changing = true;
+ };
+ lv.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "HeaderElement")
+ changed = true;
+ };
+
+ lv.HeaderTemplate = new DataTemplate (typeof (Entry)) {
+ Bindings = {
+ { Entry.TextProperty, new Binding (".") }
+ }
+ };
+
+ Assert.That (changing, Is.False);
+ Assert.That (changed, Is.False);
+
+ IListViewController controller = lv;
+ Assert.That (controller.HeaderElement, Is.Null);
+ }
+
+ [Test]
+ public void HeaderNoTemplate()
+ {
+ var lv = new ListView {
+ Header = "foo"
+ };
+
+ IListViewController controller = lv;
+ Assert.That (controller.HeaderElement, Is.Not.Null);
+ Assert.That (controller.HeaderElement, Is.InstanceOf<Label>());
+ Assert.That (((Label) controller.HeaderElement).Text, Is.EqualTo (lv.Header));
+ }
+
+ [Test]
+ public void HeaderChangedNoTemplate()
+ {
+ var lv = new ListView {
+ Header = "foo"
+ };
+
+ bool changed = false, changing = false;
+ lv.PropertyChanging += (sender, args) => {
+ if (args.PropertyName == "HeaderElement")
+ changing = true;
+ };
+ lv.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "HeaderElement")
+ changed = true;
+ };
+
+ lv.Header = "bar";
+
+ Assert.That (changing, Is.True);
+ Assert.That (changed, Is.True);
+
+ IListViewController controller = lv;
+ Assert.That (controller.HeaderElement, Is.Not.Null);
+ Assert.That (controller.HeaderElement, Is.InstanceOf<Label>());
+ Assert.That (((Label) controller.HeaderElement).Text, Is.EqualTo (lv.Header));
+ }
+
+ [Test]
+ public void HeaderViewButTemplated()
+ {
+ var lv = new ListView {
+ Header = new Entry { Text = "foo" },
+ HeaderTemplate = new DataTemplate (typeof(Label)) {
+ Bindings = {
+ { Label.TextProperty, new Binding ("Text") }
+ }
+ }
+ };
+
+ IListViewController controller = lv;
+ Assert.That (controller.HeaderElement, Is.Not.Null);
+ Assert.That (controller.HeaderElement, Is.InstanceOf<Label>());
+ Assert.That (((Label) controller.HeaderElement).Text, Is.EqualTo (((Entry)lv.Header).Text));
+ }
+
+ [Test]
+ public void HeaderTemplatedChangedToView()
+ {
+ var lv = new ListView {
+ Header = new Entry { Text = "foo" },
+ HeaderTemplate = new DataTemplate (typeof(Label)) {
+ Bindings = {
+ { Label.TextProperty, new Binding ("Text") }
+ }
+ }
+ };
+
+ bool changed = false, changing = false;
+ lv.PropertyChanging += (sender, args) => {
+ if (args.PropertyName == "HeaderElement")
+ changing = true;
+ };
+ lv.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "HeaderElement")
+ changed = true;
+ };
+
+ lv.HeaderTemplate = null;
+
+ Assert.That (changing, Is.True);
+ Assert.That (changed, Is.True);
+
+ IListViewController controller = lv;
+ Assert.That (controller.HeaderElement, Is.Not.Null);
+ Assert.That (controller.HeaderElement, Is.InstanceOf<Entry>());
+ Assert.That (((Entry) controller.HeaderElement).Text, Is.EqualTo (((Entry)lv.Header).Text));
+ }
+
+ [Test]
+ public void HeaderTemplatedSetToNull()
+ {
+ var lv = new ListView {
+ Header = "header",
+ HeaderTemplate = new DataTemplate(typeof(Label)) {
+ Bindings = {
+ { Label.TextProperty, new Binding (".") }
+ }
+ }
+ };
+
+ bool changed = false, changing = false;
+ lv.PropertyChanging += (sender, args) => {
+ if (args.PropertyName == "HeaderElement")
+ changing = true;
+ };
+ lv.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "HeaderElement")
+ changed = true;
+ };
+
+ lv.Header = null;
+
+ Assert.That (changing, Is.True);
+ Assert.That (changed, Is.True);
+
+ IListViewController controller = lv;
+ Assert.That (controller.HeaderElement, Is.Null);
+ }
+
+ [Test]
+ public void FooterAsView()
+ {
+ var label = new Label { Text = "footer" };
+ var lv = new ListView {
+ Footer = label
+ };
+
+ IListViewController controller = lv;
+ Assert.That (controller.FooterElement, Is.SameAs (label));
+ }
+
+ [Test]
+ public void FooterTemplated()
+ {
+ var lv = new ListView {
+ Footer = "footer",
+ FooterTemplate = new DataTemplate(typeof(Label)) {
+ Bindings = {
+ { Label.TextProperty, new Binding (".") }
+ }
+ }
+ };
+
+ IListViewController controller = lv;
+ Assert.That (controller.FooterElement, Is.Not.Null);
+ Assert.That (controller.FooterElement, Is.InstanceOf<Label>());
+ Assert.That (((Label) controller.FooterElement).Text, Is.EqualTo (lv.Footer));
+ }
+
+ [Test]
+ public void FooterObjectTemplatedChanged()
+ {
+ var lv = new ListView {
+ Footer = "footer",
+ FooterTemplate = new DataTemplate (typeof (Label)) {
+ Bindings = {
+ { Label.TextProperty, new Binding (".") }
+ }
+ }
+ };
+
+ bool changed = false, changing = false;
+ lv.PropertyChanging += (sender, args) => {
+ if (args.PropertyName == "FooterElement")
+ changing = true;
+ };
+ lv.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "FooterElement")
+ changed = true;
+ };
+
+ lv.Footer = "newfooter";
+
+ Assert.That (changing, Is.False);
+ Assert.That (changed, Is.False);
+
+ IListViewController controller = lv;
+ Assert.That (controller.FooterElement, Is.Not.Null);
+ Assert.That (controller.FooterElement, Is.InstanceOf<Label>());
+ Assert.That (((Label) controller.FooterElement).Text, Is.EqualTo (lv.Footer));
+ }
+
+ [Test]
+ public void FooterViewChanged()
+ {
+ var lv = new ListView {
+ Footer = new Label { Text = "footer" }
+ };
+
+ bool changed = false, changing = false;
+ lv.PropertyChanging += (sender, args) => {
+ if (args.PropertyName == "FooterElement")
+ changing = true;
+ };
+ lv.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "FooterElement")
+ changed = true;
+ };
+
+ Label label = new Label { Text = "footer" };
+ lv.Footer = label;
+
+ Assert.That (changing, Is.True);
+ Assert.That (changed, Is.True);
+
+ IListViewController controller = lv;
+ Assert.That (controller.FooterElement, Is.SameAs (label));
+ }
+
+
+ [Test]
+ public void FooterTemplateChanged()
+ {
+ var lv = new ListView {
+ Footer = "footer",
+ FooterTemplate = new DataTemplate(typeof(Label)) {
+ Bindings = {
+ { Label.TextProperty, new Binding (".") }
+ }
+ }
+ };
+
+ bool changed = false, changing = false;
+ lv.PropertyChanging += (sender, args) => {
+ if (args.PropertyName == "FooterElement")
+ changing = true;
+ };
+ lv.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "FooterElement")
+ changed = true;
+ };
+
+ lv.FooterTemplate = new DataTemplate (typeof (Entry)) {
+ Bindings = {
+ { Entry.TextProperty, new Binding (".") }
+ }
+ };
+
+ Assert.That (changing, Is.True);
+ Assert.That (changed, Is.True);
+
+ IListViewController controller = lv;
+ Assert.That (controller.FooterElement, Is.Not.Null);
+ Assert.That (controller.FooterElement, Is.InstanceOf<Entry>());
+ Assert.That (((Entry) controller.FooterElement).Text, Is.EqualTo (lv.Footer));
+ }
+
+ [Test]
+ public void FooterTemplateChangedNoObject()
+ {
+ var lv = new ListView {
+ FooterTemplate = new DataTemplate(typeof(Label)) {
+ Bindings = {
+ { Label.TextProperty, new Binding (".") }
+ }
+ }
+ };
+
+ bool changed = false, changing = false;
+ lv.PropertyChanging += (sender, args) => {
+ if (args.PropertyName == "FooterElement")
+ changing = true;
+ };
+ lv.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "FooterElement")
+ changed = true;
+ };
+
+ lv.FooterTemplate = new DataTemplate (typeof (Entry)) {
+ Bindings = {
+ { Entry.TextProperty, new Binding (".") }
+ }
+ };
+
+ Assert.That (changing, Is.False);
+ Assert.That (changed, Is.False);
+
+ IListViewController controller = lv;
+ Assert.That (controller.FooterElement, Is.Null);
+ }
+
+ [Test]
+ public void FooterNoTemplate()
+ {
+ var lv = new ListView {
+ Footer = "foo"
+ };
+
+ IListViewController controller = lv;
+ Assert.That (controller.FooterElement, Is.Not.Null);
+ Assert.That (controller.FooterElement, Is.InstanceOf<Label>());
+ Assert.That (((Label) controller.FooterElement).Text, Is.EqualTo (lv.Footer));
+ }
+
+ [Test]
+ public void FooterChangedNoTemplate()
+ {
+ var lv = new ListView {
+ Footer = "foo"
+ };
+
+ bool changed = false, changing = false;
+ lv.PropertyChanging += (sender, args) => {
+ if (args.PropertyName == "FooterElement")
+ changing = true;
+ };
+ lv.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "FooterElement")
+ changed = true;
+ };
+
+ lv.Footer = "bar";
+
+ Assert.That (changing, Is.True);
+ Assert.That (changed, Is.True);
+
+ IListViewController controller = lv;
+ Assert.That (controller.FooterElement, Is.Not.Null);
+ Assert.That (controller.FooterElement, Is.InstanceOf<Label>());
+ Assert.That (((Label) controller.FooterElement).Text, Is.EqualTo (lv.Footer));
+ }
+
+ [Test]
+ public void FooterViewButTemplated()
+ {
+ var lv = new ListView {
+ Footer = new Entry { Text = "foo" },
+ FooterTemplate = new DataTemplate (typeof(Label)) {
+ Bindings = {
+ { Label.TextProperty, new Binding ("Text") }
+ }
+ }
+ };
+
+ IListViewController controller = lv;
+ Assert.That (controller.FooterElement, Is.Not.Null);
+ Assert.That (controller.FooterElement, Is.InstanceOf<Label>());
+ Assert.That (((Label) controller.FooterElement).Text, Is.EqualTo (((Entry)lv.Footer).Text));
+ }
+
+ [Test]
+ public void FooterTemplatedChangedToView()
+ {
+ var lv = new ListView {
+ Footer = new Entry { Text = "foo" },
+ FooterTemplate = new DataTemplate (typeof(Label)) {
+ Bindings = {
+ { Label.TextProperty, new Binding ("Text") }
+ }
+ }
+ };
+
+ bool changed = false, changing = false;
+ lv.PropertyChanging += (sender, args) => {
+ if (args.PropertyName == "FooterElement")
+ changing = true;
+ };
+ lv.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "FooterElement")
+ changed = true;
+ };
+
+ lv.FooterTemplate = null;
+
+ Assert.That (changing, Is.True);
+ Assert.That (changed, Is.True);
+
+ IListViewController controller = lv;
+ Assert.That (controller.FooterElement, Is.Not.Null);
+ Assert.That (controller.FooterElement, Is.InstanceOf<Entry>());
+ Assert.That (((Entry) controller.FooterElement).Text, Is.EqualTo (((Entry)lv.Footer).Text));
+ }
+
+ [Test]
+ public void FooterTemplatedSetToNull()
+ {
+ var lv = new ListView {
+ Footer = "footer",
+ FooterTemplate = new DataTemplate(typeof(Label)) {
+ Bindings = {
+ { Label.TextProperty, new Binding (".") }
+ }
+ }
+ };
+
+ bool changed = false, changing = false;
+ lv.PropertyChanging += (sender, args) => {
+ if (args.PropertyName == "FooterElement")
+ changing = true;
+ };
+ lv.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "FooterElement")
+ changed = true;
+ };
+
+ lv.Footer = null;
+
+ Assert.That (changing, Is.True);
+ Assert.That (changed, Is.True);
+
+ IListViewController controller = lv;
+ Assert.That (controller.FooterElement, Is.Null);
+ }
+
+ [Test]
+ public void BeginRefresh()
+ {
+ var lv = new ListView();
+
+ bool refreshing = false;
+ lv.Refreshing += (sender, args) => {
+ refreshing = true;
+ };
+
+ lv.BeginRefresh();
+
+ Assert.That (refreshing, Is.True);
+ Assert.That (lv.IsRefreshing, Is.True);
+ }
+
+ [Test]
+ public void SendRefreshing()
+ {
+ var lv = new ListView();
+
+ bool refreshing = false;
+ lv.Refreshing += (sender, args) => {
+ refreshing = true;
+ };
+
+ IListViewController controller = lv;
+ controller.SendRefreshing();
+
+ Assert.That (refreshing, Is.True);
+ Assert.That (lv.IsRefreshing, Is.True);
+ }
+
+ [Test]
+ public void RefreshCommand()
+ {
+ var lv = new ListView();
+
+ bool commandExecuted = false;
+
+ Command refresh = new Command (() => commandExecuted = true);
+
+ lv.RefreshCommand = refresh;
+
+ IListViewController controller = lv;
+ controller.SendRefreshing();
+
+ Assert.That (commandExecuted, Is.True);
+ }
+
+ [TestCase (true)]
+ [TestCase (false)]
+ public void RefreshCommandCanExecute (bool initial)
+ {
+ var lv = new ListView { IsPullToRefreshEnabled = initial };
+
+ bool commandExecuted = false;
+
+ Command refresh = new Command (() => commandExecuted = true,
+ () => !initial);
+
+ lv.RefreshCommand = refresh;
+
+ Assert.That ((lv as IListViewController).RefreshAllowed, Is.EqualTo (!initial));
+ }
+
+ [TestCase (true)]
+ [TestCase (false)]
+ public void RefreshCommandCanExecuteChanges (bool initial)
+ {
+ var lv = new ListView { IsPullToRefreshEnabled = initial };
+
+ bool commandExecuted = false;
+
+ Command refresh = new Command (() => commandExecuted = true,
+ () => initial);
+
+ lv.RefreshCommand = refresh;
+
+ Assert.That ((lv as IListViewController).RefreshAllowed, Is.EqualTo (initial));
+
+ initial = !initial;
+ refresh.ChangeCanExecute();
+
+ Assert.That ((lv as IListViewController).RefreshAllowed, Is.EqualTo (initial));
+ }
+
+ [Test]
+ public void BeginRefreshDoesNothingWhenCannotExecute()
+ {
+ var lv = new ListView();
+
+ bool commandExecuted = false, eventFired = false;
+
+ lv.Refreshing += (sender, args) => eventFired = true;
+
+ Command refresh = new Command (() => commandExecuted = true,
+ () => false);
+
+ lv.RefreshCommand = refresh;
+ lv.BeginRefresh();
+
+ Assert.That (lv.IsRefreshing, Is.False);
+ Assert.That (eventFired, Is.False);
+ Assert.That (commandExecuted, Is.False);
+ }
+
+ [Test]
+ public void SendRefreshingDoesNothingWhenCannotExecute()
+ {
+ var lv = new ListView();
+
+ bool commandExecuted = false, eventFired = false;
+
+ lv.Refreshing += (sender, args) => eventFired = true;
+
+ Command refresh = new Command (() => commandExecuted = true,
+ () => false);
+
+ lv.RefreshCommand = refresh;
+
+ ((IListViewController)lv).SendRefreshing();
+
+ Assert.That (lv.IsRefreshing, Is.False);
+ Assert.That (eventFired, Is.False);
+ Assert.That (commandExecuted, Is.False);
+ }
+
+ [Test]
+ public void SettingIsRefreshingDoesntFireEvent()
+ {
+ var lv = new ListView();
+
+ bool refreshing = false;
+ lv.Refreshing += (sender, args) => {
+ refreshing = true;
+ };
+
+ lv.IsRefreshing = true;
+
+ Assert.That (refreshing, Is.False);
+ }
+
+ [Test]
+ public void EndRefresh()
+ {
+ var lv = new ListView { IsRefreshing = true };
+
+ Assert.That (lv.IsRefreshing, Is.True);
+
+ lv.EndRefresh();
+
+ Assert.That (lv.IsRefreshing, Is.False);
+ }
+
+ [Test]
+ public void CanRefreshAfterCantExecuteCommand()
+ {
+ var lv = new ListView();
+
+ bool commandExecuted = false, eventFired = false;
+
+ lv.Refreshing += (sender, args) => eventFired = true;
+
+ Command refresh = new Command (() => commandExecuted = true,
+ () => false);
+
+ lv.RefreshCommand = refresh;
+ lv.RefreshCommand = null;
+
+ ((IListViewController)lv).SendRefreshing();
+
+ Assert.That (lv.IsRefreshing, Is.True);
+ Assert.That (eventFired, Is.True);
+ Assert.That (commandExecuted, Is.False);
+ }
+
+ [Test]
+ public void StopsListeningToCommandAfterCleared()
+ {
+ var lv = new ListView();
+
+ bool commandExecuted = false, canExecuteRequested = false;
+
+ Command refresh = new Command (() => commandExecuted = true,
+ () => canExecuteRequested = true);
+
+ lv.RefreshCommand = refresh;
+ canExecuteRequested = false;
+
+ lv.RefreshCommand = null;
+
+ Assert.That (() => refresh.ChangeCanExecute(), Throws.Nothing);
+ Assert.That (canExecuteRequested, Is.False);
+
+ lv.BeginRefresh();
+
+ Assert.That (commandExecuted, Is.False);
+ }
+
+ [Test]
+ [Description ("We should be able to set selected item when using ReadOnlyList")]
+ public void SetItemSelectedOnReadOnlyList()
+ {
+ var source = new ReadOnlySource ();
+ var listView = new ListView {
+ ItemsSource = source
+ };
+
+ bool raised = false;
+ listView.ItemSelected += (sender, arg) => raised = true;
+
+ listView.SelectedItem = source [0];
+ Assert.That (raised, Is.True, "ItemSelected was raised on ReadOnlySource");
+ }
+
+ internal class ReadOnlySource : IReadOnlyList<ListItem>
+ {
+ List<ListItem> items;
+ public ReadOnlySource ()
+ {
+ items = new List<ListItem> ();
+
+ for (int i = 0; i < 100; i++) {
+ items.Add (new ListItem { Name="person " + i } );
+ }
+
+ }
+ #region IEnumerable implementation
+ public IEnumerator<ListItem> GetEnumerator ()
+ {
+ return items.GetEnumerator ();
+ }
+ #endregion
+ #region IEnumerable implementation
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
+ {
+ return items.GetEnumerator ();
+ }
+ #endregion
+ #region IReadOnlyList implementation
+ public ListItem this [int index] {
+ get {
+ return items [index];
+ }
+ }
+ #endregion
+ #region IReadOnlyCollection implementation
+ public int Count {
+ get {
+ return items.Count;
+ }
+ }
+ #endregion
+
+ }
+
+ [Test]
+ public void ChildElementsParentIsNulledWhenReset()
+ {
+ var list = new ListView();
+ list.ItemsSource = new[] { "Hi", "Bye" };
+
+ var cell = list.TemplatedItems[0];
+ Assume.That (cell.Parent, Is.SameAs (list));
+
+ list.ItemsSource = null;
+ Assert.That (cell.Parent, Is.Null);
+ }
+
+ [Test]
+ public void ChildElementsParentIsNulledWhenRemoved()
+ {
+ var collection = new ObservableCollection<string> {
+ "Hi", "Bye"
+ };
+
+ var list = new ListView();
+ list.ItemsSource = collection;
+
+ var cell = list.TemplatedItems[0];
+ Assume.That (cell.Parent, Is.SameAs (list));
+
+ collection.Remove (collection[0]);
+ Assert.That (cell.Parent, Is.Null);
+ }
+
+ [Test]
+ public void ChildElementsParentIsNulledWhenCleared()
+ {
+ var collection = new ObservableCollection<string> {
+ "Hi", "Bye"
+ };
+
+ var list = new ListView();
+ list.ItemsSource = collection;
+
+ var cell = list.TemplatedItems[0];
+ Assume.That (cell.Parent, Is.SameAs (list));
+
+ collection.Clear();
+ Assert.That (cell.Parent, Is.Null);
+ }
+
+ [TestCase (TargetPlatform.Android, ListViewCachingStrategy.RecycleElement)]
+ [TestCase (TargetPlatform.iOS, ListViewCachingStrategy.RecycleElement)]
+ [TestCase (TargetPlatform.Windows, ListViewCachingStrategy.RetainElement)]
+ [TestCase (TargetPlatform.Other, ListViewCachingStrategy.RetainElement)]
+ [TestCase (TargetPlatform.WinPhone, ListViewCachingStrategy.RetainElement)]
+ public void EnforcesCachingStrategy (TargetPlatform platform, ListViewCachingStrategy expected)
+ {
+ var oldOS = Device.OS;
+ // we need to do this because otherwise we cant set the caching strategy
+ Device.OS = platform;
+ var listView = new ListView (ListViewCachingStrategy.RecycleElement);
+
+ Assert.AreEqual (expected, listView.CachingStrategy);
+ Device.OS = oldOS;
+ }
+
+ [Test]
+ public void DefaultCacheStrategy ()
+ {
+ var listView = new ListView ();
+
+ Assert.AreEqual (ListViewCachingStrategy.RetainElement, listView.CachingStrategy);
+ }
+
+ [Test]
+ public void DoesNotRetainInRecycleMode ()
+ {
+ var items = new ObservableCollection<string> {
+ "Foo",
+ "Bar"
+ };
+
+ var oldOS = Device.OS;
+ // we need to do this because otherwise we cant set the caching strategy
+ Device.OS = TargetPlatform.Android;
+
+ var bindable = new ListView (ListViewCachingStrategy.RecycleElement);
+ bindable.ItemTemplate = new DataTemplate (typeof (TextCell)) {
+ Bindings = {
+ { TextCell.TextProperty, new Binding (".") }
+ }
+ };
+
+ bindable.ItemsSource = items;
+ var item1 = bindable.TemplatedItems[0];
+ var item2 = bindable.TemplatedItems[0];
+
+ Assert.False(ReferenceEquals (item1, item2));
+
+ Device.OS = oldOS;
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/MapSpanTests.cs b/Xamarin.Forms.Core.UnitTests/MapSpanTests.cs
new file mode 100644
index 00000000..68645116
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/MapSpanTests.cs
@@ -0,0 +1,44 @@
+using NUnit.Framework;
+using Xamarin.Forms.Maps;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class MapSpanTests : BaseTestFixture
+ {
+ [Test]
+ public void Constructor ()
+ {
+ var span = new MapSpan (new Position(0, 0), 1, 1);
+
+ Assert.AreEqual (new Position (0, 0), span.Center);
+ Assert.AreEqual (1, span.LatitudeDegrees);
+ Assert.AreEqual (1, span.LongitudeDegrees);
+ Assert.IsTrue (span.Radius.Kilometers > 54 && span.Radius.Kilometers < 56);
+ }
+
+ [Test]
+ public void Equals ()
+ {
+ Assert.True (new MapSpan (new Position (1, 2), 3, 4) == new MapSpan (new Position (1, 2), 3, 4));
+ Assert.True (new MapSpan (new Position (1, 2), 3, 4) != new MapSpan (new Position (2, 3), 4, 5));
+ Assert.True (new MapSpan (new Position (1, 2), 3, 4).Equals (new MapSpan (new Position (1, 2), 3, 4)));
+ Assert.False (new MapSpan (new Position (1, 2), 3, 4).Equals ("MapSpan"));
+ Assert.False (new MapSpan (new Position (1, 2), 3, 4).Equals (null));
+ }
+
+ [Test]
+ public void HashCode ()
+ {
+ Assert.AreEqual (new MapSpan (new Position (1, 2), 3, 4).GetHashCode (), new MapSpan (new Position (1, 2), 3, 4).GetHashCode ());
+ }
+
+ [Test]
+ public void RangeClamping ()
+ {
+ var span = new MapSpan (new Position (0, 0), -1, -2);
+ Assert.IsTrue (span.LatitudeDegrees > 0);
+ Assert.IsTrue (span.LongitudeDegrees > 0);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/MapTests.cs b/Xamarin.Forms.Core.UnitTests/MapTests.cs
new file mode 100644
index 00000000..7c798b33
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/MapTests.cs
@@ -0,0 +1,147 @@
+using System;
+using NUnit.Framework;
+using Xamarin.Forms.Maps;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class MapTests : BaseTestFixture
+ {
+ [Test]
+ public void AddPin ()
+ {
+ var map = new Map ();
+
+ var home = new Pin {
+ Label = "Home",
+ Position = new Position (88, 2),
+ Type = PinType.Place,
+ Address = "123 My Place"
+ };
+
+ map.Pins.Add (home);
+
+ Assert.AreEqual (map.Pins.Count, 1);
+ Assert.AreEqual (map.Pins[0].Label, "Home");
+ var mall = new Pin {
+ Label = "Mall",
+ Position = new Position (-12, -67),
+ Type = PinType.Place,
+ Address = "123 Fun"
+ };
+
+ map.Pins.Add (mall);
+
+ Assert.AreEqual (map.Pins.Count, 2);
+ Assert.AreEqual (map.Pins[1].Position.Latitude, -12);
+ }
+
+ [Test]
+ public void AddPinWithoutName ()
+ {
+ var map = new Map ();
+ var noNamePin = new Pin {
+ Position = new Position (50, 50),
+ Type = PinType.Generic,
+ Address = "123 Fun"
+ };
+
+ var exception = Assert.Throws<ArgumentException> (() => map.Pins.Add (noNamePin));
+ Assert.That (exception.Message, Is.EqualTo ("Pin must have a Label to be added to a map"));
+ }
+
+ [Test]
+ public void AddPinWithoutAddress ()
+ {
+ var map = new Map ();
+ var noAddressPin = new Pin {
+ Position = new Position (37.9, -20.87),
+ Label = "I have no address",
+ Type = PinType.SearchResult
+ };
+
+ map.Pins.Add (noAddressPin);
+ Assert.AreEqual (map.Pins.Count, 1);
+ Assert.AreEqual (map.Pins[0].Label, "I have no address");
+ Assert.AreEqual (map.Pins[0].Address, null);
+ }
+
+ [Test]
+ public void Constructor ()
+ {
+ var center = new Position (15.5, 176);
+ var span = new MapSpan (center, 1, 2);
+ var map = new Map (span);
+
+ Assert.AreEqual (1, map.LastMoveToRegion.LatitudeDegrees);
+ Assert.AreEqual (2, map.LastMoveToRegion.LongitudeDegrees);
+ var position = new Position (15.5, 176);
+ Assert.AreEqual (position, map.LastMoveToRegion.Center);
+ }
+
+ [Test]
+ public void RemovePin ()
+ {
+ var map = new Map ();
+ var genericPlace = new Pin {
+ Label = "Generic",
+ Position = new Position (-12, -67),
+ Type = PinType.Generic,
+ Address = "XXX"
+ };
+
+ var mall = new Pin {
+ Label = "Mall",
+ Position = new Position (-29, -87),
+ Type = PinType.Place,
+ Address = "123 Fun"
+ };
+
+ map.Pins.Add (genericPlace);
+ Assert.AreEqual (map.Pins.Count, 1);
+
+ map.Pins.Add (mall);
+ Assert.AreEqual (map.Pins.Count, 2);
+
+ map.Pins.Remove (genericPlace);
+ Assert.AreEqual (map.Pins.Count, 1);
+
+ Assert.True (map.Pins.Contains (mall));
+ Assert.False (map.Pins.Contains (genericPlace));
+ }
+
+ [Test]
+ public void VisibleRegion ()
+ {
+ var map = new Map (new MapSpan (new Position (), 0, 0));
+ map.MoveToRegion (new MapSpan (new Position (1, 2), 3, 4));
+ Assert.AreEqual (null, map.VisibleRegion);
+
+ bool signaled = false;
+ MessagingCenter.Subscribe<Map, MapSpan> (this, "MapMoveToRegion", (s, a) => {
+ signaled = true;
+ map.VisibleRegion = a;
+ }, map);
+
+ map.MoveToRegion (new MapSpan (new Position (1, 2), 3, 4));
+ Assert.AreEqual (new MapSpan (new Position (1, 2), 3, 4), map.LastMoveToRegion);
+ Assert.True (signaled);
+ }
+
+ [Test]
+ public void VisibleRegionDoubleSet ()
+ {
+ var map = new Map ();
+
+ bool signaled = false;
+ map.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "VisibleRegion")
+ signaled = true;
+ };
+
+ map.VisibleRegion = map.VisibleRegion;
+
+ Assert.False (signaled);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/MarginTests.cs b/Xamarin.Forms.Core.UnitTests/MarginTests.cs
new file mode 100644
index 00000000..fd5debfa
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/MarginTests.cs
@@ -0,0 +1,134 @@
+using NUnit.Framework;
+using Xamarin.Forms;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ public class MarginTests : BaseTestFixture
+ {
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ }
+
+ [Test]
+ public void GetSizeRequestIncludesMargins ()
+ {
+ var platform = new UnitPlatform ((b, d, e) => new SizeRequest(new Size(100,50)));
+
+ var parent = new ContentView {
+ Platform = platform,
+ IsPlatformEnabled = true,
+ };
+ var child = new Button {
+ Text = "Test",
+ Platform = platform,
+ IsPlatformEnabled = true,
+ };
+
+
+ child.Margin = new Thickness (10, 20, 30, 40);
+ parent.Content = child;
+
+ var result = parent.Measure (double.PositiveInfinity, double.PositiveInfinity, MeasureFlags.IncludeMargins);
+ Assert.AreEqual (new Size (140, 110), result.Request);
+ }
+
+ [Test]
+ public void MarginsAffectPositionInContentView ()
+ {
+ var platform = new UnitPlatform ((b, d, e) => new SizeRequest(new Size(100,50)));
+
+ var parent = new ContentView {
+ Platform = platform,
+ IsPlatformEnabled = true,
+ };
+ var child = new Button {
+ Text = "Test",
+ Platform = platform,
+ IsPlatformEnabled = true,
+ };
+
+
+ child.Margin = new Thickness (10, 20, 30, 40);
+ parent.Content = child;
+
+ parent.Layout (new Rectangle (0, 0, 140, 110));
+ Assert.AreEqual (new Rectangle (10, 20, 100, 50), child.Bounds);
+ }
+
+ [Test]
+ public void ChangingMarginCausesRelayout ()
+ {
+ var platform = new UnitPlatform ((b, d, e) => new SizeRequest(new Size(100,50)));
+
+ var parent = new ContentView {
+ Platform = platform,
+ IsPlatformEnabled = true,
+ };
+ var child = new Button {
+ Text = "Test",
+ VerticalOptions = LayoutOptions.Start,
+ HorizontalOptions = LayoutOptions.Start,
+ Platform = platform,
+ IsPlatformEnabled = true,
+ };
+
+
+ child.Margin = new Thickness (10, 20, 30, 40);
+ parent.Content = child;
+
+ parent.Layout (new Rectangle (0, 0, 1000, 1000));
+ Assert.AreEqual (new Rectangle (10, 20, 100, 50), child.Bounds);
+ }
+
+ [Test]
+ public void IntegrationTest ()
+ {
+ var platform = new UnitPlatform ((b, d, e) => new SizeRequest(new Size(100,50)));
+
+ var parent = new StackLayout {
+ Platform = platform,
+ Spacing = 0,
+ IsPlatformEnabled = true,
+ };
+
+ var child1 = new Button {
+ Text = "Test",
+ VerticalOptions = LayoutOptions.Start,
+ HorizontalOptions = LayoutOptions.Start,
+ Platform = platform,
+ IsPlatformEnabled = true,
+ };
+
+ var child2 = new Button {
+ Text = "Test",
+ Platform = platform,
+ IsPlatformEnabled = true,
+ };
+
+ child2.Margin = new Thickness (5, 10, 15, 20);
+
+ parent.Children.Add (child1);
+ parent.Children.Add (child2);
+
+ parent.Layout (new Rectangle (0, 0, 1000, 1000));
+
+ Assert.AreEqual (new Rectangle (0, 0, 100, 50), child1.Bounds);
+ Assert.AreEqual (new Rectangle (5, 60, 980, 50), child2.Bounds);
+
+ child1.Margin = new Thickness (10, 20, 30, 40);
+
+ Assert.AreEqual (new Rectangle (10, 20, 100, 50), child1.Bounds);
+ Assert.AreEqual (new Rectangle (5, 120, 980, 50), child2.Bounds);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/MasterDetailFormUnitTests.cs b/Xamarin.Forms.Core.UnitTests/MasterDetailFormUnitTests.cs
new file mode 100644
index 00000000..3c1e27ad
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/MasterDetailFormUnitTests.cs
@@ -0,0 +1,415 @@
+using System;
+
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ internal class TestDeviceInfo : DeviceInfo
+ {
+ public TestDeviceInfo ()
+ {
+ CurrentOrientation = DeviceOrientation.Portrait;
+ }
+ public override Size PixelScreenSize
+ {
+ get { return new Size (100, 200); }
+ }
+
+ public override Size ScaledScreenSize
+ {
+ get { return new Size (50, 100); }
+ }
+
+ public override double ScalingFactor
+ {
+ get { return 2; }
+ }
+ }
+
+ [TestFixture]
+ public class MasterDetailPageUnitTests : BaseTestFixture
+ {
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup ();
+ var mockDeviceInfo = new TestDeviceInfo ();
+ Device.Info = mockDeviceInfo;
+ }
+
+ [Test]
+ public void TestConstructor ()
+ {
+
+ MasterDetailPage page = new MasterDetailPage ();
+
+ Assert.Null (page.Master);
+ Assert.Null (page.Detail);
+ }
+
+ [Test]
+ public void TestMasterSetter ()
+ {
+ MasterDetailPage page = new MasterDetailPage ();
+ var child = new ContentPage {Content = new Label (), Title = "Foo"};
+ page.Master = child;
+
+ Assert.AreEqual (child, page.Master);
+ }
+
+ [Test]
+ public void TestMasterSetNull ()
+ {
+ MasterDetailPage page = new MasterDetailPage ();
+ var child = new ContentPage {Content = new Label (), Title = "Foo"};
+ page.Master = child;
+
+ Assert.Throws<ArgumentNullException> (() => { page.Master = null; });
+ }
+
+ [Test]
+ public void TestMasterChanged ()
+ {
+ MasterDetailPage page = new MasterDetailPage ();
+ var child = new ContentPage {Content = new Label (), Title = "Foo"};
+
+ bool changed = false;
+ page.PropertyChanged += (sender, e) => {
+ if (e.PropertyName == "Master")
+ changed = true;
+ };
+
+ page.Master = child;
+
+ Assert.True (changed);
+ }
+
+ [Test]
+ public void TestDetailSetter ()
+ {
+ MasterDetailPage page = new MasterDetailPage ();
+ var child = new ContentPage {Content = new Label ()};
+ page.Detail = child;
+
+ Assert.AreEqual (child, page.Detail);
+ }
+
+ [Test]
+ public void TestDetailSetNull ()
+ {
+ MasterDetailPage page = new MasterDetailPage ();
+ var child = new ContentPage {Content = new Label ()};
+ page.Detail = child;
+
+ Assert.Throws<ArgumentNullException> ( () => { page.Detail = null; });
+ }
+
+ [Test]
+ public void TestDetailChanged ()
+ {
+ MasterDetailPage page = new MasterDetailPage ();
+ var child = new ContentPage {Content = new Label ()};
+
+ bool changed = false;
+ page.PropertyChanged += (sender, e) => {
+ if (e.PropertyName == "Detail")
+ changed = true;
+ };
+
+ page.Detail = child;
+
+ Assert.True (changed);
+ }
+
+ [Test]
+ public void ThrowsWhenMasterSetWithoutValidTitle ([Values (null, "")] string title)
+ {
+ var page = new MasterDetailPage ();
+ Assert.Throws<InvalidOperationException> (() => page.Master = new ContentPage {Title = title});
+ }
+
+ [Test]
+ public void TestThrowsWhenPackedWithoutSetting ()
+ {
+ MasterDetailPage page = new MasterDetailPage ();
+ Assert.Throws<InvalidOperationException> (() => new TabbedPage {Children = {page}});
+ }
+
+ [Test]
+ public void TestDoesNotThrowWhenPackedWithSetting ()
+ {
+ MasterDetailPage page = new MasterDetailPage {
+ Master = new ContentPage {Content = new View (), Title = "Foo"},
+ Detail = new ContentPage {Content = new View ()}
+ };
+ Assert.DoesNotThrow (() => new TabbedPage {Children = {page}});
+ }
+
+ [Test]
+ public void TestMasterVisible ()
+ {
+ var page = new MasterDetailPage ();
+
+ Assert.AreEqual (false, page.IsPresented);
+
+ bool signaled = false;
+ page.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == MasterDetailPage.IsPresentedProperty.PropertyName)
+ signaled = true;
+ };
+
+ page.IsPresented = true;
+
+ Assert.AreEqual (true, page.IsPresented);
+ Assert.True (signaled);
+ }
+
+ [Test]
+ public void TestMasterVisibleDoubleSet ()
+ {
+ var page = new MasterDetailPage ();
+
+ bool signaled = false;
+ page.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == MasterDetailPage.IsPresentedProperty.PropertyName)
+ signaled = true;
+ };
+
+ page.IsPresented = page.IsPresented;
+
+ Assert.False (signaled);
+ }
+
+ [Test]
+ public void TestSetMasterBounds ()
+ {
+ var page = new MasterDetailPage {
+ Master = new ContentPage {Content = new View (), Title = "Foo"},
+ Detail = new ContentPage {Content = new View ()}
+ };
+
+ page.MasterBounds = new Rectangle (0, 0, 100, 100);
+ Assert.AreEqual (new Rectangle (0, 0, 100, 100), page.Master.Bounds);
+ Assert.AreEqual (new Rectangle (0, 0, 100, 100), page.MasterBounds);
+ }
+
+ [Test]
+ public void TestSetDetailBounds ()
+ {
+ var page = new MasterDetailPage {
+ Master = new ContentPage {Content = new View (), Title = "Foo"},
+ Detail = new ContentPage {Content = new View ()}
+ };
+
+ page.DetailBounds = new Rectangle (0, 0, 100, 100);
+ Assert.AreEqual (new Rectangle (0, 0, 100, 100), page.Detail.Bounds);
+ Assert.AreEqual (new Rectangle (0, 0, 100, 100), page.DetailBounds);
+ }
+
+ [Test]
+ public void TestLayoutChildren ()
+ {
+ var page = new MasterDetailPage {
+ Master = new ContentPage { Content = new View (), IsPlatformEnabled = true, Title = "Foo" },
+ Detail = new ContentPage { Content = new View (), IsPlatformEnabled = true },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ page.MasterBounds = new Rectangle (0, 0, 100, 200);
+ page.DetailBounds = new Rectangle (0, 0, 100, 100);
+
+ page.Master.Layout (new Rectangle(0, 0, 1, 1));
+ page.Detail.Layout (new Rectangle(0, 0, 1, 1));
+
+ page.Layout (new Rectangle (0, 0, 200, 200));
+
+ Assert.AreEqual (new Rectangle (0, 0, 100, 200), page.Master.Bounds);
+ Assert.AreEqual (new Rectangle (0, 0, 100, 100), page.Detail.Bounds);
+ }
+
+ [Test]
+ public void ThorwsInLayoutChildrenWithNullDetail ()
+ {
+ var page = new MasterDetailPage {
+ Master = new ContentPage { Content = new View (), IsPlatformEnabled = true, Title = "Foo" },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ Assert.Throws<InvalidOperationException> (() => page.Layout (new Rectangle (0, 0, 200, 200)));
+ }
+
+ [Test]
+ public void ThorwsInLayoutChildrenWithNullMaster ()
+ {
+ var page = new MasterDetailPage {
+ Detail = new ContentPage { Content = new View (), IsPlatformEnabled = true },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ Assert.Throws<InvalidOperationException> (() => page.Layout (new Rectangle(0, 0, 200, 200)));
+ }
+
+ [Test]
+ public void ThorwsInSetDetailBoundsWithNullDetail ()
+ {
+ var page = new MasterDetailPage {
+ Master = new ContentPage {Content = new View (), Title = "Foo"},
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ Assert.Throws<InvalidOperationException> (() => page.DetailBounds = new Rectangle(0, 0, 200, 200));
+ }
+
+ [Test]
+ public void ThrowsInSetMasterBoundsWithNullMaster ()
+ {
+ var page = new MasterDetailPage {
+ Detail = new ContentPage {Content = new View ()},
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ Assert.Throws<InvalidOperationException> (() => page.MasterBounds = new Rectangle(0, 0, 200, 200));
+ }
+
+ [Test]
+ public void ThrowsInSetIsPresentOnSplitModeOnTablet ()
+ {
+ Device.Idiom = TargetIdiom.Tablet;
+ var page = new MasterDetailPage {
+ Master = new ContentPage { Content = new View (), IsPlatformEnabled = true, Title = "Foo" },
+ Detail = new ContentPage { Content = new View (), IsPlatformEnabled = true },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform (),
+ MasterBehavior = MasterBehavior.Split
+ };
+
+ Assert.Throws<InvalidOperationException> (() => page.IsPresented = false);
+ }
+
+ [Test]
+ public void ThorwsInSetIsPresentOnSplitPortraitModeOnTablet ()
+ {
+ Device.Idiom = TargetIdiom.Tablet;
+ Device.Info.CurrentOrientation = DeviceOrientation.Portrait;
+
+ var page = new MasterDetailPage {
+ Master = new ContentPage { Content = new View (), IsPlatformEnabled = true, Title = "Foo" },
+ Detail = new ContentPage { Content = new View (), IsPlatformEnabled = true },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform (),
+ MasterBehavior = MasterBehavior.SplitOnPortrait
+ };
+
+ Assert.Throws<InvalidOperationException> (() => page.IsPresented = false);
+ }
+
+ [Test]
+ public void TestSetIsPresentedOnPopoverMode ()
+ {
+ Device.Info.CurrentOrientation = DeviceOrientation.Landscape;
+
+ var page = new MasterDetailPage {
+ Master = new ContentPage { Content = new View (), IsPlatformEnabled = true, Title = "Foo" },
+ Detail = new ContentPage { Content = new View (), IsPlatformEnabled = true },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform (),
+ MasterBehavior = MasterBehavior.Popover
+ };
+ page.IsPresented = true;
+
+ Assert.AreEqual (true, page.IsPresented);
+ }
+
+ [Test]
+ public void SendsBackEventToPresentedMasterFirst ()
+ {
+ var detail = new BackButtonPage () {Handle = true};
+ var master = new BackButtonPage () {Title = "Master"};
+ var mdp = new MasterDetailPage () {
+ Detail = detail,
+ Master = master,
+ IsPresented = true,
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ mdp.BackButtonPressed += (sender, args) => {
+ args.Handled = mdp.IsPresented;
+ mdp.IsPresented = false;
+ };
+
+ var detailEmitted = false;
+ var masterEmitted = false;
+
+ detail.BackPressed += (sender, args) => detailEmitted = true;
+ master.BackPressed += (sender, args) => masterEmitted = true;
+
+ var result = mdp.SendBackButtonPressed ();
+
+ Assert.True (masterEmitted);
+ Assert.False (detailEmitted);
+ Assert.True (result);
+ }
+
+ [Test]
+ public void EmitsCorrectlyWhenPresentedOnBackPressed ()
+ {
+ var detail = new BackButtonPage ();
+ var master = new BackButtonPage { Title = "Master" };
+ var mdp = new MasterDetailPage {
+ Detail = detail,
+ Master = master,
+ IsPresented = true,
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ mdp.BackButtonPressed += (sender, args) => {
+ args.Handled = mdp.IsPresented;
+ mdp.IsPresented = false;
+ };
+
+ var detailEmitted = false;
+ var masterEmitted = false;
+
+ detail.BackPressed += (sender, args) => detailEmitted = true;
+ master.BackPressed += (sender, args) => masterEmitted = true;
+
+ var result = mdp.SendBackButtonPressed ();
+
+ Assert.True (masterEmitted);
+ Assert.False (detailEmitted);
+ Assert.True (result);
+ }
+
+ [Test]
+ public void ThrowsExceptionWhenAddingAlreadyParentedDetail ()
+ {
+ var detail = new ContentPage {};
+
+ // give detail a parent
+ var nav = new NavigationPage (detail);
+
+ var mdp = new MasterDetailPage ();
+ Assert.Throws<InvalidOperationException> (() => mdp.Detail = detail);
+ }
+
+ [Test]
+ public void ThrowsExceptionWhenAddingAlreadyParentedMaster ()
+ {
+ var master = new ContentPage { Title = "Foo" };
+
+ // give master a parent
+ var nav = new NavigationPage (master);
+
+ var mdp = new MasterDetailPage ();
+ Assert.Throws<InvalidOperationException> (() => mdp.Master = master);
+ }
+ }
+
+}
diff --git a/Xamarin.Forms.Core.UnitTests/MenuItemTests.cs b/Xamarin.Forms.Core.UnitTests/MenuItemTests.cs
new file mode 100644
index 00000000..d6163058
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/MenuItemTests.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ public class MenuItemTests
+ : MenuItemTests<MenuItem>
+ {
+ }
+
+ [TestFixture]
+ public abstract class MenuItemTests<T>
+ : CommandSourceTests<T>
+ where T : MenuItem, new()
+ {
+ [Test]
+ public void Activated()
+ {
+ var item = new MenuItem();
+
+ bool activated = false;
+ item.Clicked += (sender, args) => activated = true;
+
+ item.Activate();
+
+ Assert.That (activated, Is.True);
+ }
+
+ [Test]
+ public void Command()
+ {
+ bool executed = false;
+ var param = new object();
+
+ var c = new Command (o => {
+ Assert.That (o, Is.SameAs (param));
+ executed = true;
+ });
+
+ var item = new MenuItem { Command = c, CommandParameter = param };
+ item.Activate();
+
+ Assert.That (executed, Is.True);
+ }
+
+ protected override T CreateSource()
+ {
+ return new T();
+ }
+
+ protected override void Activate (T source)
+ {
+ source.Activate();
+ }
+
+ protected override BindableProperty IsEnabledProperty
+ {
+ get { return MenuItem.IsEnabledProperty; }
+ }
+
+ protected override BindableProperty CommandProperty
+ {
+ get { return MenuItem.CommandProperty; }
+ }
+
+ protected override BindableProperty CommandParameterProperty
+ {
+ get { return MenuItem.CommandParameterProperty; }
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/MessagingCenterTests.cs b/Xamarin.Forms.Core.UnitTests/MessagingCenterTests.cs
new file mode 100644
index 00000000..46dfdcdb
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/MessagingCenterTests.cs
@@ -0,0 +1,191 @@
+using System;
+using NUnit.Framework;
+
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class MessagingCenterTests : BaseTestFixture
+ {
+ [Test]
+ public void SingleSubscriber ()
+ {
+ string sentMessage = null;
+ MessagingCenter.Subscribe<MessagingCenterTests, string> (this, "SimpleTest", (sender, args) => sentMessage = args);
+
+ MessagingCenter.Send (this, "SimpleTest", "My Message");
+
+ Assert.That (sentMessage, Is.EqualTo ("My Message"));
+
+ MessagingCenter.Unsubscribe<MessagingCenterTests, string> (this, "SimpleTest");
+ }
+
+ [Test]
+ public void Filter ()
+ {
+ string sentMessage = null;
+ MessagingCenter.Subscribe<MessagingCenterTests, string> (this, "SimpleTest", (sender, args) => sentMessage = args, this);
+
+ MessagingCenter.Send (new MessagingCenterTests (), "SimpleTest", "My Message");
+
+ Assert.That (sentMessage, Is.Null);
+
+ MessagingCenter.Send (this, "SimpleTest", "My Message");
+
+ Assert.That (sentMessage, Is.EqualTo ("My Message"));
+
+ MessagingCenter.Unsubscribe<MessagingCenterTests, string> (this, "SimpleTest");
+ }
+
+ [Test]
+ public void MultiSubscriber ()
+ {
+ var sub1 = new object ();
+ var sub2 = new object ();
+ string sentMessage1 = null;
+ string sentMessage2 = null;
+ MessagingCenter.Subscribe<MessagingCenterTests, string> (sub1, "SimpleTest", (sender, args) => sentMessage1 = args);
+ MessagingCenter.Subscribe<MessagingCenterTests, string> (sub2, "SimpleTest", (sender, args) => sentMessage2 = args);
+
+ MessagingCenter.Send (this, "SimpleTest", "My Message");
+
+ Assert.That (sentMessage1, Is.EqualTo ("My Message"));
+ Assert.That (sentMessage2, Is.EqualTo ("My Message"));
+
+ MessagingCenter.Unsubscribe<MessagingCenterTests, string> (sub1, "SimpleTest");
+ MessagingCenter.Unsubscribe<MessagingCenterTests, string> (sub2, "SimpleTest");
+ }
+
+ [Test]
+ public void Unsubscribe ()
+ {
+ string sentMessage = null;
+ MessagingCenter.Subscribe<MessagingCenterTests, string> (this, "SimpleTest", (sender, args) => sentMessage = args);
+ MessagingCenter.Unsubscribe<MessagingCenterTests, string> (this, "SimpleTest");
+
+ MessagingCenter.Send (this, "SimpleTest", "My Message");
+
+ Assert.That (sentMessage, Is.EqualTo (null));
+ }
+
+ [Test]
+ public void SendWithoutSubscribers ()
+ {
+ Assert.DoesNotThrow (() => MessagingCenter.Send (this, "SimpleTest", "My Message"));
+ }
+
+ [Test]
+ public void NoArgSingleSubscriber ()
+ {
+ bool sentMessage = false;
+ MessagingCenter.Subscribe<MessagingCenterTests> (this, "SimpleTest", sender => sentMessage = true);
+
+ MessagingCenter.Send (this, "SimpleTest");
+
+ Assert.That (sentMessage, Is.True);
+
+ MessagingCenter.Unsubscribe<MessagingCenterTests> (this, "SimpleTest");
+ }
+
+ [Test]
+ public void NoArgFilter ()
+ {
+ bool sentMessage = false;
+ MessagingCenter.Subscribe (this, "SimpleTest", (sender) => sentMessage = true, this);
+
+ MessagingCenter.Send (new MessagingCenterTests (), "SimpleTest");
+
+ Assert.That (sentMessage, Is.False);
+
+ MessagingCenter.Send (this, "SimpleTest");
+
+ Assert.That (sentMessage, Is.True);
+
+ MessagingCenter.Unsubscribe<MessagingCenterTests> (this, "SimpleTest");
+ }
+
+ [Test]
+ public void NoArgMultiSubscriber ()
+ {
+ var sub1 = new object ();
+ var sub2 = new object ();
+ bool sentMessage1 = false;
+ bool sentMessage2 = false;
+ MessagingCenter.Subscribe<MessagingCenterTests> (sub1, "SimpleTest", (sender) => sentMessage1 = true);
+ MessagingCenter.Subscribe<MessagingCenterTests> (sub2, "SimpleTest", (sender) => sentMessage2 = true);
+
+ MessagingCenter.Send (this, "SimpleTest");
+
+ Assert.That (sentMessage1, Is.True);
+ Assert.That (sentMessage2, Is.True);
+
+ MessagingCenter.Unsubscribe<MessagingCenterTests> (sub1, "SimpleTest");
+ MessagingCenter.Unsubscribe<MessagingCenterTests> (sub2, "SimpleTest");
+ }
+
+ [Test]
+ public void NoArgUnsubscribe ()
+ {
+ bool sentMessage = false;
+ MessagingCenter.Subscribe<MessagingCenterTests> (this, "SimpleTest", (sender) => sentMessage = true);
+ MessagingCenter.Unsubscribe<MessagingCenterTests> (this, "SimpleTest");
+
+ MessagingCenter.Send (this, "SimpleTest", "My Message");
+
+ Assert.That (sentMessage, Is.False);
+ }
+
+ [Test]
+ public void NoArgSendWithoutSubscribers ()
+ {
+ Assert.DoesNotThrow (() => MessagingCenter.Send (this, "SimpleTest"));
+ }
+
+ [Test]
+ public void ThrowOnNullArgs ()
+ {
+ Assert.Throws<ArgumentNullException> (() => MessagingCenter.Subscribe<MessagingCenterTests, string> (null, "Foo", (sender, args) => { }));
+ Assert.Throws<ArgumentNullException> (() => MessagingCenter.Subscribe<MessagingCenterTests, string> (this, null, (sender, args) => { }));
+ Assert.Throws<ArgumentNullException> (() => MessagingCenter.Subscribe<MessagingCenterTests, string> (this, "Foo", null));
+
+ Assert.Throws<ArgumentNullException> (() => MessagingCenter.Subscribe<MessagingCenterTests> (null, "Foo", (sender) => { }));
+ Assert.Throws<ArgumentNullException> (() => MessagingCenter.Subscribe<MessagingCenterTests> (this, null, (sender) => { }));
+ Assert.Throws<ArgumentNullException> (() => MessagingCenter.Subscribe<MessagingCenterTests> (this, "Foo", null));
+
+ Assert.Throws<ArgumentNullException> (() => MessagingCenter.Send<MessagingCenterTests, string> (null, "Foo", "Bar"));
+ Assert.Throws<ArgumentNullException> (() => MessagingCenter.Send<MessagingCenterTests, string> (this, null, "Bar"));
+
+ Assert.Throws<ArgumentNullException> (() => MessagingCenter.Send<MessagingCenterTests> (null, "Foo"));
+ Assert.Throws<ArgumentNullException> (() => MessagingCenter.Send<MessagingCenterTests> (this, null));
+
+ Assert.Throws<ArgumentNullException> (() => MessagingCenter.Unsubscribe<MessagingCenterTests> (null, "Foo"));
+ Assert.Throws<ArgumentNullException> (() => MessagingCenter.Unsubscribe<MessagingCenterTests> (this, null));
+
+ Assert.Throws<ArgumentNullException> (() => MessagingCenter.Unsubscribe<MessagingCenterTests, string> (null, "Foo"));
+ Assert.Throws<ArgumentNullException> (() => MessagingCenter.Unsubscribe<MessagingCenterTests, string> (this, null));
+ }
+
+ [Test]
+ public void UnsubscribeInCallback ()
+ {
+ int messageCount = 0;
+
+ var subscriber1 = new object ();
+ var subscriber2 = new object ();
+
+ MessagingCenter.Subscribe<MessagingCenterTests> (subscriber1, "SimpleTest", (sender) => {
+ messageCount++;
+ MessagingCenter.Unsubscribe<MessagingCenterTests> (subscriber2, "SimpleTest");
+ });
+
+ MessagingCenter.Subscribe<MessagingCenterTests> (subscriber2, "SimpleTest", (sender) => {
+ messageCount++;
+ MessagingCenter.Unsubscribe<MessagingCenterTests> (subscriber1, "SimpleTest");
+ });
+
+ MessagingCenter.Send (this, "SimpleTest");
+
+ Assert.AreEqual (1, messageCount);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/MockPlatformServices.cs b/Xamarin.Forms.Core.UnitTests/MockPlatformServices.cs
new file mode 100644
index 00000000..7d2317b3
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/MockPlatformServices.cs
@@ -0,0 +1,270 @@
+using System;
+using System.Threading.Tasks;
+using System.IO;
+using System.Threading;
+using System.Reflection;
+using System.IO.IsolatedStorage;
+using System.Collections.Generic;
+using Xamarin.Forms;
+using Xamarin.Forms.Core.UnitTests;
+using System.Security.Cryptography;
+using System.Text;
+#if WINDOWS_PHONE
+using Xamarin.Forms.Platform.WinPhone;
+#endif
+
+[assembly:Dependency (typeof(MockDeserializer))]
+[assembly:Dependency (typeof(MockResourcesProvider))]
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ internal class MockPlatformServices : IPlatformServices
+ {
+ Action<Action> invokeOnMainThread;
+ Action<Uri> openUriAction;
+ Func<Uri, CancellationToken, Task<Stream>> getStreamAsync;
+ public MockPlatformServices (Action<Action> invokeOnMainThread = null, Action<Uri> openUriAction = null, Func<Uri, CancellationToken, Task<Stream>> getStreamAsync = null)
+ {
+ this.invokeOnMainThread = invokeOnMainThread;
+ this.openUriAction = openUriAction;
+ this.getStreamAsync = getStreamAsync;
+ }
+
+ static MD5CryptoServiceProvider checksum = new MD5CryptoServiceProvider ();
+
+ public string GetMD5Hash (string input)
+ {
+ var bytes = checksum.ComputeHash (Encoding.UTF8.GetBytes (input));
+ var ret = new char [32];
+ for (int i = 0; i < 16; i++){
+ ret [i*2] = (char)hex (bytes [i] >> 4);
+ ret [i*2+1] = (char)hex (bytes [i] & 0xf);
+ }
+ return new string (ret);
+ }
+ static int hex (int v)
+ {
+ if (v < 10)
+ return '0' + v;
+ return 'a' + v-10;
+ }
+
+ public double GetNamedSize (NamedSize size, Type targetElement, bool useOldSizes)
+ {
+ switch (size) {
+ case NamedSize.Default:
+ return 10;
+ case NamedSize.Micro:
+ return 4;
+ case NamedSize.Small:
+ return 8;
+ case NamedSize.Medium:
+ return 12;
+ case NamedSize.Large:
+ return 16;
+ default:
+ throw new ArgumentOutOfRangeException ("size");
+ }
+ }
+
+ public void OpenUriAction (Uri uri)
+ {
+ if (openUriAction != null)
+ openUriAction (uri);
+ else
+ throw new NotImplementedException ();
+ }
+
+ public bool IsInvokeRequired
+ {
+ get { return false; }
+ }
+
+ public void BeginInvokeOnMainThread (Action action)
+ {
+ if (invokeOnMainThread == null)
+ action ();
+ else
+ invokeOnMainThread (action);
+ }
+
+ public void StartTimer (TimeSpan interval, Func<bool> callback)
+ {
+ Timer timer = null;
+ TimerCallback onTimeout = o => BeginInvokeOnMainThread (() => {
+ if (callback ())
+ return;
+
+ timer.Dispose ();
+ });
+ timer = new Timer (onTimeout, null, interval, interval);
+ }
+
+ public Task<Stream> GetStreamAsync (Uri uri, CancellationToken cancellationToken)
+ {
+ if (getStreamAsync == null)
+ throw new NotImplementedException ();
+ return getStreamAsync (uri, cancellationToken);
+ }
+
+ public Assembly[] GetAssemblies ()
+ {
+ return AppDomain.CurrentDomain.GetAssemblies ();
+ }
+
+ public ITimer CreateTimer (Action<object> callback)
+ {
+ return new MockTimer (new Timer (o => callback(o)));
+ }
+
+ public ITimer CreateTimer (Action<object> callback, object state, int dueTime, int period)
+ {
+ return new MockTimer (new Timer (o => callback(o), state, dueTime, period));
+ }
+
+ public ITimer CreateTimer (Action<object> callback, object state, long dueTime, long period)
+ {
+ return new MockTimer (new Timer (o => callback(o), state, dueTime, period));
+ }
+
+ public ITimer CreateTimer (Action<object> callback, object state, TimeSpan dueTime, TimeSpan period)
+ {
+ return new MockTimer (new Timer (o => callback(o), state, dueTime, period));
+ }
+
+ public ITimer CreateTimer (Action<object> callback, object state, uint dueTime, uint period)
+ {
+ return new MockTimer (new Timer (o => callback(o), state, dueTime, period));
+ }
+
+ public class MockTimer : ITimer
+ {
+ readonly Timer timer;
+ public MockTimer (Timer timer)
+ {
+ this.timer = timer;
+ }
+
+ public void Change (int dueTime, int period)
+ {
+ timer.Change (dueTime, period);
+ }
+ public void Change (long dueTime, long period)
+ {
+ timer.Change (dueTime, period);
+ }
+ public void Change (TimeSpan dueTime, TimeSpan period)
+ {
+ timer.Change (dueTime, period);
+ }
+ public void Change (uint dueTime, uint period)
+ {
+ timer.Change (dueTime, period);
+ }
+ }
+
+ public IIsolatedStorageFile GetUserStoreForApplication ()
+ {
+#if WINDOWS_PHONE
+ return new MockIsolatedStorageFile (IsolatedStorageFile.GetUserStoreForApplication ());
+#else
+ return new MockIsolatedStorageFile (IsolatedStorageFile.GetUserStoreForAssembly ());
+#endif
+ }
+
+ public class MockIsolatedStorageFile : IIsolatedStorageFile
+ {
+ readonly IsolatedStorageFile isolatedStorageFile;
+ public MockIsolatedStorageFile (IsolatedStorageFile isolatedStorageFile)
+ {
+ this.isolatedStorageFile = isolatedStorageFile;
+ }
+
+ public Task<bool> GetDirectoryExistsAsync (string path)
+ {
+ return Task.FromResult (isolatedStorageFile.DirectoryExists (path));
+ }
+
+ public Task CreateDirectoryAsync (string path)
+ {
+ isolatedStorageFile.CreateDirectory (path);
+ return Task.FromResult (true);
+ }
+
+ public Task<Stream> OpenFileAsync (string path, FileMode mode, FileAccess access)
+ {
+ Stream stream = isolatedStorageFile.OpenFile (path, (System.IO.FileMode)mode, (System.IO.FileAccess)access);
+ return Task.FromResult (stream);
+ }
+
+ public Task<Stream> OpenFileAsync (string path, FileMode mode, FileAccess access, FileShare share)
+ {
+ Stream stream = isolatedStorageFile.OpenFile (path, (System.IO.FileMode)mode, (System.IO.FileAccess)access, (System.IO.FileShare)share);
+ return Task.FromResult (stream);
+ }
+
+ public Task<bool> GetFileExistsAsync (string path)
+ {
+ return Task.FromResult (isolatedStorageFile.FileExists (path));
+ }
+
+ public Task<DateTimeOffset> GetLastWriteTimeAsync (string path)
+ {
+ return Task.FromResult (isolatedStorageFile.GetLastWriteTime (path));
+ }
+ }
+ }
+
+ internal class MockDeserializer : IDeserializer
+ {
+ public Task<IDictionary<string, object>> DeserializePropertiesAsync ()
+ {
+ return Task.FromResult<IDictionary<string, object>> (new Dictionary<string,object> ());
+ }
+
+ public Task SerializePropertiesAsync (IDictionary<string, object> properties)
+ {
+ return Task.FromResult (false);
+ }
+ }
+
+ internal class MockResourcesProvider : ISystemResourcesProvider
+ {
+ public IResourceDictionary GetSystemResources ()
+ {
+ var dictionary = new ResourceDictionary ();
+ Style style;
+ style = new Style (typeof(Label));
+ dictionary [Device.Styles.BodyStyleKey] = style;
+
+ style = new Style (typeof(Label));
+ style.Setters.Add (Label.FontSizeProperty, 50);
+ dictionary [Device.Styles.TitleStyleKey] = style;
+
+ style = new Style (typeof(Label));
+ style.Setters.Add (Label.FontSizeProperty, 40);
+ dictionary [Device.Styles.SubtitleStyleKey] = style;
+
+ style = new Style (typeof(Label));
+ style.Setters.Add (Label.FontSizeProperty, 30);
+ dictionary [Device.Styles.CaptionStyleKey] = style;
+
+ style = new Style (typeof(Label));
+ style.Setters.Add (Label.FontSizeProperty, 20);
+ dictionary [Device.Styles.ListItemTextStyleKey] = style;
+
+ style = new Style (typeof(Label));
+ style.Setters.Add (Label.FontSizeProperty, 10);
+ dictionary [Device.Styles.ListItemDetailTextStyleKey] = style;
+
+ return dictionary;
+ }
+ }
+
+ public class MockApplication : Application
+ {
+ public MockApplication ()
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/MockViewModel.cs b/Xamarin.Forms.Core.UnitTests/MockViewModel.cs
new file mode 100644
index 00000000..da2f6f96
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/MockViewModel.cs
@@ -0,0 +1,30 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ internal class MockViewModel
+ : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ string text;
+ public virtual string Text {
+ get { return text; }
+ set {
+ if (text == value)
+ return;
+
+ text = value;
+ OnPropertyChanged ("Text");
+ }
+ }
+
+ protected void OnPropertyChanged ([CallerMemberName] string propertyName = null)
+ {
+ PropertyChangedEventHandler handler = PropertyChanged;
+ if (handler != null)
+ handler (this, new PropertyChangedEventArgs (propertyName));
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/MotionTests.cs b/Xamarin.Forms.Core.UnitTests/MotionTests.cs
new file mode 100644
index 00000000..acb764d7
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/MotionTests.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ internal class BlockingTicker : Ticker
+ {
+ bool enabled;
+
+ protected override void EnableTimer ()
+ {
+ enabled = true;
+
+ while (enabled) {
+ SendSignals (16);
+ }
+ }
+
+ protected override void DisableTimer ()
+ {
+ enabled = false;
+ }
+ }
+
+ [TestFixture]
+ public class MotionTests : BaseTestFixture
+ {
+ [TestFixtureSetUp]
+ public void Init ()
+ {
+ Device.PlatformServices = new MockPlatformServices ();
+ Ticker.Default = new BlockingTicker ();
+ }
+
+ [TestFixtureTearDown]
+ public void End ()
+ {
+ Device.PlatformServices = null;
+ Ticker.Default = null;
+ }
+
+ [Test]
+ public void TestLinearTween ()
+ {
+ var tweener = new Tweener (250);
+
+ double value = 0;
+ int updates = 0;
+ tweener.ValueUpdated += (sender, args) => {
+ Assert.That (tweener.Value, Is.GreaterThanOrEqualTo (value));
+ value = tweener.Value;
+ updates++;
+ };
+ tweener.Start ();
+
+ Assert.That (updates, Is.GreaterThanOrEqualTo (10));
+ }
+
+ [Test]
+ public void ThrowsWithNullCallback ()
+ {
+ Assert.Throws<ArgumentNullException> (() => new View().Animate ("Test", (Action<double>) null));
+ }
+
+ [Test]
+ public void ThrowsWithNullTransform ()
+ {
+ Assert.Throws<ArgumentNullException> (() => new View().Animate<float> ("Test", null, f => { }));
+ }
+
+ [Test]
+ public void ThrowsWithNullSelf ()
+ {
+ Assert.Throws<ArgumentNullException> (() => AnimationExtensions.Animate (null, "Foo", d => (float)d, f => { }));
+ }
+
+ [Test]
+ public void Kinetic ()
+ {
+ var view = new View ();
+ var resultList = new List<Tuple<double, double>> ();
+ view.AnimateKinetic (
+ name: "Kinetics",
+ callback: (distance, velocity) => {
+ resultList.Add (new Tuple<double, double> (distance, velocity));
+ return true;
+ },
+ velocity: 100,
+ drag: 1);
+
+ Assert.That (resultList, Is.Not.Empty);
+ int checkVelo = 100;
+ int dragStep = 16;
+
+ foreach (var item in resultList) {
+ checkVelo -= dragStep;
+ Assert.AreEqual (checkVelo, item.Item2);
+ Assert.AreEqual (checkVelo * dragStep, item.Item1);
+ }
+ }
+
+ [Test]
+ public void KineticFinished ()
+ {
+ var view = new View ();
+ bool finished = false;
+ view.AnimateKinetic (
+ name: "Kinetics",
+ callback: (distance, velocity) => true,
+ velocity: 100,
+ drag: 1,
+ finished: () => finished = true);
+
+ Assert.True (finished);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/MultiPageTests.cs b/Xamarin.Forms.Core.UnitTests/MultiPageTests.cs
new file mode 100644
index 00000000..3d9faad5
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/MultiPageTests.cs
@@ -0,0 +1,805 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Collections.Specialized;
+using System.Linq;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ public abstract class MultiPageTests<T> : BaseTestFixture
+ where T : Page
+ {
+ protected abstract MultiPage<T> CreateMultiPage();
+ protected abstract T CreateContainedPage();
+ protected abstract int GetIndex (T page);
+
+ [SetUp]
+ public override void Setup()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ }
+
+ [Test]
+ public void TestSetChildren ()
+ {
+ var container = CreateMultiPage();
+ var page = (Page) container;
+
+ int childCount = 0;
+ page.ChildAdded += (sender, args) => childCount++;
+
+ int pagesAdded = 0;
+ container.PagesChanged += (sender, args) => {
+ if (args.Action == NotifyCollectionChangedAction.Add)
+ pagesAdded++;
+ };
+
+ container.Children.Add (CreateContainedPage());
+ container.Children.Add (CreateContainedPage());
+
+ Assert.AreEqual (2, childCount);
+ Assert.AreEqual (2, page.LogicalChildren.Count);
+ Assert.AreEqual (2, pagesAdded);
+ }
+
+ [Test]
+ public void TestOverwriteChildren()
+ {
+ var page = CreateMultiPage();
+ page.Children.Add (CreateContainedPage());
+ page.Children.Add (CreateContainedPage());
+
+ int childCount = 0;
+ int removeCount = 0;
+ page.ChildAdded += (sender, args) => childCount++;
+ page.ChildRemoved += (sender, args) => removeCount++;
+
+ foreach (var child in page.Children.ToArray())
+ page.Children.Remove ((T)child);
+
+ page.Children.Add (CreateContainedPage());
+ page.Children.Add (CreateContainedPage());
+
+ Assert.AreEqual (2, removeCount);
+ Assert.AreEqual (2, childCount);
+ Assert.AreEqual (2, page.LogicalChildren.Count);
+ }
+
+ [Test]
+ public void CurrentPageSetAfterAdd()
+ {
+ var page = CreateMultiPage();
+ Assert.That (page.CurrentPage, Is.Null);
+
+ var child = CreateContainedPage();
+
+ bool property = false;
+ page.PropertyChanged += (o, e) => {
+ if (e.PropertyName == "CurrentPage")
+ property = true;
+ };
+
+ page.Children.Add (child);
+
+ Assert.That (page.CurrentPage, Is.SameAs (child));
+ Assert.That (property, Is.True, "CurrentPage property change did not fire");
+ }
+
+ [Test]
+ public void CurrentPageChangedAfterRemove()
+ {
+ var page = CreateMultiPage();
+ var child = CreateContainedPage();
+ var child2 = CreateContainedPage();
+ page.Children.Add (child);
+ page.Children.Add (child2);
+
+ bool property = false;
+ page.PropertyChanged += (o, e) => {
+ if (e.PropertyName == "CurrentPage")
+ property = true;
+ };
+
+ page.Children.Remove (child);
+
+ Assert.That (page.CurrentPage, Is.SameAs (child2), "MultiPage.CurrentPage is not set to a new page after current was removed");
+ Assert.That (property, Is.True, "CurrentPage property change did not fire");
+ }
+
+ [Test]
+ public void CurrentPageNullAfterRemove()
+ {
+ var page = CreateMultiPage();
+ var child = CreateContainedPage();
+ page.Children.Add (child);
+
+ bool property = false;
+ page.PropertyChanged += (o, e) => {
+ if (e.PropertyName == "CurrentPage")
+ property = true;
+ };
+
+ page.Children.Remove (child);
+
+ Assert.That (page.CurrentPage, Is.Null, "MultiPage.CurrentPage is still set after that page was removed");
+ Assert.That (property, Is.True, "CurrentPage property change did not fire");
+ }
+
+ [Test]
+ public void TemplatedPage()
+ {
+ var page = CreateMultiPage();
+
+ page.ItemTemplate = new DataTemplate (() => {
+ var p = new ContentPage();
+ p.Content = new Label();
+ p.Content.SetBinding (Label.TextProperty, new Binding ("."));
+ return p;
+ });
+
+ page.ItemsSource = new[] { "Foo", "Bar" };
+
+ Action<Page, string> assertPage = (p, s) => {
+ Assert.That (p, Is.InstanceOf<ContentPage>());
+
+ var cp = (ContentPage) p;
+ Assert.That (cp.Content, Is.InstanceOf<Label>());
+ Assert.That (((Label)cp.Content).Text, Is.EqualTo (s));
+ };
+
+ var pages = page.Children.ToArray();
+ Assert.That (pages.Length, Is.EqualTo (2));
+ assertPage ((Page)pages[0], "Foo");
+ assertPage ((Page)pages[1], "Bar");
+ }
+
+ [Test]
+ public void SelectedItemSetAfterAdd()
+ {
+ var page = CreateMultiPage();
+ Assert.That (page.CurrentPage, Is.Null);
+
+ var items = new ObservableCollection<string>();
+
+ page.ItemsSource = items;
+
+ bool selected = false;
+ bool current = false;
+ page.PropertyChanged += (o, e) => {
+ if (e.PropertyName == "CurrentPage")
+ current = true;
+ else if (e.PropertyName == "SelectedItem")
+ selected = true;
+ };
+
+ items.Add ("foo");
+
+ Assert.That (page.SelectedItem, Is.SameAs (items.First()));
+ Assert.That (page.CurrentPage.BindingContext, Is.SameAs (page.SelectedItem));
+ Assert.That (current, Is.True, "CurrentPage property change did not fire");
+ Assert.That (selected, Is.True, "SelectedItem property change did not fire");
+ }
+
+ [Test]
+ public void SelectedItemNullAfterRemove()
+ {
+ var page = CreateMultiPage();
+ Assert.That (page.CurrentPage, Is.Null);
+
+ var items = new ObservableCollection<string> { "foo" };
+ page.ItemsSource = items;
+
+ bool selected = false;
+ bool current = false;
+ page.PropertyChanged += (o, e) => {
+ if (e.PropertyName == "CurrentPage")
+ current = true;
+ else if (e.PropertyName == "SelectedItem")
+ selected = true;
+ };
+
+ items.Remove ("foo");
+
+ Assert.That (page.SelectedItem, Is.Null, "MultiPage.SelectedItem is still set after that page was removed");
+ Assert.That (page.CurrentPage, Is.Null, "MultiPage.CurrentPage is still set after that page was removed");
+ Assert.That (current, Is.True, "CurrentPage property change did not fire");
+ Assert.That (selected, Is.True, "SelectedItem property change did not fire");
+ }
+
+ [Test]
+ [Description ("When ItemsSource is set with items, the first item should automatically be selected")]
+ public void SelectedItemSetAfterItemsSourceSet()
+ {
+ var page = CreateMultiPage();
+
+ bool selected = false;
+ bool current = false;
+ page.PropertyChanged += (o, e) => {
+ if (e.PropertyName == "CurrentPage")
+ current = true;
+ else if (e.PropertyName == "SelectedItem")
+ selected = true;
+ };
+
+ page.ItemsSource = new[] { "foo" };
+
+ Assert.That (page.SelectedItem, Is.SameAs (((string[]) page.ItemsSource)[0]));
+ Assert.That (page.CurrentPage.BindingContext, Is.SameAs (page.SelectedItem));
+ Assert.That (current, Is.True, "CurrentPage property change did not fire");
+ Assert.That (selected, Is.True, "SelectedItem property change did not fire");
+ }
+
+ [Test]
+ public void SelectedItemNoLongerPresent()
+ {
+ var page = CreateMultiPage();
+
+ string[] items = new[] { "foo", "bar" };
+ page.ItemsSource = items;
+ page.SelectedItem = items[1];
+
+ items = new[] { "fad", "baz" };
+ page.ItemsSource = items;
+
+ Assert.That (page.SelectedItem, Is.SameAs (items[0]));
+ }
+
+ [Test]
+ public void SelectedItemAfterMove()
+ {
+ var page = CreateMultiPage();
+
+ var items = new ObservableCollection<string> { "foo", "bar" };
+ page.ItemsSource = items;
+
+ Assert.That (page.SelectedItem, Is.SameAs (items[0]));
+ Assert.That (page.CurrentPage, Is.Not.Null);
+ Assert.That (page.CurrentPage.BindingContext, Is.SameAs (items[0]));
+
+ page.SelectedItem = items[1];
+ Assert.That (page.CurrentPage.BindingContext, Is.SameAs (items[1]));
+
+ items.Move (1, 0);
+
+ Assert.That (page.SelectedItem, Is.SameAs (items[0]));
+ Assert.That (page.CurrentPage, Is.Not.Null);
+ Assert.That (page.CurrentPage.BindingContext, Is.SameAs (items[0]));
+ }
+
+ [Test]
+ public void UntemplatedItemsSourcePage()
+ {
+ var page = CreateMultiPage();
+
+ page.ItemsSource = new[] { "Foo", "Bar" };
+
+ var pages = page.Children.ToArray();
+ Assert.That (pages.Length, Is.EqualTo (2));
+ Assert.That (((Page)pages[0]).Title, Is.EqualTo ("Foo"));
+ Assert.That (((Page)pages[1]).Title, Is.EqualTo ("Bar"));
+ }
+
+ [Test]
+ public void TemplatePagesAdded()
+ {
+ var page = CreateMultiPage();
+
+ page.ItemTemplate = new DataTemplate (() => {
+ var p = new ContentPage();
+ p.Content = new Label();
+ p.Content.SetBinding (Label.TextProperty, new Binding ("."));
+ return p;
+ });
+
+ var items = new ObservableCollection<string> { "Foo", "Bar" };
+ page.ItemsSource = items;
+
+ Action<IList<Element>, int, string> assertPage = (ps, index, s) => {
+ Page p = (Page)ps[index];
+ Assert.That (p, Is.InstanceOf<ContentPage>());
+ Assert.That (GetIndex ((T) p), Is.EqualTo (index));
+
+ var cp = (ContentPage) p;
+ Assert.That (cp.Content, Is.InstanceOf<Label>());
+ Assert.That (((Label)cp.Content).Text, Is.EqualTo (s));
+ };
+
+ items.Add ("Baz");
+
+ var pages = page.Children.ToArray();
+ Assert.That (pages.Length, Is.EqualTo (3), "Children should have 3 pages");
+ assertPage (pages, 0, "Foo");
+ assertPage (pages, 1, "Bar");
+ assertPage (pages, 2, "Baz");
+ }
+
+ [Test]
+ public void TemplatePagesRangeAdded()
+ {
+ var page = CreateMultiPage();
+
+ page.ItemTemplate = new DataTemplate (() => {
+ var p = new ContentPage();
+ p.Content = new Label();
+ p.Content.SetBinding (Label.TextProperty, new Binding ("."));
+ return p;
+ });
+
+ var items = new ObservableList<string> { "Foo", "Bar" };
+ page.ItemsSource = items;
+
+ Action<IList<Element>, int, string> assertPage = (ps, index, s) => {
+ Page p = (Page)ps[index];
+ Assert.That (p, Is.InstanceOf<ContentPage>());
+ Assert.That (GetIndex ((T) p), Is.EqualTo (index));
+
+ var cp = (ContentPage) p;
+ Assert.That (cp.Content, Is.InstanceOf<Label>());
+ Assert.That (((Label)cp.Content).Text, Is.EqualTo (s));
+ };
+
+ int addedCount = 0;
+ page.PagesChanged += (sender, e) => {
+ if (e.Action != NotifyCollectionChangedAction.Add)
+ return;
+
+ addedCount++;
+ Assert.That (e.NewItems.Count, Is.EqualTo (2));
+ };
+
+ items.AddRange (new[] { "Baz", "Bam" });
+
+ Assert.That (addedCount, Is.EqualTo (1));
+
+ var pages = page.Children.ToArray();
+ Assert.That (pages.Length, Is.EqualTo (4));
+ assertPage (pages, 0, "Foo");
+ assertPage (pages, 1, "Bar");
+ assertPage (pages, 2, "Baz");
+ assertPage (pages, 3, "Bam");
+ }
+
+ [Test]
+ public void TemplatePagesInserted()
+ {
+ var page = CreateMultiPage();
+
+ page.ItemTemplate = new DataTemplate (() => {
+ var p = new ContentPage();
+ p.Content = new Label();
+ p.Content.SetBinding (Label.TextProperty, new Binding ("."));
+ return p;
+ });
+
+ var items = new ObservableCollection<string> { "Foo", "Bar" };
+ page.ItemsSource = items;
+
+ Action<IList<Element>, int, string> assertPage = (ps, index, s) => {
+ Page p = (Page)ps[index];
+ Assert.That (p, Is.InstanceOf<ContentPage>());
+ Assert.That (GetIndex ((T) p), Is.EqualTo (index));
+
+ var cp = (ContentPage) p;
+ Assert.That (cp.Content, Is.InstanceOf<Label>());
+ Assert.That (((Label)cp.Content).Text, Is.EqualTo (s));
+ };
+
+ items.Insert (1, "Baz");
+
+ var pages = page.Children.ToArray();
+ Assert.That (pages.Length, Is.EqualTo (3));
+ assertPage (pages, 0, "Foo");
+ assertPage (pages, 1, "Baz");
+ assertPage (pages, 2, "Bar");
+ }
+
+ [Test]
+ public void TemplatePagesRangeInserted()
+ {
+ var page = CreateMultiPage();
+
+ page.ItemTemplate = new DataTemplate (() => {
+ var p = new ContentPage();
+ p.Content = new Label();
+ p.Content.SetBinding (Label.TextProperty, new Binding ("."));
+ return p;
+ });
+
+ var items = new ObservableList<string> { "Foo", "Bar" };
+ page.ItemsSource = items;
+
+ Action<IList<Element>, int, string> assertPage = (ps, index, s) => {
+ Page p = (Page)ps[index];
+ Assert.That (p, Is.InstanceOf<ContentPage>());
+ Assert.That (GetIndex ((T) p), Is.EqualTo (index));
+
+ var cp = (ContentPage) p;
+ Assert.That (cp.Content, Is.InstanceOf<Label>());
+ Assert.That (((Label)cp.Content).Text, Is.EqualTo (s));
+ };
+
+ items.InsertRange (1, new[] { "Baz", "Bam" });
+
+ var pages = page.Children.ToArray();
+ Assert.That (pages.Length, Is.EqualTo (4));
+ assertPage (pages, 0, "Foo");
+ assertPage (pages, 1, "Baz");
+ assertPage (pages, 2, "Bam");
+ assertPage (pages, 3, "Bar");
+ }
+
+ [Test]
+ public void TemplatePagesRemoved()
+ {
+ var page = CreateMultiPage();
+
+ page.ItemTemplate = new DataTemplate (() => {
+ var p = new ContentPage();
+ p.Content = new Label();
+ p.Content.SetBinding (Label.TextProperty, new Binding ("."));
+ return p;
+ });
+
+ var items = new ObservableCollection<string> { "Foo", "Bar" };
+ page.ItemsSource = items;
+
+ Action<IList<Element>, int, string> assertPage = (ps, index, s) => {
+ Page p = (Page)ps[index];
+ Assert.That (p, Is.InstanceOf<ContentPage>());
+ Assert.That (GetIndex ((T) p), Is.EqualTo (index));
+
+ var cp = (ContentPage) p;
+ Assert.That (cp.Content, Is.InstanceOf<Label>());
+ Assert.That (((Label)cp.Content).Text, Is.EqualTo (s));
+ };
+
+ items.Remove ("Foo");
+
+ var pages = page.Children.ToArray();
+ Assert.That (pages.Length, Is.EqualTo (1));
+ assertPage (pages, 0, "Bar");
+ }
+
+ [Test]
+ public void TemplatePagesRangeRemoved()
+ {
+ var page = CreateMultiPage();
+
+ page.ItemTemplate = new DataTemplate (() => {
+ var p = new ContentPage();
+ p.Content = new Label();
+ p.Content.SetBinding (Label.TextProperty, new Binding ("."));
+ return p;
+ });
+
+ var items = new ObservableList<string> { "Foo", "Bar", "Baz", "Bam", "Who" };
+ page.ItemsSource = items;
+
+ Action<IList<Element>, int, string> assertPage = (ps, index, s) => {
+ Page p = (Page)ps[index];
+ Assert.That (p, Is.InstanceOf<ContentPage>());
+ Assert.That (GetIndex ((T) p), Is.EqualTo (index));
+
+ var cp = (ContentPage) p;
+ Assert.That (cp.Content, Is.InstanceOf<Label>());
+ Assert.That (((Label)cp.Content).Text, Is.EqualTo (s));
+ };
+
+ items.RemoveAt (1, 2);
+
+ var pages = page.Children.ToArray();
+ Assert.That (pages.Length, Is.EqualTo (3));
+ assertPage (pages, 0, "Foo");
+ assertPage (pages, 1, "Bam");
+ assertPage (pages, 2, "Who");
+ }
+
+ [Test]
+ public void TemplatePagesReordered()
+ {
+ var page = CreateMultiPage();
+
+ page.ItemTemplate = new DataTemplate (() => {
+ var p = new ContentPage();
+ p.Content = new Label();
+ p.Content.SetBinding (Label.TextProperty, new Binding ("."));
+ return p;
+ });
+
+ var items = new ObservableCollection<string> { "Foo", "Bar" };
+ page.ItemsSource = items;
+
+ Action<IList<Element>, int, string> assertPage = (ps, index, s) => {
+ Page p = (Page)ps[index];
+ Assert.That (p, Is.InstanceOf<ContentPage>());
+ Assert.That (GetIndex ((T) p), Is.EqualTo (index));
+
+ var cp = (ContentPage) p;
+ Assert.That (cp.Content, Is.InstanceOf<Label>());
+ Assert.That (((Label)cp.Content).Text, Is.EqualTo (s));
+ };
+
+ items.Move (0, 1);
+
+ var pages = page.Children.ToArray();
+ Assert.That (pages.Length, Is.EqualTo (2));
+ assertPage (pages, 0, "Bar");
+ assertPage (pages, 1, "Foo");
+ }
+
+ [Test]
+ public void TemplatePagesRangeReorderedForward()
+ {
+ var page = CreateMultiPage();
+
+ page.ItemTemplate = new DataTemplate (() => {
+ var p = new ContentPage();
+ p.Content = new Label();
+ p.Content.SetBinding (Label.TextProperty, new Binding ("."));
+ return p;
+ });
+
+ var items = new ObservableList<string> { "Foo", "Bar", "Baz", "Bam", "Who", "Where" };
+ page.ItemsSource = items;
+
+ Action<IList<Element>, int, string> assertPage = (ps, index, s) => {
+ Page p = (Page)ps[index];
+ Assert.That (p, Is.InstanceOf<ContentPage>());
+ Assert.That (GetIndex ((T) p), Is.EqualTo (index));
+
+ var cp = (ContentPage) p;
+ Assert.That (cp.Content, Is.InstanceOf<Label>());
+ Assert.That (((Label)cp.Content).Text, Is.EqualTo (s));
+ };
+
+ items.Move (1, 4, 2);
+
+ var pages = page.Children.ToArray();
+ Assert.That (pages.Length, Is.EqualTo (6));
+ assertPage (pages, 0, "Foo");
+ assertPage (pages, 1, "Bam");
+ assertPage (pages, 2, "Who");
+ assertPage (pages, 3, "Bar");
+ assertPage (pages, 4, "Baz");
+ assertPage (pages, 5, "Where");
+ }
+
+ [Test]
+ public void TemplatePagesRangeReorderedBackward()
+ {
+ var page = CreateMultiPage();
+
+ page.ItemTemplate = new DataTemplate (() => {
+ var p = new ContentPage();
+ p.Content = new Label();
+ p.Content.SetBinding (Label.TextProperty, new Binding ("."));
+ return p;
+ });
+
+ var items = new ObservableList<string> { "Foo", "Bar", "Baz", "Bam", "Who", "Where", "When" };
+ page.ItemsSource = items;
+
+ Action<IList<Element>, int, string> assertPage = (ps, index, s) => {
+ Page p = (Page)ps[index];
+ Assert.That (p, Is.InstanceOf<ContentPage>());
+ Assert.That (GetIndex ((T) p), Is.EqualTo (index));
+
+ var cp = (ContentPage) p;
+ Assert.That (cp.Content, Is.InstanceOf<Label>());
+ Assert.That (((Label)cp.Content).Text, Is.EqualTo (s));
+ };
+
+ items.Move (4, 1, 2);
+
+ var pages = page.Children.ToArray();
+ Assert.That (pages.Length, Is.EqualTo (7));
+ assertPage (pages, 0, "Foo");
+ assertPage (pages, 1, "Who");
+ assertPage (pages, 2, "Where");
+ assertPage (pages, 3, "Bar");
+ assertPage (pages, 4, "Baz");
+ assertPage (pages, 5, "Bam");
+ assertPage (pages, 6, "When");
+ }
+
+ [Test]
+ public void TemplatePagesReplaced()
+ {
+ var page = CreateMultiPage();
+
+ page.ItemTemplate = new DataTemplate (() => {
+ var p = new ContentPage();
+ p.Content = new Label();
+ p.Content.SetBinding (Label.TextProperty, new Binding ("."));
+ return p;
+ });
+
+ var items = new ObservableCollection<string> { "Foo", "Bar" };
+ page.ItemsSource = items;
+
+ Action<IList<Element>, int, string> assertPage = (ps, index, s) => {
+ Page p = (Page)ps[index];
+ Assert.That (p, Is.InstanceOf<ContentPage>());
+ Assert.That (GetIndex ((T) p), Is.EqualTo (index));
+
+ var cp = (ContentPage) p;
+ Assert.That (cp.Content, Is.InstanceOf<Label>());
+ Assert.That (((Label)cp.Content).Text, Is.EqualTo (s));
+ };
+
+ items[0] = "Baz";
+
+ var pages = page.Children.ToArray();
+ Assert.That (pages.Length, Is.EqualTo (2));
+ assertPage (pages, 0, "Baz");
+ assertPage (pages, 1, "Bar");
+ }
+
+ [Test]
+ public void TemplatedPagesSourceReplaced()
+ {
+ var page = CreateMultiPage();
+
+ page.ItemTemplate = new DataTemplate (() => {
+ var p = new ContentPage();
+ p.Content = new Label();
+ p.Content.SetBinding (Label.TextProperty, new Binding ("."));
+ return p;
+ });
+
+ page.ItemsSource = new ObservableCollection<string> { "Foo", "Bar" };
+
+ Action<Page, string> assertPage = (p, s) => {
+ Assert.That (p, Is.InstanceOf<ContentPage>());
+
+ var cp = (ContentPage) p;
+ Assert.That (cp.Content, Is.InstanceOf<Label>());
+ Assert.That (((Label)cp.Content).Text, Is.EqualTo (s));
+ };
+
+ page.ItemsSource = new ObservableCollection<string> { "Baz", "Bar" };
+
+ var pages = page.Children.ToArray();
+ Assert.That (pages.Length, Is.EqualTo (2));
+ assertPage ((Page)pages[0], "Baz");
+ assertPage ((Page)pages[1], "Bar");
+ }
+
+ [Test]
+ [Description ("If you have a templated set of items, setting CurrentPage (usually from renderers) should update SelectedItem properly")]
+ public void SettingCurrentPageWithTemplatesUpdatesSelectedItem()
+ {
+ var page = CreateMultiPage();
+
+ var items = new[] { "Foo", "Bar" };
+ page.ItemsSource = items;
+
+ // If these aren't correct, the rest of the test is invalid
+ Assert.That (page.CurrentPage, Is.SameAs (page.Children[0]));
+ Assert.That (page.SelectedItem, Is.SameAs (items[0]));
+
+ page.CurrentPage = (T)page.Children[1];
+
+ Assert.That (page.SelectedItem, Is.SameAs (items[1]));
+ }
+
+ [Test]
+ public void PagesChangedOnItemsSourceChange()
+ {
+ var page = CreateMultiPage();
+
+ page.ItemsSource = new[] { "Baz", "Bam" };
+
+ int fail = 0;
+ int reset = 0;
+ page.PagesChanged += (sender, args) => {
+ if (args.Action == NotifyCollectionChangedAction.Reset)
+ reset++;
+ else
+ fail++;
+ };
+
+ page.ItemsSource = new[] { "Foo", "Bar" };
+
+ Assert.That (reset, Is.EqualTo (1), "PagesChanged wasn't raised or was raised too many times for Reset");
+ Assert.That (fail, Is.EqualTo (0), "PagesChanged was raised with an unexpected action");
+ }
+
+ [Test]
+ public void PagesChangedOnTemplateChange()
+ {
+ var page = CreateMultiPage();
+
+ page.ItemsSource = new[] { "Foo", "Bar" };
+
+ int fail = 0;
+ int reset = 0;
+ page.PagesChanged += (sender, args) => {
+ if (args.Action == NotifyCollectionChangedAction.Reset)
+ reset++;
+ else
+ fail++;
+ };
+
+ page.ItemTemplate = new DataTemplate (() => new ContentPage {
+ Content = new Label { Text = "Content" }
+ });
+
+ Assert.That (reset, Is.EqualTo (1), "PagesChanged wasn't raised or was raised too many times for Reset");
+ Assert.That (fail, Is.EqualTo (0), "PagesChanged was raised with an unexpected action");
+ }
+
+ [Test]
+ public void SelectedItemSetBeforeTemplate()
+ {
+ var page = CreateMultiPage();
+
+ string[] items = new[] { "foo", "bar" };
+ page.ItemsSource = items;
+ page.SelectedItem = items[1];
+
+ var template = new DataTemplate (typeof (ContentPage));
+ template.SetBinding (ContentPage.TitleProperty, ".");
+ page.ItemTemplate = template;
+
+ Assert.That (page.SelectedItem, Is.SameAs (items[1]));
+ }
+
+ [Test]
+ public void CurrentPageUpdatedWithTemplate()
+ {
+ var page = CreateMultiPage();
+ string[] items = new[] { "foo", "bar" };
+ page.ItemsSource = items;
+
+ var untemplated = page.CurrentPage;
+
+ bool raised = false;
+ page.PropertyChanged += (sender, e) => {
+ if (e.PropertyName == "CurrentPage")
+ raised = true;
+ };
+
+ var template = new DataTemplate(() => {
+ var p = new ContentPage { Content = new Label() };
+ p.Content.SetBinding (Label.TextProperty, ".");
+ return p;
+ });
+
+ page.ItemTemplate = template;
+
+ Assert.That (raised, Is.True, "CurrentPage did not change with the template");
+ Assert.That (page.CurrentPage, Is.Not.SameAs (untemplated));
+ }
+
+ [Test]
+ public void CurrentPageChanged()
+ {
+ var page = CreateMultiPage();
+ page.Children.Add (CreateContainedPage());
+ page.Children.Add (CreateContainedPage());
+
+ bool raised = false;
+ page.CurrentPageChanged += (sender, e) => {
+ raised = true;
+ };
+
+ page.CurrentPage = page.Children[0];
+
+ Assert.That (raised, Is.False);
+
+ page.CurrentPage = page.Children[1];
+
+ Assert.That (raised, Is.True);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/MultiTriggerTests.cs b/Xamarin.Forms.Core.UnitTests/MultiTriggerTests.cs
new file mode 100644
index 00000000..df253e76
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/MultiTriggerTests.cs
@@ -0,0 +1,134 @@
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class MultiTriggerTests : BaseTestFixture
+ {
+ class MockElement : VisualElement
+ {
+ }
+
+ [Test]
+ public void SettersAppliedOnAttachIfConditionIsTrue ()
+ {
+ var conditionbp = BindableProperty.Create ("foo", typeof(string), typeof(BindableObject), null);
+ var setterbp = BindableProperty.Create ("bar", typeof(string), typeof(BindableObject), null);
+ var element = new MockElement ();
+ var multiTrigger = new MultiTrigger (typeof(VisualElement)) {
+ Conditions = {
+ new PropertyCondition { Property = conditionbp, Value = "foobar" },
+ new BindingCondition { Binding = new Binding ("baz"), Value = "foobaz" },
+ },
+ Setters = {
+ new Setter { Property = setterbp, Value = "qux" },
+ }
+ };
+
+ element.SetValue (setterbp, "default");
+ element.SetValue (conditionbp, "foobar");
+ element.BindingContext = new {baz = "foobaz"};
+ Assert.AreEqual ("default", element.GetValue (setterbp));
+ element.Triggers.Add (multiTrigger);
+ Assert.AreEqual ("qux", element.GetValue (setterbp));
+ }
+
+ [Test]
+ public void SettersNotAppliedOnAttachIfOneConditionIsFalse ()
+ {
+ var conditionbp = BindableProperty.Create ("foo", typeof(string), typeof(BindableObject), null);
+ var setterbp = BindableProperty.Create ("bar", typeof(string), typeof(BindableObject), null);
+ var element = new MockElement ();
+ var multiTrigger = new MultiTrigger (typeof(VisualElement)) {
+ Conditions = {
+ new PropertyCondition { Property = conditionbp, Value = "foobar" },
+ new BindingCondition { Binding = new Binding ("baz"), Value = "foobaz" },
+ },
+ Setters = {
+ new Setter { Property = setterbp, Value = "qux" },
+ }
+ };
+
+ element.SetValue (setterbp, "default");
+ element.SetValue (conditionbp, "foobar");
+ element.BindingContext = new {baz = "foobazXX"};
+ Assert.AreEqual ("default", element.GetValue (setterbp));
+ element.Triggers.Add (multiTrigger);
+ Assert.AreEqual ("default", element.GetValue (setterbp));
+ }
+
+ [Test]
+ public void SettersUnappliedOnDetach ()
+ {
+ var conditionbp = BindableProperty.Create ("foo", typeof(string), typeof(BindableObject), null);
+ var setterbp = BindableProperty.Create ("bar", typeof(string), typeof(BindableObject), null);
+ var element = new MockElement ();
+ var multiTrigger = new MultiTrigger (typeof(VisualElement)) {
+ Conditions = {
+ new PropertyCondition { Property = conditionbp, Value = "foobar" },
+ new BindingCondition { Binding = new Binding ("baz"), Value = "foobaz" },
+ },
+ Setters = {
+ new Setter { Property = setterbp, Value = "qux" },
+ }
+ };
+
+ element.SetValue (setterbp, "default");
+ element.BindingContext = new {baz = "" };
+ element.Triggers.Add (multiTrigger);
+ Assert.AreEqual ("default", element.GetValue (setterbp)); //both conditions false
+
+ element.SetValue (conditionbp, "foobar");
+ Assert.AreEqual ("default", element.GetValue (setterbp)); //one condition false
+
+ element.BindingContext = new {baz = "foobaz"};
+ Assert.AreEqual ("qux", element.GetValue (setterbp)); //both condition true
+ element.Triggers.Remove (multiTrigger);
+ Assert.AreEqual ("default", element.GetValue (setterbp));
+ }
+
+ [Test]
+ public void SettersAppliedAndUnappliedOnConditionsChange ()
+ {
+ var conditionbp = BindableProperty.Create ("foo", typeof(string), typeof(BindableObject), null);
+ var setterbp = BindableProperty.Create ("bar", typeof(string), typeof(BindableObject), null);
+ var element = new MockElement ();
+ var multiTrigger = new MultiTrigger (typeof(VisualElement)) {
+ Conditions = {
+ new PropertyCondition { Property = conditionbp, Value = "foobar" },
+ new BindingCondition { Binding = new Binding ("baz"), Value = "foobaz" },
+ },
+ Setters = {
+ new Setter { Property = setterbp, Value = "qux" },
+ }
+ };
+
+ element.SetValue (setterbp, "default");
+ element.BindingContext = new {baz = "" };
+ element.Triggers.Add (multiTrigger);
+ Assert.AreEqual ("default", element.GetValue (setterbp)); //both conditions false
+
+ element.SetValue (conditionbp, "foobar");
+ Assert.AreEqual ("default", element.GetValue (setterbp)); //one condition false
+
+ element.BindingContext = new {baz = "foobaz"};
+ Assert.AreEqual ("qux", element.GetValue (setterbp)); //both condition true
+
+ element.BindingContext = new {baz = ""};
+ Assert.AreEqual ("default", element.GetValue (setterbp)); //one condition false
+
+ element.BindingContext = new {baz = "foobaz"};
+ Assert.AreEqual ("qux", element.GetValue (setterbp)); //both condition true
+
+ element.SetValue (conditionbp, "");
+ Assert.AreEqual ("default", element.GetValue (setterbp)); //one condition false
+
+ element.SetValue (conditionbp, "foobar");
+ Assert.AreEqual ("qux", element.GetValue (setterbp)); //both condition true
+
+ element.SetValue (conditionbp, "");
+ element.BindingContext = new {baz = "foobaz"};
+ Assert.AreEqual ("default", element.GetValue (setterbp)); //both conditions false
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/NavigationMenuUnitTests.cs b/Xamarin.Forms.Core.UnitTests/NavigationMenuUnitTests.cs
new file mode 100644
index 00000000..b91f0229
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/NavigationMenuUnitTests.cs
@@ -0,0 +1,191 @@
+using System;
+using System.Threading.Tasks;
+using NUnit.Framework;
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class NavigationMenuUnitTests : BaseTestFixture
+ {
+ [Test]
+ public void TestTargets ()
+ {
+ var menu = new NavigationMenu ();
+
+ Assert.That (menu.Targets, Is.Empty);
+
+ bool signaled = false;
+ menu.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "Targets")
+ signaled = true;
+ };
+
+ var newArray = new[] {
+ new ContentPage { Content = new View (), Icon = "img1.jpg" },
+ new ContentPage { Content = new View (), Icon = "img2.jpg" }
+ };
+ menu.Targets = newArray;
+
+ Assert.AreEqual (newArray, menu.Targets);
+ Assert.True (signaled);
+ }
+
+ [Test]
+ public void TestTargetsDoubleSet ()
+ {
+ var menu = new NavigationMenu ();
+
+ bool signaled = false;
+ menu.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "Targets")
+ signaled = true;
+ };
+
+ menu.Targets = menu.Targets;
+
+ Assert.False (signaled);
+ }
+
+ [Test]
+ public void TestAdd ()
+ {
+ var menu = new NavigationMenu ();
+
+ bool signaled = false;
+ menu.PropertyChanged += (sender, args) => {
+ switch (args.PropertyName) {
+ case "Targets":
+ signaled = true;
+ break;
+ }
+ };
+
+ var child = new ContentPage {
+ Content = new View (),
+ Icon = "img.jpg"
+ };
+ menu.Add (child);
+ Assert.True (menu.Targets.Contains (child));
+ Assert.True (signaled);
+ }
+
+ [Test]
+ public void IconNotSet ()
+ {
+ var menu = new NavigationMenu ();
+ var childWithoutIcon = new ContentPage { Title = "I have no image" };
+
+ var ex = Assert.Throws<Exception> (() => menu.Add (childWithoutIcon));
+ Assert.That (ex.Message, Is.EqualTo ("Icon must be set for each page before adding them to a Navigation Menu"));
+ }
+
+ [Test]
+ public void TestDoubleAdd ()
+ {
+ var menu = new NavigationMenu ();
+
+ var child = new ContentPage {
+ Icon = "img.img",
+ Content = new View ()
+ };
+
+ menu.Add (child);
+
+ bool signaled = false;
+ menu.PropertyChanged += (sender, args) => {
+ switch (args.PropertyName) {
+ case "Targets":
+ signaled = true;
+ break;
+ }
+ };
+
+ menu.Add (child);
+
+ Assert.True (menu.Targets.Contains (child));
+ Assert.False (signaled);
+ }
+
+ [Test]
+ public void TestRemove ()
+ {
+ var menu = new NavigationMenu ();
+
+ var child = new ContentPage {
+ Icon = "img.img",
+ Content = new View ()
+ };
+ menu.Add (child);
+
+ bool signaled = false;
+ menu.PropertyChanged += (sender, args) => {
+ switch (args.PropertyName) {
+ case "Targets":
+ signaled = true;
+ break;
+ }
+ };
+
+ menu.Remove (child);
+
+ Assert.False (menu.Targets.Contains (child));
+ Assert.True (signaled);
+ }
+
+ [Test]
+ public void TestDoubleRemove ()
+ {
+ var menu = new NavigationMenu ();
+
+ var child = new ContentPage {
+ Icon = "jpg.jpg",
+ Content = new View ()
+ };
+ menu.Add (child);
+ menu.Remove (child);
+
+ bool signaled = false;
+ menu.PropertyChanged += (sender, args) => {
+ switch (args.PropertyName) {
+ case "Targets":
+ signaled = true;
+ break;
+ }
+ };
+
+ menu.Remove (child);
+
+ Assert.False (menu.Targets.Contains (child));
+ Assert.False (signaled);
+ }
+
+ [Test]
+ public async Task TestSendTargetSelected ()
+ {
+ var menu = new NavigationMenu ();
+ var navForm = new NavigationPage ();
+
+ await navForm.PushAsync (new ContentPage {
+ Title = "Menu",
+ Content = menu
+ });
+
+ bool pushed = false;
+ navForm.Pushed += (sender, arg) => pushed = true;
+
+ var child = new ContentPage {
+ Icon = "img.jpg",
+ Content = new View ()
+ };
+ menu.Add (child);
+
+ menu.SendTargetSelected (child);
+
+ Assert.True (pushed);
+ Assert.AreEqual (child, navForm.CurrentPage);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/NavigationModelTests.cs b/Xamarin.Forms.Core.UnitTests/NavigationModelTests.cs
new file mode 100644
index 00000000..ec3aaf5f
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/NavigationModelTests.cs
@@ -0,0 +1,304 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class NavigationModelTests : BaseTestFixture
+ {
+ [Test]
+ public void CurrentNullWhenEmpty ()
+ {
+ var navModel = new NavigationModel ();
+ Assert.Null (navModel.CurrentPage);
+ }
+
+ [Test]
+ public void CurrentGivesLastViewWithoutModal ()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+ var page2 = new ContentPage ();
+
+ navModel.Push (page1, null);
+ navModel.Push (page2, page1);
+
+ Assert.AreEqual (page2, navModel.CurrentPage);
+ }
+
+ [Test]
+ public void CurrentGivesLastViewWithModal()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+ var page2 = new ContentPage ();
+
+ var modal1 = new ContentPage ();
+ var modal2 = new ContentPage ();
+
+ navModel.Push (page1, null);
+ navModel.Push (page2, page1);
+
+ navModel.PushModal (modal1);
+ navModel.Push (modal2, modal1);
+
+ Assert.AreEqual (modal2, navModel.CurrentPage);
+ }
+
+ [Test]
+ public void Roots ()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+ var page2 = new ContentPage ();
+
+ var modal1 = new ContentPage ();
+ var modal2 = new ContentPage ();
+
+ navModel.Push (page1, null);
+ navModel.Push (page2, page1);
+
+ navModel.PushModal (modal1);
+ navModel.Push (modal2, modal1);
+
+ Assert.True (navModel.Roots.SequenceEqual (new[] {page1, modal1}));
+ }
+
+ [Test]
+ public void PushFirstItem ()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+ navModel.Push (page1, null);
+
+ Assert.AreEqual (page1, navModel.CurrentPage);
+ Assert.AreEqual (page1, navModel.Roots.First ());
+ }
+
+ [Test]
+ public void ThrowsWhenPushingWithoutAncestor ()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+ var page2 = new ContentPage ();
+
+ navModel.Push (page1, null);
+ Assert.Throws<InvalidNavigationException> (() => navModel.Push (page2, null));
+ }
+
+ [Test]
+ public void PushFromNonRootAncestor ()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+ var page2 = new ContentPage ();
+ var page3 = new ContentPage ();
+
+ page2.Parent = page1;
+ page3.Parent = page2;
+
+ navModel.Push (page1, null);
+ navModel.Push (page2, page1);
+ navModel.Push (page3, page2);
+
+ Assert.AreEqual (page3, navModel.CurrentPage);
+ }
+
+ [Test]
+ public void ThrowsWhenPushFromInvalidAncestor ()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+ var page2 = new ContentPage ();
+
+ Assert.Throws<InvalidNavigationException> (() => navModel.Push (page2, page1));
+ }
+
+ [Test]
+ public void Pop ()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+ var page2 = new ContentPage ();
+
+ navModel.Push (page1, null);
+ navModel.Push (page2, page1);
+
+ navModel.Pop (page1);
+
+ Assert.AreEqual (page1, navModel.CurrentPage);
+ }
+
+ [Test]
+ public void ThrowsPoppingRootItem ()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+
+ navModel.Push (page1, null);
+
+ Assert.Throws<InvalidNavigationException> (() => navModel.Pop (page1));
+ }
+
+ [Test]
+ public void ThrowsPoppingRootOfModal ()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+ var page2 = new ContentPage ();
+
+ var modal1 = new ContentPage ();
+
+ navModel.Push (page1, null);
+ navModel.Push (page2, page1);
+
+ navModel.PushModal (modal1);
+ Assert.Throws<InvalidNavigationException> (() => navModel.Pop (modal1));
+ }
+
+ [Test]
+ public void ThrowsPoppingWithInvalidAncestor ()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+
+ navModel.Push (page1, null);
+
+ Assert.Throws<InvalidNavigationException> (() => navModel.Pop (new ContentPage ()));
+ }
+
+ [Test]
+ public void PopToRoot ()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+ var page2 = new ContentPage ();
+ var page3 = new ContentPage ();
+
+ page2.Parent = page1;
+ page3.Parent = page2;
+
+ navModel.Push (page1, null);
+ navModel.Push (page2, page1);
+ navModel.Push (page3, page2);
+
+ navModel.PopToRoot (page2);
+
+ Assert.AreEqual (page1, navModel.CurrentPage);
+ }
+
+ [Test]
+ public void ThrowsWhenPopToRootOnRoot ()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+
+ navModel.Push (page1, null);
+ Assert.Throws<InvalidNavigationException> (() => navModel.PopToRoot (page1));
+ }
+
+ [Test]
+ public void ThrowsWhenPopToRootWithInvalidAncestor()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+ var page2 = new ContentPage ();
+
+ navModel.Push (page1, null);
+ navModel.Push (page2, page1);
+
+ Assert.Throws<InvalidNavigationException> (() => navModel.PopToRoot (new ContentPage ()));
+ }
+
+ [Test]
+ public void PopModal ()
+ {
+ var navModel = new NavigationModel ();
+
+ var child1 = new ContentPage ();
+ var modal1 = new ContentPage ();
+
+ navModel.Push (child1, null);
+ navModel.PushModal (modal1);
+
+ navModel.PopModal ();
+
+ Assert.AreEqual (child1, navModel.CurrentPage);
+ Assert.AreEqual (1, navModel.Roots.Count ());
+ }
+
+ [Test]
+ public void ReturnsCorrectModal ()
+ {
+ var navModel = new NavigationModel ();
+
+ var child1 = new ContentPage ();
+ var modal1 = new ContentPage ();
+ var modal2 = new ContentPage ();
+
+ navModel.Push (child1, null);
+ navModel.PushModal (modal1);
+ navModel.PushModal (modal2);
+
+ Assert.AreEqual (modal2, navModel.PopModal ());
+ }
+
+ [Test]
+ public void PopTopPageWithoutModals ()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+ var page2 = new ContentPage ();
+
+ navModel.Push (page1, null);
+ navModel.Push (page2, page1);
+
+ Assert.AreEqual (page2, navModel.PopTopPage ());
+ }
+
+ [Test]
+ public void PopTopPageWithSinglePage ()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+
+ navModel.Push (page1, null);
+
+ Assert.Null (navModel.PopTopPage ());
+ }
+
+ [Test]
+ public void PopTopPageWithModal ()
+ {
+ var navModel = new NavigationModel ();
+
+ var page1 = new ContentPage ();
+ var modal1 = new ContentPage ();
+
+ navModel.Push (page1, null);
+ navModel.PushModal (modal1);
+
+ Assert.AreEqual (modal1, navModel.PopTopPage ());
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/NavigationProxyTests.cs b/Xamarin.Forms.Core.UnitTests/NavigationProxyTests.cs
new file mode 100644
index 00000000..459f4cf4
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/NavigationProxyTests.cs
@@ -0,0 +1,188 @@
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using System.Threading.Tasks;
+
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class NavigationProxyTests : BaseTestFixture
+ {
+ class NavigationTest : INavigation
+ {
+ public Page LastPushed { get; set; }
+ public Page LastPushedModal { get; set; }
+
+ public bool Popped { get; set; }
+ public bool PoppedModal { get; set; }
+
+ public Task PushAsync (Page root)
+ {
+ return PushAsync (root, true);
+ }
+
+ public Task<Page> PopAsync ()
+ {
+ return PopAsync (true);
+ }
+
+ public Task PopToRootAsync ()
+ {
+ return PopToRootAsync (true);
+ }
+
+ public Task PushModalAsync (Page root)
+ {
+ return PushModalAsync (root, true);
+ }
+
+ public Task<Page> PopModalAsync ()
+ {
+ return PopModalAsync (true);
+ }
+
+ public Task PushAsync (Page root, bool animated)
+ {
+ LastPushed = root;
+ return Task.FromResult (root);
+ }
+
+ public Task<Page> PopAsync (bool animated)
+ {
+ Popped = true;
+ return Task.FromResult (LastPushed);
+ }
+
+ public Task PopToRootAsync (bool animated)
+ {
+ return Task.FromResult<Page> (null);
+ }
+
+ public Task PushModalAsync (Page root, bool animated)
+ {
+ LastPushedModal = root;
+ return Task.FromResult<object> (null);
+ }
+
+ public Task<Page> PopModalAsync (bool animated)
+ {
+ PoppedModal = true;
+ return Task.FromResult<Page> (null);
+ }
+
+ public void RemovePage (Page page)
+ {
+ }
+
+ public void InsertPageBefore (Page page, Page before)
+ {
+ }
+
+ public System.Collections.Generic.IReadOnlyList<Page> NavigationStack
+ {
+ get { return new List<Page> (); }
+ }
+
+ public System.Collections.Generic.IReadOnlyList<Page> ModalStack
+ {
+ get { return new List<Page> (); }
+ }
+ }
+
+ [Test]
+ public void Constructor ()
+ {
+ var proxy = new NavigationProxy ();
+
+ Assert.Null (proxy.Inner);
+ }
+
+ [Test]
+ public async Task PushesIntoNextInner ()
+ {
+ var page = new ContentPage ();
+ var navProxy = new NavigationProxy ();
+
+ await navProxy.PushAsync (page);
+
+ var navTest = new NavigationTest ();
+ navProxy.Inner = navTest;
+
+ Assert.AreEqual (page, navTest.LastPushed);
+ }
+
+ [Test]
+ public async Task PushesModalIntoNextInner ()
+ {
+ var page = new ContentPage ();
+ var navProxy = new NavigationProxy ();
+
+ await navProxy.PushModalAsync (page);
+
+ var navTest = new NavigationTest ();
+ navProxy.Inner = navTest;
+
+ Assert.AreEqual (page, navTest.LastPushedModal);
+ }
+
+ [Test]
+ public async Task TestPushWithInner ()
+ {
+ var proxy = new NavigationProxy ();
+ var inner = new NavigationTest ();
+
+ proxy.Inner = inner;
+
+ var child = new ContentPage {Content = new View ()};
+ await proxy.PushAsync (child);
+
+ Assert.AreEqual (child, inner.LastPushed);
+ }
+
+ [Test]
+ public async Task TestPushModalWithInner ()
+ {
+ var proxy = new NavigationProxy ();
+ var inner = new NavigationTest ();
+
+ proxy.Inner = inner;
+
+ var child = new ContentPage {Content = new View ()};
+ await proxy.PushModalAsync (child);
+
+ Assert.AreEqual (child, inner.LastPushedModal);
+ }
+
+ [Test]
+ public async Task TestPopWithInner ()
+ {
+ var proxy = new NavigationProxy ();
+ var inner = new NavigationTest ();
+
+ proxy.Inner = inner;
+
+ var child = new ContentPage {Content = new View ()};
+ await proxy.PushAsync (child);
+
+ var result = await proxy.PopAsync ();
+ Assert.AreEqual (child, result);
+ Assert.True (inner.Popped, "Pop was never called on the inner proxy item");
+ }
+
+ [Test]
+ public async Task TestPopModalWithInner ()
+ {
+ var proxy = new NavigationProxy ();
+ var inner = new NavigationTest ();
+
+ proxy.Inner = inner;
+
+ var child = new ContentPage {Content = new View ()};
+ await proxy.PushModalAsync (child);
+
+ await proxy.PopModalAsync ();
+ Assert.True (inner.PoppedModal, "Pop was never called on the inner proxy item");
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/NavigationUnitTest.cs b/Xamarin.Forms.Core.UnitTests/NavigationUnitTest.cs
new file mode 100644
index 00000000..1d3d4bd2
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/NavigationUnitTest.cs
@@ -0,0 +1,393 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class NavigationUnitTest : BaseTestFixture
+ {
+ [Test]
+ public async Task TestNavigationImplPush ()
+ {
+ NavigationPage nav = new NavigationPage ();
+
+ Assert.IsNull (nav.CurrentPage);
+
+ Label child = new Label {Text = "Label"};
+ Page childRoot = new ContentPage {Content = child};
+
+ await nav.Navigation.PushAsync (childRoot);
+
+ Assert.AreSame (childRoot, nav.CurrentPage);
+ }
+
+ [Test]
+ public async Task TestNavigationImplPop ()
+ {
+ NavigationPage nav = new NavigationPage ();
+
+ Label child = new Label ();
+ Page childRoot = new ContentPage {Content = child};
+
+ Label child2 = new Label ();
+ Page childRoot2 = new ContentPage {Content = child2};
+
+ await nav.Navigation.PushAsync (childRoot);
+ await nav.Navigation.PushAsync (childRoot2);
+
+ bool fired = false;
+ nav.Popped += (sender, e) => fired = true;
+ var popped = await nav.Navigation.PopAsync ();
+
+ Assert.True (fired);
+ Assert.AreSame (childRoot, nav.CurrentPage);
+ Assert.AreEqual (childRoot2, popped);
+
+ await nav.PopAsync ();
+ var last = await nav.Navigation.PopAsync ();
+
+ Assert.IsNull (last);
+ }
+
+ [Test]
+ public async Task TestPushRoot ()
+ {
+ NavigationPage nav = new NavigationPage ();
+
+ Assert.IsNull (nav.CurrentPage);
+
+ Label child = new Label {Text = "Label"};
+ Page childRoot = new ContentPage {Content = child};
+
+ await nav.PushAsync (childRoot);
+
+ Assert.AreSame (childRoot, nav.CurrentPage);
+ }
+
+ [Test]
+ public async Task TestPushEvent ()
+ {
+ NavigationPage nav = new NavigationPage ();
+
+ Label child = new Label ();
+ Page childRoot = new ContentPage {Content = child};
+
+ bool fired = false;
+ nav.Pushed += (sender, e) => fired = true;
+
+ await nav.PushAsync (childRoot);
+
+ Assert.True (fired);
+ }
+
+ [Test]
+ public async Task TestDoublePush ()
+ {
+ NavigationPage nav = new NavigationPage ();
+
+ Label child = new Label ();
+ Page childRoot = new ContentPage {Content = child};
+
+ await nav.PushAsync (childRoot);
+
+ bool fired = false;
+ nav.Pushed += (sender, e) => fired = true;
+
+ await nav.PushAsync (childRoot);
+
+ Assert.False (fired);
+ Assert.AreEqual (childRoot, nav.CurrentPage);
+ }
+
+ [Test]
+ public async Task TestPop ()
+ {
+ NavigationPage nav = new NavigationPage ();
+
+ Label child = new Label ();
+ Page childRoot = new ContentPage {Content = child};
+
+ Label child2 = new Label ();
+ Page childRoot2 = new ContentPage {Content = child2};
+
+ await nav.PushAsync (childRoot);
+ await nav.PushAsync (childRoot2);
+
+ bool fired = false;
+ nav.Popped += (sender, e) => fired = true;
+ var popped = await nav.PopAsync ();
+
+ Assert.True (fired);
+ Assert.AreSame (childRoot, nav.CurrentPage);
+ Assert.AreEqual (childRoot2, popped);
+
+ await nav.PopAsync ();
+ var last = await nav.PopAsync ();
+
+ Assert.IsNull (last);
+ }
+
+ [Test]
+ public void TestTint ()
+ {
+ var nav = new NavigationPage ();
+
+ Assert.AreEqual (Color.Default, nav.Tint);
+
+ bool signaled = false;
+ nav.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "Tint")
+ signaled = true;
+ };
+
+ nav.Tint = new Color (1, 0, 0);
+
+ Assert.AreEqual (new Color (1, 0, 0), nav.Tint);
+ Assert.True (signaled);
+ }
+
+ [Test]
+ public void TestTintDoubleSet ()
+ {
+ var nav = new NavigationPage ();
+
+ bool signaled = false;
+ nav.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "Tint")
+ signaled = true;
+ };
+
+ nav.Tint = nav.Tint;
+
+ Assert.False (signaled);
+ }
+
+ [Test]
+ public async Task TestPopToRoot ()
+ {
+ var nav = new NavigationPage ();
+
+ bool signaled = false;
+ nav.PoppedToRoot += (sender, args) => signaled = true;
+
+ var root = new ContentPage {Content = new View ()};
+ var child1 = new ContentPage {Content = new View ()};
+ var child2 = new ContentPage {Content = new View ()};
+
+ await nav.PushAsync (root);
+ await nav.PushAsync (child1);
+ await nav.PushAsync (child2);
+
+ nav.PopToRootAsync ();
+
+ Assert.True (signaled);
+ Assert.AreEqual (root, nav.CurrentPage);
+ }
+
+ [Test]
+ public async Task TestStackCopy ()
+ {
+ var nav = new NavigationPage ();
+
+ bool signaled = false;
+ nav.PoppedToRoot += (sender, args) => signaled = true;
+
+ var root = new ContentPage {Content = new View ()};
+ var child1 = new ContentPage {Content = new View ()};
+ var child2 = new ContentPage {Content = new View ()};
+
+ await nav.PushAsync (root);
+ await nav.PushAsync (child1);
+ await nav.PushAsync (child2);
+
+ var copy = nav.StackCopy;
+
+ Assert.AreEqual (child2, copy.Pop ());
+ Assert.AreEqual (child1, copy.Pop ());
+ Assert.AreEqual (root, copy.Pop ());
+ }
+
+ [Test]
+ public void ConstructWithRoot ()
+ {
+ var root = new ContentPage ();
+ var nav = new NavigationPage (root);
+
+ Assert.AreEqual (root, nav.LogicalChildren[0]);
+ Assert.AreEqual (1, nav.StackDepth);
+ }
+
+ [Test]
+ public async Task NavigationChangedEventArgs ()
+ {
+ var rootPage = new ContentPage { Title = "Root" };
+ var navPage = new NavigationPage (rootPage);
+
+ var rootArg = new Page ();
+
+ navPage.Pushed += (s, e) => {
+ rootArg = e.Page;
+ };
+
+ var pushPage = new ContentPage {
+ Title = "Page 2"
+ };
+
+ await navPage.PushAsync (pushPage);
+
+ Assert.AreEqual (rootArg, pushPage);
+
+ var secondPushPage = new ContentPage {
+ Title = "Page 3"
+ };
+
+ await navPage.PushAsync (secondPushPage);
+
+ Assert.AreEqual (rootArg, secondPushPage);
+ }
+
+ [Test]
+ public async Task CurrentPageChanged()
+ {
+ var root = new ContentPage { Title = "Root" };
+ var navPage = new NavigationPage (root);
+
+ bool changing = false;
+ navPage.PropertyChanging += (object sender, PropertyChangingEventArgs e) => {
+ if (e.PropertyName == NavigationPage.CurrentPageProperty.PropertyName) {
+ Assert.That (navPage.CurrentPage, Is.SameAs (root));
+ changing = true;
+ }
+ };
+
+ var next = new ContentPage { Title = "Next" };
+
+ bool changed = false;
+ navPage.PropertyChanged += (sender, e) => {
+ if (e.PropertyName == NavigationPage.CurrentPageProperty.PropertyName) {
+ Assert.That (navPage.CurrentPage, Is.SameAs (next));
+ changed = true;
+ }
+ };
+
+ await navPage.PushAsync (next);
+
+ Assert.That (changing, Is.True, "PropertyChanging was not raised for 'CurrentPage'");
+ Assert.That (changed, Is.True, "PropertyChanged was not raised for 'CurrentPage'");
+ }
+
+ [Test]
+ public async void HandlesPopToRoot ()
+ {
+ var root = new ContentPage { Title = "Root" };
+ var navPage = new NavigationPage (root);
+
+ await navPage.PushAsync (new ContentPage ());
+ await navPage.PushAsync (new ContentPage ());
+
+ bool popped = false;
+ navPage.PoppedToRoot += (sender, args) => {
+ popped = true;
+ };
+
+ await navPage.Navigation.PopToRootAsync ();
+
+ Assert.True (popped);
+ }
+
+ [Test]
+ public void SendsBackButtonEventToCurrentPage ()
+ {
+ var current = new BackButtonPage ();
+ var navPage = new NavigationPage (current);
+
+ var emitted = false;
+ current.BackPressed += (sender, args) => emitted = true;
+
+ navPage.SendBackButtonPressed ();
+
+ Assert.True (emitted);
+ }
+
+ [Test]
+ public void DoesNotSendBackEventToNonCurrentPage ()
+ {
+ var current = new BackButtonPage ();
+ var navPage = new NavigationPage (current);
+ navPage.PushAsync (new ContentPage ());
+
+ var emitted = false;
+ current.BackPressed += (sender, args) => emitted = true;
+
+ navPage.SendBackButtonPressed ();
+
+ Assert.False (emitted);
+ }
+
+ [Test]
+ public async void NavigatesBackWhenBackButtonPressed ()
+ {
+ var root = new ContentPage { Title = "Root" };
+ var navPage = new NavigationPage (root);
+
+ await navPage.PushAsync (new ContentPage ());
+
+ var result = navPage.SendBackButtonPressed ();
+
+ Assert.AreEqual (root, navPage.CurrentPage);
+ Assert.True (result);
+ }
+
+ [Test]
+ public async void DoesNotNavigatesBackWhenBackButtonPressedIfHandled ()
+ {
+ var root = new BackButtonPage { Title = "Root" };
+ var second = new BackButtonPage () {Handle = true};
+ var navPage = new NavigationPage (root);
+
+ await navPage.PushAsync (second);
+
+ navPage.SendBackButtonPressed ();
+
+ Assert.AreEqual (second, navPage.CurrentPage);
+ }
+
+ [Test]
+ public void DoesNotHandleBackButtonWhenNoNavStack ()
+ {
+ var root = new ContentPage { Title = "Root" };
+ var navPage = new NavigationPage (root);
+
+ var result = navPage.SendBackButtonPressed ();
+ Assert.False (result);
+ }
+
+ [Test (Description = "CurrentPage should not be set to null when you attempt to pop the last page")]
+ [Property ("Bugzilla", 28335)]
+ public async Task CurrentPageNotNullPoppingRoot()
+ {
+ var root = new ContentPage { Title = "Root" };
+ var navPage = new NavigationPage (root);
+ var popped = await navPage.PopAsync ();
+ Assert.That (popped, Is.Null);
+ Assert.That (navPage.CurrentPage, Is.SameAs (root));
+ }
+ }
+
+ internal class BackButtonPage : ContentPage
+ {
+ public event EventHandler BackPressed;
+
+ public bool Handle = false;
+
+ protected override bool OnBackButtonPressed ()
+ {
+ if (BackPressed != null)
+ BackPressed (this, EventArgs.Empty);
+ return Handle;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Core.UnitTests/NotifiedPropertiesTests.cs b/Xamarin.Forms.Core.UnitTests/NotifiedPropertiesTests.cs
new file mode 100644
index 00000000..344606bf
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/NotifiedPropertiesTests.cs
@@ -0,0 +1,214 @@
+using System;
+using NUnit.Framework;
+using System.ComponentModel;
+using Xamarin.Forms.Maps;
+using System.Windows.Input;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class NotifiedPropertiesTests : BaseTestFixture
+ {
+ public abstract class PropertyTestCase
+ {
+ public string Name { get; set;}
+ public Func<INotifyPropertyChanged,object> PropertyGetter { get; set; }
+ public Action<INotifyPropertyChanged, object> PropertySetter { get; set; }
+ public object ExpectedDefaultValue { get; set; }
+ public object TestValue { get; set; }
+ public abstract INotifyPropertyChanged CreateView ();
+ public virtual string DebugName {
+ get { return Name; }
+ }
+ }
+
+ public class PropertyTestCase<TView, TProperty>:PropertyTestCase where TView : INotifyPropertyChanged
+ {
+ Func<TView> init;
+ Func<TProperty> expectedValueCreator;
+
+ public PropertyTestCase (string name, Func<TView,TProperty> propertyGetter, Action<TView, TProperty> propertySetter, Func<TProperty> expectedDefaultValue, TProperty testValue, Func<TView> init = null)
+ {
+ Name = name;
+ PropertyGetter = v => propertyGetter((TView)v);
+ PropertySetter = (v,o)=> propertySetter ((TView)v, (TProperty)o);
+ expectedValueCreator = expectedDefaultValue;
+ TestValue = testValue;
+ this.init = init;
+ }
+
+ public override INotifyPropertyChanged CreateView ()
+ {
+ ExpectedDefaultValue = expectedValueCreator ();
+ if (init != null)
+ return init ();
+ if (typeof(TView) == typeof(View))
+ return new View ();
+ return (TView)Activator.CreateInstance (typeof(TView), new object[]{ });
+ }
+
+ public override string DebugName {
+ get {
+ return typeof(TView).Name + "." + Name;
+ }
+ }
+ }
+
+ static PropertyTestCase[] Properties = {
+ new PropertyTestCase<View, ResourceDictionary> ("Resources", v => v.Resources, (v, o) => v.Resources = o, () => null, new ResourceDictionary ()),
+ new PropertyTestCase<View, bool> ("InputTransparent", v => v.InputTransparent, (v, o) => v.InputTransparent = o, () => false, true),
+ new PropertyTestCase<View, double> ("Scale", v => v.Scale, (v, o) => v.Scale = o, () => 1d, 2d),
+ new PropertyTestCase<View, double> ("Rotation", v => v.Rotation, (v, o) => v.Rotation = o, () => 0d, 90d),
+ new PropertyTestCase<View, double> ("RotationX", v => v.RotationX, (v, o) => v.RotationX = o, () => 0d, 90d),
+ new PropertyTestCase<View, double> ("RotationY", v => v.RotationY, (v, o) => v.RotationY = o, () => 0d, 90d),
+ new PropertyTestCase<View, double> ("AnchorX", v => v.AnchorX, (v, o) => v.AnchorX = o, () => 0.5d, 0d),
+ new PropertyTestCase<View, double> ("AnchorY", v => v.AnchorY, (v, o) => v.AnchorY = o, () => 0.5d, 0d),
+ new PropertyTestCase<View, double> ("TranslationX", v => v.TranslationX, (v, o) => v.TranslationX = o, () => 0d, 20d),
+ new PropertyTestCase<View, double> ("TranslationY", v => v.TranslationY, (v, o) => v.TranslationY = o, () => 0d, 20d),
+ new PropertyTestCase<View, double> ("Opacity", v => v.Opacity, (v, o) => v.Opacity = o, () => 1d, 0.5d),
+ new PropertyTestCase<View, bool> ("IsEnabled", v => v.IsEnabled, (v, o) => v.IsEnabled = o, () => true, false),
+ new PropertyTestCase<View, bool> ("IsVisible", v => v.IsVisible, (v, o) => v.IsVisible = o, () => true, false),
+ new PropertyTestCase<View, string> ("ClassId", v => v.ClassId, (v, o) => v.ClassId = o, () => null, "Foo"),
+ new PropertyTestCase<ActivityIndicator, bool> ("IsRunning", v => v.IsRunning, (v, o) => v.IsRunning = o, () => false, true),
+ new PropertyTestCase<ActivityIndicator, Color> ("Color", v => v.Color, (v, o) => v.Color = o, () => Color.Default, new Color (0, 1, 0)),
+ new PropertyTestCase<Button, string> ("Text", v => v.Text, (v, o) => v.Text = o, () => null, "Foo"),
+ new PropertyTestCase<Button, Color> ("TextColor", v => v.TextColor, (v, o) => v.TextColor = o, () => Color.Default, new Color (0, 1, 0)),
+ new PropertyTestCase<Button, Font> ("Font", v => v.Font, (v, o) => v.Font = o, () => default (Font), Font.SystemFontOfSize (20)),
+ new PropertyTestCase<Button, double> ("BorderWidth", v => v.BorderWidth, (v, o) => v.BorderWidth = o, () => 0d, 16d),
+ new PropertyTestCase<Button, int> ("BorderRadius", v => v.BorderRadius, (v, o) => v.BorderRadius = o, () => 5, 12),
+ new PropertyTestCase<Button, Color> ("BorderColor", v => v.BorderColor, (v, o) => v.BorderColor = o, () => Color.Default, new Color (0, 1, 0)),
+ new PropertyTestCase<Button, string> ("FontFamily", v => v.FontFamily, (v, o) => v.FontFamily = o, () => null, "TestingFace"),
+ new PropertyTestCase<Button, double> ("FontSize", v => v.FontSize, (v, o) => v.FontSize = o, () => Device.GetNamedSize (NamedSize.Default, typeof (Button), true), 123.0),
+ new PropertyTestCase<Button, FontAttributes> ("FontAttributes", v => v.FontAttributes, (v, o) => v.FontAttributes = o, () => FontAttributes.None, FontAttributes.Italic),
+ new PropertyTestCase<CellTests.TestCell, double> ("Height", v => v.Height, (v, o) => v.Height = o, () => -1, 10),
+ new PropertyTestCase<DatePicker, DateTime> ("MinimumDate", v => v.MinimumDate, (v, o) => v.MinimumDate = o, () => new DateTime (1900, 1, 1), new DateTime (2014, 02, 05)),
+ new PropertyTestCase<DatePicker, DateTime> ("MaximumDate", v => v.MaximumDate, (v, o) => v.MaximumDate = o, () => new DateTime (2100, 12, 31), new DateTime (2014, 02, 05)),
+ new PropertyTestCase<DatePicker, DateTime> ("Date", v => v.Date, (v, o) => v.Date = o, () => DateTime.Now.Date, new DateTime (2008, 5, 5)),
+ new PropertyTestCase<DatePicker, string> ("Format", v => v.Format, (v, o) => v.Format = o, () => "d", "D"),
+ new PropertyTestCase<Editor, string> ("Text", v => v.Text, (v, o) => v.Text = o, () => null, "Foo"),
+ new PropertyTestCase<Entry, string> ("Text", v => v.Text, (v, o) => v.Text = o, () => null, "Foo"),
+ new PropertyTestCase<Entry, string> ("Placeholder", v => v.Placeholder, (v, o) => v.Placeholder = o, () => null, "Foo"),
+ new PropertyTestCase<Entry, bool> ("IsPassword", v => v.IsPassword, (v, o) => v.IsPassword = o, () => false, true),
+ new PropertyTestCase<Entry, Color> ("TextColor", v => v.TextColor, (v, o) => v.TextColor = o, () => Color.Default, new Color (0, 1, 0)),
+ new PropertyTestCase<Frame, Color> ("BackgroundColor", v => v.BackgroundColor, (v, o) => v.BackgroundColor = o, () => Color.Default, new Color (0, 1, 0)),
+ new PropertyTestCase<Frame, Color> ("OutlineColor", v => v.OutlineColor, (v, o) => v.OutlineColor = o, () => Color.Default, new Color (0, 1, 0)),
+ new PropertyTestCase<Frame, bool> ("HasShadow", v => v.HasShadow, (v, o) => v.HasShadow = o, () => true, false),
+ new PropertyTestCase<Grid, double> ("RowSpacing", v => v.RowSpacing, (v, o) => v.RowSpacing = o, () => 6, 12),
+ new PropertyTestCase<Grid, double> ("ColumnSpacing", v => v.ColumnSpacing, (v, o) => v.ColumnSpacing = o, () => 6, 12),
+ new PropertyTestCase<NaiveLayout, Thickness> ("Padding", v => v.Padding, (v, o) => v.Padding = o, () => default(Thickness), new Thickness (20, 20, 10, 10)),
+ new PropertyTestCase<Image, ImageSource> ("Source", v => v.Source, (v, o) => v.Source = o, () => null, ImageSource.FromFile("Foo")),
+ new PropertyTestCase<Image, Aspect> ("Aspect", v => v.Aspect, (v, o) => v.Aspect = o, () => Aspect.AspectFit, Aspect.AspectFill),
+ new PropertyTestCase<Image, bool> ("IsOpaque", v => v.IsOpaque, (v, o) => v.IsOpaque = o, () => false, true),
+ new PropertyTestCase<Label, string> ("Text", v => v.Text, (v, o) => v.Text = o, () => null, "Foo"),
+ new PropertyTestCase<Label, TextAlignment> ("XAlign", v => v.XAlign, (v, o) => v.XAlign = o, () => TextAlignment.Start, TextAlignment.End),
+ new PropertyTestCase<Label, TextAlignment> ("YAlign", v => v.YAlign, (v, o) => v.YAlign = o, () => TextAlignment.Start, TextAlignment.End),
+ new PropertyTestCase<Label, Color> ("TextColor", v => v.TextColor, (v, o) => v.TextColor = o, () => Color.Default, new Color (0, 1, 0)),
+ new PropertyTestCase<Label, LineBreakMode> ("LineBreakMode", v => v.LineBreakMode, (v, o) => v.LineBreakMode = o, () => LineBreakMode.WordWrap, LineBreakMode.TailTruncation),
+ new PropertyTestCase<Label, Font> ("Font", v => v.Font, (v, o) => v.Font = o, () => default (Font), Font.SystemFontOfSize (12)),
+ new PropertyTestCase<Label, string> ("FontFamily", v => v.FontFamily, (v, o) => v.FontFamily = o, () => null, "TestingFace"),
+ new PropertyTestCase<Label, double> ("FontSize", v => v.FontSize, (v, o) => v.FontSize = o, () => Device.GetNamedSize (NamedSize.Default, typeof (Label), true), 123.0),
+ new PropertyTestCase<Label, FontAttributes> ("FontAttributes", v => v.FontAttributes, (v, o) => v.FontAttributes = o, () => FontAttributes.None, FontAttributes.Italic),
+ new PropertyTestCase<Label, FormattedString> ("FormattedText", v => v.FormattedText, (v, o) => v.FormattedText = o, () => default (FormattedString), new FormattedString()),
+ new PropertyTestCase<Map, MapType> ("MapType", v => v.MapType, (v, o) => v.MapType = o, () => MapType.Street, MapType.Satellite),
+ new PropertyTestCase<Map, bool> ("IsShowingUser", v => v.IsShowingUser, (v, o) => v.IsShowingUser = o, () => false, true),
+ new PropertyTestCase<Map, bool> ("HasScrollEnabled", v => v.HasScrollEnabled, (v, o) => v.HasScrollEnabled = o, () => true, false),
+ new PropertyTestCase<Map, bool> ("HasZoomEnabled", v => v.HasZoomEnabled, (v, o) => v.HasZoomEnabled = o, () => true, false),
+ new PropertyTestCase<OpenGLView, bool> ("HasRenderLoop", v => v.HasRenderLoop, (v, o) => v.HasRenderLoop = o, () => false, true),
+ new PropertyTestCase<Page, string> ("BackgroundImage", v => v.BackgroundImage, (v, o) => v.BackgroundImage = o, () => null, "Foo"),
+ new PropertyTestCase<Page, Color> ("BackgroundColor", v => v.BackgroundColor, (v, o) => v.BackgroundColor = o, () => default(Color), new Color (0, 1, 0)),
+ new PropertyTestCase<Page, string> ("Title", v => v.Title, (v, o) => v.Title = o, () => null, "Foo"),
+ new PropertyTestCase<Page, bool> ("IsBusy", v => v.IsBusy, (v, o) => v.IsBusy = o, () => false, true),
+ new PropertyTestCase<Page, bool> ("IgnoresContainerArea", v => v.IgnoresContainerArea, (v, o) => v.IgnoresContainerArea = o, () => false, true),
+ new PropertyTestCase<Page, Thickness> ("Padding", v => v.Padding, (v, o) => v.Padding = o, () => default(Thickness), new Thickness (12)),
+ new PropertyTestCase<Picker, string> ("Title", v=>v.Title, (v, o) =>v.Title = o, () => null, "FooBar"),
+ new PropertyTestCase<Picker, int> ("SelectedIndex", v=>v.SelectedIndex, (v, o) =>v.SelectedIndex = o, () => -1, 2, ()=>new Picker{Items= {"Foo", "Bar", "Baz", "Qux"}}),
+ new PropertyTestCase<ProgressBar, double> ("Progress", v => v.Progress, (v, o) => v.Progress = o, () => 0, .5),
+ new PropertyTestCase<SearchBar, string> ("Placeholder", v => v.Placeholder, (v, o) => v.Placeholder = o, () => null, "Foo"),
+ new PropertyTestCase<SearchBar, string> ("Text", v => v.Text, (v, o) => v.Text = o, () => null, "Foo"),
+ new PropertyTestCase<Slider, double> ("Minimum", v => v.Minimum, (v, o) => v.Minimum = o, () => 0, .5),
+ new PropertyTestCase<Slider, double> ("Maximum", v => v.Maximum, (v, o) => v.Maximum = o, () => 1, .5),
+ new PropertyTestCase<Slider, double> ("Value", v => v.Value, (v, o) => v.Value = o, () => 0, .5),
+ new PropertyTestCase<StackLayout, StackOrientation> ("Orientation", v => v.Orientation, (v, o) => v.Orientation = o, () => StackOrientation.Vertical, StackOrientation.Horizontal),
+ new PropertyTestCase<StackLayout, double> ("Spacing", v => v.Spacing, (v, o) => v.Spacing = o, () => 6, 12),
+ new PropertyTestCase<Stepper, double> ("Minimum", v => v.Minimum, (v, o) => v.Minimum = o, () => 0, 50),
+ new PropertyTestCase<Stepper, double> ("Maximum", v => v.Maximum, (v, o) => v.Maximum = o, () => 100, 50),
+ new PropertyTestCase<Stepper, double> ("Value", v => v.Value, (v, o) => v.Value = o, () => 0, 50),
+ new PropertyTestCase<Stepper, double> ("Increment", v => v.Increment, (v, o) => v.Increment = o, () => 1, 2),
+ new PropertyTestCase<TableRoot, string> ("Title", v => v.Title, (v, o) => v.Title = o, () => null, "Foo"),
+ new PropertyTestCase<TableView, int> ("RowHeight", v => v.RowHeight, (v, o) => v.RowHeight = o, () => -1, 20),
+ new PropertyTestCase<TableView, bool> ("HasUnevenRows", v => v.HasUnevenRows, (v, o) => v.HasUnevenRows = o, () => false, true),
+ new PropertyTestCase<TableView, TableIntent> ("Intent", v => v.Intent, (v, o) => v.Intent = o, () => TableIntent.Data, TableIntent.Menu),
+ new PropertyTestCase<TextCell, string> ("Text", v => v.Text, (v, o) => v.Text = o, () => null, "Foo"),
+ new PropertyTestCase<TextCell, string> ("Detail", v => v.Detail, (v, o) => v.Detail = o, () => null, "Foo"),
+ new PropertyTestCase<TextCell, Color> ("TextColor", v => v.TextColor, (v, o) => v.TextColor = o, () => Color.Default, new Color (0, 1, 0)),
+ new PropertyTestCase<TextCell, Color> ("DetailColor", v => v.DetailColor, (v, o) => v.DetailColor = o, () => Color.Default, new Color (0, 1, 0)),
+ new PropertyTestCase<TimePicker, TimeSpan> ("Time", v => v.Time, (v, o) => v.Time = o, () => default(TimeSpan), new TimeSpan (8, 0, 0)),
+ new PropertyTestCase<TimePicker, string> ("Format", v => v.Format, (v, o) => v.Format = o, () => "t", "T"),
+ new PropertyTestCase<ViewCell, View> ("View", v => v.View, (v, o) => v.View = o, () => null, new View ()),
+ new PropertyTestCase<WebView, WebViewSource> ("Source", v => v.Source, (v, o) => v.Source = o, () => null, new UrlWebViewSource { Url = "Foo" }),
+ new PropertyTestCase<TapGestureRecognizer, int> ("NumberOfTapsRequired", t => t.NumberOfTapsRequired, (t, o) => t.NumberOfTapsRequired = o, () => 1, 3),
+ new PropertyTestCase<TapGestureRecognizer, object> ("CommandParameter", t => t.CommandParameter, (t, o) => t.CommandParameter = o, () => null, "Test"),
+ new PropertyTestCase<TapGestureRecognizer, ICommand> ("Command", t => t.Command, (t, o) => t.Command = o, () => null, new Command(()=>{})),
+ new PropertyTestCase<MasterDetailPage, bool> ("IsGestureEnabled", md => md.IsGestureEnabled, (md, v) => md.IsGestureEnabled = v, () => true, false)
+ };
+
+ [SetUp]
+ public override void Setup ()
+ {
+ base.Setup ();
+ Device.PlatformServices = new MockPlatformServices ();
+ }
+
+ [TearDown]
+ public override void TearDown ()
+ {
+ base.TearDown ();
+ Device.PlatformServices = null;
+ }
+
+ [Test, TestCaseSource ("Properties")]
+ public void DefaultValues (PropertyTestCase property)
+ {
+ var view = property.CreateView ();
+ Assert.AreEqual (property.ExpectedDefaultValue, property.PropertyGetter (view), property.DebugName);
+ }
+
+ [Test, TestCaseSource ("Properties")]
+ public void Set (PropertyTestCase property)
+ {
+ var view = property.CreateView ();
+
+ bool changed = false;
+ view.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == property.Name)
+ changed = true;
+ };
+
+ var testvalue = property.TestValue;
+ property.PropertySetter (view, testvalue);
+
+ Assert.True (changed, property.DebugName);
+ Assert.AreEqual (testvalue, property.PropertyGetter (view), property.DebugName);
+ }
+
+ [Test, TestCaseSource ("Properties")]
+ public void DoubleSet (PropertyTestCase property)
+ {
+ var view = property.CreateView ();
+
+ var testvalue = property.TestValue;
+ property.PropertySetter (view, testvalue);
+
+ bool changed = false;
+ view.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == property.Name)
+ changed = true;
+ };
+
+ property.PropertySetter (view, testvalue);
+
+ Assert.False (changed, property.DebugName);
+ Assert.AreEqual (testvalue, property.PropertyGetter (view), property.DebugName);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/NotifyCollectionChangedEventArgsExtensionsTests.cs b/Xamarin.Forms.Core.UnitTests/NotifyCollectionChangedEventArgsExtensionsTests.cs
new file mode 100644
index 00000000..e9acde45
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/NotifyCollectionChangedEventArgsExtensionsTests.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class NotifyCollectionChangedEventArgsExtensionsTests : BaseTestFixture
+ {
+ [Test]
+ public void Add()
+ {
+ List<string> applied = new List<string> { "foo", "bar", "baz" };
+
+ Action reset = () => Assert.Fail ("Reset should not be called");
+ Action<object, int, bool> insert = (o, i, create) => {
+ Assert.That (create, Is.True);
+ applied.Insert (i, (string) o);
+ };
+
+ Action<object, int> removeAt = (o, i) => applied.RemoveAt (i);
+
+ var items = new ObservableCollection<string> (applied);
+ items.CollectionChanged += (s, e) => e.Apply (insert, removeAt, reset);
+
+ items.Add ("monkey");
+
+ CollectionAssert.AreEqual (items, applied);
+ }
+
+ [Test]
+ public void Insert()
+ {
+ List<string> applied = new List<string> { "foo", "bar", "baz" };
+
+ Action reset = () => Assert.Fail ("Reset should not be called");
+ Action<object, int, bool> insert = (o, i, create) => {
+ Assert.That (create, Is.True);
+ applied.Insert (i, (string) o);
+ };
+ Action<object, int> removeAt = (o, i) => applied.RemoveAt (i);
+
+ var items = new ObservableCollection<string> (applied);
+ items.CollectionChanged += (s, e) => e.Apply (insert, removeAt, reset);
+
+ items.Insert (1, "monkey");
+
+ CollectionAssert.AreEqual (items, applied);
+ }
+
+ [Test]
+ public void Move()
+ {
+ List<string> applied = new List<string> { "foo", "bar", "baz" };
+
+ Action reset = () => Assert.Fail ("Reset should not be called");
+ Action<object, int, bool> insert = (o, i, create) => {
+ Assert.That (create, Is.False);
+ applied.Insert (i, (string) o);
+ };
+
+ Action<object, int> removeAt = (o, i) => applied.RemoveAt (i);
+
+ var items = new ObservableCollection<string> (applied);
+ items.CollectionChanged += (s, e) => e.Apply (insert, removeAt, reset);
+
+ items.Move (0, 2);
+
+ CollectionAssert.AreEqual (items, applied);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/ObservableWrapperTests.cs b/Xamarin.Forms.Core.UnitTests/ObservableWrapperTests.cs
new file mode 100644
index 00000000..2dc8a58d
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/ObservableWrapperTests.cs
@@ -0,0 +1,402 @@
+using System;
+using System.Collections.ObjectModel;
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class ObservableWrapperTests : BaseTestFixture
+ {
+ [Test]
+ public void Constructor ()
+ {
+ var observableCollection = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, Button> (observableCollection);
+
+ Assert.IsEmpty (wrapper);
+
+ Assert.Throws<ArgumentNullException> (() => new ObservableWrapper<View, View> (null));
+ }
+
+ [Test]
+ public void IgnoresInternallyAdded ()
+ {
+ var observableCollection = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, Button> (observableCollection);
+
+ var child = new View ();
+
+ observableCollection.Add (child);
+
+ Assert.IsEmpty (wrapper);
+ }
+
+ [Test]
+ public void TracksExternallyAdded ()
+ {
+ var observableCollection = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, Button> (observableCollection);
+
+ var child = new Button ();
+
+ wrapper.Add (child);
+
+ Assert.AreEqual (child, wrapper[0]);
+ Assert.AreEqual (child, observableCollection[0]);
+ }
+
+ [Test]
+ public void AddWithInternalItemsAlreadyAdded ()
+ {
+ var observableCollection = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, Button> (observableCollection);
+
+ var view = new View ();
+ observableCollection.Add (view);
+
+ var btn = new Button ();
+
+ wrapper.Add (btn);
+
+ Assert.AreEqual (btn, wrapper[0]);
+ Assert.AreEqual (1, wrapper.Count);
+
+ Assert.Contains (btn, observableCollection);
+ Assert.Contains (view, observableCollection);
+ Assert.AreEqual (2, observableCollection.Count);
+ }
+
+ [Test]
+ public void IgnoresInternallyAddedSameType ()
+ {
+ var observableCollection = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, View> (observableCollection);
+
+ var child = new View ();
+
+ observableCollection.Add (child);
+
+ Assert.IsEmpty (wrapper);
+ }
+
+ [Test]
+ public void TracksExternallyAddedSameType ()
+ {
+ var observableCollection = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, View> (observableCollection);
+
+ var child = new Button ();
+
+ wrapper.Add (child);
+
+ Assert.AreEqual (child, wrapper[0]);
+ Assert.AreEqual (child, observableCollection[0]);
+ }
+
+ [Test]
+ public void AddWithInternalItemsAlreadyAddedSameType ()
+ {
+ var observableCollection = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, View> (observableCollection);
+
+ var view = new View ();
+ observableCollection.Add (view);
+
+ var btn = new Button ();
+
+ wrapper.Add (btn);
+
+ Assert.AreEqual (btn, wrapper[0]);
+ Assert.AreEqual (1, wrapper.Count);
+
+ Assert.Contains (btn, observableCollection);
+ Assert.Contains (view, observableCollection);
+ Assert.AreEqual (2, observableCollection.Count);
+ }
+
+ [Test]
+ public void CannotRemoveInternalItem ()
+ {
+ var observableCollection = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, View> (observableCollection);
+
+ var child = new View ();
+
+ observableCollection.Add (child);
+
+ Assert.IsEmpty (wrapper);
+
+ Assert.False (wrapper.Remove (child));
+
+ Assert.Contains (child, observableCollection);
+ }
+
+ [Test]
+ public void ReadOnly ()
+ {
+ var observableCollection = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, Button> (observableCollection);
+
+ Assert.False (wrapper.IsReadOnly);
+
+ wrapper.Add (new Button ());
+
+ wrapper.IsReadOnly = true;
+
+ Assert.True (wrapper.IsReadOnly);
+
+ Assert.Throws<NotSupportedException> (() => wrapper.Remove (wrapper[0]));
+ Assert.Throws<NotSupportedException> (() => wrapper.Add (new Button ()));
+ Assert.Throws<NotSupportedException> (() => wrapper.RemoveAt (0));
+ Assert.Throws<NotSupportedException> (() => wrapper.Insert (0, new Button ()));
+ Assert.Throws<NotSupportedException> (wrapper.Clear);
+ }
+
+ [Test]
+ public void Indexer ()
+ {
+ var observableCollection = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, Button> (observableCollection);
+
+ wrapper.Add (new Button ());
+
+ var newButton = new Button ();
+
+ wrapper[0] = newButton;
+
+ Assert.AreEqual (newButton, wrapper[0]);
+ }
+
+ [Test]
+ public void IndexerSameType ()
+ {
+ var observableCollection = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, View> (observableCollection);
+
+ wrapper.Add (new Button ());
+
+ var newButton = new Button ();
+
+ wrapper[0] = newButton;
+
+ Assert.AreEqual (newButton, wrapper[0]);
+ }
+
+ [Test]
+ public void CopyTo ()
+ {
+ var observableCollection = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, View> (observableCollection);
+
+ var child1 = new Button ();
+ var child2 = new Button ();
+ var child3 = new Button ();
+ var child4 = new Button ();
+ var child5 = new Button ();
+
+ observableCollection.Add (new Stepper ());
+ wrapper.Add (child1);
+ observableCollection.Add (new Button ());
+ wrapper.Add (child2);
+ wrapper.Add (child3);
+ wrapper.Add (child4);
+ wrapper.Add (child5);
+ observableCollection.Add (new Button ());
+
+ var target = new View[30];
+ wrapper.CopyTo (target, 2);
+
+ Assert.AreEqual (target[2], child1);
+ Assert.AreEqual (target[3], child2);
+ Assert.AreEqual (target[4], child3);
+ Assert.AreEqual (target[5], child4);
+ Assert.AreEqual (target[6], child5);
+ }
+
+ [Test]
+ public void INCCSimpleAdd ()
+ {
+ var oc = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, View> (oc);
+
+ var child = new Button ();
+
+ Button addedResult = null;
+ int addIndex = -1;
+ wrapper.CollectionChanged += (sender, args) => {
+ addedResult = args.NewItems[0] as Button;
+ addIndex = args.NewStartingIndex;
+ };
+
+ wrapper.Add (child);
+
+ Assert.AreEqual (0, addIndex);
+ Assert.AreEqual (child, addedResult);
+ }
+
+ [Test]
+ public void INCCSimpleAddToInner ()
+ {
+ var oc = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, View> (oc);
+
+ var child = new Button ();
+
+ Button addedResult = null;
+ int addIndex = -1;
+ wrapper.CollectionChanged += (sender, args) => {
+ addedResult = args.NewItems[0] as Button;
+ addIndex = args.NewStartingIndex;
+ };
+
+ oc.Add (child);
+
+ Assert.AreEqual (-1, addIndex);
+ Assert.AreEqual (null, addedResult);
+ }
+
+ [Test]
+ public void INCCComplexAdd ()
+ {
+ var oc = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, Button> (oc);
+
+ oc.Add (new Stepper ());
+
+ var child = new Button ();
+
+ Button addedResult = null;
+ int addIndex = -1;
+ wrapper.CollectionChanged += (sender, args) => {
+ addedResult = args.NewItems[0] as Button;
+ addIndex = args.NewStartingIndex;
+ };
+
+ wrapper.Add (child);
+
+ Assert.AreEqual (0, addIndex);
+ Assert.AreEqual (child, addedResult);
+ }
+
+ [Test]
+ public void INCCSimpleRemove ()
+ {
+ var oc = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, Button> (oc);
+
+ var child = new Button ();
+ wrapper.Add (child);
+
+ Button removedResult = null;
+ int removeIndex = -1;
+ wrapper.CollectionChanged += (sender, args) => {
+ removedResult = args.OldItems[0] as Button;
+ removeIndex = args.OldStartingIndex;
+ };
+
+ wrapper.Remove (child);
+
+ Assert.AreEqual (0, removeIndex);
+ Assert.AreEqual (child, removedResult);
+ }
+
+ [Test]
+ public void INCCSimpleRemoveFromInner ()
+ {
+ var oc = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, Button> (oc);
+
+ var child = new Button ();
+ oc.Add (child);
+
+ Button addedResult = null;
+ int addIndex = -1;
+ wrapper.CollectionChanged += (sender, args) => {
+ addedResult = args.OldItems[0] as Button;
+ addIndex = args.OldStartingIndex;
+ };
+
+ oc.Remove (child);
+
+ Assert.AreEqual (-1, addIndex);
+ Assert.AreEqual (null, addedResult);
+ }
+
+ [Test]
+ public void INCCComplexRemove ()
+ {
+ var oc = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, Button> (oc);
+
+ oc.Add (new Stepper ());
+
+ var child = new Button ();
+ wrapper.Add (child);
+
+ Button removedResult = null;
+ int removeIndex = -1;
+ wrapper.CollectionChanged += (sender, args) => {
+ removedResult = args.OldItems[0] as Button;
+ removeIndex = args.OldStartingIndex;
+ };
+
+ wrapper.Remove (child);
+
+ Assert.AreEqual (child, removedResult);
+ Assert.AreEqual (0, removeIndex);
+ }
+
+ [Test]
+ public void INCCComplexRemoveLast ()
+ {
+ var oc = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, Button> (oc);
+
+ oc.Add (new Stepper ());
+
+ wrapper.Add (new Button ());
+ wrapper.Add (new Button ());
+ var child = new Button ();
+ wrapper.Add (child);
+
+ Button removedResult = null;
+ int removeIndex = -1;
+ wrapper.CollectionChanged += (sender, args) => {
+ removedResult = args.OldItems[0] as Button;
+ removeIndex = args.OldStartingIndex;
+ };
+
+ wrapper.Remove (child);
+
+ Assert.AreEqual (child, removedResult);
+ Assert.AreEqual (2, removeIndex);
+ }
+
+ [Test]
+ public void INCCReplace ()
+ {
+ var oc = new ObservableCollection<View> ();
+ var wrapper = new ObservableWrapper<View, Button> (oc);
+
+ var child1 = new Button ();
+ var child2 = new Button ();
+
+ wrapper.Add (child1);
+
+ int index = -1;
+ Button oldItem = null;
+ Button newItem = null;
+ wrapper.CollectionChanged += (sender, args) => {
+ index = args.NewStartingIndex;
+ oldItem = args.OldItems[0] as Button;
+ newItem = args.NewItems[0] as Button;
+ };
+
+ wrapper[0] = child2;
+
+ Assert.AreEqual (0, index);
+ Assert.AreEqual (child1, oldItem);
+ Assert.AreEqual (child2, newItem);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/OpenGLViewUnitTests.cs b/Xamarin.Forms.Core.UnitTests/OpenGLViewUnitTests.cs
new file mode 100644
index 00000000..e095914c
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/OpenGLViewUnitTests.cs
@@ -0,0 +1,20 @@
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class OpenGLViewUnitTests : BaseTestFixture
+ {
+ [Test]
+ public void Display ()
+ {
+ var view = new OpenGLView ();
+ bool displayed = false;
+
+ ((IOpenGlViewController)view).DisplayRequested += (s, o) => displayed = true;
+
+ view.Display ();
+ Assert.True (displayed);
+ }
+ }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/PageTests.cs b/Xamarin.Forms.Core.UnitTests/PageTests.cs
new file mode 100644
index 00000000..f44d004f
--- /dev/null
+++ b/Xamarin.Forms.Core.UnitTests/PageTests.cs
@@ -0,0 +1,498 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using NUnit.Framework;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+ [TestFixture]
+ public class PageTests : BaseTestFixture
+ {
+ [TearDown]
+ public override void TearDown()
+ {
+ base.TearDown ();
+ MessagingCenter.ClearSubscribers();
+ }
+
+ [Test]
+ public void TestConstructor ()
+ {
+ var child = new Label ();
+ Page root = new ContentPage {Content = child};
+
+ Assert.AreEqual (root.LogicalChildren.Count, 1);
+ Assert.AreSame (root.LogicalChildren.First (), child);
+ }
+
+ [Test]
+ public void TestChildFillBehavior ()
+ {
+ var child = new Label ();
+ Page root = new ContentPage {Content = child};
+ root.IsPlatformEnabled = child.IsPlatformEnabled = true;
+
+ root.Layout (new Rectangle (0, 0, 200, 500));
+
+ Assert.AreEqual (child.Width, 200);
+ Assert.AreEqual (child.Height, 500);
+ }
+
+ [Test]
+ public void TestSizedChildBehavior ()
+ {
+ var plat = new UnitPlatform ();
+ var child = new Label {Platform = plat, IsPlatformEnabled = true, WidthRequest = 100, HorizontalOptions = LayoutOptions.Center};
+ var root = new ContentPage {Platform = plat, IsPlatformEnabled = true, Content = child};
+
+ root.Layout (new Rectangle (0, 0, 200, 500));
+
+ Assert.AreEqual (50, child.X);
+ Assert.AreEqual (100, child.Width);
+ Assert.AreEqual (500, child.Height);
+
+ child = new Label () {
+ Platform = plat, IsPlatformEnabled = true,
+ HeightRequest = 100,
+ VerticalOptions = LayoutOptions.Center
+ };
+
+ root = new ContentPage {
+ Platform = plat, IsPlatformEnabled = true,
+ Content = child
+ };
+
+ root.Layout (new Rectangle (0, 0, 200, 500));
+
+ Assert.AreEqual (0, child.X);
+ Assert.AreEqual (200, child.Y);
+ Assert.AreEqual (200, child.Width);
+ Assert.AreEqual (100, child.Height);
+
+ child = new Label ();
+ child.IsPlatformEnabled = true;
+ child.HeightRequest = 100;
+
+ root = new ContentPage {
+ Content = child,
+ Platform = plat, IsPlatformEnabled = true
+ };
+
+ root.Layout (new Rectangle (0, 0, 200, 500));
+
+ Assert.AreEqual (0, child.X);
+ Assert.AreEqual (0, child.Y);
+ Assert.AreEqual (200, child.Width);
+ Assert.AreEqual (500, child.Height);
+ }
+
+ [Test]
+ public void NativeSizedChildBehavior ()
+ {
+ var plat = new UnitPlatform ();
+ var child = new Label {Platform = plat, IsPlatformEnabled = true, HorizontalOptions = LayoutOptions.Center};
+ var root = new ContentPage {Platform = plat, IsPlatformEnabled = true, Content = child};
+
+ root.Layout (new Rectangle (0, 0, 200, 500));
+
+ Assert.AreEqual (50, child.X);
+ Assert.AreEqual (100, child.Width);
+ Assert.AreEqual (500, child.Height);
+
+ child = new Label () {
+ Platform = plat, IsPlatformEnabled = true,
+ VerticalOptions = LayoutOptions.Center
+ };
+
+ root = new ContentPage {
+ Platform = plat, IsPlatformEnabled = true,
+ Content = child
+ };
+
+ root.Layout (new Rectangle (0, 0, 200, 500));
+
+ Assert.AreEqual (0, child.X);
+ Assert.AreEqual (240, child.Y);
+ Assert.AreEqual (200, child.Width);
+ Assert.AreEqual (20, child.Height);
+ }
+
+ [Test]
+ public void TestContentPageSetContent ()
+ {
+ View child;
+ var page = new ContentPage {Content = child = new View ()};
+
+ Assert.AreEqual (child, page.Content);
+
+ bool fired = false;
+ page.PropertyChanged += (sender, args) => {
+ if (args.PropertyName == "Content")
+ fired = true;
+ };
+
+ page.Content = child;
+ Assert.False (fired);
+
+ page.Content = new View ();
+ Assert.True (fired);
+
+ page.Content = null;
+ Assert.Null (page.Content);
+ }
+
+ [Test]
+ public void TestLayoutChildrenFill ()
+ {
+ View child;
+ var page = new ContentPage {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 200,
+ IsPlatformEnabled = true
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ page.Layout (new Rectangle (0, 0, 800, 800));
+
+ Assert.AreEqual (new Rectangle (0, 0, 800, 800), child.Bounds);
+
+ page.Layout (new Rectangle (0, 0, 50, 50));
+
+ Assert.AreEqual (new Rectangle (0, 0, 50, 50), child.Bounds);
+ }
+
+ [Test]
+ public void TestLayoutChildrenStart ()
+ {
+ View child;
+ var page = new ContentPage {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 200,
+ HorizontalOptions = LayoutOptions.Start,
+ VerticalOptions = LayoutOptions.Start,
+ IsPlatformEnabled = true
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ page.Layout (new Rectangle (0, 0, 800, 800));
+
+ Assert.AreEqual (new Rectangle (0, 0, 100, 200), child.Bounds);
+
+ page.Layout (new Rectangle (0, 0, 50, 50));
+
+ Assert.AreEqual (new Rectangle (0, 0, 50, 50), child.Bounds);
+ }
+
+ [Test]
+ public void TestLayoutChildrenEnd ()
+ {
+ View child;
+ var page = new ContentPage {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 200,
+ HorizontalOptions = LayoutOptions.End,
+ VerticalOptions = LayoutOptions.End,
+ IsPlatformEnabled = true
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ page.Layout (new Rectangle (0, 0, 800, 800));
+
+ Assert.AreEqual (new Rectangle (700, 600, 100, 200), child.Bounds);
+
+ page.Layout (new Rectangle (0, 0, 50, 50));
+
+ Assert.AreEqual (new Rectangle (0, 0, 50, 50), child.Bounds);
+ }
+
+ [Test]
+ public void TestLayoutChildrenCenter ()
+ {
+ View child;
+ var page = new ContentPage {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 200,
+ HorizontalOptions = LayoutOptions.Center,
+ VerticalOptions = LayoutOptions.Center,
+ IsPlatformEnabled = true
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ page.Layout (new Rectangle (0, 0, 800, 800));
+
+ Assert.AreEqual (new Rectangle (350, 300, 100, 200), child.Bounds);
+
+ page.Layout (new Rectangle (0, 0, 50, 50));
+
+ Assert.AreEqual (new Rectangle (0, 0, 50, 50), child.Bounds);
+ }
+
+ [Test]
+ public void TestLayoutWithContainerArea ()
+ {
+ View child;
+ var page = new ContentPage {
+ Content = child = new View {
+ WidthRequest = 100,
+ HeightRequest = 200,
+ IsPlatformEnabled = true
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+
+ page.Layout (new Rectangle (0, 0, 800, 800));
+
+ Assert.AreEqual (new Rectangle (0, 0, 800, 800), child.Bounds);
+
+ page.ContainerArea = new Rectangle (10, 10, 30, 30);
+
+ Assert.AreEqual (new Rectangle (10, 10, 30, 30), child.Bounds);
+
+ page.Layout (new Rectangle (0, 0, 50, 50));
+
+ Assert.AreEqual (new Rectangle (10, 10, 30, 30), child.Bounds);
+ }
+
+ [Test]
+ public void TestThrowOnInvalidAlignment ()
+ {
+ bool thrown = false;
+
+ try {
+ new ContentPage {
+ Content = new View {
+ WidthRequest = 100,
+ HeightRequest = 200,
+ HorizontalOptions = new LayoutOptions((LayoutAlignment) int.MaxValue, false),
+ VerticalOptions = LayoutOptions.Center,
+ IsPlatformEnabled = true
+ },
+ IsPlatformEnabled = true,
+ Platform = new UnitPlatform ()
+ };
+ } catch (ArgumentOutOfRangeException) {
+ thrown = true;
+ }
+
+ Assert.True (thrown);
+ }
+
+ [Test]
+ public void BusyNotSentWhenNotVisible ()
+ {
+ var sent = false;
+ MessagingCenter.Subscribe<Page, bool> (this, Page.BusySetSignalName, (p, b) => sent = true);
+
+ new ContentPage { IsBusy = true };
+
+ Assert.That (sent, Is.False, "Busy message sent while not visible");
+ }
+
+ [Test]
+ public void BusySentWhenBusyPageAppears()
+ {
+ var sent = false;
+ MessagingCenter.Subscribe<Page, bool> (this, Page.BusySetSignalName, (p, b) => {
+ Assert.That (b, Is.True);
+ sent = true;
+ });
+
+ var page = new ContentPage { IsBusy = true };
+
+ Assert.That (sent, Is.False, "Busy message sent while not visible");
+
+ page.SendAppearing();
+
+ Assert.That (sent, Is.True, "Busy message not sent when visible");
+ }
+
+ [Test]
+ public void BusySentWhenBusyPageDisappears()
+ {
+ var page = new ContentPage { IsBusy = true };
+ page.SendAppearing();
+
+ var sent = false;
+ MessagingCenter.Subscribe<Page, bool> (this, Page.BusySetSignalName, (p, b) => {
+ Assert.That (b, Is.False);
+ sent = true;
+ });
+
+ page.SendDisappearing();
+
+ Assert.That (sent, Is.True, "Busy message not sent when visible");
+ }
+
+ [Test]
+ public void BusySentWhenVisiblePageSetToBusy()
+ {
+ var sent = false;
+ MessagingCenter.Subscribe<Page, bool> (this, Page.BusySetSignalName, (p, b) => sent = true);
+
+ var page = new ContentPage();
+ page.SendAppearing();
+
+ Assert.That (sent, Is.False, "Busy message sent appearing while not busy");
+
+ page.IsBusy = true;
+
+ Assert.That (sent, Is.True, "Busy message not sent when visible");
+ }
+
+ [Test]
+ public void DisplayAlert ()
+ {
+ var page = new ContentPage ();
+
+ AlertArguments args = null;
+ MessagingCenter.Subscribe (this, Page.AlertSignalName, (Page sender, AlertArguments e) => args = e);
+
+ var task = page.DisplayAlert ("Title", "Message", "Accept", "Cancel");
+
+ Assert.AreEqual ("Title", args.Title);
+ Assert.AreEqual ("Message", args.Message);
+ Assert.AreEqual ("Accept", args.Accept);
+ Assert.AreEqual ("Cancel", args.Cancel);
+
+ bool completed = false;
+ var continueTask = task.ContinueWith (t => completed = true);
+
+ args.SetResult (true);
+ continueTask.Wait ();
+ Assert.True (completed);
+ }
+
+ [Test]
+ public void DisplayActionSheet ()
+ {
+ var page = new ContentPage ();
+
+ ActionSheetArguments args = null;
+ MessagingCenter.Subscribe (this, Page.ActionSheetSignalName, (Page sender, ActionSheetArguments e) => args = e);
+
+ var task = page.DisplayActionSheet ("Title", "Cancel", "Destruction", "Other 1", "Other 2");
+
+ Assert.AreEqual ("Title", args.Title);
+ Assert.AreEqual ("Destruction", args.Destruction);
+ Assert.AreEqual ("Cancel", args.Cancel);
+ Assert.AreEqual ("Other 1", args.Buttons.First());
+ Assert.AreEqual ("Other 2", args.Buttons.Skip (1).First());
+
+ bool completed = false;
+ var continueTask = task.ContinueWith (t => completed = true);
+
+ args.SetResult ("Cancel");
+ continueTask.Wait ();
+ Assert.True (completed);
+ }
+
+ [Test]
+ public void SendAppearing ()
+ {
+ var page = new ContentPage ();
+
+ bool sent = false;
+ page.Appearing += (sender, args) => sent = true;
+
+ page.SendAppearing ();
+
+ Assert.True (sent);
+ }
+
+ [Test]
+ public void SendDisappearing ()
+ {
+ var page = new ContentPage ();
+
+ page.SendAppearing ();
+
+ bool sent = false;
+ page.Disappearing += (sender, args) => sent = true;
+
+ page.SendDisappearing ();
+
+ Assert.True (sent);
+ }
+
+ [Test]
+ public void SendAppearingDoesntGetCalledMultipleTimes ()
+ {
+ var page = new ContentPage ();
+
+ int countAppearing = 0;
+ page.Appearing += (sender, args) => countAppearing++;
+
+ page.SendAppearing ();
+ page.SendAppearing ();
+
+ Assert.That (countAppearing, Is.EqualTo(1));
+ }
+
+ [Test]
+ public void IsVisibleWorks