@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 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(`Error parsing feature Gherkin: ${parseFailure.message}`, {
9328
- cause: parseFailure
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: ${compileFailure.message}`,
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 Set();
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 && templateFailure.message ? templateFailure.message : 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) => Object.prototype.toString.call(value) === "[object Error]";
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(stepText, stepArguments, failure.message);
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.1",
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": "git-cz",
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": "^9.39.5",
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
- "cz-conventional-changelog": "^3.3.0",
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",
@@ -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 Set();
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()];
@@ -95,9 +95,15 @@ const parseFeature = (featureText) => {
95
95
  try {
96
96
  return { document: parser.parse(featureText), ids };
97
97
  } catch (parseFailure) {
98
- throw new Error(`Error parsing feature Gherkin: ${parseFailure.message}`, {
99
- cause: parseFailure,
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
  },
@@ -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.",
@@ -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
- const isError = (value) =>
74
- Object.prototype.toString.call(value) === "[object Error]";
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(stepText, stepArguments, failure.message);
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 {
@@ -23,4 +23,32 @@ const asText = (value) => {
23
23
 
24
24
  const describeValue = (value) => `${typeof value} ${asText(value)}`;
25
25
 
26
- export { describeValue };
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 };