diff --git a/__tests__/jest-junit.test.ts b/__tests__/jest-junit.test.ts index f4b8335..6fb6c64 100644 --- a/__tests__/jest-junit.test.ts +++ b/__tests__/jest-junit.test.ts @@ -207,4 +207,100 @@ describe('jest-junit tests', () => { // Report should have the title as the first line 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('
Expand for details') + expect(report).toContain('
') + }) + + 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('
Expand for details') + expect(report).not.toContain('
') + }) + + 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('
Expand for details') + expect(report).toContain('
') + }) + + 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('
Expand for details') + expect(report).not.toContain('
') + }) }) diff --git a/action.yml b/action.yml index 7777d56..c8dd56b 100644 --- a/action.yml +++ b/action.yml @@ -89,6 +89,14 @@ inputs: description: Customize badge title required: false 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: description: GitHub Access Token required: false diff --git a/dist/index.js b/dist/index.js index d5914fe..cdd6319 100644 --- a/dist/index.js +++ b/dist/index.js @@ -309,6 +309,7 @@ class TestReporter { useActionsSummary = core.getInput('use-actions-summary', { required: false }) === 'true'; badgeTitle = core.getInput('badge-title', { required: false }); reportTitle = core.getInput('report-title', { required: false }); + collapsed = core.getInput('collapsed', { required: false }); token = core.getInput('token', { required: true }); octokit; context = (0, github_utils_1.getCheckRunContext)(); @@ -322,6 +323,10 @@ class TestReporter { core.setFailed(`Input parameter 'list-tests' has invalid value`); 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) { core.setFailed(`Input parameter 'max-annotations' has invalid value`); return; @@ -401,7 +406,7 @@ class TestReporter { 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 failed = results.reduce((sum, tr) => sum + tr.failed, 0); const skipped = results.reduce((sum, tr) => sum + tr.skipped, 0); @@ -415,7 +420,8 @@ class TestReporter { onlySummary, useActionsSummary, badgeTitle, - reportTitle + reportTitle, + collapsed }); core.info('Summary content:'); core.info(summary); @@ -443,7 +449,8 @@ class TestReporter { onlySummary, useActionsSummary, badgeTitle, - reportTitle + reportTitle, + collapsed }); core.info('Creating annotations'); const annotations = (0, get_annotations_1.getAnnotations)(results, this.maxAnnotations); @@ -1924,7 +1931,8 @@ exports.DEFAULT_OPTIONS = { onlySummary: false, useActionsSummary: true, badgeTitle: 'tests', - reportTitle: '' + reportTitle: '', + collapsed: 'auto' }; function getReport(results, options = exports.DEFAULT_OPTIONS) { core.info('Generating check run summary'); @@ -2031,7 +2039,9 @@ function getBadge(passed, failed, skipped, options) { function getTestRunsReport(testRuns, options) { const sections = []; 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(`
Expand for details`); sections.push(` `); } @@ -2056,7 +2066,7 @@ function getTestRunsReport(testRuns, options) { const suitesReports = testRuns.map((tr, i) => getSuitesReport(tr, i, options)).flat(); sections.push(...suitesReports); } - if (totalFailed === 0) { + if (shouldCollapse) { sections.push(`
`); } return sections; diff --git a/src/main.ts b/src/main.ts index 57137ab..218377f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -49,6 +49,7 @@ class TestReporter { readonly useActionsSummary = core.getInput('use-actions-summary', {required: false}) === 'true' readonly badgeTitle = core.getInput('badge-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 octokit: InstanceType readonly context = getCheckRunContext() @@ -66,6 +67,11 @@ class TestReporter { 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) { core.setFailed(`Input parameter 'max-annotations' has invalid value`) 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 failed = results.reduce((sum, tr) => sum + tr.failed, 0) @@ -182,7 +188,8 @@ class TestReporter { onlySummary, useActionsSummary, badgeTitle, - reportTitle + reportTitle, + collapsed }) core.info('Summary content:') @@ -211,7 +218,8 @@ class TestReporter { onlySummary, useActionsSummary, badgeTitle, - reportTitle + reportTitle, + collapsed }) core.info('Creating annotations') diff --git a/src/report/get-report.ts b/src/report/get-report.ts index 5168b7a..d893b0e 100644 --- a/src/report/get-report.ts +++ b/src/report/get-report.ts @@ -16,6 +16,7 @@ export interface ReportOptions { useActionsSummary: boolean badgeTitle: string reportTitle: string + collapsed: 'auto' | 'always' | 'never' } export const DEFAULT_OPTIONS: ReportOptions = { @@ -25,7 +26,8 @@ export const DEFAULT_OPTIONS: ReportOptions = { onlySummary: false, useActionsSummary: true, badgeTitle: 'tests', - reportTitle: '' + reportTitle: '', + collapsed: 'auto' } 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[] { const sections: string[] = [] 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(`
Expand for details`) sections.push(` `) } @@ -187,7 +193,7 @@ function getTestRunsReport(testRuns: TestRunResult[], options: ReportOptions): s sections.push(...suitesReports) } - if (totalFailed === 0) { + if (shouldCollapse) { sections.push(`
`) } return sections