@g_package/jest-cucumber-fusion 3.0.1 → 3.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +47 -15
- package/package.json +10 -11
- package/src/code-suggestion.js +12 -3
- package/src/feature-source.js +11 -4
- package/src/scenario-name.js +3 -6
- package/src/test-registration.js +16 -8
- package/src/value-description.js +29 -1
package/dist/index.cjs
CHANGED
|
@@ -58,6 +58,22 @@ var asText = (value) => {
|
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
60
|
var describeValue = (value) => `${typeof value} ${asText(value)}`;
|
|
61
|
+
var textOf = (value) => {
|
|
62
|
+
try {
|
|
63
|
+
return String(value);
|
|
64
|
+
} catch {
|
|
65
|
+
return describeValue(value);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var describeThrown = (thrown) => {
|
|
69
|
+
let message;
|
|
70
|
+
try {
|
|
71
|
+
message = thrown == null ? void 0 : thrown.message;
|
|
72
|
+
} catch {
|
|
73
|
+
return describeValue(thrown);
|
|
74
|
+
}
|
|
75
|
+
return textOf(message ? message : thrown);
|
|
76
|
+
};
|
|
61
77
|
|
|
62
78
|
// src/shared-state.js
|
|
63
79
|
var STORE = /* @__PURE__ */ Symbol.for("@g_package/jest-cucumber-fusion@3");
|
|
@@ -9324,9 +9340,13 @@ var parseFeature = (featureText) => {
|
|
|
9324
9340
|
try {
|
|
9325
9341
|
return { document: parser.parse(featureText), ids };
|
|
9326
9342
|
} catch (parseFailure) {
|
|
9327
|
-
throw new Error(
|
|
9328
|
-
|
|
9329
|
-
|
|
9343
|
+
throw new Error(
|
|
9344
|
+
`Error parsing feature Gherkin: ${/** @type {Error} */
|
|
9345
|
+
parseFailure.message}`,
|
|
9346
|
+
{
|
|
9347
|
+
cause: parseFailure
|
|
9348
|
+
}
|
|
9349
|
+
);
|
|
9330
9350
|
}
|
|
9331
9351
|
};
|
|
9332
9352
|
var compilePickles = (document, absoluteFeatureFilePath, ids) => {
|
|
@@ -9334,7 +9354,8 @@ var compilePickles = (document, absoluteFeatureFilePath, ids) => {
|
|
|
9334
9354
|
return compile(document, absoluteFeatureFilePath, ids);
|
|
9335
9355
|
} catch (compileFailure) {
|
|
9336
9356
|
throw new Error(
|
|
9337
|
-
`Error parsing feature Gherkin: ${
|
|
9357
|
+
`Error parsing feature Gherkin: ${/** @type {Error} */
|
|
9358
|
+
compileFailure.message}`,
|
|
9338
9359
|
{
|
|
9339
9360
|
cause: compileFailure
|
|
9340
9361
|
}
|
|
@@ -9575,13 +9596,18 @@ var starterCodeForShape = (steps) => {
|
|
|
9575
9596
|
};
|
|
9576
9597
|
var stepsByShape = (unboundSteps) => {
|
|
9577
9598
|
const groups = /* @__PURE__ */ new Map();
|
|
9578
|
-
const named = /* @__PURE__ */ new
|
|
9599
|
+
const named = /* @__PURE__ */ new Map();
|
|
9579
9600
|
unboundSteps.forEach((step) => {
|
|
9580
9601
|
const identity = `${step.keyword}\0${step.stepText}`;
|
|
9581
|
-
if (named.has(identity)) return;
|
|
9582
|
-
named.add(identity);
|
|
9583
9602
|
const shape = shapeOf(step);
|
|
9603
|
+
if (named.has(identity)) {
|
|
9604
|
+
const position = named.get(identity);
|
|
9605
|
+
const group = groups.get(shape);
|
|
9606
|
+
if (group[position].stepArgument == null) group[position] = step;
|
|
9607
|
+
return;
|
|
9608
|
+
}
|
|
9584
9609
|
if (!groups.has(shape)) groups.set(shape, []);
|
|
9610
|
+
named.set(identity, groups.get(shape).length);
|
|
9585
9611
|
groups.get(shape).push(step);
|
|
9586
9612
|
});
|
|
9587
9613
|
return [...groups.values()];
|
|
@@ -9644,7 +9670,7 @@ var answerFromTemplate = (loadedFeature, scenario, scenarioNameTemplate) => {
|
|
|
9644
9670
|
} catch (templateFailure) {
|
|
9645
9671
|
throw refuseUnusableName(
|
|
9646
9672
|
scenario.title,
|
|
9647
|
-
`the scenarioNameTemplate threw: ${templateFailure
|
|
9673
|
+
`the scenarioNameTemplate threw: ${describeThrown(templateFailure)}`,
|
|
9648
9674
|
"make the template total over the four variables it is handed: featureTitle, featureTags, scenarioTitle and scenarioTags. A tag list may be empty, and a title may hold any character a feature file allows."
|
|
9649
9675
|
);
|
|
9650
9676
|
}
|
|
@@ -9701,7 +9727,13 @@ var namedScenarios = (loadedFeature, scenarios, scenarioNameTemplate) => scenari
|
|
|
9701
9727
|
)
|
|
9702
9728
|
})
|
|
9703
9729
|
);
|
|
9704
|
-
var isError = (value) =>
|
|
9730
|
+
var isError = (value) => {
|
|
9731
|
+
try {
|
|
9732
|
+
return Object.prototype.toString.call(value) === "[object Error]";
|
|
9733
|
+
} catch {
|
|
9734
|
+
return false;
|
|
9735
|
+
}
|
|
9736
|
+
};
|
|
9705
9737
|
var decorationOf = (stepText, stepArguments, originalMessage) => `Failing step: "${stepText}"
|
|
9706
9738
|
|
|
9707
9739
|
Step arguments: ${JSON.stringify(stepArguments)}
|
|
@@ -9718,13 +9750,13 @@ var decorateInPlace = (failure, decorated) => {
|
|
|
9718
9750
|
var decorate = (stepText, stepArguments, failure) => {
|
|
9719
9751
|
if (!isError(failure))
|
|
9720
9752
|
return new Error(
|
|
9721
|
-
decorationOf(
|
|
9722
|
-
stepText,
|
|
9723
|
-
stepArguments,
|
|
9724
|
-
failure && failure.message ? failure.message : failure
|
|
9725
|
-
)
|
|
9753
|
+
decorationOf(stepText, stepArguments, describeThrown(failure))
|
|
9726
9754
|
);
|
|
9727
|
-
const decorated = decorationOf(
|
|
9755
|
+
const decorated = decorationOf(
|
|
9756
|
+
stepText,
|
|
9757
|
+
stepArguments,
|
|
9758
|
+
textOf(failure.message)
|
|
9759
|
+
);
|
|
9728
9760
|
try {
|
|
9729
9761
|
return decorateInPlace(failure, decorated);
|
|
9730
9762
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@g_package/jest-cucumber-fusion",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
4
4
|
"description": "Write cucumber test as part of a jest run (including coverage)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -21,12 +21,13 @@
|
|
|
21
21
|
"coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
|
|
22
22
|
"lint": "eslint . --max-warnings 0",
|
|
23
23
|
"lint:secrets": "secretlint \"**/*\"",
|
|
24
|
-
"commit": "
|
|
24
|
+
"commit": "czg",
|
|
25
25
|
"semantic-release": "semantic-release",
|
|
26
26
|
"fmt": "npx prettier --write .",
|
|
27
27
|
"prepare": "node scripts/prepare-hooks.js",
|
|
28
28
|
"build": "node scripts/build-cjs.js",
|
|
29
|
-
"prepack": "npm run build"
|
|
29
|
+
"prepack": "npm run build",
|
|
30
|
+
"typecheck": "tsc -p jsconfig.json"
|
|
30
31
|
},
|
|
31
32
|
"repository": {
|
|
32
33
|
"type": "git",
|
|
@@ -47,10 +48,12 @@
|
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@commitlint/cli": "^21.2.3",
|
|
49
50
|
"@commitlint/config-conventional": "^21.2.3",
|
|
50
|
-
"@eslint/js": "^
|
|
51
|
+
"@eslint/js": "^10.0.1",
|
|
51
52
|
"@secretlint/secretlint-rule-pattern": "^13.0.7",
|
|
52
53
|
"@secretlint/secretlint-rule-preset-recommend": "^13.0.7",
|
|
53
|
-
"
|
|
54
|
+
"@types/jest": "^30.0.0",
|
|
55
|
+
"@types/node": "^18.19.130",
|
|
56
|
+
"czg": "1.14.0",
|
|
54
57
|
"esbuild": "0.28.2",
|
|
55
58
|
"eslint": "^10.12.0",
|
|
56
59
|
"eslint-plugin-jest": "^29.16.6",
|
|
@@ -62,16 +65,12 @@
|
|
|
62
65
|
"prettier": "3.9.9",
|
|
63
66
|
"secretlint": "^13.0.7",
|
|
64
67
|
"semantic-release": "^25.0.5",
|
|
65
|
-
"tsd": "^0.33.0"
|
|
68
|
+
"tsd": "^0.33.0",
|
|
69
|
+
"typescript": "^6.0.3"
|
|
66
70
|
},
|
|
67
71
|
"publishConfig": {
|
|
68
72
|
"access": "public"
|
|
69
73
|
},
|
|
70
|
-
"config": {
|
|
71
|
-
"commitizen": {
|
|
72
|
-
"path": "./node_modules/cz-conventional-changelog"
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
74
|
"release": {
|
|
76
75
|
"plugins": [
|
|
77
76
|
"@semantic-release/commit-analyzer",
|
package/src/code-suggestion.js
CHANGED
|
@@ -177,15 +177,24 @@ const starterCodeForShape = (steps) => {
|
|
|
177
177
|
// are equal, and as ambiguous when one is wider ("(\d+)" beside a decimal capture both match
|
|
178
178
|
// "1"). A step with a table and the same step without one share a shape too; parameters do not
|
|
179
179
|
// make two definitions distinct.
|
|
180
|
+
//
|
|
181
|
+
// A step named once still counts every time it appears: when only a later appearance carries
|
|
182
|
+
// a Gherkin argument, that appearance stands in for the step, so the starter code has the
|
|
183
|
+
// parameter the step will receive. The text is the same, so the entry reads the same.
|
|
180
184
|
const stepsByShape = (unboundSteps) => {
|
|
181
185
|
const groups = new Map();
|
|
182
|
-
const named = new
|
|
186
|
+
const named = new Map();
|
|
183
187
|
unboundSteps.forEach((step) => {
|
|
184
188
|
const identity = `${step.keyword}\u0000${step.stepText}`;
|
|
185
|
-
if (named.has(identity)) return;
|
|
186
|
-
named.add(identity);
|
|
187
189
|
const shape = shapeOf(step);
|
|
190
|
+
if (named.has(identity)) {
|
|
191
|
+
const position = named.get(identity);
|
|
192
|
+
const group = groups.get(shape);
|
|
193
|
+
if (group[position].stepArgument == null) group[position] = step;
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
188
196
|
if (!groups.has(shape)) groups.set(shape, []);
|
|
197
|
+
named.set(identity, groups.get(shape).length);
|
|
189
198
|
groups.get(shape).push(step);
|
|
190
199
|
});
|
|
191
200
|
return [...groups.values()];
|
package/src/feature-source.js
CHANGED
|
@@ -95,9 +95,15 @@ const parseFeature = (featureText) => {
|
|
|
95
95
|
try {
|
|
96
96
|
return { document: parser.parse(featureText), ids };
|
|
97
97
|
} catch (parseFailure) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
// @cucumber/gherkin throws Errors only: plain ones, or its own exceptions, which all
|
|
99
|
+
// extend Error (dist/Errors.js). The cast states that contract for the type check rather
|
|
100
|
+
// than adding a branch for a non-Error that Gherkin never throws.
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Error parsing feature Gherkin: ${/** @type {Error} */ (parseFailure).message}`,
|
|
103
|
+
{
|
|
104
|
+
cause: parseFailure,
|
|
105
|
+
},
|
|
106
|
+
);
|
|
101
107
|
}
|
|
102
108
|
};
|
|
103
109
|
|
|
@@ -105,8 +111,9 @@ const compilePickles = (document, absoluteFeatureFilePath, ids) => {
|
|
|
105
111
|
try {
|
|
106
112
|
return gherkin.compile(document, absoluteFeatureFilePath, ids);
|
|
107
113
|
} catch (compileFailure) {
|
|
114
|
+
// Errors only, as for the parse above.
|
|
108
115
|
throw new Error(
|
|
109
|
-
`Error parsing feature Gherkin: ${compileFailure.message}`,
|
|
116
|
+
`Error parsing feature Gherkin: ${/** @type {Error} */ (compileFailure).message}`,
|
|
110
117
|
{
|
|
111
118
|
cause: compileFailure,
|
|
112
119
|
},
|
package/src/scenario-name.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
//
|
|
12
12
|
// Pure: no filesystem, no parser, no Jest global. The port calls it; it calls the template.
|
|
13
13
|
|
|
14
|
-
import { describeValue } from "./value-description.js";
|
|
14
|
+
import { describeThrown, describeValue } from "./value-description.js";
|
|
15
15
|
|
|
16
16
|
// Kept as the first line of the refusal because the previous engine used it, so anything a
|
|
17
17
|
// consumer greps for still matches.
|
|
@@ -73,13 +73,10 @@ const answerFromTemplate = (loadedFeature, scenario, scenarioNameTemplate) => {
|
|
|
73
73
|
try {
|
|
74
74
|
return scenarioNameTemplate(templateVariablesFor(loadedFeature, scenario));
|
|
75
75
|
} catch (templateFailure) {
|
|
76
|
+
// A consumer's template can throw anything; describeThrown quotes anything.
|
|
76
77
|
throw refuseUnusableName(
|
|
77
78
|
scenario.title,
|
|
78
|
-
`the scenarioNameTemplate threw: ${
|
|
79
|
-
templateFailure && templateFailure.message
|
|
80
|
-
? templateFailure.message
|
|
81
|
-
: templateFailure
|
|
82
|
-
}`,
|
|
79
|
+
`the scenarioNameTemplate threw: ${describeThrown(templateFailure)}`,
|
|
83
80
|
"make the template total over the four variables it is handed: featureTitle, " +
|
|
84
81
|
"featureTags, scenarioTitle and scenarioTags. A tag list may be empty, and a title " +
|
|
85
82
|
"may hold any character a feature file allows.",
|
package/src/test-registration.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { findMatchingStep } from "./step-matching.js";
|
|
11
11
|
import { unmatchedStepRefusal } from "./code-suggestion.js";
|
|
12
12
|
import { nameForScenario } from "./scenario-name.js";
|
|
13
|
+
import { describeThrown, textOf } from "./value-description.js";
|
|
13
14
|
|
|
14
15
|
// BIND BEFORE REGISTERING. Every step of every scenario is matched up front, so a refusal
|
|
15
16
|
// (an unmatched step, an ambiguous one, an unsupported keyword) leaves Fusion() on the way
|
|
@@ -70,8 +71,15 @@ const namedScenarios = (loadedFeature, scenarios, scenarioNameTemplate) =>
|
|
|
70
71
|
// match. Recognised by its tag rather than instanceof, which fails for an error made in another
|
|
71
72
|
// realm (Node's own, from inside Jest's test context). Anything that cannot be rewritten (a
|
|
72
73
|
// frozen error) is wrapped with the original as its cause; anything else thrown is wrapped.
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
// Reading the tag can itself throw, for a Proxy whose traps throw or one that was revoked; such
|
|
75
|
+
// a value is no Error, so it is wrapped like any other.
|
|
76
|
+
const isError = (value) => {
|
|
77
|
+
try {
|
|
78
|
+
return Object.prototype.toString.call(value) === "[object Error]";
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
75
83
|
|
|
76
84
|
const decorationOf = (stepText, stepArguments, originalMessage) =>
|
|
77
85
|
`Failing step: "${stepText}"\n\n` +
|
|
@@ -90,13 +98,13 @@ const decorateInPlace = (failure, decorated) => {
|
|
|
90
98
|
const decorate = (stepText, stepArguments, failure) => {
|
|
91
99
|
if (!isError(failure))
|
|
92
100
|
return new Error(
|
|
93
|
-
decorationOf(
|
|
94
|
-
stepText,
|
|
95
|
-
stepArguments,
|
|
96
|
-
failure && failure.message ? failure.message : failure,
|
|
97
|
-
),
|
|
101
|
+
decorationOf(stepText, stepArguments, describeThrown(failure)),
|
|
98
102
|
);
|
|
99
|
-
const decorated = decorationOf(
|
|
103
|
+
const decorated = decorationOf(
|
|
104
|
+
stepText,
|
|
105
|
+
stepArguments,
|
|
106
|
+
textOf(failure.message),
|
|
107
|
+
);
|
|
100
108
|
try {
|
|
101
109
|
return decorateInPlace(failure, decorated);
|
|
102
110
|
} catch {
|
package/src/value-description.js
CHANGED
|
@@ -23,4 +23,32 @@ const asText = (value) => {
|
|
|
23
23
|
|
|
24
24
|
const describeValue = (value) => `${typeof value} ${asText(value)}`;
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
// How a report quotes what the consumer's code threw: its message when that is truthy,
|
|
27
|
+
// otherwise the value itself, as text. textOf is the text step alone, for a value already known
|
|
28
|
+
// to be the message.
|
|
29
|
+
//
|
|
30
|
+
// A template literal cannot make text of a Symbol, of an object with no prototype, or of an
|
|
31
|
+
// object whose message getter or toString throws, so quoting those crashed the report and the
|
|
32
|
+
// consumer saw a TypeError instead of their own failure (the PR #36 fuzz run). String() makes
|
|
33
|
+
// the same text as a template literal for everything else and accepts a Symbol; anything still
|
|
34
|
+
// unprintable is named as describeValue names it. The message is read once, so a getter that
|
|
35
|
+
// answers differently each time cannot be quoted as one thing and checked as another.
|
|
36
|
+
const textOf = (value) => {
|
|
37
|
+
try {
|
|
38
|
+
return String(value);
|
|
39
|
+
} catch {
|
|
40
|
+
return describeValue(value);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const describeThrown = (thrown) => {
|
|
45
|
+
let message;
|
|
46
|
+
try {
|
|
47
|
+
message = thrown == null ? undefined : thrown.message;
|
|
48
|
+
} catch {
|
|
49
|
+
return describeValue(thrown);
|
|
50
|
+
}
|
|
51
|
+
return textOf(message ? message : thrown);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export { describeThrown, describeValue, textOf };
|