/Xamarin.Forms.Core.UnitTests/BindableLayoutTests.cs

https://github.com/xamarin/Xamarin.Forms · C# · 429 lines · 349 code · 79 blank · 1 comment · 9 complexity · 6ac6c3bd9802f1b1c07c5aea1670c30e MD5 · raw file

  1. using NUnit.Framework;
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using System.Collections.ObjectModel;
  6. using System.Collections.Specialized;
  7. using System.ComponentModel;
  8. using System.Linq;
  9. namespace Xamarin.Forms.Core.UnitTests
  10. {
  11. [TestFixture]
  12. public class BindableLayoutTests : BaseTestFixture
  13. {
  14. [Test]
  15. public void TracksEmpty()
  16. {
  17. var layout = new StackLayout
  18. {
  19. IsPlatformEnabled = true,
  20. };
  21. var itemsSource = new ObservableCollection<int>();
  22. BindableLayout.SetItemsSource(layout, itemsSource);
  23. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  24. }
  25. [Test]
  26. public void TracksAdd()
  27. {
  28. var layout = new StackLayout
  29. {
  30. IsPlatformEnabled = true,
  31. };
  32. var itemsSource = new ObservableCollection<int>();
  33. BindableLayout.SetItemsSource(layout, itemsSource);
  34. itemsSource.Add(1);
  35. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  36. }
  37. [Test]
  38. public void TracksInsert()
  39. {
  40. var layout = new StackLayout
  41. {
  42. IsPlatformEnabled = true,
  43. };
  44. var itemsSource = new ObservableCollection<int>() { 0, 1, 2, 3, 4 };
  45. BindableLayout.SetItemsSource(layout, itemsSource);
  46. itemsSource.Insert(2, 5);
  47. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  48. }
  49. [Test]
  50. public void TracksRemove()
  51. {
  52. var layout = new StackLayout
  53. {
  54. IsPlatformEnabled = true,
  55. };
  56. var itemsSource = new ObservableCollection<int>() { 0, 1 };
  57. BindableLayout.SetItemsSource(layout, itemsSource);
  58. itemsSource.RemoveAt(0);
  59. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  60. itemsSource.Remove(1);
  61. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  62. }
  63. [Test]
  64. public void TracksRemoveAll()
  65. {
  66. var layout = new StackLayout
  67. {
  68. IsPlatformEnabled = true,
  69. };
  70. var itemsSource = new ObservableRangeCollection<int>(Enumerable.Range(0, 10));
  71. BindableLayout.SetItemsSource(layout, itemsSource);
  72. itemsSource.RemoveAll();
  73. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  74. }
  75. [Test]
  76. public void TracksReplace()
  77. {
  78. var layout = new StackLayout
  79. {
  80. IsPlatformEnabled = true,
  81. };
  82. var itemsSource = new ObservableCollection<int>() { 0, 1, 2 };
  83. BindableLayout.SetItemsSource(layout, itemsSource);
  84. itemsSource[0] = 3;
  85. itemsSource[1] = 4;
  86. itemsSource[2] = 5;
  87. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  88. }
  89. [Test]
  90. public void TracksMove()
  91. {
  92. var layout = new StackLayout
  93. {
  94. IsPlatformEnabled = true,
  95. };
  96. var itemsSource = new ObservableCollection<int>() { 0, 1 };
  97. BindableLayout.SetItemsSource(layout, itemsSource);
  98. itemsSource.Move(0, 1);
  99. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  100. itemsSource.Move(1, 0);
  101. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  102. }
  103. [Test]
  104. public void TracksClear()
  105. {
  106. var layout = new StackLayout
  107. {
  108. IsPlatformEnabled = true,
  109. };
  110. var itemsSource = new ObservableCollection<int>() { 0, 1 };
  111. BindableLayout.SetItemsSource(layout, itemsSource);
  112. itemsSource.Clear();
  113. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  114. }
  115. [Test]
  116. public void TracksNull()
  117. {
  118. var layout = new StackLayout
  119. {
  120. IsPlatformEnabled = true,
  121. };
  122. var itemsSource = new ObservableCollection<int>(Enumerable.Range(0, 10));
  123. BindableLayout.SetItemsSource(layout, itemsSource);
  124. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  125. itemsSource = null;
  126. BindableLayout.SetItemsSource(layout, itemsSource);
  127. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  128. }
  129. [Test]
  130. public void ItemTemplateIsSet()
  131. {
  132. var layout = new StackLayout
  133. {
  134. IsPlatformEnabled = true,
  135. };
  136. var itemsSource = new ObservableCollection<int>(Enumerable.Range(0, 10));
  137. BindableLayout.SetItemsSource(layout, itemsSource);
  138. BindableLayout.SetItemTemplate(layout, new DataTemplateBoxView());
  139. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  140. Assert.AreEqual(itemsSource.Count, layout.Children.Cast<BoxView>().Count());
  141. }
  142. [Test]
  143. public void ItemTemplateSelectorIsSet()
  144. {
  145. var layout = new StackLayout
  146. {
  147. IsPlatformEnabled = true,
  148. };
  149. var itemsSource = new ObservableCollection<int>(Enumerable.Range(0, 10));
  150. BindableLayout.SetItemsSource(layout, itemsSource);
  151. BindableLayout.SetItemTemplateSelector(layout, new DataTemplateSelectorFrame());
  152. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  153. Assert.AreEqual(itemsSource.Count, layout.Children.Cast<Frame>().Count());
  154. }
  155. [Test]
  156. public void ContainerIsPassedInSelectTemplate()
  157. {
  158. var layout = new StackLayout
  159. {
  160. IsPlatformEnabled = true,
  161. };
  162. var itemsSource = new ObservableCollection<int>(Enumerable.Range(0, 10));
  163. BindableLayout.SetItemsSource(layout, itemsSource);
  164. int containerPassedCount = 0;
  165. BindableLayout.SetItemTemplateSelector(layout, new MyDataTemplateSelectorTest((item, container) =>
  166. {
  167. if (container == layout)
  168. ++containerPassedCount;
  169. return null;
  170. }));
  171. Assert.AreEqual(containerPassedCount, itemsSource.Count);
  172. }
  173. [Test]
  174. public void ItemTemplateTakesPrecendenceOverItemTemplateSelector()
  175. {
  176. var layout = new StackLayout
  177. {
  178. IsPlatformEnabled = true,
  179. };
  180. var itemsSource = new ObservableCollection<int>(Enumerable.Range(0, 10));
  181. BindableLayout.SetItemsSource(layout, itemsSource);
  182. BindableLayout.SetItemTemplate(layout, new DataTemplateBoxView());
  183. BindableLayout.SetItemTemplateSelector(layout, new DataTemplateSelectorFrame());
  184. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  185. Assert.AreEqual(itemsSource.Count, layout.Children.Cast<BoxView>().Count());
  186. }
  187. [Test]
  188. public void ItemsSourceTakePrecendenceOverLayoutChildren()
  189. {
  190. var layout = new StackLayout
  191. {
  192. IsPlatformEnabled = true,
  193. };
  194. layout.Children.Add(new Label());
  195. layout.Children.Add(new Label());
  196. layout.Children.Add(new Label());
  197. var itemsSource = new ObservableCollection<int>(Enumerable.Range(0, 10));
  198. BindableLayout.SetItemsSource(layout, itemsSource);
  199. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  200. }
  201. [Test]
  202. public void LayoutIsGarbageCollectedAfterItsRemoved()
  203. {
  204. var layout = new StackLayout
  205. {
  206. IsPlatformEnabled = true,
  207. };
  208. var itemsSource = new ObservableCollection<int>(Enumerable.Range(0, 10));
  209. BindableLayout.SetItemsSource(layout, itemsSource);
  210. var pageRoot = new Grid();
  211. pageRoot.Children.Add(layout);
  212. var page = new ContentPage() { Content = pageRoot };
  213. var weakReference = new WeakReference(layout);
  214. pageRoot.Children.Remove(layout);
  215. layout = null;
  216. GC.Collect();
  217. GC.WaitForPendingFinalizers();
  218. Assert.IsFalse(weakReference.IsAlive);
  219. }
  220. [Test]
  221. public void ThrowsExceptionOnUsingDataTemplateSelectorForItemTemplate()
  222. {
  223. var layout = new StackLayout
  224. {
  225. IsPlatformEnabled = true,
  226. };
  227. var itemsSource = new ObservableCollection<int>(Enumerable.Range(0, 10));
  228. BindableLayout.SetItemsSource(layout, itemsSource);
  229. Assert.Throws(typeof(NotSupportedException), () => BindableLayout.SetItemTemplate(layout, new DataTemplateSelectorFrame()));
  230. }
  231. [Test]
  232. public void DontTrackAfterItemsSourceChanged()
  233. {
  234. var layout = new StackLayout
  235. {
  236. IsPlatformEnabled = true,
  237. };
  238. var itemsSource = new ObservableCollection<int>(Enumerable.Range(0, 10));
  239. BindableLayout.SetItemsSource(layout, itemsSource);
  240. BindableLayout.SetItemsSource(layout, new ObservableCollection<int>(Enumerable.Range(0, 10)));
  241. bool wasCalled = false;
  242. layout.ChildAdded += (_, __) => wasCalled = true;
  243. itemsSource.Add(11);
  244. Assert.IsFalse(wasCalled);
  245. }
  246. [Test]
  247. public void WorksWithNullItems()
  248. {
  249. var layout = new StackLayout
  250. {
  251. IsPlatformEnabled = true,
  252. };
  253. var itemsSource = new ObservableCollection<int?>(Enumerable.Range(0, 10).Cast<int?>());
  254. itemsSource.Add(null);
  255. BindableLayout.SetItemsSource(layout, itemsSource);
  256. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  257. }
  258. [Test]
  259. public void WorksWithDuplicateItems()
  260. {
  261. var layout = new StackLayout
  262. {
  263. IsPlatformEnabled = true,
  264. };
  265. var itemsSource = new ObservableCollection<int>(Enumerable.Range(0, 10));
  266. foreach (int item in itemsSource.ToList())
  267. {
  268. itemsSource.Add(item);
  269. }
  270. BindableLayout.SetItemsSource(layout, itemsSource);
  271. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  272. itemsSource.Remove(0);
  273. Assert.IsTrue(IsLayoutWithItemsSource(itemsSource, layout));
  274. }
  275. // Checks if for every item in the items source there's a corresponding view
  276. static bool IsLayoutWithItemsSource(IEnumerable itemsSource, Layout layout)
  277. {
  278. if (itemsSource == null)
  279. {
  280. return layout.Children.Count() == 0;
  281. }
  282. int i = 0;
  283. foreach (object item in itemsSource)
  284. {
  285. if (BindableLayout.GetItemTemplate(layout) is DataTemplate dataTemplate ||
  286. BindableLayout.GetItemTemplateSelector(layout) is DataTemplateSelector dataTemplateSelector)
  287. {
  288. if (!Equals(item, layout.Children[i].BindingContext))
  289. {
  290. return false;
  291. }
  292. }
  293. else
  294. {
  295. if (!Equals(item?.ToString(), ((Label)layout.Children[i]).Text))
  296. {
  297. return false;
  298. }
  299. }
  300. ++i;
  301. }
  302. return layout.Children.Count == i;
  303. }
  304. class DataTemplateBoxView : DataTemplate
  305. {
  306. public DataTemplateBoxView() : base(() => new BoxView())
  307. {
  308. }
  309. }
  310. class DataTemplateFrame : DataTemplate
  311. {
  312. public DataTemplateFrame() : base(() => new Frame())
  313. {
  314. }
  315. }
  316. class DataTemplateSelectorFrame : DataTemplateSelector
  317. {
  318. DataTemplateFrame dt = new DataTemplateFrame();
  319. protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
  320. {
  321. return dt;
  322. }
  323. }
  324. class ObservableRangeCollection<T> : ObservableCollection<T>
  325. {
  326. public ObservableRangeCollection(IEnumerable<T> collection)
  327. : base(collection)
  328. {
  329. }
  330. public void RemoveAll()
  331. {
  332. CheckReentrancy();
  333. var changedItems = new List<T>(Items);
  334. foreach (var i in changedItems)
  335. Items.Remove(i);
  336. OnPropertyChanged(new PropertyChangedEventArgs("Count"));
  337. OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
  338. OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, changedItems, 0));
  339. }
  340. }
  341. class MyDataTemplateSelectorTest : DataTemplateSelector
  342. {
  343. readonly Func<object, BindableObject, DataTemplate> _func;
  344. public MyDataTemplateSelectorTest(Func<object, BindableObject, DataTemplate> func)
  345. => _func = func;
  346. protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
  347. => _func(item, container);
  348. }
  349. }
  350. }