2475 строки
73 KiB
C#
2475 строки
73 KiB
C#
using System;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.Globalization;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using System.Runtime.CompilerServices;
|
||
using System.Threading.Tasks;
|
||
using NUnit.Framework;
|
||
using Xamarin.Forms.Internals;
|
||
using CategoryAttribute = NUnit.Framework.CategoryAttribute;
|
||
using DescriptionAttribute = NUnit.Framework.DescriptionAttribute;
|
||
|
||
namespace Xamarin.Forms.Core.UnitTests
|
||
{
|
||
[TestFixture]
|
||
public class BindingUnitTests
|
||
: BindingBaseUnitTests
|
||
{
|
||
[SetUp]
|
||
public override void Setup()
|
||
{
|
||
base.Setup();
|
||
log = new Logger();
|
||
|
||
Device.PlatformServices = new MockPlatformServices();
|
||
Log.Listeners.Add(log);
|
||
}
|
||
|
||
[TearDown]
|
||
public override void TearDown()
|
||
{
|
||
base.TearDown();
|
||
Device.PlatformServices = null;
|
||
Log.Listeners.Remove(log);
|
||
}
|
||
|
||
protected override BindingBase CreateBinding(BindingMode mode = BindingMode.Default, string stringFormat = null)
|
||
{
|
||
return new 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("Foo", typeof(string), typeof(MockBindable));
|
||
var binding = (Binding)CreateBinding(BindingMode.Default, "Foo {0}");
|
||
|
||
var vm = new MockViewModel { Text = "Bar" };
|
||
var bo = new MockBindable { BindingContext = vm };
|
||
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("Foo", typeof(string), typeof(MockBindable));
|
||
var binding = new Binding();
|
||
|
||
var bo = new MockBindable { BindingContext = "Foo" };
|
||
bo.SetBinding(property, binding);
|
||
|
||
Assert.That(bo.GetValue(property), Is.EqualTo("Foo"));
|
||
}
|
||
|
||
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] 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("Foo", typeof(string), typeof(MockBindable), 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("Foo", typeof(string), typeof(MockBindable), 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, bindable.GetValue(property),
|
||
"Target property changed");
|
||
Assert.AreEqual(value, viewmodel.Model.Model.Text,
|
||
"BindingContext property did not change");
|
||
Assert.That(log.Messages.Count, Is.EqualTo(0),
|
||
"An error was logged: " + log.Messages.FirstOrDefault());
|
||
}
|
||
|
||
[Test, Category("[Binding] Complex paths")]
|
||
public void ValueSetOnTwoWayWithComplexPathBinding(
|
||
[Values(true, false)] bool setContextFirst,
|
||
[Values(true, false)] bool isDefault)
|
||
{
|
||
const string value = "Foo";
|
||
var viewmodel = new ComplexMockViewModel
|
||
{
|
||
Model = new ComplexMockViewModel
|
||
{
|
||
Model = new ComplexMockViewModel
|
||
{
|
||
Text = value
|
||
}
|
||
}
|
||
};
|
||
|
||
BindingMode propertyDefault = BindingMode.OneWay;
|
||
BindingMode bindingMode = BindingMode.TwoWay;
|
||
if (isDefault)
|
||
{
|
||
propertyDefault = BindingMode.TwoWay;
|
||
bindingMode = BindingMode.Default;
|
||
}
|
||
|
||
var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable), "default value", propertyDefault);
|
||
var binding = new 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("Text", typeof(string), typeof(MockBindable), "default value", BindingMode.Default);
|
||
var binding = new Binding(usePrivateSetter ? "PropertyWithPrivateSetter.GetSetProperty" : "PropertyWithPublicSetter.GetSetProperty", bindingmode);
|
||
var viewmodel = new Outer(new Inner(value));
|
||
var bindable = new MockBindable();
|
||
|
||
if (setContextFirst)
|
||
{
|
||
bindable.BindingContext = viewmodel;
|
||
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("Text", typeof(string), typeof(MockBindable), "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] 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());
|
||
}
|
||
|
||
[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");
|
||
}
|
||
|
||
[Category("[Binding] Complex paths")]
|
||
[TestCase(BindingMode.OneWay)]
|
||
[TestCase(BindingMode.OneWayToSource)]
|
||
[TestCase(BindingMode.TwoWay)]
|
||
public async Task WeakPropertyChangedProxyDoesNotLeak(BindingMode mode)
|
||
{
|
||
var proxies = new List<WeakReference>();
|
||
WeakReference weakViewModel = null, weakBindable = null;
|
||
|
||
int i = 0;
|
||
void create()
|
||
{
|
||
if (i++ < 1024)
|
||
{
|
||
create();
|
||
return;
|
||
}
|
||
|
||
var binding = new Binding("Model.Model[1]");
|
||
var bindable = new MockBindable();
|
||
weakBindable = new WeakReference(bindable);
|
||
|
||
var viewmodel = new ComplexMockViewModel
|
||
{
|
||
Model = new ComplexMockViewModel
|
||
{
|
||
Model = new ComplexMockViewModel()
|
||
}
|
||
};
|
||
|
||
weakViewModel = new WeakReference(viewmodel);
|
||
|
||
bindable.BindingContext = viewmodel;
|
||
bindable.SetBinding(MockBindable.TextProperty, binding);
|
||
|
||
// Access private members:
|
||
// WeakPropertyChangedProxy proxy = binding._expression._parts[i]._listener;
|
||
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
|
||
var expression = binding.GetType().GetField("_expression", flags).GetValue(binding);
|
||
Assert.NotNull(expression);
|
||
var parts = expression.GetType().GetField("_parts", flags).GetValue(expression) as IEnumerable;
|
||
Assert.NotNull(parts);
|
||
foreach (var part in parts)
|
||
{
|
||
var listener = part.GetType().GetField("_listener", flags).GetValue(part);
|
||
if (listener == null)
|
||
continue;
|
||
proxies.Add(new WeakReference(listener));
|
||
}
|
||
|
||
Assert.IsNotEmpty(proxies); // Should be at least 1
|
||
};
|
||
|
||
create();
|
||
|
||
await Task.Yield();
|
||
GC.Collect();
|
||
GC.WaitForPendingFinalizers();
|
||
|
||
if (mode == BindingMode.TwoWay || mode == BindingMode.OneWay)
|
||
Assert.False(weakViewModel.IsAlive, "ViewModel wasn't collected");
|
||
|
||
if (mode == BindingMode.TwoWay || mode == BindingMode.OneWayToSource)
|
||
Assert.False(weakBindable.IsAlive, "Bindable wasn't collected");
|
||
|
||
// WeakPropertyChangedProxy won't go away until the second GC, BindingExpressionPart unsubscribes in its finalizer
|
||
await Task.Yield();
|
||
GC.Collect();
|
||
GC.WaitForPendingFinalizers();
|
||
|
||
foreach (var proxy in proxies)
|
||
{
|
||
Assert.False(proxy.IsAlive, "WeakPropertyChangedProxy 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("1");
|
||
var property = BindableProperty.Create("TargetInt", typeof(int), typeof(MockBindable), 0);
|
||
var binding = CreateBinding();
|
||
((Binding)binding).Converter = converter;
|
||
|
||
var bindable = new MockBindable();
|
||
bindable.SetBinding(property, binding);
|
||
bindable.BindingContext = vm;
|
||
|
||
Assert.AreEqual(1, bindable.GetValue(property));
|
||
|
||
Assert.That(log.Messages.Count, Is.EqualTo(0),
|
||
"An error was logged: " + log.Messages.FirstOrDefault());
|
||
}
|
||
|
||
[Test]
|
||
public void ValueConverterBack()
|
||
{
|
||
var converter = new TestConverter<string, int>();
|
||
|
||
var vm = new MockViewModel();
|
||
var property = BindableProperty.Create<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
|
||
[TestCase("en-US"), TestCase("pt-PT"), TestCase("tr-TR")]
|
||
public void ValueConverterCulture(string culture)
|
||
{
|
||
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
|
||
|
||
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(culture, 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 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], Does.Contain(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], Does.Contain(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], Does.Contain(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], Does.Contain(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], Does.Contain(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], Does.Contain(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], Does.Contain(String.Format(BindingExpression.PropertyNotFoundErrorMessage,
|
||
"MissingProperty",
|
||
"Xamarin.Forms.Core.UnitTests.BindingBaseUnitTests+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
|
||
[TestCase("en-US", "0.5", 0.5, 0.9, "0.9")]
|
||
[TestCase("pt-PT", "0,5", 0.5, 0.9, "0,9")]
|
||
public void ConvertIsCultureAware(string culture, string sliderSetStringValue, double sliderExpectedDoubleValue, double sliderSetDoubleValue, string sliderExpectedStringValue)
|
||
{
|
||
System.Threading.Thread.CurrentThread.CurrentCulture = System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
|
||
|
||
var slider = new Slider();
|
||
var vm = new MockViewModel { Text = sliderSetStringValue };
|
||
slider.BindingContext = vm;
|
||
slider.SetBinding(Slider.ValueProperty, "Text", BindingMode.TwoWay);
|
||
|
||
Assert.That(slider.Value, Is.EqualTo(sliderExpectedDoubleValue));
|
||
|
||
slider.Value = sliderSetDoubleValue;
|
||
|
||
Assert.That(vm.Text, Is.EqualTo(sliderExpectedStringValue));
|
||
}
|
||
#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 async Task BindingDoesNotStayAliveForDeadTarget()
|
||
{
|
||
TestViewModel viewModel = new TestViewModel();
|
||
WeakReference bindingRef;
|
||
|
||
{
|
||
var binding = new Binding("Foo");
|
||
|
||
var button = new Button();
|
||
button.SetBinding(Button.TextProperty, binding);
|
||
button.BindingContext = viewModel;
|
||
|
||
bindingRef = new WeakReference(binding);
|
||
}
|
||
|
||
Assume.That(viewModel.InvocationListSize(), Is.EqualTo(1));
|
||
|
||
//NOTE: this was the only way I could "for sure" get the binding to get GC'd
|
||
GC.Collect();
|
||
await Task.Delay(10);
|
||
GC.Collect();
|
||
|
||
Assert.IsFalse(bindingRef.IsAlive, "Binding should not be alive!");
|
||
}
|
||
|
||
[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);
|
||
}
|
||
|
||
class VM57081
|
||
{
|
||
string _foo;
|
||
public string Foo
|
||
{
|
||
get
|
||
{
|
||
Count++;
|
||
return _foo;
|
||
}
|
||
set { _foo = value; }
|
||
}
|
||
|
||
public int Count { get; set; }
|
||
}
|
||
|
||
[Test]
|
||
// https://bugzilla.xamarin.com/show_bug.cgi?id=57081
|
||
public void BindingWithSourceNotReappliedWhenBindingContextIsChanged()
|
||
{
|
||
var bindable = new MockBindable();
|
||
var model = new VM57081();
|
||
var bp = BindableProperty.Create("foo", typeof(string), typeof(MockBindable), null);
|
||
Assume.That(model.Count, Is.EqualTo(0));
|
||
bindable.SetBinding(bp, new Binding { Path = "Foo", Source = model });
|
||
Assume.That(model.Count, Is.EqualTo(1));
|
||
bindable.BindingContext = new object();
|
||
Assert.That(model.Count, Is.EqualTo(1));
|
||
}
|
||
|
||
[Test]
|
||
// https://bugzilla.xamarin.com/show_bug.cgi?id=57081
|
||
public void BindingWithSourceNotReappliedWhenParented()
|
||
{
|
||
var view = new ContentView();
|
||
var model = new VM57081();
|
||
Assume.That(model.Count, Is.EqualTo(0));
|
||
view.SetBinding(BindableObject.BindingContextProperty, new Binding { Path = "Foo", Source = model });
|
||
Assume.That(model.Count, Is.EqualTo(1));
|
||
var parent = new ContentView { BindingContext = new object() };
|
||
parent.Content = view;
|
||
Assert.That(model.Count, Is.EqualTo(1));
|
||
}
|
||
|
||
class MockValueTupleContext
|
||
{
|
||
public (string Foo, string Bar) Tuple { get; set; }
|
||
}
|
||
|
||
[Test]
|
||
public void ValueTupleAsBindingContext()
|
||
{
|
||
var label = new Label
|
||
{
|
||
BindingContext = new MockValueTupleContext { Tuple = (Foo: "FOO", Bar: "BAR") },
|
||
};
|
||
|
||
label.SetBinding(Label.TextProperty, "Tuple.Foo");
|
||
Assert.AreEqual("FOO", label.Text);
|
||
label.SetBinding(Label.TextProperty, "Tuple[1]");
|
||
Assert.AreEqual("BAR", label.Text);
|
||
}
|
||
|
||
[Test]
|
||
public void OneTimeBindingDoesntUpdateOnPropertyChanged()
|
||
{
|
||
var view = new VisualElement();
|
||
var bp1t = BindableProperty.Create("Foo", typeof(string), typeof(VisualElement));
|
||
var bp1w = BindableProperty.Create("Foo", typeof(string), typeof(VisualElement));
|
||
var vm = new MockViewModel("foobar");
|
||
view.BindingContext = vm;
|
||
view.SetBinding(bp1t, "Text", mode: BindingMode.OneTime);
|
||
view.SetBinding(bp1w, "Text", mode: BindingMode.OneWay);
|
||
Assert.That(view.GetValue(bp1w), Is.EqualTo("foobar"));
|
||
Assert.That(view.GetValue(bp1t), Is.EqualTo("foobar"));
|
||
|
||
vm.Text = "qux";
|
||
Assert.That(view.GetValue(bp1w), Is.EqualTo("qux"));
|
||
Assert.That(view.GetValue(bp1t), Is.EqualTo("foobar"));
|
||
}
|
||
|
||
[Test]
|
||
public void OneTimeBindingUpdatesOnBindingContextChanged()
|
||
{
|
||
var view = new VisualElement();
|
||
var bp1t = BindableProperty.Create("Foo", typeof(string), typeof(VisualElement));
|
||
var bp1w = BindableProperty.Create("Foo", typeof(string), typeof(VisualElement));
|
||
view.BindingContext = new MockViewModel("foobar");
|
||
view.SetBinding(bp1t, "Text", mode: BindingMode.OneTime);
|
||
view.SetBinding(bp1w, "Text", mode: BindingMode.OneWay);
|
||
Assert.That(view.GetValue(bp1w), Is.EqualTo("foobar"));
|
||
Assert.That(view.GetValue(bp1t), Is.EqualTo("foobar"));
|
||
|
||
view.BindingContext = new MockViewModel("qux");
|
||
Assert.That(view.GetValue(bp1w), Is.EqualTo("qux"));
|
||
Assert.That(view.GetValue(bp1t), Is.EqualTo("qux"));
|
||
}
|
||
|
||
[Test]
|
||
public void FallbackValueWhenSourceIsNull()
|
||
{
|
||
var bindable = new MockBindable();
|
||
var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable), "default");
|
||
bindable.SetBinding(property, new Binding("Foo.Bar") { FallbackValue = "fallback" });
|
||
Assert.That(bindable.GetValue(property), Is.EqualTo("fallback"));
|
||
}
|
||
|
||
[Test]
|
||
//https://github.com/xamarin/Xamarin.Forms/issues/3467
|
||
public void TargetNullValueIgnoredWhenBindingIsResolved()
|
||
{
|
||
var bindable = new MockBindable();
|
||
var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable), "default");
|
||
bindable.SetBinding(property, new Binding("Text") { TargetNullValue = "fallback" });
|
||
Assert.That(bindable.GetValue(property), Is.EqualTo("default"));
|
||
bindable.BindingContext = new MockViewModel { Text = "Foo" };
|
||
Assert.That(bindable.GetValue(property), Is.EqualTo("Foo"));
|
||
}
|
||
|
||
[Test]
|
||
public void TargetNullValueFallback()
|
||
{
|
||
var bindable = new MockBindable();
|
||
var property = BindableProperty.Create("Foo", typeof(string), typeof(MockBindable), "default");
|
||
bindable.SetBinding(property, new Binding("Text") { TargetNullValue = "fallback" });
|
||
Assert.That(bindable.GetValue(property), Is.EqualTo("default"));
|
||
bindable.BindingContext = new MockViewModel();
|
||
Assert.That(bindable.GetValue(property), Is.EqualTo("fallback"));
|
||
}
|
||
|
||
[Test]
|
||
//https://github.com/xamarin/Xamarin.Forms/issues/3994
|
||
public void INPCOnBindingWithSource()
|
||
{
|
||
var page = new ContentPage { Title = "Foo" };
|
||
page.BindingContext = page;
|
||
var label = new Label();
|
||
page.Content = label;
|
||
|
||
label.SetBinding(Label.TextProperty, new Binding("BindingContext.Title", source: page));
|
||
Assert.That(label.Text, Is.EqualTo("Foo"));
|
||
|
||
page.Title = "Bar";
|
||
Assert.That(label.Text, Is.EqualTo("Bar"));
|
||
}
|
||
|
||
[Test]
|
||
//https://github.com/xamarin/Xamarin.Forms/issues/10405
|
||
public void TypeConversionExceptionIsCaughtAndLogged()
|
||
{
|
||
var label = new Label();
|
||
label.SetBinding(Label.TextColorProperty, "color");
|
||
|
||
Assert.DoesNotThrow(() => label.BindingContext = new { color = "" });
|
||
Assert.That(log.Messages.Count, Is.EqualTo(1), "No error logged");
|
||
}
|
||
}
|
||
} |