diff --git a/.travis.yml b/.travis.yml index 271d9ca..317b8f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,10 @@ language: node_js node_js: - - "8" - "10" - "12" + - "13" + - "14" env: - - JEST_VERSION=^22.0.0 - - JEST_VERSION=^23.0.0 - - JEST_VERSION=^24.0.0 - - JEST_VERSION=^24.9.0 + - JEST_VERSION=^26.0.0 script: - npm run test:ci diff --git a/README.md b/README.md index 9b98a85..d5c65bf 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ # jest-junit A Jest reporter that creates compatible junit xml files +Note: as of jest-junit 11.0.0 NodeJS >= 10.12.0 is required. + ## Installation ```shell yarn add --dev jest-junit @@ -217,3 +219,28 @@ renders ``` + +#### Adding custom testsuite properties +New feature as of jest-junit 11.0.0! + +Create a file in your project root directory named junitProperties.js: +```js +module.exports = () => { + return { + key: "value" + } +}); +``` + +Will render +```xml + + + + + + + + + +``` diff --git a/__tests__/__snapshots__/buildJsonResults.test.js.snap b/__tests__/__snapshots__/buildJsonResults.test.js.snap index 5e8a628..9eb31b9 100644 --- a/__tests__/__snapshots__/buildJsonResults.test.js.snap +++ b/__tests__/__snapshots__/buildJsonResults.test.js.snap @@ -24,6 +24,18 @@ Object { "timestamp": "2018-02-10T14:52:31", }, }, + Object { + "properties": Array [ + Object { + "property": Object { + "_attr": Object { + "name": "best-tester", + "value": "Jason Palmer", + }, + }, + }, + ], + }, Object { "testcase": Array [ Object { @@ -50,6 +62,18 @@ Object { "timestamp": "2018-02-10T14:52:31", }, }, + Object { + "properties": Array [ + Object { + "property": Object { + "_attr": Object { + "name": "best-tester", + "value": "Jason Palmer", + }, + }, + }, + ], + }, Object { "testcase": Array [ Object { diff --git a/__tests__/buildJsonResults.test.js b/__tests__/buildJsonResults.test.js index 163e09f..6e95680 100644 --- a/__tests__/buildJsonResults.test.js +++ b/__tests__/buildJsonResults.test.js @@ -52,7 +52,8 @@ describe('buildJsonResults', () => { Object.assign({}, constants.DEFAULT_OPTIONS, { classNameTemplate: "{filename}" })); - expect(jsonResults.testsuites[1].testsuite[1].testcase[0]._attr.classname).toBe('foo.test.js'); + + expect(jsonResults.testsuites[1].testsuite[2].testcase[0]._attr.classname).toBe('foo.test.js'); }); it('should support return the function result when classNameTemplate is a function', () => { @@ -63,7 +64,7 @@ describe('buildJsonResults', () => { return 'function called with vars: ' + Object.keys(vars).join(', '); } })); - expect(jsonResults.testsuites[1].testsuite[1].testcase[0]._attr.classname) + expect(jsonResults.testsuites[1].testsuite[2].testcase[0]._attr.classname) .toBe('function called with vars: filepath, filename, classname, title, displayName'); }); @@ -73,7 +74,7 @@ describe('buildJsonResults', () => { Object.assign({}, constants.DEFAULT_OPTIONS, { titleTemplate: "{filepath}" })); - expect(jsonResults.testsuites[1].testsuite[1].testcase[0]._attr.name).toBe('path/to/test/__tests__/foo.test.js'); + expect(jsonResults.testsuites[1].testsuite[2].testcase[0]._attr.name).toBe('path/to/test/__tests__/foo.test.js'); }); it('should return the proper filepath when suiteNameTemplate is "{filepath}" and usePathForSuiteName is "false"', () => { @@ -116,7 +117,7 @@ describe('buildJsonResults', () => { const noFailingTestsReport = require('../__mocks__/no-failing-tests.json'); const jsonResults = buildJsonResults(noFailingTestsReport, '/', Object.assign({}, constants.DEFAULT_OPTIONS)); - expect(jsonResults.testsuites[1].testsuite[1].testcase[0]._attr.classname).toBe('foo baz should bar'); + expect(jsonResults.testsuites[1].testsuite[2].testcase[0]._attr.classname).toBe('foo baz should bar'); }); it('should return the proper classname when ancestorSeparator is customized', () => { @@ -125,14 +126,14 @@ describe('buildJsonResults', () => { Object.assign({}, constants.DEFAULT_OPTIONS, { ancestorSeparator: " › " })); - expect(jsonResults.testsuites[1].testsuite[1].testcase[0]._attr.classname).toBe('foo › baz should bar'); + expect(jsonResults.testsuites[1].testsuite[2].testcase[0]._attr.classname).toBe('foo › baz should bar'); }); it('should parse failure messages for failing tests', () => { const failingTestsReport = require('../__mocks__/failing-tests.json'); const jsonResults = buildJsonResults(failingTestsReport, '/path/to/test', constants.DEFAULT_OPTIONS); - const failureMsg = jsonResults.testsuites[1].testsuite[1].testcase[1].failure; + const failureMsg = jsonResults.testsuites[1].testsuite[2].testcase[1].failure; // Make sure no escape codes are there that exist in the mock expect(failureMsg.includes('\u001b')).toBe(false); @@ -158,7 +159,7 @@ describe('buildJsonResults', () => { it('should not return the file name by default', () => { const noFailingTestsReport = require('../__mocks__/no-failing-tests.json'); const jsonResults = buildJsonResults(noFailingTestsReport, '/', constants.DEFAULT_OPTIONS); - expect(jsonResults.testsuites[1].testsuite[1].testcase[0]._attr.file).toBe(undefined); + expect(jsonResults.testsuites[1].testsuite[2].testcase[0]._attr.file).toBe(undefined); }); it('should return the file name when addFileAttribute is "true"', () => { @@ -167,7 +168,7 @@ describe('buildJsonResults', () => { Object.assign({}, constants.DEFAULT_OPTIONS, { addFileAttribute: "true" })); - expect(jsonResults.testsuites[1].testsuite[1].testcase[0]._attr.file).toBe('path/to/test/__tests__/foo.test.js'); + expect(jsonResults.testsuites[1].testsuite[2].testcase[0]._attr.file).toBe('path/to/test/__tests__/foo.test.js'); }); it('should show output of console if includeConsoleOutput is true', () => { @@ -189,7 +190,7 @@ describe('buildJsonResults', () => { expect(jsonResults.testsuites[1].testsuite[1]['system-out']).not.toBeDefined(); }); - + it('should show short console output if includeShortConsoleOutput is true', () => { const reportWithShortConsoleOutput = require('../__mocks__/test-with-console-output.json'); const jsonResults = buildJsonResults(reportWithShortConsoleOutput, '/', @@ -206,7 +207,7 @@ describe('buildJsonResults', () => { Object.assign({}, constants.DEFAULT_OPTIONS, { includeShortConsoleOutput: "false" })); - - expect(jsonResults.testsuites[1].testsuite[1]['system-out']).not.toBeDefined(); + + expect(jsonResults.testsuites[1].testsuite[2]['system-out']).not.toBeDefined(); }); }); diff --git a/__tests__/getOptions.test.js b/__tests__/getOptions.test.js index 9ed5999..48fcb9b 100644 --- a/__tests__/getOptions.test.js +++ b/__tests__/getOptions.test.js @@ -6,7 +6,7 @@ const getOptions = require('../utils/getOptions.js'); jest.mock('fs', () => { return Object.assign( {}, - require.requireActual('fs'), + jest.requireActual('fs'), { existsSync: jest.fn().mockReturnValue(true) } diff --git a/__tests__/testResultProcessor.test.js b/__tests__/testResultProcessor.test.js index 7da9194..194b8da 100644 --- a/__tests__/testResultProcessor.test.js +++ b/__tests__/testResultProcessor.test.js @@ -3,7 +3,7 @@ jest.mock('mkdirp', () => { return Object.assign( {}, - require.requireActual('mkdirp'), + jest.requireActual('mkdirp'), { sync: jest.fn() } @@ -13,7 +13,7 @@ jest.mock('mkdirp', () => { jest.mock('fs', () => { return Object.assign( {}, - require.requireActual('fs'), + jest.requireActual('fs'), { writeFileSync: jest.fn() } diff --git a/constants/index.js b/constants/index.js index d40b320..a0bd786 100644 --- a/constants/index.js +++ b/constants/index.js @@ -14,6 +14,7 @@ module.exports = { JEST_JUNIT_INCLUDE_CONSOLE_OUTPUT: 'includeConsoleOutput', JEST_JUNIT_INCLUDE_SHORT_CONSOLE_OUTPUT: 'includeShortConsoleOutput', JEST_USE_PATH_FOR_SUITE_NAME: 'usePathForSuiteName', + JEST_JUNIT_TEST_SUITE_PROPERTIES_JSON_FILE: 'testSuitePropertiesFile' }, DEFAULT_OPTIONS: { suiteName: 'jest tests', @@ -27,7 +28,8 @@ module.exports = { usePathForSuiteName: 'false', addFileAttribute: 'false', includeConsoleOutput: 'false', - includeShortConsoleOutput: 'false' + includeShortConsoleOutput: 'false', + testSuitePropertiesFile: 'junitProperties.js' }, CLASSNAME_VAR: 'classname', FILENAME_VAR: 'filename', diff --git a/jest.config.js b/jest.config.js index 214e2f7..b26860f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -12,5 +12,6 @@ module.exports = { "/integration-tests/testResultsProcessor/", "/integration-tests/reporter/" ], - setupFilesAfterEnv: ["/__tests__/lib/setupTests.js"] + setupFilesAfterEnv: ["/__tests__/lib/setupTests.js"], + reporters: ['default', '.'] }; diff --git a/junitProperties.js b/junitProperties.js new file mode 100644 index 0000000..b4f911f --- /dev/null +++ b/junitProperties.js @@ -0,0 +1,5 @@ +module.exports = (suite) => { + return { + 'best-tester': 'Jason Palmer' + } +} diff --git a/package.json b/package.json index 02efc8f..ce890db 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { "name": "jest-junit", - "version": "10.0.0", + "version": "11.0.0", "description": "A jest reporter that generates junit xml files", "main": "index.js", "repository": "https://github.com/jest-community/jest-junit", "author": "Jason Palmer", "license": "Apache-2.0", "engines": { - "node": ">=8.0.0" + "node": ">=10.12.0" }, "files": [ "index.js", @@ -21,7 +21,7 @@ }, "dependencies": { "jest-validate": "^24.9.0", - "mkdirp": "^0.5.1", + "mkdirp": "^1.0.4", "strip-ansi": "^5.2.0", "uuid": "^3.3.3", "xml": "^1.0.1" diff --git a/utils/buildJsonResults.js b/utils/buildJsonResults.js index 87a0fd1..ffcba9d 100644 --- a/utils/buildJsonResults.js +++ b/utils/buildJsonResults.js @@ -3,7 +3,7 @@ const stripAnsi = require('strip-ansi'); const constants = require('../constants/index'); const path = require('path'); - +const fs = require('fs'); // Wrap the varName with template tags const toTemplateTag = function (varName) { @@ -35,6 +35,10 @@ const executionTime = function (startTime, endTime) { } module.exports = function (report, appDirectory, options) { + // Check if there is a junitProperties.js (or whatever they called it) + const junitSuitePropertiesFilePath = path.join(process.cwd(), options.testSuitePropertiesFile); + let ignoreSuitePropertiesCheck = !fs.existsSync(junitSuitePropertiesFilePath); + // Generate a single XML file for all jest tests let jsonResults = { 'testsuites': [{ @@ -114,7 +118,7 @@ module.exports = function (report, appDirectory, options) { testSuite.testsuite.push(testSuiteConsole); } - + // Write short stdout console output if available if (options.includeShortConsoleOutput === 'true' && suite.console && suite.console.length) { // Extract and then Stringify the console message value @@ -129,6 +133,30 @@ module.exports = function (report, appDirectory, options) { testSuite.testsuite.push(testSuiteConsole); } + if (!ignoreSuitePropertiesCheck) { + let junitSuiteProperties = require(junitSuitePropertiesFilePath)(suite); + + // Add any test suite properties + let testSuitePropertyMain = { + 'properties': [] + }; + + Object.keys(junitSuiteProperties).forEach((p) => { + let testSuiteProperty = { + 'property': { + _attr: { + name: p, + value: replaceVars(junitSuiteProperties[p], suiteNameVariables) + } + } + }; + + testSuitePropertyMain.properties.push(testSuiteProperty); + }); + + testSuite.testsuite.push(testSuitePropertyMain); + } + // Iterate through test cases suite.testResults.forEach((tc) => { const classname = tc.ancestorTitles.join(options.ancestorSeparator); diff --git a/yarn.lock b/yarn.lock index 3e0576b..4441354 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2237,6 +2237,11 @@ minimist@^1.1.1, minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" @@ -2265,13 +2270,25 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5.0, mkdirp@^0.5.1: +mkdirp@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= dependencies: minimist "0.0.8" +mkdirp@^0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"