From 03a57a5901e7c918977c8b4adb05ab37385beba7 Mon Sep 17 00:00:00 2001 From: davidjgoss Date: Sat, 16 Nov 2024 10:44:25 +0000 Subject: [PATCH] Replace JUnit formatter with messages-based package (#2445) --- CHANGELOG.md | 2 + dependency-lint.yml | 1 + package-lock.json | 203 +++++--- package.json | 4 +- src/configuration/argv_parser.ts | 8 +- src/formatter/builtin/html.ts | 1 - src/formatter/builtin/index.ts | 25 +- src/formatter/builtin/message.ts | 1 - src/formatter/helpers/formatters.ts | 2 - src/formatter/junit_formatter.ts | 291 ----------- src/formatter/junit_formatter_spec.ts | 639 ------------------------ src/formatter/resolve_implementation.ts | 20 +- src/plugin/types.ts | 1 - 13 files changed, 163 insertions(+), 1035 deletions(-) delete mode 100644 src/formatter/junit_formatter.ts delete mode 100644 src/formatter/junit_formatter_spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ff4e626ca..fc186e297 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). Please see [CONTRIBUTING.md](./CONTRIBUTING.md) on how to contribute to Cucumber. ## [Unreleased] +### Changed +- Replace JUnit formatter with messages-based package ([#2445](https://github.com/cucumber/cucumber-js/pull/2445)) ## [11.0.1] - 2024-09-14 ### Fixed diff --git a/dependency-lint.yml b/dependency-lint.yml index b3102040a..ab091d117 100644 --- a/dependency-lint.yml +++ b/dependency-lint.yml @@ -22,6 +22,7 @@ ignoreErrors: - '@cucumber/message-streams' # peer dependency of @cucumber/gherkin-streams unused: - '@cucumber/compatibility-kit' # files dynamically loaded in cck test, not required + - '@cucumber/junit-xml-formatter' # formatter dynamically imported at runtime - '@typescript-eslint/eslint-plugin' # peer dependency of standard-with-typescript - '@typescript-eslint/parser' # peer dependency of @typescript-eslint/eslint-plugin - '@types/*' # type definitions diff --git a/package-lock.json b/package-lock.json index de4ffafc9..16345d3f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@cucumber/gherkin-streams": "5.0.1", "@cucumber/gherkin-utils": "9.0.0", "@cucumber/html-formatter": "21.6.0", + "@cucumber/junit-xml-formatter": "0.6.0", "@cucumber/message-streams": "4.0.1", "@cucumber/messages": "24.1.0", "@cucumber/tag-expressions": "6.1.0", @@ -47,7 +48,6 @@ "tmp": "0.2.3", "type-fest": "^4.8.3", "util-arity": "^1.1.0", - "xmlbuilder": "^15.1.1", "yaml": "^2.2.2", "yup": "1.2.0" }, @@ -60,7 +60,6 @@ "@microsoft/api-extractor": "7.39.0", "@sinonjs/fake-timers": "10.0.2", "@types/chai": "4.3.4", - "@types/chai-xml": "^0.3.2", "@types/debug": "4.1.7", "@types/dirty-chai": "2.0.2", "@types/express": "4.17.15", @@ -84,7 +83,6 @@ "@typescript-eslint/parser": "6.7.4", "chai": "4.3.7", "chai-exclude": "2.1.0", - "chai-xml": "^0.4.1", "coffeescript": "2.7.0", "dependency-lint": "7.1.0", "dirty-chai": "2.0.1", @@ -699,6 +697,33 @@ "@cucumber/messages": ">=18" } }, + "node_modules/@cucumber/junit-xml-formatter": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@cucumber/junit-xml-formatter/-/junit-xml-formatter-0.6.0.tgz", + "integrity": "sha512-++PAuxliQhq7yr2Bn9P0fwBUo46OoKAK5f6M4PrwoHBqIsl/6pUS4mqpviuBrgZ8RD7BTrmASk4lUDJClAz/qA==", + "license": "MIT", + "dependencies": { + "@cucumber/query": "^13.0.2", + "@teppeis/multimaps": "^3.0.0", + "xmlbuilder": "^15.1.1" + }, + "peerDependencies": { + "@cucumber/messages": "*" + } + }, + "node_modules/@cucumber/junit-xml-formatter/node_modules/@cucumber/query": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@cucumber/query/-/query-13.0.2.tgz", + "integrity": "sha512-ykjwL99F5ZmJ3XnIRPe/eA8LvfSTc+C6ZZXrD5QrAfhfMRomBNpZT03MNnxrJ92ge18eDbculhclrIJQiVJCJg==", + "license": "MIT", + "dependencies": { + "@teppeis/multimaps": "3.0.0", + "assert": "^2.1.0" + }, + "peerDependencies": { + "@cucumber/messages": "*" + } + }, "node_modules/@cucumber/message-streams": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@cucumber/message-streams/-/message-streams-4.0.1.tgz", @@ -1444,15 +1469,6 @@ "@types/chai": "*" } }, - "node_modules/@types/chai-xml": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@types/chai-xml/-/chai-xml-0.3.2.tgz", - "integrity": "sha512-mlDXDhGL11EOov2xLOoYNyX5zss6CS4NIW8TUp3UaeEyQ+EHxwbZzNIUZj9dQhAh8drvsqF79nudAuxUc2I7ew==", - "dev": true, - "dependencies": { - "@types/chai": "*" - } - }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -2343,6 +2359,19 @@ "node": ">=0.10.0" } }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -2366,7 +2395,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -2538,7 +2566,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -2674,21 +2701,6 @@ "chai": ">= 4.0.0 < 5" } }, - "node_modules/chai-xml": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/chai-xml/-/chai-xml-0.4.1.tgz", - "integrity": "sha512-VUf5Ol4ifOAsgz+lN4tfWENgQtrKxHPWsmpL5wdbqQdkpblZkcDlaT2aFvsPQH219Yvl8vc4064yFErgBIn9bw==", - "dev": true, - "dependencies": { - "xml2js": "^0.5.0" - }, - "engines": { - "node": ">= 0.8.0" - }, - "peerDependencies": { - "chai": ">=1.10.0 " - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -3045,7 +3057,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -3060,11 +3071,12 @@ } }, "node_modules/define-properties": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", - "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", - "dev": true, + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", "dependencies": { + "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" }, @@ -3376,7 +3388,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.4" @@ -3389,7 +3400,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4282,7 +4292,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, "dependencies": { "is-callable": "^1.1.3" } @@ -4465,7 +4474,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -4667,7 +4675,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -4739,7 +4746,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -4752,7 +4758,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -4764,7 +4769,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -4776,7 +4780,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -4816,7 +4819,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4964,8 +4966,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "2.0.0", @@ -5016,6 +5017,22 @@ "node": ">=8" } }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -5094,7 +5111,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -5145,6 +5161,21 @@ "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -5172,6 +5203,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -5298,7 +5345,6 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", - "dev": true, "dependencies": { "which-typed-array": "^1.1.11" }, @@ -6443,11 +6489,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -6456,7 +6517,6 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.4", @@ -7468,12 +7528,6 @@ "dev": true, "license": "MIT" }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, "node_modules/seed-random": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/seed-random/-/seed-random-2.2.0.tgz", @@ -7616,7 +7670,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -8627,6 +8680,19 @@ "punycode": "^2.1.0" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-arity": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/util-arity/-/util-arity-1.1.0.tgz", @@ -8726,7 +8792,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", - "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", @@ -8799,28 +8864,6 @@ "typedarray-to-buffer": "^3.1.5" } }, - "node_modules/xml2js": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", - "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", - "dev": true, - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xml2js/node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", diff --git a/package.json b/package.json index c2a88dbdf..83a698ee8 100644 --- a/package.json +++ b/package.json @@ -220,6 +220,7 @@ "@cucumber/gherkin-streams": "5.0.1", "@cucumber/gherkin-utils": "9.0.0", "@cucumber/html-formatter": "21.6.0", + "@cucumber/junit-xml-formatter": "0.6.0", "@cucumber/message-streams": "4.0.1", "@cucumber/messages": "24.1.0", "@cucumber/tag-expressions": "6.1.0", @@ -252,7 +253,6 @@ "tmp": "0.2.3", "type-fest": "^4.8.3", "util-arity": "^1.1.0", - "xmlbuilder": "^15.1.1", "yaml": "^2.2.2", "yup": "1.2.0" }, @@ -262,7 +262,6 @@ "@microsoft/api-extractor": "7.39.0", "@sinonjs/fake-timers": "10.0.2", "@types/chai": "4.3.4", - "@types/chai-xml": "^0.3.2", "@types/debug": "4.1.7", "@types/dirty-chai": "2.0.2", "@types/express": "4.17.15", @@ -286,7 +285,6 @@ "@typescript-eslint/parser": "6.7.4", "chai": "4.3.7", "chai-exclude": "2.1.0", - "chai-xml": "^0.4.1", "coffeescript": "2.7.0", "dependency-lint": "7.1.0", "dirty-chai": "2.0.1", diff --git a/src/configuration/argv_parser.ts b/src/configuration/argv_parser.ts index d4eea51ab..ae75dd709 100644 --- a/src/configuration/argv_parser.ts +++ b/src/configuration/argv_parser.ts @@ -2,7 +2,7 @@ import { Command } from 'commander' import merge from 'lodash.merge' import { dialects } from '@cucumber/gherkin' import { version } from '../version' -import builtin from '../formatter/builtin' +import { documentation } from '../formatter/builtin' import { IConfiguration } from './types' export interface IParsedArgvOptions { @@ -81,9 +81,9 @@ const ArgvParser = { .option( '-f, --format ', 'specify the output format, optionally supply PATH to redirect formatter output (repeatable). Available formats:\n' + - Object.entries(builtin).reduce( - (previous, [key, formatter]) => - previous + ` ${key}: ${formatter.documentation}\n`, + Object.entries(documentation).reduce( + (previous, [key, description]) => + previous + ` ${key}: ${description}\n`, '' ), ArgvParser.collect diff --git a/src/formatter/builtin/html.ts b/src/formatter/builtin/html.ts index 7a62dc639..6fcc81304 100644 --- a/src/formatter/builtin/html.ts +++ b/src/formatter/builtin/html.ts @@ -59,7 +59,6 @@ export default { await Promise.all(writeOperations) } }, - documentation: 'Outputs a HTML report', optionsKey: 'html', } satisfies FormatterPlugin diff --git a/src/formatter/builtin/index.ts b/src/formatter/builtin/index.ts index ead63559f..32056cf8f 100644 --- a/src/formatter/builtin/index.ts +++ b/src/formatter/builtin/index.ts @@ -7,13 +7,13 @@ import SnippetsFormatter from '../snippets_formatter' import SummaryFormatter from '../summary_formatter' import UsageFormatter from '../usage_formatter' import UsageJsonFormatter from '../usage_json_formatter' -import JunitFormatter from '../junit_formatter' import messageFormatter from './message' import htmlFormatter from './html' -const builtin: Record = { +const builtin = { // new plugin-based formatters html: htmlFormatter, + junit: '@cucumber/junit-xml-formatter', message: messageFormatter, // legacy class-based formatters json: JsonFormatter, @@ -24,7 +24,22 @@ const builtin: Record = { summary: SummaryFormatter, usage: UsageFormatter, 'usage-json': UsageJsonFormatter, - junit: JunitFormatter, -} +} as const satisfies Record -export default builtin +export default builtin as Record + +export const documentation = { + // new plugin-based formatters + html: 'Outputs a HTML report', + junit: 'Produces a JUnit XML report', + message: 'Emits Cucumber messages in newline-delimited JSON', + // legacy class-based formatters + json: JsonFormatter.documentation, + progress: ProgressFormatter.documentation, + 'progress-bar': ProgressBarFormatter.documentation, + rerun: RerunFormatter.documentation, + snippets: SnippetsFormatter.documentation, + summary: SummaryFormatter.documentation, + usage: UsageFormatter.documentation, + 'usage-json': UsageJsonFormatter.documentation, +} satisfies Record diff --git a/src/formatter/builtin/message.ts b/src/formatter/builtin/message.ts index 398136c55..51e41e98f 100644 --- a/src/formatter/builtin/message.ts +++ b/src/formatter/builtin/message.ts @@ -5,5 +5,4 @@ export default { formatter({ on, write }) { on('message', (message) => write(JSON.stringify(message) + '\n')) }, - documentation: 'Emits Cucumber messages in NDJSON format', } satisfies FormatterPlugin diff --git a/src/formatter/helpers/formatters.ts b/src/formatter/helpers/formatters.ts index dde4b999a..82f9e7def 100644 --- a/src/formatter/helpers/formatters.ts +++ b/src/formatter/helpers/formatters.ts @@ -7,7 +7,6 @@ import SnippetsFormatter from '../snippets_formatter' import SummaryFormatter from '../summary_formatter' import UsageFormatter from '../usage_formatter' import UsageJsonFormatter from '../usage_json_formatter' -import JunitFormatter from '../junit_formatter' const Formatters = { getFormatters(): Record { @@ -20,7 +19,6 @@ const Formatters = { summary: SummaryFormatter, usage: UsageFormatter, 'usage-json': UsageJsonFormatter, - junit: JunitFormatter, } }, } diff --git a/src/formatter/junit_formatter.ts b/src/formatter/junit_formatter.ts deleted file mode 100644 index 03722198d..000000000 --- a/src/formatter/junit_formatter.ts +++ /dev/null @@ -1,291 +0,0 @@ -import xmlbuilder from 'xmlbuilder' -import * as messages from '@cucumber/messages' -import { - Attachment, - Duration, - Feature, - getWorstTestStepResult, - Pickle, - Rule, - TestStepResult, - TestStepResultStatus, -} from '@cucumber/messages' -import { doesHaveValue } from '../value_checker' -import { valueOrDefault } from '../value_checker' -import { ITestCaseAttempt } from './helpers/event_data_collector' -import { - getGherkinExampleRuleMap, - getGherkinStepMap, -} from './helpers/gherkin_document_parser' -import { getPickleStepMap, getStepKeyword } from './helpers/pickle_parser' -import Formatter, { IFormatterOptions } from './' - -interface IJUnitTestSuite { - name: string - failures: number - skipped: number - time: number - tests: IJUnitTestCase[] -} - -interface IJUnitTestCase { - classname: string - name: string - time: number - result: IJUnitTestCaseResult - systemOutput: string - steps: IJUnitTestStep[] -} - -interface IJUnitTestCaseFailure { - type: string - message?: string - detail: string -} - -interface IJUnitTestCaseResult { - status: TestStepResultStatus - failure?: IJUnitTestCaseFailure -} - -interface IJUnitTestStep { - attachments: Attachment[] - hidden: boolean - keyword: string - line: number - name?: string - result: TestStepResult - time: number -} - -interface IBuildJUnitTestStepOptions { - isBeforeHook: boolean - gherkinStepMap: Record - pickleStepMap: Record - testStep: messages.TestStep - testStepAttachments: messages.Attachment[] - testStepResult: messages.TestStepResult -} - -export default class JunitFormatter extends Formatter { - private readonly names: Record = {} - private readonly suiteName: string - public static readonly documentation: string = 'Outputs JUnit report' - - constructor(options: IFormatterOptions) { - super(options) - this.suiteName = valueOrDefault( - options.parsedArgvOptions.junit?.suiteName, - 'cucumber-js' - ) - options.eventBroadcaster.on('envelope', (envelope: messages.Envelope) => { - if (doesHaveValue(envelope.testRunFinished)) { - this.onTestRunFinished() - } - }) - } - - private getTestCases() { - return this.eventDataCollector - .getTestCaseAttempts() - .filter((attempt) => !attempt.willBeRetried) - } - - private getTestSteps( - testCaseAttempt: ITestCaseAttempt, - gherkinStepMap: Record, - pickleStepMap: Record - ) { - return testCaseAttempt.testCase.testSteps.map((testStep) => { - const isBeforeHook = !doesHaveValue(testStep.pickleStepId) - return this.getTestStep({ - isBeforeHook, - gherkinStepMap, - pickleStepMap, - testStep, - testStepAttachments: testCaseAttempt.stepAttachments[testStep.id], - testStepResult: testCaseAttempt.stepResults[testStep.id], - }) - }) - } - - private getTestStep({ - isBeforeHook, - gherkinStepMap, - pickleStepMap, - testStep, - testStepAttachments, - testStepResult, - }: IBuildJUnitTestStepOptions): IJUnitTestStep { - const data: Partial = {} - if (testStep.pickleStepId) { - const pickleStep = pickleStepMap[testStep.pickleStepId] - data.keyword = getStepKeyword({ pickleStep, gherkinStepMap }) - data.line = gherkinStepMap[pickleStep.astNodeIds[0]].location.line - data.name = pickleStep.text - } else { - data.keyword = isBeforeHook ? 'Before' : 'After' - data.hidden = true - } - data.result = testStepResult - data.time = testStepResult.duration - ? this.durationToSeconds(testStepResult.duration) - : 0 - data.attachments = testStepAttachments - return data as IJUnitTestStep - } - - private getTestCaseResult(steps: IJUnitTestStep[]): IJUnitTestCaseResult { - const { status, message, exception } = getWorstTestStepResult( - steps.map((step) => step.result) - ) - return { - status, - failure: - message || exception - ? { - type: exception?.type, - message: exception?.message, - detail: message, - } - : undefined, - } - } - - private durationToSeconds(duration: Duration): number { - const NANOS_IN_SECOND = 1_000_000_000 - return ( - (duration.seconds * NANOS_IN_SECOND + duration.nanos) / NANOS_IN_SECOND - ) - } - - private nameOrDefault(name: string, fallbackSuffix: string): string { - if (!name) { - return `(unnamed ${fallbackSuffix})` - } - return name - } - - private getTestCaseName( - feature: Feature, - rule: Rule | undefined, - pickle: Pickle - ) { - const featureName = this.nameOrDefault(feature.name, 'feature') - const pickleName = this.nameOrDefault(pickle.name, 'scenario') - const testCaseName = rule - ? this.nameOrDefault(rule.name, 'rule') + ': ' + pickleName - : pickleName - if (!this.names[featureName]) { - this.names[featureName] = [] - } - let index = 0 - while ( - this.names[featureName].includes( - index > 0 ? `${testCaseName} [${index}]` : testCaseName - ) - ) { - index++ - } - const name = index > 0 ? `${testCaseName} [${index}]` : testCaseName - this.names[featureName].push(name) - return name - } - - private formatTestSteps(steps: IJUnitTestStep[]): string { - return steps - .filter((step) => !step.hidden) - .map((step) => { - const statusText = step.result.status.toLowerCase() - const maxLength = 80 - statusText.length - 3 - const stepText = `${step.keyword}${step.name}` - .padEnd(maxLength, '.') - .substring(0, maxLength) - return `${stepText}...${statusText}` - }) - .join('\n') - } - - private onTestRunFinished(): void { - const testCases = this.getTestCases() - - const tests = testCases.map( - (testCaseAttempt: ITestCaseAttempt) => { - const { gherkinDocument, pickle } = testCaseAttempt - const { feature } = gherkinDocument - const gherkinExampleRuleMap = getGherkinExampleRuleMap(gherkinDocument) - const rule = gherkinExampleRuleMap[pickle.astNodeIds[0]] - const gherkinStepMap = getGherkinStepMap(gherkinDocument) - const pickleStepMap = getPickleStepMap(pickle) - - const steps = this.getTestSteps( - testCaseAttempt, - gherkinStepMap, - pickleStepMap - ) - const stepDuration = steps.reduce( - (total, step) => total + (step.time || 0), - 0 - ) - - return { - classname: this.nameOrDefault(feature.name, 'feature'), - name: this.getTestCaseName(feature, rule, pickle), - time: stepDuration, - result: this.getTestCaseResult(steps), - systemOutput: this.formatTestSteps(steps), - steps, - } - } - ) - - const passed = tests.filter( - (item) => item.result.status === TestStepResultStatus.PASSED - ).length - const skipped = tests.filter( - (item) => item.result.status === TestStepResultStatus.SKIPPED - ).length - const failures = tests.length - passed - skipped - - const testSuite: IJUnitTestSuite = { - name: this.suiteName, - tests, - failures, - skipped, - time: tests.reduce((total, test) => total + test.time, 0), - } - - this.log(this.buildXmlReport(testSuite)) - } - - private buildXmlReport(testSuite: IJUnitTestSuite): string { - const xmlReport = xmlbuilder - .create('testsuite', { invalidCharReplacement: '' }) - .att('failures', testSuite.failures) - .att('skipped', testSuite.skipped) - .att('name', testSuite.name) - .att('time', testSuite.time) - .att('tests', testSuite.tests.length) - testSuite.tests.forEach((test) => { - const xmlTestCase = xmlReport.ele('testcase', { - classname: test.classname, - name: test.name, - time: test.time, - }) - if (test.result.status === TestStepResultStatus.SKIPPED) { - xmlTestCase.ele('skipped') - } else if (test.result.status !== TestStepResultStatus.PASSED) { - const xmlFailure = xmlTestCase.ele('failure', { - type: test.result.failure?.type, - message: test.result.failure?.message, - }) - if (test.result?.failure) { - xmlFailure.cdata(test.result.failure.detail) - } - } - xmlTestCase.ele('system-out', {}).cdata(test.systemOutput) - }) - - return xmlReport.end({ pretty: true }) - } -} diff --git a/src/formatter/junit_formatter_spec.ts b/src/formatter/junit_formatter_spec.ts deleted file mode 100644 index a0721b3b3..000000000 --- a/src/formatter/junit_formatter_spec.ts +++ /dev/null @@ -1,639 +0,0 @@ -import { afterEach, beforeEach, describe, it } from 'mocha' -import { expect, use } from 'chai' -import chaiXml from 'chai-xml' -import FakeTimers, { InstalledClock } from '@sinonjs/fake-timers' -import timeMethods from '../time' -import { testFormatter } from '../../test/formatter_helpers' -import { SupportCodeLibrary } from '../support_code_library_builder/types' -import { buildSupportCodeLibrary } from '../../test/runtime_helpers' - -use(chaiXml) - -function getJUnitFormatterSupportCodeLibrary( - clock: InstalledClock -): SupportCodeLibrary { - return buildSupportCodeLibrary(__dirname, ({ Before, After, Given }) => { - Before(function () {}) // eslint-disable-line @typescript-eslint/no-empty-function - After(function () {}) // eslint-disable-line @typescript-eslint/no-empty-function - - Given('a passing step', function () { - clock.tick(1) - }) - - Given('I have in my belly', function () { - clock.tick(1) - }) - - let willPass = false - Given('a flaky step', function () { - clock.tick(1) - if (willPass) { - return - } - willPass = true - throw 'error' // eslint-disable-line @typescript-eslint/no-throw-literal - }) - - Given('a failing step', function () { - clock.tick(1) - throw 'error' // eslint-disable-line @typescript-eslint/no-throw-literal - }) - - Given('a failing step with invalid character', function () { - clock.tick(1) - throw 'Error: include \x08invalid character' // eslint-disable-line @typescript-eslint/no-throw-literal - }) - - Given('a pending step', function () { - clock.tick(1) - return 'pending' - }) - - Given('a skipped step', function () { - return 'skipped' - }) - }) -} - -describe('JunitFormatter', () => { - let clock: InstalledClock - - beforeEach(() => { - clock = FakeTimers.withGlobal(timeMethods).install() - }) - - afterEach(() => { - clock.uninstall() - }) - - describe('no features', () => { - it('outputs an empty ', async () => { - // Arrange - - // Act - const output = await testFormatter({ type: 'junit' }) - - // Assert - expect(output).xml.to.deep.equal( - '\n' + - '' - ) - }) - }) - - describe('one scenario with one step', () => { - describe('passed', () => { - it('outputs the feature', async () => { - // Arrange - const sources = [ - { - data: [ - 'Feature: my feature', - ' my feature description', - '', - ' Scenario: my scenario', - ' my scenario description', - '', - ' Given a passing step', - ].join('\n'), - uri: 'a.feature', - }, - ] - - const supportCodeLibrary = getJUnitFormatterSupportCodeLibrary(clock) - - // Act - const output = await testFormatter({ - sources, - supportCodeLibrary, - type: 'junit', - }) - - // Assert - expect(output).xml.to.deep.equal( - '\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - '' - ) - }) - }) - - describe('failed', () => { - it('includes the error message', async () => { - // Arrange - const sources = [ - { - data: [ - 'Feature: my feature', - ' Scenario: my scenario', - ' Given a failing step', - ].join('\n'), - uri: 'a.feature', - }, - ] - - const supportCodeLibrary = getJUnitFormatterSupportCodeLibrary(clock) - - // Act - const output = await testFormatter({ - sources, - supportCodeLibrary, - type: 'junit', - }) - - // Assert - expect(output).xml.to.deep.equal( - '\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '' - ) - }) - - it('failed with invalid character', async () => { - // Arrange - const sources = [ - { - data: [ - 'Feature: my feature', - ' Scenario: my scenario', - ' Given a failing step with invalid character', - ].join('\n'), - uri: 'a.feature', - }, - ] - - const supportCodeLibrary = getJUnitFormatterSupportCodeLibrary(clock) - - // Act - const output = await testFormatter({ - sources, - supportCodeLibrary, - type: 'junit', - }) - - // Assert - expect(output).xml.to.deep.equal( - '\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '' - ) - }) - }) - - describe('retried', () => { - it('only outputs the last attempt', async () => { - // Arrange - const sources = [ - { - data: [ - 'Feature: my feature', - ' Scenario: my scenario', - ' Given a flaky step', - ].join('\n'), - uri: 'a.feature', - }, - ] - - const supportCodeLibrary = getJUnitFormatterSupportCodeLibrary(clock) - - // Act - const output = await testFormatter({ - runtimeOptions: { retry: 1 }, - sources, - supportCodeLibrary, - type: 'junit', - }) - - // Assert - expect(output).xml.to.deep.equal( - '\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - '' - ) - }) - }) - - describe('pending', () => { - it('outputs the feature', async () => { - // Arrange - const sources = [ - { - data: [ - 'Feature: my feature', - ' Scenario: my scenario', - ' Given a pending step', - ].join('\n'), - uri: 'a.feature', - }, - ] - const supportCodeLibrary = getJUnitFormatterSupportCodeLibrary(clock) - - // Act - const output = await testFormatter({ - sources, - supportCodeLibrary, - type: 'junit', - }) - - // Assert - expect(output).xml.to.deep.equal( - '\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '' - ) - }) - }) - - describe('skipped', () => { - it('outputs the feature', async () => { - // Arrange - const sources = [ - { - data: [ - 'Feature: my feature', - ' Scenario: my scenario', - ' Given a passing step', - ' And a skipped step', - ' And a passing step', - ].join('\n'), - uri: 'a.feature', - }, - ] - const supportCodeLibrary = getJUnitFormatterSupportCodeLibrary(clock) - - // Act - const output = await testFormatter({ - sources, - supportCodeLibrary, - type: 'junit', - }) - - // Assert - expect(output).xml.to.deep.equal( - '\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '' - ) - }) - }) - - describe('without a step definition', () => { - it('outputs the feature', async () => { - // Arrange - const sources = [ - { - data: [ - 'Feature: my feature', - ' Scenario: my scenario', - ' Given a passing step', - ].join('\n'), - uri: 'a.feature', - }, - ] - - // Act - const output = await testFormatter({ - sources, - type: 'junit', - }) - - // Assert - expect(output).xml.to.deep.equal( - '\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '' - ) - }) - }) - }) - - describe('one scenario with a background', () => { - it('outputs the feature', async () => { - // Arrange - const sources = [ - { - data: [ - 'Feature: my feature', - '', - ' Background:', - ' Given a passing step', - '', - ' Scenario: my scenario', - '', - ' When a passing step', - ].join('\n'), - uri: 'a.feature', - }, - ] - - const supportCodeLibrary = getJUnitFormatterSupportCodeLibrary(clock) - - // Act - const output = await testFormatter({ - sources, - supportCodeLibrary, - type: 'junit', - }) - - // Assert - expect(output).xml.to.deep.equal( - '\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - '' - ) - }) - }) - - describe('scenario outline with several examples', () => { - it('outputs one test case per example with unique names', async () => { - // Arrange - const sources = [ - { - data: [ - 'Feature: my feature', - '', - ' Scenario Outline: my templated scenario', - ' Given a step', - ' Examples:', - ' | status |', - ' | passing |', - ' | failing |', - '', - ].join('\n'), - uri: 'a.feature', - }, - ] - - const supportCodeLibrary = getJUnitFormatterSupportCodeLibrary(clock) - - // Act - const output = await testFormatter({ - sources, - supportCodeLibrary, - type: 'junit', - }) - - // Assert - expect(output).xml.to.deep.equal( - '\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '' - ) - }) - }) - - describe('one rule with several examples (scenarios)', () => { - describe('passed', () => { - it('outputs the feature', async () => { - // Arrange - const sources = [ - { - data: [ - 'Feature: my feature', - ' my feature description', - '', - ' Rule: my rule', - ' my rule description', - '', - ' Example: first example', - ' first example description', - '', - ' Given a passing step', - '', - ' Example: second example', - ' second example description', - '', - ' Given a passing step', - ].join('\n'), - uri: 'a.feature', - }, - ] - - const supportCodeLibrary = getJUnitFormatterSupportCodeLibrary(clock) - - // Act - const output = await testFormatter({ - sources, - supportCodeLibrary, - type: 'junit', - }) - - // Assert - expect(output).xml.to.deep.equal( - '\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '' - ) - }) - }) - }) - - describe('unnamed features/rules/scenarios', () => { - it('defaults the names', async () => { - // Arrange - const sources = [ - { - data: [ - 'Feature:', - ' my feature description', - '', - ' Rule:', - ' my rule description', - '', - ' Example:', - ' first example description', - '', - ' Given a passing step', - '', - ' Example:', - ' second example description', - '', - ' Given a passing step', - ].join('\n'), - uri: 'a.feature', - }, - ] - - const supportCodeLibrary = getJUnitFormatterSupportCodeLibrary(clock) - - // Act - const output = await testFormatter({ - sources, - supportCodeLibrary, - type: 'junit', - }) - - // Assert - expect(output).xml.to.deep.equal( - '\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - ' \n' + - '' - ) - }) - }) - - describe('content containing CDATA', () => { - it('outputs the feature', async () => { - // Arrange - const sources = [ - { - data: [ - 'Feature: my feature', - ' my feature description', - '', - ' Scenario: my scenario', - ' my scenario description', - '', - ' Given I have in my belly', - ].join('\n'), - uri: 'a.feature', - }, - ] - - const supportCodeLibrary = getJUnitFormatterSupportCodeLibrary(clock) - - // Act - const output = await testFormatter({ - sources, - supportCodeLibrary, - type: 'junit', - }) - - // Assert - expect(output).xml.to.deep.equal( - '\n' + - '\n' + - ' \n' + - ' in my belly................................passed]]>\n' + - ' \n' + - '' - ) - }) - }) - - describe('custom test suite name', () => { - it('outputs with the custom name', async () => { - // Arrange - const sources = [ - { - data: [ - 'Feature: my feature', - ' Scenario: my scenario', - ' Given a passing step', - ].join('\n'), - uri: 'a.feature', - }, - ] - - const supportCodeLibrary = getJUnitFormatterSupportCodeLibrary(clock) - - // Act - const output = await testFormatter({ - parsedArgvOptions: { - junit: { - suiteName: 'my test suite', - }, - }, - sources, - supportCodeLibrary, - type: 'junit', - }) - - // Assert - expect(output).xml.to.deep.equal( - '\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - '' - ) - }) - }) - - describe('no custom test suite name', () => { - it('outputs with cucumber-js as test suite name', async () => { - // Arrange - const sources = [ - { - data: [ - 'Feature: my feature', - ' Scenario: my scenario', - ' Given a passing step', - ].join('\n'), - uri: 'a.feature', - }, - ] - - const supportCodeLibrary = getJUnitFormatterSupportCodeLibrary(clock) - - // Act - const output = await testFormatter({ - sources, - supportCodeLibrary, - type: 'junit', - }) - - // Assert - expect(output).xml.to.deep.equal( - '\n' + - '\n' + - ' \n' + - ' \n' + - ' \n' + - '' - ) - }) - }) -}) diff --git a/src/formatter/resolve_implementation.ts b/src/formatter/resolve_implementation.ts index 9faa48653..a8da6993a 100644 --- a/src/formatter/resolve_implementation.ts +++ b/src/formatter/resolve_implementation.ts @@ -7,14 +7,18 @@ export async function resolveImplementation( specifier: string, cwd: string ): Promise { - if (builtin[specifier]) { - return builtin[specifier] - } else { - const imported = await importCode(specifier, cwd) - const found = findClassOrPlugin(imported) - if (!found) { - throw new Error(`${specifier} does not export a function/class`) + const fromBuiltin = builtin[specifier] + if (fromBuiltin) { + if (typeof fromBuiltin !== 'string') { + return fromBuiltin + } else { + specifier = fromBuiltin } - return found } + const imported = await importCode(specifier, cwd) + const found = findClassOrPlugin(imported) + if (!found) { + throw new Error(`${specifier} does not export a function/class`) + } + return found } diff --git a/src/plugin/types.ts b/src/plugin/types.ts index 6d09aff01..d466e0c3a 100644 --- a/src/plugin/types.ts +++ b/src/plugin/types.ts @@ -78,6 +78,5 @@ export type FormatterPluginFunction = ( export interface FormatterPlugin { type: 'formatter' formatter: FormatterPluginFunction - documentation: string optionsKey?: string }