Browse Source

Merge branch 'master' into fixes/windowsIME

pull/11809/head
Benedikt Stebner 3 years ago
committed by GitHub
parent
commit
ad41e5378d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 29
      src/Avalonia.Base/Animation/AnimationInstance`1.cs
  2. 3
      src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs
  3. 4
      src/Avalonia.Base/Input/AccessKeyHandler.cs
  4. 2
      src/Avalonia.Controls/Control.cs
  5. 9
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  6. 13
      src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
  7. 365
      tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs
  8. 241
      tests/Avalonia.Base.UnitTests/Animation/AnimationIterationTests.cs
  9. 6
      tests/Avalonia.Base.UnitTests/Animation/BrushTransitionTests.cs
  10. 68
      tests/Avalonia.Base.UnitTests/Animation/KeySplineTests.cs
  11. 34
      tests/Avalonia.Base.UnitTests/Animation/SpringTests.cs
  12. 94
      tests/Avalonia.Base.UnitTests/Animation/TransitionsTests.cs
  13. 24
      tests/Avalonia.Base.UnitTests/Data/ReflectionClrPropertyInfoTests.cs
  14. 205
      tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs
  15. 37
      tests/Avalonia.Headless.UnitTests/RenderingTests.cs

29
src/Avalonia.Base/Animation/AnimationInstance`1.cs

@ -53,10 +53,10 @@ namespace Avalonia.Animation
private void FetchProperties() private void FetchProperties()
{ {
if (_animation.SpeedRatio < 0d) if (_animation.SpeedRatio < 0d)
throw new ArgumentOutOfRangeException("SpeedRatio value should not be negative."); throw new InvalidOperationException("SpeedRatio value should not be negative.");
if (_animation.Duration.TotalSeconds <= 0) if (_animation.Duration < TimeSpan.Zero)
throw new InvalidOperationException("Duration value cannot be negative or zero."); throw new InvalidOperationException("Duration value cannot be negative.");
_easeFunc = _animation.Easing; _easeFunc = _animation.Easing;
@ -110,8 +110,8 @@ namespace Avalonia.Animation
{ {
if (_animator.Property is null) if (_animator.Property is null)
throw new InvalidOperationException("Animator has no property specified."); throw new InvalidOperationException("Animator has no property specified.");
if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both) if (_fillMode is FillMode.Forward or FillMode.Both)
_targetControl.SetValue(_animator.Property, _lastInterpValue, BindingPriority.LocalValue); _targetControl.SetValue(_animator.Property, _lastInterpValue);
} }
private void DoComplete() private void DoComplete()
@ -123,11 +123,8 @@ namespace Avalonia.Animation
private void DoDelay() private void DoDelay()
{ {
if (_fillMode == FillMode.Backward || _fillMode == FillMode.Both) if (_fillMode is not (FillMode.Backward or FillMode.Both)) return;
if (_currentIteration == 0) PublishNext(_currentIteration == 0 ? _firstKFValue : _lastInterpValue);
PublishNext(_firstKFValue);
else
PublishNext(_lastInterpValue);
} }
private void DoPlayStates() private void DoPlayStates()
@ -167,9 +164,9 @@ namespace Avalonia.Animation
_currentIteration = (ulong)(opsTime / iterationTime); _currentIteration = (ulong)(opsTime / iterationTime);
// Stop animation when the current iteration is beyond the iteration count // Stop animation when the current iteration is beyond the iteration count or
// and snap the last iteration value to exact values. // when the duration is set to zero while animating and snap to the last iterated value.
if ((_currentIteration + 1) > _iterationCount) if (_currentIteration + 1 > _iterationCount || _duration == TimeSpan.Zero)
{ {
var easedTime = _easeFunc!.Ease(_playbackReversed ? 0.0 : 1.0); var easedTime = _easeFunc!.Ease(_playbackReversed ? 0.0 : 1.0);
_lastInterpValue = _interpolator(easedTime, _neutralValue); _lastInterpValue = _interpolator(easedTime, _neutralValue);
@ -192,10 +189,10 @@ namespace Avalonia.Animation
_playbackReversed = true; _playbackReversed = true;
break; break;
case PlaybackDirection.Alternate: case PlaybackDirection.Alternate:
_playbackReversed = (_currentIteration % 2 == 0) ? false : true; _playbackReversed = _currentIteration % 2 != 0;
break; break;
case PlaybackDirection.AlternateReverse: case PlaybackDirection.AlternateReverse:
_playbackReversed = (_currentIteration % 2 == 0) ? true : false; _playbackReversed = _currentIteration % 2 == 0;
break; break;
default: default:
throw new InvalidOperationException($"Animation direction value is unknown: {_playbackDirection}"); throw new InvalidOperationException($"Animation direction value is unknown: {_playbackDirection}");
@ -215,7 +212,7 @@ namespace Avalonia.Animation
iterDelay > 0) iterDelay > 0)
{ {
// The last iteration's trailing delay should be skipped. // The last iteration's trailing delay should be skipped.
if ((_currentIteration + 1) < _iterationCount) if (_currentIteration + 1 < _iterationCount)
DoDelay(); DoDelay();
else else
DoComplete(); DoComplete();

3
src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs

@ -60,7 +60,8 @@ namespace Avalonia.Data.Core
var target = Expression.Parameter(typeof(object), "target"); var target = Expression.Parameter(typeof(object), "target");
return Expression.Lambda<Func<object, object>>( return Expression.Lambda<Func<object, object>>(
Expression.Convert(Expression.Call(Expression.Convert(target, info.DeclaringType!), info.GetMethod), Expression.Convert(Expression.Call(Expression.Convert(target, info.DeclaringType!), info.GetMethod),
typeof(object))) typeof(object)),
target)
.Compile(); .Compile();
} }

4
src/Avalonia.Base/Input/AccessKeyHandler.cs

@ -159,8 +159,6 @@ namespace Avalonia.Input
_restoreFocusElement?.Focus(); _restoreFocusElement?.Focus();
_restoreFocusElement = null; _restoreFocusElement = null;
e.Handled = true;
} }
} }
else if (_altIsDown) else if (_altIsDown)
@ -200,7 +198,6 @@ namespace Avalonia.Input
if (match is not null) if (match is not null)
{ {
match.RaiseEvent(new RoutedEventArgs(AccessKeyPressedEvent)); match.RaiseEvent(new RoutedEventArgs(AccessKeyPressedEvent));
e.Handled = true;
} }
} }
} }
@ -225,7 +222,6 @@ namespace Avalonia.Input
else if (_showingAccessKeys && MainMenu != null) else if (_showingAccessKeys && MainMenu != null)
{ {
MainMenu.Open(); MainMenu.Open();
e.Handled = true;
} }
break; break;

2
src/Avalonia.Controls/Control.cs

@ -480,7 +480,7 @@ namespace Avalonia.Controls
if (e.Source == this if (e.Source == this
&& !e.Handled) && !e.Handled)
{ {
var keymap = Application.Current!.PlatformSettings?.HotkeyConfiguration.OpenContextMenu; var keymap = TopLevel.GetTopLevel(this)?.PlatformSettings?.HotkeyConfiguration.OpenContextMenu;
if (keymap is null) if (keymap is null)
{ {

9
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -88,14 +88,17 @@ namespace Avalonia.Native
var applicationPlatform = new AvaloniaNativeApplicationPlatform(); var applicationPlatform = new AvaloniaNativeApplicationPlatform();
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>() ?? new MacOSPlatformOptions();
if (_factory.MacOptions != null)
_factory.MacOptions.SetDisableAppDelegate(macOpts.DisableAvaloniaAppDelegate ? 1 : 0);
_factory.Initialize(new GCHandleDeallocator(), applicationPlatform); _factory.Initialize(new GCHandleDeallocator(), applicationPlatform);
if (_factory.MacOptions != null) if (_factory.MacOptions != null)
{ {
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>() ?? new MacOSPlatformOptions();
_factory.MacOptions.SetShowInDock(macOpts.ShowInDock ? 1 : 0); _factory.MacOptions.SetShowInDock(macOpts.ShowInDock ? 1 : 0);
_factory.MacOptions.SetDisableSetProcessName(macOpts.DisableSetProcessName ? 1 : 0); _factory.MacOptions.SetDisableSetProcessName(macOpts.DisableSetProcessName ? 1 : 0);
_factory.MacOptions.SetDisableAppDelegate(macOpts.DisableAvaloniaAppDelegate ? 1 : 0);
} }
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable

13
src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs

@ -114,23 +114,26 @@ namespace Avalonia.Headless
public Size MaxClientSize { get; } = new Size(1920, 1280); public Size MaxClientSize { get; } = new Size(1920, 1280);
public void Resize(Size clientSize, WindowResizeReason reason) public void Resize(Size clientSize, WindowResizeReason reason)
{ {
if (ClientSize == clientSize)
return;
// Emulate X11 behavior here // Emulate X11 behavior here
if (IsPopup) if (IsPopup)
DoResize(clientSize); DoResize(clientSize, reason);
else else
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
DoResize(clientSize); DoResize(clientSize, reason);
}); }, DispatcherPriority.Send);
} }
private void DoResize(Size clientSize) private void DoResize(Size clientSize, WindowResizeReason reason)
{ {
// Uncomment this check and experience a weird bug in layout engine // Uncomment this check and experience a weird bug in layout engine
if (ClientSize != clientSize) if (ClientSize != clientSize)
{ {
ClientSize = clientSize; ClientSize = clientSize;
Resized?.Invoke(clientSize, WindowResizeReason.Unspecified); Resized?.Invoke(clientSize, reason);
} }
} }

365
tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs

@ -5,7 +5,6 @@ using Avalonia.Controls.Shapes;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.PropertyStore;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Moq; using Moq;
@ -19,18 +18,15 @@ namespace Avalonia.Base.UnitTests.Animation
public void Transition_Is_Not_Applied_When_Not_Attached_To_Visual_Tree() public void Transition_Is_Not_Applied_When_Not_Attached_To_Visual_Tree()
{ {
var target = CreateTarget(); var target = CreateTarget();
var control = new Control var control = new Control { Transitions = new Transitions { target.Object }, };
{
Transitions = new Transitions { target.Object },
};
control.Opacity = 0.5; control.Opacity = 0.5;
target.Verify(x => x.Apply( target.Verify(x => x.Apply(
control, control,
It.IsAny<IClock>(), It.IsAny<IClock>(),
1.0, 1.0,
0.5), 0.5),
Times.Never); Times.Never);
} }
@ -40,10 +36,7 @@ namespace Avalonia.Base.UnitTests.Animation
using (Start()) using (Start())
{ {
var target = CreateTarget(); var target = CreateTarget();
var control = new Control var control = new Control { Transitions = new Transitions { target.Object }, };
{
Transitions = new Transitions { target.Object },
};
var root = new TestRoot var root = new TestRoot
{ {
@ -51,10 +44,7 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
new Style(x => x.OfType<Control>()) new Style(x => x.OfType<Control>())
{ {
Setters = Setters = { new Setter(Visual.OpacityProperty, 0.8), }
{
new Setter(Visual.OpacityProperty, 0.8),
}
} }
} }
}; };
@ -64,10 +54,10 @@ namespace Avalonia.Base.UnitTests.Animation
Assert.Equal(0.8, control.Opacity); Assert.Equal(0.8, control.Opacity);
target.Verify(x => x.Apply( target.Verify(x => x.Apply(
It.IsAny<Control>(), It.IsAny<Control>(),
It.IsAny<IClock>(), It.IsAny<IClock>(),
It.IsAny<object>(), It.IsAny<object>(),
It.IsAny<object>()), It.IsAny<object>()),
Times.Never); Times.Never);
} }
} }
@ -97,10 +87,10 @@ namespace Avalonia.Base.UnitTests.Animation
control.SetValue(Visual.OpacityProperty, 0.5, BindingPriority.Animation); control.SetValue(Visual.OpacityProperty, 0.5, BindingPriority.Animation);
target.Verify(x => x.Apply( target.Verify(x => x.Apply(
control, control,
It.IsAny<IClock>(), It.IsAny<IClock>(),
1.0, 1.0,
0.5), 0.5),
Times.Never); Times.Never);
} }
@ -112,53 +102,34 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
var keyframe1 = new KeyFrame() var keyframe1 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 1d), }, KeyTime = TimeSpan.FromSeconds(0)
{
new Setter(Layoutable.WidthProperty, 1d),
},
KeyTime = TimeSpan.FromSeconds(0)
}; };
var keyframe2 = new KeyFrame() var keyframe2 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 2d), }, KeyTime = TimeSpan.FromSeconds(2),
{
new Setter(Layoutable.WidthProperty, 2d),
},
KeyTime = TimeSpan.FromSeconds(2),
}; };
var keyframe3 = new KeyFrame() var keyframe3 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, invalidValue), },
{
new Setter(Layoutable.WidthProperty, invalidValue),
},
KeyTime = TimeSpan.FromSeconds(3), KeyTime = TimeSpan.FromSeconds(3),
}; };
var animation = new Avalonia.Animation.Animation() var animation = new Avalonia.Animation.Animation()
{ {
Duration = TimeSpan.FromSeconds(3), Duration = TimeSpan.FromSeconds(3),
Children = Children = { keyframe1, keyframe2, keyframe3 },
{
keyframe1,
keyframe2,
keyframe3
},
IterationCount = new IterationCount(5), IterationCount = new IterationCount(5),
PlaybackDirection = PlaybackDirection.Alternate, PlaybackDirection = PlaybackDirection.Alternate,
}; };
var rect = new Rectangle() var rect = new Rectangle() { Width = 11, };
{
Width = 11,
};
var originalValue = rect.Width; var originalValue = rect.Width;
var clock = new TestClock(); var clock = new TestClock();
var animationRun = animation.RunAsync(rect, clock); animation.RunAsync(rect, clock);
clock.Step(TimeSpan.Zero); clock.Step(TimeSpan.Zero);
Assert.Equal(rect.Width, 1); Assert.Equal(rect.Width, 1);
@ -188,10 +159,10 @@ namespace Avalonia.Base.UnitTests.Animation
control.SetValue(Visual.OpacityProperty, 0.8, BindingPriority.StyleTrigger); control.SetValue(Visual.OpacityProperty, 0.8, BindingPriority.StyleTrigger);
target.Verify(x => x.Apply( target.Verify(x => x.Apply(
It.IsAny<Control>(), It.IsAny<Control>(),
It.IsAny<IClock>(), It.IsAny<IClock>(),
It.IsAny<object>(), It.IsAny<object>(),
It.IsAny<object>()), It.IsAny<object>()),
Times.Never); Times.Never);
} }
@ -258,14 +229,15 @@ namespace Avalonia.Base.UnitTests.Animation
target.Invocations.Clear(); target.Invocations.Clear();
var root = (TestRoot)control.Parent; var root = (TestRoot)control.Parent;
Assert.NotNull(root);
root.Child = null; root.Child = null;
control.Opacity = 0.8; control.Opacity = 0.8;
target.Verify(x => x.Apply( target.Verify(x => x.Apply(
It.IsAny<Control>(), It.IsAny<Control>(),
It.IsAny<IClock>(), It.IsAny<IClock>(),
It.IsAny<object>(), It.IsAny<object>(),
It.IsAny<object>()), It.IsAny<object>()),
Times.Never); Times.Never);
} }
@ -284,6 +256,8 @@ namespace Avalonia.Base.UnitTests.Animation
It.IsAny<object>())).Returns(sub.Object); It.IsAny<object>())).Returns(sub.Object);
control.Opacity = 0.5; control.Opacity = 0.5;
Assert.NotNull(control.Transitions);
control.Transitions.RemoveAt(0); control.Transitions.RemoveAt(0);
sub.Verify(x => x.Dispose()); sub.Verify(x => x.Dispose());
@ -307,10 +281,10 @@ namespace Avalonia.Base.UnitTests.Animation
control.Opacity = 0.5; control.Opacity = 0.5;
target.Verify(x => x.Apply( target.Verify(x => x.Apply(
control, control,
It.IsAny<IClock>(), It.IsAny<IClock>(),
1.0, 1.0,
0.5), 0.5),
Times.Once); Times.Once);
control.Classes.Add("foo"); control.Classes.Add("foo");
@ -324,18 +298,17 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
using (Start()) using (Start())
{ {
var target = CreateTransition(Control.WidthProperty); var target = CreateTransition(Layoutable.WidthProperty);
var control = CreateStyledControl(transition2: target.Object); var control = CreateStyledControl(transition2: target.Object);
var sub = new Mock<IDisposable>();
control.Classes.Add("foo"); control.Classes.Add("foo");
control.Width = 100; control.Width = 100;
target.Verify(x => x.Apply( target.Verify(x => x.Apply(
control, control,
It.IsAny<IClock>(), It.IsAny<IClock>(),
double.NaN, double.NaN,
100.0), 100.0),
Times.Once); Times.Once);
} }
} }
@ -357,12 +330,12 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
Setters = Setters =
{ {
new Setter(Border.TransitionsProperty, new Setter(Animatable.TransitionsProperty,
new Transitions new Transitions
{ {
new DoubleTransition new DoubleTransition
{ {
Property = Border.OpacityProperty, Property = Visual.OpacityProperty,
Duration = TimeSpan.FromSeconds(1), Duration = TimeSpan.FromSeconds(1),
}, },
}), }),
@ -372,23 +345,20 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
Setters = Setters =
{ {
new Setter(Border.TransitionsProperty, new Setter(Animatable.TransitionsProperty,
new Transitions new Transitions
{ {
new DoubleTransition new DoubleTransition
{ {
Property = Border.OpacityProperty, Property = Visual.OpacityProperty,
Duration = TimeSpan.FromSeconds(1), Duration = TimeSpan.FromSeconds(1),
}, },
}), }),
new Setter(Border.OpacityProperty, 0.0), new Setter(Visual.OpacityProperty, 0.0),
}, },
}, },
}, },
Child = target = new Border Child = target = new Border { Background = Brushes.Red, }
{
Background = Brushes.Red,
}
}; };
root.Measure(Size.Infinity); root.Measure(Size.Infinity);
@ -421,7 +391,7 @@ namespace Avalonia.Base.UnitTests.Animation
// Assigning and then clearing Transitions ensures we have a transition state // Assigning and then clearing Transitions ensures we have a transition state
// collection created. // collection created.
control.ClearValue(Control.TransitionsProperty); control.ClearValue(Animatable.TransitionsProperty);
control.GetValueStore().BeginStyling(); control.GetValueStore().BeginStyling();
@ -431,8 +401,8 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
Setters = Setters =
{ {
new Setter(Control.OpacityProperty, 0.5), new Setter(Visual.OpacityProperty, 0.5),
new Setter(Control.TransitionsProperty, new Transitions { target.Object }), new Setter(Animatable.TransitionsProperty, new Transitions { target.Object }),
} }
}; };
@ -450,28 +420,17 @@ namespace Avalonia.Base.UnitTests.Animation
var opacityTransition = new DoubleTransition var opacityTransition = new DoubleTransition
{ {
Property = Control.OpacityProperty, Property = Visual.OpacityProperty, Duration = TimeSpan.FromSeconds(1),
Duration = TimeSpan.FromSeconds(1),
}; };
var transitions = new Transitions { opacityTransition }; var transitions = new Transitions { opacityTransition };
var borderTheme = new ControlTheme(typeof(Border)) var borderTheme = new ControlTheme(typeof(Border))
{ {
Setters = Setters = { new Setter(Animatable.TransitionsProperty, transitions), }
{
new Setter(Control.TransitionsProperty, transitions),
}
}; };
var clock = new TestClock(); var clock = new TestClock();
var root = new TestRoot var root = new TestRoot { Clock = clock, Resources = { { typeof(Border), borderTheme }, } };
{
Clock = clock,
Resources =
{
{ typeof(Border), borderTheme },
}
};
var border = new Border(); var border = new Border();
root.Child = border; root.Child = border;
@ -489,13 +448,214 @@ namespace Avalonia.Base.UnitTests.Animation
// Now clear the property; a transition is now in progress but no local value is // Now clear the property; a transition is now in progress but no local value is
// set. // set.
border.ClearValue(Border.OpacityProperty); border.ClearValue(Visual.OpacityProperty);
// Remove the transition by removing the control from the logical tree. This was // Remove the transition by removing the control from the logical tree. This was
// causing an exception. // causing an exception.
root.Child = null; root.Child = null;
} }
[Fact]
public void Run_Normal_Use_Case_Animation()
{
using (Start())
{
var keyframe1 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 1d), }, KeyTime = TimeSpan.FromSeconds(0)
};
var keyframe2 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 0.5d), }, KeyTime = TimeSpan.FromSeconds(1)
};
var animation = new Avalonia.Animation.Animation()
{
Duration = TimeSpan.FromSeconds(10), Children = { keyframe1, keyframe2 },
};
Border target;
var clock = new TestClock();
var root = new TestRoot
{
Clock = clock,
Styles = { new Style(x => x.OfType<Border>()) { Animations = { animation }, } },
Child = target = new Border { Background = Brushes.Red, }
};
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
clock.Step(TimeSpan.FromSeconds(0));
clock.Step(TimeSpan.FromSeconds(0.99));
Assert.InRange(target.Opacity, 0.5d, 0.51d);
}
}
[Fact]
public void Run_Normal_Use_Case_Animation_With_Infinite_Iteration()
{
using (Start())
{
var keyframe1 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 0d), }, KeyTime = TimeSpan.FromSeconds(0)
};
var keyframe2 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 1d), }, KeyTime = TimeSpan.FromSeconds(1)
};
var animation = new Avalonia.Animation.Animation()
{
Duration = TimeSpan.FromSeconds(1),
IterationCount = IterationCount.Infinite,
Children = { keyframe1, keyframe2 },
};
Border target;
var clock = new TestClock();
var root = new TestRoot
{
Clock = clock,
Styles = { new Style(x => x.OfType<Border>()) { Animations = { animation }, } },
Child = target = new Border { Background = Brushes.Red, }
};
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
clock.Step(TimeSpan.FromSeconds(0));
clock.Step(TimeSpan.FromSeconds(0.5));
Assert.Equal(0.5, target.Opacity);
clock.Step(TimeSpan.FromSeconds(1));
Assert.Equal(0, target.Opacity);
clock.Step(TimeSpan.FromSeconds(1.5));
Assert.Equal(0.5, target.Opacity);
clock.Step(TimeSpan.FromSeconds(2));
Assert.Equal(0, target.Opacity);
}
}
[Fact]
public void Zero_Duration_Should_Finish_Animation()
{
using (Start())
{
var keyframe1 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 1d), }, KeyTime = TimeSpan.FromSeconds(0)
};
var keyframe2 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 0.5d), }, KeyTime = TimeSpan.FromSeconds(2)
};
var animation = new Avalonia.Animation.Animation()
{
Duration = TimeSpan.FromSeconds(2),
Children = { keyframe1, keyframe2 },
FillMode = FillMode.Both
};
Border target;
var clock = new TestClock();
var root = new TestRoot
{
Clock = clock,
Styles = { new Style(x => x.OfType<Border>()) { Animations = { animation }, } },
Child = target = new Border { Background = Brushes.Red, }
};
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
clock.Step(TimeSpan.FromSeconds(0));
clock.Step(TimeSpan.FromSeconds(1));
Assert.True(target.IsAnimating(Visual.OpacityProperty));
Assert.Equal(0.75, target.Opacity);
// This is not the normal way to access and set the animations
// object's Duration property to zero that is defined in styles
// but this is still valid for the RunAsync version.
animation.Duration = TimeSpan.Zero;
clock.Step(TimeSpan.FromSeconds(1.2));
Assert.Equal(0.5, target.Opacity);
Assert.False(target.IsAnimating(Visual.OpacityProperty));
}
}
[Fact]
public void Zero_Duration_Should_Finish_Animation_With_Infinite_Iteration()
{
using (Start())
{
var keyframe1 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 0d), }, KeyTime = TimeSpan.FromSeconds(0)
};
var keyframe2 = new KeyFrame()
{
Setters = { new Setter(Visual.OpacityProperty, 1d), }, KeyTime = TimeSpan.FromSeconds(1)
};
var animation = new Avalonia.Animation.Animation()
{
Duration = TimeSpan.FromSeconds(1),
IterationCount = IterationCount.Infinite,
Children = { keyframe1, keyframe2 },
};
Border target;
var clock = new TestClock();
var root = new TestRoot
{
Clock = clock,
Styles = { new Style(x => x.OfType<Border>()) { Animations = { animation }, } },
Child = target = new Border { Background = Brushes.Red, }
};
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
clock.Step(TimeSpan.FromSeconds(0));
Assert.True(target.IsAnimating(Visual.OpacityProperty));
clock.Step(TimeSpan.FromSeconds(0.5));
Assert.Equal(0.5, target.Opacity);
clock.Step(TimeSpan.FromSeconds(1));
Assert.Equal(0, target.Opacity);
clock.Step(TimeSpan.FromSeconds(1.5));
Assert.Equal(0.5, target.Opacity);
clock.Step(TimeSpan.FromSeconds(2));
Assert.Equal(0, target.Opacity);
// This is not the normal way to access and set the animations
// object's Duration property to zero that is defined in styles
// but this is still valid for the RunAsync version.
animation.Duration = TimeSpan.Zero;
clock.Step(TimeSpan.FromSeconds(1.2));
Assert.Equal(1, target.Opacity);
Assert.False(target.IsAnimating(Visual.OpacityProperty));
}
}
private static IDisposable Start() private static IDisposable Start()
{ {
var clock = new MockGlobalClock(); var clock = new MockGlobalClock();
@ -510,12 +670,9 @@ namespace Avalonia.Base.UnitTests.Animation
private static Control CreateControl(ITransition transition) private static Control CreateControl(ITransition transition)
{ {
var control = new Control var control = new Control { Transitions = new Transitions { transition }, };
{
Transitions = new Transitions { transition },
};
var root = new TestRoot(control); var _ = new TestRoot(control);
return control; return control;
} }
@ -524,7 +681,7 @@ namespace Avalonia.Base.UnitTests.Animation
ITransition transition2 = null) ITransition transition2 = null)
{ {
transition1 = transition1 ?? CreateTarget().Object; transition1 = transition1 ?? CreateTarget().Object;
transition2 = transition2 ?? CreateTransition(Control.WidthProperty).Object; transition2 = transition2 ?? CreateTransition(Layoutable.WidthProperty).Object;
var control = new Control var control = new Control
{ {
@ -536,7 +693,7 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
new Setter new Setter
{ {
Property = Control.TransitionsProperty, Property = Animatable.TransitionsProperty,
Value = new Transitions { transition1 }, Value = new Transitions { transition1 },
} }
} }
@ -547,7 +704,7 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
new Setter new Setter
{ {
Property = Control.TransitionsProperty, Property = Animatable.TransitionsProperty,
Value = new Transitions { transition2 }, Value = new Transitions { transition2 },
} }
} }
@ -555,7 +712,7 @@ namespace Avalonia.Base.UnitTests.Animation
} }
}; };
var root = new TestRoot(control); var _ = new TestRoot(control);
return control; return control;
} }

241
tests/Avalonia.Base.UnitTests/Animation/AnimationIterationTests.cs

@ -1,16 +1,13 @@
using System; using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Animation; using Avalonia.Animation;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.Data;
using Xunit; using Xunit;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using System.Threading; using System.Threading;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Layout;
namespace Avalonia.Base.UnitTests.Animation namespace Avalonia.Base.UnitTests.Animation
{ {
@ -23,47 +20,27 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
var keyframe1 = new KeyFrame() var keyframe1 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 100d), }, KeyTime = TimeSpan.FromSeconds(0.5)
{
new Setter(Border.WidthProperty, 100d),
},
KeyTime = TimeSpan.FromSeconds(0.5)
}; };
var keyframe2 = new KeyFrame() var keyframe2 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 0d), }, KeyTime = TimeSpan.FromSeconds(0)
{
new Setter(Border.WidthProperty, 0d),
},
KeyTime = TimeSpan.FromSeconds(0)
}; };
var animation = new Avalonia.Animation.Animation() var animation = new Animation() { Duration = TimeSpan.FromSeconds(1), Children = { keyframe2, keyframe1 } };
{
Duration = TimeSpan.FromSeconds(1),
Children =
{
keyframe2,
keyframe1
}
};
var border = new Border() var border = new Border() { Height = 100d, Width = 100d };
{
Height = 100d,
Width = 100d
};
var clock = new TestClock(); var clock = new TestClock();
var animationRun = animation.RunAsync(border, clock);
clock.Step(TimeSpan.Zero); animation.RunAsync(border, clock);
clock.Step(TimeSpan.Zero);
Assert.Equal(border.Width, 0d); Assert.Equal(border.Width, 0d);
clock.Step(TimeSpan.FromSeconds(1)); clock.Step(TimeSpan.FromSeconds(1));
Assert.Equal(border.Width, 100d); Assert.Equal(border.Width, 100d);
} }
@ -72,40 +49,24 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
var keyframe1 = new KeyFrame() var keyframe1 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
}; };
var keyframe2 = new KeyFrame() var keyframe2 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
}; };
var animation = new Avalonia.Animation.Animation() var animation = new Animation()
{ {
Duration = TimeSpan.FromSeconds(3), Duration = TimeSpan.FromSeconds(3),
Delay = TimeSpan.FromSeconds(3), Delay = TimeSpan.FromSeconds(3),
DelayBetweenIterations = TimeSpan.FromSeconds(3), DelayBetweenIterations = TimeSpan.FromSeconds(3),
IterationCount = new IterationCount(2), IterationCount = new IterationCount(2),
Children = Children = { keyframe2, keyframe1 }
{
keyframe2,
keyframe1
}
}; };
var border = new Border() var border = new Border() { Height = 100d, Width = 100d };
{
Height = 100d,
Width = 100d
};
var clock = new TestClock(); var clock = new TestClock();
var animationRun = animation.RunAsync(border, clock); var animationRun = animation.RunAsync(border, clock);
@ -133,43 +94,28 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
var keyframe1 = new KeyFrame() var keyframe1 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 0d), }, Cue = new Cue(0.0d)
{
new Setter(Border.WidthProperty, 0d),
},
Cue = new Cue(0.0d)
}; };
var keyframe2 = new KeyFrame() var keyframe2 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 300d), }, Cue = new Cue(1.0d)
{
new Setter(Border.WidthProperty, 300d),
},
Cue = new Cue(1.0d)
}; };
var animation = new Avalonia.Animation.Animation() var animation = new Animation()
{ {
Duration = TimeSpan.FromSeconds(0.05d), Duration = TimeSpan.FromSeconds(0.05d),
Delay = TimeSpan.FromSeconds(0.05d), Delay = TimeSpan.FromSeconds(0.05d),
Easing = new SineEaseInOut(), Easing = new SineEaseInOut(),
FillMode = FillMode.Both, FillMode = FillMode.Both,
Children = Children = { keyframe1, keyframe2 }
{
keyframe1,
keyframe2
}
}; };
var border = new Border() var border = new Border() { Height = 100d, Width = 100d, };
{
Height = 100d,
Width = 100d,
};
var clock = new TestClock(); var clock = new TestClock();
var animationRun = animation.RunAsync(border, clock);
animation.RunAsync(border, clock);
clock.Step(TimeSpan.FromSeconds(0d)); clock.Step(TimeSpan.FromSeconds(0d));
Assert.Equal(border.Width, 0d); Assert.Equal(border.Width, 0d);
@ -186,20 +132,12 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
var keyframe1 = new KeyFrame() var keyframe1 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
}; };
var keyframe2 = new KeyFrame() var keyframe2 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
}; };
var animation = new Animation() var animation = new Animation()
@ -208,23 +146,15 @@ namespace Avalonia.Base.UnitTests.Animation
Delay = TimeSpan.FromSeconds(0), Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0), DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1), IterationCount = new IterationCount(1),
Children = Children = { keyframe2, keyframe1 }
{
keyframe2,
keyframe1
}
}; };
var border = new Border() var border = new Border() { Height = 100d, Width = 50d };
{
Height = 100d,
Width = 50d
};
var propertyChangedCount = 0; var propertyChangedCount = 0;
var animationCompletedCount = 0; var animationCompletedCount = 0;
border.PropertyChanged += (sender, e) => border.PropertyChanged += (_, e) =>
{ {
if (e.Property == Control.WidthProperty) if (e.Property == Layoutable.WidthProperty)
{ {
propertyChangedCount++; propertyChangedCount++;
} }
@ -257,20 +187,12 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
var keyframe1 = new KeyFrame() var keyframe1 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
}; };
var keyframe2 = new KeyFrame() var keyframe2 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
}; };
var animation = new Animation() var animation = new Animation()
@ -279,22 +201,14 @@ namespace Avalonia.Base.UnitTests.Animation
Delay = TimeSpan.FromSeconds(0), Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0), DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1), IterationCount = new IterationCount(1),
Children = Children = { keyframe2, keyframe1 }
{
keyframe2,
keyframe1
}
}; };
var border = new Border() var border = new Border() { Height = 100d, Width = 100d };
{
Height = 100d,
Width = 100d
};
var propertyChangedCount = 0; var propertyChangedCount = 0;
border.PropertyChanged += (sender, e) => border.PropertyChanged += (_, e) =>
{ {
if (e.Property == Control.WidthProperty) if (e.Property == Layoutable.WidthProperty)
{ {
propertyChangedCount++; propertyChangedCount++;
} }
@ -315,20 +229,12 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
var keyframe1 = new KeyFrame() var keyframe1 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
}; };
var keyframe2 = new KeyFrame() var keyframe2 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
}; };
var animation = new Animation() var animation = new Animation()
@ -337,22 +243,14 @@ namespace Avalonia.Base.UnitTests.Animation
Delay = TimeSpan.FromSeconds(0), Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0), DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1), IterationCount = new IterationCount(1),
Children = Children = { keyframe2, keyframe1 }
{
keyframe2,
keyframe1
}
}; };
var border = new Border() var border = new Border() { Height = 100d, Width = 50d };
{
Height = 100d,
Width = 50d
};
var propertyChangedCount = 0; var propertyChangedCount = 0;
border.PropertyChanged += (sender, e) => border.PropertyChanged += (_, e) =>
{ {
if (e.Property == Control.WidthProperty) if (e.Property == Layoutable.WidthProperty)
{ {
propertyChangedCount++; propertyChangedCount++;
} }
@ -361,6 +259,7 @@ namespace Avalonia.Base.UnitTests.Animation
var clock = new TestClock(); var clock = new TestClock();
var cancellationTokenSource = new CancellationTokenSource(); var cancellationTokenSource = new CancellationTokenSource();
var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token); var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
Assert.False(animationRun.IsCompleted);
Assert.Equal(0, propertyChangedCount); Assert.Equal(0, propertyChangedCount);
@ -381,24 +280,16 @@ namespace Avalonia.Base.UnitTests.Animation
} }
[Fact] [Fact]
public void Cancellation_Of_Completed_Animation_Does_Not_Fail() public void Dont_Run_Infinite_Iteration_Animation_On_RunAsync_Method()
{ {
var keyframe1 = new KeyFrame() var keyframe1 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
}; };
var keyframe2 = new KeyFrame() var keyframe2 = new KeyFrame()
{ {
Setters = Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
}; };
var animation = new Animation() var animation = new Animation()
@ -406,23 +297,47 @@ namespace Avalonia.Base.UnitTests.Animation
Duration = TimeSpan.FromSeconds(10), Duration = TimeSpan.FromSeconds(10),
Delay = TimeSpan.FromSeconds(0), Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0), DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1), IterationCount = IterationCount.Infinite,
Children = Children = { keyframe2, keyframe1 }
{
keyframe2,
keyframe1
}
}; };
var border = new Border() var border = new Border() { Height = 100d, Width = 50d };
var clock = new TestClock();
var cancellationTokenSource = new CancellationTokenSource();
var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
Assert.True(animationRun.IsCompleted);
Assert.NotNull(animationRun.Exception);
}
[Fact]
public void Cancellation_Of_Completed_Animation_Does_Not_Fail()
{
var keyframe1 = new KeyFrame()
{
Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{ {
Height = 100d, Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d)
Width = 50d
}; };
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10),
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children = { keyframe2, keyframe1 }
};
var border = new Border() { Height = 100d, Width = 50d };
var propertyChangedCount = 0; var propertyChangedCount = 0;
border.PropertyChanged += (sender, e) => border.PropertyChanged += (_, e) =>
{ {
if (e.Property == Control.WidthProperty) if (e.Property == Layoutable.WidthProperty)
{ {
propertyChangedCount++; propertyChangedCount++;
} }

6
tests/Avalonia.Base.UnitTests/Animation/BrushTransitionTests.cs

@ -36,8 +36,7 @@ namespace Avalonia.Base.UnitTests.Animation
var border = new Border() { Background = oldBrush }; var border = new Border() { Background = oldBrush };
BrushTransition sut = new BrushTransition BrushTransition sut = new BrushTransition
{ {
Duration = TimeSpan.FromSeconds(1), Duration = TimeSpan.FromSeconds(1), Property = Border.BackgroundProperty
Property = Border.BackgroundProperty
}; };
sut.Apply(border, clock, oldBrush, newBrush); sut.Apply(border, clock, oldBrush, newBrush);
@ -45,7 +44,8 @@ namespace Avalonia.Base.UnitTests.Animation
clock.Pulse(sut.Duration * progress); clock.Pulse(sut.Duration * progress);
Assert.NotNull(border.Background); Assert.NotNull(border.Background);
Assert.Equal(oldBrush.Opacity + (newBrush.Opacity - oldBrush.Opacity) * progress, border.Background.Opacity); Assert.Equal(oldBrush.Opacity + (newBrush.Opacity - oldBrush.Opacity) * progress,
border.Background.Opacity);
} }
} }
} }

68
tests/Avalonia.Base.UnitTests/Animation/KeySplineTests.cs

@ -21,6 +21,8 @@ namespace Avalonia.Base.UnitTests.Animation
var keySpline = (KeySpline)conv.ConvertFrom(input); var keySpline = (KeySpline)conv.ConvertFrom(input);
Assert.NotNull(keySpline);
Assert.Equal(1, keySpline.ControlPointX1); Assert.Equal(1, keySpline.ControlPointX1);
Assert.Equal(2, keySpline.ControlPointY1); Assert.Equal(2, keySpline.ControlPointY1);
Assert.Equal(3, keySpline.ControlPointX2); Assert.Equal(3, keySpline.ControlPointX2);
@ -28,8 +30,8 @@ namespace Avalonia.Base.UnitTests.Animation
} }
[Theory] [Theory]
[InlineData("1,2F,3,4")] [InlineData("1,2F,3,4")]
[InlineData("Foo,Bar,Fee,Buzz")] [InlineData("Foo,Bar,Fee,Buzz")]
public void Can_Handle_Invalid_String_KeySpline_Via_TypeConverter(string input) public void Can_Handle_Invalid_String_KeySpline_Via_TypeConverter(string input)
{ {
var conv = new KeySplineTypeConverter(); var conv = new KeySplineTypeConverter();
@ -104,46 +106,33 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
var keyframe1 = new KeyFrame() var keyframe1 = new KeyFrame()
{ {
Setters = Setters = { new Setter(RotateTransform.AngleProperty, -2.5d), }, KeyTime = TimeSpan.FromSeconds(0)
{
new Setter(RotateTransform.AngleProperty, -2.5d),
},
KeyTime = TimeSpan.FromSeconds(0)
}; };
var keyframe2 = new KeyFrame() var keyframe2 = new KeyFrame()
{ {
Setters = Setters = { new Setter(RotateTransform.AngleProperty, 2.5d), },
{
new Setter(RotateTransform.AngleProperty, 2.5d),
},
KeyTime = TimeSpan.FromSeconds(5), KeyTime = TimeSpan.FromSeconds(5),
KeySpline = new KeySpline(0.1123555056179775, KeySpline = new KeySpline(0.1123555056179775,
0.657303370786517, 0.657303370786517,
0.8370786516853934, 0.8370786516853934,
0.499999999999999999) 0.499999999999999999)
}; };
var animation = new Avalonia.Animation.Animation() var animation = new Avalonia.Animation.Animation()
{ {
Duration = TimeSpan.FromSeconds(5), Duration = TimeSpan.FromSeconds(5),
Children = Children = { keyframe1, keyframe2 },
{
keyframe1,
keyframe2
},
IterationCount = new IterationCount(5), IterationCount = new IterationCount(5),
PlaybackDirection = PlaybackDirection.Alternate PlaybackDirection = PlaybackDirection.Alternate
}; };
var rotateTransform = new RotateTransform(-2.5); var rotateTransform = new RotateTransform(-2.5);
var rect = new Rectangle() var rect = new Rectangle() { RenderTransform = rotateTransform };
{
RenderTransform = rotateTransform
};
var clock = new TestClock(); var clock = new TestClock();
var animationRun = animation.RunAsync(rect, clock);
animation.RunAsync(rect, clock);
// position is what you'd expect at end and beginning // position is what you'd expect at end and beginning
clock.Step(TimeSpan.Zero); clock.Step(TimeSpan.Zero);
@ -169,49 +158,36 @@ namespace Avalonia.Base.UnitTests.Animation
expected = 1.8016358493761722; expected = 1.8016358493761722;
Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance);
} }
[Fact] [Fact]
public void Check_KeySpline_Parsing_Is_Correct() public void Check_KeySpline_Parsing_Is_Correct()
{ {
var keyframe1 = new KeyFrame() var keyframe1 = new KeyFrame()
{ {
Setters = Setters = { new Setter(RotateTransform.AngleProperty, -2.5d), }, KeyTime = TimeSpan.FromSeconds(0)
{
new Setter(RotateTransform.AngleProperty, -2.5d),
},
KeyTime = TimeSpan.FromSeconds(0)
}; };
var keyframe2 = new KeyFrame() var keyframe2 = new KeyFrame()
{ {
Setters = Setters = { new Setter(RotateTransform.AngleProperty, 2.5d), }, KeyTime = TimeSpan.FromSeconds(5),
{
new Setter(RotateTransform.AngleProperty, 2.5d),
},
KeyTime = TimeSpan.FromSeconds(5),
}; };
var animation = new Avalonia.Animation.Animation() var animation = new Avalonia.Animation.Animation()
{ {
Duration = TimeSpan.FromSeconds(5), Duration = TimeSpan.FromSeconds(5),
Children = Children = { keyframe1, keyframe2 },
{
keyframe1,
keyframe2
},
IterationCount = new IterationCount(5), IterationCount = new IterationCount(5),
PlaybackDirection = PlaybackDirection.Alternate, PlaybackDirection = PlaybackDirection.Alternate,
Easing = Easing.Parse("0.1123555056179775,0.657303370786517,0.8370786516853934,0.499999999999999999") Easing = Easing.Parse(
"0.1123555056179775,0.657303370786517,0.8370786516853934,0.499999999999999999")
}; };
var rotateTransform = new RotateTransform(-2.5); var rotateTransform = new RotateTransform(-2.5);
var rect = new Rectangle() var rect = new Rectangle() { RenderTransform = rotateTransform };
{
RenderTransform = rotateTransform
};
var clock = new TestClock(); var clock = new TestClock();
var animationRun = animation.RunAsync(rect, clock);
animation.RunAsync(rect, clock);
// position is what you'd expect at end and beginning // position is what you'd expect at end and beginning
clock.Step(TimeSpan.Zero); clock.Step(TimeSpan.Zero);

34
tests/Avalonia.Base.UnitTests/Animation/SpringTests.cs

@ -18,6 +18,7 @@ public class SpringTests
var spring = (Spring)conv.ConvertFrom(input); var spring = (Spring)conv.ConvertFrom(input);
Assert.NotNull(spring);
Assert.Equal(1, spring.Mass); Assert.Equal(1, spring.Mass);
Assert.Equal(2, spring.Stiffness); Assert.Equal(2, spring.Stiffness);
Assert.Equal(3, spring.Damping); Assert.Equal(3, spring.Damping);
@ -25,8 +26,8 @@ public class SpringTests
} }
[Theory] [Theory]
[InlineData("1,2F,3,4")] [InlineData("1,2F,3,4")]
[InlineData("Foo,Bar,Fee,Buzz")] [InlineData("Foo,Bar,Fee,Buzz")]
public void Can_Handle_Invalid_String_Via_TypeConverter(string input) public void Can_Handle_Invalid_String_Via_TypeConverter(string input)
{ {
var conv = new SpringTypeConverter(); var conv = new SpringTypeConverter();
@ -37,7 +38,7 @@ public class SpringTests
[Fact] [Fact]
public void SplineEasing_Can_Be_Mutated() public void SplineEasing_Can_Be_Mutated()
{ {
var easing = new SpringEasing(1, 1, 1, 0); var easing = new SpringEasing(1, 1, 1);
Assert.Equal(0, easing.Ease(0)); Assert.Equal(0, easing.Ease(0));
Assert.Equal(0.34029984660829826, easing.Ease(1)); Assert.Equal(0.34029984660829826, easing.Ease(1));
@ -55,43 +56,28 @@ public class SpringTests
{ {
var keyframe1 = new KeyFrame() var keyframe1 = new KeyFrame()
{ {
Setters = Setters = { new Setter(RotateTransform.AngleProperty, -2.5d), }, KeyTime = TimeSpan.FromSeconds(0)
{
new Setter(RotateTransform.AngleProperty, -2.5d),
},
KeyTime = TimeSpan.FromSeconds(0)
}; };
var keyframe2 = new KeyFrame() var keyframe2 = new KeyFrame()
{ {
Setters = Setters = { new Setter(RotateTransform.AngleProperty, 2.5d), }, KeyTime = TimeSpan.FromSeconds(5)
{
new Setter(RotateTransform.AngleProperty, 2.5d),
},
KeyTime = TimeSpan.FromSeconds(5)
}; };
var animation = new Avalonia.Animation.Animation() var animation = new Avalonia.Animation.Animation()
{ {
Duration = TimeSpan.FromSeconds(5), Duration = TimeSpan.FromSeconds(5),
Children = Children = { keyframe1, keyframe2 },
{
keyframe1,
keyframe2
},
IterationCount = new IterationCount(5), IterationCount = new IterationCount(5),
PlaybackDirection = PlaybackDirection.Alternate, PlaybackDirection = PlaybackDirection.Alternate,
Easing = new SpringEasing(1, 10, 1, 0) Easing = new SpringEasing(1, 10, 1)
}; };
var rotateTransform = new RotateTransform(-2.5); var rotateTransform = new RotateTransform(-2.5);
var rect = new Rectangle() var rect = new Rectangle() { RenderTransform = rotateTransform };
{
RenderTransform = rotateTransform
};
var clock = new TestClock(); var clock = new TestClock();
var animationRun = animation.RunAsync(rect, clock); animation.RunAsync(rect, clock);
clock.Step(TimeSpan.Zero); clock.Step(TimeSpan.Zero);
Assert.Equal(rotateTransform.Angle, -2.5); Assert.Equal(rotateTransform.Angle, -2.5);

94
tests/Avalonia.Base.UnitTests/Animation/TransitionsTests.cs

@ -15,13 +15,12 @@ namespace Avalonia.Base.UnitTests.Animation
var border = new Border var border = new Border
{ {
Transitions = new Transitions Transitions = new Transitions
{
new DoubleTransition
{ {
new DoubleTransition Duration = TimeSpan.FromSeconds(1), Property = Visual.OpacityProperty,
{
Duration = TimeSpan.FromSeconds(1),
Property = Border.OpacityProperty,
}
} }
}
}; };
border.Opacity = 0; border.Opacity = 0;
@ -30,7 +29,6 @@ namespace Avalonia.Base.UnitTests.Animation
clock.Pulse(TimeSpan.FromSeconds(-0.5)); clock.Pulse(TimeSpan.FromSeconds(-0.5));
Assert.Equal(0, border.Opacity); Assert.Equal(0, border.Opacity);
} }
[Fact] [Fact]
@ -41,13 +39,12 @@ namespace Avalonia.Base.UnitTests.Animation
var border = new Border var border = new Border
{ {
Transitions = new Transitions Transitions = new Transitions
{
new DoubleTransition
{ {
new DoubleTransition Duration = TimeSpan.FromSeconds(1), Property = Visual.OpacityProperty,
{
Duration = TimeSpan.FromSeconds(1),
Property = Border.OpacityProperty,
}
} }
}
}; };
border.Opacity = 0; border.Opacity = 0;
@ -56,7 +53,6 @@ namespace Avalonia.Base.UnitTests.Animation
clock.Pulse(TimeSpan.FromMilliseconds(1001)); clock.Pulse(TimeSpan.FromMilliseconds(1001));
Assert.Equal(0, border.Opacity); Assert.Equal(0, border.Opacity);
} }
[Fact] [Fact]
@ -64,18 +60,22 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
var clock = new TestClock(); var clock = new TestClock();
int i = 0; var i = 0;
var inst = new TransitionInstance(clock, TimeSpan.Zero, TimeSpan.Zero).Subscribe(nextValue =>
new TransitionInstance(clock, TimeSpan.Zero, TimeSpan.Zero).Subscribe(nextValue =>
{ {
switch (i++) switch (i++)
{ {
case 0: Assert.Equal(0, nextValue); break; case 0:
case 1: Assert.Equal(1d, nextValue); break; Assert.Equal(0, nextValue);
break;
case 1:
Assert.Equal(1d, nextValue);
break;
} }
}); });
clock.Pulse(TimeSpan.FromMilliseconds(10)); clock.Pulse(TimeSpan.FromMilliseconds(10));
} }
[Fact] [Fact]
@ -83,26 +83,50 @@ namespace Avalonia.Base.UnitTests.Animation
{ {
var clock = new TestClock(); var clock = new TestClock();
int i = -1; var i = -1;
var inst = new TransitionInstance(clock, TimeSpan.FromMilliseconds(30), TimeSpan.FromMilliseconds(70)).Subscribe(nextValue =>
{ new TransitionInstance(clock, TimeSpan.FromMilliseconds(30), TimeSpan.FromMilliseconds(70)).Subscribe(
switch (i++) nextValue =>
{ {
case 0: Assert.Equal(0, nextValue); break; switch (i++)
case 1: Assert.Equal(0, nextValue); break; {
case 2: Assert.Equal(0, nextValue); break; case 0:
case 3: Assert.Equal(0, nextValue); break; Assert.Equal(0, nextValue);
case 4: Assert.Equal(Math.Round(10d / 70d, 4), Math.Round(nextValue, 4)); break; break;
case 5: Assert.Equal(Math.Round(20d / 70d, 4), Math.Round(nextValue, 4)); break; case 1:
case 6: Assert.Equal(Math.Round(30d / 70d, 4), Math.Round(nextValue, 4)); break; Assert.Equal(0, nextValue);
case 7: Assert.Equal(Math.Round(40d / 70d, 4), Math.Round(nextValue, 4)); break; break;
case 8: Assert.Equal(Math.Round(50d / 70d, 4), Math.Round(nextValue, 4)); break; case 2:
case 9: Assert.Equal(Math.Round(60d / 70d, 4), Math.Round(nextValue, 4)); break; Assert.Equal(0, nextValue);
case 10: Assert.Equal(1d, nextValue); break; break;
} case 3:
}); Assert.Equal(0, nextValue);
break;
case 4:
Assert.Equal(Math.Round(10d / 70d, 4), Math.Round(nextValue, 4));
break;
case 5:
Assert.Equal(Math.Round(20d / 70d, 4), Math.Round(nextValue, 4));
break;
case 6:
Assert.Equal(Math.Round(30d / 70d, 4), Math.Round(nextValue, 4));
break;
case 7:
Assert.Equal(Math.Round(40d / 70d, 4), Math.Round(nextValue, 4));
break;
case 8:
Assert.Equal(Math.Round(50d / 70d, 4), Math.Round(nextValue, 4));
break;
case 9:
Assert.Equal(Math.Round(60d / 70d, 4), Math.Round(nextValue, 4));
break;
case 10:
Assert.Equal(1d, nextValue);
break;
}
});
for (int z = 0; z <= 10; z++) for (var z = 0; z <= 10; z++)
{ {
clock.Pulse(TimeSpan.FromMilliseconds(10)); clock.Pulse(TimeSpan.FromMilliseconds(10));
} }

24
tests/Avalonia.Base.UnitTests/Data/ReflectionClrPropertyInfoTests.cs

@ -0,0 +1,24 @@
using Avalonia.Data.Core;
using Xunit;
namespace Avalonia.Base.UnitTests.Data;
public class ReflectionClrPropertyInfoTests
{
public class TestClass
{
public string Test { get; set; }
}
[Fact]
public void Can_Compile()
{
var propertyInfo = new ReflectionClrPropertyInfo(
typeof(TestClass).GetProperty(nameof(TestClass.Test))!);
var target = new TestClass();
const string result = "qwerty";
propertyInfo.Set(target, result);
Assert.Equal(result, target.Test);
Assert.Equal(result, (string)propertyInfo.Get(target));
}
}

205
tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs

@ -0,0 +1,205 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.UnitTests;
using Moq;
using Xunit;
namespace Avalonia.Base.UnitTests.Input
{
public class AccessKeyHandlerTests
{
[Fact]
public void Should_Raise_Key_Events_For_Unregistered_Access_Key()
{
var root = new TestRoot();
var target = new AccessKeyHandler();
var events = new List<string>();
target.SetOwner(root);
root.KeyDown += (s, e) => events.Add($"KeyDown {e.Key}");
root.KeyUp += (s, e) => events.Add($"KeyUp {e.Key}");
KeyDown(root, Key.LeftAlt);
KeyDown(root, Key.A, KeyModifiers.Alt);
KeyUp(root, Key.A, KeyModifiers.Alt);
KeyUp(root, Key.LeftAlt);
Assert.Equal(new[]
{
"KeyDown LeftAlt",
"KeyDown A",
"KeyUp A",
"KeyUp LeftAlt",
}, events);
}
[Fact]
public void Should_Raise_Key_Events_For_Unregistered_Access_Key_With_MainMenu()
{
var root = new TestRoot();
var target = new AccessKeyHandler();
var menu = Mock.Of<IMainMenu>();
var events = new List<string>();
target.SetOwner(root);
target.MainMenu = menu;
root.KeyDown += (s, e) => events.Add($"KeyDown {e.Key}");
root.KeyUp += (s, e) => events.Add($"KeyUp {e.Key}");
KeyDown(root, Key.LeftAlt);
KeyDown(root, Key.A, KeyModifiers.Alt);
KeyUp(root, Key.A, KeyModifiers.Alt);
KeyUp(root, Key.LeftAlt);
Assert.Equal(new[]
{
"KeyDown LeftAlt",
"KeyDown A",
"KeyUp A",
"KeyUp LeftAlt",
}, events);
}
[Fact]
public void Should_Raise_Key_Events_For_Alt_Key()
{
var root = new TestRoot();
var target = new AccessKeyHandler();
var events = new List<string>();
target.SetOwner(root);
root.KeyDown += (s, e) => events.Add($"KeyDown {e.Key}");
root.KeyUp += (s, e) => events.Add($"KeyUp {e.Key}");
KeyDown(root, Key.LeftAlt);
KeyUp(root, Key.LeftAlt);
Assert.Equal(new[]
{
"KeyDown LeftAlt",
"KeyUp LeftAlt",
}, events);
}
[Fact]
public void Should_Raise_Key_Events_For_Alt_Key_With_MainMenu()
{
var root = new TestRoot();
var target = new AccessKeyHandler();
var menu = new Mock<IMainMenu>();
var events = new List<string>();
menu.SetupAllProperties();
menu.Setup(x => x.Open()).Callback(() => menu.Setup(x => x.IsOpen).Returns(true));
target.SetOwner(root);
target.MainMenu = menu.Object;
root.KeyDown += (s, e) => events.Add($"KeyDown {e.Key}");
root.KeyUp += (s, e) => events.Add($"KeyUp {e.Key}");
KeyDown(root, Key.LeftAlt);
KeyUp(root, Key.LeftAlt);
KeyDown(root, Key.LeftAlt);
KeyUp(root, Key.LeftAlt);
Assert.Equal(new[]
{
"KeyDown LeftAlt",
"KeyUp LeftAlt",
"KeyDown LeftAlt",
"KeyUp LeftAlt",
}, events);
}
[Fact]
public void Should_Raise_Key_Events_For_Registered_Access_Key()
{
var button = new Button();
var root = new TestRoot(button);
var target = new AccessKeyHandler();
var events = new List<string>();
target.SetOwner(root);
target.Register('A', button);
root.KeyDown += (s, e) => events.Add($"KeyDown {e.Key}");
root.KeyUp += (s, e) => events.Add($"KeyUp {e.Key}");
KeyDown(root, Key.LeftAlt);
KeyDown(root, Key.A, KeyModifiers.Alt);
KeyUp(root, Key.A, KeyModifiers.Alt);
KeyUp(root, Key.LeftAlt);
// This differs from WPF which doesn't raise the `A` key event, but matches UWP.
Assert.Equal(new[]
{
"KeyDown LeftAlt",
"KeyDown A",
"KeyUp A",
"KeyUp LeftAlt",
}, events);
}
[Fact]
public void Should_Raise_AccessKeyPressed_For_Registered_Access_Key()
{
var button = new Button();
var root = new TestRoot(button);
var target = new AccessKeyHandler();
var raised = 0;
target.SetOwner(root);
target.Register('A', button);
button.AddHandler(AccessKeyHandler.AccessKeyPressedEvent, (s, e) => ++raised);
KeyDown(root, Key.LeftAlt);
Assert.Equal(0, raised);
KeyDown(root, Key.A, KeyModifiers.Alt);
Assert.Equal(1, raised);
KeyUp(root, Key.A, KeyModifiers.Alt);
KeyUp(root, Key.LeftAlt);
Assert.Equal(1, raised);
}
[Fact]
public void Should_Open_MainMenu_On_Alt_KeyUp()
{
var root = new TestRoot();
var target = new AccessKeyHandler();
var menu = new Mock<IMainMenu>();
target.SetOwner(root);
target.MainMenu = menu.Object;
KeyDown(root, Key.LeftAlt);
menu.Verify(x => x.Open(), Times.Never);
KeyUp(root, Key.LeftAlt);
menu.Verify(x => x.Open(), Times.Once);
}
private static void KeyDown(IInputElement target, Key key, KeyModifiers modifiers = KeyModifiers.None)
{
target.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = key,
KeyModifiers = modifiers,
});
}
private static void KeyUp(IInputElement target, Key key, KeyModifiers modifiers = KeyModifiers.None)
{
target.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyUpEvent,
Key = key,
KeyModifiers = modifiers,
});
}
}
}

37
tests/Avalonia.Headless.UnitTests/RenderingTests.cs

@ -1,4 +1,5 @@
using Avalonia.Controls; using System.Collections.ObjectModel;
using Avalonia.Controls;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading; using Avalonia.Threading;
@ -28,6 +29,40 @@ public class RenderingTests
}, },
SizeToContent = SizeToContent.WidthAndHeight SizeToContent = SizeToContent.WidthAndHeight
}; };
window.Show();
var frame = window.CaptureRenderedFrame();
Assert.NotNull(frame);
}
#if NUNIT
[AvaloniaTest, Timeout(10000)]
#elif XUNIT
[AvaloniaFact(Timeout = 10000)]
#endif
public void Should_Not_Hang_With_Non_Trivial_Layout()
{
var window = new Window
{
Content = new ContentControl
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
Padding = new Thickness(1),
Content = new ListBox
{
ItemsSource = new ObservableCollection<string>()
{
"Test 1",
"Test 2"
}
}
},
SizeToContent = SizeToContent.WidthAndHeight
};
window.Show(); window.Show();
var frame = window.CaptureRenderedFrame(); var frame = window.CaptureRenderedFrame();

Loading…
Cancel
Save