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 <mdjastrzebski@gmail.com>
This commit is contained in:
Alan Kenyon
2020-10-21 05:50:09 -04:00
committed by GitHub
parent c9d4809e8f
commit 696b28b9c3
2 changed files with 52 additions and 14 deletions
+23
View File
@@ -282,3 +282,26 @@ test('is not fooled by non-native disabled prop', () => {
fireEvent.press(screen.getByText('Trigger Test'));
expect(handlePress).toHaveBeenCalledTimes(1);
});
function TestChildTouchableComponent({ onPress, someProp }) {
return (
<View>
<TouchableOpacity onPress={onPress} disabled={someProp}>
<Text>Trigger</Text>
</TouchableOpacity>
</View>
);
}
test('is not fooled by non-responder wrapping host elements', () => {
const handlePress = jest.fn();
const screen = render(
<View>
<TestChildTouchableComponent onPress={handlePress} someProp={true} />
</View>
);
fireEvent.press(screen.getByText('Trigger'));
expect(handlePress).not.toHaveBeenCalled();
});
+29 -14
View File
@@ -2,32 +2,47 @@
import act from './act';
import { ErrorWithStack } from './helpers/errors';
const isTextInputComponent = (element: ReactTestInstance) => {
const isHostElement = (element?: ReactTestInstance) => {
return typeof element?.type === 'string';
};
const isTextInput = (element?: ReactTestInstance) => {
// eslint-disable-next-line import/no-extraneous-dependencies
const { TextInput } = require('react-native');
return element.type === TextInput;
return element?.type === TextInput;
};
const isTouchResponder = (element?: ReactTestInstance) => {
if (!isHostElement(element)) return false;
return !!element?.props.onStartShouldSetResponder || isTextInput(element);
};
const isEventEnabled = (
element?: ReactTestInstance,
touchResponder?: ReactTestInstance
) => {
if (isTextInput(element)) return element?.props.editable !== false;
return touchResponder?.props.onStartShouldSetResponder?.() !== false;
};
const findEventHandler = (
element: ReactTestInstance,
eventName: string,
callsite?: any,
nearestHostDescendent?: ReactTestInstance,
nearestTouchResponder?: ReactTestInstance,
hasDescendandHandler?: boolean
) => {
const touchResponder = isTouchResponder(element)
? element
: nearestTouchResponder;
const handler = getEventHandler(element, eventName);
const hasHandler = handler != null || hasDescendandHandler;
const isHostComponent = typeof element.type === 'string';
const hostElement = isHostComponent ? element : nearestHostDescendent;
const isEventEnabled = isTextInputComponent(element)
? element.props.editable !== false
: hostElement?.props.onStartShouldSetResponder?.() !== false;
if (handler && isEventEnabled) return handler;
if (handler && isEventEnabled(element, touchResponder)) return handler;
// Do not bubble event to the root element
const hasHandler = handler != null || hasDescendandHandler;
if (element.parent === null || element.parent.parent === null) {
if (hasHandler) {
return null;
@@ -43,7 +58,7 @@ const findEventHandler = (
element.parent,
eventName,
callsite,
hostElement,
touchResponder,
hasHandler
);
};