ListBoxTests.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Collections.Specialized;
  5. using System.Linq;
  6. using System.Reactive.Subjects;
  7. using Avalonia.Collections;
  8. using Avalonia.Controls.Presenters;
  9. using Avalonia.Controls.Primitives;
  10. using Avalonia.Controls.Templates;
  11. using Avalonia.Data;
  12. using Avalonia.Input;
  13. using Avalonia.Layout;
  14. using Avalonia.LogicalTree;
  15. using Avalonia.Styling;
  16. using Avalonia.UnitTests;
  17. using Avalonia.VisualTree;
  18. using Xunit;
  19. namespace Avalonia.Controls.UnitTests
  20. {
  21. public class ListBoxTests
  22. {
  23. private MouseTestHelper _mouse = new MouseTestHelper();
  24. [Fact]
  25. public void Should_Use_ItemTemplate_To_Create_Item_Content()
  26. {
  27. var target = new ListBox
  28. {
  29. Template = ListBoxTemplate(),
  30. ItemsSource = new[] { "Foo" },
  31. ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
  32. };
  33. Prepare(target);
  34. var container = (ListBoxItem)target.Presenter.Panel.Children[0];
  35. Assert.IsType<Canvas>(container.Presenter.Child);
  36. }
  37. [Fact]
  38. public void ListBox_Should_Find_ItemsPresenter_In_ScrollViewer()
  39. {
  40. var target = new ListBox
  41. {
  42. Template = ListBoxTemplate(),
  43. };
  44. Prepare(target);
  45. Assert.IsType<ItemsPresenter>(target.Presenter);
  46. }
  47. [Fact]
  48. public void ListBox_Should_Find_Scrollviewer_In_Template()
  49. {
  50. var target = new ListBox
  51. {
  52. Template = ListBoxTemplate(),
  53. };
  54. ScrollViewer viewer = null;
  55. target.TemplateApplied += (sender, e) =>
  56. {
  57. viewer = target.Scroll as ScrollViewer;
  58. };
  59. Prepare(target);
  60. Assert.NotNull(viewer);
  61. }
  62. [Fact]
  63. public void ListBoxItem_Containers_Should_Be_Generated()
  64. {
  65. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  66. {
  67. var items = new[] { "Foo", "Bar", "Baz " };
  68. var target = new ListBox
  69. {
  70. Template = ListBoxTemplate(),
  71. ItemsSource = items,
  72. };
  73. Prepare(target);
  74. var text = target.Presenter.Panel.Children
  75. .OfType<ListBoxItem>()
  76. .Select(x => x.Presenter.Child)
  77. .OfType<TextBlock>()
  78. .Select(x => x.Text)
  79. .ToList();
  80. Assert.Equal(items, text);
  81. }
  82. }
  83. [Fact]
  84. public void Container_Should_Have_Theme_Set_To_ItemContainerTheme()
  85. {
  86. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  87. {
  88. var items = new[] { "Foo", "Bar", "Baz " };
  89. var theme = new ControlTheme(typeof(ListBoxItem));
  90. var target = new ListBox
  91. {
  92. Template = ListBoxTemplate(),
  93. ItemsSource = items,
  94. ItemContainerTheme = theme,
  95. };
  96. Prepare(target);
  97. var container = (ListBoxItem)target.Presenter.Panel.Children[0];
  98. Assert.Same(container.Theme, theme);
  99. }
  100. }
  101. [Fact]
  102. public void Inline_Item_Should_Have_Theme_Set_To_ItemContainerTheme()
  103. {
  104. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  105. {
  106. var theme = new ControlTheme(typeof(ListBoxItem));
  107. var target = new ListBox
  108. {
  109. Template = ListBoxTemplate(),
  110. Items = { new ListBoxItem() },
  111. ItemContainerTheme = theme,
  112. };
  113. Prepare(target);
  114. var container = (ListBoxItem)target.Presenter.Panel.Children[0];
  115. Assert.Same(container.Theme, theme);
  116. }
  117. }
  118. [Fact]
  119. public void LogicalChildren_Should_Be_Set_For_DataTemplate_Generated_Items()
  120. {
  121. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  122. {
  123. var target = new ListBox
  124. {
  125. Template = ListBoxTemplate(),
  126. ItemsSource = new[] { "Foo", "Bar", "Baz " },
  127. };
  128. Prepare(target);
  129. Assert.Equal(3, target.GetLogicalChildren().Count());
  130. foreach (var child in target.GetLogicalChildren())
  131. {
  132. Assert.IsType<ListBoxItem>(child);
  133. }
  134. }
  135. }
  136. [Fact]
  137. public void DataContexts_Should_Be_Correctly_Set()
  138. {
  139. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  140. {
  141. var items = new object[]
  142. {
  143. "Foo",
  144. new Item("Bar"),
  145. new TextBlock { Text = "Baz" },
  146. new ListBoxItem { Content = "Qux" },
  147. };
  148. var target = new ListBox
  149. {
  150. Template = ListBoxTemplate(),
  151. DataContext = "Base",
  152. DataTemplates =
  153. {
  154. new FuncDataTemplate<Item>((x, _) => new Button { Content = x })
  155. },
  156. ItemsSource = items,
  157. };
  158. Prepare(target);
  159. var dataContexts = target.Presenter.Panel.Children
  160. .Cast<Control>()
  161. .Select(x => x.DataContext)
  162. .ToList();
  163. Assert.Equal(
  164. new object[] { items[0], items[1], "Base", "Base" },
  165. dataContexts);
  166. }
  167. }
  168. [Fact]
  169. public void Selection_Should_Be_Cleared_On_Recycled_Items()
  170. {
  171. using (UnitTestApplication.Start(TestServices.StyledWindow))
  172. {
  173. var target = new ListBox
  174. {
  175. Template = ListBoxTemplate(),
  176. ItemsSource = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
  177. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
  178. SelectedIndex = 0,
  179. };
  180. Prepare(target);
  181. // Make sure we're virtualized and first item is selected.
  182. Assert.Equal(10, target.Presenter.Panel.Children.Count);
  183. Assert.True(((ListBoxItem)target.Presenter.Panel.Children[0]).IsSelected);
  184. // Scroll down a page.
  185. target.Scroll.Offset = new Vector(0, 10);
  186. Layout(target);
  187. // Make sure recycled item isn't now selected.
  188. Assert.False(((ListBoxItem)target.Presenter.Panel.Children[0]).IsSelected);
  189. }
  190. }
  191. [Fact]
  192. public void ScrollViewer_Should_Have_Correct_Extent_And_Viewport()
  193. {
  194. using (UnitTestApplication.Start(TestServices.StyledWindow))
  195. {
  196. var target = new ListBox
  197. {
  198. Template = ListBoxTemplate(),
  199. ItemsSource = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
  200. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
  201. SelectedIndex = 0,
  202. };
  203. Prepare(target);
  204. Assert.Equal(new Size(100, 200), target.Scroll.Extent);
  205. Assert.Equal(new Size(100, 100), target.Scroll.Viewport);
  206. }
  207. }
  208. [Fact]
  209. public void Containers_Correct_After_Clear_Add_Remove()
  210. {
  211. using (UnitTestApplication.Start(TestServices.StyledWindow))
  212. {
  213. // Issue #1936
  214. var items = new AvaloniaList<string>(Enumerable.Range(0, 11).Select(x => $"Item {x}"));
  215. var target = new ListBox
  216. {
  217. Template = ListBoxTemplate(),
  218. ItemsSource = items,
  219. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
  220. SelectedIndex = 0,
  221. };
  222. Prepare(target);
  223. items.Clear();
  224. items.AddRange(Enumerable.Range(0, 11).Select(x => $"Item {x}"));
  225. Layout(target);
  226. items.Remove("Item 2");
  227. Layout(target);
  228. var actual = target.GetRealizedContainers().Cast<ListBoxItem>().Select(x => (string)x.Content).ToList();
  229. Assert.Equal(items.OrderBy(x => x), actual.OrderBy(x => x));
  230. }
  231. }
  232. [Fact]
  233. public void Toggle_Selection_Should_Update_Containers()
  234. {
  235. using (UnitTestApplication.Start(TestServices.StyledWindow))
  236. {
  237. var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToArray();
  238. var target = new ListBox
  239. {
  240. Template = ListBoxTemplate(),
  241. ItemsSource = items,
  242. SelectionMode = SelectionMode.Toggle,
  243. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 })
  244. };
  245. Prepare(target);
  246. var lbItems = target.GetLogicalChildren().OfType<ListBoxItem>().ToArray();
  247. var item = lbItems[0];
  248. Assert.Equal(false, item.IsSelected);
  249. RaisePressedEvent(target, item, MouseButton.Left);
  250. Assert.Equal(true, item.IsSelected);
  251. RaisePressedEvent(target, item, MouseButton.Left);
  252. Assert.Equal(false, item.IsSelected);
  253. }
  254. }
  255. [Fact]
  256. public void Can_Decrease_Number_Of_Materialized_Items_By_Removing_From_Source_Collection()
  257. {
  258. using (UnitTestApplication.Start(TestServices.StyledWindow))
  259. {
  260. var items = new AvaloniaList<string>(Enumerable.Range(0, 20).Select(x => $"Item {x}"));
  261. var target = new ListBox
  262. {
  263. Template = ListBoxTemplate(),
  264. ItemsSource = items,
  265. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 })
  266. };
  267. Prepare(target);
  268. target.Scroll.Offset = new Vector(0, 1);
  269. items.RemoveRange(0, 11);
  270. }
  271. }
  272. private void RaisePressedEvent(ListBox listBox, ListBoxItem item, MouseButton mouseButton)
  273. {
  274. _mouse.Click(listBox, item, mouseButton);
  275. }
  276. [Fact]
  277. public void LayoutManager_Should_Measure_Arrange_All()
  278. {
  279. using (UnitTestApplication.Start(TestServices.StyledWindow))
  280. {
  281. var items = new AvaloniaList<string>(Enumerable.Range(1, 7).Select(v => v.ToString()));
  282. var wnd = new Window() { SizeToContent = SizeToContent.WidthAndHeight };
  283. wnd.IsVisible = true;
  284. var target = new ListBox();
  285. wnd.Content = target;
  286. var lm = wnd.LayoutManager;
  287. target.Height = 110;
  288. target.Width = 50;
  289. target.DataContext = items;
  290. target.ItemTemplate = new FuncDataTemplate<object>((c, _) =>
  291. {
  292. var tb = new TextBlock() { Height = 10, Width = 30 };
  293. tb.Bind(TextBlock.TextProperty, new Data.Binding());
  294. return tb;
  295. }, true);
  296. lm.ExecuteInitialLayoutPass();
  297. target.ItemsSource = items;
  298. lm.ExecuteLayoutPass();
  299. items.Insert(3, "3+");
  300. lm.ExecuteLayoutPass();
  301. items.Insert(4, "4+");
  302. lm.ExecuteLayoutPass();
  303. //RESET
  304. items.Clear();
  305. foreach (var i in Enumerable.Range(1, 7))
  306. {
  307. items.Add(i.ToString());
  308. }
  309. //working bit better with this line no outof memory or remaining to arrange/measure ???
  310. //lm.ExecuteLayoutPass();
  311. items.Insert(2, "2+");
  312. lm.ExecuteLayoutPass();
  313. //after few more layout cycles layoutmanager shouldn't hold any more visual for measure/arrange
  314. lm.ExecuteLayoutPass();
  315. lm.ExecuteLayoutPass();
  316. var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
  317. var toMeasure = lm.GetType().GetField("_toMeasure", flags).GetValue(lm) as System.Collections.Generic.IEnumerable<Layout.Layoutable>;
  318. var toArrange = lm.GetType().GetField("_toArrange", flags).GetValue(lm) as System.Collections.Generic.IEnumerable<Layout.Layoutable>;
  319. Assert.Equal(0, toMeasure.Count());
  320. Assert.Equal(0, toArrange.Count());
  321. }
  322. }
  323. [Fact]
  324. public void ListBox_Should_Be_Valid_After_Remove_Of_Item_In_NonVisibleArea()
  325. {
  326. using (UnitTestApplication.Start(TestServices.StyledWindow))
  327. {
  328. var items = new AvaloniaList<string>(Enumerable.Range(1, 30).Select(v => v.ToString()));
  329. var wnd = new Window() { Width = 100, Height = 100, IsVisible = true };
  330. var target = new ListBox()
  331. {
  332. AutoScrollToSelectedItem = true,
  333. Height = 100,
  334. Width = 50,
  335. ItemTemplate = new FuncDataTemplate<object>((c, _) => new Border() { Height = 10 }),
  336. ItemsSource = items,
  337. };
  338. wnd.Content = target;
  339. var lm = wnd.LayoutManager;
  340. lm.ExecuteInitialLayoutPass();
  341. //select last / scroll to last item
  342. target.SelectedItem = items.Last();
  343. lm.ExecuteLayoutPass();
  344. //remove the first item (in non realized area of the listbox)
  345. items.Remove("1");
  346. lm.ExecuteLayoutPass();
  347. Assert.Equal("30", target.ContainerFromIndex(items.Count - 1).DataContext);
  348. Assert.Equal("29", target.ContainerFromIndex(items.Count - 2).DataContext);
  349. Assert.Equal("28", target.ContainerFromIndex(items.Count - 3).DataContext);
  350. Assert.Equal("27", target.ContainerFromIndex(items.Count - 4).DataContext);
  351. Assert.Equal("26", target.ContainerFromIndex(items.Count - 5).DataContext);
  352. }
  353. }
  354. [Fact]
  355. public void Clicking_Item_Should_Raise_BringIntoView_For_Correct_Control()
  356. {
  357. using (UnitTestApplication.Start(TestServices.StyledWindow))
  358. {
  359. // Issue #3934
  360. var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToArray();
  361. var target = new ListBox
  362. {
  363. Template = ListBoxTemplate(),
  364. ItemsSource = items,
  365. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
  366. SelectionMode = SelectionMode.AlwaysSelected,
  367. };
  368. Prepare(target);
  369. // First an item that is not index 0 must be selected.
  370. _mouse.Click(target.Presenter.Panel.Children[1]);
  371. Assert.Equal(1, target.Selection.AnchorIndex);
  372. // We're going to be clicking on item 9.
  373. var item = (ListBoxItem)target.Presenter.Panel.Children[9];
  374. var raised = 0;
  375. // Make sure a RequestBringIntoView event is raised for item 9. It won't be handled
  376. // by the ScrollContentPresenter as the item is already visible, so we don't need
  377. // handledEventsToo: true. Issue #3934 failed here because item 0 was being scrolled
  378. // into view due to SelectionMode.AlwaysSelected.
  379. target.AddHandler(Control.RequestBringIntoViewEvent, (s, e) =>
  380. {
  381. Assert.Same(item, e.TargetObject);
  382. ++raised;
  383. });
  384. // Click item 9.
  385. _mouse.Click(item);
  386. Assert.Equal(1, raised);
  387. }
  388. }
  389. [Fact]
  390. public void Adding_And_Selecting_Item_With_AutoScrollToSelectedItem_Should_NotHide_FirstItem()
  391. {
  392. using (UnitTestApplication.Start(TestServices.StyledWindow))
  393. {
  394. var items = new AvaloniaList<string>();
  395. var wnd = new Window() { Width = 100, Height = 100, IsVisible = true };
  396. var target = new ListBox()
  397. {
  398. VerticalAlignment = VerticalAlignment.Top,
  399. AutoScrollToSelectedItem = true,
  400. Width = 50,
  401. ItemTemplate = new FuncDataTemplate<object>((c, _) => new Border() { Height = 10 }),
  402. ItemsSource = items,
  403. };
  404. wnd.Content = target;
  405. var lm = wnd.LayoutManager;
  406. lm.ExecuteInitialLayoutPass();
  407. var panel = target.Presenter.Panel;
  408. items.Add("Item 1");
  409. target.Selection.Select(0);
  410. lm.ExecuteLayoutPass();
  411. Assert.Equal(1, panel.Children.Count);
  412. items.Add("Item 2");
  413. target.Selection.Select(1);
  414. lm.ExecuteLayoutPass();
  415. Assert.Equal(2, panel.Children.Count);
  416. //make sure we have enough space to show all items
  417. Assert.True(panel.Bounds.Height >= panel.Children.Sum(c => c.Bounds.Height));
  418. //make sure we show items and they completelly visible, not only partially
  419. Assert.True(panel.Children[0].Bounds.Top >= 0 && panel.Children[0].Bounds.Bottom <= panel.Bounds.Height, "first item is not completelly visible!");
  420. Assert.True(panel.Children[1].Bounds.Top >= 0 && panel.Children[1].Bounds.Bottom <= panel.Bounds.Height, "second item is not completelly visible!");
  421. }
  422. }
  423. [Fact]
  424. public void Initial_Binding_Of_SelectedItems_Should_Not_Cause_Write_To_SelectedItems()
  425. {
  426. var target = new ListBox
  427. {
  428. [!ListBox.ItemsSourceProperty] = new Binding("Items"),
  429. [!ListBox.SelectedItemsProperty] = new Binding("SelectedItems"),
  430. };
  431. var viewModel = new
  432. {
  433. Items = new[] { "Foo", "Bar", "Baz " },
  434. SelectedItems = new ObservableCollection<string> { "Bar" },
  435. };
  436. var raised = 0;
  437. viewModel.SelectedItems.CollectionChanged += (s, e) => ++raised;
  438. target.DataContext = viewModel;
  439. Assert.Equal(0, raised);
  440. Assert.Equal(new[] { "Bar" }, viewModel.SelectedItems);
  441. Assert.Equal(new[] { "Bar" }, target.SelectedItems);
  442. Assert.Equal(new[] { "Bar" }, target.Selection.SelectedItems);
  443. }
  444. private static FuncControlTemplate ListBoxTemplate()
  445. {
  446. return new FuncControlTemplate<ListBox>((parent, scope) =>
  447. new ScrollViewer
  448. {
  449. Name = "PART_ScrollViewer",
  450. Template = ScrollViewerTemplate(),
  451. Content = new ItemsPresenter
  452. {
  453. Name = "PART_ItemsPresenter",
  454. [~ItemsPresenter.ItemsPanelProperty] = parent.GetObservable(ItemsControl.ItemsPanelProperty).ToBinding(),
  455. }.RegisterInNameScope(scope)
  456. }.RegisterInNameScope(scope));
  457. }
  458. private static FuncControlTemplate ListBoxItemTemplate()
  459. {
  460. return new FuncControlTemplate<ListBoxItem>((parent, scope) =>
  461. new ContentPresenter
  462. {
  463. Name = "PART_ContentPresenter",
  464. [!ContentPresenter.ContentProperty] = parent[!ListBoxItem.ContentProperty],
  465. [!ContentPresenter.ContentTemplateProperty] = parent[!ListBoxItem.ContentTemplateProperty],
  466. }.RegisterInNameScope(scope));
  467. }
  468. private static FuncControlTemplate ScrollViewerTemplate()
  469. {
  470. return new FuncControlTemplate<ScrollViewer>((parent, scope) =>
  471. new Panel
  472. {
  473. Children =
  474. {
  475. new ScrollContentPresenter
  476. {
  477. Name = "PART_ContentPresenter",
  478. [~ScrollContentPresenter.ContentProperty] = parent.GetObservable(ScrollViewer.ContentProperty).ToBinding(),
  479. [~~ScrollContentPresenter.ExtentProperty] = parent[~~ScrollViewer.ExtentProperty],
  480. [~~ScrollContentPresenter.OffsetProperty] = parent[~~ScrollViewer.OffsetProperty],
  481. [~~ScrollContentPresenter.ViewportProperty] = parent[~~ScrollViewer.ViewportProperty],
  482. [~ScrollContentPresenter.CanHorizontallyScrollProperty] = parent[~ScrollViewer.CanHorizontallyScrollProperty],
  483. [~ScrollContentPresenter.CanVerticallyScrollProperty] = parent[~ScrollViewer.CanVerticallyScrollProperty],
  484. }.RegisterInNameScope(scope),
  485. new ScrollBar
  486. {
  487. Name = "verticalScrollBar",
  488. [~ScrollBar.MaximumProperty] = parent[~ScrollViewer.VerticalScrollBarMaximumProperty],
  489. [~~ScrollBar.ValueProperty] = parent[~~ScrollViewer.VerticalScrollBarValueProperty],
  490. }
  491. }
  492. });
  493. }
  494. private static void Prepare(ListBox target)
  495. {
  496. target.Width = target.Height = 100;
  497. var root = new TestRoot(target)
  498. {
  499. Resources =
  500. {
  501. {
  502. typeof(ListBoxItem),
  503. new ControlTheme(typeof(ListBoxItem))
  504. {
  505. Setters = { new Setter(ListBoxItem.TemplateProperty, ListBoxItemTemplate()) }
  506. }
  507. }
  508. }
  509. };
  510. root.LayoutManager.ExecuteInitialLayoutPass();
  511. }
  512. private static void Layout(Control c)
  513. {
  514. ((ILayoutRoot)c.GetVisualRoot()).LayoutManager.ExecuteLayoutPass();
  515. }
  516. private class Item
  517. {
  518. public Item(string value)
  519. {
  520. Value = value;
  521. }
  522. public string Value { get; }
  523. }
  524. [Fact]
  525. public void SelectedItem_Validation()
  526. {
  527. var target = new ListBox
  528. {
  529. Template = ListBoxTemplate(),
  530. ItemsSource = new[] { "Foo" },
  531. ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
  532. SelectionMode = SelectionMode.AlwaysSelected,
  533. };
  534. Prepare(target);
  535. var exception = new System.InvalidCastException("failed validation");
  536. var textObservable = new BehaviorSubject<BindingNotification>(new BindingNotification(exception, BindingErrorType.DataValidationError));
  537. target.Bind(ComboBox.SelectedItemProperty, textObservable);
  538. Assert.True(DataValidationErrors.GetHasErrors(target));
  539. Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception }));
  540. }
  541. [Fact]
  542. public void Handles_Resetting_Items()
  543. {
  544. var items = new ResettingCollection(100);
  545. var target = new ListBox
  546. {
  547. Template = ListBoxTemplate(),
  548. ItemsSource = items,
  549. ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas { Height = 10 }),
  550. };
  551. Prepare(target);
  552. var realized = target.GetRealizedContainers()
  553. .Cast<ListBoxItem>()
  554. .Select(x => (string)x.DataContext)
  555. .ToList();
  556. Assert.Equal(Enumerable.Range(0, 10).Select(x => $"Item{x}"), realized);
  557. items.Reverse();
  558. Layout(target);
  559. realized = target.GetRealizedContainers()
  560. .Cast<ListBoxItem>()
  561. .Select(x => (string)x.DataContext)
  562. .ToList();
  563. Assert.Equal(Enumerable.Range(0, 10).Select(x => $"Item{99 - x}"), realized);
  564. }
  565. [Fact]
  566. public void Handles_Resetting_Items_With_Existing_Selection_And_AutoScrollToSelectedItem()
  567. {
  568. var items = new ResettingCollection(100);
  569. var target = new ListBox
  570. {
  571. Template = ListBoxTemplate(),
  572. ItemsSource = items,
  573. ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas { Height = 10 }),
  574. AutoScrollToSelectedItem = true,
  575. SelectedIndex = 1,
  576. };
  577. Prepare(target);
  578. var realized = target.GetRealizedContainers()
  579. .Cast<ListBoxItem>()
  580. .Select(x => (string)x.DataContext)
  581. .ToList();
  582. Assert.Equal(Enumerable.Range(0, 10).Select(x => $"Item{x}"), realized);
  583. items.Reverse();
  584. Layout(target);
  585. realized = target.GetRealizedContainers()
  586. .Cast<ListBoxItem>()
  587. .Select(x => (string)x.DataContext)
  588. .ToList();
  589. // "Item1" should remain selected, and now be at the bottom of the viewport.
  590. Assert.Equal(Enumerable.Range(0, 10).Select(x => $"Item{10 - x}"), realized);
  591. }
  592. private static void RaiseKeyEvent(ListBox listBox, Key key, KeyModifiers inputModifiers = 0)
  593. {
  594. listBox.RaiseEvent(new KeyEventArgs
  595. {
  596. RoutedEvent = InputElement.KeyDownEvent,
  597. KeyModifiers = inputModifiers,
  598. Key = key
  599. });
  600. }
  601. [Fact]
  602. public void WrapSelection_Should_Wrap()
  603. {
  604. using (UnitTestApplication.Start(TestServices.RealFocus))
  605. {
  606. var items = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToArray();
  607. var target = new ListBox
  608. {
  609. Template = ListBoxTemplate(),
  610. ItemsSource = items,
  611. ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
  612. WrapSelection = true
  613. };
  614. Prepare(target);
  615. var lbItems = target.GetLogicalChildren().OfType<ListBoxItem>().ToArray();
  616. var first = lbItems.First();
  617. var beforeLast = lbItems[^2];
  618. var last = lbItems.Last();
  619. first.Focus();
  620. RaisePressedEvent(target, first, MouseButton.Left);
  621. Assert.Equal(true, first.IsSelected);
  622. RaiseKeyEvent(target, Key.Up);
  623. Assert.Equal(true, last.IsSelected);
  624. RaiseKeyEvent(target, Key.Up);
  625. Assert.Equal(true, beforeLast.IsSelected);
  626. RaiseKeyEvent(target, Key.Down);
  627. Assert.Equal(true, last.IsSelected);
  628. RaiseKeyEvent(target, Key.Down);
  629. Assert.Equal(true, first.IsSelected);
  630. target.WrapSelection = false;
  631. RaiseKeyEvent(target, Key.Up);
  632. Assert.Equal(true, first.IsSelected);
  633. }
  634. }
  635. private class ResettingCollection : List<string>, INotifyCollectionChanged
  636. {
  637. public ResettingCollection(int itemCount)
  638. {
  639. AddRange(Enumerable.Range(0, itemCount).Select(x => $"Item{x}"));
  640. }
  641. public new void Reverse()
  642. {
  643. base.Reverse();
  644. CollectionChanged?.Invoke(
  645. this,
  646. new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
  647. }
  648. public event NotifyCollectionChangedEventHandler CollectionChanged;
  649. }
  650. }
  651. }