AvaloniaObjectTests_Direct.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  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.ComponentModel;
  6. using System.Reactive.Subjects;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using Avalonia.Data;
  10. using Avalonia.Logging;
  11. using Avalonia.Platform;
  12. using Avalonia.UnitTests;
  13. using Moq;
  14. using Xunit;
  15. namespace Avalonia.Base.UnitTests
  16. {
  17. public class AvaloniaObjectTests_Direct
  18. {
  19. [Fact]
  20. public void GetValue_Gets_Default_Value()
  21. {
  22. var target = new Class1();
  23. Assert.Equal("initial", target.GetValue(Class1.FooProperty));
  24. }
  25. [Fact]
  26. public void GetValue_Gets_Value_NonGeneric()
  27. {
  28. var target = new Class1();
  29. Assert.Equal("initial", target.GetValue((AvaloniaProperty)Class1.FooProperty));
  30. }
  31. [Fact]
  32. public void GetValue_On_Unregistered_Property_Throws_Exception()
  33. {
  34. var target = new Class2();
  35. Assert.Throws<ArgumentException>(() => target.GetValue(Class1.BarProperty));
  36. }
  37. [Fact]
  38. public void SetValue_Sets_Value()
  39. {
  40. var target = new Class1();
  41. target.SetValue(Class1.FooProperty, "newvalue");
  42. Assert.Equal("newvalue", target.Foo);
  43. }
  44. [Fact]
  45. public void SetValue_Sets_Value_NonGeneric()
  46. {
  47. var target = new Class1();
  48. target.SetValue((AvaloniaProperty)Class1.FooProperty, "newvalue");
  49. Assert.Equal("newvalue", target.Foo);
  50. }
  51. [Fact]
  52. public void SetValue_NonGeneric_Coerces_UnsetValue_To_Default_Value()
  53. {
  54. var target = new Class1();
  55. target.SetValue((AvaloniaProperty)Class1.BazProperty, AvaloniaProperty.UnsetValue);
  56. Assert.Equal(-1, target.Baz);
  57. }
  58. [Fact]
  59. public void SetValue_Raises_PropertyChanged()
  60. {
  61. var target = new Class1();
  62. bool raised = false;
  63. target.PropertyChanged += (s, e) =>
  64. raised = e.Property == Class1.FooProperty &&
  65. (string)e.OldValue == "initial" &&
  66. (string)e.NewValue == "newvalue" &&
  67. e.Priority == BindingPriority.LocalValue;
  68. target.SetValue(Class1.FooProperty, "newvalue");
  69. Assert.True(raised);
  70. }
  71. [Fact]
  72. public void SetValue_Raises_Changed()
  73. {
  74. var target = new Class1();
  75. bool raised = false;
  76. Class1.FooProperty.Changed.Subscribe(e =>
  77. raised = e.Property == Class1.FooProperty &&
  78. (string)e.OldValue == "initial" &&
  79. (string)e.NewValue == "newvalue" &&
  80. e.Priority == BindingPriority.LocalValue);
  81. target.SetValue(Class1.FooProperty, "newvalue");
  82. Assert.True(raised);
  83. }
  84. [Fact]
  85. public void Setting_Object_Property_To_UnsetValue_Reverts_To_Default_Value()
  86. {
  87. Class1 target = new Class1();
  88. target.SetValue(Class1.FrankProperty, "newvalue");
  89. target.SetValue(Class1.FrankProperty, AvaloniaProperty.UnsetValue);
  90. Assert.Equal("Kups", target.GetValue(Class1.FrankProperty));
  91. }
  92. [Fact]
  93. public void Setting_Object_Property_To_DoNothing_Does_Nothing()
  94. {
  95. Class1 target = new Class1();
  96. target.SetValue(Class1.FrankProperty, "newvalue");
  97. target.SetValue(Class1.FrankProperty, BindingOperations.DoNothing);
  98. Assert.Equal("newvalue", target.GetValue(Class1.FrankProperty));
  99. }
  100. [Fact]
  101. public void Bind_Raises_PropertyChanged()
  102. {
  103. var target = new Class1();
  104. var source = new Subject<BindingValue<string>>();
  105. bool raised = false;
  106. target.PropertyChanged += (s, e) =>
  107. raised = e.Property == Class1.FooProperty &&
  108. (string)e.OldValue == "initial" &&
  109. (string)e.NewValue == "newvalue" &&
  110. e.Priority == BindingPriority.LocalValue;
  111. target.Bind(Class1.FooProperty, source);
  112. source.OnNext("newvalue");
  113. Assert.True(raised);
  114. }
  115. [Fact]
  116. public void PropertyChanged_Not_Raised_When_Value_Unchanged()
  117. {
  118. var target = new Class1();
  119. var source = new Subject<BindingValue<string>>();
  120. var raised = 0;
  121. target.PropertyChanged += (s, e) => ++raised;
  122. target.Bind(Class1.FooProperty, source);
  123. source.OnNext("newvalue");
  124. source.OnNext("newvalue");
  125. Assert.Equal(1, raised);
  126. }
  127. [Fact]
  128. public void SetValue_On_Unregistered_Property_Throws_Exception()
  129. {
  130. var target = new Class2();
  131. Assert.Throws<ArgumentException>(() => target.SetValue(Class1.BarProperty, "value"));
  132. }
  133. [Fact]
  134. public void ClearValue_Restores_Default_value()
  135. {
  136. var target = new Class1();
  137. Assert.Equal("initial", target.GetValue(Class1.FooProperty));
  138. }
  139. [Fact]
  140. public void ClearValue_Raises_PropertyChanged()
  141. {
  142. Class1 target = new Class1();
  143. var raised = 0;
  144. target.SetValue(Class1.FooProperty, "newvalue");
  145. target.PropertyChanged += (s, e) =>
  146. {
  147. Assert.Same(target, s);
  148. Assert.Equal(Class1.FooProperty, e.Property);
  149. Assert.Equal("newvalue", (string)e.OldValue);
  150. Assert.Equal("unset", (string)e.NewValue);
  151. ++raised;
  152. };
  153. target.ClearValue(Class1.FooProperty);
  154. Assert.Equal(1, raised);
  155. }
  156. [Fact]
  157. public void GetObservable_Returns_Values()
  158. {
  159. var target = new Class1();
  160. List<string> values = new List<string>();
  161. target.GetObservable(Class1.FooProperty).Subscribe(x => values.Add(x));
  162. target.Foo = "newvalue";
  163. Assert.Equal(new[] { "initial", "newvalue" }, values);
  164. }
  165. [Fact]
  166. public void Bind_Binds_Property_Value()
  167. {
  168. var target = new Class1();
  169. var source = new Subject<string>();
  170. var sub = target.Bind(Class1.FooProperty, source);
  171. Assert.Equal("initial", target.Foo);
  172. source.OnNext("first");
  173. Assert.Equal("first", target.Foo);
  174. source.OnNext("second");
  175. Assert.Equal("second", target.Foo);
  176. sub.Dispose();
  177. source.OnNext("third");
  178. Assert.Equal("second", target.Foo);
  179. }
  180. [Fact]
  181. public void Bind_Binds_Property_Value_NonGeneric()
  182. {
  183. var target = new Class1();
  184. var source = new Subject<string>();
  185. var sub = target.Bind((AvaloniaProperty)Class1.FooProperty, source);
  186. Assert.Equal("initial", target.Foo);
  187. source.OnNext("first");
  188. Assert.Equal("first", target.Foo);
  189. source.OnNext("second");
  190. Assert.Equal("second", target.Foo);
  191. sub.Dispose();
  192. source.OnNext("third");
  193. Assert.Equal("second", target.Foo);
  194. }
  195. [Fact]
  196. public void Bind_NonGeneric_Accepts_UnsetValue()
  197. {
  198. var target = new Class1();
  199. var source = new Subject<object>();
  200. var sub = target.Bind((AvaloniaProperty)Class1.BazProperty, source);
  201. Assert.Equal(5, target.Baz);
  202. source.OnNext(6);
  203. Assert.Equal(6, target.Baz);
  204. source.OnNext(AvaloniaProperty.UnsetValue);
  205. Assert.Equal(-1, target.Baz);
  206. }
  207. [Fact]
  208. public void Bind_Handles_Wrong_Type()
  209. {
  210. var target = new Class1();
  211. var source = new Subject<object>();
  212. var sub = target.Bind(Class1.FooProperty, source);
  213. source.OnNext(45);
  214. Assert.Equal("unset", target.Foo);
  215. }
  216. [Fact]
  217. public void Bind_Handles_Wrong_Value_Type()
  218. {
  219. var target = new Class1();
  220. var source = new Subject<object>();
  221. var sub = target.Bind(Class1.BazProperty, source);
  222. source.OnNext("foo");
  223. Assert.Equal(-1, target.Baz);
  224. }
  225. [Fact]
  226. public void ReadOnly_Property_Cannot_Be_Set()
  227. {
  228. var target = new Class1();
  229. Assert.Throws<ArgumentException>(() =>
  230. target.SetValue(Class1.BarProperty, "newvalue"));
  231. }
  232. [Fact]
  233. public void ReadOnly_Property_Cannot_Be_Set_NonGeneric()
  234. {
  235. var target = new Class1();
  236. Assert.Throws<ArgumentException>(() =>
  237. target.SetValue((AvaloniaProperty)Class1.BarProperty, "newvalue"));
  238. }
  239. [Fact]
  240. public void ReadOnly_Property_Cannot_Be_Bound()
  241. {
  242. var target = new Class1();
  243. var source = new Subject<string>();
  244. Assert.Throws<ArgumentException>(() =>
  245. target.Bind(Class1.BarProperty, source));
  246. }
  247. [Fact]
  248. public void ReadOnly_Property_Cannot_Be_Bound_NonGeneric()
  249. {
  250. var target = new Class1();
  251. var source = new Subject<string>();
  252. Assert.Throws<ArgumentException>(() =>
  253. target.Bind(Class1.BarProperty, source));
  254. }
  255. [Fact]
  256. public void GetValue_Gets_Value_On_AddOwnered_Property()
  257. {
  258. var target = new Class2();
  259. Assert.Equal("initial2", target.GetValue(Class2.FooProperty));
  260. }
  261. [Fact]
  262. public void GetValue_Gets_Value_On_AddOwnered_Property_Using_Original()
  263. {
  264. var target = new Class2();
  265. Assert.Equal("initial2", target.GetValue(Class1.FooProperty));
  266. }
  267. [Fact]
  268. public void GetValue_Gets_Value_On_AddOwnered_Property_Using_Original_NonGeneric()
  269. {
  270. var target = new Class2();
  271. Assert.Equal("initial2", target.GetValue((AvaloniaProperty)Class1.FooProperty));
  272. }
  273. [Fact]
  274. public void SetValue_Sets_Value_On_AddOwnered_Property_Using_Original()
  275. {
  276. var target = new Class2();
  277. target.SetValue(Class1.FooProperty, "newvalue");
  278. Assert.Equal("newvalue", target.Foo);
  279. }
  280. [Fact]
  281. public void SetValue_Sets_Value_On_AddOwnered_Property_Using_Original_NonGeneric()
  282. {
  283. var target = new Class2();
  284. target.SetValue((AvaloniaProperty)Class1.FooProperty, "newvalue");
  285. Assert.Equal("newvalue", target.Foo);
  286. }
  287. [Fact]
  288. public void UnsetValue_Is_Used_On_AddOwnered_Property()
  289. {
  290. var target = new Class2();
  291. target.SetValue((AvaloniaProperty)Class1.FooProperty, AvaloniaProperty.UnsetValue);
  292. Assert.Equal("unset", target.Foo);
  293. }
  294. [Fact]
  295. public void Bind_Binds_AddOwnered_Property_Value()
  296. {
  297. var target = new Class2();
  298. var source = new Subject<string>();
  299. var sub = target.Bind(Class1.FooProperty, source);
  300. Assert.Equal("initial2", target.Foo);
  301. source.OnNext("first");
  302. Assert.Equal("first", target.Foo);
  303. source.OnNext("second");
  304. Assert.Equal("second", target.Foo);
  305. sub.Dispose();
  306. source.OnNext("third");
  307. Assert.Equal("second", target.Foo);
  308. }
  309. [Fact]
  310. public void Bind_Binds_AddOwnered_Property_Value_NonGeneric()
  311. {
  312. var target = new Class2();
  313. var source = new Subject<string>();
  314. var sub = target.Bind((AvaloniaProperty)Class1.FooProperty, source);
  315. Assert.Equal("initial2", target.Foo);
  316. source.OnNext("first");
  317. Assert.Equal("first", target.Foo);
  318. source.OnNext("second");
  319. Assert.Equal("second", target.Foo);
  320. sub.Dispose();
  321. source.OnNext("third");
  322. Assert.Equal("second", target.Foo);
  323. }
  324. [Fact]
  325. public void Binding_Error_Reverts_To_Default_Value()
  326. {
  327. var target = new Class1();
  328. var source = new Subject<BindingValue<string>>();
  329. target.Bind(Class1.FooProperty, source);
  330. source.OnNext("initial");
  331. source.OnNext(BindingValue<string>.BindingError(new InvalidOperationException("Foo")));
  332. Assert.Equal("unset", target.GetValue(Class1.FooProperty));
  333. }
  334. [Fact]
  335. public void Binding_Error_With_FallbackValue_Causes_Target_Update()
  336. {
  337. var target = new Class1();
  338. var source = new Subject<BindingValue<string>>();
  339. target.Bind(Class1.FooProperty, source);
  340. source.OnNext("initial");
  341. source.OnNext(BindingValue<string>.BindingError(new InvalidOperationException("Foo"), "bar"));
  342. Assert.Equal("bar", target.GetValue(Class1.FooProperty));
  343. }
  344. [Fact]
  345. public void DataValidationError_Does_Not_Cause_Target_Update()
  346. {
  347. var target = new Class1();
  348. var source = new Subject<BindingValue<string>>();
  349. target.Bind(Class1.FooProperty, source);
  350. source.OnNext("initial");
  351. source.OnNext(BindingValue<string>.DataValidationError(new InvalidOperationException("Foo")));
  352. Assert.Equal("initial", target.GetValue(Class1.FooProperty));
  353. }
  354. [Fact]
  355. public void DataValidationError_With_FallbackValue_Causes_Target_Update()
  356. {
  357. var target = new Class1();
  358. var source = new Subject<BindingValue<string>>();
  359. target.Bind(Class1.FooProperty, source);
  360. source.OnNext("initial");
  361. source.OnNext(BindingValue<string>.DataValidationError(new InvalidOperationException("Foo"), "bar"));
  362. Assert.Equal("bar", target.GetValue(Class1.FooProperty));
  363. }
  364. [Fact]
  365. public void BindingError_With_FallbackValue_Causes_Target_Update()
  366. {
  367. var target = new Class1();
  368. var source = new Subject<BindingValue<string>>();
  369. target.Bind(Class1.FooProperty, source);
  370. source.OnNext("initial");
  371. source.OnNext(BindingValue<string>.BindingError(new InvalidOperationException("Foo"), "fallback"));
  372. Assert.Equal("fallback", target.GetValue(Class1.FooProperty));
  373. }
  374. [Fact]
  375. public void Binding_To_Direct_Property_Logs_BindingError()
  376. {
  377. var target = new Class1();
  378. var source = new Subject<BindingValue<string>>();
  379. var called = false;
  380. LogCallback checkLogMessage = (level, area, src, mt, pv) =>
  381. {
  382. if (level == LogEventLevel.Warning &&
  383. area == LogArea.Binding &&
  384. mt == "Error in binding to {Target}.{Property}: {Message}" &&
  385. pv.Length == 3 &&
  386. pv[0] is Class1 &&
  387. object.ReferenceEquals(pv[1], Class1.FooProperty) &&
  388. (string)pv[2] == "Binding Error Message")
  389. {
  390. called = true;
  391. }
  392. };
  393. using (TestLogSink.Start(checkLogMessage))
  394. {
  395. target.Bind(Class1.FooProperty, source);
  396. source.OnNext("baz");
  397. source.OnNext(BindingValue<string>.BindingError(new InvalidOperationException("Binding Error Message")));
  398. }
  399. Assert.True(called);
  400. }
  401. [Fact]
  402. public async Task Bind_Executes_On_UIThread()
  403. {
  404. var target = new Class1();
  405. var source = new Subject<object>();
  406. var currentThreadId = Thread.CurrentThread.ManagedThreadId;
  407. var threadingInterfaceMock = new Mock<IPlatformThreadingInterface>();
  408. threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread)
  409. .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId);
  410. var services = new TestServices(
  411. threadingInterface: threadingInterfaceMock.Object);
  412. using (UnitTestApplication.Start(services))
  413. {
  414. target.Bind(Class1.FooProperty, source);
  415. await Task.Run(() => source.OnNext("foobar"));
  416. }
  417. }
  418. [Fact]
  419. public void AddOwner_Should_Inherit_DefaultBindingMode()
  420. {
  421. var foo = new DirectProperty<Class1, string>(
  422. "foo",
  423. o => "foo",
  424. null,
  425. new DirectPropertyMetadata<string>(defaultBindingMode: BindingMode.TwoWay),
  426. false);
  427. var bar = foo.AddOwner<Class2>(o => "bar");
  428. Assert.Equal(BindingMode.TwoWay, bar.GetMetadata<Class1>().DefaultBindingMode);
  429. Assert.Equal(BindingMode.TwoWay, bar.GetMetadata<Class2>().DefaultBindingMode);
  430. }
  431. [Fact]
  432. public void AddOwner_Can_Override_DefaultBindingMode()
  433. {
  434. var foo = new DirectProperty<Class1, string>(
  435. "foo",
  436. o => "foo",
  437. null,
  438. new DirectPropertyMetadata<string>(defaultBindingMode: BindingMode.TwoWay),
  439. false);
  440. var bar = foo.AddOwner<Class2>(o => "bar", defaultBindingMode: BindingMode.OneWayToSource);
  441. Assert.Equal(BindingMode.TwoWay, bar.GetMetadata<Class1>().DefaultBindingMode);
  442. Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata<Class2>().DefaultBindingMode);
  443. }
  444. [Fact]
  445. public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
  446. {
  447. var viewModel = new TestStackOverflowViewModel()
  448. {
  449. Value = 50
  450. };
  451. var target = new Class1();
  452. target.Bind(Class1.DoubleValueProperty, new Binding("Value")
  453. {
  454. Mode = BindingMode.TwoWay,
  455. Source = viewModel
  456. });
  457. var child = new Class1();
  458. child[!!Class1.DoubleValueProperty] = target[!!Class1.DoubleValueProperty];
  459. Assert.Equal(1, viewModel.SetterInvokedCount);
  460. // Issues #855 and #824 were causing a StackOverflowException at this point.
  461. target.DoubleValue = 51.001;
  462. Assert.Equal(2, viewModel.SetterInvokedCount);
  463. double expected = 51;
  464. Assert.Equal(expected, viewModel.Value);
  465. Assert.Equal(expected, target.DoubleValue);
  466. Assert.Equal(expected, child.DoubleValue);
  467. }
  468. private class Class1 : AvaloniaObject
  469. {
  470. public static readonly DirectProperty<Class1, string> FooProperty =
  471. AvaloniaProperty.RegisterDirect<Class1, string>(
  472. nameof(Foo),
  473. o => o.Foo,
  474. (o, v) => o.Foo = v,
  475. unsetValue: "unset");
  476. public static readonly DirectProperty<Class1, string> BarProperty =
  477. AvaloniaProperty.RegisterDirect<Class1, string>(nameof(Bar), o => o.Bar);
  478. public static readonly DirectProperty<Class1, int> BazProperty =
  479. AvaloniaProperty.RegisterDirect<Class1, int>(
  480. nameof(Baz),
  481. o => o.Baz,
  482. (o, v) => o.Baz = v,
  483. unsetValue: -1);
  484. public static readonly DirectProperty<Class1, double> DoubleValueProperty =
  485. AvaloniaProperty.RegisterDirect<Class1, double>(
  486. nameof(DoubleValue),
  487. o => o.DoubleValue,
  488. (o, v) => o.DoubleValue = v);
  489. public static readonly DirectProperty<Class1, object> FrankProperty =
  490. AvaloniaProperty.RegisterDirect<Class1, object>(
  491. nameof(Frank),
  492. o => o.Frank,
  493. (o, v) => o.Frank = v,
  494. unsetValue: "Kups");
  495. private string _foo = "initial";
  496. private readonly string _bar = "bar";
  497. private int _baz = 5;
  498. private double _doubleValue;
  499. private object _frank;
  500. public string Foo
  501. {
  502. get { return _foo; }
  503. set { SetAndRaise(FooProperty, ref _foo, value); }
  504. }
  505. public string Bar
  506. {
  507. get { return _bar; }
  508. }
  509. public int Baz
  510. {
  511. get { return _baz; }
  512. set { SetAndRaise(BazProperty, ref _baz, value); }
  513. }
  514. public double DoubleValue
  515. {
  516. get { return _doubleValue; }
  517. set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); }
  518. }
  519. public object Frank
  520. {
  521. get { return _frank; }
  522. set { SetAndRaise(FrankProperty, ref _frank, value); }
  523. }
  524. }
  525. private class Class2 : AvaloniaObject
  526. {
  527. public static readonly DirectProperty<Class2, string> FooProperty =
  528. Class1.FooProperty.AddOwner<Class2>(o => o.Foo, (o, v) => o.Foo = v);
  529. private string _foo = "initial2";
  530. static Class2()
  531. {
  532. }
  533. public string Foo
  534. {
  535. get { return _foo; }
  536. set { SetAndRaise(FooProperty, ref _foo, value); }
  537. }
  538. }
  539. private class TestStackOverflowViewModel : INotifyPropertyChanged
  540. {
  541. public int SetterInvokedCount { get; private set; }
  542. public const int MaxInvokedCount = 1000;
  543. private double _value;
  544. public event PropertyChangedEventHandler PropertyChanged;
  545. public double Value
  546. {
  547. get { return _value; }
  548. set
  549. {
  550. if (_value != value)
  551. {
  552. SetterInvokedCount++;
  553. if (SetterInvokedCount < MaxInvokedCount)
  554. {
  555. _value = (int)value;
  556. if (_value > 75) _value = 75;
  557. if (_value < 25) _value = 25;
  558. }
  559. else
  560. {
  561. _value = value;
  562. }
  563. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
  564. }
  565. }
  566. }
  567. }
  568. }
  569. }