AvaloniaObjectTests_Direct.cs 23 KB

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