AvaloniaObjectTests_Direct.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  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.Reactive.Subjects;
  6. using Avalonia.Data;
  7. using Avalonia.Logging;
  8. using Avalonia.UnitTests;
  9. using Xunit;
  10. namespace Avalonia.Base.UnitTests
  11. {
  12. public class AvaloniaObjectTests_Direct
  13. {
  14. [Fact]
  15. public void GetValue_Gets_Value()
  16. {
  17. var target = new Class1();
  18. Assert.Equal("initial", target.GetValue(Class1.FooProperty));
  19. }
  20. [Fact]
  21. public void GetValue_Gets_Value_NonGeneric()
  22. {
  23. var target = new Class1();
  24. Assert.Equal("initial", target.GetValue((AvaloniaProperty)Class1.FooProperty));
  25. }
  26. [Fact]
  27. public void GetValue_On_Unregistered_Property_Throws_Exception()
  28. {
  29. var target = new Class2();
  30. Assert.Throws<ArgumentException>(() => target.GetValue(Class1.BarProperty));
  31. }
  32. [Fact]
  33. public void SetValue_Sets_Value()
  34. {
  35. var target = new Class1();
  36. target.SetValue(Class1.FooProperty, "newvalue");
  37. Assert.Equal("newvalue", target.Foo);
  38. }
  39. [Fact]
  40. public void SetValue_Sets_Value_NonGeneric()
  41. {
  42. var target = new Class1();
  43. target.SetValue((AvaloniaProperty)Class1.FooProperty, "newvalue");
  44. Assert.Equal("newvalue", target.Foo);
  45. }
  46. [Fact]
  47. public void SetValue_NonGeneric_Coerces_UnsetValue_To_Default_Value()
  48. {
  49. var target = new Class1();
  50. target.SetValue((AvaloniaProperty)Class1.BazProperty, AvaloniaProperty.UnsetValue);
  51. Assert.Equal(-1, target.Baz);
  52. }
  53. [Fact]
  54. public void SetValue_Raises_PropertyChanged()
  55. {
  56. var target = new Class1();
  57. bool raised = false;
  58. target.PropertyChanged += (s, e) =>
  59. raised = e.Property == Class1.FooProperty &&
  60. (string)e.OldValue == "initial" &&
  61. (string)e.NewValue == "newvalue" &&
  62. e.Priority == BindingPriority.LocalValue;
  63. target.SetValue(Class1.FooProperty, "newvalue");
  64. Assert.True(raised);
  65. }
  66. [Fact]
  67. public void SetValue_Raises_Changed()
  68. {
  69. var target = new Class1();
  70. bool raised = false;
  71. Class1.FooProperty.Changed.Subscribe(e =>
  72. raised = e.Property == Class1.FooProperty &&
  73. (string)e.OldValue == "initial" &&
  74. (string)e.NewValue == "newvalue" &&
  75. e.Priority == BindingPriority.LocalValue);
  76. target.SetValue(Class1.FooProperty, "newvalue");
  77. Assert.True(raised);
  78. }
  79. [Fact]
  80. public void SetValue_On_Unregistered_Property_Throws_Exception()
  81. {
  82. var target = new Class2();
  83. Assert.Throws<ArgumentException>(() => target.SetValue(Class1.BarProperty, "value"));
  84. }
  85. [Fact]
  86. public void GetObservable_Returns_Values()
  87. {
  88. var target = new Class1();
  89. List<string> values = new List<string>();
  90. target.GetObservable(Class1.FooProperty).Subscribe(x => values.Add(x));
  91. target.Foo = "newvalue";
  92. Assert.Equal(new[] { "initial", "newvalue" }, values);
  93. }
  94. [Fact]
  95. public void Bind_Binds_Property_Value()
  96. {
  97. var target = new Class1();
  98. var source = new Subject<string>();
  99. var sub = target.Bind(Class1.FooProperty, source);
  100. Assert.Equal("initial", target.Foo);
  101. source.OnNext("first");
  102. Assert.Equal("first", target.Foo);
  103. source.OnNext("second");
  104. Assert.Equal("second", target.Foo);
  105. sub.Dispose();
  106. source.OnNext("third");
  107. Assert.Equal("second", target.Foo);
  108. }
  109. [Fact]
  110. public void Bind_Binds_Property_Value_NonGeneric()
  111. {
  112. var target = new Class1();
  113. var source = new Subject<string>();
  114. var sub = target.Bind((AvaloniaProperty)Class1.FooProperty, source);
  115. Assert.Equal("initial", target.Foo);
  116. source.OnNext("first");
  117. Assert.Equal("first", target.Foo);
  118. source.OnNext("second");
  119. Assert.Equal("second", target.Foo);
  120. sub.Dispose();
  121. source.OnNext("third");
  122. Assert.Equal("second", target.Foo);
  123. }
  124. [Fact]
  125. public void Bind_NonGeneric_Uses_UnsetValue()
  126. {
  127. var target = new Class1();
  128. var source = new Subject<object>();
  129. var sub = target.Bind((AvaloniaProperty)Class1.BazProperty, source);
  130. Assert.Equal(5, target.Baz);
  131. source.OnNext(6);
  132. Assert.Equal(6, target.Baz);
  133. source.OnNext(AvaloniaProperty.UnsetValue);
  134. Assert.Equal(-1, target.Baz);
  135. }
  136. [Fact]
  137. public void Bind_Handles_Wrong_Type()
  138. {
  139. var target = new Class1();
  140. var source = new Subject<object>();
  141. var sub = target.Bind(Class1.FooProperty, source);
  142. source.OnNext(45);
  143. Assert.Equal(null, target.Foo);
  144. }
  145. [Fact]
  146. public void Bind_Handles_Wrong_Value_Type()
  147. {
  148. var target = new Class1();
  149. var source = new Subject<object>();
  150. var sub = target.Bind(Class1.BazProperty, source);
  151. source.OnNext("foo");
  152. Assert.Equal(0, target.Baz);
  153. }
  154. [Fact]
  155. public void ReadOnly_Property_Cannot_Be_Set()
  156. {
  157. var target = new Class1();
  158. Assert.Throws<ArgumentException>(() =>
  159. target.SetValue(Class1.BarProperty, "newvalue"));
  160. }
  161. [Fact]
  162. public void ReadOnly_Property_Cannot_Be_Set_NonGeneric()
  163. {
  164. var target = new Class1();
  165. Assert.Throws<ArgumentException>(() =>
  166. target.SetValue((AvaloniaProperty)Class1.BarProperty, "newvalue"));
  167. }
  168. [Fact]
  169. public void ReadOnly_Property_Cannot_Be_Bound()
  170. {
  171. var target = new Class1();
  172. var source = new Subject<string>();
  173. Assert.Throws<ArgumentException>(() =>
  174. target.Bind(Class1.BarProperty, source));
  175. }
  176. [Fact]
  177. public void ReadOnly_Property_Cannot_Be_Bound_NonGeneric()
  178. {
  179. var target = new Class1();
  180. var source = new Subject<string>();
  181. Assert.Throws<ArgumentException>(() =>
  182. target.Bind(Class1.BarProperty, source));
  183. }
  184. [Fact]
  185. public void GetValue_Gets_Value_On_AddOwnered_Property()
  186. {
  187. var target = new Class2();
  188. Assert.Equal("initial2", target.GetValue(Class2.FooProperty));
  189. }
  190. [Fact]
  191. public void GetValue_Gets_Value_On_AddOwnered_Property_Using_Original()
  192. {
  193. var target = new Class2();
  194. Assert.Equal("initial2", target.GetValue(Class1.FooProperty));
  195. }
  196. [Fact]
  197. public void GetValue_Gets_Value_On_AddOwnered_Property_Using_Original_NonGeneric()
  198. {
  199. var target = new Class2();
  200. Assert.Equal("initial2", target.GetValue((AvaloniaProperty)Class1.FooProperty));
  201. }
  202. [Fact]
  203. public void SetValue_Sets_Value_On_AddOwnered_Property_Using_Original()
  204. {
  205. var target = new Class2();
  206. target.SetValue(Class1.FooProperty, "newvalue");
  207. Assert.Equal("newvalue", target.Foo);
  208. }
  209. [Fact]
  210. public void SetValue_Sets_Value_On_AddOwnered_Property_Using_Original_NonGeneric()
  211. {
  212. var target = new Class2();
  213. target.SetValue((AvaloniaProperty)Class1.FooProperty, "newvalue");
  214. Assert.Equal("newvalue", target.Foo);
  215. }
  216. [Fact]
  217. public void UnsetValue_Is_Used_On_AddOwnered_Property()
  218. {
  219. var target = new Class2();
  220. target.SetValue((AvaloniaProperty)Class1.FooProperty, AvaloniaProperty.UnsetValue);
  221. Assert.Equal("unset", target.Foo);
  222. }
  223. [Fact]
  224. public void Bind_Binds_AddOwnered_Property_Value()
  225. {
  226. var target = new Class2();
  227. var source = new Subject<string>();
  228. var sub = target.Bind(Class1.FooProperty, source);
  229. Assert.Equal("initial2", target.Foo);
  230. source.OnNext("first");
  231. Assert.Equal("first", target.Foo);
  232. source.OnNext("second");
  233. Assert.Equal("second", target.Foo);
  234. sub.Dispose();
  235. source.OnNext("third");
  236. Assert.Equal("second", target.Foo);
  237. }
  238. [Fact]
  239. public void Bind_Binds_AddOwnered_Property_Value_NonGeneric()
  240. {
  241. var target = new Class2();
  242. var source = new Subject<string>();
  243. var sub = target.Bind((AvaloniaProperty)Class1.FooProperty, source);
  244. Assert.Equal("initial2", target.Foo);
  245. source.OnNext("first");
  246. Assert.Equal("first", target.Foo);
  247. source.OnNext("second");
  248. Assert.Equal("second", target.Foo);
  249. sub.Dispose();
  250. source.OnNext("third");
  251. Assert.Equal("second", target.Foo);
  252. }
  253. [Fact]
  254. public void Binding_To_Direct_Property_Does_Not_Get_Collected()
  255. {
  256. var target = new Class2();
  257. Func<WeakReference> setupBinding = () =>
  258. {
  259. var source = new Subject<string>();
  260. var sub = target.Bind((AvaloniaProperty)Class1.FooProperty, source);
  261. source.OnNext("foo");
  262. return new WeakReference(source);
  263. };
  264. var weakSource = setupBinding();
  265. GC.Collect();
  266. Assert.Equal("foo", target.Foo);
  267. Assert.True(weakSource.IsAlive);
  268. }
  269. [Fact]
  270. public void Binding_To_Direct_Property_Gets_Collected_When_Completed()
  271. {
  272. var target = new Class2();
  273. var weakSource = SetupDirectBinding(target);
  274. Action completeSource = () =>
  275. {
  276. ((ISubject<string>)weakSource.Target).OnCompleted();
  277. };
  278. completeSource();
  279. GC.Collect();
  280. Assert.False(weakSource.IsAlive);
  281. }
  282. [Fact]
  283. public void Property_Notifies_Initialized()
  284. {
  285. Class1 target;
  286. bool raised = false;
  287. Class1.FooProperty.Initialized.Subscribe(e =>
  288. raised = e.Property == Class1.FooProperty &&
  289. e.OldValue == AvaloniaProperty.UnsetValue &&
  290. (string)e.NewValue == "initial" &&
  291. e.Priority == BindingPriority.Unset);
  292. target = new Class1();
  293. Assert.True(raised);
  294. }
  295. [Fact]
  296. public void BindingError_Does_Not_Cause_Target_Update()
  297. {
  298. var target = new Class1();
  299. var source = new Subject<object>();
  300. target.Bind(Class1.FooProperty, source);
  301. source.OnNext("initial");
  302. source.OnNext(new BindingError(new InvalidOperationException("Foo")));
  303. Assert.Equal("initial", target.GetValue(Class1.FooProperty));
  304. }
  305. [Fact]
  306. public void BindingError_With_FallbackValue_Causes_Target_Update()
  307. {
  308. var target = new Class1();
  309. var source = new Subject<object>();
  310. target.Bind(Class1.FooProperty, source);
  311. source.OnNext("initial");
  312. source.OnNext(new BindingError(new InvalidOperationException("Foo"), "fallback"));
  313. Assert.Equal("fallback", target.GetValue(Class1.FooProperty));
  314. }
  315. [Fact]
  316. public void Binding_To_Direct_Property_Logs_BindingError()
  317. {
  318. var target = new Class1();
  319. var source = new Subject<object>();
  320. var called = false;
  321. LogCallback checkLogMessage = (level, area, src, mt, pv) =>
  322. {
  323. if (level == LogEventLevel.Error &&
  324. area == LogArea.Binding &&
  325. mt == "Error binding to {Target}.{Property}: {Message}" &&
  326. pv.Length == 3 &&
  327. pv[0] is Class1 &&
  328. object.ReferenceEquals(pv[1], Class1.FooProperty) &&
  329. (string)pv[2] == "Binding Error Message")
  330. {
  331. called = true;
  332. }
  333. };
  334. using (TestLogSink.Start(checkLogMessage))
  335. {
  336. target.Bind(Class1.FooProperty, source);
  337. source.OnNext("baz");
  338. source.OnNext(new BindingError(new InvalidOperationException("Binding Error Message")));
  339. }
  340. Assert.True(called);
  341. }
  342. private WeakReference SetupDirectBinding(Class2 target)
  343. {
  344. var source = new Subject<string>();
  345. var sub = target.Bind((AvaloniaProperty)Class1.FooProperty, source);
  346. return new WeakReference(source);
  347. }
  348. private class Class1 : AvaloniaObject
  349. {
  350. public static readonly DirectProperty<Class1, string> FooProperty =
  351. AvaloniaProperty.RegisterDirect<Class1, string>(
  352. "Foo",
  353. o => o.Foo,
  354. (o, v) => o.Foo = v,
  355. unsetValue: "unset");
  356. public static readonly DirectProperty<Class1, string> BarProperty =
  357. AvaloniaProperty.RegisterDirect<Class1, string>("Bar", o => o.Bar);
  358. public static readonly DirectProperty<Class1, int> BazProperty =
  359. AvaloniaProperty.RegisterDirect<Class1, int>(
  360. "Bar",
  361. o => o.Baz,
  362. (o,v) => o.Baz = v,
  363. unsetValue: -1);
  364. private string _foo = "initial";
  365. private readonly string _bar = "bar";
  366. private int _baz = 5;
  367. public string Foo
  368. {
  369. get { return _foo; }
  370. set { SetAndRaise(FooProperty, ref _foo, value); }
  371. }
  372. public string Bar
  373. {
  374. get { return _bar; }
  375. }
  376. public int Baz
  377. {
  378. get { return _baz; }
  379. set { SetAndRaise(BazProperty, ref _baz, value); }
  380. }
  381. }
  382. private class Class2 : AvaloniaObject
  383. {
  384. public static readonly DirectProperty<Class2, string> FooProperty =
  385. Class1.FooProperty.AddOwner<Class2>(o => o.Foo, (o, v) => o.Foo = v);
  386. private string _foo = "initial2";
  387. static Class2()
  388. {
  389. }
  390. public string Foo
  391. {
  392. get { return _foo; }
  393. set { SetAndRaise(FooProperty, ref _foo, value); }
  394. }
  395. }
  396. }
  397. }