Skip to content

Commit b9f4011

Browse files
Create text field testkit (#1794)
* add testIds for charCounter and floating placehoolder * add the driver and tests * export and run prepush Co-authored-by: Ethan Sharabi <[email protected]>
1 parent b9c4c55 commit b9f4011

File tree

11 files changed

+293
-10
lines changed

11 files changed

+293
-10
lines changed

generatedTypes/src/incubator/TextField/CharCounter.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ export interface CharCounterProps {
1010
* Pass custom style to character counter text
1111
*/
1212
charCounterStyle?: TextStyle;
13+
testID: string;
1314
}
1415
declare const CharCounter: {
15-
({ maxLength, charCounterStyle }: CharCounterProps): JSX.Element | null;
16+
({ maxLength, charCounterStyle, testID }: CharCounterProps): JSX.Element | null;
1617
displayName: string;
1718
};
1819
export default CharCounter;

generatedTypes/src/incubator/TextField/FloatingPlaceholder.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ export interface FloatingPlaceholderProps {
2020
floatOnFocus?: boolean;
2121
validationMessagePosition?: ValidationMessagePosition;
2222
extraOffset?: number;
23+
testID: string;
2324
}
2425
declare const FloatingPlaceholder: {
25-
({ placeholder, floatingPlaceholderColor, floatingPlaceholderStyle, floatOnFocus, validationMessagePosition, extraOffset }: FloatingPlaceholderProps): JSX.Element;
26+
({ placeholder, floatingPlaceholderColor, floatingPlaceholderStyle, floatOnFocus, validationMessagePosition, extraOffset, testID }: FloatingPlaceholderProps): JSX.Element;
2627
displayName: string;
2728
};
2829
export default FloatingPlaceholder;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { RenderAPI } from '@testing-library/react-native';
2+
import { ReactTestInstance } from 'react-test-renderer';
3+
declare const TextFieldDriverFactory: ({ wrapperComponent, testID }: {
4+
wrapperComponent: RenderAPI;
5+
testID: string;
6+
}) => Promise<{
7+
exists: () => boolean;
8+
getRootElement: () => ReactTestInstance | null;
9+
content: () => any;
10+
isDisabled: () => any;
11+
changeText: (text: string) => void;
12+
isPlaceholderVisible: () => boolean | undefined;
13+
getPlaceholderContent: () => any;
14+
getLabelRootElement: () => ReactTestInstance | null;
15+
isLabelExists: () => boolean;
16+
getLabelContent: () => any;
17+
getValidationMsgRootElement: () => ReactTestInstance | null;
18+
isValidationMsgExists: () => boolean;
19+
getValidationMsgContent: () => any;
20+
getCharCounterRootElement: () => ReactTestInstance | null;
21+
isCharCounterExists: () => boolean;
22+
getCharCounterContent: () => any;
23+
}>;
24+
export default TextFieldDriverFactory;

generatedTypes/src/incubator/TextField/usePreset.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export default function usePreset({ preset, ...props }: InternalTextFieldProps):
2222
formatter?: ((value?: string | undefined) => string | undefined) | undefined;
2323
children?: import("react").ReactNode;
2424
style?: import("react-native").StyleProp<import("react-native").TextStyle>;
25-
testID?: string | undefined;
25+
testID: string;
2626
removeClippedSubviews?: boolean | undefined;
2727
onLayout?: ((event: import("react-native").LayoutChangeEvent) => void) | undefined;
2828
onContentSizeChange?: ((e: import("react-native").NativeSyntheticEvent<import("react-native").TextInputContentSizeChangeEventData>) => void) | undefined;
@@ -423,7 +423,7 @@ export default function usePreset({ preset, ...props }: InternalTextFieldProps):
423423
formatter?: ((value?: string | undefined) => string | undefined) | undefined;
424424
children?: import("react").ReactNode;
425425
style?: import("react-native").StyleProp<import("react-native").TextStyle>;
426-
testID?: string | undefined;
426+
testID: string;
427427
removeClippedSubviews?: boolean | undefined;
428428
onLayout?: ((event: import("react-native").LayoutChangeEvent) => void) | undefined;
429429
onContentSizeChange?: ((e: import("react-native").NativeSyntheticEvent<import("react-native").TextInputContentSizeChangeEventData>) => void) | undefined;
@@ -936,7 +936,7 @@ export default function usePreset({ preset, ...props }: InternalTextFieldProps):
936936
textAlignVertical?: "auto" | "center" | "top" | "bottom" | undefined;
937937
includeFontPadding?: boolean | undefined;
938938
} | null;
939-
testID?: string | undefined;
939+
testID: string;
940940
removeClippedSubviews?: boolean | undefined;
941941
onLayout?: ((event: import("react-native").LayoutChangeEvent) => void) | undefined;
942942
onContentSizeChange?: ((e: import("react-native").NativeSyntheticEvent<import("react-native").TextInputContentSizeChangeEventData>) => void) | undefined;

src/incubator/TextField/CharCounter.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@ export interface CharCounterProps {
1414
* Pass custom style to character counter text
1515
*/
1616
charCounterStyle?: TextStyle;
17+
testID: string;
1718
}
1819

19-
const CharCounter = ({maxLength, charCounterStyle}: CharCounterProps) => {
20+
const CharCounter = ({maxLength, charCounterStyle, testID}: CharCounterProps) => {
2021
const {value} = useContext(FieldContext);
21-
2222
if (_.isUndefined(maxLength)) {
2323
return null;
2424
}
2525

2626
return (
27-
<Text grey30 style={[styles.container, charCounterStyle]}>
27+
<Text grey30 style={[styles.container, charCounterStyle]} testID={testID}>
2828
{`${_.size(value)}/${maxLength}`}
2929
</Text>
3030
);

src/incubator/TextField/FloatingPlaceholder.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface FloatingPlaceholderProps {
2727
floatOnFocus?: boolean;
2828
validationMessagePosition?: ValidationMessagePosition;
2929
extraOffset?: number;
30+
testID: string;
3031
}
3132

3233
const FLOATING_PLACEHOLDER_SCALE = 0.875;
@@ -37,7 +38,8 @@ const FloatingPlaceholder = ({
3738
floatingPlaceholderStyle,
3839
floatOnFocus,
3940
validationMessagePosition,
40-
extraOffset = 0
41+
extraOffset = 0,
42+
testID
4143
}: FloatingPlaceholderProps) => {
4244
const context = useContext(FieldContext);
4345
const [placeholderOffset, setPlaceholderOffset] = useState({
@@ -92,6 +94,7 @@ const FloatingPlaceholder = ({
9294
color={getColorByState(floatingPlaceholderColor, context)}
9395
style={[styles.placeholder, floatingPlaceholderStyle, animatedStyle]}
9496
onLayout={onPlaceholderLayout}
97+
testID={testID}
9598
>
9699
{placeholder}
97100
</Text>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {fireEvent, RenderAPI} from '@testing-library/react-native';
2+
import {ReactTestInstance} from 'react-test-renderer';
3+
import TextDriver from '../../components/text/Text.driver';
4+
import _ from 'lodash';
5+
6+
const TextFieldDriverFactory = async ({wrapperComponent, testID}: {wrapperComponent: RenderAPI, testID: string}) => {
7+
const textInput: ReactTestInstance | null = await wrapperComponent.queryByTestId(testID);
8+
const label = await TextDriver({wrapperComponent, testID: `${testID}.label`});
9+
const validationMsg = await TextDriver({wrapperComponent, testID: `${testID}.validationMessage`});
10+
const floatingPlaceholder = await TextDriver({wrapperComponent, testID: `${testID}.floatingPlaceholder`});
11+
const charCounter = await TextDriver({wrapperComponent, testID: `${testID}.charCounter`});
12+
function isPlaceholderVisible() {
13+
if (textInput) {
14+
const hasPlaceholderProp = !!_.get(textInput, 'props.placeholder');
15+
const hasInputText = !!_.get(textInput, 'props.value');
16+
return hasPlaceholderProp && (!hasInputText || (hasInputText && floatingPlaceholder.exists()));
17+
} else {
18+
console.warn(`TextField component with testId:${testID}, is not found. So you can't get his placeholder`);
19+
}
20+
}
21+
return {
22+
exists: () => !!textInput,
23+
getRootElement: () => textInput,
24+
content: () => {
25+
if (textInput) {
26+
return textInput.props.value;
27+
} else {
28+
console.warn(`TextField component with testId:${testID}, is not found. So you can't get the content`);
29+
return null;
30+
}
31+
},
32+
isDisabled: () => {
33+
if (textInput) {
34+
return _.get(textInput, 'props.accessibilityState.disabled');
35+
} else {
36+
console.warn(`TextField component with testId:${testID}, is not found. So you can't get the content`);
37+
return null;
38+
}
39+
},
40+
changeText: (text: string) => {
41+
if (textInput) {
42+
fireEvent.changeText(textInput, text);
43+
} else {
44+
console.warn(`TextFieldDriverFactory: cannot change text because testID:${testID} were not found`);
45+
}
46+
},
47+
// placeholder
48+
isPlaceholderVisible,
49+
getPlaceholderContent: () => {
50+
if (isPlaceholderVisible()) {
51+
return _.get(textInput, 'props.placeholder');
52+
} else {
53+
console.warn(`You cant get placeholder content, cause placeholder is not visible.`);
54+
return null;
55+
}
56+
},
57+
// label
58+
getLabelRootElement: () => label.getRootElement(),
59+
isLabelExists: () => label.exists() && !floatingPlaceholder.exists(),
60+
getLabelContent: () => label.getTextContent(),
61+
// validation message
62+
getValidationMsgRootElement: () => validationMsg.getRootElement(),
63+
isValidationMsgExists: () => validationMsg.exists() && !_.isEmpty(validationMsg.getTextContent()),
64+
getValidationMsgContent: () => validationMsg.getTextContent(),
65+
//leadingAccessory, trailingAccessory, bottomAccessory
66+
// char counter
67+
getCharCounterRootElement: () => charCounter.getRootElement(),
68+
isCharCounterExists: () => charCounter.exists(),
69+
getCharCounterContent: () => charCounter.getTextContent()
70+
71+
};
72+
};
73+
74+
export default TextFieldDriverFactory;
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import React, {useState} from 'react';
2+
import TextFieldTestKit from '../TextField.driver';
3+
import {render, waitFor} from '@testing-library/react-native';
4+
import TextField from '../index';
5+
import View from '../../../components/view';
6+
7+
const TEXT_FIELD_TEST_ID = 'text_field_test_id';
8+
9+
function renderWrapperScreenWithTextField(textFieldProps) {
10+
return render(<ScreenWithTextField {...textFieldProps}/>);
11+
}
12+
13+
function ScreenWithTextField(textFieldProps) {
14+
const [value, setValue] = useState(textFieldProps.value);
15+
return (<View>
16+
<TextField {...textFieldProps} testID={TEXT_FIELD_TEST_ID} value={value} onChangeText={setValue}/>
17+
</View>);
18+
}
19+
20+
21+
describe('TextField', () => {
22+
it('should render textField', async () => {
23+
const wrapperComponent = renderWrapperScreenWithTextField({});
24+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
25+
expect(textFieldDriver.exists()).toBe(true);
26+
});
27+
28+
it('should render textField with correct content', async () => {
29+
const wrapperComponent = renderWrapperScreenWithTextField({value: 'aa'});
30+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
31+
expect(textFieldDriver.content()).toEqual('aa');
32+
});
33+
34+
it('should change the text correctly', async () => {
35+
const wrapperComponent = renderWrapperScreenWithTextField({value: 'aa'});
36+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
37+
expect(textFieldDriver.content()).toEqual('aa');
38+
textFieldDriver.changeText('bb');
39+
await waitFor(() => expect(textFieldDriver.content()).toEqual('bb'));
40+
});
41+
42+
describe('editable', () => {
43+
it('should be editable', async () => {
44+
const wrapperComponent = renderWrapperScreenWithTextField({});
45+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
46+
expect(textFieldDriver.isDisabled()).toBe(false);
47+
});
48+
49+
it('should render textField that is not editable', async () => {
50+
const wrapperComponent = renderWrapperScreenWithTextField({editable: false});
51+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
52+
expect(textFieldDriver.isDisabled()).toBe(true);
53+
});
54+
});
55+
56+
describe('placeholder', () => {
57+
it('should render placeholder with correct text', async () => {
58+
const wrapperComponent = renderWrapperScreenWithTextField({placeholder: 'mock placeholder'});
59+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
60+
expect(textFieldDriver.isPlaceholderVisible()).toBe(true);
61+
expect(textFieldDriver.getPlaceholderContent()).toEqual('mock placeholder');
62+
});
63+
64+
it('should not render placeholder', async () => {
65+
const wrapperComponent = renderWrapperScreenWithTextField({});
66+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
67+
expect(textFieldDriver.isPlaceholderVisible()).toBe(false);
68+
});
69+
70+
it('should not render placeholder after user changing the input text(no floating prop)', async () => {
71+
const wrapperComponent = renderWrapperScreenWithTextField({placeholder: 'mock placeholder'});
72+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
73+
expect(textFieldDriver.isPlaceholderVisible()).toBe(true);
74+
textFieldDriver.changeText('mock input value');
75+
await waitFor(() => expect(textFieldDriver.isPlaceholderVisible()).toBe(false));
76+
});
77+
78+
it('should render placeholder(floating) after user changing text if floatingPlaceholder prop sent', async () => {
79+
const wrapperComponent = renderWrapperScreenWithTextField({placeholder: 'mock placeholder', floatingPlaceholder: true});
80+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
81+
expect(textFieldDriver.isPlaceholderVisible()).toBe(true);
82+
textFieldDriver.changeText('mock input value');
83+
await waitFor(() => expect(textFieldDriver.isPlaceholderVisible()).toBe(true));
84+
await waitFor(() => expect(textFieldDriver.getPlaceholderContent()).toEqual('mock placeholder'));
85+
86+
});
87+
});
88+
89+
describe('Label', () => {
90+
it('should not render label if prop is not passed', async () => {
91+
const wrapperComponent = renderWrapperScreenWithTextField({});
92+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
93+
expect(textFieldDriver.isLabelExists()).toBe(false);
94+
});
95+
96+
it('should render a label', async () => {
97+
const wrapperComponent = renderWrapperScreenWithTextField({label: 'mock label'});
98+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
99+
expect(textFieldDriver.isLabelExists()).toBe(true);
100+
expect(textFieldDriver.getLabelContent()).toEqual('mock label');
101+
});
102+
103+
it('should not render label if floatingPlaceholder prop is passed', async () => {
104+
const wrapperComponent = renderWrapperScreenWithTextField({label: 'mock label', floatingPlaceholder: true});
105+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
106+
expect(textFieldDriver.isLabelExists()).toBe(false);
107+
});
108+
});
109+
110+
describe('validation message', () => {
111+
it('should not render validationMessage if enableErrors prop not supplied', async () => {
112+
const wrapperComponent = renderWrapperScreenWithTextField({value: '', validate: 'required', validationMessage: 'mock message', validateOnStart: true});
113+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
114+
expect(textFieldDriver.isValidationMsgExists()).toBe(false);
115+
});
116+
117+
it('should render validationMessage on start if input required and validateOnStart passed', async () => {
118+
const wrapperComponent = renderWrapperScreenWithTextField({value: '', validate: 'required', validationMessage: 'mock message', enableErrors: true, validateOnStart: true});
119+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
120+
expect(textFieldDriver.isValidationMsgExists()).toBe(true);
121+
expect(textFieldDriver.getValidationMsgContent()).toEqual('mock message');
122+
});
123+
124+
it('should render validationMessage when input is requires after changing the input to empty string', async () => {
125+
const wrapperComponent = renderWrapperScreenWithTextField({value: 'mock value', validate: 'required', validationMessage: 'mock message', enableErrors: true, validateOnChange: true});
126+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
127+
expect(textFieldDriver.isValidationMsgExists()).toBe(false);
128+
expect(textFieldDriver.getValidationMsgContent()).toEqual('');
129+
textFieldDriver.changeText('');
130+
await waitFor(() => expect(textFieldDriver.isValidationMsgExists()).toBe(true));
131+
expect(textFieldDriver.getValidationMsgContent()).toEqual('mock message');
132+
});
133+
});
134+
135+
describe('char counter', () => {
136+
it('should render char counter.', async () => {
137+
const wrapperComponent = renderWrapperScreenWithTextField({showCharCounter: true, maxLength: 10});
138+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
139+
expect(textFieldDriver.isCharCounterExists()).toBe(true);
140+
});
141+
142+
it('should not render counter if maxLength prop not supplied', async () => {
143+
const wrapperComponent = renderWrapperScreenWithTextField({showCharCounter: true});
144+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
145+
expect(textFieldDriver.isCharCounterExists()).toBe(false);
146+
});
147+
148+
it('should not render counter if showCharCounter prop not supplied', async () => {
149+
const wrapperComponent = renderWrapperScreenWithTextField({maxLength: 10});
150+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
151+
expect(textFieldDriver.isCharCounterExists()).toBe(false);
152+
});
153+
154+
it('should render char counter, with "0/10" if value not supplied', async () => {
155+
const wrapperComponent = renderWrapperScreenWithTextField({showCharCounter: true, maxLength: 10});
156+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
157+
expect(textFieldDriver.getCharCounterContent()).toEqual('0/10');
158+
});
159+
160+
it('should render char counter with correct content supplied', async () => {
161+
const wrapperComponent = renderWrapperScreenWithTextField({value: 'abc', showCharCounter: true, maxLength: 10});
162+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
163+
expect(textFieldDriver.getCharCounterContent()).toEqual('3/10');
164+
});
165+
166+
it('should update char counter after changing the text', async () => {
167+
const wrapperComponent = renderWrapperScreenWithTextField({value: 'ab', showCharCounter: true, maxLength: 10});
168+
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
169+
expect(textFieldDriver.getCharCounterContent()).toEqual('2/10');
170+
textFieldDriver.changeText('abcd');
171+
await waitFor(() => expect(textFieldDriver.getCharCounterContent()).toEqual('4/10'));
172+
});
173+
});
174+
});

src/incubator/TextField/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ const TextField = (props: InternalTextFieldProps) => {
212212
floatOnFocus={floatOnFocus}
213213
validationMessagePosition={validationMessagePosition}
214214
extraOffset={leadingAccessoryMeasurements?.width}
215+
testID={`${props.testID}.floatingPlaceholder`}
215216
/>
216217
)}
217218
{children || (
@@ -242,7 +243,7 @@ const TextField = (props: InternalTextFieldProps) => {
242243
/>
243244
)}
244245
{bottomAccessory}
245-
{showCharCounter && <CharCounter maxLength={others.maxLength} charCounterStyle={charCounterStyle}/>}
246+
{showCharCounter && <CharCounter maxLength={others.maxLength} charCounterStyle={charCounterStyle} testID={`${props.testID}.charCounter`}/>}
246247
</View>
247248
</View>
248249
</FieldContext.Provider>

0 commit comments

Comments
 (0)