ExpressionObserverTests_Indexer.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Collections.ObjectModel;
  6. using System.Reactive.Linq;
  7. using System.Threading.Tasks;
  8. using Avalonia.Collections;
  9. using Avalonia.Diagnostics;
  10. using Avalonia.Data.Core;
  11. using Avalonia.UnitTests;
  12. using Xunit;
  13. using Avalonia.Markup.Parsers;
  14. namespace Avalonia.Base.UnitTests.Data.Core
  15. {
  16. public class ExpressionObserverTests_Indexer
  17. {
  18. [Fact]
  19. public async Task Should_Get_Array_Value()
  20. {
  21. var data = new { Foo = new [] { "foo", "bar" } };
  22. var target = ExpressionObserver.Create(data, x => x.Foo[1]);
  23. var result = await target.Take(1);
  24. Assert.Equal("bar", result);
  25. GC.KeepAlive(data);
  26. }
  27. [Fact]
  28. public async Task Should_Get_MultiDimensional_Array_Value()
  29. {
  30. var data = new { Foo = new[,] { { "foo", "bar" }, { "baz", "qux" } } };
  31. var target = ExpressionObserver.Create(data, o => o.Foo[1, 1]);
  32. var result = await target.Take(1);
  33. Assert.Equal("qux", result);
  34. GC.KeepAlive(data);
  35. }
  36. [Fact]
  37. public async Task Should_Get_Value_For_String_Indexer()
  38. {
  39. var data = new { Foo = new Dictionary<string, string> { { "foo", "bar" }, { "baz", "qux" } } };
  40. var target = ExpressionObserver.Create(data, o => o.Foo["foo"]);
  41. var result = await target.Take(1);
  42. Assert.Equal("bar", result);
  43. GC.KeepAlive(data);
  44. }
  45. [Fact]
  46. public async Task Should_Get_Value_For_Non_String_Indexer()
  47. {
  48. var data = new { Foo = new Dictionary<double, string> { { 1.0, "bar" }, { 2.0, "qux" } } };
  49. var target = ExpressionObserver.Create(data, o => o.Foo[1.0]);
  50. var result = await target.Take(1);
  51. Assert.Equal("bar", result);
  52. GC.KeepAlive(data);
  53. }
  54. [Fact]
  55. public async Task Array_Out_Of_Bounds_Should_Return_UnsetValue()
  56. {
  57. var data = new { Foo = new[] { "foo", "bar" } };
  58. var target = ExpressionObserver.Create(data, o => o.Foo[2]);
  59. var result = await target.Take(1);
  60. Assert.Equal(AvaloniaProperty.UnsetValue, result);
  61. GC.KeepAlive(data);
  62. }
  63. [Fact]
  64. public async Task List_Out_Of_Bounds_Should_Return_UnsetValue()
  65. {
  66. var data = new { Foo = new List<string> { "foo", "bar" } };
  67. var target = ExpressionObserver.Create(data, o => o.Foo[2]);
  68. var result = await target.Take(1);
  69. Assert.Equal(AvaloniaProperty.UnsetValue, result);
  70. GC.KeepAlive(data);
  71. }
  72. [Fact]
  73. public async Task Should_Get_List_Value()
  74. {
  75. var data = new { Foo = new List<string> { "foo", "bar" } };
  76. var target = ExpressionObserver.Create(data, o => o.Foo[1]);
  77. var result = await target.Take(1);
  78. Assert.Equal("bar", result);
  79. GC.KeepAlive(data);
  80. }
  81. [Fact]
  82. public void Should_Track_INCC_Add()
  83. {
  84. var data = new { Foo = new AvaloniaList<string> { "foo", "bar" } };
  85. var target = ExpressionObserver.Create(data, o => o.Foo[2]);
  86. var result = new List<object>();
  87. using (var sub = target.Subscribe(x => result.Add(x)))
  88. {
  89. data.Foo.Add("baz");
  90. }
  91. Assert.Equal(new[] { AvaloniaProperty.UnsetValue, "baz" }, result);
  92. Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers());
  93. GC.KeepAlive(data);
  94. }
  95. [Fact]
  96. public void Should_Track_INCC_Remove()
  97. {
  98. var data = new { Foo = new AvaloniaList<string> { "foo", "bar" } };
  99. var target = ExpressionObserver.Create(data, o => o.Foo[0]);
  100. var result = new List<object>();
  101. using (var sub = target.Subscribe(x => result.Add(x)))
  102. {
  103. data.Foo.RemoveAt(0);
  104. }
  105. Assert.Equal(new[] { "foo", "bar" }, result);
  106. Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers());
  107. GC.KeepAlive(data);
  108. }
  109. [Fact]
  110. public void Should_Track_INCC_Replace()
  111. {
  112. var data = new { Foo = new AvaloniaList<string> { "foo", "bar" } };
  113. var target = ExpressionObserver.Create(data, o => o.Foo[1]);
  114. var result = new List<object>();
  115. using (var sub = target.Subscribe(x => result.Add(x)))
  116. {
  117. data.Foo[1] = "baz";
  118. }
  119. Assert.Equal(new[] { "bar", "baz" }, result);
  120. Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers());
  121. GC.KeepAlive(data);
  122. }
  123. [Fact]
  124. public void Should_Track_INCC_Move()
  125. {
  126. // Using ObservableCollection here because AvaloniaList does not yet have a Move
  127. // method, but even if it did we need to test with ObservableCollection as well
  128. // as AvaloniaList as it implements PropertyChanged as an explicit interface event.
  129. var data = new { Foo = new ObservableCollection<string> { "foo", "bar" } };
  130. var target = ExpressionObserver.Create(data, o => o.Foo[1]);
  131. var result = new List<object>();
  132. var sub = target.Subscribe(x => result.Add(x));
  133. data.Foo.Move(0, 1);
  134. Assert.Equal(new[] { "bar", "foo" }, result);
  135. GC.KeepAlive(sub);
  136. GC.KeepAlive(data);
  137. }
  138. [Fact]
  139. public void Should_Track_INCC_Reset()
  140. {
  141. var data = new { Foo = new AvaloniaList<string> { "foo", "bar" } };
  142. var target = ExpressionObserver.Create(data, o => o.Foo[1]);
  143. var result = new List<object>();
  144. var sub = target.Subscribe(x => result.Add(x));
  145. data.Foo.Clear();
  146. Assert.Equal(new[] { "bar", AvaloniaProperty.UnsetValue }, result);
  147. GC.KeepAlive(sub);
  148. GC.KeepAlive(data);
  149. }
  150. [Fact]
  151. public void Should_Track_NonIntegerIndexer()
  152. {
  153. var data = new { Foo = new NonIntegerIndexer() };
  154. data.Foo["foo"] = "bar";
  155. data.Foo["baz"] = "qux";
  156. var target = ExpressionObserver.Create(data, o => o.Foo["foo"]);
  157. var result = new List<object>();
  158. using (var sub = target.Subscribe(x => result.Add(x)))
  159. {
  160. data.Foo["foo"] = "bar2";
  161. }
  162. var expected = new[] { "bar", "bar2" };
  163. Assert.Equal(expected, result);
  164. Assert.Equal(0, data.Foo.PropertyChangedSubscriptionCount);
  165. GC.KeepAlive(data);
  166. }
  167. [Fact]
  168. public void Should_SetArrayIndex()
  169. {
  170. var data = new { Foo = new[] { "foo", "bar" } };
  171. var target = ExpressionObserver.Create(data, o => o.Foo[1]);
  172. using (target.Subscribe(_ => { }))
  173. {
  174. Assert.True(target.SetValue("baz"));
  175. }
  176. Assert.Equal("baz", data.Foo[1]);
  177. GC.KeepAlive(data);
  178. }
  179. [Fact]
  180. public void Should_Set_ExistingDictionaryEntry()
  181. {
  182. var data = new
  183. {
  184. Foo = new Dictionary<string, int>
  185. {
  186. {"foo", 1 }
  187. }
  188. };
  189. var target = ExpressionObserver.Create(data, o => o.Foo["foo"]);
  190. using (target.Subscribe(_ => { }))
  191. {
  192. Assert.True(target.SetValue(4));
  193. }
  194. Assert.Equal(4, data.Foo["foo"]);
  195. GC.KeepAlive(data);
  196. }
  197. [Fact]
  198. public void Should_Add_NewDictionaryEntry()
  199. {
  200. var data = new
  201. {
  202. Foo = new Dictionary<string, int>
  203. {
  204. {"foo", 1 }
  205. }
  206. };
  207. var target = ExpressionObserver.Create(data, o => o.Foo["bar"]);
  208. using (target.Subscribe(_ => { }))
  209. {
  210. Assert.True(target.SetValue(4));
  211. }
  212. Assert.Equal(4, data.Foo["bar"]);
  213. GC.KeepAlive(data);
  214. }
  215. [Fact]
  216. public void Should_Set_NonIntegerIndexer()
  217. {
  218. var data = new { Foo = new NonIntegerIndexer() };
  219. data.Foo["foo"] = "bar";
  220. data.Foo["baz"] = "qux";
  221. var target = ExpressionObserver.Create(data, o => o.Foo["foo"]);
  222. using (target.Subscribe(_ => { }))
  223. {
  224. Assert.True(target.SetValue("bar2"));
  225. }
  226. Assert.Equal("bar2", data.Foo["foo"]);
  227. GC.KeepAlive(data);
  228. }
  229. [Fact]
  230. public async Task Indexer_Only_Binding_Works()
  231. {
  232. var data = new[] { 1, 2, 3 };
  233. var target = ExpressionObserver.Create(data, o => o[1]);
  234. var value = await target.Take(1);
  235. Assert.Equal(data[1], value);
  236. }
  237. private class NonIntegerIndexer : NotifyingBase
  238. {
  239. private readonly Dictionary<string, string> _storage = new Dictionary<string, string>();
  240. public string this[string key]
  241. {
  242. get
  243. {
  244. return _storage[key];
  245. }
  246. set
  247. {
  248. _storage[key] = value;
  249. RaisePropertyChanged(CommonPropertyNames.IndexerName);
  250. }
  251. }
  252. }
  253. }
  254. }