Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(reporter): add support for function type to classname option in the junit reporter #6839

Merged
merged 8 commits into from
Nov 18, 2024
8 changes: 6 additions & 2 deletions docs/guide/reporters.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,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
hi-ogawa marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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
Loading