joon_bug
joon_bug

Reputation: 57

How do I access app.state from a Cypress test in a Remix project

Cypress has a way to expose the app's state to the test runner -- in React it usually looks like this:

class MyComponent extends React.Component {
  constructor (props) {
    super(props)
    // only expose the app during E2E tests
    if (window.Cypress) {
      window.app = this
    }
  }
  ...
}

Then you could access your state in a test with

cy.window()
  .its('app.state')
  .should('deep.equal', myStateObject)

However, the setup for Remix projects relies on functional components. I've tried this in my root.tsx component with a useEffect call:

export default function App() {
    useEffect(() => {
        window.app = App;
    }, []}
}

as well as in the root route (routes/index.tsx) by importing the <App /> component and using the logic in the useEffect function above. Neither of these options are working and I'm not sure where else to go here. Remix's GitHub issues are devoid of questions about this issue, so maybe I'm going about this the wrong way. Any help is appreciated! Thanks!

Upvotes: 2

Views: 594

Answers (1)

TesterDick
TesterDick

Reputation: 10545

I haven't done much work with Remix, but there is a question here that might be useful:
React - getting a component from a DOM element for debugging.

Note the last paragraph

Function components
Function components don't have "instances" in the same way classes do, so you can't just modify the FindReact function to return an object with forceUpdate, setState, etc. on it for function components.

That said, you can at least obtain the React-fiber node for that path, containing its props, state, and such. To do so, modify the last line of the FindReact function to just: return compFiber;

There's a lib cypress-react-app-actions that implements this for Cypress

export const getReactFiber = (el) => {
  const key = Object.keys(el).find((key) => {
    return (
      key.startsWith('__reactFiber$') || // react 17+
      key.startsWith('__reactInternalInstance$') // react <17
    )
  })
  if (!key) {
    return
  }
  return el[key]
}

// react 16+
export const getComponent = (fiber) => {
  let parentFiber = fiber.return
  while (typeof parentFiber.type == 'string') {
    parentFiber = parentFiber.return
  }
  return parentFiber
}

One of the example tests is

/// <reference types="cypress" />

import { getReactFiber, getComponent } from '../support/utils'

it('calls Example double()', () => {
  cy.visit('/')
  cy.get('.Example').within(() => {         // select via className of component 
    cy.contains('[data-cy=count]', '0')
    cy.get('[data-cy=add]').click().click()
    cy.contains('[data-cy=count]', '2')
    cy.root().then((el$) => {
      const fiber = getReactFiber(el$[0])
      console.log(fiber)
      const component = getComponent(fiber)
      console.log(component.stateNode)
      cy.log('calling **double()**')
      component.stateNode.double()       // work with component for functional
    })
    cy.contains('[data-cy=count]', '4')
  })
})

This example is for class components, but given the info in Function components section above, you would use the component object rather than component.stateNode.

Upvotes: 1

Related Questions