@ -93,6 +93,256 @@ namespace Avalonia.Base.UnitTests.Animation
Assert . True ( animationRun . Status = = TaskStatus . RanToCompletion ) ;
Assert . Equal ( border . Width , 1 0 0d ) ;
}
[Fact]
public void Pause_Animation_When_IsEffectivelyVisible_Is_False ( )
{
var keyframe1 = new KeyFrame ( )
{
Setters = { new Setter ( Layoutable . WidthProperty , 2 0 0d ) , } , Cue = new Cue ( 1d )
} ;
var keyframe2 = new KeyFrame ( )
{
Setters = { new Setter ( Layoutable . WidthProperty , 1 5 0d ) , } , Cue = new Cue ( 0.5d )
} ;
var keyframe3 = new KeyFrame ( )
{
Setters = { new Setter ( Layoutable . WidthProperty , 1 0 0d ) , } , Cue = new Cue ( 0d )
} ;
var animation = new Animation ( )
{
Duration = TimeSpan . FromSeconds ( 3 ) ,
Delay = TimeSpan . FromSeconds ( 3 ) ,
DelayBetweenIterations = TimeSpan . FromSeconds ( 3 ) ,
IterationCount = new IterationCount ( 2 ) ,
Children = { keyframe1 , keyframe2 , keyframe3 }
} ;
var border = new Border ( ) { Height = 1 0 0d , Width = 1 0 0d } ;
var clock = new TestClock ( ) ;
var animationRun = animation . RunAsync ( border , clock , TestContext . Current . CancellationToken ) ;
border . Measure ( Size . Infinity ) ;
border . Arrange ( new Rect ( border . DesiredSize ) ) ;
clock . Step ( TimeSpan . Zero ) ;
clock . Step ( TimeSpan . FromSeconds ( 0 ) ) ;
Assert . Equal ( 1 0 0d , border . Width ) ;
// Hide the border — this should pause the animation clock.
border . IsVisible = false ;
clock . Step ( TimeSpan . FromSeconds ( 4.5 ) ) ;
// Width should not change while invisible (animation is paused).
Assert . Equal ( 1 0 0d , border . Width ) ;
// Show the border — animation resumes from where it left off.
border . IsVisible = true ;
// The pause absorbed 4.5s of wall-clock time, so internal time = wall - 4.5.
// To reach internal time 6s (end of iteration 1, cue 1.0 -> width 200):
// wall = 4.5 + 6 = 10.5
clock . Step ( TimeSpan . FromSeconds ( 1 0.5 ) ) ;
Assert . Equal ( 2 0 0d , border . Width ) ;
// To complete the animation (internal time 14s triggers trailing delay of iter 2):
// wall = 4.5 + 14 = 18.5
clock . Step ( TimeSpan . FromSeconds ( 1 8.5 ) ) ;
Assert . True ( animationRun . Status = = TaskStatus . RanToCompletion ) ;
Assert . Equal ( 1 0 0d , border . Width ) ;
}
[Fact]
public void Pause_Animation_When_IsEffectivelyVisible_Is_False_Nested ( )
{
var keyframe1 = new KeyFrame ( )
{
Setters = { new Setter ( Layoutable . WidthProperty , 2 0 0d ) , } , Cue = new Cue ( 1d )
} ;
var keyframe2 = new KeyFrame ( )
{
Setters = { new Setter ( Layoutable . WidthProperty , 1 5 0d ) , } , Cue = new Cue ( 0.5d )
} ;
var keyframe3 = new KeyFrame ( )
{
Setters = { new Setter ( Layoutable . WidthProperty , 1 0 0d ) , } , Cue = new Cue ( 0d )
} ;
var animation = new Animation ( )
{
Duration = TimeSpan . FromSeconds ( 3 ) ,
Delay = TimeSpan . FromSeconds ( 3 ) ,
DelayBetweenIterations = TimeSpan . FromSeconds ( 3 ) ,
IterationCount = new IterationCount ( 2 ) ,
Children = { keyframe1 , keyframe2 , keyframe3 }
} ;
var border = new Border ( ) { Height = 1 0 0d , Width = 1 0 0d } ;
var borderParent = new Border { Child = border } ;
var clock = new TestClock ( ) ;
var animationRun = animation . RunAsync ( border , clock , TestContext . Current . CancellationToken ) ;
border . Measure ( Size . Infinity ) ;
border . Arrange ( new Rect ( border . DesiredSize ) ) ;
clock . Step ( TimeSpan . Zero ) ;
clock . Step ( TimeSpan . FromSeconds ( 0 ) ) ;
Assert . Equal ( 1 0 0d , border . Width ) ;
// Hide the parent — this makes border.IsEffectivelyVisible false,
// which should pause the animation clock.
borderParent . IsVisible = false ;
clock . Step ( TimeSpan . FromSeconds ( 4.5 ) ) ;
// Width should not change while parent is invisible (animation is paused).
Assert . Equal ( 1 0 0d , border . Width ) ;
// Show the parent — animation resumes from where it left off.
borderParent . IsVisible = true ;
// The pause absorbed 4.5s of wall-clock time, so internal time = wall - 4.5.
// To reach internal time 6s (end of iteration 1, cue 1.0 -> width 200):
// wall = 4.5 + 6 = 10.5
clock . Step ( TimeSpan . FromSeconds ( 1 0.5 ) ) ;
Assert . Equal ( 2 0 0d , border . Width ) ;
// To complete the animation (internal time 14s triggers trailing delay of iter 2):
// wall = 4.5 + 14 = 18.5
clock . Step ( TimeSpan . FromSeconds ( 1 8.5 ) ) ;
Assert . True ( animationRun . Status = = TaskStatus . RanToCompletion ) ;
Assert . Equal ( 1 0 0d , border . Width ) ;
}
[Fact]
public void Stop_And_Dispose_Animation_When_Detached_From_Visual_Tree ( )
{
var keyframe1 = new KeyFrame ( )
{
Setters = { new Setter ( Layoutable . WidthProperty , 2 0 0d ) , } , Cue = new Cue ( 1d )
} ;
var keyframe2 = new KeyFrame ( )
{
Setters = { new Setter ( Layoutable . WidthProperty , 1 0 0d ) , } , Cue = new Cue ( 0d )
} ;
var animation = new Animation ( )
{
Duration = TimeSpan . FromSeconds ( 1 0 ) ,
IterationCount = new IterationCount ( 1 ) ,
Children = { keyframe2 , keyframe1 }
} ;
var border = new Border ( ) { Height = 1 0 0d , Width = 5 0d } ;
var root = new TestRoot ( border ) ;
var clock = new TestClock ( ) ;
var animationRun = animation . RunAsync ( border , clock , TestContext . Current . CancellationToken ) ;
clock . Step ( TimeSpan . Zero ) ;
clock . Step ( TimeSpan . FromSeconds ( 0 ) ) ;
Assert . False ( animationRun . IsCompleted ) ;
// Detach from visual tree
root . Child = null ;
// Animation should be completed/disposed
Assert . True ( animationRun . IsCompleted ) ;
// Further clock ticks should not affect the border
var widthAfterDetach = border . Width ;
clock . Step ( TimeSpan . FromSeconds ( 5 ) ) ;
clock . Step ( TimeSpan . FromSeconds ( 1 0 ) ) ;
Assert . Equal ( widthAfterDetach , border . Width ) ;
}
[Fact]
public void Pause_Animation_When_Control_Starts_Invisible ( )
{
var keyframe1 = new KeyFrame ( )
{
Setters = { new Setter ( Layoutable . WidthProperty , 2 0 0d ) , } , Cue = new Cue ( 1d )
} ;
var keyframe2 = new KeyFrame ( )
{
Setters = { new Setter ( Layoutable . WidthProperty , 1 0 0d ) , } , Cue = new Cue ( 0d )
} ;
var animation = new Animation ( )
{
Duration = TimeSpan . FromSeconds ( 3 ) ,
IterationCount = new IterationCount ( 1 ) ,
Children = { keyframe2 , keyframe1 }
} ;
var border = new Border ( ) { Height = 1 0 0d , Width = 1 0 0d , IsVisible = false } ;
var clock = new TestClock ( ) ;
var animationRun = animation . RunAsync ( border , clock , TestContext . Current . CancellationToken ) ;
// Clock ticks while invisible should not advance the animation.
clock . Step ( TimeSpan . Zero ) ;
clock . Step ( TimeSpan . FromSeconds ( 1 ) ) ;
clock . Step ( TimeSpan . FromSeconds ( 2 ) ) ;
Assert . Equal ( 1 0 0d , border . Width ) ;
Assert . False ( animationRun . IsCompleted ) ;
// Make visible — animation starts from the beginning.
border . IsVisible = true ;
// The pause absorbed 2s of wall-clock time, so to reach internal time 3s:
// wall = 2 + 3 = 5
clock . Step ( TimeSpan . FromSeconds ( 5 ) ) ;
Assert . True ( animationRun . IsCompleted ) ;
Assert . Equal ( 1 0 0d , border . Width ) ;
}
[Fact]
public void Animation_Plays_Correctly_After_Reattach ( )
{
var keyframe1 = new KeyFrame ( )
{
Setters = { new Setter ( Layoutable . WidthProperty , 2 0 0d ) , } , Cue = new Cue ( 1d )
} ;
var keyframe2 = new KeyFrame ( )
{
Setters = { new Setter ( Layoutable . WidthProperty , 1 0 0d ) , } , Cue = new Cue ( 0d )
} ;
var animation = new Animation ( )
{
Duration = TimeSpan . FromSeconds ( 5 ) ,
IterationCount = new IterationCount ( 1 ) ,
FillMode = FillMode . Forward ,
Children = { keyframe2 , keyframe1 }
} ;
var border = new Border ( ) { Height = 1 0 0d , Width = 5 0d } ;
var root = new TestRoot ( border ) ;
var clock = new TestClock ( ) ;
var animationRun = animation . RunAsync ( border , clock , TestContext . Current . CancellationToken ) ;
clock . Step ( TimeSpan . Zero ) ;
Assert . False ( animationRun . IsCompleted ) ;
// Detach — animation completes.
root . Child = null ;
Assert . True ( animationRun . IsCompleted ) ;
// Reattach and start a fresh animation.
root . Child = border ;
var clock2 = new TestClock ( ) ;
var animationRun2 = animation . RunAsync ( border , clock2 , TestContext . Current . CancellationToken ) ;
clock2 . Step ( TimeSpan . Zero ) ;
Assert . False ( animationRun2 . IsCompleted ) ;
clock2 . Step ( TimeSpan . FromSeconds ( 5 ) ) ;
Assert . True ( animationRun2 . IsCompleted ) ;
Assert . Equal ( 2 0 0d , border . Width ) ;
}
[Fact]
public void Check_FillModes_Start_and_End_Values_if_Retained ( )