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
}