mirror of
https://github.com/dorny/test-reporter.git
synced 2025-12-15 13:57:09 +01:00
Merge pull request #664 from pespinel/feature/collapsed-option
This commit is contained in:
commit
b3812e0f5b
5 changed files with 140 additions and 12 deletions
|
|
@ -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>')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
22
dist/index.js
generated
vendored
22
dist/index.js
generated
vendored
|
|
@ -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);
|
||||||
|
|
@ -1924,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');
|
||||||
|
|
@ -2031,7 +2039,9 @@ function getBadge(passed, failed, skipped, options) {
|
||||||
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(` `);
|
||||||
}
|
}
|
||||||
|
|
@ -2056,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;
|
||||||
|
|
|
||||||
14
src/main.ts
14
src/main.ts
|
|
@ -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')
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
@ -154,7 +156,11 @@ export function getBadge(passed: number, failed: number, skipped: number, option
|
||||||
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(` `)
|
||||||
}
|
}
|
||||||
|
|
@ -187,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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue