-
Notifications
You must be signed in to change notification settings - Fork 273
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
fix: non-editable wrapped TextInput events #1385
fix: non-editable wrapped TextInput events #1385
Conversation
src/fireEvent.ts
Outdated
@@ -56,6 +56,9 @@ const isEventEnabled = ( | |||
eventName?: string | |||
) => { | |||
if (isTextInput(element)) return element?.props.editable !== false; | |||
if (isTextInput(touchResponder)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not too sure about this. It will fix this issue indeed but saying that no event should be fired if at any point we pass through a non editable TextInput is not really correct either. For instance if I did a fireEvent.press on a non editable TextInput that's wrapped by a Pressable the press would not be run while it should. This may be a lesser evil still but there may be some other problems that we haven't thought of
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the end fireEvent is just a low level API that calls props and we try to do too much with it so it has many unexpected behavior. How I see it long term is to fix this kind of issues using the new API userEvent but it's still at its early stage. It's not ideal that we can change text of non editable TextInputs but maybe to test those cases the easiest would be to use a custom matcher instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to call fireEvent.press
on a non editable TextInput that's wrapped by a Pressable the press handler shouldn't be called (at least that's the behaviour that user will experience and we already have in the browser). If touch responder isn't able to handle the event I think that's enough because it means that we base on the host component props in the first place and that should match the end user experience.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pierrezimmermannbam could you post some test cases that you suspect wouldn't work so that @TMaszko could include in this PR? With fireEvent
and handling Responder API we kinda reverse-engineer the event system of RN, which may actually change depending on the platform (especially web), so expect edge cases that would be great to understand and document
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The following test will fail
test('can pres pressable that wraps non editable textinput', () => {
const mockOnPress = jest.fn();
const { getByPlaceholderText } = render(
<Pressable onPress={mockOnPress}>
<TextInput placeholder="hello" editable={false} />
</Pressable>
);
fireEvent.press(getByPlaceholderText('hello'));
expect(mockOnPress).toHaveBeenCalled();
});
We consider all text inputs to be touch responders, so when we do a press targeting a TextInput, it is considered to be the touch responder, even though it may be non editable. So we fix a bug but introduce another, possibly more. I'm fine with this pr being merged, it looks like a lesser evil but I feel like we're trying to extend too much the fireEvent API so it will always be flawed and it should in my opinion be considered as just an API that calls a prop (in fact I think it should be renamed to fireProp). For the long term i believe we need the userEvent API to really fix those issues and keep the fireEvent API only as a way to call custom props.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main issue with fireEvent is that is has almost the same behavior no matter what prop we call, so we disable all events on non editable text inputs for instance, but we should still be able to call onPressIn
test('can trigger onPressIn on disabled textinput', () => {
const mockOnPressIn = jest.fn();
const { getByPlaceholderText } = render(
<TextInput
placeholder="hello"
editable={false}
onPressIn={mockOnPressIn}
/>
);
fireEvent(getByPlaceholderText('hello'), 'pressIn');
expect(mockOnPressIn).toHaveBeenCalled();
});
This test fails as well but it shouldn't
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get why this test should pass. Isn't it a proper behaviour to block events when component is basically disabled
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The editable prop controls wether the value of the input can be modified or not but it still responds to touch events when it's not editable. You can try to press a non editable text input, the onPressIn prop is called in both cases
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TMaszko, @pierrezimmermannbam I've been curious to get the understanding of how the TexInput
events actually work when running in a regular RN app. I've build a small experiments app to test it, and submitted it as PR #1391.
The relevant experiment code is here: https://github.com/callstack/react-native-testing-library/pull/1391/files#diff-800ebe5060dbf16ec52e1cf9c65e7c2fa0f0cd86af32dcfdf913e13576831a70
For editable: false
scenario both onPressIn
and onPressOut
events are being triggered on iOS:
LOG Event: pressIn {"changedTouches": [[Circular]], "identifier": 1, "locationX": 57.33332824707031, "locationY": 35, "pageX": 77.33332824707031, "pageY": 146, "target": 117, "timestamp": 707054389.9047917, "touches": [[Circular]]}
LOG Event: pressOut {"changedTouches": [[Circular]], "identifier": 1, "locationX": 57.33332824707031, "locationY": 35, "pageX": 77.33332824707031, "pageY": 146, "target": 117, "timestamp": 707054477.4037917, "touches": []}
But they are not being called on Android(!).
As for original @TMaszko issue #1384, it is correct in the respect that non-editable TextInput
does not rise the focus
/blur
events.
I'm yet to prepare experiments involving Pressable
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've also created a second test case, when non-editable TextInput
is wrapped in Pressable
:
<Pressable
onPress={handlePressablePress}
onPressIn={handlePressablePressIn}
onPressOut={handlePressablePressOut}
style={styles.pressable}
>
<TextInput
style={styles.textInput}
value={value}
onChangeText={handleChangeText}
editable={false}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
/>
</Pressable>
In this cases, when I press on the TextInput
I get following events on iOS:
LOG Event: pressIn {"changedTouches": [[Circular]], "identifier": 1, "locationX": 165.66665649414062, "locationY": 16.333328247070312, "pageX": 205.66665649414062, "pageY": 147.3333282470703, "target": 65, "timestamp": 717273074.8629583, "touches": [[Circular]]}
LOG Event: pressOut {"changedTouches": [[Circular]], "identifier": 1, "locationX": 165.66665649414062, "locationY": 16.333328247070312, "pageX": 205.66665649414062, "pageY": 147.3333282470703, "target": 65, "timestamp": 717273155.2179583, "touches": []}
So TextInput
s pressIn
/pressOunt
event are being invoked.
On the other hand on Android I get following events:
LOG Event: Pressable.pressIn {"changedTouches": [[Circular]], "identifier": 0, "locationX": 146.18182373046875, "locationY": 60.727272033691406, "pageX": 146.18182373046875, "pageY": 140.72727966308594, "target": 795, "targetSurface": -1, "timestamp": 514525, "touches": [[Circular]]}
LOG Event: Pressable.press {"changedTouches": [[Circular]], "identifier": 0, "locationX": 146.18182373046875, "locationY": 60.727272033691406, "pageX": 146.18182373046875, "pageY": 140.72727966308594, "target": 795, "targetSurface": -1, "timestamp": 514619, "touches": []}
LOG Event: Pressable.pressOut {"changedTouches": [[Circular]], "identifier": 0, "locationX": 146.18182373046875, "locationY": 60.727272033691406, "pageX": 146.18182373046875, "pageY": 140.72727966308594, "target": 795, "targetSurface": -1, "timestamp": 514619, "touches": []}
So now it's Pressables
s pressIn
/pressOut
event are being invoked.
There is a surprising and significant difference the platforms.
Based on the experiments described here, and here I've came to the conclusion that there is a difference between iOS and Android in terms of handling the I hope that will help us focus the review discussion. Regarding the idea of checking touch responder As per @pierrezimmermannbam comment, the touch responder variable is being reset when walking up the element tree a new touch responder is encountered. So in the |
I think we need to figure out what to do with the discrepancy between iOS and Android platforms mentioned above. Both approaches seems to have a reasoning behind them, but they are not compatibile with each other. At the moment we do not support separate simulations for iOS and Android, we have one unified event processing. The good part is that the mentioned events ( @pierrezimmermannbam, @TMaszko I am curious your thoughts on this topic. |
@TMaszko I've added tests based on #1261 that I believe should be part of this PR. They basically check if Note: the new test are failing, specifically because |
@mdjastrzebski this is very interesting, I hadn't done tests on Android, it's quite surprising indeed! I'm wondering if we want to simulate all of this behavior, including the difference between platforms. I feel like we're hitting the limitations of testing in a node environment here, specially with TextInput component being mocked in React Native preset and it feels like we'd need to reimplement the TextInput for everything to work as expected so I'm not really sure it's worth it. Maybe we should just recommend using matchers from jest-native to test disabled pressables / text inputs? I guess the issue is that fireEvent already partially handles non editable text inputs or disabled pressables which gives the impression it handles all cases which is not true. Simplifying fireEvent could be an option but it would be a huge breaking change and it may not be understood so this may not be the best option. I think it would be better to implement all of this in userEvent instead and then keep fireEvent as just an API that calls props |
@pierrezimmermannbam I am coming to the conclusion that if In the real world of RN apps, putting As for this PR, it seems to be improving our simulation by basing the |
d5e19b4
to
7eeae11
Compare
I've prepared the PR for review:
The subject is quite complex, so I am looking forward for a challenging review @pierrezimmermannbam, @MattAgn, @AugustinLF, @thymikee, @TMaszko :-) |
That sounds like a fair tradeoff to me, we should however make sure that we document it. Overall I agree that it seems like this PR makes it better. It's not perfect, but it's going to be a bit better. |
6af91e4
to
8065370
Compare
I agree, I think this is a good tradeoff for now! |
8065370
to
c6f2d96
Compare
@pierrezimmermannbam @AugustinLF @MattAgn From my perspective this PR is ready for merging. I'm looking forward for code review approval or further change request for this PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM but I've got quite involved in the PR, so looking for more objective reviewers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a small clarification regarding the test name/comment, but nothing that prevents from merging. And since I don't have a good suggestion (I'm not sure what exactly this test is testing), it's good to be merged for me :)
c6f2d96
to
81f216b
Compare
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## main #1385 +/- ##
=======================================
Coverage 96.85% 96.85%
=======================================
Files 51 51
Lines 3398 3399 +1
Branches 506 507 +1
=======================================
+ Hits 3291 3292 +1
Misses 107 107
☔ View full report in Codecov by Sentry. |
….2 (#2445) [](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [@testing-library/react-native](https://callstack.github.io/react-native-testing-library) ([source](https://togithub.com/callstack/react-native-testing-library)) | [`12.0.1` -> `12.1.2`](https://renovatebot.com/diffs/npm/@testing-library%2freact-native/12.0.1/12.1.2) | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes <details> <summary>callstack/react-native-testing-library</summary> ### [`v12.1.2`](https://togithub.com/callstack/react-native-testing-library/releases/tag/v12.1.2) [Compare Source](https://togithub.com/callstack/react-native-testing-library/compare/v12.1.1...v12.1.2) #### What's Changed #### Fixes - fix: pointer events evaluation by [@​mdjastrzebski](https://togithub.com/mdjastrzebski) in [https://github.com/callstack/react-native-testing-library/pull/1395](https://togithub.com/callstack/react-native-testing-library/pull/1395) - fix: non-editable wrapped TextInput events by [@​TMaszko](https://togithub.com/TMaszko) in [https://github.com/callstack/react-native-testing-library/pull/1385](https://togithub.com/callstack/react-native-testing-library/pull/1385) - fix: support `onXxx` event name for TextInput event checks by [@​mdjastrzebski](https://togithub.com/mdjastrzebski) in [https://github.com/callstack/react-native-testing-library/pull/1404](https://togithub.com/callstack/react-native-testing-library/pull/1404) #### Docs, Chores, etc - docs: add config example for pnpm by [@​yjose](https://togithub.com/yjose) in [https://github.com/callstack/react-native-testing-library/pull/1400](https://togithub.com/callstack/react-native-testing-library/pull/1400) - chore: move/remove deprecation functions by [@​mdjastrzebski](https://togithub.com/mdjastrzebski) in [https://github.com/callstack/react-native-testing-library/pull/1402](https://togithub.com/callstack/react-native-testing-library/pull/1402) - refactor: component tree dead code by [@​mdjastrzebski](https://togithub.com/mdjastrzebski) in [https://github.com/callstack/react-native-testing-library/pull/1403](https://togithub.com/callstack/react-native-testing-library/pull/1403) - refactor: `fireEvent` cleanup by [@​mdjastrzebski](https://togithub.com/mdjastrzebski) in [https://github.com/callstack/react-native-testing-library/pull/1401](https://togithub.com/callstack/react-native-testing-library/pull/1401) #### New Contributors - [@​yjose](https://togithub.com/yjose) made their first contribution in [https://github.com/callstack/react-native-testing-library/pull/1400](https://togithub.com/callstack/react-native-testing-library/pull/1400) 👏 - [@​TMaszko](https://togithub.com/TMaszko) made their first contribution in [https://github.com/callstack/react-native-testing-library/pull/1385](https://togithub.com/callstack/react-native-testing-library/pull/1385) 👏 **Full Changelog**: callstack/react-native-testing-library@v12.1.1...v12.1.2 ### [`v12.1.1`](https://togithub.com/callstack/react-native-testing-library/releases/tag/v12.1.1) [Compare Source](https://togithub.com/callstack/react-native-testing-library/compare/v12.1.0...v12.1.1) #### What's Changed #### Fixes - fix: remove incorrect dependencies by [@​mdjastrzebski](https://togithub.com/mdjastrzebski) in [https://github.com/callstack/react-native-testing-library/pull/1399](https://togithub.com/callstack/react-native-testing-library/pull/1399) **Full Changelog**: callstack/react-native-testing-library@v12.1.0...v12.1.1 ### [`v12.1.0`](https://togithub.com/callstack/react-native-testing-library/releases/tag/v12.1.0) [Compare Source](https://togithub.com/callstack/react-native-testing-library/compare/v12.0.1...v12.1.0) #### What's Changed ##### Improvements - feat: Render element tree in query error messages by [@​stevehanson](https://togithub.com/stevehanson) in [https://github.com/callstack/react-native-testing-library/pull/1378](https://togithub.com/callstack/react-native-testing-library/pull/1378) ##### Bugfixes - Proper stack trace for findBy\* and findAllBy\* queries by [@​mdjastrzebski](https://togithub.com/mdjastrzebski) in [https://github.com/callstack/react-native-testing-library/pull/1394](https://togithub.com/callstack/react-native-testing-library/pull/1394) #### New Contributors - [@​stevehanson](https://togithub.com/stevehanson) made their first contributions in [#​1377](https://togithub.com/callstack/react-native-testing-library/issues/1377), [#​1378](https://togithub.com/callstack/react-native-testing-library/issues/1378) and [#​1390](https://togithub.com/callstack/react-native-testing-library/issues/1390) 👏 ##### Chores, docs, deps, etc - Fix broken link in contributing.md by [@​stevehanson](https://togithub.com/stevehanson) in [https://github.com/callstack/react-native-testing-library/pull/1377](https://togithub.com/callstack/react-native-testing-library/pull/1377) - chore: update deps 2023-04-04 by [@​mdjastrzebski](https://togithub.com/mdjastrzebski) in [https://github.com/callstack/react-native-testing-library/pull/1380](https://togithub.com/callstack/react-native-testing-library/pull/1380) - Fix typo in "derived" in v12 migration guide by [@​CodingItWrong](https://togithub.com/CodingItWrong) in [https://github.com/callstack/react-native-testing-library/pull/1376](https://togithub.com/callstack/react-native-testing-library/pull/1376) - chore: fix migration guide role prop naming by [@​mdjastrzebski](https://togithub.com/mdjastrzebski) in [https://github.com/callstack/react-native-testing-library/pull/1382](https://togithub.com/callstack/react-native-testing-library/pull/1382) - fix: "Edit this Page" link in docs results in 404 by [@​stevehanson](https://togithub.com/stevehanson) in [https://github.com/callstack/react-native-testing-library/pull/1390](https://togithub.com/callstack/react-native-testing-library/pull/1390) - refactor: remove stale tests by [@​mdjastrzebski](https://togithub.com/mdjastrzebski) in [https://github.com/callstack/react-native-testing-library/pull/1392](https://togithub.com/callstack/react-native-testing-library/pull/1392) - chore: experiments app by [@​mdjastrzebski](https://togithub.com/mdjastrzebski) in [https://github.com/callstack/react-native-testing-library/pull/1391](https://togithub.com/callstack/react-native-testing-library/pull/1391) **Full Changelog**: callstack/react-native-testing-library@v12.0.1...v12.1.0 </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/dooboolab-community/react-native-iap). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS45OC40IiwidXBkYXRlZEluVmVyIjoiMzUuMTMxLjAiLCJ0YXJnZXRCcmFuY2giOiJtYWluIn0=--> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Summary
[Description by @mdjastrzebski]
This PR modifies the
fireEvent
handling forTextInputs
by checking theeditable
prop status of the nearest touch responder of given element. The nearest touch responder is the nearest descendant element that is both host element as well as advertises as touch responder. One such element isTextInput
.The previous version of the code, checked the
editable
prop status on the element itself, which could lead to situations whenTextInput
wrappers would execute events, e.g.changeText
,focus
, etc on non-editableTextInput
, because the check has been limited to hostTextInput
and wrapping it compositeTextInput
coming from RN package. Any additionalTextInput
wrapper would not have it'seditable
prop checked and would refer to hostTextInput
editable
prop.During experiments conducted on iOS and Android, I've discovered that most of the events are blocked by
editable={false}
prop, but some events likelayout
,contentSizeChange
, etc are being called anyway. This PR includes special exclusion for such events.Fixes #1384
Fixes #1261
Test plan
Green tests