Skip to content

Nested custom Text componentns not working correctly #515

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

Closed
mym0404 opened this issue Aug 19, 2020 · 12 comments
Closed

Nested custom Text componentns not working correctly #515

mym0404 opened this issue Aug 19, 2020 · 12 comments
Labels
bug Something isn't working

Comments

@mym0404
Copy link

mym0404 commented Aug 19, 2020

Describe the bug

If custom Text components are nested and find with text, then the wrong other element is retrieved.

Steps to Reproduce

const CustomText = ({ children, ...props }: PropsWithChildren<TextProps>) => {
  return <Text {...props}>{children}</Text>;
};
CustomText.displayName = 'CustomText';

const NestedText = () => {
  return (
    <View>
      <CustomText testID="outer">
        <CustomText testID="inner1"></CustomText>
        <CustomText testID="inner2">Hello</CustomText>
      </CustomText>
    </View>
  );
};

it('wrong result', () => {
  const { getByText, debug } = render(<NestedText />);

  const element = getByText(/Hello/);
  debug();

  expect(element.props.testID).toBe('inner2');
});
  console.log
    <View>
      <Text
        testID="outer"
      >
        <Text
          testID="inner1"
        />
        <Text
          testID="inner2"
        >
          Hello
        </Text>
      </Text>
    </View>

      at debugDeep (node_modules/@testing-library/react-native/build/helpers/debugDeep.js:19:13)


Error: expect(received).toBe(expected) // Object.is equality

Expected: "inner2"
Received: "outer"
<Click to see difference>


    at Object.<anonymous> (/Users/mj/Desktop/Turing/MathKing/src/components/shared/AiTeacherCommentText.test.tsx:79:32)
    at Object.asyncJestTest (/Users/mj/Desktop/Turing/MathKing/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:100:37)
    at /Users/mj/Desktop/Turing/MathKing/node_modules/jest-jasmine2/build/queueRunner.js:47:12
    at new Promise (<anonymous>)
    at mapper (/Users/mj/Desktop/Turing/MathKing/node_modules/jest-jasmine2/build/queueRunner.js:30:19)
    at /Users/mj/Desktop/Turing/MathKing/node_modules/jest-jasmine2/build/queueRunner.js:77:41
    at processTicksAndRejections (internal/process/task_queues.js:93:5)

Versions

  npmPackages:
    @testing-library/react-native: 7.0.2 => 7.0.2 
    react: 16.13.1 => 16.13.1 
    react-native: 0.63.2 => 0.63.2 
    react-test-renderer: 16.13.1 => 16.13.1 

@mym0404
Copy link
Author

mym0404 commented Aug 19, 2020

One question more, If I set displayName of CustomText like CustomText.displayName = 'customText'; why debug just show it as <Text> ?

@thymikee thymikee added the bug Something isn't working label Aug 19, 2020
@thymikee
Copy link
Member

Yup, this is an edge case we don't handle at the moment. Please feel free to send a PR fixing that :)
Related work: #489

One question more, If I set displayName of CustomText like CustomText.displayName = 'customText'; why debug just show it as ?

It's because debug will only show host (native) components and CustomText is not one of these. You can call debug.shallow() using a shallow renderer to display custom component hierarchy to some extent.

@mym0404
Copy link
Author

mym0404 commented Aug 19, 2020

Ok I will try Thank you :)

@mym0404
Copy link
Author

mym0404 commented Aug 20, 2020

This bug is related to a recursive way of find text node in this library.
I can temporary fix like the following.

const matches = testingLib.getAllByText(text);
const element = matches[matches.length - 1];

@mym0404 mym0404 closed this as completed Aug 20, 2020
@mym0404 mym0404 reopened this Aug 20, 2020
@mikeduminy
Copy link
Contributor

Trying to match across nested nodes in an attempt to match what the user sees is an issue for several of our tests too. Most notably when we try to fire an event on an inner Text node.

Right now I can't think of a way to achieve this nested matching and the specific matching behaviour beforehand. Landing #573 would at least give us a way to implement our own overrides, but long term WDYT of adding a matching option to ByText queries - something like a smart mode (for the current behaviour) and a basic/legacy/simple mode? @thymikee

We would have to think about how the exact texting matching option in #554 affects this, if at all.

@thymikee
Copy link
Member

We could still support nested Texts by traversing up the tree once the original query fail. If the direct parents are Text nodes, we could go up to the first one, construct the string and match using the specified matcher/normalizer. What do you think?

@mikeduminy
Copy link
Contributor

I'm not sure I follow. Is this the kind of algorithm you had in mind?

  1. If current node is a Text node, build string from children (string and number) and run through matcher / normalize. Return node if match.
  2. If parent is a Text node, increment stepUp counter, build string from children of parent (and their children to a depth of stepUp counter) and run through matcher / normalizer. Return if match.
  3. Goto 3 for as long as parent is a Text node and there is no match.

@thymikee
Copy link
Member

Yea, something like this.

@mikeduminy
Copy link
Contributor

I tried it and found the following problems:

It is somewhat complicated by the native view being wrapped with a React.forwardRef, determining if a parent or child is actually a text element is not cleanly possible.

It matches 2 instances in this case:

<Text>
  <CustomText>Hello</CustomText> <CustomText>World!</CustomText>
</Text>

Reversing the search method like this matches the lowest level element, not its parent, because when searching the instance.find is trying to find elements for which getNodeByText returns true, and using this algorithm it will only return true when evaluating the lowest node.

@thymikee
Copy link
Member

We only care about host (native) components, with type of "string". This will eliminate any ForwardRefs, Functions etc.

I was rather thinking of building a separate tree of host components while searching for the lowest Text node. If the search fails we have the tree which we can then traverse up for finding the Text that's furthest away from the leaf text node, and then construct the text based on its children.

@mikeduminy
Copy link
Contributor

This sounds like some effort needs to go into this feature. I personally don't need it, what I need is parity with v6 for getByText, which is why I suggested bringing back the simple get text node implementation and having the current smarter implementation behind a flag.

@AugustinLF
Copy link
Collaborator

Fixed (I believe as part of #489).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants