ControlTests.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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 scope = new NameScope();
  61. var window = new Window
  62. {
  63. Content = new Canvas
  64. {
  65. Name = "foo"
  66. }.RegisterInNameScope(scope)
  67. }.WithNameScope(scope);
  68. window.Show();
  69. // Do a layout and make sure that Canvas gets added to visual tree.
  70. window.LayoutManager.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. window.LayoutManager.ExecuteLayoutPass();
  76. Assert.Null(window.Presenter.Child);
  77. return window;
  78. };
  79. var result = run();
  80. dotMemory.Check(memory =>
  81. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  82. }
  83. }
  84. [Fact]
  85. public void ScrollViewer_With_Content_Is_Freed()
  86. {
  87. using (Start())
  88. {
  89. Func<Window> run = () =>
  90. {
  91. var window = new Window
  92. {
  93. Content = new ScrollViewer
  94. {
  95. Content = new Canvas()
  96. }
  97. };
  98. window.Show();
  99. // Do a layout and make sure that ScrollViewer gets added to visual tree and its
  100. // template applied.
  101. window.LayoutManager.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. window.LayoutManager.ExecuteLayoutPass();
  107. Assert.Null(window.Presenter.Child);
  108. return window;
  109. };
  110. var result = run();
  111. dotMemory.Check(memory =>
  112. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
  113. dotMemory.Check(memory =>
  114. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  115. }
  116. }
  117. [Fact]
  118. public void TextBox_Is_Freed()
  119. {
  120. using (Start())
  121. {
  122. Func<Window> run = () =>
  123. {
  124. var window = new Window
  125. {
  126. Content = new TextBox()
  127. };
  128. window.Show();
  129. // Do a layout and make sure that TextBox gets added to visual tree and its
  130. // template applied.
  131. window.LayoutManager.ExecuteInitialLayoutPass(window);
  132. Assert.IsType<TextBox>(window.Presenter.Child);
  133. Assert.NotEmpty(window.Presenter.Child.GetVisualChildren());
  134. // Clear the content and ensure the TextBox is removed.
  135. window.Content = null;
  136. window.LayoutManager.ExecuteLayoutPass();
  137. Assert.Null(window.Presenter.Child);
  138. return window;
  139. };
  140. var result = run();
  141. dotMemory.Check(memory =>
  142. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
  143. }
  144. }
  145. [Fact]
  146. public void TextBox_With_Xaml_Binding_Is_Freed()
  147. {
  148. using (Start())
  149. {
  150. Func<Window> run = () =>
  151. {
  152. var window = new Window
  153. {
  154. DataContext = new Node { Name = "foo" },
  155. Content = new TextBox()
  156. };
  157. var binding = new Avalonia.Data.Binding
  158. {
  159. Path = "Name"
  160. };
  161. var textBox = (TextBox)window.Content;
  162. textBox.Bind(TextBox.TextProperty, binding);
  163. window.Show();
  164. // Do a layout and make sure that TextBox gets added to visual tree and its
  165. // Text property set.
  166. window.LayoutManager.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. window.LayoutManager.ExecuteLayoutPass();
  173. Assert.Null(window.Presenter.Child);
  174. return window;
  175. };
  176. var result = run();
  177. dotMemory.Check(memory =>
  178. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
  179. dotMemory.Check(memory =>
  180. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Node>()).ObjectsCount));
  181. }
  182. }
  183. [Fact]
  184. public void TextBox_Class_Listeners_Are_Freed()
  185. {
  186. using (Start())
  187. {
  188. TextBox textBox;
  189. var window = new Window
  190. {
  191. Content = textBox = new TextBox()
  192. };
  193. window.Show();
  194. // Do a layout and make sure that TextBox gets added to visual tree and its
  195. // template applied.
  196. window.LayoutManager.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. window.LayoutManager.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 (Start())
  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 =
  231. {
  232. new FuncTreeDataTemplate<Node>(
  233. (x, _) => new TextBlock { Text = x.Name },
  234. x => x.Children)
  235. },
  236. Items = nodes
  237. }
  238. };
  239. window.Show();
  240. // Do a layout and make sure that TreeViewItems get realized.
  241. window.LayoutManager.ExecuteInitialLayoutPass(window);
  242. Assert.Single(target.ItemContainerGenerator.Containers);
  243. // Clear the content and ensure the TreeView is removed.
  244. window.Content = null;
  245. window.LayoutManager.ExecuteLayoutPass();
  246. Assert.Null(window.Presenter.Child);
  247. return window;
  248. };
  249. var result = run();
  250. dotMemory.Check(memory =>
  251. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TreeView>()).ObjectsCount));
  252. }
  253. }
  254. [Fact]
  255. public void Slider_Is_Freed()
  256. {
  257. using (Start())
  258. {
  259. Func<Window> run = () =>
  260. {
  261. var window = new Window
  262. {
  263. Content = new Slider()
  264. };
  265. window.Show();
  266. // Do a layout and make sure that Slider gets added to visual tree.
  267. window.LayoutManager.ExecuteInitialLayoutPass(window);
  268. Assert.IsType<Slider>(window.Presenter.Child);
  269. // Clear the content and ensure the Slider is removed.
  270. window.Content = null;
  271. window.LayoutManager.ExecuteLayoutPass();
  272. Assert.Null(window.Presenter.Child);
  273. return window;
  274. };
  275. var result = run();
  276. dotMemory.Check(memory =>
  277. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Slider>()).ObjectsCount));
  278. }
  279. }
  280. [Fact]
  281. public void RendererIsDisposed()
  282. {
  283. using (Start())
  284. {
  285. var renderer = new Mock<IRenderer>();
  286. renderer.Setup(x => x.Dispose());
  287. var impl = new Mock<IWindowImpl>();
  288. impl.SetupGet(x => x.Scaling).Returns(1);
  289. impl.SetupProperty(x => x.Closed);
  290. impl.Setup(x => x.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer.Object);
  291. impl.Setup(x => x.Dispose()).Callback(() => impl.Object.Closed());
  292. AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatform>()
  293. .ToConstant(new MockWindowingPlatform(() => impl.Object));
  294. var window = new Window()
  295. {
  296. Content = new Button()
  297. };
  298. window.Show();
  299. window.Close();
  300. renderer.Verify(r => r.Dispose());
  301. }
  302. }
  303. private IDisposable Start()
  304. {
  305. return UnitTestApplication.Start(TestServices.StyledWindow);
  306. }
  307. private class Node
  308. {
  309. public string Name { get; set; }
  310. public IEnumerable<Node> Children { get; set; }
  311. }
  312. private class NullRenderer : IRenderer
  313. {
  314. public bool DrawFps { get; set; }
  315. public bool DrawDirtyRects { get; set; }
  316. public event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated;
  317. public void AddDirty(IVisual visual)
  318. {
  319. }
  320. public void Dispose()
  321. {
  322. }
  323. public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter) => null;
  324. public void Paint(Rect rect)
  325. {
  326. }
  327. public void Resized(Size size)
  328. {
  329. }
  330. public void Start()
  331. {
  332. }
  333. public void Stop()
  334. {
  335. }
  336. }
  337. }
  338. }