|
|
|
@ -7,7 +7,9 @@ using Xunit; |
|
|
|
using Avalonia.Animation.Easings; |
|
|
|
using System.Threading; |
|
|
|
using System.Reactive.Linq; |
|
|
|
using Avalonia.Data; |
|
|
|
using Avalonia.Layout; |
|
|
|
using Avalonia.UnitTests; |
|
|
|
|
|
|
|
namespace Avalonia.Base.UnitTests.Animation |
|
|
|
{ |
|
|
|
@ -604,6 +606,110 @@ namespace Avalonia.Base.UnitTests.Animation |
|
|
|
Assert.Equal(100.0, border.Width); |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public void Animation_Completes_Gracefully_When_First_KeyFrame_Value_Is_Null() |
|
|
|
{ |
|
|
|
var clock = new MockGlobalClock(); |
|
|
|
var services = new TestServices(globalClock: clock); |
|
|
|
|
|
|
|
using (UnitTestApplication.Start(services)) |
|
|
|
{ |
|
|
|
var nullBinding = new Binding("NonExistentProperty"); |
|
|
|
|
|
|
|
var animation = new Animation |
|
|
|
{ |
|
|
|
Duration = TimeSpan.FromSeconds(1), |
|
|
|
FillMode = FillMode.Both, |
|
|
|
Children = |
|
|
|
{ |
|
|
|
new KeyFrame |
|
|
|
{ |
|
|
|
KeyTime = TimeSpan.FromSeconds(0), |
|
|
|
Setters = { new Setter(Layoutable.WidthProperty, nullBinding) } |
|
|
|
}, |
|
|
|
new KeyFrame |
|
|
|
{ |
|
|
|
KeyTime = TimeSpan.FromSeconds(1), |
|
|
|
Setters = { new Setter(Layoutable.WidthProperty, 200d) } |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
var border = new Border { Width = 100d, Height = 100d }; |
|
|
|
|
|
|
|
var root = new TestRoot(border); |
|
|
|
root.LayoutManager.ExecuteInitialLayoutPass(); |
|
|
|
|
|
|
|
var animationTask = animation.RunAsync(border, clock); |
|
|
|
|
|
|
|
// Pulse the clock - this should not throw even though
|
|
|
|
// the first keyframe's value is null (falls back to neutral value)
|
|
|
|
var exception = Record.Exception(() => clock.Pulse(TimeSpan.Zero)); |
|
|
|
Assert.Null(exception); |
|
|
|
|
|
|
|
// The animation should continue running (using neutral value as fallback)
|
|
|
|
clock.Pulse(TimeSpan.FromSeconds(0.5)); |
|
|
|
Assert.False(animationTask.IsCompleted); |
|
|
|
|
|
|
|
// Animation completes after its full duration
|
|
|
|
clock.Pulse(TimeSpan.FromSeconds(1)); |
|
|
|
Assert.True(animationTask.IsCompleted); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
[Fact] |
|
|
|
public void Animation_With_Unresolved_Binding_Does_Not_Throw_NullReferenceException() |
|
|
|
{ |
|
|
|
// Additional test to verify the null reference fix for animator first keyframe value
|
|
|
|
|
|
|
|
var clock = new MockGlobalClock(); |
|
|
|
var services = new TestServices(globalClock: clock); |
|
|
|
|
|
|
|
using (UnitTestApplication.Start(services)) |
|
|
|
{ |
|
|
|
// Binding to a property that doesn't exist - will evaluate to null
|
|
|
|
var binding = new Binding("MissingProperty"); |
|
|
|
|
|
|
|
var animation = new Animation |
|
|
|
{ |
|
|
|
Duration = TimeSpan.FromSeconds(1), |
|
|
|
IterationCount = new IterationCount(1), |
|
|
|
Children = |
|
|
|
{ |
|
|
|
new KeyFrame |
|
|
|
{ |
|
|
|
Cue = new Cue(0d), |
|
|
|
Setters = { new Setter(Layoutable.WidthProperty, binding) } |
|
|
|
}, |
|
|
|
new KeyFrame |
|
|
|
{ |
|
|
|
Cue = new Cue(1d), |
|
|
|
Setters = { new Setter(Layoutable.WidthProperty, 300d) } |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
var control = new Border { Width = 50d }; |
|
|
|
var root = new TestRoot(control); |
|
|
|
root.LayoutManager.ExecuteInitialLayoutPass(); |
|
|
|
|
|
|
|
// Start animation - the first keyframe value will be null due to unresolved binding
|
|
|
|
var task = animation.RunAsync(control, clock); |
|
|
|
|
|
|
|
// The fix ensures this doesn't throw NullReferenceException
|
|
|
|
// Animation falls back to neutral value and continues
|
|
|
|
clock.Pulse(TimeSpan.Zero); |
|
|
|
clock.Pulse(TimeSpan.FromSeconds(0.1)); |
|
|
|
|
|
|
|
// Animation should still be running (uses neutral value as fallback)
|
|
|
|
Assert.False(task.IsCompleted); |
|
|
|
|
|
|
|
// Animation completes after its full duration
|
|
|
|
clock.Pulse(TimeSpan.FromSeconds(1)); |
|
|
|
Assert.True(task.IsCompleted); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private sealed class FakeAnimator : InterpolatingAnimator<double> |
|
|
|
{ |
|
|
|
public double LastProgress { get; set; } = double.NaN; |
|
|
|
|