From 9f3b9c8356225cb17c1b9257252b580a5a126c4b Mon Sep 17 00:00:00 2001 From: SimeonC <1085899+SimeonC@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:11:19 +0900 Subject: [PATCH 01/17] refactor: convert to class syntax --- src/builder.js | 77 +++++++---- src/factory.js | 31 +++-- src/test_case.js | 326 ++++++++++++++++++++++++++++------------------ src/test_suite.js | 174 +++++++++++++++---------- 4 files changed, 374 insertions(+), 234 deletions(-) diff --git a/src/builder.js b/src/builder.js index 017b5cc..ac6f968 100644 --- a/src/builder.js +++ b/src/builder.js @@ -1,3 +1,4 @@ +// @ts-check var _ = require('lodash'); var xmlBuilder = require('xmlbuilder'); var path = require('path'); @@ -6,38 +7,58 @@ var fs = require('fs'); var TestSuite = require('./test_suite'); var TestCase = require('./test_case'); -function JUnitReportBuilder(factory) { - this._factory = factory; - this._testSuitesAndCases = []; -} +class JUnitReportBuilder { + /** + * @param {import('./factory')} factory + */ + constructor(factory) { + this._factory = factory; + this._testSuitesAndCases = []; + } -JUnitReportBuilder.prototype.writeTo = function (reportPath) { - makeDir.sync(path.dirname(reportPath)); - fs.writeFileSync(reportPath, this.build(), 'utf8'); -}; + /** + * @param {string} reportPath + */ + writeTo(reportPath) { + makeDir.sync(path.dirname(reportPath)); + fs.writeFileSync(reportPath, this.build(), 'utf8'); + } -JUnitReportBuilder.prototype.build = function () { - var xmlTree = xmlBuilder.create('testsuites', { encoding: 'UTF-8', invalidCharReplacement: '' }); - _.forEach(this._testSuitesAndCases, function (suiteOrCase) { - suiteOrCase.build(xmlTree); - }); - return xmlTree.end({ pretty: true }); -}; + /** + * @returns {string} xml file content + */ + build() { + var xmlTree = xmlBuilder.create('testsuites', { encoding: 'UTF-8', invalidCharReplacement: '' }); + _.forEach(this._testSuitesAndCases, function (suiteOrCase) { + suiteOrCase.build(xmlTree); + }); + return xmlTree.end({ pretty: true }); + } -JUnitReportBuilder.prototype.testSuite = function () { - var suite = this._factory.newTestSuite(); - this._testSuitesAndCases.push(suite); - return suite; -}; + /** + * @returns {import('./test_suite')} + */ + testSuite() { + var suite = this._factory.newTestSuite(); + this._testSuitesAndCases.push(suite); + return suite; + } -JUnitReportBuilder.prototype.testCase = function () { - var testCase = this._factory.newTestCase(); - this._testSuitesAndCases.push(testCase); - return testCase; -}; + /** + * @returns {import('./test_case')} + */ + testCase() { + var testCase = this._factory.newTestCase(); + this._testSuitesAndCases.push(testCase); + return testCase; + } -JUnitReportBuilder.prototype.newBuilder = function () { - return this._factory.newBuilder(); -}; + /** + * @returns {ReturnType} + */ + newBuilder() { + return this._factory.newBuilder(); + } +} module.exports = JUnitReportBuilder; diff --git a/src/factory.js b/src/factory.js index 4bbeffb..8a71d1d 100644 --- a/src/factory.js +++ b/src/factory.js @@ -2,18 +2,29 @@ var Builder = require('./builder'); var TestSuite = require('./test_suite'); var TestCase = require('./test_case'); -function Factory() {} +class Factory { + constructor() {} -Factory.prototype.newBuilder = function () { - return new Builder(this); -}; + /** + * @returns {Builder} + */ + newBuilder() { + return new Builder(this); + } -Factory.prototype.newTestSuite = function () { - return new TestSuite(this); -}; + /** + * @returns {TestSuite} + */ + newTestSuite() { + return new TestSuite(this); + } -Factory.prototype.newTestCase = function () { - return new TestCase(this); -}; + /** + * @returns {TestCase} + */ + newTestCase() { + return new TestCase(this); + } +} module.exports = Factory; diff --git a/src/test_case.js b/src/test_case.js index bb1b900..b08c99d 100755 --- a/src/test_case.js +++ b/src/test_case.js @@ -1,144 +1,210 @@ +// @ts-check var _ = require('lodash'); -function TestCase() { - this._error = false; - this._failure = false; - this._skipped = false; - this._standardOutput = undefined; - this._standardError = undefined; - this._stacktrace = undefined; - this._attributes = {}; - this._errorAttributes = {}; - this._failureAttributes = {}; - this._errorAttachment = undefined; - this._errorContent = undefined; - this._properties = []; -} +class TestCase { + constructor() { + this._error = false; + this._failure = false; + this._skipped = false; + this._standardOutput = undefined; + this._standardError = undefined; + this._stacktrace = undefined; + this._attributes = {}; + this._errorAttributes = {}; + this._failureAttributes = {}; + this._errorAttachment = undefined; + this._errorContent = undefined; + this._properties = []; + } -TestCase.prototype.className = function (className) { - this._attributes.classname = className; - return this; -}; - -TestCase.prototype.name = function (name) { - this._attributes.name = name; - return this; -}; - -TestCase.prototype.time = function (timeInSeconds) { - this._attributes.time = timeInSeconds; - return this; -}; - -TestCase.prototype.file = function (filepath) { - this._attributes.file = filepath; - return this; -}; - -TestCase.prototype.failure = function (message, type) { - this._failure = true; - if (message) { - this._failureAttributes.message = message; - } - if (type) { - this._failureAttributes.type = type; - } - return this; -}; - -TestCase.prototype.error = function (message, type, content) { - this._error = true; - if (message) { - this._errorAttributes.message = message; - } - if (type) { - this._errorAttributes.type = type; - } - if (content) { - this._errorContent = content; - } - return this; -}; - -TestCase.prototype.stacktrace = function (stacktrace) { - this._failure = true; - this._stacktrace = stacktrace; - return this; -}; - -TestCase.prototype.skipped = function () { - this._skipped = true; - return this; -}; - -TestCase.prototype.standardOutput = function (log) { - this._standardOutput = log; - return this; -}; - -TestCase.prototype.standardError = function (log) { - this._standardError = log; - return this; -}; - -TestCase.prototype.getFailureCount = function () { - return Number(this._failure); -}; - -TestCase.prototype.getErrorCount = function () { - return Number(this._error); -}; - -TestCase.prototype.getSkippedCount = function () { - return Number(this._skipped); -}; - -TestCase.prototype.errorAttachment = function (path) { - this._errorAttachment = path; - return this; -}; - -TestCase.prototype.property = function (name, value) { - this._properties.push({ name: name, value: value }); - return this; -}; - -TestCase.prototype.build = function (parentElement) { - var testCaseElement = parentElement.ele('testcase', this._attributes); - if (this._properties.length) { - var propertiesElement = testCaseElement.ele('properties'); - _.forEach(this._properties, function (property) { - propertiesElement.ele('property', { - name: property.name, - value: property.value, - }); - }); + /** + * @param {string} className + * @chainable + */ + className(className) { + this._attributes.classname = className; + return this; + } + + /** + * @param {string} name + * @chainable + */ + name(name) { + this._attributes.name = name; + return this; } - if (this._failure) { - var failureElement = testCaseElement.ele('failure', this._failureAttributes); - if (this._stacktrace) { - failureElement.cdata(this._stacktrace); + + /** + * @param {number} timeInSeconds + * @chainable + */ + time(timeInSeconds) { + this._attributes.time = timeInSeconds; + return this; + } + + /** + * @param {string} filepath + * @chainable + */ + file(filepath) { + this._attributes.file = filepath; + return this; + } + + /** + * @param {string} message + * @param {string} type + * @chainable + */ + failure(message, type) { + this._failure = true; + if (message) { + this._failureAttributes.message = message; } + if (type) { + this._failureAttributes.type = type; + } + return this; } - if (this._error) { - var errorElement = testCaseElement.ele('error', this._errorAttributes); - if (this._errorContent) { - errorElement.cdata(this._errorContent); + + /** + * @param {string} message + * @param {string} type + * @param {string} content + * @chainable + */ + error(message, type, content) { + this._error = true; + if (message) { + this._errorAttributes.message = message; + } + if (type) { + this._errorAttributes.type = type; } + if (content) { + this._errorContent = content; + } + return this; + } + + /** + * @param {string} stacktrace + * @chainable + */ + stacktrace(stacktrace) { + this._failure = true; + this._stacktrace = stacktrace; + return this; + } + + /** + * @chainable + */ + skipped() { + this._skipped = true; + return this; + } + + /** + * @param {string} log + * @chainable + */ + standardOutput(log) { + this._standardOutput = log; + return this; + } + + /** + * @param {string} log + * @chainable + */ + standardError(log) { + this._standardError = log; + return this; + } + + /** + * @returns {number} + */ + getFailureCount() { + return Number(this._failure); } - if (this._skipped) { - testCaseElement.ele('skipped'); + + /** + * @returns {number} + */ + getErrorCount() { + return Number(this._error); } - if (this._standardOutput) { - testCaseElement.ele('system-out').cdata(this._standardOutput); + + /** + * @returns {number} + */ + getSkippedCount() { + return Number(this._skipped); } - var systemError; - if (this._standardError) { - systemError = testCaseElement.ele('system-err').cdata(this._standardError); - if (this._errorAttachment) { - systemError.txt('[[ATTACHMENT|' + this._errorAttachment + ']]'); + /** + * @param {string} path + * @chainable + */ + errorAttachment(path) { + this._errorAttachment = path; + return this; + } + + /** + * @param {string} name + * @param {string} value + * @chainable + */ + property(name, value) { + this._properties.push({ name: name, value: value }); + return this; + } + + /** + * @param {import('xmlbuilder').XMLElement} parentElement + */ + build(parentElement) { + var testCaseElement = parentElement.ele('testcase', this._attributes); + if (this._properties.length) { + var propertiesElement = testCaseElement.ele('properties'); + _.forEach(this._properties, function (property) { + propertiesElement.ele('property', { + name: property.name, + value: property.value, + }); + }); + } + if (this._failure) { + var failureElement = testCaseElement.ele('failure', this._failureAttributes); + if (this._stacktrace) { + failureElement.cdata(this._stacktrace); + } + } + if (this._error) { + var errorElement = testCaseElement.ele('error', this._errorAttributes); + if (this._errorContent) { + errorElement.cdata(this._errorContent); + } + } + if (this._skipped) { + testCaseElement.ele('skipped'); + } + if (this._standardOutput) { + testCaseElement.ele('system-out').cdata(this._standardOutput); + } + var systemError; + if (this._standardError) { + systemError = testCaseElement.ele('system-err').cdata(this._standardError); + + if (this._errorAttachment) { + systemError.txt('[[ATTACHMENT|' + this._errorAttachment + ']]'); + } } } -}; +} module.exports = TestCase; diff --git a/src/test_suite.js b/src/test_suite.js index ea43f79..1679cf2 100755 --- a/src/test_suite.js +++ b/src/test_suite.js @@ -1,86 +1,128 @@ +// @ts-check var _ = require('lodash'); var formatDate = require('date-format').asString; -function TestSuite(factory) { - this._factory = factory; - this._attributes = {}; - this._testCases = []; - this._properties = []; -} +class TestSuite { + /** + * @param {import('./factory')} factory + */ + constructor(factory) { + this._factory = factory; + this._attributes = {}; + this._testCases = []; + this._properties = []; + } -TestSuite.prototype.name = function (name) { - this._attributes.name = name; - return this; -}; + /** + * @param {string} name + * @chainable + */ + name(name) { + this._attributes.name = name; + return this; + } -TestSuite.prototype.time = function (timeInSeconds) { - this._attributes.time = timeInSeconds; - return this; -}; + /** + * @param {number} timeInSeconds + * @chainable + */ + time(timeInSeconds) { + this._attributes.time = timeInSeconds; + return this; + } -TestSuite.prototype.timestamp = function (timestamp) { - if (_.isDate(timestamp)) { - this._attributes.timestamp = formatDate('yyyy-MM-ddThh:mm:ss', timestamp); - } else { - this._attributes.timestamp = timestamp; + /** + * @param {Date|string} timestamp + * @chainable + */ + timestamp(timestamp) { + if (_.isDate(timestamp)) { + this._attributes.timestamp = formatDate('yyyy-MM-ddThh:mm:ss', timestamp); + } else { + this._attributes.timestamp = timestamp; + } + return this; } - return this; -}; -TestSuite.prototype.property = function (name, value) { - this._properties.push({ name: name, value: value }); - return this; -}; + /** + * @param {string} name + * @param {string} value + * @chainable + */ + property(name, value) { + this._properties.push({ name: name, value: value }); + return this; + } -TestSuite.prototype.testCase = function () { - var testCase = this._factory.newTestCase(); - this._testCases.push(testCase); - return testCase; -}; + /** + * @returns {import('./test_case')} + */ + testCase() { + var testCase = this._factory.newTestCase(); + this._testCases.push(testCase); + return testCase; + } -TestSuite.prototype.getFailureCount = function () { - return this._sumTestCaseCounts(function (testCase) { - return testCase.getFailureCount(); - }); -}; + /** + * @returns {number} + */ + getFailureCount() { + return this._sumTestCaseCounts(function (testCase) { + return testCase.getFailureCount(); + }); + } -TestSuite.prototype.getErrorCount = function () { - return this._sumTestCaseCounts(function (testCase) { - return testCase.getErrorCount(); - }); -}; + /** + * @returns {number} + */ + getErrorCount() { + return this._sumTestCaseCounts(function (testCase) { + return testCase.getErrorCount(); + }); + } -TestSuite.prototype.getSkippedCount = function () { - return this._sumTestCaseCounts(function (testCase) { - return testCase.getSkippedCount(); - }); -}; + /** + * @returns {number} + */ + getSkippedCount() { + return this._sumTestCaseCounts(function (testCase) { + return testCase.getSkippedCount(); + }); + } -TestSuite.prototype._sumTestCaseCounts = function (counterFunction) { - var counts = _.map(this._testCases, counterFunction); - return _.sum(counts); -}; + /** + * @param {(testCase: import('./test_case')) => number} counterFunction + * @returns {number} + */ + _sumTestCaseCounts(counterFunction) { + var counts = _.map(this._testCases, counterFunction); + return _.sum(counts); + } -TestSuite.prototype.build = function (parentElement) { - this._attributes.tests = this._testCases.length; - this._attributes.failures = this.getFailureCount(); - this._attributes.errors = this.getErrorCount(); - this._attributes.skipped = this.getSkippedCount(); - var suiteElement = parentElement.ele('testsuite', this._attributes); + /** + * @param {import('xmlbuilder').XMLElement} parentElement + */ + build(parentElement) { + this._attributes.tests = this._testCases.length; + this._attributes.failures = this.getFailureCount(); + this._attributes.errors = this.getErrorCount(); + this._attributes.skipped = this.getSkippedCount(); + var suiteElement = parentElement.ele('testsuite', this._attributes); - if (this._properties.length) { - var propertiesElement = suiteElement.ele('properties'); - _.forEach(this._properties, function (property) { - propertiesElement.ele('property', { - name: property.name, - value: property.value, + if (this._properties.length) { + var propertiesElement = suiteElement.ele('properties'); + _.forEach(this._properties, function (property) { + propertiesElement.ele('property', { + name: property.name, + value: property.value, + }); }); + } + + _.forEach(this._testCases, function (testCase) { + testCase.build(suiteElement); }); } - - _.forEach(this._testCases, function (testCase) { - testCase.build(suiteElement); - }); -}; +} module.exports = TestSuite; From af8bcaf5bcfc27b6d058a2d7c1d7d27888517394 Mon Sep 17 00:00:00 2001 From: SimeonC <1085899+SimeonC@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:39:31 +0900 Subject: [PATCH 02/17] feat: refactor to support testsuites root element attributes/properties Fixes #8 --- spec/e2e_spec.js | 53 ++++++++++++++++- spec/expected_report.xml | 2 +- spec/test_case_spec.js | 2 +- spec/test_suite_spec.js | 12 +++- src/builder.js | 41 ++++++------- src/factory.js | 24 +++++--- src/index.js | 2 +- src/test_case.js | 95 +++++++++++------------------- src/test_group.js | 90 ++++++++++++++++++++++++++++ src/test_node.js | 124 +++++++++++++++++++++++++++++++++++++++ src/test_suite.js | 124 ++------------------------------------- src/test_suites.js | 35 +++++++++++ 12 files changed, 386 insertions(+), 218 deletions(-) create mode 100755 src/test_group.js create mode 100644 src/test_node.js create mode 100755 src/test_suites.js diff --git a/spec/e2e_spec.js b/spec/e2e_spec.js index cda7d2e..08ae1f7 100644 --- a/spec/e2e_spec.js +++ b/spec/e2e_spec.js @@ -14,8 +14,28 @@ describe('JUnit Report builder', function () { }), ); - const reportWith = (content) => - '\n' + '\n' + content + '\n' + ''; + const parseProperties = (properties = {}) => { + let result = ''; + ['tests', 'failures', 'errors', 'skipped'].forEach((key) => { + result += key + '="' + (properties[key] || 0) + '" '; + }); + for (const key in properties) { + if (['tests', 'failures', 'errors', 'skipped'].includes(key)) { + continue; + } + result += key + '="' + properties[key] + '" '; + } + return result.trim(); + }; + + const reportWith = (content, testSuitesProperties) => + '\n' + + '\n' + + content + + '\n' + + ''; it('should produce a report identical to the expected one', function () { builder.testCase().className('root.test.Class1'); @@ -44,9 +64,18 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( // prettier-ignore '\n' + - '', + '', )); + it('should set testsuites name', () => { + builder.testSuites().name('testSuitesName'); + expect(builder.build()).toBe( + // prettier-ignore + '\n' + + '', + ); + }); + it('should produce an empty test suite when a test suite reported', function () { builder.testSuite(); @@ -54,6 +83,7 @@ describe('JUnit Report builder', function () { reportWith( // prettier-ignore ' ', + { tests: 0, failures: 0, errors: 0, skipped: 0 }, ), ); }); @@ -65,6 +95,7 @@ describe('JUnit Report builder', function () { reportWith( // prettier-ignore ' ', + { tests: 1, failures: 0, errors: 0, skipped: 0 }, ), ); }); @@ -78,6 +109,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 1, failures: 1, errors: 0, skipped: 0 }, ), ); }); @@ -91,6 +123,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 1, failures: 1, errors: 0, skipped: 0 }, ), ); }); @@ -104,6 +137,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 1, failures: 0, errors: 1, skipped: 0 }, ), ); }); @@ -117,6 +151,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 1, failures: 0, errors: 1, skipped: 0 }, ), ); }); @@ -130,6 +165,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 1, failures: 0, errors: 0, skipped: 0 }, ), ); }); @@ -145,6 +181,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 1, failures: 1, errors: 0, skipped: 0 }, ), ); }); @@ -160,6 +197,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 1, failures: 0, errors: 1, skipped: 0 }, ), ); }); @@ -175,6 +213,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 1, failures: 0, errors: 0, skipped: 1 }, ), ); }); @@ -210,6 +249,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 1, failures: 0, errors: 0, skipped: 0 }, ), ); }); @@ -225,6 +265,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 1, failures: 0, errors: 0, skipped: 0 }, ), ); }); @@ -240,6 +281,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 1, failures: 0, errors: 0, skipped: 0 }, ), ); }); @@ -262,6 +304,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 1, failures: 0, errors: 0, skipped: 0 }, ), ); }); @@ -277,6 +320,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 2, failures: 0, errors: 0, skipped: 0 }, ), ); }); @@ -290,6 +334,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 1, failures: 0, errors: 0, skipped: 0 }, ), ); }); @@ -303,6 +348,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 1, failures: 0, errors: 1, skipped: 0 }, ), ); }); @@ -316,6 +362,7 @@ describe('JUnit Report builder', function () { ' \n' + ' \n' + ' ', + { tests: 1, failures: 0, errors: 1, skipped: 0 }, ), ); }); diff --git a/spec/expected_report.xml b/spec/expected_report.xml index 5ef522d..d0ffe04 100755 --- a/spec/expected_report.xml +++ b/spec/expected_report.xml @@ -1,5 +1,5 @@ - + diff --git a/spec/test_case_spec.js b/spec/test_case_spec.js index a3ad455..d81e15d 100644 --- a/spec/test_case_spec.js +++ b/spec/test_case_spec.js @@ -1,4 +1,4 @@ -const TestCase = require('../src/test_case'); +const { TestCase } = require('../src/test_case'); describe('Test Case builder', function () { let testCase = null; diff --git a/spec/test_suite_spec.js b/spec/test_suite_spec.js index dc5fe69..bca4d4e 100644 --- a/spec/test_suite_spec.js +++ b/spec/test_suite_spec.js @@ -1,4 +1,4 @@ -const TestSuite = require('../src/test_suite'); +const { TestSuite } = require('../src/test_suite'); describe('Test Suite builder', function () { let testSuite = null; @@ -9,7 +9,13 @@ describe('Test Suite builder', function () { beforeEach(function () { const factory = jasmine.createSpyObj('factory', ['newTestCase']); - testCase = jasmine.createSpyObj('testCase', ['build', 'getFailureCount', 'getErrorCount', 'getSkippedCount']); + testCase = jasmine.createSpyObj('testCase', [ + 'build', + 'getFailureCount', + 'getErrorCount', + 'getSkippedCount', + 'getTestCaseCount', + ]); factory.newTestCase.and.callFake(() => testCase); @@ -33,6 +39,8 @@ describe('Test Suite builder', function () { } }); + testCase.getTestCaseCount.and.callFake(() => 1); + testCase.getFailureCount.and.callFake(() => 0); testCase.getErrorCount.and.callFake(() => 0); diff --git a/src/builder.js b/src/builder.js index ac6f968..c4e68b3 100644 --- a/src/builder.js +++ b/src/builder.js @@ -1,19 +1,16 @@ // @ts-check -var _ = require('lodash'); -var xmlBuilder = require('xmlbuilder'); var path = require('path'); var makeDir = require('make-dir'); var fs = require('fs'); -var TestSuite = require('./test_suite'); -var TestCase = require('./test_case'); +var { TestSuites } = require('./test_suites'); class JUnitReportBuilder { /** - * @param {import('./factory')} factory + * @param {import('./factory').Factory} factory */ constructor(factory) { this._factory = factory; - this._testSuitesAndCases = []; + this._rootTestSuites = new TestSuites(factory); } /** @@ -25,40 +22,40 @@ class JUnitReportBuilder { } /** - * @returns {string} xml file content + * @returns {string} */ build() { - var xmlTree = xmlBuilder.create('testsuites', { encoding: 'UTF-8', invalidCharReplacement: '' }); - _.forEach(this._testSuitesAndCases, function (suiteOrCase) { - suiteOrCase.build(xmlTree); - }); + var xmlTree = this._rootTestSuites.build(); return xmlTree.end({ pretty: true }); } /** - * @returns {import('./test_suite')} + * @returns {import('./test_suites').TestSuites} + */ + testSuites() { + return this._rootTestSuites; + } + + /** + * @returns {import('./test_suite').TestSuite} */ testSuite() { - var suite = this._factory.newTestSuite(); - this._testSuitesAndCases.push(suite); - return suite; + return this._rootTestSuites.testSuite(); } /** - * @returns {import('./test_case')} + * @returns {import('./test_case').TestCase} */ testCase() { - var testCase = this._factory.newTestCase(); - this._testSuitesAndCases.push(testCase); - return testCase; + return this._rootTestSuites.testCase(); } /** - * @returns {ReturnType} + * @returns {JUnitReportBuilder} */ newBuilder() { - return this._factory.newBuilder(); + return new JUnitReportBuilder(this._factory); } } -module.exports = JUnitReportBuilder; +module.exports = { Builder: JUnitReportBuilder }; diff --git a/src/factory.js b/src/factory.js index 8a71d1d..d17d36e 100644 --- a/src/factory.js +++ b/src/factory.js @@ -1,30 +1,36 @@ -var Builder = require('./builder'); -var TestSuite = require('./test_suite'); -var TestCase = require('./test_case'); +var { Builder } = require('./builder'); +var { TestSuites } = require('./test_suites'); +var { TestSuite } = require('./test_suite'); +var { TestCase } = require('./test_case'); class Factory { - constructor() {} - /** - * @returns {Builder} + * @returns {import('./builder').Builder} */ newBuilder() { return new Builder(this); } /** - * @returns {TestSuite} + * @returns {import('./test_suite').TestSuite} */ newTestSuite() { return new TestSuite(this); } /** - * @returns {TestCase} + * @returns {import('./test_case').TestCase} */ newTestCase() { return new TestCase(this); } + + /** + * @returns {import('./test_suites').TestSuites} + */ + newTestSuites() { + return new TestSuites(this); + } } -module.exports = Factory; +module.exports = { Factory: Factory }; diff --git a/src/index.js b/src/index.js index af0080b..efb1484 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,3 @@ -var Factory = require('./factory'); +var { Factory } = require('./factory'); module.exports = new Factory().newBuilder(); diff --git a/src/test_case.js b/src/test_case.js index b08c99d..82bef56 100755 --- a/src/test_case.js +++ b/src/test_case.js @@ -1,50 +1,28 @@ // @ts-check var _ = require('lodash'); -class TestCase { - constructor() { +var { TestNode } = require('./test_node'); + +class TestCase extends TestNode { + /** + * @param {import('./factory').Factory} factory + */ + constructor(factory) { + super(factory, 'testcase'); this._error = false; this._failure = false; this._skipped = false; this._standardOutput = undefined; this._standardError = undefined; this._stacktrace = undefined; - this._attributes = {}; this._errorAttributes = {}; this._failureAttributes = {}; this._errorAttachment = undefined; this._errorContent = undefined; - this._properties = []; - } - - /** - * @param {string} className - * @chainable - */ - className(className) { - this._attributes.classname = className; - return this; - } - - /** - * @param {string} name - * @chainable - */ - name(name) { - this._attributes.name = name; - return this; - } - - /** - * @param {number} timeInSeconds - * @chainable - */ - time(timeInSeconds) { - this._attributes.time = timeInSeconds; - return this; } /** * @param {string} filepath + * @returns {TestCase} * @chainable */ file(filepath) { @@ -53,8 +31,9 @@ class TestCase { } /** - * @param {string} message - * @param {string} type + * @param {string} [message] + * @param {string} [type] + * @returns {TestCase} * @chainable */ failure(message, type) { @@ -69,9 +48,10 @@ class TestCase { } /** - * @param {string} message - * @param {string} type - * @param {string} content + * @param {string} [message] + * @param {string} [type] + * @param {string} [content] + * @returns {TestCase} * @chainable */ error(message, type, content) { @@ -89,7 +69,8 @@ class TestCase { } /** - * @param {string} stacktrace + * @param {string} [stacktrace] + * @returns {TestCase} * @chainable */ stacktrace(stacktrace) { @@ -99,6 +80,7 @@ class TestCase { } /** + * @returns {TestCase} * @chainable */ skipped() { @@ -107,7 +89,8 @@ class TestCase { } /** - * @param {string} log + * @param {string} [log] + * @returns {TestCase} * @chainable */ standardOutput(log) { @@ -116,7 +99,8 @@ class TestCase { } /** - * @param {string} log + * @param {string} [log] + * @returns {TestCase} * @chainable */ standardError(log) { @@ -124,6 +108,13 @@ class TestCase { return this; } + /** + * @returns {number} + */ + getTestCaseCount() { + return 1; + } + /** * @returns {number} */ @@ -146,7 +137,9 @@ class TestCase { } /** + * * @param {string} path + * @returns {TestCase} * @chainable */ errorAttachment(path) { @@ -154,30 +147,11 @@ class TestCase { return this; } - /** - * @param {string} name - * @param {string} value - * @chainable - */ - property(name, value) { - this._properties.push({ name: name, value: value }); - return this; - } - /** * @param {import('xmlbuilder').XMLElement} parentElement */ build(parentElement) { - var testCaseElement = parentElement.ele('testcase', this._attributes); - if (this._properties.length) { - var propertiesElement = testCaseElement.ele('properties'); - _.forEach(this._properties, function (property) { - propertiesElement.ele('property', { - name: property.name, - value: property.value, - }); - }); - } + const testCaseElement = this.buildNode(this.createNode(parentElement)); if (this._failure) { var failureElement = testCaseElement.ele('failure', this._failureAttributes); if (this._stacktrace) { @@ -204,7 +178,8 @@ class TestCase { systemError.txt('[[ATTACHMENT|' + this._errorAttachment + ']]'); } } + return testCaseElement; } } -module.exports = TestCase; +module.exports = { TestCase: TestCase }; diff --git a/src/test_group.js b/src/test_group.js new file mode 100755 index 0000000..77aed49 --- /dev/null +++ b/src/test_group.js @@ -0,0 +1,90 @@ +// @ts-check +var _ = require('lodash'); +var formatDate = require('date-format').asString; +var { TestNode } = require('./test_node'); + +class TestGroup extends TestNode { + /** + * @param {string|Date} timestamp + * @returns {TestGroup} + * @chainable + */ + timestamp(timestamp) { + if (_.isDate(timestamp)) { + this._attributes.timestamp = formatDate('yyyy-MM-ddThh:mm:ss', timestamp); + } else { + this._attributes.timestamp = timestamp; + } + return this; + } + + /** + * @returns {number} + */ + getTestCaseCount() { + return this._sumTestCaseCounts(function (testCase) { + return testCase.getTestCaseCount(); + }); + } + + /** + * @returns {number} + */ + getFailureCount() { + return this._sumTestCaseCounts(function (testCase) { + return testCase.getFailureCount(); + }); + } + + /** + * @returns {number} + */ + getErrorCount() { + return this._sumTestCaseCounts(function (testCase) { + return testCase.getErrorCount(); + }); + } + + /** + * @returns {number} + */ + getSkippedCount() { + return this._sumTestCaseCounts(function (testCase) { + return testCase.getSkippedCount(); + }); + } + + /** + * @protected + * @param {Function} counterFunction + * @returns + */ + _sumTestCaseCounts(counterFunction) { + var counts = _.map(this._children, counterFunction); + // console.log('[debug]', counts, this._children); + return _.sum(counts); + } + + /** + * @returns {import('./test_case').TestCase} + */ + testCase() { + var testCase = this._factory.newTestCase(); + this._children.push(testCase); + return testCase; + } + + /** + * @param {import('xmlbuilder').XMLElement} parentElement + * @returns {import('xmlbuilder').XMLElement} + */ + build(parentElement) { + this._attributes.tests = this.getTestCaseCount(); + this._attributes.failures = this.getFailureCount(); + this._attributes.errors = this.getErrorCount(); + this._attributes.skipped = this.getSkippedCount(); + return super.build(parentElement); + } +} + +module.exports = { TestGroup: TestGroup }; diff --git a/src/test_node.js b/src/test_node.js new file mode 100644 index 0000000..4fd53cf --- /dev/null +++ b/src/test_node.js @@ -0,0 +1,124 @@ +// @ts-check +var _ = require('lodash'); + +class TestNode { + /** + * @param {import('./factory').Factory} factory + * @param {string} nodeName + */ + constructor(factory, nodeName) { + this._factory = factory; + this._nodeName = nodeName; + this._attributes = {}; + this._properties = []; + this._children = []; + } + + /** + * @param {string} name + * @param {string} value + * @returns {TestNode} + * @chainable + */ + property(name, value) { + this._properties.push({ name: name, value: value }); + return this; + } + + /** + * @param {string} className + * @returns {TestNode} + * @chainable + */ + className(className) { + this._attributes.classname = className; + return this; + } + + /** + * @param {string} name + * @returns {TestNode} + * @chainable + */ + name(name) { + this._attributes.name = name; + return this; + } + + /** + * @param {string} timeInSeconds + * @returns {TestNode} + * @chainable + */ + time(timeInSeconds) { + this._attributes.time = timeInSeconds; + return this; + } + + /** + * @protected + * @param {import('xmlbuilder').XMLElement} parentElement + * @returns {import('xmlbuilder').XMLElement} + */ + createNode(parentElement) { + return parentElement.ele(this._nodeName, this._attributes); + } + + /** + * @protected + * @param {import('xmlbuilder').XMLElement} element + * @returns {import('xmlbuilder').XMLElement} + */ + buildNode(element) { + if (this._properties.length) { + var propertiesElement = element.ele('properties'); + _.forEach(this._properties, function (property) { + propertiesElement.ele('property', { + name: property.name, + value: property.value, + }); + }); + } + _.forEach(this._children, function (child) { + child.build(element); + }); + return element; + } + + /** + * @returns {number} + */ + getTestCaseCount() { + throw new Error('Not implemented'); + } + + /** + * @returns {number} + */ + getFailureCount() { + return 0; + } + + /** + * @returns {number} + */ + getErrorCount() { + return 0; + } + + /** + * @returns {number} + */ + getSkippedCount() { + return 0; + } + + /** + * @param {import('xmlbuilder').XMLElement} parentElement + */ + build(parentElement) { + return this.buildNode(this.createNode(parentElement)); + } +} + +module.exports = { TestNode: TestNode }; diff --git a/src/test_suite.js b/src/test_suite.js index 1679cf2..91e343b 100755 --- a/src/test_suite.js +++ b/src/test_suite.js @@ -1,128 +1,14 @@ // @ts-check var _ = require('lodash'); -var formatDate = require('date-format').asString; +var { TestGroup } = require('./test_group'); -class TestSuite { +class TestSuite extends TestGroup { /** - * @param {import('./factory')} factory + * @param {import('./factory').Factory} factory */ constructor(factory) { - this._factory = factory; - this._attributes = {}; - this._testCases = []; - this._properties = []; - } - - /** - * @param {string} name - * @chainable - */ - name(name) { - this._attributes.name = name; - return this; - } - - /** - * @param {number} timeInSeconds - * @chainable - */ - time(timeInSeconds) { - this._attributes.time = timeInSeconds; - return this; - } - - /** - * @param {Date|string} timestamp - * @chainable - */ - timestamp(timestamp) { - if (_.isDate(timestamp)) { - this._attributes.timestamp = formatDate('yyyy-MM-ddThh:mm:ss', timestamp); - } else { - this._attributes.timestamp = timestamp; - } - return this; - } - - /** - * @param {string} name - * @param {string} value - * @chainable - */ - property(name, value) { - this._properties.push({ name: name, value: value }); - return this; - } - - /** - * @returns {import('./test_case')} - */ - testCase() { - var testCase = this._factory.newTestCase(); - this._testCases.push(testCase); - return testCase; - } - - /** - * @returns {number} - */ - getFailureCount() { - return this._sumTestCaseCounts(function (testCase) { - return testCase.getFailureCount(); - }); - } - - /** - * @returns {number} - */ - getErrorCount() { - return this._sumTestCaseCounts(function (testCase) { - return testCase.getErrorCount(); - }); - } - - /** - * @returns {number} - */ - getSkippedCount() { - return this._sumTestCaseCounts(function (testCase) { - return testCase.getSkippedCount(); - }); - } - - /** - * @param {(testCase: import('./test_case')) => number} counterFunction - * @returns {number} - */ - _sumTestCaseCounts(counterFunction) { - var counts = _.map(this._testCases, counterFunction); - return _.sum(counts); - } - - /** - * @param {import('xmlbuilder').XMLElement} parentElement - */ - build(parentElement) { - this._attributes.tests = this._testCases.length; - this._attributes.failures = this.getFailureCount(); - this._attributes.errors = this.getErrorCount(); - this._attributes.skipped = this.getSkippedCount(); - var suiteElement = parentElement.ele('testsuite', this._attributes); - - if (this._properties.length) { - var propertiesElement = suiteElement.ele('properties'); - _.forEach(this._properties, function (property) { - propertiesElement.ele('property', { - name: property.name, - value: property.value, - }); - }); - } - - _.forEach(this._testCases, function (testCase) { - testCase.build(suiteElement); - }); + super(factory, 'testsuite'); } } -module.exports = TestSuite; +module.exports = { TestSuite: TestSuite }; diff --git a/src/test_suites.js b/src/test_suites.js new file mode 100755 index 0000000..2c70e82 --- /dev/null +++ b/src/test_suites.js @@ -0,0 +1,35 @@ +// @ts-check +var xmlBuilder = require('xmlbuilder'); +var { TestGroup } = require('./test_group'); + +class TestSuites extends TestGroup { + /** + * @param {import('./factory').Factory} factory + */ + constructor(factory) { + super(factory, 'testsuites'); + } + + /** + * @returns {import('./test_suite').TestSuite} + */ + testSuite() { + var suite = this._factory.newTestSuite(); + this._children.push(suite); + return suite; + } + + /** + * @protected + * @returns {import('xmlbuilder').XMLElement} + */ + createNode() { + const node = xmlBuilder.create('testsuites', { encoding: 'UTF-8', invalidCharReplacement: '' }); + Object.keys(this._attributes).forEach((key) => { + node.att(key, this._attributes[key]); + }); + return node; + } +} + +module.exports = { TestSuites: TestSuites }; From 62793ca612c4a2ff4c8c21d442f16b2b03b0386c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A4rsson?= Date: Thu, 1 Feb 2024 11:06:28 +0100 Subject: [PATCH 03/17] chore: Remove code comment --- src/test_group.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test_group.js b/src/test_group.js index 77aed49..add6c10 100755 --- a/src/test_group.js +++ b/src/test_group.js @@ -61,7 +61,6 @@ class TestGroup extends TestNode { */ _sumTestCaseCounts(counterFunction) { var counts = _.map(this._children, counterFunction); - // console.log('[debug]', counts, this._children); return _.sum(counts); } From ea27a995b698b098c07c4e2e1c46a402fcc37598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A4rsson?= Date: Thu, 1 Feb 2024 11:08:23 +0100 Subject: [PATCH 04/17] refactor: Remove unused default values These should always be overloaded. --- src/test_node.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test_node.js b/src/test_node.js index 4fd53cf..09747ca 100644 --- a/src/test_node.js +++ b/src/test_node.js @@ -96,21 +96,21 @@ class TestNode { * @returns {number} */ getFailureCount() { - return 0; + throw new Error('Not implemented'); } /** * @returns {number} */ getErrorCount() { - return 0; + throw new Error('Not implemented'); } /** * @returns {number} */ getSkippedCount() { - return 0; + throw new Error('Not implemented'); } /** From ac308faed6042e9f3499f4f4bf16156ff3fbc172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A4rsson?= Date: Thu, 1 Feb 2024 11:27:00 +0100 Subject: [PATCH 05/17] refactor: Move child element list to `TestGroup` --- src/test_group.js | 22 ++++++++++++++++++++++ src/test_node.js | 4 ---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/test_group.js b/src/test_group.js index add6c10..db61c31 100755 --- a/src/test_group.js +++ b/src/test_group.js @@ -4,6 +4,15 @@ var formatDate = require('date-format').asString; var { TestNode } = require('./test_node'); class TestGroup extends TestNode { + /** + * @param {import('./factory').Factory} factory + * @param {string} nodeName + */ + constructor(factory, nodeName) { + super(factory, nodeName); + this._children = []; + } + /** * @param {string|Date} timestamp * @returns {TestGroup} @@ -84,6 +93,19 @@ class TestGroup extends TestNode { this._attributes.skipped = this.getSkippedCount(); return super.build(parentElement); } + + /** + * @protected + * @param {import('xmlbuilder').XMLElement} element + * @returns {import('xmlbuilder').XMLElement} + */ + buildNode(element) { + element = super.buildNode(element); + _.forEach(this._children, function (child) { + child.build(element); + }); + return element; + } } module.exports = { TestGroup: TestGroup }; diff --git a/src/test_node.js b/src/test_node.js index 09747ca..a6961b9 100644 --- a/src/test_node.js +++ b/src/test_node.js @@ -11,7 +11,6 @@ class TestNode { this._nodeName = nodeName; this._attributes = {}; this._properties = []; - this._children = []; } /** @@ -79,9 +78,6 @@ class TestNode { }); }); } - _.forEach(this._children, function (child) { - child.build(element); - }); return element; } From 5ebe11df612d8ef67507c3f58c1bdb1e7988c61a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A4rsson?= Date: Thu, 1 Feb 2024 11:30:15 +0100 Subject: [PATCH 06/17] Move `className()` back to `TestCase` According to a best effort spec, it only exists here. See https://github.com/testmoapp/junitxml. --- src/test_case.js | 10 ++++++++++ src/test_node.js | 10 ---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/test_case.js b/src/test_case.js index 82bef56..0dc0a42 100755 --- a/src/test_case.js +++ b/src/test_case.js @@ -20,6 +20,16 @@ class TestCase extends TestNode { this._errorContent = undefined; } + /** + * @param {string} className + * @returns {TestCase} + * @chainable + */ + className(className) { + this._attributes.classname = className; + return this; + } + /** * @param {string} filepath * @returns {TestCase} diff --git a/src/test_node.js b/src/test_node.js index a6961b9..c242c3b 100644 --- a/src/test_node.js +++ b/src/test_node.js @@ -24,16 +24,6 @@ class TestNode { return this; } - /** - * @param {string} className - * @returns {TestNode} - * @chainable - */ - className(className) { - this._attributes.classname = className; - return this; - } - /** * @param {string} name * @returns {TestNode} From c405574e00c2d2bb4ff154ce253693ebad85183b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A4rsson?= Date: Thu, 1 Feb 2024 11:44:56 +0100 Subject: [PATCH 07/17] refactor: Rename parameter for consistency --- src/test_group.js | 6 +++--- src/test_node.js | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test_group.js b/src/test_group.js index db61c31..fa86153 100755 --- a/src/test_group.js +++ b/src/test_group.js @@ -6,10 +6,10 @@ var { TestNode } = require('./test_node'); class TestGroup extends TestNode { /** * @param {import('./factory').Factory} factory - * @param {string} nodeName + * @param {string} elementName */ - constructor(factory, nodeName) { - super(factory, nodeName); + constructor(factory, elementName) { + super(factory, elementName); this._children = []; } diff --git a/src/test_node.js b/src/test_node.js index c242c3b..93889a5 100644 --- a/src/test_node.js +++ b/src/test_node.js @@ -4,11 +4,11 @@ var _ = require('lodash'); class TestNode { /** * @param {import('./factory').Factory} factory - * @param {string} nodeName + * @param {string} elementName */ - constructor(factory, nodeName) { + constructor(factory, elementName) { this._factory = factory; - this._nodeName = nodeName; + this._elementName = elementName; this._attributes = {}; this._properties = []; } @@ -50,7 +50,7 @@ class TestNode { * @returns {import('xmlbuilder').XMLElement} */ createNode(parentElement) { - return parentElement.ele(this._nodeName, this._attributes); + return parentElement.ele(this._elementName, this._attributes); } /** From 19ac2bf5f10a1138bcb80c0f54a0fb62518992de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A4rsson?= Date: Thu, 1 Feb 2024 11:47:15 +0100 Subject: [PATCH 08/17] refactor: Rename method --- src/test_case.js | 2 +- src/test_node.js | 4 ++-- src/test_suites.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test_case.js b/src/test_case.js index 0dc0a42..666f3be 100755 --- a/src/test_case.js +++ b/src/test_case.js @@ -161,7 +161,7 @@ class TestCase extends TestNode { * @param {import('xmlbuilder').XMLElement} parentElement */ build(parentElement) { - const testCaseElement = this.buildNode(this.createNode(parentElement)); + const testCaseElement = this.buildNode(this.createElement(parentElement)); if (this._failure) { var failureElement = testCaseElement.ele('failure', this._failureAttributes); if (this._stacktrace) { diff --git a/src/test_node.js b/src/test_node.js index 93889a5..d7bb007 100644 --- a/src/test_node.js +++ b/src/test_node.js @@ -49,7 +49,7 @@ class TestNode { * @param {import('xmlbuilder').XMLElement} parentElement * @returns {import('xmlbuilder').XMLElement} */ - createNode(parentElement) { + createElement(parentElement) { return parentElement.ele(this._elementName, this._attributes); } @@ -103,7 +103,7 @@ class TestNode { * @param {import('xmlbuilder').XMLElement} parentElement */ build(parentElement) { - return this.buildNode(this.createNode(parentElement)); + return this.buildNode(this.createElement(parentElement)); } } diff --git a/src/test_suites.js b/src/test_suites.js index 2c70e82..f667c72 100755 --- a/src/test_suites.js +++ b/src/test_suites.js @@ -23,7 +23,7 @@ class TestSuites extends TestGroup { * @protected * @returns {import('xmlbuilder').XMLElement} */ - createNode() { + createElement() { const node = xmlBuilder.create('testsuites', { encoding: 'UTF-8', invalidCharReplacement: '' }); Object.keys(this._attributes).forEach((key) => { node.att(key, this._attributes[key]); From 934d355c3db2b33fa6484e4aaf3ac2c7596e9918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A4rsson?= Date: Thu, 1 Feb 2024 11:48:28 +0100 Subject: [PATCH 09/17] refactor: Use the factory --- src/builder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builder.js b/src/builder.js index c4e68b3..2f56d1a 100644 --- a/src/builder.js +++ b/src/builder.js @@ -54,7 +54,7 @@ class JUnitReportBuilder { * @returns {JUnitReportBuilder} */ newBuilder() { - return new JUnitReportBuilder(this._factory); + return this._factory.newBuilder(); } } From d13908aed8c30bed6ee101e5d3a73c4681e84ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A4rsson?= Date: Thu, 1 Feb 2024 13:15:21 +0100 Subject: [PATCH 10/17] style: Move important public method to the top --- src/test_group.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test_group.js b/src/test_group.js index fa86153..ae6f20b 100755 --- a/src/test_group.js +++ b/src/test_group.js @@ -27,6 +27,15 @@ class TestGroup extends TestNode { return this; } + /** + * @returns {import('./test_case').TestCase} + */ + testCase() { + var testCase = this._factory.newTestCase(); + this._children.push(testCase); + return testCase; + } + /** * @returns {number} */ @@ -73,15 +82,6 @@ class TestGroup extends TestNode { return _.sum(counts); } - /** - * @returns {import('./test_case').TestCase} - */ - testCase() { - var testCase = this._factory.newTestCase(); - this._children.push(testCase); - return testCase; - } - /** * @param {import('xmlbuilder').XMLElement} parentElement * @returns {import('xmlbuilder').XMLElement} From ada9e92fee6219445141a981d067ec324ac75d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A4rsson?= Date: Thu, 1 Feb 2024 13:21:06 +0100 Subject: [PATCH 11/17] Set the name of `` from the builder --- spec/e2e_spec.js | 2 +- src/builder.js | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/spec/e2e_spec.js b/spec/e2e_spec.js index 08ae1f7..f2443e1 100644 --- a/spec/e2e_spec.js +++ b/spec/e2e_spec.js @@ -68,7 +68,7 @@ describe('JUnit Report builder', function () { )); it('should set testsuites name', () => { - builder.testSuites().name('testSuitesName'); + builder.name('testSuitesName'); expect(builder.build()).toBe( // prettier-ignore '\n' + diff --git a/src/builder.js b/src/builder.js index 2f56d1a..f08b8fe 100644 --- a/src/builder.js +++ b/src/builder.js @@ -30,10 +30,13 @@ class JUnitReportBuilder { } /** - * @returns {import('./test_suites').TestSuites} + * @param {string} name + * @returns {JUnitReportBuilder} + * @chainable */ - testSuites() { - return this._rootTestSuites; + name(name) { + this._rootTestSuites.name(name); + return this; } /** From 21613616a6b80dd4b5eb612f674105c40e280035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A4rsson?= Date: Thu, 1 Feb 2024 13:21:40 +0100 Subject: [PATCH 12/17] style: Use a real function body for the test --- spec/e2e_spec.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/e2e_spec.js b/spec/e2e_spec.js index f2443e1..deda9cd 100644 --- a/spec/e2e_spec.js +++ b/spec/e2e_spec.js @@ -60,12 +60,13 @@ describe('JUnit Report builder', function () { expect(actual).toBe(expected); }); - it('should produce an empty list of test suites when nothing reported', () => + it('should produce an empty list of test suites when nothing reported', () => { expect(builder.build()).toBe( // prettier-ignore '\n' + '', - )); + ); + }); it('should set testsuites name', () => { builder.name('testSuitesName'); From a84446bc6d35bfec446d781175823848dd34d38f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A4rsson?= Date: Thu, 1 Feb 2024 13:43:05 +0100 Subject: [PATCH 13/17] refactor: More XML in e2e tests --- spec/e2e_spec.js | 107 +++++++++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 46 deletions(-) diff --git a/spec/e2e_spec.js b/spec/e2e_spec.js index deda9cd..b184f90 100644 --- a/spec/e2e_spec.js +++ b/spec/e2e_spec.js @@ -28,14 +28,7 @@ describe('JUnit Report builder', function () { return result.trim(); }; - const reportWith = (content, testSuitesProperties) => - '\n' + - '\n' + - content + - '\n' + - ''; + const reportWith = (content, testSuitesProperties) => '\n' + content; it('should produce a report identical to the expected one', function () { builder.testCase().className('root.test.Class1'); @@ -83,8 +76,9 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore - ' ', - { tests: 0, failures: 0, errors: 0, skipped: 0 }, + '\n' + + ' \n' + + '', ), ); }); @@ -95,8 +89,9 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore - ' ', - { tests: 1, failures: 0, errors: 0, skipped: 0 }, + '\n' + + ' \n' + + '', ), ); }); @@ -107,10 +102,11 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + - ' ', - { tests: 1, failures: 1, errors: 0, skipped: 0 }, + ' \n' + + '', ), ); }); @@ -121,10 +117,11 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + - ' ', - { tests: 1, failures: 1, errors: 0, skipped: 0 }, + ' \n' + + '', ), ); }); @@ -135,10 +132,11 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + - ' ', - { tests: 1, failures: 0, errors: 1, skipped: 0 }, + ' \n' + + '', ), ); }); @@ -149,10 +147,11 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + - ' ', - { tests: 1, failures: 0, errors: 1, skipped: 0 }, + ' \n' + + '', ), ); }); @@ -163,10 +162,11 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + - ' ', - { tests: 1, failures: 0, errors: 0, skipped: 0 }, + ' \n' + + '', ), ); }); @@ -177,12 +177,13 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + ' \n' + ' \n' + - ' ', - { tests: 1, failures: 1, errors: 0, skipped: 0 }, + ' \n' + + '', ), ); }); @@ -193,12 +194,13 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + ' \n' + ' \n' + - ' ', - { tests: 1, failures: 0, errors: 1, skipped: 0 }, + ' \n' + + '', ), ); }); @@ -209,12 +211,13 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + ' \n' + ' \n' + - ' ', - { tests: 1, failures: 0, errors: 0, skipped: 1 }, + ' \n' + + '', ), ); }); @@ -225,7 +228,9 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore - ' ', + '\n' + + ' \n' + + '', ), ); }); @@ -236,7 +241,9 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore - ' ', + '\n' + + ' \n' + + '', ), ); }); @@ -247,10 +254,11 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + - ' ', - { tests: 1, failures: 0, errors: 0, skipped: 0 }, + ' \n' + + '', ), ); }); @@ -261,12 +269,13 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + ' \n' + ' \n' + - ' ', - { tests: 1, failures: 0, errors: 0, skipped: 0 }, + ' \n' + + '', ), ); }); @@ -277,12 +286,13 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + ' \n' + ' \n' + - ' ', - { tests: 1, failures: 0, errors: 0, skipped: 0 }, + ' \n' + + '', ), ); }); @@ -297,6 +307,7 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + ' \n' + @@ -304,8 +315,8 @@ describe('JUnit Report builder', function () { ' [[ATTACHMENT|absolute/path/to/attachment]]\n' + ' \n' + ' \n' + - ' ', - { tests: 1, failures: 0, errors: 0, skipped: 0 }, + ' \n' + + '', ), ); }); @@ -318,10 +329,11 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + - ' ', - { tests: 2, failures: 0, errors: 0, skipped: 0 }, + ' \n' + + '', ), ); }); @@ -332,10 +344,11 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + - ' ', - { tests: 1, failures: 0, errors: 0, skipped: 0 }, + ' \n' + + '', ), ); }); @@ -346,10 +359,11 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + - ' ', - { tests: 1, failures: 0, errors: 1, skipped: 0 }, + ' \n' + + '', ), ); }); @@ -360,10 +374,11 @@ describe('JUnit Report builder', function () { expect(builder.build()).toBe( reportWith( // prettier-ignore + '\n' + ' \n' + ' \n' + - ' ', - { tests: 1, failures: 0, errors: 1, skipped: 0 }, + ' \n' + + '', ), ); }); From 2be8b1b63c7f8407d7eea0c16d9ee2acfc7c5745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A4rsson?= Date: Thu, 1 Feb 2024 13:56:24 +0100 Subject: [PATCH 14/17] refactor: Create root element in `TestNode` To fix type warning for override with changed signature. --- src/test_group.js | 2 +- src/test_node.js | 22 +++++++++++++++++++--- src/test_suites.js | 13 ------------- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/test_group.js b/src/test_group.js index ae6f20b..b4cbd9e 100755 --- a/src/test_group.js +++ b/src/test_group.js @@ -83,7 +83,7 @@ class TestGroup extends TestNode { } /** - * @param {import('xmlbuilder').XMLElement} parentElement + * @param {import('xmlbuilder').XMLElement} [parentElement] * @returns {import('xmlbuilder').XMLElement} */ build(parentElement) { diff --git a/src/test_node.js b/src/test_node.js index d7bb007..a686ca2 100644 --- a/src/test_node.js +++ b/src/test_node.js @@ -1,5 +1,6 @@ // @ts-check var _ = require('lodash'); +var xmlBuilder = require('xmlbuilder'); class TestNode { /** @@ -46,11 +47,26 @@ class TestNode { /** * @protected - * @param {import('xmlbuilder').XMLElement} parentElement + * @param {import('xmlbuilder').XMLElement} [parentElement] * @returns {import('xmlbuilder').XMLElement} */ createElement(parentElement) { - return parentElement.ele(this._elementName, this._attributes); + if (parentElement) { + return parentElement.ele(this._elementName, this._attributes); + } + return this.createRootElement(); + } + + /** + * @private + * @returns {import('xmlbuilder').XMLElement} + */ + createRootElement() { + const element = xmlBuilder.create(this._elementName, { encoding: 'UTF-8', invalidCharReplacement: '' }); + Object.keys(this._attributes).forEach((key) => { + element.att(key, this._attributes[key]); + }); + return element; } /** @@ -100,7 +116,7 @@ class TestNode { } /** - * @param {import('xmlbuilder').XMLElement} parentElement + * @param {import('xmlbuilder').XMLElement} [parentElement] */ build(parentElement) { return this.buildNode(this.createElement(parentElement)); diff --git a/src/test_suites.js b/src/test_suites.js index f667c72..a32fba1 100755 --- a/src/test_suites.js +++ b/src/test_suites.js @@ -1,5 +1,4 @@ // @ts-check -var xmlBuilder = require('xmlbuilder'); var { TestGroup } = require('./test_group'); class TestSuites extends TestGroup { @@ -18,18 +17,6 @@ class TestSuites extends TestGroup { this._children.push(suite); return suite; } - - /** - * @protected - * @returns {import('xmlbuilder').XMLElement} - */ - createElement() { - const node = xmlBuilder.create('testsuites', { encoding: 'UTF-8', invalidCharReplacement: '' }); - Object.keys(this._attributes).forEach((key) => { - node.att(key, this._attributes[key]); - }); - return node; - } } module.exports = { TestSuites: TestSuites }; From ebd06f448d04ee0c6c57e4cb6742d0d1f8f108f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A4rsson?= Date: Thu, 1 Feb 2024 13:57:37 +0100 Subject: [PATCH 15/17] style: Public methods at the top --- src/test_node.js | 70 ++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/src/test_node.js b/src/test_node.js index a686ca2..7f3ba7d 100644 --- a/src/test_node.js +++ b/src/test_node.js @@ -45,6 +45,41 @@ class TestNode { return this; } + /** + * @returns {number} + */ + getTestCaseCount() { + throw new Error('Not implemented'); + } + + /** + * @returns {number} + */ + getFailureCount() { + throw new Error('Not implemented'); + } + + /** + * @returns {number} + */ + getErrorCount() { + throw new Error('Not implemented'); + } + + /** + * @returns {number} + */ + getSkippedCount() { + throw new Error('Not implemented'); + } + + /** + * @param {import('xmlbuilder').XMLElement} [parentElement] + */ + build(parentElement) { + return this.buildNode(this.createElement(parentElement)); + } + /** * @protected * @param {import('xmlbuilder').XMLElement} [parentElement] @@ -86,41 +121,6 @@ class TestNode { } return element; } - - /** - * @returns {number} - */ - getTestCaseCount() { - throw new Error('Not implemented'); - } - - /** - * @returns {number} - */ - getFailureCount() { - throw new Error('Not implemented'); - } - - /** - * @returns {number} - */ - getErrorCount() { - throw new Error('Not implemented'); - } - - /** - * @returns {number} - */ - getSkippedCount() { - throw new Error('Not implemented'); - } - - /** - * @param {import('xmlbuilder').XMLElement} [parentElement] - */ - build(parentElement) { - return this.buildNode(this.createElement(parentElement)); - } } module.exports = { TestNode: TestNode }; From 45e59f1ba8ef82048f2cce4d00fe9ddc6b78d4ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A4rsson?= Date: Thu, 1 Feb 2024 14:01:49 +0100 Subject: [PATCH 16/17] docs: Add missing return type --- src/test_group.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test_group.js b/src/test_group.js index b4cbd9e..a2ae6d1 100755 --- a/src/test_group.js +++ b/src/test_group.js @@ -75,7 +75,7 @@ class TestGroup extends TestNode { /** * @protected * @param {Function} counterFunction - * @returns + * @returns {number} */ _sumTestCaseCounts(counterFunction) { var counts = _.map(this._children, counterFunction); From 848a18fdbfe88dda6341fd0c83f6ee93e5b51d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20P=C3=A4rsson?= Date: Thu, 1 Feb 2024 14:04:16 +0100 Subject: [PATCH 17/17] Update changelog for the upcoming release --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index f5f2198..454f61d 100755 --- a/README.md +++ b/README.md @@ -62,6 +62,11 @@ Please refer to the [e2e_spec.coffee](spec/e2e_spec.coffee) for more details on ## Changelog +### 3.2.0 + +- Support name and test count attributes for the root test suites element. Thanks to [Simeon Cheeseman](https://github.com/SimeonC). +- Describe parameter types and return types with JSDoc. Thanks to [Simeon Cheeseman](https://github.com/SimeonC). + ### 3.1.0 - Add support for generic properties for test cases. Thanks to [Pietro Ferrulli](https://github.com/Pi-fe).