executable-stories-formatters 0.7.4 → 0.7.5
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 +765 -28
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +758 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +757 -27
- 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,331 @@ 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
|
+
}
|
|
3456
|
+
|
|
3457
|
+
.toc-feature {
|
|
3458
|
+
margin-bottom: 0.25rem;
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
.toc-feature-toggle {
|
|
3462
|
+
display: flex;
|
|
3463
|
+
align-items: center;
|
|
3464
|
+
width: 100%;
|
|
3465
|
+
padding: 0.375rem 1rem;
|
|
3466
|
+
border: none;
|
|
3467
|
+
background: none;
|
|
3468
|
+
text-align: left;
|
|
3469
|
+
cursor: pointer;
|
|
3470
|
+
font-size: 0.8125rem;
|
|
3471
|
+
font-weight: 600;
|
|
3472
|
+
color: var(--foreground);
|
|
3473
|
+
font-family: var(--font-sans);
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
.toc-feature-toggle:hover {
|
|
3477
|
+
background: var(--accent);
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
.toc-feature-toggle[aria-expanded="false"] + .toc-scenarios {
|
|
3481
|
+
display: none;
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
.toc-scenarios {
|
|
3485
|
+
display: flex;
|
|
3486
|
+
flex-direction: column;
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3489
|
+
.toc-scenario {
|
|
3490
|
+
display: flex;
|
|
3491
|
+
align-items: baseline;
|
|
3492
|
+
gap: 0.375rem;
|
|
3493
|
+
padding: 0.25rem 1rem 0.25rem 1.5rem;
|
|
3494
|
+
color: var(--muted-foreground);
|
|
3495
|
+
text-decoration: none;
|
|
3496
|
+
font-size: 0.8125rem;
|
|
3497
|
+
line-height: 1.4;
|
|
3498
|
+
border-left: 2px solid transparent;
|
|
3499
|
+
transition: all 0.1s ease;
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3502
|
+
.toc-scenario:hover {
|
|
3503
|
+
color: var(--foreground);
|
|
3504
|
+
background: var(--accent);
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
.toc-scenario.toc-active {
|
|
3508
|
+
color: var(--foreground);
|
|
3509
|
+
border-left-color: var(--primary);
|
|
3510
|
+
font-weight: 500;
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
.toc-scenario.toc-failed {
|
|
3514
|
+
border-left-color: var(--error, var(--destructive));
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
.toc-status {
|
|
3518
|
+
flex-shrink: 0;
|
|
3519
|
+
font-size: 0.75rem;
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3522
|
+
.toc-toggle {
|
|
3523
|
+
display: inline-flex;
|
|
3524
|
+
align-items: center;
|
|
3525
|
+
justify-content: center;
|
|
3526
|
+
width: 2.25rem;
|
|
3527
|
+
height: 2.25rem;
|
|
3528
|
+
border: 1px solid var(--border);
|
|
3529
|
+
border-radius: var(--radius);
|
|
3530
|
+
background: var(--background);
|
|
3531
|
+
cursor: pointer;
|
|
3532
|
+
color: var(--foreground);
|
|
3533
|
+
font-size: 1rem;
|
|
3534
|
+
transition: all 0.15s ease;
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
.toc-toggle:hover {
|
|
3538
|
+
background: var(--accent);
|
|
3539
|
+
}
|
|
3540
|
+
|
|
3541
|
+
/* Mobile: overlay sidebar */
|
|
3542
|
+
@media (max-width: 767px) {
|
|
3543
|
+
.toc-sidebar {
|
|
3544
|
+
position: fixed;
|
|
3545
|
+
left: 0;
|
|
3546
|
+
top: 0;
|
|
3547
|
+
z-index: 50;
|
|
3548
|
+
box-shadow: var(--shadow-sm, 0 1px 3px rgb(0 0 0 / 0.1));
|
|
3549
|
+
transform: translateX(-100%);
|
|
3550
|
+
transition: transform 0.2s ease;
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
.toc-sidebar.toc-mobile-open {
|
|
3554
|
+
transform: translateX(0);
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
/* ============================================================================
|
|
3559
|
+
Theme Picker
|
|
3560
|
+
============================================================================ */
|
|
3561
|
+
.theme-picker {
|
|
3562
|
+
height: 2.25rem;
|
|
3563
|
+
padding: 0 0.5rem;
|
|
3564
|
+
border: 1px solid var(--border);
|
|
3565
|
+
border-radius: var(--radius);
|
|
3566
|
+
background: var(--background);
|
|
3567
|
+
color: var(--foreground);
|
|
3568
|
+
font-size: 0.8125rem;
|
|
3569
|
+
font-family: var(--font-sans);
|
|
3570
|
+
cursor: pointer;
|
|
3571
|
+
transition: all 0.15s ease;
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
.theme-picker:hover {
|
|
3575
|
+
background: var(--accent);
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
.theme-picker:focus-visible {
|
|
3579
|
+
outline: 2px solid var(--ring);
|
|
3580
|
+
outline-offset: 2px;
|
|
3581
|
+
}
|
|
3582
|
+
|
|
2939
3583
|
`;
|
|
2940
3584
|
|
|
2941
3585
|
// src/formatters/html/themes/default.ts
|
|
@@ -12195,6 +12839,11 @@ function resolveTheme(nameOrTheme) {
|
|
|
12195
12839
|
function getAvailableThemes() {
|
|
12196
12840
|
return [...THEME_REGISTRY.keys()];
|
|
12197
12841
|
}
|
|
12842
|
+
function getCssOnlyThemes() {
|
|
12843
|
+
return [...THEME_REGISTRY.values()].filter(
|
|
12844
|
+
(theme) => !theme.buildBody && !theme.generateTemplate
|
|
12845
|
+
);
|
|
12846
|
+
}
|
|
12198
12847
|
|
|
12199
12848
|
// src/formatters/html/renderers/status.ts
|
|
12200
12849
|
function getStatusIcon(status) {
|
|
@@ -12476,7 +13125,7 @@ function renderStep(step, stepResult, index, deps) {
|
|
|
12476
13125
|
const stepClass = isContinuation ? "step continuation" : "step";
|
|
12477
13126
|
const stepDocs = deps.renderDocs(step.docs, "step-docs");
|
|
12478
13127
|
const textHtml = deps.highlightStepParams ? deps.highlightStepParams(step.text) : deps.escapeHtml(step.text);
|
|
12479
|
-
return `<div class="${stepClass}">
|
|
13128
|
+
return `<div class="${stepClass}" data-keyword="${deps.escapeHtml(keywordTrimmed)}" data-text="${deps.escapeHtml(step.text)}">
|
|
12480
13129
|
<span class="step-status ${statusClass}">${statusIcon}</span>
|
|
12481
13130
|
<span class="step-keyword">${deps.escapeHtml(step.keyword)}</span>
|
|
12482
13131
|
<span class="step-text">${textHtml}</span>
|
|
@@ -12607,7 +13256,11 @@ function renderScenario(args, deps) {
|
|
|
12607
13256
|
</div>
|
|
12608
13257
|
<div class="scenario-meta">${tags}${tickets}${sourceLink}${traceBadge}${metricBadges}</div>
|
|
12609
13258
|
</div>
|
|
12610
|
-
<
|
|
13259
|
+
<div class="scenario-actions">
|
|
13260
|
+
<button class="copy-scenario-btn" onclick="copyScenarioAsMarkdown('scenario-${tc.id}')" aria-label="Copy scenario as markdown" title="Copy as Markdown">⎘</button>
|
|
13261
|
+
<button class="permalink-anchor" onclick="copyPermalink('scenario-${tc.id}')" aria-label="Copy link to scenario" title="Copy link">#</button>
|
|
13262
|
+
<span class="scenario-duration">${duration}</span>
|
|
13263
|
+
</div>
|
|
12611
13264
|
</div>
|
|
12612
13265
|
<div class="scenario-content">
|
|
12613
13266
|
${storyDocs}
|
|
@@ -12817,6 +13470,7 @@ function renderFeature(args, deps) {
|
|
|
12817
13470
|
const featureName = suitePaths.length > 0 && suitePaths[0].length > 0 ? suitePaths[0][0] : file.split("/").pop()?.replace(/\.[^.]+$/, "") ?? file;
|
|
12818
13471
|
const collapsedClass = deps.startCollapsed ? " collapsed" : "";
|
|
12819
13472
|
const ariaExpanded = !deps.startCollapsed;
|
|
13473
|
+
const featureSlug = `feature-${slugify(file)}`;
|
|
12820
13474
|
const scenarios = testCases.map(
|
|
12821
13475
|
(tc) => deps.renderScenario(
|
|
12822
13476
|
{ tc, metrics: args.metricsMap?.get(tc.id) },
|
|
@@ -12824,8 +13478,9 @@ function renderFeature(args, deps) {
|
|
|
12824
13478
|
)
|
|
12825
13479
|
).join("\n");
|
|
12826
13480
|
return `
|
|
12827
|
-
<div class="feature${collapsedClass}">
|
|
13481
|
+
<div class="feature${collapsedClass}" id="${featureSlug}">
|
|
12828
13482
|
<div class="feature-header" role="button" tabindex="0" aria-expanded="${ariaExpanded}">
|
|
13483
|
+
<button class="permalink-anchor" onclick="copyPermalink('${featureSlug}')" aria-label="Copy link to feature" title="Copy link">#</button>
|
|
12829
13484
|
<div class="feature-info">
|
|
12830
13485
|
<div class="feature-title">${deps.escapeHtml(featureName)}</div>
|
|
12831
13486
|
<div class="feature-path">${deps.escapeHtml(file)}</div>
|
|
@@ -12938,6 +13593,57 @@ function renderFailureSummary(args, deps) {
|
|
|
12938
13593
|
</div>`;
|
|
12939
13594
|
}
|
|
12940
13595
|
|
|
13596
|
+
// src/formatters/html/renderers/toc.ts
|
|
13597
|
+
function groupBy4(items, keyFn) {
|
|
13598
|
+
const map = /* @__PURE__ */ new Map();
|
|
13599
|
+
for (const item of items) {
|
|
13600
|
+
const key = keyFn(item);
|
|
13601
|
+
const existing = map.get(key);
|
|
13602
|
+
if (existing) {
|
|
13603
|
+
existing.push(item);
|
|
13604
|
+
} else {
|
|
13605
|
+
map.set(key, [item]);
|
|
13606
|
+
}
|
|
13607
|
+
}
|
|
13608
|
+
return map;
|
|
13609
|
+
}
|
|
13610
|
+
function renderToc(args, deps) {
|
|
13611
|
+
const { run } = args;
|
|
13612
|
+
if (run.testCases.length === 0) return "";
|
|
13613
|
+
const byFile = groupBy4(run.testCases, (tc) => tc.sourceFile);
|
|
13614
|
+
const features = [];
|
|
13615
|
+
for (const [file, testCases] of byFile) {
|
|
13616
|
+
const suitePaths = testCases.map((tc) => tc.titlePath).filter((p) => p.length > 0);
|
|
13617
|
+
const featureName = suitePaths.length > 0 && suitePaths[0].length > 0 ? suitePaths[0][0] : file.split("/").pop()?.replace(/\.[^.]+$/, "") ?? file;
|
|
13618
|
+
const featureSlug = `feature-${slugify(file)}`;
|
|
13619
|
+
const scenarios = testCases.map((tc) => {
|
|
13620
|
+
const statusIcon = deps.getStatusIcon(tc.status);
|
|
13621
|
+
const statusClass = `status-${tc.status}`;
|
|
13622
|
+
const failedClass = tc.status === "failed" ? " toc-failed" : "";
|
|
13623
|
+
return `<a class="toc-scenario${failedClass}" href="#scenario-${tc.id}">
|
|
13624
|
+
<span class="toc-status ${statusClass}">${statusIcon}</span>
|
|
13625
|
+
${deps.escapeHtml(tc.story.scenario)}
|
|
13626
|
+
</a>`;
|
|
13627
|
+
}).join("\n");
|
|
13628
|
+
features.push(`<div class="toc-feature">
|
|
13629
|
+
<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}">
|
|
13630
|
+
${deps.escapeHtml(featureName)}
|
|
13631
|
+
</button>
|
|
13632
|
+
<div class="toc-scenarios">
|
|
13633
|
+
${scenarios}
|
|
13634
|
+
</div>
|
|
13635
|
+
</div>`);
|
|
13636
|
+
}
|
|
13637
|
+
return `<nav class="toc-sidebar" aria-label="Table of contents">
|
|
13638
|
+
<div class="toc-header">
|
|
13639
|
+
<span class="toc-title">Contents</span>
|
|
13640
|
+
</div>
|
|
13641
|
+
<div class="toc-body">
|
|
13642
|
+
${features.join("\n")}
|
|
13643
|
+
</div>
|
|
13644
|
+
</nav>`;
|
|
13645
|
+
}
|
|
13646
|
+
|
|
12941
13647
|
// src/formatters/html/renderers/index.ts
|
|
12942
13648
|
function normalizeOptions(options = {}) {
|
|
12943
13649
|
return {
|
|
@@ -12951,7 +13657,9 @@ function normalizeOptions(options = {}) {
|
|
|
12951
13657
|
markdownEnabled: options.markdownEnabled ?? true,
|
|
12952
13658
|
permalinkBaseUrl: options.permalinkBaseUrl,
|
|
12953
13659
|
ticketUrlTemplate: options.ticketUrlTemplate,
|
|
12954
|
-
|
|
13660
|
+
tocEnabled: options.tocEnabled ?? true,
|
|
13661
|
+
theme: options.theme ?? "default",
|
|
13662
|
+
themePickerEnabled: options.themePickerEnabled ?? false
|
|
12955
13663
|
};
|
|
12956
13664
|
}
|
|
12957
13665
|
function createHtmlFormatter(options = {}) {
|
|
@@ -12993,6 +13701,10 @@ function createHtmlFormatter(options = {}) {
|
|
|
12993
13701
|
scenarioDeps
|
|
12994
13702
|
};
|
|
12995
13703
|
const tagBarDeps = { escapeHtml };
|
|
13704
|
+
const tocDeps = {
|
|
13705
|
+
escapeHtml,
|
|
13706
|
+
getStatusIcon
|
|
13707
|
+
};
|
|
12996
13708
|
const bodyDeps = {
|
|
12997
13709
|
renderMetaInfo,
|
|
12998
13710
|
renderSummary,
|
|
@@ -13011,6 +13723,16 @@ function createHtmlFormatter(options = {}) {
|
|
|
13011
13723
|
const bodyFn = theme.buildBody ?? buildBody;
|
|
13012
13724
|
const body = bodyFn({ run }, bodyDeps);
|
|
13013
13725
|
const templateFn = theme.generateTemplate ?? generateHtmlTemplate;
|
|
13726
|
+
const isStructuralTheme = !!(theme.buildBody || theme.generateTemplate);
|
|
13727
|
+
const tocHtml = opts.tocEnabled && !isStructuralTheme ? renderToc({ run }, tocDeps) : void 0;
|
|
13728
|
+
let themePickerHtml;
|
|
13729
|
+
let additionalThemeCss;
|
|
13730
|
+
if (opts.themePickerEnabled) {
|
|
13731
|
+
const cssOnlyThemes = getCssOnlyThemes();
|
|
13732
|
+
const pickerOptions = cssOnlyThemes.map((t) => `<option value="${t.name}"${t.name === theme.name ? " selected" : ""}>${t.label}</option>`).join("");
|
|
13733
|
+
themePickerHtml = `<select class="theme-picker" aria-label="Select theme">${pickerOptions}</select>`;
|
|
13734
|
+
additionalThemeCss = cssOnlyThemes.filter((t) => t.name !== theme.name).map((t) => ({ name: t.name, label: t.label, css: t.css }));
|
|
13735
|
+
}
|
|
13014
13736
|
return templateFn(
|
|
13015
13737
|
opts.title,
|
|
13016
13738
|
theme.css,
|
|
@@ -13022,7 +13744,11 @@ function createHtmlFormatter(options = {}) {
|
|
|
13022
13744
|
mermaidEnabled: opts.mermaidEnabled,
|
|
13023
13745
|
markdownEnabled: opts.markdownEnabled,
|
|
13024
13746
|
additionalJs: theme.additionalJs,
|
|
13025
|
-
additionalImports: theme.additionalImports
|
|
13747
|
+
additionalImports: theme.additionalImports,
|
|
13748
|
+
tocHtml,
|
|
13749
|
+
themePickerHtml,
|
|
13750
|
+
additionalThemeCss,
|
|
13751
|
+
activeThemeName: theme.name
|
|
13026
13752
|
}
|
|
13027
13753
|
);
|
|
13028
13754
|
}
|
|
@@ -13078,7 +13804,7 @@ var JUnitFormatter = class {
|
|
|
13078
13804
|
lines.push(
|
|
13079
13805
|
`<testsuites name="${escapeXml(this.options.suiteName)}" tests="${tests}" failures="${failures}" errors="${errors}" skipped="${skipped}" time="${time}">`
|
|
13080
13806
|
);
|
|
13081
|
-
const byFile =
|
|
13807
|
+
const byFile = groupBy5(run.testCases, (tc) => tc.sourceFile);
|
|
13082
13808
|
for (const [file, testCases] of byFile) {
|
|
13083
13809
|
lines.push(...this.buildTestSuite(file, testCases, indent, newline));
|
|
13084
13810
|
}
|
|
@@ -13251,7 +13977,7 @@ var JUnitFormatter = class {
|
|
|
13251
13977
|
function escapeXml(str) {
|
|
13252
13978
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
13253
13979
|
}
|
|
13254
|
-
function
|
|
13980
|
+
function groupBy5(items, keyFn) {
|
|
13255
13981
|
const map = /* @__PURE__ */ new Map();
|
|
13256
13982
|
for (const item of items) {
|
|
13257
13983
|
const key = keyFn(item);
|
|
@@ -13426,7 +14152,7 @@ var MarkdownFormatter = class {
|
|
|
13426
14152
|
* Render scenarios grouped by file.
|
|
13427
14153
|
*/
|
|
13428
14154
|
renderByFile(lines, testCases) {
|
|
13429
|
-
const byFile =
|
|
14155
|
+
const byFile = groupBy6(testCases, (tc) => tc.sourceFile);
|
|
13430
14156
|
for (const [file, fileTestCases] of byFile) {
|
|
13431
14157
|
lines.push(`## ${file}`);
|
|
13432
14158
|
lines.push("");
|
|
@@ -13443,7 +14169,7 @@ var MarkdownFormatter = class {
|
|
|
13443
14169
|
* Render suite groups.
|
|
13444
14170
|
*/
|
|
13445
14171
|
renderSuiteGroups(lines, testCases, baseLevel) {
|
|
13446
|
-
const bySuite =
|
|
14172
|
+
const bySuite = groupBy6(
|
|
13447
14173
|
testCases,
|
|
13448
14174
|
(tc) => tc.titlePath.join(this.options.suiteSeparator)
|
|
13449
14175
|
);
|
|
@@ -13737,7 +14463,7 @@ var MarkdownFormatter = class {
|
|
|
13737
14463
|
return entries;
|
|
13738
14464
|
}
|
|
13739
14465
|
};
|
|
13740
|
-
function
|
|
14466
|
+
function groupBy6(items, keyFn) {
|
|
13741
14467
|
const map = /* @__PURE__ */ new Map();
|
|
13742
14468
|
for (const item of items) {
|
|
13743
14469
|
const key = keyFn(item);
|
|
@@ -17478,7 +18204,9 @@ var ReportGenerator = class {
|
|
|
17478
18204
|
markdownEnabled: options.html?.markdownEnabled ?? true,
|
|
17479
18205
|
permalinkBaseUrl: options.html?.permalinkBaseUrl,
|
|
17480
18206
|
ticketUrlTemplate: options.html?.ticketUrlTemplate,
|
|
17481
|
-
theme: options.html?.theme ?? "default"
|
|
18207
|
+
theme: options.html?.theme ?? "default",
|
|
18208
|
+
tocEnabled: options.html?.tocEnabled ?? true,
|
|
18209
|
+
themePickerEnabled: options.html?.themePickerEnabled ?? false
|
|
17482
18210
|
},
|
|
17483
18211
|
junit: {
|
|
17484
18212
|
suiteName: options.junit?.suiteName ?? "Test Suite",
|
|
@@ -17601,7 +18329,9 @@ var ReportGenerator = class {
|
|
|
17601
18329
|
mermaidEnabled: this.options.html.mermaidEnabled,
|
|
17602
18330
|
markdownEnabled: this.options.html.markdownEnabled,
|
|
17603
18331
|
permalinkBaseUrl: this.options.html.permalinkBaseUrl,
|
|
17604
|
-
ticketUrlTemplate: this.options.html.ticketUrlTemplate
|
|
18332
|
+
ticketUrlTemplate: this.options.html.ticketUrlTemplate,
|
|
18333
|
+
tocEnabled: this.options.html.tocEnabled,
|
|
18334
|
+
themePickerEnabled: this.options.html.themePickerEnabled
|
|
17605
18335
|
});
|
|
17606
18336
|
return formatter.format(run);
|
|
17607
18337
|
}
|
|
@@ -17725,6 +18455,7 @@ function normalizePlaywrightResults(testResults, adapterOptions, canonicalizeOpt
|
|
|
17725
18455
|
generateRunId,
|
|
17726
18456
|
generateTestCaseId,
|
|
17727
18457
|
getAvailableThemes,
|
|
18458
|
+
getCssOnlyThemes,
|
|
17728
18459
|
hasSufficientHistory,
|
|
17729
18460
|
listScenarios,
|
|
17730
18461
|
loadHistory,
|