diff --git a/command-snapshot.json b/command-snapshot.json index 5ea14fbe..f8971742 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -30,7 +30,8 @@ "output-dir", "result-format", "target-org", - "test-run-id" + "test-run-id", + "concise" ], "plugin": "@salesforce/plugin-apex" }, @@ -82,7 +83,8 @@ "target-org", "test-level", "tests", - "wait" + "wait", + "concise" ], "plugin": "@salesforce/plugin-apex" }, diff --git a/messages/gettest.md b/messages/gettest.md index 65b84ebb..c711eb65 100644 --- a/messages/gettest.md +++ b/messages/gettest.md @@ -33,6 +33,10 @@ ID of the test run. Directory in which to store test result files. +# flags.concise.summary + +Display only failed test results; works with human-readable output only. + # apexLibErr Unknown error in Apex Library: %s diff --git a/messages/runtest.md b/messages/runtest.md index c0859e9b..f4f7c67d 100644 --- a/messages/runtest.md +++ b/messages/runtest.md @@ -120,6 +120,10 @@ Runs test methods from a single Apex class synchronously; if not specified, test Display detailed code coverage per test. +# flags.concise.summary + +Display only failed test results; works with human-readable output only. + # runTestReportCommand Run "%s apex get test -i %s -o %s" to retrieve test results diff --git a/src/commands/apex/get/test.ts b/src/commands/apex/get/test.ts index 5c96cd15..2cb1cc16 100644 --- a/src/commands/apex/get/test.ts +++ b/src/commands/apex/get/test.ts @@ -48,6 +48,9 @@ export default class Test extends SfCommand { summary: messages.getMessage('flags.output-dir.summary'), }), 'result-format': resultFormatFlag, + concise: Flags.boolean({ + summary: messages.getMessage('flags.concise.summary'), + }), }; public async run(): Promise { @@ -65,6 +68,7 @@ export default class Test extends SfCommand { 'result-format': flags['result-format'], json: flags.json, 'code-coverage': flags['code-coverage'], + concise: flags['concise'], }); } } diff --git a/src/commands/apex/run/test.ts b/src/commands/apex/run/test.ts index e301a020..dbe67f1f 100644 --- a/src/commands/apex/run/test.ts +++ b/src/commands/apex/run/test.ts @@ -94,6 +94,9 @@ export default class Test extends SfCommand { summary: messages.getMessage('flags.detailed-coverage.summary'), dependsOn: ['code-coverage'], }), + concise: Flags.boolean({ + summary: messages.getMessage('flags.concise.summary'), + }), }; protected cancellationTokenSource = new CancellationTokenSource(); diff --git a/src/reporters/testReporter.ts b/src/reporters/testReporter.ts index 4dbe69b4..973201a3 100644 --- a/src/reporters/testReporter.ts +++ b/src/reporters/testReporter.ts @@ -44,6 +44,7 @@ export class TestReporter { synchronous?: boolean; json?: boolean; 'code-coverage'?: boolean; + concise: boolean; } ): Promise { if (options['output-dir']) { @@ -54,6 +55,7 @@ export class TestReporter { options['output-dir'], options['result-format'] as ResultFormat | undefined, Boolean(options['detailed-coverage']), + options['concise'], options.synchronous ); @@ -68,7 +70,7 @@ export class TestReporter { } switch (options['result-format']) { case 'human': - this.logHuman(result, options['detailed-coverage'] as boolean, options['output-dir']); + this.logHuman(result, options['detailed-coverage'] as boolean, options['concise'], options['output-dir']); break; case 'tap': this.logTap(result); @@ -86,7 +88,7 @@ export class TestReporter { } break; default: - this.logHuman(result, options['detailed-coverage'] as boolean, options['output-dir']); + this.logHuman(result, options['detailed-coverage'] as boolean, options['concise'], options['output-dir']); } } catch (e) { this.ux.styledJSON(result); @@ -113,6 +115,7 @@ export class TestReporter { outputDir: string, resultFormat: ResultFormat | undefined, detailedCoverage: boolean, + concise: boolean, synchronous = false ): OutputDirConfig { const outputDirConfig: OutputDirConfig = { @@ -161,7 +164,7 @@ export class TestReporter { case ResultFormat.human: outputDirConfig.fileInfos?.push({ filename: 'test-result.txt', - content: new HumanReporter().format(result, detailedCoverage), + content: new HumanReporter().format(result, detailedCoverage, concise), }); break; default: @@ -181,12 +184,12 @@ export class TestReporter { } } - private logHuman(result: TestResult, detailedCoverage: boolean, outputDir?: string): void { + private logHuman(result: TestResult, detailedCoverage: boolean, concise: boolean, outputDir?: string): void { if (outputDir) { this.ux.log(messages.getMessage('outputDirHint', [outputDir])); } const humanReporter = new HumanReporter(); - const output = humanReporter.format(result, detailedCoverage); + const output = humanReporter.format(result, detailedCoverage, concise); this.ux.log(output); } diff --git a/test/commands/apex/get/test.test.ts b/test/commands/apex/get/test.test.ts index 6b949173..927698f2 100644 --- a/test/commands/apex/get/test.test.ts +++ b/test/commands/apex/get/test.test.ts @@ -11,7 +11,14 @@ import { Ux, stubSfCommandUx } from '@salesforce/sf-plugins-core'; import { expect, config } from 'chai'; import { TestService } from '@salesforce/apex-node'; import Test from '../../../../src/commands/apex/get/test.js'; -import { runWithFailures, testRunSimple, testRunSimpleResult, testRunWithFailuresResult } from '../../../testData.js'; +import { + runWithCoverage, + runWithFailureAndSuccess, + runWithFailures, + testRunSimple, + testRunSimpleResult, + testRunWithFailuresResult, +} from '../../../testData.js'; config.truncateThreshold = 0; @@ -105,6 +112,16 @@ describe('apex:test:report', () => { await Test.run(['--output-dir', 'myDirectory', '--test-run-id', '707xxxxxxxxxxxx', '--result-format', 'human']); expect(logStub.firstCall.args[0]).to.contain('Test result files written to myDirectory'); }); + + it('should only display failed test with human format with concise flag', async () => { + sandbox.stub(TestService.prototype, 'reportAsyncResults').resolves(runWithFailureAndSuccess); + await Test.run(['--test-run-id', '707xxxxxxxxxxxx', '--result-format', 'human', '--concise']); + expect(logStub.firstCall.args[0]).to.contain('Test Summary'); + expect(logStub.firstCall.args[0]).to.contain('Test Results'); + expect(logStub.firstCall.args[0]).to.contain('MyFailingTest'); + expect(logStub.firstCall.args[0]).to.not.contain('MyPassingTest'); + expect(logStub.firstCall.args[0]).to.not.contain('Apex Code Coverage by Class'); + }); }); describe('test success', () => { @@ -168,5 +185,13 @@ describe('apex:test:report', () => { await Test.run(['--output-dir', 'myDirectory', '--test-run-id', '707xxxxxxxxxxxx', '--result-format', 'human']); expect(logStub.firstCall.args[0]).to.contain('Test result files written to myDirectory'); }); + + it('should only display summary with human format and code coverage and concise parameters', async () => { + sandbox.stub(TestService.prototype, 'reportAsyncResults').resolves(runWithCoverage); + await Test.run(['--test-run-id', '707xxxxxxxxxxxx', '--result-format', 'human', '--code-coverage', '--concise']); + expect(logStub.firstCall.args[0]).to.contain('Test Summary'); + expect(logStub.firstCall.args[0]).to.not.contain('Test Results'); + expect(logStub.firstCall.args[0]).to.not.contain('Apex Code Coverage by Class'); + }); }); }); diff --git a/test/commands/apex/run/test.test.ts b/test/commands/apex/run/test.test.ts index 4ce252fb..7d0a9702 100644 --- a/test/commands/apex/run/test.test.ts +++ b/test/commands/apex/run/test.test.ts @@ -13,6 +13,7 @@ import { TestService } from '@salesforce/apex-node'; import Test from '../../../../src/commands/apex/run/test.js'; import { runWithCoverage, + runWithFailureAndSuccess, runWithFailures, testRunSimple, testRunSimpleResult, @@ -113,6 +114,16 @@ describe('apex:test:run', () => { expect(logStub.firstCall.args[0]).to.not.contain('Apex Code Coverage by Class'); }); + it('should only display failed test with human format with concise flag', async () => { + sandbox.stub(TestService.prototype, 'runTestSynchronous').resolves(runWithFailureAndSuccess); + await Test.run(['--tests', 'MyApexTests', '--result-format', 'human', '--synchronous', '--concise']); + expect(logStub.firstCall.args[0]).to.contain('Test Summary'); + expect(logStub.firstCall.args[0]).to.contain('Test Results'); + expect(logStub.firstCall.args[0]).to.contain('MyFailingTest'); + expect(logStub.firstCall.args[0]).to.not.contain('MyPassingTest'); + expect(logStub.firstCall.args[0]).to.not.contain('Apex Code Coverage by Class'); + }); + it('will build the sync correct payload', async () => { const buildPayloadSpy = sandbox.spy(TestService.prototype, 'buildSyncPayload'); const runTestSynchronousSpy = sandbox.stub(TestService.prototype, 'runTestSynchronous').resolves(runWithFailures); @@ -459,6 +470,22 @@ describe('apex:test:run', () => { ], }); }); + + it('should only display summary with human format and code coverage and concise parameters', async () => { + sandbox.stub(TestService.prototype, 'runTestSynchronous').resolves(runWithCoverage); + await Test.run([ + '--tests', + 'MyApexTests', + '--result-format', + 'human', + '--synchronous', + '--code-coverage', + '--concise', + ]); + expect(logStub.firstCall.args[0]).to.contain('Test Summary'); + expect(logStub.firstCall.args[0]).to.not.contain('Test Results'); + expect(logStub.firstCall.args[0]).to.not.contain('Apex Code Coverage by Class'); + }); }); describe('validateFlags', () => { diff --git a/test/testData.ts b/test/testData.ts index 84b71bcf..21ebc980 100644 --- a/test/testData.ts +++ b/test/testData.ts @@ -375,6 +375,68 @@ export const failureResult = { ], }; +export const runWithFailureAndSuccess: TestResult = { + summary: { + failRate: '50%', + testsRan: 2, + orgId: '00D4xx00000FH4IEAW', + outcome: 'Failed', + passing: 1, + failing: 1, + skipped: 0, + passRate: '50%', + skipRate: '0%', + testStartTime: '2020-08-25T00:48:02.000+0000', + testExecutionTimeInMs: 53, + commandTimeInMs: 60, + testTotalTimeInMs: 53, + hostname: 'https://na139.salesforce.com', + testRunId: '707xx0000AUS2gH', + userId: '005xx000000uEgSAAU', + username: 'test@example.com', + }, + tests: [ + { + id: '07Mxx00000ErgiHUAR', + queueItemId: '709xx000001IlUMQA0', + stackTrace: 'Error running test', + message: null, + asyncApexJobId: '707xx0000AUS2gHQQT', + methodName: 'failingTestConfig', + outcome: ApexTestResultOutcome.Fail, + apexLogId: null, + apexClass: { + id: '01pxx00000NWwb3AAD', + name: 'MyFailingTest', + namespacePrefix: '', + fullName: 'MyFailingTest', + }, + runTime: 53, + testTimestamp: '2020-08-25T00:48:02.000+0000', + fullName: 'MyFailingTest.testConfig', + }, + { + id: '07Mxx00000ErgiHUAR', + queueItemId: '709xx000001IlUMQA0', + stackTrace: '', + message: '', + asyncApexJobId: '707xx0000AUS2gHQQT', + methodName: 'passingTestConfig', + outcome: ApexTestResultOutcome.Pass, + apexLogId: null, + apexClass: { + id: '01pxx00000NWwb3AAD', + name: 'MyPassingTest', + namespacePrefix: '', + fullName: 'MyPassingTest', + }, + runTime: 53, + testTimestamp: '2020-08-25T00:48:02.000+0000', + fullName: 'MyPassingTest.testConfig', + }, + ], +}; + export const jsonResult: RunResult = { summary: { commandTime: '60 ms',