Material-ui: TypeError when rendering for jest snapshots

Created on 20 Nov 2017  ·  18Comments  ·  Source: mui-org/material-ui

  • [x] I have searched the issues of this repository and believe that this is not a duplicate.

Expected Behavior


I expect the tests to run without a problem.

Current Behavior

I get an error:

TypeError: parentInstance.children.indexOf is not a function

I think this is due to portals not working properly in combination with jest snapshots.

Steps to Reproduce (for bugs)

import React from 'react';
import { storiesOf } from '@storybook/react';
import AlertDialog from './AlertDialog';

storiesOf('AlertDialog', module)
  .add('basic', () => <AlertDialog bodyKey="test" onClick={() => {}} titleKey="test" open />);

Where <AlertDialog /> is a boring wrapper around <Dialog />.

I'm sorry for not including a code snippet. I don't know how to set up the tests to illustrate the issue, but I tried my best to provide as much relevant information as possible.

Context

I'm using storybooks with storysnaps (jest snapshots), and our tests are failing. I'm pretty sure it has something to do with https://github.com/airbnb/enzyme/issues/1150 and potentially https://github.com/reactjs/react-modal/issues/553 could benefit from a solution as well.

Your Environment

| Tech | Version |
|--------------|---------|
| Material-UI | 1.0.0-beta.21 |
| React | 16.0.0 |
| browser | N/A |

Additional

I have a feeling this is something that could be caught in storyshots but I'm not experienced enough with the material at hand. Any pointers, or better yet solutions would be greatly appreciated.

I'm not against fixing it and contributing either (if I get the right pointers to help me out).

question test

Most helpful comment

Came across the same issue...
Environment

  • react 16.1.0
  • material-ui 1.0.0-beta.20
  • jest 21.2.1

Component

import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import Button from 'material-ui/Button';
import Dialog, {
  DialogActions,
  DialogContent,
  DialogContentText,
  DialogTitle,
} from 'material-ui/Dialog';


const styles = () => ({
  div: {
    display: 'block',
    textAlign: 'center'
  }
});
class Error extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      open: true

    };
  }

  handleRequestClose = () => {
    this.setState({ open: false });
  }

  render() {
    const { classes, errorMsg } = this.props;
    return (
      <div className={classes.div}>
        <Dialog ignoreBackdropClick ignoreEscapeKeyUp open={this.state.open} onRequestClose={this.handleRequestClose}>
          <DialogTitle>{"You got an error!"}</DialogTitle>
          <DialogContent>
            <DialogContentText>
              Error: {errorMsg}
            </DialogContentText>
          </DialogContent>
          <DialogActions>
            <Button onClick={this.handleRequestClose} color="accent" autoFocus>
              Dismiss
            </Button>
          </DialogActions>
        </Dialog>
      </div>
    );
  }
}

Error.propTypes = {
  classes: PropTypes.object.isRequired,
  errorMsg: PropTypes.string
};

export default withStyles(styles)(Error);

Test

import React from 'react';
import Error from '../shared/Error';
import renderer from 'react-test-renderer';

describe('Error component', () => {
  it('should render', () => {
    const tree = renderer.create(
      <Error />
    ).toJSON();
    expect(tree).toMatchSnapshot();
  });
});

Error message
● Error component › should render

    TypeError: parentInstance.children.indexOf is not a function

      at appendChild (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7146:39)
      at commitPlacement (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:4893:13)
      at commitAllHostEffects (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5651:13)
      at HTMLUnknownElement.callCallback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:1584:14)
      at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:219:27)
      at HTMLUnknownElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:126:9)
      at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:87:17)
      at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/nodes/HTMLElement-impl.js:36:27)
      at HTMLUnknownElement.dispatchEvent (node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:61:35)
      at Object.invokeGuardedCallbackDev (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:1623:16)
      at invokeGuardedCallback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:1480:29)
      at commitRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5771:9)
      at performWorkOnRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6730:42)
      at performWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6680:7)
      at requestWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6596:7)
      at scheduleWorkImpl (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6479:11)
      at scheduleWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6441:12)
      at scheduleTopLevelUpdate (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6904:5)
      at Object.updateContainer (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6942:7)
      at Object.create (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7562:18)
      at Object.<anonymous> (src/components/pages/shared/Error.spec.js:7:44)

All 18 comments

Your issue has been closed because it does not conform to our issue requirements.
Please provide a full reproduction test case. This would help a lot 👷 .
A live example would be perfect. This codesandbox.io template _may_ be a good starting point. Thank you!

I'm sorry for not including a code snippet. I don't know how to set up the tests to illustrate the issue, but I tried my best to provide as much relevant information as possible.

The information provided aren't actionable on our end. We need a reproduction repository. Also, what is this AlertDialog component?

@oliviertassinari

An error occured when fetching the sandbox:

Could not find package.json

While I do understand you want to keep things tidy in here, I'm not spending too much time on this. I'll just comment out my dialog stories for now. If anyone else comes across this issue, maybe they're willing to make the example.

@RWOverdijk Thanks, I will have a look at what's going on with codesandbox!

Yes, same on our side. We have limited ressources, trying reproducing issues has a poor value/cost ratio in comparaison to other tasks. It's why we use a strict policy.

Came across the same issue...
Environment

  • react 16.1.0
  • material-ui 1.0.0-beta.20
  • jest 21.2.1

Component

import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from 'material-ui/styles';
import Button from 'material-ui/Button';
import Dialog, {
  DialogActions,
  DialogContent,
  DialogContentText,
  DialogTitle,
} from 'material-ui/Dialog';


const styles = () => ({
  div: {
    display: 'block',
    textAlign: 'center'
  }
});
class Error extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      open: true

    };
  }

  handleRequestClose = () => {
    this.setState({ open: false });
  }

  render() {
    const { classes, errorMsg } = this.props;
    return (
      <div className={classes.div}>
        <Dialog ignoreBackdropClick ignoreEscapeKeyUp open={this.state.open} onRequestClose={this.handleRequestClose}>
          <DialogTitle>{"You got an error!"}</DialogTitle>
          <DialogContent>
            <DialogContentText>
              Error: {errorMsg}
            </DialogContentText>
          </DialogContent>
          <DialogActions>
            <Button onClick={this.handleRequestClose} color="accent" autoFocus>
              Dismiss
            </Button>
          </DialogActions>
        </Dialog>
      </div>
    );
  }
}

Error.propTypes = {
  classes: PropTypes.object.isRequired,
  errorMsg: PropTypes.string
};

export default withStyles(styles)(Error);

Test

import React from 'react';
import Error from '../shared/Error';
import renderer from 'react-test-renderer';

describe('Error component', () => {
  it('should render', () => {
    const tree = renderer.create(
      <Error />
    ).toJSON();
    expect(tree).toMatchSnapshot();
  });
});

Error message
● Error component › should render

    TypeError: parentInstance.children.indexOf is not a function

      at appendChild (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7146:39)
      at commitPlacement (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:4893:13)
      at commitAllHostEffects (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5651:13)
      at HTMLUnknownElement.callCallback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:1584:14)
      at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:219:27)
      at HTMLUnknownElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:126:9)
      at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:87:17)
      at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/nodes/HTMLElement-impl.js:36:27)
      at HTMLUnknownElement.dispatchEvent (node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:61:35)
      at Object.invokeGuardedCallbackDev (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:1623:16)
      at invokeGuardedCallback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:1480:29)
      at commitRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5771:9)
      at performWorkOnRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6730:42)
      at performWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6680:7)
      at requestWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6596:7)
      at scheduleWorkImpl (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6479:11)
      at scheduleWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6441:12)
      at scheduleTopLevelUpdate (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6904:5)
      at Object.updateContainer (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6942:7)
      at Object.create (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7562:18)
      at Object.<anonymous> (src/components/pages/shared/Error.spec.js:7:44)

Just run into the same issue. It has something to do with the Portals. I made a simple reproduce project here: https://github.com/Skaronator/material-ui-issue-9243
Just run:

yarn
yarn test

And you'll get the following error:

$ react-scripts test --env=jsdom
 FAIL  src\App.test.js
  ● should match Snapshot

    TypeError: parentInstance.children.indexOf is not a function

      at appendChild (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7218:39)
      at commitPlacement (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:4904:13)
      at commitAllHostEffects (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5678:13)
      at HTMLUnknownElement.callCallback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:1610:14)
      at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:219:27)
      at HTMLUnknownElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:126:9)
      at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:87:17)
      at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/nodes/HTMLElement-impl.js:36:27)
      at HTMLUnknownElement.dispatchEvent (node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:61:35)
      at Object.invokeGuardedCallbackDev (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:1649:16)
      at invokeGuardedCallback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:1506:29)
      at commitRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5798:9)
      at performWorkOnRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6800:42)
      at performWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6750:7)
      at requestWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6661:7)
      at scheduleWorkImpl (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6515:11)
      at scheduleWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6472:12)
      at scheduleTopLevelUpdate (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6976:5)
      at Object.updateContainer (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7014:7)
      at Object.create (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7637:18)
      at Object.<anonymous>.it (src/App.test.js:8:46)
          at new Promise (<anonymous>)
      at Promise.resolve.then.el (node_modules/p-map/index.js:46:16)
          at <anonymous>
      at process._tickCallback (internal/process/next_tick.js:188:7)

  × should match Snapshot (61ms)

@Skaronator Enzyme do not support the portal API of React: https://github.com/airbnb/enzyme/issues/252. We use a mockPortal.js for our tests internally.

i have the same problem

I can confirm with @oliviertassinari that mockPortal.js solves this issue. (not all issues trying to snapshot a material-ui dialog, but this particular one)

Another mocking solution that works is the following

jest.mock('rc-util/lib/Portal')

(add this to the top of the test file)

Obviously the rc-util package is needed for this.

@Skaronator Enzyme do not support the portal API of React: airbnb/enzyme#252. We use a mockPortal.js for our tests internally.

Enzyme now supports the portal API of React. We have removed our mockPortal.js module.

@reyronald We don't rely on rc-util/lib/Portal. We have been using our own Portal component: https://material-ui-next.com/utils/portal/.

Enzyme now supports the portal API of React.

Oh cool - didn't know that. They didn't even close the issue on thier repo.

package.json

"material-ui": "1.0.0-beta.42",
"enzyme": "3.3.0",
"enzyme-adapter-react-16": "1.1.1",
"react-test-renderer": "16.3.2",

Test

import React from 'react';
import renderer from 'react-test-renderer';
import { BtnIcon } from 'Components/buttons/btn-icon';

const buttonIcon = <i className="fa fa-close" />;

describe('BtnIcon should match Snapshot', () => {
  it('without hint', () => {
    const renderedValue = renderer.create(
      <BtnIcon>{buttonIcon}</BtnIcon>
    ).toJSON();
    expect(renderedValue).toMatchSnapshot();
  });
});

btn-icon.js

import React from 'react';
import PropTypes from 'prop-types';
import IconButton from 'material-ui/IconButton';
import Hint from 'Components/hint';

const BtnIcon = ({
  children, className, onClick, disabled, hint, hintPlacement, wrapperClassName
}) => (
  <Hint
    text={hint}
    placement={hintPlacement}
    className="tooltip-popper--btn-icon"
    wrapperClassName={wrapperClassName}
  >
    <div> {/* Little hack for disabled button inside */}
      <IconButton
        className={`btn btn-icon ${className} ${disabled ? 'disabled' : ''}`}
        onClick={onClick}
        disabled={disabled}
        tabIndex={-1}
      >
        {children}
      </IconButton>
    </div>
  </Hint>
);

BtnIcon.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.object
  ]).isRequired,
  className: PropTypes.string,
  onClick: PropTypes.func,
  disabled: PropTypes.bool,
  hint: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.object
  ]),
  hintPlacement: PropTypes.string,
  wrapperClassName: PropTypes.string
};

BtnIcon.defaultProps = {
  className: '',
  onClick: null,
  disabled: false,
  hint: '',
  wrapperClassName: '',
  hintPlacement: undefined
};

export { BtnIcon };

hint.js - tooltip

import React from 'react';
import PropTypes from 'prop-types';
import Tooltip from 'material-ui/Tooltip';
import './hint.scss';

const Hint = props => (
  <Tooltip
    title={props.text}
    placement={props.placement}
    classes={{
      root: `hint-root ${props.wrapperClassName}`,
      popper: `hint-popper ${props.className}`,
      tooltip: 'hint__text',
      open: 'opened',
      tooltipPlacementLeft: 'left',
      tooltipPlacementRight: 'right',
      tooltipPlacementTop: 'top',
      tooltipPlacementBottom: 'bottom'
    }}
  >
    {props.children}
  </Tooltip>
);

Hint.propTypes = {
  children: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.object
  ]).isRequired,
  text: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.object
  ]),
  placement: PropTypes.string,
  className: PropTypes.string,
  wrapperClassName: PropTypes.string
};

Hint.defaultProps = {
  wrapperClassName: '',
  className: '',
  text: '',
  placement: 'top'
};

export default Hint;

And I'm getting the same error:

FAIL  __tests__/components/buttons/btn-icon/btn-icon.test.js
  BtnIcon should match Snapshot
    ✕ without hint (96ms)

  ● BtnIcon should match Snapshot › without hint

    TypeError: parentInstance.children.indexOf is not a function

at appendChild (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8565:39)
      at commitPlacement (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5779:13)
      at commitAllHostEffects (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6946:13)
      at HTMLUnknownElement.callCallback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:906:14)
      at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:193:27)
      at HTMLUnknownElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:119:9)
      at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:82:17)
      at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/nodes/HTMLElement-impl.js:30:27)
      at HTMLUnknownElement.dispatchEvent (node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:157:21)
      at Object.invokeGuardedCallbackDev (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:944:16)
      at invokeGuardedCallback (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:993:29)
      at commitRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7122:9)
      at completeRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8065:36)
      at performWorkOnRoot (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8015:11)
      at performWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7933:9)
      at performSyncWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7910:5)
      at requestWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7810:7)
      at scheduleWorkImpl (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7685:13)
      at scheduleWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7645:12)
      at scheduleRootUpdate (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8273:5)
      at updateContainerAtExpirationTime (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8301:12)
      at Object.updateContainer (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:8328:14)
      at Object.create (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:9009:18)
      at Object.<anonymous> (__tests__/components/buttons/btn-icon/btn-icon.test.js:27:53)

console.error node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5206
    The above error occurred in the <Popper> component:
        in Popper (created by Tooltip)
        in Portal (created by Tooltip)
        in Manager (created by Tooltip)
        in Tooltip (created by WithStyles(Tooltip))
        in WithStyles(Tooltip) (created by Hint)
        in Hint (created by BtnIcon)
        in BtnIcon

    Consider adding an error boundary to your tree to customize error handling behavior.
    Visit https://fb.me/react-error-boundaries to learn more about error boundaries.

  console.error node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5206
    The above error occurred in the <div> component:
        in div (created by Popper)
        in Popper (created by Tooltip)
        in Portal (created by Tooltip)
        in Manager (created by Tooltip)
        in Tooltip (created by WithStyles(Tooltip))
        in WithStyles(Tooltip) (created by Hint)
        in Hint (created by BtnIcon)
        in BtnIcon

    Consider adding an error boundary to your tree to customize error handling behavior.
    Visit https://fb.me/react-error-boundaries to learn more about error boundaries.

  console.error node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5206
    The above error occurred in the <BtnIcon> component:
        in BtnIcon

    Consider adding an error boundary to your tree to customize error handling behavior.
    Visit https://fb.me/react-error-boundaries to learn more about error boundaries.

How can I solve this issue while we are waiting for a new enzyme (enzyme that will support portals)?

Solved it with - import { createMount } from 'material-ui/test-utils'; I hope it will be helpful for someone.
Also you can use simple { mount } - import { mount } from 'enzyme';
But it will not render Tooltip at snapshot. Enzyme release new version with supporting Portals soon, I hope.

Example:

import React from 'react';
import { createMount } from 'material-ui/test-utils';
import { BtnIcon } from 'Components/buttons/btn-icon';

describe('BtnIcon should match Snapshot', () => {
  it('without hint', () => {
    const renderedValue = createMount()(
      // Code of this component - <BtnIcon /> you can find above
      <BtnIcon>{buttonIcon}</BtnIcon>
    );
    expect(renderedValue.html()).toMatchSnapshot();
  });
});

I still have this issue using material-ui and react-test-renderer 16.13.0.

Is the solution here to use createMount from material-ui/test-utils?

I would like to continue to use react-test-renderer.

const tree = renderer.create(<MyComp />);
Was this page helpful?
0 / 5 - 0 ratings