Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ void OnAnimationLoaded(object? sender, EventArgs e)
tcs.SetResult(true);
}

void OnAnimationFailed(object? sender, EventArgs e)
void OnAnimationFailed(object? sender, SKLottieAnimationFailedEventArgs e)
{
Cleanup();
tcs.SetException(new Exception("Unable to load Lottie animation."));
if (e.Exception != null)
tcs.SetException(new Exception("Unable to load Lottie animation.", e.Exception));
else
tcs.SetException(new Exception("Unable to load Lottie animation (animation source returned null)."));
}

void OnTimeout()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,75 @@ public async Task NegativeUpdatesAfterPositiveGoesBack()
Assert.Equal(0, animationCompleted);
}

[Fact]
public async Task AnimationFailedContainsException()
{
// create - use non-existent file to trigger actual load failure with exception
var source = new SKFileLottieImageSource { File = "nonexistent.json" };
var lottie = new SKLottieView { Source = source };

Comment on lines +154 to +156

Copilot AI Feb 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AnimationFailed handler is attached after setting Source via the object initializer. Because Source triggers an async load immediately, AnimationFailed can fire before the subscription is added, making this test flaky. Create the view first, attach handlers, then assign lottie.Source = source (or use WaitingLottieView which subscribes before Source is set).

Copilot uses AI. Check for mistakes.
SKLottieAnimationFailedEventArgs? failedEventArgs = null;
var tcs = new TaskCompletionSource<bool>();

lottie.AnimationFailed += (s, e) =>
{
failedEventArgs = e;
tcs.SetResult(true);
};

// wait for animation to fail (using consistent 3000ms timeout)
var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(3000));

// test - verify the failure event was triggered with proper event args
Assert.Equal(tcs.Task, completedTask);
Assert.NotNull(failedEventArgs);
Assert.IsType<SKLottieAnimationFailedEventArgs>(failedEventArgs);
// Verify exception is populated when file doesn't exist
Assert.NotNull(failedEventArgs.Exception);
}

[Fact]
public async Task AnimationFailedNotTriggeredForEmptySource()
{
// create - empty source should NOT trigger AnimationFailed (early return)
var source = new SKFileLottieImageSource();
var lottie = new SKLottieView { Source = source };

var failedTriggered = false;
var loadedTriggered = false;

lottie.AnimationFailed += (s, e) => failedTriggered = true;
lottie.AnimationLoaded += (s, e) => loadedTriggered = true;

// wait to ensure events have time to fire (using consistent 3000ms)
await Task.Delay(3000);

// test - verify neither event was triggered for empty source
Comment on lines +186 to +193

Copilot AI Feb 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test uses a fixed 3000ms Task.Delay, which will always slow the suite by 3 seconds even when behavior is correct. Since an empty source returns early and should never raise events, consider using WaitForAnimation with a short timeout (and asserting it cancels) or Task.WhenAny with a much shorter delay to keep the test fast while still catching unexpected event raises.

Suggested change
lottie.AnimationFailed += (s, e) => failedTriggered = true;
lottie.AnimationLoaded += (s, e) => loadedTriggered = true;
// wait to ensure events have time to fire (using consistent 3000ms)
await Task.Delay(3000);
// test - verify neither event was triggered for empty source
var failedTcs = new TaskCompletionSource<bool>();
var loadedTcs = new TaskCompletionSource<bool>();
lottie.AnimationFailed += (s, e) =>
{
failedTriggered = true;
failedTcs.TrySetResult(true);
};
lottie.AnimationLoaded += (s, e) =>
{
loadedTriggered = true;
loadedTcs.TrySetResult(true);
};
// wait briefly to ensure events would have time to fire if they were going to
var timeoutTask = Task.Delay(200);
var completedTask = await Task.WhenAny(failedTcs.Task, loadedTcs.Task, timeoutTask);
// test - verify neither event was triggered for empty source
Assert.Equal(timeoutTask, completedTask);

Copilot uses AI. Check for mistakes.
Assert.False(failedTriggered);
Assert.False(loadedTriggered);
}

[Fact]
public async Task AnimationLoadedNotTriggeredOnFailure()
{
// create - non-existent file to trigger failure
var source = new SKFileLottieImageSource { File = "nonexistent.json" };
var lottie = new SKLottieView { Source = source };

Comment on lines +202 to +204

Copilot AI Feb 24, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AnimationLoaded/AnimationFailed handlers are attached after setting Source via the object initializer. Because Source immediately starts the async load, the failure event can fire before the subscription is added, making the test flaky on fast failures. Attach handlers first, then set lottie.Source = source (or use WaitingLottieView).

Copilot uses AI. Check for mistakes.
var loadedTriggered = false;
var tcs = new TaskCompletionSource<bool>();

lottie.AnimationLoaded += (s, e) => loadedTriggered = true;
lottie.AnimationFailed += (s, e) => tcs.SetResult(true);

// wait for failure
var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(3000));

// test - verify AnimationLoaded was NOT triggered on failure
Assert.Equal(tcs.Task, completedTask);
Assert.False(loadedTriggered);
}
Comment thread
mattleibow marked this conversation as resolved.

[Theory]
[InlineData(SKLottieRepeatMode.Restart, 1, 1)]
[InlineData(SKLottieRepeatMode.Restart, 2, 2)]
Expand Down
Loading