Skip to content

Commit

Permalink
Refactor the device tests to avoid duplicate tests (#14466)
Browse files Browse the repository at this point in the history
* Refactor the device tests to avoid duplicate tests
* Several duplicated tests because the HandlerTestBase class was re-used for the same control
* There is now a new HandlerTestBasement with _just_ helper methods for all handler tests
* Refactored the set of cross-handler tests for Focus, TextInput and TextStyle to depend on HandlerTestBasement
* Not all platforms support all the permutations, yet...
  • Loading branch information
mattleibow authored Apr 10, 2023
1 parent 20e6293 commit 523653d
Show file tree
Hide file tree
Showing 34 changed files with 1,094 additions and 971 deletions.
2 changes: 0 additions & 2 deletions src/Controls/tests/DeviceTests/Elements/Border/BorderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

namespace Microsoft.Maui.DeviceTests
{
#if !TIZEN
[Category(TestCategory.Border)]
public partial class BorderTests : ControlsHandlerTestBase
{
Expand All @@ -33,5 +32,4 @@ public async Task RoundedRectangleBorderLayoutIsCorrect()
await AssertColorAtPoint(border, expected, typeof(BorderHandler), 10, 10);
}
}
#endif
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(_MauiDotNetTfm);$(MauiPlatforms)</TargetFrameworks>
<TargetFrameworks>$(MauiDeviceTestsPlatforms)</TargetFrameworks>
<SingleProject>true</SingleProject>
<RootNamespace>Microsoft.Maui.DeviceTests</RootNamespace>
<AssemblyName>Microsoft.Maui.DeviceTests.Shared</AssemblyName>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using System.Threading.Tasks;
using Microsoft.Maui.DeviceTests.Stubs;
using Microsoft.Maui.Hosting;
using Xunit;

namespace Microsoft.Maui.DeviceTests
{
public abstract partial class FocusHandlerTests<THandler, TStub, TLayoutStub> : HandlerTestBasement<THandler, TStub>
where THandler : class, IViewHandler, new()
where TStub : IStubBase, new()
where TLayoutStub : IStubBase, ILayout, new()
{
[Fact]
public async Task FocusAndIsFocusedIsWorking()
{
EnsureHandlerCreated(builder =>
{
builder.ConfigureMauiHandlers(handler =>
{
handler.AddHandler<TLayoutStub, LayoutHandler>();
handler.AddHandler<TStub, THandler>();
});
});

// create layout with 2 elements
TStub inputControl1;
TStub inputControl2;
var layout = new TLayoutStub
{
(inputControl1 = new TStub { Width = 100, Height = 50 }),
(inputControl2 = new TStub { Width = 100, Height = 50 })
};
layout.Width = 100;
layout.Height = 150;

await InvokeOnMainThreadAsync(async () =>
{
var contentViewHandler = CreateHandler<LayoutHandler>(layout);
await contentViewHandler.PlatformView.AttachAndRun(async () =>
{
var platform1 = inputControl1.ToPlatform();
var platform2 = inputControl2.ToPlatform();

// focus the first control
inputControl1.Handler.Invoke(nameof(IView.Focus), new FocusRequest(false));

// assert
await inputControl1.WaitForFocused();
Assert.True(inputControl1.IsFocused);
Assert.False(inputControl2.IsFocused);

// focus the second control
inputControl2.Handler.Invoke(nameof(IView.Focus), new FocusRequest(false));

// assert
await inputControl2.WaitForFocused();
Assert.False(inputControl1.IsFocused);
Assert.True(inputControl2.IsFocused);
});
});
}

// TODO: Android is not unfocusing
#if IOS || MACCATALYST || WINDOWS
[Fact]
public async Task UnfocusAndIsFocusedIsWorking()
{
EnsureHandlerCreated(builder =>
{
builder.ConfigureMauiHandlers(handler =>
{
handler.AddHandler<TLayoutStub, LayoutHandler>();
handler.AddHandler<TStub, THandler>();
});
});

// create layout with 2 elements
TStub inputControl1;
TStub inputControl2;
var layout = new TLayoutStub
{
(inputControl1 = new TStub { Width = 100, Height = 50 }),
(inputControl2 = new TStub { Width = 100, Height = 50 })
};
layout.Width = 100;
layout.Height = 150;

await InvokeOnMainThreadAsync(async () =>
{
var contentViewHandler = CreateHandler<LayoutHandler>(layout);
await contentViewHandler.PlatformView.AttachAndRun(async () =>
{
var platform1 = inputControl1.ToPlatform();
var platform2 = inputControl2.ToPlatform();

// focus the first control
inputControl1.Handler.Invoke(nameof(IView.Focus), new FocusRequest(false));

// assert
await inputControl1.WaitForFocused();
Assert.True(inputControl1.IsFocused);
Assert.False(inputControl2.IsFocused);

// UNfocus the first control (revert the focus)
inputControl1.Handler.Invoke(nameof(IView.Unfocus));

// assert
await inputControl1.WaitForUnFocused();
Assert.False(inputControl1.IsFocused);
Assert.False(inputControl2.IsFocused);
});
});
}
#endif
}
}
190 changes: 1 addition & 189 deletions src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,195 +13,7 @@

namespace Microsoft.Maui.DeviceTests
{
public class HandlerTestBase : TestBase, IDisposable
public class HandlerTestBase : HandlerTestBasement
{
MauiApp _mauiApp;
IServiceProvider _servicesProvider;
IMauiContext _mauiContext;
bool _isCreated;

public void EnsureHandlerCreated(Action<MauiAppBuilder> additionalCreationActions = null)
{
if (_isCreated)
{
return;
}

_isCreated = true;


var appBuilder = MauiApp.CreateBuilder();

appBuilder.Services.AddSingleton<IDispatcherProvider>(svc => TestDispatcher.Provider);
appBuilder.Services.AddScoped<IDispatcher>(svc => TestDispatcher.Current);
appBuilder.Services.AddSingleton<IApplication>((_) => new CoreApplicationStub());

appBuilder = ConfigureBuilder(appBuilder);
additionalCreationActions?.Invoke(appBuilder);

_mauiApp = appBuilder.Build();
_servicesProvider = _mauiApp.Services;

_mauiContext = new ContextStub(_servicesProvider);
}

protected virtual MauiAppBuilder ConfigureBuilder(MauiAppBuilder mauiAppBuilder)
{
return mauiAppBuilder;
}

protected IMauiContext MauiContext
{
get
{
EnsureHandlerCreated();
return _mauiContext;
}
}

protected IServiceProvider ApplicationServices
{
get
{
EnsureHandlerCreated();
return _servicesProvider;
}
}

protected Task SetValueAsync<TValue, THandler>(IView view, TValue value, Action<THandler, TValue> func)
where THandler : IElementHandler, new()
{
return InvokeOnMainThreadAsync(() =>
{
var handler = CreateHandler<THandler>(view);
func(handler, value);
});
}

protected THandler CreateHandler<THandler>(IElement view, IMauiContext mauiContext = null)
where THandler : IElementHandler, new()
=> CreateHandler<THandler, THandler>(view, mauiContext);


protected void InitializeViewHandler(IElement element, IElementHandler handler, IMauiContext mauiContext = null)
{
mauiContext ??= MauiContext;
handler.SetMauiContext(mauiContext);
element.Handler = handler;
handler.SetVirtualView(element);

if (element is IView view && handler is IViewHandler viewHandler)
{
#if ANDROID
// If the Android views don't have LayoutParams set, updating some properties (e.g., Text)
// can run into issues when deciding whether a re-layout is required. Normally this isn't
// an issue because the LayoutParams get set when the view is added to a ViewGroup, but
// since we're not doing that here, we need to ensure they have LayoutParams so that tests
// which update properties don't crash.

var aView = viewHandler.PlatformView as Android.Views.View;
if (aView.LayoutParameters == null)
{
aView.LayoutParameters =
new Android.Views.ViewGroup.LayoutParams(
Android.Views.ViewGroup.LayoutParams.WrapContent,
Android.Views.ViewGroup.LayoutParams.WrapContent);
}

var size = view.Measure(view.Width, view.Height);
var w = size.Width;
var h = size.Height;
#elif IOS || MACCATALYST
var size = view.Measure(double.PositiveInfinity, double.PositiveInfinity);
var w = size.Width;
var h = size.Height;

// No measure method should be returning infinite values
Assert.False(double.IsPositiveInfinity(w));
Assert.False(double.IsPositiveInfinity(h));

#else
// Windows cannot measure without the view being loaded
// iOS needs more love when I get an IDE again
var w = view.Width.Equals(double.NaN) ? -1 : view.Width;
var h = view.Height.Equals(double.NaN) ? -1 : view.Height;
#endif

view.Arrange(new Rect(0, 0, w, h));
viewHandler.PlatformArrange(view.Frame);
}
}

protected TCustomHandler CreateHandler<THandler, TCustomHandler>(IElement element, IMauiContext mauiContext)
where THandler : IElementHandler, new()
where TCustomHandler : THandler, new()
{
if (element.Handler is TCustomHandler t)
return t;

mauiContext ??= MauiContext;
var handler = Activator.CreateInstance<TCustomHandler>();
InitializeViewHandler(element, handler, mauiContext);
return handler;
}

#if PLATFORM
protected IPlatformViewHandler CreateHandler(IElement view, Type handlerType)
{
if (view.Handler is IPlatformViewHandler t)
return t;

var handler = (IPlatformViewHandler)Activator.CreateInstance(handlerType);
InitializeViewHandler(view, handler, MauiContext);
return handler;

}

protected Task ValidateHasColor(IView view, Color color, Type handlerType, Action action = null, string updatePropertyValue = null)
{
#if !TIZEN
return InvokeOnMainThreadAsync(async () =>
{
var handler = CreateHandler(view, handlerType);
var plaformView = handler.ToPlatform();
action?.Invoke();
if (!string.IsNullOrEmpty(updatePropertyValue))
{
handler.UpdateValue(updatePropertyValue);
}

await plaformView.AssertContainsColor(color);
});
#else
throw new NotImplementedException();
#endif
}

protected Task AssertColorAtPoint(IView view, Color color, Type handlerType, int x, int y)
{
#if !TIZEN
return InvokeOnMainThreadAsync(async () =>
{
var plaformView = CreateHandler(view, handlerType).ToPlatform();
#if WINDOWS
await plaformView.AssertColorAtPointAsync(color.ToWindowsColor(), x, y);
#else
await plaformView.AssertColorAtPointAsync(color.ToPlatform(), x, y);
#endif
});
#else
throw new NotImplementedException();
#endif
}

#endif

public void Dispose()
{
((IDisposable)_mauiApp)?.Dispose();
_mauiApp = null;
_servicesProvider = null;
_mauiContext = null;
}
}
}
Loading

0 comments on commit 523653d

Please sign in to comment.