SelectingItemsControlTests_Multiple.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  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 System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using Perspex.Collections;
  8. using Perspex.Controls.Presenters;
  9. using Perspex.Controls.Primitives;
  10. using Perspex.Controls.Templates;
  11. using Perspex.Markup.Xaml.Binding;
  12. using Xunit;
  13. namespace Perspex.Controls.UnitTests.Primitives
  14. {
  15. public class SelectingItemsControlTests_Multiple
  16. {
  17. [Fact]
  18. public void Setting_SelectedIndex_Should_Add_To_SelectedItems()
  19. {
  20. var target = new TestSelector
  21. {
  22. Items = new[] { "foo", "bar" },
  23. Template = Template(),
  24. };
  25. target.ApplyTemplate();
  26. target.SelectedIndex = 1;
  27. Assert.Equal(new[] { "bar" }, target.SelectedItems.Cast<object>().ToList());
  28. }
  29. [Fact]
  30. public void Adding_SelectedItems_Should_Set_SelectedIndex()
  31. {
  32. var target = new TestSelector
  33. {
  34. Items = new[] { "foo", "bar" },
  35. Template = Template(),
  36. };
  37. target.ApplyTemplate();
  38. target.SelectedItems.Add("bar");
  39. Assert.Equal(1, target.SelectedIndex);
  40. }
  41. [Fact]
  42. public void Assigning_SelectedItems_Should_Set_SelectedIndex()
  43. {
  44. var target = new TestSelector
  45. {
  46. Items = new[] { "foo", "bar" },
  47. Template = Template(),
  48. };
  49. target.ApplyTemplate();
  50. target.SelectedItems = new[] { "bar" };
  51. Assert.Equal(1, target.SelectedIndex);
  52. }
  53. [Fact]
  54. public void Reassigning_SelectedItems_Should_Clear_Selection()
  55. {
  56. var target = new TestSelector
  57. {
  58. Items = new[] { "foo", "bar" },
  59. Template = Template(),
  60. };
  61. target.ApplyTemplate();
  62. target.SelectedItems.Add("bar");
  63. target.SelectedItems = new PerspexList<object>();
  64. Assert.Equal(-1, target.SelectedIndex);
  65. Assert.Equal(null, target.SelectedItem);
  66. }
  67. [Fact]
  68. public void Adding_First_SelectedItem_Should_Raise_SelectedIndex_SelectedItem_Changed()
  69. {
  70. var target = new TestSelector
  71. {
  72. Items = new[] { "foo", "bar" },
  73. Template = Template(),
  74. };
  75. bool indexRaised = false;
  76. bool itemRaised = false;
  77. target.PropertyChanged += (s, e) =>
  78. {
  79. indexRaised |= e.Property.Name == "SelectedIndex" &&
  80. (int)e.OldValue == -1 &&
  81. (int)e.NewValue == 1;
  82. itemRaised |= e.Property.Name == "SelectedItem" &&
  83. (string)e.OldValue == null &&
  84. (string)e.NewValue == "bar";
  85. };
  86. target.ApplyTemplate();
  87. target.SelectedItems.Add("bar");
  88. Assert.True(indexRaised);
  89. Assert.True(itemRaised);
  90. }
  91. [Fact]
  92. public void Adding_Subsequent_SelectedItems_Should_Not_Raise_SelectedIndex_SelectedItem_Changed()
  93. {
  94. var target = new TestSelector
  95. {
  96. Items = new[] { "foo", "bar" },
  97. Template = Template(),
  98. };
  99. target.ApplyTemplate();
  100. target.SelectedItems.Add("foo");
  101. bool raised = false;
  102. target.PropertyChanged += (s, e) =>
  103. raised |= e.Property.Name == "SelectedIndex" ||
  104. e.Property.Name == "SelectedItem";
  105. target.SelectedItems.Add("bar");
  106. Assert.False(raised);
  107. }
  108. [Fact]
  109. public void Removing_Last_SelectedItem_Should_Raise_SelectedIndex_Changed()
  110. {
  111. var target = new TestSelector
  112. {
  113. Items = new[] { "foo", "bar" },
  114. Template = Template(),
  115. };
  116. target.ApplyTemplate();
  117. target.SelectedItems.Add("foo");
  118. bool raised = false;
  119. target.PropertyChanged += (s, e) =>
  120. raised |= e.Property.Name == "SelectedIndex" &&
  121. (int)e.OldValue == 0 &&
  122. (int)e.NewValue == -1;
  123. target.SelectedItems.RemoveAt(0);
  124. Assert.True(raised);
  125. }
  126. [Fact]
  127. public void Adding_SelectedItems_Should_Set_Item_IsSelected()
  128. {
  129. var items = new[]
  130. {
  131. new ListBoxItem(),
  132. new ListBoxItem(),
  133. new ListBoxItem(),
  134. };
  135. var target = new TestSelector
  136. {
  137. Items = items,
  138. Template = Template(),
  139. };
  140. target.ApplyTemplate();
  141. target.SelectedItems.Add(items[0]);
  142. target.SelectedItems.Add(items[1]);
  143. var foo = target.Presenter.Panel.Children[0];
  144. Assert.True(items[0].IsSelected);
  145. Assert.True(items[1].IsSelected);
  146. Assert.False(items[2].IsSelected);
  147. }
  148. [Fact]
  149. public void Assigning_SelectedItems_Should_Set_Item_IsSelected()
  150. {
  151. var items = new[]
  152. {
  153. new ListBoxItem(),
  154. new ListBoxItem(),
  155. new ListBoxItem(),
  156. };
  157. var target = new TestSelector
  158. {
  159. Items = items,
  160. Template = Template(),
  161. };
  162. target.ApplyTemplate();
  163. target.SelectedItems = new PerspexList<object> { items[0], items[1] };
  164. Assert.True(items[0].IsSelected);
  165. Assert.True(items[1].IsSelected);
  166. Assert.False(items[2].IsSelected);
  167. }
  168. [Fact]
  169. public void Removing_SelectedItems_Should_Clear_Item_IsSelected()
  170. {
  171. var items = new[]
  172. {
  173. new ListBoxItem(),
  174. new ListBoxItem(),
  175. new ListBoxItem(),
  176. };
  177. var target = new TestSelector
  178. {
  179. Items = items,
  180. Template = Template(),
  181. };
  182. target.ApplyTemplate();
  183. target.SelectedItems.Add(items[0]);
  184. target.SelectedItems.Add(items[1]);
  185. target.SelectedItems.Remove(items[1]);
  186. Assert.True(items[0].IsSelected);
  187. Assert.False(items[1].IsSelected);
  188. }
  189. [Fact]
  190. public void Reassigning_SelectedItems_Should_Clear_Item_IsSelected()
  191. {
  192. var items = new[]
  193. {
  194. new ListBoxItem(),
  195. new ListBoxItem(),
  196. new ListBoxItem(),
  197. };
  198. var target = new TestSelector
  199. {
  200. Items = items,
  201. Template = Template(),
  202. };
  203. target.ApplyTemplate();
  204. target.SelectedItems.Add(items[0]);
  205. target.SelectedItems.Add(items[1]);
  206. target.SelectedItems = new PerspexList<object> { items[0], items[1] };
  207. Assert.False(items[0].IsSelected);
  208. Assert.False(items[1].IsSelected);
  209. }
  210. [Fact]
  211. public void Replacing_First_SelectedItem_Should_Update_SelectedItem_SelectedIndex()
  212. {
  213. var items = new[]
  214. {
  215. new ListBoxItem(),
  216. new ListBoxItem(),
  217. new ListBoxItem(),
  218. };
  219. var target = new TestSelector
  220. {
  221. Items = items,
  222. Template = Template(),
  223. };
  224. target.ApplyTemplate();
  225. target.SelectedIndex = 1;
  226. target.SelectedItems[0] = items[2];
  227. Assert.Equal(2, target.SelectedIndex);
  228. Assert.Equal(items[2], target.SelectedItem);
  229. Assert.False(items[0].IsSelected);
  230. Assert.False(items[1].IsSelected);
  231. Assert.True(items[2].IsSelected);
  232. }
  233. [Fact]
  234. public void Range_Select_Should_Select_Range()
  235. {
  236. var target = new TestSelector
  237. {
  238. Items = new[]
  239. {
  240. "foo",
  241. "bar",
  242. "baz",
  243. "qux",
  244. "qiz",
  245. "lol",
  246. },
  247. SelectionMode = SelectionMode.Multiple,
  248. Template = Template(),
  249. };
  250. target.ApplyTemplate();
  251. target.SelectedIndex = 1;
  252. target.SelectRange(3);
  253. Assert.Equal(new[] { "bar", "baz", "qux" }, target.SelectedItems.Cast<object>().ToList());
  254. }
  255. [Fact]
  256. public void Range_Select_Backwards_Should_Select_Range()
  257. {
  258. var target = new TestSelector
  259. {
  260. Items = new[]
  261. {
  262. "foo",
  263. "bar",
  264. "baz",
  265. "qux",
  266. "qiz",
  267. "lol",
  268. },
  269. SelectionMode = SelectionMode.Multiple,
  270. Template = Template(),
  271. };
  272. target.ApplyTemplate();
  273. target.SelectedIndex = 3;
  274. target.SelectRange(1);
  275. Assert.Equal(new[] { "qux", "baz", "bar" }, target.SelectedItems.Cast<object>().ToList());
  276. }
  277. [Fact]
  278. public void Second_Range_Select_Backwards_Should_Select_From_Original_Selection()
  279. {
  280. var target = new TestSelector
  281. {
  282. Items = new[]
  283. {
  284. "foo",
  285. "bar",
  286. "baz",
  287. "qux",
  288. "qiz",
  289. "lol",
  290. },
  291. SelectionMode = SelectionMode.Multiple,
  292. Template = Template(),
  293. };
  294. target.ApplyTemplate();
  295. target.SelectedIndex = 2;
  296. target.SelectRange(5);
  297. target.SelectRange(4);
  298. Assert.Equal(new[] { "baz", "qux", "qiz" }, target.SelectedItems.Cast<object>().ToList());
  299. }
  300. [Fact]
  301. public void Suprious_SelectedIndex_Changes_Should_Not_Be_Triggered()
  302. {
  303. var target = new TestSelector
  304. {
  305. Items = new[] { "foo", "bar", "baz" },
  306. Template = Template(),
  307. };
  308. target.ApplyTemplate();
  309. var selectedIndexes = new List<int>();
  310. target.GetObservable(TestSelector.SelectedIndexProperty).Subscribe(x => selectedIndexes.Add(x));
  311. target.SelectedItems = new PerspexList<object> { "bar", "baz" };
  312. target.SelectedItem = "foo";
  313. Assert.Equal(0, target.SelectedIndex);
  314. Assert.Equal(new[] { -1, 1, 0 }, selectedIndexes);
  315. }
  316. /// <summary>
  317. /// Tests a problem discovered with ListBox with selection.
  318. /// </summary>
  319. /// <remarks>
  320. /// - Items is bound to DataContext first, followed by say SelectedIndex
  321. /// - When the ListBox is removed from the visual tree, DataContext becomes null (as it's
  322. /// inherited)
  323. /// - This changes Items to null, which changes SelectedIndex to null as there are no
  324. /// longer any items
  325. /// - However, the news that DataContext is now null hasn't yet reached the SelectedItems
  326. /// binding and so the unselection is sent back to the ViewModel
  327. ///
  328. /// This is a similar problem to that tested by XamlBindingTest.Should_Not_Write_To_Old_DataContext.
  329. /// However, that tests a general property binding problem: here we are writing directly
  330. /// to the SelectedItems collection - not via a binding - so it's something that the
  331. /// binding system cannot solve. Instead we solve it by not clearing SelectedItems when
  332. /// DataContext is in the process of changing.
  333. /// </remarks>
  334. [Fact]
  335. public void Should_Not_Write_To_Old_DataContext()
  336. {
  337. var vm = new OldDataContextViewModel();
  338. var target = new TestSelector();
  339. var itemsBinding = new XamlBinding
  340. {
  341. SourcePropertyPath = "Items",
  342. BindingMode = BindingMode.OneWay,
  343. };
  344. var selectedItemsBinding = new XamlBinding
  345. {
  346. SourcePropertyPath = "SelectedItems",
  347. BindingMode = BindingMode.OneWay,
  348. };
  349. // Bind Items and SelectedItems to the VM.
  350. itemsBinding.Bind(target, TestSelector.ItemsProperty);
  351. selectedItemsBinding.Bind(target, TestSelector.SelectedItemsProperty);
  352. // Set DataContext and SelectedIndex
  353. target.DataContext = vm;
  354. target.SelectedIndex = 1;
  355. // Make sure SelectedItems are written back to VM.
  356. Assert.Equal(new[] { "bar" }, vm.SelectedItems);
  357. // Clear DataContext and ensure that SelectedItems is still set in the VM.
  358. target.DataContext = null;
  359. Assert.Equal(new[] { "bar" }, vm.SelectedItems);
  360. }
  361. private FuncControlTemplate Template()
  362. {
  363. return new FuncControlTemplate<SelectingItemsControl>(control =>
  364. new ItemsPresenter
  365. {
  366. Name = "itemsPresenter",
  367. [~ItemsPresenter.ItemsProperty] = control[~ItemsControl.ItemsProperty],
  368. [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty],
  369. });
  370. }
  371. private class TestSelector : SelectingItemsControl
  372. {
  373. public static readonly new PerspexProperty<IList> SelectedItemsProperty =
  374. SelectingItemsControl.SelectedItemsProperty;
  375. public new IList SelectedItems
  376. {
  377. get { return base.SelectedItems; }
  378. set { base.SelectedItems = value; }
  379. }
  380. public new SelectionMode SelectionMode
  381. {
  382. get { return base.SelectionMode; }
  383. set { base.SelectionMode = value; }
  384. }
  385. public void SelectRange(int index)
  386. {
  387. UpdateSelection(index, true, true);
  388. }
  389. }
  390. private class OldDataContextViewModel
  391. {
  392. public OldDataContextViewModel()
  393. {
  394. Items = new List<string> { "foo", "bar" };
  395. SelectedItems = new List<string>();
  396. }
  397. public List<string> Items { get; }
  398. public List<string> SelectedItems { get; }
  399. }
  400. }
  401. }