mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
fix: set text position should not reset component text (#49450)
Summary: fix: https://github.com/facebook/react-native/issues/49368 description is provided inside the ticket. When we use TextInput on ios and manage selection with the selection prop, TextInput is reset when we change selection. ## Changelog: [IOS] [FIXED] - Fix selection makes TextInput clear its content when using children Pull Request resolved: https://github.com/facebook/react-native/pull/49450 Test Plan: Tested with sample provided in ticket. I also test it with my app on both android and ios, but I cannot share video Reviewed By: sammy-SC Differential Revision: D69984616 Pulled By: cipolleschi fbshipit-source-id: a17169608f9df0ea1cb579e6038345f8e48bbc27
This commit is contained in:
committed by
React Native Bot
parent
23c9dbc563
commit
301532b51f
@@ -1019,7 +1019,7 @@ function useTextInputStateSynchronization_STATE({
|
||||
mostRecentEventCount: number,
|
||||
selection: ?Selection,
|
||||
inputRef: React.RefObject<null | HostInstance>,
|
||||
text: string,
|
||||
text?: string,
|
||||
viewCommands: ViewCommands,
|
||||
}): {
|
||||
setLastNativeText: string => void,
|
||||
@@ -1100,7 +1100,7 @@ function useTextInputStateSynchronization_REFS({
|
||||
mostRecentEventCount: number,
|
||||
selection: ?Selection,
|
||||
inputRef: React.RefObject<null | HostInstance>,
|
||||
text: string,
|
||||
text?: string,
|
||||
viewCommands: ViewCommands,
|
||||
}): {
|
||||
setLastNativeText: string => void,
|
||||
@@ -1314,7 +1314,7 @@ function InternalTextInput(props: Props): React.Node {
|
||||
? props.value
|
||||
: typeof props.defaultValue === 'string'
|
||||
? props.defaultValue
|
||||
: '';
|
||||
: undefined;
|
||||
|
||||
const viewCommands =
|
||||
AndroidTextInputCommands ||
|
||||
|
||||
+72
-16
@@ -7,7 +7,7 @@
|
||||
* @format
|
||||
*/
|
||||
|
||||
const {create} = require('../../../../jest/renderer');
|
||||
const {create, update} = require('../../../../jest/renderer');
|
||||
const ReactNativeFeatureFlags = require('../../../../src/private/featureflags/ReactNativeFeatureFlags');
|
||||
const ReactNative = require('../../../ReactNative/RendererProxy');
|
||||
const {
|
||||
@@ -20,8 +20,14 @@ const ReactTestRenderer = require('react-test-renderer');
|
||||
|
||||
jest.unmock('../TextInput');
|
||||
|
||||
[true, false].forEach(useRefsForTextInputState => {
|
||||
describe(`TextInput tests (useRefsForTextInputState = ${useRefsForTextInputState}`, () => {
|
||||
[
|
||||
{useRefsForTextInputState: true, useTextChildren: true},
|
||||
{useRefsForTextInputState: false, useTextChildren: true},
|
||||
{useRefsForTextInputState: true, useTextChildren: false},
|
||||
{useRefsForTextInputState: false, useTextChildren: false},
|
||||
].forEach(testCase => {
|
||||
const {useRefsForTextInputState, useTextChildren} = testCase;
|
||||
describe(`TextInput tests (useRefsForTextInputState = ${useRefsForTextInputState}) useTextChildren = ${useTextChildren}`, () => {
|
||||
let input;
|
||||
let inputRef;
|
||||
let onChangeListener;
|
||||
@@ -43,15 +49,16 @@ jest.unmock('../TextInput');
|
||||
return (
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
value={state.text}
|
||||
value={useTextChildren ? undefined : state.text}
|
||||
onChangeText={text => {
|
||||
onChangeTextListener(text);
|
||||
setState({text});
|
||||
}}
|
||||
onChange={event => {
|
||||
onChangeListener(event);
|
||||
}}
|
||||
/>
|
||||
}}>
|
||||
{useTextChildren ? state.text : undefined}
|
||||
</TextInput>
|
||||
);
|
||||
}
|
||||
const renderTree = await create(<TextInputWrapper />);
|
||||
@@ -75,12 +82,20 @@ jest.unmock('../TextInput');
|
||||
);
|
||||
});
|
||||
it('calls onChange callbacks', () => {
|
||||
expect(input.props.value).toBe(initialValue);
|
||||
if (!useTextChildren) {
|
||||
expect(input.props.value).toBe(initialValue);
|
||||
} else {
|
||||
expect(input.props.children).toBe(initialValue);
|
||||
}
|
||||
const message = 'This is a test message';
|
||||
ReactTestRenderer.act(() => {
|
||||
enter(input, message);
|
||||
});
|
||||
expect(input.props.value).toBe(message);
|
||||
if (!useTextChildren) {
|
||||
expect(input.props.value).toBe(message);
|
||||
} else {
|
||||
expect(input.props.children).toBe(message);
|
||||
}
|
||||
expect(onChangeTextListener).toHaveBeenCalledWith(message);
|
||||
expect(onChangeListener).toHaveBeenCalledWith({
|
||||
nativeEvent: {text: message},
|
||||
@@ -90,7 +105,12 @@ jest.unmock('../TextInput');
|
||||
async function createTextInput(extraProps) {
|
||||
const textInputRef = React.createRef(null);
|
||||
await create(
|
||||
<TextInput ref={textInputRef} value="value1" {...extraProps} />,
|
||||
<TextInput
|
||||
ref={textInputRef}
|
||||
value={useTextChildren ? undefined : 'value1'}
|
||||
{...extraProps}>
|
||||
{useTextChildren ? 'value1' : undefined}
|
||||
</TextInput>,
|
||||
);
|
||||
return textInputRef;
|
||||
}
|
||||
@@ -134,14 +154,55 @@ jest.unmock('../TextInput');
|
||||
expect(TextInput.State.currentlyFocusedInput()).toBe(null);
|
||||
});
|
||||
|
||||
it('change selection keeps content', async () => {
|
||||
const defaultValue = 'value1';
|
||||
// create content
|
||||
let renderTree = await create(
|
||||
<TextInput
|
||||
value={useTextChildren ? undefined : defaultValue}
|
||||
position={{start: 1, end: 1}}>
|
||||
{useTextChildren ? defaultValue : undefined}
|
||||
</TextInput>,
|
||||
);
|
||||
input = renderTree.root.findByType(TextInput);
|
||||
expect(
|
||||
useTextChildren ? input.children[0].props.children : input.props.value,
|
||||
).toBe(defaultValue);
|
||||
expect(input.props.position.start).toBe(1);
|
||||
expect(input.props.position.end).toBe(1);
|
||||
|
||||
// update position
|
||||
renderTree = await update(
|
||||
renderTree,
|
||||
<TextInput
|
||||
value={useTextChildren ? undefined : defaultValue}
|
||||
position={{start: 2, end: 2}}>
|
||||
{useTextChildren ? defaultValue : undefined}
|
||||
</TextInput>,
|
||||
);
|
||||
expect(
|
||||
useTextChildren ? input.children[0].props.children : input.props.value,
|
||||
).toBe(defaultValue);
|
||||
expect(input.props.position.start).toBe(2);
|
||||
expect(input.props.position.end).toBe(2);
|
||||
});
|
||||
|
||||
it('should unfocus when other TextInput is focused', async () => {
|
||||
const textInputRe1 = React.createRef(null);
|
||||
const textInputRe2 = React.createRef(null);
|
||||
|
||||
await create(
|
||||
<>
|
||||
<TextInput ref={textInputRe1} value="value1" />
|
||||
<TextInput ref={textInputRe2} value="value2" />
|
||||
<TextInput
|
||||
ref={textInputRe1}
|
||||
value={useTextChildren ? undefined : 'value1'}>
|
||||
{useTextChildren ? 'value1' : undefined}
|
||||
</TextInput>
|
||||
<TextInput
|
||||
ref={textInputRe2}
|
||||
value={useTextChildren ? undefined : 'value2'}>
|
||||
{useTextChildren ? 'value2' : undefined}
|
||||
</TextInput>
|
||||
</>,
|
||||
);
|
||||
ReactNative.findNodeHandle = jest.fn().mockImplementation(ref => {
|
||||
@@ -210,7 +271,6 @@ jest.unmock('../TextInput');
|
||||
rejectResponderTermination={true}
|
||||
selection={null}
|
||||
submitBehavior="blurAndSubmit"
|
||||
text=""
|
||||
textContentType="emailAddress"
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
@@ -255,7 +315,6 @@ jest.unmock('../TextInput');
|
||||
rejectResponderTermination={true}
|
||||
selection={null}
|
||||
submitBehavior="blurAndSubmit"
|
||||
text=""
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
`);
|
||||
@@ -301,7 +360,6 @@ jest.unmock('../TextInput');
|
||||
selection={null}
|
||||
submitBehavior="blurAndSubmit"
|
||||
testID="testID"
|
||||
text=""
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
`);
|
||||
@@ -432,7 +490,6 @@ jest.unmock('../TextInput');
|
||||
role="main"
|
||||
selection={null}
|
||||
submitBehavior="blurAndSubmit"
|
||||
text=""
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
`);
|
||||
@@ -489,7 +546,6 @@ jest.unmock('../TextInput');
|
||||
]
|
||||
}
|
||||
submitBehavior="blurAndSubmit"
|
||||
text=""
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
`);
|
||||
|
||||
+112
-8
@@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TextInput tests (useRefsForTextInputState = false should render as expected: should deep render when mocked (please verify output manually) 1`] = `
|
||||
exports[`TextInput tests (useRefsForTextInputState = false) useTextChildren = false should render as expected: should deep render when mocked (please verify output manually) 1`] = `
|
||||
<RCTSinglelineTextInputView
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
@@ -23,12 +23,11 @@ exports[`TextInput tests (useRefsForTextInputState = false should render as expe
|
||||
rejectResponderTermination={true}
|
||||
selection={null}
|
||||
submitBehavior="blurAndSubmit"
|
||||
text=""
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`TextInput tests (useRefsForTextInputState = false should render as expected: should deep render when not mocked (please verify output manually) 1`] = `
|
||||
exports[`TextInput tests (useRefsForTextInputState = false) useTextChildren = false should render as expected: should deep render when not mocked (please verify output manually) 1`] = `
|
||||
<RCTSinglelineTextInputView
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
@@ -51,12 +50,11 @@ exports[`TextInput tests (useRefsForTextInputState = false should render as expe
|
||||
rejectResponderTermination={true}
|
||||
selection={null}
|
||||
submitBehavior="blurAndSubmit"
|
||||
text=""
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`TextInput tests (useRefsForTextInputState = true should render as expected: should deep render when mocked (please verify output manually) 1`] = `
|
||||
exports[`TextInput tests (useRefsForTextInputState = false) useTextChildren = true should render as expected: should deep render when mocked (please verify output manually) 1`] = `
|
||||
<RCTSinglelineTextInputView
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
@@ -79,12 +77,119 @@ exports[`TextInput tests (useRefsForTextInputState = true should render as expec
|
||||
rejectResponderTermination={true}
|
||||
selection={null}
|
||||
submitBehavior="blurAndSubmit"
|
||||
text=""
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`TextInput tests (useRefsForTextInputState = true should render as expected: should deep render when not mocked (please verify output manually) 1`] = `
|
||||
exports[`TextInput tests (useRefsForTextInputState = false) useTextChildren = true should render as expected: should deep render when not mocked (please verify output manually) 1`] = `
|
||||
<RCTSinglelineTextInputView
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
focusable={true}
|
||||
forwardedRef={null}
|
||||
mostRecentEventCount={0}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onScroll={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
onSelectionChangeShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
rejectResponderTermination={true}
|
||||
selection={null}
|
||||
submitBehavior="blurAndSubmit"
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`TextInput tests (useRefsForTextInputState = true) useTextChildren = false should render as expected: should deep render when mocked (please verify output manually) 1`] = `
|
||||
<RCTSinglelineTextInputView
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
focusable={true}
|
||||
forwardedRef={null}
|
||||
mostRecentEventCount={0}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onScroll={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
onSelectionChangeShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
rejectResponderTermination={true}
|
||||
selection={null}
|
||||
submitBehavior="blurAndSubmit"
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`TextInput tests (useRefsForTextInputState = true) useTextChildren = false should render as expected: should deep render when not mocked (please verify output manually) 1`] = `
|
||||
<RCTSinglelineTextInputView
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
focusable={true}
|
||||
forwardedRef={null}
|
||||
mostRecentEventCount={0}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onScroll={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
onSelectionChangeShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
rejectResponderTermination={true}
|
||||
selection={null}
|
||||
submitBehavior="blurAndSubmit"
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`TextInput tests (useRefsForTextInputState = true) useTextChildren = true should render as expected: should deep render when mocked (please verify output manually) 1`] = `
|
||||
<RCTSinglelineTextInputView
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
focusable={true}
|
||||
forwardedRef={null}
|
||||
mostRecentEventCount={0}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
onResponderRelease={[Function]}
|
||||
onResponderTerminate={[Function]}
|
||||
onResponderTerminationRequest={[Function]}
|
||||
onScroll={[Function]}
|
||||
onSelectionChange={[Function]}
|
||||
onSelectionChangeShouldSetResponder={[Function]}
|
||||
onStartShouldSetResponder={[Function]}
|
||||
rejectResponderTermination={true}
|
||||
selection={null}
|
||||
submitBehavior="blurAndSubmit"
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`TextInput tests (useRefsForTextInputState = true) useTextChildren = true should render as expected: should deep render when not mocked (please verify output manually) 1`] = `
|
||||
<RCTSinglelineTextInputView
|
||||
accessible={true}
|
||||
allowFontScaling={true}
|
||||
@@ -107,7 +212,6 @@ exports[`TextInput tests (useRefsForTextInputState = true should render as expec
|
||||
rejectResponderTermination={true}
|
||||
selection={null}
|
||||
submitBehavior="blurAndSubmit"
|
||||
text=""
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user