Skip to content

Commit 696b28b

Browse files
fix: non-responder wrapping host elements ignore disabled prop (#572)
* test showing the issue * don't pass non-responder host elements as decsecendants * linter fixs * attempting to improve readability * refactor: re-structure code for clarity & redability Co-authored-by: Maciej Jastrzebski <[email protected]>
1 parent c9d4809 commit 696b28b

File tree

2 files changed

+52
-14
lines changed

2 files changed

+52
-14
lines changed

src/__tests__/fireEvent.test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,3 +282,26 @@ test('is not fooled by non-native disabled prop', () => {
282282
fireEvent.press(screen.getByText('Trigger Test'));
283283
expect(handlePress).toHaveBeenCalledTimes(1);
284284
});
285+
286+
function TestChildTouchableComponent({ onPress, someProp }) {
287+
return (
288+
<View>
289+
<TouchableOpacity onPress={onPress} disabled={someProp}>
290+
<Text>Trigger</Text>
291+
</TouchableOpacity>
292+
</View>
293+
);
294+
}
295+
296+
test('is not fooled by non-responder wrapping host elements', () => {
297+
const handlePress = jest.fn();
298+
299+
const screen = render(
300+
<View>
301+
<TestChildTouchableComponent onPress={handlePress} someProp={true} />
302+
</View>
303+
);
304+
305+
fireEvent.press(screen.getByText('Trigger'));
306+
expect(handlePress).not.toHaveBeenCalled();
307+
});

src/fireEvent.js

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,47 @@
22
import act from './act';
33
import { ErrorWithStack } from './helpers/errors';
44

5-
const isTextInputComponent = (element: ReactTestInstance) => {
5+
const isHostElement = (element?: ReactTestInstance) => {
6+
return typeof element?.type === 'string';
7+
};
8+
9+
const isTextInput = (element?: ReactTestInstance) => {
610
// eslint-disable-next-line import/no-extraneous-dependencies
711
const { TextInput } = require('react-native');
8-
return element.type === TextInput;
12+
return element?.type === TextInput;
13+
};
14+
15+
const isTouchResponder = (element?: ReactTestInstance) => {
16+
if (!isHostElement(element)) return false;
17+
18+
return !!element?.props.onStartShouldSetResponder || isTextInput(element);
19+
};
20+
21+
const isEventEnabled = (
22+
element?: ReactTestInstance,
23+
touchResponder?: ReactTestInstance
24+
) => {
25+
if (isTextInput(element)) return element?.props.editable !== false;
26+
27+
return touchResponder?.props.onStartShouldSetResponder?.() !== false;
928
};
1029

1130
const findEventHandler = (
1231
element: ReactTestInstance,
1332
eventName: string,
1433
callsite?: any,
15-
nearestHostDescendent?: ReactTestInstance,
34+
nearestTouchResponder?: ReactTestInstance,
1635
hasDescendandHandler?: boolean
1736
) => {
18-
const handler = getEventHandler(element, eventName);
19-
const hasHandler = handler != null || hasDescendandHandler;
20-
21-
const isHostComponent = typeof element.type === 'string';
22-
const hostElement = isHostComponent ? element : nearestHostDescendent;
37+
const touchResponder = isTouchResponder(element)
38+
? element
39+
: nearestTouchResponder;
2340

24-
const isEventEnabled = isTextInputComponent(element)
25-
? element.props.editable !== false
26-
: hostElement?.props.onStartShouldSetResponder?.() !== false;
27-
28-
if (handler && isEventEnabled) return handler;
41+
const handler = getEventHandler(element, eventName);
42+
if (handler && isEventEnabled(element, touchResponder)) return handler;
2943

3044
// Do not bubble event to the root element
45+
const hasHandler = handler != null || hasDescendandHandler;
3146
if (element.parent === null || element.parent.parent === null) {
3247
if (hasHandler) {
3348
return null;
@@ -43,7 +58,7 @@ const findEventHandler = (
4358
element.parent,
4459
eventName,
4560
callsite,
46-
hostElement,
61+
touchResponder,
4762
hasHandler
4863
);
4964
};

0 commit comments

Comments
 (0)