RangeBaseTests.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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.ComponentModel;
  5. using Avalonia.Controls.Primitives;
  6. using Avalonia.Controls.Templates;
  7. using Avalonia.Data;
  8. using Avalonia.Markup.Xaml.Data;
  9. using Avalonia.Styling;
  10. using Xunit;
  11. namespace Avalonia.Controls.UnitTests.Primitives
  12. {
  13. public class RangeBaseTests
  14. {
  15. [Fact]
  16. public void Maximum_Should_Be_Coerced_To_Minimum()
  17. {
  18. var target = new TestRange
  19. {
  20. Minimum = 100,
  21. Maximum = 50,
  22. };
  23. Assert.Equal(100, target.Minimum);
  24. Assert.Equal(100, target.Maximum);
  25. }
  26. [Fact]
  27. public void Value_Should_Be_Coerced_To_Range()
  28. {
  29. var target = new TestRange
  30. {
  31. Minimum = 0,
  32. Maximum = 50,
  33. Value = 100,
  34. };
  35. Assert.Equal(0, target.Minimum);
  36. Assert.Equal(50, target.Maximum);
  37. Assert.Equal(50, target.Value);
  38. }
  39. [Fact]
  40. public void Changing_Minimum_Should_Coerce_Value_And_Maximum()
  41. {
  42. var target = new TestRange
  43. {
  44. Minimum = 0,
  45. Maximum = 100,
  46. Value = 50,
  47. };
  48. target.Minimum = 200;
  49. Assert.Equal(200, target.Minimum);
  50. Assert.Equal(200, target.Maximum);
  51. Assert.Equal(200, target.Value);
  52. }
  53. [Fact]
  54. public void Changing_Maximum_Should_Coerce_Value()
  55. {
  56. var target = new TestRange
  57. {
  58. Minimum = 0,
  59. Maximum = 100,
  60. Value = 100,
  61. };
  62. target.Maximum = 50;
  63. Assert.Equal(0, target.Minimum);
  64. Assert.Equal(50, target.Maximum);
  65. Assert.Equal(50, target.Value);
  66. }
  67. [Fact]
  68. public void Properties_Should_Not_Accept_Nan_And_Inifinity()
  69. {
  70. var target = new TestRange();
  71. Assert.Throws<ArgumentException>(() => target.Minimum = double.NaN);
  72. Assert.Throws<ArgumentException>(() => target.Minimum = double.PositiveInfinity);
  73. Assert.Throws<ArgumentException>(() => target.Minimum = double.NegativeInfinity);
  74. Assert.Throws<ArgumentException>(() => target.Maximum = double.NaN);
  75. Assert.Throws<ArgumentException>(() => target.Maximum = double.PositiveInfinity);
  76. Assert.Throws<ArgumentException>(() => target.Maximum = double.NegativeInfinity);
  77. Assert.Throws<ArgumentException>(() => target.Value = double.NaN);
  78. Assert.Throws<ArgumentException>(() => target.Value = double.PositiveInfinity);
  79. Assert.Throws<ArgumentException>(() => target.Value = double.NegativeInfinity);
  80. }
  81. [Theory]
  82. [InlineData(true)]
  83. [InlineData(false)]
  84. public void SetValue_Should_Not_Cause_StackOverflow(bool useXamlBinding)
  85. {
  86. var viewModel = new TestStackOverflowViewModel()
  87. {
  88. Value = 50
  89. };
  90. Track track = null;
  91. var target = new TestRange()
  92. {
  93. Template = new FuncControlTemplate<RangeBase>(c =>
  94. {
  95. track = new Track()
  96. {
  97. Width = 100,
  98. Orientation = Orientation.Horizontal,
  99. [~~Track.MinimumProperty] = c[~~RangeBase.MinimumProperty],
  100. [~~Track.MaximumProperty] = c[~~RangeBase.MaximumProperty],
  101. Name = "PART_Track",
  102. Thumb = new Thumb()
  103. };
  104. if (useXamlBinding)
  105. {
  106. track.Bind(Track.ValueProperty, new Binding("Value")
  107. {
  108. Mode = BindingMode.TwoWay,
  109. Source = c,
  110. Priority = BindingPriority.Style
  111. });
  112. }
  113. else
  114. {
  115. track[~~Track.ValueProperty] = c[~~RangeBase.ValueProperty];
  116. }
  117. return track;
  118. }),
  119. Minimum = 0,
  120. Maximum = 100,
  121. DataContext = viewModel
  122. };
  123. target.Bind(TestRange.ValueProperty, new Binding("Value") { Mode = BindingMode.TwoWay });
  124. target.ApplyTemplate();
  125. track.Measure(new Size(100, 0));
  126. track.Arrange(new Rect(0, 0, 100, 0));
  127. Assert.Equal(1, viewModel.SetterInvokedCount);
  128. //here in real life stack overflow exception is thrown issue #855 and #824
  129. target.Value = 51.001;
  130. Assert.Equal(2, viewModel.SetterInvokedCount);
  131. double expected = 51;
  132. Assert.Equal(expected, viewModel.Value);
  133. Assert.Equal(expected, target.Value);
  134. Assert.Equal(expected, track.Value);
  135. }
  136. private class TestRange : RangeBase
  137. {
  138. }
  139. private class TestStackOverflowViewModel : INotifyPropertyChanged
  140. {
  141. public int SetterInvokedCount { get; private set; }
  142. public const int MaxInvokedCount = 1000;
  143. private double _value;
  144. public event PropertyChangedEventHandler PropertyChanged;
  145. public double Value
  146. {
  147. get { return _value; }
  148. set
  149. {
  150. if (_value != value)
  151. {
  152. SetterInvokedCount++;
  153. if (SetterInvokedCount < MaxInvokedCount)
  154. {
  155. _value = (int)value;
  156. if (_value > 75) _value = 75;
  157. if (_value < 25) _value = 25;
  158. }
  159. else
  160. {
  161. _value = value;
  162. }
  163. PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
  164. }
  165. }
  166. }
  167. }
  168. }
  169. }