StyledElementTests.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Reactive.Linq;
  5. using Avalonia.Controls;
  6. using Avalonia.Controls.Templates;
  7. using Avalonia.Data;
  8. using Avalonia.LogicalTree;
  9. using Avalonia.Media;
  10. using Avalonia.Styling;
  11. using Avalonia.UnitTests;
  12. using Moq;
  13. using Xunit;
  14. namespace Avalonia.Base.UnitTests.Styling
  15. {
  16. public class StyledElementTests
  17. {
  18. [Fact]
  19. public void Classes_Should_Initially_Be_Empty()
  20. {
  21. var target = new StyledElement();
  22. Assert.Empty(target.Classes);
  23. }
  24. [Fact]
  25. public void Setting_Parent_Should_Also_Set_InheritanceParent()
  26. {
  27. var parent = new Decorator();
  28. var target = new TestControl();
  29. parent.Child = target;
  30. Assert.Equal(parent, target.Parent);
  31. Assert.Equal(parent, target.InheritanceParent);
  32. }
  33. [Fact]
  34. public void InheritanceParent_Should_Be_Cleared_When_Removed_From_Parent()
  35. {
  36. var parent = new Decorator();
  37. var target = new TestControl();
  38. parent.Child = target;
  39. parent.Child = null;
  40. Assert.Null(target.InheritanceParent);
  41. }
  42. [Fact]
  43. public void Adding_Element_With_Null_Parent_To_Logical_Tree_Should_Throw()
  44. {
  45. var target = new Border();
  46. var visualParent = new Panel();
  47. var logicalParent = new Panel();
  48. var root = new TestRoot();
  49. // Set the logical parent...
  50. ((ISetLogicalParent)target).SetParent(logicalParent);
  51. // ...so that when it's added to `visualParent`, the parent won't be set again.
  52. visualParent.Children.Add(target);
  53. // Clear the logical parent. It's now a logical child of `visualParent` but doesn't have
  54. // a logical parent itself.
  55. ((ISetLogicalParent)target).SetParent(null);
  56. // In this case, attaching the control to a logical tree should throw.
  57. logicalParent.Children.Add(visualParent);
  58. Assert.Throws<InvalidOperationException>(() => root.Child = logicalParent);
  59. }
  60. [Fact]
  61. public void AttachedToLogicalTree_Should_Be_Called_When_Added_To_Tree()
  62. {
  63. var root = new TestRoot();
  64. var parent = new Border();
  65. var child = new Border();
  66. var grandchild = new Border();
  67. var parentRaised = false;
  68. var childRaised = false;
  69. var grandchildRaised = false;
  70. parent.AttachedToLogicalTree += (s, e) => parentRaised = true;
  71. child.AttachedToLogicalTree += (s, e) => childRaised = true;
  72. grandchild.AttachedToLogicalTree += (s, e) => grandchildRaised = true;
  73. parent.Child = child;
  74. child.Child = grandchild;
  75. Assert.False(parentRaised);
  76. Assert.False(childRaised);
  77. Assert.False(grandchildRaised);
  78. root.Child = parent;
  79. Assert.True(parentRaised);
  80. Assert.True(childRaised);
  81. Assert.True(grandchildRaised);
  82. }
  83. [Fact]
  84. public void AttachedToLogicalTree_Should_Be_Called_Before_Parent_Change_Signalled()
  85. {
  86. var root = new TestRoot();
  87. var child = new Border();
  88. var raised = new List<string>();
  89. child.AttachedToLogicalTree += (s, e) =>
  90. {
  91. Assert.Equal(root, child.Parent);
  92. raised.Add("attached");
  93. };
  94. child.GetObservable(StyledElement.ParentProperty).Skip(1).Subscribe(_ => raised.Add("parent"));
  95. root.Child = child;
  96. Assert.Equal(new[] { "attached", "parent" }, raised);
  97. }
  98. [Fact]
  99. public void AttachedToLogicalTree_Should_Not_Be_Called_With_GlobalStyles_As_Root()
  100. {
  101. var globalStyles = Mock.Of<IGlobalStyles>();
  102. var root = new TestRoot { StylingParent = globalStyles };
  103. var child = new Border();
  104. var raised = false;
  105. child.AttachedToLogicalTree += (s, e) =>
  106. {
  107. Assert.Equal(root, e.Root);
  108. raised = true;
  109. };
  110. root.Child = child;
  111. Assert.True(raised);
  112. }
  113. [Fact]
  114. public void AttachedToLogicalTree_Should_Have_Source_Set()
  115. {
  116. var root = new TestRoot();
  117. var canvas = new Canvas();
  118. var border = new Border { Child = canvas };
  119. var raised = 0;
  120. void Attached(object sender, LogicalTreeAttachmentEventArgs e)
  121. {
  122. Assert.Same(border, e.Source);
  123. ++raised;
  124. }
  125. border.AttachedToLogicalTree += Attached;
  126. canvas.AttachedToLogicalTree += Attached;
  127. root.Child = border;
  128. Assert.Equal(2, raised);
  129. }
  130. [Fact]
  131. public void AttachedToLogicalTree_Should_Have_Parent_Set()
  132. {
  133. var root = new TestRoot();
  134. var canvas = new Canvas();
  135. var border = new Border { Child = canvas };
  136. var raised = 0;
  137. void Attached(object sender, LogicalTreeAttachmentEventArgs e)
  138. {
  139. Assert.Same(root, e.Parent);
  140. ++raised;
  141. }
  142. border.AttachedToLogicalTree += Attached;
  143. canvas.AttachedToLogicalTree += Attached;
  144. root.Child = border;
  145. Assert.Equal(2, raised);
  146. }
  147. [Fact]
  148. public void DetachedFromLogicalTree_Should_Be_Called_When_Removed_From_Tree()
  149. {
  150. var root = new TestRoot();
  151. var parent = new Border();
  152. var child = new Border();
  153. var grandchild = new Border();
  154. var parentRaised = false;
  155. var childRaised = false;
  156. var grandchildRaised = false;
  157. parent.Child = child;
  158. child.Child = grandchild;
  159. root.Child = parent;
  160. parent.DetachedFromLogicalTree += (s, e) => parentRaised = true;
  161. child.DetachedFromLogicalTree += (s, e) => childRaised = true;
  162. grandchild.DetachedFromLogicalTree += (s, e) => grandchildRaised = true;
  163. root.Child = null;
  164. Assert.True(parentRaised);
  165. Assert.True(childRaised);
  166. Assert.True(grandchildRaised);
  167. }
  168. [Fact]
  169. public void DetachedFromLogicalTree_Should_Not_Be_Called_With_GlobalStyles_As_Root()
  170. {
  171. var globalStyles = Mock.Of<IGlobalStyles>();
  172. var root = new TestRoot { StylingParent = globalStyles };
  173. var child = new Border();
  174. var raised = false;
  175. child.DetachedFromLogicalTree += (s, e) =>
  176. {
  177. Assert.Equal(root, e.Root);
  178. raised = true;
  179. };
  180. root.Child = child;
  181. root.Child = null;
  182. Assert.True(raised);
  183. }
  184. [Fact]
  185. public void Parent_Should_Be_Null_When_DetachedFromLogicalTree_Called()
  186. {
  187. var target = new TestControl();
  188. var root = new TestRoot(target);
  189. var called = 0;
  190. target.DetachedFromLogicalTree += (s, e) =>
  191. {
  192. Assert.Null(target.Parent);
  193. Assert.Null(target.InheritanceParent);
  194. ++called;
  195. };
  196. root.Child = null;
  197. Assert.Equal(1, called);
  198. }
  199. [Fact]
  200. public void Adding_Tree_To_Root_Should_Style_Controls()
  201. {
  202. var root = new TestRoot
  203. {
  204. Styles =
  205. {
  206. new Style(x => x.Is<Control>())
  207. {
  208. Setters = { new Setter(Control.TagProperty, "foo") }
  209. }
  210. }
  211. };
  212. var grandchild = new Control();
  213. var child = new Border { Child = grandchild };
  214. var parent = new Border { Child = child };
  215. Assert.Null(parent.Tag);
  216. Assert.Null(child.Tag);
  217. Assert.Null(grandchild.Tag);
  218. root.Child = parent;
  219. Assert.Equal("foo", parent.Tag);
  220. Assert.Equal("foo", child.Tag);
  221. Assert.Equal("foo", grandchild.Tag);
  222. }
  223. [Fact]
  224. public void Styles_Not_Applied_Until_Initialization_Finished()
  225. {
  226. var root = new TestRoot
  227. {
  228. Styles =
  229. {
  230. new Style(x => x.Is<Control>())
  231. {
  232. Setters = { new Setter(Control.TagProperty, "foo") }
  233. }
  234. }
  235. };
  236. var child = new Border();
  237. ((ISupportInitialize)child).BeginInit();
  238. root.Child = child;
  239. Assert.Null(child.Tag);
  240. ((ISupportInitialize)child).EndInit();
  241. Assert.Equal("foo", child.Tag);
  242. }
  243. [Fact]
  244. public void Name_Cannot_Be_Set_After_Added_To_Logical_Tree()
  245. {
  246. var root = new TestRoot();
  247. var child = new Border();
  248. root.Child = child;
  249. Assert.Throws<InvalidOperationException>(() => child.Name = "foo");
  250. }
  251. [Fact]
  252. public void Name_Can_Be_Set_While_Initializing()
  253. {
  254. using (AvaloniaLocator.EnterScope())
  255. {
  256. var root = new TestRoot();
  257. var child = new Border();
  258. child.BeginInit();
  259. root.Child = child;
  260. child.Name = "foo";
  261. child.EndInit();
  262. }
  263. }
  264. [Fact]
  265. public void Style_Is_Removed_When_Control_Removed_From_Logical_Tree()
  266. {
  267. using var app = UnitTestApplication.Start();
  268. var target = new Border();
  269. var root = new TestRoot
  270. {
  271. Styles =
  272. {
  273. new Style(x => x.OfType<Border>())
  274. {
  275. Setters =
  276. {
  277. new Setter(Border.BackgroundProperty, Brushes.Red),
  278. }
  279. }
  280. },
  281. Child = target,
  282. };
  283. Assert.Equal(Brushes.Red, target.Background);
  284. root.Child = null;
  285. Assert.Null(target.Background);
  286. }
  287. [Fact]
  288. public void EndInit_Should_Raise_Initialized()
  289. {
  290. var root = new TestRoot();
  291. var target = new Border();
  292. var called = false;
  293. target.Initialized += (s, e) => called = true;
  294. ((ISupportInitialize)target).BeginInit();
  295. root.Child = target;
  296. ((ISupportInitialize)target).EndInit();
  297. Assert.True(called);
  298. Assert.True(target.IsInitialized);
  299. }
  300. [Fact]
  301. public void Attaching_To_Visual_Tree_Should_Raise_Initialized()
  302. {
  303. var root = new TestRoot();
  304. var target = new Border();
  305. var called = false;
  306. target.Initialized += (s, e) => called = true;
  307. root.Child = target;
  308. Assert.True(called);
  309. Assert.True(target.IsInitialized);
  310. }
  311. [Fact]
  312. public void DataContextChanged_Should_Be_Called()
  313. {
  314. var root = new TestStackPanel
  315. {
  316. Name = "root",
  317. Children =
  318. {
  319. new TestControl
  320. {
  321. Name = "a1",
  322. Child = new TestControl
  323. {
  324. Name = "b1",
  325. }
  326. },
  327. new TestControl
  328. {
  329. Name = "a2",
  330. DataContext = "foo",
  331. },
  332. }
  333. };
  334. var called = new List<string>();
  335. void Record(object sender, EventArgs e) => called.Add(((StyledElement)sender).Name);
  336. root.DataContextChanged += Record;
  337. foreach (TestControl c in root.GetLogicalDescendants())
  338. {
  339. c.DataContextChanged += Record;
  340. }
  341. root.DataContext = "foo";
  342. Assert.Equal(new[] { "root", "a1", "b1", }, called);
  343. }
  344. [Fact]
  345. public void DataContext_Notifications_Should_Be_Called_In_Correct_Order()
  346. {
  347. var root = new TestStackPanel
  348. {
  349. Name = "root",
  350. Children =
  351. {
  352. new TestControl
  353. {
  354. Name = "a1",
  355. Child = new TestControl
  356. {
  357. Name = "b1",
  358. }
  359. },
  360. new TestControl
  361. {
  362. Name = "a2",
  363. DataContext = "foo",
  364. },
  365. }
  366. };
  367. var called = new List<string>();
  368. foreach (IDataContextEvents c in root.GetSelfAndLogicalDescendants())
  369. {
  370. c.DataContextBeginUpdate += (s, e) => called.Add("begin " + ((StyledElement)s).Name);
  371. c.DataContextChanged += (s, e) => called.Add("changed " + ((StyledElement)s).Name);
  372. c.DataContextEndUpdate += (s, e) => called.Add("end " + ((StyledElement)s).Name);
  373. }
  374. root.DataContext = "foo";
  375. Assert.Equal(
  376. new[]
  377. {
  378. "begin root",
  379. "begin a1",
  380. "begin b1",
  381. "changed root",
  382. "changed a1",
  383. "changed b1",
  384. "end b1",
  385. "end a1",
  386. "end root",
  387. },
  388. called);
  389. }
  390. [Fact]
  391. public void DataContext_Notifications_Should_Be_Called_In_Correct_Order_When_Setting_Parent()
  392. {
  393. var root = new TestStackPanel
  394. {
  395. Name = "root",
  396. DataContext = "foo",
  397. };
  398. var children = new[]
  399. {
  400. new TestControl
  401. {
  402. Name = "a1",
  403. Child = new TestControl
  404. {
  405. Name = "b1",
  406. }
  407. },
  408. new TestControl
  409. {
  410. Name = "a2",
  411. DataContext = "foo",
  412. },
  413. };
  414. var called = new List<string>();
  415. foreach (IDataContextEvents c in new[] { children[0], children[0].Child, children[1] })
  416. {
  417. c.DataContextBeginUpdate += (s, e) => called.Add("begin " + ((StyledElement)s).Name);
  418. c.DataContextChanged += (s, e) => called.Add("changed " + ((StyledElement)s).Name);
  419. c.DataContextEndUpdate += (s, e) => called.Add("end " + ((StyledElement)s).Name);
  420. }
  421. root.Children.AddRange(children);
  422. Assert.Equal(
  423. new[]
  424. {
  425. "begin a1",
  426. "begin b1",
  427. "changed a1",
  428. "changed b1",
  429. "end b1",
  430. "end a1",
  431. },
  432. called);
  433. }
  434. [Fact]
  435. public void Resources_Owner_Is_Set()
  436. {
  437. var target = new TestControl();
  438. Assert.Same(target, ((ResourceDictionary)target.Resources).Owner);
  439. }
  440. [Fact]
  441. public void Assigned_Resources_Parent_Is_Set()
  442. {
  443. var resources = new Mock<IResourceDictionary>();
  444. var target = new TestControl { Resources = resources.Object };
  445. resources.Verify(x => x.AddOwner(target));
  446. }
  447. [Fact]
  448. public void Assigning_Resources_Raises_ResourcesChanged()
  449. {
  450. var resources = new ResourceDictionary { { "foo", "bar" } };
  451. var target = new TestControl();
  452. var raised = 0;
  453. target.ResourcesChanged += (s, e) => ++raised;
  454. target.Resources = resources;
  455. Assert.Equal(1, raised);
  456. }
  457. [Fact]
  458. public void Styles_Owner_Is_Set()
  459. {
  460. var target = new TestControl();
  461. Assert.Same(target, target.Styles.Owner);
  462. }
  463. [Fact]
  464. public void Adding_To_Logical_Tree_Raises_ResourcesChanged()
  465. {
  466. var target = new TestRoot();
  467. var parent = new Decorator { Resources = { { "foo", "bar" } } };
  468. var raised = 0;
  469. target.ResourcesChanged += (s, e) => ++raised;
  470. parent.Child = target;
  471. Assert.Equal(1, raised);
  472. }
  473. [Fact]
  474. public void SetParent_Does_Not_Crash_Due_To_Reentrancy()
  475. {
  476. // Issue #3708
  477. using var app = UnitTestApplication.Start(TestServices.StyledWindow);
  478. ContentControl target;
  479. var root = new TestRoot
  480. {
  481. DataContext = false,
  482. Child = target = new ContentControl
  483. {
  484. Styles =
  485. {
  486. new Style(x => x.OfType<ContentControl>())
  487. {
  488. Setters =
  489. {
  490. new Setter(
  491. ContentControl.ContentProperty,
  492. new FuncTemplate<Control>(() => new TextBlock { Text = "Enabled" })),
  493. },
  494. },
  495. new Style(x => x.OfType<ContentControl>().Class(":disabled"))
  496. {
  497. Setters =
  498. {
  499. new Setter(
  500. ContentControl.ContentProperty,
  501. new FuncTemplate<Control>(() => new TextBlock { Text = "Disabled" })),
  502. },
  503. },
  504. },
  505. [!ContentControl.IsEnabledProperty] = new Binding(),
  506. }
  507. };
  508. root.Measure(Size.Infinity);
  509. root.Arrange(new Rect(0, 0, 100, 100));
  510. var textBlock = Assert.IsType<TextBlock>(target.Content);
  511. Assert.Equal("Disabled", textBlock.Text);
  512. // #3708 was crashing here with AvaloniaInternalException.
  513. root.Child = null;
  514. }
  515. private interface IDataContextEvents
  516. {
  517. event EventHandler DataContextBeginUpdate;
  518. event EventHandler DataContextChanged;
  519. event EventHandler DataContextEndUpdate;
  520. }
  521. private class TestControl : Decorator, IDataContextEvents
  522. {
  523. public event EventHandler DataContextBeginUpdate;
  524. public event EventHandler DataContextEndUpdate;
  525. public new AvaloniaObject InheritanceParent => base.InheritanceParent;
  526. protected override void OnDataContextBeginUpdate()
  527. {
  528. DataContextBeginUpdate?.Invoke(this, EventArgs.Empty);
  529. base.OnDataContextBeginUpdate();
  530. }
  531. protected override void OnDataContextEndUpdate()
  532. {
  533. DataContextEndUpdate?.Invoke(this, EventArgs.Empty);
  534. base.OnDataContextEndUpdate();
  535. }
  536. }
  537. private class TestStackPanel : StackPanel, IDataContextEvents
  538. {
  539. public event EventHandler DataContextBeginUpdate;
  540. public event EventHandler DataContextEndUpdate;
  541. protected override void OnDataContextBeginUpdate()
  542. {
  543. DataContextBeginUpdate?.Invoke(this, EventArgs.Empty);
  544. base.OnDataContextBeginUpdate();
  545. }
  546. protected override void OnDataContextEndUpdate()
  547. {
  548. DataContextEndUpdate?.Invoke(this, EventArgs.Empty);
  549. base.OnDataContextEndUpdate();
  550. }
  551. }
  552. }
  553. }