executable-stories-formatters 0.15.1 → 0.16.0
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/cli.js +197 -55
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +197 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +197 -55
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -187,6 +187,41 @@ function generateFeatureId(uri) {
|
|
|
187
187
|
function generateScenarioId(featureId, scenarioName) {
|
|
188
188
|
return `${featureId};${slugify(scenarioName)}`;
|
|
189
189
|
}
|
|
190
|
+
function normalizeText(text2) {
|
|
191
|
+
return text2.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
|
|
192
|
+
}
|
|
193
|
+
function tokenize(text2) {
|
|
194
|
+
const normalized = normalizeText(text2);
|
|
195
|
+
return normalized.length === 0 ? [] : normalized.split(" ");
|
|
196
|
+
}
|
|
197
|
+
function behaviourFingerprint(input) {
|
|
198
|
+
const steps = input.steps.map((step) => `${step.keyword.toLowerCase()}:${normalizeText(step.text)}`).join("\n");
|
|
199
|
+
const covers = [...input.covers ?? []].map((path12) => path12.trim()).filter(Boolean).sort().join(",");
|
|
200
|
+
if (steps.length === 0 && covers.length === 0) return "";
|
|
201
|
+
return (0, import_node_crypto.createHash)("sha1").update(`${steps}\0${covers}`).digest("hex").slice(0, 16);
|
|
202
|
+
}
|
|
203
|
+
function jaccard(a, b) {
|
|
204
|
+
if (a.size === 0 && b.size === 0) return 1;
|
|
205
|
+
if (a.size === 0 || b.size === 0) return 0;
|
|
206
|
+
let intersection = 0;
|
|
207
|
+
for (const value of a) if (b.has(value)) intersection += 1;
|
|
208
|
+
return intersection / (a.size + b.size - intersection);
|
|
209
|
+
}
|
|
210
|
+
function stepTokens(steps) {
|
|
211
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
212
|
+
for (const step of steps) {
|
|
213
|
+
for (const token of tokenize(step.text)) tokens.add(token);
|
|
214
|
+
}
|
|
215
|
+
return tokens;
|
|
216
|
+
}
|
|
217
|
+
function behaviourSimilarity(a, b) {
|
|
218
|
+
const aSteps = stepTokens(a.steps);
|
|
219
|
+
const bSteps = stepTokens(b.steps);
|
|
220
|
+
if (aSteps.size === 0 || bSteps.size === 0) return 0;
|
|
221
|
+
const stepScore = jaccard(aSteps, bSteps);
|
|
222
|
+
const titleScore = jaccard(new Set(tokenize(a.scenario)), new Set(tokenize(b.scenario)));
|
|
223
|
+
return stepScore * 0.7 + titleScore * 0.3;
|
|
224
|
+
}
|
|
190
225
|
|
|
191
226
|
// src/converters/acl/steps.ts
|
|
192
227
|
function deriveStepResults(steps, scenarioStatus, error) {
|
|
@@ -16808,6 +16843,10 @@ function titleFor(kind) {
|
|
|
16808
16843
|
return "Added";
|
|
16809
16844
|
case "removed":
|
|
16810
16845
|
return "Removed";
|
|
16846
|
+
case "renamed":
|
|
16847
|
+
return "Renamed";
|
|
16848
|
+
case "moved":
|
|
16849
|
+
return "Moved";
|
|
16811
16850
|
case "changed":
|
|
16812
16851
|
return "Changed";
|
|
16813
16852
|
default:
|
|
@@ -16856,6 +16895,8 @@ function createPrCommentSummary(diff, maxScenarios = 10) {
|
|
|
16856
16895
|
addSection(lines, diff, "fixed", maxScenarios);
|
|
16857
16896
|
addSection(lines, diff, "added", maxScenarios);
|
|
16858
16897
|
addSection(lines, diff, "removed", maxScenarios);
|
|
16898
|
+
addSection(lines, diff, "renamed", maxScenarios);
|
|
16899
|
+
addSection(lines, diff, "moved", maxScenarios);
|
|
16859
16900
|
addSection(lines, diff, "changed", maxScenarios);
|
|
16860
16901
|
return lines.join("\n").trimEnd();
|
|
16861
16902
|
}
|
|
@@ -16902,8 +16943,10 @@ function sortDiffs(scenarios) {
|
|
|
16902
16943
|
fixed: 1,
|
|
16903
16944
|
added: 2,
|
|
16904
16945
|
removed: 3,
|
|
16905
|
-
|
|
16906
|
-
|
|
16946
|
+
renamed: 4,
|
|
16947
|
+
moved: 5,
|
|
16948
|
+
changed: 6,
|
|
16949
|
+
unchanged: 7
|
|
16907
16950
|
};
|
|
16908
16951
|
return [...scenarios].sort((a, b) => {
|
|
16909
16952
|
if (rank[a.kind] !== rank[b.kind]) {
|
|
@@ -16918,69 +16961,108 @@ function sortDiffs(scenarios) {
|
|
|
16918
16961
|
return a.scenario.localeCompare(b.scenario);
|
|
16919
16962
|
});
|
|
16920
16963
|
}
|
|
16964
|
+
var SIMILARITY_THRESHOLD = 0.75;
|
|
16965
|
+
function identityInput(tc) {
|
|
16966
|
+
return {
|
|
16967
|
+
scenario: tc.story.scenario,
|
|
16968
|
+
sourceFile: tc.sourceFile,
|
|
16969
|
+
steps: tc.story.steps,
|
|
16970
|
+
covers: tc.story.covers
|
|
16971
|
+
};
|
|
16972
|
+
}
|
|
16973
|
+
function changedFieldsOf(flags) {
|
|
16974
|
+
return Object.entries(flags).filter(([, changed]) => changed).map(([field]) => field);
|
|
16975
|
+
}
|
|
16976
|
+
function allChangedFlags(errorMessage) {
|
|
16977
|
+
return {
|
|
16978
|
+
status: true,
|
|
16979
|
+
steps: true,
|
|
16980
|
+
docs: true,
|
|
16981
|
+
tags: true,
|
|
16982
|
+
tickets: true,
|
|
16983
|
+
source: true,
|
|
16984
|
+
duration: true,
|
|
16985
|
+
attachments: true,
|
|
16986
|
+
error: Boolean(errorMessage),
|
|
16987
|
+
titlePath: true
|
|
16988
|
+
};
|
|
16989
|
+
}
|
|
16990
|
+
function matchIdentities(removed, added) {
|
|
16991
|
+
const pairs = [];
|
|
16992
|
+
const remainingRemoved = new Set(removed);
|
|
16993
|
+
const remainingAdded = new Set(added);
|
|
16994
|
+
const removedByFp = /* @__PURE__ */ new Map();
|
|
16995
|
+
const addedByFp = /* @__PURE__ */ new Map();
|
|
16996
|
+
for (const tc of remainingRemoved) {
|
|
16997
|
+
const fp = behaviourFingerprint(identityInput(tc));
|
|
16998
|
+
if (fp === "") continue;
|
|
16999
|
+
(removedByFp.get(fp) ?? removedByFp.set(fp, []).get(fp)).push(tc);
|
|
17000
|
+
}
|
|
17001
|
+
for (const tc of remainingAdded) {
|
|
17002
|
+
const fp = behaviourFingerprint(identityInput(tc));
|
|
17003
|
+
if (fp === "") continue;
|
|
17004
|
+
(addedByFp.get(fp) ?? addedByFp.set(fp, []).get(fp)).push(tc);
|
|
17005
|
+
}
|
|
17006
|
+
for (const [fp, removedGroup] of removedByFp) {
|
|
17007
|
+
const addedGroup = addedByFp.get(fp);
|
|
17008
|
+
if (removedGroup.length === 1 && addedGroup && addedGroup.length === 1) {
|
|
17009
|
+
pairs.push({ before: removedGroup[0], after: addedGroup[0], confidence: 1, matchedBy: "fingerprint" });
|
|
17010
|
+
remainingRemoved.delete(removedGroup[0]);
|
|
17011
|
+
remainingAdded.delete(addedGroup[0]);
|
|
17012
|
+
}
|
|
17013
|
+
}
|
|
17014
|
+
for (const before of [...remainingRemoved]) {
|
|
17015
|
+
let best;
|
|
17016
|
+
let bestScore = 0;
|
|
17017
|
+
let tied = false;
|
|
17018
|
+
for (const after of remainingAdded) {
|
|
17019
|
+
const score = behaviourSimilarity(identityInput(before), identityInput(after));
|
|
17020
|
+
if (score > bestScore) {
|
|
17021
|
+
bestScore = score;
|
|
17022
|
+
best = after;
|
|
17023
|
+
tied = false;
|
|
17024
|
+
} else if (score === bestScore) {
|
|
17025
|
+
tied = true;
|
|
17026
|
+
}
|
|
17027
|
+
}
|
|
17028
|
+
if (best && !tied && bestScore >= SIMILARITY_THRESHOLD) {
|
|
17029
|
+
pairs.push({ before, after: best, confidence: Math.round(bestScore * 100) / 100, matchedBy: "similarity" });
|
|
17030
|
+
remainingRemoved.delete(before);
|
|
17031
|
+
remainingAdded.delete(best);
|
|
17032
|
+
}
|
|
17033
|
+
}
|
|
17034
|
+
return {
|
|
17035
|
+
pairs,
|
|
17036
|
+
unmatchedRemoved: [...remainingRemoved],
|
|
17037
|
+
unmatchedAdded: [...remainingAdded]
|
|
17038
|
+
};
|
|
17039
|
+
}
|
|
17040
|
+
function identityKind(before, after) {
|
|
17041
|
+
const titleChanged = before.story.scenario !== after.story.scenario;
|
|
17042
|
+
const fileChanged = before.sourceFile !== after.sourceFile;
|
|
17043
|
+
return fileChanged && !titleChanged ? "moved" : "renamed";
|
|
17044
|
+
}
|
|
16921
17045
|
function diffRuns(baseline, current) {
|
|
16922
17046
|
const baselineById = new Map(baseline.testCases.map((tc) => [tc.id, tc]));
|
|
16923
17047
|
const currentById = new Map(current.testCases.map((tc) => [tc.id, tc]));
|
|
16924
17048
|
const ids = /* @__PURE__ */ new Set([...baselineById.keys(), ...currentById.keys()]);
|
|
16925
17049
|
const scenarios = [];
|
|
17050
|
+
const removedCases = [];
|
|
17051
|
+
const addedCases = [];
|
|
16926
17052
|
for (const id of ids) {
|
|
16927
17053
|
const before = baselineById.get(id);
|
|
16928
17054
|
const after = currentById.get(id);
|
|
16929
17055
|
if (!before && after) {
|
|
16930
|
-
|
|
16931
|
-
status: true,
|
|
16932
|
-
steps: true,
|
|
16933
|
-
docs: true,
|
|
16934
|
-
tags: true,
|
|
16935
|
-
tickets: true,
|
|
16936
|
-
source: true,
|
|
16937
|
-
duration: true,
|
|
16938
|
-
attachments: true,
|
|
16939
|
-
error: Boolean(after.errorMessage),
|
|
16940
|
-
titlePath: true
|
|
16941
|
-
};
|
|
16942
|
-
scenarios.push({
|
|
16943
|
-
kind: "added",
|
|
16944
|
-
id,
|
|
16945
|
-
scenario: after.story.scenario,
|
|
16946
|
-
sourceFile: after.sourceFile,
|
|
16947
|
-
sourceLine: after.sourceLine,
|
|
16948
|
-
current: toScenarioSnapshot(after),
|
|
16949
|
-
flags: flags2,
|
|
16950
|
-
changedFields: Object.entries(flags2).filter(([, changed]) => changed).map(([field]) => field)
|
|
16951
|
-
});
|
|
17056
|
+
addedCases.push(after);
|
|
16952
17057
|
continue;
|
|
16953
17058
|
}
|
|
16954
17059
|
if (before && !after) {
|
|
16955
|
-
|
|
16956
|
-
status: true,
|
|
16957
|
-
steps: true,
|
|
16958
|
-
docs: true,
|
|
16959
|
-
tags: true,
|
|
16960
|
-
tickets: true,
|
|
16961
|
-
source: true,
|
|
16962
|
-
duration: true,
|
|
16963
|
-
attachments: true,
|
|
16964
|
-
error: Boolean(before.errorMessage),
|
|
16965
|
-
titlePath: true
|
|
16966
|
-
};
|
|
16967
|
-
scenarios.push({
|
|
16968
|
-
kind: "removed",
|
|
16969
|
-
id,
|
|
16970
|
-
scenario: before.story.scenario,
|
|
16971
|
-
sourceFile: before.sourceFile,
|
|
16972
|
-
sourceLine: before.sourceLine,
|
|
16973
|
-
baseline: toScenarioSnapshot(before),
|
|
16974
|
-
flags: flags2,
|
|
16975
|
-
changedFields: Object.entries(flags2).filter(([, changed]) => changed).map(([field]) => field)
|
|
16976
|
-
});
|
|
16977
|
-
continue;
|
|
16978
|
-
}
|
|
16979
|
-
if (!before || !after) {
|
|
17060
|
+
removedCases.push(before);
|
|
16980
17061
|
continue;
|
|
16981
17062
|
}
|
|
17063
|
+
if (!before || !after) continue;
|
|
16982
17064
|
const flags = buildFlags(before, after);
|
|
16983
|
-
const changedFields =
|
|
17065
|
+
const changedFields = changedFieldsOf(flags);
|
|
16984
17066
|
const kind = getPrimaryKind(before, after, changedFields.length > 0);
|
|
16985
17067
|
scenarios.push({
|
|
16986
17068
|
kind,
|
|
@@ -16995,12 +17077,59 @@ function diffRuns(baseline, current) {
|
|
|
16995
17077
|
durationDeltaMs: after.durationMs - before.durationMs
|
|
16996
17078
|
});
|
|
16997
17079
|
}
|
|
17080
|
+
const { pairs, unmatchedRemoved, unmatchedAdded } = matchIdentities(removedCases, addedCases);
|
|
17081
|
+
for (const { before, after, confidence, matchedBy } of pairs) {
|
|
17082
|
+
const flags = buildFlags(before, after);
|
|
17083
|
+
scenarios.push({
|
|
17084
|
+
kind: identityKind(before, after),
|
|
17085
|
+
id: after.id,
|
|
17086
|
+
previousId: before.id,
|
|
17087
|
+
scenario: after.story.scenario,
|
|
17088
|
+
sourceFile: after.sourceFile,
|
|
17089
|
+
sourceLine: after.sourceLine,
|
|
17090
|
+
baseline: toScenarioSnapshot(before),
|
|
17091
|
+
current: toScenarioSnapshot(after),
|
|
17092
|
+
flags,
|
|
17093
|
+
changedFields: changedFieldsOf(flags),
|
|
17094
|
+
durationDeltaMs: after.durationMs - before.durationMs,
|
|
17095
|
+
matchConfidence: confidence,
|
|
17096
|
+
matchedBy
|
|
17097
|
+
});
|
|
17098
|
+
}
|
|
17099
|
+
for (const after of unmatchedAdded) {
|
|
17100
|
+
const flags = allChangedFlags(after.errorMessage);
|
|
17101
|
+
scenarios.push({
|
|
17102
|
+
kind: "added",
|
|
17103
|
+
id: after.id,
|
|
17104
|
+
scenario: after.story.scenario,
|
|
17105
|
+
sourceFile: after.sourceFile,
|
|
17106
|
+
sourceLine: after.sourceLine,
|
|
17107
|
+
current: toScenarioSnapshot(after),
|
|
17108
|
+
flags,
|
|
17109
|
+
changedFields: changedFieldsOf(flags)
|
|
17110
|
+
});
|
|
17111
|
+
}
|
|
17112
|
+
for (const before of unmatchedRemoved) {
|
|
17113
|
+
const flags = allChangedFlags(before.errorMessage);
|
|
17114
|
+
scenarios.push({
|
|
17115
|
+
kind: "removed",
|
|
17116
|
+
id: before.id,
|
|
17117
|
+
scenario: before.story.scenario,
|
|
17118
|
+
sourceFile: before.sourceFile,
|
|
17119
|
+
sourceLine: before.sourceLine,
|
|
17120
|
+
baseline: toScenarioSnapshot(before),
|
|
17121
|
+
flags,
|
|
17122
|
+
changedFields: changedFieldsOf(flags)
|
|
17123
|
+
});
|
|
17124
|
+
}
|
|
16998
17125
|
const sorted = sortDiffs(scenarios);
|
|
16999
17126
|
const summary = {
|
|
17000
17127
|
totalBaseline: baseline.testCases.length,
|
|
17001
17128
|
totalCurrent: current.testCases.length,
|
|
17002
17129
|
added: sorted.filter((s) => s.kind === "added").length,
|
|
17003
17130
|
removed: sorted.filter((s) => s.kind === "removed").length,
|
|
17131
|
+
renamed: sorted.filter((s) => s.kind === "renamed").length,
|
|
17132
|
+
moved: sorted.filter((s) => s.kind === "moved").length,
|
|
17004
17133
|
changed: sorted.filter((s) => s.kind === "changed").length,
|
|
17005
17134
|
regressed: sorted.filter((s) => s.kind === "regressed").length,
|
|
17006
17135
|
fixed: sorted.filter((s) => s.kind === "fixed").length,
|
|
@@ -17028,6 +17157,10 @@ function statusLabel(kind) {
|
|
|
17028
17157
|
return "Added";
|
|
17029
17158
|
case "removed":
|
|
17030
17159
|
return "Removed";
|
|
17160
|
+
case "renamed":
|
|
17161
|
+
return "Renamed";
|
|
17162
|
+
case "moved":
|
|
17163
|
+
return "Moved";
|
|
17031
17164
|
case "changed":
|
|
17032
17165
|
return "Changed";
|
|
17033
17166
|
default:
|
|
@@ -17353,6 +17486,8 @@ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', fun
|
|
|
17353
17486
|
<div class="summary-card"><strong>${diff.summary.fixed}</strong><span>Fixed</span></div>
|
|
17354
17487
|
<div class="summary-card"><strong>${diff.summary.added}</strong><span>Added</span></div>
|
|
17355
17488
|
<div class="summary-card"><strong>${diff.summary.removed}</strong><span>Removed</span></div>
|
|
17489
|
+
<div class="summary-card"><strong>${diff.summary.renamed}</strong><span>Renamed</span></div>
|
|
17490
|
+
<div class="summary-card"><strong>${diff.summary.moved}</strong><span>Moved</span></div>
|
|
17356
17491
|
<div class="summary-card"><strong>${diff.summary.changed}</strong><span>Changed</span></div>
|
|
17357
17492
|
<div class="summary-card"><strong>${diff.summary.unchanged}</strong><span>Unchanged</span></div>
|
|
17358
17493
|
</section>
|
|
@@ -17413,6 +17548,10 @@ function formatStatus(kind) {
|
|
|
17413
17548
|
return "Added";
|
|
17414
17549
|
case "removed":
|
|
17415
17550
|
return "Removed";
|
|
17551
|
+
case "renamed":
|
|
17552
|
+
return "Renamed";
|
|
17553
|
+
case "moved":
|
|
17554
|
+
return "Moved";
|
|
17416
17555
|
case "changed":
|
|
17417
17556
|
return "Changed";
|
|
17418
17557
|
default:
|
|
@@ -17565,13 +17704,13 @@ var RunDiffMarkdownFormatter = class {
|
|
|
17565
17704
|
lines.push("No regressions or fixes detected. Remaining changes are neutral.");
|
|
17566
17705
|
}
|
|
17567
17706
|
lines.push("");
|
|
17568
|
-
lines.push("| Added | Removed | Regressed | Fixed | Changed | Unchanged |");
|
|
17569
|
-
lines.push("| ---: | ---: | ---: | ---: | ---: | ---: |");
|
|
17707
|
+
lines.push("| Added | Removed | Renamed | Moved | Regressed | Fixed | Changed | Unchanged |");
|
|
17708
|
+
lines.push("| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |");
|
|
17570
17709
|
lines.push(
|
|
17571
|
-
`| ${diff.summary.added} | ${diff.summary.removed} | ${diff.summary.regressed} | ${diff.summary.fixed} | ${diff.summary.changed} | ${diff.summary.unchanged} |`
|
|
17710
|
+
`| ${diff.summary.added} | ${diff.summary.removed} | ${diff.summary.renamed} | ${diff.summary.moved} | ${diff.summary.regressed} | ${diff.summary.fixed} | ${diff.summary.changed} | ${diff.summary.unchanged} |`
|
|
17572
17711
|
);
|
|
17573
17712
|
lines.push("");
|
|
17574
|
-
for (const kind of ["regressed", "fixed", "added", "removed", "changed"]) {
|
|
17713
|
+
for (const kind of ["regressed", "fixed", "added", "removed", "renamed", "moved", "changed"]) {
|
|
17575
17714
|
const scenarios = diff.scenarios.filter((scenario) => scenario.kind === kind);
|
|
17576
17715
|
if (scenarios.length === 0) continue;
|
|
17577
17716
|
lines.push(`## ${formatStatus(kind)} (${scenarios.length})`);
|
|
@@ -19003,7 +19142,10 @@ function diffStoryReports(baseline, current) {
|
|
|
19003
19142
|
};
|
|
19004
19143
|
});
|
|
19005
19144
|
const summary = { added: 0, removed: 0, regressed: 0, fixed: 0, changed: 0, unchanged: 0 };
|
|
19006
|
-
for (const s of scenarios)
|
|
19145
|
+
for (const s of scenarios) {
|
|
19146
|
+
if (s.kind === "renamed" || s.kind === "moved") continue;
|
|
19147
|
+
summary[s.kind] += 1;
|
|
19148
|
+
}
|
|
19007
19149
|
return { schemaVersion: "1.0", summary, scenarios };
|
|
19008
19150
|
}
|
|
19009
19151
|
|