Skip to content

Commit b87a2e3

Browse files
committed
Merge branch 'master' of github.com:wix/react-native-ui-lib
2 parents b02ddb0 + 730634c commit b87a2e3

File tree

10 files changed

+99
-35
lines changed

10 files changed

+99
-35
lines changed

demo/src/screens/componentScreens/TextFieldScreen.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import _ from 'lodash';
22
import React, {Component} from 'react';
3-
import {TextInput, StyleSheet, ScrollView, ActivityIndicator} from 'react-native';
4-
import {Assets, Colors, Spacings, Typography, View, Text, Button, Keyboard, TextField, TextFieldMethods} from 'react-native-ui-lib'; //eslint-disable-line
3+
import {StyleSheet, ScrollView, ActivityIndicator} from 'react-native';
4+
import {Assets, Colors, Spacings, Typography, View, Text, Button, Keyboard, TextField, TextFieldRef} from 'react-native-ui-lib'; //eslint-disable-line
55
const {KeyboardAwareInsetsView} = Keyboard;
66

77
const priceFormatter = Intl.NumberFormat('en-US');
88

99
export default class TextFieldScreen extends Component {
10-
input = React.createRef<TextInput>();
11-
input2 = React.createRef<TextInput>();
12-
inputWithValidation = React.createRef<TextFieldMethods>();
10+
input = React.createRef<TextFieldRef>();
11+
input2 = React.createRef<TextFieldRef>();
12+
inputWithValidation = React.createRef<TextFieldRef>();
1313
state = {
1414
errorPosition: TextField.validationMessagePositions.TOP,
1515
shouldDisable: false,
@@ -162,7 +162,6 @@ export default class TextFieldScreen extends Component {
162162

163163
<View row top marginT-s4>
164164
<TextField
165-
// @ts-expect-error
166165
ref={this.inputWithValidation}
167166
placeholder="Enter full name"
168167
validate="required"

src/incubator/TextField/__tests__/index.driver.spec.tsx

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,28 @@ import {waitFor} from '@testing-library/react-native';
33
import View from '../../../components/view';
44
import {TextFieldDriver} from '../TextField.driver';
55
import TextField from '../index';
6-
import {Validator} from '../types';
6+
import {TextFieldProps} from '../types';
77

88
const TEXT_FIELD_TEST_ID = 'text_field_test_id';
9-
interface TextFieldProps {
10-
value?: string;
11-
placeholder?: string;
12-
label?: string;
13-
validationMessage?: string;
14-
validate?: Validator;
15-
validateOnStart?: boolean;
16-
validateOnChange?: boolean;
17-
enableErrors?: boolean;
18-
editable?: boolean;
19-
readonly?: boolean;
20-
floatingPlaceholder?: boolean;
21-
showCharCounter?: boolean;
22-
maxLength?: number;
23-
}
249

2510
function TestCase(textFieldProps?: TextFieldProps) {
2611
const [value, setValue] = useState(textFieldProps?.value);
27-
return (<View>
28-
<TextField {...textFieldProps} testID={TEXT_FIELD_TEST_ID} value={value} onChangeText={setValue}/>
29-
</View>);
12+
return (
13+
<View>
14+
<TextField {...textFieldProps} testID={TEXT_FIELD_TEST_ID} value={value} onChangeText={setValue}/>
15+
</View>
16+
);
3017
}
3118

19+
const validate = jest.fn((value: string) => {
20+
return !!value;
21+
});
3222

3323
describe('TextField', () => {
34-
afterEach(() => TextFieldDriver.clear());
24+
afterEach(() => {
25+
TextFieldDriver.clear();
26+
jest.clearAllMocks();
27+
});
3528

3629
it('should render textField', async () => {
3730
const component = <TestCase/>;
@@ -248,4 +241,38 @@ describe('TextField', () => {
248241
await waitFor(async () => expect(await textFieldDriver.getCharCounterContent()).toEqual('4/10'));
249242
});
250243
});
244+
245+
describe('validateOnBlur', () => {
246+
it('validate is called with undefined when defaultValue is not given', async () => {
247+
const component = (
248+
<TestCase
249+
validateOnBlur
250+
validationMessage={'Not valid'}
251+
validate={[validate]}
252+
/>
253+
);
254+
const textFieldDriver = new TextFieldDriver({component, testID: TEXT_FIELD_TEST_ID});
255+
textFieldDriver.focus();
256+
textFieldDriver.blur();
257+
await waitFor(() => expect(validate).toHaveBeenCalledTimes(1));
258+
await waitFor(() => expect(validate).toHaveBeenCalledWith(undefined));
259+
});
260+
261+
it('validate is called with defaultValue when defaultValue is given', async () => {
262+
const defaultValue = '1';
263+
const component = (
264+
<TestCase
265+
validateOnBlur
266+
validationMessage={'Not valid'}
267+
validate={[validate]}
268+
defaultValue={defaultValue}
269+
/>
270+
);
271+
const textFieldDriver = new TextFieldDriver({component, testID: TEXT_FIELD_TEST_ID});
272+
textFieldDriver.focus();
273+
textFieldDriver.blur();
274+
await waitFor(() => expect(validate).toHaveBeenCalledTimes(1));
275+
await waitFor(() => expect(validate).toHaveBeenCalledWith(defaultValue));
276+
});
277+
});
251278
});

src/incubator/TextField/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import {
1717
InternalTextFieldProps,
1818
ValidationMessagePosition,
1919
FieldContextType,
20-
TextFieldMethods
20+
TextFieldMethods,
21+
TextFieldRef
2122
} from './types';
2223
import {shouldHidePlaceholder} from './Presenter';
2324
import Input from './Input';
@@ -218,6 +219,7 @@ export {
218219
FieldContextType,
219220
StaticMembers as TextFieldStaticMembers,
220221
TextFieldMethods,
222+
TextFieldRef,
221223
ValidationMessagePosition as TextFieldValidationMessagePosition
222224
};
223225
export default asBaseComponent<TextFieldProps, StaticMembers>(forwardRef(TextField as any), {

src/incubator/TextField/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,5 @@ export interface TextFieldMethods {
259259
validate: () => boolean;
260260
isValid: () => boolean;
261261
}
262+
263+
export type TextFieldRef = TextInput & TextFieldMethods;

src/incubator/TextField/useFieldState.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export default function useFieldState({
1414
onChangeValidity,
1515
...props
1616
}: FieldStateProps) {
17-
const [value, setValue] = useState(props.value ?? props.defaultValue);
17+
const propsValue = props.value ?? props.defaultValue;
18+
const [value, setValue] = useState(propsValue);
1819
const [isFocused, setIsFocused] = useState(false);
1920
const [isValid, setIsValid] = useState<boolean | undefined>(undefined);
2021
const [failingValidatorIndex, setFailingValidatorIndex] = useState<number | undefined>(undefined);
@@ -39,16 +40,16 @@ export default function useFieldState({
3940
}, []);
4041

4142
useEffect(() => {
42-
if (props.value !== value) {
43-
setValue(props.value);
43+
if (propsValue !== value) {
44+
setValue(propsValue);
4445

45-
if (validateOnChange && (_.isUndefined(props.defaultValue) || value !== props.defaultValue)) {
46-
validateField(props.value);
46+
if (validateOnChange) {
47+
validateField(propsValue);
4748
}
4849
}
49-
/* On purpose listen only to props.value change */
50+
/* On purpose listen only to propsValue change */
5051
/* eslint-disable-next-line react-hooks/exhaustive-deps*/
51-
}, [props.value, validateOnChange]);
52+
}, [propsValue, validateOnChange]);
5253

5354
useDidUpdate(() => {
5455
if (!_.isUndefined(isValid)) {

src/incubator/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// export {default as Calendar} from './Calendar';
22
export {default as ExpandableOverlay} from './expandableOverlay';
33
// @ts-ignore
4-
export {default as TextField, TextFieldProps, FieldContextType, TextFieldMethods, TextFieldValidationMessagePosition} from './TextField';
4+
export {default as TextField, TextFieldProps, FieldContextType, TextFieldMethods, TextFieldRef, TextFieldValidationMessagePosition} from './TextField';
55
export {default as Toast, ToastProps, ToastPresets} from './toast';
66
export {default as TouchableOpacity, TouchableOpacityProps} from './TouchableOpacity';
77
export {default as PanView, PanViewProps, PanViewDirections, PanViewDismissThreshold} from './panView';

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import * as Incubator from './incubator';
2929
export {
3030
TextFieldProps,
3131
TextFieldMethods,
32+
TextFieldRef,
3233
TextFieldValidationMessagePosition,
3334
FieldContextType,
3435
ToastProps,

src/testkit/Component.driver.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,18 @@ export class ComponentDriver {
4545
.then((driver) => driver.press());
4646
};
4747

48+
focus = async () => {
49+
return this.uniDriver
50+
.selectorByTestId(this.testID)
51+
.then((driver) => driver.focus());
52+
};
53+
54+
blur = async () => {
55+
return this.uniDriver
56+
.selectorByTestId(this.testID)
57+
.then((driver) => driver.blur());
58+
};
59+
4860
protected getByTestId = (testID: string) => {
4961
return this.uniDriver
5062
.selectorByTestId(testID)

src/testkit/UniDriver.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export interface UniDriver {
77
instance(): Promise<any>;
88
getInstanceProps(): Promise<any>;
99
press(): void;
10+
focus(): void;
11+
blur(): void;
1012
typeText(text: string): Promise<void>;
1113
scrollX(deltaX: number): Promise<void>;
1214
scrollY(deltaY: number): Promise<void>;

src/testkit/drivers/TestingLibraryDriver.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,24 @@ export class TestingLibraryDriver implements UniDriver {
8585
fireEvent.press(this.reactTestInstances[0]);
8686
};
8787

88+
focus = (): void => {
89+
if (!this.reactTestInstances) {
90+
throw new NoSelectorException();
91+
}
92+
this.validateExplicitInstance();
93+
this.validateSingleInstance();
94+
fireEvent(this.reactTestInstances[0], 'focus');
95+
};
96+
97+
blur = (): void => {
98+
if (!this.reactTestInstances) {
99+
throw new NoSelectorException();
100+
}
101+
this.validateExplicitInstance();
102+
this.validateSingleInstance();
103+
fireEvent(this.reactTestInstances[0], 'blur');
104+
};
105+
88106
typeText = async (text: string): Promise<void> => {
89107
console.log('this.reactTestInstances: ', this.reactTestInstances);
90108
if (!this.reactTestInstances) {

0 commit comments

Comments
 (0)