AvaloniaObjectTests_Direct.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  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.Threading;
  11. using Avalonia.UnitTests;
  12. using Moq;
  13. using Nito.AsyncEx;
  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. e.OldValue.GetValueOrDefault() == "initial" &&
  79. e.NewValue.GetValueOrDefault() == "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(BindingPriority.LocalValue, e.Priority);
  149. Assert.Equal(Class1.FooProperty, e.Property);
  150. Assert.Equal("newvalue", (string)e.OldValue);
  151. Assert.Equal("unset", (string)e.NewValue);
  152. ++raised;
  153. };
  154. target.ClearValue(Class1.FooProperty);
  155. Assert.Equal(1, raised);
  156. }
  157. [Fact]
  158. public void GetObservable_Returns_Values()
  159. {
  160. var target = new Class1();
  161. List<string> values = new List<string>();
  162. target.GetObservable(Class1.FooProperty).Subscribe(x => values.Add(x));
  163. target.Foo = "newvalue";
  164. Assert.Equal(new[] { "initial", "newvalue" }, values);
  165. }
  166. [Fact]
  167. public void Bind_Binds_Property_Value()
  168. {
  169. var target = new Class1();
  170. var source = new Subject<string>();
  171. var sub = target.Bind(Class1.FooProperty, source);
  172. Assert.Equal("initial", target.Foo);
  173. source.OnNext("first");
  174. Assert.Equal("first", target.Foo);
  175. source.OnNext("second");
  176. Assert.Equal("second", target.Foo);
  177. sub.Dispose();
  178. source.OnNext("third");
  179. Assert.Equal("second", target.Foo);
  180. }
  181. [Fact]
  182. public void Bind_Binds_Property_Value_NonGeneric()
  183. {
  184. var target = new Class1();
  185. var source = new Subject<string>();
  186. var sub = target.Bind((AvaloniaProperty)Class1.FooProperty, source);
  187. Assert.Equal("initial", target.Foo);
  188. source.OnNext("first");
  189. Assert.Equal("first", target.Foo);
  190. source.OnNext("second");
  191. Assert.Equal("second", target.Foo);
  192. sub.Dispose();
  193. source.OnNext("third");
  194. Assert.Equal("second", target.Foo);
  195. }
  196. [Fact]
  197. public void Bind_NonGeneric_Accepts_UnsetValue()
  198. {
  199. var target = new Class1();
  200. var source = new Subject<object>();
  201. var sub = target.Bind((AvaloniaProperty)Class1.BazProperty, source);
  202. Assert.Equal(5, target.Baz);
  203. source.OnNext(6);
  204. Assert.Equal(6, target.Baz);
  205. source.OnNext(AvaloniaProperty.UnsetValue);
  206. Assert.Equal(-1, target.Baz);
  207. }
  208. [Fact]
  209. public void Bind_Handles_Wrong_Type()
  210. {
  211. var target = new Class1();
  212. var source = new Subject<object>();
  213. var sub = target.Bind(Class1.FooProperty, source);
  214. source.OnNext(45);
  215. Assert.Equal("unset", target.Foo);
  216. }
  217. [Fact]
  218. public void Bind_Handles_Wrong_Value_Type()
  219. {
  220. var target = new Class1();
  221. var source = new Subject<object>();
  222. var sub = target.Bind(Class1.BazProperty, source);
  223. source.OnNext("foo");
  224. Assert.Equal(-1, target.Baz);
  225. }
  226. [Fact]
  227. public void ReadOnly_Property_Cannot_Be_Set()
  228. {
  229. var target = new Class1();
  230. Assert.Throws<ArgumentException>(() =>
  231. target.SetValue(Class1.BarProperty, "newvalue"));
  232. }
  233. [Fact]
  234. public void ReadOnly_Property_Cannot_Be_Set_NonGeneric()
  235. {
  236. var target = new Class1();
  237. Assert.Throws<ArgumentException>(() =>
  238. target.SetValue((AvaloniaProperty)Class1.BarProperty, "newvalue"));
  239. }
  240. [Fact]
  241. public void ReadOnly_Property_Cannot_Be_Bound()
  242. {
  243. var target = new Class1();
  244. var source = new Subject<string>();
  245. Assert.Throws<ArgumentException>(() =>
  246. target.Bind(Class1.BarProperty, source));
  247. }
  248. [Fact]
  249. public void ReadOnly_Property_Cannot_Be_Bound_NonGeneric()
  250. {
  251. var target = new Class1();
  252. var source = new Subject<string>();
  253. Assert.Throws<ArgumentException>(() =>
  254. target.Bind(Class1.BarProperty, source));
  255. }
  256. [Fact]
  257. public void GetValue_Gets_Value_On_AddOwnered_Property()
  258. {
  259. var target = new Class2();
  260. Assert.Equal("initial2", target.GetValue(Class2.FooProperty));
  261. }
  262. [Fact]
  263. public void GetValue_Gets_Value_On_AddOwnered_Property_Using_Original()
  264. {
  265. var target = new Class2();
  266. Assert.Equal("initial2", target.GetValue(Class1.FooProperty));
  267. }
  268. [Fact]
  269. public void GetValue_Gets_Value_On_AddOwnered_Property_Using_Original_NonGeneric()
  270. {
  271. var target = new Class2();
  272. Assert.Equal("initial2", target.GetValue((AvaloniaProperty)Class1.FooProperty));
  273. }
  274. [Fact]
  275. public void SetValue_Sets_Value_On_AddOwnered_Property_Using_Original()
  276. {
  277. var target = new Class2();
  278. target.SetValue(Class1.FooProperty, "newvalue");
  279. Assert.Equal("newvalue", target.Foo);
  280. }
  281. [Fact]
  282. public void SetValue_Sets_Value_On_AddOwnered_Property_Using_Original_NonGeneric()
  283. {
  284. var target = new Class2();
  285. target.SetValue((AvaloniaProperty)Class1.FooProperty, "newvalue");
  286. Assert.Equal("newvalue", target.Foo);
  287. }
  288. [Fact]
  289. public void UnsetValue_Is_Used_On_AddOwnered_Property()
  290. {
  291. var target = new Class2();
  292. target.SetValue((AvaloniaProperty)Class1.FooProperty, AvaloniaProperty.UnsetValue);
  293. Assert.Equal("unset", target.Foo);
  294. }
  295. [Fact]
  296. public void Bind_Binds_AddOwnered_Property_Value()
  297. {
  298. var target = new Class2();
  299. var source = new Subject<string>();
  300. var sub = target.Bind(Class1.FooProperty, source);
  301. Assert.Equal("initial2", target.Foo);
  302. source.OnNext("first");
  303. Assert.Equal("first", target.Foo);
  304. source.OnNext("second");
  305. Assert.Equal("second", target.Foo);
  306. sub.Dispose();
  307. source.OnNext("third");
  308. Assert.Equal("second", target.Foo);
  309. }
  310. [Fact]
  311. public void Bind_Binds_AddOwnered_Property_Value_NonGeneric()
  312. {
  313. var target = new Class2();
  314. var source = new Subject<string>();
  315. var sub = target.Bind((AvaloniaProperty)Class1.FooProperty, source);
  316. Assert.Equal("initial2", target.Foo);
  317. source.OnNext("first");
  318. Assert.Equal("first", target.Foo);
  319. source.OnNext("second");
  320. Assert.Equal("second", target.Foo);
  321. sub.Dispose();
  322. source.OnNext("third");
  323. Assert.Equal("second", target.Foo);
  324. }
  325. [Fact]
  326. public void Binding_Error_Reverts_To_Default_Value()
  327. {
  328. var target = new Class1();
  329. var source = new Subject<BindingValue<string>>();
  330. target.Bind(Class1.FooProperty, source);
  331. source.OnNext("initial");
  332. source.OnNext(BindingValue<string>.BindingError(new InvalidOperationException("Foo")));
  333. Assert.Equal("unset", target.GetValue(Class1.FooProperty));
  334. }
  335. [Fact]
  336. public void Binding_Error_With_FallbackValue_Causes_Target_Update()
  337. {
  338. var target = new Class1();
  339. var source = new Subject<BindingValue<string>>();
  340. target.Bind(Class1.FooProperty, source);
  341. source.OnNext("initial");
  342. source.OnNext(BindingValue<string>.BindingError(new InvalidOperationException("Foo"), "bar"));
  343. Assert.Equal("bar", target.GetValue(Class1.FooProperty));
  344. }
  345. [Fact]
  346. public void DataValidationError_Does_Not_Cause_Target_Update()
  347. {
  348. var target = new Class1();
  349. var source = new Subject<BindingValue<string>>();
  350. target.Bind(Class1.FooProperty, source);
  351. source.OnNext("initial");
  352. source.OnNext(BindingValue<string>.DataValidationError(new InvalidOperationException("Foo")));
  353. Assert.Equal("initial", target.GetValue(Class1.FooProperty));
  354. }
  355. [Fact]
  356. public void DataValidationError_With_FallbackValue_Causes_Target_Update()
  357. {
  358. var target = new Class1();
  359. var source = new Subject<BindingValue<string>>();
  360. target.Bind(Class1.FooProperty, source);
  361. source.OnNext("initial");
  362. source.OnNext(BindingValue<string>.DataValidationError(new InvalidOperationException("Foo"), "bar"));
  363. Assert.Equal("bar", target.GetValue(Class1.FooProperty));
  364. }
  365. [Fact]
  366. public void BindingError_With_FallbackValue_Causes_Target_Update()
  367. {
  368. var target = new Class1();
  369. var source = new Subject<BindingValue<string>>();
  370. target.Bind(Class1.FooProperty, source);
  371. source.OnNext("initial");
  372. source.OnNext(BindingValue<string>.BindingError(new InvalidOperationException("Foo"), "fallback"));
  373. Assert.Equal("fallback", target.GetValue(Class1.FooProperty));
  374. }
  375. [Fact]
  376. public void Bind_Executes_On_UIThread()
  377. {
  378. AsyncContext.Run(async () =>
  379. {
  380. var target = new Class1();
  381. var source = new Subject<object>();
  382. var currentThreadId = Thread.CurrentThread.ManagedThreadId;
  383. var raised = 0;
  384. var dispatcherMock = new Mock<IDispatcherImpl>();
  385. dispatcherMock.SetupGet(mock => mock.CurrentThreadIsLoopThread)
  386. .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId);
  387. var services = new TestServices(
  388. dispatcherImpl: dispatcherMock.Object);
  389. target.PropertyChanged += (s, e) =>
  390. {
  391. Assert.Equal(currentThreadId, Thread.CurrentThread.ManagedThreadId);
  392. ++raised;
  393. };
  394. using (UnitTestApplication.Start(services))
  395. {
  396. target.Bind(Class1.FooProperty, source);
  397. await Task.Run(() => source.OnNext("foobar"));
  398. Dispatcher.UIThread.RunJobs();
  399. Assert.Equal("foobar", target.Foo);
  400. Assert.Equal(1, raised);
  401. }
  402. });
  403. }
  404. [Fact]
  405. public void AddOwner_Should_Inherit_DefaultBindingMode()
  406. {
  407. var foo = new DirectProperty<Class1, string>(
  408. "foo",
  409. o => "foo",
  410. null,
  411. new DirectPropertyMetadata<string>(defaultBindingMode: BindingMode.TwoWay));
  412. var bar = foo.AddOwner<Class2>(o => "bar");
  413. Assert.Equal(BindingMode.TwoWay, bar.GetMetadata<Class1>().DefaultBindingMode);
  414. Assert.Equal(BindingMode.TwoWay, bar.GetMetadata<Class2>().DefaultBindingMode);
  415. }
  416. [Fact]
  417. public void AddOwner_Can_Override_DefaultBindingMode()
  418. {
  419. var foo = new DirectProperty<Class1, string>(
  420. "foo",
  421. o => "foo",
  422. null,
  423. new DirectPropertyMetadata<string>(defaultBindingMode: BindingMode.TwoWay));
  424. var bar = foo.AddOwner<Class2>(o => "bar", defaultBindingMode: BindingMode.OneWayToSource);
  425. Assert.Equal(BindingMode.TwoWay, bar.GetMetadata<Class1>().DefaultBindingMode);
  426. Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata<Class2>().DefaultBindingMode);
  427. }
  428. [Fact]
  429. public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
  430. {
  431. var viewModel = new TestStackOverflowViewModel()
  432. {
  433. Value = 50
  434. };
  435. var target = new Class1();
  436. target.Bind(Class1.DoubleValueProperty, new Binding("Value")
  437. {
  438. Mode = BindingMode.TwoWay,
  439. Source = viewModel
  440. });
  441. var child = new Class1();
  442. child[!!Class1.DoubleValueProperty] = target[!!Class1.DoubleValueProperty];
  443. Assert.Equal(1, viewModel.SetterInvokedCount);
  444. // Issues #855 and #824 were causing a StackOverflowException at this point.
  445. target.DoubleValue = 51.001;
  446. Assert.Equal(2, viewModel.SetterInvokedCount);
  447. double expected = 51;
  448. Assert.Equal(expected, viewModel.Value);
  449. Assert.Equal(expected, target.DoubleValue);
  450. Assert.Equal(expected, child.DoubleValue);
  451. }
  452. private class Class1 : AvaloniaObject
  453. {
  454. public static readonly DirectProperty<Class1, string> FooProperty =
  455. AvaloniaProperty.RegisterDirect<Class1, string>(
  456. nameof(Foo),
  457. o => o.Foo,
  458. (o, v) => o.Foo = v,
  459. unsetValue: "unset");
  460. public static readonly DirectProperty<Class1, string> BarProperty =
  461. AvaloniaProperty.RegisterDirect<Class1, string>(nameof(Bar), o => o.Bar);
  462. public static readonly DirectProperty<Class1, int> BazProperty =
  463. AvaloniaProperty.RegisterDirect<Class1, int>(
  464. nameof(Baz),
  465. o => o.Baz,
  466. (o, v) => o.Baz = v,
  467. unsetValue: -1);
  468. public static readonly DirectProperty<Class1, double> DoubleValueProperty =
  469. AvaloniaProperty.RegisterDirect<Class1, double>(
  470. nameof(DoubleValue),
  471. o => o.DoubleValue,
  472. (o, v) => o.DoubleValue = v);
  473. public static readonly DirectProperty<Class1, object> FrankProperty =
  474. AvaloniaProperty.RegisterDirect<Class1, object>(
  475. nameof(Frank),
  476. o => o.Frank,
  477. (o, v) => o.Frank = v,
  478. unsetValue: "Kups");
  479. private string _foo = "initial";
  480. private readonly string _bar = "bar";
  481. private int _baz = 5;
  482. private double _doubleValue;
  483. private object _frank;
  484. public string Foo
  485. {
  486. get { return _foo; }
  487. set { SetAndRaise(FooProperty, ref _foo, value); }
  488. }
  489. public string Bar
  490. {
  491. get { return _bar; }
  492. }
  493. public int Baz
  494. {
  495. get { return _baz; }
  496. set { SetAndRaise(BazProperty, ref _baz, value); }
  497. }
  498. public double DoubleValue
  499. {
  500. get { return _doubleValue; }
  501. set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); }
  502. }
  503. public object Frank
  504. {
  505. get { return _frank; }
  506. set { SetAndRaise(FrankProperty, ref _frank, value); }
  507. }
  508. }
  509. private class Class2 : AvaloniaObject
  510. {
  511. public static readonly DirectProperty<Class2, string> FooProperty =
  512. Class1.FooProperty.AddOwner<Class2>(o => o.Foo, (o, v) => o.Foo = v);
  513. private string _foo = "initial2";
  514. static Class2()
  515. {
  516. }
  517. public string Foo
  518. {
  519. get { return _foo; }
  520. set { SetAndRaise(FooProperty, ref _foo, value); }
  521. }
  522. }
  523. private class TestStackOverflowViewModel : INotifyPropertyChanged
  524. {
  525. public int SetterInvokedCount { get; private set; }
  526. public const int MaxInvokedCount = 1000;
  527. private double _value;
  528. public event PropertyChangedEventHandler PropertyChanged;
  529. public double Value
  530. {
  531. get { return _value; }
  532. set
  533. {
  534. if (_value != value)
  535. {
  536. SetterInvokedCount++;
  537. if (SetterInvokedCount < MaxInvokedCount)
  538. {
  539. _value = (int)value;
  540. if (_value > 75) _value = 75;
  541. if (_value < 25) _value = 25;
  542. }
  543. else
  544. {
  545. _value = value;
  546. }
  547. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
  548. }
  549. }
  550. }
  551. }
  552. }
  553. }