├── .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 |
--------------------------------------------------------------------------------