ControlTests.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  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.Media;
  11. using Avalonia.Platform;
  12. using Avalonia.Rendering;
  13. using Avalonia.Styling;
  14. using Avalonia.UnitTests;
  15. using Avalonia.VisualTree;
  16. using JetBrains.dotMemoryUnit;
  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 (Start())
  33. {
  34. Func<Window> run = () =>
  35. {
  36. var window = new Window
  37. {
  38. Content = new Canvas()
  39. };
  40. window.Show();
  41. // Do a layout and make sure that Canvas gets added to visual tree.
  42. window.LayoutManager.ExecuteInitialLayoutPass(window);
  43. Assert.IsType<Canvas>(window.Presenter.Child);
  44. // Clear the content and ensure the Canvas is removed.
  45. window.Content = null;
  46. window.LayoutManager.ExecuteLayoutPass();
  47. Assert.Null(window.Presenter.Child);
  48. return window;
  49. };
  50. var result = run();
  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 (Start())
  59. {
  60. Func<Window> run = () =>
  61. {
  62. var scope = new NameScope();
  63. var window = new Window
  64. {
  65. Content = new Canvas
  66. {
  67. Name = "foo"
  68. }.RegisterInNameScope(scope)
  69. };
  70. NameScope.SetNameScope(window, scope);
  71. window.Show();
  72. // Do a layout and make sure that Canvas gets added to visual tree.
  73. window.LayoutManager.ExecuteInitialLayoutPass(window);
  74. Assert.IsType<Canvas>(window.Find<Canvas>("foo"));
  75. Assert.IsType<Canvas>(window.Presenter.Child);
  76. // Clear the content and ensure the Canvas is removed.
  77. window.Content = null;
  78. NameScope.SetNameScope(window, null);
  79. window.LayoutManager.ExecuteLayoutPass();
  80. Assert.Null(window.Presenter.Child);
  81. return window;
  82. };
  83. var result = run();
  84. dotMemory.Check(memory =>
  85. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  86. }
  87. }
  88. [Fact]
  89. public void ScrollViewer_With_Content_Is_Freed()
  90. {
  91. using (Start())
  92. {
  93. Func<Window> run = () =>
  94. {
  95. var window = new Window
  96. {
  97. Content = new ScrollViewer
  98. {
  99. Content = new Canvas()
  100. }
  101. };
  102. window.Show();
  103. // Do a layout and make sure that ScrollViewer gets added to visual tree and its
  104. // template applied.
  105. window.LayoutManager.ExecuteInitialLayoutPass(window);
  106. Assert.IsType<ScrollViewer>(window.Presenter.Child);
  107. Assert.IsType<Canvas>(((ScrollViewer)window.Presenter.Child).Presenter.Child);
  108. // Clear the content and ensure the ScrollViewer is removed.
  109. window.Content = null;
  110. window.LayoutManager.ExecuteLayoutPass();
  111. Assert.Null(window.Presenter.Child);
  112. return window;
  113. };
  114. var result = run();
  115. dotMemory.Check(memory =>
  116. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
  117. dotMemory.Check(memory =>
  118. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  119. }
  120. }
  121. [Fact]
  122. public void TextBox_Is_Freed()
  123. {
  124. using (Start())
  125. {
  126. Func<Window> run = () =>
  127. {
  128. var window = new Window
  129. {
  130. Content = new TextBox()
  131. };
  132. window.Show();
  133. // Do a layout and make sure that TextBox gets added to visual tree and its
  134. // template applied.
  135. window.LayoutManager.ExecuteInitialLayoutPass(window);
  136. Assert.IsType<TextBox>(window.Presenter.Child);
  137. Assert.NotEmpty(window.Presenter.Child.GetVisualChildren());
  138. // Clear the content and ensure the TextBox is removed.
  139. window.Content = null;
  140. window.LayoutManager.ExecuteLayoutPass();
  141. Assert.Null(window.Presenter.Child);
  142. return window;
  143. };
  144. var result = run();
  145. dotMemory.Check(memory =>
  146. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
  147. }
  148. }
  149. [Fact]
  150. public void TextBox_With_Xaml_Binding_Is_Freed()
  151. {
  152. using (Start())
  153. {
  154. Func<Window> run = () =>
  155. {
  156. var window = new Window
  157. {
  158. DataContext = new Node { Name = "foo" },
  159. Content = new TextBox()
  160. };
  161. var binding = new Avalonia.Data.Binding
  162. {
  163. Path = "Name"
  164. };
  165. var textBox = (TextBox)window.Content;
  166. textBox.Bind(TextBox.TextProperty, binding);
  167. window.Show();
  168. // Do a layout and make sure that TextBox gets added to visual tree and its
  169. // Text property set.
  170. window.LayoutManager.ExecuteInitialLayoutPass(window);
  171. Assert.IsType<TextBox>(window.Presenter.Child);
  172. Assert.Equal("foo", ((TextBox)window.Presenter.Child).Text);
  173. // Clear the content and DataContext and ensure the TextBox is removed.
  174. window.Content = null;
  175. window.DataContext = null;
  176. window.LayoutManager.ExecuteLayoutPass();
  177. Assert.Null(window.Presenter.Child);
  178. return window;
  179. };
  180. var result = run();
  181. dotMemory.Check(memory =>
  182. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
  183. dotMemory.Check(memory =>
  184. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Node>()).ObjectsCount));
  185. }
  186. }
  187. [Fact]
  188. public void TextBox_Class_Listeners_Are_Freed()
  189. {
  190. using (Start())
  191. {
  192. TextBox textBox;
  193. var window = new Window
  194. {
  195. Content = textBox = new TextBox()
  196. };
  197. window.Show();
  198. // Do a layout and make sure that TextBox gets added to visual tree and its
  199. // template applied.
  200. window.LayoutManager.ExecuteInitialLayoutPass(window);
  201. Assert.Same(textBox, window.Presenter.Child);
  202. // Get the border from the TextBox template.
  203. var border = textBox.GetTemplateChildren().FirstOrDefault(x => x.Name == "border");
  204. // The TextBox should have subscriptions to its Classes collection from the
  205. // default theme.
  206. Assert.NotEmpty(((INotifyCollectionChangedDebug)textBox.Classes).GetCollectionChangedSubscribers());
  207. // Clear the content and ensure the TextBox is removed.
  208. window.Content = null;
  209. window.LayoutManager.ExecuteLayoutPass();
  210. Assert.Null(window.Presenter.Child);
  211. // Check that the TextBox has no subscriptions to its Classes collection.
  212. Assert.Null(((INotifyCollectionChangedDebug)textBox.Classes).GetCollectionChangedSubscribers());
  213. }
  214. }
  215. [Fact]
  216. public void TreeView_Is_Freed()
  217. {
  218. using (Start())
  219. {
  220. Func<Window> run = () =>
  221. {
  222. var nodes = new[]
  223. {
  224. new Node
  225. {
  226. Children = new[] { new Node() },
  227. }
  228. };
  229. TreeView target;
  230. var window = new Window
  231. {
  232. Content = target = new TreeView
  233. {
  234. DataTemplates =
  235. {
  236. new FuncTreeDataTemplate<Node>(
  237. (x, _) => new TextBlock { Text = x.Name },
  238. x => x.Children)
  239. },
  240. Items = nodes
  241. }
  242. };
  243. window.Show();
  244. // Do a layout and make sure that TreeViewItems get realized.
  245. window.LayoutManager.ExecuteInitialLayoutPass(window);
  246. Assert.Single(target.ItemContainerGenerator.Containers);
  247. // Clear the content and ensure the TreeView is removed.
  248. window.Content = null;
  249. window.LayoutManager.ExecuteLayoutPass();
  250. Assert.Null(window.Presenter.Child);
  251. return window;
  252. };
  253. var result = run();
  254. dotMemory.Check(memory =>
  255. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TreeView>()).ObjectsCount));
  256. }
  257. }
  258. [Fact]
  259. public void Slider_Is_Freed()
  260. {
  261. using (Start())
  262. {
  263. Func<Window> run = () =>
  264. {
  265. var window = new Window
  266. {
  267. Content = new Slider()
  268. };
  269. window.Show();
  270. // Do a layout and make sure that Slider gets added to visual tree.
  271. window.LayoutManager.ExecuteInitialLayoutPass(window);
  272. Assert.IsType<Slider>(window.Presenter.Child);
  273. // Clear the content and ensure the Slider is removed.
  274. window.Content = null;
  275. window.LayoutManager.ExecuteLayoutPass();
  276. Assert.Null(window.Presenter.Child);
  277. return window;
  278. };
  279. var result = run();
  280. dotMemory.Check(memory =>
  281. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Slider>()).ObjectsCount));
  282. }
  283. }
  284. [Fact]
  285. public void RendererIsDisposed()
  286. {
  287. using (Start())
  288. {
  289. var renderer = new Mock<IRenderer>();
  290. renderer.Setup(x => x.Dispose());
  291. var impl = new Mock<IWindowImpl>();
  292. impl.SetupGet(x => x.Scaling).Returns(1);
  293. impl.SetupProperty(x => x.Closed);
  294. impl.Setup(x => x.CreateRenderer(It.IsAny<IRenderRoot>())).Returns(renderer.Object);
  295. impl.Setup(x => x.Dispose()).Callback(() => impl.Object.Closed());
  296. AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatform>()
  297. .ToConstant(new MockWindowingPlatform(() => impl.Object));
  298. var window = new Window()
  299. {
  300. Content = new Button()
  301. };
  302. window.Show();
  303. window.Close();
  304. renderer.Verify(r => r.Dispose());
  305. }
  306. }
  307. [Fact]
  308. public void Control_With_Style_RenderTransform_Is_Freed()
  309. {
  310. // # Issue #3545
  311. using (Start())
  312. {
  313. Func<Window> run = () =>
  314. {
  315. var window = new Window
  316. {
  317. Styles =
  318. {
  319. new Style(x => x.OfType<Canvas>())
  320. {
  321. Setters =
  322. {
  323. new Setter
  324. {
  325. Property = Visual.RenderTransformProperty,
  326. Value = new RotateTransform(45),
  327. }
  328. }
  329. }
  330. },
  331. Content = new Canvas()
  332. };
  333. window.Show();
  334. // Do a layout and make sure that Canvas gets added to visual tree with
  335. // its render transform.
  336. window.LayoutManager.ExecuteInitialLayoutPass(window);
  337. var canvas = Assert.IsType<Canvas>(window.Presenter.Child);
  338. Assert.IsType<RotateTransform>(canvas.RenderTransform);
  339. // Clear the content and ensure the Canvas is removed.
  340. window.Content = null;
  341. window.LayoutManager.ExecuteLayoutPass();
  342. Assert.Null(window.Presenter.Child);
  343. return window;
  344. };
  345. var result = run();
  346. dotMemory.Check(memory =>
  347. Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
  348. }
  349. }
  350. private IDisposable Start()
  351. {
  352. return UnitTestApplication.Start(TestServices.StyledWindow);
  353. }
  354. private class Node
  355. {
  356. public string Name { get; set; }
  357. public IEnumerable<Node> Children { get; set; }
  358. }
  359. private class NullRenderer : IRenderer
  360. {
  361. public bool DrawFps { get; set; }
  362. public bool DrawDirtyRects { get; set; }
  363. public event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated;
  364. public void AddDirty(IVisual visual)
  365. {
  366. }
  367. public void Dispose()
  368. {
  369. }
  370. public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter) => null;
  371. public IVisual HitTestFirst(Point p, IVisual root, Func<IVisual, bool> filter) => null;
  372. public void Paint(Rect rect)
  373. {
  374. }
  375. public void RecalculateChildren(IVisual visual)
  376. {
  377. }
  378. public void Resized(Size size)
  379. {
  380. }
  381. public void Start()
  382. {
  383. }
  384. public void Stop()
  385. {
  386. }
  387. }
  388. }
  389. }