ComboBoxTests.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  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 Popup
  176. {
  177. Name = "PART_Popup",
  178. Child = new ScrollViewer
  179. {
  180. Name = "PART_ScrollViewer",
  181. Content = new ItemsPresenter
  182. {
  183. Name = "PART_ItemsPresenter",
  184. ItemsPanel = new FuncTemplate<Panel>(() => new VirtualizingStackPanel()),
  185. }.RegisterInNameScope(scope)
  186. }.RegisterInNameScope(scope)
  187. }.RegisterInNameScope(scope),
  188. new TextBox
  189. {
  190. Name = "PART_EditableTextBox"
  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. [Fact]
  541. public void When_Editable_Input_Text_Matches_An_Item_It_Is_Selected()
  542. {
  543. var target = new ComboBox
  544. {
  545. DisplayMemberBinding = new Binding(),
  546. IsEditable = true,
  547. ItemsSource = new[] { "foo", "bar" }
  548. };
  549. target.SelectedItem = null;
  550. Assert.Null(target.SelectedItem);
  551. target.Text = "foo";
  552. Assert.NotNull(target.SelectedItem);
  553. Assert.Equal(target.SelectedItem, "foo");
  554. }
  555. [Fact]
  556. public void When_Editable_TextSearch_TextBinding_Is_Prioritised_Over_DisplayMember()
  557. {
  558. var items = new[]
  559. {
  560. new Item("Value 1", "Display 1"),
  561. new Item("Value 2", "Display 2")
  562. };
  563. var target = new ComboBox
  564. {
  565. DisplayMemberBinding = new Binding("Display"),
  566. IsEditable = true,
  567. ItemsSource = items
  568. };
  569. TextSearch.SetTextBinding(target, new Binding("Value"));
  570. target.SelectedItem = null;
  571. Assert.Null(target.SelectedItem);
  572. target.Text = "Value 1";
  573. Assert.NotNull(target.SelectedItem);
  574. Assert.Equal(target.SelectedItem, items[0]);
  575. }
  576. [Fact]
  577. public void When_Items_Source_Changes_It_Selects_An_Item_By_Text()
  578. {
  579. var items = new[]
  580. {
  581. new Item("Value 1", "Display 1"),
  582. new Item("Value 2", "Display 2")
  583. };
  584. var items2 = new[]
  585. {
  586. new Item("Value 1", "Display 3"),
  587. new Item("Value 2", "Display 4")
  588. };
  589. var target = new ComboBox
  590. {
  591. DisplayMemberBinding = new Binding("Display"),
  592. IsEditable = true,
  593. ItemsSource = items
  594. };
  595. TextSearch.SetTextBinding(target, new Binding("Value"));
  596. target.SelectedItem = null;
  597. Assert.Null(target.SelectedItem);
  598. target.Text = "Value 1";
  599. Assert.NotNull(target.SelectedItem);
  600. Assert.Equal(target.SelectedItem, items[0]);
  601. target.ItemsSource = items2;
  602. Assert.NotNull(target.SelectedItem);
  603. Assert.Equal(target.SelectedItem, items2[0]);
  604. Assert.Equal(target.Text, "Value 1");
  605. }
  606. private void RaiseTabKeyPress(Control target, bool withShift = false)
  607. {
  608. target.RaiseEvent(new KeyEventArgs
  609. {
  610. RoutedEvent = InputElement.KeyDownEvent,
  611. Key = Key.Tab,
  612. KeyModifiers = withShift ? KeyModifiers.Shift : KeyModifiers.None
  613. });
  614. target.RaiseEvent(new KeyEventArgs
  615. {
  616. RoutedEvent = InputElement.KeyUpEvent,
  617. Key = Key.Tab,
  618. KeyModifiers = withShift ? KeyModifiers.Shift : KeyModifiers.None
  619. });
  620. }
  621. [Fact]
  622. public void When_Tabbing_Out_With_Dropdown_Open_It_Closes()
  623. {
  624. using var app = UnitTestApplication.Start(TestServices.RealFocus);
  625. var target = new ComboBox
  626. {
  627. ItemsSource = new[] { "Foo", "Bar" }
  628. };
  629. var nextControl = new ComboBox
  630. {
  631. ItemsSource = new[] { "Baz" }
  632. };
  633. var container = new StackPanel
  634. {
  635. Children =
  636. {
  637. target,
  638. nextControl
  639. }
  640. };
  641. var root = new TestRoot(container);
  642. var keyboardNavHandler = new KeyboardNavigationHandler();
  643. keyboardNavHandler.SetOwner(root);
  644. target.Focus();
  645. _helper.Down(target);
  646. _helper.Up(target);
  647. Assert.True(target.IsFocused);
  648. Assert.True(target.IsDropDownOpen);
  649. RaiseTabKeyPress(target);
  650. Assert.False(target.IsFocused);
  651. Assert.True(nextControl.IsFocused);
  652. Assert.False(target.IsDropDownOpen);
  653. }
  654. [Fact]
  655. public void When_Editable_And_Item_Selected_Via_Text_Then_Focus_Swaps_Via_Tab_Swapping_Back_Should_Focus_TextBox()
  656. {
  657. using var app = UnitTestApplication.Start(TestServices.RealFocus);
  658. var items = new[]
  659. {
  660. new Item("Value 1", "Display 1"),
  661. new Item("Value 2", "Display 2")
  662. };
  663. var target = new ComboBox
  664. {
  665. DisplayMemberBinding = new Binding("Display"),
  666. IsEditable = true,
  667. IsTabStop = false,
  668. ItemsSource = items,
  669. Template = GetTemplate()
  670. };
  671. TextSearch.SetTextBinding(target, new Binding("Value"));
  672. KeyboardNavigation.SetTabNavigation(target, KeyboardNavigationMode.Local);
  673. var previousControl = new ComboBox
  674. {
  675. ItemsSource = new[] { "Baz" }
  676. };
  677. var container = new StackPanel
  678. {
  679. Children =
  680. {
  681. previousControl,
  682. target
  683. }
  684. };
  685. var root = new TestRoot(container);
  686. var keyboardNavHandler = new KeyboardNavigationHandler();
  687. keyboardNavHandler.SetOwner(root);
  688. target.ApplyTemplate();
  689. target.Presenter.ApplyTemplate();
  690. var containerPanel = target.GetTemplateChildren().OfType<Panel>().FirstOrDefault(x => x.Name == "container");
  691. var editableTextBox = containerPanel?.GetVisualDescendants().OfType<TextBox>().FirstOrDefault(x => x.Name == "PART_EditableTextBox");
  692. var popup = containerPanel?.GetVisualDescendants().OfType<Popup>().FirstOrDefault(x => x.Name == "PART_Popup");
  693. var popupScrollViewer = popup?.Child as ScrollViewer;
  694. var scrollViewerItemsPresenter = popupScrollViewer?.Content as ItemsPresenter;
  695. var popupVirtualizingStackPanel = scrollViewerItemsPresenter?.GetVisualDescendants().OfType<VirtualizingStackPanel>().FirstOrDefault();
  696. Assert.NotNull(popupVirtualizingStackPanel);
  697. //force the popup to render the ComboBoxItem(s) as they are what get set as "focused" if this test fails
  698. popupVirtualizingStackPanel.Measure(Size.Infinity);
  699. target.Focus();
  700. Assert.True(editableTextBox.IsFocused);
  701. target.Text = "Value 1";
  702. Assert.Same(target.SelectedItem, items[0]);
  703. var item1 = scrollViewerItemsPresenter.ContainerFromIndex(0);
  704. Assert.IsType<ComboBoxItem>(item1);
  705. RaiseTabKeyPress(target, withShift: true);
  706. Assert.False(target.IsFocused);
  707. Assert.True(previousControl.IsFocused);
  708. RaiseTabKeyPress(previousControl);
  709. var focused = root.FocusManager.GetFocusedElement();
  710. Assert.Same(editableTextBox, focused);
  711. }
  712. }
  713. }