Compare commits

..

21 Commits

Author SHA1 Message Date
Jozef Izso
7b7927aa7d test-reporter release v2.2.0 2025-11-13 21:35:26 +01:00
Jozef Izso
eeac280b8e Merge pull request #676 from dorny/dependabot/npm_and_yarn/js-yaml-4.1.1 2025-11-13 20:55:51 +01:00
dependabot[bot]
6939db53fb Bump js-yaml from 4.1.0 to 4.1.1
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-12 23:17:28 +00:00
Jozef Izso
b3812e0f5b Merge pull request #664 from pespinel/feature/collapsed-option 2025-11-12 18:03:25 +01:00
Jozef Izso
cd299561e7 tests: refactor input collapsed=auto to individual tests
Generated-by: Claude Sonnet 4.5
2025-11-12 14:42:24 +01:00
Jozef Izso
c7935221e6 feat: add validation for the collapsed input parameter
Generated-by: Claude Sonnet 4.5
2025-11-12 14:42:24 +01:00
Jozef Izso
5fb0582760 chore: run linter to fix code style issues 2025-11-12 14:42:24 +01:00
pespinel
7148297f02 test: fix linter and create tests 2025-11-12 14:42:23 +01:00
pespinel
828632acd0 feat: add collapsed option to control report visibility 2025-11-12 14:42:23 +01:00
Jozef Izso
4a41472ca4 Merge pull request #672 from dorny/bugfix/671-allow_hyphens_in_badge_image_names 2025-11-10 17:14:53 +01:00
Jozef Izso
22dc7b52f4 Rebuild dist/ code 2025-11-05 22:54:32 +01:00
Jozef Izso
bed521d765 Fix badge encoding for values including the _ underscore character 2025-11-05 22:54:32 +01:00
Jozef Izso
6079ce3d17 Add unit tests for getBadge() function to ensure values are encoded for img.shields.io service 2025-11-05 22:54:32 +01:00
Jozef Izso
de77f76b7e Fix badge image by correctly encoding the URI components 2025-11-05 21:18:09 +01:00
Jozef Izso
c883ae9738 Merge pull request #668 from dorny/feature/update_packages 2025-10-25 11:54:17 +02:00
Jozef Izso
35be98f7e7 Update npm packages to latest minor updates
Update report:
 @types/node  ^20.19.13  →  ^20.19.23
 @vercel/ncc    ^0.38.3  →    ^0.38.4
 jest           ^30.1.3  →    ^30.2.0
 ts-jest        ^29.4.1  →    ^29.4.5
 typescript      ^5.9.2  →     ^5.9.3
2025-10-25 11:30:47 +02:00
Jozef Izso
f372a8338e Merge pull request #662 from dorny/dependabot/github_actions/actions/setup-node-6 2025-10-25 11:09:08 +02:00
Jozef Izso
948dd03d7b Merge pull request #665 from dorny/dependabot/github_actions/actions/upload-artifact-5 2025-10-25 11:06:43 +02:00
dependabot[bot]
cf9db500ed Bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-24 23:05:54 +00:00
dependabot[bot]
ba33405987 Bump actions/setup-node from 5 to 6
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 5 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-14 23:06:32 +00:00
Jozef Izso
34d8269ede Merge pull request #658 from dorny/feature/github_actions 2025-09-12 13:33:58 +02:00
11 changed files with 635 additions and 367 deletions

View File

@@ -24,7 +24,7 @@ jobs:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- name: Set Node.js - name: Set Node.js
uses: actions/setup-node@v5 uses: actions/setup-node@v6
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
@@ -46,7 +46,7 @@ jobs:
id: diff id: diff
# If index.js was different than expected, upload the expected version as an artifact # If index.js was different than expected, upload the expected version as an artifact
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v5
if: ${{ failure() && steps.diff.conclusion == 'failure' }} if: ${{ failure() && steps.diff.conclusion == 'failure' }}
with: with:
name: dist name: dist

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- uses: actions/setup-node@v5 - uses: actions/setup-node@v6
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
- run: npm ci - run: npm ci
@@ -25,7 +25,7 @@ jobs:
- name: Upload test results - name: Upload test results
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v5
with: with:
name: test-results name: test-results
path: __tests__/__results__/*.xml path: __tests__/__results__/*.xml

View File

@@ -1,5 +1,11 @@
# Changelog # Changelog
## 2.2.0
* Feature: Add collapsed option to control report summary visibility https://github.com/dorny/test-reporter/pull/664
* Fix badge encoding for values including underscore and hyphens https://github.com/dorny/test-reporter/pull/672
* Fix missing `report-title` attribute in action definition https://github.com/dorny/test-reporter/pull/637
* Refactor variable names to fix shadowing issues https://github.com/dorny/test-reporter/pull/630
## 2.1.1 ## 2.1.1
* Fix error when a TestMethod element does not have a className attribute in a trx file https://github.com/dorny/test-reporter/pull/623 * Fix error when a TestMethod element does not have a className attribute in a trx file https://github.com/dorny/test-reporter/pull/623
* Add stack trace from trx to summary https://github.com/dorny/test-reporter/pull/615 * Add stack trace from trx to summary https://github.com/dorny/test-reporter/pull/615

View File

@@ -207,4 +207,100 @@ describe('jest-junit tests', () => {
// Report should have the title as the first line // Report should have the title as the first line
expect(report).toMatch(/^# My Custom Title\n/) expect(report).toMatch(/^# My Custom Title\n/)
}) })
it('report can be collapsed when configured', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'jest-junit.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}
const parser = new JestJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
const report = getReport([result], {
...DEFAULT_OPTIONS,
collapsed: 'always'
})
// Report should include collapsible details
expect(report).toContain('<details><summary>Expand for details</summary>')
expect(report).toContain('</details>')
})
it('report is not collapsed when configured to never', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'jest-junit.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}
const parser = new JestJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
const report = getReport([result], {
...DEFAULT_OPTIONS,
collapsed: 'never'
})
// Report should not include collapsible details
expect(report).not.toContain('<details><summary>Expand for details</summary>')
expect(report).not.toContain('</details>')
})
it('report auto-collapses when all tests pass', async () => {
// Test with a fixture that has all passing tests (no failures)
const fixturePath = path.join(__dirname, 'fixtures', 'jest-junit-eslint.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}
const parser = new JestJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
// Verify this fixture has no failures
expect(result.failed).toBe(0)
const report = getReport([result], {
...DEFAULT_OPTIONS,
collapsed: 'auto'
})
// Should collapse when all tests pass
expect(report).toContain('<details><summary>Expand for details</summary>')
expect(report).toContain('</details>')
})
it('report does not auto-collapse when tests fail', async () => {
// Test with a fixture that has failing tests
const fixturePath = path.join(__dirname, 'fixtures', 'jest-junit.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}
const parser = new JestJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
// Verify this fixture has failures
expect(result.failed).toBeGreaterThan(0)
const report = getReport([result], {
...DEFAULT_OPTIONS,
collapsed: 'auto'
})
// Should not collapse when there are failures
expect(report).not.toContain('<details><summary>Expand for details</summary>')
expect(report).not.toContain('</details>')
})
}) })

View File

@@ -0,0 +1,120 @@
import {getBadge, DEFAULT_OPTIONS, ReportOptions} from '../../src/report/get-report'
describe('getBadge', () => {
describe('URI encoding with special characters', () => {
it('generates correct URI with simple badge title', () => {
const options: ReportOptions = {
...DEFAULT_OPTIONS,
badgeTitle: 'tests'
}
const badge = getBadge(5, 0, 1, options)
expect(badge).toBe('![Tests passed successfully](https://img.shields.io/badge/tests-5%20passed%2C%201%20skipped-success)')
})
it('handles badge title with single hyphen', () => {
const options: ReportOptions = {
...DEFAULT_OPTIONS,
badgeTitle: 'unit-tests'
}
const badge = getBadge(3, 0, 0, options)
// The hyphen in the badge title should be encoded as --
expect(badge).toBe('![Tests passed successfully](https://img.shields.io/badge/unit--tests-3%20passed-success)')
})
it('handles badge title with multiple hyphens', () => {
const options: ReportOptions = {
...DEFAULT_OPTIONS,
badgeTitle: 'integration-api-tests'
}
const badge = getBadge(10, 0, 0, options)
// All hyphens in the title should be encoded as --
expect(badge).toBe('![Tests passed successfully](https://img.shields.io/badge/integration--api--tests-10%20passed-success)')
})
it('handles badge title with multiple underscores', () => {
const options: ReportOptions = {
...DEFAULT_OPTIONS,
badgeTitle: 'my_integration_test'
}
const badge = getBadge(10, 0, 0, options)
// All underscores in the title should be encoded as __
expect(badge).toBe('![Tests passed successfully](https://img.shields.io/badge/my__integration__test-10%20passed-success)')
})
it('handles badge title with version format containing hyphen', () => {
const options: ReportOptions = {
...DEFAULT_OPTIONS,
badgeTitle: 'MariaDb 12.0-ubi database tests'
}
const badge = getBadge(1, 0, 0, options)
// The hyphen in "12.0-ubi" should be encoded as --
expect(badge).toBe('![Tests passed successfully](https://img.shields.io/badge/MariaDb%2012.0--ubi%20database%20tests-1%20passed-success)')
})
it('handles badge title with dots and hyphens', () => {
const options: ReportOptions = {
...DEFAULT_OPTIONS,
badgeTitle: 'v1.2.3-beta-test'
}
const badge = getBadge(4, 1, 0, options)
expect(badge).toBe('![Tests failed](https://img.shields.io/badge/v1.2.3--beta--test-4%20passed%2C%201%20failed-critical)')
})
it('preserves structural hyphens between label and message', () => {
const options: ReportOptions = {
...DEFAULT_OPTIONS,
badgeTitle: 'test-suite'
}
const badge = getBadge(2, 3, 1, options)
// The URI should have literal hyphens separating title-message-color
expect(badge).toBe('![Tests failed](https://img.shields.io/badge/test--suite-2%20passed%2C%203%20failed%2C%201%20skipped-critical)')
})
})
describe('generates test outcome as color name for imgshields', () => {
it('uses success color when all tests pass', () => {
const options: ReportOptions = {...DEFAULT_OPTIONS}
const badge = getBadge(5, 0, 0, options)
expect(badge).toContain('-success)')
})
it('uses critical color when tests fail', () => {
const options: ReportOptions = {...DEFAULT_OPTIONS}
const badge = getBadge(5, 2, 0, options)
expect(badge).toContain('-critical)')
})
it('uses yellow color when no tests found', () => {
const options: ReportOptions = {...DEFAULT_OPTIONS}
const badge = getBadge(0, 0, 0, options)
expect(badge).toContain('-yellow)')
})
})
describe('badge message composition', () => {
it('includes only passed count when no failures or skips', () => {
const options: ReportOptions = {...DEFAULT_OPTIONS}
const badge = getBadge(5, 0, 0, options)
expect(badge).toBe('![Tests passed successfully](https://img.shields.io/badge/tests-5%20passed-success)')
})
it('includes passed and failed counts', () => {
const options: ReportOptions = {...DEFAULT_OPTIONS}
const badge = getBadge(5, 2, 0, options)
expect(badge).toBe('![Tests failed](https://img.shields.io/badge/tests-5%20passed%2C%202%20failed-critical)')
})
it('includes passed, failed and skipped counts', () => {
const options: ReportOptions = {...DEFAULT_OPTIONS}
const badge = getBadge(5, 2, 1, options)
expect(badge).toBe('![Tests failed](https://img.shields.io/badge/tests-5%20passed%2C%202%20failed%2C%201%20skipped-critical)')
})
it('uses "none" message when no tests', () => {
const options: ReportOptions = {...DEFAULT_OPTIONS}
const badge = getBadge(0, 0, 0, options)
expect(badge).toBe('![Tests passed successfully](https://img.shields.io/badge/tests-none-yellow)')
})
})
})

View File

@@ -89,6 +89,14 @@ inputs:
description: Customize badge title description: Customize badge title
required: false required: false
default: 'tests' default: 'tests'
collapsed:
description: |
Controls whether test report details are collapsed or expanded. Supported options:
- auto: Collapse only if all tests pass (default behavior)
- always: Always collapse the report details
- never: Always expand the report details
required: false
default: 'auto'
token: token:
description: GitHub Access Token description: GitHub Access Token
required: false required: false

32
dist/index.js generated vendored
View File

@@ -309,6 +309,7 @@ class TestReporter {
useActionsSummary = core.getInput('use-actions-summary', { required: false }) === 'true'; useActionsSummary = core.getInput('use-actions-summary', { required: false }) === 'true';
badgeTitle = core.getInput('badge-title', { required: false }); badgeTitle = core.getInput('badge-title', { required: false });
reportTitle = core.getInput('report-title', { required: false }); reportTitle = core.getInput('report-title', { required: false });
collapsed = core.getInput('collapsed', { required: false });
token = core.getInput('token', { required: true }); token = core.getInput('token', { required: true });
octokit; octokit;
context = (0, github_utils_1.getCheckRunContext)(); context = (0, github_utils_1.getCheckRunContext)();
@@ -322,6 +323,10 @@ class TestReporter {
core.setFailed(`Input parameter 'list-tests' has invalid value`); core.setFailed(`Input parameter 'list-tests' has invalid value`);
return; return;
} }
if (this.collapsed !== 'auto' && this.collapsed !== 'always' && this.collapsed !== 'never') {
core.setFailed(`Input parameter 'collapsed' has invalid value`);
return;
}
if (isNaN(this.maxAnnotations) || this.maxAnnotations < 0 || this.maxAnnotations > 50) { if (isNaN(this.maxAnnotations) || this.maxAnnotations < 0 || this.maxAnnotations > 50) {
core.setFailed(`Input parameter 'max-annotations' has invalid value`); core.setFailed(`Input parameter 'max-annotations' has invalid value`);
return; return;
@@ -401,7 +406,7 @@ class TestReporter {
throw error; throw error;
} }
} }
const { listSuites, listTests, onlySummary, useActionsSummary, badgeTitle, reportTitle } = this; const { listSuites, listTests, onlySummary, useActionsSummary, badgeTitle, reportTitle, collapsed } = this;
const passed = results.reduce((sum, tr) => sum + tr.passed, 0); const passed = results.reduce((sum, tr) => sum + tr.passed, 0);
const failed = results.reduce((sum, tr) => sum + tr.failed, 0); const failed = results.reduce((sum, tr) => sum + tr.failed, 0);
const skipped = results.reduce((sum, tr) => sum + tr.skipped, 0); const skipped = results.reduce((sum, tr) => sum + tr.skipped, 0);
@@ -415,7 +420,8 @@ class TestReporter {
onlySummary, onlySummary,
useActionsSummary, useActionsSummary,
badgeTitle, badgeTitle,
reportTitle reportTitle,
collapsed
}); });
core.info('Summary content:'); core.info('Summary content:');
core.info(summary); core.info(summary);
@@ -443,7 +449,8 @@ class TestReporter {
onlySummary, onlySummary,
useActionsSummary, useActionsSummary,
badgeTitle, badgeTitle,
reportTitle reportTitle,
collapsed
}); });
core.info('Creating annotations'); core.info('Creating annotations');
const annotations = (0, get_annotations_1.getAnnotations)(results, this.maxAnnotations); const annotations = (0, get_annotations_1.getAnnotations)(results, this.maxAnnotations);
@@ -1909,6 +1916,7 @@ var __importStar = (this && this.__importStar) || (function () {
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.DEFAULT_OPTIONS = void 0; exports.DEFAULT_OPTIONS = void 0;
exports.getReport = getReport; exports.getReport = getReport;
exports.getBadge = getBadge;
const core = __importStar(__nccwpck_require__(7484)); const core = __importStar(__nccwpck_require__(7484));
const markdown_utils_1 = __nccwpck_require__(5129); const markdown_utils_1 = __nccwpck_require__(5129);
const node_utils_1 = __nccwpck_require__(5384); const node_utils_1 = __nccwpck_require__(5384);
@@ -1923,7 +1931,8 @@ exports.DEFAULT_OPTIONS = {
onlySummary: false, onlySummary: false,
useActionsSummary: true, useActionsSummary: true,
badgeTitle: 'tests', badgeTitle: 'tests',
reportTitle: '' reportTitle: '',
collapsed: 'auto'
}; };
function getReport(results, options = exports.DEFAULT_OPTIONS) { function getReport(results, options = exports.DEFAULT_OPTIONS) {
core.info('Generating check run summary'); core.info('Generating check run summary');
@@ -2022,13 +2031,17 @@ function getBadge(passed, failed, skipped, options) {
color = 'yellow'; color = 'yellow';
} }
const hint = failed > 0 ? 'Tests failed' : 'Tests passed successfully'; const hint = failed > 0 ? 'Tests failed' : 'Tests passed successfully';
const uri = encodeURIComponent(`${options.badgeTitle}-${message}-${color}`); const encodedBadgeTitle = encodeImgShieldsURIComponent(options.badgeTitle);
return `![${hint}](https://img.shields.io/badge/${uri})`; const encodedMessage = encodeImgShieldsURIComponent(message);
const encodedColor = encodeImgShieldsURIComponent(color);
return `![${hint}](https://img.shields.io/badge/${encodedBadgeTitle}-${encodedMessage}-${encodedColor})`;
} }
function getTestRunsReport(testRuns, options) { function getTestRunsReport(testRuns, options) {
const sections = []; const sections = [];
const totalFailed = testRuns.reduce((sum, tr) => sum + tr.failed, 0); const totalFailed = testRuns.reduce((sum, tr) => sum + tr.failed, 0);
if (totalFailed === 0) { // Determine if report should be collapsed based on collapsed option
const shouldCollapse = options.collapsed === 'always' || (options.collapsed === 'auto' && totalFailed === 0);
if (shouldCollapse) {
sections.push(`<details><summary>Expand for details</summary>`); sections.push(`<details><summary>Expand for details</summary>`);
sections.push(` `); sections.push(` `);
} }
@@ -2053,7 +2066,7 @@ function getTestRunsReport(testRuns, options) {
const suitesReports = testRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat(); const suitesReports = testRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat();
sections.push(...suitesReports); sections.push(...suitesReports);
} }
if (totalFailed === 0) { if (shouldCollapse) {
sections.push(`</details>`); sections.push(`</details>`);
} }
return sections; return sections;
@@ -2153,6 +2166,9 @@ function getResultIcon(result) {
return ''; return '';
} }
} }
function encodeImgShieldsURIComponent(component) {
return encodeURIComponent(component).replace(/-/g, '--').replace(/_/g, '__');
}
/***/ }), /***/ }),

680
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "test-reporter", "name": "test-reporter",
"version": "2.1.1", "version": "2.2.0",
"private": true, "private": true,
"description": "Presents test results from popular testing frameworks as Github check run", "description": "Presents test results from popular testing frameworks as Github check run",
"main": "lib/main.js", "main": "lib/main.js",
@@ -49,12 +49,12 @@
"@octokit/webhooks-types": "^7.6.1", "@octokit/webhooks-types": "^7.6.1",
"@types/adm-zip": "^0.5.7", "@types/adm-zip": "^0.5.7",
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"@types/node": "^20.19.13", "@types/node": "^20.19.23",
"@types/picomatch": "^4.0.2", "@types/picomatch": "^4.0.2",
"@types/xml2js": "^0.4.14", "@types/xml2js": "^0.4.14",
"@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0", "@typescript-eslint/parser": "^7.18.0",
"@vercel/ncc": "^0.38.3", "@vercel/ncc": "^0.38.4",
"eol-converter-cli": "^1.1.0", "eol-converter-cli": "^1.1.0",
"eslint": "^8.57.1", "eslint": "^8.57.1",
"eslint-import-resolver-typescript": "^3.10.1", "eslint-import-resolver-typescript": "^3.10.1",
@@ -62,12 +62,12 @@
"eslint-plugin-import": "^2.32.0", "eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^28.14.0", "eslint-plugin-jest": "^28.14.0",
"eslint-plugin-prettier": "^5.5.4", "eslint-plugin-prettier": "^5.5.4",
"jest": "^30.1.3", "jest": "^30.2.0",
"jest-junit": "^16.0.0", "jest-junit": "^16.0.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.1",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"ts-jest": "^29.4.1", "ts-jest": "^29.4.5",
"typescript": "^5.9.2" "typescript": "^5.9.3"
}, },
"jest-junit": { "jest-junit": {
"suiteName": "jest tests", "suiteName": "jest tests",

View File

@@ -49,6 +49,7 @@ class TestReporter {
readonly useActionsSummary = core.getInput('use-actions-summary', {required: false}) === 'true' readonly useActionsSummary = core.getInput('use-actions-summary', {required: false}) === 'true'
readonly badgeTitle = core.getInput('badge-title', {required: false}) readonly badgeTitle = core.getInput('badge-title', {required: false})
readonly reportTitle = core.getInput('report-title', {required: false}) readonly reportTitle = core.getInput('report-title', {required: false})
readonly collapsed = core.getInput('collapsed', {required: false}) as 'auto' | 'always' | 'never'
readonly token = core.getInput('token', {required: true}) readonly token = core.getInput('token', {required: true})
readonly octokit: InstanceType<typeof GitHub> readonly octokit: InstanceType<typeof GitHub>
readonly context = getCheckRunContext() readonly context = getCheckRunContext()
@@ -66,6 +67,11 @@ class TestReporter {
return return
} }
if (this.collapsed !== 'auto' && this.collapsed !== 'always' && this.collapsed !== 'never') {
core.setFailed(`Input parameter 'collapsed' has invalid value`)
return
}
if (isNaN(this.maxAnnotations) || this.maxAnnotations < 0 || this.maxAnnotations > 50) { if (isNaN(this.maxAnnotations) || this.maxAnnotations < 0 || this.maxAnnotations > 50) {
core.setFailed(`Input parameter 'max-annotations' has invalid value`) core.setFailed(`Input parameter 'max-annotations' has invalid value`)
return return
@@ -166,7 +172,7 @@ class TestReporter {
} }
} }
const {listSuites, listTests, onlySummary, useActionsSummary, badgeTitle, reportTitle} = this const {listSuites, listTests, onlySummary, useActionsSummary, badgeTitle, reportTitle, collapsed} = this
const passed = results.reduce((sum, tr) => sum + tr.passed, 0) const passed = results.reduce((sum, tr) => sum + tr.passed, 0)
const failed = results.reduce((sum, tr) => sum + tr.failed, 0) const failed = results.reduce((sum, tr) => sum + tr.failed, 0)
@@ -182,7 +188,8 @@ class TestReporter {
onlySummary, onlySummary,
useActionsSummary, useActionsSummary,
badgeTitle, badgeTitle,
reportTitle reportTitle,
collapsed
}) })
core.info('Summary content:') core.info('Summary content:')
@@ -211,7 +218,8 @@ class TestReporter {
onlySummary, onlySummary,
useActionsSummary, useActionsSummary,
badgeTitle, badgeTitle,
reportTitle reportTitle,
collapsed
}) })
core.info('Creating annotations') core.info('Creating annotations')

View File

@@ -16,6 +16,7 @@ export interface ReportOptions {
useActionsSummary: boolean useActionsSummary: boolean
badgeTitle: string badgeTitle: string
reportTitle: string reportTitle: string
collapsed: 'auto' | 'always' | 'never'
} }
export const DEFAULT_OPTIONS: ReportOptions = { export const DEFAULT_OPTIONS: ReportOptions = {
@@ -25,7 +26,8 @@ export const DEFAULT_OPTIONS: ReportOptions = {
onlySummary: false, onlySummary: false,
useActionsSummary: true, useActionsSummary: true,
badgeTitle: 'tests', badgeTitle: 'tests',
reportTitle: '' reportTitle: '',
collapsed: 'auto'
} }
export function getReport(results: TestRunResult[], options: ReportOptions = DEFAULT_OPTIONS): string { export function getReport(results: TestRunResult[], options: ReportOptions = DEFAULT_OPTIONS): string {
@@ -125,7 +127,7 @@ function getReportBadge(results: TestRunResult[], options: ReportOptions): strin
return getBadge(passed, failed, skipped, options) return getBadge(passed, failed, skipped, options)
} }
function getBadge(passed: number, failed: number, skipped: number, options: ReportOptions): string { export function getBadge(passed: number, failed: number, skipped: number, options: ReportOptions): string {
const text = [] const text = []
if (passed > 0) { if (passed > 0) {
text.push(`${passed} passed`) text.push(`${passed} passed`)
@@ -145,14 +147,20 @@ function getBadge(passed: number, failed: number, skipped: number, options: Repo
color = 'yellow' color = 'yellow'
} }
const hint = failed > 0 ? 'Tests failed' : 'Tests passed successfully' const hint = failed > 0 ? 'Tests failed' : 'Tests passed successfully'
const uri = encodeURIComponent(`${options.badgeTitle}-${message}-${color}`) const encodedBadgeTitle = encodeImgShieldsURIComponent(options.badgeTitle)
return `![${hint}](https://img.shields.io/badge/${uri})` const encodedMessage = encodeImgShieldsURIComponent(message)
const encodedColor = encodeImgShieldsURIComponent(color)
return `![${hint}](https://img.shields.io/badge/${encodedBadgeTitle}-${encodedMessage}-${encodedColor})`
} }
function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): string[] { function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): string[] {
const sections: string[] = [] const sections: string[] = []
const totalFailed = testRuns.reduce((sum, tr) => sum + tr.failed, 0) const totalFailed = testRuns.reduce((sum, tr) => sum + tr.failed, 0)
if (totalFailed === 0) {
// Determine if report should be collapsed based on collapsed option
const shouldCollapse = options.collapsed === 'always' || (options.collapsed === 'auto' && totalFailed === 0)
if (shouldCollapse) {
sections.push(`<details><summary>Expand for details</summary>`) sections.push(`<details><summary>Expand for details</summary>`)
sections.push(` `) sections.push(` `)
} }
@@ -185,7 +193,7 @@ function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): s
sections.push(...suitesReports) sections.push(...suitesReports)
} }
if (totalFailed === 0) { if (shouldCollapse) {
sections.push(`</details>`) sections.push(`</details>`)
} }
return sections return sections
@@ -305,3 +313,7 @@ function getResultIcon(result: TestExecutionResult): string {
return '' return ''
} }
} }
function encodeImgShieldsURIComponent(component: string): string {
return encodeURIComponent(component).replace(/-/g, '--').replace(/_/g, '__')
}