From eb81d1e355cf3a9ef50781fbc4f07682b0ebbae5 Mon Sep 17 00:00:00 2001 From: Nikhil Pakhloo Date: Wed, 8 Jul 2026 15:34:52 +0530 Subject: [PATCH 1/4] fix(android): show soft keyboard on text selection --- .../react/views/textinput/ReactEditText.kt | 17 +++++++++++++++ .../textinput/ReactTextInputPropertyTest.kt | 21 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt index 0415dca1126b..9f023587c000 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt @@ -130,6 +130,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat private var keyListener: InternalKeyListener? = null private var detectScrollMovement = false private var onKeyPress = false + private var selectionWasCollapsed = true private val textAttributes: TextAttributes private var typefaceDirty = false private var fontFamily: String? = null @@ -492,6 +493,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat if (selectionWatcher != null && hasFocus()) { selectionWatcher?.onSelectionChanged(selStart, selEnd) } + maybeShowSoftKeyboardForSelection(selStart, selEnd) } override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) { @@ -499,6 +501,21 @@ public open class ReactEditText public constructor(context: Context) : AppCompat if (focused && selectionWatcher != null) { selectionWatcher?.onSelectionChanged(selectionStart, selectionEnd) } + if (focused) { + maybeShowSoftKeyboardForSelection(selectionStart, selectionEnd) + } + } + + private fun maybeShowSoftKeyboardForSelection(selectionStart: Int, selectionEnd: Int) { + if (!hasFocus() || !showSoftInputOnFocus) { + return + } + + val selectionIsCollapsed = selectionStart == selectionEnd + if (!selectionIsCollapsed && selectionWasCollapsed) { + showSoftKeyboard() + } + selectionWasCollapsed = selectionIsCollapsed } internal fun setSelectionWatcher(selectionWatcher: SelectionWatcher?) { diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt index d99a2c8be1cd..710314fc902d 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt @@ -11,6 +11,7 @@ package com.facebook.react.views.textinput +import android.content.Context import android.graphics.Color import android.os.Build import android.text.InputFilter @@ -46,6 +47,15 @@ import org.robolectric.RuntimeEnvironment @RunWith(RobolectricTestRunner::class) class ReactTextInputPropertyTest { + private class TestReactEditText(context: Context) : ReactEditText(context) { + var showSoftKeyboardCallCount = 0 + + override fun showSoftKeyboard(): Boolean { + showSoftKeyboardCallCount++ + return true + } + } + private lateinit var context: BridgeReactContext private lateinit var catalystInstanceMock: CatalystInstance private lateinit var themedContext: ThemedReactContext @@ -74,6 +84,17 @@ class ReactTextInputPropertyTest { view = manager.createViewInstance(themedContext) } + @Test + fun testShowsSoftKeyboardWhenSelectionStartsWhileFocused() { + val textInput = TestReactEditText(themedContext) + textInput.setText("hello") + textInput.onFocusChanged(true, View.FOCUS_DOWN, null) + + textInput.setSelection(1, 3) + + assertThat(textInput.showSoftKeyboardCallCount).isEqualTo(1) + } + @Test fun testAutoCorrect() { manager.updateProperties(view, buildStyles()) From 321dfba3c742f2f848e8f11fea1b4fa1c80c86bd Mon Sep 17 00:00:00 2001 From: Nikhil Pakhloo Date: Wed, 8 Jul 2026 15:58:57 +0530 Subject: [PATCH 2/4] chore: format fantom docs --- private/react-native-fantom/__docs__/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/private/react-native-fantom/__docs__/README.md b/private/react-native-fantom/__docs__/README.md index d5997c130e8d..dbfb007c8c1b 100644 --- a/private/react-native-fantom/__docs__/README.md +++ b/private/react-native-fantom/__docs__/README.md @@ -187,12 +187,12 @@ hi underlying native (Fabric/TurboModules) code, so tests stay close to real usage and are more resilient to internal refactors. Observe results through the public surface too — e.g. assert the rendered output - (`root.getRenderedOutput(...)`) or values delivered to public listeners - rather than reading private state. + (`root.getRenderedOutput(...)`) or values delivered to public listeners rather + than reading private state. - When Fantom doesn't support something (a native module, a capability, a way to observe a result, etc.), it's fine to reach into internals to work around - that limitation. Prefer a short comment explaining why the internal access is - necessary. + that limitation. Prefer a short comment explaining why the internal access + is necessary. - Place test files in `__tests__` directories alongside the code being tested. - Benchmark tests use the `-benchmark-itest.js` suffix. - Use `Fantom.runTask()` to render and run synchronous operations; it ensures From 16df7ca03c5408c0c63c06627118abee7282ea41 Mon Sep 17 00:00:00 2001 From: Nikhil Pakhloo Date: Tue, 14 Jul 2026 11:40:54 +0530 Subject: [PATCH 3/4] Test: Fix compilation errors in Android TextInput soft keyboard test --- .../textinput/ReactTextInputPropertyTest.kt | 1099 ++++++++--------- 1 file changed, 548 insertions(+), 551 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt index 710314fc902d..db18f739102b 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt @@ -1,551 +1,548 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -// TODO T207169925: Migrate CatalystInstance to Reacthost and remove the Suppress("DEPRECATION") -// annotation -@file:Suppress("DEPRECATION") - -package com.facebook.react.views.textinput - -import android.content.Context -import android.graphics.Color -import android.os.Build -import android.text.InputFilter -import android.text.InputFilter.AllCaps -import android.text.InputType -import android.text.Layout -import android.text.SpannableString -import android.text.Spanned -import android.util.DisplayMetrics -import android.view.Gravity -import android.view.View -import android.view.inputmethod.EditorInfo -import androidx.autofill.HintConstants -import androidx.core.content.res.ResourcesCompat.ID_NULL -import com.facebook.react.bridge.BridgeReactContext -import com.facebook.react.bridge.CatalystInstance -import com.facebook.react.bridge.JavaOnlyMap -import com.facebook.react.bridge.ReactTestHelper.createMockCatalystInstance -import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests -import com.facebook.react.uimanager.DisplayMetricsHolder -import com.facebook.react.uimanager.ReactStylesDiffMap -import com.facebook.react.uimanager.ThemedReactContext -import com.facebook.react.views.text.DefaultStyleValuesUtil.getDefaultTextColorHint -import com.facebook.react.views.text.ReactTextUpdate -import org.assertj.core.api.Assertions.assertThat -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment - -/** Verify {@link EditText} view property being applied properly by {@link ReactTextInputManager} */ -@RunWith(RobolectricTestRunner::class) -class ReactTextInputPropertyTest { - - private class TestReactEditText(context: Context) : ReactEditText(context) { - var showSoftKeyboardCallCount = 0 - - override fun showSoftKeyboard(): Boolean { - showSoftKeyboardCallCount++ - return true - } - } - - private lateinit var context: BridgeReactContext - private lateinit var catalystInstanceMock: CatalystInstance - private lateinit var themedContext: ThemedReactContext - private lateinit var manager: ReactTextInputManager - private lateinit var view: ReactEditText - - private val generalKeyboardTypeFlags: Int = - (InputType.TYPE_CLASS_NUMBER or - InputType.TYPE_NUMBER_FLAG_DECIMAL or - InputType.TYPE_NUMBER_FLAG_SIGNED or - InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS or - InputType.TYPE_CLASS_TEXT or - InputType.TYPE_CLASS_PHONE or - InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD and - InputType.TYPE_TEXT_VARIATION_PASSWORD.inv()) - - @Before - fun setup() { - ReactNativeFeatureFlagsForTests.setUp() - context = BridgeReactContext(RuntimeEnvironment.getApplication()) - catalystInstanceMock = createMockCatalystInstance() - context.initializeWithInstance(catalystInstanceMock) - themedContext = ThemedReactContext(context, context.baseContext, null, ID_NULL) - manager = ReactTextInputManager() - DisplayMetricsHolder.setScreenDisplayMetrics(DisplayMetrics()) - view = manager.createViewInstance(themedContext) - } - - @Test - fun testShowsSoftKeyboardWhenSelectionStartsWhileFocused() { - val textInput = TestReactEditText(themedContext) - textInput.setText("hello") - textInput.onFocusChanged(true, View.FOCUS_DOWN, null) - - textInput.setSelection(1, 3) - - assertThat(textInput.showSoftKeyboardCallCount).isEqualTo(1) - } - - @Test - fun testAutoCorrect() { - manager.updateProperties(view, buildStyles()) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero - - manager.updateProperties(view, buildStyles("autoCorrect", true)) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero - - manager.updateProperties(view, buildStyles("autoCorrect", false)) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero - - manager.updateProperties(view, buildStyles("autoCorrect", null)) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero - } - - @Test - fun testAutoCapitalize() { - manager.updateProperties(view, buildStyles()) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero - - manager.updateProperties( - view, - buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), - ) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero - - manager.updateProperties( - view, - buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_WORDS), - ) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero - - manager.updateProperties( - view, - buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS), - ) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isNotZero - - manager.updateProperties(view, buildStyles("autoCapitalize", InputType.TYPE_CLASS_TEXT)) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero - } - - @Test - fun testAutoCapitalizeDoesNotStripNumericFlags() { - val numericTypeFlags = - (InputType.TYPE_CLASS_NUMBER or - InputType.TYPE_NUMBER_FLAG_DECIMAL or - InputType.TYPE_NUMBER_FLAG_SIGNED) - - manager.updateProperties(view, buildStyles("keyboardType", "numeric")) - assertThat(view.inputType and numericTypeFlags).isEqualTo(numericTypeFlags) - - manager.updateProperties( - view, - buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), - ) - assertThat(view.inputType and InputType.TYPE_NUMBER_FLAG_SIGNED).isNotZero - assertThat(view.inputType and InputType.TYPE_NUMBER_FLAG_DECIMAL).isNotZero - } - - @Test - fun testAutoCapitalizeAndNumericKeyboardInSameTransaction() { - val numericTypeFlags = - (InputType.TYPE_CLASS_NUMBER or - InputType.TYPE_NUMBER_FLAG_DECIMAL or - InputType.TYPE_NUMBER_FLAG_SIGNED) - - manager.updateProperties( - view, - buildStyles( - "autoCapitalize", - InputType.TYPE_TEXT_FLAG_CAP_SENTENCES, - "keyboardType", - "numeric", - ), - ) - assertThat(view.inputType and numericTypeFlags).isEqualTo(numericTypeFlags) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero - } - - @Test - fun testAutoCapitalizeReappliesWhenKeyboardTypeChangesFromNumericToText() { - // CAP_SENTENCES (0x4000) doesn't share a bit position with any numeric flag, - // unlike CAP_WORDS (0x2000) / CAP_CHARACTERS (0x1000). - manager.updateProperties( - view, - buildStyles( - "autoCapitalize", - InputType.TYPE_TEXT_FLAG_CAP_SENTENCES, - "keyboardType", - "numeric", - ), - ) - assertThat(view.inputType and InputType.TYPE_MASK_CLASS).isEqualTo(InputType.TYPE_CLASS_NUMBER) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero - - manager.updateProperties(view, buildStyles("keyboardType", "default")) - assertThat(view.inputType and InputType.TYPE_MASK_CLASS).isEqualTo(InputType.TYPE_CLASS_TEXT) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isNotZero - } - - @Test - fun testPlaceholder() { - manager.updateProperties(view, buildStyles()) - assertThat(view.hint).isNull() - - manager.updateProperties(view, buildStyles("placeholder", "sometext")) - assertThat(view.hint).isEqualTo("sometext") - - manager.updateProperties(view, buildStyles("placeholder", null)) - assertThat(view.hint).isNull() - } - - @Test - fun testEditable() { - manager.updateProperties(view, buildStyles()) - assertThat(view.isEnabled).isTrue - - manager.updateProperties(view, buildStyles("editable", false)) - assertThat(view.isEnabled).isFalse - - manager.updateProperties(view, buildStyles("editable", null)) - assertThat(view.isEnabled).isTrue - - manager.updateProperties(view, buildStyles("editable", false)) - assertThat(view.isEnabled).isFalse - - manager.updateProperties(view, buildStyles("editable", true)) - assertThat(view.isEnabled).isTrue - } - - @Test - fun testAutoCompleteExtendedHints() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - return - } - - val expectedHints = - listOf( - "2fa-app-otp" to HintConstants.AUTOFILL_HINT_2FA_APP_OTP, - "email-otp" to HintConstants.AUTOFILL_HINT_EMAIL_OTP, - "flight-confirmation-code" to HintConstants.AUTOFILL_HINT_FLIGHT_CONFIRMATION_CODE, - "flight-number" to HintConstants.AUTOFILL_HINT_FLIGHT_NUMBER, - "gift-card-number" to HintConstants.AUTOFILL_HINT_GIFT_CARD_NUMBER, - "gift-card-pin" to HintConstants.AUTOFILL_HINT_GIFT_CARD_PIN, - "loyalty-account-number" to HintConstants.AUTOFILL_HINT_LOYALTY_ACCOUNT_NUMBER, - "postal-address-dependent-locality" to - HintConstants.AUTOFILL_HINT_POSTAL_ADDRESS_DEPENDENT_LOCALITY, - "postal-address-unit" to HintConstants.AUTOFILL_HINT_POSTAL_ADDRESS_APT_NUMBER, - "promo-code" to HintConstants.AUTOFILL_HINT_PROMO_CODE, - "upi-vpa" to HintConstants.AUTOFILL_HINT_UPI_VPA, - "wifi-password" to HintConstants.AUTOFILL_HINT_WIFI_PASSWORD, - ) - - expectedHints.forEach { (autoComplete, expectedHint) -> - manager.updateProperties(view, buildStyles("autoComplete", autoComplete)) - - assertThat(view.importantForAutofill).isEqualTo(View.IMPORTANT_FOR_AUTOFILL_YES) - assertThat(view.autofillHints?.toList()).containsExactly(expectedHint) - } - } - - @Test - fun testPlaceholderTextColor() { - val defaultPlaceholderColorStateList = getDefaultTextColorHint(view.context) - var colors = view.hintTextColors - assertThat(colors).isEqualTo(defaultPlaceholderColorStateList) - - manager.updateProperties(view, buildStyles("placeholderTextColor", null)) - colors = view.hintTextColors - assertThat(colors).isEqualTo(defaultPlaceholderColorStateList) - - manager.updateProperties(view, buildStyles("placeholderTextColor", Color.RED)) - colors = view.hintTextColors - assertThat(colors.defaultColor).isEqualTo(Color.RED) - - manager.updateProperties(view, buildStyles("placeholderTextColor", null)) - colors = view.hintTextColors - assertThat(colors).isEqualTo(defaultPlaceholderColorStateList) - } - - @Test - fun testMultiline() { - manager.updateProperties(view, buildStyles()) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero - - manager.updateProperties(view, buildStyles("multiline", false)) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero - - manager.updateProperties(view, buildStyles("multiline", true)) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero - - manager.updateProperties(view, buildStyles("multiline", null)) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero - } - - @Test - fun testBlurMultiline() { - val editorInfo = - EditorInfo().apply { - imeOptions = EditorInfo.IME_ACTION_DONE or EditorInfo.IME_FLAG_NO_ENTER_ACTION - } - manager.updateProperties(view, buildStyles("multiline", true)) - manager.updateProperties(view, buildStyles("submitBehavior", "blurAndSubmit")) - view.onCreateInputConnection(editorInfo) - assertThat(editorInfo.imeOptions).isEqualTo(EditorInfo.IME_ACTION_DONE) - } - - @Test - fun testNumLines() { - manager.updateProperties(view, buildStyles()) - assertThat(view.minLines).isEqualTo(1) - - manager.updateProperties(view, buildStyles("numberOfLines", 5)) - assertThat(view.minLines).isEqualTo(5) - - manager.updateProperties(view, buildStyles("numberOfLines", 4)) - assertThat(view.minLines).isEqualTo(4) - } - - @Test - fun testKeyboardTypeClassText() { - manager.updateProperties(view, buildStyles("keyboardType", null)) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(InputType.TYPE_CLASS_TEXT) - - manager.updateProperties(view, buildStyles()) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(InputType.TYPE_CLASS_TEXT) - - manager.updateProperties(view, buildStyles("keyboardType", "text")) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(InputType.TYPE_CLASS_TEXT) - } - - @Test - fun testKeyboardTypePad() { - val numberPadTypeFlags = InputType.TYPE_CLASS_NUMBER - val decimalPadTypeFlags = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL - - manager.updateProperties(view, buildStyles("keyboardType", "phone-pad")) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(InputType.TYPE_CLASS_PHONE) - - manager.updateProperties(view, buildStyles("keyboardType", "number-pad")) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(numberPadTypeFlags) - - manager.updateProperties(view, buildStyles("keyboardType", "decimal-pad")) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(decimalPadTypeFlags) - } - - @Test - fun testKeyboardTypeNumeric() { - val numericTypeFlags = - (InputType.TYPE_CLASS_NUMBER or - InputType.TYPE_NUMBER_FLAG_DECIMAL or - InputType.TYPE_NUMBER_FLAG_SIGNED) - - manager.updateProperties(view, buildStyles("keyboardType", "numeric")) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(numericTypeFlags) - } - - @Test - fun testKeyboardTypeEmail() { - val emailTypeFlags = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS or InputType.TYPE_CLASS_TEXT - - manager.updateProperties(view, buildStyles("keyboardType", "email-address")) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(emailTypeFlags) - } - - @Test - fun testKeyboardTypeUrl() { - manager.updateProperties(view, buildStyles("keyboardType", "url")) - assertThat(view.inputType and generalKeyboardTypeFlags) - .isEqualTo(InputType.TYPE_TEXT_VARIATION_URI) - } - - @Test - fun testKeyboardTypeVisiblePassword() { - val passwordVisibilityFlag = - InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD and - InputType.TYPE_TEXT_VARIATION_PASSWORD.inv() - - manager.updateProperties(view, buildStyles("keyboardType", "visible-password")) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(passwordVisibilityFlag) - } - - @Test - fun testPasswordInput() { - manager.updateProperties(view, buildStyles()) - assertThat(view.inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD).isZero - - manager.updateProperties(view, buildStyles("secureTextEntry", false)) - assertThat(view.inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD).isZero - - manager.updateProperties(view, buildStyles("secureTextEntry", true)) - assertThat(view.inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD).isNotZero - - manager.updateProperties(view, buildStyles("secureTextEntry", null)) - assertThat(view.inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD).isZero - } - - @Test - fun testIncrementalInputTypeUpdates() { - manager.updateProperties(view, buildStyles()) - assertThat(view.inputType and InputType.TYPE_CLASS_NUMBER).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero - - manager.updateProperties(view, buildStyles("multiline", true)) - assertThat(view.inputType and InputType.TYPE_CLASS_NUMBER).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero - - manager.updateProperties(view, buildStyles("autoCorrect", false)) - assertThat(view.inputType and InputType.TYPE_CLASS_NUMBER).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero - - manager.updateProperties(view, buildStyles("keyboardType", "NUMERIC")) - assertThat(view.inputType and InputType.TYPE_CLASS_NUMBER).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero - - manager.updateProperties(view, buildStyles("multiline", null)) - assertThat(view.inputType and InputType.TYPE_CLASS_NUMBER).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero - } - - @Test - fun testTextAlign() { - val defaultGravity = view.gravity - val defaultHorizontalGravity = defaultGravity and Gravity.HORIZONTAL_GRAVITY_MASK - val defaultVerticalGravity = defaultGravity and Gravity.VERTICAL_GRAVITY_MASK - - assertThat(view.gravity).isNotEqualTo(Gravity.NO_GRAVITY) - - // region TextAlign - manager.updateProperties(view, buildStyles("textAlign", "left")) - assertThat(view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(Gravity.LEFT) - - manager.updateProperties(view, buildStyles("textAlign", "right")) - assertThat(view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(Gravity.RIGHT) - - manager.updateProperties(view, buildStyles("textAlign", "center")) - assertThat(view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK) - .isEqualTo(Gravity.CENTER_HORIZONTAL) - - manager.updateProperties(view, buildStyles("textAlign", "start")) - assertThat( - view.gravity and - (Gravity.HORIZONTAL_GRAVITY_MASK or Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) - ) - .isEqualTo(Gravity.START) - - manager.updateProperties(view, buildStyles("textAlign", "end")) - assertThat( - view.gravity and - (Gravity.HORIZONTAL_GRAVITY_MASK or Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) - ) - .isEqualTo(Gravity.END) - - manager.updateProperties(view, buildStyles("textAlign", null)) - assertThat(view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(defaultHorizontalGravity) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - manager.updateProperties(view, buildStyles("textAlign", "justify")) - assertThat(view.justificationMode).isEqualTo(Layout.JUSTIFICATION_MODE_INTER_WORD) - } - // endregion - - // region TextAlignVertical - manager.updateProperties(view, buildStyles("textAlignVertical", "top")) - assertThat(view.gravity and Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.TOP) - - manager.updateProperties(view, buildStyles("textAlignVertical", "bottom")) - assertThat(view.gravity and Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.BOTTOM) - - manager.updateProperties(view, buildStyles("textAlignVertical", "center")) - assertThat(view.gravity and Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.CENTER_VERTICAL) - - manager.updateProperties(view, buildStyles("textAlignVertical", null)) - assertThat(view.gravity and Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(defaultVerticalGravity) - // endregion - - // region TextAlign + TextAlignVertical - manager.updateProperties( - view, - buildStyles("textAlign", "center", "textAlignVertical", "center"), - ) - assertThat(view.gravity).isEqualTo(Gravity.CENTER) - - manager.updateProperties(view, buildStyles("textAlign", "right", "textAlignVertical", "bottom")) - assertThat(view.gravity).isEqualTo(Gravity.RIGHT or Gravity.BOTTOM) - - manager.updateProperties(view, buildStyles("textAlign", null, "textAlignVertical", null)) - assertThat(view.gravity).isEqualTo(defaultGravity) - // endregion - } - - @Test - fun testMaxLength() { - val filters = arrayOf(AllCaps()) - view.filters = filters - manager.setMaxLength(view, null) - assertThat(view.filters).isEqualTo(filters) - } - - @Test - fun testSecureTextDoesNotReplaceSameTextFromJS() { - val markerSpan = MarkerSpan() - val textUpdate = - SpannableString("secret").apply { - setSpan(markerSpan, 0, length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) - } - - manager.updateProperties(view, buildStyles("secureTextEntry", true)) - view.setText("secret") - - view.maybeSetTextFromJS( - ReactTextUpdate( - textUpdate, - 0, - view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK, - Layout.BREAK_STRATEGY_HIGH_QUALITY, - 0, - ) - ) - - assertThat(checkNotNull(view.text).getSpans(0, view.length(), MarkerSpan::class.java)).isEmpty() - } - - private fun buildStyles(vararg keysAndValues: Any?): ReactStylesDiffMap { - return ReactStylesDiffMap(JavaOnlyMap.of(*keysAndValues)) - } - - private class MarkerSpan -} +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// TODO T207169925: Migrate CatalystInstance to Reacthost and remove the Suppress("DEPRECATION") +// annotation +@file:Suppress("DEPRECATION") + +package com.facebook.react.views.textinput + +import android.content.Context +import android.graphics.Color +import android.os.Build +import android.text.InputFilter +import android.text.InputFilter.AllCaps +import android.text.InputType +import android.text.Layout +import android.text.SpannableString +import android.text.Spanned +import android.util.DisplayMetrics +import android.view.Gravity +import android.view.View +import android.view.inputmethod.EditorInfo +import androidx.autofill.HintConstants +import androidx.core.content.res.ResourcesCompat.ID_NULL +import com.facebook.react.bridge.BridgeReactContext +import com.facebook.react.bridge.CatalystInstance +import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.ReactTestHelper.createMockCatalystInstance +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests +import com.facebook.react.uimanager.DisplayMetricsHolder +import com.facebook.react.uimanager.ReactStylesDiffMap +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.views.text.DefaultStyleValuesUtil.getDefaultTextColorHint +import com.facebook.react.views.text.ReactTextUpdate +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.RuntimeEnvironment +import org.robolectric.Shadows.shadowOf +import android.view.inputmethod.InputMethodManager + +/** Verify {@link EditText} view property being applied properly by {@link ReactTextInputManager} */ +@RunWith(RobolectricTestRunner::class) +class ReactTextInputPropertyTest { + + private lateinit var context: BridgeReactContext + private lateinit var catalystInstanceMock: CatalystInstance + private lateinit var themedContext: ThemedReactContext + private lateinit var manager: ReactTextInputManager + private lateinit var view: ReactEditText + + private val generalKeyboardTypeFlags: Int = + (InputType.TYPE_CLASS_NUMBER or + InputType.TYPE_NUMBER_FLAG_DECIMAL or + InputType.TYPE_NUMBER_FLAG_SIGNED or + InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS or + InputType.TYPE_CLASS_TEXT or + InputType.TYPE_CLASS_PHONE or + InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD and + InputType.TYPE_TEXT_VARIATION_PASSWORD.inv()) + + @Before + fun setup() { + ReactNativeFeatureFlagsForTests.setUp() + context = BridgeReactContext(RuntimeEnvironment.getApplication()) + catalystInstanceMock = createMockCatalystInstance() + context.initializeWithInstance(catalystInstanceMock) + themedContext = ThemedReactContext(context, context.baseContext, null, ID_NULL) + manager = ReactTextInputManager() + DisplayMetricsHolder.setScreenDisplayMetrics(DisplayMetrics()) + view = manager.createViewInstance(themedContext) + } + + @Test + fun testShowsSoftKeyboardWhenSelectionStartsWhileFocused() { + val inputMethodManager = + RuntimeEnvironment.getApplication().getSystemService(Context.INPUT_METHOD_SERVICE) + as InputMethodManager + val shadowImm = shadowOf(inputMethodManager) + + view.setText("hello") + view.requestFocusFromJS() + + view.setSelection(1, 3) + + assertThat(shadowImm.isSoftInputVisible).isTrue + } + + @Test + fun testAutoCorrect() { + manager.updateProperties(view, buildStyles()) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero + + manager.updateProperties(view, buildStyles("autoCorrect", true)) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isNotZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero + + manager.updateProperties(view, buildStyles("autoCorrect", false)) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero + + manager.updateProperties(view, buildStyles("autoCorrect", null)) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero + } + + @Test + fun testAutoCapitalize() { + manager.updateProperties(view, buildStyles()) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero + + manager.updateProperties( + view, + buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), + ) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isNotZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero + + manager.updateProperties( + view, + buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_WORDS), + ) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS).isNotZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero + + manager.updateProperties( + view, + buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS), + ) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isNotZero + + manager.updateProperties(view, buildStyles("autoCapitalize", InputType.TYPE_CLASS_TEXT)) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero + } + + @Test + fun testAutoCapitalizeDoesNotStripNumericFlags() { + val numericTypeFlags = + (InputType.TYPE_CLASS_NUMBER or + InputType.TYPE_NUMBER_FLAG_DECIMAL or + InputType.TYPE_NUMBER_FLAG_SIGNED) + + manager.updateProperties(view, buildStyles("keyboardType", "numeric")) + assertThat(view.inputType and numericTypeFlags).isEqualTo(numericTypeFlags) + + manager.updateProperties( + view, + buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), + ) + assertThat(view.inputType and InputType.TYPE_NUMBER_FLAG_SIGNED).isNotZero + assertThat(view.inputType and InputType.TYPE_NUMBER_FLAG_DECIMAL).isNotZero + } + + @Test + fun testAutoCapitalizeAndNumericKeyboardInSameTransaction() { + val numericTypeFlags = + (InputType.TYPE_CLASS_NUMBER or + InputType.TYPE_NUMBER_FLAG_DECIMAL or + InputType.TYPE_NUMBER_FLAG_SIGNED) + + manager.updateProperties( + view, + buildStyles( + "autoCapitalize", + InputType.TYPE_TEXT_FLAG_CAP_SENTENCES, + "keyboardType", + "numeric", + ), + ) + assertThat(view.inputType and numericTypeFlags).isEqualTo(numericTypeFlags) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero + } + + @Test + fun testAutoCapitalizeReappliesWhenKeyboardTypeChangesFromNumericToText() { + // CAP_SENTENCES (0x4000) doesn't share a bit position with any numeric flag, + // unlike CAP_WORDS (0x2000) / CAP_CHARACTERS (0x1000). + manager.updateProperties( + view, + buildStyles( + "autoCapitalize", + InputType.TYPE_TEXT_FLAG_CAP_SENTENCES, + "keyboardType", + "numeric", + ), + ) + assertThat(view.inputType and InputType.TYPE_MASK_CLASS).isEqualTo(InputType.TYPE_CLASS_NUMBER) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero + + manager.updateProperties(view, buildStyles("keyboardType", "default")) + assertThat(view.inputType and InputType.TYPE_MASK_CLASS).isEqualTo(InputType.TYPE_CLASS_TEXT) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isNotZero + } + + @Test + fun testPlaceholder() { + manager.updateProperties(view, buildStyles()) + assertThat(view.hint).isNull() + + manager.updateProperties(view, buildStyles("placeholder", "sometext")) + assertThat(view.hint).isEqualTo("sometext") + + manager.updateProperties(view, buildStyles("placeholder", null)) + assertThat(view.hint).isNull() + } + + @Test + fun testEditable() { + manager.updateProperties(view, buildStyles()) + assertThat(view.isEnabled).isTrue + + manager.updateProperties(view, buildStyles("editable", false)) + assertThat(view.isEnabled).isFalse + + manager.updateProperties(view, buildStyles("editable", null)) + assertThat(view.isEnabled).isTrue + + manager.updateProperties(view, buildStyles("editable", false)) + assertThat(view.isEnabled).isFalse + + manager.updateProperties(view, buildStyles("editable", true)) + assertThat(view.isEnabled).isTrue + } + + @Test + fun testAutoCompleteExtendedHints() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + return + } + + val expectedHints = + listOf( + "2fa-app-otp" to HintConstants.AUTOFILL_HINT_2FA_APP_OTP, + "email-otp" to HintConstants.AUTOFILL_HINT_EMAIL_OTP, + "flight-confirmation-code" to HintConstants.AUTOFILL_HINT_FLIGHT_CONFIRMATION_CODE, + "flight-number" to HintConstants.AUTOFILL_HINT_FLIGHT_NUMBER, + "gift-card-number" to HintConstants.AUTOFILL_HINT_GIFT_CARD_NUMBER, + "gift-card-pin" to HintConstants.AUTOFILL_HINT_GIFT_CARD_PIN, + "loyalty-account-number" to HintConstants.AUTOFILL_HINT_LOYALTY_ACCOUNT_NUMBER, + "postal-address-dependent-locality" to + HintConstants.AUTOFILL_HINT_POSTAL_ADDRESS_DEPENDENT_LOCALITY, + "postal-address-unit" to HintConstants.AUTOFILL_HINT_POSTAL_ADDRESS_APT_NUMBER, + "promo-code" to HintConstants.AUTOFILL_HINT_PROMO_CODE, + "upi-vpa" to HintConstants.AUTOFILL_HINT_UPI_VPA, + "wifi-password" to HintConstants.AUTOFILL_HINT_WIFI_PASSWORD, + ) + + expectedHints.forEach { (autoComplete, expectedHint) -> + manager.updateProperties(view, buildStyles("autoComplete", autoComplete)) + + assertThat(view.importantForAutofill).isEqualTo(View.IMPORTANT_FOR_AUTOFILL_YES) + assertThat(view.autofillHints?.toList()).containsExactly(expectedHint) + } + } + + @Test + fun testPlaceholderTextColor() { + val defaultPlaceholderColorStateList = getDefaultTextColorHint(view.context) + var colors = view.hintTextColors + assertThat(colors).isEqualTo(defaultPlaceholderColorStateList) + + manager.updateProperties(view, buildStyles("placeholderTextColor", null)) + colors = view.hintTextColors + assertThat(colors).isEqualTo(defaultPlaceholderColorStateList) + + manager.updateProperties(view, buildStyles("placeholderTextColor", Color.RED)) + colors = view.hintTextColors + assertThat(colors.defaultColor).isEqualTo(Color.RED) + + manager.updateProperties(view, buildStyles("placeholderTextColor", null)) + colors = view.hintTextColors + assertThat(colors).isEqualTo(defaultPlaceholderColorStateList) + } + + @Test + fun testMultiline() { + manager.updateProperties(view, buildStyles()) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero + + manager.updateProperties(view, buildStyles("multiline", false)) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero + + manager.updateProperties(view, buildStyles("multiline", true)) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero + + manager.updateProperties(view, buildStyles("multiline", null)) + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero + } + + @Test + fun testBlurMultiline() { + val editorInfo = + EditorInfo().apply { + imeOptions = EditorInfo.IME_ACTION_DONE or EditorInfo.IME_FLAG_NO_ENTER_ACTION + } + manager.updateProperties(view, buildStyles("multiline", true)) + manager.updateProperties(view, buildStyles("submitBehavior", "blurAndSubmit")) + view.onCreateInputConnection(editorInfo) + assertThat(editorInfo.imeOptions).isEqualTo(EditorInfo.IME_ACTION_DONE) + } + + @Test + fun testNumLines() { + manager.updateProperties(view, buildStyles()) + assertThat(view.minLines).isEqualTo(1) + + manager.updateProperties(view, buildStyles("numberOfLines", 5)) + assertThat(view.minLines).isEqualTo(5) + + manager.updateProperties(view, buildStyles("numberOfLines", 4)) + assertThat(view.minLines).isEqualTo(4) + } + + @Test + fun testKeyboardTypeClassText() { + manager.updateProperties(view, buildStyles("keyboardType", null)) + assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(InputType.TYPE_CLASS_TEXT) + + manager.updateProperties(view, buildStyles()) + assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(InputType.TYPE_CLASS_TEXT) + + manager.updateProperties(view, buildStyles("keyboardType", "text")) + assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(InputType.TYPE_CLASS_TEXT) + } + + @Test + fun testKeyboardTypePad() { + val numberPadTypeFlags = InputType.TYPE_CLASS_NUMBER + val decimalPadTypeFlags = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL + + manager.updateProperties(view, buildStyles("keyboardType", "phone-pad")) + assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(InputType.TYPE_CLASS_PHONE) + + manager.updateProperties(view, buildStyles("keyboardType", "number-pad")) + assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(numberPadTypeFlags) + + manager.updateProperties(view, buildStyles("keyboardType", "decimal-pad")) + assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(decimalPadTypeFlags) + } + + @Test + fun testKeyboardTypeNumeric() { + val numericTypeFlags = + (InputType.TYPE_CLASS_NUMBER or + InputType.TYPE_NUMBER_FLAG_DECIMAL or + InputType.TYPE_NUMBER_FLAG_SIGNED) + + manager.updateProperties(view, buildStyles("keyboardType", "numeric")) + assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(numericTypeFlags) + } + + @Test + fun testKeyboardTypeEmail() { + val emailTypeFlags = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS or InputType.TYPE_CLASS_TEXT + + manager.updateProperties(view, buildStyles("keyboardType", "email-address")) + assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(emailTypeFlags) + } + + @Test + fun testKeyboardTypeUrl() { + manager.updateProperties(view, buildStyles("keyboardType", "url")) + assertThat(view.inputType and generalKeyboardTypeFlags) + .isEqualTo(InputType.TYPE_TEXT_VARIATION_URI) + } + + @Test + fun testKeyboardTypeVisiblePassword() { + val passwordVisibilityFlag = + InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD and + InputType.TYPE_TEXT_VARIATION_PASSWORD.inv() + + manager.updateProperties(view, buildStyles("keyboardType", "visible-password")) + assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(passwordVisibilityFlag) + } + + @Test + fun testPasswordInput() { + manager.updateProperties(view, buildStyles()) + assertThat(view.inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD).isZero + + manager.updateProperties(view, buildStyles("secureTextEntry", false)) + assertThat(view.inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD).isZero + + manager.updateProperties(view, buildStyles("secureTextEntry", true)) + assertThat(view.inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD).isNotZero + + manager.updateProperties(view, buildStyles("secureTextEntry", null)) + assertThat(view.inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD).isZero + } + + @Test + fun testIncrementalInputTypeUpdates() { + manager.updateProperties(view, buildStyles()) + assertThat(view.inputType and InputType.TYPE_CLASS_NUMBER).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero + + manager.updateProperties(view, buildStyles("multiline", true)) + assertThat(view.inputType and InputType.TYPE_CLASS_NUMBER).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero + + manager.updateProperties(view, buildStyles("autoCorrect", false)) + assertThat(view.inputType and InputType.TYPE_CLASS_NUMBER).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero + + manager.updateProperties(view, buildStyles("keyboardType", "NUMERIC")) + assertThat(view.inputType and InputType.TYPE_CLASS_NUMBER).isNotZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero + + manager.updateProperties(view, buildStyles("multiline", null)) + assertThat(view.inputType and InputType.TYPE_CLASS_NUMBER).isNotZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero + assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero + } + + @Test + fun testTextAlign() { + val defaultGravity = view.gravity + val defaultHorizontalGravity = defaultGravity and Gravity.HORIZONTAL_GRAVITY_MASK + val defaultVerticalGravity = defaultGravity and Gravity.VERTICAL_GRAVITY_MASK + + assertThat(view.gravity).isNotEqualTo(Gravity.NO_GRAVITY) + + // region TextAlign + manager.updateProperties(view, buildStyles("textAlign", "left")) + assertThat(view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(Gravity.LEFT) + + manager.updateProperties(view, buildStyles("textAlign", "right")) + assertThat(view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(Gravity.RIGHT) + + manager.updateProperties(view, buildStyles("textAlign", "center")) + assertThat(view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK) + .isEqualTo(Gravity.CENTER_HORIZONTAL) + + manager.updateProperties(view, buildStyles("textAlign", "start")) + assertThat( + view.gravity and + (Gravity.HORIZONTAL_GRAVITY_MASK or Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) + ) + .isEqualTo(Gravity.START) + + manager.updateProperties(view, buildStyles("textAlign", "end")) + assertThat( + view.gravity and + (Gravity.HORIZONTAL_GRAVITY_MASK or Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) + ) + .isEqualTo(Gravity.END) + + manager.updateProperties(view, buildStyles("textAlign", null)) + assertThat(view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(defaultHorizontalGravity) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + manager.updateProperties(view, buildStyles("textAlign", "justify")) + assertThat(view.justificationMode).isEqualTo(Layout.JUSTIFICATION_MODE_INTER_WORD) + } + // endregion + + // region TextAlignVertical + manager.updateProperties(view, buildStyles("textAlignVertical", "top")) + assertThat(view.gravity and Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.TOP) + + manager.updateProperties(view, buildStyles("textAlignVertical", "bottom")) + assertThat(view.gravity and Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.BOTTOM) + + manager.updateProperties(view, buildStyles("textAlignVertical", "center")) + assertThat(view.gravity and Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.CENTER_VERTICAL) + + manager.updateProperties(view, buildStyles("textAlignVertical", null)) + assertThat(view.gravity and Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(defaultVerticalGravity) + // endregion + + // region TextAlign + TextAlignVertical + manager.updateProperties( + view, + buildStyles("textAlign", "center", "textAlignVertical", "center"), + ) + assertThat(view.gravity).isEqualTo(Gravity.CENTER) + + manager.updateProperties(view, buildStyles("textAlign", "right", "textAlignVertical", "bottom")) + assertThat(view.gravity).isEqualTo(Gravity.RIGHT or Gravity.BOTTOM) + + manager.updateProperties(view, buildStyles("textAlign", null, "textAlignVertical", null)) + assertThat(view.gravity).isEqualTo(defaultGravity) + // endregion + } + + @Test + fun testMaxLength() { + val filters = arrayOf(AllCaps()) + view.filters = filters + manager.setMaxLength(view, null) + assertThat(view.filters).isEqualTo(filters) + } + + @Test + fun testSecureTextDoesNotReplaceSameTextFromJS() { + val markerSpan = MarkerSpan() + val textUpdate = + SpannableString("secret").apply { + setSpan(markerSpan, 0, length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) + } + + manager.updateProperties(view, buildStyles("secureTextEntry", true)) + view.setText("secret") + + view.maybeSetTextFromJS( + ReactTextUpdate( + textUpdate, + 0, + view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK, + Layout.BREAK_STRATEGY_HIGH_QUALITY, + 0, + ) + ) + + assertThat(checkNotNull(view.text).getSpans(0, view.length(), MarkerSpan::class.java)).isEmpty() + } + + private fun buildStyles(vararg keysAndValues: Any?): ReactStylesDiffMap { + return ReactStylesDiffMap(JavaOnlyMap.of(*keysAndValues)) + } + + private class MarkerSpan +} From 446b5a52738123b705346047ec1c7ed5d485a069 Mon Sep 17 00:00:00 2001 From: Nikhil Pakhloo <121868367+nikhilpakhloo@users.noreply.github.com> Date: Tue, 14 Jul 2026 12:02:04 +0530 Subject: [PATCH 4/4] Update print statement from 'Hello' to 'Goodbye' --- .../textinput/ReactTextInputPropertyTest.kt | 532 ------------------ 1 file changed, 532 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt index 6b2dc6434cc9..db18f739102b 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt @@ -1,4 +1,3 @@ - /* * Copyright (c) Meta Platforms, Inc. and affiliates. * @@ -547,534 +546,3 @@ class ReactTextInputPropertyTest { private class MarkerSpan } - -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -// TODO T207169925: Migrate CatalystInstance to Reacthost and remove the Suppress("DEPRECATION") -// annotation -@file:Suppress("DEPRECATION") - -package com.facebook.react.views.textinput - -import android.graphics.Color -import android.os.Build -import android.text.InputFilter -import android.text.InputFilter.AllCaps -import android.text.InputType -import android.text.Layout -import android.text.SpannableString -import android.text.Spanned -import android.util.DisplayMetrics -import android.view.Gravity -import android.view.View -import android.view.inputmethod.EditorInfo -import androidx.autofill.HintConstants -import androidx.core.content.res.ResourcesCompat.ID_NULL -import com.facebook.react.bridge.BridgeReactContext -import com.facebook.react.bridge.CatalystInstance -import com.facebook.react.bridge.JavaOnlyMap -import com.facebook.react.bridge.ReactTestHelper.createMockCatalystInstance -import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests -import com.facebook.react.uimanager.DisplayMetricsHolder -import com.facebook.react.uimanager.ReactStylesDiffMap -import com.facebook.react.uimanager.ThemedReactContext -import com.facebook.react.views.text.DefaultStyleValuesUtil.getDefaultTextColorHint -import com.facebook.react.views.text.ReactTextUpdate -import org.assertj.core.api.Assertions.assertThat -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment - -/** Verify {@link EditText} view property being applied properly by {@link ReactTextInputManager} */ -@RunWith(RobolectricTestRunner::class) -class ReactTextInputPropertyTest { - - private lateinit var context: BridgeReactContext - private lateinit var catalystInstanceMock: CatalystInstance - private lateinit var themedContext: ThemedReactContext - private lateinit var manager: ReactTextInputManager - private lateinit var view: ReactEditText - - private val generalKeyboardTypeFlags: Int = - (InputType.TYPE_CLASS_NUMBER or - InputType.TYPE_NUMBER_FLAG_DECIMAL or - InputType.TYPE_NUMBER_FLAG_SIGNED or - InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS or - InputType.TYPE_CLASS_TEXT or - InputType.TYPE_CLASS_PHONE or - InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD and - InputType.TYPE_TEXT_VARIATION_PASSWORD.inv()) - - @Before - fun setup() { - ReactNativeFeatureFlagsForTests.setUp() - context = BridgeReactContext(RuntimeEnvironment.getApplication()) - catalystInstanceMock = createMockCatalystInstance() - context.initializeWithInstance(catalystInstanceMock) - themedContext = ThemedReactContext(context, context.baseContext, null, ID_NULL) - manager = ReactTextInputManager() - DisplayMetricsHolder.setScreenDisplayMetrics(DisplayMetrics()) - view = manager.createViewInstance(themedContext) - } - - @Test - fun testAutoCorrect() { - manager.updateProperties(view, buildStyles()) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero - - manager.updateProperties(view, buildStyles("autoCorrect", true)) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero - - manager.updateProperties(view, buildStyles("autoCorrect", false)) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero - - manager.updateProperties(view, buildStyles("autoCorrect", null)) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero - } - - @Test - fun testAutoCapitalize() { - manager.updateProperties(view, buildStyles()) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero - - manager.updateProperties( - view, - buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), - ) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero - - manager.updateProperties( - view, - buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_WORDS), - ) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero - - manager.updateProperties( - view, - buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS), - ) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isNotZero - - manager.updateProperties(view, buildStyles("autoCapitalize", InputType.TYPE_CLASS_TEXT)) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_WORDS).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero - } - - @Test - fun testAutoCapitalizeDoesNotStripNumericFlags() { - val numericTypeFlags = - (InputType.TYPE_CLASS_NUMBER or - InputType.TYPE_NUMBER_FLAG_DECIMAL or - InputType.TYPE_NUMBER_FLAG_SIGNED) - - manager.updateProperties(view, buildStyles("keyboardType", "numeric")) - assertThat(view.inputType and numericTypeFlags).isEqualTo(numericTypeFlags) - - manager.updateProperties( - view, - buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_SENTENCES), - ) - assertThat(view.inputType and InputType.TYPE_NUMBER_FLAG_SIGNED).isNotZero - assertThat(view.inputType and InputType.TYPE_NUMBER_FLAG_DECIMAL).isNotZero - } - - @Test - fun testAutoCapitalizeAndNumericKeyboardInSameTransaction() { - val numericTypeFlags = - (InputType.TYPE_CLASS_NUMBER or - InputType.TYPE_NUMBER_FLAG_DECIMAL or - InputType.TYPE_NUMBER_FLAG_SIGNED) - - manager.updateProperties( - view, - buildStyles( - "autoCapitalize", - InputType.TYPE_TEXT_FLAG_CAP_SENTENCES, - "keyboardType", - "numeric", - ), - ) - assertThat(view.inputType and numericTypeFlags).isEqualTo(numericTypeFlags) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero - } - - @Test - fun testAutoCapitalizeReappliesWhenKeyboardTypeChangesFromNumericToText() { - // CAP_SENTENCES (0x4000) doesn't share a bit position with any numeric flag, - // unlike CAP_WORDS (0x2000) / CAP_CHARACTERS (0x1000). - manager.updateProperties( - view, - buildStyles( - "autoCapitalize", - InputType.TYPE_TEXT_FLAG_CAP_SENTENCES, - "keyboardType", - "numeric", - ), - ) - assertThat(view.inputType and InputType.TYPE_MASK_CLASS).isEqualTo(InputType.TYPE_CLASS_NUMBER) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero - - manager.updateProperties(view, buildStyles("keyboardType", "default")) - assertThat(view.inputType and InputType.TYPE_MASK_CLASS).isEqualTo(InputType.TYPE_CLASS_TEXT) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isNotZero - } - - @Test - fun testPlaceholder() { - manager.updateProperties(view, buildStyles()) - assertThat(view.hint).isNull() - - manager.updateProperties(view, buildStyles("placeholder", "sometext")) - assertThat(view.hint).isEqualTo("sometext") - - manager.updateProperties(view, buildStyles("placeholder", null)) - assertThat(view.hint).isNull() - } - - @Test - fun testEditable() { - manager.updateProperties(view, buildStyles()) - assertThat(view.isEnabled).isTrue - - manager.updateProperties(view, buildStyles("editable", false)) - assertThat(view.isEnabled).isFalse - - manager.updateProperties(view, buildStyles("editable", null)) - assertThat(view.isEnabled).isTrue - - manager.updateProperties(view, buildStyles("editable", false)) - assertThat(view.isEnabled).isFalse - - manager.updateProperties(view, buildStyles("editable", true)) - assertThat(view.isEnabled).isTrue - } - - @Test - fun testAutoCompleteExtendedHints() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { - return - } - - val expectedHints = listOf( - "2fa-app-otp" to HintConstants.AUTOFILL_HINT_2FA_APP_OTP, - "email-otp" to HintConstants.AUTOFILL_HINT_EMAIL_OTP, - "flight-confirmation-code" to HintConstants.AUTOFILL_HINT_FLIGHT_CONFIRMATION_CODE, - "flight-number" to HintConstants.AUTOFILL_HINT_FLIGHT_NUMBER, - "gift-card-number" to HintConstants.AUTOFILL_HINT_GIFT_CARD_NUMBER, - "gift-card-pin" to HintConstants.AUTOFILL_HINT_GIFT_CARD_PIN, - "loyalty-account-number" to HintConstants.AUTOFILL_HINT_LOYALTY_ACCOUNT_NUMBER, - "postal-address-dependent-locality" to - HintConstants.AUTOFILL_HINT_POSTAL_ADDRESS_DEPENDENT_LOCALITY, - "postal-address-unit" to HintConstants.AUTOFILL_HINT_POSTAL_ADDRESS_APT_NUMBER, - "promo-code" to HintConstants.AUTOFILL_HINT_PROMO_CODE, - "upi-vpa" to HintConstants.AUTOFILL_HINT_UPI_VPA, - "wifi-password" to HintConstants.AUTOFILL_HINT_WIFI_PASSWORD, - ) - - expectedHints.forEach { (autoComplete, expectedHint) -> - manager.updateProperties(view, buildStyles("autoComplete", autoComplete)) - - assertThat(view.importantForAutofill).isEqualTo(View.IMPORTANT_FOR_AUTOFILL_YES) - assertThat(view.autofillHints?.toList()).containsExactly(expectedHint) - } - } - - @Test - fun testPlaceholderTextColor() { - val defaultPlaceholderColorStateList = getDefaultTextColorHint(view.context) - var colors = view.hintTextColors - assertThat(colors).isEqualTo(defaultPlaceholderColorStateList) - - manager.updateProperties(view, buildStyles("placeholderTextColor", null)) - colors = view.hintTextColors - assertThat(colors).isEqualTo(defaultPlaceholderColorStateList) - - manager.updateProperties(view, buildStyles("placeholderTextColor", Color.RED)) - colors = view.hintTextColors - assertThat(colors.defaultColor).isEqualTo(Color.RED) - - manager.updateProperties(view, buildStyles("placeholderTextColor", null)) - colors = view.hintTextColors - assertThat(colors).isEqualTo(defaultPlaceholderColorStateList) - } - - @Test - fun testMultiline() { - manager.updateProperties(view, buildStyles()) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero - - manager.updateProperties(view, buildStyles("multiline", false)) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero - - manager.updateProperties(view, buildStyles("multiline", true)) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero - - manager.updateProperties(view, buildStyles("multiline", null)) - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero - } - - @Test - fun testBlurMultiline() { - val editorInfo = - EditorInfo().apply { - imeOptions = EditorInfo.IME_ACTION_DONE or EditorInfo.IME_FLAG_NO_ENTER_ACTION - } - manager.updateProperties(view, buildStyles("multiline", true)) - manager.updateProperties(view, buildStyles("submitBehavior", "blurAndSubmit")) - view.onCreateInputConnection(editorInfo) - assertThat(editorInfo.imeOptions).isEqualTo(EditorInfo.IME_ACTION_DONE) - } - - @Test - fun testNumLines() { - manager.updateProperties(view, buildStyles()) - assertThat(view.minLines).isEqualTo(1) - - manager.updateProperties(view, buildStyles("numberOfLines", 5)) - assertThat(view.minLines).isEqualTo(5) - - manager.updateProperties(view, buildStyles("numberOfLines", 4)) - assertThat(view.minLines).isEqualTo(4) - } - - @Test - fun testKeyboardTypeClassText() { - manager.updateProperties(view, buildStyles("keyboardType", null)) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(InputType.TYPE_CLASS_TEXT) - - manager.updateProperties(view, buildStyles()) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(InputType.TYPE_CLASS_TEXT) - - manager.updateProperties(view, buildStyles("keyboardType", "text")) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(InputType.TYPE_CLASS_TEXT) - } - - @Test - fun testKeyboardTypePad() { - val numberPadTypeFlags = InputType.TYPE_CLASS_NUMBER - val decimalPadTypeFlags = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL - - manager.updateProperties(view, buildStyles("keyboardType", "phone-pad")) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(InputType.TYPE_CLASS_PHONE) - - manager.updateProperties(view, buildStyles("keyboardType", "number-pad")) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(numberPadTypeFlags) - - manager.updateProperties(view, buildStyles("keyboardType", "decimal-pad")) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(decimalPadTypeFlags) - } - - @Test - fun testKeyboardTypeNumeric() { - val numericTypeFlags = - (InputType.TYPE_CLASS_NUMBER or - InputType.TYPE_NUMBER_FLAG_DECIMAL or - InputType.TYPE_NUMBER_FLAG_SIGNED) - - manager.updateProperties(view, buildStyles("keyboardType", "numeric")) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(numericTypeFlags) - } - - @Test - fun testKeyboardTypeEmail() { - val emailTypeFlags = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS or InputType.TYPE_CLASS_TEXT - - manager.updateProperties(view, buildStyles("keyboardType", "email-address")) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(emailTypeFlags) - } - - @Test - fun testKeyboardTypeUrl() { - manager.updateProperties(view, buildStyles("keyboardType", "url")) - assertThat(view.inputType and generalKeyboardTypeFlags) - .isEqualTo(InputType.TYPE_TEXT_VARIATION_URI) - } - - @Test - fun testKeyboardTypeVisiblePassword() { - val passwordVisibilityFlag = - InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD and - InputType.TYPE_TEXT_VARIATION_PASSWORD.inv() - - manager.updateProperties(view, buildStyles("keyboardType", "visible-password")) - assertThat(view.inputType and generalKeyboardTypeFlags).isEqualTo(passwordVisibilityFlag) - } - - @Test - fun testPasswordInput() { - manager.updateProperties(view, buildStyles()) - assertThat(view.inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD).isZero - - manager.updateProperties(view, buildStyles("secureTextEntry", false)) - assertThat(view.inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD).isZero - - manager.updateProperties(view, buildStyles("secureTextEntry", true)) - assertThat(view.inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD).isNotZero - - manager.updateProperties(view, buildStyles("secureTextEntry", null)) - assertThat(view.inputType and InputType.TYPE_TEXT_VARIATION_PASSWORD).isZero - } - - @Test - fun testIncrementalInputTypeUpdates() { - manager.updateProperties(view, buildStyles()) - assertThat(view.inputType and InputType.TYPE_CLASS_NUMBER).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero - - manager.updateProperties(view, buildStyles("multiline", true)) - assertThat(view.inputType and InputType.TYPE_CLASS_NUMBER).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isZero - - manager.updateProperties(view, buildStyles("autoCorrect", false)) - assertThat(view.inputType and InputType.TYPE_CLASS_NUMBER).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero - - manager.updateProperties(view, buildStyles("keyboardType", "NUMERIC")) - assertThat(view.inputType and InputType.TYPE_CLASS_NUMBER).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero - - manager.updateProperties(view, buildStyles("multiline", null)) - assertThat(view.inputType and InputType.TYPE_CLASS_NUMBER).isNotZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_AUTO_CORRECT).isZero - assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS).isNotZero - } - - @Test - fun testTextAlign() { - val defaultGravity = view.gravity - val defaultHorizontalGravity = defaultGravity and Gravity.HORIZONTAL_GRAVITY_MASK - val defaultVerticalGravity = defaultGravity and Gravity.VERTICAL_GRAVITY_MASK - - assertThat(view.gravity).isNotEqualTo(Gravity.NO_GRAVITY) - - // region TextAlign - manager.updateProperties(view, buildStyles("textAlign", "left")) - assertThat(view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(Gravity.LEFT) - - manager.updateProperties(view, buildStyles("textAlign", "right")) - assertThat(view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(Gravity.RIGHT) - - manager.updateProperties(view, buildStyles("textAlign", "center")) - assertThat(view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK) - .isEqualTo(Gravity.CENTER_HORIZONTAL) - - manager.updateProperties(view, buildStyles("textAlign", "start")) - assertThat( - view.gravity and - (Gravity.HORIZONTAL_GRAVITY_MASK or Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) - ) - .isEqualTo(Gravity.START) - - manager.updateProperties(view, buildStyles("textAlign", "end")) - assertThat( - view.gravity and - (Gravity.HORIZONTAL_GRAVITY_MASK or Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) - ) - .isEqualTo(Gravity.END) - - manager.updateProperties(view, buildStyles("textAlign", null)) - assertThat(view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK).isEqualTo(defaultHorizontalGravity) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - manager.updateProperties(view, buildStyles("textAlign", "justify")) - assertThat(view.justificationMode).isEqualTo(Layout.JUSTIFICATION_MODE_INTER_WORD) - } - // endregion - - // region TextAlignVertical - manager.updateProperties(view, buildStyles("textAlignVertical", "top")) - assertThat(view.gravity and Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.TOP) - - manager.updateProperties(view, buildStyles("textAlignVertical", "bottom")) - assertThat(view.gravity and Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.BOTTOM) - - manager.updateProperties(view, buildStyles("textAlignVertical", "center")) - assertThat(view.gravity and Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(Gravity.CENTER_VERTICAL) - - manager.updateProperties(view, buildStyles("textAlignVertical", null)) - assertThat(view.gravity and Gravity.VERTICAL_GRAVITY_MASK).isEqualTo(defaultVerticalGravity) - // endregion - - // region TextAlign + TextAlignVertical - manager.updateProperties( - view, - buildStyles("textAlign", "center", "textAlignVertical", "center"), - ) - assertThat(view.gravity).isEqualTo(Gravity.CENTER) - - manager.updateProperties(view, buildStyles("textAlign", "right", "textAlignVertical", "bottom")) - assertThat(view.gravity).isEqualTo(Gravity.RIGHT or Gravity.BOTTOM) - - manager.updateProperties(view, buildStyles("textAlign", null, "textAlignVertical", null)) - assertThat(view.gravity).isEqualTo(defaultGravity) - // endregion - } - - @Test - fun testMaxLength() { - val filters = arrayOf(AllCaps()) - view.filters = filters - manager.setMaxLength(view, null) - assertThat(view.filters).isEqualTo(filters) - } - - @Test - fun testSecureTextDoesNotReplaceSameTextFromJS() { - val markerSpan = MarkerSpan() - val textUpdate = - SpannableString("secret").apply { - setSpan(markerSpan, 0, length, Spanned.SPAN_INCLUSIVE_INCLUSIVE) - } - - manager.updateProperties(view, buildStyles("secureTextEntry", true)) - view.setText("secret") - - view.maybeSetTextFromJS( - ReactTextUpdate( - textUpdate, - 0, - view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK, - Layout.BREAK_STRATEGY_HIGH_QUALITY, - 0, - ) - ) - - assertThat(checkNotNull(view.text).getSpans(0, view.length(), MarkerSpan::class.java)).isEmpty() - } - - private fun buildStyles(vararg keysAndValues: Any?): ReactStylesDiffMap { - return ReactStylesDiffMap(JavaOnlyMap.of(*keysAndValues)) - } - - private class MarkerSpan -} -