Skip to content
This repository has been archived by the owner on Jul 30, 2020. It is now read-only.

Impossible to get FlatList element #12

Closed
elyalvarado opened this issue May 4, 2019 · 5 comments
Closed

Impossible to get FlatList element #12

elyalvarado opened this issue May 4, 2019 · 5 comments

Comments

@elyalvarado
Copy link
Contributor

Even though from some other parts of the code (events) it looks like is possible to get and interact with a FlatList component, it is not possible to select it with any of the queries as it is rendered as a RCTScrollView and is not whitelisted in the defaultFilter

  • react-native or expo: react-native
  • native-testing-library version: 2.0.0
  • react-native version: 0.59.5
  • node version:8.15.1
  • npm (or yarn) version: yarn 1.15.2

Relevant code or config:

  const MessageList = () => <FlatList testID='testFlatList' />
  test('can get FlatList', () => {
    const { queryByTestId } = render(<MessageList />)
    expect(queryByTestId('testFlatList')).not.toBeEmpty();
  })

What you did:

Tried to get a FlatList in some tests

What happened:

Got the following error:


Error: Unable to find an element with the testID of: internalFlatList

<RCTScrollView
  ListEmptyComponent={null}
  ListFooterComponent={null}
  contentContainerStyle={
    Object {
      "flexGrow": 1,
      "paddingHorizontal": 10,
    }
  }
  data={
    Array [
      Object {
        "callID": "1",
        "content": "This is the first message",
        "onAction": [Function],
        "subtitle": "From x to y",
        "title": "Giver1",
      },
      Object {
        "callID": "2",
        "content": "This is the second message",
        "onAction": [Function],
        "subtitle": "From x to y",
        "title": "Giver2",
      },
    ]
  }
  disableVirtualization={false}
  getItem={[Function]}
  getItemCount={[Function]}
  horizontal={false}
  initialNumToRender={10}
  keyExtractor={[Function]}
  maxToRenderPerBatch={10}
  numColumns={1}
  onContentSizeChange={[Function]}
  onEndReached={[Function]}
  onEndReachedThreshold={0.1}
  onLayout={[Function]}
  onMomentumScrollEnd={[Function]}
  onRefresh={[Function]}
  onScroll={[Function]}
  onScrollBeginDrag={[Function]}
  onScrollEndDrag={[Function]}
  refreshControl={
    <RefreshControlMock
      onRefresh={[Function]}
      refreshing={false}
    />
  }
  refreshing={false}
  removeClippedSubviews={false}
  renderItem={[Function]}
  scrollEventThrottle={50}
  stickyHeaderIndices={Array []}
  testID="internalFlatList"
  updateCellsBatchingPeriod={50}
  viewabilityConfigCallbackPairs={Array []}
  windowSize={21}
/>

    at getElementError (/Users/elyalvarado/Workspace/Happy/HappyRN/node_modules/native-testing-library/dist/query-helpers.js:1:947)
    at /Users/elyalvarado/Workspace/Happy/HappyRN/node_modules/native-testing-library/dist/query-helpers.js:1:3902
    at /Users/elyalvarado/Workspace/Happy/HappyRN/node_modules/native-testing-library/dist/query-helpers.js:1:3437
    at Object.getByTestId (/Users/elyalvarado/Workspace/Happy/HappyRN/App/components/__tests__/MessageList.test.tsx:135:22)
    at Object.asyncJestTest (/Users/elyalvarado/Workspace/Happy/HappyRN/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:102:37)
    at resolve (/Users/elyalvarado/Workspace/Happy/HappyRN/node_modules/jest-jasmine2/build/queueRunner.js:43:12)
    at new Promise (<anonymous>)
    at mapper (/Users/elyalvarado/Workspace/Happy/HappyRN/node_modules/jest-jasmine2/build/queueRunner.js:26:19)
    at promise.then (/Users/elyalvarado/Workspace/Happy/HappyRN/node_modules/jest-jasmine2/build/queueRunner.js:73:41)
    at <anonymous>

Problem description:

This is a native component, and the user can interact with it by scrolling to the end or pulling to refresh

Suggested solution:

I really don't know if this is within the guiding principles, but I think that the user can interact with the FlatList directly because it has specific behaviour that makes it different to other scrollviews in react-native, specifically the onEndReached and onRefresh events.

@bcarroll22
Copy link
Collaborator

Hey Ely, thanks for bringing up this issue, and thanks for all the details!

Let me start by saying, I appreciate you referencing the guiding principles in your description. I'd say that as far as the current release goes this likely can't be fixed in a way that adheres to them. The reason for this is that the 2.0.0 release uses the react-native jest-preset and they mostly mock the components that actually require native components. Since a FlatList is just a wrapper (even though it's part of the core library) that implements a ScrollView, and is not actually a native component itself, there's not a way to both follow the guiding principles and grab that element specifically.

That said, the 3.0.0 release that's currently being worked on sort of pivots from the approach of the react-native jest preset. We'll be including our own jest preset that's a superset of the react-native one which mocks the components we actually allow querying and firing elements on. Current state of the release, FlatList still isn't mocked, but I'm open to seeing a PR of what it would look like. The main reason I didn't include it myself is that mocking it to the degree it would need to be in order to do this seemed non-trivial.

Long story short, unfortunately this likely can't be fixed well in ~2.0.0, but I'm open to hearing how you believe we can improve it in ~3.0.0. I think I'd also like to think about it some more and hear some more feedback on this use case.

@bcarroll22
Copy link
Collaborator

I just released 3.0.0, and wanted to be sure to follow up with you. I'll start by saying, you'll want to use the new jest preset following the install guide, and make sure you check out the docs all around, quite a bit changed in this release (mostly around making it more like react testing library).

I spent a lot of time thinking about this since you opened this issue, and I have a couple of thoughts:

First, I can't really think of a reason you'd need to specifically query the actual FlatList. The source of FlatList shows that it passes its props on to VirtualizedList, so the event will bubble and fire on the VirtualizedList, which is where the FlatList events actually happen (not the FlatList itself). In 2.0, it couldn't bubble there properly, which was bad, but now it can with 3.0. Nothing about the FlatList itself really matters in the test, it'll all work out fine even though you can't query it directly.

Second, and this is the big one, the major sticking point for me is following the guiding principles of testing library, and given that events can bubble to a valid target, there's really no reason you'd need to add a testID to the FlatList and select it. You could select anything inside the list, then fireEvent.scroll or any other event you want to fire on the list, and that will bubble to the proper responder. I find that using a testID is almost never necessary, and that using them is usually related to some sort of unexpected/annoying behavior in tests.

My official recommendation on this issue:

  1. Upgrade to 3.0 which has more comprehensive handling of native elements and events
  2. Select an element inside the list and fire the event on that element
  3. When you reach for testID, ask if it's really necessary (because it probably shouldn't be)

Let me know if you have any other issues, hope you enjoy the 3.0 release ☺️

@bcarroll22
Copy link
Collaborator

I'm going to close this now due to inactivity. With the new release, a lot has changed, and I believe this use case is now covered by following things in the posts I made above. Good luck!

@rafaelcavalcante
Copy link

rafaelcavalcante commented Jun 11, 2020

@elyalvarado, have you figured out how to simulate the pull to refresh event?

@elyalvarado
Copy link
Contributor Author

@elyalvarado, have you figured out how to simulate the onRefresh event?

Well, what I'm doing is patching native-testing-library @testing-library/react-native/dist/preset/configure.js file, and removing react-native/Libraries/Components/ScrollView/ScrollView from the list of coreComponents which basically removes it from the incomplete (for this case) mocking logic implemented in native-testing-library.

And then I can just fire the native events and run my expectations as usual:

  it('calls the passed onRefresh prop when FlatList gets refreshed', () => {
    const onRefresh = jest.fn()
    const { getByTestId } = render(<MessageList {...defaultProps} onRefresh={onRefresh} />)
    const flatList = getByTestId('internalFlatList')

    fireEvent(flatList.props.refreshControl, new NativeTestEvent('refresh', {}))

    expect(onRefresh).toHaveBeenCalledTimes(1)
  })

Just be aware that if you do this and are doing snapshot testing, then the component name will probably change.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants