Skip to content

Commit

Permalink
feat(reporter): add support for function type to classname option in …
Browse files Browse the repository at this point in the history
…the junit reporter (#6839)

Co-authored-by: Jean-Philippe Leclerc <[email protected]>
Co-authored-by: Jean-Philippe Leclerc <[email protected]>
Co-authored-by: Hiroshi Ogawa <[email protected]>
  • Loading branch information
4 people authored Nov 18, 2024
1 parent e04a136 commit dc238e9
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 5 deletions.
8 changes: 6 additions & 2 deletions docs/guide/reporters.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,13 +290,17 @@ AssertionError: expected 5 to be 4 // Object.is equality
</testsuites>
```

The outputted XML contains nested `testsuites` and `testcase` tags. You can use the reporter options to configure these attributes:
The outputted XML contains nested `testsuites` and `testcase` tags. These can also be customized via reporter options `suiteName` and `classnameTemplate`. `classnameTemplate` can either be a template string or a function.

The supported placeholders for the `classnameTemplate` option are:
- filename
- filepath

```ts
export default defineConfig({
test: {
reporters: [
['junit', { suiteName: 'custom suite name', classname: 'custom-classname' }]
['junit', { suiteName: 'custom suite name', classnameTemplate: 'filename:{filename} - filepath:{filepath}' }]
]
},
})
Expand Down
32 changes: 31 additions & 1 deletion packages/vitest/src/node/reporters/junit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,20 @@ import { getOutputFile } from '../../utils/config-helpers'
import { capturePrintError } from '../error'
import { IndentedLogger } from './renderers/indented-logger'

interface ClassnameTemplateVariables {
filename: string
filepath: string
}

export interface JUnitOptions {
outputFile?: string
/** @deprecated Use `classnameTemplate` instead. */
classname?: string

/**
* Template for the classname attribute. Can be either a string or a function. The string can contain placeholders {filename} and {filepath}.
*/
classnameTemplate?: string | ((classnameVariables: ClassnameTemplateVariables) => string)
suiteName?: string
/**
* Write <system-out> and <system-err> for console output
Expand Down Expand Up @@ -195,10 +206,29 @@ export class JUnitReporter implements Reporter {

async writeTasks(tasks: Task[], filename: string): Promise<void> {
for (const task of tasks) {
let classname = filename

const templateVars: ClassnameTemplateVariables = {
filename: task.file.name,
filepath: task.file.filepath,
}

if (typeof this.options.classnameTemplate === 'function') {
classname = this.options.classnameTemplate(templateVars)
}
else if (typeof this.options.classnameTemplate === 'string') {
classname = this.options.classnameTemplate
.replace(/\{filename\}/g, templateVars.filename)
.replace(/\{filepath\}/g, templateVars.filepath)
}
else if (typeof this.options.classname === 'string') {
classname = this.options.classname
}

await this.writeElement(
'testcase',
{
classname: this.options.classname ?? filename,
classname,
file: this.options.addFileAttribute ? filename : undefined,
name: task.name,
time: getDuration(task),
Expand Down
34 changes: 33 additions & 1 deletion test/reporters/src/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,37 @@ const suite: Suite = {
tasks: [],
}

const passedFile: File = {
id: '1223128da3',
name: 'basic.test.ts',
type: 'suite',
suite,
meta: {},
mode: 'run',
filepath: '/vitest/test/core/test/basic.test.ts',
result: { state: 'pass', duration: 145.99284195899963 },
tasks: [
],
projectName: '',
file: null!,
}
passedFile.file = passedFile
passedFile.tasks.push({
id: '1223128da3_0_0',
type: 'test',
name: 'Math.sqrt()',
mode: 'run',
fails: undefined,
suite,
meta: {},
file: passedFile,
result: {
state: 'pass',
duration: 1.4422860145568848,
},
context: null as any,
})

const error: ErrorWithDiff = {
name: 'AssertionError',
message: 'expected 2.23606797749979 to equal 2',
Expand Down Expand Up @@ -176,5 +207,6 @@ file.tasks = [suite]
suite.tasks = tasks

const files = [file]
const passedFiles = [passedFile]

export { files }
export { files, passedFiles }
44 changes: 44 additions & 0 deletions test/reporters/tests/__snapshots__/reporters.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,39 @@ exports[`JUnit reporter 1`] = `
"
`;

exports[`JUnit reporter with custom function classnameTemplate 1`] = `
"<?xml version="1.0" encoding="UTF-8" ?>
<testsuites name="vitest tests" tests="1" failures="0" errors="0" time="0">
<testsuite name="test/core/test/basic.test.ts" timestamp="2022-01-19T10:10:01.759Z" hostname="hostname" tests="1" failures="0" errors="0" skipped="0" time="0.145992842">
<testcase classname="filename:basic.test.ts - filepath:/vitest/test/core/test/basic.test.ts" name="Math.sqrt()" time="0.001442286">
</testcase>
</testsuite>
</testsuites>
"
`;

exports[`JUnit reporter with custom string classname 1`] = `
"<?xml version="1.0" encoding="UTF-8" ?>
<testsuites name="vitest tests" tests="1" failures="0" errors="0" time="0">
<testsuite name="test/core/test/basic.test.ts" timestamp="2022-01-19T10:10:01.759Z" hostname="hostname" tests="1" failures="0" errors="0" skipped="0" time="0.145992842">
<testcase classname="my-custom-classname" name="Math.sqrt()" time="0.001442286">
</testcase>
</testsuite>
</testsuites>
"
`;

exports[`JUnit reporter with custom string classnameTemplate 1`] = `
"<?xml version="1.0" encoding="UTF-8" ?>
<testsuites name="vitest tests" tests="1" failures="0" errors="0" time="0">
<testsuite name="test/core/test/basic.test.ts" timestamp="2022-01-19T10:10:01.759Z" hostname="hostname" tests="1" failures="0" errors="0" skipped="0" time="0.145992842">
<testcase classname="filename:basic.test.ts - filepath:/vitest/test/core/test/basic.test.ts" name="Math.sqrt()" time="0.001442286">
</testcase>
</testsuite>
</testsuites>
"
`;

exports[`JUnit reporter with outputFile 1`] = `
"JUNIT report written to <process-cwd>/report.xml
"
Expand Down Expand Up @@ -62,6 +95,17 @@ exports[`JUnit reporter with outputFile object in non-existing directory 2`] = `
"
`;
exports[`JUnit reporter without classname 1`] = `
"<?xml version="1.0" encoding="UTF-8" ?>
<testsuites name="vitest tests" tests="1" failures="0" errors="0" time="0">
<testsuite name="test/core/test/basic.test.ts" timestamp="2022-01-19T10:10:01.759Z" hostname="hostname" tests="1" failures="0" errors="0" skipped="0" time="0.145992842">
<testcase classname="test/core/test/basic.test.ts" name="Math.sqrt()" time="0.001442286">
</testcase>
</testsuite>
</testsuites>
"
`;
exports[`json reporter (no outputFile entry) 1`] = `
{
"numFailedTestSuites": 1,
Expand Down
57 changes: 56 additions & 1 deletion test/reporters/tests/reporters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { JUnitReporter } from '../../../packages/vitest/src/node/reporters/junit
import { TapReporter } from '../../../packages/vitest/src/node/reporters/tap'
import { TapFlatReporter } from '../../../packages/vitest/src/node/reporters/tap-flat'
import { getContext } from '../src/context'
import { files } from '../src/data'
import { files, passedFiles } from '../src/data'

const beautify = (json: string) => JSON.parse(json)

Expand Down Expand Up @@ -60,6 +60,61 @@ test('JUnit reporter', async () => {
expect(context.output).toMatchSnapshot()
})

test('JUnit reporter without classname', async () => {
// Arrange
const reporter = new JUnitReporter({})
const context = getContext()

// Act
await reporter.onInit(context.vitest)

await reporter.onFinished(passedFiles)

// Assert
expect(context.output).toMatchSnapshot()
})

test('JUnit reporter with custom string classname', async () => {
// Arrange
const reporter = new JUnitReporter({ classname: 'my-custom-classname' })
const context = getContext()

// Act
await reporter.onInit(context.vitest)

await reporter.onFinished(passedFiles)

// Assert
expect(context.output).toMatchSnapshot()
})

test('JUnit reporter with custom function classnameTemplate', async () => {
// Arrange
const reporter = new JUnitReporter({ classnameTemplate: task => `filename:${task.filename} - filepath:${task.filepath}` })
const context = getContext()

// Act
await reporter.onInit(context.vitest)

await reporter.onFinished(passedFiles)

// Assert
expect(context.output).toMatchSnapshot()
})
test('JUnit reporter with custom string classnameTemplate', async () => {
// Arrange
const reporter = new JUnitReporter({ classnameTemplate: `filename:{filename} - filepath:{filepath}` })
const context = getContext()

// Act
await reporter.onInit(context.vitest)

await reporter.onFinished(passedFiles)

// Assert
expect(context.output).toMatchSnapshot()
})

test('JUnit reporter (no outputFile entry)', async () => {
// Arrange
const reporter = new JUnitReporter({})
Expand Down

0 comments on commit dc238e9

Please sign in to comment.