AvaloniaObjectTests_Direct.cs 19 KB

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