ControlTests.cs 12 KB

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