Material-ui: Unit Tests fail for components using withStyles

Created on 12 Apr 2017  路  3Comments  路  Source: mui-org/material-ui

Problem description

I have written serveral components using the code in the next branch and have styled them using withStyles. Everything works as expected expect for unit testing.

The output below are from my attempt at converting a Material-UI component in the next branch to use withStyles

Using the default method of static context types the base nope of the wrapper looks like:

ShallowWrapper {
  root: [Circular],
  unrendered: 
   { '$$typeof': Symbol(react.element),
     type: 
      { [Function: AppBar]
        propTypes: [Object],
        defaultProps: [Object],
        contextTypes: [Object] },
     key: null,
     ref: null,
     props: { children: 'Hello World', accent: false },
     _owner: null,
     _store: {} },
  renderer: 
   ReactShallowRenderer {
     _instance: 
      ShallowComponentWrapper {
        _debugID: 186,
        _currentElement: [Object],
        _rootNodeID: 0,
        _compositeType: 0,
        _instance: [Object],
        _hostParent: null,
        _hostContainerInfo: null,
        _updateBatchNumber: null,
        _pendingElement: null,
        _pendingStateQueue: null,
        _pendingReplaceState: false,
        _pendingForceUpdate: false,
        _renderedNodeType: 1,
        _renderedComponent: [Object],
        _context: [Object],
        _mountOrder: 2,
        _topLevelWrapper: null,
        _pendingCallbacks: null,
        _calledComponentWillUnmount: false,
        _warnedAboutRefsInRender: false },
     render: [Function: render],
     getRenderOutput: [Function: getRenderOutput] },
  node: 
   { '$$typeof': Symbol(react.element),
     type: 
      { [Function: Paper]
        propTypes: [Object],
        defaultProps: [Object],
        contextTypes: [Object] },
     key: null,
     ref: null,
     props: 
      { square: true,
        elevation: 4,
        className: 'MuiAppBar-appBar-1743332029 MuiAppBar-primary-15947162',
        children: 'Hello World' },
     _owner: null,
     _store: {} },
  nodes: 
   [ { '$$typeof': Symbol(react.element),
       type: [Object],
       key: null,
       ref: null,
       props: [Object],
       _owner: null,
       _store: {} } ],
  length: 1,
  options: { context: { theme: [Object], styleManager: [Object] } },
  complexSelector: 
   ComplexSelector {
     buildPredicate: [Function: buildPredicate],
     findWhereUnwrapped: [Function: findWhereUnwrapped],
     childrenOfNode: [Function: childrenOfNode] } }

As you can see the class name is a string of classes

Using withStyles the wrapper output looks like:

ShallowWrapper {
  root: [Circular],
  unrendered: 
   { '$$typeof': Symbol(react.element),
     type: { [Function: WithStyle] contextTypes: [Object], displayName: 'withStyles(AppBar)' },
     key: null,
     ref: null,
     props: { children: 'Hello World' },
     _owner: null,
     _store: {} },
  renderer: 
   ReactShallowRenderer {
     _instance: 
      ShallowComponentWrapper {
        _debugID: 186,
        _currentElement: [Object],
        _rootNodeID: 0,
        _compositeType: 2,
        _instance: [Object],
        _hostParent: null,
        _hostContainerInfo: null,
        _updateBatchNumber: null,
        _pendingElement: null,
        _pendingStateQueue: null,
        _pendingReplaceState: false,
        _pendingForceUpdate: false,
        _renderedNodeType: 1,
        _renderedComponent: [Object],
        _context: [Object],
        _mountOrder: 2,
        _topLevelWrapper: null,
        _pendingCallbacks: null,
        _calledComponentWillUnmount: false,
        _warnedAboutRefsInRender: false },
     render: [Function: render],
     getRenderOutput: [Function: getRenderOutput] },
  node: 
   { '$$typeof': Symbol(react.element),
     type: { [Function: AppBar] propTypes: [Object], defaultProps: [Object] },
     key: null,
     ref: null,
     props: { classes: [Object], children: 'Hello World', accent: false },
     _owner: null,
     _store: {} },
  nodes: 
   [ { '$$typeof': Symbol(react.element),
       type: [Object],
       key: null,
       ref: null,
       props: [Object],
       _owner: null,
       _store: {} } ],
  length: 1,
  options: { context: { theme: [Object], styleManager: [Object] } },
  complexSelector: 
   ComplexSelector {
     buildPredicate: [Function: buildPredicate],
     findWhereUnwrapped: [Function: findWhereUnwrapped],
     childrenOfNode: [Function: childrenOfNode] } }

Link to minimal working code that reproduces the issue

Here is a sample component:

// Core
import React, { PropTypes } from 'react'
import classnames from 'classnames'
import { createStyleSheet, withStyles } from 'material-ui/styles'
import { amber, darkWhite, green, red } from 'material-ui/styles/colors'

// Components
import { Layout } from 'material-ui'

export const styles = createStyleSheet('CpiColorScale', theme => {
  const { spacing } = theme

  return {
    root: {
      margin: spacing.unit
    },
    cell: {
      minWidth: spacing.unit * 8,
      padding: spacing.unit * 0.4
    },
    high: {
      backgroundColor: red[500]
    },
    low: {
      backgroundColor: green[500]
    },
    medium: {
      backgroundColor: amber[500]
    },
    text: {
      color: darkWhite,
      fontSize: 14,
      margin: 0
    }
  }
})

const CpiColorScale = ({ classes, thresholdLow = '0-2%', thresholdMid = '2-4%', thresholdHigh = '5%+', ...other }) => (
  <div className={ classes.root } { ...other }>
    <Layout container align='center' direction='row'>
      <div className={ classnames(classes.cell, classes.low) }>
        <span className={ classes.text }>{ thresholdLow }</span>
      </div>
      <div className={ classnames(classes.cell, classes.medium) }>
        <span className={ classes.text }>{ thresholdMid }</span>
      </div>
      <div className={ classnames(classes.cell, classes.high) }>
        <span className={ classes.text }>{ thresholdHigh }</span>
      </div>
    </Layout>
  </div>
)

CpiColorScale.propTypes = {
  classes: PropTypes.object,
  thresholdLow: PropTypes.string,
  thresholdMid: PropTypes.string,
  thresholdHigh: PropTypes.string
}

export default withStyles(styles)(CpiColorScale)

Here is its unit test:

// Core
import React from 'react'
import { assert } from 'chai'
import { createShallow } from 'material-ui/test-utils'

// Components
import CpiColorScale, { styles } from './CpiColorScale'

describe('CpiColorScale - Component', () => {
  let classes
  let shallow

  before(()=> {
    shallow = createShallow()
    classes = shallow.context.styleManager.render(styles)
  })

  it('should have props', () => {
    const wrapper = shallow(<CpiColorScale thresholdLow='0-2%' thresholdMid='2-4%' thresholdHigh='5%+' />)

    assert.strictEqual(wrapper.props().thresholdLow, '0-2%')
    assert.strictEqual(wrapper.props().thresholdMid, '2-4%')
    assert.strictEqual(wrapper.props().thresholdHigh, '5%+')
  })

  it('should render', () => {
    const wrapper = shallow(<CpiColorScale />)

    assert.strictEqual(wrapper.hasClass(classes.root), true)
  })
})

Versions

  • Material-UI: next
  • React: 15.5.3
  • Browser: NA
question test

All 3 comments

Questions would be best suited asked on StackOveflow.

You need to shallow render some level deeper in the tree. Enzyme exposes a .dive() helper to do so.
At @doctolib, we also ended up writing a .until() enzyme operator to do it recursively:

function shallowRecursively(wrapper, selector, { context }) {
  if (wrapper.isEmptyRender() || typeof wrapper.node.type === 'string') {
    return wrapper
  }

  const instance = wrapper.root.instance()
  if (instance.getChildContext) {
    context = {
      ...context,
      ...instance.getChildContext(),
    }
  }

  const nextWrapper = wrapper.shallow({ context })

  if (selector && wrapper.is(selector)) {
    return nextWrapper
  }

  return shallowRecursively(nextWrapper, selector, { context })
}

export default function until(selector, { context } = this.options) {
  return this.single('until', () => shallowRecursively(this, selector, { context }))
}

Tips, use console.log(wrapper.debug()) to get a better view of the actual tree, that would have made the issue description easier to understand.

Thanks for the follow up. Dive was exactly what I needed.

Was this page helpful?
0 / 5 - 0 ratings