From ab17690f4dc1601d7dbb6b59c991029a4ea4159a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:33:43 +0000 Subject: [PATCH 1/2] Initial plan From b74e7f13955733e22384bcf8464c5554ca779698 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:49:55 +0000 Subject: [PATCH 2/2] Implement fix for iOS Entry password text loss and add test cases Co-authored-by: PureWeen <5375137+PureWeen@users.noreply.github.com> --- .../TestCases.HostApp/Issues/Issue30085.cs | 78 +++++++++++++++++++ .../src/Platform/iOS/TextFieldExtensions.cs | 5 ++ .../Handlers/Entry/EntryHandlerTests.iOS.cs | 51 ++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 src/Controls/tests/TestCases.HostApp/Issues/Issue30085.cs diff --git a/src/Controls/tests/TestCases.HostApp/Issues/Issue30085.cs b/src/Controls/tests/TestCases.HostApp/Issues/Issue30085.cs new file mode 100644 index 000000000000..00de69733090 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/Issue30085.cs @@ -0,0 +1,78 @@ +using System; + +namespace Maui.Controls.Sample.Issues +{ + [Issue(IssueTracker.Github, 30085, "Entry with IsPassword toggling loses previously entered text on iOS when IsPassword is re-enabled", PlatformAffected.iOS)] + public class Issue30085 : TestContentPage + { + public Issue30085() + { + } + + protected override void Init() + { + var entry = new Entry + { + IsPassword = true, + Placeholder = "Password", + Background = Brush.Gray, + ReturnType = ReturnType.Done, + AutomationId = "passwordEntry" + }; + + var toggleButton = new Button + { + Text = "Toggle Password Visibility", + AutomationId = "toggleButton" + }; + + var resultLabel = new Label + { + Text = "Enter text, toggle twice, then type more text. Original text should be preserved.", + AutomationId = "resultLabel" + }; + + var statusLabel = new Label + { + Text = "Status: Ready for testing", + AutomationId = "statusLabel" + }; + + toggleButton.Clicked += (sender, e) => + { + entry.IsPassword = !entry.IsPassword; + toggleButton.Text = entry.IsPassword ? "Show Password" : "Hide Password"; + statusLabel.Text = $"Status: Password visibility is {(entry.IsPassword ? "hidden" : "visible")}"; + }; + + // Add a text changed handler to show current text for testing + entry.TextChanged += (sender, e) => + { + resultLabel.Text = $"Current text: '{e.NewTextValue}' (Length: {e.NewTextValue?.Length ?? 0})"; + }; + + Content = new StackLayout + { + Padding = 20, + Children = + { + resultLabel, + entry, + toggleButton, + statusLabel, + new Label + { + Text = "Test Steps:\n" + + "1. Type 'password123' in the entry field\n" + + "2. Tap 'Toggle Password Visibility' to show text\n" + + "3. Tap 'Toggle Password Visibility' again to hide text\n" + + "4. Type '456' at the end\n" + + "5. The text should be 'password123456'", + FontSize = 12, + TextColor = Colors.Gray + } + } + }; + } + } +} \ No newline at end of file diff --git a/src/Core/src/Platform/iOS/TextFieldExtensions.cs b/src/Core/src/Platform/iOS/TextFieldExtensions.cs index 3b5211076a0c..d199fe2f1eb1 100644 --- a/src/Core/src/Platform/iOS/TextFieldExtensions.cs +++ b/src/Core/src/Platform/iOS/TextFieldExtensions.cs @@ -25,9 +25,14 @@ public static void UpdateIsPassword(this UITextField textField, IEntry entry) { if (entry.IsPassword && textField.IsFirstResponder) { + // Store the current text before disabling to prevent text loss + var currentText = textField.Text; textField.Enabled = false; textField.SecureTextEntry = true; textField.Enabled = entry.IsEnabled; + // Restore the text after re-enabling if it was cleared + if (textField.Text != currentText) + textField.Text = currentText; textField.BecomeFirstResponder(); } else diff --git a/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.iOS.cs b/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.iOS.cs index 753c158cb38b..a4d79a492ea2 100644 --- a/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.iOS.cs +++ b/src/Core/tests/DeviceTests/Handlers/Entry/EntryHandlerTests.iOS.cs @@ -863,5 +863,56 @@ int GetNativeSelectionLength(EntryHandler entryHandler) return -1; } + + [Fact(DisplayName = "Password Toggling Preserves Text When Focused")] + public async Task PasswordTogglingPreservesTextWhenFocused() + { + var initialText = "password123"; + var additionalText = "456"; + var expectedFinalText = initialText + additionalText; + + var entry = new EntryStub() + { + Text = initialText, + IsPassword = true + }; + + await InvokeOnMainThreadAsync(async () => + { + var handler = CreateHandler(entry); + var platformView = GetNativeEntry(handler); + + // Simulate the field being focused (this is key to reproducing the issue) + platformView.BecomeFirstResponder(); + + // Verify initial state - password mode with text + Assert.True(GetNativeIsPassword(handler)); + Assert.Equal(initialText, GetNativeText(handler)); + + // Toggle password off (text becomes visible) + entry.IsPassword = false; + handler.UpdateValue(nameof(IEntry.IsPassword)); + + // Verify password mode is off and text is preserved + Assert.False(GetNativeIsPassword(handler)); + Assert.Equal(initialText, GetNativeText(handler)); + + // Toggle password back on (this is where the bug occurs) + entry.IsPassword = true; + handler.UpdateValue(nameof(IEntry.IsPassword)); + + // Verify password mode is on and text is still preserved + Assert.True(GetNativeIsPassword(handler)); + Assert.Equal(initialText, GetNativeText(handler)); + + // Simulate user typing additional text (this triggers the text loss bug) + SetNativeText(handler, expectedFinalText); + + // Verify the original text is still preserved + Assert.Equal(expectedFinalText, GetNativeText(handler)); + + platformView.ResignFirstResponder(); + }); + } } } \ No newline at end of file