diff --git a/__tests__/__snapshots__/dart-json.test.ts.snap b/__tests__/__snapshots__/dart-json.test.ts.snap index f956f31..a97ccc5 100644 --- a/__tests__/__snapshots__/dart-json.test.ts.snap +++ b/__tests__/__snapshots__/dart-json.test.ts.snap @@ -3,7 +3,41 @@ exports[`dart-json tests matches report snapshot 1`] = ` Object { "annotations": Array [], - "summary": "**6** tests were completed in **3.760s** with **1** passed, **1** skipped and **4** failed.", + "summary": "**6** tests were completed in **3.760s** with **1** passed, **1** skipped and **4** failed. +| Result | Suite | Tests | Time | Passed ✔️ | Failed ❌ | Skipped ✖️ | +| :---: | :--- | ---: | ---: | ---: | ---: | ---: | +| ❌ | [test\\\\main_test.dart](#ts-0-test-maintest-dart) | 4 | 74ms | 1 | 3 | 0 | +| ❌ | [test\\\\second_test.dart](#ts-1-test-secondtest-dart) | 2 | 51ms | 0 | 1 | 1 | +# Test Suites + +## test\\\\main_test.dart ❌ + +### Test 1 + +| Result | Test | Time | +| :---: | :--- | ---: | +| ✔️ | Test 1 Passing test | 36ms | + +### Test 1 Test 1.1 + +| Result | Test | Time | +| :---: | :--- | ---: | +| ❌ | Test 1 Test 1.1 Failing test | 20ms | +| ❌ | Test 1 Test 1.1 Exception in target unit | 6ms | + +### Test 2 + +| Result | Test | Time | +| :---: | :--- | ---: | +| ❌ | Test 2 Exception in test | 12ms | + +## test\\\\second_test.dart ❌ + +| Result | Test | Time | +| :---: | :--- | ---: | +| ❌ | Timeout test | 37ms | +| ✖️ | Skipped test | 14ms | +", "title": "Dart tests ❌", } `; diff --git a/package-lock.json b/package-lock.json index 2f9ed95..b74f916 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7867,9 +7867,9 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", "dev": true }, "lodash.memoize": { diff --git a/src/parsers/dart-json/dart-json-parser.ts b/src/parsers/dart-json/dart-json-parser.ts index 4d4fa9a..a731fc3 100644 --- a/src/parsers/dart-json/dart-json-parser.ts +++ b/src/parsers/dart-json/dart-json-parser.ts @@ -1,6 +1,6 @@ import {ParseOptions, TestResult} from '../test-parser' - -import {Icon} from '../../utils/markdown-utils' +import {Align, Icon, link, table} from '../../utils/markdown-utils' +import {slug} from '../../utils/slugger' import { ReportEvent, @@ -35,28 +35,27 @@ class TestRun { class TestSuite { constructor(readonly suite: Suite) {} - tests = new TestGroup() groups: {[id: number]: TestGroup} = {} get count(): number { - return this.tests.count + Object.values(this.groups).reduce((sum, g) => sum + g.count, 0) + return Object.values(this.groups).reduce((sum, g) => sum + g.count, 0) } get passed(): number { - return this.tests.passed + Object.values(this.groups).reduce((sum, g) => sum + g.passed, 0) + return Object.values(this.groups).reduce((sum, g) => sum + g.passed, 0) } get failed(): number { - return this.tests.failed + Object.values(this.groups).reduce((sum, g) => sum + g.failed, 0) + return Object.values(this.groups).reduce((sum, g) => sum + g.failed, 0) } get skipped(): number { - return this.tests.skipped + Object.values(this.groups).reduce((sum, g) => sum + g.skipped, 0) + return Object.values(this.groups).reduce((sum, g) => sum + g.skipped, 0) } get time(): number { - return this.tests.time + Object.values(this.groups).reduce((sum, g) => sum + g.time, 0) + return Object.values(this.groups).reduce((sum, g) => sum + g.time, 0) } } class TestGroup { - constructor(readonly group?: Group) {} - tests: TestSuiteTest[] = [] + constructor(readonly group: Group) {} + tests: TestCase[] = [] get count(): number { return this.tests.length } @@ -74,7 +73,7 @@ class TestGroup { } } -class TestSuiteTest { +class TestCase { constructor(readonly testStart: TestStartEvent) { this.groupId = testStart.test.groupIDs[testStart.test.groupIDs.length - 1] } @@ -116,7 +115,7 @@ function getTestRun(content: string): TestRun { let success = false let totalTime = 0 const suites: {[id: number]: TestSuite} = {} - const tests: {[id: number]: TestSuiteTest} = {} + const tests: {[id: number]: TestCase} = {} for (const evt of events) { if (isSuiteEvent(evt)) { @@ -124,10 +123,9 @@ function getTestRun(content: string): TestRun { } else if (isGroupEvent(evt)) { suites[evt.group.suiteID].groups[evt.group.id] = new TestGroup(evt.group) } else if (isTestStartEvent(evt) && evt.test.url !== null) { - const test: TestSuiteTest = new TestSuiteTest(evt) + const test: TestCase = new TestCase(evt) const suite = suites[evt.test.suiteID] - const group = - evt.test.groupIDs.length === 0 ? suite.tests : suite.groups[evt.test.groupIDs[evt.test.groupIDs.length - 1]] + const group = suite.groups[evt.test.groupIDs[evt.test.groupIDs.length - 1]] group.tests.push(test) tests[evt.test.id] = test } else if (isTestDoneEvent(evt) && !evt.hidden) { @@ -143,13 +141,70 @@ function getTestRun(content: string): TestRun { return new TestRun(Object.values(suites), success, totalTime) } -function getSummary(testRun: TestRun): string { - const tests = testRun.count - const time = `${(testRun.time / 1000).toFixed(3)}s` - const passed = testRun.passed - const skipped = testRun.skipped - const failed = testRun.failed +function getSummary(tr: TestRun): string { + const time = `${(tr.time / 1000).toFixed(3)}s` + const headingLine = `**${tr.count}** tests were completed in **${time}** with **${tr.passed}** passed, **${tr.skipped}** skipped and **${tr.failed}** failed.` + + const suitesSummary = tr.suites.map((s, i) => { + const icon = s.failed === 0 ? Icon.success : Icon.fail + const tsTime = `${s.time}ms` + const tsName = s.suite.path + const tsAddr = makeSuiteSlug(i, tsName).link + const tsNameLink = link(tsName, tsAddr) + return [icon, tsNameLink, s.count, tsTime, s.passed, s.failed, s.skipped] + }) + + const summary = table( + ['Result', 'Suite', 'Tests', 'Time', `Passed ${Icon.success}`, `Failed ${Icon.fail}`, `Skipped ${Icon.skip}`], + [Align.Center, Align.Left, Align.Right, Align.Right, Align.Right, Align.Right, Align.Right], + ...suitesSummary + ) + + const suites = tr.suites.map((ts, i) => getSuiteSummary(ts, i)).join('\n') + const suitesSection = `# Test Suites\n\n${suites}` + + return `${headingLine}\n${summary}\n${suitesSection}` +} + +function getSuiteSummary(ts: TestSuite, index: number): string { + const icon = ts.failed === 0 ? Icon.success : Icon.fail + + const groups = Object.values(ts.groups) + groups.sort((a, b) => (a.group.line ?? 0) - (b.group.line ?? 0)) + + const content = groups + .filter(grp => grp.count > 0) + .map(grp => { + const header = grp.group.name !== null ? `### ${grp.group.name}\n\n` : '' + grp.tests.sort((a, b) => (a.testStart.test.line ?? 0) - (b.testStart.test.line ?? 0)) + const tests = table( + ['Result', 'Test', 'Time'], + [Align.Center, Align.Left, Align.Right], + ...grp.tests.map(tc => { + const name = tc.testStart.test.name + const time = `${tc.time}ms` + const result = getTestCaseIcon(tc) + return [result, name, time] + }) + ) + + return `${header}${tests}\n` + }) + .join('\n') + + const tsName = ts.suite.path + const tsSlug = makeSuiteSlug(index, tsName) + const tsNameLink = `${tsName}` + return `## ${tsNameLink} ${icon}\n\n${content}` +} + +function makeSuiteSlug(index: number, name: string): {id: string; link: string} { + // use "ts-$index-" as prefix to avoid slug conflicts after escaping the paths + return slug(`ts-${index}-${name}`) +} - const headingLine = `**${tests}** tests were completed in **${time}** with **${passed}** passed, **${skipped}** skipped and **${failed}** failed.` - return `${headingLine}` +function getTestCaseIcon(test: TestCase): string { + if (test.isFailed) return Icon.fail + if (test.isSkipped) return Icon.skip + return Icon.success }