stripe / stripe-apps

Stripe Apps lets you embed custom user experiences directly in the Stripe Dashboard and orchestrate the Stripe API.
https://stripe.com/docs/stripe-apps
MIT License
148 stars 73 forks source link

There is no documented way to test a component passed to a prop #243

Open ericfrank-stripe opened 2 years ago

ericfrank-stripe commented 2 years ago

Your Problem

See: https://github.com/stripe/stripe-apps/blob/master/examples/messaging/src/views/Messaging.test.tsx

If a React component is passed to a prop, e.g.:

<ListItem title={<Box>Hello</Box>} />

There's no way (that we've found yet) to make assertions about the content of that prop. For example, there's no clear way to assert that it renders the text Hello.

jbachhardie commented 2 years ago

I just realized when encountering this problem that there's also no way to trigger, say, a button press on Button components that are passed in either. This makes it impossible to test views that use the "actions" prop, for example.

dalan-stripe commented 2 years ago

I'd say a more pertinent problem is when trying to test functionality on <Buttons> passed in same fashion to something like primaryAction.

In this case, I want to ensure that requirements are met before allowing the user to proceed to the next step in this FocusView.

<FocusView
        primaryAction={
          <Button
            disabled={submitDisabled}
            type="primary"
            onPress={() => runJob()}
          >
            Submit
          </Button>
        }
        />

There's no way to determine in a test if my intended logic is resulting in the Button being enabled.

wrapper.find(FocusView)?.props.primaryAction // this is a thing! 

But the object shape looks like this:

// console.log(wrapper!.find(FocusView)?.props.primaryAction)

    {
      kind: 3,
      children: [Getter],
      appendChild: [Function: appendChild],
      removeChild: [Function: removeChild],
      insertChildBefore: [Function: insertChildBefore],
      parent: [Getter],
      top: [Getter],
      createText: [Function: createText],
      createComponent: [Function: createComponent]
    }
dalan-stripe commented 2 years ago

I just realized when encountering this problem that there's also no way to trigger, say, a button press on Button components that are passed in either. This makes it impossible to test views that use the "actions" prop, for example.

@jbachhardie that's actually a different problem that I believe is solved by the .trigger method. E.g.:

https://stripe.com/docs/stripe-apps/ui-testing#element-properties-and-methods

wrapper!
      .find(TextArea)!
      .trigger("onChange", { target: { value: "Hello, World!" } });

await update();
WPaczula commented 2 years ago

I workaround that by mocking the elements like that. Let's say ListItem has value prop where I pass a ReactNode. The test renderer won't allow me to find that value, so I mocked the ListItem:

jest.mock("@stripe/ui-extension-sdk/ui", () => {
  const module = jest.requireActual("@stripe/ui-extension-sdk/ui")
  const MockListItem = ({ value, title, secondaryTitle }: any) => (
    <Box>
      <Inline>{value}</Inline>
      <Inline>{title}</Inline>
      <Inline>{secondaryTitle}</Inline>
    </Box>
  )

  return {
    ...(module as any),
    ListItem: MockListItem,
  }
})

This way I can find the node passed as value for example using findWhere(node => node.text === 'X'). I don't like this solution longterm because I'm mocking the main component used in the file, which is weird. Looking forward to this issue to be resolved 👀