ControlTests.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  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. };
  68. NameScope.SetNameScope(window, scope);
  69. window.Show();
  70. // Do a layout and make sure that Canvas gets added to visual tree.
  71. window.LayoutManager.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. NameScope.SetNameScope(window, null);
  77. window.LayoutManager.ExecuteLayoutPass();
  78. Assert.Null(window.Presenter.Child);
  79. return window;
  80. };
  81. var result = run();
  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 (Start())
  90. {
  91. Func<Window> run = () =>
  92. {
  93. var window = new Window
  94. {
  95. Content = new ScrollViewer
  96. {
  97. Content = new Canvas()
  98. }
  99. };
  100. window.Show();
  101. // Do a layout and make sure that ScrollViewer gets added to visual tree and its
  102. // template applied.
  103. window.LayoutManager.ExecuteInitialLayoutPass(window);
  104. Assert.IsType<ScrollViewer>(window.Presenter.Child);
  105. Assert.IsType<Canvas>(((ScrollViewer)window.Presenter.Child).Presenter.Child);
  106. // Clear the content and ensure the ScrollViewer is removed.
  107. window.Content = null;
  108. window.LayoutManager.ExecuteLayoutPass();
  109. Assert.Null(window.Presenter.Child);
  110. return window;
  111. };
  112. var result = run();
  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 (Start())
  123. {
  124. Func<Window> run = () =>
  125. {
  126. var window = new Window
  127. {
  128. Content = new TextBox()
  129. };
  130. window.Show();
  131. // Do a layout and make sure that TextBox gets added to visual tree and its
  132. // template applied.
  133. window.LayoutManager.ExecuteInitialLayoutPass(window);
  134. Assert.IsType<TextBox>(window.Presenter.Child);
  135. Assert.NotEmpty(window.Presenter.Child.GetVisualChildren());
  136. // Clear the content and ensure the TextBox is removed.
  137. window.Content = null;
  138. window.LayoutManager.ExecuteLayoutPass();
  139. Assert.Null(window.Presenter.Child);
  140. return window;
  141. };
  142. var result = run();
  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 (Start())
  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.Data.Binding
  160. {
  161. Path = "Name"
  162. };
  163. var textBox = (TextBox)window.Content;
  164. textBox.Bind(TextBox.TextProperty, binding);
  165. window.Show();
  166. // Do a layout and make sure that TextBox gets added to visual tree and its
  167. // Text property set.
  168. window.LayoutManager.ExecuteInitialLayoutPass(window);
  169. Assert.IsType<TextBox>(window.Presenter.Child);
  170. Assert.Equal("foo", ((TextBox)window.Presenter.Child).Text);
  171. // Clear the content and DataContext and ensure the TextBox is removed.
  172. window.Content = null;
  173. window.DataContext = null;
  174. window.LayoutManager.ExecuteLayoutPass();
  175. Assert.Null(window.Presenter.Child);
  176. return window;
  177. };
  178. var result = run();
  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 (Start())
  189. {
  190. TextBox textBox;
  191. var window = new Window
  192. {
  193. Content = textBox = new TextBox()
  194. };
  195. window.Show();
  196. // Do a layout and make sure that TextBox gets added to visual tree and its
  197. // template applied.
  198. window.LayoutManager.ExecuteInitialLayoutPass(window);
  199. Assert.Same(textBox, window.Presenter.Child);
  200. // Get the border from the TextBox template.
  201. var border = textBox.GetTemplateChildren().FirstOrDefault(x => x.Name == "border");
  202. // The TextBox should have subscriptions to its Classes collection from the
  203. // default theme.
  204. Assert.NotEmpty(((INotifyCollectionChangedDebug)textBox.Classes).GetCollectionChangedSubscribers());
  205. // Clear the content and ensure the TextBox is removed.
  206. window.Content = null;
  207. window.LayoutManager.ExecuteLayoutPass();
  208. Assert.Null(window.Presenter.Child);
  209. // Check that the TextBox has no subscriptions to its Classes collection.
  210. Assert.Null(((INotifyCollectionChangedDebug)textBox.Classes).GetCollectionChangedSubscribers());
  211. }
  212. }
  213. [Fact]
  214. public void TreeView_Is_Freed()
  215. {
  216. using (Start())
  217. {
  218. Func<Window> run = () =>
  219. {
  220. var nodes = new[]
  221. {
  222. new Node
  223. {
  224. Children = new[] { new Node() },
  225. }
  226. };
  227. TreeView target;
  228. var window = new Window
  229. {
  230. Content = target = new TreeView
  231. {
  232. DataTemplates =
  233. {
  234. new FuncTreeDataTemplate<Node>(
  235. (x, _) => new TextBlock { Text = x.Name },
  236. x => x.Children)
  237. },
  238. Items = nodes
  239. }
  240. };
  241. window.Show();
  242. // Do a layout and make sure that TreeViewItems get realized.
  243. window.LayoutManager.ExecuteInitialLayoutPass(window);
  244. Assert.Single(target.ItemContainerGenerator.Containers);
  245. // Clear the content and ensure the TreeView is removed.
  246. window.Content = null;
  247. window.LayoutManager.ExecuteLayoutPass();
  248. Assert.Null(window.Presenter.Child);
  249. return window;
  250. };
  251. var result = run();
  252. dotMemory.Check(memory =>
  253. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TreeView>()).ObjectsCount));
  254. }
  255. }
  256. [Fact]
  257. public void Slider_Is_Freed()
  258. {
  259. using (Start())
  260. {
  261. Func<Window> run = () =>
  262. {
  263. var window = new Window
  264. {
  265. Content = new Slider()
  266. };
  267. window.Show();
  268. // Do a layout and make sure that Slider gets added to visual tree.
  269. window.LayoutManager.ExecuteInitialLayoutPass(window);
  270. Assert.IsType<Slider>(window.Presenter.Child);
  271. // Clear the content and ensure the Slider is removed.
  272. window.Content = null;
  273. window.LayoutManager.ExecuteLayoutPass();
  274. Assert.Null(window.Presenter.Child);
  275. return window;
  276. };
  277. var result = run();
  278. dotMemory.Check(memory =>
  279. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Slider>()).ObjectsCount));
  280. }
  281. }
  282. [Fact]
  283. public void RendererIsDisposed()
  284. {
  285. using (Start())
  286. {
  287. var renderer = new Mock<IRenderer>();
  288. renderer.Setup(x => x.Dispose());
  289. var impl = new Mock<IWindowImpl>();
  290. impl.SetupGet(x => x.Scaling).Returns(1);
  291. impl.SetupProperty(x => x.Closed);
  292. impl.Setup(x => x.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer.Object);
  293. impl.Setup(x => x.Dispose()).Callback(() => impl.Object.Closed());
  294. AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatform>()
  295. .ToConstant(new MockWindowingPlatform(() => impl.Object));
  296. var window = new Window()
  297. {
  298. Content = new Button()
  299. };
  300. window.Show();
  301. window.Close();
  302. renderer.Verify(r => r.Dispose());
  303. }
  304. }
  305. private IDisposable Start()
  306. {
  307. return UnitTestApplication.Start(TestServices.StyledWindow);
  308. }
  309. private class Node
  310. {
  311. public string Name { get; set; }
  312. public IEnumerable<Node> Children { get; set; }
  313. }
  314. private class NullRenderer : IRenderer
  315. {
  316. public bool DrawFps { get; set; }
  317. public bool DrawDirtyRects { get; set; }
  318. public event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated;
  319. public void AddDirty(IVisual visual)
  320. {
  321. }
  322. public void Dispose()
  323. {
  324. }
  325. public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter) => null;
  326. public void Paint(Rect rect)
  327. {
  328. }
  329. public void Resized(Size size)
  330. {
  331. }
  332. public void Start()
  333. {
  334. }
  335. public void Stop()
  336. {
  337. }
  338. }
  339. }
  340. }