gotwarlost / istanbul

Yet another JS code coverage tool that computes statement, line, function and branch coverage with module loader hooks to transparently add coverage when running tests. Supports all JS coverage use cases including unit tests, server side functional tests and browser tests. Built for scale.
Other
8.7k stars 786 forks source link

Branches that don't exist get reported as missing #941

Open throoze opened 3 years ago

throoze commented 3 years ago

I'm getting a coverage report with missing coverage for branches that aren't there (I think):

Screen Shot 2021-02-23 at 00 39 06

Here's the test:

/**
 * @jest-environment jsdom
 */
import React from 'react';
import {mount} from 'enzyme';
import {StyledSnackbar, StyledText} from './snackbar.style';

jest.mock('react-native-paper', () => ({
  Colors: jest.requireActual('react-native-paper').Colors,
  Snackbar: () => 'Snackbar',
}));

describe('snackbar.style', () => {
  describe('StyledSnackbar', () => {
    test.each`
      variantName  | variant
      ${'error'}   | ${'error'}
      ${'warning'} | ${'warn'}
      ${'info'}    | ${'info'}
      ${'default'} | ${'default'}
    `('should match snapshot for variant $variantName', ({variant}) => {
      const component = mount(<StyledSnackbar variant={variant} />);
      expect(component.exists()).toBeTruthy();
      expect(component).toMatchSnapshot();
    });
  });
  describe('StyledText', () => {
    test.each`
      variantName  | variant
      ${'error'}   | ${'error'}
      ${'warning'} | ${'warn'}
      ${'info'}    | ${'info'}
      ${'default'} | ${'default'}
    `('should match snapshot for variant $variantName', ({variant}) => {
      const component = mount(<StyledText variant={variant} />);
      expect(component.exists()).toBeTruthy();
      expect(component).toMatchSnapshot();
    });
  });
});

Another example:

Screen Shot 2021-02-23 at 00 30 17

And its test:

import React from 'react';
import {shallow} from 'enzyme';
import {Snackbar} from './snackbar';
import {StyledSnackbar, StyledText} from './snackbar.style';

describe('Snackbar', () => {
  it('should render correctly with default props', () => {
    const component = shallow(<Snackbar>Children</Snackbar>);
    expect(component.exists()).toBeTruthy();
    expect(component.text()).toEqual('Children');
    expect(component.find(StyledSnackbar).prop('variant')).toEqual('default');
    expect(component.find(StyledText).prop('variant')).toEqual('default');
  });

  test.each`
    variantName  | variant
    ${'error'}   | ${'error'}
    ${'warning'} | ${'warn'}
    ${'info'}    | ${'info'}
    ${'default'} | ${'default'}
  `('should render correctly with variant $variantName', ({variant}) => {
    const component = shallow(<Snackbar {...{variant}}>Children</Snackbar>);
    expect(component.exists()).toBeTruthy();
    expect(component.text()).toEqual('Children');
    expect(component.find(StyledSnackbar).prop('variant')).toEqual(variant);
    expect(component.find(StyledText).prop('variant')).toEqual(variant);
  });
});

I haven't been able to test anything with toHaveStyleRule and jest-styled-components because of some issue with my configuration and/or mismatching versions, but still i thing that file should be totally covered

Here's my configuration in case I'm missing something:

package.json:

{
  "dependencies": {
    "@react-native-community/masked-view": "^0.1.10",
    "@react-native-firebase/app": "^10.8.0",
    "@react-native-firebase/auth": "^10.8.0",
    "@react-navigation/drawer": "^5.12.3",
    "@react-navigation/native": "^5.9.2",
    "@react-navigation/stack": "^5.14.2",
    "formik": "^2.2.6",
    "react": "16.13.1",
    "react-native": "0.63.4",
    "react-native-gesture-handler": "^1.10.1",
    "react-native-paper": "^4.7.1",
    "react-native-reanimated": "^1.13.2",
    "react-native-safe-area-context": "^3.1.9",
    "react-native-screens": "^2.17.1",
    "react-native-svg": "^12.1.0",
    "react-native-vector-icons": "^8.0.0",
    "styled-components": "^5.2.1",
    "yup": "^0.32.9"
  },
  "devDependencies": {
    "@babel/core": "^7.8.4",
    "@babel/runtime": "^7.8.4",
    "@react-native-community/eslint-config": "^1.1.0",
    "babel-jest": "^25.1.0",
    "enzyme": "^3.11.0",
    "enzyme-adapter-react-16": "^1.15.6",
    "enzyme-to-json": "^3.6.1",
    "eslint": "^6.5.1",
    "jest": "^25.1.0",
    "jest-environment-enzyme": "^7.1.2",
    "jest-enzyme": "^7.1.2",
    "jest-styled-components": "^7.0.3",
    "metro-react-native-babel-preset": "^0.59.0",
    "react-dom": "16.13.1",
    "react-native-svg-transformer": "^0.14.3",
    "react-test-renderer": "16.13.1"
  },
  "jest": {
    "preset": "react-native",
    "transform": {
      "^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
    },
    "testEnvironment": "enzyme",
    "testEnvironmentOptions": {
      "enzymeAdapter": "react16"
    },
    "collectCoverage": true,
    "collectCoverageFrom": [
      "<rootDir>/src/**/**.js"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 90,
        "functions": 90,
        "lines": 90,
        "statements": 90
      }
    },
    "setupFilesAfterEnv": [
      "jest-enzyme",
      "<rootDir>/jest.setup.js",
      "<rootDir>/node_modules/react-native-gesture-handler/jestSetup.js"
    ],
    "transformIgnorePatterns": [
      "node_modules/(?!(jest-)?react-native|@?react-navigation)"
    ]
  },
  "resolutions": {
    "styled-components": "^5"
  }
}

babel.config.js:

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  env: {
    production: {
      plugins: ['react-native-paper/babel'],
    },
  },
};

jest.setup.js:

import 'react-native-gesture-handler/jestSetup';
import 'jest-styled-components/native'; // doesn't work!
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

const {JSDOM} = require('jsdom');

const jsdom = new JSDOM();
const {window} = jsdom;

function copyProps(src, target) {
  const props = Object.getOwnPropertyNames(src)
    .filter((prop) => typeof target[prop] === 'undefined')
    .map((prop) => Object.getOwnPropertyDescriptor(src, prop));
  Object.defineProperties(target, props);
}

global.window = window;
global.document = window.document;
global.navigator = {
  userAgent: 'node.js',
};

copyProps(window, global);

jest.mock('react-native-reanimated', () => {
  const Reanimated = require('react-native-reanimated/mock');

  // The mock for `call` immediately calls the callback which is incorrect
  // So we override it with a no-op
  Reanimated.default.call = () => {};

  return Reanimated;
});
import {NativeModules} from 'react-native';

NativeModules.RNGestureHandlerModule = {
  attachGestureHandler: jest.fn(),
  createGestureHandler: jest.fn(),
  dropGestureHandler: jest.fn(),
  updateGestureHandler: jest.fn(),
  State: {},
  Directions: {},
};
NativeModules.ImagePickerManager = {
  showImagePicker: jest.fn(),
};
NativeModules.Linking = {
  canOpenUrl: jest.fn().mockResolvedValue(true),
  openUrl: jest.fn().mockResolvedValue(true),
};
NativeModules.Platform = {
  OS: 'iOS',
};

jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');
jest.mock('react-native/Libraries/Animated/src/animations/TimingAnimation');

Enzyme.configure({adapter: new Adapter()});

Any help would be appreciated! Please let me know if I'm wrong and why are there any branches there... Thanks a lot!