ComboBoxTests.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reactive.Subjects;
  5. using Avalonia.Controls.Presenters;
  6. using Avalonia.Controls.Primitives;
  7. using Avalonia.Controls.Shapes;
  8. using Avalonia.Controls.Templates;
  9. using Avalonia.Data;
  10. using Avalonia.Input;
  11. using Avalonia.LogicalTree;
  12. using Avalonia.Media;
  13. using Avalonia.VisualTree;
  14. using Avalonia.UnitTests;
  15. using Xunit;
  16. namespace Avalonia.Controls.UnitTests
  17. {
  18. public class ComboBoxTests : ScopedTestBase
  19. {
  20. MouseTestHelper _helper = new MouseTestHelper();
  21. [Fact]
  22. public void Clicking_On_Control_Toggles_IsDropDownOpen()
  23. {
  24. var target = new ComboBox
  25. {
  26. ItemsSource = new[] { "Foo", "Bar" },
  27. };
  28. _helper.Down(target);
  29. _helper.Up(target);
  30. Assert.True(target.IsDropDownOpen);
  31. Assert.True(target.Classes.Contains(ComboBox.pcDropdownOpen));
  32. _helper.Down(target);
  33. _helper.Up(target);
  34. Assert.False(target.IsDropDownOpen);
  35. Assert.True(!target.Classes.Contains(ComboBox.pcDropdownOpen));
  36. }
  37. [Fact]
  38. public void Clicking_On_Control_PseudoClass()
  39. {
  40. var target = new ComboBox
  41. {
  42. ItemsSource = new[] { "Foo", "Bar" },
  43. };
  44. _helper.Down(target);
  45. Assert.True(target.Classes.Contains(ComboBox.pcPressed));
  46. _helper.Up(target);
  47. Assert.True(!target.Classes.Contains(ComboBox.pcPressed));
  48. Assert.True(target.Classes.Contains(ComboBox.pcDropdownOpen));
  49. _helper.Down(target);
  50. Assert.True(!target.Classes.Contains(ComboBox.pcPressed));
  51. _helper.Up(target);
  52. Assert.True(!target.Classes.Contains(ComboBox.pcPressed));
  53. Assert.False(target.IsDropDownOpen);
  54. Assert.True(!target.Classes.Contains(ComboBox.pcDropdownOpen));
  55. }
  56. [Fact]
  57. public void WrapSelection_Should_Work()
  58. {
  59. using (UnitTestApplication.Start(TestServices.RealFocus))
  60. {
  61. var target = new ComboBox
  62. {
  63. Items =
  64. {
  65. new ComboBoxItem() { Content = "bla" },
  66. new ComboBoxItem() { Content = "dd" },
  67. new ComboBoxItem() { Content = "sdf", IsEnabled = false }
  68. },
  69. Template = GetTemplate(),
  70. WrapSelection = true
  71. };
  72. var root = new TestRoot(target);
  73. target.ApplyTemplate();
  74. ((Control)target.Presenter).ApplyTemplate();
  75. target.Focus();
  76. Assert.Equal(target.SelectedIndex, -1);
  77. Assert.True(target.IsFocused);
  78. target.RaiseEvent(new KeyEventArgs
  79. {
  80. RoutedEvent = InputElement.KeyDownEvent,
  81. Key = Key.Up,
  82. });
  83. Assert.Equal(target.SelectedIndex, 1);
  84. target.RaiseEvent(new KeyEventArgs
  85. {
  86. RoutedEvent = InputElement.KeyDownEvent,
  87. Key = Key.Down,
  88. });
  89. Assert.Equal(target.SelectedIndex, 0);
  90. }
  91. }
  92. [Fact]
  93. public void Focuses_Next_Item_On_Key_Down()
  94. {
  95. using (UnitTestApplication.Start(TestServices.RealFocus))
  96. {
  97. var target = new ComboBox
  98. {
  99. Items =
  100. {
  101. new ComboBoxItem() { Content = "bla" },
  102. new ComboBoxItem() { Content = "dd", IsEnabled = false },
  103. new ComboBoxItem() { Content = "sdf" }
  104. },
  105. Template = GetTemplate()
  106. };
  107. var root = new TestRoot(target);
  108. target.ApplyTemplate();
  109. ((Control)target.Presenter).ApplyTemplate();
  110. target.Focus();
  111. Assert.Equal(target.SelectedIndex, -1);
  112. Assert.True(target.IsFocused);
  113. target.RaiseEvent(new KeyEventArgs
  114. {
  115. RoutedEvent = InputElement.KeyDownEvent,
  116. Key = Key.Down,
  117. });
  118. Assert.Equal(target.SelectedIndex, 0);
  119. target.RaiseEvent(new KeyEventArgs
  120. {
  121. RoutedEvent = InputElement.KeyDownEvent,
  122. Key = Key.Down,
  123. });
  124. Assert.Equal(target.SelectedIndex, 2);
  125. }
  126. }
  127. [Fact]
  128. public void SelectionBoxItem_Is_Rectangle_With_VisualBrush_When_Selection_Is_Control()
  129. {
  130. var target = new ComboBox
  131. {
  132. Items = { new Canvas() },
  133. SelectedIndex = 0,
  134. };
  135. var root = new TestRoot(target);
  136. var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle;
  137. Assert.NotNull(rectangle);
  138. var brush = rectangle.Fill as VisualBrush;
  139. Assert.NotNull(brush);
  140. Assert.Same(target.Items[0], brush.Visual);
  141. }
  142. [Fact]
  143. public void SelectionBoxItem_Rectangle_Is_Removed_From_Logical_Tree()
  144. {
  145. var target = new ComboBox
  146. {
  147. Items = { new Canvas() },
  148. SelectedIndex = 0,
  149. Template = GetTemplate(),
  150. };
  151. var root = new TestRoot { Child = target };
  152. target.ApplyTemplate();
  153. ((Control)target.Presenter).ApplyTemplate();
  154. var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle;
  155. Assert.True(((ILogical)target).IsAttachedToLogicalTree);
  156. Assert.True(((ILogical)rectangle).IsAttachedToLogicalTree);
  157. rectangle.DetachedFromLogicalTree += (s, e) => { };
  158. root.Child = null;
  159. Assert.False(((ILogical)target).IsAttachedToLogicalTree);
  160. Assert.False(((ILogical)rectangle).IsAttachedToLogicalTree);
  161. }
  162. private static FuncControlTemplate GetTemplate()
  163. {
  164. return new FuncControlTemplate<ComboBox>((parent, scope) =>
  165. {
  166. return new Panel
  167. {
  168. Name = "container",
  169. Children =
  170. {
  171. new ContentControl
  172. {
  173. [!ContentControl.ContentProperty] = parent[!ComboBox.SelectionBoxItemProperty],
  174. },
  175. new ToggleButton
  176. {
  177. Name = "toggle",
  178. }.RegisterInNameScope(scope),
  179. new Popup
  180. {
  181. Name = "PART_Popup",
  182. Child = new ScrollViewer
  183. {
  184. Name = "PART_ScrollViewer",
  185. Content = new ItemsPresenter
  186. {
  187. Name = "PART_ItemsPresenter",
  188. ItemsPanel = new FuncTemplate<Panel>(() => new VirtualizingStackPanel()),
  189. }.RegisterInNameScope(scope)
  190. }.RegisterInNameScope(scope)
  191. }.RegisterInNameScope(scope)
  192. }
  193. };
  194. });
  195. }
  196. [Fact]
  197. public void Detaching_Closed_ComboBox_Keeps_Current_Focus()
  198. {
  199. using (UnitTestApplication.Start(TestServices.RealFocus))
  200. {
  201. var target = new ComboBox
  202. {
  203. Items = { new Canvas() },
  204. SelectedIndex = 0,
  205. Template = GetTemplate(),
  206. };
  207. var other = new Control { Focusable = true };
  208. StackPanel panel;
  209. var root = new TestRoot { Child = panel = new StackPanel { Children = { target, other } } };
  210. target.ApplyTemplate();
  211. target.Presenter.ApplyTemplate();
  212. other.Focus();
  213. Assert.True(other.IsFocused);
  214. panel.Children.Remove(target);
  215. Assert.True(other.IsFocused);
  216. }
  217. }
  218. [Theory]
  219. [InlineData(-1, 2, "c", "A item", "B item", "C item")]
  220. [InlineData(0, 1, "b", "A item", "B item", "C item")]
  221. [InlineData(2, 2, "x", "A item", "B item", "C item")]
  222. [InlineData(0, 34, "y", "0 item", "1 item", "2 item", "3 item", "4 item", "5 item", "6 item", "7 item", "8 item", "9 item", "A item", "B item", "C item", "D item", "E item", "F item", "G item", "H item", "I item", "J item", "K item", "L item", "M item", "N item", "O item", "P item", "Q item", "R item", "S item", "T item", "U item", "V item", "W item", "X item", "Y item", "Z item")]
  223. public void TextSearch_Should_Have_Expected_SelectedIndex(
  224. int initialSelectedIndex,
  225. int expectedSelectedIndex,
  226. string searchTerm,
  227. params string[] contents)
  228. {
  229. TestTextSearch(
  230. initialSelectedIndex,
  231. expectedSelectedIndex,
  232. searchTerm,
  233. _ => { },
  234. contents.Select(content => new ComboBoxItem { Content = content }));
  235. }
  236. [Theory]
  237. [InlineData(-1, 1, "c", new[] { "A item", "B item", "C item" }, new[] { "B search", "C search", "A search" })]
  238. [InlineData(0, 2, "baz", new[] { "A item", "B item", "C item" }, new[] { "foo", "bar", "baz" })]
  239. public void TextSearch_With_TextSearchText_Should_Have_Expected_SelectedIndex(
  240. int initialSelectedIndex,
  241. int expectedSelectedIndex,
  242. string searchTerm,
  243. string[] contents,
  244. string[] searchTexts)
  245. {
  246. Assert.Equal(contents.Length, searchTexts.Length);
  247. TestTextSearch(
  248. initialSelectedIndex,
  249. expectedSelectedIndex,
  250. searchTerm,
  251. _ => { },
  252. contents.Select((item, index) =>
  253. {
  254. var comboBoxItem = new ComboBoxItem { Content = item };
  255. TextSearch.SetText(comboBoxItem, searchTexts[index]);
  256. return comboBoxItem;
  257. }));
  258. }
  259. [Theory]
  260. [InlineData(-1, 1, "c", new[] { "A item", "B item", "C item" }, new[] { "B search", "C search", "A search" })]
  261. [InlineData(0, 2, "baz", new[] { "A item", "B item", "C item" }, new[] { "foo", "bar", "baz" })]
  262. public void TextSearch_With_DisplayMemberBinding_Should_Have_Expected_SelectedIndex(
  263. int initialSelectedIndex,
  264. int expectedSelectedIndex,
  265. string searchTerm,
  266. string[] values,
  267. string[] displays)
  268. {
  269. Assert.Equal(values.Length, displays.Length);
  270. TestTextSearch(
  271. initialSelectedIndex,
  272. expectedSelectedIndex,
  273. searchTerm,
  274. comboBox => comboBox.DisplayMemberBinding = new Binding(nameof(Item.Display)),
  275. values.Select((value, index) => new Item(value, displays[index])));
  276. }
  277. [Theory]
  278. [InlineData(-1, 1, "c", new[] { "A item", "B item", "C item" }, new[] { "B search", "C search", "A search" })]
  279. [InlineData(0, 2, "baz", new[] { "A item", "B item", "C item" }, new[] { "foo", "bar", "baz" })]
  280. public void TextSearch_With_TextSearchBinding_Should_Have_Expected_SelectedIndex(
  281. int initialSelectedIndex,
  282. int expectedSelectedIndex,
  283. string searchTerm,
  284. string[] values,
  285. string[] displays)
  286. {
  287. Assert.Equal(values.Length, displays.Length);
  288. TestTextSearch(
  289. initialSelectedIndex,
  290. expectedSelectedIndex,
  291. searchTerm,
  292. comboBox => TextSearch.SetTextBinding(comboBox, new Binding(nameof(Item.Display))),
  293. values.Select((value, index) => new Item(value, displays[index])));
  294. }
  295. private static void TestTextSearch(
  296. int initialSelectedIndex,
  297. int expectedSelectedIndex,
  298. string searchTerm,
  299. Action<ComboBox> configureComboBox,
  300. IEnumerable<object> itemsSource)
  301. {
  302. using (UnitTestApplication.Start(TestServices.StyledWindow))
  303. {
  304. var target = new ComboBox
  305. {
  306. Template = GetTemplate(),
  307. ItemsSource = itemsSource.ToArray(),
  308. };
  309. configureComboBox(target);
  310. TestRoot root = new(target)
  311. {
  312. ClientSize = new(500,500)
  313. };
  314. root.LayoutManager.ExecuteInitialLayoutPass();
  315. target.SelectedIndex = initialSelectedIndex;
  316. var args = new TextInputEventArgs
  317. {
  318. Text = searchTerm,
  319. RoutedEvent = InputElement.TextInputEvent
  320. };
  321. target.RaiseEvent(args);
  322. Assert.Equal(expectedSelectedIndex, target.SelectedIndex);
  323. }
  324. }
  325. [Fact]
  326. public void SelectedItem_Validation()
  327. {
  328. using (UnitTestApplication.Start(TestServices.MockThreadingInterface))
  329. {
  330. var target = new ComboBox
  331. {
  332. Template = GetTemplate(),
  333. };
  334. target.ApplyTemplate();
  335. target.Presenter.ApplyTemplate();
  336. var exception = new System.InvalidCastException("failed validation");
  337. var textObservable = new BehaviorSubject<BindingNotification>(new BindingNotification(exception, BindingErrorType.DataValidationError));
  338. target.Bind(ComboBox.SelectedItemProperty, textObservable);
  339. Assert.True(DataValidationErrors.GetHasErrors(target));
  340. Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception }));
  341. }
  342. }
  343. [Fact]
  344. public void Close_Window_On_Alt_F4_When_ComboBox_Is_Focus()
  345. {
  346. var inputManagerMock = new Moq.Mock<IInputManager>();
  347. var services = TestServices.StyledWindow.With(inputManager: inputManagerMock.Object);
  348. using (UnitTestApplication.Start(TestServices.StyledWindow))
  349. {
  350. var window = new Window();
  351. window.KeyDown += (s, e) =>
  352. {
  353. if (e.Handled == false
  354. && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt) == true
  355. && e.Key == Key.F4 )
  356. {
  357. e.Handled = true;
  358. window.Close();
  359. }
  360. };
  361. var count = 0;
  362. var target = new ComboBox
  363. {
  364. Items = { new Canvas() },
  365. SelectedIndex = 0,
  366. Template = GetTemplate(),
  367. };
  368. window.Content = target;
  369. window.Closing +=
  370. (sender, e) =>
  371. {
  372. count++;
  373. };
  374. window.Show();
  375. target.Focus();
  376. _helper.Down(target);
  377. _helper.Up(target);
  378. Assert.True(target.IsDropDownOpen);
  379. target.RaiseEvent(new KeyEventArgs
  380. {
  381. RoutedEvent = InputElement.KeyDownEvent,
  382. KeyModifiers = KeyModifiers.Alt,
  383. Key = Key.F4
  384. });
  385. Assert.Equal(1, count);
  386. }
  387. }
  388. [Fact]
  389. public void FlowDirection_Of_RectangleContent_Should_Be_LeftToRight()
  390. {
  391. using var app = UnitTestApplication.Start(TestServices.StyledWindow);
  392. var target = new ComboBox
  393. {
  394. FlowDirection = FlowDirection.RightToLeft,
  395. Items =
  396. {
  397. new ComboBoxItem()
  398. {
  399. Content = new Control()
  400. }
  401. },
  402. Template = GetTemplate()
  403. };
  404. var root = new TestRoot(target);
  405. target.ApplyTemplate();
  406. target.SelectedIndex = 0;
  407. var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle;
  408. Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection);
  409. }
  410. [Fact]
  411. public void FlowDirection_Of_RectangleContent_Updated_After_InvalidateMirrorTransform()
  412. {
  413. using var app = UnitTestApplication.Start(TestServices.StyledWindow);
  414. var parentContent = new Decorator()
  415. {
  416. Child = new Control()
  417. };
  418. var target = new ComboBox
  419. {
  420. Items =
  421. {
  422. new ComboBoxItem()
  423. {
  424. Content = parentContent.Child
  425. }
  426. },
  427. Template = GetTemplate()
  428. };
  429. var root = new TestRoot(target);
  430. target.ApplyTemplate();
  431. target.SelectedIndex = 0;
  432. var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle;
  433. Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection);
  434. parentContent.FlowDirection = FlowDirection.RightToLeft;
  435. target.FlowDirection = FlowDirection.RightToLeft;
  436. Assert.Equal(FlowDirection.RightToLeft, rectangle.FlowDirection);
  437. }
  438. [Fact]
  439. public void FlowDirection_Of_RectangleContent_Updated_After_OpenPopup()
  440. {
  441. using (UnitTestApplication.Start(TestServices.StyledWindow))
  442. {
  443. var parentContent = new Decorator()
  444. {
  445. Child = new Control()
  446. };
  447. var target = new ComboBox
  448. {
  449. FlowDirection = FlowDirection.RightToLeft,
  450. Items =
  451. {
  452. new ComboBoxItem()
  453. {
  454. Content = parentContent.Child,
  455. Template = null // ugly hack, so we can "attach" same child to the two different trees
  456. }
  457. },
  458. Template = GetTemplate()
  459. };
  460. var root = new TestRoot(target);
  461. target.ApplyTemplate();
  462. target.SelectedIndex = 0;
  463. var rectangle = target.GetValue(ComboBox.SelectionBoxItemProperty) as Rectangle;
  464. Assert.Equal(FlowDirection.LeftToRight, rectangle.FlowDirection);
  465. parentContent.FlowDirection = FlowDirection.RightToLeft;
  466. var popup = target.GetVisualDescendants().OfType<Popup>().First();
  467. popup.PlacementTarget = new Window();
  468. popup.Open();
  469. Assert.Equal(FlowDirection.RightToLeft, rectangle.FlowDirection);
  470. }
  471. }
  472. [Fact]
  473. public void SelectionBoxItemTemplate_Overrides_ItemTemplate()
  474. {
  475. IDataTemplate itemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x + "!" });
  476. IDataTemplate selectionBoxItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x });
  477. var target = new ComboBox
  478. {
  479. ItemsSource = new []{ "Foo" },
  480. SelectionBoxItemTemplate = selectionBoxItemTemplate,
  481. ItemTemplate = itemTemplate,
  482. };
  483. Assert.Equal(selectionBoxItemTemplate, target.SelectionBoxItemTemplate);
  484. }
  485. [Fact]
  486. public void SelectionBoxItemTemplate_Inherits_From_ItemTemplate_When_NotSet()
  487. {
  488. IDataTemplate itemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x + "!" });
  489. var target = new ComboBox
  490. {
  491. ItemsSource = new []{ "Foo" },
  492. ItemTemplate = itemTemplate,
  493. };
  494. Assert.Equal(itemTemplate, target.SelectionBoxItemTemplate);
  495. }
  496. [Fact]
  497. public void SelectionBoxItemTemplate_Overrides_ItemTemplate_After_ItemTemplate_Changed()
  498. {
  499. IDataTemplate itemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x + "!" });
  500. IDataTemplate selectionBoxItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x });
  501. IDataTemplate itemTemplate2 = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x + "?" });
  502. var target = new ComboBox
  503. {
  504. ItemsSource = new[] { "Foo" },
  505. SelectionBoxItemTemplate = selectionBoxItemTemplate,
  506. ItemTemplate = itemTemplate,
  507. };
  508. Assert.Equal(selectionBoxItemTemplate, target.SelectionBoxItemTemplate);
  509. target.ItemTemplate = itemTemplate2;
  510. Assert.Equal(selectionBoxItemTemplate, target.SelectionBoxItemTemplate);
  511. }
  512. [Fact]
  513. public void SelectionBoxItemTemplate_Inherits_From_ItemTemplate_When_ItemTemplate_Changed()
  514. {
  515. IDataTemplate itemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x + "!" });
  516. IDataTemplate selectionBoxItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x });
  517. IDataTemplate itemTemplate2 = new FuncDataTemplate<string>((x, _) => new TextBlock { Text = x + "?" });
  518. var target = new ComboBox { ItemsSource = new[] { "Foo" }, ItemTemplate = itemTemplate, };
  519. Assert.Equal(itemTemplate, target.SelectionBoxItemTemplate);
  520. target.ItemTemplate = itemTemplate2;
  521. target.SelectionBoxItemTemplate = null;
  522. Assert.Equal(itemTemplate2, target.SelectionBoxItemTemplate);
  523. }
  524. [Fact]
  525. public void DisplayMemberBinding_Is_Not_Applied_To_SelectionBoxItem_Without_Selection()
  526. {
  527. var target = new ComboBox
  528. {
  529. DisplayMemberBinding = new Binding(),
  530. ItemsSource = new[] { "foo", "bar" }
  531. };
  532. target.SelectedItem = null;
  533. Assert.Null(target.SelectionBoxItem);
  534. target.SelectedItem = "foo";
  535. Assert.NotNull(target.SelectionBoxItem);
  536. target.SelectedItem = null;
  537. Assert.Null(target.SelectionBoxItem);
  538. }
  539. private sealed record Item(string Value, string Display);
  540. }
  541. }