From 8dd30d0b92f410d39476016b1960c9d0216b5d7d Mon Sep 17 00:00:00 2001 From: Matteo Dell'Acqua <82184604+MatteoH2O1999@users.noreply.github.com> Date: Mon, 24 Jul 2023 10:21:34 +0200 Subject: [PATCH] Fix skipped and todo tests in Github Actions Reporter (#14309) --- CHANGELOG.md | 1 + e2e/Utils.ts | 11 -- e2e/runJest.ts | 2 +- .../src/GitHubActionsReporter.ts | 38 +++-- .../__tests__/GitHubActionsReporter.test.ts | 155 +++++++++++++++++- .../GitHubActionsReporter.test.ts.snap | 46 +++++- packages/test-utils/src/index.ts | 2 + packages/test-utils/src/normalizeIcons.ts | 17 ++ 8 files changed, 236 insertions(+), 36 deletions(-) create mode 100644 packages/test-utils/src/normalizeIcons.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 461f20949ee2..cf47e4302d60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - `[jest-circus]` Fix snapshot matchers in concurrent tests when nr of tests exceeds `maxConcurrency` ([#14335](https://github.com/jestjs/jest/pull/14335)) - `[jest-snapshot]` Move `@types/prettier` from `dependencies` to `devDependencies` ([#14328](https://github.com/jestjs/jest/pull/14328)) +- `[jest-reporters]` Add "skipped" and "todo" symbols to Github Actions Reporter ([#14309](https://github.com/jestjs/jest/pull/14309)) ### Chore & Maintenance diff --git a/e2e/Utils.ts b/e2e/Utils.ts index 3f0eaf20f4b1..31757a49891c 100644 --- a/e2e/Utils.ts +++ b/e2e/Utils.ts @@ -310,17 +310,6 @@ export const extractSummaries = ( .map(({start, end}) => extractSortedSummary(stdout.slice(start, end))); }; -export const normalizeIcons = (str: string) => { - if (!str) { - return str; - } - - // Make sure to keep in sync with `jest-util/src/specialChars` - return str - .replace(new RegExp('\u00D7', 'gu'), '\u2715') - .replace(new RegExp('\u221A', 'gu'), '\u2713'); -}; - // Certain environments (like CITGM and GH Actions) do not come with mercurial installed let hgIsInstalled: boolean | null = null; diff --git a/e2e/runJest.ts b/e2e/runJest.ts index 8de50ea7f2ea..2d198657f7de 100644 --- a/e2e/runJest.ts +++ b/e2e/runJest.ts @@ -13,9 +13,9 @@ import execa = require('execa'); import * as fs from 'graceful-fs'; import stripAnsi = require('strip-ansi'); import type {FormattedTestResults} from '@jest/test-result'; +import {normalizeIcons} from '@jest/test-utils'; import type {Config} from '@jest/types'; import {ErrorWithStack} from 'jest-util'; -import {normalizeIcons} from './Utils'; const JEST_PATH = path.resolve(__dirname, '../packages/jest-cli/bin/jest.js'); diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index 7cbf65ce2734..df9640214742 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -10,6 +10,7 @@ import stripAnsi = require('strip-ansi'); import type { AggregatedResult, AssertionResult, + Status, Test, TestContext, TestResult, @@ -21,6 +22,7 @@ import { getTopFrame, separateMessageFromStack, } from 'jest-message-util'; +import {specialChars} from 'jest-util'; import BaseReporter from './BaseReporter'; type AnnotationOptions = { @@ -32,6 +34,7 @@ type AnnotationOptions = { }; const titleSeparator = ' \u203A '; +const ICONS = specialChars.ICONS; type PerformanceInfo = { end: number; @@ -42,7 +45,7 @@ type PerformanceInfo = { type ResultTreeLeaf = { name: string; - passed: boolean; + status: Status; duration: number; children: Array; }; @@ -215,17 +218,15 @@ export default class GitHubActionsReporter extends BaseReporter { const branches: Array> = []; suiteResult.forEach(element => { if (element.ancestorTitles.length === 0) { - let passed = true; - if (element.status !== 'passed') { + if (element.status === 'failed') { root.passed = false; - passed = false; } const duration = element.duration || 1; root.children.push({ children: [], duration, name: element.title, - passed, + status: element.status, }); } else { let alreadyInserted = false; @@ -263,21 +264,19 @@ export default class GitHubActionsReporter extends BaseReporter { }; const branches: Array> = []; suiteResult.forEach(element => { - let passed = true; let duration = element.duration; if (!duration || isNaN(duration)) { duration = 1; } if (this.arrayEqual(element.ancestorTitles, ancestors)) { - if (element.status !== 'passed') { + if (element.status === 'failed') { node.passed = false; - passed = false; } node.children.push({ children: [], duration, name: element.title, - passed, + status: element.status, }); } else if ( this.arrayChild( @@ -354,10 +353,20 @@ export default class GitHubActionsReporter extends BaseReporter { } const spaces = ' '.repeat(numberSpaces); let resultSymbol; - if (resultTree.passed) { - resultSymbol = chalk.green('\u2713'); - } else { - resultSymbol = chalk.red('\u00D7'); + switch (resultTree.status) { + case 'passed': + resultSymbol = chalk.green(ICONS.success); + break; + case 'failed': + resultSymbol = chalk.red(ICONS.failed); + break; + case 'todo': + resultSymbol = chalk.magenta(ICONS.todo); + break; + case 'pending': + case 'skipped': + resultSymbol = chalk.yellow(ICONS.pending); + break; } this.log( `${spaces + resultSymbol} ${resultTree.name} (${ @@ -365,6 +374,9 @@ export default class GitHubActionsReporter extends BaseReporter { } ms)`, ); } else { + if (!('passed' in resultTree)) { + throw new Error('Expected a node. Got a leaf'); + } if (resultTree.passed) { if (alreadyGrouped) { this.log(' '.repeat(depth) + resultTree.name); diff --git a/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts b/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts index 700653b70c78..609b5697a5e4 100644 --- a/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts +++ b/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts @@ -8,17 +8,25 @@ import type { AggregatedResult, AssertionResult, + Status, Test, TestCaseResult, TestResult, } from '@jest/test-result'; +import {normalizeIcons} from '@jest/test-utils'; import type {Config} from '@jest/types'; -import GitHubActionsReporter from '../GitHubActionsReporter'; +import BaseGitHubActionsReporter from '../GitHubActionsReporter'; afterEach(() => { jest.clearAllMocks(); }); +class GitHubActionsReporter extends BaseGitHubActionsReporter { + override log(message: string): void { + super.log(normalizeIcons(message)); + } +} + const mockedStderrWrite = jest .spyOn(process.stderr, 'write') .mockImplementation(() => true); @@ -174,7 +182,7 @@ describe('logs', () => { children: [], duration: 10, name: 'test', - passed: false, + status: 'failed', }, ], name: '/', @@ -217,7 +225,7 @@ describe('logs', () => { children: [], duration: 10, name: 'test', - passed: true, + status: 'passed', }, ], name: '/', @@ -262,7 +270,7 @@ describe('logs', () => { children: [], duration: 10, name: 'test', - passed: false, + status: 'failed', }, ], name: 'Test describe', @@ -311,7 +319,68 @@ describe('logs', () => { children: [], duration: 10, name: 'test', - passed: true, + status: 'passed', + }, + ], + name: 'Test describe', + passed: true, + }, + ], + name: '/', + passed: true, + performanceInfo: { + end: 30, + runtime: 20, + slow: false, + start: 10, + }, + }; + const gha = new GitHubActionsReporter({} as Config.GlobalConfig, { + silent: false, + }); + + const generated = gha['getResultTree'](testResults, '/', suitePerf); + + expect(mockedStderrWrite).not.toHaveBeenCalled(); + expect(generated).toEqual(expectedResults); + }); + + test('skipped single test and todo single test inside describe', () => { + const testResults = [ + { + ancestorTitles: ['Test describe'], + duration: 10, + status: 'skipped', + title: 'test', + }, + { + ancestorTitles: ['Test describe'], + duration: 14, + status: 'todo', + title: 'test2', + }, + ] as unknown as Array; + const suitePerf = { + end: 30, + runtime: 20, + slow: false, + start: 10, + }; + const expectedResults = { + children: [ + { + children: [ + { + children: [], + duration: 10, + name: 'test', + status: 'skipped', + }, + { + children: [], + duration: 14, + name: 'test2', + status: 'todo', }, ], name: 'Test describe', @@ -346,7 +415,7 @@ describe('logs', () => { children: [], duration: 10, name: 'test', - passed: false, + status: 'failed' as Status, }, ], name: '/', @@ -374,7 +443,7 @@ describe('logs', () => { children: [], duration: 10, name: 'test', - passed: true, + status: 'passed' as Status, }, ], name: '/', @@ -404,7 +473,7 @@ describe('logs', () => { children: [], duration: 10, name: 'test', - passed: false, + status: 'failed' as Status, }, ], name: 'Test describe', @@ -438,7 +507,75 @@ describe('logs', () => { children: [], duration: 10, name: 'test', - passed: true, + status: 'passed' as Status, + }, + ], + name: 'Test describe', + passed: true, + }, + ], + name: '/', + passed: true, + performanceInfo: { + end: 30, + runtime: 20, + slow: false, + start: 10, + }, + }; + const gha = new GitHubActionsReporter({} as Config.GlobalConfig, { + silent: false, + }); + + gha['printResultTree'](generatedTree); + + expect(mockedStderrWrite.mock.calls).toMatchSnapshot(); + }); + + test('todo single test inside describe', () => { + const generatedTree = { + children: [ + { + children: [ + { + children: [], + duration: 10, + name: 'test', + status: 'todo' as Status, + }, + ], + name: 'Test describe', + passed: true, + }, + ], + name: '/', + passed: true, + performanceInfo: { + end: 30, + runtime: 20, + slow: false, + start: 10, + }, + }; + const gha = new GitHubActionsReporter({} as Config.GlobalConfig, { + silent: false, + }); + + gha['printResultTree'](generatedTree); + + expect(mockedStderrWrite.mock.calls).toMatchSnapshot(); + }); + + test('skipped single test inside describe', () => { + const generatedTree = { + children: [ + { + children: [ + { + children: [], + duration: 10, + name: 'test', + status: 'skipped' as Status, }, ], name: 'Test describe', diff --git a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap index 1010136fe994..296da56d83af 100644 --- a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap +++ b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap @@ -100,7 +100,7 @@ Array [ ", ], Array [ - " × test (10 ms) + " ✕ test (10 ms) ", ], ] @@ -113,7 +113,7 @@ Array [ ", ], Array [ - " × test (10 ms) + " ✕ test (10 ms) ", ], ] @@ -156,3 +156,45 @@ Array [ ], ] `; + +exports[`logs Result tree output skipped single test inside describe 1`] = ` +Array [ + Array [ + "::group::PASS / (20 ms) +", + ], + Array [ + " Test describe +", + ], + Array [ + " ○ test (10 ms) +", + ], + Array [ + "::endgroup:: +", + ], +] +`; + +exports[`logs Result tree output todo single test inside describe 1`] = ` +Array [ + Array [ + "::group::PASS / (20 ms) +", + ], + Array [ + " Test describe +", + ], + Array [ + " ✎ test (10 ms) +", + ], + Array [ + "::endgroup:: +", + ], +] +`; diff --git a/packages/test-utils/src/index.ts b/packages/test-utils/src/index.ts index 982674483d24..efb9649a66c3 100644 --- a/packages/test-utils/src/index.ts +++ b/packages/test-utils/src/index.ts @@ -15,3 +15,5 @@ export { } from './ConditionalTest'; export {makeGlobalConfig, makeProjectConfig} from './config'; + +export {normalizeIcons} from './normalizeIcons'; diff --git a/packages/test-utils/src/normalizeIcons.ts b/packages/test-utils/src/normalizeIcons.ts new file mode 100644 index 000000000000..6c672384d642 --- /dev/null +++ b/packages/test-utils/src/normalizeIcons.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +export function normalizeIcons(str: string): string { + if (!str) { + return str; + } + + // Make sure to keep in sync with `jest-util/src/specialChars` + return str + .replace(new RegExp('\u00D7', 'gu'), '\u2715') + .replace(new RegExp('\u221A', 'gu'), '\u2713'); +}