├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── CI.yml │ └── prCommentFlow.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── LICENSE ├── README.md ├── example ├── base-summary.json ├── head-summary.json └── runner.js ├── jest.config.js ├── package.json ├── src ├── __mocks__ │ ├── coverageDiffer.ts │ ├── diffChecker.ts │ └── resultFormatter.ts ├── __snapshots__ │ ├── coverageDiffer.test.ts.snap │ └── resultFormatter.test.ts.snap ├── common.ts ├── coverageDiffer.test.ts ├── coverageDiffer.ts ├── diffChecker.test.ts ├── diffChecker.ts ├── helpers.test.ts ├── helpers.ts ├── index.test.ts ├── index.ts ├── resultFormatter.test.ts ├── resultFormatter.ts ├── summaries.fixture.ts └── testHelpers.ts ├── tsconfig.build.json ├── tsconfig.json ├── typings └── markdown-table.d.ts └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | lib/ 3 | coverage/ 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['eslint:recommended', 'prettier'], 3 | env: { 4 | commonjs: true, 5 | node: true, 6 | es6: true, 7 | jest: true 8 | }, 9 | parserOptions: { 10 | ecmaVersion: 6, 11 | sourceType: 'module' 12 | }, 13 | parser: '@typescript-eslint/parser', 14 | plugins: ['@typescript-eslint'], 15 | rules: { 16 | '@typescript-eslint/no-unused-vars': 2 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: PR Test flow 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-node@v2 12 | with: 13 | node-version: '14' 14 | cache: 'yarn' 15 | - name: Install modules 16 | run: yarn 17 | - name: Run tests 18 | run: yarn test 19 | lint: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v2 23 | with: 24 | node-version: '14' 25 | cache: 'yarn' 26 | - name: Install modules 27 | run: yarn 28 | - name: Run lint 29 | run: yarn lint 30 | -------------------------------------------------------------------------------- /.github/workflows/prCommentFlow.yml: -------------------------------------------------------------------------------- 1 | name: PR Comment flow 2 | on: 3 | issue_comment: 4 | types: 5 | - created 6 | jobs: 7 | test: 8 | if: contains(github.event.comment.body, '/test') 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Github API Request 12 | id: request 13 | uses: octokit/request-action@v2.0.0 14 | with: 15 | route: ${{ github.event.issue.pull_request.url }} 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | - name: Checkout PR Branch 19 | uses: actions/checkout@v2 20 | with: 21 | token: ${{ secrets.GITHUB_TOKEN }} 22 | repository: ${{ fromJson(steps.request.outputs.data).head.repo.full_name }} 23 | ref: ${{ steps.pr_data.outputs.branch }} 24 | - uses: actions/setup-node@v2 25 | with: 26 | node-version: '14' 27 | cache: 'yarn' 28 | - name: Install modules 29 | run: yarn 30 | - name: Run tests 31 | run: yarn test 32 | lint: 33 | if: contains(github.event.comment.body, '/test') 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Github API Request 37 | id: request 38 | uses: octokit/request-action@v2.0.0 39 | with: 40 | route: ${{ github.event.issue.pull_request.url }} 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | - name: Checkout PR Branch 44 | uses: actions/checkout@v2 45 | with: 46 | token: ${{ secrets.GITHUB_TOKEN }} 47 | repository: ${{ fromJson(steps.request.outputs.data).head.repo.full_name }} 48 | ref: ${{ steps.pr_data.outputs.branch }} 49 | - uses: actions/setup-node@v2 50 | with: 51 | node-version: '14' 52 | cache: 'yarn' 53 | - name: Install modules 54 | run: yarn 55 | - name: Run lint 56 | run: yarn lint 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Optional npm cache directory 40 | .npm 41 | 42 | # Optional eslint cache 43 | .eslintcache 44 | 45 | # Optional REPL history 46 | .node_repl_history 47 | 48 | # Output of 'npm pack' 49 | *.tgz 50 | 51 | # Yarn Integrity file 52 | .yarn-integrity 53 | 54 | # dotenv environment variables file 55 | .env 56 | 57 | lib/ 58 | coverage/ 59 | docs/ 60 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | trailingComma: 'none' 4 | }; 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Flavius Gosu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coverage-diff 2 | 3 | ## What it does 4 | 5 | - Accepts base-summary.json and head-summary.json 6 | - Diffs the two jsons and outputs info about 7 | - Percentage of changed coverage (in changed/added files) 8 | - Formatted as markdown table 9 | - Fail if coverage decreases on a file (check on percentage) for any of the lines/func/statements/branches. Can 10 |  be configured to only check some criteria. 11 | - Separate threshold for new files `newFileCoverageThreshold`, defaults to `coverageThreshold` if not passed 12 | - Written in Typescript. 13 | 14 | ## Usage 15 | 16 | ```js 17 | import fs from 'fs'; 18 | import { diff as coverageDiff } from 'coverage-diff'; 19 | 20 | const base = JSON.parse(fs.readFileSync('./base-summary.json')); 21 | const head = JSON.parse(fs.readFileSync('./head-summary.json')); 22 | const diff = coverageDiff(base, head); 23 | 24 | console.log(diff.diff); 25 | console.log(diff.results); 26 | console.log(diff.regression); 27 | ``` 28 | 29 | Out: 30 | 31 | | Ok | File | Lines | Branches | Functions | Statements | 32 | | --- | ----- | ------------- | ------------- | ------------ | ------------- | 33 | | 🔴 | file1 | 80%
(+10%) | 14%
(-30%) | 3%
(+20%) | 20%
(-10%) | 34 | | ✅ | file2 | 20%
(+10%) | 8%
(-30%) | 2%
(-20%) | 5%
(-10%) | 35 | 36 | API at https://flaviusone.github.io/coverage-diff/ 37 | -------------------------------------------------------------------------------- /example/base-summary.json: -------------------------------------------------------------------------------- 1 | {"total": {"lines":{"total":1485,"covered":1339,"skipped":0,"pct":90.17},"statements":{"total":1560,"covered":1356,"skipped":0,"pct":86.92},"functions":{"total":419,"covered":371,"skipped":0,"pct":88.54},"branches":{"total":745,"covered":592,"skipped":0,"pct":79.46}} 2 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-apollo-proxy/src/fixtureLink.js": {"lines":{"total":7,"covered":6,"skipped":0,"pct":85.71},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":7,"covered":6,"skipped":0,"pct":85.71},"branches":{"total":6,"covered":4,"skipped":0,"pct":66.67}} 3 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-apollo-proxy/src/index.js": {"lines":{"total":25,"covered":24,"skipped":0,"pct":96},"functions":{"total":6,"covered":6,"skipped":0,"pct":100},"statements":{"total":25,"covered":24,"skipped":0,"pct":96},"branches":{"total":16,"covered":13,"skipped":0,"pct":81.25}} 4 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-config/src/config-templates.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 5 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-config/src/index.js": {"lines":{"total":31,"covered":29,"skipped":0,"pct":93.55},"functions":{"total":9,"covered":7,"skipped":0,"pct":77.78},"statements":{"total":31,"covered":29,"skipped":0,"pct":93.55},"branches":{"total":13,"covered":12,"skipped":0,"pct":92.31}} 6 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-config/src/log.js": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":5,"covered":5,"skipped":0,"pct":100},"branches":{"total":3,"covered":2,"skipped":0,"pct":66.67}} 7 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-context-proxy/src/index.js": {"lines":{"total":8,"covered":8,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":8,"covered":8,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 8 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-fetch-proxy/src/index.js": {"lines":{"total":21,"covered":21,"skipped":0,"pct":100},"functions":{"total":8,"covered":7,"skipped":0,"pct":87.5},"statements":{"total":22,"covered":21,"skipped":0,"pct":95.45},"branches":{"total":6,"covered":4,"skipped":0,"pct":66.67}} 9 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/connect-loader.js": {"lines":{"total":64,"covered":60,"skipped":0,"pct":93.75},"functions":{"total":12,"covered":12,"skipped":0,"pct":100},"statements":{"total":64,"covered":60,"skipped":0,"pct":93.75},"branches":{"total":33,"covered":27,"skipped":0,"pct":81.82}} 10 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/create-context.js": {"lines":{"total":34,"covered":31,"skipped":0,"pct":91.18},"functions":{"total":10,"covered":10,"skipped":0,"pct":100},"statements":{"total":35,"covered":32,"skipped":0,"pct":91.43},"branches":{"total":20,"covered":17,"skipped":0,"pct":85}} 11 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/dom-renderer.js": {"lines":{"total":21,"covered":20,"skipped":0,"pct":95.24},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":21,"covered":20,"skipped":0,"pct":95.24},"branches":{"total":9,"covered":7,"skipped":0,"pct":77.78}} 12 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/index.js": {"lines":{"total":3,"covered":0,"skipped":0,"pct":0},"functions":{"total":4,"covered":0,"skipped":0,"pct":0},"statements":{"total":11,"covered":0,"skipped":0,"pct":0},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}} 13 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/mount.js": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":5,"covered":5,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 14 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/types.js": {"lines":{"total":1,"covered":0,"skipped":0,"pct":0},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 15 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/components/ErrorCatchProxy/index.js": {"lines":{"total":7,"covered":7,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":7,"covered":7,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 16 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/components/ErrorCatchProxy/styles.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 17 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/components/Loader/index.js": {"lines":{"total":12,"covered":10,"skipped":0,"pct":83.33},"functions":{"total":5,"covered":3,"skipped":0,"pct":60},"statements":{"total":12,"covered":10,"skipped":0,"pct":83.33},"branches":{"total":6,"covered":2,"skipped":0,"pct":33.33}} 18 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/components/PropsProxy/index.js": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 19 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/utils/is-component-class.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}} 20 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/utils/module-type.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 21 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-localstorage-proxy/src/index.js": {"lines":{"total":25,"covered":25,"skipped":0,"pct":100},"functions":{"total":11,"covered":11,"skipped":0,"pct":100},"statements":{"total":25,"covered":25,"skipped":0,"pct":100},"branches":{"total":7,"covered":3,"skipped":0,"pct":42.86}} 22 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-normalize-props-proxy/src/index.js": {"lines":{"total":10,"covered":10,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":10,"covered":10,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 23 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/index.js": {"lines":{"total":12,"covered":12,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":12,"covered":12,"skipped":0,"pct":100},"branches":{"total":4,"covered":3,"skipped":0,"pct":75}} 24 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/ComponentPlayground/index.js": {"lines":{"total":104,"covered":98,"skipped":0,"pct":94.23},"functions":{"total":35,"covered":33,"skipped":0,"pct":94.29},"statements":{"total":105,"covered":99,"skipped":0,"pct":94.29},"branches":{"total":76,"covered":68,"skipped":0,"pct":89.47}} 25 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/DragHandle/index.js": {"lines":{"total":24,"covered":24,"skipped":0,"pct":100},"functions":{"total":9,"covered":7,"skipped":0,"pct":77.78},"statements":{"total":24,"covered":24,"skipped":0,"pct":100},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}} 26 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/FixtureEditor/index.js": {"lines":{"total":15,"covered":13,"skipped":0,"pct":86.67},"functions":{"total":7,"covered":5,"skipped":0,"pct":71.43},"statements":{"total":16,"covered":14,"skipped":0,"pct":87.5},"branches":{"total":8,"covered":7,"skipped":0,"pct":87.5}} 27 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/FixtureList/data-mapper.js": {"lines":{"total":51,"covered":51,"skipped":0,"pct":100},"functions":{"total":13,"covered":13,"skipped":0,"pct":100},"statements":{"total":52,"covered":52,"skipped":0,"pct":100},"branches":{"total":20,"covered":20,"skipped":0,"pct":100}} 28 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/FixtureList/filter.js": {"lines":{"total":11,"covered":11,"skipped":0,"pct":100},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":11,"covered":11,"skipped":0,"pct":100},"branches":{"total":12,"covered":12,"skipped":0,"pct":100}} 29 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/FixtureList/index.js": {"lines":{"total":38,"covered":35,"skipped":0,"pct":92.11},"functions":{"total":10,"covered":9,"skipped":0,"pct":90},"statements":{"total":38,"covered":35,"skipped":0,"pct":92.11},"branches":{"total":16,"covered":15,"skipped":0,"pct":93.75}} 30 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/FixtureList/persistence.js": {"lines":{"total":20,"covered":18,"skipped":0,"pct":90},"functions":{"total":7,"covered":6,"skipped":0,"pct":85.71},"statements":{"total":20,"covered":18,"skipped":0,"pct":90},"branches":{"total":8,"covered":5,"skipped":0,"pct":62.5}} 31 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/StarryBg/index.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 32 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/SvgIcon/index.js": {"lines":{"total":19,"covered":19,"skipped":0,"pct":100},"functions":{"total":9,"covered":9,"skipped":0,"pct":100},"statements":{"total":19,"covered":19,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 33 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/Tree/index.js": {"lines":{"total":66,"covered":63,"skipped":0,"pct":95.45},"functions":{"total":14,"covered":14,"skipped":0,"pct":100},"statements":{"total":66,"covered":63,"skipped":0,"pct":95.45},"branches":{"total":36,"covered":33,"skipped":0,"pct":91.67}} 34 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/screens/DisplayScreen/index.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":1,"covered":1,"skipped":0,"pct":100}} 35 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/screens/LoadingScreen/index.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 36 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/screens/MissingScreen/index.js": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 37 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/screens/NoLoaderScreen/index.js": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 38 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/screens/WelcomeScreen/index.js": {"lines":{"total":11,"covered":11,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":11,"covered":11,"skipped":0,"pct":100},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}} 39 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/utils/enzyme.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 40 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/utils/page-title.js": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":5,"covered":5,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}} 41 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-redux-proxy/src/index.js": {"lines":{"total":33,"covered":33,"skipped":0,"pct":100},"functions":{"total":12,"covered":12,"skipped":0,"pct":100},"statements":{"total":33,"covered":33,"skipped":0,"pct":100},"branches":{"total":12,"covered":10,"skipped":0,"pct":83.33}} 42 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-router-proxy/src/LocationInterceptor.js": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":9,"covered":9,"skipped":0,"pct":100},"branches":{"total":4,"covered":2,"skipped":0,"pct":50}} 43 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-router-proxy/src/index.js": {"lines":{"total":20,"covered":19,"skipped":0,"pct":95},"functions":{"total":6,"covered":6,"skipped":0,"pct":100},"statements":{"total":20,"covered":19,"skipped":0,"pct":95},"branches":{"total":8,"covered":7,"skipped":0,"pct":87.5}} 44 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-scripts/src/upgrade-fixtures.js": {"lines":{"total":14,"covered":13,"skipped":0,"pct":92.86},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":14,"covered":13,"skipped":0,"pct":92.86},"branches":{"total":2,"covered":1,"skipped":0,"pct":50}} 45 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-scripts/src/transforms/add-component-to-fixture.js": {"lines":{"total":18,"covered":18,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":19,"covered":19,"skipped":0,"pct":100},"branches":{"total":9,"covered":9,"skipped":0,"pct":100}} 46 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/import-module.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":5,"covered":5,"skipped":0,"pct":100}} 47 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/index.js": {"lines":{"total":3,"covered":0,"skipped":0,"pct":0},"functions":{"total":4,"covered":0,"skipped":0,"pct":0},"statements":{"total":11,"covered":0,"skipped":0,"pct":0},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}} 48 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/linked-list.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 49 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/types.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 50 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/unserializable-parts.js": {"lines":{"total":25,"covered":25,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":25,"covered":25,"skipped":0,"pct":100},"branches":{"total":23,"covered":23,"skipped":0,"pct":100}} 51 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/jest/index.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 52 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/jest/types.js": {"lines":{"total":2,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":3,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 53 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/react/index.js": {"lines":{"total":1,"covered":0,"skipped":0,"pct":0},"functions":{"total":2,"covered":0,"skipped":0,"pct":0},"statements":{"total":5,"covered":0,"skipped":0,"pct":0},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}} 54 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/react/proxy-prop-types.js": {"lines":{"total":1,"covered":0,"skipped":0,"pct":0},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":3,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 55 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/react/types.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 56 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/server/index.js": {"lines":{"total":4,"covered":0,"skipped":0,"pct":0},"functions":{"total":3,"covered":0,"skipped":0,"pct":0},"statements":{"total":10,"covered":0,"skipped":0,"pct":0},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}} 57 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/server/module-exists.js": {"lines":{"total":4,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":5,"covered":0,"skipped":0,"pct":0},"branches":{"total":3,"covered":0,"skipped":0,"pct":0}} 58 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/server/resolve-user-path.js": {"lines":{"total":4,"covered":0,"skipped":0,"pct":0},"functions":{"total":2,"covered":0,"skipped":0,"pct":0},"statements":{"total":10,"covered":0,"skipped":0,"pct":0},"branches":{"total":8,"covered":0,"skipped":0,"pct":0}} 59 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-state-proxy/src/index.js": {"lines":{"total":65,"covered":61,"skipped":0,"pct":93.85},"functions":{"total":15,"covered":15,"skipped":0,"pct":100},"statements":{"total":65,"covered":61,"skipped":0,"pct":93.85},"branches":{"total":22,"covered":17,"skipped":0,"pct":77.27}} 60 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-telescope/src/index.js": {"lines":{"total":21,"covered":19,"skipped":0,"pct":90.48},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":21,"covered":19,"skipped":0,"pct":90.48},"branches":{"total":5,"covered":2,"skipped":0,"pct":40}} 61 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-test/src/enzyme.js": {"lines":{"total":21,"covered":19,"skipped":0,"pct":90.48},"functions":{"total":6,"covered":6,"skipped":0,"pct":100},"statements":{"total":21,"covered":19,"skipped":0,"pct":90.48},"branches":{"total":8,"covered":6,"skipped":0,"pct":75}} 62 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-test/src/generic.js": {"lines":{"total":16,"covered":15,"skipped":0,"pct":93.75},"functions":{"total":6,"covered":6,"skipped":0,"pct":100},"statements":{"total":16,"covered":15,"skipped":0,"pct":93.75},"branches":{"total":11,"covered":8,"skipped":0,"pct":72.73}} 63 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager/src/fixture-extensions.js": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":5,"covered":5,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 64 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager/src/index.js": {"lines":{"total":39,"covered":38,"skipped":0,"pct":97.44},"functions":{"total":11,"covered":11,"skipped":0,"pct":100},"statements":{"total":39,"covered":38,"skipped":0,"pct":97.44},"branches":{"total":24,"covered":20,"skipped":0,"pct":83.33}} 65 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager/src/match-fixture-path.js": {"lines":{"total":15,"covered":15,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":17,"covered":17,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}} 66 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/types.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 67 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/client/get-components.js": {"lines":{"total":73,"covered":61,"skipped":0,"pct":83.56},"functions":{"total":13,"covered":11,"skipped":0,"pct":84.62},"statements":{"total":74,"covered":62,"skipped":0,"pct":83.78},"branches":{"total":49,"covered":41,"skipped":0,"pct":83.67}} 68 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/client/index.js": {"lines":{"total":1,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":4,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 69 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/client/utils/default-namer.js": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":5,"covered":5,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 70 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/client/utils/infer-component-name.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}} 71 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/server/extract-components-from-fixture-file.js": {"lines":{"total":60,"covered":52,"skipped":0,"pct":86.67},"functions":{"total":9,"covered":9,"skipped":0,"pct":100},"statements":{"total":61,"covered":53,"skipped":0,"pct":86.89},"branches":{"total":32,"covered":25,"skipped":0,"pct":78.13}} 72 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/server/find-fixture-files.js": {"lines":{"total":13,"covered":13,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":14,"covered":14,"skipped":0,"pct":100},"branches":{"total":9,"covered":7,"skipped":0,"pct":77.78}} 73 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/server/index.js": {"lines":{"total":1,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":4,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 74 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-xhr-proxy/src/index.js": {"lines":{"total":25,"covered":22,"skipped":0,"pct":88},"functions":{"total":8,"covered":7,"skipped":0,"pct":87.5},"statements":{"total":25,"covered":22,"skipped":0,"pct":88},"branches":{"total":8,"covered":4,"skipped":0,"pct":50}} 75 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/client/loader-entry.js": {"lines":{"total":7,"covered":6,"skipped":0,"pct":85.71},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":7,"covered":6,"skipped":0,"pct":85.71},"branches":{"total":2,"covered":1,"skipped":0,"pct":50}} 76 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/client/mount.js": {"lines":{"total":46,"covered":40,"skipped":0,"pct":86.96},"functions":{"total":11,"covered":10,"skipped":0,"pct":90.91},"statements":{"total":48,"covered":42,"skipped":0,"pct":87.5},"branches":{"total":23,"covered":18,"skipped":0,"pct":78.26}} 77 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/client/react-devtools-hook.js": {"lines":{"total":4,"covered":1,"skipped":0,"pct":25},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":4,"covered":1,"skipped":0,"pct":25},"branches":{"total":2,"covered":1,"skipped":0,"pct":50}} 78 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/client/react-error-overlay.js": {"lines":{"total":12,"covered":1,"skipped":0,"pct":8.33},"functions":{"total":2,"covered":0,"skipped":0,"pct":0},"statements":{"total":12,"covered":1,"skipped":0,"pct":8.33},"branches":{"total":8,"covered":1,"skipped":0,"pct":12.5}} 79 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/client/user-modules.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 80 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/default-webpack-config.js": {"lines":{"total":22,"covered":22,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":22,"covered":22,"skipped":0,"pct":100},"branches":{"total":18,"covered":17,"skipped":0,"pct":94.44}} 81 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/embed-modules-webpack-loader.js": {"lines":{"total":35,"covered":35,"skipped":0,"pct":100},"functions":{"total":15,"covered":15,"skipped":0,"pct":100},"statements":{"total":38,"covered":38,"skipped":0,"pct":100},"branches":{"total":4,"covered":3,"skipped":0,"pct":75}} 82 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/export.js": {"lines":{"total":34,"covered":22,"skipped":0,"pct":64.71},"functions":{"total":8,"covered":7,"skipped":0,"pct":87.5},"statements":{"total":34,"covered":22,"skipped":0,"pct":64.71},"branches":{"total":8,"covered":3,"skipped":0,"pct":37.5}} 83 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/extend-webpack-config.js": {"lines":{"total":21,"covered":20,"skipped":0,"pct":95.24},"functions":{"total":4,"covered":3,"skipped":0,"pct":75},"statements":{"total":21,"covered":20,"skipped":0,"pct":95.24},"branches":{"total":35,"covered":34,"skipped":0,"pct":97.14}} 84 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/playground-html.js": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 85 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/print-fixture-files.js": {"lines":{"total":6,"covered":0,"skipped":0,"pct":0},"functions":{"total":5,"covered":0,"skipped":0,"pct":0},"statements":{"total":21,"covered":0,"skipped":0,"pct":0},"branches":{"total":8,"covered":0,"skipped":0,"pct":0}} 86 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/server.js": {"lines":{"total":46,"covered":33,"skipped":0,"pct":71.74},"functions":{"total":7,"covered":4,"skipped":0,"pct":57.14},"statements":{"total":46,"covered":33,"skipped":0,"pct":71.74},"branches":{"total":19,"covered":14,"skipped":0,"pct":73.68}} 87 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/user-webpack-config.js": {"lines":{"total":8,"covered":8,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":8,"covered":8,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 88 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-querystring-router/src/index.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 89 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-querystring-router/src/router.js": {"lines":{"total":25,"covered":23,"skipped":0,"pct":92},"functions":{"total":8,"covered":8,"skipped":0,"pct":100},"statements":{"total":26,"covered":24,"skipped":0,"pct":92.31},"branches":{"total":4,"covered":2,"skipped":0,"pct":50}} 90 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-querystring-router/src/uri.js": {"lines":{"total":25,"covered":24,"skipped":0,"pct":96},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":25,"covered":24,"skipped":0,"pct":96},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}} 91 | } 92 | -------------------------------------------------------------------------------- /example/head-summary.json: -------------------------------------------------------------------------------- 1 | {"total": {"lines":{"total":1497,"covered":1322,"skipped":0,"pct":88.31},"statements":{"total":1577,"covered":1339,"skipped":0,"pct":84.91},"functions":{"total":424,"covered":361,"skipped":0,"pct":85.14},"branches":{"total":788,"covered":586,"skipped":0,"pct":74.37}} 2 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-apollo-proxy/src/index.js": {"lines":{"total":12,"covered":10,"skipped":0,"pct":83.33},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":12,"covered":10,"skipped":0,"pct":83.33},"branches":{"total":2,"covered":1,"skipped":0,"pct":50}} 3 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-config/src/config-templates.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 4 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-config/src/index.js": {"lines":{"total":31,"covered":29,"skipped":0,"pct":93.55},"functions":{"total":9,"covered":7,"skipped":0,"pct":77.78},"statements":{"total":31,"covered":29,"skipped":0,"pct":93.55},"branches":{"total":13,"covered":12,"skipped":0,"pct":92.31}} 5 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-config/src/log.js": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":5,"covered":5,"skipped":0,"pct":100},"branches":{"total":3,"covered":2,"skipped":0,"pct":66.67}} 6 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-context-proxy/src/index.js": {"lines":{"total":8,"covered":8,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":8,"covered":8,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 7 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-fetch-proxy/src/index.js": {"lines":{"total":21,"covered":21,"skipped":0,"pct":100},"functions":{"total":8,"covered":7,"skipped":0,"pct":87.5},"statements":{"total":22,"covered":21,"skipped":0,"pct":95.45},"branches":{"total":6,"covered":4,"skipped":0,"pct":66.67}} 8 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/connect-loader.js": {"lines":{"total":64,"covered":60,"skipped":0,"pct":93.75},"functions":{"total":12,"covered":12,"skipped":0,"pct":100},"statements":{"total":64,"covered":60,"skipped":0,"pct":93.75},"branches":{"total":33,"covered":27,"skipped":0,"pct":81.82}} 9 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/create-context.js": {"lines":{"total":33,"covered":30,"skipped":0,"pct":90.91},"functions":{"total":10,"covered":10,"skipped":0,"pct":100},"statements":{"total":34,"covered":31,"skipped":0,"pct":91.18},"branches":{"total":20,"covered":17,"skipped":0,"pct":85}} 10 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/dom-renderer.js": {"lines":{"total":21,"covered":20,"skipped":0,"pct":95.24},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":21,"covered":20,"skipped":0,"pct":95.24},"branches":{"total":9,"covered":7,"skipped":0,"pct":77.78}} 11 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/index.js": {"lines":{"total":3,"covered":0,"skipped":0,"pct":0},"functions":{"total":4,"covered":0,"skipped":0,"pct":0},"statements":{"total":11,"covered":0,"skipped":0,"pct":0},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}} 12 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/mount.js": {"lines":{"total":6,"covered":6,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":6,"covered":6,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 13 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/types.js": {"lines":{"total":1,"covered":0,"skipped":0,"pct":0},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 14 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/components/ErrorCatchProxy/index.js": {"lines":{"total":7,"covered":7,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":7,"covered":7,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 15 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/components/ErrorCatchProxy/styles.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 16 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/components/Loader/index.js": {"lines":{"total":12,"covered":10,"skipped":0,"pct":83.33},"functions":{"total":5,"covered":3,"skipped":0,"pct":60},"statements":{"total":12,"covered":10,"skipped":0,"pct":83.33},"branches":{"total":6,"covered":2,"skipped":0,"pct":33.33}} 17 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/components/PropsProxy/index.js": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 18 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/utils/is-component-class.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}} 19 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-loader/src/utils/module-type.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 20 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-localstorage-proxy/src/index.js": {"lines":{"total":25,"covered":25,"skipped":0,"pct":100},"functions":{"total":11,"covered":11,"skipped":0,"pct":100},"statements":{"total":25,"covered":25,"skipped":0,"pct":100},"branches":{"total":7,"covered":3,"skipped":0,"pct":42.86}} 21 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-normalize-props-proxy/src/index.js": {"lines":{"total":10,"covered":10,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":10,"covered":10,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 22 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/index.js": {"lines":{"total":12,"covered":12,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":12,"covered":12,"skipped":0,"pct":100},"branches":{"total":4,"covered":3,"skipped":0,"pct":75}} 23 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/ComponentPlayground/index.js": {"lines":{"total":110,"covered":103,"skipped":0,"pct":93.64},"functions":{"total":36,"covered":33,"skipped":0,"pct":91.67},"statements":{"total":111,"covered":104,"skipped":0,"pct":93.69},"branches":{"total":92,"covered":79,"skipped":0,"pct":85.87}} 24 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/DragHandle/index.js": {"lines":{"total":24,"covered":24,"skipped":0,"pct":100},"functions":{"total":9,"covered":7,"skipped":0,"pct":77.78},"statements":{"total":24,"covered":24,"skipped":0,"pct":100},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}} 25 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/FixtureEditor/index.js": {"lines":{"total":15,"covered":13,"skipped":0,"pct":86.67},"functions":{"total":7,"covered":5,"skipped":0,"pct":71.43},"statements":{"total":16,"covered":14,"skipped":0,"pct":87.5},"branches":{"total":8,"covered":7,"skipped":0,"pct":87.5}} 26 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/FixtureList/data-mapper.js": {"lines":{"total":51,"covered":51,"skipped":0,"pct":100},"functions":{"total":13,"covered":13,"skipped":0,"pct":100},"statements":{"total":52,"covered":52,"skipped":0,"pct":100},"branches":{"total":20,"covered":20,"skipped":0,"pct":100}} 27 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/FixtureList/filter.js": {"lines":{"total":11,"covered":11,"skipped":0,"pct":100},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":11,"covered":11,"skipped":0,"pct":100},"branches":{"total":12,"covered":12,"skipped":0,"pct":100}} 28 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/FixtureList/index.js": {"lines":{"total":38,"covered":35,"skipped":0,"pct":92.11},"functions":{"total":10,"covered":9,"skipped":0,"pct":90},"statements":{"total":38,"covered":35,"skipped":0,"pct":92.11},"branches":{"total":16,"covered":15,"skipped":0,"pct":93.75}} 29 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/FixtureList/persistence.js": {"lines":{"total":20,"covered":18,"skipped":0,"pct":90},"functions":{"total":7,"covered":6,"skipped":0,"pct":85.71},"statements":{"total":20,"covered":18,"skipped":0,"pct":90},"branches":{"total":8,"covered":5,"skipped":0,"pct":62.5}} 30 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/ResponsiveLoader/Demo.js": {"lines":{"total":4,"covered":0,"skipped":0,"pct":0},"functions":{"total":2,"covered":0,"skipped":0,"pct":0},"statements":{"total":9,"covered":0,"skipped":0,"pct":0},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}} 31 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/ResponsiveLoader/Header.js": {"lines":{"total":16,"covered":3,"skipped":0,"pct":18.75},"functions":{"total":9,"covered":0,"skipped":0,"pct":0},"statements":{"total":16,"covered":3,"skipped":0,"pct":18.75},"branches":{"total":27,"covered":0,"skipped":0,"pct":0}} 32 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/ResponsiveLoader/index.js": {"lines":{"total":41,"covered":30,"skipped":0,"pct":73.17},"functions":{"total":6,"covered":4,"skipped":0,"pct":66.67},"statements":{"total":41,"covered":30,"skipped":0,"pct":73.17},"branches":{"total":41,"covered":20,"skipped":0,"pct":48.78}} 33 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/StarryBg/index.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":2,"covered":2,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 34 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/SvgIcon/index.js": {"lines":{"total":21,"covered":21,"skipped":0,"pct":100},"functions":{"total":10,"covered":10,"skipped":0,"pct":100},"statements":{"total":21,"covered":21,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 35 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/Tree/index.js": {"lines":{"total":66,"covered":63,"skipped":0,"pct":95.45},"functions":{"total":14,"covered":14,"skipped":0,"pct":100},"statements":{"total":66,"covered":63,"skipped":0,"pct":95.45},"branches":{"total":36,"covered":33,"skipped":0,"pct":91.67}} 36 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/screens/DisplayScreen/index.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":1,"covered":1,"skipped":0,"pct":100}} 37 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/screens/LoadingScreen/index.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 38 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/screens/MissingScreen/index.js": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 39 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/screens/NoLoaderScreen/index.js": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 40 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/components/screens/WelcomeScreen/index.js": {"lines":{"total":11,"covered":11,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":11,"covered":11,"skipped":0,"pct":100},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}} 41 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/utils/enzyme.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 42 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-playground/src/utils/page-title.js": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":5,"covered":5,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}} 43 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-redux-proxy/src/index.js": {"lines":{"total":21,"covered":21,"skipped":0,"pct":100},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":21,"covered":21,"skipped":0,"pct":100},"branches":{"total":10,"covered":9,"skipped":0,"pct":90}} 44 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-router-proxy/src/LocationInterceptor.js": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":9,"covered":9,"skipped":0,"pct":100},"branches":{"total":4,"covered":2,"skipped":0,"pct":50}} 45 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-router-proxy/src/index.js": {"lines":{"total":20,"covered":19,"skipped":0,"pct":95},"functions":{"total":6,"covered":6,"skipped":0,"pct":100},"statements":{"total":20,"covered":19,"skipped":0,"pct":95},"branches":{"total":8,"covered":7,"skipped":0,"pct":87.5}} 46 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-scripts/src/upgrade-fixtures.js": {"lines":{"total":14,"covered":13,"skipped":0,"pct":92.86},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":14,"covered":13,"skipped":0,"pct":92.86},"branches":{"total":2,"covered":1,"skipped":0,"pct":50}} 47 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-scripts/src/transforms/add-component-to-fixture.js": {"lines":{"total":18,"covered":18,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":19,"covered":19,"skipped":0,"pct":100},"branches":{"total":9,"covered":9,"skipped":0,"pct":100}} 48 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/import-module.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":5,"covered":5,"skipped":0,"pct":100}} 49 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/index.js": {"lines":{"total":3,"covered":0,"skipped":0,"pct":0},"functions":{"total":4,"covered":0,"skipped":0,"pct":0},"statements":{"total":11,"covered":0,"skipped":0,"pct":0},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}} 50 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/linked-list.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 51 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/types.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 52 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/unserializable-parts.js": {"lines":{"total":25,"covered":25,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":25,"covered":25,"skipped":0,"pct":100},"branches":{"total":23,"covered":23,"skipped":0,"pct":100}} 53 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/jest/index.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 54 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/jest/types.js": {"lines":{"total":2,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":3,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 55 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/react/index.js": {"lines":{"total":1,"covered":0,"skipped":0,"pct":0},"functions":{"total":2,"covered":0,"skipped":0,"pct":0},"statements":{"total":5,"covered":0,"skipped":0,"pct":0},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}} 56 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/react/proxy-prop-types.js": {"lines":{"total":1,"covered":0,"skipped":0,"pct":0},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":3,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 57 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/react/types.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 58 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/server/index.js": {"lines":{"total":4,"covered":0,"skipped":0,"pct":0},"functions":{"total":3,"covered":0,"skipped":0,"pct":0},"statements":{"total":10,"covered":0,"skipped":0,"pct":0},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}} 59 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/server/module-exists.js": {"lines":{"total":4,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":5,"covered":0,"skipped":0,"pct":0},"branches":{"total":3,"covered":0,"skipped":0,"pct":0}} 60 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-shared/src/server/resolve-user-path.js": {"lines":{"total":4,"covered":0,"skipped":0,"pct":0},"functions":{"total":2,"covered":0,"skipped":0,"pct":0},"statements":{"total":10,"covered":0,"skipped":0,"pct":0},"branches":{"total":8,"covered":0,"skipped":0,"pct":0}} 61 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-state-proxy/src/index.js": {"lines":{"total":65,"covered":61,"skipped":0,"pct":93.85},"functions":{"total":15,"covered":15,"skipped":0,"pct":100},"statements":{"total":65,"covered":61,"skipped":0,"pct":93.85},"branches":{"total":22,"covered":17,"skipped":0,"pct":77.27}} 62 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-telescope/src/index.js": {"lines":{"total":21,"covered":19,"skipped":0,"pct":90.48},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":21,"covered":19,"skipped":0,"pct":90.48},"branches":{"total":5,"covered":2,"skipped":0,"pct":40}} 63 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-test/src/enzyme.js": {"lines":{"total":14,"covered":12,"skipped":0,"pct":85.71},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":14,"covered":12,"skipped":0,"pct":85.71},"branches":{"total":6,"covered":4,"skipped":0,"pct":66.67}} 64 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-test/src/generic.js": {"lines":{"total":16,"covered":12,"skipped":0,"pct":75},"functions":{"total":6,"covered":4,"skipped":0,"pct":66.67},"statements":{"total":16,"covered":12,"skipped":0,"pct":75},"branches":{"total":11,"covered":5,"skipped":0,"pct":45.45}} 65 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager/src/fixture-extensions.js": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":5,"covered":5,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 66 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager/src/index.js": {"lines":{"total":39,"covered":38,"skipped":0,"pct":97.44},"functions":{"total":11,"covered":11,"skipped":0,"pct":100},"statements":{"total":39,"covered":38,"skipped":0,"pct":97.44},"branches":{"total":24,"covered":20,"skipped":0,"pct":83.33}} 67 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager/src/match-fixture-path.js": {"lines":{"total":15,"covered":15,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":17,"covered":17,"skipped":0,"pct":100},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}} 68 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/types.js": {"lines":{"total":0,"covered":0,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":0,"covered":0,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 69 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/client/get-components.js": {"lines":{"total":73,"covered":61,"skipped":0,"pct":83.56},"functions":{"total":13,"covered":11,"skipped":0,"pct":84.62},"statements":{"total":74,"covered":62,"skipped":0,"pct":83.78},"branches":{"total":49,"covered":41,"skipped":0,"pct":83.67}} 70 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/client/index.js": {"lines":{"total":1,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":4,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 71 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/client/utils/default-namer.js": {"lines":{"total":5,"covered":5,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":5,"covered":5,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 72 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/client/utils/infer-component-name.js": {"lines":{"total":3,"covered":3,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}} 73 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/server/extract-components-from-fixture-file.js": {"lines":{"total":54,"covered":47,"skipped":0,"pct":87.04},"functions":{"total":9,"covered":9,"skipped":0,"pct":100},"statements":{"total":55,"covered":48,"skipped":0,"pct":87.27},"branches":{"total":28,"covered":22,"skipped":0,"pct":78.57}} 74 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/server/find-fixture-files.js": {"lines":{"total":13,"covered":13,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":14,"covered":14,"skipped":0,"pct":100},"branches":{"total":9,"covered":7,"skipped":0,"pct":77.78}} 75 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-voyager2/src/server/index.js": {"lines":{"total":1,"covered":0,"skipped":0,"pct":0},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":4,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 76 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos-xhr-proxy/src/index.js": {"lines":{"total":25,"covered":22,"skipped":0,"pct":88},"functions":{"total":8,"covered":7,"skipped":0,"pct":87.5},"statements":{"total":25,"covered":22,"skipped":0,"pct":88},"branches":{"total":8,"covered":4,"skipped":0,"pct":50}} 77 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/client/loader-entry.js": {"lines":{"total":6,"covered":5,"skipped":0,"pct":83.33},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":6,"covered":5,"skipped":0,"pct":83.33},"branches":{"total":2,"covered":1,"skipped":0,"pct":50}} 78 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/client/mount.js": {"lines":{"total":41,"covered":37,"skipped":0,"pct":90.24},"functions":{"total":10,"covered":10,"skipped":0,"pct":100},"statements":{"total":43,"covered":39,"skipped":0,"pct":90.7},"branches":{"total":14,"covered":13,"skipped":0,"pct":92.86}} 79 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/client/react-devtools-hook.js": {"lines":{"total":4,"covered":1,"skipped":0,"pct":25},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":4,"covered":1,"skipped":0,"pct":25},"branches":{"total":2,"covered":1,"skipped":0,"pct":50}} 80 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/client/react-error-overlay.js": {"lines":{"total":12,"covered":1,"skipped":0,"pct":8.33},"functions":{"total":2,"covered":0,"skipped":0,"pct":0},"statements":{"total":12,"covered":1,"skipped":0,"pct":8.33},"branches":{"total":8,"covered":1,"skipped":0,"pct":12.5}} 81 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/client/user-modules.js": {"lines":{"total":2,"covered":2,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":3,"covered":3,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 82 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/default-webpack-config.js": {"lines":{"total":16,"covered":16,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":16,"covered":16,"skipped":0,"pct":100},"branches":{"total":10,"covered":10,"skipped":0,"pct":100}} 83 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/embed-modules-webpack-loader.js": {"lines":{"total":35,"covered":35,"skipped":0,"pct":100},"functions":{"total":15,"covered":15,"skipped":0,"pct":100},"statements":{"total":38,"covered":38,"skipped":0,"pct":100},"branches":{"total":4,"covered":3,"skipped":0,"pct":75}} 84 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/export.js": {"lines":{"total":34,"covered":22,"skipped":0,"pct":64.71},"functions":{"total":8,"covered":7,"skipped":0,"pct":87.5},"statements":{"total":34,"covered":22,"skipped":0,"pct":64.71},"branches":{"total":8,"covered":3,"skipped":0,"pct":37.5}} 85 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/extend-webpack-config.js": {"lines":{"total":21,"covered":20,"skipped":0,"pct":95.24},"functions":{"total":4,"covered":3,"skipped":0,"pct":75},"statements":{"total":21,"covered":20,"skipped":0,"pct":95.24},"branches":{"total":35,"covered":34,"skipped":0,"pct":97.14}} 86 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/playground-html.js": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 87 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/print-fixture-files.js": {"lines":{"total":6,"covered":0,"skipped":0,"pct":0},"functions":{"total":5,"covered":0,"skipped":0,"pct":0},"statements":{"total":21,"covered":0,"skipped":0,"pct":0},"branches":{"total":8,"covered":0,"skipped":0,"pct":0}} 88 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/server.js": {"lines":{"total":46,"covered":33,"skipped":0,"pct":71.74},"functions":{"total":7,"covered":4,"skipped":0,"pct":57.14},"statements":{"total":46,"covered":33,"skipped":0,"pct":71.74},"branches":{"total":19,"covered":14,"skipped":0,"pct":73.68}} 89 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-cosmos/src/server/user-webpack-config.js": {"lines":{"total":8,"covered":8,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":8,"covered":8,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}} 90 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-querystring-router/src/index.js": {"lines":{"total":1,"covered":1,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":1,"covered":1,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 91 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-querystring-router/src/router.js": {"lines":{"total":25,"covered":23,"skipped":0,"pct":92},"functions":{"total":8,"covered":8,"skipped":0,"pct":100},"statements":{"total":26,"covered":24,"skipped":0,"pct":92.31},"branches":{"total":4,"covered":2,"skipped":0,"pct":50}} 92 | ,"/Users/flavius.tirnacop/Projects/react-cosmos/packages/react-querystring-router/src/uri.js": {"lines":{"total":25,"covered":24,"skipped":0,"pct":96},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":25,"covered":24,"skipped":0,"pct":96},"branches":{"total":6,"covered":6,"skipped":0,"pct":100}} 93 | } 94 | -------------------------------------------------------------------------------- /example/runner.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const fs = require('fs'); 4 | const coverageDiff = require('../lib/'); 5 | 6 | const base = JSON.parse(fs.readFileSync('./base-summary.json')); 7 | const head = JSON.parse(fs.readFileSync('./head-summary.json')); 8 | const diff = coverageDiff.diff(base, head); 9 | 10 | console.log(diff.diff); 11 | console.log(diff.results); 12 | console.log(diff.regression); 13 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | collectCoverageFrom: ['src/**/*.{js,ts}', '!**/*.d.ts'], 4 | coverageReporters: ['lcov'], 5 | coverageDirectory: '/coverage', 6 | transform: { 7 | '^.+\\.(js|ts)$': 'ts-jest' 8 | }, 9 | globals: { 10 | 'ts-jest': { 11 | isolatedModules: true, 12 | diagnostics: false 13 | } 14 | }, 15 | testMatch: ['**/?(*.)test.(js|ts)'], 16 | testEnvironment: 'node', 17 | moduleFileExtensions: ['ts', 'js', 'json'] 18 | }; 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coverage-diff", 3 | "version": "3.2.0", 4 | "description": "Reports differences between two istanbul JSON code coverage summaries.", 5 | "main": "lib/index", 6 | "types": "lib/index", 7 | "repository": "git@github.com:flaviusone/coverage-diff.git", 8 | "author": "Flavius Tirnacop ", 9 | "license": "MIT", 10 | "scripts": { 11 | "build": "./node_modules/.bin/tsc -p tsconfig.build.json", 12 | "build-docs": "typedoc --out docs/ src/index.ts", 13 | "clean": "rm -rf lib docs coverage", 14 | "eslint": "eslint 'src/**'", 15 | "lint": "yarn clean && yarn eslint && yarn prettier", 16 | "postpublish": "yarn publish-docs", 17 | "precommit": "lint-staged", 18 | "prepublishOnly": "yarn clean && yarn lint && yarn test && yarn build", 19 | "prettier": "prettier -c '**/*.{js,ts,md}'", 20 | "publish-docs": "yarn build-docs && gh-pages -d docs -b gh-pages && yarn clean", 21 | "test": "yarn run tsc && yarn run jest", 22 | "jest": "jest --maxWorkers=4", 23 | "jest-watch": "yarn jest --watch", 24 | "jest-coverage": "yarn jest --coverage", 25 | "tsc": "./node_modules/.bin/tsc --noEmit --pretty" 26 | }, 27 | "lint-staged": { 28 | "*.{js,ts}": "eslint", 29 | "*.{js,ts,md}": "prettier -c" 30 | }, 31 | "devDependencies": { 32 | "@types/jest": "22.2.3", 33 | "@types/node": "16.11.7", 34 | "@typescript-eslint/eslint-plugin": "5.14.0", 35 | "@typescript-eslint/parser": "5.14.0", 36 | "eslint": "8.11.0", 37 | "eslint-config-prettier": "8.5.0", 38 | "gh-pages": "3.2.3", 39 | "jest": "27.5.1", 40 | "lint-staged": "12.3.5", 41 | "prettier": "2.5.1", 42 | "ts-jest": "27.1.3", 43 | "typedoc": "0.22.13", 44 | "typescript": "4.6.2" 45 | }, 46 | "dependencies": { 47 | "markdown-table": "2.0.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/__mocks__/coverageDiffer.ts: -------------------------------------------------------------------------------- 1 | export const coverageDiffer = () => 'coverageDiffer mock'; 2 | -------------------------------------------------------------------------------- /src/__mocks__/diffChecker.ts: -------------------------------------------------------------------------------- 1 | export const diffChecker = () => ({ 2 | diff: 'mocked diff', 3 | regression: 'mockedRegression', 4 | files: 'mockedFiles', 5 | totals: { deltas: 'mockedTotals', pcts: 'mockedPcts' } 6 | }); 7 | -------------------------------------------------------------------------------- /src/__mocks__/resultFormatter.ts: -------------------------------------------------------------------------------- 1 | export const resultFormatter = () => 'resultFormatter mock'; 2 | -------------------------------------------------------------------------------- /src/__snapshots__/coverageDiffer.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`coverageDiffer file coverage decreased should match snapshot 1`] = ` 4 | Object { 5 | "fileA": Object { 6 | "branches": Object { 7 | "covered": -1, 8 | "pct": -50, 9 | "skipped": 0, 10 | "total": 0, 11 | }, 12 | "functions": Object { 13 | "covered": -1, 14 | "pct": -50, 15 | "skipped": 0, 16 | "total": 0, 17 | }, 18 | "isNewFile": false, 19 | "lines": Object { 20 | "covered": -1, 21 | "pct": -50, 22 | "skipped": 0, 23 | "total": 0, 24 | }, 25 | "statements": Object { 26 | "covered": -1, 27 | "pct": -50, 28 | "skipped": 0, 29 | "total": 0, 30 | }, 31 | }, 32 | "total": Object { 33 | "branches": Object { 34 | "covered": -1, 35 | "pct": -50, 36 | "skipped": 0, 37 | "total": 0, 38 | }, 39 | "functions": Object { 40 | "covered": -1, 41 | "pct": -50, 42 | "skipped": 0, 43 | "total": 0, 44 | }, 45 | "isNewFile": false, 46 | "lines": Object { 47 | "covered": -1, 48 | "pct": -50, 49 | "skipped": 0, 50 | "total": 0, 51 | }, 52 | "statements": Object { 53 | "covered": -1, 54 | "pct": -50, 55 | "skipped": 0, 56 | "total": 0, 57 | }, 58 | }, 59 | } 60 | `; 61 | 62 | exports[`coverageDiffer file coverage increased should match snapshot 1`] = ` 63 | Object { 64 | "fileA": Object { 65 | "branches": Object { 66 | "covered": 1, 67 | "pct": 50, 68 | "skipped": 0, 69 | "total": 0, 70 | }, 71 | "functions": Object { 72 | "covered": 1, 73 | "pct": 50, 74 | "skipped": 0, 75 | "total": 0, 76 | }, 77 | "isNewFile": false, 78 | "lines": Object { 79 | "covered": 1, 80 | "pct": 50, 81 | "skipped": 0, 82 | "total": 0, 83 | }, 84 | "statements": Object { 85 | "covered": 1, 86 | "pct": 50, 87 | "skipped": 0, 88 | "total": 0, 89 | }, 90 | }, 91 | "total": Object { 92 | "branches": Object { 93 | "covered": 1, 94 | "pct": 50, 95 | "skipped": 0, 96 | "total": 0, 97 | }, 98 | "functions": Object { 99 | "covered": 1, 100 | "pct": 50, 101 | "skipped": 0, 102 | "total": 0, 103 | }, 104 | "isNewFile": false, 105 | "lines": Object { 106 | "covered": 1, 107 | "pct": 50, 108 | "skipped": 0, 109 | "total": 0, 110 | }, 111 | "statements": Object { 112 | "covered": 1, 113 | "pct": 50, 114 | "skipped": 0, 115 | "total": 0, 116 | }, 117 | }, 118 | } 119 | `; 120 | 121 | exports[`coverageDiffer new file should match snapshot 1`] = ` 122 | Object { 123 | "fileA": Object { 124 | "branches": Object { 125 | "covered": 0, 126 | "pct": 0, 127 | "skipped": 0, 128 | "total": 0, 129 | }, 130 | "functions": Object { 131 | "covered": 0, 132 | "pct": 0, 133 | "skipped": 0, 134 | "total": 0, 135 | }, 136 | "isNewFile": false, 137 | "lines": Object { 138 | "covered": 0, 139 | "pct": 0, 140 | "skipped": 0, 141 | "total": 0, 142 | }, 143 | "statements": Object { 144 | "covered": 0, 145 | "pct": 0, 146 | "skipped": 0, 147 | "total": 0, 148 | }, 149 | }, 150 | "fileB": Object { 151 | "branches": Object { 152 | "covered": 2, 153 | "pct": 100, 154 | "skipped": 0, 155 | "total": 2, 156 | }, 157 | "functions": Object { 158 | "covered": 2, 159 | "pct": 100, 160 | "skipped": 0, 161 | "total": 2, 162 | }, 163 | "isNewFile": true, 164 | "lines": Object { 165 | "covered": 2, 166 | "pct": 100, 167 | "skipped": 0, 168 | "total": 2, 169 | }, 170 | "statements": Object { 171 | "covered": 2, 172 | "pct": 100, 173 | "skipped": 0, 174 | "total": 2, 175 | }, 176 | }, 177 | "total": Object { 178 | "branches": Object { 179 | "covered": 0, 180 | "pct": 0, 181 | "skipped": 0, 182 | "total": 0, 183 | }, 184 | "functions": Object { 185 | "covered": 0, 186 | "pct": 0, 187 | "skipped": 0, 188 | "total": 0, 189 | }, 190 | "isNewFile": false, 191 | "lines": Object { 192 | "covered": 0, 193 | "pct": 0, 194 | "skipped": 0, 195 | "total": 0, 196 | }, 197 | "statements": Object { 198 | "covered": 0, 199 | "pct": 0, 200 | "skipped": 0, 201 | "total": 0, 202 | }, 203 | }, 204 | } 205 | `; 206 | -------------------------------------------------------------------------------- /src/__snapshots__/resultFormatter.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`resultFormatter should format files results as markdown table 1`] = ` 4 | "| Ok | File (✨=New File) | Lines | Branches | Functions | Statements | 5 | | -- | ----------------- | ------------- | ------------- | ------------ | ------------- | 6 | | 🔴 | file1 | 80%
(+10%) | 14%
(-30%) | 3%
(+20%) | 20%
(-10%) | 7 | | ✅ | file2 | 20%
(+10%) | 8%
(+30%) | 2%
(+20%) | 5%
(+10%) | 8 | | 🔴 | file3 | 20%
(+10%) | 8%
(+30%) | 2%
(+20%) | 5%
(+10%) | 9 | | ✅ | ✨ file4 | 20%
(+10%) | 8%
(+30%) | 2%
(+20%) | 5%
(+10%) | 10 | 11 | Total: 12 | 13 | | Lines | Branches | Functions | Statements | 14 | | ----------- | ----------- | ----------- | ----------- | 15 | | 100%(+100%) | 100%(+100%) | 100%(+100%) | 100%(+100%) |" 16 | `; 17 | 18 | exports[`resultFormatter should print descriptive message if coverage did't change 1`] = ` 19 | "Coverage values did not change👌. 20 | 21 | Total: 22 | 23 | | Lines | Branches | Functions | Statements | 24 | | ----------- | ----------- | ----------- | ----------- | 25 | | 100%(+100%) | 100%(+100%) | 100%(+100%) | 100%(+100%) |" 26 | `; 27 | -------------------------------------------------------------------------------- /src/common.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | export interface CoverageSummary { 3 | lines: CoverageInfo; 4 | statements: CoverageInfo; 5 | functions: CoverageInfo; 6 | branches: CoverageInfo; 7 | isNewFile: boolean; 8 | } 9 | 10 | export interface CoverageInfo { 11 | total: number; 12 | covered: number; 13 | skipped: number; 14 | pct: number; // Coverage percentage (covered/total ratio). 15 | } 16 | 17 | export interface JsonSummary { 18 | [key: string]: CoverageSummary; 19 | } 20 | 21 | export interface CoverageDiffOutput { 22 | diff: JsonSummary; 23 | results: string; 24 | regression: boolean; 25 | belowThreshold: boolean; 26 | } 27 | 28 | export type Criteria = 'lines' | 'branches' | 'functions' | 'statements'; 29 | 30 | export interface ConfigOptions { 31 | checkCriteria?: Array; 32 | /* Fail coverage check if per-file coverage is lower */ 33 | coverageThreshold?: number; 34 | /* Fail coverage check if per-file coverage decrease is lower */ 35 | coverageDecreaseThreshold?: number; 36 | /* Fail coverage check if per-file coverage is lower than this for new files only */ 37 | newFileCoverageThreshold?: number; 38 | /* Function to generate a custom output based on the diff */ 39 | customFormatter?: (files: FilesResults, totals: FileResultFormat) => string; 40 | } 41 | 42 | export interface DiffCheckResults { 43 | files: FilesResults; 44 | totals: FileResultFormat; 45 | diff: JsonSummary; 46 | regression: boolean; 47 | belowThreshold: boolean; 48 | } 49 | 50 | export interface FilesResults { 51 | [key: string]: FileResultFormat; 52 | } 53 | 54 | export interface FileResultFormat { 55 | deltas: FileResultFields; 56 | pcts: FileResultFields; 57 | decreased: boolean; 58 | belowThreshold: boolean; 59 | isNewFile: boolean; 60 | } 61 | 62 | export interface FileResultFields { 63 | lines: number; 64 | functions: number; 65 | statements: number; 66 | branches: number; 67 | } 68 | -------------------------------------------------------------------------------- /src/coverageDiffer.test.ts: -------------------------------------------------------------------------------- 1 | import { coverageDiffer } from './coverageDiffer'; 2 | import { 3 | fileFullCovered, 4 | fileHalfCovered, 5 | fileNotCovered, 6 | newFile 7 | } from './summaries.fixture'; 8 | 9 | describe('coverageDiffer', () => { 10 | describe('file coverage increased ', () => { 11 | it('should match snapshot', () => { 12 | expect(coverageDiffer(fileNotCovered, fileHalfCovered)).toMatchSnapshot(); 13 | }); 14 | }); 15 | 16 | describe('file coverage decreased ', () => { 17 | it('should match snapshot', () => { 18 | expect( 19 | coverageDiffer(fileFullCovered, fileHalfCovered) 20 | ).toMatchSnapshot(); 21 | }); 22 | }); 23 | 24 | describe('new file ', () => { 25 | it('should match snapshot', () => { 26 | expect(coverageDiffer(fileFullCovered, newFile)).toMatchSnapshot(); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/coverageDiffer.ts: -------------------------------------------------------------------------------- 1 | import { objectToMap, mapToObject } from './helpers'; 2 | import { JsonSummary, CoverageSummary, CoverageInfo } from './common'; 3 | 4 | /** 5 | * Compares two Istanbul json-summary formatted coverage objects and outputs the 6 | * diff between them. 7 | */ 8 | export const coverageDiffer = ( 9 | base: JsonSummary, 10 | head: JsonSummary 11 | ): JsonSummary => { 12 | const baseMap = objectToMap(base); 13 | const headMap = objectToMap(head); 14 | const diffMap = new Map(); 15 | 16 | // Compare head against base for changed/added files. 17 | headMap.forEach((v, k) => { 18 | const fileSummary = baseMap.get(k); 19 | 20 | if (fileSummary) { 21 | // Changed file. 22 | diffMap.set(k, diffSummary(v, fileSummary)); 23 | } else { 24 | // New file. 25 | diffMap.set(k, { ...v, isNewFile: true }); 26 | } 27 | }); 28 | 29 | return mapToObject(diffMap); 30 | }; 31 | 32 | /** 33 | * Returns the diff of two CoverageSummary objects. 34 | */ 35 | const diffSummary = ( 36 | summaryA: CoverageSummary, 37 | summaryB: CoverageSummary 38 | ): CoverageSummary => { 39 | return { 40 | lines: diffInfo(summaryA.lines, summaryB.lines), 41 | statements: diffInfo(summaryA.statements, summaryB.statements), 42 | functions: diffInfo(summaryA.functions, summaryB.functions), 43 | branches: diffInfo(summaryA.branches, summaryB.branches), 44 | isNewFile: false 45 | }; 46 | }; 47 | 48 | /** 49 | * Returns the diff of two CoverageInfo objects. 50 | */ 51 | const diffInfo = (infoA: CoverageInfo, infoB: CoverageInfo): CoverageInfo => { 52 | return { 53 | total: infoA.total - infoB.total, 54 | covered: infoA.covered - infoB.covered, 55 | skipped: infoA.skipped - infoB.skipped, 56 | pct: Math.round((infoA.pct - infoB.pct) * 100) / 100 57 | }; 58 | }; 59 | -------------------------------------------------------------------------------- /src/diffChecker.test.ts: -------------------------------------------------------------------------------- 1 | import { diffChecker } from './diffChecker'; 2 | import { 3 | fileFullCovered, 4 | onlyLinesIncreased, 5 | fileNotCovered, 6 | fileHalfCovered, 7 | fileFullCoveredfileHalfCovered, 8 | newFileNotCovered, 9 | newFileHalfCovered 10 | } from './summaries.fixture'; 11 | import { DiffCheckResults } from './common'; 12 | import { expectToMatchObject } from './testHelpers'; 13 | 14 | describe('diffChecker', () => { 15 | let result: DiffCheckResults; 16 | describe('coverage increased', () => { 17 | beforeEach(() => { 18 | result = diffChecker(fileNotCovered, fileFullCovered); 19 | }); 20 | it('should not regress', () => { 21 | expect(result.regression).toBe(false); 22 | }); 23 | 24 | it('should not be below threshold', () => { 25 | expect(result.belowThreshold).toBe(false); 26 | }); 27 | 28 | it('outputs correct total percentages for file', () => { 29 | expectToMatchObject(result.files['fileA'], { 30 | decreased: false, 31 | belowThreshold: false, // threshold is 100 32 | pcts: { 33 | branches: 100, 34 | functions: 100, 35 | lines: 100, 36 | statements: 100 37 | } 38 | }); 39 | }); 40 | it('outputs correct deltas for file', () => { 41 | expectToMatchObject(result.files['fileA'], { 42 | deltas: { 43 | branches: 100, 44 | functions: 100, 45 | lines: 100, 46 | statements: 100 47 | } 48 | }); 49 | }); 50 | }); 51 | 52 | describe('coverage decreased', () => { 53 | beforeEach(() => { 54 | result = diffChecker(fileFullCovered, fileNotCovered); 55 | }); 56 | 57 | it('should not regress', () => { 58 | expect(result.regression).toBe(true); 59 | }); 60 | 61 | it('should be below threshold', () => { 62 | // default for threshold is 100%, so this will be true 63 | expect(result.belowThreshold).toBe(true); 64 | }); 65 | 66 | it('outputs correct total percentages for file', () => { 67 | expectToMatchObject(result.files['fileA'], { 68 | belowThreshold: true, // threshold is 100 69 | decreased: true, 70 | pcts: { 71 | branches: 0, 72 | functions: 0, 73 | lines: 0, 74 | statements: 0 75 | } 76 | }); 77 | }); 78 | it('outputs correct deltas for file', () => { 79 | expectToMatchObject(result.files['fileA'], { 80 | deltas: { 81 | branches: -100, 82 | functions: -100, 83 | lines: -100, 84 | statements: -100 85 | } 86 | }); 87 | }); 88 | }); 89 | 90 | describe('coverage decreased over total threshold', () => { 91 | beforeEach(() => { 92 | // Set coverageDecreaseThreshold to 100% so we only test coverageThreshold. 93 | result = diffChecker( 94 | fileFullCovered, 95 | fileHalfCovered, 96 | undefined, 97 | 40, 98 | 100 99 | ); 100 | }); 101 | 102 | it('should not regress', () => { 103 | // Total is now 50% but overall threshold is 40% so it still passes. 104 | expect(result.regression).toBe(false); 105 | }); 106 | 107 | it('should not be below threshold', () => { 108 | // Total is now 50% but overall threshold is 40% so it still passes. 109 | expect(result.belowThreshold).toBe(false); 110 | }); 111 | 112 | it('outputs correct total percentages', () => { 113 | expectToMatchObject(result.totals, { 114 | decreased: false, // Threshold is 100 115 | belowThreshold: false, // threshold is 40 116 | pcts: { 117 | branches: 50, 118 | functions: 50, 119 | lines: 50, 120 | statements: 50 121 | } 122 | }); 123 | }); 124 | }); 125 | 126 | describe('coverage decreased under total threshold', () => { 127 | beforeEach(() => { 128 | // Set coverageDecreaseThreshold to 100% so we only test coverageThreshold. 129 | result = diffChecker( 130 | fileFullCovered, 131 | fileHalfCovered, 132 | undefined, 133 | 60, 134 | 100 135 | ); 136 | }); 137 | 138 | it('should not regress', () => { 139 | // Total is now 50% but the first file didn't change so no regression 140 | expect(result.regression).toBe(false); 141 | }); 142 | 143 | it('should be below threshold', () => { 144 | // Total is now 50% but file threshold is 60% so it should be belowThreshold. 145 | expect(result.belowThreshold).toBe(true); 146 | }); 147 | 148 | it('outputs correct total percentages', () => { 149 | expectToMatchObject(result.totals, { 150 | belowThreshold: true, 151 | decreased: false, // Threshold is 60 152 | pcts: { 153 | branches: 50, 154 | functions: 50, 155 | lines: 50, 156 | statements: 50 157 | } 158 | }); 159 | }); 160 | }); 161 | 162 | describe('new file with no coverage below threshold', () => { 163 | beforeEach(() => { 164 | // Set coverageDecreaseThreshold to 100% so we only test coverageThreshold. 165 | result = diffChecker( 166 | fileFullCovered, 167 | newFileNotCovered, 168 | undefined, 169 | 60, 170 | 100, 171 | 60 172 | ); 173 | }); 174 | 175 | it('should not regress', () => { 176 | // Total is now 50% but the first file didn't change so no regression 177 | expect(result.regression).toBe(false); 178 | }); 179 | 180 | it('should be below threshold', () => { 181 | // Total is now 50% but file threshold is 60% so it should be belowThreshold. 182 | expect(result.belowThreshold).toBe(true); 183 | }); 184 | 185 | it('outputs correct total percentages', () => { 186 | expectToMatchObject(result.totals, { 187 | belowThreshold: true, 188 | decreased: false, // Threshold is 60 189 | pcts: { 190 | branches: 50, 191 | functions: 50, 192 | lines: 50, 193 | statements: 50 194 | } 195 | }); 196 | }); 197 | }); 198 | 199 | describe('new file with no coverage above threshold', () => { 200 | beforeEach(() => { 201 | // Set coverageDecreaseThreshold to 100% so we only test coverageThreshold. 202 | result = diffChecker( 203 | fileFullCovered, 204 | newFileNotCovered, 205 | undefined, 206 | 40, 207 | 100, 208 | 40 209 | ); 210 | }); 211 | 212 | it('should not regress', () => { 213 | // Total is now 50% but the first file didn't change so no regression 214 | expect(result.regression).toBe(false); 215 | }); 216 | 217 | it('should be below threshold', () => { 218 | // Total is now 50% but file threshold is 40%, and new file threshold is 40, new file is below threshold, global is above threshold 219 | expect(result.belowThreshold).toBe(true); 220 | }); 221 | 222 | it('outputs correct total percentages', () => { 223 | expectToMatchObject(result.totals, { 224 | belowThreshold: false, 225 | decreased: false, // Threshold is 40 226 | pcts: { 227 | branches: 50, 228 | functions: 50, 229 | lines: 50, 230 | statements: 50 231 | } 232 | }); 233 | }); 234 | }); 235 | 236 | describe('new file with half coverage below threshold', () => { 237 | beforeEach(() => { 238 | // Set coverageDecreaseThreshold to 100% so we only test coverageThreshold. 239 | result = diffChecker( 240 | fileFullCovered, 241 | newFileHalfCovered, 242 | undefined, 243 | 40, 244 | 100, 245 | 60 246 | ); 247 | }); 248 | 249 | it('should not regress', () => { 250 | // Total is now 75% and the first file didn't change so no regression 251 | expect(result.regression).toBe(false); 252 | }); 253 | 254 | it('should be below threshold', () => { 255 | // Total is now 75% but file threshold is 60%, new file is below threshold, global is above threshold 256 | expect(result.belowThreshold).toBe(true); 257 | }); 258 | 259 | it('outputs correct total percentages', () => { 260 | expectToMatchObject(result.totals, { 261 | belowThreshold: false, // Threshold is 60, total is 75, total is not below, but file is 262 | decreased: false, 263 | pcts: { 264 | branches: 75, 265 | functions: 75, 266 | lines: 75, 267 | statements: 75 268 | } 269 | }); 270 | }); 271 | }); 272 | 273 | describe('new file with half coverage above threshold', () => { 274 | beforeEach(() => { 275 | // Set coverageDecreaseThreshold to 100% so we only test coverageThreshold. 276 | result = diffChecker( 277 | fileFullCovered, 278 | newFileNotCovered, 279 | undefined, 280 | 40, 281 | 100, 282 | 45 283 | ); 284 | }); 285 | 286 | it('should not regress', () => { 287 | // Total is now 50% but the first file didn't change so no regression 288 | expect(result.regression).toBe(false); 289 | }); 290 | 291 | it('should not be below threshold', () => { 292 | // Total is now 50% but file threshold is 40%, and new file threshold is 45, new file is below threshold, global is above threshold 293 | expect(result.belowThreshold).toBe(true); 294 | }); 295 | 296 | it('outputs correct total percentages', () => { 297 | expectToMatchObject(result.totals, { 298 | belowThreshold: false, // Threshold is 40, total is 50 299 | decreased: false, 300 | pcts: { 301 | branches: 50, 302 | functions: 50, 303 | lines: 50, 304 | statements: 50 305 | } 306 | }); 307 | }); 308 | }); 309 | 310 | describe('skip non changed files', () => { 311 | it('should skip non changed files', () => { 312 | result = diffChecker(fileFullCovered, fileFullCovered); 313 | expect(result.files).toEqual({}); 314 | }); 315 | 316 | it('should return total percentages', () => { 317 | expect( 318 | diffChecker(fileFullCovered, fileFullCovered, undefined, 0).totals 319 | ).toMatchObject({ 320 | belowThreshold: false, // threshold is 100 321 | deltas: { lines: 0, functions: 0, statements: 0, branches: 0 }, 322 | pcts: { 323 | lines: 100, 324 | functions: 100, 325 | statements: 100, 326 | branches: 100 327 | } 328 | }); 329 | }); 330 | }); 331 | describe('only check lines', () => { 332 | it('should only check lines', () => { 333 | result = diffChecker(fileHalfCovered, onlyLinesIncreased, ['lines']); 334 | 335 | expect(result.files['fileA'].deltas.lines).toEqual(50); 336 | }); 337 | }); 338 | describe('total decreased', () => { 339 | it('should not regress', () => { 340 | // Set coverageThreshold to 0 as we have a new file half covered. 341 | expect( 342 | diffChecker( 343 | fileFullCovered, 344 | fileFullCoveredfileHalfCovered, 345 | undefined, 346 | 0 347 | ).regression 348 | ).toBe(false); 349 | }); 350 | }); 351 | }); 352 | -------------------------------------------------------------------------------- /src/diffChecker.ts: -------------------------------------------------------------------------------- 1 | import { coverageDiffer } from './coverageDiffer'; 2 | import { objectToMap, mapToObject } from './helpers'; 3 | import { getSummaryPercentages } from './helpers'; 4 | import { defaultOptions } from './index'; 5 | import { 6 | JsonSummary, 7 | FileResultFormat, 8 | DiffCheckResults, 9 | CoverageSummary, 10 | Criteria 11 | } from './common'; 12 | /** 13 | * Takes a json-summary formatted object (a diff) and checks if per-file 14 | * coverage changed (increase/decrease). 15 | * Returns a structured result and regression status. 16 | */ 17 | export const diffChecker = ( 18 | base: JsonSummary, 19 | head: JsonSummary, 20 | checkCriteria = defaultOptions.checkCriteria!, 21 | coverageThreshold = defaultOptions.coverageThreshold!, 22 | coverageDecreaseThreshold = defaultOptions.coverageDecreaseThreshold!, 23 | newFileCoverageThreshold = coverageThreshold 24 | ): DiffCheckResults => { 25 | let regression = false; 26 | let belowThreshold = false; 27 | const diff = coverageDiffer(base, head); 28 | const diffMap = objectToMap(diff); 29 | const percentageMap: Map = new Map(); 30 | const coverageDecreased = (x: number) => 31 | x < 0 ? Math.abs(x) >= coverageDecreaseThreshold : false; 32 | const isBelowThreshold = (x: number) => x < coverageThreshold; 33 | const isBelowNewFileThreshold = (x: number) => x < newFileCoverageThreshold; 34 | 35 | const checkItemBelowThreshold = ( 36 | diff: CoverageSummary, 37 | coverageToCompare: CoverageSummary, 38 | checkCriteria: Criteria[] 39 | ) => { 40 | const condition = diff.isNewFile 41 | ? isBelowNewFileThreshold 42 | : isBelowThreshold; 43 | return checkCoverageForCondition( 44 | coverageToCompare, 45 | checkCriteria, 46 | condition 47 | ); 48 | }; 49 | 50 | const checkItemDecreased = ( 51 | diff: CoverageSummary, 52 | checkCriteria: Criteria[] 53 | ) => { 54 | if (diff.isNewFile) return false; 55 | return checkCoverageForCondition(diff, checkCriteria, coverageDecreased); 56 | }; 57 | 58 | diffMap.forEach((diff, fileName) => { 59 | const diffPercentages = getSummaryPercentages(diff); 60 | if (shouldExcludeItem(diff, diffPercentages)) { 61 | return; 62 | } 63 | 64 | const itemDecreased = checkItemDecreased(diff, checkCriteria); 65 | const itemBelowThreshold = checkItemBelowThreshold( 66 | diff, 67 | head[fileName], 68 | checkCriteria 69 | ); 70 | 71 | // Coverage decreased on a file, regress. 72 | // only check file specific regressions, ignore regressions in the total, regression should still be set 73 | // if any non-new file was changed 74 | if (fileName !== 'total') { 75 | if (itemDecreased) { 76 | regression = true; 77 | } 78 | } 79 | // If Total or any file is below threshold, return belowThreshold as true 80 | if (itemBelowThreshold) { 81 | belowThreshold = true; 82 | } 83 | 84 | percentageMap.set(fileName, { 85 | deltas: { 86 | ...diffPercentages 87 | }, 88 | pcts: getSummaryPercentages(head[fileName]), 89 | isNewFile: diff.isNewFile, 90 | decreased: itemDecreased, 91 | belowThreshold: itemBelowThreshold 92 | }); 93 | }); 94 | 95 | let totals = percentageMap.get('total'); 96 | 97 | if (!totals) { 98 | totals = { 99 | deltas: { lines: 0, functions: 0, statements: 0, branches: 0 }, 100 | pcts: { 101 | lines: head.total.lines.pct, 102 | functions: head.total.functions.pct, 103 | statements: head.total.statements.pct, 104 | branches: head.total.branches.pct 105 | }, 106 | decreased: false, 107 | belowThreshold: checkCoverageForCondition( 108 | head.total, 109 | checkCriteria, 110 | isBelowThreshold 111 | ), 112 | isNewFile: false 113 | }; 114 | } 115 | 116 | // Exclude total from files output. 117 | percentageMap.delete('total'); 118 | 119 | return { 120 | files: mapToObject(percentageMap), 121 | diff, 122 | totals, 123 | regression, 124 | belowThreshold 125 | }; 126 | }; 127 | 128 | const checkCoverageForCondition = ( 129 | coverage: CoverageSummary, 130 | checkCriteria: Criteria[], 131 | condition: (x: number) => boolean 132 | ) => { 133 | const diffPercentages = getSummaryPercentages(coverage); 134 | 135 | const values = checkCriteria.map((criteria) => diffPercentages[criteria]); 136 | 137 | return values.some(condition); 138 | }; 139 | 140 | const zeroTest = (x: number) => x === 0; 141 | 142 | const shouldExcludeItem = ( 143 | diff: CoverageSummary, 144 | diffPercentages: ReturnType 145 | ) => { 146 | if (diff.isNewFile) { 147 | return false; 148 | } else { 149 | // if every value is zero, exclude the item from the diff because nothing has changed 150 | return Object.values(diffPercentages).every(zeroTest); 151 | } 152 | }; 153 | -------------------------------------------------------------------------------- /src/helpers.test.ts: -------------------------------------------------------------------------------- 1 | import { objectToMap, mapToObject } from './helpers'; 2 | 3 | const myObject = { 4 | foo: 'bar' 5 | }; 6 | const myMap = new Map([ 7 | ['foo', 'bar'], 8 | ['bar', 'foo'] 9 | ]); 10 | 11 | describe('helpers', () => { 12 | describe('objectToMap', () => { 13 | it('should return a map', () => { 14 | expect(objectToMap(myObject)).toBeInstanceOf(Map); 15 | }); 16 | 17 | it('should correctly map object data', () => { 18 | expect(objectToMap(myObject).get('foo')).toEqual('bar'); 19 | }); 20 | }); 21 | 22 | describe('objectToMap', () => { 23 | it('should correctly create object from map', () => { 24 | expect(mapToObject(myMap)).toMatchObject({ 25 | foo: 'bar', 26 | bar: 'foo' 27 | }); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | import { CoverageSummary } from './common'; 2 | 3 | // Typing this is so complex. Better to inline the code altogether. 4 | export const mapToObject = ( 5 | map: Map 6 | ): { [key: string]: T } => { 7 | let objMap: { [key: string]: T } = {}; 8 | map.forEach((v, k) => { 9 | objMap[k] = v; 10 | }); 11 | return objMap; 12 | }; 13 | 14 | export const objectToMap = (obj: { 15 | [key: string]: T; 16 | }): Map => new Map(Object.entries(obj)); 17 | 18 | export const getSummaryPercentages = (summary: CoverageSummary) => ({ 19 | lines: summary.lines.pct, 20 | statements: summary.statements.pct, 21 | functions: summary.functions.pct, 22 | branches: summary.branches.pct 23 | }); 24 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { ConfigOptions, CoverageDiffOutput } from './common'; 2 | import * as diffChecker from './diffChecker'; 3 | import * as resultFormatter from './resultFormatter'; 4 | import * as coverageDiff from './index'; 5 | 6 | jest.mock('./diffChecker'); 7 | jest.mock('./resultFormatter'); 8 | 9 | import { fileNotCovered, fileFullCovered } from './summaries.fixture'; 10 | 11 | const diffCheckerSpy = jest.spyOn(diffChecker, 'diffChecker'); 12 | const resultFormatterSpy = jest.spyOn(resultFormatter, 'resultFormatter'); 13 | 14 | describe('diff', () => { 15 | describe('default options', () => { 16 | let diffOutput: CoverageDiffOutput; 17 | beforeEach(() => { 18 | diffOutput = coverageDiff.diff(fileNotCovered, fileFullCovered); 19 | }); 20 | 21 | it('should call the diffChecker module', () => { 22 | expect(diffCheckerSpy).toHaveBeenCalledWith( 23 | fileNotCovered, 24 | fileFullCovered, 25 | coverageDiff.defaultOptions.checkCriteria, 26 | coverageDiff.defaultOptions.coverageThreshold, 27 | coverageDiff.defaultOptions.coverageDecreaseThreshold, 28 | coverageDiff.defaultOptions.newFileCoverageThreshold // will be undefined - defaults to coverageThreshold in diffChecker 29 | ); 30 | }); 31 | 32 | it('should call the resultFormatter module', () => { 33 | expect(resultFormatterSpy).toHaveBeenCalledWith('mockedFiles', { 34 | deltas: 'mockedTotals', 35 | pcts: 'mockedPcts' 36 | }); 37 | }); 38 | 39 | it('should return diff info', () => { 40 | expect(diffOutput).toEqual({ 41 | diff: 'mocked diff', 42 | results: 'resultFormatter mock', 43 | regression: 'mockedRegression' 44 | }); 45 | }); 46 | }); 47 | describe('custom Options', () => { 48 | it('should call the diffChecker module', () => { 49 | const mockedOptions: ConfigOptions = { 50 | checkCriteria: ['lines'], 51 | coverageThreshold: 100, 52 | coverageDecreaseThreshold: 0, 53 | newFileCoverageThreshold: 1, 54 | customFormatter: jest.fn() 55 | }; 56 | 57 | coverageDiff.diff(fileNotCovered, fileFullCovered, mockedOptions); 58 | 59 | expect(diffCheckerSpy).toHaveBeenCalledWith( 60 | fileNotCovered, 61 | fileFullCovered, 62 | mockedOptions.checkCriteria, 63 | mockedOptions.coverageThreshold, 64 | mockedOptions.coverageDecreaseThreshold, 65 | mockedOptions.newFileCoverageThreshold 66 | ); 67 | expect(mockedOptions.customFormatter).toHaveBeenCalledWith( 68 | 'mockedFiles', 69 | { 70 | deltas: 'mockedTotals', 71 | pcts: 'mockedPcts' 72 | } 73 | ); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { diffChecker } from './diffChecker'; 2 | import { resultFormatter } from './resultFormatter'; 3 | import { JsonSummary, CoverageDiffOutput, ConfigOptions } from './common'; 4 | 5 | export const defaultOptions: ConfigOptions = { 6 | checkCriteria: ['lines', 'branches', 'functions', 'statements'], 7 | coverageThreshold: 100, 8 | coverageDecreaseThreshold: 0 9 | }; 10 | 11 | /** 12 | * Compares two Istanbul json-summary formatted coverage objects and outputs the 13 | * diff between them. 14 | */ 15 | export function diff( 16 | base: JsonSummary, 17 | head: JsonSummary, 18 | options = defaultOptions 19 | ): CoverageDiffOutput { 20 | const { 21 | checkCriteria, 22 | coverageThreshold, 23 | coverageDecreaseThreshold, 24 | newFileCoverageThreshold, 25 | customFormatter 26 | } = options; 27 | 28 | const { regression, files, totals, diff, belowThreshold } = diffChecker( 29 | base, 30 | head, 31 | checkCriteria, 32 | coverageThreshold, 33 | coverageDecreaseThreshold, 34 | newFileCoverageThreshold 35 | ); 36 | 37 | const results = 38 | customFormatter?.(files, totals) || resultFormatter(files, totals); 39 | 40 | return { 41 | diff, 42 | results, 43 | regression, 44 | belowThreshold 45 | }; 46 | } 47 | export { JsonSummary, CoverageDiffOutput, ConfigOptions }; 48 | export default diff; 49 | -------------------------------------------------------------------------------- /src/resultFormatter.test.ts: -------------------------------------------------------------------------------- 1 | import { FilesResults, FileResultFormat } from './common'; 2 | import { resultFormatter } from './resultFormatter'; 3 | 4 | const filesResults: FilesResults = { 5 | file1: { 6 | isNewFile: false, 7 | deltas: { 8 | lines: 10, 9 | statements: -10, 10 | functions: 20, 11 | branches: -30 12 | }, 13 | pcts: { 14 | lines: 80, 15 | statements: 20, 16 | functions: 3, 17 | branches: 14 18 | }, 19 | decreased: true, 20 | belowThreshold: false 21 | }, 22 | file2: { 23 | isNewFile: false, 24 | deltas: { 25 | lines: 10, 26 | statements: 10, 27 | functions: 20, 28 | branches: 30 29 | }, 30 | pcts: { 31 | lines: 20, 32 | statements: 5, 33 | functions: 2, 34 | branches: 8 35 | }, 36 | decreased: false, 37 | belowThreshold: false 38 | }, 39 | file3: { 40 | isNewFile: false, 41 | deltas: { 42 | lines: 10, 43 | statements: 10, 44 | functions: 20, 45 | branches: 30 46 | }, 47 | pcts: { 48 | lines: 20, 49 | statements: 5, 50 | functions: 2, 51 | branches: 8 52 | }, 53 | decreased: false, 54 | belowThreshold: true 55 | }, 56 | file4: { 57 | isNewFile: true, 58 | deltas: { 59 | lines: 10, 60 | statements: 10, 61 | functions: 20, 62 | branches: 30 63 | }, 64 | pcts: { 65 | lines: 20, 66 | statements: 5, 67 | functions: 2, 68 | branches: 8 69 | }, 70 | decreased: false, 71 | belowThreshold: false 72 | } 73 | }; 74 | 75 | const totalResults: FileResultFormat = { 76 | isNewFile: false, 77 | deltas: { 78 | lines: 100, 79 | functions: 100, 80 | branches: 100, 81 | statements: 100 82 | }, 83 | pcts: { 84 | lines: 100, 85 | statements: 100, 86 | functions: 100, 87 | branches: 100 88 | }, 89 | decreased: false, 90 | belowThreshold: false 91 | }; 92 | 93 | describe('resultFormatter', () => { 94 | it('should format files results as markdown table', () => { 95 | expect(resultFormatter(filesResults, totalResults)).toMatchSnapshot(); 96 | }); 97 | 98 | it("should print descriptive message if coverage did't change", () => { 99 | expect(resultFormatter({}, totalResults)).toMatchSnapshot(); 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /src/resultFormatter.ts: -------------------------------------------------------------------------------- 1 | import markdownTable from 'markdown-table'; 2 | import { FilesResults, FileResultFormat } from './common'; 3 | 4 | export const resultFormatter = ( 5 | files: FilesResults, 6 | total: FileResultFormat 7 | ): string => { 8 | const formattedFiles = formatFilesResults(files); 9 | const formattedTotal = formatTotal(total); 10 | return `${formattedFiles}${formattedTotal}`; 11 | }; 12 | 13 | const formatTotal = (total: FileResultFormat): string => { 14 | const table: Array<(string | number)[]> = []; 15 | const { lines, branches, functions, statements } = total.pcts; 16 | 17 | const lDelta = formatDelta(total.deltas.lines); 18 | const bDelta = formatDelta(total.deltas.branches); 19 | const fDelta = formatDelta(total.deltas.functions); 20 | const sDelta = formatDelta(total.deltas.statements); 21 | 22 | table.push(['Lines', 'Branches', 'Functions', 'Statements']); 23 | table.push([ 24 | `${lines}%(${lDelta})`, 25 | `${branches}%(${bDelta})`, 26 | `${functions}%(${fDelta})`, 27 | `${statements}%(${sDelta})` 28 | ]); 29 | 30 | return '\n\nTotal:\n\n' + markdownTable(table); 31 | }; 32 | 33 | const formatFilesResults = (files: FilesResults): string => { 34 | let noChange = true; 35 | const table: Array<(string | number)[]> = []; 36 | const header = [ 37 | 'Ok', 38 | 'File (✨=New File)', 39 | 'Lines', 40 | 'Branches', 41 | 'Functions', 42 | 'Statements' 43 | ]; 44 | table.push(header); 45 | 46 | Object.keys(files).forEach((file) => { 47 | const { deltas, pcts, decreased, belowThreshold, isNewFile } = files[file]; 48 | const row = [ 49 | decreased || belowThreshold ? '🔴' : '✅', 50 | `${isNewFile ? '✨ ' : ''}${file}`, 51 | `${pcts.lines}%
(${formatDelta(deltas.lines)})`, 52 | `${pcts.branches}%
(${formatDelta(deltas.branches)})`, 53 | `${pcts.functions}%
(${formatDelta(deltas.functions)})`, 54 | `${pcts.statements}%
(${formatDelta(deltas.statements)})` 55 | ]; 56 | 57 | table.push(row); 58 | noChange = false; 59 | }); 60 | 61 | return noChange ? 'Coverage values did not change👌.' : markdownTable(table); 62 | }; 63 | 64 | const formatDelta = (num: number): string => { 65 | return num >= 0 ? `+${num}%` : `${num}%`; 66 | }; 67 | -------------------------------------------------------------------------------- /src/summaries.fixture.ts: -------------------------------------------------------------------------------- 1 | import { JsonSummary } from './common'; 2 | 3 | export const fileFullCovered: JsonSummary = { 4 | total: { 5 | isNewFile: false, 6 | lines: { 7 | total: 2, 8 | covered: 2, 9 | skipped: 0, 10 | pct: 100 11 | }, 12 | statements: { 13 | total: 2, 14 | covered: 2, 15 | skipped: 0, 16 | pct: 100 17 | }, 18 | functions: { 19 | total: 2, 20 | covered: 2, 21 | skipped: 0, 22 | pct: 100 23 | }, 24 | branches: { 25 | total: 2, 26 | covered: 2, 27 | skipped: 0, 28 | pct: 100 29 | } 30 | }, 31 | fileA: { 32 | isNewFile: false, 33 | lines: { 34 | total: 2, 35 | covered: 2, 36 | skipped: 0, 37 | pct: 100 38 | }, 39 | statements: { 40 | total: 2, 41 | covered: 2, 42 | skipped: 0, 43 | pct: 100 44 | }, 45 | functions: { 46 | total: 2, 47 | covered: 2, 48 | skipped: 0, 49 | pct: 100 50 | }, 51 | branches: { 52 | total: 2, 53 | covered: 2, 54 | skipped: 0, 55 | pct: 100 56 | } 57 | } 58 | }; 59 | 60 | export const fileFullCoveredfileHalfCovered: JsonSummary = { 61 | ...fileFullCovered, 62 | fileB: { 63 | isNewFile: false, 64 | lines: { 65 | total: 2, 66 | covered: 1, 67 | skipped: 0, 68 | pct: 50 69 | }, 70 | statements: { 71 | total: 2, 72 | covered: 1, 73 | skipped: 0, 74 | pct: 50 75 | }, 76 | functions: { 77 | total: 2, 78 | covered: 1, 79 | skipped: 0, 80 | pct: 50 81 | }, 82 | branches: { 83 | total: 2, 84 | covered: 1, 85 | skipped: 0, 86 | pct: 50 87 | } 88 | }, 89 | total: { 90 | isNewFile: false, 91 | lines: { 92 | total: 4, 93 | covered: 3, 94 | skipped: 0, 95 | pct: 75 96 | }, 97 | statements: { 98 | total: 4, 99 | covered: 3, 100 | skipped: 0, 101 | pct: 75 102 | }, 103 | functions: { 104 | total: 4, 105 | covered: 3, 106 | skipped: 0, 107 | pct: 75 108 | }, 109 | branches: { 110 | total: 4, 111 | covered: 3, 112 | skipped: 0, 113 | pct: 75 114 | } 115 | } 116 | }; 117 | 118 | export const fileNotCovered: JsonSummary = { 119 | total: { 120 | isNewFile: false, 121 | lines: { 122 | total: 2, 123 | covered: 0, 124 | skipped: 0, 125 | pct: 0 126 | }, 127 | statements: { 128 | total: 2, 129 | covered: 0, 130 | skipped: 0, 131 | pct: 0 132 | }, 133 | functions: { 134 | total: 2, 135 | covered: 0, 136 | skipped: 0, 137 | pct: 0 138 | }, 139 | branches: { 140 | total: 2, 141 | covered: 0, 142 | skipped: 0, 143 | pct: 0 144 | } 145 | }, 146 | fileA: { 147 | isNewFile: false, 148 | lines: { 149 | total: 2, 150 | covered: 0, 151 | skipped: 0, 152 | pct: 0 153 | }, 154 | statements: { 155 | total: 2, 156 | covered: 0, 157 | skipped: 0, 158 | pct: 0 159 | }, 160 | functions: { 161 | total: 2, 162 | covered: 0, 163 | skipped: 0, 164 | pct: 0 165 | }, 166 | branches: { 167 | total: 2, 168 | covered: 0, 169 | skipped: 0, 170 | pct: 0 171 | } 172 | } 173 | }; 174 | 175 | export const fileHalfCovered: JsonSummary = { 176 | total: { 177 | isNewFile: false, 178 | lines: { 179 | total: 2, 180 | covered: 1, 181 | skipped: 0, 182 | pct: 50 183 | }, 184 | statements: { 185 | total: 2, 186 | covered: 1, 187 | skipped: 0, 188 | pct: 50 189 | }, 190 | functions: { 191 | total: 2, 192 | covered: 1, 193 | skipped: 0, 194 | pct: 50 195 | }, 196 | branches: { 197 | total: 2, 198 | covered: 1, 199 | skipped: 0, 200 | pct: 50 201 | } 202 | }, 203 | fileA: { 204 | isNewFile: false, 205 | lines: { 206 | total: 2, 207 | covered: 1, 208 | skipped: 0, 209 | pct: 50 210 | }, 211 | statements: { 212 | total: 2, 213 | covered: 1, 214 | skipped: 0, 215 | pct: 50 216 | }, 217 | functions: { 218 | total: 2, 219 | covered: 1, 220 | skipped: 0, 221 | pct: 50 222 | }, 223 | branches: { 224 | total: 2, 225 | covered: 1, 226 | skipped: 0, 227 | pct: 50 228 | } 229 | } 230 | }; 231 | 232 | export const coverageNotChanged: JsonSummary = { 233 | ...fileFullCovered, 234 | fileA: { 235 | isNewFile: false, 236 | lines: { 237 | total: 2, 238 | covered: 1, 239 | skipped: 0, 240 | pct: 0 241 | }, 242 | statements: { 243 | total: 2, 244 | covered: 1, 245 | skipped: 0, 246 | pct: 0 247 | }, 248 | functions: { 249 | total: 2, 250 | covered: 1, 251 | skipped: 0, 252 | pct: 0 253 | }, 254 | branches: { 255 | total: 2, 256 | covered: 1, 257 | skipped: 0, 258 | pct: 0 259 | } 260 | } 261 | }; 262 | 263 | export const coverageDecreased: JsonSummary = { 264 | total: { 265 | isNewFile: false, 266 | lines: { 267 | total: 0, 268 | covered: 1, 269 | skipped: 0, 270 | pct: 50 271 | }, 272 | statements: { 273 | total: 0, 274 | covered: 1, 275 | skipped: 0, 276 | pct: 50 277 | }, 278 | functions: { 279 | total: 0, 280 | covered: 1, 281 | skipped: 0, 282 | pct: 50 283 | }, 284 | branches: { 285 | total: 0, 286 | covered: 1, 287 | skipped: 0, 288 | pct: 50 289 | } 290 | }, 291 | fileA: { 292 | isNewFile: false, 293 | lines: { 294 | total: 0, 295 | covered: -1, 296 | skipped: 0, 297 | pct: -50 298 | }, 299 | statements: { 300 | total: 0, 301 | covered: -1, 302 | skipped: 0, 303 | pct: -50 304 | }, 305 | functions: { 306 | total: 0, 307 | covered: -1, 308 | skipped: 0, 309 | pct: -50 310 | }, 311 | branches: { 312 | total: 0, 313 | covered: -1, 314 | skipped: 0, 315 | pct: -50 316 | } 317 | } 318 | }; 319 | 320 | export const totalDecreased: JsonSummary = { 321 | ...coverageNotChanged, 322 | total: { 323 | isNewFile: false, 324 | lines: { 325 | total: 2, 326 | covered: 1, 327 | skipped: 0, 328 | pct: -50 329 | }, 330 | statements: { 331 | total: 2, 332 | covered: 1, 333 | skipped: 0, 334 | pct: -50 335 | }, 336 | functions: { 337 | total: 2, 338 | covered: 1, 339 | skipped: 0, 340 | pct: -50 341 | }, 342 | branches: { 343 | total: 2, 344 | covered: 1, 345 | skipped: 0, 346 | pct: -50 347 | } 348 | } 349 | }; 350 | 351 | export const onlyLinesIncreased: JsonSummary = { 352 | ...fileHalfCovered, 353 | fileA: { 354 | ...fileHalfCovered.fileA, 355 | lines: { 356 | total: 2, 357 | covered: 2, 358 | skipped: 0, 359 | pct: 100 360 | } 361 | } 362 | }; 363 | 364 | export const newFile: JsonSummary = { 365 | ...fileFullCovered, 366 | fileB: { 367 | ...fileFullCovered.fileA 368 | } 369 | }; 370 | 371 | export const newFileNotCovered: JsonSummary = { 372 | ...fileFullCovered, 373 | total: { 374 | isNewFile: false, 375 | lines: { 376 | total: 4, 377 | covered: 2, 378 | skipped: 0, 379 | pct: 50 380 | }, 381 | statements: { 382 | total: 4, 383 | covered: 2, 384 | skipped: 0, 385 | pct: 50 386 | }, 387 | functions: { 388 | total: 4, 389 | covered: 2, 390 | skipped: 0, 391 | pct: 50 392 | }, 393 | branches: { 394 | total: 4, 395 | covered: 2, 396 | skipped: 0, 397 | pct: 50 398 | } 399 | }, 400 | fileB: { 401 | ...fileNotCovered.fileA, 402 | isNewFile: true 403 | } 404 | }; 405 | 406 | export const newFileHalfCovered: JsonSummary = { 407 | ...fileFullCovered, 408 | total: { 409 | isNewFile: false, 410 | lines: { 411 | total: 4, 412 | covered: 3, 413 | skipped: 0, 414 | pct: 75 415 | }, 416 | statements: { 417 | total: 4, 418 | covered: 3, 419 | skipped: 0, 420 | pct: 75 421 | }, 422 | functions: { 423 | total: 4, 424 | covered: 3, 425 | skipped: 0, 426 | pct: 75 427 | }, 428 | branches: { 429 | total: 4, 430 | covered: 3, 431 | skipped: 0, 432 | pct: 75 433 | } 434 | }, 435 | fileB: { 436 | ...fileHalfCovered.fileA, 437 | isNewFile: true 438 | } 439 | }; 440 | -------------------------------------------------------------------------------- /src/testHelpers.ts: -------------------------------------------------------------------------------- 1 | type RecursivePartial = { 2 | [P in keyof T]?: T[P] | RecursivePartial; 3 | }; 4 | 5 | export const expectToEqual: (actual: T, expected: T) => void = ( 6 | actual, 7 | expected 8 | ) => { 9 | expect(actual).toEqual(expected); 10 | }; 11 | 12 | export const expectToMatchObject: ( 13 | ...args: [T, RecursivePartial] | [T[], Array>] 14 | ) => void = (actual, expected) => { 15 | expect(actual).toMatchObject(expected); 16 | }; 17 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["**/*.test.*", "**/*.fixture.*", "**/__mocks__/"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2021", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "lib", 7 | "removeComments": true, 8 | "strict": true, 9 | "esModuleInterop": true 10 | }, 11 | "include": ["./src/**/*", "./typings/**/*"] 12 | } 13 | -------------------------------------------------------------------------------- /typings/markdown-table.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'markdown-table' { 2 | export default function markdownTable( 3 | table: Array<(string | number)[]> 4 | ): string; 5 | } 6 | --------------------------------------------------------------------------------