diff --git a/__mocks__/failing-tests-with-esc.json b/__mocks__/failing-tests-with-esc.json new file mode 100644 index 0000000..bc95281 --- /dev/null +++ b/__mocks__/failing-tests-with-esc.json @@ -0,0 +1,68 @@ +{ + "numFailedTestSuites": 0, + "numFailedTests": 0, + "numPassedTestSuites": 1, + "numPassedTests": 1, + "numPendingTestSuites": 0, + "numPendingTests": 0, + "numRuntimeErrorTestSuites": 0, + "numTotalTestSuites": 1, + "numTotalTests": 1, + "snapshot": { + "added": 0, + "failure": false, + "filesAdded": 0, + "filesRemoved": 0, + "filesUnmatched": 0, + "filesUpdated": 0, + "matched": 0, + "total": 0, + "unchecked": 0, + "unmatched": 0, + "updated": 0 + }, + "startTime": 1489712747092, + "success": true, + "testResults": [ + { + "console": [], + "failureMessage": "\u001b[1m\u001b[31m \u001b[1m● \u001b[1mSample Failing Test › Should fail\u001b[39m\u001b[22m\n\n foo\u001bbar\n\u001b[2m \n \u001b[2mat _callee$ (\u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:26:15)\u001b[2m\n \u001b[2mat tryCatch (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:64:40)\u001b[2m\n \u001b[2mat GeneratorFunctionPrototype.invoke [as _invoke] (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:299:22)\u001b[2m\n \u001b[2mat GeneratorFunctionPrototype.prototype.(anonymous function) [as next] (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:116:21)\u001b[2m\n \u001b[2mat step (\u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:2:394)\u001b[2m\n \u001b[2mat \u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:2:554\u001b[2m\u001b[22m\n", + "numFailingTests": 1, + "numPassingTests": 0, + "numPendingTests": 0, + "perfStats": { + "end": 1499904221109, + "start": 1499904215586 + }, + "snapshot": { + "added": 0, + "fileDeleted": false, + "matched": 0, + "unchecked": 0, + "unmatched": 0, + "updated": 0 + }, + "testFilePath": "/path/to/failing.test.js", + "testResults": [ + { + "ancestorTitles": [ + "Sample Failing Test", + "Inner", + "Inner Inner" + ], + "duration": 3930, + "failureMessages": [ + "\u001b[1m\u001b[31m \u001b[1m● \u001b[1mSample Failing Test › Inner › Inner Inner › Should fail\u001b[39m\u001b[22m\n\n foo\u001bbar\n\u001b[2m \n \u001b[2mat _callee$ (\u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:26:15)\u001b[2m\n \u001b[2mat tryCatch (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:64:40)\u001b[2m\n \u001b[2mat GeneratorFunctionPrototype.invoke [as _invoke] (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:299:22)\u001b[2m\n \u001b[2mat GeneratorFunctionPrototype.prototype.(anonymous function) [as next] (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:116:21)\u001b[2m\n \u001b[2mat step (\u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:2:394)\u001b[2m\n \u001b[2mat \u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:2:554\u001b[2m\u001b[22m\n" + ], + "fullName": "Sample Failing Test Inner Inner Inner Should fail", + "numPassingAsserts": 0, + "status": "failed", + "title": "Should fail" + } + ], + "sourceMaps": {}, + "skipped": false + } + ], + "wasInterrupted": false +} diff --git a/__tests__/testResultProcessor.test.js b/__tests__/testResultProcessor.test.js index b78805a..d7cfd42 100644 --- a/__tests__/testResultProcessor.test.js +++ b/__tests__/testResultProcessor.test.js @@ -27,8 +27,22 @@ const path = require('path'); const testResultProcessor = require('../'); describe('jest-junit', () => { + beforeEach(() => { + const foundKeys = Object.keys(process.env).filter(k => k.startsWith('JEST_JUNIT')); + if (foundKeys.length > 0) { + throw new Error(`process.env should not have JEST_JUNIT keys set. Found: ${foundKeys.join(', ')}`); + } + }); + + afterEach(() => { + jest.clearAllMocks(); - afterEach(() => jest.clearAllMocks()) + for (let key in process.env) { + if (key.startsWith('JEST_JUNIT')) { + delete process.env[key]; + } + } + }); it('should generate valid xml with default name', () => { const noFailingTestsReport = require('../__mocks__/no-failing-tests.json'); @@ -67,6 +81,20 @@ describe('jest-junit', () => { expect(xmlDoc).toBeTruthy(); }); + it('should generate valid xml despite illegal characters', () => { + const failingTestsWithEscReport = require('../__mocks__/failing-tests-with-esc.json'); + testResultProcessor(failingTestsWithEscReport); + + // Ensure fs.writeFileSync is called + expect(fs.writeFileSync).toHaveBeenCalledTimes(1); + + // Ensure file would have been generated + expect(fs.writeFileSync).toHaveBeenLastCalledWith(path.resolve('junit.xml'), expect.any(String)); + + // Ensure generated file is valid xml + const xmlDoc = libxmljs.parseXml(fs.writeFileSync.mock.calls[0][1]); + expect(xmlDoc).toBeTruthy(); + }); it('should generate xml at the output filepath defined by JEST_JUNIT_OUTPUT_FILE', () => { process.env.JEST_JUNIT_OUTPUT_FILE = 'path_to_output/output_name.xml' @@ -83,6 +111,7 @@ describe('jest-junit', () => { }); it('should generate xml at the output filepath defined by outputFile config', () => { + process.env.JEST_JUNIT_OUTPUT_FILE = 'path_to_output/output_name.xml' const noFailingTestsReport = require('../__mocks__/no-failing-tests.json'); testResultProcessor(noFailingTestsReport, {outputFile: 'path_to_output/output_name.xml' }); diff --git a/utils/buildJsonResults.js b/utils/buildJsonResults.js index 1b41a7c..6d1e59e 100644 --- a/utils/buildJsonResults.js +++ b/utils/buildJsonResults.js @@ -74,7 +74,7 @@ const generateTestCase = function(junitOptions, suiteOptions, tc, filepath, file failureMessages.forEach((failure) => { const tagName = tc.status === testFailureStatus ? 'failure': testErrorStatus testCase.testcase.push({ - [tagName]: stripAnsi(failure) + [tagName]: strip(failure) }); }) } @@ -102,6 +102,11 @@ const addErrorTestResult = function (suite) { }) } +// Strips escape codes for readability and illegal XML characters to produce valid output. +const strip = function (str) { + return stripAnsi(str).replace(/\u001b/g, ''); +} + module.exports = function (report, appDirectory, options, rootDir = null) { // Check if there is a junitProperties.js (or whatever they called it) const junitSuitePropertiesFilePath = getTestSuitePropertiesPath(