Skip to content

Commit

Permalink
Merge pull request #30 from dangoslen/suite-name-template
Browse files Browse the repository at this point in the history
Suite Name Template Feature
  • Loading branch information
palmerj3 committed Oct 18, 2017
2 parents 49a67d2 + 8209d1c commit 3a620d7
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 24 deletions.
56 changes: 43 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,24 @@ jest --ci --testResultsProcessor="jest-junit"

`jest-junit` offers five configurations based on environment variables or a `jest-junit` key defined in `package.json`. All configuration values should be **strings**.

| Variable Name | Description | Default |
|--|--|--|
| `JEST_SUITE_NAME` | `name` attribute of `<testsuites>` | `"jest tests"` |
| `JEST_JUNIT_OUTPUT` | File path to save the output. | `"./junit.xml"` |
| `JEST_JUNIT_CLASSNAME` | Template string for the `classname` attribute of `<testcase>`. | `"{classname} {title}"` |
| `JEST_JUNIT_TITLE` | Template string for the `name` attribute of `<testcase>`. | `"{classname} {title}"` |
| `JEST_JUNIT_ANCESTOR_SEPARATOR` | Character(s) used to join the `describe` blocks. | `" "` |
| `JEST_USE_PATH_FOR_SUITE_NAME` | Use file path as the `name` attribute of `<testsuite>` | `"false"` |
| Variable Name | Description | Default | Possible Injection Values
|--|--|--|--|
| `JEST_SUITE_NAME` | `name` attribute of `<testsuites>` | `"jest tests"` | N/A
| `JEST_JUNIT_OUTPUT` | File path to save the output. | `"./junit.xml"` | N/A
| `JEST_JUNIT_SUITE_NAME` | Template string for `name` attribute of the `<testsuite>`. | `"{title}"` | `{title}`, `{filepath}`, `{filename}`
| `JEST_JUNIT_CLASSNAME` | Template string for the `classname` attribute of `<testcase>`. | `"{classname} {title}"` | `{classname}`, `{title}`
| `JEST_JUNIT_TITLE` | Template string for the `name` attribute of `<testcase>`. | `"{classname} {title}"` | `{classname}`, `{title}`
| `JEST_JUNIT_ANCESTOR_SEPARATOR` | Character(s) used to join the `describe` blocks. | `" "` | N/A
| `JEST_USE_PATH_FOR_SUITE_NAME` | **DEPRECATED. Use `suiteNameTemplate` instead.** Use file path as the `name` attribute of `<testsuite>` | `"false"` | N/A

Example:

You can configure these options via the command line as seen below:

```shell
JEST_SUITE_NAME="Jest JUnit Unit Tests" JEST_JUNIT_OUTPUT="./artifacts/junit.xml" jest
```

You can also define a `jest-junit` key in your `package.json`. All are **string** values.
Or you can also define a `jest-junit` key in your `package.json`. All are **string** values.

```
{
Expand All @@ -62,8 +64,14 @@ You can also define a `jest-junit` key in your `package.json`. All are **string
}
```

For the following test:
### Configuration Precedence
If using the `usePathForSuiteName` and `suiteNameTemplate`, the `usePathForSuiteName` value will take precedence. ie: if `usePathForSuiteName=true` and `suiteNameTemplate="{filename}"`, the filepath will be used as the `name` attribute of the `<testsuite>` in the rendered `jest-junit.xml`).

### Examples

Below are some example configuration values and the rendered `.xml` to created by `jest-junit`.

The following test defined in the file `/__tests__/addition.test.js` will be used for all examples:
```js
describe('addition', () => {
describe('positive numbers', () => {
Expand All @@ -74,6 +82,7 @@ describe('addition', () => {
});
```

#### Example 1
The default output:

```xml
Expand All @@ -85,12 +94,15 @@ The default output:
</testsuites>
```

Changing the `classNameTemplate` and `titleTemplate`:
#### Example 2
Using the `classNameTemplate` and `titleTemplate`:

```shell
JEST_JUNIT_CLASSNAME="{classname}" JEST_JUNIT_TITLE="{title}" jest
```

renders

```xml
<testsuites name="jest tests">
<testsuite name="addition" tests="1" errors="0" failures="0" skipped="0" timestamp="2017-07-13T09:45:42" time="0.154">
Expand All @@ -100,11 +112,13 @@ JEST_JUNIT_CLASSNAME="{classname}" JEST_JUNIT_TITLE="{title}" jest
</testsuites>
```

Changing just the `ancestorSeparator`:
#### Example 3
Using the `ancestorSeparator`:

```shell
JEST_JUNIT_ANCESTOR_SEPARATOR="" jest
```
renders

```xml
<testsuites name="jest tests">
Expand All @@ -114,3 +128,19 @@ JEST_JUNIT_ANCESTOR_SEPARATOR=" › " jest
</testsuite>
</testsuites>
```

#### Example 4
Using the `suiteNameTemplate`:

```shell
JEST_JUNIT_SUIT_NAME ="{filename}" jest
```

```xml
<testsuites name="jest tests">
<testsuite name="addition.test.js" tests="1" errors="0" failures="0" skipped="0" timestamp="2017-07-13T09:42:28" time="0.161">
<testcase classname="addition positive numbers should add up" name="addition positive numbers should add up" time="0.004">
</testcase>
</testsuite>
</testsuites>
```
22 changes: 22 additions & 0 deletions __tests__/buildJsonResults.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,27 @@ describe('buildJsonResults', () => {
expect(jsonResults.testsuites[1].testsuite[0]._attr.name).toBe('foo');
});

it('should return the proper filename when suiteNameTemplate is "{filename}"', () => {
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
const jsonResults = buildJsonResults(noFailingTestsReport, '',
Object.assign({}, constants.DEFAULT_OPTIONS, { suiteNameTemplate: "{filename}" }));
expect(jsonResults.testsuites[1].testsuite[0]._attr.name).toBe('foo.test.js');
});

it('should return the proper filepath when suiteNameTemplate is "{filepath}" and usePathForSuiteName is "false"', () => {
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
const jsonResults = buildJsonResults(noFailingTestsReport, '',
Object.assign({}, constants.DEFAULT_OPTIONS, { suiteNameTemplate: "{filepath}" }));
expect(jsonResults.testsuites[1].testsuite[0]._attr.name).toBe('/path/to/test/__tests__/foo.test.js');
});

it('should return the proper name from ancestorTitles when suiteNameTemplate is set to "{title}" and usePathForSuiteName is "true"', () => {
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
const jsonResults = buildJsonResults(noFailingTestsReport, '',
Object.assign({}, constants.DEFAULT_OPTIONS, { usePathForSuiteName: "true" }));
expect(jsonResults.testsuites[1].testsuite[0]._attr.name).toBe('/path/to/test/__tests__/foo.test.js');
});

it('should return the proper name from testFilePath when usePathForSuiteName is "true"; no appDirectory set', () => {
const noFailingTestsReport = require('../__mocks__/no-failing-tests.json');
const jsonResults = buildJsonResults(noFailingTestsReport, '',
Expand Down Expand Up @@ -49,4 +70,5 @@ describe('buildJsonResults', () => {
expect(failureMsg.includes('\u001b')).toBe(false);

});

});
4 changes: 4 additions & 0 deletions constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = {
JEST_SUITE_NAME: 'suiteName',
JEST_JUNIT_OUTPUT: 'output',
JEST_JUNIT_CLASSNAME: 'classNameTemplate',
JEST_JUNIT_SUITE_NAME: 'suiteNameTemplate',
JEST_JUNIT_TITLE: 'titleTemplate',
JEST_JUNIT_ANCESTOR_SEPARATOR: 'ancestorSeparator',
JEST_USE_PATH_FOR_SUITE_NAME: 'usePathForSuiteName',
Expand All @@ -15,10 +16,13 @@ module.exports = {
suiteName: 'jest tests',
output: path.join(process.cwd(), './junit.xml'),
classNameTemplate: '{classname} {title}',
suiteNameTemplate: '{title}',
titleTemplate: '{classname} {title}',
ancestorSeparator: ' ',
usePathForSuiteName: 'false',
},
CLASSNAME_VAR: '{classname}',
FILENAME_VAR: '{filename}',
FILEPATH_VAR: '{filepath}',
TITLE_VAR: '{title}',
};
48 changes: 37 additions & 11 deletions utils/buildJsonResults.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

const stripAnsi = require('strip-ansi');
const constants = require('../constants/index');
const path = require('path');

const replaceVars = function (str, classname, title) {
return str
.replace(constants.CLASSNAME_VAR, classname)
.replace(constants.TITLE_VAR, title);
// Takes a string and a Map of 'tag' values to replacement values
const replaceVars = function (str, replacementMap) {
Object.keys(replacementMap).forEach((key) => {
str = str.replace(key, replacementMap[key]);
});

return str;
};

module.exports = function (report, appDirectory, options) {
Expand All @@ -28,14 +32,31 @@ module.exports = function (report, appDirectory, options) {
return;
}

// If the usePathForSuiteName option is true and the
// suiteNameTemplate value is set to the default, overrides
// the suiteNameTemplate.
if(options.usePathForSuiteName === 'true'
&& options.suiteNameTemplate === constants.TITLE_VAR) {

options.suiteNameTemplate = constants.FILEPATH_VAR;
}

// Build variables for suite name
const filepath = suite.testFilePath.replace(appDirectory, '');
const filename = path.basename(filepath);
const suiteTitle = suite.testResults[0].ancestorTitles[0];

// Build replacement map
let suiteReplacementMap = {};
suiteReplacementMap[constants.FILEPATH_VAR] = filepath;
suiteReplacementMap[constants.FILENAME_VAR] = filename;
suiteReplacementMap[constants.TITLE_VAR] = suiteTitle;

// Add <testsuite /> properties
let testSuite = {
'testsuite': [{
_attr: {
name: options.usePathForSuiteName === "true" ?
suite.testFilePath.replace(appDirectory, '') :
suite.testResults[0].ancestorTitles[0],
tests: suite.numFailingTests + suite.numPassingTests + suite.numPendingTests,
name: replaceVars(options.suiteNameTemplate, suiteReplacementMap),
errors: 0, // not supported
failures: suite.numFailingTests,
skipped: suite.numPendingTests,
Expand All @@ -48,13 +69,18 @@ module.exports = function (report, appDirectory, options) {
// Iterate through test cases
suite.testResults.forEach((tc) => {
const classname = tc.ancestorTitles.join(options.ancestorSeparator);
const title = tc.title;
const testTitle = tc.title;

// Build replacement map
let testReplacementMap = {};
testReplacementMap[constants.CLASSNAME_VAR] = classname;
testReplacementMap[constants.TITLE_VAR] = testTitle;

let testCase = {
'testcase': [{
_attr: {
classname: replaceVars(options.classNameTemplate, classname, title),
name: replaceVars(options.titleTemplate, classname, title),
classname: replaceVars(options.classNameTemplate, testReplacementMap),
name: replaceVars(options.titleTemplate, testReplacementMap),
time: tc.duration / 1000
}
}]
Expand Down

0 comments on commit 3a620d7

Please sign in to comment.