react-starter-kit: Testing: TypeError: Cannot read property 'apply' of undefined at StyledComponent.componentWillMount

After setting up tests with Jest and getting through the auto-mocking issues described in #264, I’m getting:

TypeError: Cannot read property 'apply' of undefined at StyledComponent.componentWillMount (decorators/withStyles.js:19:50)

The specific line in the withStyles decorator causing the error is:

this.removeCss = this.context.insertCss.apply(undefined, styles);

insertCss is undefined. How can the context be passed into the decorator for testing?

<bountysource-plugin>

Want to back this issue? Post a bounty on it! We accept bounties via Bountysource. </bountysource-plugin>

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 2
  • Comments: 17

Most upvoted comments

I resolved this in the following way: I modified package.json in accordance with the jest docs by adding this:

  "jest": {
    "moduleNameMapper": {
      "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js",
      "\\.(css|less)$": "identity-obj-proxy",
      "isomorphic-style-loader/lib/withStyles": "<rootDir>/tools/mocks/withStyles.js"
    }
  }

Notice that I’ve appended a moduleNameMapper for isomorphic-style-loader/lib/withStyles. I then created the specified file: I created a mocks folder in the same folder as the webpack config (tools). I created a file in that folder called withStyles.js. It has the following content:

module.exports = () => component => component

Beside exporting the component with withStyles decorator, we need to export it as a stand alone.This will solve the issue

export class GridView extends Component { ... }

export default withStyles(s)(GridView);

And in test component use standalone one

import GridView from './GridView';

and in other cases use default one

import { GridView } from './GridView';

For now, I patched it with

import emptyFunction from '../../node_modules/fbjs/lib/emptyFunction';

...

    const insertCss = this.context.insertCss || emptyFunction;
    this.removeCss = insertCss.apply(undefined, styles);

If you want to add storybook to the project:

inside .storybook/ create a new file called ContextProvider.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';

export default class ContextProvider extends Component {
  static propTypes = {
    children: PropTypes.node.isRequired,
    context: PropTypes.object.isRequired,
  };

  static contextTypes = {
    insertCss: PropTypes.func,
  };

  static childContextTypes = {
    insertCss: PropTypes.func.isRequired,
  };

  getChildContext() {
    return this.props.context;
  }

  render() {
    return this.props.children;
  }
}

update .storybook/webpack.config.js

require('babel-register');

const config = require('../tools/webpack.config.js').default[0];

module.exports = {
  plugins: config.plugins.filter(
    (plugin) => ['DefinePlugin', 'ProvidePlugin', 'ExtractTextPlugin'].includes(plugin.constructor.name)
  ),
  devtool: 'source-map',
  resolve: config.resolve,
  module: {
    loaders: config.module.loaders,
    rules: config.module.rules
  }
}

and finally, inside .storybook/config.js

import React from 'react';
import PropTypes from 'prop-types';
import { configure, addDecorator } from '@storybook/react';
import ContextProvider from './ContextProvider';

function loadStories() {
  require('../stories');
}

const context = {
  insertCss: (...styles) => {
    // eslint-disable-next-line no-underscore-dangle
    const removeCss = styles.map(x => x._insertCss());
    return () => {
      removeCss.forEach(f => f());
    };
  },
};

addDecorator(story =>
  <ContextProvider context={context}>
    <div>
      {story()}
    </div>
  </ContextProvider>,
);

configure(loadStories, module);

does anyone have a working Storybook example with this boilerplate?

@bhargavigundaa’s approach works for me fine on classes, but I on functional components where I export const GridView = ({ foo, bar }) =>

I still run into the same apply of undefined.

This worked for me:

describe('Matrix', () => {

  it('renders numbers', () => {
    const wrapper = render(
      <App context={{ insertCss: () => {} }}>
        <Matrix values={[[1,2,3,4],[5,6,7,8],[9,10,11,12]]}>
        </Matrix>
      </App>
    );
    console.log(wrapper.html())
    expect(wrapper.find('div.child').length).to.eq(1);
  });
});