BindingTests.cs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. // Copyright (c) The Perspex Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using Moq;
  4. using Perspex.Controls;
  5. using Perspex.Data;
  6. using Perspex.Markup.Data;
  7. using Perspex.Markup.Xaml.Data;
  8. using ReactiveUI;
  9. using Xunit;
  10. namespace Perspex.Markup.Xaml.UnitTests.Data
  11. {
  12. public class BindingTests
  13. {
  14. [Fact]
  15. public void OneWay_Binding_Should_Be_Set_Up()
  16. {
  17. var source = new Source { Foo = "foo" };
  18. var target = new TextBlock { DataContext = source };
  19. var binding = new Binding
  20. {
  21. Path = "Foo",
  22. Mode = BindingMode.OneWay,
  23. };
  24. target.Bind(TextBox.TextProperty, binding);
  25. Assert.Equal("foo", target.Text);
  26. source.Foo = "bar";
  27. Assert.Equal("bar", target.Text);
  28. target.Text = "baz";
  29. Assert.Equal("bar", source.Foo);
  30. }
  31. [Fact]
  32. public void TwoWay_Binding_Should_Be_Set_Up()
  33. {
  34. var source = new Source { Foo = "foo" };
  35. var target = new TextBlock { DataContext = source };
  36. var binding = new Binding
  37. {
  38. Path = "Foo",
  39. Mode = BindingMode.TwoWay,
  40. };
  41. target.Bind(TextBox.TextProperty, binding);
  42. Assert.Equal("foo", target.Text);
  43. source.Foo = "bar";
  44. Assert.Equal("bar", target.Text);
  45. target.Text = "baz";
  46. Assert.Equal("baz", source.Foo);
  47. }
  48. [Fact]
  49. public void OneTime_Binding_Should_Be_Set_Up()
  50. {
  51. var source = new Source { Foo = "foo" };
  52. var target = new TextBlock { DataContext = source };
  53. var binding = new Binding
  54. {
  55. Path = "Foo",
  56. Mode = BindingMode.OneTime,
  57. };
  58. target.Bind(TextBox.TextProperty, binding);
  59. Assert.Equal("foo", target.Text);
  60. source.Foo = "bar";
  61. Assert.Equal("foo", target.Text);
  62. target.Text = "baz";
  63. Assert.Equal("bar", source.Foo);
  64. }
  65. [Fact]
  66. public void OneWayToSource_Binding_Should_Be_Set_Up()
  67. {
  68. var source = new Source { Foo = "foo" };
  69. var target = new TextBlock { DataContext = source, Text = "bar" };
  70. var binding = new Binding
  71. {
  72. Path = "Foo",
  73. Mode = BindingMode.OneWayToSource,
  74. };
  75. target.Bind(TextBox.TextProperty, binding);
  76. Assert.Equal("bar", source.Foo);
  77. target.Text = "baz";
  78. Assert.Equal("baz", source.Foo);
  79. source.Foo = "quz";
  80. Assert.Equal("baz", target.Text);
  81. }
  82. [Fact]
  83. public void Default_BindingMode_Should_Be_Used()
  84. {
  85. // Default for TextBox.Text is two-way.
  86. var source = new Source { Foo = "foo" };
  87. var target = new TextBlock { DataContext = source };
  88. var binding = new Binding
  89. {
  90. Path = "Foo",
  91. };
  92. target.Bind(TextBox.TextProperty, binding);
  93. Assert.Equal("foo", target.Text);
  94. source.Foo = "bar";
  95. Assert.Equal("bar", target.Text);
  96. target.Text = "baz";
  97. Assert.Equal("baz", source.Foo);
  98. }
  99. [Fact]
  100. public void DataContext_Binding_Should_Use_Parent_DataContext()
  101. {
  102. var parentDataContext = Mock.Of<IHeadered>(x => x.Header == (object)"Foo");
  103. var parent = new Decorator
  104. {
  105. Child = new Control(),
  106. DataContext = parentDataContext,
  107. };
  108. var binding = new Binding
  109. {
  110. Path = "Header",
  111. };
  112. parent.Child.Bind(Control.DataContextProperty, binding);
  113. Assert.Equal("Foo", parent.Child.DataContext);
  114. parentDataContext = Mock.Of<IHeadered>(x => x.Header == (object)"Bar");
  115. parent.DataContext = parentDataContext;
  116. Assert.Equal("Bar", parent.Child.DataContext);
  117. }
  118. [Fact]
  119. public void DataContext_Binding_Should_Track_Parent()
  120. {
  121. var parent = new Decorator
  122. {
  123. DataContext = new { Foo = "foo" },
  124. };
  125. var child = new Control();
  126. var binding = new Binding
  127. {
  128. Path = "Foo",
  129. };
  130. child.Bind(Control.DataContextProperty, binding);
  131. Assert.Null(child.DataContext);
  132. parent.Child = child;
  133. Assert.Equal("foo", child.DataContext);
  134. }
  135. [Fact]
  136. public void Should_Use_DefaultValueConverter_When_No_Converter_Specified()
  137. {
  138. var target = new TextBlock(); ;
  139. var binding = new Binding
  140. {
  141. Path = "Foo",
  142. };
  143. var result = binding.CreateSubject(target, TextBox.TextProperty);
  144. Assert.IsType<DefaultValueConverter>(((ExpressionSubject)result).Converter);
  145. }
  146. [Fact]
  147. public void Should_Use_Supplied_Converter()
  148. {
  149. var target = new TextBlock();
  150. var converter = new Mock<IValueConverter>();
  151. var binding = new Binding
  152. {
  153. Converter = converter.Object,
  154. Path = "Foo",
  155. };
  156. var result = binding.CreateSubject(target, TextBox.TextProperty);
  157. Assert.Same(converter.Object, ((ExpressionSubject)result).Converter);
  158. }
  159. [Fact]
  160. public void Should_Pass_ConverterParameter_To_Supplied_Converter()
  161. {
  162. var target = new TextBlock();
  163. var converter = new Mock<IValueConverter>();
  164. var binding = new Binding
  165. {
  166. Converter = converter.Object,
  167. ConverterParameter = "foo",
  168. Path = "Bar",
  169. };
  170. var result = binding.CreateSubject(target, TextBox.TextProperty);
  171. Assert.Same("foo", ((ExpressionSubject)result).ConverterParameter);
  172. }
  173. /// <summary>
  174. /// Tests a problem discovered with ListBox with selection.
  175. /// </summary>
  176. /// <remarks>
  177. /// - Items is bound to DataContext first, followed by say SelectedIndex
  178. /// - When the ListBox is removed from the visual tree, DataContext becomes null (as it's
  179. /// inherited)
  180. /// - This changes Items to null, which changes SelectedIndex to null as there are no
  181. /// longer any items
  182. /// - However, the news that DataContext is now null hasn't yet reached the SelectedIndex
  183. /// binding and so the unselection is sent back to the ViewModel
  184. /// </remarks>
  185. [Fact]
  186. public void Should_Not_Write_To_Old_DataContext()
  187. {
  188. var vm = new OldDataContextViewModel();
  189. var target = new OldDataContextTest();
  190. var fooBinding = new Binding
  191. {
  192. Path = "Foo",
  193. Mode = BindingMode.TwoWay,
  194. };
  195. var barBinding = new Binding
  196. {
  197. Path = "Bar",
  198. Mode = BindingMode.TwoWay,
  199. };
  200. // Bind Foo and Bar to the VM.
  201. target.Bind(OldDataContextTest.FooProperty, fooBinding);
  202. target.Bind(OldDataContextTest.BarProperty, barBinding);
  203. target.DataContext = vm;
  204. // Make sure the control's Foo and Bar properties are read from the VM
  205. Assert.Equal(1, target.GetValue(OldDataContextTest.FooProperty));
  206. Assert.Equal(2, target.GetValue(OldDataContextTest.BarProperty));
  207. // Set DataContext to null.
  208. target.DataContext = null;
  209. // Foo and Bar are no longer bound so they return 0, their default value.
  210. Assert.Equal(0, target.GetValue(OldDataContextTest.FooProperty));
  211. Assert.Equal(0, target.GetValue(OldDataContextTest.BarProperty));
  212. // The problem was here - DataContext is now null, setting Foo to 0. Bar is bound to
  213. // Foo so Bar also gets set to 0. However the Bar binding still had a reference to
  214. // the VM and so vm.Bar was set to 0 erroneously.
  215. Assert.Equal(1, vm.Foo);
  216. Assert.Equal(2, vm.Bar);
  217. }
  218. public class Source : ReactiveObject
  219. {
  220. private string _foo;
  221. public string Foo
  222. {
  223. get { return _foo; }
  224. set { this.RaiseAndSetIfChanged(ref _foo, value); }
  225. }
  226. }
  227. private class OldDataContextViewModel
  228. {
  229. public int Foo { get; set; } = 1;
  230. public int Bar { get; set; } = 2;
  231. }
  232. private class OldDataContextTest : Control
  233. {
  234. public static readonly StyledProperty<int> FooProperty =
  235. PerspexProperty.Register<OldDataContextTest, int>("Foo");
  236. public static readonly StyledProperty<int> BarProperty =
  237. PerspexProperty.Register<OldDataContextTest, int>("Bar");
  238. public OldDataContextTest()
  239. {
  240. Bind(BarProperty, this.GetObservable(FooProperty));
  241. }
  242. }
  243. }
  244. }