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.cjs
CHANGED
|
@@ -65,6 +65,7 @@ __export(src_exports, {
|
|
|
65
65
|
generateRunId: () => generateRunId,
|
|
66
66
|
generateTestCaseId: () => generateTestCaseId,
|
|
67
67
|
getAvailableThemes: () => getAvailableThemes,
|
|
68
|
+
getCssOnlyThemes: () => getCssOnlyThemes,
|
|
68
69
|
hasSufficientHistory: () => hasSufficientHistory,
|
|
69
70
|
listScenarios: () => listScenarios,
|
|
70
71
|
loadHistory: () => loadHistory,
|
|
@@ -952,6 +953,7 @@ function applyAllFilters() {
|
|
|
952
953
|
});
|
|
953
954
|
|
|
954
955
|
updateFilterResults(visibleCount, totalCount);
|
|
956
|
+
syncTocVisibility();
|
|
955
957
|
writeUrlState();
|
|
956
958
|
}
|
|
957
959
|
|
|
@@ -968,13 +970,135 @@ function updateFilterResults(visible, total) {
|
|
|
968
970
|
if (tc) tc.textContent = total;
|
|
969
971
|
}
|
|
970
972
|
|
|
971
|
-
// Keyboard
|
|
973
|
+
// Keyboard navigation
|
|
974
|
+
var focusedScenarioIndex = -1;
|
|
975
|
+
|
|
976
|
+
function getVisibleScenarios() {
|
|
977
|
+
return Array.from(document.querySelectorAll('.scenario')).filter(function(s) {
|
|
978
|
+
return s.style.display !== 'none' && s.closest('.feature').style.display !== 'none';
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function focusScenario(index) {
|
|
983
|
+
var scenarios = getVisibleScenarios();
|
|
984
|
+
if (scenarios.length === 0) return;
|
|
985
|
+
|
|
986
|
+
// Remove previous focus
|
|
987
|
+
var prev = document.querySelector('.scenario-focused');
|
|
988
|
+
if (prev) prev.classList.remove('scenario-focused');
|
|
989
|
+
|
|
990
|
+
// Wrap around
|
|
991
|
+
if (index < 0) index = scenarios.length - 1;
|
|
992
|
+
if (index >= scenarios.length) index = 0;
|
|
993
|
+
focusedScenarioIndex = index;
|
|
994
|
+
|
|
995
|
+
var scenario = scenarios[index];
|
|
996
|
+
scenario.classList.add('scenario-focused');
|
|
997
|
+
scenario.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function showShortcutsOverlay() {
|
|
1001
|
+
if (document.querySelector('.shortcuts-overlay')) return;
|
|
1002
|
+
var overlay = document.createElement('div');
|
|
1003
|
+
overlay.className = 'shortcuts-overlay';
|
|
1004
|
+
overlay.innerHTML = '<div class="shortcuts-modal">' +
|
|
1005
|
+
'<div class="shortcuts-title">Keyboard Shortcuts</div>' +
|
|
1006
|
+
'<div class="shortcuts-grid">' +
|
|
1007
|
+
'<kbd>j</kbd><span>Next scenario</span>' +
|
|
1008
|
+
'<kbd>k</kbd><span>Previous scenario</span>' +
|
|
1009
|
+
'<kbd>Enter</kbd><span>Expand/collapse scenario</span>' +
|
|
1010
|
+
'<kbd>Escape</kbd><span>Collapse scenario / close</span>' +
|
|
1011
|
+
'<kbd>/</kbd><span>Focus search</span>' +
|
|
1012
|
+
'<kbd>?</kbd><span>Toggle this help</span>' +
|
|
1013
|
+
'<kbd>e</kbd><span>Expand all</span>' +
|
|
1014
|
+
'<kbd>c</kbd><span>Collapse all</span>' +
|
|
1015
|
+
'<kbd>t</kbd><span>Toggle table of contents</span>' +
|
|
1016
|
+
'</div></div>';
|
|
1017
|
+
overlay.addEventListener('click', function(ev) {
|
|
1018
|
+
if (ev.target === overlay) hideShortcutsOverlay();
|
|
1019
|
+
});
|
|
1020
|
+
document.body.appendChild(overlay);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function hideShortcutsOverlay() {
|
|
1024
|
+
var overlay = document.querySelector('.shortcuts-overlay');
|
|
1025
|
+
if (overlay) overlay.remove();
|
|
1026
|
+
}
|
|
1027
|
+
|
|
972
1028
|
function initKeyboardShortcuts() {
|
|
973
1029
|
document.addEventListener('keydown', function(e) {
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1030
|
+
var tag = e.target.tagName;
|
|
1031
|
+
if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') {
|
|
1032
|
+
if (e.key === 'Escape') {
|
|
1033
|
+
e.target.blur();
|
|
1034
|
+
if (e.target.classList.contains('search-input')) {
|
|
1035
|
+
e.target.value = '';
|
|
1036
|
+
applyAllFilters();
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
1043
|
+
|
|
1044
|
+
switch (e.key) {
|
|
1045
|
+
case 'j':
|
|
1046
|
+
e.preventDefault();
|
|
1047
|
+
focusScenario(focusedScenarioIndex + 1);
|
|
1048
|
+
break;
|
|
1049
|
+
case 'k':
|
|
1050
|
+
e.preventDefault();
|
|
1051
|
+
focusScenario(focusedScenarioIndex - 1);
|
|
1052
|
+
break;
|
|
1053
|
+
case 'Enter':
|
|
1054
|
+
e.preventDefault();
|
|
1055
|
+
var scenarios = getVisibleScenarios();
|
|
1056
|
+
if (focusedScenarioIndex >= 0 && focusedScenarioIndex < scenarios.length) {
|
|
1057
|
+
var s = scenarios[focusedScenarioIndex];
|
|
1058
|
+
var h = s.querySelector('.scenario-header');
|
|
1059
|
+
if (h) toggleCollapse(h, s);
|
|
1060
|
+
}
|
|
1061
|
+
break;
|
|
1062
|
+
case 'Escape':
|
|
1063
|
+
if (document.querySelector('.shortcuts-overlay')) {
|
|
1064
|
+
hideShortcutsOverlay();
|
|
1065
|
+
} else {
|
|
1066
|
+
var scenarios2 = getVisibleScenarios();
|
|
1067
|
+
if (focusedScenarioIndex >= 0 && focusedScenarioIndex < scenarios2.length) {
|
|
1068
|
+
var sc = scenarios2[focusedScenarioIndex];
|
|
1069
|
+
if (!sc.classList.contains('collapsed')) {
|
|
1070
|
+
sc.classList.add('collapsed');
|
|
1071
|
+
var sh = sc.querySelector('.scenario-header');
|
|
1072
|
+
if (sh) sh.setAttribute('aria-expanded', 'false');
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
break;
|
|
1077
|
+
case '/':
|
|
1078
|
+
e.preventDefault();
|
|
1079
|
+
var input = document.querySelector('.search-input');
|
|
1080
|
+
if (input) input.focus();
|
|
1081
|
+
break;
|
|
1082
|
+
case '?':
|
|
1083
|
+
e.preventDefault();
|
|
1084
|
+
if (document.querySelector('.shortcuts-overlay')) {
|
|
1085
|
+
hideShortcutsOverlay();
|
|
1086
|
+
} else {
|
|
1087
|
+
showShortcutsOverlay();
|
|
1088
|
+
}
|
|
1089
|
+
break;
|
|
1090
|
+
case 'e':
|
|
1091
|
+
e.preventDefault();
|
|
1092
|
+
expandAll();
|
|
1093
|
+
break;
|
|
1094
|
+
case 'c':
|
|
1095
|
+
e.preventDefault();
|
|
1096
|
+
collapseAll();
|
|
1097
|
+
break;
|
|
1098
|
+
case 't':
|
|
1099
|
+
e.preventDefault();
|
|
1100
|
+
if (typeof toggleToc === 'function') toggleToc();
|
|
1101
|
+
break;
|
|
978
1102
|
}
|
|
979
1103
|
});
|
|
980
1104
|
}
|
|
@@ -1112,6 +1236,189 @@ function writeUrlState() {
|
|
|
1112
1236
|
var url = window.location.pathname + (qs ? '?' + qs : '');
|
|
1113
1237
|
history.replaceState(null, '', url);
|
|
1114
1238
|
}
|
|
1239
|
+
|
|
1240
|
+
// Permalink copy
|
|
1241
|
+
function copyPermalink(anchorId) {
|
|
1242
|
+
var url = location.origin + location.pathname + location.search + '#' + anchorId;
|
|
1243
|
+
navigator.clipboard.writeText(url).then(function() {
|
|
1244
|
+
var el = document.getElementById(anchorId);
|
|
1245
|
+
if (el) showCopyToast(el);
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
function showCopyToast(el) {
|
|
1250
|
+
var existing = el.querySelector('.copy-toast');
|
|
1251
|
+
if (existing) existing.remove();
|
|
1252
|
+
var toast = document.createElement('span');
|
|
1253
|
+
toast.className = 'copy-toast';
|
|
1254
|
+
toast.textContent = 'Copied!';
|
|
1255
|
+
var header = el.querySelector('.feature-header, .scenario-header');
|
|
1256
|
+
if (header) {
|
|
1257
|
+
header.style.position = 'relative';
|
|
1258
|
+
header.appendChild(toast);
|
|
1259
|
+
}
|
|
1260
|
+
setTimeout(function() { toast.remove(); }, 1500);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// Copy scenario as markdown
|
|
1264
|
+
function copyScenarioAsMarkdown(scenarioId) {
|
|
1265
|
+
var scenario = document.getElementById(scenarioId);
|
|
1266
|
+
if (!scenario) return;
|
|
1267
|
+
|
|
1268
|
+
var title = (scenario.querySelector('.scenario-name') || {}).textContent || '';
|
|
1269
|
+
var steps = scenario.querySelectorAll('.step, .step.continuation');
|
|
1270
|
+
var lines = ['### Scenario: ' + title.trim(), ''];
|
|
1271
|
+
|
|
1272
|
+
steps.forEach(function(step) {
|
|
1273
|
+
var keyword = step.getAttribute('data-keyword') || '';
|
|
1274
|
+
var text = step.getAttribute('data-text') || '';
|
|
1275
|
+
lines.push('- **' + keyword + '** ' + text);
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
var errorBox = scenario.querySelector('.error-message');
|
|
1279
|
+
if (errorBox) {
|
|
1280
|
+
var errorText = errorBox.textContent || '';
|
|
1281
|
+
lines.push('');
|
|
1282
|
+
lines.push('> **Error:** ' + errorText.trim());
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
var md = lines.join('\\n');
|
|
1286
|
+
navigator.clipboard.writeText(md).then(function() {
|
|
1287
|
+
showCopyToast(scenario);
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// Hash scroll on load
|
|
1292
|
+
function initHashScroll() {
|
|
1293
|
+
if (!location.hash) return;
|
|
1294
|
+
var target = document.querySelector(location.hash);
|
|
1295
|
+
if (!target) return;
|
|
1296
|
+
var feature = target.closest('.feature');
|
|
1297
|
+
if (feature && feature.classList.contains('collapsed')) {
|
|
1298
|
+
feature.classList.remove('collapsed');
|
|
1299
|
+
var fh = feature.querySelector('.feature-header');
|
|
1300
|
+
if (fh) fh.setAttribute('aria-expanded', 'true');
|
|
1301
|
+
}
|
|
1302
|
+
if (target.classList.contains('collapsed')) {
|
|
1303
|
+
target.classList.remove('collapsed');
|
|
1304
|
+
var sh = target.querySelector('.scenario-header');
|
|
1305
|
+
if (sh) sh.setAttribute('aria-expanded', 'true');
|
|
1306
|
+
}
|
|
1307
|
+
setTimeout(function() {
|
|
1308
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1309
|
+
target.classList.add('hash-highlight');
|
|
1310
|
+
}, 100);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// Table of contents
|
|
1314
|
+
function toggleToc() {
|
|
1315
|
+
var sidebar = document.querySelector('.toc-sidebar');
|
|
1316
|
+
var wrapper = document.querySelector('.report-layout');
|
|
1317
|
+
if (!sidebar || !wrapper) return;
|
|
1318
|
+
var isMobile = window.matchMedia('(max-width: 767px)').matches;
|
|
1319
|
+
if (isMobile) {
|
|
1320
|
+
sidebar.classList.toggle('toc-mobile-open');
|
|
1321
|
+
} else {
|
|
1322
|
+
wrapper.classList.toggle('toc-hidden');
|
|
1323
|
+
var hidden = wrapper.classList.contains('toc-hidden');
|
|
1324
|
+
localStorage.setItem('toc-visible', String(!hidden));
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
function initToc() {
|
|
1329
|
+
var sidebar = document.querySelector('.toc-sidebar');
|
|
1330
|
+
if (!sidebar) return;
|
|
1331
|
+
|
|
1332
|
+
var saved = localStorage.getItem('toc-visible');
|
|
1333
|
+
var wrapper = document.querySelector('.report-layout');
|
|
1334
|
+
if (saved === 'false' && wrapper) {
|
|
1335
|
+
wrapper.classList.add('toc-hidden');
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// Active tracking via IntersectionObserver
|
|
1339
|
+
var observer = new IntersectionObserver(function(entries) {
|
|
1340
|
+
entries.forEach(function(entry) {
|
|
1341
|
+
if (entry.isIntersecting) {
|
|
1342
|
+
var id = entry.target.id;
|
|
1343
|
+
if (!id) return;
|
|
1344
|
+
document.querySelectorAll('.toc-scenario, .toc-feature-toggle').forEach(function(el) {
|
|
1345
|
+
el.classList.remove('toc-active');
|
|
1346
|
+
});
|
|
1347
|
+
var tocLink = sidebar.querySelector('a[href="#' + id + '"]');
|
|
1348
|
+
if (tocLink) tocLink.classList.add('toc-active');
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
}, { rootMargin: '-10% 0px -80% 0px' });
|
|
1352
|
+
|
|
1353
|
+
document.querySelectorAll('.feature, .scenario').forEach(function(el) {
|
|
1354
|
+
if (el.id) observer.observe(el);
|
|
1355
|
+
});
|
|
1356
|
+
|
|
1357
|
+
// Click navigation: expand collapsed parents
|
|
1358
|
+
sidebar.querySelectorAll('.toc-scenario').forEach(function(link) {
|
|
1359
|
+
link.addEventListener('click', function(e) {
|
|
1360
|
+
var hash = link.getAttribute('href');
|
|
1361
|
+
if (!hash) return;
|
|
1362
|
+
var target = document.querySelector(hash);
|
|
1363
|
+
if (!target) return;
|
|
1364
|
+
var feature = target.closest('.feature');
|
|
1365
|
+
if (feature && feature.classList.contains('collapsed')) {
|
|
1366
|
+
feature.classList.remove('collapsed');
|
|
1367
|
+
var fh = feature.querySelector('.feature-header');
|
|
1368
|
+
if (fh) fh.setAttribute('aria-expanded', 'true');
|
|
1369
|
+
}
|
|
1370
|
+
if (target.classList.contains('collapsed')) {
|
|
1371
|
+
target.classList.remove('collapsed');
|
|
1372
|
+
var sh = target.querySelector('.scenario-header');
|
|
1373
|
+
if (sh) sh.setAttribute('aria-expanded', 'true');
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// Theme picker
|
|
1380
|
+
function initThemePicker() {
|
|
1381
|
+
var picker = document.querySelector('.theme-picker');
|
|
1382
|
+
if (!picker) return;
|
|
1383
|
+
|
|
1384
|
+
var saved = localStorage.getItem('report-theme');
|
|
1385
|
+
if (saved) {
|
|
1386
|
+
picker.value = saved;
|
|
1387
|
+
switchReportTheme(saved);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
picker.addEventListener('change', function(e) {
|
|
1391
|
+
switchReportTheme(e.target.value);
|
|
1392
|
+
localStorage.setItem('report-theme', e.target.value);
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
function switchReportTheme(name) {
|
|
1397
|
+
document.querySelectorAll('style[data-theme-name]').forEach(function(s) {
|
|
1398
|
+
s.disabled = s.dataset.themeName !== name;
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// Sync TOC visibility with filters
|
|
1403
|
+
function syncTocVisibility() {
|
|
1404
|
+
var sidebar = document.querySelector('.toc-sidebar');
|
|
1405
|
+
if (!sidebar) return;
|
|
1406
|
+
|
|
1407
|
+
sidebar.querySelectorAll('.toc-scenario').forEach(function(link) {
|
|
1408
|
+
var href = link.getAttribute('href');
|
|
1409
|
+
if (!href) return;
|
|
1410
|
+
var target = document.querySelector(href);
|
|
1411
|
+
link.style.display = (target && target.style.display !== 'none') ? '' : 'none';
|
|
1412
|
+
});
|
|
1413
|
+
|
|
1414
|
+
sidebar.querySelectorAll('.toc-feature').forEach(function(feature) {
|
|
1415
|
+
var visibleScenarios = feature.querySelectorAll('.toc-scenario');
|
|
1416
|
+
var anyVisible = Array.from(visibleScenarios).some(function(s) {
|
|
1417
|
+
return s.style.display !== 'none';
|
|
1418
|
+
});
|
|
1419
|
+
feature.style.display = anyVisible ? '' : 'none';
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1115
1422
|
`;
|
|
1116
1423
|
var JS_MARKDOWN_FN = `
|
|
1117
1424
|
function parseMarkdownSections(marked) {
|
|
@@ -1152,6 +1459,9 @@ function generateScript(options) {
|
|
|
1152
1459
|
initCalls.push("initCollapse();");
|
|
1153
1460
|
initCalls.push("initDetailLevel();");
|
|
1154
1461
|
initCalls.push("applyAllFilters();");
|
|
1462
|
+
initCalls.push("initHashScroll();");
|
|
1463
|
+
initCalls.push("initToc();");
|
|
1464
|
+
initCalls.push("initThemePicker();");
|
|
1155
1465
|
const initScript = `
|
|
1156
1466
|
// Initialize on load
|
|
1157
1467
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -1213,6 +1523,7 @@ function generateHtmlTemplate(title, styles, body, options = {}) {
|
|
|
1213
1523
|
}
|
|
1214
1524
|
const cdnStylesHtml = cdnStyles.length > 0 ? "\n " + cdnStyles.join("\n ") : "";
|
|
1215
1525
|
const esmScriptHtml = generateEsmScript(options);
|
|
1526
|
+
const additionalThemeStyles = (options.additionalThemeCss ?? []).map((t) => `<style data-theme-name="${escapeHtml(t.name)}" disabled>${t.css}</style>`).join("\n ");
|
|
1216
1527
|
return `<!DOCTYPE html>
|
|
1217
1528
|
<html lang="en"${themeAttr} data-detail-level="full">
|
|
1218
1529
|
<head>
|
|
@@ -1220,19 +1531,27 @@ function generateHtmlTemplate(title, styles, body, options = {}) {
|
|
|
1220
1531
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1221
1532
|
<meta name="color-scheme" content="light dark">
|
|
1222
1533
|
<title>${escapeHtml(title)}</title>${cdnStylesHtml}
|
|
1223
|
-
<style>${styles}</style>
|
|
1534
|
+
<style${options.additionalThemeCss ? ` data-theme-name="${escapeHtml(options.activeThemeName ?? "default")}"` : ""}>${styles}</style>
|
|
1535
|
+
${additionalThemeStyles}
|
|
1224
1536
|
</head>
|
|
1225
1537
|
<body>
|
|
1226
|
-
<div class="
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
<div class="
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1538
|
+
<div class="report-layout">
|
|
1539
|
+
${options.tocHtml ?? ""}
|
|
1540
|
+
<div class="main-content">
|
|
1541
|
+
<div class="container">
|
|
1542
|
+
<header class="header">
|
|
1543
|
+
<h1>${escapeHtml(title)}</h1>
|
|
1544
|
+
<div class="header-actions">
|
|
1545
|
+
<button type="button" class="toc-toggle" onclick="toggleToc()" aria-label="Toggle table of contents" title="Toggle contents">☰</button>
|
|
1546
|
+
${includeSearch ? '<input type="text" class="search-input" placeholder="Search scenarios..." aria-label="Search scenarios">' : ""}
|
|
1547
|
+
<button type="button" class="detail-toggle" onclick="toggleDetailLevel()" aria-label="Toggle detail level" title="Toggle documentation detail"></button>
|
|
1548
|
+
${options.themePickerHtml ?? ""}
|
|
1549
|
+
${includeDarkMode ? '<button type="button" class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle theme"></button>' : ""}
|
|
1550
|
+
</div>
|
|
1551
|
+
</header>
|
|
1552
|
+
${body}
|
|
1233
1553
|
</div>
|
|
1234
|
-
</
|
|
1235
|
-
${body}
|
|
1554
|
+
</div>
|
|
1236
1555
|
</div>
|
|
1237
1556
|
<script>${script}</script>${esmScriptHtml}
|
|
1238
1557
|
</body>
|
|
@@ -2936,6 +3255,337 @@ body {
|
|
|
2936
3255
|
display: none;
|
|
2937
3256
|
}
|
|
2938
3257
|
|
|
3258
|
+
/* ============================================================================
|
|
3259
|
+
Permalink Anchors
|
|
3260
|
+
============================================================================ */
|
|
3261
|
+
.permalink-anchor {
|
|
3262
|
+
display: inline-flex;
|
|
3263
|
+
align-items: center;
|
|
3264
|
+
justify-content: center;
|
|
3265
|
+
width: 1.5rem;
|
|
3266
|
+
height: 1.5rem;
|
|
3267
|
+
border: none;
|
|
3268
|
+
background: none;
|
|
3269
|
+
color: var(--muted-foreground);
|
|
3270
|
+
cursor: pointer;
|
|
3271
|
+
opacity: 0;
|
|
3272
|
+
transition: opacity 0.15s ease;
|
|
3273
|
+
font-size: 0.875rem;
|
|
3274
|
+
font-weight: 600;
|
|
3275
|
+
padding: 0;
|
|
3276
|
+
flex-shrink: 0;
|
|
3277
|
+
}
|
|
3278
|
+
|
|
3279
|
+
.feature-header:hover .permalink-anchor,
|
|
3280
|
+
.scenario-header:hover .permalink-anchor,
|
|
3281
|
+
.permalink-anchor:focus-visible {
|
|
3282
|
+
opacity: 1;
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
.permalink-anchor:hover {
|
|
3286
|
+
color: var(--primary);
|
|
3287
|
+
}
|
|
3288
|
+
|
|
3289
|
+
.copy-toast {
|
|
3290
|
+
position: absolute;
|
|
3291
|
+
right: 0.5rem;
|
|
3292
|
+
top: 50%;
|
|
3293
|
+
transform: translateY(-50%);
|
|
3294
|
+
background: var(--foreground);
|
|
3295
|
+
color: var(--background);
|
|
3296
|
+
padding: 0.25rem 0.5rem;
|
|
3297
|
+
border-radius: var(--radius);
|
|
3298
|
+
font-size: 0.75rem;
|
|
3299
|
+
font-weight: 500;
|
|
3300
|
+
pointer-events: none;
|
|
3301
|
+
animation: fadeOut 1.5s ease forwards;
|
|
3302
|
+
z-index: 10;
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
@keyframes fadeOut {
|
|
3306
|
+
0%, 70% { opacity: 1; }
|
|
3307
|
+
100% { opacity: 0; }
|
|
3308
|
+
}
|
|
3309
|
+
|
|
3310
|
+
.hash-highlight {
|
|
3311
|
+
animation: hashPulse 2s ease;
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
@keyframes hashPulse {
|
|
3315
|
+
0%, 100% { background: transparent; }
|
|
3316
|
+
20% { background: color-mix(in srgb, var(--primary) 12%, transparent); }
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
.scenario-actions {
|
|
3320
|
+
display: flex;
|
|
3321
|
+
align-items: center;
|
|
3322
|
+
gap: 0.25rem;
|
|
3323
|
+
flex-shrink: 0;
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
.copy-scenario-btn {
|
|
3327
|
+
display: inline-flex;
|
|
3328
|
+
align-items: center;
|
|
3329
|
+
justify-content: center;
|
|
3330
|
+
width: 1.5rem;
|
|
3331
|
+
height: 1.5rem;
|
|
3332
|
+
border: none;
|
|
3333
|
+
background: none;
|
|
3334
|
+
color: var(--muted-foreground);
|
|
3335
|
+
cursor: pointer;
|
|
3336
|
+
opacity: 0;
|
|
3337
|
+
transition: opacity 0.15s ease;
|
|
3338
|
+
font-size: 0.875rem;
|
|
3339
|
+
padding: 0;
|
|
3340
|
+
flex-shrink: 0;
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
.scenario-header:hover .copy-scenario-btn,
|
|
3344
|
+
.copy-scenario-btn:focus-visible {
|
|
3345
|
+
opacity: 1;
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3348
|
+
.copy-scenario-btn:hover {
|
|
3349
|
+
color: var(--primary);
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
/* ============================================================================
|
|
3353
|
+
Keyboard Navigation
|
|
3354
|
+
============================================================================ */
|
|
3355
|
+
.scenario-focused {
|
|
3356
|
+
border-left: 2px solid var(--primary);
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
.shortcuts-overlay {
|
|
3360
|
+
position: fixed;
|
|
3361
|
+
inset: 0;
|
|
3362
|
+
background: rgb(0 0 0 / 0.5);
|
|
3363
|
+
display: flex;
|
|
3364
|
+
align-items: center;
|
|
3365
|
+
justify-content: center;
|
|
3366
|
+
z-index: 100;
|
|
3367
|
+
}
|
|
3368
|
+
|
|
3369
|
+
.shortcuts-modal {
|
|
3370
|
+
background: var(--card);
|
|
3371
|
+
color: var(--card-foreground);
|
|
3372
|
+
border: 1px solid var(--border);
|
|
3373
|
+
border-radius: calc(var(--radius) * 2);
|
|
3374
|
+
padding: 1.5rem 2rem;
|
|
3375
|
+
max-width: 400px;
|
|
3376
|
+
width: 90vw;
|
|
3377
|
+
box-shadow: var(--shadow-md, 0 4px 12px rgb(0 0 0 / 0.15));
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
.shortcuts-title {
|
|
3381
|
+
font-weight: 600;
|
|
3382
|
+
font-size: 1.125rem;
|
|
3383
|
+
margin-bottom: 1rem;
|
|
3384
|
+
padding-bottom: 0.5rem;
|
|
3385
|
+
border-bottom: 1px solid var(--border);
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3388
|
+
.shortcuts-grid {
|
|
3389
|
+
display: grid;
|
|
3390
|
+
grid-template-columns: auto 1fr;
|
|
3391
|
+
gap: 0.5rem 1rem;
|
|
3392
|
+
align-items: center;
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3395
|
+
.shortcuts-grid kbd {
|
|
3396
|
+
display: inline-flex;
|
|
3397
|
+
align-items: center;
|
|
3398
|
+
justify-content: center;
|
|
3399
|
+
min-width: 1.75rem;
|
|
3400
|
+
padding: 0.125rem 0.375rem;
|
|
3401
|
+
background: var(--muted);
|
|
3402
|
+
border: 1px solid var(--border);
|
|
3403
|
+
border-radius: calc(var(--radius) * 0.5);
|
|
3404
|
+
font-family: var(--font-mono);
|
|
3405
|
+
font-size: 0.75rem;
|
|
3406
|
+
font-weight: 500;
|
|
3407
|
+
color: var(--muted-foreground);
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
.shortcuts-grid span {
|
|
3411
|
+
font-size: 0.875rem;
|
|
3412
|
+
color: var(--foreground);
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
/* ============================================================================
|
|
3416
|
+
Table of Contents Sidebar
|
|
3417
|
+
============================================================================ */
|
|
3418
|
+
.report-layout {
|
|
3419
|
+
display: flex;
|
|
3420
|
+
min-height: 100vh;
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
.report-layout.toc-hidden .toc-sidebar {
|
|
3424
|
+
display: none;
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
.main-content {
|
|
3428
|
+
flex: 1;
|
|
3429
|
+
min-width: 0;
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
.toc-sidebar {
|
|
3433
|
+
width: 260px;
|
|
3434
|
+
flex-shrink: 0;
|
|
3435
|
+
position: sticky;
|
|
3436
|
+
top: 0;
|
|
3437
|
+
height: 100vh;
|
|
3438
|
+
overflow-y: auto;
|
|
3439
|
+
border-right: 1px solid var(--border);
|
|
3440
|
+
background: var(--card);
|
|
3441
|
+
padding: 1rem 0;
|
|
3442
|
+
font-size: 0.8125rem;
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
.toc-header {
|
|
3446
|
+
padding: 0 1rem 0.75rem;
|
|
3447
|
+
border-bottom: 1px solid var(--border);
|
|
3448
|
+
margin-bottom: 0.5rem;
|
|
3449
|
+
}
|
|
3450
|
+
|
|
3451
|
+
.toc-title {
|
|
3452
|
+
font-weight: 600;
|
|
3453
|
+
font-size: 0.875rem;
|
|
3454
|
+
color: var(--foreground);
|
|
3455
|
+
text-decoration: none;
|
|
3456
|
+
cursor: pointer;
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
a.toc-title:hover {
|
|
3460
|
+
color: var(--primary);
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
.toc-feature {
|
|
3464
|
+
margin-bottom: 0.25rem;
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3467
|
+
.toc-feature-toggle {
|
|
3468
|
+
display: flex;
|
|
3469
|
+
align-items: center;
|
|
3470
|
+
width: 100%;
|
|
3471
|
+
padding: 0.375rem 1rem;
|
|
3472
|
+
border: none;
|
|
3473
|
+
background: none;
|
|
3474
|
+
text-align: left;
|
|
3475
|
+
cursor: pointer;
|
|
3476
|
+
font-size: 0.8125rem;
|
|
3477
|
+
font-weight: 600;
|
|
3478
|
+
color: var(--foreground);
|
|
3479
|
+
font-family: var(--font-sans);
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3482
|
+
.toc-feature-toggle:hover {
|
|
3483
|
+
background: var(--accent);
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3486
|
+
.toc-feature-toggle[aria-expanded="false"] + .toc-scenarios {
|
|
3487
|
+
display: none;
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
.toc-scenarios {
|
|
3491
|
+
display: flex;
|
|
3492
|
+
flex-direction: column;
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
.toc-scenario {
|
|
3496
|
+
display: flex;
|
|
3497
|
+
align-items: baseline;
|
|
3498
|
+
gap: 0.375rem;
|
|
3499
|
+
padding: 0.25rem 1rem 0.25rem 1.5rem;
|
|
3500
|
+
color: var(--muted-foreground);
|
|
3501
|
+
text-decoration: none;
|
|
3502
|
+
font-size: 0.8125rem;
|
|
3503
|
+
line-height: 1.4;
|
|
3504
|
+
border-left: 2px solid transparent;
|
|
3505
|
+
transition: all 0.1s ease;
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
.toc-scenario:hover {
|
|
3509
|
+
color: var(--foreground);
|
|
3510
|
+
background: var(--accent);
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
.toc-scenario.toc-active {
|
|
3514
|
+
color: var(--foreground);
|
|
3515
|
+
border-left-color: var(--primary);
|
|
3516
|
+
font-weight: 500;
|
|
3517
|
+
}
|
|
3518
|
+
|
|
3519
|
+
.toc-scenario.toc-failed {
|
|
3520
|
+
border-left-color: var(--error, var(--destructive));
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
.toc-status {
|
|
3524
|
+
flex-shrink: 0;
|
|
3525
|
+
font-size: 0.75rem;
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3528
|
+
.toc-toggle {
|
|
3529
|
+
display: inline-flex;
|
|
3530
|
+
align-items: center;
|
|
3531
|
+
justify-content: center;
|
|
3532
|
+
width: 2.25rem;
|
|
3533
|
+
height: 2.25rem;
|
|
3534
|
+
border: 1px solid var(--border);
|
|
3535
|
+
border-radius: var(--radius);
|
|
3536
|
+
background: var(--background);
|
|
3537
|
+
cursor: pointer;
|
|
3538
|
+
color: var(--foreground);
|
|
3539
|
+
font-size: 1rem;
|
|
3540
|
+
transition: all 0.15s ease;
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3543
|
+
.toc-toggle:hover {
|
|
3544
|
+
background: var(--accent);
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
/* Mobile: overlay sidebar */
|
|
3548
|
+
@media (max-width: 767px) {
|
|
3549
|
+
.toc-sidebar {
|
|
3550
|
+
position: fixed;
|
|
3551
|
+
left: 0;
|
|
3552
|
+
top: 0;
|
|
3553
|
+
z-index: 50;
|
|
3554
|
+
box-shadow: var(--shadow-sm, 0 1px 3px rgb(0 0 0 / 0.1));
|
|
3555
|
+
transform: translateX(-100%);
|
|
3556
|
+
transition: transform 0.2s ease;
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
.toc-sidebar.toc-mobile-open {
|
|
3560
|
+
transform: translateX(0);
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
/* ============================================================================
|
|
3565
|
+
Theme Picker
|
|
3566
|
+
============================================================================ */
|
|
3567
|
+
.theme-picker {
|
|
3568
|
+
height: 2.25rem;
|
|
3569
|
+
padding: 0 0.5rem;
|
|
3570
|
+
border: 1px solid var(--border);
|
|
3571
|
+
border-radius: var(--radius);
|
|
3572
|
+
background: var(--background);
|
|
3573
|
+
color: var(--foreground);
|
|
3574
|
+
font-size: 0.8125rem;
|
|
3575
|
+
font-family: var(--font-sans);
|
|
3576
|
+
cursor: pointer;
|
|
3577
|
+
transition: all 0.15s ease;
|
|
3578
|
+
}
|
|
3579
|
+
|
|
3580
|
+
.theme-picker:hover {
|
|
3581
|
+
background: var(--accent);
|
|
3582
|
+
}
|
|
3583
|
+
|
|
3584
|
+
.theme-picker:focus-visible {
|
|
3585
|
+
outline: 2px solid var(--ring);
|
|
3586
|
+
outline-offset: 2px;
|
|
3587
|
+
}
|
|
3588
|
+
|
|
2939
3589
|
`;
|
|
2940
3590
|
|
|
2941
3591
|
// src/formatters/html/themes/default.ts
|
|
@@ -2985,7 +3635,7 @@ function corporateBuildBody(args, deps) {
|
|
|
2985
3635
|
const sidebar = `
|
|
2986
3636
|
<nav class="toc">
|
|
2987
3637
|
<div class="toc-header">
|
|
2988
|
-
<
|
|
3638
|
+
<a href="#" class="toc-title" onclick="window.scrollTo({top:0,behavior:'smooth'});return false;">Test Report</a>
|
|
2989
3639
|
<div class="toc-stats">
|
|
2990
3640
|
<div class="toc-stat-row">
|
|
2991
3641
|
<span class="toc-stat-label">Total</span>
|
|
@@ -12195,6 +12845,11 @@ function resolveTheme(nameOrTheme) {
|
|
|
12195
12845
|
function getAvailableThemes() {
|
|
12196
12846
|
return [...THEME_REGISTRY.keys()];
|
|
12197
12847
|
}
|
|
12848
|
+
function getCssOnlyThemes() {
|
|
12849
|
+
return [...THEME_REGISTRY.values()].filter(
|
|
12850
|
+
(theme) => !theme.buildBody && !theme.generateTemplate
|
|
12851
|
+
);
|
|
12852
|
+
}
|
|
12198
12853
|
|
|
12199
12854
|
// src/formatters/html/renderers/status.ts
|
|
12200
12855
|
function getStatusIcon(status) {
|
|
@@ -12476,7 +13131,7 @@ function renderStep(step, stepResult, index, deps) {
|
|
|
12476
13131
|
const stepClass = isContinuation ? "step continuation" : "step";
|
|
12477
13132
|
const stepDocs = deps.renderDocs(step.docs, "step-docs");
|
|
12478
13133
|
const textHtml = deps.highlightStepParams ? deps.highlightStepParams(step.text) : deps.escapeHtml(step.text);
|
|
12479
|
-
return `<div class="${stepClass}">
|
|
13134
|
+
return `<div class="${stepClass}" data-keyword="${deps.escapeHtml(keywordTrimmed)}" data-text="${deps.escapeHtml(step.text)}">
|
|
12480
13135
|
<span class="step-status ${statusClass}">${statusIcon}</span>
|
|
12481
13136
|
<span class="step-keyword">${deps.escapeHtml(step.keyword)}</span>
|
|
12482
13137
|
<span class="step-text">${textHtml}</span>
|
|
@@ -12607,7 +13262,11 @@ function renderScenario(args, deps) {
|
|
|
12607
13262
|
</div>
|
|
12608
13263
|
<div class="scenario-meta">${tags}${tickets}${sourceLink}${traceBadge}${metricBadges}</div>
|
|
12609
13264
|
</div>
|
|
12610
|
-
<
|
|
13265
|
+
<div class="scenario-actions">
|
|
13266
|
+
<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>
|
|
13267
|
+
<button class="permalink-anchor" onclick="copyPermalink('scenario-${tc.id}')" aria-label="Copy link to scenario" title="Copy link">#</button>
|
|
13268
|
+
<span class="scenario-duration">${duration}</span>
|
|
13269
|
+
</div>
|
|
12611
13270
|
</div>
|
|
12612
13271
|
<div class="scenario-content">
|
|
12613
13272
|
${storyDocs}
|
|
@@ -12817,6 +13476,7 @@ function renderFeature(args, deps) {
|
|
|
12817
13476
|
const featureName = suitePaths.length > 0 && suitePaths[0].length > 0 ? suitePaths[0][0] : file.split("/").pop()?.replace(/\.[^.]+$/, "") ?? file;
|
|
12818
13477
|
const collapsedClass = deps.startCollapsed ? " collapsed" : "";
|
|
12819
13478
|
const ariaExpanded = !deps.startCollapsed;
|
|
13479
|
+
const featureSlug = `feature-${slugify(file)}`;
|
|
12820
13480
|
const scenarios = testCases.map(
|
|
12821
13481
|
(tc) => deps.renderScenario(
|
|
12822
13482
|
{ tc, metrics: args.metricsMap?.get(tc.id) },
|
|
@@ -12824,8 +13484,9 @@ function renderFeature(args, deps) {
|
|
|
12824
13484
|
)
|
|
12825
13485
|
).join("\n");
|
|
12826
13486
|
return `
|
|
12827
|
-
<div class="feature${collapsedClass}">
|
|
13487
|
+
<div class="feature${collapsedClass}" id="${featureSlug}">
|
|
12828
13488
|
<div class="feature-header" role="button" tabindex="0" aria-expanded="${ariaExpanded}">
|
|
13489
|
+
<button class="permalink-anchor" onclick="copyPermalink('${featureSlug}')" aria-label="Copy link to feature" title="Copy link">#</button>
|
|
12829
13490
|
<div class="feature-info">
|
|
12830
13491
|
<div class="feature-title">${deps.escapeHtml(featureName)}</div>
|
|
12831
13492
|
<div class="feature-path">${deps.escapeHtml(file)}</div>
|
|
@@ -12938,6 +13599,57 @@ function renderFailureSummary(args, deps) {
|
|
|
12938
13599
|
</div>`;
|
|
12939
13600
|
}
|
|
12940
13601
|
|
|
13602
|
+
// src/formatters/html/renderers/toc.ts
|
|
13603
|
+
function groupBy4(items, keyFn) {
|
|
13604
|
+
const map = /* @__PURE__ */ new Map();
|
|
13605
|
+
for (const item of items) {
|
|
13606
|
+
const key = keyFn(item);
|
|
13607
|
+
const existing = map.get(key);
|
|
13608
|
+
if (existing) {
|
|
13609
|
+
existing.push(item);
|
|
13610
|
+
} else {
|
|
13611
|
+
map.set(key, [item]);
|
|
13612
|
+
}
|
|
13613
|
+
}
|
|
13614
|
+
return map;
|
|
13615
|
+
}
|
|
13616
|
+
function renderToc(args, deps) {
|
|
13617
|
+
const { run } = args;
|
|
13618
|
+
if (run.testCases.length === 0) return "";
|
|
13619
|
+
const byFile = groupBy4(run.testCases, (tc) => tc.sourceFile);
|
|
13620
|
+
const features = [];
|
|
13621
|
+
for (const [file, testCases] of byFile) {
|
|
13622
|
+
const suitePaths = testCases.map((tc) => tc.titlePath).filter((p) => p.length > 0);
|
|
13623
|
+
const featureName = suitePaths.length > 0 && suitePaths[0].length > 0 ? suitePaths[0][0] : file.split("/").pop()?.replace(/\.[^.]+$/, "") ?? file;
|
|
13624
|
+
const featureSlug = `feature-${slugify(file)}`;
|
|
13625
|
+
const scenarios = testCases.map((tc) => {
|
|
13626
|
+
const statusIcon = deps.getStatusIcon(tc.status);
|
|
13627
|
+
const statusClass = `status-${tc.status}`;
|
|
13628
|
+
const failedClass = tc.status === "failed" ? " toc-failed" : "";
|
|
13629
|
+
return `<a class="toc-scenario${failedClass}" href="#scenario-${tc.id}">
|
|
13630
|
+
<span class="toc-status ${statusClass}">${statusIcon}</span>
|
|
13631
|
+
${deps.escapeHtml(tc.story.scenario)}
|
|
13632
|
+
</a>`;
|
|
13633
|
+
}).join("\n");
|
|
13634
|
+
features.push(`<div class="toc-feature">
|
|
13635
|
+
<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}">
|
|
13636
|
+
${deps.escapeHtml(featureName)}
|
|
13637
|
+
</button>
|
|
13638
|
+
<div class="toc-scenarios">
|
|
13639
|
+
${scenarios}
|
|
13640
|
+
</div>
|
|
13641
|
+
</div>`);
|
|
13642
|
+
}
|
|
13643
|
+
return `<nav class="toc-sidebar" aria-label="Table of contents">
|
|
13644
|
+
<div class="toc-header">
|
|
13645
|
+
<a href="#" class="toc-title" onclick="window.scrollTo({top:0,behavior:'smooth'});return false;">Contents</a>
|
|
13646
|
+
</div>
|
|
13647
|
+
<div class="toc-body">
|
|
13648
|
+
${features.join("\n")}
|
|
13649
|
+
</div>
|
|
13650
|
+
</nav>`;
|
|
13651
|
+
}
|
|
13652
|
+
|
|
12941
13653
|
// src/formatters/html/renderers/index.ts
|
|
12942
13654
|
function normalizeOptions(options = {}) {
|
|
12943
13655
|
return {
|
|
@@ -12951,7 +13663,9 @@ function normalizeOptions(options = {}) {
|
|
|
12951
13663
|
markdownEnabled: options.markdownEnabled ?? true,
|
|
12952
13664
|
permalinkBaseUrl: options.permalinkBaseUrl,
|
|
12953
13665
|
ticketUrlTemplate: options.ticketUrlTemplate,
|
|
12954
|
-
|
|
13666
|
+
tocEnabled: options.tocEnabled ?? true,
|
|
13667
|
+
theme: options.theme ?? "default",
|
|
13668
|
+
themePickerEnabled: options.themePickerEnabled ?? false
|
|
12955
13669
|
};
|
|
12956
13670
|
}
|
|
12957
13671
|
function createHtmlFormatter(options = {}) {
|
|
@@ -12993,6 +13707,10 @@ function createHtmlFormatter(options = {}) {
|
|
|
12993
13707
|
scenarioDeps
|
|
12994
13708
|
};
|
|
12995
13709
|
const tagBarDeps = { escapeHtml };
|
|
13710
|
+
const tocDeps = {
|
|
13711
|
+
escapeHtml,
|
|
13712
|
+
getStatusIcon
|
|
13713
|
+
};
|
|
12996
13714
|
const bodyDeps = {
|
|
12997
13715
|
renderMetaInfo,
|
|
12998
13716
|
renderSummary,
|
|
@@ -13011,6 +13729,16 @@ function createHtmlFormatter(options = {}) {
|
|
|
13011
13729
|
const bodyFn = theme.buildBody ?? buildBody;
|
|
13012
13730
|
const body = bodyFn({ run }, bodyDeps);
|
|
13013
13731
|
const templateFn = theme.generateTemplate ?? generateHtmlTemplate;
|
|
13732
|
+
const isStructuralTheme = !!(theme.buildBody || theme.generateTemplate);
|
|
13733
|
+
const tocHtml = opts.tocEnabled && !isStructuralTheme ? renderToc({ run }, tocDeps) : void 0;
|
|
13734
|
+
let themePickerHtml;
|
|
13735
|
+
let additionalThemeCss;
|
|
13736
|
+
if (opts.themePickerEnabled) {
|
|
13737
|
+
const cssOnlyThemes = getCssOnlyThemes();
|
|
13738
|
+
const pickerOptions = cssOnlyThemes.map((t) => `<option value="${t.name}"${t.name === theme.name ? " selected" : ""}>${t.label}</option>`).join("");
|
|
13739
|
+
themePickerHtml = `<select class="theme-picker" aria-label="Select theme">${pickerOptions}</select>`;
|
|
13740
|
+
additionalThemeCss = cssOnlyThemes.filter((t) => t.name !== theme.name).map((t) => ({ name: t.name, label: t.label, css: t.css }));
|
|
13741
|
+
}
|
|
13014
13742
|
return templateFn(
|
|
13015
13743
|
opts.title,
|
|
13016
13744
|
theme.css,
|
|
@@ -13022,7 +13750,11 @@ function createHtmlFormatter(options = {}) {
|
|
|
13022
13750
|
mermaidEnabled: opts.mermaidEnabled,
|
|
13023
13751
|
markdownEnabled: opts.markdownEnabled,
|
|
13024
13752
|
additionalJs: theme.additionalJs,
|
|
13025
|
-
additionalImports: theme.additionalImports
|
|
13753
|
+
additionalImports: theme.additionalImports,
|
|
13754
|
+
tocHtml,
|
|
13755
|
+
themePickerHtml,
|
|
13756
|
+
additionalThemeCss,
|
|
13757
|
+
activeThemeName: theme.name
|
|
13026
13758
|
}
|
|
13027
13759
|
);
|
|
13028
13760
|
}
|
|
@@ -13078,7 +13810,7 @@ var JUnitFormatter = class {
|
|
|
13078
13810
|
lines.push(
|
|
13079
13811
|
`<testsuites name="${escapeXml(this.options.suiteName)}" tests="${tests}" failures="${failures}" errors="${errors}" skipped="${skipped}" time="${time}">`
|
|
13080
13812
|
);
|
|
13081
|
-
const byFile =
|
|
13813
|
+
const byFile = groupBy5(run.testCases, (tc) => tc.sourceFile);
|
|
13082
13814
|
for (const [file, testCases] of byFile) {
|
|
13083
13815
|
lines.push(...this.buildTestSuite(file, testCases, indent, newline));
|
|
13084
13816
|
}
|
|
@@ -13251,7 +13983,7 @@ var JUnitFormatter = class {
|
|
|
13251
13983
|
function escapeXml(str) {
|
|
13252
13984
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
13253
13985
|
}
|
|
13254
|
-
function
|
|
13986
|
+
function groupBy5(items, keyFn) {
|
|
13255
13987
|
const map = /* @__PURE__ */ new Map();
|
|
13256
13988
|
for (const item of items) {
|
|
13257
13989
|
const key = keyFn(item);
|
|
@@ -13426,7 +14158,7 @@ var MarkdownFormatter = class {
|
|
|
13426
14158
|
* Render scenarios grouped by file.
|
|
13427
14159
|
*/
|
|
13428
14160
|
renderByFile(lines, testCases) {
|
|
13429
|
-
const byFile =
|
|
14161
|
+
const byFile = groupBy6(testCases, (tc) => tc.sourceFile);
|
|
13430
14162
|
for (const [file, fileTestCases] of byFile) {
|
|
13431
14163
|
lines.push(`## ${file}`);
|
|
13432
14164
|
lines.push("");
|
|
@@ -13443,7 +14175,7 @@ var MarkdownFormatter = class {
|
|
|
13443
14175
|
* Render suite groups.
|
|
13444
14176
|
*/
|
|
13445
14177
|
renderSuiteGroups(lines, testCases, baseLevel) {
|
|
13446
|
-
const bySuite =
|
|
14178
|
+
const bySuite = groupBy6(
|
|
13447
14179
|
testCases,
|
|
13448
14180
|
(tc) => tc.titlePath.join(this.options.suiteSeparator)
|
|
13449
14181
|
);
|
|
@@ -13737,7 +14469,7 @@ var MarkdownFormatter = class {
|
|
|
13737
14469
|
return entries;
|
|
13738
14470
|
}
|
|
13739
14471
|
};
|
|
13740
|
-
function
|
|
14472
|
+
function groupBy6(items, keyFn) {
|
|
13741
14473
|
const map = /* @__PURE__ */ new Map();
|
|
13742
14474
|
for (const item of items) {
|
|
13743
14475
|
const key = keyFn(item);
|
|
@@ -17449,7 +18181,7 @@ var ReportGenerator = class {
|
|
|
17449
18181
|
excludeTags: options.excludeTags ?? [],
|
|
17450
18182
|
formats: options.formats ?? ["cucumber-json"],
|
|
17451
18183
|
outputDir: options.outputDir ?? "reports",
|
|
17452
|
-
outputName: options.outputName ?? "
|
|
18184
|
+
outputName: options.outputName ?? "index",
|
|
17453
18185
|
outputNameTimestamp: options.outputNameTimestamp ?? false,
|
|
17454
18186
|
sortTestCases: options.sortTestCases ?? "none",
|
|
17455
18187
|
output: {
|
|
@@ -17478,7 +18210,9 @@ var ReportGenerator = class {
|
|
|
17478
18210
|
markdownEnabled: options.html?.markdownEnabled ?? true,
|
|
17479
18211
|
permalinkBaseUrl: options.html?.permalinkBaseUrl,
|
|
17480
18212
|
ticketUrlTemplate: options.html?.ticketUrlTemplate,
|
|
17481
|
-
theme: options.html?.theme ?? "default"
|
|
18213
|
+
theme: options.html?.theme ?? "default",
|
|
18214
|
+
tocEnabled: options.html?.tocEnabled ?? true,
|
|
18215
|
+
themePickerEnabled: options.html?.themePickerEnabled ?? false
|
|
17482
18216
|
},
|
|
17483
18217
|
junit: {
|
|
17484
18218
|
suiteName: options.junit?.suiteName ?? "Test Suite",
|
|
@@ -17601,7 +18335,9 @@ var ReportGenerator = class {
|
|
|
17601
18335
|
mermaidEnabled: this.options.html.mermaidEnabled,
|
|
17602
18336
|
markdownEnabled: this.options.html.markdownEnabled,
|
|
17603
18337
|
permalinkBaseUrl: this.options.html.permalinkBaseUrl,
|
|
17604
|
-
ticketUrlTemplate: this.options.html.ticketUrlTemplate
|
|
18338
|
+
ticketUrlTemplate: this.options.html.ticketUrlTemplate,
|
|
18339
|
+
tocEnabled: this.options.html.tocEnabled,
|
|
18340
|
+
themePickerEnabled: this.options.html.themePickerEnabled
|
|
17605
18341
|
});
|
|
17606
18342
|
return formatter.format(run);
|
|
17607
18343
|
}
|
|
@@ -17725,6 +18461,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
17725
18461
|
generateRunId,
|
|
17726
18462
|
generateTestCaseId,
|
|
17727
18463
|
getAvailableThemes,
|
|
18464
|
+
getCssOnlyThemes,
|
|
17728
18465
|
hasSufficientHistory,
|
|
17729
18466
|
listScenarios,
|
|
17730
18467
|
loadHistory,
|