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/cli.js
CHANGED
|
@@ -702,6 +702,41 @@ function generateFeatureId(uri) {
|
|
|
702
702
|
function generateScenarioId(featureId, scenarioName) {
|
|
703
703
|
return `${featureId};${slugify(scenarioName)}`;
|
|
704
704
|
}
|
|
705
|
+
function normalizeText(text2) {
|
|
706
|
+
return text2.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
|
|
707
|
+
}
|
|
708
|
+
function tokenize(text2) {
|
|
709
|
+
const normalized = normalizeText(text2);
|
|
710
|
+
return normalized.length === 0 ? [] : normalized.split(" ");
|
|
711
|
+
}
|
|
712
|
+
function behaviourFingerprint(input) {
|
|
713
|
+
const steps = input.steps.map((step) => `${step.keyword.toLowerCase()}:${normalizeText(step.text)}`).join("\n");
|
|
714
|
+
const covers = [...input.covers ?? []].map((path18) => path18.trim()).filter(Boolean).sort().join(",");
|
|
715
|
+
if (steps.length === 0 && covers.length === 0) return "";
|
|
716
|
+
return createHash("sha1").update(`${steps}\0${covers}`).digest("hex").slice(0, 16);
|
|
717
|
+
}
|
|
718
|
+
function jaccard(a, b) {
|
|
719
|
+
if (a.size === 0 && b.size === 0) return 1;
|
|
720
|
+
if (a.size === 0 || b.size === 0) return 0;
|
|
721
|
+
let intersection = 0;
|
|
722
|
+
for (const value of a) if (b.has(value)) intersection += 1;
|
|
723
|
+
return intersection / (a.size + b.size - intersection);
|
|
724
|
+
}
|
|
725
|
+
function stepTokens(steps) {
|
|
726
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
727
|
+
for (const step of steps) {
|
|
728
|
+
for (const token of tokenize(step.text)) tokens.add(token);
|
|
729
|
+
}
|
|
730
|
+
return tokens;
|
|
731
|
+
}
|
|
732
|
+
function behaviourSimilarity(a, b) {
|
|
733
|
+
const aSteps = stepTokens(a.steps);
|
|
734
|
+
const bSteps = stepTokens(b.steps);
|
|
735
|
+
if (aSteps.size === 0 || bSteps.size === 0) return 0;
|
|
736
|
+
const stepScore = jaccard(aSteps, bSteps);
|
|
737
|
+
const titleScore = jaccard(new Set(tokenize(a.scenario)), new Set(tokenize(b.scenario)));
|
|
738
|
+
return stepScore * 0.7 + titleScore * 0.3;
|
|
739
|
+
}
|
|
705
740
|
|
|
706
741
|
// src/converters/acl/steps.ts
|
|
707
742
|
function deriveStepResults(steps, scenarioStatus, error) {
|
|
@@ -17401,6 +17436,10 @@ function titleFor(kind) {
|
|
|
17401
17436
|
return "Added";
|
|
17402
17437
|
case "removed":
|
|
17403
17438
|
return "Removed";
|
|
17439
|
+
case "renamed":
|
|
17440
|
+
return "Renamed";
|
|
17441
|
+
case "moved":
|
|
17442
|
+
return "Moved";
|
|
17404
17443
|
case "changed":
|
|
17405
17444
|
return "Changed";
|
|
17406
17445
|
default:
|
|
@@ -17449,6 +17488,8 @@ function createPrCommentSummary(diff, maxScenarios = 10) {
|
|
|
17449
17488
|
addSection(lines, diff, "fixed", maxScenarios);
|
|
17450
17489
|
addSection(lines, diff, "added", maxScenarios);
|
|
17451
17490
|
addSection(lines, diff, "removed", maxScenarios);
|
|
17491
|
+
addSection(lines, diff, "renamed", maxScenarios);
|
|
17492
|
+
addSection(lines, diff, "moved", maxScenarios);
|
|
17452
17493
|
addSection(lines, diff, "changed", maxScenarios);
|
|
17453
17494
|
return lines.join("\n").trimEnd();
|
|
17454
17495
|
}
|
|
@@ -17525,8 +17566,10 @@ function sortDiffs(scenarios) {
|
|
|
17525
17566
|
fixed: 1,
|
|
17526
17567
|
added: 2,
|
|
17527
17568
|
removed: 3,
|
|
17528
|
-
|
|
17529
|
-
|
|
17569
|
+
renamed: 4,
|
|
17570
|
+
moved: 5,
|
|
17571
|
+
changed: 6,
|
|
17572
|
+
unchanged: 7
|
|
17530
17573
|
};
|
|
17531
17574
|
return [...scenarios].sort((a, b) => {
|
|
17532
17575
|
if (rank[a.kind] !== rank[b.kind]) {
|
|
@@ -17541,69 +17584,108 @@ function sortDiffs(scenarios) {
|
|
|
17541
17584
|
return a.scenario.localeCompare(b.scenario);
|
|
17542
17585
|
});
|
|
17543
17586
|
}
|
|
17587
|
+
var SIMILARITY_THRESHOLD = 0.75;
|
|
17588
|
+
function identityInput(tc) {
|
|
17589
|
+
return {
|
|
17590
|
+
scenario: tc.story.scenario,
|
|
17591
|
+
sourceFile: tc.sourceFile,
|
|
17592
|
+
steps: tc.story.steps,
|
|
17593
|
+
covers: tc.story.covers
|
|
17594
|
+
};
|
|
17595
|
+
}
|
|
17596
|
+
function changedFieldsOf(flags) {
|
|
17597
|
+
return Object.entries(flags).filter(([, changed]) => changed).map(([field]) => field);
|
|
17598
|
+
}
|
|
17599
|
+
function allChangedFlags(errorMessage) {
|
|
17600
|
+
return {
|
|
17601
|
+
status: true,
|
|
17602
|
+
steps: true,
|
|
17603
|
+
docs: true,
|
|
17604
|
+
tags: true,
|
|
17605
|
+
tickets: true,
|
|
17606
|
+
source: true,
|
|
17607
|
+
duration: true,
|
|
17608
|
+
attachments: true,
|
|
17609
|
+
error: Boolean(errorMessage),
|
|
17610
|
+
titlePath: true
|
|
17611
|
+
};
|
|
17612
|
+
}
|
|
17613
|
+
function matchIdentities(removed, added) {
|
|
17614
|
+
const pairs = [];
|
|
17615
|
+
const remainingRemoved = new Set(removed);
|
|
17616
|
+
const remainingAdded = new Set(added);
|
|
17617
|
+
const removedByFp = /* @__PURE__ */ new Map();
|
|
17618
|
+
const addedByFp = /* @__PURE__ */ new Map();
|
|
17619
|
+
for (const tc of remainingRemoved) {
|
|
17620
|
+
const fp = behaviourFingerprint(identityInput(tc));
|
|
17621
|
+
if (fp === "") continue;
|
|
17622
|
+
(removedByFp.get(fp) ?? removedByFp.set(fp, []).get(fp)).push(tc);
|
|
17623
|
+
}
|
|
17624
|
+
for (const tc of remainingAdded) {
|
|
17625
|
+
const fp = behaviourFingerprint(identityInput(tc));
|
|
17626
|
+
if (fp === "") continue;
|
|
17627
|
+
(addedByFp.get(fp) ?? addedByFp.set(fp, []).get(fp)).push(tc);
|
|
17628
|
+
}
|
|
17629
|
+
for (const [fp, removedGroup] of removedByFp) {
|
|
17630
|
+
const addedGroup = addedByFp.get(fp);
|
|
17631
|
+
if (removedGroup.length === 1 && addedGroup && addedGroup.length === 1) {
|
|
17632
|
+
pairs.push({ before: removedGroup[0], after: addedGroup[0], confidence: 1, matchedBy: "fingerprint" });
|
|
17633
|
+
remainingRemoved.delete(removedGroup[0]);
|
|
17634
|
+
remainingAdded.delete(addedGroup[0]);
|
|
17635
|
+
}
|
|
17636
|
+
}
|
|
17637
|
+
for (const before of [...remainingRemoved]) {
|
|
17638
|
+
let best;
|
|
17639
|
+
let bestScore = 0;
|
|
17640
|
+
let tied = false;
|
|
17641
|
+
for (const after of remainingAdded) {
|
|
17642
|
+
const score = behaviourSimilarity(identityInput(before), identityInput(after));
|
|
17643
|
+
if (score > bestScore) {
|
|
17644
|
+
bestScore = score;
|
|
17645
|
+
best = after;
|
|
17646
|
+
tied = false;
|
|
17647
|
+
} else if (score === bestScore) {
|
|
17648
|
+
tied = true;
|
|
17649
|
+
}
|
|
17650
|
+
}
|
|
17651
|
+
if (best && !tied && bestScore >= SIMILARITY_THRESHOLD) {
|
|
17652
|
+
pairs.push({ before, after: best, confidence: Math.round(bestScore * 100) / 100, matchedBy: "similarity" });
|
|
17653
|
+
remainingRemoved.delete(before);
|
|
17654
|
+
remainingAdded.delete(best);
|
|
17655
|
+
}
|
|
17656
|
+
}
|
|
17657
|
+
return {
|
|
17658
|
+
pairs,
|
|
17659
|
+
unmatchedRemoved: [...remainingRemoved],
|
|
17660
|
+
unmatchedAdded: [...remainingAdded]
|
|
17661
|
+
};
|
|
17662
|
+
}
|
|
17663
|
+
function identityKind(before, after) {
|
|
17664
|
+
const titleChanged = before.story.scenario !== after.story.scenario;
|
|
17665
|
+
const fileChanged = before.sourceFile !== after.sourceFile;
|
|
17666
|
+
return fileChanged && !titleChanged ? "moved" : "renamed";
|
|
17667
|
+
}
|
|
17544
17668
|
function diffRuns(baseline, current) {
|
|
17545
17669
|
const baselineById = new Map(baseline.testCases.map((tc) => [tc.id, tc]));
|
|
17546
17670
|
const currentById = new Map(current.testCases.map((tc) => [tc.id, tc]));
|
|
17547
17671
|
const ids = /* @__PURE__ */ new Set([...baselineById.keys(), ...currentById.keys()]);
|
|
17548
17672
|
const scenarios = [];
|
|
17673
|
+
const removedCases = [];
|
|
17674
|
+
const addedCases = [];
|
|
17549
17675
|
for (const id of ids) {
|
|
17550
17676
|
const before = baselineById.get(id);
|
|
17551
17677
|
const after = currentById.get(id);
|
|
17552
17678
|
if (!before && after) {
|
|
17553
|
-
|
|
17554
|
-
status: true,
|
|
17555
|
-
steps: true,
|
|
17556
|
-
docs: true,
|
|
17557
|
-
tags: true,
|
|
17558
|
-
tickets: true,
|
|
17559
|
-
source: true,
|
|
17560
|
-
duration: true,
|
|
17561
|
-
attachments: true,
|
|
17562
|
-
error: Boolean(after.errorMessage),
|
|
17563
|
-
titlePath: true
|
|
17564
|
-
};
|
|
17565
|
-
scenarios.push({
|
|
17566
|
-
kind: "added",
|
|
17567
|
-
id,
|
|
17568
|
-
scenario: after.story.scenario,
|
|
17569
|
-
sourceFile: after.sourceFile,
|
|
17570
|
-
sourceLine: after.sourceLine,
|
|
17571
|
-
current: toScenarioSnapshot(after),
|
|
17572
|
-
flags: flags2,
|
|
17573
|
-
changedFields: Object.entries(flags2).filter(([, changed]) => changed).map(([field]) => field)
|
|
17574
|
-
});
|
|
17679
|
+
addedCases.push(after);
|
|
17575
17680
|
continue;
|
|
17576
17681
|
}
|
|
17577
17682
|
if (before && !after) {
|
|
17578
|
-
|
|
17579
|
-
status: true,
|
|
17580
|
-
steps: true,
|
|
17581
|
-
docs: true,
|
|
17582
|
-
tags: true,
|
|
17583
|
-
tickets: true,
|
|
17584
|
-
source: true,
|
|
17585
|
-
duration: true,
|
|
17586
|
-
attachments: true,
|
|
17587
|
-
error: Boolean(before.errorMessage),
|
|
17588
|
-
titlePath: true
|
|
17589
|
-
};
|
|
17590
|
-
scenarios.push({
|
|
17591
|
-
kind: "removed",
|
|
17592
|
-
id,
|
|
17593
|
-
scenario: before.story.scenario,
|
|
17594
|
-
sourceFile: before.sourceFile,
|
|
17595
|
-
sourceLine: before.sourceLine,
|
|
17596
|
-
baseline: toScenarioSnapshot(before),
|
|
17597
|
-
flags: flags2,
|
|
17598
|
-
changedFields: Object.entries(flags2).filter(([, changed]) => changed).map(([field]) => field)
|
|
17599
|
-
});
|
|
17600
|
-
continue;
|
|
17601
|
-
}
|
|
17602
|
-
if (!before || !after) {
|
|
17683
|
+
removedCases.push(before);
|
|
17603
17684
|
continue;
|
|
17604
17685
|
}
|
|
17686
|
+
if (!before || !after) continue;
|
|
17605
17687
|
const flags = buildFlags(before, after);
|
|
17606
|
-
const changedFields =
|
|
17688
|
+
const changedFields = changedFieldsOf(flags);
|
|
17607
17689
|
const kind = getPrimaryKind(before, after, changedFields.length > 0);
|
|
17608
17690
|
scenarios.push({
|
|
17609
17691
|
kind,
|
|
@@ -17618,12 +17700,59 @@ function diffRuns(baseline, current) {
|
|
|
17618
17700
|
durationDeltaMs: after.durationMs - before.durationMs
|
|
17619
17701
|
});
|
|
17620
17702
|
}
|
|
17703
|
+
const { pairs, unmatchedRemoved, unmatchedAdded } = matchIdentities(removedCases, addedCases);
|
|
17704
|
+
for (const { before, after, confidence, matchedBy } of pairs) {
|
|
17705
|
+
const flags = buildFlags(before, after);
|
|
17706
|
+
scenarios.push({
|
|
17707
|
+
kind: identityKind(before, after),
|
|
17708
|
+
id: after.id,
|
|
17709
|
+
previousId: before.id,
|
|
17710
|
+
scenario: after.story.scenario,
|
|
17711
|
+
sourceFile: after.sourceFile,
|
|
17712
|
+
sourceLine: after.sourceLine,
|
|
17713
|
+
baseline: toScenarioSnapshot(before),
|
|
17714
|
+
current: toScenarioSnapshot(after),
|
|
17715
|
+
flags,
|
|
17716
|
+
changedFields: changedFieldsOf(flags),
|
|
17717
|
+
durationDeltaMs: after.durationMs - before.durationMs,
|
|
17718
|
+
matchConfidence: confidence,
|
|
17719
|
+
matchedBy
|
|
17720
|
+
});
|
|
17721
|
+
}
|
|
17722
|
+
for (const after of unmatchedAdded) {
|
|
17723
|
+
const flags = allChangedFlags(after.errorMessage);
|
|
17724
|
+
scenarios.push({
|
|
17725
|
+
kind: "added",
|
|
17726
|
+
id: after.id,
|
|
17727
|
+
scenario: after.story.scenario,
|
|
17728
|
+
sourceFile: after.sourceFile,
|
|
17729
|
+
sourceLine: after.sourceLine,
|
|
17730
|
+
current: toScenarioSnapshot(after),
|
|
17731
|
+
flags,
|
|
17732
|
+
changedFields: changedFieldsOf(flags)
|
|
17733
|
+
});
|
|
17734
|
+
}
|
|
17735
|
+
for (const before of unmatchedRemoved) {
|
|
17736
|
+
const flags = allChangedFlags(before.errorMessage);
|
|
17737
|
+
scenarios.push({
|
|
17738
|
+
kind: "removed",
|
|
17739
|
+
id: before.id,
|
|
17740
|
+
scenario: before.story.scenario,
|
|
17741
|
+
sourceFile: before.sourceFile,
|
|
17742
|
+
sourceLine: before.sourceLine,
|
|
17743
|
+
baseline: toScenarioSnapshot(before),
|
|
17744
|
+
flags,
|
|
17745
|
+
changedFields: changedFieldsOf(flags)
|
|
17746
|
+
});
|
|
17747
|
+
}
|
|
17621
17748
|
const sorted = sortDiffs(scenarios);
|
|
17622
17749
|
const summary = {
|
|
17623
17750
|
totalBaseline: baseline.testCases.length,
|
|
17624
17751
|
totalCurrent: current.testCases.length,
|
|
17625
17752
|
added: sorted.filter((s) => s.kind === "added").length,
|
|
17626
17753
|
removed: sorted.filter((s) => s.kind === "removed").length,
|
|
17754
|
+
renamed: sorted.filter((s) => s.kind === "renamed").length,
|
|
17755
|
+
moved: sorted.filter((s) => s.kind === "moved").length,
|
|
17627
17756
|
changed: sorted.filter((s) => s.kind === "changed").length,
|
|
17628
17757
|
regressed: sorted.filter((s) => s.kind === "regressed").length,
|
|
17629
17758
|
fixed: sorted.filter((s) => s.kind === "fixed").length,
|
|
@@ -17651,6 +17780,10 @@ function statusLabel(kind) {
|
|
|
17651
17780
|
return "Added";
|
|
17652
17781
|
case "removed":
|
|
17653
17782
|
return "Removed";
|
|
17783
|
+
case "renamed":
|
|
17784
|
+
return "Renamed";
|
|
17785
|
+
case "moved":
|
|
17786
|
+
return "Moved";
|
|
17654
17787
|
case "changed":
|
|
17655
17788
|
return "Changed";
|
|
17656
17789
|
default:
|
|
@@ -17976,6 +18109,8 @@ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', fun
|
|
|
17976
18109
|
<div class="summary-card"><strong>${diff.summary.fixed}</strong><span>Fixed</span></div>
|
|
17977
18110
|
<div class="summary-card"><strong>${diff.summary.added}</strong><span>Added</span></div>
|
|
17978
18111
|
<div class="summary-card"><strong>${diff.summary.removed}</strong><span>Removed</span></div>
|
|
18112
|
+
<div class="summary-card"><strong>${diff.summary.renamed}</strong><span>Renamed</span></div>
|
|
18113
|
+
<div class="summary-card"><strong>${diff.summary.moved}</strong><span>Moved</span></div>
|
|
17979
18114
|
<div class="summary-card"><strong>${diff.summary.changed}</strong><span>Changed</span></div>
|
|
17980
18115
|
<div class="summary-card"><strong>${diff.summary.unchanged}</strong><span>Unchanged</span></div>
|
|
17981
18116
|
</section>
|
|
@@ -18036,6 +18171,10 @@ function formatStatus(kind) {
|
|
|
18036
18171
|
return "Added";
|
|
18037
18172
|
case "removed":
|
|
18038
18173
|
return "Removed";
|
|
18174
|
+
case "renamed":
|
|
18175
|
+
return "Renamed";
|
|
18176
|
+
case "moved":
|
|
18177
|
+
return "Moved";
|
|
18039
18178
|
case "changed":
|
|
18040
18179
|
return "Changed";
|
|
18041
18180
|
default:
|
|
@@ -18188,13 +18327,13 @@ var RunDiffMarkdownFormatter = class {
|
|
|
18188
18327
|
lines.push("No regressions or fixes detected. Remaining changes are neutral.");
|
|
18189
18328
|
}
|
|
18190
18329
|
lines.push("");
|
|
18191
|
-
lines.push("| Added | Removed | Regressed | Fixed | Changed | Unchanged |");
|
|
18192
|
-
lines.push("| ---: | ---: | ---: | ---: | ---: | ---: |");
|
|
18330
|
+
lines.push("| Added | Removed | Renamed | Moved | Regressed | Fixed | Changed | Unchanged |");
|
|
18331
|
+
lines.push("| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |");
|
|
18193
18332
|
lines.push(
|
|
18194
|
-
`| ${diff.summary.added} | ${diff.summary.removed} | ${diff.summary.regressed} | ${diff.summary.fixed} | ${diff.summary.changed} | ${diff.summary.unchanged} |`
|
|
18333
|
+
`| ${diff.summary.added} | ${diff.summary.removed} | ${diff.summary.renamed} | ${diff.summary.moved} | ${diff.summary.regressed} | ${diff.summary.fixed} | ${diff.summary.changed} | ${diff.summary.unchanged} |`
|
|
18195
18334
|
);
|
|
18196
18335
|
lines.push("");
|
|
18197
|
-
for (const kind of ["regressed", "fixed", "added", "removed", "changed"]) {
|
|
18336
|
+
for (const kind of ["regressed", "fixed", "added", "removed", "renamed", "moved", "changed"]) {
|
|
18198
18337
|
const scenarios = diff.scenarios.filter((scenario) => scenario.kind === kind);
|
|
18199
18338
|
if (scenarios.length === 0) continue;
|
|
18200
18339
|
lines.push(`## ${formatStatus(kind)} (${scenarios.length})`);
|
|
@@ -19143,7 +19282,10 @@ function diffStoryReports(baseline, current) {
|
|
|
19143
19282
|
};
|
|
19144
19283
|
});
|
|
19145
19284
|
const summary = { added: 0, removed: 0, regressed: 0, fixed: 0, changed: 0, unchanged: 0 };
|
|
19146
|
-
for (const s of scenarios)
|
|
19285
|
+
for (const s of scenarios) {
|
|
19286
|
+
if (s.kind === "renamed" || s.kind === "moved") continue;
|
|
19287
|
+
summary[s.kind] += 1;
|
|
19288
|
+
}
|
|
19147
19289
|
return { schemaVersion: "1.0", summary, scenarios };
|
|
19148
19290
|
}
|
|
19149
19291
|
|