Skip to content

Add testID prop for E2E testing support with prefixed internal component IDs #43

@cyril36

Description

@cyril36

Hi! 👋

Firstly, thanks for your work on this project! 🙂

Today I used patch-package to patch react-native-google-places-textinput@0.9.1 for the project I'm working on.

To be able to create proper testing flow with maestro I needed to add testID props to the suggestion dropdown.
I have updated the code to add testID to GooglePlacesTextInputProps and propagate it to each component of GooglePlacesTextInput with the component name + testID value

if testID==location
container-location - Main container View
input-wrapper-location - Input wrapper View
textinput-location - TextInput
clear-button-location - Clear button TouchableOpacity
loading-indicator-location - ActivityIndicator
suggestions-container-location - Suggestions container View
suggestions-list-location - FlatList
suggestion-touchableopacity-0-location - Suggestion item TouchableOpacity (index 0)
suggestion-text-0-location - Suggestion main text (index 0)
suggestion-secondarytext-0-location - Suggestion secondary text (index 0)

Here is the diff that solved my problem:

diff --git a/node_modules/react-native-google-places-textinput/lib/module/GooglePlacesTextInput.js b/node_modules/react-native-google-places-textinput/lib/module/GooglePlacesTextInput.js
index b12e7bf..ee8e06c 100644
--- a/node_modules/react-native-google-places-textinput/lib/module/GooglePlacesTextInput.js
+++ b/node_modules/react-native-google-places-textinput/lib/module/GooglePlacesTextInput.js
@@ -42,6 +42,7 @@ const GooglePlacesTextInput = /*#__PURE__*/forwardRef(({
   onBlur,
   accessibilityLabels = {},
   suggestionTextProps = {},
+  testID,
   ...restTextInputProps
 }, ref) => {
   const [predictions, setPredictions] = useState([]);
@@ -288,6 +289,8 @@ const GooglePlacesTextInput = /*#__PURE__*/forwardRef(({
       }
     }, 10);
   };
+  // Test ID suffix for all components
+  const testIDSuffix = testID ? `-${testID}` : '';
   const renderSuggestion = ({
     item,
     index
@@ -303,6 +306,7 @@ const GooglePlacesTextInput = /*#__PURE__*/forwardRef(({
     const defaultAccessibilityLabel = `${mainText.text}${secondaryText ? `, ${secondaryText.text}` : ''}`;
     const accessibilityLabel = accessibilityLabels.suggestionItem?.(item.placePrediction) || defaultAccessibilityLabel;
     return /*#__PURE__*/_jsxs(TouchableOpacity, {
+      testID: `suggestion-touchableopacity-${index}${testIDSuffix}`,
       accessibilityRole: "button",
       accessibilityLabel: accessibilityLabel,
       accessibilityHint: "Double tap to select this place",
@@ -321,11 +325,13 @@ const GooglePlacesTextInput = /*#__PURE__*/forwardRef(({
         }
       }),
       children: [/*#__PURE__*/_jsx(Text, {
+        testID: `suggestion-text-${index}${testIDSuffix}`,
         style: [styles.mainText, style.suggestionText?.main, getTextAlign()],
         numberOfLines: suggestionTextProps.mainTextNumberOfLines,
         ellipsizeMode: suggestionTextProps.ellipsizeMode || 'tail',
         children: mainText.text
       }), secondaryText && /*#__PURE__*/_jsx(Text, {
+        testID: `suggestion-secondarytext-${index}${testIDSuffix}`,
         style: [styles.secondaryText, style.suggestionText?.secondary, getTextAlign()],
         numberOfLines: suggestionTextProps.secondaryTextNumberOfLines,
         ellipsizeMode: suggestionTextProps.ellipsizeMode || 'tail',
@@ -388,10 +394,13 @@ const GooglePlacesTextInput = /*#__PURE__*/forwardRef(({
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, []);
   return /*#__PURE__*/_jsxs(View, {
+    testID: `container${testIDSuffix}`,
     style: [styles.container, style.container],
     children: [/*#__PURE__*/_jsxs(View, {
+      testID: `input-wrapper${testIDSuffix}`,
       children: [/*#__PURE__*/_jsx(TextInput, {
         ...restTextInputProps,
+        testID: `textinput${testIDSuffix}`,
         ref: inputRef,
         style: [styles.input, style.input, getPadding(), getTextAlign()],
         placeholder: placeHolderText,
@@ -405,6 +414,7 @@ const GooglePlacesTextInput = /*#__PURE__*/forwardRef(({
         accessibilityRole: "search",
         accessibilityLabel: accessibilityLabels.input || placeHolderText
       }), showClearButton && inputText !== '' && /*#__PURE__*/_jsx(TouchableOpacity, {
+        testID: `clear-button${testIDSuffix}`,
         style: [styles.clearButton, getIconPosition(12)],
         onPress: () => {
           if (debounceTimeout.current) {
@@ -433,6 +443,7 @@ const GooglePlacesTextInput = /*#__PURE__*/forwardRef(({
           })
         })
       }), (loading || detailsLoading) && showLoadingIndicator && /*#__PURE__*/_jsx(ActivityIndicator, {
+        testID: `loading-indicator${testIDSuffix}`,
         style: [styles.loadingIndicator, getIconPosition(45)],
         size: 'small',
         color: style.loadingIndicator?.color || '#000000',
@@ -440,8 +451,10 @@ const GooglePlacesTextInput = /*#__PURE__*/forwardRef(({
         accessibilityLabel: accessibilityLabels.loadingIndicator || 'Loading suggestions'
       })]
     }), showSuggestions && predictions.length > 0 && /*#__PURE__*/_jsx(View, {
+      testID: `suggestions-container${testIDSuffix}`,
       style: [styles.suggestionsContainer, style.suggestionsContainer],
       children: /*#__PURE__*/_jsx(FlatList, {
+        testID: `suggestions-list${testIDSuffix}`,
         data: predictions,
         renderItem: renderSuggestion,
         keyExtractor: item => item.placePrediction.placeId,
diff --git a/node_modules/react-native-google-places-textinput/src/GooglePlacesTextInput.tsx b/node_modules/react-native-google-places-textinput/src/GooglePlacesTextInput.tsx
index 8c7fd26..c8134fa 100644
--- a/node_modules/react-native-google-places-textinput/src/GooglePlacesTextInput.tsx
+++ b/node_modules/react-native-google-places-textinput/src/GooglePlacesTextInput.tsx
@@ -152,6 +152,11 @@ interface GooglePlacesTextInputProps
   enableDebug?: boolean;
   accessibilityLabels?: GooglePlacesAccessibilityLabels;
   suggestionTextProps?: SuggestionTextProps;
+  /**
+   * Test ID prefix for all internal components (for E2E testing)
+   * Components will be prefixed: textinput-{testID}, suggestion-touchableopacity-{index}-{testID}, etc.
+   */
+  testID?: string;
 }
 
 interface GooglePlacesTextInputRef {
@@ -204,6 +209,7 @@ const GooglePlacesTextInput = forwardRef<
       onBlur,
       accessibilityLabels = {},
       suggestionTextProps = {},
+      testID,
       ...restTextInputProps
     },
     ref
@@ -508,6 +514,9 @@ const GooglePlacesTextInput = forwardRef<
       }, 10);
     };
 
+    // Test ID suffix for all components
+    const testIDSuffix = testID ? `-${testID}` : '';
+
     const renderSuggestion = ({
       item,
       index,
@@ -533,6 +542,7 @@ const GooglePlacesTextInput = forwardRef<
 
       return (
         <TouchableOpacity
+          testID={`suggestion-touchableopacity-${index}${testIDSuffix}`}
           accessibilityRole="button"
           accessibilityLabel={accessibilityLabel}
           accessibilityHint="Double tap to select this place"
@@ -555,6 +565,7 @@ const GooglePlacesTextInput = forwardRef<
             } as any))}
         >
           <Text
+            testID={`suggestion-text-${index}${testIDSuffix}`}
             style={[
               styles.mainText,
               style.suggestionText?.main,
@@ -567,6 +578,7 @@ const GooglePlacesTextInput = forwardRef<
           </Text>
           {secondaryText && (
             <Text
+              testID={`suggestion-secondarytext-${index}${testIDSuffix}`}
               style={[
                 styles.secondaryText,
                 style.suggestionText?.secondary,
@@ -631,10 +643,11 @@ const GooglePlacesTextInput = forwardRef<
     }, []);
 
     return (
-      <View style={[styles.container, style.container]}>
-        <View>
+      <View testID={`container${testIDSuffix}`} style={[styles.container, style.container]}>
+        <View testID={`input-wrapper${testIDSuffix}`}>
           <TextInput
             {...restTextInputProps}
+            testID={`textinput${testIDSuffix}`}
             ref={inputRef}
             style={[styles.input, style.input, getPadding(), getTextAlign()]}
             placeholder={placeHolderText}
@@ -651,6 +664,7 @@ const GooglePlacesTextInput = forwardRef<
           {/* Clear button - shown only if showClearButton is true */}
           {showClearButton && inputText !== '' && (
             <TouchableOpacity
+              testID={`clear-button${testIDSuffix}`}
               style={[styles.clearButton, getIconPosition(12)]}
               onPress={() => {
                 if (debounceTimeout.current) {
@@ -692,6 +706,7 @@ const GooglePlacesTextInput = forwardRef<
           {/* Loading indicator */}
           {(loading || detailsLoading) && showLoadingIndicator && (
             <ActivityIndicator
+              testID={`loading-indicator${testIDSuffix}`}
               style={[styles.loadingIndicator, getIconPosition(45)]}
               size={'small'}
               color={style.loadingIndicator?.color || '#000000'}
@@ -706,9 +721,11 @@ const GooglePlacesTextInput = forwardRef<
         {/* Suggestions */}
         {showSuggestions && predictions.length > 0 && (
           <View
+            testID={`suggestions-container${testIDSuffix}`}
             style={[styles.suggestionsContainer, style.suggestionsContainer]}
           >
             <FlatList
+              testID={`suggestions-list${testIDSuffix}`}
               data={predictions}
               renderItem={renderSuggestion}
               keyExtractor={(item) => item.placePrediction.placeId}

This issue body was partially generated by patch-package.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions