Skip to content

Commit 744f951

Browse files
authored
Enable Windows Image device tests (#20167)
1 parent dbf7aad commit 744f951

File tree

7 files changed

+235
-20
lines changed

7 files changed

+235
-20
lines changed

src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBasementOfT.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@
33
using Microsoft.Maui.DeviceTests.Stubs;
44
using Xunit;
55
using Xunit.Sdk;
6+
#if __IOS__ || MACCATALYST
7+
using PlatformView = UIKit.UIView;
8+
#elif __ANDROID__
9+
using PlatformView = Android.Views.View;
10+
#elif WINDOWS
11+
using PlatformView = Microsoft.UI.Xaml.FrameworkElement;
12+
#elif TIZEN
13+
using PlatformView = Tizen.NUI.BaseComponents.View;
14+
#elif (NETSTANDARD || !PLATFORM)
15+
using PlatformView = System.Object;
16+
#endif
617

718
namespace Microsoft.Maui.DeviceTests
819
{
@@ -45,6 +56,20 @@ public Task<T> AttachAndRun<T>(IView view, Func<THandler, Task<T>> action)
4556
}, MauiContext, async (view) => (IPlatformViewHandler)(await CreateHandlerAsync(view)));
4657
}
4758

59+
public Task AttachAndRun(PlatformView view, Action action) =>
60+
#if WINDOWS
61+
view.AttachAndRun(action, MauiContext);
62+
#else
63+
view.AttachAndRun(action);
64+
#endif
65+
66+
public Task AttachAndRun(PlatformView view, Func<Task> action) =>
67+
#if WINDOWS
68+
view.AttachAndRun(action, MauiContext);
69+
#else
70+
view.AttachAndRun(action);
71+
#endif
72+
4873
protected Task<THandler> CreateHandlerAsync(IView view)
4974
{
5075
return InvokeOnMainThreadAsync(() =>

src/Core/tests/DeviceTests/Core.DeviceTests.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555
<None Include="@(Compile)" />
5656
</ItemGroup>
5757
<ItemGroup Condition="$(TargetFramework.Contains('-windows'))">
58-
<Compile Remove="Handlers\Image\*.cs" />
5958
<Compile Remove="Handlers\ImageButton\*.cs" />
6059
<Compile Remove="Handlers\ShapeView\*.cs" />
6160
<Content Include="Platforms\Windows\Assets\**" Link="Assets\%(RecursiveDir)%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using Microsoft.UI.Xaml.Media.Imaging;
3+
using WImage = Microsoft.UI.Xaml.Controls.Image;
4+
using WStretch = Microsoft.UI.Xaml.Media.Stretch;
5+
6+
namespace Microsoft.Maui.DeviceTests
7+
{
8+
public partial class ImageHandlerTests<TImageHandler, TStub>
9+
{
10+
WImage GetPlatformImageView(IImageHandler imageHandler) =>
11+
imageHandler.PlatformView;
12+
13+
bool GetNativeIsAnimationPlaying(IImageHandler imageHandler) =>
14+
GetPlatformImageView(imageHandler).Source is BitmapImage bitmapImage && bitmapImage.IsPlaying;
15+
16+
Aspect GetNativeAspect(IImageHandler imageHandler) =>
17+
GetPlatformImageView(imageHandler).Stretch switch
18+
{
19+
WStretch.Uniform => Aspect.AspectFit,
20+
WStretch.UniformToFill => Aspect.AspectFill,
21+
WStretch.Fill => Aspect.Fill,
22+
WStretch.None => Aspect.Center,
23+
_ => throw new ArgumentOutOfRangeException("Stretch")
24+
};
25+
}
26+
}

src/Core/tests/DeviceTests/Handlers/Image/ImageHandlerTests.cs

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#elif IOS || MACCATALYST
1414
using UIKit;
1515
using PlatformImageType = UIKit.UIImage;
16+
#elif WINDOWS
17+
using PlatformImageType = Microsoft.UI.Xaml.Controls.Image;
1618
#endif
1719

1820
namespace Microsoft.Maui.DeviceTests
@@ -32,11 +34,14 @@ public abstract partial class ImageHandlerTests<TImageHandler, TStub> : CoreHand
3234
#elif IOS || MACCATALYST
3335
const string ImageEventAppResourceMemberName = "Image";
3436
const string ImageEventCustomMemberName = "Image";
37+
#elif WINDOWS
38+
const string ImageEventAppResourceMemberName = "Source";
39+
const string ImageEventCustomMemberName = "Source";
3540
#endif
3641

3742
[Theory(
38-
#if IOS || MACCATALYST
39-
Skip = "Test failing on iOS"
43+
#if IOS || MACCATALYST || WINDOWS
44+
Skip = "Test failing on iOS and WINDOWS"
4045
#endif
4146
)]
4247
[InlineData("#FF0000")]
@@ -55,28 +60,28 @@ await InvokeOnMainThreadAsync(async () =>
5560
var handler = CreateHandler(image);
5661
var platformView = GetPlatformImageView(handler);
5762

58-
await platformView.AttachAndRun(async () =>
63+
await AttachAndRun(platformView, async () =>
5964
{
6065
// the first one works
6166
image.Source = new FileImageSourceStub(firstPath);
6267
handler.UpdateValue(nameof(IImage.Source));
6368
await image.WaitUntilLoaded();
6469

65-
await platformView.AssertContainsColor(Colors.Blue.ToPlatform(), MauiContext);
70+
await platformView.AssertContainsColor(Colors.Blue, MauiContext);
6671

6772
// the second one does not
6873
image.Source = new FileImageSourceStub(secondPath);
6974
handler.UpdateValue(nameof(IImage.Source));
7075
await image.WaitUntilLoaded();
7176

72-
await platformView.AssertContainsColor(expectedColor.ToPlatform(), MauiContext);
77+
await platformView.AssertContainsColor(expectedColor, MauiContext);
7378
});
7479
});
7580
}
7681

7782
[Theory(
78-
#if _ANDROID__
79-
Skip = "Test failing on ANDROID"
83+
#if ANDROID || WINDOWS
84+
Skip = "Test failing on ANDROID and WINDOWS"
8085
#endif
8186
)]
8287
[InlineData("red.png", "#FF0000")]
@@ -116,7 +121,9 @@ await InvokeOnMainThreadAsync(async () =>
116121
#endif
117122
)]
118123
[InlineData("animated_heart.gif", true)]
124+
#if !WINDOWS
119125
[InlineData("animated_heart.gif", false)]
126+
#endif
120127
public async virtual Task AnimatedSourceInitializesCorrectly(string filename, bool isAnimating)
121128
{
122129
var image = new TStub
@@ -131,7 +138,7 @@ await InvokeOnMainThreadAsync(async () =>
131138

132139
await image.WaitUntilLoaded();
133140

134-
await GetPlatformImageView(handler).AttachAndRun(() =>
141+
await AttachAndRun(GetPlatformImageView(handler), () =>
135142
{
136143
Assert.Equal(isAnimating, GetNativeIsAnimationPlaying(handler));
137144
});
@@ -196,7 +203,11 @@ await InvokeOnMainThreadAsync(async () =>
196203
Assert.NotNull(exception);
197204
}
198205

199-
[Fact]
206+
[Fact(
207+
#if WINDOWS
208+
Skip = "Hanging on Windows."
209+
#endif
210+
)]
200211
public async Task ImageLoadSequenceIsCorrect()
201212
{
202213
await ImageLoadSequenceIsCorrectImplementation();
@@ -259,12 +270,15 @@ public async Task ImageLoadSequenceIsCorrect()
259270
});
260271
}
261272

262-
[Fact]
273+
[Fact(
274+
#if WINDOWS
275+
Skip = "Hanging on Windows."
276+
#endif
277+
)]
263278
public async Task InterruptingLoadCancelsAndStartsOver()
264279
{
265280
await InterruptingLoadCancelsAndStartsOverImplementation();
266281
}
267-
268282
async Task<List<(string Member, object Value)>> InterruptingLoadCancelsAndStartsOverImplementation()
269283
{
270284
var image = new TStub
@@ -325,7 +339,11 @@ await InvokeOnMainThreadAsync(async () =>
325339
return events;
326340
}
327341

328-
[Theory]
342+
[Theory(
343+
#if WINDOWS
344+
Skip = "To be implemented on Windows."
345+
#endif
346+
)]
329347
[InlineData("#FF0000")]
330348
[InlineData("#00FF00")]
331349
[InlineData("#000000")]
@@ -353,7 +371,11 @@ await InvokeOnMainThreadAsync(async () =>
353371
});
354372
}
355373

356-
[Fact]
374+
[Fact(
375+
#if WINDOWS
376+
Skip = "To be implemented on Windows."
377+
#endif
378+
)]
357379
public async Task InitializingSourceOnlyUpdatesImageOnce()
358380
{
359381
var image = new TStub
@@ -382,7 +404,11 @@ await InvokeOnMainThreadAsync(async () =>
382404
});
383405
}
384406

385-
[Fact]
407+
[Fact(
408+
#if WINDOWS
409+
Skip = "To be implemented on Windows."
410+
#endif
411+
)]
386412
public async Task UpdatingSourceOnlyUpdatesImageOnce()
387413
{
388414
var image = new TStub
@@ -420,7 +446,11 @@ await InvokeOnMainThreadAsync(async () =>
420446
});
421447
}
422448

423-
[Fact]
449+
[Fact(
450+
#if WINDOWS
451+
Skip = "Hanging on Windows."
452+
#endif
453+
)]
424454
public async Task ImageLoadSequenceIsCorrectWithChecks()
425455
{
426456
var events = await ImageLoadSequenceIsCorrectImplementation();
@@ -437,7 +467,11 @@ public async Task ImageLoadSequenceIsCorrectWithChecks()
437467
#endif
438468
}
439469

440-
[Fact]
470+
[Fact(
471+
#if WINDOWS
472+
Skip = "Hanging on Windows."
473+
#endif
474+
)]
441475
public async Task InterruptingLoadCancelsAndStartsOverWithChecks()
442476
{
443477
var events = await InterruptingLoadCancelsAndStartsOverImplementation();
@@ -475,7 +509,11 @@ static int GetDrawableId(string image) =>
475509
MauiProgram.DefaultContext.Resources.GetDrawableId(MauiProgram.DefaultContext.PackageName, image);
476510
#endif
477511

478-
[Fact]
512+
[Fact(
513+
#if WINDOWS
514+
Skip = "To be implemented on Windows."
515+
#endif
516+
)]
479517
public async Task UpdatingSourceToNullClearsImage()
480518
{
481519
var image = new TStub
@@ -503,7 +541,11 @@ await InvokeOnMainThreadAsync(async () =>
503541
});
504542
}
505543

506-
[Fact]
544+
[Fact(
545+
#if WINDOWS
546+
Skip = "To be implemented on Windows."
547+
#endif
548+
)]
507549
public async Task UpdatingSourceToNonexistentSourceClearsImage()
508550
{
509551
var image = new TStub
@@ -520,7 +562,7 @@ await InvokeOnMainThreadAsync(async () =>
520562

521563
image.Source = new FileImageSourceStub("fail.png");
522564
handler.UpdateValue(nameof(IImage.Source));
523-
await handler.PlatformView.AttachAndRun(() => { });
565+
await AttachAndRun(handler.PlatformView, () => { });
524566

525567
await image.WaitUntilLoaded(5000);
526568
await handler.PlatformView.AssertDoesNotContainColor(Colors.Red, MauiContext);
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using System;
2+
using System.IO;
3+
using System.Runtime.InteropServices.WindowsRuntime;
4+
using System.Text;
5+
using Microsoft.Maui.Devices;
6+
using Microsoft.Maui.Graphics;
7+
using Microsoft.Maui.Storage;
8+
using Microsoft.UI.Xaml.Media.Imaging;
9+
using Windows.Graphics.Imaging;
10+
using Xunit;
11+
using WColor = Windows.UI.Color;
12+
13+
namespace Microsoft.Maui.DeviceTests
14+
{
15+
public abstract partial class BaseImageSourceServiceTests
16+
{
17+
public static string CreateBitmapFile(int width, int height, Color color, string filename = null) =>
18+
CreateBitmapFile(width, height, color.ToWindowsColor(), filename);
19+
20+
public static string CreateBitmapFile(int width, int height, WColor color, string filename = null)
21+
{
22+
filename ??= Guid.NewGuid().ToString("N") + ".png";
23+
if (!Path.IsPathRooted(filename))
24+
{
25+
filename = Path.Combine(FileSystem.CacheDirectory, Guid.NewGuid().ToString("N"), filename);
26+
}
27+
var dir = Path.GetDirectoryName(filename);
28+
Directory.CreateDirectory(dir);
29+
30+
using var src = CreateBitmapStream(width, height, color);
31+
using var dst = File.Create(filename);
32+
src.CopyTo(dst);
33+
34+
return filename;
35+
}
36+
37+
public static Stream CreateBitmapStream(int width, int height, Color color) =>
38+
CreateBitmapStream(width, height, color.ToWindowsColor());
39+
40+
public static Stream CreateBitmapStream(int width, int height, WColor color)
41+
{
42+
var bitmap = CreateBitmap(width, height, color);
43+
44+
var stream = new MemoryStream();
45+
46+
var encoder = BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream.AsRandomAccessStream()).GetAwaiter().GetResult();
47+
48+
encoder.SetPixelData(
49+
BitmapPixelFormat.Bgra8,
50+
BitmapAlphaMode.Ignore,
51+
(uint)bitmap.PixelWidth,
52+
(uint)bitmap.PixelHeight,
53+
96,
54+
96,
55+
bitmap.PixelBuffer.ToArray());
56+
57+
stream.Position = 0;
58+
59+
return stream;
60+
}
61+
62+
public static WriteableBitmap CreateBitmap(int width, int height, Color color) =>
63+
CreateBitmap(width, height, color.ToWindowsColor());
64+
65+
public static WriteableBitmap CreateBitmap(int width, int height, WColor color)
66+
{
67+
var bitmap = new WriteableBitmap(width, height);
68+
69+
using (var stream = bitmap.PixelBuffer.AsStream())
70+
{
71+
var pixels = new byte[width * height * 4];
72+
73+
for (var y = 0; y < height; y++)
74+
{
75+
for (var x = 0; x < width; x++)
76+
{
77+
var index = (y * width + x) * 4;
78+
79+
pixels[index + 0] = color.B;
80+
pixels[index + 1] = color.G;
81+
pixels[index + 2] = color.R;
82+
pixels[index + 3] = color.A;
83+
}
84+
}
85+
86+
stream.Write(pixels, 0, pixels.Length);
87+
}
88+
89+
bitmap.Invalidate();
90+
91+
return bitmap;
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)