Jest: Coverage 100% but not everything is invoked

Created on 26 Aug 2016  路  16Comments  路  Source: facebook/jest

Hi

I'm checking my coverage reports for a given component and I was surprised to hit a 100% for it. Here is the component:

// (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP

import React, { Component, PropTypes } from 'react';
import classnames from 'classnames';
import Header from './Header';
import ListItem from './ListItem';
import TabNextIcon from './icons/base/TabNext';
import Collapsible from './Collapsible';

import CSSClassnames from '../utils/CSSClassnames';

const CLASS_ROOT = CSSClassnames.ACCORDION_PANEL;

export default class AccordionPanel extends Component {
  constructor(props, context) {
    super(props, context);
    this._onClickPanel = this._onClickPanel.bind(this);
    this.state = {
      active: props.active || false
    };
  }

  componentWillReceiveProps (nextProps) {
    if (this.props.active !== nextProps.active) {
      this.setState({ active: nextProps.active });
    }
  }

  _onClickPanel () {
    this.setState({ active : !this.state.active });
    this.props.onActive();
  }

  render () {
    const { animate, className, children, heading } = this.props;

    const classes = classnames(
      CLASS_ROOT,
      className,
      {
        [`${CLASS_ROOT}--active`]: this.state.active
      }
    );

    return (
      <ListItem className={classes} direction="column" pad="none">
        <Header
          role="tab"
          className={`${CLASS_ROOT}__header`}
          pad={{horizontal: 'medium', vertical: 'small'}}
          full="horizontal"
          direction="row"
          justify="between"
          align="center"
          onClick={this._onClickPanel}
          responsive={false}
        >
          {heading}
          <TabNextIcon className={`${CLASS_ROOT}__control`} />
        </Header>
        <Collapsible
          role="tabpanel"
          active={this.state.active}
          animate={animate}
        >
          {children}
        </Collapsible>
      </ListItem>
    );
  }
};

AccordionPanel.propTypes = {
  active: PropTypes.bool,
  animate: PropTypes.bool,
  heading: PropTypes.node.isRequired,
  onActive: PropTypes.func
};

componentWillReceiveProps and _onClickPanel were never invoked in my test, so believe we should not be at 100%. Here is my test using the snapshot strategy:

// (C) Copyright 2014-2015 Hewlett-Packard Development Company, L.P.

import React from 'react';
import renderer from 'react/lib/ReactTestRenderer';

import Accordion from '../../src/js/components/Accordion';
import AccordionPanel from '../../src/js/components/AccordionPanel';

// needed because this:
// https://github.com/facebook/jest/issues/1353
jest.mock('react-dom');

describe('Accordion', () => {
  it('has correct default options', () => {
    const component = renderer.create(
      <Accordion>
        <AccordionPanel heading="First Title">
          <p>test</p>
        </AccordionPanel>
      </Accordion>
    );
    let tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  });
  it('has correct openMulti=true rendering', () => {
    const component = renderer.create(
      <Accordion openMulti>
        <AccordionPanel active heading="First Title">
          <p>test 1</p>
        </AccordionPanel>
        <AccordionPanel active heading="Second Title">
          <p>test 2</p>
        </AccordionPanel>
      </Accordion>
    );
    let tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  });
  it('has correct initialIndex={0} rendering', () => {
    const component = renderer.create(
      <Accordion initialIndex={0}>
        <AccordionPanel active heading="First Title">
          <p>test 1</p>
        </AccordionPanel>
        <AccordionPanel active heading="Second Title">
          <p>test 2</p>
        </AccordionPanel>
      </Accordion>
    );
    let tree = component.toJSON();
    expect(tree).toMatchSnapshot();
  });
});

Here is a print screen of the report results. It is only counting 16 lines of code, where my component has 54 lines. I guess this could be the reason for the issue. It seems that nothing inside the component is being instrumented.

screen shot 2016-08-26 at 11 54 13 am

All 16 comments

it seems like it doesn't have any coverage data for anything inside the class definition, which is weird. Thanks for reporting this! i'll investigate

hey @alansouzati
to understand what exactly is going on with this file what we can do is
add a line right before this line https://github.com/facebook/jest/blob/master/packages/jest-runtime/src/transform.js#L328

with something like
filename.match('AccordionPanel') && console.log(wrappedResult);

then run jest --coverage --no-cache and see the final source of this file with all instrumentation added to the source

trying that now

({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){/* istanbul ignore next */'use strict'; /* istanbul ignore next */var __cov_udPmu$ouzqJ8kgvNrunNVZZV6Os = function () {var path = '/Users/alansouza/grommet-workspace/grommet/src/js/components/AccordionPanel.js',hash = 'f1f5429525dad50aaba42fdf7c7e235f23b3f125',global = new Function('return this')(),gcv = '__coverage__',coverageData = { path: '/Users/alansouza/grommet-workspace/grommet/src/js/components/AccordionPanel.js', statementMap: { '1': { start: { line: 1, column: 39 }, end: { line: 1, column: 101 } }, '2': { start: { line: 1, column: 124 }, end: { line: 1, column: 171 } }, '3': { start: { line: 1, column: 195 }, end: { line: 1, column: 235 } }, '4': { start: { line: 1, column: 258 }, end: { line: 1, column: 314 } }, '5': { start: { line: 1, column: 338 }, end: { line: 1, column: 377 } }, '6': { start: { line: 1, column: 401 }, end: { line: 1, column: 448 } }, '7': { start: { line: 1, column: 472 }, end: { line: 1, column: 512 } }, '8': { start: { line: 1, column: 533 }, end: { line: 1, column: 577 } }, '9': { start: { line: 1, column: 598 }, end: { line: 1, column: 635 } }, '10': { start: { line: 1, column: 670 }, end: { line: 1, column: 728 } }, '11': { start: { line: 1, column: 763 }, end: { line: 1, column: 814 } }, '12': { start: { line: 1, column: 832 }, end: { line: 1, column: 873 } }, '13': { start: { line: 1, column: 891 }, end: { line: 1, column: 925 } }, '14': { start: { line: 10, column: 207 }, end: { line: 10, column: 261 } }, '15': { start: { line: 12, column: 43 }, end: { line: 12, column: 82 } }, '16': { start: { line: 73, column: 0 }, end: { line: 77, column: 62 } } }, fnMap: { '1': { name: '_interopRequireDefault', decl: { start: { line: 10, column: 178 }, end: { line: 10, column: 200 } }, loc: { start: { line: 10, column: 206 }, end: { line: 10, column: 262 } } } }, branchMap: { '1': { loc: { start: { line: 10, column: 214 }, end: { line: 10, column: 260 } }, type: 'cond-expr', locations: [{ start: { line: 10, column: 238 }, end: { line: 10, column: 241 } }, { start: { line: 10, column: 244 }, end: { line: 10, column: 260 } }] }, '2': { loc: { start: { line: 10, column: 214 }, end: { line: 10, column: 235 } }, type: 'binary-expr', locations: [{ start: { line: 10, column: 214 }, end: { line: 10, column: 217 } }, { start: { line: 10, column: 221 }, end: { line: 10, column: 235 } }] } }, s: { '1': 0, '2': 0, '3': 0, '4': 0, '5': 0, '6': 0, '7': 0, '8': 0, '9': 0, '10': 0, '11': 0, '12': 0, '13': 0, '14': 0, '15': 0, '16': 0 }, f: { '1': 0 }, b: { '1': [0, 0], '2': [0, 0] } },coverage = global[gcv] || (global[gcv] = {});if (coverage[path] && coverage[path].hash === hash) {return coverage[path];}coverageData.hash = hash;return coverage[path] = coverageData;}();++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['1'];Object.defineProperty(exports, "__esModule", { value: true });var _defineProperty2 = /* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['2'], require('babel-runtime/helpers/defineProperty'));var _defineProperty3 = /* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['3'], _interopRequireDefault(_defineProperty2));var _getPrototypeOf = /* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['4'], require('babel-runtime/core-js/object/get-prototype-of'));var _getPrototypeOf2 = /* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['5'], _interopRequireDefault(_getPrototypeOf));var _classCallCheck2 = /* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['6'], require('babel-runtime/helpers/classCallCheck'));var _classCallCheck3 = /* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['7'], _interopRequireDefault(_classCallCheck2));var _createClass2 = /* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['8'], require('babel-runtime/helpers/createClass'));var _createClass3 = /* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['9'], _interopRequireDefault(_createClass2));var _possibleConstructorReturn2 = /* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['10'], require('babel-runtime/helpers/possibleConstructorReturn'));var _possibleConstructorReturn3 = /* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['11'], _interopRequireDefault(_possibleConstructorReturn2));var _inherits2 = /* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['12'], require('babel-runtime/helpers/inherits'));var _inherits3 = /* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['13'], _interopRequireDefault(_inherits2));

var /* istanbul ignore next */_react = require('react'); /* istanbul ignore next */var _react2 = _interopRequireDefault(_react);
var /* istanbul ignore next */_classnames2 = require('classnames'); /* istanbul ignore next */var _classnames3 = _interopRequireDefault(_classnames2);
var /* istanbul ignore next */_Header = require('./Header'); /* istanbul ignore next */var _Header2 = _interopRequireDefault(_Header);
var /* istanbul ignore next */_ListItem = require('./ListItem'); /* istanbul ignore next */var _ListItem2 = _interopRequireDefault(_ListItem);
var /* istanbul ignore next */_TabNext = require('./icons/base/TabNext'); /* istanbul ignore next */var _TabNext2 = _interopRequireDefault(_TabNext);
var /* istanbul ignore next */_Collapsible = require('./Collapsible'); /* istanbul ignore next */var _Collapsible2 = _interopRequireDefault(_Collapsible);

var /* istanbul ignore next */_CSSClassnames = require('../utils/CSSClassnames'); /* istanbul ignore next */var _CSSClassnames2 = _interopRequireDefault(_CSSClassnames);function _interopRequireDefault(obj) {/* istanbul ignore next */++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.f['1'];++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['14'];return (/* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.b['2'][0], obj) && /* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.b['2'][1], obj.__esModule) ? /* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.b['1'][0], obj) : /* istanbul ignore next */(++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.b['1'][1], { default: obj }));}

var CLASS_ROOT = /* istanbul ignore next */( /* istanbul ignore next */++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['15'], _CSSClassnames2.default.ACCORDION_PANEL); // (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP
/* istanbul ignore next */var
AccordionPanel = function (_Component) {(0, _inherits3.default)(AccordionPanel, _Component);
  function /* istanbul ignore next */AccordionPanel(props, context) {/* istanbul ignore next */(0, _classCallCheck3.default)(this, AccordionPanel);var _this = (0, _possibleConstructorReturn3.default)(this, (AccordionPanel.__proto__ || (0, _getPrototypeOf2.default)(AccordionPanel)).call(this,
    props, context));
    /* istanbul ignore next */_this._onClickPanel = /* istanbul ignore next */_this._onClickPanel.bind( /* istanbul ignore next */_this);
    /* istanbul ignore next */_this.state = {
      active: props.active || false }; /* istanbul ignore next */return _this;

  }(0, _createClass3.default)(AccordionPanel, [{ key: 'componentWillReceiveProps', value: function componentWillReceiveProps(

    nextProps) {
      if (this.props.active !== nextProps.active) {
        this.setState({ active: nextProps.active });
      }
    } }, { key: '_onClickPanel', value: function _onClickPanel()

    {
      this.setState({ active: !this.state.active });
      this.props.onActive();
    } }, { key: 'render', value: function render()

    {/* istanbul ignore next */var _props =
      this.props; /* istanbul ignore next */var animate = _props.animate; /* istanbul ignore next */var className = _props.className; /* istanbul ignore next */var children = _props.children; /* istanbul ignore next */var heading = _props.heading;

      var classes = /* istanbul ignore next */(0, _classnames3.default)(
      CLASS_ROOT,
      className, /* istanbul ignore next */(0, _defineProperty3.default)({},

      CLASS_ROOT + '--active', this.state.active));



      return (
        /* istanbul ignore next */_react2.default.createElement( /* istanbul ignore next */_ListItem2.default, /* istanbul ignore next */{ className: classes, direction: 'column', pad: 'none' },
        /* istanbul ignore next */_react2.default.createElement( /* istanbul ignore next */_Header2.default, /* istanbul ignore next */{
          role: 'tab',
          className: /* istanbul ignore next */CLASS_ROOT + '__header',
          pad: { horizontal: 'medium', vertical: 'small' },
          full: 'horizontal',
          direction: 'row',
          justify: 'between',
          align: 'center',
          onClick: this._onClickPanel,
          responsive: false },

        heading,
        /* istanbul ignore next */_react2.default.createElement( /* istanbul ignore next */_TabNext2.default, /* istanbul ignore next */{ className: /* istanbul ignore next */CLASS_ROOT + '__control' })),

        /* istanbul ignore next */_react2.default.createElement( /* istanbul ignore next */_Collapsible2.default, /* istanbul ignore next */{
          role: 'tabpanel',
          active: this.state.active,
          animate: animate },

        children)));



    } }]);return AccordionPanel;}(_react.Component); /* istanbul ignore next */AccordionPanel.displayName = 'AccordionPanel'; /* istanbul ignore next */exports.default = AccordionPanel;
; /* istanbul ignore next */++__cov_udPmu$ouzqJ8kgvNrunNVZZV6Os.s['16'];

AccordionPanel.propTypes = {
  active: /* istanbul ignore next */_react.PropTypes.bool,
  animate: /* istanbul ignore next */_react.PropTypes.bool,
  heading: /* istanbul ignore next */_react.PropTypes.node.isRequired,
  onActive: /* istanbul ignore next */_react.PropTypes.func }; /* istanbul ignore next */module.exports = exports['default'];
}});

unfortunately im not familiar with instanbul to inspect it myself

hey @cpojer
this is what i think is happening:

  1. There is a transformer for ES6 classes to ES5 functions
  2. We add /* istanbul ignore next */ to nodes added by babel
  3. the whole class definition gets ignored because of the added statement

i'm not even sure how to approach thing bug. can we configure which transforms can add /* istanbul ignore next */?

babel-jest adds this but I don't think we can customize it.

@alansouzati what is in your babelrc file?
we can try disabling babel-plugin-transform-es2015-classes plugin and see if it works

can you try this in your end?

import React from 'react';

var hi = "hi";

export default class Index extends React.Component {

  _onClickTest () {
    console.log('hi');
  }

  render () {
    return <div onClick={this._onClickTest}>{this.props.children}</div>;
  }
}

For the simple fact that I added var hi = "hi"; in between, the coverage stopped looking inside the class definition.

Im actually going to push the project I've created that reproduces the issue

https://github.com/alansouzati/jest-coverage-test

just run jest and check the coverage reports, I'm hoping you will be able to reproduce it

@cpojer is a fix for this in 15? I've been running into this too :(

We are unsure what exactly is happening here, this issue might be in Jest 15 :(

I have good news. The problem was happening because I was using [email protected] switching to babel-jest@test fixes the issue. I've updated jest-coverage-test to reflect the fix.

Thanks for being so responsive 馃帀

Oh that's great to know. We actually just published Jest 15: facebook.github.io/jest/blog/2016/09/01/jest-15.html

馃嵒

Was this page helpful?
0 / 5 - 0 ratings

Related issues

StephanBijzitter picture StephanBijzitter  路  3Comments

kgowru picture kgowru  路  3Comments

kentor picture kentor  路  3Comments

ianp picture ianp  路  3Comments

hramos picture hramos  路  3Comments