ControlTests.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  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.Generic;
  5. using System.Linq;
  6. using JetBrains.dotMemoryUnit;
  7. using Avalonia.Collections;
  8. using Avalonia.Controls;
  9. using Avalonia.Controls.Primitives;
  10. using Avalonia.Controls.Templates;
  11. using Avalonia.Diagnostics;
  12. using Avalonia.Layout;
  13. using Avalonia.Rendering;
  14. using Avalonia.Styling;
  15. using Avalonia.UnitTests;
  16. using Avalonia.VisualTree;
  17. using Moq;
  18. using Xunit;
  19. using Xunit.Abstractions;
  20. namespace Avalonia.LeakTests
  21. {
  22. [DotMemoryUnit(FailIfRunWithoutSupport = false)]
  23. public class ControlTests
  24. {
  25. public ControlTests(ITestOutputHelper atr)
  26. {
  27. DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine);
  28. }
  29. [Fact]
  30. public void Canvas_Is_Freed()
  31. {
  32. using (UnitTestApplication.Start(TestServices.StyledWindow))
  33. {
  34. Func<Window> run = () =>
  35. {
  36. var window = new Window
  37. {
  38. Content = new Canvas()
  39. };
  40. // Do a layout and make sure that Canvas gets added to visual tree.
  41. LayoutManager.Instance.ExecuteInitialLayoutPass(window);
  42. Assert.IsType<Canvas>(window.Presenter.Child);
  43. // Clear the content and ensure the Canvas is removed.
  44. window.Content = null;
  45. LayoutManager.Instance.ExecuteLayoutPass();
  46. Assert.Null(window.Presenter.Child);
  47. return window;
  48. };
  49. var result = run();
  50. PurgeMoqReferences();
  51. dotMemory.Check(memory =>
  52. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  53. }
  54. }
  55. [Fact]
  56. public void Named_Canvas_Is_Freed()
  57. {
  58. using (UnitTestApplication.Start(TestServices.StyledWindow))
  59. {
  60. Func<Window> run = () =>
  61. {
  62. var window = new Window
  63. {
  64. Content = new Canvas
  65. {
  66. Name = "foo"
  67. }
  68. };
  69. // Do a layout and make sure that Canvas gets added to visual tree.
  70. LayoutManager.Instance.ExecuteInitialLayoutPass(window);
  71. Assert.IsType<Canvas>(window.Find<Canvas>("foo"));
  72. Assert.IsType<Canvas>(window.Presenter.Child);
  73. // Clear the content and ensure the Canvas is removed.
  74. window.Content = null;
  75. LayoutManager.Instance.ExecuteLayoutPass();
  76. Assert.Null(window.Presenter.Child);
  77. return window;
  78. };
  79. var result = run();
  80. PurgeMoqReferences();
  81. dotMemory.Check(memory =>
  82. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  83. }
  84. }
  85. [Fact]
  86. public void ScrollViewer_With_Content_Is_Freed()
  87. {
  88. using (UnitTestApplication.Start(TestServices.StyledWindow))
  89. {
  90. Func<Window> run = () =>
  91. {
  92. var window = new Window
  93. {
  94. Content = new ScrollViewer
  95. {
  96. Content = new Canvas()
  97. }
  98. };
  99. // Do a layout and make sure that ScrollViewer gets added to visual tree and its
  100. // template applied.
  101. LayoutManager.Instance.ExecuteInitialLayoutPass(window);
  102. Assert.IsType<ScrollViewer>(window.Presenter.Child);
  103. Assert.IsType<Canvas>(((ScrollViewer)window.Presenter.Child).Presenter.Child);
  104. // Clear the content and ensure the ScrollViewer is removed.
  105. window.Content = null;
  106. LayoutManager.Instance.ExecuteLayoutPass();
  107. Assert.Null(window.Presenter.Child);
  108. return window;
  109. };
  110. var result = run();
  111. PurgeMoqReferences();
  112. dotMemory.Check(memory =>
  113. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
  114. dotMemory.Check(memory =>
  115. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  116. }
  117. }
  118. [Fact]
  119. public void TextBox_Is_Freed()
  120. {
  121. using (UnitTestApplication.Start(TestServices.StyledWindow))
  122. {
  123. Func<Window> run = () =>
  124. {
  125. var window = new Window
  126. {
  127. Content = new TextBox()
  128. };
  129. // Do a layout and make sure that TextBox gets added to visual tree and its
  130. // template applied.
  131. LayoutManager.Instance.ExecuteInitialLayoutPass(window);
  132. Assert.IsType<TextBox>(window.Presenter.Child);
  133. Assert.NotEqual(0, window.Presenter.Child.GetVisualChildren().Count());
  134. // Clear the content and ensure the TextBox is removed.
  135. window.Content = null;
  136. LayoutManager.Instance.ExecuteLayoutPass();
  137. Assert.Null(window.Presenter.Child);
  138. return window;
  139. };
  140. var result = run();
  141. PurgeMoqReferences();
  142. dotMemory.Check(memory =>
  143. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
  144. }
  145. }
  146. [Fact]
  147. public void TextBox_With_Xaml_Binding_Is_Freed()
  148. {
  149. using (UnitTestApplication.Start(TestServices.StyledWindow))
  150. {
  151. Func<Window> run = () =>
  152. {
  153. var window = new Window
  154. {
  155. DataContext = new Node { Name = "foo" },
  156. Content = new TextBox()
  157. };
  158. var binding = new Avalonia.Markup.Xaml.Data.Binding
  159. {
  160. Path = "Name"
  161. };
  162. var textBox = (TextBox)window.Content;
  163. textBox.Bind(TextBox.TextProperty, binding);
  164. // Do a layout and make sure that TextBox gets added to visual tree and its
  165. // Text property set.
  166. LayoutManager.Instance.ExecuteInitialLayoutPass(window);
  167. Assert.IsType<TextBox>(window.Presenter.Child);
  168. Assert.Equal("foo", ((TextBox)window.Presenter.Child).Text);
  169. // Clear the content and DataContext and ensure the TextBox is removed.
  170. window.Content = null;
  171. window.DataContext = null;
  172. LayoutManager.Instance.ExecuteLayoutPass();
  173. Assert.Null(window.Presenter.Child);
  174. return window;
  175. };
  176. var result = run();
  177. PurgeMoqReferences();
  178. dotMemory.Check(memory =>
  179. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
  180. dotMemory.Check(memory =>
  181. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Node>()).ObjectsCount));
  182. }
  183. }
  184. [Fact]
  185. public void TextBox_Class_Listeners_Are_Freed()
  186. {
  187. using (UnitTestApplication.Start(TestServices.StyledWindow))
  188. {
  189. TextBox textBox;
  190. var window = new Window
  191. {
  192. Content = textBox = new TextBox()
  193. };
  194. // Do a layout and make sure that TextBox gets added to visual tree and its
  195. // template applied.
  196. LayoutManager.Instance.ExecuteInitialLayoutPass(window);
  197. Assert.Same(textBox, window.Presenter.Child);
  198. // Get the border from the TextBox template.
  199. var border = textBox.GetTemplateChildren().FirstOrDefault(x => x.Name == "border");
  200. // The TextBox should have subscriptions to its Classes collection from the
  201. // default theme.
  202. Assert.NotEmpty(((INotifyCollectionChangedDebug)textBox.Classes).GetCollectionChangedSubscribers());
  203. // Clear the content and ensure the TextBox is removed.
  204. window.Content = null;
  205. LayoutManager.Instance.ExecuteLayoutPass();
  206. Assert.Null(window.Presenter.Child);
  207. // Check that the TextBox has no subscriptions to its Classes collection.
  208. Assert.Null(((INotifyCollectionChangedDebug)textBox.Classes).GetCollectionChangedSubscribers());
  209. }
  210. }
  211. [Fact]
  212. public void TreeView_Is_Freed()
  213. {
  214. using (UnitTestApplication.Start(TestServices.StyledWindow))
  215. {
  216. Func<Window> run = () =>
  217. {
  218. var nodes = new[]
  219. {
  220. new Node
  221. {
  222. Children = new[] { new Node() },
  223. }
  224. };
  225. TreeView target;
  226. var window = new Window
  227. {
  228. Content = target = new TreeView
  229. {
  230. DataTemplates = new DataTemplates
  231. {
  232. new FuncTreeDataTemplate<Node>(
  233. x => new TextBlock { Text = x.Name },
  234. x => x.Children)
  235. },
  236. Items = nodes
  237. }
  238. };
  239. // Do a layout and make sure that TreeViewItems get realized.
  240. LayoutManager.Instance.ExecuteInitialLayoutPass(window);
  241. Assert.Equal(1, target.ItemContainerGenerator.Containers.Count());
  242. // Clear the content and ensure the TreeView is removed.
  243. window.Content = null;
  244. LayoutManager.Instance.ExecuteLayoutPass();
  245. Assert.Null(window.Presenter.Child);
  246. return window;
  247. };
  248. var result = run();
  249. PurgeMoqReferences();
  250. dotMemory.Check(memory =>
  251. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TreeView>()).ObjectsCount));
  252. }
  253. }
  254. private static void PurgeMoqReferences()
  255. {
  256. // Moq holds onto references in its mock of IRenderer in case we want to check if a method has been called;
  257. // clear these.
  258. var renderer = Mock.Get(AvaloniaLocator.Current.GetService<IRenderer>());
  259. renderer.ResetCalls();
  260. }
  261. private class Node
  262. {
  263. public string Name { get; set; }
  264. public IEnumerable<Node> Children { get; set; }
  265. }
  266. }
  267. }