ControlTests.cs 12 KB

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