Skip to content

Commit

Permalink
feat: Adds file based coverage report (#8)
Browse files Browse the repository at this point in the history
* feat: Adds first implementation of uncovered lines

* refactor: Generalizes summary-function to be reused for file-based reports

* feat: Adds detailed file report

* feat: switches to use own html table template

* ci: Try actions checkout v3 to have correct sha

* chore: Adds error-handling for json file parsing

* docs: Adjusts action icon

* docs: Adds new image and describes new file-report

* docs: Replaces thresholds image
  • Loading branch information
davelosert authored Jul 8, 2022
1 parent 415a009 commit 34f51ec
Show file tree
Hide file tree
Showing 23 changed files with 507 additions and 108 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: 'Install Node'
uses: actions/setup-node@v2
with:
Expand Down
44 changes: 27 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,29 @@ A GitHub Action to report [vitest](https://vitest.dev/) coverage results as a Gi

![Coverage Report as Step Summary](./docs/coverage-report.png)

## Usage
It will create a high-level coverage summary for all coverage-category as well as a file-based report linking to the files itself and the uncovered lines for easy discovery.

This action requires you to use `vitest` to create a `json-summary` report as `coverage/coverage-summary.json` (will be configurable in the future) before invoking the action. You can do this by either running `vitest` like this:
## Usage

```shell
npx vitest run --coverage.reporter json-summary
```
This action requires you to use `vitest` to create a coverage report with the `json-summary`-reporter and optionally the `json`-reporter (if you don't provide it, uncovered lines won't appear in the report).

Or by adding the configuration to you `vite.config.js`-File:
You can configure the reporters within the `vitest.config.js` file likes this:

```js
import { defineConfig } from 'vite';

export default defineConfig({
test: {
coverage: {
// you can include other reporters, but 'json-summary' is required
reporter: ['text', 'json-summary'],
// you can include other reporters, but 'json-summary' is required, json is recommended
reporter: ['text', 'json-summary', 'json'],
}
}
});
```

Then execute `npx vitest --coverage` in a step before this action.

### Example Workflow

```yml
Expand All @@ -46,12 +46,21 @@ jobs:
- name: 'Install Deps'
run: npm install
- name: 'Test'
run: npx vitest --coverage.report json-summary
run: npx vitest --coverage
- name: 'Report Coverage'
if: always() # Also generate the report if tests are failing
uses: davelosert/vitest-coverage-report-action@v1
```
### Options
| Option | Description | Default |
| ----------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------- |
| json-summary-path | The path to the json summary file. Uses "coverage/coverage-summary.json" by default. | `./coverage/coverage-summary.json` |
| json-final-path | The path to the json final file. Uses "coverage/coverage-final.json" by default. | `./coverage/coverage-final.json` |
| vite-config-path | The path to the vite config file. Uses "vite.config.js" by default. | `./vitest.config.js` |
| github-token | A github access token with permissions to write to issues. Uses secrets.GITHUB_TOKEN by default. | `./vitest.config.js` |

### Coverage Thresholds

This action will read the coverage thresholds defined in the `coverage`-property of the `vite.config.js`-file and mark the status of the generated report accordingly.
Expand All @@ -64,10 +73,10 @@ import { defineConfig } from 'vite';
export default defineConfig({
test: {
coverage: {
lines: 80,
branches: 80,
functions: 80,
statements: 80
lines: 60,
branches: 60,
functions: 60,
statements: 60
}
}
});
Expand All @@ -77,19 +86,20 @@ the report would look like this:

![Coverage Threshold Report](./docs/coverage-report-threshold.png)

If there are no thresholds defined, the status will be '🔵'.

## Current Status

This is a work in progress project. Currently, it will only take an already created `json-summary`-report, convert it to markdown and export it to:
This is a work in progress project. Currently, it will only take an already created `json-summary` and `json`-report, convert it to markdown and export it to:

1. a comment within an associated pull-request (if there is one)
2. the [GitHub Step Summary](https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables) of the current action

### Future Plans

- [x] Make summary file configurable
- [ ] Also report detailed file-coverage (coverage per file and unconvered lines) based on the `json`-Reporter
- [x] Also report detailed file-coverage (coverage per file and unconvered lines) based on the `json`-Reporter
- [ ] Invoke 'vitest' directly from the action
- [ ] Also provide test results (failed tests etc.) in the generated markdown reports
- [ ] Add option to let the action fail if coverage thresholds are not met
- [ ] Also report test results themselves
- [ ] Beatufiy the report with better markdown
- [x] Beatufiy the report with better markdown
9 changes: 8 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ author: 'David Losert'
inputs:
github-token:
required: false
description: 'A github access token. Uses secrets.GITHUB_TOKEN by default.'
description: 'A github access token with permissions to write to issues. Uses secrets.GITHUB_TOKEN by default.'
default: ${{ github.token }}
vite-config-path:
required: false
Expand All @@ -14,6 +14,13 @@ inputs:
required: false
description: 'The path to the json summary file. Uses "coverage/coverage-summary.json" by default.'
default: coverage/coverage-summary.json
json-final-path:
required: false
description: 'The path to the json final file. Uses "coverage/coverage-final.json" by default.'
default: coverage/coverage-final.json
runs:
using: 'node16'
main: 'dist/index.js'
branding:
icon: 'check-circle'
color: 'green'
Binary file modified docs/coverage-report-threshold.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/coverage-report.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
44 changes: 43 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@
"homepage": "https://github.com/davelosert/vitest-coverage-report-action#readme",
"dependencies": {
"@actions/core": "1.8.2",
"@actions/github": "5.0.3"
"@actions/github": "5.0.3",
"common-tags": "1.8.2",
"markdown-table": "3.0.2"
},
"devDependencies": {
"@semantic-release/exec": "6.0.3",
"@types/common-tags": "1.8.1",
"@types/node": "17.0.41",
"c8": "7.11.3",
"esbuild": "0.14.43",
Expand Down
70 changes: 70 additions & 0 deletions src/generateFileCoverageHtml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as path from 'path';
import { generateBlobFileUrl } from './generateFileUrl';
import { getUncoveredLinesFromStatements } from './getUncoveredLinesFromStatements';
import { JsonFinal } from './types/JsonFinal';
import { JsonSummary } from './types/JsonSummary';
import { Thresholds } from './types/Threshold';
import { oneLine } from 'common-tags';

type Sources = {
jsonSummary: JsonSummary;
jsonFinal: JsonFinal;
}

const workspacePath = process.cwd();
const generateFileCoverageHtml = ({ jsonSummary, jsonFinal }: Sources) => {
const filePaths = Object.keys(jsonSummary).filter((key) => key !== 'total');
const reportData: string = filePaths.map((filePath) => {
const coverageSummary = jsonSummary[filePath];
const lineCoverage = jsonFinal[filePath];

// LineCoverage might be empty if coverage-final.json was not provided.
const uncoveredLines = lineCoverage ? getUncoveredLinesFromStatements(jsonFinal[filePath]) : [];
const relativeFilePath = path.relative(workspacePath, filePath);
const url = generateBlobFileUrl(relativeFilePath);

return `
<tr>
<td align="left"><a href="${url}">${relativeFilePath}</a></td>
<td align="right">${coverageSummary.statements.pct}%</td>
<td align="right">${coverageSummary.branches.pct}%</td>
<td align="right">${coverageSummary.functions.pct}%</td>
<td align="right">${coverageSummary.lines.pct}%</td>
<td align="left">${uncoveredLines.map((range) => {
let end = '';
let endUrl = '';
if(range.start !== range.end) {
end = `-${range.end}`;
endUrl = `-L${range.end}`;
}
const rangeUrl = `${url}#L${range.start}${endUrl}`;
return `<a href="${rangeUrl}">${range.start}${end}</a>`;
}).join(', ')}</td>
</tr>`
}).join('');

return oneLine`
<table>
<thead>
<tr>
<th align="left">File</th>
<th align="right">Stmts</th>
<th align="right">% Branch</th>
<th align="right">% Funcs</th>
<th align="right">% Lines</th>
<th align="left">Uncovered Lines</th>
</tr>
</thead>
<tbody>
${ reportData }
</tbody>
</table>
`
}

export {
generateFileCoverageHtml
};
18 changes: 18 additions & 0 deletions src/generateFileUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as github from '@actions/github';

const generateBlobFileUrl = (relativeFilePath: string) => {
const sha = github.context.payload.pull_request ?
github.context.payload.pull_request.head.sha
: github.context.sha

return [
github.context.serverUrl,
github.context.repo.owner,
github.context.repo.repo,
'blob',
sha,
relativeFilePath
].join('/')
};

export { generateBlobFileUrl };
49 changes: 0 additions & 49 deletions src/generateSummaryTableData.ts

This file was deleted.

Loading

0 comments on commit 34f51ec

Please sign in to comment.