SetterTests.cs 16 KB

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