Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 2 additions & 2 deletions eng/pipelines/common/device-tests-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ steps:
inputs:
testResultsFormat: xUnit
testResultsFiles: '$(TestResultsDirectory)/**/TestResults.xml'
testRunTitle: '$(System.PhaseName)'
testRunTitle: '$(System.PhaseName) (attempt: $(System.JobAttempt))'

- task: PublishBuildArtifacts@1
displayName: Publish Artifacts
condition: always()
inputs:
artifactName: $(System.PhaseName)
artifactName: $(System.PhaseName)_attempt_$(System.JobAttempt)

# This must always be placed as the last step in the job
- template: agent-rebooter/mac.v1.yml@yaml-templates
Expand Down
1 change: 0 additions & 1 deletion src/Controls/src/Core/Entry/Entry.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ public static void MapText(IEntryHandler handler, Entry entry)
static void MapFocus(IViewHandler handler, IView view, object args)
{
handler.ShowKeyboardIfFocused(view);
EntryHandler.CommandMapper.Chained?.Invoke(handler, view, nameof(IView.Focus), args);
}
}
}
12 changes: 4 additions & 8 deletions src/Controls/src/Core/Entry/Entry.Mapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,14 @@ public partial class Entry
[nameof(TextTransform)] = MapText,
};

static CommandMapper<IEntry, IEntryHandler> ControlsCommandMapper = new(EntryHandler.CommandMapper)
{
#if ANDROID
[nameof(IEntry.Focus)] = MapFocus
#endif
};

internal static new void RemapForControls()
{
// Adjust the mappings to preserve Controls.Entry legacy behaviors
EntryHandler.Mapper = ControlsEntryMapper;
EntryHandler.CommandMapper = ControlsCommandMapper;

#if ANDROID
EntryHandler.CommandMapper.AppendToMapping(nameof(IEntry.Focus), MapFocus);
Comment thread
mattleibow marked this conversation as resolved.
Outdated
#endif
}
}
}
1 change: 0 additions & 1 deletion src/Controls/src/Core/HandlerImpl/Editor/Editor.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ public static void MapText(IEditorHandler handler, Editor editor)
static void MapFocus(IViewHandler handler, IView view, object args)
{
handler.ShowKeyboardIfFocused(view);
EditorHandler.CommandMapper.Chained?.Invoke(handler, view, nameof(IView.Focus), args);
}
}
}
12 changes: 4 additions & 8 deletions src/Controls/src/Core/HandlerImpl/Editor/Editor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,14 @@ public partial class Editor
[nameof(TextTransform)] = MapText,
};

static CommandMapper<IEditor, IEditorHandler> ControlsCommandMapper = new(EditorHandler.CommandMapper)
{
#if ANDROID
[nameof(IEditor.Focus)] = MapFocus
#endif
};

internal static new void RemapForControls()
{
// Adjust the mappings to preserve Controls.Editor legacy behaviors
EditorHandler.Mapper = ControlsEditorMapper;
EditorHandler.CommandMapper = ControlsCommandMapper;

#if ANDROID
EditorHandler.CommandMapper.AppendToMapping(nameof(IEditor.Focus), MapFocus);
Comment thread
mattleibow marked this conversation as resolved.
Outdated
#endif
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ public static void MapText(ISearchBarHandler handler, SearchBar searchBar)
static void MapFocus(IViewHandler handler, IView view, object args)
{
handler.ShowKeyboardIfFocused(view);
SearchBarHandler.CommandMapper.Chained?.Invoke(handler, view, nameof(IView.Focus), args);
}
}
}
12 changes: 4 additions & 8 deletions src/Controls/src/Core/HandlerImpl/SearchBar/SearchBar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,14 @@ public partial class SearchBar
[nameof(TextTransform)] = MapText,
};

static CommandMapper<ISearchBar, ISearchBarHandler> ControlsCommandMapper = new(SearchBarHandler.CommandMapper)
{
#if ANDROID
[nameof(ISearchBar.Focus)] = MapFocus
#endif
};

internal static new void RemapForControls()
{
// Adjust the mappings to preserve Controls.SearchBar legacy behaviors
SearchBarHandler.Mapper = ControlsSearchBarMapper;
SearchBarHandler.CommandMapper = ControlsCommandMapper;

#if ANDROID
SearchBarHandler.CommandMapper.AppendToMapping(nameof(ISearchBar.Focus), MapFocus);
Comment thread
mattleibow marked this conversation as resolved.
Outdated
#endif
}
}
}
12 changes: 4 additions & 8 deletions src/Controls/src/Core/HandlerImpl/VisualElement/VisualElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,11 @@ public partial class VisualElement
[nameof(IViewHandler.ContainerView)] = MapContainerView,
};

static CommandMapper<IView, IViewHandler> ControlsViewCommandMapper = new(ViewHandler.ViewCommandMapper)
{
[nameof(IView.Focus)] = MapFocus,
};

internal static void RemapForControls()
{
ViewHandler.ViewMapper = ControlsVisualElementMapper;
ViewHandler.ViewCommandMapper = ControlsViewCommandMapper;

ViewHandler.ViewCommandMapper.AppendToMapping<VisualElement, IViewHandler>(nameof(IView.Focus), MapFocus);
Comment thread
mattleibow marked this conversation as resolved.
Outdated
}

public static void MapBackgroundColor(IViewHandler handler, IView view)
Expand Down Expand Up @@ -72,10 +68,10 @@ static void MapContainerView(IViewHandler arg1, IView arg2)

static void MapFocus(IViewHandler handler, IView view, object args)
{
if (args is not FocusRequest fr)
if (args is not FocusRequest fr || view is not VisualElement element)
return;

view.MapFocus(handler, fr);
element.MapFocus(fr);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,11 @@ public static void UpdateText(this UITextView textView, InputView inputView)
var cursorPosition = textView.IsFirstResponder ? textView.GetCursorPosition(cursorOffset) : newText.Length;

if (oldText != newText)
{
textView.Text = newText;

textView.SetTextRange(cursorPosition, 0);
textView.SetTextRange(cursorPosition, 0);
Comment thread
mattleibow marked this conversation as resolved.
Outdated
}
}

public static void UpdateText(this UITextField textField, InputView inputView)
Expand All @@ -67,9 +69,11 @@ public static void UpdateText(this UITextField textField, InputView inputView)
var cursorPosition = textField.IsEditing ? textField.GetCursorPosition(cursorOffset) : newText.Length;

if (oldText != newText)
{
textField.Text = newText;

textField.SetTextRange(cursorPosition, 0);
textField.SetTextRange(cursorPosition, 0);
Comment thread
mattleibow marked this conversation as resolved.
Outdated
}
}

public static void UpdateLineBreakMode(this UILabel platformLabel, Label label)
Expand Down
32 changes: 11 additions & 21 deletions src/Controls/src/Core/ViewExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -452,35 +452,25 @@ static internal bool RequestFocus(this VisualElement view)

// if there is no handler, we need to still run some code
var focusRequest = new FocusRequest();
MapFocus(view, null, focusRequest);
MapFocus(view, focusRequest);
return focusRequest.Result;
}

static internal void MapFocus(this IView view, IViewHandler? handler, FocusRequest focusRequest)
static internal void MapFocus(this VisualElement view, FocusRequest focusRequest)
{
if (view is VisualElement ve)
// the virtual view is already focused
if (view.IsFocused)
{
// the virtual view is already focused
if (ve.IsFocused)
{
focusRequest.TrySetResult(true);
return;
}

// if there are legacy events, then use that
if (ve.HasFocusChangeRequestedEvent)
{
var arg = new VisualElement.FocusRequestArgs { Focus = true };
ve.InvokeFocusChangeRequested(arg);
focusRequest.TrySetResult(arg.Result);
return;
}
focusRequest.TrySetResult(true);
return;
}

// otherwise, fall back to "base"
if (handler is not null)
// if there are legacy events, then use that
if (view.HasFocusChangeRequestedEvent)
{
ViewHandler.MapFocus(handler, view, focusRequest);
var arg = new VisualElement.FocusRequestArgs { Focus = true };
view.InvokeFocusChangeRequested(arg);
focusRequest.TrySetResult(arg.Result);
return;
}

Expand Down
40 changes: 40 additions & 0 deletions src/Controls/tests/DeviceTests/Elements/Editor/EditorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,26 @@ await ValidatePropertyInitValue<int, EditorHandler>(
0);
}

[Theory(DisplayName = "Unset CursorPosition is kept at zero at initialization with TextTransform")]
[InlineData("This is a test!!!")]
[InlineData("a")]
[InlineData("")]
[InlineData(" ")]
public async Task UnsetCursorPositionKeepsToZeroOnInitializationWithTextTransform(string text)
{
var editor = new Editor
{
Text = text,
TextTransform = TextTransform.Uppercase
};

await ValidatePropertyInitValue<int, EditorHandler>(
editor,
() => editor.CursorPosition,
GetPlatformCursorPosition,
0);
}

[Theory(DisplayName = "CursorPosition moves to the end on text change after initialization"
#if WINDOWS
, Skip = "For some reason, the PlatformView events are not being fired on tests after the handler is created, something is swallowing them. " +
Expand Down Expand Up @@ -268,6 +288,26 @@ await ValidatePropertyInitValue<int, EditorHandler>(
0);
}

[Theory(DisplayName = "Unset SelectionLength is kept at zero at initialization with TextTransform")]
[InlineData("This is a test!!!")]
[InlineData("a")]
[InlineData("")]
[InlineData(" ")]
public async Task UnsetSelectionLengthKeepsToZeroOnInitializationWithTextTransform(string text)
{
var editor = new Editor
{
Text = text,
TextTransform = TextTransform.Uppercase
};

await ValidatePropertyInitValue<int, EditorHandler>(
editor,
() => editor.SelectionLength,
GetPlatformSelectionLength,
0);
}

[Theory(DisplayName = "SelectionLength is kept at zero on text change after initialization"
#if WINDOWS
, Skip = "For some reason, the PlatformView events are not being fired on tests after the handler is created, something is swallowing them. " +
Expand Down
40 changes: 40 additions & 0 deletions src/Controls/tests/DeviceTests/Elements/Entry/EntryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,26 @@ await ValidatePropertyInitValue<int, EntryHandler>(
0);
}

[Theory(DisplayName = "Unset CursorPosition is kept at zero at initialization with TextTransform")]
[InlineData("This is a test!!!")]
[InlineData("a")]
[InlineData("")]
[InlineData(" ")]
public async Task UnsetCursorPositionIsKeptAtZeroAtInitializationWithTextTransform(string text)
{
var entry = new Entry
{
Text = text,
TextTransform = TextTransform.Uppercase
};

await ValidatePropertyInitValue<int, EntryHandler>(
entry,
() => entry.CursorPosition,
GetPlatformCursorPosition,
0);
}

[Theory(DisplayName = "CursorPosition moves to the end on text change by code after initialization"
#if WINDOWS
, Skip = "For some reason, the PlatformView events are not being fired on tests after the handler is created, something is swallowing them. " +
Expand Down Expand Up @@ -290,6 +310,26 @@ await ValidatePropertyInitValue<int, EntryHandler>(
0);
}

[Theory(DisplayName = "Unset SelectionLength is kept at zero at initialization with TextTransform")]
[InlineData("This is a test!!!")]
[InlineData("a")]
[InlineData("")]
[InlineData(" ")]
public async Task UnsetSelectionLengthIsKeptAtZeroAtInitializationWithTextTransform(string text)
{
var entry = new Entry
{
Text = text,
TextTransform = TextTransform.Uppercase
};

await ValidatePropertyInitValue<int, EntryHandler>(
entry,
() => entry.SelectionLength,
GetPlatformSelectionLength,
0);
}

[Theory(DisplayName = "SelectionLength is kept at zero on text change by code after initialization"
#if WINDOWS
, Skip = "For some reason, the PlatformView events are not being fired on tests after the handler is created, something is swallowing them. " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public static void MapKeyboard(ISearchBarHandler handler, ISearchBar searchBar)
handler.PlatformView?.UpdateKeyboard(searchBar);
}

static void MapFocus(ISearchBarHandler handler, ISearchBar searchBar, object? args)
public static void MapFocus(ISearchBarHandler handler, ISearchBar searchBar, object? args)
Comment thread
PureWeen marked this conversation as resolved.
{
if (args is FocusRequest request)
handler.QueryEditor?.Focus(request);
Expand Down
1 change: 1 addition & 0 deletions src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ static Microsoft.Maui.FontSize.operator ==(Microsoft.Maui.FontSize left, Microso
static Microsoft.Maui.Graphics.PaintExtensions.ToDrawable(this Microsoft.Maui.Graphics.Paint? paint, Android.Content.Context? context) -> Android.Graphics.Drawables.Drawable?
static Microsoft.Maui.GridLength.operator !=(Microsoft.Maui.GridLength left, Microsoft.Maui.GridLength right) -> bool
static Microsoft.Maui.GridLength.operator ==(Microsoft.Maui.GridLength left, Microsoft.Maui.GridLength right) -> bool
static Microsoft.Maui.Handlers.SearchBarHandler.MapFocus(Microsoft.Maui.Handlers.ISearchBarHandler! handler, Microsoft.Maui.ISearchBar! searchBar, object? args) -> void
static Microsoft.Maui.Handlers.StepperHandler.MapIsEnabled(Microsoft.Maui.Handlers.IStepperHandler! handler, Microsoft.Maui.IStepper! stepper) -> void
static Microsoft.Maui.Layouts.FlexBasis.operator !=(Microsoft.Maui.Layouts.FlexBasis left, Microsoft.Maui.Layouts.FlexBasis right) -> bool
static Microsoft.Maui.Layouts.FlexBasis.operator ==(Microsoft.Maui.Layouts.FlexBasis left, Microsoft.Maui.Layouts.FlexBasis right) -> bool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,9 @@ public class ButtonTextStyleTests : TextStyleHandlerTests<ButtonHandler, ButtonS
{
}

// TODO: only windows button focus tests are working
#if WINDOWS
// TODO: buttons are not focusable on Android without FocusableInTouchMode=true and iOS is having issues
// https://github.com/dotnet/maui/issues/6482
[Category(TestCategory.Button)]
public class ButtonFocusTests : FocusHandlerTests<ButtonHandler, ButtonStub, VerticalStackLayoutStub>
{
Expand Down
12 changes: 12 additions & 0 deletions src/TestUtils/src/DeviceTests/AssertionExtensions.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ public static string CreateColorError(this Bitmap bitmap, string message) =>
public static string CreateEqualError(this Bitmap bitmap, Bitmap other, string message) =>
$"{message} This is what it looked like: <img>{bitmap.ToBase64String()}</img> and <img>{other.ToBase64String()}</img>";

public static string CreateScreenshotError(this Bitmap bitmap, string message) =>
$"{message} This is what it looked like:<img>{bitmap.ToBase64String()}</img>";

public static AColor ColorAtPoint(this Bitmap bitmap, int x, int y, bool includeAlpha = false)
{
int pixel = bitmap.GetPixel(x, y);
Expand Down Expand Up @@ -594,6 +597,15 @@ public static Task AssertNotEqualAsync(this Bitmap bitmap, Bitmap other)
return Task.CompletedTask;
}

public static async Task ThrowScreenshot(this AView view, IMauiContext mauiContext, string? message = null, Exception? ex = null)
{
var bitmap = await view.ToBitmap(mauiContext);
if (ex is null)
throw new XunitException(CreateScreenshotError(bitmap, message ?? "There was an error."));
else
throw new XunitException(CreateScreenshotError(bitmap, message ?? "There was an error: " + ex.Message), ex);
}

public static TextUtils.TruncateAt? ToPlatform(this LineBreakMode mode) =>
mode switch
{
Expand Down
Loading