diff --git a/src/Core/src/Handlers/Editor/EditorHandler.Android.cs b/src/Core/src/Handlers/Editor/EditorHandler.Android.cs index f59393b0a851..c70dbcf0e384 100644 --- a/src/Core/src/Handlers/Editor/EditorHandler.Android.cs +++ b/src/Core/src/Handlers/Editor/EditorHandler.Android.cs @@ -102,8 +102,12 @@ public static void MapHorizontalTextAlignment(IEditorHandler handler, IEditor ed public static void MapVerticalTextAlignment(IEditorHandler handler, IEditor editor) => handler.PlatformView?.UpdateVerticalTextAlignment(editor); - public static void MapKeyboard(IEditorHandler handler, IEditor editor) => + public static void MapKeyboard(IEditorHandler handler, IEditor editor) + { + handler.UpdateValue(nameof(IEditor.Text)); + handler.PlatformView?.UpdateKeyboard(editor); + } public static void MapCursorPosition(IEditorHandler handler, ITextInput editor) => handler.PlatformView?.UpdateCursorPosition(editor); diff --git a/src/Core/src/Handlers/Entry/EntryHandler.Android.cs b/src/Core/src/Handlers/Entry/EntryHandler.Android.cs index ac60efa26e1c..15c87ac0906f 100644 --- a/src/Core/src/Handlers/Entry/EntryHandler.Android.cs +++ b/src/Core/src/Handlers/Entry/EntryHandler.Android.cs @@ -73,8 +73,12 @@ public static void MapText(IEntryHandler handler, IEntry entry) => public static void MapTextColor(IEntryHandler handler, IEntry entry) => handler.PlatformView?.UpdateTextColor(entry); - public static void MapIsPassword(IEntryHandler handler, IEntry entry) => + public static void MapIsPassword(IEntryHandler handler, IEntry entry) + { + handler.UpdateValue(nameof(IEntry.Text)); + handler.PlatformView?.UpdateIsPassword(entry); + } public static void MapHorizontalTextAlignment(IEntryHandler handler, IEntry entry) => handler.PlatformView?.UpdateHorizontalTextAlignment(entry); @@ -103,11 +107,19 @@ public static void MapPlaceholderColor(IEntryHandler handler, IEntry entry) public static void MapFont(IEntryHandler handler, IEntry entry) => handler.PlatformView?.UpdateFont(entry, handler.GetRequiredService()); - public static void MapIsReadOnly(IEntryHandler handler, IEntry entry) => + public static void MapIsReadOnly(IEntryHandler handler, IEntry entry) + { + handler.UpdateValue(nameof(IEntry.Text)); + handler.PlatformView?.UpdateIsReadOnly(entry); + } + + public static void MapKeyboard(IEntryHandler handler, IEntry entry) + { + handler.UpdateValue(nameof(IEntry.Text)); - public static void MapKeyboard(IEntryHandler handler, IEntry entry) => handler.PlatformView?.UpdateKeyboard(entry); + } public static void MapReturnType(IEntryHandler handler, IEntry entry) => handler.PlatformView?.UpdateReturnType(entry); diff --git a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs index 8db07133ff04..1d21a1dc52fc 100644 --- a/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs +++ b/src/Core/src/Handlers/SearchBar/SearchBarHandler.Android.cs @@ -121,6 +121,8 @@ public static void MapCancelButtonColor(ISearchBarHandler handler, ISearchBar se public static void MapKeyboard(ISearchBarHandler handler, ISearchBar searchBar) { + handler.UpdateValue(nameof(ISearchBar.Text)); + handler.PlatformView?.UpdateKeyboard(searchBar); } diff --git a/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBasementOfT.cs b/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBasementOfT.cs index b190bac4fe57..477deeb2aebb 100644 --- a/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBasementOfT.cs +++ b/src/Core/tests/DeviceTests.Shared/HandlerTests/HandlerTestBasementOfT.cs @@ -74,6 +74,26 @@ protected Task GetValueAsync(IView view, Func GetValueAsync(IView view, Func func) + where TCustomHandler : IElementHandler, new() + { + return InvokeOnMainThreadAsync(() => + { + var handler = CreateHandler(view); + return func(handler); + }); + } + + protected Task GetValueAsync(IView view, Func> func) + where TCustomHandler : IElementHandler, new() + { + return InvokeOnMainThreadAsync(() => + { + var handler = CreateHandler(view); + return func(handler); + }); + } + protected Task SetValueAsync(IView view, TValue value, Action func) { return SetValueAsync(view, value, func); @@ -98,6 +118,26 @@ async protected Task ValidatePropertyInitValue( Assert.Equal(expectedValue, values.PlatformViewValue); } + async protected Task ValidatePropertyInitValue( + IView view, + Func GetValue, + Func GetPlatformValue, + TValue expectedValue) + where TCustomHandler : IElementHandler, new() + { + var values = await GetValueAsync(view, (TCustomHandler handler) => + { + return new + { + ViewValue = GetValue(), + PlatformViewValue = GetPlatformValue(handler) + }; + }); + + Assert.Equal(expectedValue, values.ViewValue); + Assert.Equal(expectedValue, values.PlatformViewValue); + } + async protected Task ValidatePropertyInitValue( IView view, Func GetValue, @@ -118,6 +158,27 @@ async protected Task ValidatePropertyInitValue( Assert.Equal(expectedPlatformValue, values.PlatformViewValue); } + async protected Task ValidatePropertyInitValue( + IView view, + Func GetValue, + Func GetPlatformValue, + TValue expectedValue, + TValue expectedPlatformValue) + where TCustomHandler : IElementHandler, new() + { + var values = await GetValueAsync(view, (TCustomHandler handler) => + { + return new + { + ViewValue = GetValue(), + PlatformViewValue = GetPlatformValue(handler) + }; + }); + + Assert.Equal(expectedValue, values.ViewValue); + Assert.Equal(expectedPlatformValue, values.PlatformViewValue); + } + async protected Task ValidatePropertyUpdatesValue( IView view, string property, diff --git a/src/Core/tests/DeviceTests/Handlers/Editor/EditorHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Editor/EditorHandlerTests.cs index e01e30d35040..ea69e5944ff9 100644 --- a/src/Core/tests/DeviceTests/Handlers/Editor/EditorHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Editor/EditorHandlerTests.cs @@ -21,6 +21,23 @@ public async Task TextInitializesCorrectly() await ValidatePropertyInitValue(editor, () => editor.Text, GetNativeText, editor.Text); } + [Fact(DisplayName = "Text Property Initializes Correctly when Keyboard Mapper is Executed Before Text Mapper")] + public async Task TextInitializesCorrectlyWhenKeyboardIsBeforeText() + { + var editor = new EditorStub() + { + Text = "Test Text Here" + }; + + CustomEditorHandler.TestMapper = new PropertyMapper(EditorHandler.Mapper) + { + // this mapper is run first and then the ones in the ctor arg (EditorHandler.Mapper) + [nameof(IEditor.Keyboard)] = EditorHandler.MapKeyboard + }; + + await ValidatePropertyInitValue(editor, () => editor.Text, GetNativeText, editor.Text); + } + [Theory(DisplayName = "Text Updates Correctly")] [InlineData(null, null)] [InlineData(null, "Hello")] @@ -515,5 +532,18 @@ protected override int GetCursorStartPosition(EditorHandler editorHandler) => protected override void UpdateCursorStartPosition(EditorHandler editorHandler, int position) => EditorHandlerTests.UpdateCursorStartPosition(editorHandler, position); } + + class CustomEditorHandler : EditorHandler + { + // make a copy of the Core mappers because we don't want any Controls changes or to override us + public static PropertyMapper TestMapper = new(Mapper); + public static CommandMapper TestCommandMapper = new(CommandMapper); + + // make sure to use our mappers + public CustomEditorHandler() + : base(TestMapper, TestCommandMapper) + { + } + } } } diff --git a/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.cs index 60086d52d4f1..c09453a46cf7 100644 --- a/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.cs @@ -23,6 +23,57 @@ public async Task TextInitializesCorrectly() await ValidatePropertyInitValue(entry, () => entry.Text, GetNativeText, entry.Text); } + [Fact(DisplayName = "Text Property Initializes Correctly when Keyboard Mapper is Executed Before Text Mapper")] + public async Task TextInitializesCorrectlyWhenKeyboardIsBeforeText() + { + var entry = new EntryStub() + { + Text = "Test Text Here" + }; + + CustomEntryHandler.TestMapper = new PropertyMapper(EntryHandler.Mapper) + { + // this mapper is run first and then the ones in the ctor arg (EntryHandler.Mapper) + [nameof(IEntry.Keyboard)] = EntryHandler.MapKeyboard + }; + + await ValidatePropertyInitValue(entry, () => entry.Text, GetNativeText, entry.Text); + } + + [Fact(DisplayName = "Text Property Initializes Correctly when IsReadOnly Mapper is Executed Before Text Mapper")] + public async Task TextInitializesCorrectlyWhenIsReadOnlyIsBeforeText() + { + var entry = new EntryStub() + { + Text = "Test Text Here" + }; + + CustomEntryHandler.TestMapper = new PropertyMapper(EntryHandler.Mapper) + { + // this mapper is run first and then the ones in the ctor arg (EntryHandler.Mapper) + [nameof(IEntry.IsReadOnly)] = EntryHandler.MapIsReadOnly + }; + + await ValidatePropertyInitValue(entry, () => entry.Text, GetNativeText, entry.Text); + } + + [Fact(DisplayName = "Text Property Initializes Correctly when IsPassword Mapper is Executed Before Text Mapper")] + public async Task TextInitializesCorrectlyWhenIsPasswordIsBeforeText() + { + var entry = new EntryStub() + { + Text = "Test Text Here" + }; + + CustomEntryHandler.TestMapper = new PropertyMapper(EntryHandler.Mapper) + { + // this mapper is run first and then the ones in the ctor arg (EntryHandler.Mapper) + [nameof(IEntry.IsPassword)] = EntryHandler.MapIsPassword + }; + + await ValidatePropertyInitValue(entry, () => entry.Text, GetNativeText, entry.Text); + } + [Fact(DisplayName = "TextColor Initializes Correctly")] public async Task TextColorInitializesCorrectly() { @@ -734,5 +785,18 @@ protected override int GetCursorStartPosition(EntryHandler entryHandler) => protected override void UpdateCursorStartPosition(EntryHandler entryHandler, int position) => EntryHandlerTests.UpdateCursorStartPosition(entryHandler, position); } + + class CustomEntryHandler : EntryHandler + { + // make a copy of the Core mappers because we don't want any Controls changes or to override us + public static PropertyMapper TestMapper = new(Mapper); + public static CommandMapper TestCommandMapper = new(CommandMapper); + + // make sure to use our mappers + public CustomEntryHandler() + : base(TestMapper, TestCommandMapper) + { + } + } } } diff --git a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs index 5b7e6d84e80d..07a3ca879331 100644 --- a/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs +++ b/src/Core/tests/DeviceTests/Handlers/SearchBar/SearchBarHandlerTests.cs @@ -41,6 +41,23 @@ public async Task TextInitializesCorrectly() await ValidatePropertyInitValue(searchBar, () => searchBar.Text, GetNativeText, searchBar.Text); } + [Fact(DisplayName = "Text Property Initializes Correctly when Keyboard Mapper is Executed Before Text Mapper")] + public async Task TextInitializesCorrectlyWhenKeyboardIsBeforeText() + { + var searchBar = new SearchBarStub() + { + Text = "Test Text Here" + }; + + CustomSearchBarHandler.TestMapper = new PropertyMapper(SearchBarHandler.Mapper) + { + // this mapper is run first and then the ones in the ctor arg (SearchBarHandler.Mapper) + [nameof(ISearchBar.Keyboard)] = SearchBarHandler.MapKeyboard + }; + + await ValidatePropertyInitValue(searchBar, () => searchBar.Text, GetNativeText, searchBar.Text); + } + [Theory(DisplayName = "Query Text Updates Correctly")] [InlineData(null, null)] [InlineData(null, "Query")] @@ -451,5 +468,18 @@ public SearchBarFocusTests() } } #endif + + class CustomSearchBarHandler : SearchBarHandler + { + // make a copy of the Core mappers because we don't want any Controls changes or to override us + public static PropertyMapper TestMapper = new(Mapper); + public static CommandMapper TestCommandMapper = new(CommandMapper); + + // make sure to use our mappers + public CustomSearchBarHandler() + : base(TestMapper, TestCommandMapper) + { + } + } } } \ No newline at end of file