From 3719fee468ca4d11e4ecf40423ddf57e5df83da1 Mon Sep 17 00:00:00 2001 From: Edu Date: Fri, 8 Sep 2023 16:15:47 +0200 Subject: [PATCH 1/9] Flag to exit early the _updateState function to prevent unwanted behaviours --- .../TextInput/RCTTextInputComponentView.mm | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 9e41f9d3b5f20b..5a5516a92cea7e 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -55,6 +55,15 @@ @implementation RCTTextInputComponentView { */ BOOL _comingFromJS; BOOL _didMoveToWindow; + + /* + * A flag that when set to true, `[self _updateState]` will exit prematurely. + * + * This prevents `[self _updateState]` from being called twice when the `value` prop is used to update the text + * in the `SinglelineTextInputView`. + * This double execution was causing issues on the double-spacing behavior or text-replacement. + */ + BOOL _stateUpdated; } #pragma mark - UIView overrides @@ -71,6 +80,7 @@ - (instancetype)initWithFrame:(CGRect)frame _ignoreNextTextInputCall = NO; _comingFromJS = NO; _didMoveToWindow = NO; + _stateUpdated = NO; [self addSubview:_backedTextInputView]; } @@ -220,6 +230,11 @@ - (void)updateState:(State::Shared const &)state oldState:(State::Shared const & return; } + if (_stateUpdated) { + return; + } + _stateUpdated = YES; + auto data = _state->getData(); if (!oldState) { @@ -259,6 +274,7 @@ - (void)prepareForRecycle _lastStringStateWasUpdatedWith = nil; _ignoreNextTextInputCall = NO; _didMoveToWindow = NO; + _stateUpdated = NO; [_backedTextInputView resignFirstResponder]; } @@ -375,6 +391,8 @@ - (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSStrin - (void)textInputDidChange { + _stateUpdated = NO; + if (_comingFromJS) { return; } From 122564ef05e2ed93bc2c680b4715c85538e18704 Mon Sep 17 00:00:00 2001 From: Edu Date: Tue, 12 Dec 2023 17:03:20 +0100 Subject: [PATCH 2/9] Implemented fix for the souble space --- .../TextInput/RCTTextInputComponentView.mm | 24 + packages/rn-tester/Podfile.lock | 164 ++- .../RNTesterPods.xcodeproj/project.pbxproj | 4 - .../TextInput/TextInputExample.ios.js | 1222 ++++++++--------- .../TextInput/TextInputSharedExamples.js | 564 ++++---- 5 files changed, 1032 insertions(+), 946 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 44e74da5cea06f..1e6ad1d71d0c3d 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -55,6 +55,15 @@ @implementation RCTTextInputComponentView { */ BOOL _comingFromJS; BOOL _didMoveToWindow; + + /* + * A flag that when set to true, `[self _updateState]` will exit prematurely. + * + * This prevents `[self _updateState]` from being called twice when the `value` prop is used to update the text + * in the `SinglelineTextInputView`. + * This double execution was causing issues on the double-spacing behavior or text-replacement. + */ + BOOL _stateUpdated; } #pragma mark - UIView overrides @@ -70,6 +79,7 @@ - (instancetype)initWithFrame:(CGRect)frame _ignoreNextTextInputCall = NO; _comingFromJS = NO; _didMoveToWindow = NO; + _stateUpdated = NO; [self addSubview:_backedTextInputView]; } @@ -224,6 +234,12 @@ - (void)updateState:(const State::Shared &)state oldState:(const State::Shared & return; } + if (_stateUpdated) { + return; + } + _stateUpdated = YES; + + auto data = _state->getData(); if (!oldState) { @@ -262,6 +278,7 @@ - (void)prepareForRecycle _lastStringStateWasUpdatedWith = nil; _ignoreNextTextInputCall = NO; _didMoveToWindow = NO; + _stateUpdated = NO; [_backedTextInputView resignFirstResponder]; } @@ -378,6 +395,7 @@ - (BOOL)textInputShouldChangeTextInRange:(NSRange)range replacementText:(NSStrin - (void)textInputDidChange { + _stateUpdated = NO; if (_comingFromJS) { return; } @@ -552,8 +570,14 @@ - (TextInputMetrics)_textInputMetrics - (void)_updateState { if (!_state) { + + return; + } + if (_stateUpdated) { return; } + _stateUpdated = YES; + NSAttributedString *attributedString = _backedTextInputView.attributedText; auto data = _state->getData(); _lastStringStateWasUpdatedWith = attributedString; diff --git a/packages/rn-tester/Podfile.lock b/packages/rn-tester/Podfile.lock index c3ac6564e8ec77..8311ecc2099f7c 100644 --- a/packages/rn-tester/Podfile.lock +++ b/packages/rn-tester/Podfile.lock @@ -98,6 +98,7 @@ PODS: - React-Core - React-debug - React-Fabric + - React-FabricImage - React-graphics - React-jsi - React-jsiexecutor @@ -920,7 +921,6 @@ PODS: - React-debug - React-Fabric - React-graphics - - React-RCTImage - React-rendererdebug - React-utils - React-jserrorhandler (1000.0.0): @@ -945,6 +945,8 @@ PODS: - React-jsi (= 1000.0.0) - React-perflogger (= 1000.0.0) - React-jsinspector (1000.0.0) + - React-jsitracing (1000.0.0): + - React-jsi - React-logger (1000.0.0): - glog - React-Mapbuffer (1000.0.0): @@ -978,13 +980,21 @@ PODS: - RCTTypeSafety - React-Core - React-CoreModules + - React-debug + - React-Fabric + - React-graphics - React-hermes - React-nativeconfig - React-NativeModulesApple - React-RCTFabric - React-RCTImage - React-RCTNetwork + - React-rendererdebug + - React-RuntimeApple + - React-RuntimeCore + - React-RuntimeHermes - React-runtimescheduler + - React-utils - ReactCommon - React-RCTBlob (1000.0.0): - hermes-engine @@ -1075,8 +1085,44 @@ PODS: - RCT-Folly (= 2023.08.07.00) - React-debug - React-rncore (1000.0.0) + - React-RuntimeApple (1000.0.0): + - hermes-engine + - RCT-Folly/Fabric (= 2023.08.07.00) + - React-callinvoker + - React-Core/Default + - React-CoreModules + - React-cxxreact + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-Mapbuffer + - React-NativeModulesApple + - React-RCTFabric + - React-RuntimeCore + - React-runtimeexecutor + - React-RuntimeHermes + - React-utils + - React-RuntimeCore (1000.0.0): + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2023.08.07.00) + - React-cxxreact + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-runtimeexecutor + - React-runtimescheduler + - React-utils - React-runtimeexecutor (1000.0.0): - React-jsi (= 1000.0.0) + - React-RuntimeHermes (1000.0.0): + - hermes-engine + - RCT-Folly/Fabric (= 2023.08.07.00) + - React-jsi + - React-jsitracing + - React-nativeconfig + - React-RuntimeCore + - React-utils - React-runtimescheduler (1000.0.0): - glog - hermes-engine @@ -1195,6 +1241,7 @@ DEPENDENCIES: - React-jsi (from `../react-native/ReactCommon/jsi`) - React-jsiexecutor (from `../react-native/ReactCommon/jsiexecutor`) - React-jsinspector (from `../react-native/ReactCommon/jsinspector-modern`) + - React-jsitracing (from `../react-native/ReactCommon/hermes/executor/`) - React-logger (from `../react-native/ReactCommon/logger`) - React-Mapbuffer (from `../react-native/ReactCommon`) - React-nativeconfig (from `../react-native/ReactCommon`) @@ -1215,7 +1262,10 @@ DEPENDENCIES: - React-RCTVibration (from `../react-native/Libraries/Vibration`) - React-rendererdebug (from `../react-native/ReactCommon/react/renderer/debug`) - React-rncore (from `../react-native/ReactCommon`) + - React-RuntimeApple (from `../react-native/ReactCommon/react/runtime/platform/ios`) + - React-RuntimeCore (from `../react-native/ReactCommon/react/runtime`) - React-runtimeexecutor (from `../react-native/ReactCommon/runtimeexecutor`) + - React-RuntimeHermes (from `../react-native/ReactCommon/react/runtime`) - React-runtimescheduler (from `../react-native/ReactCommon/react/renderer/runtimescheduler`) - React-utils (from `../react-native/ReactCommon/react/utils`) - ReactCommon-Samples (from `../react-native/ReactCommon/react/nativemodule/samples`) @@ -1286,6 +1336,8 @@ EXTERNAL SOURCES: :path: "../react-native/ReactCommon/jsiexecutor" React-jsinspector: :path: "../react-native/ReactCommon/jsinspector-modern" + React-jsitracing: + :path: "../react-native/ReactCommon/hermes/executor/" React-logger: :path: "../react-native/ReactCommon/logger" React-Mapbuffer: @@ -1326,8 +1378,14 @@ EXTERNAL SOURCES: :path: "../react-native/ReactCommon/react/renderer/debug" React-rncore: :path: "../react-native/ReactCommon" + React-RuntimeApple: + :path: "../react-native/ReactCommon/react/runtime/platform/ios" + React-RuntimeCore: + :path: "../react-native/ReactCommon/react/runtime" React-runtimeexecutor: :path: "../react-native/ReactCommon/runtimeexecutor" + React-RuntimeHermes: + :path: "../react-native/ReactCommon/react/runtime" React-runtimescheduler: :path: "../react-native/ReactCommon/react/renderer/runtimescheduler" React-utils: @@ -1344,62 +1402,66 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 26fad476bfa736552bbfa698a06cc530475c1505 DoubleConversion: fea03f2699887d960129cc54bba7e52542b6f953 - FBLazyVector: f4492a543c5a8fa1502d3a5867e3f7252497cfe8 + FBLazyVector: 59cb54c1499b397632fcc981b1c03868b8250c5d fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2 - hermes-engine: 3cf266f7eba66a16bcc83d939def2899e6d1a717 - MyNativeView: 1fb0d21fda6ef12fd5f425f9888c81345a86a3e7 - NativeCxxModuleExample: 19d10636011c914264a5922c6f0f22a2e8fd0910 + hermes-engine: 0ef97d8d9083104de10e05424e2651ed81baae23 + MyNativeView: 1ec1acd16067ac22bced65cf00a900a7926bd393 + NativeCxxModuleExample: f74b5dd25436f3a7f696c4b1be7e0d8da686cd72 OCMock: 9491e4bec59e0b267d52a9184ff5605995e74be8 RCT-Folly: 823c6f6ec910a75d4ad28898b4a11cdee140b92a RCTDeprecation: 3808e36294137f9ee5668f4df2e73dc079cd1dcf - RCTRequired: 82c56a03b3efd524bfdb581a906add903f78f978 - RCTTypeSafety: 5f57d4ae5dfafc85a0f575d756c909b584722c52 - React: cb6dc75e09f32aeddb4d8fb58a394a67219a92fe - React-callinvoker: bae59cbd6affd712bbfc703839dad868ff35069d - React-Codegen: c1e6ea0005a6ecf187d2772f14e5386f1c1f4853 - React-Core: aaf2be188dfb2c342bc504042a92bfff31e2048f - React-CoreModules: ad1b7cb8efe5f3c7e88548b6ea6aad8507774471 - React-cxxreact: 04bf6a12caa850f5d2c7bd6d66dfecd4741989b5 - React-debug: 296b501a90c41f83961f58c6d96a01330d499da5 - React-Fabric: d9d966fc90b6b3efd047e19df3c13c36e7e8458b - React-FabricImage: ca726038a733ebc92724728dc3a1823cd60fc74f - React-graphics: fe23f0be844a21d42642596183f8ce0e54861ecf - React-hermes: f192759ffeb9714917e9c39850ac349d8ea982d8 - React-ImageManager: 691c4a56320ab9ab10482cd6306b3a4da004b79c - React-jserrorhandler: 79fb3a8860fb1ea22dc77765aac15775593d4f8f - React-jsi: 81f4e5b414c992c16c02e22e975a45aeb2b166e4 - React-jsiexecutor: 1212e26a01ce4de7443123f0ce090ec1affb3220 - React-jsinspector: c867db3338992200616103b2f0ca6882c0c0482d - React-logger: 8486d7a1d32b972414b1d34a93470ee2562c6ee2 - React-Mapbuffer: fd0d0306c1c4326be5f18a61e978d32a66b20a85 - React-nativeconfig: 40a2c848083ef4065c163c854e1c82b5f9e9db84 - React-NativeModulesApple: fba3f5302c67e68463b52d90d4d9f869ff3ba1a1 - React-perflogger: 70d009f755dd10002183454cdf5ad9b22de4a1d7 - React-RCTActionSheet: 943bd5f540f3af1e5a149c13c4de81858edf718a - React-RCTAnimation: 750184a8efe97073d15215f6465d96cbb3d3b5ba - React-RCTAppDelegate: 722aead43eaaf2200ae93ba48779bb28da451593 - React-RCTBlob: 20a233b87b0748b5ec5fd52cb6e1668e651ed775 - React-RCTFabric: 0013c2447102a997cc54997197af85dad5f663ef - React-RCTImage: 16f53775b5d50cbd060f758fc2fcff0934e5eead - React-RCTLinking: efa67827466e50e07c5471447c12e474cbc5e336 - React-RCTNetwork: ce2bd048cbdf9101ec255d486310709f19600e79 - React-RCTPushNotification: c34ef3969207da3ddc777f36a252f99754b89e2d - React-RCTSettings: d6f1d3ff1880f64b8664a35b3cce2e21d0db9859 - React-RCTTest: b4eefa65f8440c9de3ce8959407cad3f0698c935 - React-RCTText: d9925903524a7b179cf7803162a98038e0bfb4fd - React-RCTVibration: 14322c13fb0c5cc2884b714b11d277dc7fc326c4 - React-rendererdebug: 2e58409db231638bc3e78143d8844066a3302939 - React-rncore: e903b3d2819a25674403c548ec103f34bf02ba2b - React-runtimeexecutor: e1c32bc249dd3cf3919cb4664fd8dc84ef70cff7 - React-runtimescheduler: 6529d155a98010b6e8f0a3a86d4184b89a886633 - React-utils: 87ed8c079c9991831112fe716f2686c430699fc3 - ReactCommon: 4511ea0e8f349de031de7cad88c2ecf871ab69f9 - ReactCommon-Samples: cfc3383af93a741319e038977c2ae1082e4ff49e - ScreenshotManager: 753da20873c2ada484bdee4143a7248084d3fd35 + RCTRequired: ff8d570b3fde812af2014547c87aaac3ac1a8169 + RCTTypeSafety: 3a24cdb5cbb98cea1a2d9bf623070f22bc608046 + React: 49756bc8766afd576109dc0f8a58d88cd2d089c8 + React-callinvoker: 287e040b8385e190545cb1afff043c9ab33a7af0 + React-Codegen: feef8181325890b506018f693c1293f1aa627cea + React-Core: 85963e26029fcf27ad686baff34c456e96c35c80 + React-CoreModules: 60d964d2c88a44fb1b506c1f2dc3c1ba887e2ce7 + React-cxxreact: c79ca9e327441b9b9834d068fe96094d9c2868f1 + React-debug: ea68922df299efcb0b810b2a1ad42a7ce034ef98 + React-Fabric: f45aff302dff311470a379a8e48f6f8ee6eceb96 + React-FabricImage: 39b487d74ab0d0f7e424af331d8978a79beee316 + React-graphics: c740ba748dd9753a4f72386a172e361f7c01ae17 + React-hermes: 96661d7eabfc6389f530d644cbd3018a3eab9071 + React-ImageManager: 5437ceb88b54c03d35a93943c34735e7bed00c65 + React-jserrorhandler: 4ab6292f84608b5eeb0becae9d59a322f7d88ed1 + React-jsi: 4388ee3367e414b8476913e47df9f96e19ecba2d + React-jsiexecutor: b5ab6233c440db6ba9bad9f769256a7ca7640a18 + React-jsinspector: 8571b9c73d5e1d724480b7cc06259977448cc818 + React-jsitracing: f7f5c5f4dce3d0f1e3dce6c85f61bb4f36658b47 + React-logger: 0ff0a99c488647cdd963789c6e5be9c4325ad1f5 + React-Mapbuffer: d796a65605d360f987ee09ee692fc56a6f936dee + React-nativeconfig: edf06cdef9f8ba2bec9c46d1884c2fa76b9aa757 + React-NativeModulesApple: 48f72d19d17908c741941b7e259f9dcc5697e4ff + React-perflogger: c883563f8e4e99b6c91ae407f8a7158e461b89a6 + React-RCTActionSheet: 66d053adf24533f2288d071ea529f76afc0d3f67 + React-RCTAnimation: 964e0ebe48f548ad8aa3c86411ea308fca775245 + React-RCTAppDelegate: 5054928974a1252a70303d869a8fd75a56e71264 + React-RCTBlob: 20c11cb87d0e6d6d0bde8c05760f2aea91bbb7fb + React-RCTFabric: ed79858bd542b35d7c2583e35f0b0ee00221575d + React-RCTImage: e7ad976baf543d840a0edf61e108f2b6fff9f877 + React-RCTLinking: 87b33c82ab997d714435232af1ac4adf20834134 + React-RCTNetwork: d1250417e1fb661d364f2324f245f0f8b335a0e0 + React-RCTPushNotification: df1a22cbf2677d5c845745fc40ae2bf18f0de81b + React-RCTSettings: 4f524943a69d6275294db1bb5db85e3e15d29263 + React-RCTTest: e04603ff6db70a97cbbee9773f82b4219b0269e2 + React-RCTText: 389b160574660c7285d1de81ccf547eba2592577 + React-RCTVibration: bb043ef73e641865f793737d7349baa2472cd4be + React-rendererdebug: 311e6fa5eba2fe40b8356bb36bb30a6420971231 + React-rncore: 75cfc23e6770ed2103b1e846ed4c54e1ae42cd2b + React-RuntimeApple: 94a92f50a9c1541ceec879e7e17744ddf3427950 + React-RuntimeCore: 32d5b7bbabd1fde2b4f3db1e1a003daa702d8326 + React-runtimeexecutor: 6f19b30c072e03d6e279eb7e0bae841a64f81ea6 + React-RuntimeHermes: dee069b466973dac912deee752e59172a849dc7f + React-runtimescheduler: b1d984fc83ba4da37df373821e21e699a72630f2 + React-utils: 9b654a3136330b4cfa7e1d318e59fbbd86fbc5d1 + ReactCommon: 066655bdfea83db6d68130e9c52c24ecb4566c47 + ReactCommon-Samples: 745fc9fc5ec7f4d8a4471495f36fed402996c658 + ScreenshotManager: d9c4ee0cfd08bf51f3d8eb0e53d6dc25846331f2 SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d - Yoga: dcc90b6f6863e467adabdd4f4bd963da1faddf1d + Yoga: 0531ddeed60d2d7739e5da338047a96759df33e0 -PODFILE CHECKSUM: 426f495a1ad44f3cabea3b7dd2b66a0b71083e26 +PODFILE CHECKSUM: 5afcf37691103b83159fb73be088b7cadc67af7b COCOAPODS: 1.13.0 diff --git a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj index 393fa1e581ed79..54e1c929e70bae 100644 --- a/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj +++ b/packages/rn-tester/RNTesterPods.xcodeproj/project.pbxproj @@ -941,8 +941,6 @@ OTHER_LDFLAGS = ( "-ObjC", "-lc++", - "-Wl", - "-ld_classic", ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../react-native"; SDKROOT = iphoneos; @@ -1033,8 +1031,6 @@ OTHER_LDFLAGS = ( "-ObjC", "-lc++", - "-Wl", - "-ld_classic", ); REACT_NATIVE_PATH = "${PODS_ROOT}/../../react-native"; SDKROOT = iphoneos; diff --git a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js index da496b20598021..74216120374426 100644 --- a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js +++ b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js @@ -325,617 +325,617 @@ const styles = StyleSheet.create({ const examples: Array = [ ...TextInputSharedExamples, - { - title: 'Live Re-Write (ひ -> 日)', - render: function (): React.Node { - return ; - }, - }, - { - title: 'Keyboard Input Accessory View', - render: function (): React.Node { - return ( - - - - - ); - }, - }, - { - title: "Default Input Accessory View with returnKeyType = 'done'", - render: function (): React.Node { - const keyboardTypesWithDoneButton = [ - 'number-pad', - 'phone-pad', - 'decimal-pad', - 'ascii-capable-number-pad', - ]; - const examples = keyboardTypesWithDoneButton.map(type => { - return ( - - - - ); - }); - return {examples}; - }, - }, - { - title: 'Nested content and `value` property', - render: function (): React.Node { - return ( - - - - (first raw text node) - (internal raw text node) - (last raw text node) - - - - - (first raw text node) - (internal raw text node) - (last raw text node) - - - - ); - }, - }, - { - title: 'Keyboard appearance', - render: function (): React.Node { - const keyboardAppearance = ['default', 'light', 'dark']; - const examples = keyboardAppearance.map(type => { - return ( - - - - ); - }); - return {examples}; - }, - }, - { - title: 'Return key types', - render: function (): React.Node { - const returnKeyTypes = [ - 'default', - 'go', - 'google', - 'join', - 'next', - 'route', - 'search', - 'send', - 'yahoo', - 'done', - 'emergency-call', - ]; - const examples = returnKeyTypes.map(type => { - return ( - - - - ); - }); - return {examples}; - }, - }, - { - title: 'Enable return key automatically', - render: function (): React.Node { - return ( - - - - - - ); - }, - }, - { - title: 'Secure text entry', - render: function (): React.Node { - return ; - }, - }, - { - title: 'Colored input text', - render: function (): React.Node { - return ( - - - - - ); - }, - }, - { - title: 'Colored highlight/cursor for text input', - render: function (): React.Node { - return ( - - - - - ); - }, - }, - { - title: 'Clear button mode', - render: function (): React.Node { - const clearButtonModes = [ - 'never', - 'while-editing', - 'unless-editing', - 'always', - ]; - const examples = clearButtonModes.map(mode => { - return ( - - - - ); - }); - return {examples}; - }, - }, - { - title: 'Clear and select', - render: function (): React.Node { - return ( - - - - - - - - - - - - - - - ); - }, - }, - { - title: 'Multiline blur on submit', - render: function (): React.Node { - return ( - - - Alert.alert('Alert', event.nativeEvent.text) - } - /> - - ); - }, - }, - { - title: 'Multiline', - render: function (): React.Node { - return ( - - - - - - - - ); - }, - }, - { - title: 'Editable and Read only', - render: function (): React.Node { - return ( - - - - - - - ); - }, - }, - { - title: 'TextInput Intrinsic Size', - render: function (): React.Node { - return ( - - Singleline TextInput - - - - Multiline TextInput - - - - - - - - ); - }, - }, - { - title: 'Auto-expanding', - render: function (): React.Node { - return ( - - - - ); - }, - }, - { - title: 'Auto-expanding', - render: function (): React.Node { - return ( - - - huge - generic generic generic - - small small small small small small - - regular regular - - huge huge huge huge huge - - generic generic generic - - - ); - }, - }, - { - title: 'TextInput maxLength', - render: function (): React.Node { - return ( - - - - - - - - - - - - - - - ); - }, - }, - { - title: 'Text Auto Complete', - render: function (): React.Node { - return ( - - - - - - - - - - - - - - - ); - }, - }, - { - title: 'Text Content Type', - render: function (): React.Node { - return ( - - - - - - - - - - - - - - - - - - ); - }, - }, - { - title: 'TextInput Placeholder Styles', - render: function (): React.Node { - return ( - - - - - - - - - ); - }, - }, - { - title: 'showSoftInputOnFocus', - render: function (): React.Node { - return ( - - - - - - ); - }, - }, - { - title: 'Line Break Strategy', - render: function (): React.Node { - const lineBreakStrategy = ['none', 'standard', 'hangul-word', 'push-out']; - const textByCode = { - en: 'lineBreakStrategy lineBreakStrategy lineBreakStrategy lineBreakStrategy', - ko: '한글개행한글개행 한글개행한글개행 한글개행한글개행 한글개행한글개행 한글개행한글개행 한글개행한글개행', - ja: 'かいぎょう かいぎょう かいぎょう かいぎょう かいぎょう かいぎょう', - cn: '改行 改行 改行 改行 改行 改行 改行 改行 改行 改行 改行 改行', - }; - return ( - - {lineBreakStrategy.map(strategy => { - return ( - - {`Strategy: ${strategy}`} - {Object.keys(textByCode).map(code => { - return ( - - {`[${code}]`} - - - ); - })} - - ); - })} - - ); - }, - }, - { - title: 'iOS autoformatting behaviors', - render: function (): React.Node { - return ( - - - - - - - - - ); - }, - }, + // { + // title: 'Live Re-Write (ひ -> 日)', + // render: function (): React.Node { + // return ; + // }, + // }, + // { + // title: 'Keyboard Input Accessory View', + // render: function (): React.Node { + // return ( + // + // + // + // + // ); + // }, + // }, + // { + // title: "Default Input Accessory View with returnKeyType = 'done'", + // render: function (): React.Node { + // const keyboardTypesWithDoneButton = [ + // 'number-pad', + // 'phone-pad', + // 'decimal-pad', + // 'ascii-capable-number-pad', + // ]; + // const examples = keyboardTypesWithDoneButton.map(type => { + // return ( + // + // + // + // ); + // }); + // return {examples}; + // }, + // }, + // { + // title: 'Nested content and `value` property', + // render: function (): React.Node { + // return ( + // + // + // + // (first raw text node) + // (internal raw text node) + // (last raw text node) + // + // + // + // + // (first raw text node) + // (internal raw text node) + // (last raw text node) + // + // + // + // ); + // }, + // }, + // { + // title: 'Keyboard appearance', + // render: function (): React.Node { + // const keyboardAppearance = ['default', 'light', 'dark']; + // const examples = keyboardAppearance.map(type => { + // return ( + // + // + // + // ); + // }); + // return {examples}; + // }, + // }, + // { + // title: 'Return key types', + // render: function (): React.Node { + // const returnKeyTypes = [ + // 'default', + // 'go', + // 'google', + // 'join', + // 'next', + // 'route', + // 'search', + // 'send', + // 'yahoo', + // 'done', + // 'emergency-call', + // ]; + // const examples = returnKeyTypes.map(type => { + // return ( + // + // + // + // ); + // }); + // return {examples}; + // }, + // }, + // { + // title: 'Enable return key automatically', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Secure text entry', + // render: function (): React.Node { + // return ; + // }, + // }, + // { + // title: 'Colored input text', + // render: function (): React.Node { + // return ( + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Colored highlight/cursor for text input', + // render: function (): React.Node { + // return ( + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Clear button mode', + // render: function (): React.Node { + // const clearButtonModes = [ + // 'never', + // 'while-editing', + // 'unless-editing', + // 'always', + // ]; + // const examples = clearButtonModes.map(mode => { + // return ( + // + // + // + // ); + // }); + // return {examples}; + // }, + // }, + // { + // title: 'Clear and select', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Multiline blur on submit', + // render: function (): React.Node { + // return ( + // + // + // Alert.alert('Alert', event.nativeEvent.text) + // } + // /> + // + // ); + // }, + // }, + // { + // title: 'Multiline', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Editable and Read only', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'TextInput Intrinsic Size', + // render: function (): React.Node { + // return ( + // + // Singleline TextInput + // + // + // + // Multiline TextInput + // + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Auto-expanding', + // render: function (): React.Node { + // return ( + // + // + // + // ); + // }, + // }, + // { + // title: 'Auto-expanding', + // render: function (): React.Node { + // return ( + // + // + // huge + // generic generic generic + // + // small small small small small small + // + // regular regular + // + // huge huge huge huge huge + // + // generic generic generic + // + // + // ); + // }, + // }, + // { + // title: 'TextInput maxLength', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Text Auto Complete', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Text Content Type', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'TextInput Placeholder Styles', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'showSoftInputOnFocus', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Line Break Strategy', + // render: function (): React.Node { + // const lineBreakStrategy = ['none', 'standard', 'hangul-word', 'push-out']; + // const textByCode = { + // en: 'lineBreakStrategy lineBreakStrategy lineBreakStrategy lineBreakStrategy', + // ko: '한글개행한글개행 한글개행한글개행 한글개행한글개행 한글개행한글개행 한글개행한글개행 한글개행한글개행', + // ja: 'かいぎょう かいぎょう かいぎょう かいぎょう かいぎょう かいぎょう', + // cn: '改行 改行 改行 改行 改行 改行 改行 改行 改行 改行 改行 改行', + // }; + // return ( + // + // {lineBreakStrategy.map(strategy => { + // return ( + // + // {`Strategy: ${strategy}`} + // {Object.keys(textByCode).map(code => { + // return ( + // + // {`[${code}]`} + // + // + // ); + // })} + // + // ); + // })} + // + // ); + // }, + // }, + // { + // title: 'iOS autoformatting behaviors', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // + // + // ); + // }, + // }, ]; module.exports = ({ diff --git a/packages/rn-tester/js/examples/TextInput/TextInputSharedExamples.js b/packages/rn-tester/js/examples/TextInput/TextInputSharedExamples.js index d03a0b9a33a2cc..a3b0d345c3bad0 100644 --- a/packages/rn-tester/js/examples/TextInput/TextInputSharedExamples.js +++ b/packages/rn-tester/js/examples/TextInput/TextInputSharedExamples.js @@ -589,14 +589,18 @@ class SelectionExample extends React.Component< function UncontrolledExample() { const [isFocused, setIsFocused] = React.useState(false); - + const [text1, setText] = React.useState(''); + console.log('UncontrolledExample', text1); return ( setIsFocused(true)} - onBlur={() => setIsFocused(false)} + // value={text1} + // onChange={({nativeEvent: {eventCount, target, text}}) => setText(text)} + // onChangeText={setText} + // style={isFocused ? styles.focusedUncontrolled : styles.default} + // onFocus={() => setIsFocused(true)} + // onBlur={() => setIsFocused(false)} /> ); } @@ -835,285 +839,285 @@ function MultilineStyledTextInput({ } module.exports = ([ - { - title: 'Auto-focus', - render: function (): React.Node { - return ( - - ); - }, - }, - { - name: 'maxLength', - title: "Live Re-Write ( -> '_') + maxLength", - render: function (): React.Node { - return ; - }, - }, - { - title: 'Live Re-Write (no spaces allowed)', - render: function (): React.Node { - return ; - }, - }, - { - name: 'clearButton', - title: 'Live Re-Write (no spaces allowed) and clear', - render: function (): React.Node { - return ; - }, - }, - { - title: 'Auto-capitalize', - name: 'autoCapitalize', - render: function (): React.Node { - return ( - - - - - - - - - - - - - - - ); - }, - }, - { - title: 'Auto-correct', - render: function (): React.Node { - return ( - - - - - - - - - ); - }, - }, - { - title: 'Keyboard types', - name: 'keyboardTypes', - render: function (): React.Node { - const keyboardTypes = [ - 'default', - 'ascii-capable', - 'numbers-and-punctuation', - 'url', - 'number-pad', - 'phone-pad', - 'name-phone-pad', - 'email-address', - 'decimal-pad', - 'twitter', - 'web-search', - 'ascii-capable-number-pad', - 'numeric', - ]; - const examples = keyboardTypes.map(type => { - return ( - - - - ); - }); - return {examples}; - }, - }, - { - title: 'Input modes', - name: 'inputModes', - render: function (): React.Node { - const inputMode = [ - 'none', - 'text', - 'decimal', - 'numeric', - 'tel', - 'search', - 'email', - 'url', - ]; - const examples = inputMode.map(mode => { - return ( - - - - ); - }); - return {examples}; - }, - }, - { - title: 'Blur on submit', - render: function (): React.Element { - return ; - }, - }, - { - title: 'enterKeyHint modes', - name: 'enterKeyHintTypes', - render: function (): React.Node { - const enterKeyHintTypesHints = [ - 'enter', - 'done', - 'go', - 'next', - 'previous', - 'search', - 'send', - ]; - const examples = enterKeyHintTypesHints.map(hint => { - return ( - - - - ); - }); - return {examples}; - }, - }, - { - title: 'Submit behavior', - render: function (): React.Element { - return ; - }, - }, - { - title: 'Event handling', - render: function (): React.Element { - return ; - }, - }, - { - title: 'fontFamily, fontWeight and fontStyle', - render: function (): React.Node { - const fontFamilyA = Platform.OS === 'ios' ? 'Cochin' : 'sans-serif'; - const fontFamilyB = Platform.OS === 'ios' ? 'Courier' : 'serif'; + // { + // title: 'Auto-focus', + // render: function (): React.Node { + // return ( + // + // ); + // }, + // }, + // { + // name: 'maxLength', + // title: "Live Re-Write ( -> '_') + maxLength", + // render: function (): React.Node { + // return ; + // }, + // }, + // { + // title: 'Live Re-Write (no spaces allowed)', + // render: function (): React.Node { + // return ; + // }, + // }, + // { + // name: 'clearButton', + // title: 'Live Re-Write (no spaces allowed) and clear', + // render: function (): React.Node { + // return ; + // }, + // }, + // { + // title: 'Auto-capitalize', + // name: 'autoCapitalize', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // + // + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Auto-correct', + // render: function (): React.Node { + // return ( + // + // + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Keyboard types', + // name: 'keyboardTypes', + // render: function (): React.Node { + // const keyboardTypes = [ + // 'default', + // 'ascii-capable', + // 'numbers-and-punctuation', + // 'url', + // 'number-pad', + // 'phone-pad', + // 'name-phone-pad', + // 'email-address', + // 'decimal-pad', + // 'twitter', + // 'web-search', + // 'ascii-capable-number-pad', + // 'numeric', + // ]; + // const examples = keyboardTypes.map(type => { + // return ( + // + // + // + // ); + // }); + // return {examples}; + // }, + // }, + // { + // title: 'Input modes', + // name: 'inputModes', + // render: function (): React.Node { + // const inputMode = [ + // 'none', + // 'text', + // 'decimal', + // 'numeric', + // 'tel', + // 'search', + // 'email', + // 'url', + // ]; + // const examples = inputMode.map(mode => { + // return ( + // + // + // + // ); + // }); + // return {examples}; + // }, + // }, + // { + // title: 'Blur on submit', + // render: function (): React.Element { + // return ; + // }, + // }, + // { + // title: 'enterKeyHint modes', + // name: 'enterKeyHintTypes', + // render: function (): React.Node { + // const enterKeyHintTypesHints = [ + // 'enter', + // 'done', + // 'go', + // 'next', + // 'previous', + // 'search', + // 'send', + // ]; + // const examples = enterKeyHintTypesHints.map(hint => { + // return ( + // + // + // + // ); + // }); + // return {examples}; + // }, + // }, + // { + // title: 'Submit behavior', + // render: function (): React.Element { + // return ; + // }, + // }, + // { + // title: 'Event handling', + // render: function (): React.Element { + // return ; + // }, + // }, + // { + // title: 'fontFamily, fontWeight and fontStyle', + // render: function (): React.Node { + // const fontFamilyA = Platform.OS === 'ios' ? 'Cochin' : 'sans-serif'; + // const fontFamilyB = Platform.OS === 'ios' ? 'Courier' : 'serif'; - return ( - - - - - - - - ); - }, - }, - { - title: 'Attributed text', - name: 'attributedText', - render: function (): React.Node { - return ; - }, - }, - { - title: 'Text selection & cursor placement', - name: 'cursorPlacement', - render: function (): React.Node { - return ( - - - - - ); - }, - }, - { - title: 'Text selection & cursor placement (imperative)', - name: 'cursorPlacementImperative', - render: function (): React.Node { - return ( - - - - - ); - }, - }, + // return ( + // + // + // + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Attributed text', + // name: 'attributedText', + // render: function (): React.Node { + // return ; + // }, + // }, + // { + // title: 'Text selection & cursor placement', + // name: 'cursorPlacement', + // render: function (): React.Node { + // return ( + // + // + // + // + // ); + // }, + // }, + // { + // title: 'Text selection & cursor placement (imperative)', + // name: 'cursorPlacementImperative', + // render: function (): React.Node { + // return ( + // + // + // + // + // ); + // }, + // }, { title: 'Uncontrolled component with layout changes', name: 'uncontrolledComponent', render: () => , }, - { - title: 'Text styles', - name: 'textStyles', - render: () => , - }, + // { + // title: 'Text styles', + // name: 'textStyles', + // render: () => , + // }, ]: Array); From a290eaa68b6bee5f1c9233835c21b8f69fde6229 Mon Sep 17 00:00:00 2001 From: Edu Date: Mon, 18 Dec 2023 14:21:35 +0100 Subject: [PATCH 3/9] TextInput some e2e tests --- packages/rn-tester-e2e/tests/helpers/utils.js | 19 + .../tests/screens/components.screen.js | 22 + .../components/textInputComponent.screen.js | 134 ++ .../textInputComponentScreen.test.js | 76 + .../TextInput/TextInputExample.ios.js | 1251 +++++++++-------- .../TextInput/TextInputSharedExamples.js | 562 ++++---- 6 files changed, 1170 insertions(+), 894 deletions(-) create mode 100644 packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js create mode 100644 packages/rn-tester-e2e/tests/specs/components/textInputComponentScreen.test.js diff --git a/packages/rn-tester-e2e/tests/helpers/utils.js b/packages/rn-tester-e2e/tests/helpers/utils.js index 9516297600513d..937dd0b469b21a 100644 --- a/packages/rn-tester-e2e/tests/helpers/utils.js +++ b/packages/rn-tester-e2e/tests/helpers/utils.js @@ -26,6 +26,25 @@ class Utils { await driver.$(locator).click(); } + async setElementText(locator: string, text: string): Promise { + await driver.$(locator).waitForDisplayed(); + await driver.$(locator).click(); + await driver.$(locator).setValue(text); + } + + async doubleTapKeyboardSpacebar(locator: string): Promise { + await driver.$(locator).waitForDisplayed(); + const screenSize = await driver.getWindowRect(); + + // Calculate the coordinates of the spacebar based on percentages + const keyboardX = screenSize.width * 0.5; // 50% from the left edge + const keyboardY = screenSize.height * 0.9; // 0.85 - 0.92 works from the top edge (adjust as needed) + + await driver.executeScript('mobile: doubleTap', [ + {x: keyboardX, y: keyboardY, duration: 1.0}, + ]); + } + async getElementText(locator: string): Promise { await driver.$(locator).waitForDisplayed(); return driver.$(locator).getText(); diff --git a/packages/rn-tester-e2e/tests/screens/components.screen.js b/packages/rn-tester-e2e/tests/screens/components.screen.js index 605309ce668b59..dcd77f8bcc3610 100644 --- a/packages/rn-tester-e2e/tests/screens/components.screen.js +++ b/packages/rn-tester-e2e/tests/screens/components.screen.js @@ -29,6 +29,7 @@ const refreshControlComponentLabel = 'RefreshControl Adds pull-to-refresh support to a scrollview.'; const scrollViewSimpleExampleComponentLabel = 'ScrollViewSimpleExample Component that enables scrolling through child components.'; +const textInputComponentLabel = 'TextInput Single and multi-line text inputs.'; type ComponentsScreenType = { buttonComponentLabelElement: string, @@ -41,6 +42,7 @@ type ComponentsScreenType = { pressableComponentLabelElement: string, refreshControlComponentLabelElement: string, scrollViewSimpleExampleComponentLabelElement: string, + textInputComponentLabelElement: string, checkButtonComponentIsDisplayed: () => Promise, checkActivityIndicatorComponentIsDisplayed: () => Promise, checkKeyboardAvoidingViewComponentIsDisplayed: () => Promise, @@ -51,6 +53,7 @@ type ComponentsScreenType = { checkPressableComponentIsDisplayed: () => Promise, checkRefreshControlComponentIsDisplayed: () => Promise, checkScrollViewSimpleExampleComponentIsDisplayed: () => Promise, + checkTextInputComponentIsDisplayed: () => Promise, clickButtonComponent: () => Promise, clickActivityIndicatorComponent: () => Promise, clickKeyboardAvoidingViewComponent: () => Promise, @@ -61,6 +64,7 @@ type ComponentsScreenType = { clickPressableComponent: () => Promise, clickRefreshControlComponent: () => Promise, clickScrollViewSimpleExampleComponent: () => Promise, + clickTextInputComponent: () => Promise, }; export const ComponentsScreen: ComponentsScreenType = { @@ -105,6 +109,11 @@ export const ComponentsScreen: ComponentsScreenType = { ios: iOSLabel(scrollViewSimpleExampleComponentLabel), android: `~${scrollViewSimpleExampleComponentLabel}`, }), + textInputComponentLabelElement: Utils.platformSelect({ + ios: iOSLabel(textInputComponentLabel), + android: `~${textInputComponentLabel}`, + }), + // Methods to interact with top level elements in the list checkButtonComponentIsDisplayed: async function ( this: ComponentsScreenType, @@ -172,6 +181,14 @@ export const ComponentsScreen: ComponentsScreenType = { this.scrollViewSimpleExampleComponentLabelElement, ); }, + checkTextInputComponentIsDisplayed: async function ( + this: ComponentsScreenType, + ): Promise { + return await Utils.checkElementExistence( + this.textInputComponentLabelElement, + ); + }, + clickButtonComponent: async function ( this: ComponentsScreenType, ): Promise { @@ -222,4 +239,9 @@ export const ComponentsScreen: ComponentsScreenType = { ): Promise { await Utils.clickElement(this.scrollViewSimpleExampleComponentLabelElement); }, + clickTextInputComponent: async function ( + this: ComponentsScreenType, + ): Promise { + await Utils.clickElement(this.textInputComponentLabelElement); + }, }; diff --git a/packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js b/packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js new file mode 100644 index 00000000000000..21865c4bd614ef --- /dev/null +++ b/packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js @@ -0,0 +1,134 @@ +/** + * 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. + * + * @flow + * @format + */ + +import { + UtilsSingleton as Utils, + iOSName, + androidWidget, +} from '../../helpers/utils'; + +type TextInputComponentScreenType = { + textInputScreenElement: string, + textInputReWriteElement: string, + textInputNoSpaceAllowElement: string, + textInputReWriteClearElement: string, + textInputControlledDoubleSpaceElement: string, + btnClearElement: string, + checkTextIsReWrited: () => Promise, + checkLongTextIsReWrited: () => Promise, + checkNoSpaceAllowed: () => Promise, + checkAddTextAndClearButton: () => Promise, + checkDoubleSpaceControlledTextInput: () => Promise, + scrollToTextAndClearButtonElement: () => Promise, + scrollToDoubleSpaceElement: () => Promise, + scrollUntilTextInputComponentIsDisplayed: () => Promise, +}; + +export const TextInputComponentScreen: TextInputComponentScreenType = { + // reference in the Components list + textInputScreenElement: Utils.platformSelect({ + ios: iOSName('TextInput'), + android: androidWidget('TextView', 'text', 'TextInput'), + }), + // References to elements within the Button Component screen + textInputReWriteElement: Utils.platformSelect({ + ios: iOSName('rewrite_sp_underscore_input'), + android: androidWidget( + 'EditText', + 'resource-id', + 'rewrite_sp_underscore_input', + ), + }), + textInputNoSpaceAllowElement: Utils.platformSelect({ + ios: iOSName('rewrite_no_sp_input'), + android: androidWidget('EditText', 'resource-id', 'rewrite_no_sp_input'), + }), + textInputReWriteClearElement: Utils.platformSelect({ + ios: iOSName('rewrite_clear_input'), + android: androidWidget('EditText', 'resource-id', 'rewrite_clear_input'), + }), + btnClearElement: Utils.platformSelect({ + ios: iOSName('rewrite_clear_button'), + android: androidWidget('Button', 'resource-id', 'rewrite_clear_button'), + }), + textInputControlledDoubleSpaceElement: Utils.platformSelect({ + ios: iOSName('rewrite_double_space'), + android: androidWidget('Button', 'resource-id', 'rewrite_double_space'), + }), + // Methods to interact with the elements + checkTextIsReWrited: async function ( + this: TextInputComponentScreenType, + ): Promise { + const text = 'foo space replace'; + await Utils.clickElement(this.textInputReWriteElement); + await Utils.setElementText(this.textInputReWriteElement, text); + return await Utils.getElementText(this.textInputReWriteElement); + }, + checkLongTextIsReWrited: async function ( + this: TextInputComponentScreenType, + ): Promise { + const text = 'foobars space replacement'; + await Utils.clickElement(this.textInputReWriteElement); + await Utils.setElementText(this.textInputReWriteElement, text); + return Utils.getElementText(this.textInputReWriteElement); + }, + checkNoSpaceAllowed: async function ( + this: TextInputComponentScreenType, + ): Promise { + const text = 'foo bar no space test'; + await Utils.clickElement(this.textInputNoSpaceAllowElement); + await Utils.setElementText(this.textInputNoSpaceAllowElement, text); + return await Utils.getElementText(this.textInputNoSpaceAllowElement); + }, + checkAddTextAndClearButton: async function ( + this: TextInputComponentScreenType, + ): Promise { + const text = 'testing clear text'; + await Utils.clickElement(this.textInputReWriteClearElement); + await Utils.setElementText(this.textInputReWriteClearElement, text); + await Utils.clickElement(this.btnClearElement); + return await Utils.getElementText(this.textInputReWriteClearElement); + }, + checkDoubleSpaceControlledTextInput: async function ( + this: TextInputComponentScreenType, + ): Promise { + const text = 'testing'; + await Utils.setElementText( + this.textInputControlledDoubleSpaceElement, + text, + ); + await Utils.doubleTapKeyboardSpacebar( + this.textInputControlledDoubleSpaceElement, + ); + return await Utils.getElementText( + this.textInputControlledDoubleSpaceElement, + ); + }, + clickSubmitApplication: async function ( + this: TextInputComponentScreenType, + ): Promise { + await Utils.clickElement(this.btnSubmitElement); + }, + scrollUntilTextInputComponentIsDisplayed: async function ( + this: TextInputComponentScreenType, + ): Promise { + return await Utils.scrollToElement(this.textInputScreenElement); + }, + scrollToTextAndClearButtonElement: async function ( + this: TextInputComponentScreenType, + ): Promise { + await Utils.scrollToElement(this.textInputReWriteClearElement); + }, + scrollToDoubleSpaceElement: async function ( + this: TextInputComponentScreenType, + ): Promise { + await Utils.scrollToElement(this.textInputControlledDoubleSpaceElement); + }, +}; diff --git a/packages/rn-tester-e2e/tests/specs/components/textInputComponentScreen.test.js b/packages/rn-tester-e2e/tests/specs/components/textInputComponentScreen.test.js new file mode 100644 index 00000000000000..5549787614dace --- /dev/null +++ b/packages/rn-tester-e2e/tests/specs/components/textInputComponentScreen.test.js @@ -0,0 +1,76 @@ +/** + * 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. + * + * @flow + * @format + */ + +const {ComponentsScreen} = require('../../screens/components.screen.js'); +const { + TextInputComponentScreen, +} = require('../../screens/components/textInputComponent.screen.js'); + +describe('Test is checking text replacement', () => { + test('Should replace properly space by underscore', async () => { + TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); + expect( + await ComponentsScreen.checkTextInputComponentIsDisplayed(), + ).toBeTruthy(); + await ComponentsScreen.clickTextInputComponent(); + expect(await TextInputComponentScreen.checkTextIsReWrited()).toEqual( + 'foo_space_replace', + ); + }); + test('Should replace properly space by underscore and limit character amount', async () => { + TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); + expect( + await ComponentsScreen.checkTextInputComponentIsDisplayed(), + ).toBeTruthy(); + await ComponentsScreen.clickTextInputComponent(); + expect(await TextInputComponentScreen.checkLongTextIsReWrited()).toEqual( + 'foobars_space_replac', + ); + }); + + test('Should remove all spaces ', async () => { + TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); + expect( + await ComponentsScreen.checkTextInputComponentIsDisplayed(), + ).toBeTruthy(); + // await ComponentsScreen.clickTextInputComponent(); + expect(await TextInputComponentScreen.checkNoSpaceAllowed()).toEqual( + 'foobarnospacetest', + ); + }); +}); + +describe('Test is clearing text by Button', () => { + test('Should remove properly the text when clear button clicked', async () => { + TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); + expect( + await ComponentsScreen.checkTextInputComponentIsDisplayed(), + ).toBeTruthy(); + await ComponentsScreen.clickTextInputComponent(); + await TextInputComponentScreen.scrollToTextAndClearButtonElement(); + expect(await TextInputComponentScreen.checkAddTextAndClearButton()).toEqual( + '', + ); + }); +}); + +describe('Test double space to dot', () => { + test('Should replace to dot in a Controlled TextInput', async () => { + TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); + expect( + await ComponentsScreen.checkTextInputComponentIsDisplayed(), + ).toBeTruthy(); + await ComponentsScreen.clickTextInputComponent(); + await TextInputComponentScreen.scrollToDoubleSpaceElement(); + expect( + await TextInputComponentScreen.checkDoubleSpaceControlledTextInput(), + ).toEqual('testing. '); + }); +}); diff --git a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js index 74216120374426..c02f3635f95df9 100644 --- a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js +++ b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js @@ -265,6 +265,29 @@ class AutogrowingTextInputExample extends React.Component< } } +class RewriteDoubleSpaceExample extends React.Component<$FlowFixMeProps, any> { + constructor(props: any | void) { + super(props); + this.state = {text: ''}; + } + render(): React.Node { + return ( + + { + this.setState({text}); + }} + style={styles.default} + value={this.state.text} + /> + + ); + } +} + const styles = StyleSheet.create({ default: { borderWidth: StyleSheet.hairlineWidth, @@ -325,617 +348,623 @@ const styles = StyleSheet.create({ const examples: Array = [ ...TextInputSharedExamples, - // { - // title: 'Live Re-Write (ひ -> 日)', - // render: function (): React.Node { - // return ; - // }, - // }, - // { - // title: 'Keyboard Input Accessory View', - // render: function (): React.Node { - // return ( - // - // - // - // - // ); - // }, - // }, - // { - // title: "Default Input Accessory View with returnKeyType = 'done'", - // render: function (): React.Node { - // const keyboardTypesWithDoneButton = [ - // 'number-pad', - // 'phone-pad', - // 'decimal-pad', - // 'ascii-capable-number-pad', - // ]; - // const examples = keyboardTypesWithDoneButton.map(type => { - // return ( - // - // - // - // ); - // }); - // return {examples}; - // }, - // }, - // { - // title: 'Nested content and `value` property', - // render: function (): React.Node { - // return ( - // - // - // - // (first raw text node) - // (internal raw text node) - // (last raw text node) - // - // - // - // - // (first raw text node) - // (internal raw text node) - // (last raw text node) - // - // - // - // ); - // }, - // }, - // { - // title: 'Keyboard appearance', - // render: function (): React.Node { - // const keyboardAppearance = ['default', 'light', 'dark']; - // const examples = keyboardAppearance.map(type => { - // return ( - // - // - // - // ); - // }); - // return {examples}; - // }, - // }, - // { - // title: 'Return key types', - // render: function (): React.Node { - // const returnKeyTypes = [ - // 'default', - // 'go', - // 'google', - // 'join', - // 'next', - // 'route', - // 'search', - // 'send', - // 'yahoo', - // 'done', - // 'emergency-call', - // ]; - // const examples = returnKeyTypes.map(type => { - // return ( - // - // - // - // ); - // }); - // return {examples}; - // }, - // }, - // { - // title: 'Enable return key automatically', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Secure text entry', - // render: function (): React.Node { - // return ; - // }, - // }, - // { - // title: 'Colored input text', - // render: function (): React.Node { - // return ( - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Colored highlight/cursor for text input', - // render: function (): React.Node { - // return ( - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Clear button mode', - // render: function (): React.Node { - // const clearButtonModes = [ - // 'never', - // 'while-editing', - // 'unless-editing', - // 'always', - // ]; - // const examples = clearButtonModes.map(mode => { - // return ( - // - // - // - // ); - // }); - // return {examples}; - // }, - // }, - // { - // title: 'Clear and select', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // - // - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Multiline blur on submit', - // render: function (): React.Node { - // return ( - // - // - // Alert.alert('Alert', event.nativeEvent.text) - // } - // /> - // - // ); - // }, - // }, - // { - // title: 'Multiline', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Editable and Read only', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'TextInput Intrinsic Size', - // render: function (): React.Node { - // return ( - // - // Singleline TextInput - // - // - // - // Multiline TextInput - // - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Auto-expanding', - // render: function (): React.Node { - // return ( - // - // - // - // ); - // }, - // }, - // { - // title: 'Auto-expanding', - // render: function (): React.Node { - // return ( - // - // - // huge - // generic generic generic - // - // small small small small small small - // - // regular regular - // - // huge huge huge huge huge - // - // generic generic generic - // - // - // ); - // }, - // }, - // { - // title: 'TextInput maxLength', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // - // - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Text Auto Complete', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // - // - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Text Content Type', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // - // - // - // - // - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'TextInput Placeholder Styles', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'showSoftInputOnFocus', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Line Break Strategy', - // render: function (): React.Node { - // const lineBreakStrategy = ['none', 'standard', 'hangul-word', 'push-out']; - // const textByCode = { - // en: 'lineBreakStrategy lineBreakStrategy lineBreakStrategy lineBreakStrategy', - // ko: '한글개행한글개행 한글개행한글개행 한글개행한글개행 한글개행한글개행 한글개행한글개행 한글개행한글개행', - // ja: 'かいぎょう かいぎょう かいぎょう かいぎょう かいぎょう かいぎょう', - // cn: '改行 改行 改行 改行 改行 改行 改行 改行 改行 改行 改行 改行', - // }; - // return ( - // - // {lineBreakStrategy.map(strategy => { - // return ( - // - // {`Strategy: ${strategy}`} - // {Object.keys(textByCode).map(code => { - // return ( - // - // {`[${code}]`} - // - // - // ); - // })} - // - // ); - // })} - // - // ); - // }, - // }, - // { - // title: 'iOS autoformatting behaviors', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // - // - // ); - // }, - // }, + { + title: 'Live Re-Write (double space to period)', + render: function (): React.Node { + return ; + }, + }, + { + title: 'Live Re-Write (ひ -> 日)', + render: function (): React.Node { + return ; + }, + }, + { + title: 'Keyboard Input Accessory View', + render: function (): React.Node { + return ( + + + + + ); + }, + }, + { + title: "Default Input Accessory View with returnKeyType = 'done'", + render: function (): React.Node { + const keyboardTypesWithDoneButton = [ + 'number-pad', + 'phone-pad', + 'decimal-pad', + 'ascii-capable-number-pad', + ]; + const examples = keyboardTypesWithDoneButton.map(type => { + return ( + + + + ); + }); + return {examples}; + }, + }, + { + title: 'Nested content and `value` property', + render: function (): React.Node { + return ( + + + + (first raw text node) + (internal raw text node) + (last raw text node) + + + + + (first raw text node) + (internal raw text node) + (last raw text node) + + + + ); + }, + }, + { + title: 'Keyboard appearance', + render: function (): React.Node { + const keyboardAppearance = ['default', 'light', 'dark']; + const examples = keyboardAppearance.map(type => { + return ( + + + + ); + }); + return {examples}; + }, + }, + { + title: 'Return key types', + render: function (): React.Node { + const returnKeyTypes = [ + 'default', + 'go', + 'google', + 'join', + 'next', + 'route', + 'search', + 'send', + 'yahoo', + 'done', + 'emergency-call', + ]; + const examples = returnKeyTypes.map(type => { + return ( + + + + ); + }); + return {examples}; + }, + }, + { + title: 'Enable return key automatically', + render: function (): React.Node { + return ( + + + + + + ); + }, + }, + { + title: 'Secure text entry', + render: function (): React.Node { + return ; + }, + }, + { + title: 'Colored input text', + render: function (): React.Node { + return ( + + + + + ); + }, + }, + { + title: 'Colored highlight/cursor for text input', + render: function (): React.Node { + return ( + + + + + ); + }, + }, + { + title: 'Clear button mode', + render: function (): React.Node { + const clearButtonModes = [ + 'never', + 'while-editing', + 'unless-editing', + 'always', + ]; + const examples = clearButtonModes.map(mode => { + return ( + + + + ); + }); + return {examples}; + }, + }, + { + title: 'Clear and select', + render: function (): React.Node { + return ( + + + + + + + + + + + + + + + ); + }, + }, + { + title: 'Multiline blur on submit', + render: function (): React.Node { + return ( + + + Alert.alert('Alert', event.nativeEvent.text) + } + /> + + ); + }, + }, + { + title: 'Multiline', + render: function (): React.Node { + return ( + + + + + + + + ); + }, + }, + { + title: 'Editable and Read only', + render: function (): React.Node { + return ( + + + + + + + ); + }, + }, + { + title: 'TextInput Intrinsic Size', + render: function (): React.Node { + return ( + + Singleline TextInput + + + + Multiline TextInput + + + + + + + + ); + }, + }, + { + title: 'Auto-expanding', + render: function (): React.Node { + return ( + + + + ); + }, + }, + { + title: 'Auto-expanding', + render: function (): React.Node { + return ( + + + huge + generic generic generic + + small small small small small small + + regular regular + + huge huge huge huge huge + + generic generic generic + + + ); + }, + }, + { + title: 'TextInput maxLength', + render: function (): React.Node { + return ( + + + + + + + + + + + + + + + ); + }, + }, + { + title: 'Text Auto Complete', + render: function (): React.Node { + return ( + + + + + + + + + + + + + + + ); + }, + }, + { + title: 'Text Content Type', + render: function (): React.Node { + return ( + + + + + + + + + + + + + + + + + + ); + }, + }, + { + title: 'TextInput Placeholder Styles', + render: function (): React.Node { + return ( + + + + + + + + + ); + }, + }, + { + title: 'showSoftInputOnFocus', + render: function (): React.Node { + return ( + + + + + + ); + }, + }, + { + title: 'Line Break Strategy', + render: function (): React.Node { + const lineBreakStrategy = ['none', 'standard', 'hangul-word', 'push-out']; + const textByCode = { + en: 'lineBreakStrategy lineBreakStrategy lineBreakStrategy lineBreakStrategy', + ko: '한글개행한글개행 한글개행한글개행 한글개행한글개행 한글개행한글개행 한글개행한글개행 한글개행한글개행', + ja: 'かいぎょう かいぎょう かいぎょう かいぎょう かいぎょう かいぎょう', + cn: '改行 改行 改行 改行 改行 改行 改行 改行 改行 改行 改行 改行', + }; + return ( + + {lineBreakStrategy.map(strategy => { + return ( + + {`Strategy: ${strategy}`} + {Object.keys(textByCode).map(code => { + return ( + + {`[${code}]`} + + + ); + })} + + ); + })} + + ); + }, + }, + { + title: 'iOS autoformatting behaviors', + render: function (): React.Node { + return ( + + + + + + + + + ); + }, + }, ]; module.exports = ({ diff --git a/packages/rn-tester/js/examples/TextInput/TextInputSharedExamples.js b/packages/rn-tester/js/examples/TextInput/TextInputSharedExamples.js index a3b0d345c3bad0..5c83a67c5e8e8e 100644 --- a/packages/rn-tester/js/examples/TextInput/TextInputSharedExamples.js +++ b/packages/rn-tester/js/examples/TextInput/TextInputSharedExamples.js @@ -589,18 +589,14 @@ class SelectionExample extends React.Component< function UncontrolledExample() { const [isFocused, setIsFocused] = React.useState(false); - const [text1, setText] = React.useState(''); - console.log('UncontrolledExample', text1); + return ( setText(text)} - // onChangeText={setText} - // style={isFocused ? styles.focusedUncontrolled : styles.default} - // onFocus={() => setIsFocused(true)} - // onBlur={() => setIsFocused(false)} + style={isFocused ? styles.focusedUncontrolled : styles.default} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} /> ); } @@ -839,285 +835,285 @@ function MultilineStyledTextInput({ } module.exports = ([ - // { - // title: 'Auto-focus', - // render: function (): React.Node { - // return ( - // - // ); - // }, - // }, - // { - // name: 'maxLength', - // title: "Live Re-Write ( -> '_') + maxLength", - // render: function (): React.Node { - // return ; - // }, - // }, - // { - // title: 'Live Re-Write (no spaces allowed)', - // render: function (): React.Node { - // return ; - // }, - // }, - // { - // name: 'clearButton', - // title: 'Live Re-Write (no spaces allowed) and clear', - // render: function (): React.Node { - // return ; - // }, - // }, - // { - // title: 'Auto-capitalize', - // name: 'autoCapitalize', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // - // - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Auto-correct', - // render: function (): React.Node { - // return ( - // - // - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Keyboard types', - // name: 'keyboardTypes', - // render: function (): React.Node { - // const keyboardTypes = [ - // 'default', - // 'ascii-capable', - // 'numbers-and-punctuation', - // 'url', - // 'number-pad', - // 'phone-pad', - // 'name-phone-pad', - // 'email-address', - // 'decimal-pad', - // 'twitter', - // 'web-search', - // 'ascii-capable-number-pad', - // 'numeric', - // ]; - // const examples = keyboardTypes.map(type => { - // return ( - // - // - // - // ); - // }); - // return {examples}; - // }, - // }, - // { - // title: 'Input modes', - // name: 'inputModes', - // render: function (): React.Node { - // const inputMode = [ - // 'none', - // 'text', - // 'decimal', - // 'numeric', - // 'tel', - // 'search', - // 'email', - // 'url', - // ]; - // const examples = inputMode.map(mode => { - // return ( - // - // - // - // ); - // }); - // return {examples}; - // }, - // }, - // { - // title: 'Blur on submit', - // render: function (): React.Element { - // return ; - // }, - // }, - // { - // title: 'enterKeyHint modes', - // name: 'enterKeyHintTypes', - // render: function (): React.Node { - // const enterKeyHintTypesHints = [ - // 'enter', - // 'done', - // 'go', - // 'next', - // 'previous', - // 'search', - // 'send', - // ]; - // const examples = enterKeyHintTypesHints.map(hint => { - // return ( - // - // - // - // ); - // }); - // return {examples}; - // }, - // }, - // { - // title: 'Submit behavior', - // render: function (): React.Element { - // return ; - // }, - // }, - // { - // title: 'Event handling', - // render: function (): React.Element { - // return ; - // }, - // }, - // { - // title: 'fontFamily, fontWeight and fontStyle', - // render: function (): React.Node { - // const fontFamilyA = Platform.OS === 'ios' ? 'Cochin' : 'sans-serif'; - // const fontFamilyB = Platform.OS === 'ios' ? 'Courier' : 'serif'; + { + title: 'Auto-focus', + render: function (): React.Node { + return ( + + ); + }, + }, + { + name: 'maxLength', + title: "Live Re-Write ( -> '_') + maxLength", + render: function (): React.Node { + return ; + }, + }, + { + title: 'Live Re-Write (no spaces allowed)', + render: function (): React.Node { + return ; + }, + }, + { + name: 'clearButton', + title: 'Live Re-Write (no spaces allowed) and clear', + render: function (): React.Node { + return ; + }, + }, + { + title: 'Auto-capitalize', + name: 'autoCapitalize', + render: function (): React.Node { + return ( + + + + + + + + + + + + + + + ); + }, + }, + { + title: 'Auto-correct', + render: function (): React.Node { + return ( + + + + + + + + + ); + }, + }, + { + title: 'Keyboard types', + name: 'keyboardTypes', + render: function (): React.Node { + const keyboardTypes = [ + 'default', + 'ascii-capable', + 'numbers-and-punctuation', + 'url', + 'number-pad', + 'phone-pad', + 'name-phone-pad', + 'email-address', + 'decimal-pad', + 'twitter', + 'web-search', + 'ascii-capable-number-pad', + 'numeric', + ]; + const examples = keyboardTypes.map(type => { + return ( + + + + ); + }); + return {examples}; + }, + }, + { + title: 'Input modes', + name: 'inputModes', + render: function (): React.Node { + const inputMode = [ + 'none', + 'text', + 'decimal', + 'numeric', + 'tel', + 'search', + 'email', + 'url', + ]; + const examples = inputMode.map(mode => { + return ( + + + + ); + }); + return {examples}; + }, + }, + { + title: 'Blur on submit', + render: function (): React.Element { + return ; + }, + }, + { + title: 'enterKeyHint modes', + name: 'enterKeyHintTypes', + render: function (): React.Node { + const enterKeyHintTypesHints = [ + 'enter', + 'done', + 'go', + 'next', + 'previous', + 'search', + 'send', + ]; + const examples = enterKeyHintTypesHints.map(hint => { + return ( + + + + ); + }); + return {examples}; + }, + }, + { + title: 'Submit behavior', + render: function (): React.Element { + return ; + }, + }, + { + title: 'Event handling', + render: function (): React.Element { + return ; + }, + }, + { + title: 'fontFamily, fontWeight and fontStyle', + render: function (): React.Node { + const fontFamilyA = Platform.OS === 'ios' ? 'Cochin' : 'sans-serif'; + const fontFamilyB = Platform.OS === 'ios' ? 'Courier' : 'serif'; - // return ( - // - // - // - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Attributed text', - // name: 'attributedText', - // render: function (): React.Node { - // return ; - // }, - // }, - // { - // title: 'Text selection & cursor placement', - // name: 'cursorPlacement', - // render: function (): React.Node { - // return ( - // - // - // - // - // ); - // }, - // }, - // { - // title: 'Text selection & cursor placement (imperative)', - // name: 'cursorPlacementImperative', - // render: function (): React.Node { - // return ( - // - // - // - // - // ); - // }, - // }, + return ( + + + + + + + + ); + }, + }, + { + title: 'Attributed text', + name: 'attributedText', + render: function (): React.Node { + return ; + }, + }, + { + title: 'Text selection & cursor placement', + name: 'cursorPlacement', + render: function (): React.Node { + return ( + + + + + ); + }, + }, + { + title: 'Text selection & cursor placement (imperative)', + name: 'cursorPlacementImperative', + render: function (): React.Node { + return ( + + + + + ); + }, + }, { title: 'Uncontrolled component with layout changes', name: 'uncontrolledComponent', render: () => , }, - // { - // title: 'Text styles', - // name: 'textStyles', - // render: () => , - // }, + { + title: 'Text styles', + name: 'textStyles', + render: () => , + }, ]: Array); From 1038128549905e9eb08781f5bbe6ed18570ba4e6 Mon Sep 17 00:00:00 2001 From: Edu Date: Thu, 4 Jan 2024 10:12:53 +0100 Subject: [PATCH 4/9] Integration tests for TextInput --- packages/rn-tester-e2e/tests/helpers/utils.js | 22 ++++--- .../components/textInputComponent.screen.js | 59 +++++++++++++++++-- .../textInputComponentScreen.test.js | 31 ++++++---- .../TextInput/TextInputExample.ios.js | 1 - 4 files changed, 86 insertions(+), 27 deletions(-) diff --git a/packages/rn-tester-e2e/tests/helpers/utils.js b/packages/rn-tester-e2e/tests/helpers/utils.js index 937dd0b469b21a..c3a382a197c270 100644 --- a/packages/rn-tester-e2e/tests/helpers/utils.js +++ b/packages/rn-tester-e2e/tests/helpers/utils.js @@ -34,15 +34,19 @@ class Utils { async doubleTapKeyboardSpacebar(locator: string): Promise { await driver.$(locator).waitForDisplayed(); - const screenSize = await driver.getWindowRect(); + const {width, height} = await driver.getWindowRect(); - // Calculate the coordinates of the spacebar based on percentages - const keyboardX = screenSize.width * 0.5; // 50% from the left edge - const keyboardY = screenSize.height * 0.9; // 0.85 - 0.92 works from the top edge (adjust as needed) + try { + // Calculate the coordinates of the spacebar based on percentages + const keyboardX = width * 0.5; // 50% from the left edge + const keyboardY = height * 0.9; // 0.85 - 0.92 works all iPhones from the top edge (adjust as needed) - await driver.executeScript('mobile: doubleTap', [ - {x: keyboardX, y: keyboardY, duration: 1.0}, - ]); + await driver.executeScript('mobile: doubleTap', [ + {x: keyboardX, y: keyboardY, duration: 1.0}, + ]); + } catch (err) { + console.log('Can not double tap the spacebar'); + } } async getElementText(locator: string): Promise { @@ -102,6 +106,10 @@ export const iOSName = (name: string): string => { return `[name="${name}"]`; }; +export const iOSChildType = (parentLocator: string, name: string): string => { + return `${parentLocator} [type="${name}"]`; +}; + export const androidWidget = ( type: string, selector: string, diff --git a/packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js b/packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js index 21865c4bd614ef..e9979a2da38059 100644 --- a/packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js +++ b/packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js @@ -11,15 +11,20 @@ import { UtilsSingleton as Utils, iOSName, + iOSChildType, androidWidget, } from '../../helpers/utils'; type TextInputComponentScreenType = { textInputScreenElement: string, textInputReWriteElement: string, + textInputReWriteChildElement: () => string, textInputNoSpaceAllowElement: string, + textInputNoSpaceAllowChildElement: () => string, textInputReWriteClearElement: string, + textInputReWriteClearChildElement: () => string, textInputControlledDoubleSpaceElement: string, + textInputControlledDoubleSpaceChildElement: () => string, btnClearElement: string, checkTextIsReWrited: () => Promise, checkLongTextIsReWrited: () => Promise, @@ -27,6 +32,7 @@ type TextInputComponentScreenType = { checkAddTextAndClearButton: () => Promise, checkDoubleSpaceControlledTextInput: () => Promise, scrollToTextAndClearButtonElement: () => Promise, + scrollToTextNoSpaceAllowElement: () => Promise, scrollToDoubleSpaceElement: () => Promise, scrollUntilTextInputComponentIsDisplayed: () => Promise, }; @@ -37,7 +43,7 @@ export const TextInputComponentScreen: TextInputComponentScreenType = { ios: iOSName('TextInput'), android: androidWidget('TextView', 'text', 'TextInput'), }), - // References to elements within the Button Component screen + // References to elements within the TextInput Component screen textInputReWriteElement: Utils.platformSelect({ ios: iOSName('rewrite_sp_underscore_input'), android: androidWidget( @@ -46,14 +52,41 @@ export const TextInputComponentScreen: TextInputComponentScreenType = { 'rewrite_sp_underscore_input', ), }), + textInputReWriteChildElement: function () { + return Utils.platformSelect({ + ios: iOSChildType( + this.textInputReWriteElement, + 'XCUIElementTypeTextField', + ), + android: this.textInputReWriteElement, + }); + }, textInputNoSpaceAllowElement: Utils.platformSelect({ ios: iOSName('rewrite_no_sp_input'), android: androidWidget('EditText', 'resource-id', 'rewrite_no_sp_input'), }), + textInputNoSpaceAllowChildElement: function () { + return Utils.platformSelect({ + ios: iOSChildType( + this.textInputNoSpaceAllowElement, + 'XCUIElementTypeTextField', + ), + android: this.textInputNoSpaceAllowElement, + }); + }, textInputReWriteClearElement: Utils.platformSelect({ ios: iOSName('rewrite_clear_input'), android: androidWidget('EditText', 'resource-id', 'rewrite_clear_input'), }), + textInputReWriteClearChildElement: function () { + return Utils.platformSelect({ + ios: iOSChildType( + this.textInputReWriteClearElement, + 'XCUIElementTypeTextView', + ), + android: this.textInputReWriteClearElement, + }); + }, btnClearElement: Utils.platformSelect({ ios: iOSName('rewrite_clear_button'), android: androidWidget('Button', 'resource-id', 'rewrite_clear_button'), @@ -62,6 +95,15 @@ export const TextInputComponentScreen: TextInputComponentScreenType = { ios: iOSName('rewrite_double_space'), android: androidWidget('Button', 'resource-id', 'rewrite_double_space'), }), + textInputControlledDoubleSpaceChildElement: function () { + return Utils.platformSelect({ + ios: iOSChildType( + this.textInputControlledDoubleSpaceElement, + 'XCUIElementTypeTextField', + ), + android: this.textInputControlledDoubleSpaceElement, + }); + }, // Methods to interact with the elements checkTextIsReWrited: async function ( this: TextInputComponentScreenType, @@ -69,7 +111,7 @@ export const TextInputComponentScreen: TextInputComponentScreenType = { const text = 'foo space replace'; await Utils.clickElement(this.textInputReWriteElement); await Utils.setElementText(this.textInputReWriteElement, text); - return await Utils.getElementText(this.textInputReWriteElement); + return await Utils.getElementText(this.textInputReWriteChildElement()); }, checkLongTextIsReWrited: async function ( this: TextInputComponentScreenType, @@ -77,7 +119,7 @@ export const TextInputComponentScreen: TextInputComponentScreenType = { const text = 'foobars space replacement'; await Utils.clickElement(this.textInputReWriteElement); await Utils.setElementText(this.textInputReWriteElement, text); - return Utils.getElementText(this.textInputReWriteElement); + return Utils.getElementText(this.textInputReWriteChildElement()); }, checkNoSpaceAllowed: async function ( this: TextInputComponentScreenType, @@ -85,7 +127,7 @@ export const TextInputComponentScreen: TextInputComponentScreenType = { const text = 'foo bar no space test'; await Utils.clickElement(this.textInputNoSpaceAllowElement); await Utils.setElementText(this.textInputNoSpaceAllowElement, text); - return await Utils.getElementText(this.textInputNoSpaceAllowElement); + return await Utils.getElementText(this.textInputNoSpaceAllowChildElement()); }, checkAddTextAndClearButton: async function ( this: TextInputComponentScreenType, @@ -94,7 +136,7 @@ export const TextInputComponentScreen: TextInputComponentScreenType = { await Utils.clickElement(this.textInputReWriteClearElement); await Utils.setElementText(this.textInputReWriteClearElement, text); await Utils.clickElement(this.btnClearElement); - return await Utils.getElementText(this.textInputReWriteClearElement); + return await Utils.getElementText(this.textInputReWriteClearChildElement()); }, checkDoubleSpaceControlledTextInput: async function ( this: TextInputComponentScreenType, @@ -108,7 +150,7 @@ export const TextInputComponentScreen: TextInputComponentScreenType = { this.textInputControlledDoubleSpaceElement, ); return await Utils.getElementText( - this.textInputControlledDoubleSpaceElement, + this.textInputControlledDoubleSpaceChildElement(), ); }, clickSubmitApplication: async function ( @@ -121,6 +163,11 @@ export const TextInputComponentScreen: TextInputComponentScreenType = { ): Promise { return await Utils.scrollToElement(this.textInputScreenElement); }, + scrollToTextNoSpaceAllowElement: async function ( + this: TextInputComponentScreenType, + ): Promise { + await Utils.scrollToElement(this.textInputNoSpaceAllowElement); + }, scrollToTextAndClearButtonElement: async function ( this: TextInputComponentScreenType, ): Promise { diff --git a/packages/rn-tester-e2e/tests/specs/components/textInputComponentScreen.test.js b/packages/rn-tester-e2e/tests/specs/components/textInputComponentScreen.test.js index 5549787614dace..fa89c68885ca39 100644 --- a/packages/rn-tester-e2e/tests/specs/components/textInputComponentScreen.test.js +++ b/packages/rn-tester-e2e/tests/specs/components/textInputComponentScreen.test.js @@ -13,6 +13,8 @@ const { TextInputComponentScreen, } = require('../../screens/components/textInputComponent.screen.js'); +const isIOS = (process?.env?.E2E_DEVICE || 'ios') === 'ios'; + describe('Test is checking text replacement', () => { test('Should replace properly space by underscore', async () => { TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); @@ -40,7 +42,8 @@ describe('Test is checking text replacement', () => { expect( await ComponentsScreen.checkTextInputComponentIsDisplayed(), ).toBeTruthy(); - // await ComponentsScreen.clickTextInputComponent(); + await ComponentsScreen.clickTextInputComponent(); + await TextInputComponentScreen.scrollToTextNoSpaceAllowElement(); expect(await TextInputComponentScreen.checkNoSpaceAllowed()).toEqual( 'foobarnospacetest', ); @@ -61,16 +64,18 @@ describe('Test is clearing text by Button', () => { }); }); -describe('Test double space to dot', () => { - test('Should replace to dot in a Controlled TextInput', async () => { - TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); - expect( - await ComponentsScreen.checkTextInputComponentIsDisplayed(), - ).toBeTruthy(); - await ComponentsScreen.clickTextInputComponent(); - await TextInputComponentScreen.scrollToDoubleSpaceElement(); - expect( - await TextInputComponentScreen.checkDoubleSpaceControlledTextInput(), - ).toEqual('testing. '); +if (isIOS) { + describe('Test double space to dot', () => { + test('Should replace to dot in a Controlled TextInput', async () => { + TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); + expect( + await ComponentsScreen.checkTextInputComponentIsDisplayed(), + ).toBeTruthy(); + await ComponentsScreen.clickTextInputComponent(); + await TextInputComponentScreen.scrollToDoubleSpaceElement(); + expect( + await TextInputComponentScreen.checkDoubleSpaceControlledTextInput(), + ).toEqual('testing. '); + }); }); -}); +} diff --git a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js index c02f3635f95df9..10f0f1ef304563 100644 --- a/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js +++ b/packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js @@ -275,7 +275,6 @@ class RewriteDoubleSpaceExample extends React.Component<$FlowFixMeProps, any> { { this.setState({text}); From f4f492f4f9c568e67d265e9719bcea4ccd900c14 Mon Sep 17 00:00:00 2001 From: Edu Date: Thu, 4 Jan 2024 11:27:56 +0100 Subject: [PATCH 5/9] Fixed typo --- .../rn-tester/js/examples/TextInput/TextInputSharedExamples.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rn-tester/js/examples/TextInput/TextInputSharedExamples.js b/packages/rn-tester/js/examples/TextInput/TextInputSharedExamples.js index 5c83a67c5e8e8e..d03a0b9a33a2cc 100644 --- a/packages/rn-tester/js/examples/TextInput/TextInputSharedExamples.js +++ b/packages/rn-tester/js/examples/TextInput/TextInputSharedExamples.js @@ -592,7 +592,7 @@ function UncontrolledExample() { return ( setIsFocused(true)} From 06f33ae09a3118817f45347828915d3920796d20 Mon Sep 17 00:00:00 2001 From: Edu Date: Thu, 4 Jan 2024 12:22:26 +0100 Subject: [PATCH 6/9] Fixed types for flow --- .../components/textInputComponent.screen.js | 21 +++++++++++-------- .../textInputComponentScreen.test.js | 10 ++++----- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js b/packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js index e9979a2da38059..1634e68b0da517 100644 --- a/packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js +++ b/packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js @@ -52,7 +52,9 @@ export const TextInputComponentScreen: TextInputComponentScreenType = { 'rewrite_sp_underscore_input', ), }), - textInputReWriteChildElement: function () { + textInputReWriteChildElement: function ( + this: TextInputComponentScreenType, + ): string { return Utils.platformSelect({ ios: iOSChildType( this.textInputReWriteElement, @@ -65,7 +67,9 @@ export const TextInputComponentScreen: TextInputComponentScreenType = { ios: iOSName('rewrite_no_sp_input'), android: androidWidget('EditText', 'resource-id', 'rewrite_no_sp_input'), }), - textInputNoSpaceAllowChildElement: function () { + textInputNoSpaceAllowChildElement: function ( + this: TextInputComponentScreenType, + ): string { return Utils.platformSelect({ ios: iOSChildType( this.textInputNoSpaceAllowElement, @@ -78,7 +82,9 @@ export const TextInputComponentScreen: TextInputComponentScreenType = { ios: iOSName('rewrite_clear_input'), android: androidWidget('EditText', 'resource-id', 'rewrite_clear_input'), }), - textInputReWriteClearChildElement: function () { + textInputReWriteClearChildElement: function ( + this: TextInputComponentScreenType, + ): string { return Utils.platformSelect({ ios: iOSChildType( this.textInputReWriteClearElement, @@ -95,7 +101,9 @@ export const TextInputComponentScreen: TextInputComponentScreenType = { ios: iOSName('rewrite_double_space'), android: androidWidget('Button', 'resource-id', 'rewrite_double_space'), }), - textInputControlledDoubleSpaceChildElement: function () { + textInputControlledDoubleSpaceChildElement: function ( + this: TextInputComponentScreenType, + ): string { return Utils.platformSelect({ ios: iOSChildType( this.textInputControlledDoubleSpaceElement, @@ -153,11 +161,6 @@ export const TextInputComponentScreen: TextInputComponentScreenType = { this.textInputControlledDoubleSpaceChildElement(), ); }, - clickSubmitApplication: async function ( - this: TextInputComponentScreenType, - ): Promise { - await Utils.clickElement(this.btnSubmitElement); - }, scrollUntilTextInputComponentIsDisplayed: async function ( this: TextInputComponentScreenType, ): Promise { diff --git a/packages/rn-tester-e2e/tests/specs/components/textInputComponentScreen.test.js b/packages/rn-tester-e2e/tests/specs/components/textInputComponentScreen.test.js index fa89c68885ca39..466bea96340569 100644 --- a/packages/rn-tester-e2e/tests/specs/components/textInputComponentScreen.test.js +++ b/packages/rn-tester-e2e/tests/specs/components/textInputComponentScreen.test.js @@ -17,7 +17,7 @@ const isIOS = (process?.env?.E2E_DEVICE || 'ios') === 'ios'; describe('Test is checking text replacement', () => { test('Should replace properly space by underscore', async () => { - TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); + await TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); expect( await ComponentsScreen.checkTextInputComponentIsDisplayed(), ).toBeTruthy(); @@ -27,7 +27,7 @@ describe('Test is checking text replacement', () => { ); }); test('Should replace properly space by underscore and limit character amount', async () => { - TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); + await TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); expect( await ComponentsScreen.checkTextInputComponentIsDisplayed(), ).toBeTruthy(); @@ -38,7 +38,7 @@ describe('Test is checking text replacement', () => { }); test('Should remove all spaces ', async () => { - TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); + await TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); expect( await ComponentsScreen.checkTextInputComponentIsDisplayed(), ).toBeTruthy(); @@ -52,7 +52,7 @@ describe('Test is checking text replacement', () => { describe('Test is clearing text by Button', () => { test('Should remove properly the text when clear button clicked', async () => { - TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); + await TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); expect( await ComponentsScreen.checkTextInputComponentIsDisplayed(), ).toBeTruthy(); @@ -67,7 +67,7 @@ describe('Test is clearing text by Button', () => { if (isIOS) { describe('Test double space to dot', () => { test('Should replace to dot in a Controlled TextInput', async () => { - TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); + await TextInputComponentScreen.scrollUntilTextInputComponentIsDisplayed(); expect( await ComponentsScreen.checkTextInputComponentIsDisplayed(), ).toBeTruthy(); From 280a3f41a15e5cc1b9d6c1b80d0ef65813e4afa3 Mon Sep 17 00:00:00 2001 From: Edu Date: Thu, 4 Jan 2024 13:06:44 +0100 Subject: [PATCH 7/9] sorted the imports --- .../tests/screens/components/textInputComponent.screen.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js b/packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js index 1634e68b0da517..f69ef3937fbe9b 100644 --- a/packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js +++ b/packages/rn-tester-e2e/tests/screens/components/textInputComponent.screen.js @@ -10,9 +10,9 @@ import { UtilsSingleton as Utils, - iOSName, - iOSChildType, androidWidget, + iOSChildType, + iOSName, } from '../../helpers/utils'; type TextInputComponentScreenType = { From 319a28f0a613a12626799dba6c8df50c31521d0e Mon Sep 17 00:00:00 2001 From: Edu Date: Wed, 9 Oct 2024 20:58:05 +0200 Subject: [PATCH 8/9] Add missing default attributes so the check works --- .../RCTAttributedTextUtils.mm | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm index 2b2cf02fa11604..bac33aee702169 100644 --- a/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm +++ b/packages/react-native/ReactCommon/react/renderer/textlayoutmanager/platform/ios/react/renderer/textlayoutmanager/RCTAttributedTextUtils.mm @@ -178,6 +178,22 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex return effectiveBackgroundColor ?: [UIColor clearColor]; } +NSDictionary *RCTDefaultTextAttributes() { + static NSDictionary *defaultAttributes; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + defaultAttributes = @{ + NSFontAttributeName : [UIFont systemFontOfSize:14.0f], + NSForegroundColorAttributeName : [UIColor blackColor], + NSBackgroundColorAttributeName : [UIColor clearColor], + NSParagraphStyleAttributeName  : [NSParagraphStyle defaultParagraphStyle], + NSShadowAttributeName : [NSShadow new], + // Add other missing attributes as needed + }; + }); + return defaultAttributes; +} + NSDictionary *RCTNSTextAttributesFromTextAttributes(const TextAttributes &textAttributes) { NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:10]; @@ -302,6 +318,14 @@ inline static CGFloat RCTEffectiveFontSizeMultiplierFromTextAttributes(const Tex attributes[RCTTextAttributesAccessibilityRoleAttributeName] = [NSString stringWithUTF8String:roleStr.c_str()]; } + // Apply default attributes if they're missing + NSDictionary *defaultAttributes = RCTDefaultTextAttributes(); + for (NSAttributedStringKey key in defaultAttributes) { + if (![attributes objectForKey:key]) { + attributes[key] = defaultAttributes[key]; + } + } + return [attributes copy]; } From f8ed5b7e1793b970e00bd2961ef25ca73fcd0742 Mon Sep 17 00:00:00 2001 From: Edu Date: Thu, 10 Oct 2024 12:56:25 +0200 Subject: [PATCH 9/9] Fixed TextInput Examples --- .../js/examples/TextInput/ExampleTextInput.js | 79 ++++--------------- .../TextInput/TextInputExample.ios.js | 28 ------- 2 files changed, 17 insertions(+), 90 deletions(-) diff --git a/packages/rn-tester/js/examples/TextInput/ExampleTextInput.js b/packages/rn-tester/js/examples/TextInput/ExampleTextInput.js index e7866beabfd856..635529801f7410 100644 --- a/packages/rn-tester/js/examples/TextInput/ExampleTextInput.js +++ b/packages/rn-tester/js/examples/TextInput/ExampleTextInput.js @@ -10,17 +10,8 @@ */ import {RNTesterThemeContext} from '../../components/RNTesterTheme'; -import React, {forwardRef, useContext, useState} from 'react'; -import { - StyleSheet, - View, - TextInput, - Button, - Text, - TouchableOpacity, - Clipboard, - Alert, -} from 'react-native'; +import React, {forwardRef, useContext} from 'react'; +import {StyleSheet, TextInput} from 'react-native'; const ExampleTextInput: React.AbstractComponent< React.ElementConfig, @@ -28,68 +19,32 @@ const ExampleTextInput: React.AbstractComponent< ...React.ElementRef, |}>, > = forwardRef((props, ref) => { - const [inputText, setInputText] = useState(''); - const [displayText, setDisplayText] = useState(''); - - const handleSend = () => { - setDisplayText(inputText); - setInputText(''); - }; - - const handleLongPress = () => { - Clipboard.setString(displayText); - Alert.alert('Copied to Clipboard', displayText); - }; + const theme = useContext(RNTesterThemeContext); return ( - - -