AvaloniaObjectTests_Coercion.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reactive.Subjects;
  4. using Avalonia.Controls;
  5. using Avalonia.Data;
  6. using Avalonia.Styling;
  7. using Avalonia.UnitTests;
  8. using Xunit;
  9. namespace Avalonia.Base.UnitTests
  10. {
  11. public class AvaloniaObjectTests_Coercion
  12. {
  13. [Fact]
  14. public void Coerces_Set_Value()
  15. {
  16. var target = new Class1();
  17. target.Foo = 150;
  18. Assert.Equal(100, target.Foo);
  19. }
  20. [Fact]
  21. public void Coerces_Set_Value_Attached()
  22. {
  23. var target = new Class1();
  24. target.SetValue(Class1.AttachedProperty, 150);
  25. Assert.Equal(100, target.GetValue(Class1.AttachedProperty));
  26. }
  27. [Fact]
  28. public void Coerces_Set_Value_Attached_On_Class_Not_Derived_From_Owner()
  29. {
  30. var target = new Class2();
  31. target.SetValue(Class1.AttachedProperty, 150);
  32. Assert.Equal(100, target.GetValue(Class1.AttachedProperty));
  33. }
  34. [Fact]
  35. public void Coerces_Bound_Value()
  36. {
  37. var target = new Class1();
  38. var source = new Subject<BindingValue<int>>();
  39. target.Bind(Class1.FooProperty, source);
  40. source.OnNext(150);
  41. Assert.Equal(100, target.Foo);
  42. }
  43. [Fact]
  44. public void CoerceValue_Updates_Value()
  45. {
  46. var target = new Class1 { Foo = 99 };
  47. Assert.Equal(99, target.Foo);
  48. target.MaxFoo = 50;
  49. target.CoerceValue(Class1.FooProperty);
  50. Assert.Equal(50, target.Foo);
  51. }
  52. [Fact]
  53. public void CoerceValue_Updates_Base_Value()
  54. {
  55. var target = new Class1 { Foo = 99 };
  56. target.SetValue(Class1.FooProperty, 88, BindingPriority.Animation);
  57. Assert.Equal(88, target.Foo);
  58. Assert.Equal(99, target.GetBaseValue(Class1.FooProperty));
  59. target.MaxFoo = 50;
  60. target.CoerceValue(Class1.FooProperty);
  61. Assert.Equal(50, target.Foo);
  62. Assert.Equal(50, target.GetBaseValue(Class1.FooProperty));
  63. }
  64. [Fact]
  65. public void CoerceValue_Raises_PropertyChanged()
  66. {
  67. var target = new Class1 { Foo = 99 };
  68. var raised = 0;
  69. target.PropertyChanged += (s, e) =>
  70. {
  71. Assert.Equal(Class1.FooProperty, e.Property);
  72. Assert.Equal(99, e.OldValue);
  73. Assert.Equal(50, e.NewValue);
  74. Assert.Equal(BindingPriority.LocalValue, e.Priority);
  75. ++raised;
  76. };
  77. Assert.Equal(99, target.Foo);
  78. target.MaxFoo = 50;
  79. target.CoerceValue(Class1.FooProperty);
  80. Assert.Equal(50, target.Foo);
  81. Assert.Equal(1, raised);
  82. }
  83. [Fact]
  84. public void CoerceValue_Raises_PropertyChangedCore_For_Base_Value()
  85. {
  86. var target = new Class1 { Foo = 99 };
  87. target.SetValue(Class1.FooProperty, 88, BindingPriority.Animation);
  88. Assert.Equal(88, target.Foo);
  89. Assert.Equal(99, target.GetBaseValue(Class1.FooProperty));
  90. target.MaxFoo = 50;
  91. target.CoreChanges.Clear();
  92. target.CoerceValue(Class1.FooProperty);
  93. Assert.Equal(2, target.CoreChanges.Count);
  94. }
  95. [Fact]
  96. public void CoerceValue_Calls_Coerce_Callback_Only_Once()
  97. {
  98. var target = new Class1 { Foo = 99 };
  99. target.MaxFoo = 50;
  100. target.CoerceFooInvocations.Clear();
  101. target.CoerceValue(Class1.FooProperty);
  102. Assert.Equal(new[] { 99 }, target.CoerceFooInvocations);
  103. }
  104. [Fact]
  105. public void Coerced_Value_Can_Be_Restored_If_Limit_Changed()
  106. {
  107. var target = new Class1();
  108. target.Foo = 150;
  109. Assert.Equal(100, target.Foo);
  110. target.MaxFoo = 200;
  111. target.CoerceValue(Class1.FooProperty);
  112. Assert.Equal(150, target.Foo);
  113. }
  114. [Fact]
  115. public void Coerced_Value_Can_Be_Restored_From_Previously_Active_Binding()
  116. {
  117. var target = new Class1();
  118. var source1 = new Subject<BindingValue<int>>();
  119. var source2 = new Subject<BindingValue<int>>();
  120. target.Bind(Class1.FooProperty, source1, BindingPriority.Style);
  121. source1.OnNext(150);
  122. target.Bind(Class1.FooProperty, source2);
  123. source2.OnNext(160);
  124. Assert.Equal(100, target.Foo);
  125. target.MaxFoo = 200;
  126. source2.OnCompleted();
  127. Assert.Equal(150, target.Foo);
  128. }
  129. [Fact]
  130. public void CoerceValue_Updates_Inherited_Value()
  131. {
  132. var parent = new Class1 { Inherited = 99 };
  133. var child = new AvaloniaObject { InheritanceParent = parent };
  134. var raised = 0;
  135. child.InheritanceParent = parent;
  136. child.PropertyChanged += (s, e) =>
  137. {
  138. Assert.Equal(Class1.InheritedProperty, e.Property);
  139. Assert.Equal(99, e.OldValue);
  140. Assert.Equal(50, e.NewValue);
  141. Assert.Equal(BindingPriority.Inherited, e.Priority);
  142. ++raised;
  143. };
  144. Assert.Equal(99, child.GetValue(Class1.InheritedProperty));
  145. parent.MaxFoo = 50;
  146. parent.CoerceValue(Class1.InheritedProperty);
  147. Assert.Equal(50, child.GetValue(Class1.InheritedProperty));
  148. Assert.Equal(1, raised);
  149. }
  150. [Fact]
  151. public void Coercion_Can_Be_Overridden()
  152. {
  153. var target = new Class2();
  154. target.Foo = 150;
  155. Assert.Equal(-150, target.Foo);
  156. }
  157. [Fact]
  158. public void Default_Value_Can_Be_Coerced()
  159. {
  160. var target = new Class1();
  161. var raised = 0;
  162. target.MinFoo = 20;
  163. target.PropertyChanged += (s, e) =>
  164. {
  165. Assert.Equal(Class1.FooProperty, e.Property);
  166. Assert.Equal(11, e.OldValue);
  167. Assert.Equal(20, e.NewValue);
  168. Assert.Equal(BindingPriority.Unset, e.Priority);
  169. ++raised;
  170. };
  171. target.CoerceValue(Class1.FooProperty);
  172. Assert.Equal(20, target.Foo);
  173. Assert.Equal(1, raised);
  174. }
  175. [Fact]
  176. public void Default_Value_Is_Coerced_Only_Once()
  177. {
  178. var target = new Class1();
  179. target.MinFoo = 20;
  180. target.CoerceFooInvocations.Clear();
  181. target.CoerceValue(Class1.FooProperty);
  182. Assert.Equal(new[] { 11 }, target.CoerceFooInvocations);
  183. }
  184. [Fact]
  185. public void Second_Coerce_Of_Default_Value_Is_Passed_Uncoerced_Value()
  186. {
  187. var target = new Class1();
  188. target.MinFoo = 20;
  189. target.CoerceFooInvocations.Clear();
  190. target.CoerceValue(Class1.FooProperty);
  191. target.CoerceValue(Class1.FooProperty);
  192. Assert.Equal(new[] { 11, 11 }, target.CoerceFooInvocations);
  193. }
  194. [Fact]
  195. public void ClearValue_Respects_Coerced_Default_Value()
  196. {
  197. var target = new Class1();
  198. var raised = 0;
  199. target.Foo = 30;
  200. target.MinFoo = 20;
  201. target.PropertyChanged += (s, e) =>
  202. {
  203. Assert.Equal(Class1.FooProperty, e.Property);
  204. Assert.Equal(30, e.OldValue);
  205. Assert.Equal(20, e.NewValue);
  206. Assert.Equal(BindingPriority.Unset, e.Priority);
  207. ++raised;
  208. };
  209. target.ClearValue(Class1.FooProperty);
  210. Assert.Equal(20, target.Foo);
  211. Assert.Equal(1, raised);
  212. }
  213. [Fact]
  214. public void Deactivating_Style_Respects_Coerced_Default_Value()
  215. {
  216. var target = new Control1
  217. {
  218. MinFoo = 20,
  219. };
  220. var root = new TestRoot
  221. {
  222. Styles =
  223. {
  224. new Style(x => x.OfType<Control1>().Class("foo"))
  225. {
  226. Setters =
  227. {
  228. new Setter(Control1.FooProperty, 50),
  229. },
  230. },
  231. },
  232. Child = target,
  233. };
  234. var raised = 0;
  235. target.Classes.Add("foo");
  236. root.LayoutManager.ExecuteInitialLayoutPass();
  237. Assert.Equal(50, target.Foo);
  238. target.PropertyChanged += (s, e) =>
  239. {
  240. Assert.Equal(Control1.FooProperty, e.Property);
  241. Assert.Equal(50, e.OldValue);
  242. Assert.Equal(20, e.NewValue);
  243. Assert.Equal(BindingPriority.Unset, e.Priority);
  244. ++raised;
  245. };
  246. target.Classes.Remove("foo");
  247. Assert.Equal(20, target.Foo);
  248. Assert.Equal(1, raised);
  249. }
  250. [Fact]
  251. public void If_Initial_State_Has_Coerced_Default_Value_Then_CoerceValue_Must_Be_Called()
  252. {
  253. // This test is just explicitly describing an edge-case. If the initial state of the
  254. // object results in a coerced property value then CoerceValue must be called before
  255. // coercion takes effect. Confirmed as matching the behavior of WPF.
  256. var target = new Class3();
  257. Assert.Equal(11, target.Foo);
  258. target.CoerceValue(Class3.FooProperty);
  259. Assert.Equal(50, target.Foo);
  260. }
  261. private class Class1 : AvaloniaObject
  262. {
  263. public static readonly StyledProperty<int> FooProperty =
  264. AvaloniaProperty.Register<Class1, int>(
  265. "Foo",
  266. defaultValue: 11,
  267. coerce: CoerceFoo);
  268. public static readonly AttachedProperty<int> AttachedProperty =
  269. AvaloniaProperty.RegisterAttached<Class1, AvaloniaObject, int>(
  270. "Attached",
  271. defaultValue: 11,
  272. coerce: CoerceFoo);
  273. public static readonly StyledProperty<int> InheritedProperty =
  274. AvaloniaProperty.RegisterAttached<Class1, Class1, int>(
  275. "Attached",
  276. defaultValue: 11,
  277. inherits: true,
  278. coerce: CoerceFoo);
  279. public int Foo
  280. {
  281. get => GetValue(FooProperty);
  282. set => SetValue(FooProperty, value);
  283. }
  284. public int Inherited
  285. {
  286. get => GetValue(InheritedProperty);
  287. set => SetValue(InheritedProperty, value);
  288. }
  289. public int MinFoo { get; set; } = 0;
  290. public int MaxFoo { get; set; } = 100;
  291. public List<int> CoerceFooInvocations { get; } = new();
  292. public List<AvaloniaPropertyChangedEventArgs> CoreChanges { get; } = new();
  293. public static int CoerceFoo(AvaloniaObject instance, int value)
  294. {
  295. (instance as Class1)?.CoerceFooInvocations.Add(value);
  296. return instance is Class1 o ?
  297. Math.Clamp(value, o.MinFoo, o.MaxFoo) :
  298. Math.Clamp(value, 0, 100);
  299. }
  300. protected override void OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change)
  301. {
  302. CoreChanges.Add(Clone(change));
  303. base.OnPropertyChangedCore(change);
  304. }
  305. private static AvaloniaPropertyChangedEventArgs Clone(AvaloniaPropertyChangedEventArgs change)
  306. {
  307. var e = (AvaloniaPropertyChangedEventArgs<int>)change;
  308. return new AvaloniaPropertyChangedEventArgs<int>(
  309. change.Sender,
  310. e.Property,
  311. e.OldValue,
  312. e.NewValue,
  313. change.Priority,
  314. change.IsEffectiveValueChange);
  315. }
  316. }
  317. private class Class2 : AvaloniaObject
  318. {
  319. public static readonly StyledProperty<int> FooProperty =
  320. Class1.FooProperty.AddOwner<Class2>();
  321. static Class2()
  322. {
  323. FooProperty.OverrideMetadata<Class2>(
  324. new StyledPropertyMetadata<int>(
  325. coerce: CoerceFoo));
  326. }
  327. public int Foo
  328. {
  329. get => GetValue(FooProperty);
  330. set => SetValue(FooProperty, value);
  331. }
  332. public static int CoerceFoo(AvaloniaObject instance, int value)
  333. {
  334. return -value;
  335. }
  336. }
  337. private class Class3: AvaloniaObject
  338. {
  339. public static readonly StyledProperty<int> FooProperty =
  340. AvaloniaProperty.Register<Class3, int>(
  341. "Foo",
  342. defaultValue: 11,
  343. coerce: CoerceFoo);
  344. public int Foo
  345. {
  346. get => GetValue(FooProperty);
  347. set => SetValue(FooProperty, value);
  348. }
  349. public static int CoerceFoo(AvaloniaObject instance, int value)
  350. {
  351. var o = (Class3)instance;
  352. return Math.Clamp(value, 50, 100);
  353. }
  354. }
  355. private class Control1 : Control
  356. {
  357. public static readonly StyledProperty<int> FooProperty =
  358. AvaloniaProperty.Register<Control1, int>(
  359. "Foo",
  360. defaultValue: 11,
  361. coerce: CoerceFoo);
  362. public int Foo
  363. {
  364. get => GetValue(FooProperty);
  365. set => SetValue(FooProperty, value);
  366. }
  367. public int MinFoo { get; set; } = 0;
  368. public int MaxFoo { get; set; } = 100;
  369. public static int CoerceFoo(AvaloniaObject instance, int value)
  370. {
  371. var o = (Control1)instance;
  372. return Math.Clamp(value, o.MinFoo, o.MaxFoo);
  373. }
  374. }
  375. }
  376. }