PopupTests.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. // Copyright (c) The Avalonia 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.Specialized;
  5. using System.Diagnostics;
  6. using System.Linq;
  7. using Moq;
  8. using Avalonia.Controls.Presenters;
  9. using Avalonia.Controls.Primitives;
  10. using Avalonia.Controls.Templates;
  11. using Avalonia.Layout;
  12. using Avalonia.LogicalTree;
  13. using Avalonia.Platform;
  14. using Avalonia.Styling;
  15. using Avalonia.UnitTests;
  16. using Avalonia.VisualTree;
  17. using Xunit;
  18. using Avalonia.Input;
  19. namespace Avalonia.Controls.UnitTests.Primitives
  20. {
  21. public class PopupTests
  22. {
  23. protected bool UsePopupHost;
  24. [Fact]
  25. public void Setting_Child_Should_Set_Child_Controls_LogicalParent()
  26. {
  27. var target = new Popup();
  28. var child = new Control();
  29. target.Child = child;
  30. Assert.Equal(child.Parent, target);
  31. Assert.Equal(((ILogical)child).LogicalParent, target);
  32. }
  33. [Fact]
  34. public void Clearing_Child_Should_Clear_Child_Controls_Parent()
  35. {
  36. var target = new Popup();
  37. var child = new Control();
  38. target.Child = child;
  39. target.Child = null;
  40. Assert.Null(child.Parent);
  41. Assert.Null(((ILogical)child).LogicalParent);
  42. }
  43. [Fact]
  44. public void Child_Control_Should_Appear_In_LogicalChildren()
  45. {
  46. var target = new Popup();
  47. var child = new Control();
  48. target.Child = child;
  49. Assert.Equal(new[] { child }, target.GetLogicalChildren());
  50. }
  51. [Fact]
  52. public void Clearing_Child_Should_Remove_From_LogicalChildren()
  53. {
  54. var target = new Popup();
  55. var child = new Control();
  56. target.Child = child;
  57. target.Child = null;
  58. Assert.Equal(new ILogical[0], ((ILogical)target).LogicalChildren.ToList());
  59. }
  60. [Fact]
  61. public void Setting_Child_Should_Fire_LogicalChildren_CollectionChanged()
  62. {
  63. var target = new Popup();
  64. var child = new Control();
  65. var called = false;
  66. ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
  67. called = e.Action == NotifyCollectionChangedAction.Add;
  68. target.Child = child;
  69. Assert.True(called);
  70. }
  71. [Fact]
  72. public void Clearing_Child_Should_Fire_LogicalChildren_CollectionChanged()
  73. {
  74. var target = new Popup();
  75. var child = new Control();
  76. var called = false;
  77. target.Child = child;
  78. ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) =>
  79. called = e.Action == NotifyCollectionChangedAction.Remove;
  80. target.Child = null;
  81. Assert.True(called);
  82. }
  83. [Fact]
  84. public void Changing_Child_Should_Fire_LogicalChildren_CollectionChanged()
  85. {
  86. var target = new Popup();
  87. var child1 = new Control();
  88. var child2 = new Control();
  89. var called = false;
  90. target.Child = child1;
  91. ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true;
  92. target.Child = child2;
  93. Assert.True(called);
  94. }
  95. [Fact]
  96. public void Setting_Child_Should_Not_Set_Childs_VisualParent()
  97. {
  98. var target = new Popup();
  99. var child = new Control();
  100. target.Child = child;
  101. Assert.Null(((IVisual)child).VisualParent);
  102. }
  103. [Fact]
  104. public void PopupRoot_Should_Initially_Be_Null()
  105. {
  106. using (CreateServices())
  107. {
  108. var target = new Popup();
  109. Assert.Null(((Visual)target.Host));
  110. }
  111. }
  112. [Fact]
  113. public void PopupRoot_Should_Have_Popup_As_LogicalParent()
  114. {
  115. using (CreateServices())
  116. {
  117. var target = new Popup() {PlacementTarget = PreparedWindow()};
  118. target.Open();
  119. Assert.Equal(target, ((Visual)target.Host).Parent);
  120. Assert.Equal(target, ((Visual)target.Host).GetLogicalParent());
  121. }
  122. }
  123. [Fact]
  124. public void PopupRoot_Should_Be_Detached_From_Logical_Tree_When_Popup_Is_Detached()
  125. {
  126. using (CreateServices())
  127. {
  128. var target = new Popup() {PlacementMode = PlacementMode.Pointer};
  129. var root = PreparedWindow(target);
  130. target.Open();
  131. var popupRoot = (ILogical)((Visual)target.Host);
  132. Assert.True(popupRoot.IsAttachedToLogicalTree);
  133. root.Content = null;
  134. Assert.False(((ILogical)target).IsAttachedToLogicalTree);
  135. }
  136. }
  137. [Fact]
  138. public void Popup_Open_Should_Raise_Single_Opened_Event()
  139. {
  140. using (CreateServices())
  141. {
  142. var window = PreparedWindow();
  143. var target = new Popup() {PlacementMode = PlacementMode.Pointer};
  144. window.Content = target;
  145. int openedCount = 0;
  146. target.Opened += (sender, args) =>
  147. {
  148. openedCount++;
  149. };
  150. target.Open();
  151. Assert.Equal(1, openedCount);
  152. }
  153. }
  154. [Fact]
  155. public void Popup_Close_Should_Raise_Single_Closed_Event()
  156. {
  157. using (CreateServices())
  158. {
  159. var window = PreparedWindow();
  160. var target = new Popup() {PlacementMode = PlacementMode.Pointer};
  161. window.Content = target;
  162. window.ApplyTemplate();
  163. target.Open();
  164. int closedCount = 0;
  165. target.Closed += (sender, args) =>
  166. {
  167. closedCount++;
  168. };
  169. target.Close();
  170. Assert.Equal(1, closedCount);
  171. }
  172. }
  173. [Fact]
  174. public void Popup_Close_On_Closed_Popup_Should_Not_Raise_Closed_Event()
  175. {
  176. using (CreateServices())
  177. {
  178. var window = PreparedWindow();
  179. var target = new Popup() { PlacementMode = PlacementMode.Pointer };
  180. window.Content = target;
  181. window.ApplyTemplate();
  182. int closedCount = 0;
  183. target.Closed += (sender, args) =>
  184. {
  185. closedCount++;
  186. };
  187. target.Close();
  188. target.Close();
  189. target.Close();
  190. target.Close();
  191. Assert.Equal(0, closedCount);
  192. }
  193. }
  194. [Fact]
  195. public void Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent()
  196. {
  197. using (CreateServices())
  198. {
  199. PopupContentControl target;
  200. var root = PreparedWindow(target = new PopupContentControl
  201. {
  202. Content = new Border(),
  203. Template = new FuncControlTemplate<PopupContentControl>(PopupContentControlTemplate),
  204. });
  205. root.Show();
  206. target.ApplyTemplate();
  207. var popup = (Popup)target.GetTemplateChildren().First(x => x.Name == "popup");
  208. popup.Open();
  209. var popupRoot = (Control)popup.Host;
  210. popupRoot.Measure(Size.Infinity);
  211. popupRoot.Arrange(new Rect(popupRoot.DesiredSize));
  212. var children = popupRoot.GetVisualDescendants().ToList();
  213. var types = children.Select(x => x.GetType().Name).ToList();
  214. Assert.Equal(
  215. new[]
  216. {
  217. "VisualLayerManager",
  218. "ContentPresenter",
  219. "ContentPresenter",
  220. "Border",
  221. },
  222. types);
  223. var templatedParents = children
  224. .OfType<IControl>()
  225. .Select(x => x.TemplatedParent).ToList();
  226. Assert.Equal(
  227. new object[]
  228. {
  229. popupRoot,
  230. popupRoot,
  231. target,
  232. null,
  233. },
  234. templatedParents);
  235. }
  236. }
  237. Window PreparedWindow(object content = null)
  238. {
  239. var w = new Window {Content = content};
  240. w.ApplyTemplate();
  241. return w;
  242. }
  243. [Fact]
  244. public void DataContextBeginUpdate_Should_Not_Be_Called_For_Controls_That_Dont_Inherit()
  245. {
  246. using (CreateServices())
  247. {
  248. TestControl child;
  249. var popup = new Popup
  250. {
  251. Child = child = new TestControl(),
  252. DataContext = "foo",
  253. PlacementTarget = PreparedWindow()
  254. };
  255. var beginCalled = false;
  256. child.DataContextBeginUpdate += (s, e) => beginCalled = true;
  257. // Test for #1245. Here, the child's logical parent is the popup but it's not yet
  258. // attached to a visual tree because the popup hasn't been opened.
  259. Assert.Same(popup, ((ILogical)child).LogicalParent);
  260. Assert.Same(popup, child.InheritanceParent);
  261. Assert.Null(child.GetVisualRoot());
  262. popup.Open();
  263. // #1245 was caused by the fact that DataContextBeginUpdate was called on `target`
  264. // when the PopupRoot was created, even though PopupRoot isn't the
  265. // InheritanceParent of child.
  266. Assert.False(beginCalled);
  267. }
  268. }
  269. [Fact]
  270. public void Popup_Host_Type_Should_Match_Platform_Preference()
  271. {
  272. using (CreateServices())
  273. {
  274. var target = new Popup() {PlacementTarget = PreparedWindow()};
  275. target.Open();
  276. if (UsePopupHost)
  277. Assert.IsType<OverlayPopupHost>(target.Host);
  278. else
  279. Assert.IsType<PopupRoot>(target.Host);
  280. }
  281. }
  282. private IDisposable CreateServices()
  283. {
  284. return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:
  285. new MockWindowingPlatform(null,
  286. () =>
  287. {
  288. if(UsePopupHost)
  289. return null;
  290. return MockWindowingPlatform.CreatePopupMock().Object;
  291. })));
  292. }
  293. private static IControl PopupContentControlTemplate(PopupContentControl control, INameScope scope)
  294. {
  295. return new Popup
  296. {
  297. Name = "popup",
  298. PlacementTarget = control,
  299. Child = new ContentPresenter
  300. {
  301. [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
  302. }
  303. }.RegisterInNameScope(scope);
  304. }
  305. private class PopupContentControl : ContentControl
  306. {
  307. }
  308. private class TestControl : Decorator
  309. {
  310. public event EventHandler DataContextBeginUpdate;
  311. public new IAvaloniaObject InheritanceParent => base.InheritanceParent;
  312. protected override void OnDataContextBeginUpdate()
  313. {
  314. DataContextBeginUpdate?.Invoke(this, EventArgs.Empty);
  315. base.OnDataContextBeginUpdate();
  316. }
  317. }
  318. }
  319. public class PopupTestsWithPopupRoot : PopupTests
  320. {
  321. public PopupTestsWithPopupRoot()
  322. {
  323. UsePopupHost = true;
  324. }
  325. }
  326. }