2117 строки
66 KiB
C#
2117 строки
66 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Globalization;
|
||
using System.ComponentModel;
|
||
using System.Linq;
|
||
using System.Runtime.CompilerServices;
|
||
using System.Threading.Tasks;
|
||
using NUnit.Framework;
|
||
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");
|
||
}
|
||
|
||
// 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
|
||
[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 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.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
|
||
[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 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);
|
||
}
|
||
}
|
||
} |