Skip to content
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

findComponent doesn't work as expected on <Text> components. #101

Closed
radglob opened this issue Sep 4, 2018 · 15 comments
Closed

findComponent doesn't work as expected on <Text> components. #101

radglob opened this issue Sep 4, 2018 · 15 comments
Assignees

Comments

@radglob
Copy link

radglob commented Sep 4, 2018

According to the README.md, <Text> components should be inspectable using findComponent, and I've been unable to do this. In the following example spec:

export default (spec) => {
  spec.describe('Test', () => {
    spec.it('works', async () => {
      await spec.fillIn('Test.Input', 'test');
      const text = await spec.findComponent('Test.Text');
      await containsText(text, 'test');
    })
  })
}

Assume here that Test.Input and Test.Text exist, that filling in Test.Input sets the text in Test.Text, and that containsText matches the example in the README.md. When this test is run, it returns an error, logging Cannot read property 'children' of undefined. It looks like findComponent isn't returning what is expected.

This is on React Native 0.56.

@Rafael-Martins
Copy link

Rafael-Martins commented Nov 9, 2018

Hey, maybe I can help you, please post a console.log of your text variable( const text = await spec.findComponent('Test.Text'); ).

@AbigailMcP
Copy link
Contributor

Hi @sdroadie !

findComponent will return the specified component if found. You should then be able to call props on your component and call any of the props if they are functions / check their values.

I just tried out a quick example using React Native 0.57 and can't see any issues...

With a hooked up TextInput component I tweaked the containsText function to check the value prop as follows, and the test passes:

export async function containsText(component, text) {
  if (!component.props.value.includes(text)) {
    throw new Error(`Could not find text ${text}`);
  };
}

I'm closing this issue for now as I can't replicate, but please let me know if this is still an issue and I'll try to help further (as @Rafael-Martins said, a log of your text variable would be useful)

@funador
Copy link

funador commented Jun 20, 2019

Hi @AbigailMcP! I am having a similar issue with Cannot read property 'children' of undefined with RN 0.59.9 + Cavy 2.1.1

// spec
const nameFeedback = await spec.findComponent('Text.Name');
console.log('nameFeedback', nameFeedback);
await containsText(nameFeedback, 'hello');
// helper
const containsText = async (component, text) => {
  if (!component.props.children.includes(text)) {
    throw new Error(`Could not find text ${text}`);
  }
};
// component
<Text style={hasError} ref={useCavy()('Text.Name')}>
  {message}
</Text>

Here is the log:

ReactNativeFiberHostComponent {_nativeTag: 2565, _children: Array(1), viewConfig: {…}}
  viewConfig:
  directEventTypes: {topTextLayout: {…}}
  uiViewClassName: "RCTText"
  validAttributes: {pointerEvents: true, accessible: true, accessibilityActions: true, accessibilityLabel: true, accessibilityComponentType: true, …}
  __proto__: Object
_children: Array(1)
  0: 2563
  length: 1
  __proto__: Array(0)
_nativeTag: 2565
__proto__: Object

findComponent is grabbing the right element, but I can't see how to access the props/children/value etc. Also tried with value as suggested in the comment above. Appreciate any insight you can provide.

@funador
Copy link

funador commented Jun 20, 2019

So it doesn't look like ReactNativeFiberHostComponent exposes the props of the Text component. To get around this I put the ref on a wrapping class component (can't have a ref on a function component) and then you can access the props on the class to get at the text being rendered. A bit clunky, but it works.

@AbigailMcP
Copy link
Contributor

Ahhh, that makes sense - thanks for sharing your solution @funador! Maybe something similar was at play when @sdroadie was having issues.

It might not be much extra help, but Cavy does have a helper function wrap that effectively allows you to add a ref to a function component. There's an example of this in the 'Hook up components' section of the README.

@funador
Copy link

funador commented Jun 21, 2019

@AbigailMcP question.... would wrap work for RN components like Text and View or only function components we have written.

import React, { Component } from 'react';
import { View, Text } from 'react-native';
import { FunctionComponent } from 'some-ui-library';
import { hook, wrap } from 'cavy';

class Scene extends Component {
  render() {
    // This works
    const TestableFunctionComponent = wrap(FunctionComponent);
    // Does this work?
    const TestableText = wrap(Text)

    return (
      <View>
        <TestableText
          ref={this.props.generateTestHook('Scene.Text')}
        />
        <TestableFunctionComponent
          ref={this.props.generateTestHook('Scene.FunctionComponent')}
          otherProp={...}
        />
      </View>      
    );
  }
}

@AbigailMcP
Copy link
Contributor

Hey @funador - I'm not sure I completely understand your use case. Text and View are both class components - Cavy's wrap function is for interacting with function components.

Under the hood, wrap wraps a function component and uses forwardRef and useImperativeHandle to make the properties of that function component available via the ref you pass in.

Maybe I'm misunderstanding what you're trying to do!

@funador
Copy link

funador commented Jun 21, 2019

I am trying to access props on Text for example. Even though they are classes what is getting exposed by RN is ReactNativeFiberHostComponent which does not allow access component.props.children like in the example you provide.

Wondering if there is a way around it or if we have to test a class/function with wrap that then renders Text. Hope that is clearer.

@AbigailMcP
Copy link
Contributor

Ahhh I see, yes it's a bit frustrating but that's the only way around it at the moment.

We'll have a think about whether Cavy could help out more in those situations, but for now I'll add a note to the README about writing your own tester functions using RN own components like <Test>. Especially since this seems as though it could be quite a common thing to test!

Thanks for pointing this out @funador 👌

@swalahamani
Copy link

@AbigailMcP any updates or practical work-around on this?

@mptorz
Copy link

mptorz commented Oct 4, 2019

I am having exactly the same problem. @AbigailMcP I couldn't really find a note. If it is added can you post a link here. Also since this problem hasn't really been resolved, would it be possible to reopen this issue?

@AbigailMcP AbigailMcP reopened this Oct 8, 2019
@AbigailMcP
Copy link
Contributor

AbigailMcP commented Oct 9, 2019

Hi everyone! A quick update on the above issue.

The problem
For components like Text, React Native renderer creates an instance of ReactNativeFiberHostComponent which is what gets passed to the ref callback. You cannot access the original props on this so testing anything other than the fact the element is on the page is impossible.

The solution (update to wrap function)
At the moment Cavy's wrap function only works with function components, forwarding the ref so that it can call the inner component's props.

We're proposing that you should be able to pass a non-function component like Text to wrap. In this case, a React class component with testable props is returned.

You'll then be able to test Text as follows:

// src/MyPage.js

import React from 'react';
import { View, Text } from 'react-native';
import { useCavy, wrap } from 'cavy';

export default ({ data }) => {
  const generateTestHook = useCavy();
  const TestableText = wrap(Text);

  return (
    <View>
      <TestableText ref={generateTestHook('MyPage.Title')}>
        {data.title}
      </TestableText>
    </View>
  )
};

In your test:

// specs/MySpec.js

import { containsText } from './helpers';

export default function(spec) {
  spec.describe('In this context', async function() {
    spec.it('shows this text', async function() {
      const textComponent = await spec.findComponent('MyPage.Title');
      await containsText(text, 'Welcome');
    });
  });
}

With custom helper function:

// specs/helpers.js

export async function containsText(component, text) {
  if (!component.props.children.includes(text)) {
    throw new Error(`Could not find text ${text}`);
  };
}

I also propose that we make that containsText helper ☝️ part of Cavy, as it seems it would be pretty popular (@jalada).

I'll be working on this enhancement over the next day or so - will keep y'all updated.

@AbigailMcP
Copy link
Contributor

@mptorz just FYI this will affect the typings for wrap!

@mptorz
Copy link

mptorz commented Oct 9, 2019

No worries! I am quite busy at work, so I will probably do the PR with typings over the weekend anyway :) Thanks for addressing this issue!

@AbigailMcP
Copy link
Contributor

Included in version 3.1.0 released today 🎉

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

No branches or pull requests

6 participants