Skip to content

Commit

Permalink
[release/7.0.2xx] [net7.0] Blank image when Source set to null or on …
Browse files Browse the repository at this point in the history
…image loading error (#14071)

* Blank image when Source set to null or on image loading error Fixes #8787

* Auto-format source code

* Null check cleanup

* Fix test

---------

Co-authored-by: E.Z. Hart <hartez@gmail.com>
Co-authored-by: GitHub Actions Autoformatter <autoformat@example.com>
  • Loading branch information
3 people authored Mar 21, 2023
1 parent cad4d2e commit 06cab83
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 20 deletions.
1 change: 1 addition & 0 deletions src/Core/src/Platform/Android/ImageSourcePartExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ internal static class ImageSourcePartExtensions
}
catch (Exception ex)
{
setImage?.Invoke(null);
events?.LoadingFailed(ex);
}
finally
Expand Down
32 changes: 17 additions & 15 deletions src/Core/src/Platform/ImageSourcePartLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public ImageSourcePartLoader(
{
Handler = handler;
_imageSourcePart = imageSourcePart;

SetImage = setImage;
}

Expand All @@ -52,27 +53,28 @@ public void Reset()

public async Task UpdateImageSourceAsync()
{
if (PlatformView != null)
if (PlatformView is null)
{
var token = this.SourceManager.BeginLoad();
var imageSource = _imageSourcePart();
return;
}

var token = this.SourceManager.BeginLoad();
var imageSource = _imageSourcePart();

if (imageSource != null)
{
if (imageSource?.Source is not null)
{
#if __IOS__ || __ANDROID__ || WINDOWS || TIZEN
var result = await imageSource.UpdateSourceAsync(PlatformView, ImageSourceServiceProvider, SetImage!, token)
.ConfigureAwait(false);
var result = await imageSource.UpdateSourceAsync(PlatformView, ImageSourceServiceProvider, SetImage!, token)
.ConfigureAwait(false);

SourceManager.CompleteLoad(result);
SourceManager.CompleteLoad(result);
#else
await Task.CompletedTask;
await Task.CompletedTask;
#endif
}
else
{
SetImage?.Invoke(null);
SourceManager.CompleteLoad(null);
}
}
else
{
SetImage?.Invoke(null);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Core/src/Platform/Windows/ImageSourcePartExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ internal static class ImageSourcePartExtensions
}
catch (Exception ex)
{
setImage?.Invoke(null);
events?.LoadingFailed(ex);
}
finally
Expand Down
1 change: 1 addition & 0 deletions src/Core/src/Platform/iOS/ImageSourcePartExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ internal static class ImageSourcePartExtensions
}
catch (Exception ex)
{
setImage?.Invoke(null);
events?.LoadingFailed(ex);
}
finally
Expand Down
58 changes: 57 additions & 1 deletion src/Core/tests/DeviceTests/Handlers/Image/ImageHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui.DeviceTests.Stubs;
using Xunit;
using System.ComponentModel;

#if ANDROID
using Android.Graphics.Drawables;
Expand Down Expand Up @@ -333,7 +334,10 @@ await InvokeOnMainThreadAsync(async () =>
await image.Wait();
Assert.Empty(handler.ImageEvents);
// We expect that if the Image is created with no Source set, the platform image view
// will get a `null` image set
Assert.NotEmpty(handler.ImageEvents);
Assert.Null(handler.ImageEvents[0].Value);
await handler.PlatformView.AssertContainsColor(expectedColor);
});
Expand Down Expand Up @@ -460,5 +464,57 @@ protected TCustomHandler CreateHandler<TCustomHandler>(IView view)
static int GetDrawableId(string image) =>
MauiProgram.DefaultContext.Resources.GetDrawableId(MauiProgram.DefaultContext.PackageName, image);
#endif

[Fact]
public async Task UpdatingSourceToNullClearsImage()
{
var image = new TStub
{
Background = new SolidPaintStub(Colors.Black),
Source = new FileImageSourceStub("red.png"),
};

await InvokeOnMainThreadAsync(async () =>
{
var handler = CreateHandler<CountedImageHandler>(image);
await image.Wait();
await handler.PlatformView.AssertContainsColor(Colors.Red);
handler.ImageEvents.Clear();
image.Source = null;
handler.UpdateValue(nameof(IImage.Source));
await image.Wait();
await handler.PlatformView.AssertDoesNotContainColor(Colors.Red);
});
}

[Fact]
public async Task UpdatingSourceToNonexistentSourceClearsImage()
{
var image = new TStub
{
Background = new SolidPaintStub(Colors.Black),
Source = new FileImageSourceStub("red.png"),
};

await InvokeOnMainThreadAsync(async () =>
{
var handler = CreateHandler<ImageHandler>(image);
await image.Wait();
await handler.PlatformView.AssertContainsColor(Colors.Red);
image.Source = new FileImageSourceStub("fail.png");
handler.UpdateValue(nameof(IImage.Source));
await handler.PlatformView.AttachAndRun(() => { });
await image.Wait(5000);
await handler.PlatformView.AssertDoesNotContainColor(Colors.Red);
});
}
}
}
40 changes: 38 additions & 2 deletions src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Platform;
using Xunit;
using Xunit.Sdk;
using AColor = Android.Graphics.Color;
using AView = Android.Views.View;

Expand Down Expand Up @@ -296,7 +297,13 @@ public static Bitmap AssertColorAtTopRight(this Bitmap bitmap, AColor expectedCo
return bitmap.AssertColorAtPoint(expectedColor, bitmap.Width - 1, bitmap.Height - 1);
}

public static Bitmap AssertContainsColor(this Bitmap bitmap, AColor expectedColor)
public static Task<Bitmap> AssertContainsColor(this Bitmap bitmap, Graphics.Color expectedColor, Func<Maui.Graphics.RectF, Maui.Graphics.RectF>? withinRectModifier = null)
=> Task.FromResult(bitmap.AssertContainsColor(expectedColor.ToPlatform()));

public static Task<Bitmap> AssertDoesNotContainColor(this Bitmap bitmap, Graphics.Color unexpectedColor, Func<Maui.Graphics.RectF, Maui.Graphics.RectF>? withinRectModifier = null)
=> Task.FromResult(bitmap.AssertDoesNotContainColor(unexpectedColor.ToPlatform()));

public static Bitmap AssertContainsColor(this Bitmap bitmap, AColor expectedColor, Func<Maui.Graphics.RectF, Maui.Graphics.RectF>? withinRectModifier = null)
{
for (int x = 0; x < bitmap.Width; x++)
{
Expand All @@ -309,19 +316,48 @@ public static Bitmap AssertContainsColor(this Bitmap bitmap, AColor expectedColo
}
}

Assert.True(false, CreateColorError(bitmap, $"Color {expectedColor} not found."));
throw new XunitException($"Color {expectedColor} not found.");
}

public static Bitmap AssertDoesNotContainColor(this Bitmap bitmap, AColor unexpectedColor, Func<Maui.Graphics.RectF, Maui.Graphics.RectF>? withinRectModifier = null)
{
var imageRect = new Graphics.RectF(0, 0, bitmap.Width, bitmap.Height);

if (withinRectModifier is not null)
imageRect = withinRectModifier.Invoke(imageRect);

for (int x = (int)imageRect.X; x < (int)imageRect.Width; x++)
{
for (int y = (int)imageRect.Y; y < (int)imageRect.Height; y++)
{
if (bitmap.ColorAtPoint(x, y, true).IsEquivalent(unexpectedColor))
{
throw new XunitException($"Color {unexpectedColor} was found at point {x}, {y}.");
}
}
}

return bitmap;
}

public static Task<Bitmap> AssertContainsColor(this AView view, Graphics.Color expectedColor) =>
AssertContainsColor(view, expectedColor.ToPlatform());

public static Task<Bitmap> AssertDoesNotContainColor(this AView view, Graphics.Color unexpectedColor) =>
AssertDoesNotContainColor(view, unexpectedColor.ToPlatform());

public static async Task<Bitmap> AssertContainsColor(this AView view, AColor expectedColor)
{
var bitmap = await view.ToBitmap();
return AssertContainsColor(bitmap, expectedColor);
}

public static async Task<Bitmap> AssertDoesNotContainColor(this AView view, AColor unexpectedColor)
{
var bitmap = await view.ToBitmap();
return AssertDoesNotContainColor(bitmap, unexpectedColor);
}

public static async Task<Bitmap> AssertColorAtPointAsync(this AView view, AColor expectedColor, int x, int y)
{
var bitmap = await view.ToBitmap();
Expand Down
37 changes: 35 additions & 2 deletions src/TestUtils/src/DeviceTests/AssertionExtensions.iOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,22 @@ public static async Task<UIImage> AssertContainsColor(this UIView view, UIColor
return bitmap.AssertContainsColor(expectedColor);
}

public static async Task<UIImage> AssertDoesNotContainColor(this UIView view, UIColor unexpectedColor)
{
var bitmap = await view.ToBitmap();
return bitmap.AssertDoesNotContainColor(unexpectedColor);
}

public static Task<UIImage> AssertContainsColor(this UIView view, Microsoft.Maui.Graphics.Color expectedColor) =>
AssertContainsColor(view, expectedColor.ToPlatform());

public static UIImage AssertContainsColor(this UIImage bitmap, UIColor expectedColor)
public static Task<UIImage> AssertDoesNotContainColor(this UIView view, Microsoft.Maui.Graphics.Color unexpectedColor) =>
AssertDoesNotContainColor(view, unexpectedColor.ToPlatform());

public static Task<UIImage> AssertContainsColor(this UIImage image, Graphics.Color expectedColor, Func<Graphics.RectF, Graphics.RectF>? withinRectModifier = null)
=> Task.FromResult(image.AssertContainsColor(expectedColor.ToPlatform(), withinRectModifier));

public static UIImage AssertContainsColor(this UIImage bitmap, UIColor expectedColor, Func<Graphics.RectF, Graphics.RectF>? withinRectModifier = null)
{
for (int x = 0; x < bitmap.Size.Width; x++)
{
Expand All @@ -316,10 +328,31 @@ public static UIImage AssertContainsColor(this UIImage bitmap, UIColor expectedC
}
}

Assert.True(false, CreateColorError(bitmap, $"Color {expectedColor} not found."));
throw new XunitException($"Color {expectedColor} not found.");
}

public static UIImage AssertDoesNotContainColor(this UIImage bitmap, UIColor unexpectedColor, Func<Graphics.RectF, Graphics.RectF>? withinRectModifier = null)
{
var imageRect = new Graphics.RectF(0, 0, (float)bitmap.Size.Width.Value, (float)bitmap.Size.Height.Value);

if (withinRectModifier is not null)
imageRect = withinRectModifier.Invoke(imageRect);

for (int x = (int)imageRect.X; x < (int)imageRect.Width; x++)
{
for (int y = (int)imageRect.Y; y < (int)imageRect.Height; y++)
{
if (ColorComparison.ARGBEquivalent(bitmap.ColorAtPoint(x, y), unexpectedColor))
{
throw new XunitException($"Color {unexpectedColor} was found at point {x}, {y}.");
}
}
}

return bitmap;
}


public static Task AssertEqualAsync(this UIImage bitmap, UIImage other)
{
Assert.NotNull(bitmap);
Expand Down

0 comments on commit 06cab83

Please sign in to comment.