Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adds name option #233

Merged
merged 4 commits into from
Jun 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ jobs:
uses: ./
with:
working-directory: "./test/mockReports"
name: "Mock Reports"
- name: "Test Default Action"
# run step also on failure of the previous step
if: always()
uses: ./
with:
file-coverage-mode: "all"
name: "Root"
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,37 @@ This action requires permissions set to `pull-request: write` in order for it to
| `vite-config-path` | The path to the vite config file. Will check the same paths as vite and vitest | Checks pattern `vite[st].config.{t|mt|ct|j|mj|cj}s` |
| `github-token` | A GitHub access token with permissions to write to issues (defaults to `secrets.GITHUB_TOKEN`). | `${{ github.token }}` |
| `working-directory` | Run action within a custom directory (for monorepos). | `./` |
| `name` | Give the report a name. This is useful if you want multiple reports for different test suites within the same PR. Needs to be unique. | '' |
| `file-coverage-mode`| Defines how file-based coverage is reported. Possible values are `all`, `changes` or `none`. | `changes` |

### File Coverage Mode
#### File Coverage Mode

* `changes` - show Files coverage only for project files changed in that pull request (works only with `pull_request`, `pull_request_review`, `pull_request_review_comment` actions)
* `all` - show it grouped by changed and not changed files in that pull request (works only with `pull_request`, `pull_request_review`, `pull_request_review_comment` actions)
* `none` - do not show any File coverage details (only total Summary)

#### Name

If you have multiple test-suites but want to report the coverage in a single PR, you have to provide a unique `name` for each action-step that parses a summary-report, e.g.:

```yml
## ...
- name: 'Report Frontend Coverage'
if: always() # Also generate the report if tests are failing
uses: davelosert/vitest-coverage-report-action@v2
with:
name: 'Frontend'
json-summary-path: './coverage/coverage-summary-frontend.json'
json-final-path: './coverage/coverage-final-frontend.json
- name: 'Report Backend Coverage'
if: always() # Also generate the report if tests are failing
uses: davelosert/vitest-coverage-report-action@v2
with:
name: 'Backend'
json-summary-path: './coverage/coverage-summary-backend.json'
json-final-path: './coverage/coverage-final-backend.json'
```

### 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 Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ inputs:
required: false
description: 'Custom working directory'
default: ./
name:
required: false
description: 'The name of the coverage report. Can be used to execute this action multiple times. '
default: ''
runs:
using: 'node16'
main: 'dist/index.js'
Expand Down
40 changes: 40 additions & 0 deletions src/generateHeadline.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { describe, it, expect } from 'vitest';
import { generateHeadline } from './generateHeadline';

describe('generateHeadline()', () => {
it('returns only static headline if no name and no working-directory is provided.', () => {
const headline = generateHeadline({
name: '',
workingDirectory: './',
});

expect(headline).toEqual('Coverage Report');
});

it('adds name to headline if only name is provided.', () => {
const headline = generateHeadline({
name: 'My Project',
workingDirectory: './'
});

expect(headline).toEqual('Coverage Report for My Project');
});

it('adds working-directory to headline if only working-directory is provided.', () => {
const headline = generateHeadline({
workingDirectory: '/path/to/project',
name: ''
});

expect(headline).toEqual('Coverage Report for /path/to/project');
});

it('adds name and working-directory in parentheses to headline if both are provided.', () => {
const headline = generateHeadline({
name: 'My Project',
workingDirectory: '/path/to/project'
});

expect(headline).toEqual('Coverage Report for My Project (/path/to/project)');
});
});
25 changes: 25 additions & 0 deletions src/generateHeadline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

type HeadlineArgs = {
workingDirectory?: string;
name?: string;
}

function generateHeadline(options: HeadlineArgs) {
if(options.name && options.workingDirectory !== './') {
return `Coverage Report for ${options.name} (${options.workingDirectory})`;
}

if(options.name) {
return `Coverage Report for ${options.name}`;
}

if(options.workingDirectory !== './') {
return `Coverage Report for ${options.workingDirectory}`;
}

return "Coverage Report";
}

export {
generateHeadline
};
60 changes: 21 additions & 39 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,29 @@
import { generateSummaryTableHtml } from './generateSummaryTableHtml.js';
import path from 'node:path';
import { parseVitestJsonFinal, parseVitestJsonSummary } from './parseJsonReports.js';
import { writeSummaryToPR } from './writeSummaryToPR.js';
import * as core from '@actions/core';
import { RequestError } from '@octokit/request-error'
import { parseCoverageThresholds } from './parseCoverageThresholds.js';
import { generateFileCoverageHtml } from './generateFileCoverageHtml.js';
import { getViteConfigPath } from './getViteConfigPath.js';
import { getPullChanges } from './getPullChanges.js';
import { FileCoverageMode, getCoverageModeFrom } from './FileCoverageMode.js'
import { FileCoverageMode } from './FileCoverageMode.js'
import { readOptions } from './options.js';
import { generateHeadline } from './generateHeadline.js';

const run = async () => {
const {
workingDirectory,
fileCoverageMode,
jsonSummaryPath,
jsonFinalPath,
thresholds
jsonSummaryPath,
name,
thresholds,
workingDirectory
} = await readOptions();

const jsonSummary = await parseVitestJsonSummary(jsonSummaryPath);

let summaryHeading = "Coverage Summary";
if (workingDirectory !== './') {
summaryHeading += ` for \`${workingDirectory}\``;
}

const jsonSummary = await parseVitestJsonSummary(jsonSummaryPath);
const tableData = generateSummaryTableHtml(jsonSummary.total, thresholds);
const summary = core.summary
.addHeading(summaryHeading, 2)
.addHeading(generateHeadline({ workingDirectory, name }), 2)
.addRaw(tableData)

if (fileCoverageMode !== FileCoverageMode.None) {
Expand All @@ -41,7 +36,10 @@ const run = async () => {
}

try {
await writeSummaryToPR(summary);
await writeSummaryToPR({
summary,
markerPostfix: getMarkerPostfix({ name, workingDirectory })
});
} catch (error) {
if (error instanceof RequestError && (error.status === 404 || error.status === 403)) {
core.warning(
Expand All @@ -59,31 +57,15 @@ const run = async () => {
await summary.write();
};

function getMarkerPostfix({ name, workingDirectory }: { name: string, workingDirectory: string }) {
if(name) return name;
if(workingDirectory !== './') return workingDirectory;
return 'root'
}


run().then(() => {
core.info('Report generated successfully.');
}).catch((err) => {
core.error(err);
});

async function readOptions() {
// Working directory can be used to modify all default/provided paths (for monorepos, etc)
const workingDirectory = core.getInput('working-directory');

const fileCoverageModeRaw = core.getInput('file-coverage-mode'); // all/changes/none
const fileCoverageMode = getCoverageModeFrom(fileCoverageModeRaw);

const jsonSummaryPath = path.resolve(workingDirectory, core.getInput('json-summary-path'));
const viteConfigPath = await getViteConfigPath(workingDirectory, core.getInput("vite-config-path"));

const thresholds = await parseCoverageThresholds(viteConfigPath);

const jsonFinalPath = path.resolve(workingDirectory, core.getInput('json-final-path'));

return {
workingDirectory,
fileCoverageMode,
jsonSummaryPath,
thresholds,
jsonFinalPath
}
}
});
34 changes: 34 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as core from '@actions/core';
import { getCoverageModeFrom } from './FileCoverageMode';
import * as path from 'node:path';
import { getViteConfigPath } from './getViteConfigPath';
import { parseCoverageThresholds } from './parseCoverageThresholds';

async function readOptions() {
// Working directory can be used to modify all default/provided paths (for monorepos, etc)
const workingDirectory = core.getInput('working-directory');

const fileCoverageModeRaw = core.getInput('file-coverage-mode'); // all/changes/none
const fileCoverageMode = getCoverageModeFrom(fileCoverageModeRaw);

const jsonSummaryPath = path.resolve(workingDirectory, core.getInput('json-summary-path'));
const viteConfigPath = await getViteConfigPath(workingDirectory, core.getInput("vite-config-path"));

const thresholds = await parseCoverageThresholds(viteConfigPath);

const jsonFinalPath = path.resolve(workingDirectory, core.getInput('json-final-path'));
const name = core.getInput('name');

return {
fileCoverageMode,
jsonFinalPath,
jsonSummaryPath,
name,
thresholds,
workingDirectory
}
}

export {
readOptions
}
11 changes: 7 additions & 4 deletions src/writeSummaryToPR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import * as github from '@actions/github';
import * as core from '@actions/core';


const COMMENT_MARKER = '<!-- vitest-coverage-report-marker -->';
const COMMENT_MARKER = (markerPostfix = 'root') => `<!-- vitest-coverage-report-marker-${markerPostfix} -->`;
type Octokit = ReturnType<typeof github.getOctokit>;

const writeSummaryToPR = async (summary: typeof core.summary) => {
const writeSummaryToPR = async ({ summary, markerPostfix }: {
summary: typeof core.summary;
markerPostfix?: string;
}) => {
if (!github.context.payload.pull_request) {
core.info('[vitest-coverage-report] Not in the context of a pull request. Skipping comment creation.');
return;
Expand All @@ -14,8 +17,8 @@ const writeSummaryToPR = async (summary: typeof core.summary) => {
const gitHubToken = core.getInput('github-token').trim();
const octokit: Octokit = github.getOctokit(gitHubToken);

const commentBody = `${summary.stringify()}\n\n${COMMENT_MARKER}`;
const existingComment = await findCommentByBody(octokit, COMMENT_MARKER);
const commentBody = `${summary.stringify()}\n\n${COMMENT_MARKER(markerPostfix)}`;
const existingComment = await findCommentByBody(octokit, COMMENT_MARKER(markerPostfix));

if (existingComment) {
await octokit.rest.issues.updateComment({
Expand Down