executable-stories-formatters 0.7.4 → 0.7.6
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 +775 -32
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +766 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -2
- package/dist/index.d.ts +26 -2
- package/dist/index.js +765 -29
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -852,6 +852,7 @@ function applyAllFilters() {
|
|
|
852
852
|
});
|
|
853
853
|
|
|
854
854
|
updateFilterResults(visibleCount, totalCount);
|
|
855
|
+
syncTocVisibility();
|
|
855
856
|
writeUrlState();
|
|
856
857
|
}
|
|
857
858
|
|
|
@@ -868,13 +869,135 @@ function updateFilterResults(visible, total) {
|
|
|
868
869
|
if (tc) tc.textContent = total;
|
|
869
870
|
}
|
|
870
871
|
|
|
871
|
-
// Keyboard
|
|
872
|
+
// Keyboard navigation
|
|
873
|
+
var focusedScenarioIndex = -1;
|
|
874
|
+
|
|
875
|
+
function getVisibleScenarios() {
|
|
876
|
+
return Array.from(document.querySelectorAll('.scenario')).filter(function(s) {
|
|
877
|
+
return s.style.display !== 'none' && s.closest('.feature').style.display !== 'none';
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function focusScenario(index) {
|
|
882
|
+
var scenarios = getVisibleScenarios();
|
|
883
|
+
if (scenarios.length === 0) return;
|
|
884
|
+
|
|
885
|
+
// Remove previous focus
|
|
886
|
+
var prev = document.querySelector('.scenario-focused');
|
|
887
|
+
if (prev) prev.classList.remove('scenario-focused');
|
|
888
|
+
|
|
889
|
+
// Wrap around
|
|
890
|
+
if (index < 0) index = scenarios.length - 1;
|
|
891
|
+
if (index >= scenarios.length) index = 0;
|
|
892
|
+
focusedScenarioIndex = index;
|
|
893
|
+
|
|
894
|
+
var scenario = scenarios[index];
|
|
895
|
+
scenario.classList.add('scenario-focused');
|
|
896
|
+
scenario.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function showShortcutsOverlay() {
|
|
900
|
+
if (document.querySelector('.shortcuts-overlay')) return;
|
|
901
|
+
var overlay = document.createElement('div');
|
|
902
|
+
overlay.className = 'shortcuts-overlay';
|
|
903
|
+
overlay.innerHTML = '<div class="shortcuts-modal">' +
|
|
904
|
+
'<div class="shortcuts-title">Keyboard Shortcuts</div>' +
|
|
905
|
+
'<div class="shortcuts-grid">' +
|
|
906
|
+
'<kbd>j</kbd><span>Next scenario</span>' +
|
|
907
|
+
'<kbd>k</kbd><span>Previous scenario</span>' +
|
|
908
|
+
'<kbd>Enter</kbd><span>Expand/collapse scenario</span>' +
|
|
909
|
+
'<kbd>Escape</kbd><span>Collapse scenario / close</span>' +
|
|
910
|
+
'<kbd>/</kbd><span>Focus search</span>' +
|
|
911
|
+
'<kbd>?</kbd><span>Toggle this help</span>' +
|
|
912
|
+
'<kbd>e</kbd><span>Expand all</span>' +
|
|
913
|
+
'<kbd>c</kbd><span>Collapse all</span>' +
|
|
914
|
+
'<kbd>t</kbd><span>Toggle table of contents</span>' +
|
|
915
|
+
'</div></div>';
|
|
916
|
+
overlay.addEventListener('click', function(ev) {
|
|
917
|
+
if (ev.target === overlay) hideShortcutsOverlay();
|
|
918
|
+
});
|
|
919
|
+
document.body.appendChild(overlay);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function hideShortcutsOverlay() {
|
|
923
|
+
var overlay = document.querySelector('.shortcuts-overlay');
|
|
924
|
+
if (overlay) overlay.remove();
|
|
925
|
+
}
|
|
926
|
+
|
|
872
927
|
function initKeyboardShortcuts() {
|
|
873
928
|
document.addEventListener('keydown', function(e) {
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
929
|
+
var tag = e.target.tagName;
|
|
930
|
+
if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') {
|
|
931
|
+
if (e.key === 'Escape') {
|
|
932
|
+
e.target.blur();
|
|
933
|
+
if (e.target.classList.contains('search-input')) {
|
|
934
|
+
e.target.value = '';
|
|
935
|
+
applyAllFilters();
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
942
|
+
|
|
943
|
+
switch (e.key) {
|
|
944
|
+
case 'j':
|
|
945
|
+
e.preventDefault();
|
|
946
|
+
focusScenario(focusedScenarioIndex + 1);
|
|
947
|
+
break;
|
|
948
|
+
case 'k':
|
|
949
|
+
e.preventDefault();
|
|
950
|
+
focusScenario(focusedScenarioIndex - 1);
|
|
951
|
+
break;
|
|
952
|
+
case 'Enter':
|
|
953
|
+
e.preventDefault();
|
|
954
|
+
var scenarios = getVisibleScenarios();
|
|
955
|
+
if (focusedScenarioIndex >= 0 && focusedScenarioIndex < scenarios.length) {
|
|
956
|
+
var s = scenarios[focusedScenarioIndex];
|
|
957
|
+
var h = s.querySelector('.scenario-header');
|
|
958
|
+
if (h) toggleCollapse(h, s);
|
|
959
|
+
}
|
|
960
|
+
break;
|
|
961
|
+
case 'Escape':
|
|
962
|
+
if (document.querySelector('.shortcuts-overlay')) {
|
|
963
|
+
hideShortcutsOverlay();
|
|
964
|
+
} else {
|
|
965
|
+
var scenarios2 = getVisibleScenarios();
|
|
966
|
+
if (focusedScenarioIndex >= 0 && focusedScenarioIndex < scenarios2.length) {
|
|
967
|
+
var sc = scenarios2[focusedScenarioIndex];
|
|
968
|
+
if (!sc.classList.contains('collapsed')) {
|
|
969
|
+
sc.classList.add('collapsed');
|
|
970
|
+
var sh = sc.querySelector('.scenario-header');
|
|
971
|
+
if (sh) sh.setAttribute('aria-expanded', 'false');
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
break;
|
|
976
|
+
case '/':
|
|
977
|
+
e.preventDefault();
|
|
978
|
+
var input = document.querySelector('.search-input');
|
|
979
|
+
if (input) input.focus();
|
|
980
|
+
break;
|
|
981
|
+
case '?':
|
|
982
|
+
e.preventDefault();
|
|
983
|
+
if (document.querySelector('.shortcuts-overlay')) {
|
|
984
|
+
hideShortcutsOverlay();
|
|
985
|
+
} else {
|
|
986
|
+
showShortcutsOverlay();
|
|
987
|
+
}
|
|
988
|
+
break;
|
|
989
|
+
case 'e':
|
|
990
|
+
e.preventDefault();
|
|
991
|
+
expandAll();
|
|
992
|
+
break;
|
|
993
|
+
case 'c':
|
|
994
|
+
e.preventDefault();
|
|
995
|
+
collapseAll();
|
|
996
|
+
break;
|
|
997
|
+
case 't':
|
|
998
|
+
e.preventDefault();
|
|
999
|
+
if (typeof toggleToc === 'function') toggleToc();
|
|
1000
|
+
break;
|
|
878
1001
|
}
|
|
879
1002
|
});
|
|
880
1003
|
}
|
|
@@ -1012,6 +1135,189 @@ function writeUrlState() {
|
|
|
1012
1135
|
var url = window.location.pathname + (qs ? '?' + qs : '');
|
|
1013
1136
|
history.replaceState(null, '', url);
|
|
1014
1137
|
}
|
|
1138
|
+
|
|
1139
|
+
// Permalink copy
|
|
1140
|
+
function copyPermalink(anchorId) {
|
|
1141
|
+
var url = location.origin + location.pathname + location.search + '#' + anchorId;
|
|
1142
|
+
navigator.clipboard.writeText(url).then(function() {
|
|
1143
|
+
var el = document.getElementById(anchorId);
|
|
1144
|
+
if (el) showCopyToast(el);
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
function showCopyToast(el) {
|
|
1149
|
+
var existing = el.querySelector('.copy-toast');
|
|
1150
|
+
if (existing) existing.remove();
|
|
1151
|
+
var toast = document.createElement('span');
|
|
1152
|
+
toast.className = 'copy-toast';
|
|
1153
|
+
toast.textContent = 'Copied!';
|
|
1154
|
+
var header = el.querySelector('.feature-header, .scenario-header');
|
|
1155
|
+
if (header) {
|
|
1156
|
+
header.style.position = 'relative';
|
|
1157
|
+
header.appendChild(toast);
|
|
1158
|
+
}
|
|
1159
|
+
setTimeout(function() { toast.remove(); }, 1500);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Copy scenario as markdown
|
|
1163
|
+
function copyScenarioAsMarkdown(scenarioId) {
|
|
1164
|
+
var scenario = document.getElementById(scenarioId);
|
|
1165
|
+
if (!scenario) return;
|
|
1166
|
+
|
|
1167
|
+
var title = (scenario.querySelector('.scenario-name') || {}).textContent || '';
|
|
1168
|
+
var steps = scenario.querySelectorAll('.step, .step.continuation');
|
|
1169
|
+
var lines = ['### Scenario: ' + title.trim(), ''];
|
|
1170
|
+
|
|
1171
|
+
steps.forEach(function(step) {
|
|
1172
|
+
var keyword = step.getAttribute('data-keyword') || '';
|
|
1173
|
+
var text = step.getAttribute('data-text') || '';
|
|
1174
|
+
lines.push('- **' + keyword + '** ' + text);
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
var errorBox = scenario.querySelector('.error-message');
|
|
1178
|
+
if (errorBox) {
|
|
1179
|
+
var errorText = errorBox.textContent || '';
|
|
1180
|
+
lines.push('');
|
|
1181
|
+
lines.push('> **Error:** ' + errorText.trim());
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
var md = lines.join('\\n');
|
|
1185
|
+
navigator.clipboard.writeText(md).then(function() {
|
|
1186
|
+
showCopyToast(scenario);
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// Hash scroll on load
|
|
1191
|
+
function initHashScroll() {
|
|
1192
|
+
if (!location.hash) return;
|
|
1193
|
+
var target = document.querySelector(location.hash);
|
|
1194
|
+
if (!target) return;
|
|
1195
|
+
var feature = target.closest('.feature');
|
|
1196
|
+
if (feature && feature.classList.contains('collapsed')) {
|
|
1197
|
+
feature.classList.remove('collapsed');
|
|
1198
|
+
var fh = feature.querySelector('.feature-header');
|
|
1199
|
+
if (fh) fh.setAttribute('aria-expanded', 'true');
|
|
1200
|
+
}
|
|
1201
|
+
if (target.classList.contains('collapsed')) {
|
|
1202
|
+
target.classList.remove('collapsed');
|
|
1203
|
+
var sh = target.querySelector('.scenario-header');
|
|
1204
|
+
if (sh) sh.setAttribute('aria-expanded', 'true');
|
|
1205
|
+
}
|
|
1206
|
+
setTimeout(function() {
|
|
1207
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1208
|
+
target.classList.add('hash-highlight');
|
|
1209
|
+
}, 100);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// Table of contents
|
|
1213
|
+
function toggleToc() {
|
|
1214
|
+
var sidebar = document.querySelector('.toc-sidebar');
|
|
1215
|
+
var wrapper = document.querySelector('.report-layout');
|
|
1216
|
+
if (!sidebar || !wrapper) return;
|
|
1217
|
+
var isMobile = window.matchMedia('(max-width: 767px)').matches;
|
|
1218
|
+
if (isMobile) {
|
|
1219
|
+
sidebar.classList.toggle('toc-mobile-open');
|
|
1220
|
+
} else {
|
|
1221
|
+
wrapper.classList.toggle('toc-hidden');
|
|
1222
|
+
var hidden = wrapper.classList.contains('toc-hidden');
|
|
1223
|
+
localStorage.setItem('toc-visible', String(!hidden));
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
function initToc() {
|
|
1228
|
+
var sidebar = document.querySelector('.toc-sidebar');
|
|
1229
|
+
if (!sidebar) return;
|
|
1230
|
+
|
|
1231
|
+
var saved = localStorage.getItem('toc-visible');
|
|
1232
|
+
var wrapper = document.querySelector('.report-layout');
|
|
1233
|
+
if (saved === 'false' && wrapper) {
|
|
1234
|
+
wrapper.classList.add('toc-hidden');
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// Active tracking via IntersectionObserver
|
|
1238
|
+
var observer = new IntersectionObserver(function(entries) {
|
|
1239
|
+
entries.forEach(function(entry) {
|
|
1240
|
+
if (entry.isIntersecting) {
|
|
1241
|
+
var id = entry.target.id;
|
|
1242
|
+
if (!id) return;
|
|
1243
|
+
document.querySelectorAll('.toc-scenario, .toc-feature-toggle').forEach(function(el) {
|
|
1244
|
+
el.classList.remove('toc-active');
|
|
1245
|
+
});
|
|
1246
|
+
var tocLink = sidebar.querySelector('a[href="#' + id + '"]');
|
|
1247
|
+
if (tocLink) tocLink.classList.add('toc-active');
|
|
1248
|
+
}
|
|
1249
|
+
});
|
|
1250
|
+
}, { rootMargin: '-10% 0px -80% 0px' });
|
|
1251
|
+
|
|
1252
|
+
document.querySelectorAll('.feature, .scenario').forEach(function(el) {
|
|
1253
|
+
if (el.id) observer.observe(el);
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
// Click navigation: expand collapsed parents
|
|
1257
|
+
sidebar.querySelectorAll('.toc-scenario').forEach(function(link) {
|
|
1258
|
+
link.addEventListener('click', function(e) {
|
|
1259
|
+
var hash = link.getAttribute('href');
|
|
1260
|
+
if (!hash) return;
|
|
1261
|
+
var target = document.querySelector(hash);
|
|
1262
|
+
if (!target) return;
|
|
1263
|
+
var feature = target.closest('.feature');
|
|
1264
|
+
if (feature && feature.classList.contains('collapsed')) {
|
|
1265
|
+
feature.classList.remove('collapsed');
|
|
1266
|
+
var fh = feature.querySelector('.feature-header');
|
|
1267
|
+
if (fh) fh.setAttribute('aria-expanded', 'true');
|
|
1268
|
+
}
|
|
1269
|
+
if (target.classList.contains('collapsed')) {
|
|
1270
|
+
target.classList.remove('collapsed');
|
|
1271
|
+
var sh = target.querySelector('.scenario-header');
|
|
1272
|
+
if (sh) sh.setAttribute('aria-expanded', 'true');
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// Theme picker
|
|
1279
|
+
function initThemePicker() {
|
|
1280
|
+
var picker = document.querySelector('.theme-picker');
|
|
1281
|
+
if (!picker) return;
|
|
1282
|
+
|
|
1283
|
+
var saved = localStorage.getItem('report-theme');
|
|
1284
|
+
if (saved) {
|
|
1285
|
+
picker.value = saved;
|
|
1286
|
+
switchReportTheme(saved);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
picker.addEventListener('change', function(e) {
|
|
1290
|
+
switchReportTheme(e.target.value);
|
|
1291
|
+
localStorage.setItem('report-theme', e.target.value);
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
function switchReportTheme(name) {
|
|
1296
|
+
document.querySelectorAll('style[data-theme-name]').forEach(function(s) {
|
|
1297
|
+
s.disabled = s.dataset.themeName !== name;
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// Sync TOC visibility with filters
|
|
1302
|
+
function syncTocVisibility() {
|
|
1303
|
+
var sidebar = document.querySelector('.toc-sidebar');
|
|
1304
|
+
if (!sidebar) return;
|
|
1305
|
+
|
|
1306
|
+
sidebar.querySelectorAll('.toc-scenario').forEach(function(link) {
|
|
1307
|
+
var href = link.getAttribute('href');
|
|
1308
|
+
if (!href) return;
|
|
1309
|
+
var target = document.querySelector(href);
|
|
1310
|
+
link.style.display = (target && target.style.display !== 'none') ? '' : 'none';
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
sidebar.querySelectorAll('.toc-feature').forEach(function(feature) {
|
|
1314
|
+
var visibleScenarios = feature.querySelectorAll('.toc-scenario');
|
|
1315
|
+
var anyVisible = Array.from(visibleScenarios).some(function(s) {
|
|
1316
|
+
return s.style.display !== 'none';
|
|
1317
|
+
});
|
|
1318
|
+
feature.style.display = anyVisible ? '' : 'none';
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1015
1321
|
`;
|
|
1016
1322
|
var JS_MARKDOWN_FN = `
|
|
1017
1323
|
function parseMarkdownSections(marked) {
|
|
@@ -1052,6 +1358,9 @@ function generateScript(options) {
|
|
|
1052
1358
|
initCalls.push("initCollapse();");
|
|
1053
1359
|
initCalls.push("initDetailLevel();");
|
|
1054
1360
|
initCalls.push("applyAllFilters();");
|
|
1361
|
+
initCalls.push("initHashScroll();");
|
|
1362
|
+
initCalls.push("initToc();");
|
|
1363
|
+
initCalls.push("initThemePicker();");
|
|
1055
1364
|
const initScript = `
|
|
1056
1365
|
// Initialize on load
|
|
1057
1366
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -1113,6 +1422,7 @@ function generateHtmlTemplate(title, styles, body, options = {}) {
|
|
|
1113
1422
|
}
|
|
1114
1423
|
const cdnStylesHtml = cdnStyles.length > 0 ? "\n " + cdnStyles.join("\n ") : "";
|
|
1115
1424
|
const esmScriptHtml = generateEsmScript(options);
|
|
1425
|
+
const additionalThemeStyles = (options.additionalThemeCss ?? []).map((t) => `<style data-theme-name="${escapeHtml(t.name)}" disabled>${t.css}</style>`).join("\n ");
|
|
1116
1426
|
return `<!DOCTYPE html>
|
|
1117
1427
|
<html lang="en"${themeAttr} data-detail-level="full">
|
|
1118
1428
|
<head>
|
|
@@ -1120,19 +1430,27 @@ function generateHtmlTemplate(title, styles, body, options = {}) {
|
|
|
1120
1430
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1121
1431
|
<meta name="color-scheme" content="light dark">
|
|
1122
1432
|
<title>${escapeHtml(title)}</title>${cdnStylesHtml}
|
|
1123
|
-
<style>${styles}</style>
|
|
1433
|
+
<style${options.additionalThemeCss ? ` data-theme-name="${escapeHtml(options.activeThemeName ?? "default")}"` : ""}>${styles}</style>
|
|
1434
|
+
${additionalThemeStyles}
|
|
1124
1435
|
</head>
|
|
1125
1436
|
<body>
|
|
1126
|
-
<div class="
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
<div class="
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1437
|
+
<div class="report-layout">
|
|
1438
|
+
${options.tocHtml ?? ""}
|
|
1439
|
+
<div class="main-content">
|
|
1440
|
+
<div class="container">
|
|
1441
|
+
<header class="header">
|
|
1442
|
+
<h1>${escapeHtml(title)}</h1>
|
|
1443
|
+
<div class="header-actions">
|
|
1444
|
+
<button type="button" class="toc-toggle" onclick="toggleToc()" aria-label="Toggle table of contents" title="Toggle contents">☰</button>
|
|
1445
|
+
${includeSearch ? '<input type="text" class="search-input" placeholder="Search scenarios..." aria-label="Search scenarios">' : ""}
|
|
1446
|
+
<button type="button" class="detail-toggle" onclick="toggleDetailLevel()" aria-label="Toggle detail level" title="Toggle documentation detail"></button>
|
|
1447
|
+
${options.themePickerHtml ?? ""}
|
|
1448
|
+
${includeDarkMode ? '<button type="button" class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle theme"></button>' : ""}
|
|
1449
|
+
</div>
|
|
1450
|
+
</header>
|
|
1451
|
+
${body}
|
|
1133
1452
|
</div>
|
|
1134
|
-
</
|
|
1135
|
-
${body}
|
|
1453
|
+
</div>
|
|
1136
1454
|
</div>
|
|
1137
1455
|
<script>${script}</script>${esmScriptHtml}
|
|
1138
1456
|
</body>
|
|
@@ -2836,6 +3154,337 @@ body {
|
|
|
2836
3154
|
display: none;
|
|
2837
3155
|
}
|
|
2838
3156
|
|
|
3157
|
+
/* ============================================================================
|
|
3158
|
+
Permalink Anchors
|
|
3159
|
+
============================================================================ */
|
|
3160
|
+
.permalink-anchor {
|
|
3161
|
+
display: inline-flex;
|
|
3162
|
+
align-items: center;
|
|
3163
|
+
justify-content: center;
|
|
3164
|
+
width: 1.5rem;
|
|
3165
|
+
height: 1.5rem;
|
|
3166
|
+
border: none;
|
|
3167
|
+
background: none;
|
|
3168
|
+
color: var(--muted-foreground);
|
|
3169
|
+
cursor: pointer;
|
|
3170
|
+
opacity: 0;
|
|
3171
|
+
transition: opacity 0.15s ease;
|
|
3172
|
+
font-size: 0.875rem;
|
|
3173
|
+
font-weight: 600;
|
|
3174
|
+
padding: 0;
|
|
3175
|
+
flex-shrink: 0;
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
.feature-header:hover .permalink-anchor,
|
|
3179
|
+
.scenario-header:hover .permalink-anchor,
|
|
3180
|
+
.permalink-anchor:focus-visible {
|
|
3181
|
+
opacity: 1;
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
.permalink-anchor:hover {
|
|
3185
|
+
color: var(--primary);
|
|
3186
|
+
}
|
|
3187
|
+
|
|
3188
|
+
.copy-toast {
|
|
3189
|
+
position: absolute;
|
|
3190
|
+
right: 0.5rem;
|
|
3191
|
+
top: 50%;
|
|
3192
|
+
transform: translateY(-50%);
|
|
3193
|
+
background: var(--foreground);
|
|
3194
|
+
color: var(--background);
|
|
3195
|
+
padding: 0.25rem 0.5rem;
|
|
3196
|
+
border-radius: var(--radius);
|
|
3197
|
+
font-size: 0.75rem;
|
|
3198
|
+
font-weight: 500;
|
|
3199
|
+
pointer-events: none;
|
|
3200
|
+
animation: fadeOut 1.5s ease forwards;
|
|
3201
|
+
z-index: 10;
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
@keyframes fadeOut {
|
|
3205
|
+
0%, 70% { opacity: 1; }
|
|
3206
|
+
100% { opacity: 0; }
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
.hash-highlight {
|
|
3210
|
+
animation: hashPulse 2s ease;
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
@keyframes hashPulse {
|
|
3214
|
+
0%, 100% { background: transparent; }
|
|
3215
|
+
20% { background: color-mix(in srgb, var(--primary) 12%, transparent); }
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
.scenario-actions {
|
|
3219
|
+
display: flex;
|
|
3220
|
+
align-items: center;
|
|
3221
|
+
gap: 0.25rem;
|
|
3222
|
+
flex-shrink: 0;
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3225
|
+
.copy-scenario-btn {
|
|
3226
|
+
display: inline-flex;
|
|
3227
|
+
align-items: center;
|
|
3228
|
+
justify-content: center;
|
|
3229
|
+
width: 1.5rem;
|
|
3230
|
+
height: 1.5rem;
|
|
3231
|
+
border: none;
|
|
3232
|
+
background: none;
|
|
3233
|
+
color: var(--muted-foreground);
|
|
3234
|
+
cursor: pointer;
|
|
3235
|
+
opacity: 0;
|
|
3236
|
+
transition: opacity 0.15s ease;
|
|
3237
|
+
font-size: 0.875rem;
|
|
3238
|
+
padding: 0;
|
|
3239
|
+
flex-shrink: 0;
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3242
|
+
.scenario-header:hover .copy-scenario-btn,
|
|
3243
|
+
.copy-scenario-btn:focus-visible {
|
|
3244
|
+
opacity: 1;
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
.copy-scenario-btn:hover {
|
|
3248
|
+
color: var(--primary);
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
/* ============================================================================
|
|
3252
|
+
Keyboard Navigation
|
|
3253
|
+
============================================================================ */
|
|
3254
|
+
.scenario-focused {
|
|
3255
|
+
border-left: 2px solid var(--primary);
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
.shortcuts-overlay {
|
|
3259
|
+
position: fixed;
|
|
3260
|
+
inset: 0;
|
|
3261
|
+
background: rgb(0 0 0 / 0.5);
|
|
3262
|
+
display: flex;
|
|
3263
|
+
align-items: center;
|
|
3264
|
+
justify-content: center;
|
|
3265
|
+
z-index: 100;
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
.shortcuts-modal {
|
|
3269
|
+
background: var(--card);
|
|
3270
|
+
color: var(--card-foreground);
|
|
3271
|
+
border: 1px solid var(--border);
|
|
3272
|
+
border-radius: calc(var(--radius) * 2);
|
|
3273
|
+
padding: 1.5rem 2rem;
|
|
3274
|
+
max-width: 400px;
|
|
3275
|
+
width: 90vw;
|
|
3276
|
+
box-shadow: var(--shadow-md, 0 4px 12px rgb(0 0 0 / 0.15));
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3279
|
+
.shortcuts-title {
|
|
3280
|
+
font-weight: 600;
|
|
3281
|
+
font-size: 1.125rem;
|
|
3282
|
+
margin-bottom: 1rem;
|
|
3283
|
+
padding-bottom: 0.5rem;
|
|
3284
|
+
border-bottom: 1px solid var(--border);
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
.shortcuts-grid {
|
|
3288
|
+
display: grid;
|
|
3289
|
+
grid-template-columns: auto 1fr;
|
|
3290
|
+
gap: 0.5rem 1rem;
|
|
3291
|
+
align-items: center;
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
.shortcuts-grid kbd {
|
|
3295
|
+
display: inline-flex;
|
|
3296
|
+
align-items: center;
|
|
3297
|
+
justify-content: center;
|
|
3298
|
+
min-width: 1.75rem;
|
|
3299
|
+
padding: 0.125rem 0.375rem;
|
|
3300
|
+
background: var(--muted);
|
|
3301
|
+
border: 1px solid var(--border);
|
|
3302
|
+
border-radius: calc(var(--radius) * 0.5);
|
|
3303
|
+
font-family: var(--font-mono);
|
|
3304
|
+
font-size: 0.75rem;
|
|
3305
|
+
font-weight: 500;
|
|
3306
|
+
color: var(--muted-foreground);
|
|
3307
|
+
}
|
|
3308
|
+
|
|
3309
|
+
.shortcuts-grid span {
|
|
3310
|
+
font-size: 0.875rem;
|
|
3311
|
+
color: var(--foreground);
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
/* ============================================================================
|
|
3315
|
+
Table of Contents Sidebar
|
|
3316
|
+
============================================================================ */
|
|
3317
|
+
.report-layout {
|
|
3318
|
+
display: flex;
|
|
3319
|
+
min-height: 100vh;
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
.report-layout.toc-hidden .toc-sidebar {
|
|
3323
|
+
display: none;
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
.main-content {
|
|
3327
|
+
flex: 1;
|
|
3328
|
+
min-width: 0;
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
.toc-sidebar {
|
|
3332
|
+
width: 260px;
|
|
3333
|
+
flex-shrink: 0;
|
|
3334
|
+
position: sticky;
|
|
3335
|
+
top: 0;
|
|
3336
|
+
height: 100vh;
|
|
3337
|
+
overflow-y: auto;
|
|
3338
|
+
border-right: 1px solid var(--border);
|
|
3339
|
+
background: var(--card);
|
|
3340
|
+
padding: 1rem 0;
|
|
3341
|
+
font-size: 0.8125rem;
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
.toc-header {
|
|
3345
|
+
padding: 0 1rem 0.75rem;
|
|
3346
|
+
border-bottom: 1px solid var(--border);
|
|
3347
|
+
margin-bottom: 0.5rem;
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
.toc-title {
|
|
3351
|
+
font-weight: 600;
|
|
3352
|
+
font-size: 0.875rem;
|
|
3353
|
+
color: var(--foreground);
|
|
3354
|
+
text-decoration: none;
|
|
3355
|
+
cursor: pointer;
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
a.toc-title:hover {
|
|
3359
|
+
color: var(--primary);
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
.toc-feature {
|
|
3363
|
+
margin-bottom: 0.25rem;
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
.toc-feature-toggle {
|
|
3367
|
+
display: flex;
|
|
3368
|
+
align-items: center;
|
|
3369
|
+
width: 100%;
|
|
3370
|
+
padding: 0.375rem 1rem;
|
|
3371
|
+
border: none;
|
|
3372
|
+
background: none;
|
|
3373
|
+
text-align: left;
|
|
3374
|
+
cursor: pointer;
|
|
3375
|
+
font-size: 0.8125rem;
|
|
3376
|
+
font-weight: 600;
|
|
3377
|
+
color: var(--foreground);
|
|
3378
|
+
font-family: var(--font-sans);
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
.toc-feature-toggle:hover {
|
|
3382
|
+
background: var(--accent);
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
.toc-feature-toggle[aria-expanded="false"] + .toc-scenarios {
|
|
3386
|
+
display: none;
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
.toc-scenarios {
|
|
3390
|
+
display: flex;
|
|
3391
|
+
flex-direction: column;
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
.toc-scenario {
|
|
3395
|
+
display: flex;
|
|
3396
|
+
align-items: baseline;
|
|
3397
|
+
gap: 0.375rem;
|
|
3398
|
+
padding: 0.25rem 1rem 0.25rem 1.5rem;
|
|
3399
|
+
color: var(--muted-foreground);
|
|
3400
|
+
text-decoration: none;
|
|
3401
|
+
font-size: 0.8125rem;
|
|
3402
|
+
line-height: 1.4;
|
|
3403
|
+
border-left: 2px solid transparent;
|
|
3404
|
+
transition: all 0.1s ease;
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
.toc-scenario:hover {
|
|
3408
|
+
color: var(--foreground);
|
|
3409
|
+
background: var(--accent);
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
.toc-scenario.toc-active {
|
|
3413
|
+
color: var(--foreground);
|
|
3414
|
+
border-left-color: var(--primary);
|
|
3415
|
+
font-weight: 500;
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3418
|
+
.toc-scenario.toc-failed {
|
|
3419
|
+
border-left-color: var(--error, var(--destructive));
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
.toc-status {
|
|
3423
|
+
flex-shrink: 0;
|
|
3424
|
+
font-size: 0.75rem;
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
.toc-toggle {
|
|
3428
|
+
display: inline-flex;
|
|
3429
|
+
align-items: center;
|
|
3430
|
+
justify-content: center;
|
|
3431
|
+
width: 2.25rem;
|
|
3432
|
+
height: 2.25rem;
|
|
3433
|
+
border: 1px solid var(--border);
|
|
3434
|
+
border-radius: var(--radius);
|
|
3435
|
+
background: var(--background);
|
|
3436
|
+
cursor: pointer;
|
|
3437
|
+
color: var(--foreground);
|
|
3438
|
+
font-size: 1rem;
|
|
3439
|
+
transition: all 0.15s ease;
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
.toc-toggle:hover {
|
|
3443
|
+
background: var(--accent);
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
/* Mobile: overlay sidebar */
|
|
3447
|
+
@media (max-width: 767px) {
|
|
3448
|
+
.toc-sidebar {
|
|
3449
|
+
position: fixed;
|
|
3450
|
+
left: 0;
|
|
3451
|
+
top: 0;
|
|
3452
|
+
z-index: 50;
|
|
3453
|
+
box-shadow: var(--shadow-sm, 0 1px 3px rgb(0 0 0 / 0.1));
|
|
3454
|
+
transform: translateX(-100%);
|
|
3455
|
+
transition: transform 0.2s ease;
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
.toc-sidebar.toc-mobile-open {
|
|
3459
|
+
transform: translateX(0);
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
/* ============================================================================
|
|
3464
|
+
Theme Picker
|
|
3465
|
+
============================================================================ */
|
|
3466
|
+
.theme-picker {
|
|
3467
|
+
height: 2.25rem;
|
|
3468
|
+
padding: 0 0.5rem;
|
|
3469
|
+
border: 1px solid var(--border);
|
|
3470
|
+
border-radius: var(--radius);
|
|
3471
|
+
background: var(--background);
|
|
3472
|
+
color: var(--foreground);
|
|
3473
|
+
font-size: 0.8125rem;
|
|
3474
|
+
font-family: var(--font-sans);
|
|
3475
|
+
cursor: pointer;
|
|
3476
|
+
transition: all 0.15s ease;
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3479
|
+
.theme-picker:hover {
|
|
3480
|
+
background: var(--accent);
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
.theme-picker:focus-visible {
|
|
3484
|
+
outline: 2px solid var(--ring);
|
|
3485
|
+
outline-offset: 2px;
|
|
3486
|
+
}
|
|
3487
|
+
|
|
2839
3488
|
`;
|
|
2840
3489
|
|
|
2841
3490
|
// src/formatters/html/themes/default.ts
|
|
@@ -2885,7 +3534,7 @@ function corporateBuildBody(args, deps) {
|
|
|
2885
3534
|
const sidebar = `
|
|
2886
3535
|
<nav class="toc">
|
|
2887
3536
|
<div class="toc-header">
|
|
2888
|
-
<
|
|
3537
|
+
<a href="#" class="toc-title" onclick="window.scrollTo({top:0,behavior:'smooth'});return false;">Test Report</a>
|
|
2889
3538
|
<div class="toc-stats">
|
|
2890
3539
|
<div class="toc-stat-row">
|
|
2891
3540
|
<span class="toc-stat-label">Total</span>
|
|
@@ -12095,6 +12744,11 @@ function resolveTheme(nameOrTheme) {
|
|
|
12095
12744
|
function getAvailableThemes() {
|
|
12096
12745
|
return [...THEME_REGISTRY.keys()];
|
|
12097
12746
|
}
|
|
12747
|
+
function getCssOnlyThemes() {
|
|
12748
|
+
return [...THEME_REGISTRY.values()].filter(
|
|
12749
|
+
(theme) => !theme.buildBody && !theme.generateTemplate
|
|
12750
|
+
);
|
|
12751
|
+
}
|
|
12098
12752
|
|
|
12099
12753
|
// src/formatters/html/renderers/status.ts
|
|
12100
12754
|
function getStatusIcon(status) {
|
|
@@ -12376,7 +13030,7 @@ function renderStep(step, stepResult, index, deps) {
|
|
|
12376
13030
|
const stepClass = isContinuation ? "step continuation" : "step";
|
|
12377
13031
|
const stepDocs = deps.renderDocs(step.docs, "step-docs");
|
|
12378
13032
|
const textHtml = deps.highlightStepParams ? deps.highlightStepParams(step.text) : deps.escapeHtml(step.text);
|
|
12379
|
-
return `<div class="${stepClass}">
|
|
13033
|
+
return `<div class="${stepClass}" data-keyword="${deps.escapeHtml(keywordTrimmed)}" data-text="${deps.escapeHtml(step.text)}">
|
|
12380
13034
|
<span class="step-status ${statusClass}">${statusIcon}</span>
|
|
12381
13035
|
<span class="step-keyword">${deps.escapeHtml(step.keyword)}</span>
|
|
12382
13036
|
<span class="step-text">${textHtml}</span>
|
|
@@ -12507,7 +13161,11 @@ function renderScenario(args, deps) {
|
|
|
12507
13161
|
</div>
|
|
12508
13162
|
<div class="scenario-meta">${tags}${tickets}${sourceLink}${traceBadge}${metricBadges}</div>
|
|
12509
13163
|
</div>
|
|
12510
|
-
<
|
|
13164
|
+
<div class="scenario-actions">
|
|
13165
|
+
<button class="copy-scenario-btn" onclick="copyScenarioAsMarkdown('scenario-${tc.id}')" aria-label="Copy scenario as markdown" title="Copy as Markdown"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>
|
|
13166
|
+
<button class="permalink-anchor" onclick="copyPermalink('scenario-${tc.id}')" aria-label="Copy link to scenario" title="Copy link">#</button>
|
|
13167
|
+
<span class="scenario-duration">${duration}</span>
|
|
13168
|
+
</div>
|
|
12511
13169
|
</div>
|
|
12512
13170
|
<div class="scenario-content">
|
|
12513
13171
|
${storyDocs}
|
|
@@ -12717,6 +13375,7 @@ function renderFeature(args, deps) {
|
|
|
12717
13375
|
const featureName = suitePaths.length > 0 && suitePaths[0].length > 0 ? suitePaths[0][0] : file.split("/").pop()?.replace(/\.[^.]+$/, "") ?? file;
|
|
12718
13376
|
const collapsedClass = deps.startCollapsed ? " collapsed" : "";
|
|
12719
13377
|
const ariaExpanded = !deps.startCollapsed;
|
|
13378
|
+
const featureSlug = `feature-${slugify(file)}`;
|
|
12720
13379
|
const scenarios = testCases.map(
|
|
12721
13380
|
(tc) => deps.renderScenario(
|
|
12722
13381
|
{ tc, metrics: args.metricsMap?.get(tc.id) },
|
|
@@ -12724,8 +13383,9 @@ function renderFeature(args, deps) {
|
|
|
12724
13383
|
)
|
|
12725
13384
|
).join("\n");
|
|
12726
13385
|
return `
|
|
12727
|
-
<div class="feature${collapsedClass}">
|
|
13386
|
+
<div class="feature${collapsedClass}" id="${featureSlug}">
|
|
12728
13387
|
<div class="feature-header" role="button" tabindex="0" aria-expanded="${ariaExpanded}">
|
|
13388
|
+
<button class="permalink-anchor" onclick="copyPermalink('${featureSlug}')" aria-label="Copy link to feature" title="Copy link">#</button>
|
|
12729
13389
|
<div class="feature-info">
|
|
12730
13390
|
<div class="feature-title">${deps.escapeHtml(featureName)}</div>
|
|
12731
13391
|
<div class="feature-path">${deps.escapeHtml(file)}</div>
|
|
@@ -12838,6 +13498,57 @@ function renderFailureSummary(args, deps) {
|
|
|
12838
13498
|
</div>`;
|
|
12839
13499
|
}
|
|
12840
13500
|
|
|
13501
|
+
// src/formatters/html/renderers/toc.ts
|
|
13502
|
+
function groupBy4(items, keyFn) {
|
|
13503
|
+
const map = /* @__PURE__ */ new Map();
|
|
13504
|
+
for (const item of items) {
|
|
13505
|
+
const key = keyFn(item);
|
|
13506
|
+
const existing = map.get(key);
|
|
13507
|
+
if (existing) {
|
|
13508
|
+
existing.push(item);
|
|
13509
|
+
} else {
|
|
13510
|
+
map.set(key, [item]);
|
|
13511
|
+
}
|
|
13512
|
+
}
|
|
13513
|
+
return map;
|
|
13514
|
+
}
|
|
13515
|
+
function renderToc(args, deps) {
|
|
13516
|
+
const { run } = args;
|
|
13517
|
+
if (run.testCases.length === 0) return "";
|
|
13518
|
+
const byFile = groupBy4(run.testCases, (tc) => tc.sourceFile);
|
|
13519
|
+
const features = [];
|
|
13520
|
+
for (const [file, testCases] of byFile) {
|
|
13521
|
+
const suitePaths = testCases.map((tc) => tc.titlePath).filter((p) => p.length > 0);
|
|
13522
|
+
const featureName = suitePaths.length > 0 && suitePaths[0].length > 0 ? suitePaths[0][0] : file.split("/").pop()?.replace(/\.[^.]+$/, "") ?? file;
|
|
13523
|
+
const featureSlug = `feature-${slugify(file)}`;
|
|
13524
|
+
const scenarios = testCases.map((tc) => {
|
|
13525
|
+
const statusIcon = deps.getStatusIcon(tc.status);
|
|
13526
|
+
const statusClass = `status-${tc.status}`;
|
|
13527
|
+
const failedClass = tc.status === "failed" ? " toc-failed" : "";
|
|
13528
|
+
return `<a class="toc-scenario${failedClass}" href="#scenario-${tc.id}">
|
|
13529
|
+
<span class="toc-status ${statusClass}">${statusIcon}</span>
|
|
13530
|
+
${deps.escapeHtml(tc.story.scenario)}
|
|
13531
|
+
</a>`;
|
|
13532
|
+
}).join("\n");
|
|
13533
|
+
features.push(`<div class="toc-feature">
|
|
13534
|
+
<button class="toc-feature-toggle" aria-expanded="true" onclick="this.setAttribute('aria-expanded', this.getAttribute('aria-expanded') === 'true' ? 'false' : 'true'); this.nextElementSibling.style.display = this.getAttribute('aria-expanded') === 'true' ? '' : 'none'" data-feature="#${featureSlug}">
|
|
13535
|
+
${deps.escapeHtml(featureName)}
|
|
13536
|
+
</button>
|
|
13537
|
+
<div class="toc-scenarios">
|
|
13538
|
+
${scenarios}
|
|
13539
|
+
</div>
|
|
13540
|
+
</div>`);
|
|
13541
|
+
}
|
|
13542
|
+
return `<nav class="toc-sidebar" aria-label="Table of contents">
|
|
13543
|
+
<div class="toc-header">
|
|
13544
|
+
<a href="#" class="toc-title" onclick="window.scrollTo({top:0,behavior:'smooth'});return false;">Contents</a>
|
|
13545
|
+
</div>
|
|
13546
|
+
<div class="toc-body">
|
|
13547
|
+
${features.join("\n")}
|
|
13548
|
+
</div>
|
|
13549
|
+
</nav>`;
|
|
13550
|
+
}
|
|
13551
|
+
|
|
12841
13552
|
// src/formatters/html/renderers/index.ts
|
|
12842
13553
|
function normalizeOptions(options = {}) {
|
|
12843
13554
|
return {
|
|
@@ -12851,7 +13562,9 @@ function normalizeOptions(options = {}) {
|
|
|
12851
13562
|
markdownEnabled: options.markdownEnabled ?? true,
|
|
12852
13563
|
permalinkBaseUrl: options.permalinkBaseUrl,
|
|
12853
13564
|
ticketUrlTemplate: options.ticketUrlTemplate,
|
|
12854
|
-
|
|
13565
|
+
tocEnabled: options.tocEnabled ?? true,
|
|
13566
|
+
theme: options.theme ?? "default",
|
|
13567
|
+
themePickerEnabled: options.themePickerEnabled ?? false
|
|
12855
13568
|
};
|
|
12856
13569
|
}
|
|
12857
13570
|
function createHtmlFormatter(options = {}) {
|
|
@@ -12893,6 +13606,10 @@ function createHtmlFormatter(options = {}) {
|
|
|
12893
13606
|
scenarioDeps
|
|
12894
13607
|
};
|
|
12895
13608
|
const tagBarDeps = { escapeHtml };
|
|
13609
|
+
const tocDeps = {
|
|
13610
|
+
escapeHtml,
|
|
13611
|
+
getStatusIcon
|
|
13612
|
+
};
|
|
12896
13613
|
const bodyDeps = {
|
|
12897
13614
|
renderMetaInfo,
|
|
12898
13615
|
renderSummary,
|
|
@@ -12911,6 +13628,16 @@ function createHtmlFormatter(options = {}) {
|
|
|
12911
13628
|
const bodyFn = theme.buildBody ?? buildBody;
|
|
12912
13629
|
const body = bodyFn({ run }, bodyDeps);
|
|
12913
13630
|
const templateFn = theme.generateTemplate ?? generateHtmlTemplate;
|
|
13631
|
+
const isStructuralTheme = !!(theme.buildBody || theme.generateTemplate);
|
|
13632
|
+
const tocHtml = opts.tocEnabled && !isStructuralTheme ? renderToc({ run }, tocDeps) : void 0;
|
|
13633
|
+
let themePickerHtml;
|
|
13634
|
+
let additionalThemeCss;
|
|
13635
|
+
if (opts.themePickerEnabled) {
|
|
13636
|
+
const cssOnlyThemes = getCssOnlyThemes();
|
|
13637
|
+
const pickerOptions = cssOnlyThemes.map((t) => `<option value="${t.name}"${t.name === theme.name ? " selected" : ""}>${t.label}</option>`).join("");
|
|
13638
|
+
themePickerHtml = `<select class="theme-picker" aria-label="Select theme">${pickerOptions}</select>`;
|
|
13639
|
+
additionalThemeCss = cssOnlyThemes.filter((t) => t.name !== theme.name).map((t) => ({ name: t.name, label: t.label, css: t.css }));
|
|
13640
|
+
}
|
|
12914
13641
|
return templateFn(
|
|
12915
13642
|
opts.title,
|
|
12916
13643
|
theme.css,
|
|
@@ -12922,7 +13649,11 @@ function createHtmlFormatter(options = {}) {
|
|
|
12922
13649
|
mermaidEnabled: opts.mermaidEnabled,
|
|
12923
13650
|
markdownEnabled: opts.markdownEnabled,
|
|
12924
13651
|
additionalJs: theme.additionalJs,
|
|
12925
|
-
additionalImports: theme.additionalImports
|
|
13652
|
+
additionalImports: theme.additionalImports,
|
|
13653
|
+
tocHtml,
|
|
13654
|
+
themePickerHtml,
|
|
13655
|
+
additionalThemeCss,
|
|
13656
|
+
activeThemeName: theme.name
|
|
12926
13657
|
}
|
|
12927
13658
|
);
|
|
12928
13659
|
}
|
|
@@ -12978,7 +13709,7 @@ var JUnitFormatter = class {
|
|
|
12978
13709
|
lines.push(
|
|
12979
13710
|
`<testsuites name="${escapeXml(this.options.suiteName)}" tests="${tests}" failures="${failures}" errors="${errors}" skipped="${skipped}" time="${time}">`
|
|
12980
13711
|
);
|
|
12981
|
-
const byFile =
|
|
13712
|
+
const byFile = groupBy5(run.testCases, (tc) => tc.sourceFile);
|
|
12982
13713
|
for (const [file, testCases] of byFile) {
|
|
12983
13714
|
lines.push(...this.buildTestSuite(file, testCases, indent, newline));
|
|
12984
13715
|
}
|
|
@@ -13151,7 +13882,7 @@ var JUnitFormatter = class {
|
|
|
13151
13882
|
function escapeXml(str) {
|
|
13152
13883
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
13153
13884
|
}
|
|
13154
|
-
function
|
|
13885
|
+
function groupBy5(items, keyFn) {
|
|
13155
13886
|
const map = /* @__PURE__ */ new Map();
|
|
13156
13887
|
for (const item of items) {
|
|
13157
13888
|
const key = keyFn(item);
|
|
@@ -13326,7 +14057,7 @@ var MarkdownFormatter = class {
|
|
|
13326
14057
|
* Render scenarios grouped by file.
|
|
13327
14058
|
*/
|
|
13328
14059
|
renderByFile(lines, testCases) {
|
|
13329
|
-
const byFile =
|
|
14060
|
+
const byFile = groupBy6(testCases, (tc) => tc.sourceFile);
|
|
13330
14061
|
for (const [file, fileTestCases] of byFile) {
|
|
13331
14062
|
lines.push(`## ${file}`);
|
|
13332
14063
|
lines.push("");
|
|
@@ -13343,7 +14074,7 @@ var MarkdownFormatter = class {
|
|
|
13343
14074
|
* Render suite groups.
|
|
13344
14075
|
*/
|
|
13345
14076
|
renderSuiteGroups(lines, testCases, baseLevel) {
|
|
13346
|
-
const bySuite =
|
|
14077
|
+
const bySuite = groupBy6(
|
|
13347
14078
|
testCases,
|
|
13348
14079
|
(tc) => tc.titlePath.join(this.options.suiteSeparator)
|
|
13349
14080
|
);
|
|
@@ -13637,7 +14368,7 @@ var MarkdownFormatter = class {
|
|
|
13637
14368
|
return entries;
|
|
13638
14369
|
}
|
|
13639
14370
|
};
|
|
13640
|
-
function
|
|
14371
|
+
function groupBy6(items, keyFn) {
|
|
13641
14372
|
const map = /* @__PURE__ */ new Map();
|
|
13642
14373
|
for (const item of items) {
|
|
13643
14374
|
const key = keyFn(item);
|
|
@@ -17348,7 +18079,7 @@ var ReportGenerator = class {
|
|
|
17348
18079
|
excludeTags: options.excludeTags ?? [],
|
|
17349
18080
|
formats: options.formats ?? ["cucumber-json"],
|
|
17350
18081
|
outputDir: options.outputDir ?? "reports",
|
|
17351
|
-
outputName: options.outputName ?? "
|
|
18082
|
+
outputName: options.outputName ?? "index",
|
|
17352
18083
|
outputNameTimestamp: options.outputNameTimestamp ?? false,
|
|
17353
18084
|
sortTestCases: options.sortTestCases ?? "none",
|
|
17354
18085
|
output: {
|
|
@@ -17377,7 +18108,9 @@ var ReportGenerator = class {
|
|
|
17377
18108
|
markdownEnabled: options.html?.markdownEnabled ?? true,
|
|
17378
18109
|
permalinkBaseUrl: options.html?.permalinkBaseUrl,
|
|
17379
18110
|
ticketUrlTemplate: options.html?.ticketUrlTemplate,
|
|
17380
|
-
theme: options.html?.theme ?? "default"
|
|
18111
|
+
theme: options.html?.theme ?? "default",
|
|
18112
|
+
tocEnabled: options.html?.tocEnabled ?? true,
|
|
18113
|
+
themePickerEnabled: options.html?.themePickerEnabled ?? false
|
|
17381
18114
|
},
|
|
17382
18115
|
junit: {
|
|
17383
18116
|
suiteName: options.junit?.suiteName ?? "Test Suite",
|
|
@@ -17500,7 +18233,9 @@ var ReportGenerator = class {
|
|
|
17500
18233
|
mermaidEnabled: this.options.html.mermaidEnabled,
|
|
17501
18234
|
markdownEnabled: this.options.html.markdownEnabled,
|
|
17502
18235
|
permalinkBaseUrl: this.options.html.permalinkBaseUrl,
|
|
17503
|
-
ticketUrlTemplate: this.options.html.ticketUrlTemplate
|
|
18236
|
+
ticketUrlTemplate: this.options.html.ticketUrlTemplate,
|
|
18237
|
+
tocEnabled: this.options.html.tocEnabled,
|
|
18238
|
+
themePickerEnabled: this.options.html.themePickerEnabled
|
|
17504
18239
|
});
|
|
17505
18240
|
return formatter.format(run);
|
|
17506
18241
|
}
|
|
@@ -17623,6 +18358,7 @@ export {
|
|
|
17623
18358
|
generateRunId,
|
|
17624
18359
|
generateTestCaseId,
|
|
17625
18360
|
getAvailableThemes,
|
|
18361
|
+
getCssOnlyThemes,
|
|
17626
18362
|
hasSufficientHistory,
|
|
17627
18363
|
listScenarios,
|
|
17628
18364
|
loadHistory,
|