SetterTests.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. using System;
  2. using System.Globalization;
  3. using System.Reactive.Subjects;
  4. using Avalonia.Controls;
  5. using Avalonia.Controls.Templates;
  6. using Avalonia.Data;
  7. using Avalonia.Data.Converters;
  8. using Avalonia.Media;
  9. using Avalonia.Styling;
  10. using Avalonia.UnitTests;
  11. using Moq;
  12. using Xunit;
  13. #nullable enable
  14. namespace Avalonia.Base.UnitTests.Styling
  15. {
  16. public class SetterTests
  17. {
  18. [Fact]
  19. public void Cannot_Assign_Control_To_Value()
  20. {
  21. var target = new Setter();
  22. Assert.Throws<InvalidOperationException>(() => target.Value = new Border());
  23. }
  24. [Fact]
  25. public void Setter_Should_Apply_Binding_To_Property()
  26. {
  27. var control = new TextBlock();
  28. var subject = new BehaviorSubject<object>("foo");
  29. var binding = subject.ToBinding();
  30. var setter = new Setter(TextBlock.TagProperty, binding);
  31. Apply(setter, control);
  32. Assert.Equal("foo", control.Tag);
  33. }
  34. [Fact]
  35. public void Setter_Should_Handle_Binding_Producing_UnsetValue()
  36. {
  37. var control = new TextBlock();
  38. var subject = new BehaviorSubject<object>(AvaloniaProperty.UnsetValue);
  39. var binding = subject.ToBinding();
  40. var setter = new Setter(TextBlock.TagProperty, binding);
  41. Apply(setter, control);
  42. Assert.Equal(null, control.Text);
  43. }
  44. [Fact]
  45. public void Setter_Should_Materialize_Template_To_Property()
  46. {
  47. var control = new Decorator();
  48. var template = new FuncTemplate<Canvas>(() => new Canvas());
  49. var style = Mock.Of<IStyle>();
  50. var setter = new Setter(Decorator.ChildProperty, template);
  51. Apply(setter, control);
  52. Assert.IsType<Canvas>(control.Child);
  53. }
  54. [Fact]
  55. public void Can_Set_Direct_Property_In_Style_Without_Activator()
  56. {
  57. var control = new DirectPropertyClass();
  58. var target = new Setter();
  59. var style = new Style(x => x.Is<DirectPropertyClass>())
  60. {
  61. Setters =
  62. {
  63. new Setter(DirectPropertyClass.FooProperty, "foo"),
  64. }
  65. };
  66. Apply(style, control);
  67. Assert.Equal("foo", control.Foo);
  68. }
  69. [Fact]
  70. public void Can_Set_Direct_Property_Binding_In_Style_Without_Activator()
  71. {
  72. var control = new DirectPropertyClass();
  73. var target = new Setter();
  74. var source = new BehaviorSubject<object?>("foo");
  75. var style = new Style(x => x.Is<DirectPropertyClass>())
  76. {
  77. Setters =
  78. {
  79. new Setter(DirectPropertyClass.FooProperty, source.ToBinding()),
  80. }
  81. };
  82. Apply(style, control);
  83. Assert.Equal("foo", control.Foo);
  84. }
  85. [Fact]
  86. public void Cannot_Set_Direct_Property_Binding_In_Style_With_Activator()
  87. {
  88. var control = new DirectPropertyClass();
  89. var target = new Setter();
  90. var source = new BehaviorSubject<object?>("foo");
  91. var style = new Style(x => x.Is<DirectPropertyClass>().Class("foo"))
  92. {
  93. Setters =
  94. {
  95. new Setter(DirectPropertyClass.FooProperty, source.ToBinding()),
  96. }
  97. };
  98. Assert.Throws<InvalidOperationException>(() => Apply(style, control));
  99. }
  100. [Fact]
  101. public void Cannot_Set_Direct_Property_In_Style_With_Activator()
  102. {
  103. var control = new DirectPropertyClass();
  104. var target = new Setter();
  105. var style = new Style(x => x.Is<DirectPropertyClass>().Class("foo"))
  106. {
  107. Setters =
  108. {
  109. new Setter(DirectPropertyClass.FooProperty, "foo"),
  110. }
  111. };
  112. Assert.Throws<InvalidOperationException>(() => Apply(style, control));
  113. }
  114. [Fact]
  115. public void Does_Not_Call_Converter_ConvertBack_On_OneWay_Binding()
  116. {
  117. var control = new Decorator
  118. {
  119. Name = "foo",
  120. Classes = { "foo" },
  121. };
  122. var binding = new Binding("Name", BindingMode.OneWay)
  123. {
  124. Converter = new TestConverter(),
  125. RelativeSource = new RelativeSource(RelativeSourceMode.Self),
  126. };
  127. var style = new Style(x => x.OfType<Decorator>().Class("foo"))
  128. {
  129. Setters =
  130. {
  131. new Setter(Decorator.TagProperty, binding)
  132. },
  133. };
  134. Apply(style, control);
  135. Assert.Equal("foobar", control.Tag);
  136. // Issue #1218 caused TestConverter.ConvertBack to throw here.
  137. control.Classes.Remove("foo");
  138. Assert.Null(control.Tag);
  139. }
  140. [Fact]
  141. public void Setter_Should_Apply_Value_Without_Activator_With_Style_Priority()
  142. {
  143. var control = new Border();
  144. var style = new Style(x => x.OfType<Border>())
  145. {
  146. Setters =
  147. {
  148. new Setter(Control.TagProperty, "foo"),
  149. },
  150. };
  151. var raised = 0;
  152. control.PropertyChanged += (s, e) =>
  153. {
  154. Assert.Equal(Control.TagProperty, e.Property);
  155. Assert.Equal(BindingPriority.Style, e.Priority);
  156. ++raised;
  157. };
  158. Apply(style, control);
  159. Assert.Equal(1, raised);
  160. }
  161. [Fact]
  162. public void Setter_Should_Apply_Value_With_Activator_With_StyleTrigger_Priority()
  163. {
  164. var control = new Border { Classes = { "foo" } };
  165. var style = new Style(x => x.OfType<Border>().Class("foo"))
  166. {
  167. Setters =
  168. {
  169. new Setter(Control.TagProperty, "foo"),
  170. },
  171. };
  172. var activator = new Subject<bool>();
  173. var raised = 0;
  174. control.PropertyChanged += (s, e) =>
  175. {
  176. Assert.Equal(Border.TagProperty, e.Property);
  177. Assert.Equal(BindingPriority.StyleTrigger, e.Priority);
  178. ++raised;
  179. };
  180. Apply(style, control);
  181. Assert.Equal(1, raised);
  182. }
  183. [Fact]
  184. public void Setter_Should_Apply_Binding_Without_Activator_With_Style_Priority()
  185. {
  186. var control = new Border
  187. {
  188. DataContext = "foo",
  189. };
  190. var style = new Style(x => x.OfType<Border>())
  191. {
  192. Setters =
  193. {
  194. new Setter(Control.TagProperty, new Binding()),
  195. },
  196. };
  197. var raised = 0;
  198. control.PropertyChanged += (s, e) =>
  199. {
  200. Assert.Equal(Control.TagProperty, e.Property);
  201. Assert.Equal(BindingPriority.Style, e.Priority);
  202. ++raised;
  203. };
  204. Apply(style, control);
  205. Assert.Equal(1, raised);
  206. }
  207. [Fact]
  208. public void Setter_Should_Apply_Binding_With_Activator_With_StyleTrigger_Priority()
  209. {
  210. var control = new Border
  211. {
  212. Classes = { "foo" },
  213. DataContext = "foo",
  214. };
  215. var style = new Style(x => x.OfType<Border>().Class("foo"))
  216. {
  217. Setters =
  218. {
  219. new Setter(Control.TagProperty, new Binding()),
  220. },
  221. };
  222. var raised = 0;
  223. control.PropertyChanged += (s, e) =>
  224. {
  225. Assert.Equal(Control.TagProperty, e.Property);
  226. Assert.Equal(BindingPriority.StyleTrigger, e.Priority);
  227. ++raised;
  228. };
  229. Apply(style, control);
  230. Assert.Equal(1, raised);
  231. }
  232. [Fact]
  233. public void Direct_Property_Setter_With_TwoWay_Binding_Should_Update_Source()
  234. {
  235. using var app = UnitTestApplication.Start(TestServices.MockThreadingInterface);
  236. var data = new Data { Foo = "foo" };
  237. var control = new DirectPropertyClass
  238. {
  239. DataContext = data,
  240. };
  241. var style = new Style(x => x.OfType<DirectPropertyClass>())
  242. {
  243. Setters =
  244. {
  245. new Setter
  246. {
  247. Property = DirectPropertyClass.FooProperty,
  248. Value = new Binding
  249. {
  250. Path = "Foo",
  251. Mode = BindingMode.TwoWay
  252. }
  253. }
  254. },
  255. };
  256. Apply(style, control);
  257. Assert.Equal("foo", control.Foo);
  258. control.Foo = "bar";
  259. Assert.Equal("bar", data.Foo);
  260. }
  261. [Fact]
  262. public void Styled_Property_Setter_With_TwoWay_Binding_Should_Update_Source()
  263. {
  264. var data = new Data { Bar = Brushes.Red };
  265. var control = new Border
  266. {
  267. DataContext = data,
  268. };
  269. var style = new Style(x => x.OfType<Border>())
  270. {
  271. Setters =
  272. {
  273. new Setter
  274. {
  275. Property = Border.BackgroundProperty,
  276. Value = new Binding
  277. {
  278. Path = "Bar",
  279. Mode = BindingMode.TwoWay
  280. }
  281. }
  282. },
  283. };
  284. Apply(style, control);
  285. Assert.Equal(Brushes.Red, control.Background);
  286. control.Background = Brushes.Green;
  287. Assert.Equal(Brushes.Green, data.Bar);
  288. }
  289. [Fact]
  290. public void Non_Active_Styled_Property_Binding_Should_Be_Unsubscribed()
  291. {
  292. var data = new Data { Bar = Brushes.Red };
  293. var control = new Border
  294. {
  295. DataContext = data,
  296. };
  297. var style1 = new Style(x => x.OfType<Border>())
  298. {
  299. Setters =
  300. {
  301. new Setter
  302. {
  303. Property = Border.BackgroundProperty,
  304. Value = new Binding("Bar"),
  305. }
  306. },
  307. };
  308. var style2 = new Style(x => x.OfType<Border>().Class("foo"))
  309. {
  310. Setters =
  311. {
  312. new Setter
  313. {
  314. Property = Border.BackgroundProperty,
  315. Value = Brushes.Green,
  316. }
  317. },
  318. };
  319. Apply(style1, control);
  320. Apply(style2, control);
  321. // `style1` is initially active.
  322. Assert.Equal(Brushes.Red, control.Background);
  323. Assert.Equal(1, data.PropertyChangedSubscriptionCount);
  324. // Activate `style2`.
  325. control.Classes.Add("foo");
  326. Assert.Equal(Brushes.Green, control.Background);
  327. // The binding from `style1` is now inactive and so should be unsubscribed.
  328. Assert.Equal(0, data.PropertyChangedSubscriptionCount);
  329. }
  330. [Fact]
  331. public void Non_Active_Styled_Property_Setter_With_TwoWay_Binding_Should_Not_Update_Source()
  332. {
  333. var data = new Data { Bar = Brushes.Red };
  334. var control = new Border
  335. {
  336. DataContext = data,
  337. };
  338. var style1 = new Style(x => x.OfType<Border>())
  339. {
  340. Setters =
  341. {
  342. new Setter
  343. {
  344. Property = Border.BackgroundProperty,
  345. Value = new Binding
  346. {
  347. Path = "Bar",
  348. Mode = BindingMode.TwoWay
  349. }
  350. }
  351. },
  352. };
  353. var style2 = new Style(x => x.OfType<Border>().Class("foo"))
  354. {
  355. Setters =
  356. {
  357. new Setter
  358. {
  359. Property = Border.BackgroundProperty,
  360. Value = Brushes.Green,
  361. }
  362. },
  363. };
  364. Apply(style1, control);
  365. Apply(style2, control);
  366. // `style1` is initially active.
  367. Assert.Equal(Brushes.Red, control.Background);
  368. // Activate `style2`.
  369. control.Classes.Add("foo");
  370. Assert.Equal(Brushes.Green, control.Background);
  371. // The two-way binding from `style1` is now inactive and so should not write back to
  372. // the DataContext.
  373. Assert.Equal(Brushes.Red, data.Bar);
  374. }
  375. [Fact]
  376. public void Styled_Property_Setter_With_TwoWay_Binding_Updates_Source_When_Made_Active()
  377. {
  378. var data = new Data { Bar = Brushes.Red };
  379. var control = new Border
  380. {
  381. Classes = { "foo" },
  382. DataContext = data,
  383. };
  384. var style1 = new Style(x => x.OfType<Border>())
  385. {
  386. Setters =
  387. {
  388. new Setter
  389. {
  390. Property = Border.BackgroundProperty,
  391. Value = new Binding
  392. {
  393. Path = "Bar",
  394. Mode = BindingMode.TwoWay
  395. }
  396. }
  397. },
  398. };
  399. var style2 = new Style(x => x.OfType<Border>().Class("foo"))
  400. {
  401. Setters =
  402. {
  403. new Setter
  404. {
  405. Property = Border.BackgroundProperty,
  406. Value = Brushes.Green,
  407. }
  408. },
  409. };
  410. Apply(style1, control);
  411. Apply(style2, control);
  412. // `style2` is initially active.
  413. Assert.Equal(Brushes.Green, control.Background);
  414. // Deactivate `style2`.
  415. control.Classes.Remove("foo");
  416. Assert.Equal(Brushes.Red, control.Background);
  417. // The two-way binding from `style1` is now active and so should write back to the
  418. // DataContext.
  419. control.Background = Brushes.Blue;
  420. Assert.Equal(Brushes.Blue, data.Bar);
  421. }
  422. private void Apply(Style style, StyledElement element)
  423. {
  424. StyleHelpers.TryAttach(style, element);
  425. }
  426. private void Apply(Setter setter, Control control)
  427. {
  428. var style = new Style(x => x.Is<Control>())
  429. {
  430. Setters = { setter },
  431. };
  432. Apply(style, control);
  433. }
  434. private class Data : NotifyingBase
  435. {
  436. public string? Foo { get; set; }
  437. public IBrush? Bar { get; set; }
  438. }
  439. private class TestConverter : IValueConverter
  440. {
  441. public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
  442. {
  443. return value + "bar";
  444. }
  445. public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
  446. {
  447. throw new NotImplementedException();
  448. }
  449. }
  450. private class DirectPropertyClass : StyledElement
  451. {
  452. public static readonly DirectProperty<DirectPropertyClass, string?> FooProperty = AvaloniaProperty.RegisterDirect<DirectPropertyClass, string?>(nameof(Foo),
  453. x => x.Foo, (x, v) => x.Foo = v);
  454. private string? _foo;
  455. public string? Foo
  456. {
  457. get => _foo;
  458. set => SetAndRaise(FooProperty, ref _foo, value);
  459. }
  460. }
  461. }
  462. }