Skip to content

Create text field testkit #1794

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion generatedTypes/src/incubator/TextField/CharCounter.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ export interface CharCounterProps {
* Pass custom style to character counter text
*/
charCounterStyle?: TextStyle;
testID: string;
}
declare const CharCounter: {
({ maxLength, charCounterStyle }: CharCounterProps): JSX.Element | null;
({ maxLength, charCounterStyle, testID }: CharCounterProps): JSX.Element | null;
displayName: string;
};
export default CharCounter;
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ export interface FloatingPlaceholderProps {
floatOnFocus?: boolean;
validationMessagePosition?: ValidationMessagePosition;
extraOffset?: number;
testID: string;
}
declare const FloatingPlaceholder: {
({ placeholder, floatingPlaceholderColor, floatingPlaceholderStyle, floatOnFocus, validationMessagePosition, extraOffset }: FloatingPlaceholderProps): JSX.Element;
({ placeholder, floatingPlaceholderColor, floatingPlaceholderStyle, floatOnFocus, validationMessagePosition, extraOffset, testID }: FloatingPlaceholderProps): JSX.Element;
displayName: string;
};
export default FloatingPlaceholder;
24 changes: 24 additions & 0 deletions generatedTypes/src/incubator/TextField/TextField.driver.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { RenderAPI } from '@testing-library/react-native';
import { ReactTestInstance } from 'react-test-renderer';
declare const TextFieldDriverFactory: ({ wrapperComponent, testID }: {
wrapperComponent: RenderAPI;
testID: string;
}) => Promise<{
exists: () => boolean;
getRootElement: () => ReactTestInstance | null;
content: () => any;
isDisabled: () => any;
changeText: (text: string) => void;
isPlaceholderVisible: () => boolean | undefined;
getPlaceholderContent: () => any;
getLabelRootElement: () => ReactTestInstance | null;
isLabelExists: () => boolean;
getLabelContent: () => any;
getValidationMsgRootElement: () => ReactTestInstance | null;
isValidationMsgExists: () => boolean;
getValidationMsgContent: () => any;
getCharCounterRootElement: () => ReactTestInstance | null;
isCharCounterExists: () => boolean;
getCharCounterContent: () => any;
}>;
export default TextFieldDriverFactory;
6 changes: 3 additions & 3 deletions generatedTypes/src/incubator/TextField/usePreset.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function usePreset({ preset, ...props }: InternalTextFieldProps):
formatter?: ((value?: string | undefined) => string | undefined) | undefined;
children?: import("react").ReactNode;
style?: import("react-native").StyleProp<import("react-native").TextStyle>;
testID?: string | undefined;
testID: string;
removeClippedSubviews?: boolean | undefined;
onLayout?: ((event: import("react-native").LayoutChangeEvent) => void) | undefined;
onContentSizeChange?: ((e: import("react-native").NativeSyntheticEvent<import("react-native").TextInputContentSizeChangeEventData>) => void) | undefined;
Expand Down Expand Up @@ -423,7 +423,7 @@ export default function usePreset({ preset, ...props }: InternalTextFieldProps):
formatter?: ((value?: string | undefined) => string | undefined) | undefined;
children?: import("react").ReactNode;
style?: import("react-native").StyleProp<import("react-native").TextStyle>;
testID?: string | undefined;
testID: string;
removeClippedSubviews?: boolean | undefined;
onLayout?: ((event: import("react-native").LayoutChangeEvent) => void) | undefined;
onContentSizeChange?: ((e: import("react-native").NativeSyntheticEvent<import("react-native").TextInputContentSizeChangeEventData>) => void) | undefined;
Expand Down Expand Up @@ -936,7 +936,7 @@ export default function usePreset({ preset, ...props }: InternalTextFieldProps):
textAlignVertical?: "auto" | "center" | "top" | "bottom" | undefined;
includeFontPadding?: boolean | undefined;
} | null;
testID?: string | undefined;
testID: string;
removeClippedSubviews?: boolean | undefined;
onLayout?: ((event: import("react-native").LayoutChangeEvent) => void) | undefined;
onContentSizeChange?: ((e: import("react-native").NativeSyntheticEvent<import("react-native").TextInputContentSizeChangeEventData>) => void) | undefined;
Expand Down
6 changes: 3 additions & 3 deletions src/incubator/TextField/CharCounter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ export interface CharCounterProps {
* Pass custom style to character counter text
*/
charCounterStyle?: TextStyle;
testID: string;
}

const CharCounter = ({maxLength, charCounterStyle}: CharCounterProps) => {
const CharCounter = ({maxLength, charCounterStyle, testID}: CharCounterProps) => {
const {value} = useContext(FieldContext);

if (_.isUndefined(maxLength)) {
return null;
}

return (
<Text grey30 style={[styles.container, charCounterStyle]}>
<Text grey30 style={[styles.container, charCounterStyle]} testID={testID}>
{`${_.size(value)}/${maxLength}`}
</Text>
);
Expand Down
5 changes: 4 additions & 1 deletion src/incubator/TextField/FloatingPlaceholder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface FloatingPlaceholderProps {
floatOnFocus?: boolean;
validationMessagePosition?: ValidationMessagePosition;
extraOffset?: number;
testID: string;
}

const FLOATING_PLACEHOLDER_SCALE = 0.875;
Expand All @@ -37,7 +38,8 @@ const FloatingPlaceholder = ({
floatingPlaceholderStyle,
floatOnFocus,
validationMessagePosition,
extraOffset = 0
extraOffset = 0,
testID
}: FloatingPlaceholderProps) => {
const context = useContext(FieldContext);
const [placeholderOffset, setPlaceholderOffset] = useState({
Expand Down Expand Up @@ -92,6 +94,7 @@ const FloatingPlaceholder = ({
color={getColorByState(floatingPlaceholderColor, context)}
style={[styles.placeholder, floatingPlaceholderStyle, animatedStyle]}
onLayout={onPlaceholderLayout}
testID={testID}
>
{placeholder}
</Text>
Expand Down
74 changes: 74 additions & 0 deletions src/incubator/TextField/TextField.driver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {fireEvent, RenderAPI} from '@testing-library/react-native';
import {ReactTestInstance} from 'react-test-renderer';
import TextDriver from '../../components/text/Text.driver';
import _ from 'lodash';

const TextFieldDriverFactory = async ({wrapperComponent, testID}: {wrapperComponent: RenderAPI, testID: string}) => {
const textInput: ReactTestInstance | null = await wrapperComponent.queryByTestId(testID);
const label = await TextDriver({wrapperComponent, testID: `${testID}.label`});
const validationMsg = await TextDriver({wrapperComponent, testID: `${testID}.validationMessage`});
const floatingPlaceholder = await TextDriver({wrapperComponent, testID: `${testID}.floatingPlaceholder`});
const charCounter = await TextDriver({wrapperComponent, testID: `${testID}.charCounter`});
function isPlaceholderVisible() {
if (textInput) {
const hasPlaceholderProp = !!_.get(textInput, 'props.placeholder');
const hasInputText = !!_.get(textInput, 'props.value');
return hasPlaceholderProp && (!hasInputText || (hasInputText && floatingPlaceholder.exists()));
} else {
console.warn(`TextField component with testId:${testID}, is not found. So you can't get his placeholder`);
}
}
return {
exists: () => !!textInput,
getRootElement: () => textInput,
content: () => {
if (textInput) {
return textInput.props.value;
} else {
console.warn(`TextField component with testId:${testID}, is not found. So you can't get the content`);
return null;
}
},
isDisabled: () => {
if (textInput) {
return _.get(textInput, 'props.accessibilityState.disabled');
} else {
console.warn(`TextField component with testId:${testID}, is not found. So you can't get the content`);
return null;
}
},
changeText: (text: string) => {
if (textInput) {
fireEvent.changeText(textInput, text);
} else {
console.warn(`TextFieldDriverFactory: cannot change text because testID:${testID} were not found`);
}
},
// placeholder
isPlaceholderVisible,
getPlaceholderContent: () => {
if (isPlaceholderVisible()) {
return _.get(textInput, 'props.placeholder');
} else {
console.warn(`You cant get placeholder content, cause placeholder is not visible.`);
return null;
}
},
// label
getLabelRootElement: () => label.getRootElement(),
isLabelExists: () => label.exists() && !floatingPlaceholder.exists(),
getLabelContent: () => label.getTextContent(),
// validation message
getValidationMsgRootElement: () => validationMsg.getRootElement(),
isValidationMsgExists: () => validationMsg.exists() && !_.isEmpty(validationMsg.getTextContent()),
getValidationMsgContent: () => validationMsg.getTextContent(),
//leadingAccessory, trailingAccessory, bottomAccessory
// char counter
getCharCounterRootElement: () => charCounter.getRootElement(),
isCharCounterExists: () => charCounter.exists(),
getCharCounterContent: () => charCounter.getTextContent()

};
};

export default TextFieldDriverFactory;
174 changes: 174 additions & 0 deletions src/incubator/TextField/__tests__/index.driver.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import React, {useState} from 'react';
import TextFieldTestKit from '../TextField.driver';
import {render, waitFor} from '@testing-library/react-native';
import TextField from '../index';
import View from '../../../components/view';

const TEXT_FIELD_TEST_ID = 'text_field_test_id';

function renderWrapperScreenWithTextField(textFieldProps) {
return render(<ScreenWithTextField {...textFieldProps}/>);
}

function ScreenWithTextField(textFieldProps) {
const [value, setValue] = useState(textFieldProps.value);
return (<View>
<TextField {...textFieldProps} testID={TEXT_FIELD_TEST_ID} value={value} onChangeText={setValue}/>
</View>);
}


describe('TextField', () => {
it('should render textField', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.exists()).toBe(true);
});

it('should render textField with correct content', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({value: 'aa'});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.content()).toEqual('aa');
});

it('should change the text correctly', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({value: 'aa'});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.content()).toEqual('aa');
textFieldDriver.changeText('bb');
await waitFor(() => expect(textFieldDriver.content()).toEqual('bb'));
});

describe('editable', () => {
it('should be editable', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.isDisabled()).toBe(false);
});

it('should render textField that is not editable', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({editable: false});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.isDisabled()).toBe(true);
});
});

describe('placeholder', () => {
it('should render placeholder with correct text', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({placeholder: 'mock placeholder'});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.isPlaceholderVisible()).toBe(true);
expect(textFieldDriver.getPlaceholderContent()).toEqual('mock placeholder');
});

it('should not render placeholder', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.isPlaceholderVisible()).toBe(false);
});

it('should not render placeholder after user changing the input text(no floating prop)', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({placeholder: 'mock placeholder'});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.isPlaceholderVisible()).toBe(true);
textFieldDriver.changeText('mock input value');
await waitFor(() => expect(textFieldDriver.isPlaceholderVisible()).toBe(false));
});

it('should render placeholder(floating) after user changing text if floatingPlaceholder prop sent', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({placeholder: 'mock placeholder', floatingPlaceholder: true});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.isPlaceholderVisible()).toBe(true);
textFieldDriver.changeText('mock input value');
await waitFor(() => expect(textFieldDriver.isPlaceholderVisible()).toBe(true));
await waitFor(() => expect(textFieldDriver.getPlaceholderContent()).toEqual('mock placeholder'));

});
});

describe('Label', () => {
it('should not render label if prop is not passed', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.isLabelExists()).toBe(false);
});

it('should render a label', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({label: 'mock label'});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.isLabelExists()).toBe(true);
expect(textFieldDriver.getLabelContent()).toEqual('mock label');
});

it('should not render label if floatingPlaceholder prop is passed', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({label: 'mock label', floatingPlaceholder: true});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.isLabelExists()).toBe(false);
});
});

describe('validation message', () => {
it('should not render validationMessage if enableErrors prop not supplied', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({value: '', validate: 'required', validationMessage: 'mock message', validateOnStart: true});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.isValidationMsgExists()).toBe(false);
});

it('should render validationMessage on start if input required and validateOnStart passed', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({value: '', validate: 'required', validationMessage: 'mock message', enableErrors: true, validateOnStart: true});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.isValidationMsgExists()).toBe(true);
expect(textFieldDriver.getValidationMsgContent()).toEqual('mock message');
});

it('should render validationMessage when input is requires after changing the input to empty string', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({value: 'mock value', validate: 'required', validationMessage: 'mock message', enableErrors: true, validateOnChange: true});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.isValidationMsgExists()).toBe(false);
expect(textFieldDriver.getValidationMsgContent()).toEqual('');
textFieldDriver.changeText('');
await waitFor(() => expect(textFieldDriver.isValidationMsgExists()).toBe(true));
expect(textFieldDriver.getValidationMsgContent()).toEqual('mock message');
});
});

describe('char counter', () => {
it('should render char counter.', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({showCharCounter: true, maxLength: 10});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.isCharCounterExists()).toBe(true);
});

it('should not render counter if maxLength prop not supplied', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({showCharCounter: true});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.isCharCounterExists()).toBe(false);
});

it('should not render counter if showCharCounter prop not supplied', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({maxLength: 10});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.isCharCounterExists()).toBe(false);
});

it('should render char counter, with "0/10" if value not supplied', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({showCharCounter: true, maxLength: 10});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.getCharCounterContent()).toEqual('0/10');
});

it('should render char counter with correct content supplied', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({value: 'abc', showCharCounter: true, maxLength: 10});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.getCharCounterContent()).toEqual('3/10');
});

it('should update char counter after changing the text', async () => {
const wrapperComponent = renderWrapperScreenWithTextField({value: 'ab', showCharCounter: true, maxLength: 10});
const textFieldDriver = await TextFieldTestKit({wrapperComponent, testID: TEXT_FIELD_TEST_ID});
expect(textFieldDriver.getCharCounterContent()).toEqual('2/10');
textFieldDriver.changeText('abcd');
await waitFor(() => expect(textFieldDriver.getCharCounterContent()).toEqual('4/10'));
});
});
});
3 changes: 2 additions & 1 deletion src/incubator/TextField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ const TextField = (props: InternalTextFieldProps) => {
floatOnFocus={floatOnFocus}
validationMessagePosition={validationMessagePosition}
extraOffset={leadingAccessoryMeasurements?.width}
testID={`${props.testID}.floatingPlaceholder`}
/>
)}
{children || (
Expand Down Expand Up @@ -242,7 +243,7 @@ const TextField = (props: InternalTextFieldProps) => {
/>
)}
{bottomAccessory}
{showCharCounter && <CharCounter maxLength={others.maxLength} charCounterStyle={charCounterStyle}/>}
{showCharCounter && <CharCounter maxLength={others.maxLength} charCounterStyle={charCounterStyle} testID={`${props.testID}.charCounter`}/>}
</View>
</View>
</FieldContext.Provider>
Expand Down
Loading