From 93576afd6a1ab715095b7a1fc7aa2d81dd212fa2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 03:10:32 +0000 Subject: [PATCH 1/6] Initial plan From b215637c74b83b91d12630d3a8ac38fea94499d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 03:13:01 +0000 Subject: [PATCH 2/6] Update tests and docs for AnimationFailed exception support Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com> --- docs/docs/sklottieview.md | 10 +++---- .../Controls/Lottie/SKLottieViewExtensions.cs | 8 ++++-- .../Controls/Lottie/SKLottieViewTest.cs | 26 +++++++++++++++++++ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/docs/docs/sklottieview.md b/docs/docs/sklottieview.md index df62a8a643..b8269394e5 100644 --- a/docs/docs/sklottieview.md +++ b/docs/docs/sklottieview.md @@ -24,11 +24,11 @@ There are several properties that can be used to control th animation playback: There are a few events that can be used to be notified of animation loading events: -| Event | Type | Description | -| :---------------------- | :-------------- | :---------- | -| **AnimationLoaded** | `EventHandler` | Invoked when the animation has loaded successfully. | -| **AnimationFailed** | `EventHandler` | Invoked when there was an error loading the animation. | -| **AnimationCompleted** | `EventHandler` | Invoked when the animation is finished playing (after all the repeats). Infinite animations never complete so will not trigger the event. | +| Event | Type | Description | +| :---------------------- | :--------------------------------------------- | :---------- | +| **AnimationLoaded** | `EventHandler` | Invoked when the animation has loaded successfully. | +| **AnimationFailed** | `EventHandler` | Invoked when there was an error loading the animation. The event args contain an `Exception` property with details about the failure. | +| **AnimationCompleted** | `EventHandler` | Invoked when the animation is finished playing (after all the repeats). Infinite animations never complete so will not trigger the event. | ## Parts diff --git a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewExtensions.cs b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewExtensions.cs index a48cb03093..1b9117f260 100644 --- a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewExtensions.cs +++ b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewExtensions.cs @@ -22,10 +22,14 @@ 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.")); + var message = "Unable to load Lottie animation."; + if (e.Exception != null) + tcs.SetException(new Exception(message, e.Exception)); + else + tcs.SetException(new Exception(message)); } void OnTimeout() diff --git a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs index 376da44ac1..12b569994a 100644 --- a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs +++ b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs @@ -147,6 +147,32 @@ public async Task NegativeUpdatesAfterPositiveGoesBack() Assert.Equal(0, animationCompleted); } + [Fact] + public async Task AnimationFailedContainsException() + { + // create + var source = new SKFileLottieImageSource(); + var lottie = new SKLottieView { Source = source }; + + SKLottieAnimationFailedEventArgs? failedEventArgs = null; + var tcs = new TaskCompletionSource(); + + lottie.AnimationFailed += (s, e) => + { + failedEventArgs = e; + tcs.SetResult(true); + }; + + // wait for animation to fail + await Task.WhenAny(tcs.Task, Task.Delay(2000)); + + // test + Assert.NotNull(failedEventArgs); + // The exception may or may not be set depending on the failure mode, + // but the event args should always be of the correct type + Assert.IsType(failedEventArgs); + } + [Theory] [InlineData(SKLottieRepeatMode.Restart, 1, 1)] [InlineData(SKLottieRepeatMode.Restart, 2, 2)] From c4507de2119efdcf34d38eb8197b34a6db5aacdb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 03:14:39 +0000 Subject: [PATCH 3/6] Improve AnimationFailed test to verify event is triggered Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com> --- .../Controls/Lottie/SKLottieViewTest.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs index 12b569994a..7ea4797b0d 100644 --- a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs +++ b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs @@ -164,12 +164,11 @@ public async Task AnimationFailedContainsException() }; // wait for animation to fail - await Task.WhenAny(tcs.Task, Task.Delay(2000)); - - // test + var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(2000)); + + // test - verify the failure event was triggered + Assert.Equal(tcs.Task, completedTask); Assert.NotNull(failedEventArgs); - // The exception may or may not be set depending on the failure mode, - // but the event args should always be of the correct type Assert.IsType(failedEventArgs); } From 7e515180eadd512ed2463e79cbbd55a07d788381 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 03:15:10 +0000 Subject: [PATCH 4/6] Add clarifying comment about exception property in test Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com> --- .../Controls/Lottie/SKLottieViewTest.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs index 7ea4797b0d..a47ffb5dfc 100644 --- a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs +++ b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs @@ -150,7 +150,7 @@ public async Task NegativeUpdatesAfterPositiveGoesBack() [Fact] public async Task AnimationFailedContainsException() { - // create + // create - empty source will fail to load var source = new SKFileLottieImageSource(); var lottie = new SKLottieView { Source = source }; @@ -166,7 +166,8 @@ public async Task AnimationFailedContainsException() // wait for animation to fail var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(2000)); - // test - verify the failure event was triggered + // test - verify the failure event was triggered with proper event args + // Note: Exception property may be null if animation returns null without throwing Assert.Equal(tcs.Task, completedTask); Assert.NotNull(failedEventArgs); Assert.IsType(failedEventArgs); From 0a59b7ef2967329f6a56e582c118d7f7e4b97ea0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 20:00:11 +0000 Subject: [PATCH 5/6] Fix AnimationFailed test and add comprehensive edge case coverage - Fix test to use non-existent file instead of empty source to properly trigger AnimationFailed event - Increase timeout to 3000ms for consistency with other tests - Improve exception message to differentiate between exception-based failures and null returns - Add test for empty source edge case (should NOT trigger AnimationFailed) - Add test to verify AnimationLoaded is NOT triggered on failure - Improve markdown table alignment in documentation - Verify exception is populated when file doesn't exist Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com> --- docs/docs/sklottieview.md | 10 ++-- .../Controls/Lottie/SKLottieViewExtensions.cs | 5 +- .../Controls/Lottie/SKLottieViewTest.cs | 53 +++++++++++++++++-- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/docs/docs/sklottieview.md b/docs/docs/sklottieview.md index b8269394e5..6931c6ff92 100644 --- a/docs/docs/sklottieview.md +++ b/docs/docs/sklottieview.md @@ -24,11 +24,11 @@ There are several properties that can be used to control th animation playback: There are a few events that can be used to be notified of animation loading events: -| Event | Type | Description | -| :---------------------- | :--------------------------------------------- | :---------- | -| **AnimationLoaded** | `EventHandler` | Invoked when the animation has loaded successfully. | -| **AnimationFailed** | `EventHandler` | Invoked when there was an error loading the animation. The event args contain an `Exception` property with details about the failure. | -| **AnimationCompleted** | `EventHandler` | Invoked when the animation is finished playing (after all the repeats). Infinite animations never complete so will not trigger the event. | +| Event | Type | Description | +| :--------------------- | :----------------------------------------------- | :---------- | +| **AnimationLoaded** | `EventHandler` | Invoked when the animation has loaded successfully. | +| **AnimationFailed** | `EventHandler` | Invoked when there was an error loading the animation. The event args contain an `Exception` property with details about the failure. | +| **AnimationCompleted** | `EventHandler` | Invoked when the animation is finished playing (after all the repeats). Infinite animations never complete so will not trigger the event. | ## Parts diff --git a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewExtensions.cs b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewExtensions.cs index 1b9117f260..61accc7c3d 100644 --- a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewExtensions.cs +++ b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewExtensions.cs @@ -25,11 +25,10 @@ void OnAnimationLoaded(object? sender, EventArgs e) void OnAnimationFailed(object? sender, SKLottieAnimationFailedEventArgs e) { Cleanup(); - var message = "Unable to load Lottie animation."; if (e.Exception != null) - tcs.SetException(new Exception(message, e.Exception)); + tcs.SetException(new Exception("Unable to load Lottie animation.", e.Exception)); else - tcs.SetException(new Exception(message)); + tcs.SetException(new Exception("Unable to load Lottie animation (returned null).")); } void OnTimeout() diff --git a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs index a47ffb5dfc..d1bca700ab 100644 --- a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs +++ b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs @@ -150,8 +150,8 @@ public async Task NegativeUpdatesAfterPositiveGoesBack() [Fact] public async Task AnimationFailedContainsException() { - // create - empty source will fail to load - var source = new SKFileLottieImageSource(); + // 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 }; SKLottieAnimationFailedEventArgs? failedEventArgs = null; @@ -163,14 +163,57 @@ public async Task AnimationFailedContainsException() tcs.SetResult(true); }; - // wait for animation to fail - var completedTask = await Task.WhenAny(tcs.Task, Task.Delay(2000)); + // 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 - // Note: Exception property may be null if animation returns null without throwing Assert.Equal(tcs.Task, completedTask); Assert.NotNull(failedEventArgs); Assert.IsType(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 + await Task.Delay(1000); + + // test - verify neither event was triggered for empty source + 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 }; + + var loadedTriggered = false; + var tcs = new TaskCompletionSource(); + + 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); } [Theory] From 5a0e527c82bf05ef8a9a5666ac0ff40f28b83e27 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 20:01:05 +0000 Subject: [PATCH 6/6] Refine test timing and error messages - Increase empty source test delay from 1000ms to 3000ms for reliability - Clarify exception message to specify 'animation source returned null' Co-authored-by: mattleibow <1096616+mattleibow@users.noreply.github.com> --- .../Controls/Lottie/SKLottieViewExtensions.cs | 2 +- .../Controls/Lottie/SKLottieViewTest.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewExtensions.cs b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewExtensions.cs index 61accc7c3d..a22463d2d2 100644 --- a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewExtensions.cs +++ b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewExtensions.cs @@ -28,7 +28,7 @@ void OnAnimationFailed(object? sender, SKLottieAnimationFailedEventArgs e) 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 (returned null).")); + tcs.SetException(new Exception("Unable to load Lottie animation (animation source returned null).")); } void OnTimeout() diff --git a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs index d1bca700ab..369a953b11 100644 --- a/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs +++ b/tests/SkiaSharp.Extended.UI.Maui.Tests/Controls/Lottie/SKLottieViewTest.cs @@ -187,8 +187,8 @@ public async Task AnimationFailedNotTriggeredForEmptySource() lottie.AnimationFailed += (s, e) => failedTriggered = true; lottie.AnimationLoaded += (s, e) => loadedTriggered = true; - // wait to ensure events have time to fire - await Task.Delay(1000); + // wait to ensure events have time to fire (using consistent 3000ms) + await Task.Delay(3000); // test - verify neither event was triggered for empty source Assert.False(failedTriggered);