From 1c258e768091c83e60c61fae520b218d551c7aa1 Mon Sep 17 00:00:00 2001 From: Denis Zubritskiy Date: Mon, 5 Jun 2023 12:18:56 +0300 Subject: [PATCH 01/11] Fix expression in CreateGetter --- .../Data/Core/ClrPropertyInfo.cs | 3 ++- .../Data/ReflectionClrPropertyInfoTests.cs | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.Base.UnitTests/Data/ReflectionClrPropertyInfoTests.cs diff --git a/src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs b/src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs index 24149c17e0..6027676501 100644 --- a/src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs +++ b/src/Avalonia.Base/Data/Core/ClrPropertyInfo.cs @@ -60,7 +60,8 @@ namespace Avalonia.Data.Core var target = Expression.Parameter(typeof(object), "target"); return Expression.Lambda>( Expression.Convert(Expression.Call(Expression.Convert(target, info.DeclaringType!), info.GetMethod), - typeof(object))) + typeof(object)), + target) .Compile(); } diff --git a/tests/Avalonia.Base.UnitTests/Data/ReflectionClrPropertyInfoTests.cs b/tests/Avalonia.Base.UnitTests/Data/ReflectionClrPropertyInfoTests.cs new file mode 100644 index 0000000000..5c51dd0c06 --- /dev/null +++ b/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)); + } +} From 26d47460967501bebb0790dd68e6576919b5e439 Mon Sep 17 00:00:00 2001 From: jankrib Date: Tue, 13 Jun 2023 13:35:13 +0200 Subject: [PATCH 02/11] Break layout flip flop in headless --- .../Avalonia.Headless/HeadlessWindowImpl.cs | 13 ++++++---- .../RenderingTests.cs | 24 ++++++++++++++++++- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index 84aca6e94f..c7eb07ba10 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -114,23 +114,26 @@ namespace Avalonia.Headless public Size MaxClientSize { get; } = new Size(1920, 1280); public void Resize(Size clientSize, WindowResizeReason reason) { + if (ClientSize == clientSize) + return; + // Emulate X11 behavior here if (IsPopup) - DoResize(clientSize); + DoResize(clientSize, reason); else 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 if (ClientSize != clientSize) { ClientSize = clientSize; - Resized?.Invoke(clientSize, WindowResizeReason.Unspecified); + Resized?.Invoke(clientSize, reason); } } diff --git a/tests/Avalonia.Headless.UnitTests/RenderingTests.cs b/tests/Avalonia.Headless.UnitTests/RenderingTests.cs index 3f45bf97e4..cda459e7f9 100644 --- a/tests/Avalonia.Headless.UnitTests/RenderingTests.cs +++ b/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.Media; using Avalonia.Threading; @@ -24,6 +25,27 @@ public class RenderingTests Content = new PathIcon { Data = StreamGeometry.Parse("M0,9 L10,0 20,9 19,10 10,2 1,10 z") +#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() + { + "Test 1", + "Test 2" + } } }, SizeToContent = SizeToContent.WidthAndHeight From 682b6746258ded4b13bcb46560b0e2b4f6078890 Mon Sep 17 00:00:00 2001 From: jankrib Date: Tue, 13 Jun 2023 13:47:22 +0200 Subject: [PATCH 03/11] Fix merge issue in test --- tests/Avalonia.Headless.UnitTests/RenderingTests.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/Avalonia.Headless.UnitTests/RenderingTests.cs b/tests/Avalonia.Headless.UnitTests/RenderingTests.cs index cda459e7f9..591863bcdb 100644 --- a/tests/Avalonia.Headless.UnitTests/RenderingTests.cs +++ b/tests/Avalonia.Headless.UnitTests/RenderingTests.cs @@ -25,6 +25,18 @@ public class RenderingTests Content = new PathIcon { Data = StreamGeometry.Parse("M0,9 L10,0 20,9 19,10 10,2 1,10 z") + } + }, + SizeToContent = SizeToContent.WidthAndHeight + }; + + window.Show(); + + var frame = window.CaptureRenderedFrame(); + + Assert.NotNull(frame); + } + #if NUNIT [AvaloniaTest, Timeout(10000)] #elif XUNIT @@ -50,6 +62,7 @@ public class RenderingTests }, SizeToContent = SizeToContent.WidthAndHeight }; + window.Show(); var frame = window.CaptureRenderedFrame(); From 8e9532a580b41bfc71cec7a7dd422a0eba1b71f8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 15 Jun 2023 11:28:21 +0200 Subject: [PATCH 04/11] Added tests for AccessKeyHandler. Some failing: tests are chosen to match UWP behavior, not WPF (which hides key press events for access keys). --- .../Input/AccessKeyHandlerTests.cs | 205 ++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs diff --git a/tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs b/tests/Avalonia.Base.UnitTests/Input/AccessKeyHandlerTests.cs new file mode 100644 index 0000000000..4d8ece4dda --- /dev/null +++ b/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(); + + 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(); + var events = new List(); + + 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(); + + 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(); + var events = new List(); + + 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(); + + 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(); + + 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, + }); + } + } +} From ca22dd8783f253fd489fd7c48f485e265922c5f6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 15 Jun 2023 11:50:54 +0200 Subject: [PATCH 05/11] Get platform settings from TopLevel. Allows easier unit testing. --- src/Avalonia.Controls/Control.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 6e0063c9ec..ae7d4f3fb4 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -480,7 +480,7 @@ namespace Avalonia.Controls if (e.Source == this && !e.Handled) { - var keymap = Application.Current!.PlatformSettings?.HotkeyConfiguration.OpenContextMenu; + var keymap = TopLevel.GetTopLevel(this)?.PlatformSettings?.HotkeyConfiguration.OpenContextMenu; if (keymap is null) { From 001c81d1afb6eb7f74706d50cc073d7dd615acc1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 15 Jun 2023 11:55:41 +0200 Subject: [PATCH 06/11] Don't mark events as handled in AccessKeyHandler. Fixes #11633 --- src/Avalonia.Base/Input/AccessKeyHandler.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Avalonia.Base/Input/AccessKeyHandler.cs b/src/Avalonia.Base/Input/AccessKeyHandler.cs index 2e0268a644..38692d8b77 100644 --- a/src/Avalonia.Base/Input/AccessKeyHandler.cs +++ b/src/Avalonia.Base/Input/AccessKeyHandler.cs @@ -159,8 +159,6 @@ namespace Avalonia.Input _restoreFocusElement?.Focus(); _restoreFocusElement = null; - - e.Handled = true; } } else if (_altIsDown) @@ -200,7 +198,6 @@ namespace Avalonia.Input if (match is not null) { match.RaiseEvent(new RoutedEventArgs(AccessKeyPressedEvent)); - e.Handled = true; } } } @@ -225,7 +222,6 @@ namespace Avalonia.Input else if (_showingAccessKeys && MainMenu != null) { MainMenu.Open(); - e.Handled = true; } break; From f8a767f06980085c38badf9cfe61ce40d3a90fc1 Mon Sep 17 00:00:00 2001 From: JuliaEmp Date: Thu, 15 Jun 2023 15:44:05 +0200 Subject: [PATCH 07/11] set Disable App Delegate before Initialize --- src/Avalonia.Native/AvaloniaNativePlatform.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index b32382084d..5d5e17839f 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -88,14 +88,17 @@ namespace Avalonia.Native var applicationPlatform = new AvaloniaNativeApplicationPlatform(); + var macOpts = AvaloniaLocator.Current.GetService() ?? new MacOSPlatformOptions(); + + if (_factory.MacOptions != null) + _factory.MacOptions.SetDisableAppDelegate(macOpts.DisableAvaloniaAppDelegate ? 1 : 0); + _factory.Initialize(new GCHandleDeallocator(), applicationPlatform); + if (_factory.MacOptions != null) { - var macOpts = AvaloniaLocator.Current.GetService() ?? new MacOSPlatformOptions(); - _factory.MacOptions.SetShowInDock(macOpts.ShowInDock ? 1 : 0); _factory.MacOptions.SetDisableSetProcessName(macOpts.DisableSetProcessName ? 1 : 0); - _factory.MacOptions.SetDisableAppDelegate(macOpts.DisableAvaloniaAppDelegate ? 1 : 0); } AvaloniaLocator.CurrentMutable From 69941d8fc7402c4370f782741f581589574d98b8 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Thu, 15 Jun 2023 22:02:20 +0800 Subject: [PATCH 08/11] Fix and remove warnings on Animations code --- .../Animation/AnimatableTests.cs | 163 +++++------- .../Animation/AnimationIterationTests.cs | 241 ++++++------------ .../Animation/BrushTransitionTests.cs | 6 +- .../Animation/KeySplineTests.cs | 68 ++--- .../Animation/SpringTests.cs | 34 +-- .../Animation/TransitionsTests.cs | 94 ++++--- 6 files changed, 232 insertions(+), 374 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs index d81c6e75f3..b27535b201 100644 --- a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs +++ b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs @@ -19,18 +19,15 @@ namespace Avalonia.Base.UnitTests.Animation public void Transition_Is_Not_Applied_When_Not_Attached_To_Visual_Tree() { var target = CreateTarget(); - var control = new Control - { - Transitions = new Transitions { target.Object }, - }; + var control = new Control { Transitions = new Transitions { target.Object }, }; control.Opacity = 0.5; target.Verify(x => x.Apply( - control, - It.IsAny(), - 1.0, - 0.5), + control, + It.IsAny(), + 1.0, + 0.5), Times.Never); } @@ -40,10 +37,7 @@ namespace Avalonia.Base.UnitTests.Animation using (Start()) { var target = CreateTarget(); - var control = new Control - { - Transitions = new Transitions { target.Object }, - }; + var control = new Control { Transitions = new Transitions { target.Object }, }; var root = new TestRoot { @@ -51,10 +45,7 @@ namespace Avalonia.Base.UnitTests.Animation { new Style(x => x.OfType()) { - Setters = - { - new Setter(Visual.OpacityProperty, 0.8), - } + Setters = { new Setter(Visual.OpacityProperty, 0.8), } } } }; @@ -64,10 +55,10 @@ namespace Avalonia.Base.UnitTests.Animation Assert.Equal(0.8, control.Opacity); target.Verify(x => x.Apply( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny()), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Never); } } @@ -97,10 +88,10 @@ namespace Avalonia.Base.UnitTests.Animation control.SetValue(Visual.OpacityProperty, 0.5, BindingPriority.Animation); target.Verify(x => x.Apply( - control, - It.IsAny(), - 1.0, - 0.5), + control, + It.IsAny(), + 1.0, + 0.5), Times.Never); } @@ -112,53 +103,34 @@ namespace Avalonia.Base.UnitTests.Animation { var keyframe1 = new KeyFrame() { - Setters = - { - new Setter(Layoutable.WidthProperty, 1d), - }, - KeyTime = TimeSpan.FromSeconds(0) + Setters = { new Setter(Layoutable.WidthProperty, 1d), }, KeyTime = TimeSpan.FromSeconds(0) }; var keyframe2 = new KeyFrame() { - Setters = - { - new Setter(Layoutable.WidthProperty, 2d), - }, - KeyTime = TimeSpan.FromSeconds(2), + Setters = { new Setter(Layoutable.WidthProperty, 2d), }, KeyTime = TimeSpan.FromSeconds(2), }; var keyframe3 = new KeyFrame() { - Setters = - { - new Setter(Layoutable.WidthProperty, invalidValue), - }, + Setters = { new Setter(Layoutable.WidthProperty, invalidValue), }, KeyTime = TimeSpan.FromSeconds(3), }; var animation = new Avalonia.Animation.Animation() { Duration = TimeSpan.FromSeconds(3), - Children = - { - keyframe1, - keyframe2, - keyframe3 - }, + Children = { keyframe1, keyframe2, keyframe3 }, IterationCount = new IterationCount(5), PlaybackDirection = PlaybackDirection.Alternate, }; - var rect = new Rectangle() - { - Width = 11, - }; + var rect = new Rectangle() { Width = 11, }; var originalValue = rect.Width; var clock = new TestClock(); - var animationRun = animation.RunAsync(rect, clock); + animation.RunAsync(rect, clock); clock.Step(TimeSpan.Zero); Assert.Equal(rect.Width, 1); @@ -188,10 +160,10 @@ namespace Avalonia.Base.UnitTests.Animation control.SetValue(Visual.OpacityProperty, 0.8, BindingPriority.StyleTrigger); target.Verify(x => x.Apply( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny()), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Never); } @@ -258,14 +230,15 @@ namespace Avalonia.Base.UnitTests.Animation target.Invocations.Clear(); var root = (TestRoot)control.Parent; + Assert.NotNull(root); root.Child = null; control.Opacity = 0.8; target.Verify(x => x.Apply( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny()), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny()), Times.Never); } @@ -284,6 +257,8 @@ namespace Avalonia.Base.UnitTests.Animation It.IsAny())).Returns(sub.Object); control.Opacity = 0.5; + Assert.NotNull(control.Transitions); + control.Transitions.RemoveAt(0); sub.Verify(x => x.Dispose()); @@ -307,10 +282,10 @@ namespace Avalonia.Base.UnitTests.Animation control.Opacity = 0.5; target.Verify(x => x.Apply( - control, - It.IsAny(), - 1.0, - 0.5), + control, + It.IsAny(), + 1.0, + 0.5), Times.Once); control.Classes.Add("foo"); @@ -324,18 +299,17 @@ namespace Avalonia.Base.UnitTests.Animation { using (Start()) { - var target = CreateTransition(Control.WidthProperty); + var target = CreateTransition(Layoutable.WidthProperty); var control = CreateStyledControl(transition2: target.Object); - var sub = new Mock(); control.Classes.Add("foo"); control.Width = 100; target.Verify(x => x.Apply( - control, - It.IsAny(), - double.NaN, - 100.0), + control, + It.IsAny(), + double.NaN, + 100.0), Times.Once); } } @@ -357,12 +331,12 @@ namespace Avalonia.Base.UnitTests.Animation { Setters = { - new Setter(Border.TransitionsProperty, + new Setter(Animatable.TransitionsProperty, new Transitions { new DoubleTransition { - Property = Border.OpacityProperty, + Property = Visual.OpacityProperty, Duration = TimeSpan.FromSeconds(1), }, }), @@ -372,23 +346,20 @@ namespace Avalonia.Base.UnitTests.Animation { Setters = { - new Setter(Border.TransitionsProperty, + new Setter(Animatable.TransitionsProperty, new Transitions { new DoubleTransition { - Property = Border.OpacityProperty, + Property = Visual.OpacityProperty, Duration = TimeSpan.FromSeconds(1), }, }), - new Setter(Border.OpacityProperty, 0.0), + new Setter(Visual.OpacityProperty, 0.0), }, }, }, - Child = target = new Border - { - Background = Brushes.Red, - } + Child = target = new Border { Background = Brushes.Red, } }; root.Measure(Size.Infinity); @@ -421,7 +392,7 @@ namespace Avalonia.Base.UnitTests.Animation // Assigning and then clearing Transitions ensures we have a transition state // collection created. - control.ClearValue(Control.TransitionsProperty); + control.ClearValue(Animatable.TransitionsProperty); control.GetValueStore().BeginStyling(); @@ -431,8 +402,8 @@ namespace Avalonia.Base.UnitTests.Animation { Setters = { - new Setter(Control.OpacityProperty, 0.5), - new Setter(Control.TransitionsProperty, new Transitions { target.Object }), + new Setter(Visual.OpacityProperty, 0.5), + new Setter(Animatable.TransitionsProperty, new Transitions { target.Object }), } }; @@ -450,28 +421,17 @@ namespace Avalonia.Base.UnitTests.Animation var opacityTransition = new DoubleTransition { - Property = Control.OpacityProperty, - Duration = TimeSpan.FromSeconds(1), + Property = Visual.OpacityProperty, Duration = TimeSpan.FromSeconds(1), }; var transitions = new Transitions { opacityTransition }; var borderTheme = new ControlTheme(typeof(Border)) { - Setters = - { - new Setter(Control.TransitionsProperty, transitions), - } + Setters = { new Setter(Animatable.TransitionsProperty, transitions), } }; var clock = new TestClock(); - var root = new TestRoot - { - Clock = clock, - Resources = - { - { typeof(Border), borderTheme }, - } - }; + var root = new TestRoot { Clock = clock, Resources = { { typeof(Border), borderTheme }, } }; var border = new Border(); root.Child = border; @@ -489,7 +449,7 @@ namespace Avalonia.Base.UnitTests.Animation // Now clear the property; a transition is now in progress but no local value is // set. - border.ClearValue(Border.OpacityProperty); + border.ClearValue(Visual.OpacityProperty); // Remove the transition by removing the control from the logical tree. This was // causing an exception. @@ -510,12 +470,9 @@ namespace Avalonia.Base.UnitTests.Animation private static Control CreateControl(ITransition transition) { - var control = new Control - { - Transitions = new Transitions { transition }, - }; + var control = new Control { Transitions = new Transitions { transition }, }; - var root = new TestRoot(control); + var _ = new TestRoot(control); return control; } @@ -524,7 +481,7 @@ namespace Avalonia.Base.UnitTests.Animation ITransition transition2 = null) { transition1 = transition1 ?? CreateTarget().Object; - transition2 = transition2 ?? CreateTransition(Control.WidthProperty).Object; + transition2 = transition2 ?? CreateTransition(Layoutable.WidthProperty).Object; var control = new Control { @@ -536,7 +493,7 @@ namespace Avalonia.Base.UnitTests.Animation { new Setter { - Property = Control.TransitionsProperty, + Property = Animatable.TransitionsProperty, Value = new Transitions { transition1 }, } } @@ -547,7 +504,7 @@ namespace Avalonia.Base.UnitTests.Animation { new Setter { - Property = Control.TransitionsProperty, + Property = Animatable.TransitionsProperty, Value = new Transitions { transition2 }, } } @@ -555,7 +512,7 @@ namespace Avalonia.Base.UnitTests.Animation } }; - var root = new TestRoot(control); + var _ = new TestRoot(control); return control; } diff --git a/tests/Avalonia.Base.UnitTests/Animation/AnimationIterationTests.cs b/tests/Avalonia.Base.UnitTests/Animation/AnimationIterationTests.cs index 58e908aca9..dde59365a1 100644 --- a/tests/Avalonia.Base.UnitTests/Animation/AnimationIterationTests.cs +++ b/tests/Avalonia.Base.UnitTests/Animation/AnimationIterationTests.cs @@ -1,16 +1,13 @@ using System; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Styling; -using Avalonia.UnitTests; -using Avalonia.Data; using Xunit; using Avalonia.Animation.Easings; using System.Threading; using System.Reactive.Linq; +using Avalonia.Layout; namespace Avalonia.Base.UnitTests.Animation { @@ -23,47 +20,27 @@ namespace Avalonia.Base.UnitTests.Animation { var keyframe1 = new KeyFrame() { - Setters = - { - new Setter(Border.WidthProperty, 100d), - }, - KeyTime = TimeSpan.FromSeconds(0.5) + Setters = { new Setter(Layoutable.WidthProperty, 100d), }, KeyTime = TimeSpan.FromSeconds(0.5) }; var keyframe2 = new KeyFrame() { - Setters = - { - new Setter(Border.WidthProperty, 0d), - }, - KeyTime = TimeSpan.FromSeconds(0) + Setters = { new Setter(Layoutable.WidthProperty, 0d), }, KeyTime = TimeSpan.FromSeconds(0) }; - var animation = new Avalonia.Animation.Animation() - { - Duration = TimeSpan.FromSeconds(1), - Children = - { - keyframe2, - keyframe1 - } - }; + var animation = new Animation() { Duration = TimeSpan.FromSeconds(1), Children = { keyframe2, keyframe1 } }; - var border = new Border() - { - Height = 100d, - Width = 100d - }; + var border = new Border() { Height = 100d, Width = 100d }; 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); - clock.Step(TimeSpan.FromSeconds(1)); + clock.Step(TimeSpan.FromSeconds(1)); Assert.Equal(border.Width, 100d); - } @@ -72,40 +49,24 @@ namespace Avalonia.Base.UnitTests.Animation { var keyframe1 = new KeyFrame() { - Setters = - { - new Setter(Border.WidthProperty, 200d), - }, - Cue = new Cue(1d) + Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d) }; var keyframe2 = new KeyFrame() { - Setters = - { - new Setter(Border.WidthProperty, 100d), - }, - Cue = new Cue(0d) + Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d) }; - var animation = new Avalonia.Animation.Animation() + var animation = new Animation() { Duration = TimeSpan.FromSeconds(3), Delay = TimeSpan.FromSeconds(3), DelayBetweenIterations = TimeSpan.FromSeconds(3), IterationCount = new IterationCount(2), - Children = - { - keyframe2, - keyframe1 - } + Children = { keyframe2, keyframe1 } }; - var border = new Border() - { - Height = 100d, - Width = 100d - }; + var border = new Border() { Height = 100d, Width = 100d }; var clock = new TestClock(); var animationRun = animation.RunAsync(border, clock); @@ -133,43 +94,28 @@ namespace Avalonia.Base.UnitTests.Animation { var keyframe1 = new KeyFrame() { - Setters = - { - new Setter(Border.WidthProperty, 0d), - }, - Cue = new Cue(0.0d) + Setters = { new Setter(Layoutable.WidthProperty, 0d), }, Cue = new Cue(0.0d) }; var keyframe2 = new KeyFrame() { - Setters = - { - new Setter(Border.WidthProperty, 300d), - }, - Cue = new Cue(1.0d) + Setters = { new Setter(Layoutable.WidthProperty, 300d), }, Cue = new Cue(1.0d) }; - var animation = new Avalonia.Animation.Animation() + var animation = new Animation() { Duration = TimeSpan.FromSeconds(0.05d), Delay = TimeSpan.FromSeconds(0.05d), Easing = new SineEaseInOut(), FillMode = FillMode.Both, - Children = - { - keyframe1, - keyframe2 - } + Children = { keyframe1, keyframe2 } }; - var border = new Border() - { - Height = 100d, - Width = 100d, - }; + var border = new Border() { Height = 100d, Width = 100d, }; var clock = new TestClock(); - var animationRun = animation.RunAsync(border, clock); + + animation.RunAsync(border, clock); clock.Step(TimeSpan.FromSeconds(0d)); Assert.Equal(border.Width, 0d); @@ -186,20 +132,12 @@ namespace Avalonia.Base.UnitTests.Animation { var keyframe1 = new KeyFrame() { - Setters = - { - new Setter(Border.WidthProperty, 200d), - }, - Cue = new Cue(1d) + Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d) }; var keyframe2 = new KeyFrame() { - Setters = - { - new Setter(Border.WidthProperty, 100d), - }, - Cue = new Cue(0d) + Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d) }; var animation = new Animation() @@ -208,23 +146,15 @@ namespace Avalonia.Base.UnitTests.Animation Delay = TimeSpan.FromSeconds(0), DelayBetweenIterations = TimeSpan.FromSeconds(0), IterationCount = new IterationCount(1), - Children = - { - keyframe2, - keyframe1 - } + Children = { keyframe2, keyframe1 } }; - var border = new Border() - { - Height = 100d, - Width = 50d - }; + var border = new Border() { Height = 100d, Width = 50d }; var propertyChangedCount = 0; var animationCompletedCount = 0; - border.PropertyChanged += (sender, e) => + border.PropertyChanged += (_, e) => { - if (e.Property == Control.WidthProperty) + if (e.Property == Layoutable.WidthProperty) { propertyChangedCount++; } @@ -257,20 +187,12 @@ namespace Avalonia.Base.UnitTests.Animation { var keyframe1 = new KeyFrame() { - Setters = - { - new Setter(Border.WidthProperty, 200d), - }, - Cue = new Cue(1d) + Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d) }; var keyframe2 = new KeyFrame() { - Setters = - { - new Setter(Border.WidthProperty, 100d), - }, - Cue = new Cue(0d) + Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d) }; var animation = new Animation() @@ -279,22 +201,14 @@ namespace Avalonia.Base.UnitTests.Animation Delay = TimeSpan.FromSeconds(0), DelayBetweenIterations = TimeSpan.FromSeconds(0), IterationCount = new IterationCount(1), - Children = - { - keyframe2, - keyframe1 - } + Children = { keyframe2, keyframe1 } }; - var border = new Border() - { - Height = 100d, - Width = 100d - }; + var border = new Border() { Height = 100d, Width = 100d }; var propertyChangedCount = 0; - border.PropertyChanged += (sender, e) => + border.PropertyChanged += (_, e) => { - if (e.Property == Control.WidthProperty) + if (e.Property == Layoutable.WidthProperty) { propertyChangedCount++; } @@ -315,20 +229,12 @@ namespace Avalonia.Base.UnitTests.Animation { var keyframe1 = new KeyFrame() { - Setters = - { - new Setter(Border.WidthProperty, 200d), - }, - Cue = new Cue(1d) + Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d) }; var keyframe2 = new KeyFrame() { - Setters = - { - new Setter(Border.WidthProperty, 100d), - }, - Cue = new Cue(0d) + Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d) }; var animation = new Animation() @@ -337,22 +243,14 @@ namespace Avalonia.Base.UnitTests.Animation Delay = TimeSpan.FromSeconds(0), DelayBetweenIterations = TimeSpan.FromSeconds(0), IterationCount = new IterationCount(1), - Children = - { - keyframe2, - keyframe1 - } + Children = { keyframe2, keyframe1 } }; - var border = new Border() - { - Height = 100d, - Width = 50d - }; + var border = new Border() { Height = 100d, Width = 50d }; var propertyChangedCount = 0; - border.PropertyChanged += (sender, e) => + border.PropertyChanged += (_, e) => { - if (e.Property == Control.WidthProperty) + if (e.Property == Layoutable.WidthProperty) { propertyChangedCount++; } @@ -361,6 +259,7 @@ namespace Avalonia.Base.UnitTests.Animation var clock = new TestClock(); var cancellationTokenSource = new CancellationTokenSource(); var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token); + Assert.False(animationRun.IsCompleted); Assert.Equal(0, propertyChangedCount); @@ -381,24 +280,16 @@ namespace Avalonia.Base.UnitTests.Animation } [Fact] - public void Cancellation_Of_Completed_Animation_Does_Not_Fail() + public void Dont_Run_Infinite_Iteration_Animation_On_RunAsync_Method() { var keyframe1 = new KeyFrame() { - Setters = - { - new Setter(Border.WidthProperty, 200d), - }, - Cue = new Cue(1d) + Setters = { new Setter(Layoutable.WidthProperty, 200d), }, Cue = new Cue(1d) }; var keyframe2 = new KeyFrame() { - Setters = - { - new Setter(Border.WidthProperty, 100d), - }, - Cue = new Cue(0d) + Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d) }; var animation = new Animation() @@ -406,23 +297,47 @@ namespace Avalonia.Base.UnitTests.Animation Duration = TimeSpan.FromSeconds(10), Delay = TimeSpan.FromSeconds(0), DelayBetweenIterations = TimeSpan.FromSeconds(0), - IterationCount = new IterationCount(1), - Children = - { - keyframe2, - keyframe1 - } + IterationCount = IterationCount.Infinite, + Children = { 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, - Width = 50d + Setters = { new Setter(Layoutable.WidthProperty, 100d), }, Cue = new Cue(0d) }; + + 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; - border.PropertyChanged += (sender, e) => + border.PropertyChanged += (_, e) => { - if (e.Property == Control.WidthProperty) + if (e.Property == Layoutable.WidthProperty) { propertyChangedCount++; } diff --git a/tests/Avalonia.Base.UnitTests/Animation/BrushTransitionTests.cs b/tests/Avalonia.Base.UnitTests/Animation/BrushTransitionTests.cs index acc32fd6a2..363ecafddb 100644 --- a/tests/Avalonia.Base.UnitTests/Animation/BrushTransitionTests.cs +++ b/tests/Avalonia.Base.UnitTests/Animation/BrushTransitionTests.cs @@ -36,8 +36,7 @@ namespace Avalonia.Base.UnitTests.Animation var border = new Border() { Background = oldBrush }; BrushTransition sut = new BrushTransition { - Duration = TimeSpan.FromSeconds(1), - Property = Border.BackgroundProperty + Duration = TimeSpan.FromSeconds(1), Property = Border.BackgroundProperty }; sut.Apply(border, clock, oldBrush, newBrush); @@ -45,7 +44,8 @@ namespace Avalonia.Base.UnitTests.Animation clock.Pulse(sut.Duration * progress); 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); } } } diff --git a/tests/Avalonia.Base.UnitTests/Animation/KeySplineTests.cs b/tests/Avalonia.Base.UnitTests/Animation/KeySplineTests.cs index 3cd12d92ec..61e8103bd5 100644 --- a/tests/Avalonia.Base.UnitTests/Animation/KeySplineTests.cs +++ b/tests/Avalonia.Base.UnitTests/Animation/KeySplineTests.cs @@ -21,6 +21,8 @@ namespace Avalonia.Base.UnitTests.Animation var keySpline = (KeySpline)conv.ConvertFrom(input); + Assert.NotNull(keySpline); + Assert.Equal(1, keySpline.ControlPointX1); Assert.Equal(2, keySpline.ControlPointY1); Assert.Equal(3, keySpline.ControlPointX2); @@ -28,8 +30,8 @@ namespace Avalonia.Base.UnitTests.Animation } [Theory] - [InlineData("1,2F,3,4")] - [InlineData("Foo,Bar,Fee,Buzz")] + [InlineData("1,2F,3,4")] + [InlineData("Foo,Bar,Fee,Buzz")] public void Can_Handle_Invalid_String_KeySpline_Via_TypeConverter(string input) { var conv = new KeySplineTypeConverter(); @@ -104,46 +106,33 @@ namespace Avalonia.Base.UnitTests.Animation { var keyframe1 = new KeyFrame() { - Setters = - { - new Setter(RotateTransform.AngleProperty, -2.5d), - }, - KeyTime = TimeSpan.FromSeconds(0) + Setters = { new Setter(RotateTransform.AngleProperty, -2.5d), }, KeyTime = TimeSpan.FromSeconds(0) }; var keyframe2 = new KeyFrame() { - Setters = - { - new Setter(RotateTransform.AngleProperty, 2.5d), - }, + Setters = { new Setter(RotateTransform.AngleProperty, 2.5d), }, KeyTime = TimeSpan.FromSeconds(5), KeySpline = new KeySpline(0.1123555056179775, - 0.657303370786517, - 0.8370786516853934, - 0.499999999999999999) + 0.657303370786517, + 0.8370786516853934, + 0.499999999999999999) }; var animation = new Avalonia.Animation.Animation() { Duration = TimeSpan.FromSeconds(5), - Children = - { - keyframe1, - keyframe2 - }, + Children = { keyframe1, keyframe2 }, IterationCount = new IterationCount(5), PlaybackDirection = PlaybackDirection.Alternate }; var rotateTransform = new RotateTransform(-2.5); - var rect = new Rectangle() - { - RenderTransform = rotateTransform - }; + var rect = new Rectangle() { RenderTransform = rotateTransform }; var clock = new TestClock(); - var animationRun = animation.RunAsync(rect, clock); + + animation.RunAsync(rect, clock); // position is what you'd expect at end and beginning clock.Step(TimeSpan.Zero); @@ -169,49 +158,36 @@ namespace Avalonia.Base.UnitTests.Animation expected = 1.8016358493761722; Assert.True(Math.Abs(rotateTransform.Angle - expected) <= tolerance); } - + [Fact] public void Check_KeySpline_Parsing_Is_Correct() { var keyframe1 = new KeyFrame() { - Setters = - { - new Setter(RotateTransform.AngleProperty, -2.5d), - }, - KeyTime = TimeSpan.FromSeconds(0) + Setters = { new Setter(RotateTransform.AngleProperty, -2.5d), }, KeyTime = TimeSpan.FromSeconds(0) }; var keyframe2 = new KeyFrame() { - Setters = - { - new Setter(RotateTransform.AngleProperty, 2.5d), - }, - KeyTime = TimeSpan.FromSeconds(5), + Setters = { new Setter(RotateTransform.AngleProperty, 2.5d), }, KeyTime = TimeSpan.FromSeconds(5), }; var animation = new Avalonia.Animation.Animation() { Duration = TimeSpan.FromSeconds(5), - Children = - { - keyframe1, - keyframe2 - }, + Children = { keyframe1, keyframe2 }, IterationCount = new IterationCount(5), 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 rect = new Rectangle() - { - RenderTransform = rotateTransform - }; + var rect = new Rectangle() { RenderTransform = rotateTransform }; var clock = new TestClock(); - var animationRun = animation.RunAsync(rect, clock); + + animation.RunAsync(rect, clock); // position is what you'd expect at end and beginning clock.Step(TimeSpan.Zero); diff --git a/tests/Avalonia.Base.UnitTests/Animation/SpringTests.cs b/tests/Avalonia.Base.UnitTests/Animation/SpringTests.cs index 47c0e48033..ed2c00e63c 100644 --- a/tests/Avalonia.Base.UnitTests/Animation/SpringTests.cs +++ b/tests/Avalonia.Base.UnitTests/Animation/SpringTests.cs @@ -18,6 +18,7 @@ public class SpringTests var spring = (Spring)conv.ConvertFrom(input); + Assert.NotNull(spring); Assert.Equal(1, spring.Mass); Assert.Equal(2, spring.Stiffness); Assert.Equal(3, spring.Damping); @@ -25,8 +26,8 @@ public class SpringTests } [Theory] - [InlineData("1,2F,3,4")] - [InlineData("Foo,Bar,Fee,Buzz")] + [InlineData("1,2F,3,4")] + [InlineData("Foo,Bar,Fee,Buzz")] public void Can_Handle_Invalid_String_Via_TypeConverter(string input) { var conv = new SpringTypeConverter(); @@ -37,7 +38,7 @@ public class SpringTests [Fact] 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.34029984660829826, easing.Ease(1)); @@ -55,43 +56,28 @@ public class SpringTests { var keyframe1 = new KeyFrame() { - Setters = - { - new Setter(RotateTransform.AngleProperty, -2.5d), - }, - KeyTime = TimeSpan.FromSeconds(0) + Setters = { new Setter(RotateTransform.AngleProperty, -2.5d), }, KeyTime = TimeSpan.FromSeconds(0) }; var keyframe2 = new KeyFrame() { - Setters = - { - new Setter(RotateTransform.AngleProperty, 2.5d), - }, - KeyTime = TimeSpan.FromSeconds(5) + Setters = { new Setter(RotateTransform.AngleProperty, 2.5d), }, KeyTime = TimeSpan.FromSeconds(5) }; var animation = new Avalonia.Animation.Animation() { Duration = TimeSpan.FromSeconds(5), - Children = - { - keyframe1, - keyframe2 - }, + Children = { keyframe1, keyframe2 }, IterationCount = new IterationCount(5), PlaybackDirection = PlaybackDirection.Alternate, - Easing = new SpringEasing(1, 10, 1, 0) + Easing = new SpringEasing(1, 10, 1) }; var rotateTransform = new RotateTransform(-2.5); - var rect = new Rectangle() - { - RenderTransform = rotateTransform - }; + var rect = new Rectangle() { RenderTransform = rotateTransform }; var clock = new TestClock(); - var animationRun = animation.RunAsync(rect, clock); + animation.RunAsync(rect, clock); clock.Step(TimeSpan.Zero); Assert.Equal(rotateTransform.Angle, -2.5); diff --git a/tests/Avalonia.Base.UnitTests/Animation/TransitionsTests.cs b/tests/Avalonia.Base.UnitTests/Animation/TransitionsTests.cs index 65cf90b642..2737c2cebf 100644 --- a/tests/Avalonia.Base.UnitTests/Animation/TransitionsTests.cs +++ b/tests/Avalonia.Base.UnitTests/Animation/TransitionsTests.cs @@ -15,13 +15,12 @@ namespace Avalonia.Base.UnitTests.Animation var border = new Border { Transitions = new Transitions + { + new DoubleTransition { - new DoubleTransition - { - Duration = TimeSpan.FromSeconds(1), - Property = Border.OpacityProperty, - } + Duration = TimeSpan.FromSeconds(1), Property = Visual.OpacityProperty, } + } }; border.Opacity = 0; @@ -30,7 +29,6 @@ namespace Avalonia.Base.UnitTests.Animation clock.Pulse(TimeSpan.FromSeconds(-0.5)); Assert.Equal(0, border.Opacity); - } [Fact] @@ -41,13 +39,12 @@ namespace Avalonia.Base.UnitTests.Animation var border = new Border { Transitions = new Transitions + { + new DoubleTransition { - new DoubleTransition - { - Duration = TimeSpan.FromSeconds(1), - Property = Border.OpacityProperty, - } + Duration = TimeSpan.FromSeconds(1), Property = Visual.OpacityProperty, } + } }; border.Opacity = 0; @@ -56,7 +53,6 @@ namespace Avalonia.Base.UnitTests.Animation clock.Pulse(TimeSpan.FromMilliseconds(1001)); Assert.Equal(0, border.Opacity); - } [Fact] @@ -64,18 +60,22 @@ namespace Avalonia.Base.UnitTests.Animation { var clock = new TestClock(); - int i = 0; - var inst = new TransitionInstance(clock, TimeSpan.Zero, TimeSpan.Zero).Subscribe(nextValue => + var i = 0; + + new TransitionInstance(clock, TimeSpan.Zero, TimeSpan.Zero).Subscribe(nextValue => { switch (i++) { - case 0: Assert.Equal(0, nextValue); break; - case 1: Assert.Equal(1d, nextValue); break; + case 0: + Assert.Equal(0, nextValue); + break; + case 1: + Assert.Equal(1d, nextValue); + break; } }); clock.Pulse(TimeSpan.FromMilliseconds(10)); - } [Fact] @@ -83,26 +83,50 @@ namespace Avalonia.Base.UnitTests.Animation { var clock = new TestClock(); - int i = -1; - var inst = new TransitionInstance(clock, TimeSpan.FromMilliseconds(30), TimeSpan.FromMilliseconds(70)).Subscribe(nextValue => - { - switch (i++) + var i = -1; + + new TransitionInstance(clock, TimeSpan.FromMilliseconds(30), TimeSpan.FromMilliseconds(70)).Subscribe( + nextValue => { - case 0: Assert.Equal(0, nextValue); break; - case 1: Assert.Equal(0, nextValue); break; - case 2: Assert.Equal(0, nextValue); 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; - } - }); + switch (i++) + { + case 0: + Assert.Equal(0, nextValue); + break; + case 1: + Assert.Equal(0, nextValue); + break; + case 2: + Assert.Equal(0, nextValue); + 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)); } From 5c60b525c8e564d40a305fa24a69f47bd7277a10 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Thu, 15 Jun 2023 23:01:48 +0800 Subject: [PATCH 09/11] more tests --- .../Animation/AnimatableTests.cs | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs index b27535b201..e6c62f6892 100644 --- a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs +++ b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Controls.Shapes; @@ -455,6 +456,114 @@ namespace Avalonia.Base.UnitTests.Animation // causing an exception. 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()) + { + 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()) + { + 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); + } + } private static IDisposable Start() { From a841b5b7e02183dd27a18fd8e1fed89518cb60af Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Thu, 15 Jun 2023 23:37:45 +0800 Subject: [PATCH 10/11] Implement zero duration stop support --- .../Animation/AnimationInstance`1.cs | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Base/Animation/AnimationInstance`1.cs b/src/Avalonia.Base/Animation/AnimationInstance`1.cs index 682629c801..92d2c2c8b5 100644 --- a/src/Avalonia.Base/Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Base/Animation/AnimationInstance`1.cs @@ -53,10 +53,10 @@ namespace Avalonia.Animation private void FetchProperties() { 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) - throw new InvalidOperationException("Duration value cannot be negative or zero."); + if (_animation.Duration < TimeSpan.Zero) + throw new InvalidOperationException("Duration value cannot be negative."); _easeFunc = _animation.Easing; @@ -110,8 +110,8 @@ namespace Avalonia.Animation { if (_animator.Property is null) throw new InvalidOperationException("Animator has no property specified."); - if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both) - _targetControl.SetValue(_animator.Property, _lastInterpValue, BindingPriority.LocalValue); + if (_fillMode is FillMode.Forward or FillMode.Both) + _targetControl.SetValue(_animator.Property, _lastInterpValue); } private void DoComplete() @@ -123,11 +123,8 @@ namespace Avalonia.Animation private void DoDelay() { - if (_fillMode == FillMode.Backward || _fillMode == FillMode.Both) - if (_currentIteration == 0) - PublishNext(_firstKFValue); - else - PublishNext(_lastInterpValue); + if (_fillMode is not (FillMode.Backward or FillMode.Both)) return; + PublishNext(_currentIteration == 0 ? _firstKFValue : _lastInterpValue); } private void DoPlayStates() @@ -167,9 +164,9 @@ namespace Avalonia.Animation _currentIteration = (ulong)(opsTime / iterationTime); - // Stop animation when the current iteration is beyond the iteration count - // and snap the last iteration value to exact values. - if ((_currentIteration + 1) > _iterationCount) + // Stop animation when the current iteration is beyond the iteration count or + // when the duration is set to zero while animating and snap to the last iterated value. + if (_currentIteration + 1 > _iterationCount || _duration == TimeSpan.Zero) { var easedTime = _easeFunc!.Ease(_playbackReversed ? 0.0 : 1.0); _lastInterpValue = _interpolator(easedTime, _neutralValue); @@ -192,10 +189,10 @@ namespace Avalonia.Animation _playbackReversed = true; break; case PlaybackDirection.Alternate: - _playbackReversed = (_currentIteration % 2 == 0) ? false : true; + _playbackReversed = _currentIteration % 2 != 0; break; case PlaybackDirection.AlternateReverse: - _playbackReversed = (_currentIteration % 2 == 0) ? true : false; + _playbackReversed = _currentIteration % 2 == 0; break; default: throw new InvalidOperationException($"Animation direction value is unknown: {_playbackDirection}"); @@ -215,7 +212,7 @@ namespace Avalonia.Animation iterDelay > 0) { // The last iteration's trailing delay should be skipped. - if ((_currentIteration + 1) < _iterationCount) + if (_currentIteration + 1 < _iterationCount) DoDelay(); else DoComplete(); From 80595873a20a77bc9a1de8dc27dfe35bb8d63c88 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Thu, 15 Jun 2023 23:37:58 +0800 Subject: [PATCH 11/11] Add tests for the zero duration stuff --- .../Animation/AnimatableTests.cs | 151 ++++++++++++++---- 1 file changed, 121 insertions(+), 30 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs index e6c62f6892..7ceaddfa16 100644 --- a/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs +++ b/tests/Avalonia.Base.UnitTests/Animation/AnimatableTests.cs @@ -1,12 +1,10 @@ using System; -using System.Globalization; using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Data; using Avalonia.Layout; using Avalonia.Media; -using Avalonia.PropertyStore; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -456,7 +454,7 @@ namespace Avalonia.Base.UnitTests.Animation // causing an exception. root.Child = null; } - + [Fact] public void Run_Normal_Use_Case_Animation() { @@ -474,8 +472,7 @@ namespace Avalonia.Base.UnitTests.Animation var animation = new Avalonia.Animation.Animation() { - Duration = TimeSpan.FromSeconds(10), - Children = { keyframe1, keyframe2 }, + Duration = TimeSpan.FromSeconds(10), Children = { keyframe1, keyframe2 }, }; Border target; @@ -483,16 +480,7 @@ namespace Avalonia.Base.UnitTests.Animation var root = new TestRoot { Clock = clock, - Styles = - { - new Style(x => x.OfType()) - { - Animations = - { - animation - }, - } - }, + Styles = { new Style(x => x.OfType()) { Animations = { animation }, } }, Child = target = new Border { Background = Brushes.Red, } }; @@ -502,10 +490,10 @@ namespace Avalonia.Base.UnitTests.Animation clock.Step(TimeSpan.FromSeconds(0)); clock.Step(TimeSpan.FromSeconds(0.99)); - Assert.InRange(target.Opacity , 0.5d, 0.51d); + Assert.InRange(target.Opacity, 0.5d, 0.51d); } } - + [Fact] public void Run_Normal_Use_Case_Animation_With_Infinite_Iteration() { @@ -533,16 +521,7 @@ namespace Avalonia.Base.UnitTests.Animation var root = new TestRoot { Clock = clock, - Styles = - { - new Style(x => x.OfType()) - { - Animations = - { - animation - }, - } - }, + Styles = { new Style(x => x.OfType()) { Animations = { animation }, } }, Child = target = new Border { Background = Brushes.Red, } }; @@ -550,18 +529,130 @@ namespace Avalonia.Base.UnitTests.Animation 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()) { 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()) { 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)); } }