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/cli.js
CHANGED
|
@@ -1529,6 +1529,7 @@ function applyAllFilters() {
|
|
|
1529
1529
|
});
|
|
1530
1530
|
|
|
1531
1531
|
updateFilterResults(visibleCount, totalCount);
|
|
1532
|
+
syncTocVisibility();
|
|
1532
1533
|
writeUrlState();
|
|
1533
1534
|
}
|
|
1534
1535
|
|
|
@@ -1545,13 +1546,135 @@ function updateFilterResults(visible, total) {
|
|
|
1545
1546
|
if (tc) tc.textContent = total;
|
|
1546
1547
|
}
|
|
1547
1548
|
|
|
1548
|
-
// Keyboard
|
|
1549
|
+
// Keyboard navigation
|
|
1550
|
+
var focusedScenarioIndex = -1;
|
|
1551
|
+
|
|
1552
|
+
function getVisibleScenarios() {
|
|
1553
|
+
return Array.from(document.querySelectorAll('.scenario')).filter(function(s) {
|
|
1554
|
+
return s.style.display !== 'none' && s.closest('.feature').style.display !== 'none';
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
function focusScenario(index) {
|
|
1559
|
+
var scenarios = getVisibleScenarios();
|
|
1560
|
+
if (scenarios.length === 0) return;
|
|
1561
|
+
|
|
1562
|
+
// Remove previous focus
|
|
1563
|
+
var prev = document.querySelector('.scenario-focused');
|
|
1564
|
+
if (prev) prev.classList.remove('scenario-focused');
|
|
1565
|
+
|
|
1566
|
+
// Wrap around
|
|
1567
|
+
if (index < 0) index = scenarios.length - 1;
|
|
1568
|
+
if (index >= scenarios.length) index = 0;
|
|
1569
|
+
focusedScenarioIndex = index;
|
|
1570
|
+
|
|
1571
|
+
var scenario = scenarios[index];
|
|
1572
|
+
scenario.classList.add('scenario-focused');
|
|
1573
|
+
scenario.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
function showShortcutsOverlay() {
|
|
1577
|
+
if (document.querySelector('.shortcuts-overlay')) return;
|
|
1578
|
+
var overlay = document.createElement('div');
|
|
1579
|
+
overlay.className = 'shortcuts-overlay';
|
|
1580
|
+
overlay.innerHTML = '<div class="shortcuts-modal">' +
|
|
1581
|
+
'<div class="shortcuts-title">Keyboard Shortcuts</div>' +
|
|
1582
|
+
'<div class="shortcuts-grid">' +
|
|
1583
|
+
'<kbd>j</kbd><span>Next scenario</span>' +
|
|
1584
|
+
'<kbd>k</kbd><span>Previous scenario</span>' +
|
|
1585
|
+
'<kbd>Enter</kbd><span>Expand/collapse scenario</span>' +
|
|
1586
|
+
'<kbd>Escape</kbd><span>Collapse scenario / close</span>' +
|
|
1587
|
+
'<kbd>/</kbd><span>Focus search</span>' +
|
|
1588
|
+
'<kbd>?</kbd><span>Toggle this help</span>' +
|
|
1589
|
+
'<kbd>e</kbd><span>Expand all</span>' +
|
|
1590
|
+
'<kbd>c</kbd><span>Collapse all</span>' +
|
|
1591
|
+
'<kbd>t</kbd><span>Toggle table of contents</span>' +
|
|
1592
|
+
'</div></div>';
|
|
1593
|
+
overlay.addEventListener('click', function(ev) {
|
|
1594
|
+
if (ev.target === overlay) hideShortcutsOverlay();
|
|
1595
|
+
});
|
|
1596
|
+
document.body.appendChild(overlay);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
function hideShortcutsOverlay() {
|
|
1600
|
+
var overlay = document.querySelector('.shortcuts-overlay');
|
|
1601
|
+
if (overlay) overlay.remove();
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1549
1604
|
function initKeyboardShortcuts() {
|
|
1550
1605
|
document.addEventListener('keydown', function(e) {
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1606
|
+
var tag = e.target.tagName;
|
|
1607
|
+
if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') {
|
|
1608
|
+
if (e.key === 'Escape') {
|
|
1609
|
+
e.target.blur();
|
|
1610
|
+
if (e.target.classList.contains('search-input')) {
|
|
1611
|
+
e.target.value = '';
|
|
1612
|
+
applyAllFilters();
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
|
1619
|
+
|
|
1620
|
+
switch (e.key) {
|
|
1621
|
+
case 'j':
|
|
1622
|
+
e.preventDefault();
|
|
1623
|
+
focusScenario(focusedScenarioIndex + 1);
|
|
1624
|
+
break;
|
|
1625
|
+
case 'k':
|
|
1626
|
+
e.preventDefault();
|
|
1627
|
+
focusScenario(focusedScenarioIndex - 1);
|
|
1628
|
+
break;
|
|
1629
|
+
case 'Enter':
|
|
1630
|
+
e.preventDefault();
|
|
1631
|
+
var scenarios = getVisibleScenarios();
|
|
1632
|
+
if (focusedScenarioIndex >= 0 && focusedScenarioIndex < scenarios.length) {
|
|
1633
|
+
var s = scenarios[focusedScenarioIndex];
|
|
1634
|
+
var h = s.querySelector('.scenario-header');
|
|
1635
|
+
if (h) toggleCollapse(h, s);
|
|
1636
|
+
}
|
|
1637
|
+
break;
|
|
1638
|
+
case 'Escape':
|
|
1639
|
+
if (document.querySelector('.shortcuts-overlay')) {
|
|
1640
|
+
hideShortcutsOverlay();
|
|
1641
|
+
} else {
|
|
1642
|
+
var scenarios2 = getVisibleScenarios();
|
|
1643
|
+
if (focusedScenarioIndex >= 0 && focusedScenarioIndex < scenarios2.length) {
|
|
1644
|
+
var sc = scenarios2[focusedScenarioIndex];
|
|
1645
|
+
if (!sc.classList.contains('collapsed')) {
|
|
1646
|
+
sc.classList.add('collapsed');
|
|
1647
|
+
var sh = sc.querySelector('.scenario-header');
|
|
1648
|
+
if (sh) sh.setAttribute('aria-expanded', 'false');
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
break;
|
|
1653
|
+
case '/':
|
|
1654
|
+
e.preventDefault();
|
|
1655
|
+
var input = document.querySelector('.search-input');
|
|
1656
|
+
if (input) input.focus();
|
|
1657
|
+
break;
|
|
1658
|
+
case '?':
|
|
1659
|
+
e.preventDefault();
|
|
1660
|
+
if (document.querySelector('.shortcuts-overlay')) {
|
|
1661
|
+
hideShortcutsOverlay();
|
|
1662
|
+
} else {
|
|
1663
|
+
showShortcutsOverlay();
|
|
1664
|
+
}
|
|
1665
|
+
break;
|
|
1666
|
+
case 'e':
|
|
1667
|
+
e.preventDefault();
|
|
1668
|
+
expandAll();
|
|
1669
|
+
break;
|
|
1670
|
+
case 'c':
|
|
1671
|
+
e.preventDefault();
|
|
1672
|
+
collapseAll();
|
|
1673
|
+
break;
|
|
1674
|
+
case 't':
|
|
1675
|
+
e.preventDefault();
|
|
1676
|
+
if (typeof toggleToc === 'function') toggleToc();
|
|
1677
|
+
break;
|
|
1555
1678
|
}
|
|
1556
1679
|
});
|
|
1557
1680
|
}
|
|
@@ -1689,6 +1812,189 @@ function writeUrlState() {
|
|
|
1689
1812
|
var url = window.location.pathname + (qs ? '?' + qs : '');
|
|
1690
1813
|
history.replaceState(null, '', url);
|
|
1691
1814
|
}
|
|
1815
|
+
|
|
1816
|
+
// Permalink copy
|
|
1817
|
+
function copyPermalink(anchorId) {
|
|
1818
|
+
var url = location.origin + location.pathname + location.search + '#' + anchorId;
|
|
1819
|
+
navigator.clipboard.writeText(url).then(function() {
|
|
1820
|
+
var el = document.getElementById(anchorId);
|
|
1821
|
+
if (el) showCopyToast(el);
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
function showCopyToast(el) {
|
|
1826
|
+
var existing = el.querySelector('.copy-toast');
|
|
1827
|
+
if (existing) existing.remove();
|
|
1828
|
+
var toast = document.createElement('span');
|
|
1829
|
+
toast.className = 'copy-toast';
|
|
1830
|
+
toast.textContent = 'Copied!';
|
|
1831
|
+
var header = el.querySelector('.feature-header, .scenario-header');
|
|
1832
|
+
if (header) {
|
|
1833
|
+
header.style.position = 'relative';
|
|
1834
|
+
header.appendChild(toast);
|
|
1835
|
+
}
|
|
1836
|
+
setTimeout(function() { toast.remove(); }, 1500);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// Copy scenario as markdown
|
|
1840
|
+
function copyScenarioAsMarkdown(scenarioId) {
|
|
1841
|
+
var scenario = document.getElementById(scenarioId);
|
|
1842
|
+
if (!scenario) return;
|
|
1843
|
+
|
|
1844
|
+
var title = (scenario.querySelector('.scenario-name') || {}).textContent || '';
|
|
1845
|
+
var steps = scenario.querySelectorAll('.step, .step.continuation');
|
|
1846
|
+
var lines = ['### Scenario: ' + title.trim(), ''];
|
|
1847
|
+
|
|
1848
|
+
steps.forEach(function(step) {
|
|
1849
|
+
var keyword = step.getAttribute('data-keyword') || '';
|
|
1850
|
+
var text = step.getAttribute('data-text') || '';
|
|
1851
|
+
lines.push('- **' + keyword + '** ' + text);
|
|
1852
|
+
});
|
|
1853
|
+
|
|
1854
|
+
var errorBox = scenario.querySelector('.error-message');
|
|
1855
|
+
if (errorBox) {
|
|
1856
|
+
var errorText = errorBox.textContent || '';
|
|
1857
|
+
lines.push('');
|
|
1858
|
+
lines.push('> **Error:** ' + errorText.trim());
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
var md = lines.join('\\n');
|
|
1862
|
+
navigator.clipboard.writeText(md).then(function() {
|
|
1863
|
+
showCopyToast(scenario);
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
// Hash scroll on load
|
|
1868
|
+
function initHashScroll() {
|
|
1869
|
+
if (!location.hash) return;
|
|
1870
|
+
var target = document.querySelector(location.hash);
|
|
1871
|
+
if (!target) return;
|
|
1872
|
+
var feature = target.closest('.feature');
|
|
1873
|
+
if (feature && feature.classList.contains('collapsed')) {
|
|
1874
|
+
feature.classList.remove('collapsed');
|
|
1875
|
+
var fh = feature.querySelector('.feature-header');
|
|
1876
|
+
if (fh) fh.setAttribute('aria-expanded', 'true');
|
|
1877
|
+
}
|
|
1878
|
+
if (target.classList.contains('collapsed')) {
|
|
1879
|
+
target.classList.remove('collapsed');
|
|
1880
|
+
var sh = target.querySelector('.scenario-header');
|
|
1881
|
+
if (sh) sh.setAttribute('aria-expanded', 'true');
|
|
1882
|
+
}
|
|
1883
|
+
setTimeout(function() {
|
|
1884
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
1885
|
+
target.classList.add('hash-highlight');
|
|
1886
|
+
}, 100);
|
|
1887
|
+
}
|
|
1888
|
+
|
|
1889
|
+
// Table of contents
|
|
1890
|
+
function toggleToc() {
|
|
1891
|
+
var sidebar = document.querySelector('.toc-sidebar');
|
|
1892
|
+
var wrapper = document.querySelector('.report-layout');
|
|
1893
|
+
if (!sidebar || !wrapper) return;
|
|
1894
|
+
var isMobile = window.matchMedia('(max-width: 767px)').matches;
|
|
1895
|
+
if (isMobile) {
|
|
1896
|
+
sidebar.classList.toggle('toc-mobile-open');
|
|
1897
|
+
} else {
|
|
1898
|
+
wrapper.classList.toggle('toc-hidden');
|
|
1899
|
+
var hidden = wrapper.classList.contains('toc-hidden');
|
|
1900
|
+
localStorage.setItem('toc-visible', String(!hidden));
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
function initToc() {
|
|
1905
|
+
var sidebar = document.querySelector('.toc-sidebar');
|
|
1906
|
+
if (!sidebar) return;
|
|
1907
|
+
|
|
1908
|
+
var saved = localStorage.getItem('toc-visible');
|
|
1909
|
+
var wrapper = document.querySelector('.report-layout');
|
|
1910
|
+
if (saved === 'false' && wrapper) {
|
|
1911
|
+
wrapper.classList.add('toc-hidden');
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
// Active tracking via IntersectionObserver
|
|
1915
|
+
var observer = new IntersectionObserver(function(entries) {
|
|
1916
|
+
entries.forEach(function(entry) {
|
|
1917
|
+
if (entry.isIntersecting) {
|
|
1918
|
+
var id = entry.target.id;
|
|
1919
|
+
if (!id) return;
|
|
1920
|
+
document.querySelectorAll('.toc-scenario, .toc-feature-toggle').forEach(function(el) {
|
|
1921
|
+
el.classList.remove('toc-active');
|
|
1922
|
+
});
|
|
1923
|
+
var tocLink = sidebar.querySelector('a[href="#' + id + '"]');
|
|
1924
|
+
if (tocLink) tocLink.classList.add('toc-active');
|
|
1925
|
+
}
|
|
1926
|
+
});
|
|
1927
|
+
}, { rootMargin: '-10% 0px -80% 0px' });
|
|
1928
|
+
|
|
1929
|
+
document.querySelectorAll('.feature, .scenario').forEach(function(el) {
|
|
1930
|
+
if (el.id) observer.observe(el);
|
|
1931
|
+
});
|
|
1932
|
+
|
|
1933
|
+
// Click navigation: expand collapsed parents
|
|
1934
|
+
sidebar.querySelectorAll('.toc-scenario').forEach(function(link) {
|
|
1935
|
+
link.addEventListener('click', function(e) {
|
|
1936
|
+
var hash = link.getAttribute('href');
|
|
1937
|
+
if (!hash) return;
|
|
1938
|
+
var target = document.querySelector(hash);
|
|
1939
|
+
if (!target) return;
|
|
1940
|
+
var feature = target.closest('.feature');
|
|
1941
|
+
if (feature && feature.classList.contains('collapsed')) {
|
|
1942
|
+
feature.classList.remove('collapsed');
|
|
1943
|
+
var fh = feature.querySelector('.feature-header');
|
|
1944
|
+
if (fh) fh.setAttribute('aria-expanded', 'true');
|
|
1945
|
+
}
|
|
1946
|
+
if (target.classList.contains('collapsed')) {
|
|
1947
|
+
target.classList.remove('collapsed');
|
|
1948
|
+
var sh = target.querySelector('.scenario-header');
|
|
1949
|
+
if (sh) sh.setAttribute('aria-expanded', 'true');
|
|
1950
|
+
}
|
|
1951
|
+
});
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
// Theme picker
|
|
1956
|
+
function initThemePicker() {
|
|
1957
|
+
var picker = document.querySelector('.theme-picker');
|
|
1958
|
+
if (!picker) return;
|
|
1959
|
+
|
|
1960
|
+
var saved = localStorage.getItem('report-theme');
|
|
1961
|
+
if (saved) {
|
|
1962
|
+
picker.value = saved;
|
|
1963
|
+
switchReportTheme(saved);
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
picker.addEventListener('change', function(e) {
|
|
1967
|
+
switchReportTheme(e.target.value);
|
|
1968
|
+
localStorage.setItem('report-theme', e.target.value);
|
|
1969
|
+
});
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
function switchReportTheme(name) {
|
|
1973
|
+
document.querySelectorAll('style[data-theme-name]').forEach(function(s) {
|
|
1974
|
+
s.disabled = s.dataset.themeName !== name;
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
// Sync TOC visibility with filters
|
|
1979
|
+
function syncTocVisibility() {
|
|
1980
|
+
var sidebar = document.querySelector('.toc-sidebar');
|
|
1981
|
+
if (!sidebar) return;
|
|
1982
|
+
|
|
1983
|
+
sidebar.querySelectorAll('.toc-scenario').forEach(function(link) {
|
|
1984
|
+
var href = link.getAttribute('href');
|
|
1985
|
+
if (!href) return;
|
|
1986
|
+
var target = document.querySelector(href);
|
|
1987
|
+
link.style.display = (target && target.style.display !== 'none') ? '' : 'none';
|
|
1988
|
+
});
|
|
1989
|
+
|
|
1990
|
+
sidebar.querySelectorAll('.toc-feature').forEach(function(feature) {
|
|
1991
|
+
var visibleScenarios = feature.querySelectorAll('.toc-scenario');
|
|
1992
|
+
var anyVisible = Array.from(visibleScenarios).some(function(s) {
|
|
1993
|
+
return s.style.display !== 'none';
|
|
1994
|
+
});
|
|
1995
|
+
feature.style.display = anyVisible ? '' : 'none';
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1692
1998
|
`;
|
|
1693
1999
|
var JS_MARKDOWN_FN = `
|
|
1694
2000
|
function parseMarkdownSections(marked) {
|
|
@@ -1729,6 +2035,9 @@ function generateScript(options) {
|
|
|
1729
2035
|
initCalls.push("initCollapse();");
|
|
1730
2036
|
initCalls.push("initDetailLevel();");
|
|
1731
2037
|
initCalls.push("applyAllFilters();");
|
|
2038
|
+
initCalls.push("initHashScroll();");
|
|
2039
|
+
initCalls.push("initToc();");
|
|
2040
|
+
initCalls.push("initThemePicker();");
|
|
1732
2041
|
const initScript = `
|
|
1733
2042
|
// Initialize on load
|
|
1734
2043
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -1790,6 +2099,7 @@ function generateHtmlTemplate(title, styles, body, options = {}) {
|
|
|
1790
2099
|
}
|
|
1791
2100
|
const cdnStylesHtml = cdnStyles.length > 0 ? "\n " + cdnStyles.join("\n ") : "";
|
|
1792
2101
|
const esmScriptHtml = generateEsmScript(options);
|
|
2102
|
+
const additionalThemeStyles = (options.additionalThemeCss ?? []).map((t) => `<style data-theme-name="${escapeHtml(t.name)}" disabled>${t.css}</style>`).join("\n ");
|
|
1793
2103
|
return `<!DOCTYPE html>
|
|
1794
2104
|
<html lang="en"${themeAttr} data-detail-level="full">
|
|
1795
2105
|
<head>
|
|
@@ -1797,19 +2107,27 @@ function generateHtmlTemplate(title, styles, body, options = {}) {
|
|
|
1797
2107
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1798
2108
|
<meta name="color-scheme" content="light dark">
|
|
1799
2109
|
<title>${escapeHtml(title)}</title>${cdnStylesHtml}
|
|
1800
|
-
<style>${styles}</style>
|
|
2110
|
+
<style${options.additionalThemeCss ? ` data-theme-name="${escapeHtml(options.activeThemeName ?? "default")}"` : ""}>${styles}</style>
|
|
2111
|
+
${additionalThemeStyles}
|
|
1801
2112
|
</head>
|
|
1802
2113
|
<body>
|
|
1803
|
-
<div class="
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
<div class="
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
2114
|
+
<div class="report-layout">
|
|
2115
|
+
${options.tocHtml ?? ""}
|
|
2116
|
+
<div class="main-content">
|
|
2117
|
+
<div class="container">
|
|
2118
|
+
<header class="header">
|
|
2119
|
+
<h1>${escapeHtml(title)}</h1>
|
|
2120
|
+
<div class="header-actions">
|
|
2121
|
+
<button type="button" class="toc-toggle" onclick="toggleToc()" aria-label="Toggle table of contents" title="Toggle contents">☰</button>
|
|
2122
|
+
${includeSearch ? '<input type="text" class="search-input" placeholder="Search scenarios..." aria-label="Search scenarios">' : ""}
|
|
2123
|
+
<button type="button" class="detail-toggle" onclick="toggleDetailLevel()" aria-label="Toggle detail level" title="Toggle documentation detail"></button>
|
|
2124
|
+
${options.themePickerHtml ?? ""}
|
|
2125
|
+
${includeDarkMode ? '<button type="button" class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle theme"></button>' : ""}
|
|
2126
|
+
</div>
|
|
2127
|
+
</header>
|
|
2128
|
+
${body}
|
|
1810
2129
|
</div>
|
|
1811
|
-
</
|
|
1812
|
-
${body}
|
|
2130
|
+
</div>
|
|
1813
2131
|
</div>
|
|
1814
2132
|
<script>${script}</script>${esmScriptHtml}
|
|
1815
2133
|
</body>
|
|
@@ -3513,6 +3831,331 @@ body {
|
|
|
3513
3831
|
display: none;
|
|
3514
3832
|
}
|
|
3515
3833
|
|
|
3834
|
+
/* ============================================================================
|
|
3835
|
+
Permalink Anchors
|
|
3836
|
+
============================================================================ */
|
|
3837
|
+
.permalink-anchor {
|
|
3838
|
+
display: inline-flex;
|
|
3839
|
+
align-items: center;
|
|
3840
|
+
justify-content: center;
|
|
3841
|
+
width: 1.5rem;
|
|
3842
|
+
height: 1.5rem;
|
|
3843
|
+
border: none;
|
|
3844
|
+
background: none;
|
|
3845
|
+
color: var(--muted-foreground);
|
|
3846
|
+
cursor: pointer;
|
|
3847
|
+
opacity: 0;
|
|
3848
|
+
transition: opacity 0.15s ease;
|
|
3849
|
+
font-size: 0.875rem;
|
|
3850
|
+
font-weight: 600;
|
|
3851
|
+
padding: 0;
|
|
3852
|
+
flex-shrink: 0;
|
|
3853
|
+
}
|
|
3854
|
+
|
|
3855
|
+
.feature-header:hover .permalink-anchor,
|
|
3856
|
+
.scenario-header:hover .permalink-anchor,
|
|
3857
|
+
.permalink-anchor:focus-visible {
|
|
3858
|
+
opacity: 1;
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3861
|
+
.permalink-anchor:hover {
|
|
3862
|
+
color: var(--primary);
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3865
|
+
.copy-toast {
|
|
3866
|
+
position: absolute;
|
|
3867
|
+
right: 0.5rem;
|
|
3868
|
+
top: 50%;
|
|
3869
|
+
transform: translateY(-50%);
|
|
3870
|
+
background: var(--foreground);
|
|
3871
|
+
color: var(--background);
|
|
3872
|
+
padding: 0.25rem 0.5rem;
|
|
3873
|
+
border-radius: var(--radius);
|
|
3874
|
+
font-size: 0.75rem;
|
|
3875
|
+
font-weight: 500;
|
|
3876
|
+
pointer-events: none;
|
|
3877
|
+
animation: fadeOut 1.5s ease forwards;
|
|
3878
|
+
z-index: 10;
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
@keyframes fadeOut {
|
|
3882
|
+
0%, 70% { opacity: 1; }
|
|
3883
|
+
100% { opacity: 0; }
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3886
|
+
.hash-highlight {
|
|
3887
|
+
animation: hashPulse 2s ease;
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3890
|
+
@keyframes hashPulse {
|
|
3891
|
+
0%, 100% { background: transparent; }
|
|
3892
|
+
20% { background: color-mix(in srgb, var(--primary) 12%, transparent); }
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3895
|
+
.scenario-actions {
|
|
3896
|
+
display: flex;
|
|
3897
|
+
align-items: center;
|
|
3898
|
+
gap: 0.25rem;
|
|
3899
|
+
flex-shrink: 0;
|
|
3900
|
+
}
|
|
3901
|
+
|
|
3902
|
+
.copy-scenario-btn {
|
|
3903
|
+
display: inline-flex;
|
|
3904
|
+
align-items: center;
|
|
3905
|
+
justify-content: center;
|
|
3906
|
+
width: 1.5rem;
|
|
3907
|
+
height: 1.5rem;
|
|
3908
|
+
border: none;
|
|
3909
|
+
background: none;
|
|
3910
|
+
color: var(--muted-foreground);
|
|
3911
|
+
cursor: pointer;
|
|
3912
|
+
opacity: 0;
|
|
3913
|
+
transition: opacity 0.15s ease;
|
|
3914
|
+
font-size: 0.875rem;
|
|
3915
|
+
padding: 0;
|
|
3916
|
+
flex-shrink: 0;
|
|
3917
|
+
}
|
|
3918
|
+
|
|
3919
|
+
.scenario-header:hover .copy-scenario-btn,
|
|
3920
|
+
.copy-scenario-btn:focus-visible {
|
|
3921
|
+
opacity: 1;
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3924
|
+
.copy-scenario-btn:hover {
|
|
3925
|
+
color: var(--primary);
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
/* ============================================================================
|
|
3929
|
+
Keyboard Navigation
|
|
3930
|
+
============================================================================ */
|
|
3931
|
+
.scenario-focused {
|
|
3932
|
+
border-left: 2px solid var(--primary);
|
|
3933
|
+
}
|
|
3934
|
+
|
|
3935
|
+
.shortcuts-overlay {
|
|
3936
|
+
position: fixed;
|
|
3937
|
+
inset: 0;
|
|
3938
|
+
background: rgb(0 0 0 / 0.5);
|
|
3939
|
+
display: flex;
|
|
3940
|
+
align-items: center;
|
|
3941
|
+
justify-content: center;
|
|
3942
|
+
z-index: 100;
|
|
3943
|
+
}
|
|
3944
|
+
|
|
3945
|
+
.shortcuts-modal {
|
|
3946
|
+
background: var(--card);
|
|
3947
|
+
color: var(--card-foreground);
|
|
3948
|
+
border: 1px solid var(--border);
|
|
3949
|
+
border-radius: calc(var(--radius) * 2);
|
|
3950
|
+
padding: 1.5rem 2rem;
|
|
3951
|
+
max-width: 400px;
|
|
3952
|
+
width: 90vw;
|
|
3953
|
+
box-shadow: var(--shadow-md, 0 4px 12px rgb(0 0 0 / 0.15));
|
|
3954
|
+
}
|
|
3955
|
+
|
|
3956
|
+
.shortcuts-title {
|
|
3957
|
+
font-weight: 600;
|
|
3958
|
+
font-size: 1.125rem;
|
|
3959
|
+
margin-bottom: 1rem;
|
|
3960
|
+
padding-bottom: 0.5rem;
|
|
3961
|
+
border-bottom: 1px solid var(--border);
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
.shortcuts-grid {
|
|
3965
|
+
display: grid;
|
|
3966
|
+
grid-template-columns: auto 1fr;
|
|
3967
|
+
gap: 0.5rem 1rem;
|
|
3968
|
+
align-items: center;
|
|
3969
|
+
}
|
|
3970
|
+
|
|
3971
|
+
.shortcuts-grid kbd {
|
|
3972
|
+
display: inline-flex;
|
|
3973
|
+
align-items: center;
|
|
3974
|
+
justify-content: center;
|
|
3975
|
+
min-width: 1.75rem;
|
|
3976
|
+
padding: 0.125rem 0.375rem;
|
|
3977
|
+
background: var(--muted);
|
|
3978
|
+
border: 1px solid var(--border);
|
|
3979
|
+
border-radius: calc(var(--radius) * 0.5);
|
|
3980
|
+
font-family: var(--font-mono);
|
|
3981
|
+
font-size: 0.75rem;
|
|
3982
|
+
font-weight: 500;
|
|
3983
|
+
color: var(--muted-foreground);
|
|
3984
|
+
}
|
|
3985
|
+
|
|
3986
|
+
.shortcuts-grid span {
|
|
3987
|
+
font-size: 0.875rem;
|
|
3988
|
+
color: var(--foreground);
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3991
|
+
/* ============================================================================
|
|
3992
|
+
Table of Contents Sidebar
|
|
3993
|
+
============================================================================ */
|
|
3994
|
+
.report-layout {
|
|
3995
|
+
display: flex;
|
|
3996
|
+
min-height: 100vh;
|
|
3997
|
+
}
|
|
3998
|
+
|
|
3999
|
+
.report-layout.toc-hidden .toc-sidebar {
|
|
4000
|
+
display: none;
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
.main-content {
|
|
4004
|
+
flex: 1;
|
|
4005
|
+
min-width: 0;
|
|
4006
|
+
}
|
|
4007
|
+
|
|
4008
|
+
.toc-sidebar {
|
|
4009
|
+
width: 260px;
|
|
4010
|
+
flex-shrink: 0;
|
|
4011
|
+
position: sticky;
|
|
4012
|
+
top: 0;
|
|
4013
|
+
height: 100vh;
|
|
4014
|
+
overflow-y: auto;
|
|
4015
|
+
border-right: 1px solid var(--border);
|
|
4016
|
+
background: var(--card);
|
|
4017
|
+
padding: 1rem 0;
|
|
4018
|
+
font-size: 0.8125rem;
|
|
4019
|
+
}
|
|
4020
|
+
|
|
4021
|
+
.toc-header {
|
|
4022
|
+
padding: 0 1rem 0.75rem;
|
|
4023
|
+
border-bottom: 1px solid var(--border);
|
|
4024
|
+
margin-bottom: 0.5rem;
|
|
4025
|
+
}
|
|
4026
|
+
|
|
4027
|
+
.toc-title {
|
|
4028
|
+
font-weight: 600;
|
|
4029
|
+
font-size: 0.875rem;
|
|
4030
|
+
color: var(--foreground);
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4033
|
+
.toc-feature {
|
|
4034
|
+
margin-bottom: 0.25rem;
|
|
4035
|
+
}
|
|
4036
|
+
|
|
4037
|
+
.toc-feature-toggle {
|
|
4038
|
+
display: flex;
|
|
4039
|
+
align-items: center;
|
|
4040
|
+
width: 100%;
|
|
4041
|
+
padding: 0.375rem 1rem;
|
|
4042
|
+
border: none;
|
|
4043
|
+
background: none;
|
|
4044
|
+
text-align: left;
|
|
4045
|
+
cursor: pointer;
|
|
4046
|
+
font-size: 0.8125rem;
|
|
4047
|
+
font-weight: 600;
|
|
4048
|
+
color: var(--foreground);
|
|
4049
|
+
font-family: var(--font-sans);
|
|
4050
|
+
}
|
|
4051
|
+
|
|
4052
|
+
.toc-feature-toggle:hover {
|
|
4053
|
+
background: var(--accent);
|
|
4054
|
+
}
|
|
4055
|
+
|
|
4056
|
+
.toc-feature-toggle[aria-expanded="false"] + .toc-scenarios {
|
|
4057
|
+
display: none;
|
|
4058
|
+
}
|
|
4059
|
+
|
|
4060
|
+
.toc-scenarios {
|
|
4061
|
+
display: flex;
|
|
4062
|
+
flex-direction: column;
|
|
4063
|
+
}
|
|
4064
|
+
|
|
4065
|
+
.toc-scenario {
|
|
4066
|
+
display: flex;
|
|
4067
|
+
align-items: baseline;
|
|
4068
|
+
gap: 0.375rem;
|
|
4069
|
+
padding: 0.25rem 1rem 0.25rem 1.5rem;
|
|
4070
|
+
color: var(--muted-foreground);
|
|
4071
|
+
text-decoration: none;
|
|
4072
|
+
font-size: 0.8125rem;
|
|
4073
|
+
line-height: 1.4;
|
|
4074
|
+
border-left: 2px solid transparent;
|
|
4075
|
+
transition: all 0.1s ease;
|
|
4076
|
+
}
|
|
4077
|
+
|
|
4078
|
+
.toc-scenario:hover {
|
|
4079
|
+
color: var(--foreground);
|
|
4080
|
+
background: var(--accent);
|
|
4081
|
+
}
|
|
4082
|
+
|
|
4083
|
+
.toc-scenario.toc-active {
|
|
4084
|
+
color: var(--foreground);
|
|
4085
|
+
border-left-color: var(--primary);
|
|
4086
|
+
font-weight: 500;
|
|
4087
|
+
}
|
|
4088
|
+
|
|
4089
|
+
.toc-scenario.toc-failed {
|
|
4090
|
+
border-left-color: var(--error, var(--destructive));
|
|
4091
|
+
}
|
|
4092
|
+
|
|
4093
|
+
.toc-status {
|
|
4094
|
+
flex-shrink: 0;
|
|
4095
|
+
font-size: 0.75rem;
|
|
4096
|
+
}
|
|
4097
|
+
|
|
4098
|
+
.toc-toggle {
|
|
4099
|
+
display: inline-flex;
|
|
4100
|
+
align-items: center;
|
|
4101
|
+
justify-content: center;
|
|
4102
|
+
width: 2.25rem;
|
|
4103
|
+
height: 2.25rem;
|
|
4104
|
+
border: 1px solid var(--border);
|
|
4105
|
+
border-radius: var(--radius);
|
|
4106
|
+
background: var(--background);
|
|
4107
|
+
cursor: pointer;
|
|
4108
|
+
color: var(--foreground);
|
|
4109
|
+
font-size: 1rem;
|
|
4110
|
+
transition: all 0.15s ease;
|
|
4111
|
+
}
|
|
4112
|
+
|
|
4113
|
+
.toc-toggle:hover {
|
|
4114
|
+
background: var(--accent);
|
|
4115
|
+
}
|
|
4116
|
+
|
|
4117
|
+
/* Mobile: overlay sidebar */
|
|
4118
|
+
@media (max-width: 767px) {
|
|
4119
|
+
.toc-sidebar {
|
|
4120
|
+
position: fixed;
|
|
4121
|
+
left: 0;
|
|
4122
|
+
top: 0;
|
|
4123
|
+
z-index: 50;
|
|
4124
|
+
box-shadow: var(--shadow-sm, 0 1px 3px rgb(0 0 0 / 0.1));
|
|
4125
|
+
transform: translateX(-100%);
|
|
4126
|
+
transition: transform 0.2s ease;
|
|
4127
|
+
}
|
|
4128
|
+
|
|
4129
|
+
.toc-sidebar.toc-mobile-open {
|
|
4130
|
+
transform: translateX(0);
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4134
|
+
/* ============================================================================
|
|
4135
|
+
Theme Picker
|
|
4136
|
+
============================================================================ */
|
|
4137
|
+
.theme-picker {
|
|
4138
|
+
height: 2.25rem;
|
|
4139
|
+
padding: 0 0.5rem;
|
|
4140
|
+
border: 1px solid var(--border);
|
|
4141
|
+
border-radius: var(--radius);
|
|
4142
|
+
background: var(--background);
|
|
4143
|
+
color: var(--foreground);
|
|
4144
|
+
font-size: 0.8125rem;
|
|
4145
|
+
font-family: var(--font-sans);
|
|
4146
|
+
cursor: pointer;
|
|
4147
|
+
transition: all 0.15s ease;
|
|
4148
|
+
}
|
|
4149
|
+
|
|
4150
|
+
.theme-picker:hover {
|
|
4151
|
+
background: var(--accent);
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
.theme-picker:focus-visible {
|
|
4155
|
+
outline: 2px solid var(--ring);
|
|
4156
|
+
outline-offset: 2px;
|
|
4157
|
+
}
|
|
4158
|
+
|
|
3516
4159
|
`;
|
|
3517
4160
|
|
|
3518
4161
|
// src/formatters/html/themes/default.ts
|
|
@@ -12769,6 +13412,11 @@ function resolveTheme(nameOrTheme) {
|
|
|
12769
13412
|
}
|
|
12770
13413
|
return theme;
|
|
12771
13414
|
}
|
|
13415
|
+
function getCssOnlyThemes() {
|
|
13416
|
+
return [...THEME_REGISTRY.values()].filter(
|
|
13417
|
+
(theme) => !theme.buildBody && !theme.generateTemplate
|
|
13418
|
+
);
|
|
13419
|
+
}
|
|
12772
13420
|
|
|
12773
13421
|
// src/formatters/html/renderers/status.ts
|
|
12774
13422
|
function getStatusIcon(status) {
|
|
@@ -13050,7 +13698,7 @@ function renderStep(step, stepResult, index, deps) {
|
|
|
13050
13698
|
const stepClass = isContinuation ? "step continuation" : "step";
|
|
13051
13699
|
const stepDocs = deps.renderDocs(step.docs, "step-docs");
|
|
13052
13700
|
const textHtml = deps.highlightStepParams ? deps.highlightStepParams(step.text) : deps.escapeHtml(step.text);
|
|
13053
|
-
return `<div class="${stepClass}">
|
|
13701
|
+
return `<div class="${stepClass}" data-keyword="${deps.escapeHtml(keywordTrimmed)}" data-text="${deps.escapeHtml(step.text)}">
|
|
13054
13702
|
<span class="step-status ${statusClass}">${statusIcon}</span>
|
|
13055
13703
|
<span class="step-keyword">${deps.escapeHtml(step.keyword)}</span>
|
|
13056
13704
|
<span class="step-text">${textHtml}</span>
|
|
@@ -13176,7 +13824,11 @@ function renderScenario(args, deps) {
|
|
|
13176
13824
|
</div>
|
|
13177
13825
|
<div class="scenario-meta">${tags}${tickets}${sourceLink}${traceBadge}${metricBadges}</div>
|
|
13178
13826
|
</div>
|
|
13179
|
-
<
|
|
13827
|
+
<div class="scenario-actions">
|
|
13828
|
+
<button class="copy-scenario-btn" onclick="copyScenarioAsMarkdown('scenario-${tc.id}')" aria-label="Copy scenario as markdown" title="Copy as Markdown">⎘</button>
|
|
13829
|
+
<button class="permalink-anchor" onclick="copyPermalink('scenario-${tc.id}')" aria-label="Copy link to scenario" title="Copy link">#</button>
|
|
13830
|
+
<span class="scenario-duration">${duration}</span>
|
|
13831
|
+
</div>
|
|
13180
13832
|
</div>
|
|
13181
13833
|
<div class="scenario-content">
|
|
13182
13834
|
${storyDocs}
|
|
@@ -13386,6 +14038,7 @@ function renderFeature(args, deps) {
|
|
|
13386
14038
|
const featureName = suitePaths.length > 0 && suitePaths[0].length > 0 ? suitePaths[0][0] : file.split("/").pop()?.replace(/\.[^.]+$/, "") ?? file;
|
|
13387
14039
|
const collapsedClass = deps.startCollapsed ? " collapsed" : "";
|
|
13388
14040
|
const ariaExpanded = !deps.startCollapsed;
|
|
14041
|
+
const featureSlug = `feature-${slugify(file)}`;
|
|
13389
14042
|
const scenarios = testCases.map(
|
|
13390
14043
|
(tc) => deps.renderScenario(
|
|
13391
14044
|
{ tc, metrics: args.metricsMap?.get(tc.id) },
|
|
@@ -13393,8 +14046,9 @@ function renderFeature(args, deps) {
|
|
|
13393
14046
|
)
|
|
13394
14047
|
).join("\n");
|
|
13395
14048
|
return `
|
|
13396
|
-
<div class="feature${collapsedClass}">
|
|
14049
|
+
<div class="feature${collapsedClass}" id="${featureSlug}">
|
|
13397
14050
|
<div class="feature-header" role="button" tabindex="0" aria-expanded="${ariaExpanded}">
|
|
14051
|
+
<button class="permalink-anchor" onclick="copyPermalink('${featureSlug}')" aria-label="Copy link to feature" title="Copy link">#</button>
|
|
13398
14052
|
<div class="feature-info">
|
|
13399
14053
|
<div class="feature-title">${deps.escapeHtml(featureName)}</div>
|
|
13400
14054
|
<div class="feature-path">${deps.escapeHtml(file)}</div>
|
|
@@ -13507,6 +14161,57 @@ function renderFailureSummary(args, deps) {
|
|
|
13507
14161
|
</div>`;
|
|
13508
14162
|
}
|
|
13509
14163
|
|
|
14164
|
+
// src/formatters/html/renderers/toc.ts
|
|
14165
|
+
function groupBy4(items, keyFn) {
|
|
14166
|
+
const map = /* @__PURE__ */ new Map();
|
|
14167
|
+
for (const item of items) {
|
|
14168
|
+
const key = keyFn(item);
|
|
14169
|
+
const existing = map.get(key);
|
|
14170
|
+
if (existing) {
|
|
14171
|
+
existing.push(item);
|
|
14172
|
+
} else {
|
|
14173
|
+
map.set(key, [item]);
|
|
14174
|
+
}
|
|
14175
|
+
}
|
|
14176
|
+
return map;
|
|
14177
|
+
}
|
|
14178
|
+
function renderToc(args, deps) {
|
|
14179
|
+
const { run } = args;
|
|
14180
|
+
if (run.testCases.length === 0) return "";
|
|
14181
|
+
const byFile = groupBy4(run.testCases, (tc) => tc.sourceFile);
|
|
14182
|
+
const features = [];
|
|
14183
|
+
for (const [file, testCases] of byFile) {
|
|
14184
|
+
const suitePaths = testCases.map((tc) => tc.titlePath).filter((p) => p.length > 0);
|
|
14185
|
+
const featureName = suitePaths.length > 0 && suitePaths[0].length > 0 ? suitePaths[0][0] : file.split("/").pop()?.replace(/\.[^.]+$/, "") ?? file;
|
|
14186
|
+
const featureSlug = `feature-${slugify(file)}`;
|
|
14187
|
+
const scenarios = testCases.map((tc) => {
|
|
14188
|
+
const statusIcon = deps.getStatusIcon(tc.status);
|
|
14189
|
+
const statusClass = `status-${tc.status}`;
|
|
14190
|
+
const failedClass = tc.status === "failed" ? " toc-failed" : "";
|
|
14191
|
+
return `<a class="toc-scenario${failedClass}" href="#scenario-${tc.id}">
|
|
14192
|
+
<span class="toc-status ${statusClass}">${statusIcon}</span>
|
|
14193
|
+
${deps.escapeHtml(tc.story.scenario)}
|
|
14194
|
+
</a>`;
|
|
14195
|
+
}).join("\n");
|
|
14196
|
+
features.push(`<div class="toc-feature">
|
|
14197
|
+
<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}">
|
|
14198
|
+
${deps.escapeHtml(featureName)}
|
|
14199
|
+
</button>
|
|
14200
|
+
<div class="toc-scenarios">
|
|
14201
|
+
${scenarios}
|
|
14202
|
+
</div>
|
|
14203
|
+
</div>`);
|
|
14204
|
+
}
|
|
14205
|
+
return `<nav class="toc-sidebar" aria-label="Table of contents">
|
|
14206
|
+
<div class="toc-header">
|
|
14207
|
+
<span class="toc-title">Contents</span>
|
|
14208
|
+
</div>
|
|
14209
|
+
<div class="toc-body">
|
|
14210
|
+
${features.join("\n")}
|
|
14211
|
+
</div>
|
|
14212
|
+
</nav>`;
|
|
14213
|
+
}
|
|
14214
|
+
|
|
13510
14215
|
// src/formatters/html/renderers/index.ts
|
|
13511
14216
|
function normalizeOptions(options = {}) {
|
|
13512
14217
|
return {
|
|
@@ -13520,7 +14225,9 @@ function normalizeOptions(options = {}) {
|
|
|
13520
14225
|
markdownEnabled: options.markdownEnabled ?? true,
|
|
13521
14226
|
permalinkBaseUrl: options.permalinkBaseUrl,
|
|
13522
14227
|
ticketUrlTemplate: options.ticketUrlTemplate,
|
|
13523
|
-
|
|
14228
|
+
tocEnabled: options.tocEnabled ?? true,
|
|
14229
|
+
theme: options.theme ?? "default",
|
|
14230
|
+
themePickerEnabled: options.themePickerEnabled ?? false
|
|
13524
14231
|
};
|
|
13525
14232
|
}
|
|
13526
14233
|
function createHtmlFormatter(options = {}) {
|
|
@@ -13562,6 +14269,10 @@ function createHtmlFormatter(options = {}) {
|
|
|
13562
14269
|
scenarioDeps
|
|
13563
14270
|
};
|
|
13564
14271
|
const tagBarDeps = { escapeHtml };
|
|
14272
|
+
const tocDeps = {
|
|
14273
|
+
escapeHtml,
|
|
14274
|
+
getStatusIcon
|
|
14275
|
+
};
|
|
13565
14276
|
const bodyDeps = {
|
|
13566
14277
|
renderMetaInfo,
|
|
13567
14278
|
renderSummary,
|
|
@@ -13580,6 +14291,16 @@ function createHtmlFormatter(options = {}) {
|
|
|
13580
14291
|
const bodyFn = theme.buildBody ?? buildBody;
|
|
13581
14292
|
const body = bodyFn({ run }, bodyDeps);
|
|
13582
14293
|
const templateFn = theme.generateTemplate ?? generateHtmlTemplate;
|
|
14294
|
+
const isStructuralTheme = !!(theme.buildBody || theme.generateTemplate);
|
|
14295
|
+
const tocHtml = opts.tocEnabled && !isStructuralTheme ? renderToc({ run }, tocDeps) : void 0;
|
|
14296
|
+
let themePickerHtml;
|
|
14297
|
+
let additionalThemeCss;
|
|
14298
|
+
if (opts.themePickerEnabled) {
|
|
14299
|
+
const cssOnlyThemes = getCssOnlyThemes();
|
|
14300
|
+
const pickerOptions = cssOnlyThemes.map((t) => `<option value="${t.name}"${t.name === theme.name ? " selected" : ""}>${t.label}</option>`).join("");
|
|
14301
|
+
themePickerHtml = `<select class="theme-picker" aria-label="Select theme">${pickerOptions}</select>`;
|
|
14302
|
+
additionalThemeCss = cssOnlyThemes.filter((t) => t.name !== theme.name).map((t) => ({ name: t.name, label: t.label, css: t.css }));
|
|
14303
|
+
}
|
|
13583
14304
|
return templateFn(
|
|
13584
14305
|
opts.title,
|
|
13585
14306
|
theme.css,
|
|
@@ -13591,7 +14312,11 @@ function createHtmlFormatter(options = {}) {
|
|
|
13591
14312
|
mermaidEnabled: opts.mermaidEnabled,
|
|
13592
14313
|
markdownEnabled: opts.markdownEnabled,
|
|
13593
14314
|
additionalJs: theme.additionalJs,
|
|
13594
|
-
additionalImports: theme.additionalImports
|
|
14315
|
+
additionalImports: theme.additionalImports,
|
|
14316
|
+
tocHtml,
|
|
14317
|
+
themePickerHtml,
|
|
14318
|
+
additionalThemeCss,
|
|
14319
|
+
activeThemeName: theme.name
|
|
13595
14320
|
}
|
|
13596
14321
|
);
|
|
13597
14322
|
}
|
|
@@ -13647,7 +14372,7 @@ var JUnitFormatter = class {
|
|
|
13647
14372
|
lines.push(
|
|
13648
14373
|
`<testsuites name="${escapeXml(this.options.suiteName)}" tests="${tests}" failures="${failures}" errors="${errors}" skipped="${skipped}" time="${time}">`
|
|
13649
14374
|
);
|
|
13650
|
-
const byFile =
|
|
14375
|
+
const byFile = groupBy5(run.testCases, (tc) => tc.sourceFile);
|
|
13651
14376
|
for (const [file, testCases] of byFile) {
|
|
13652
14377
|
lines.push(...this.buildTestSuite(file, testCases, indent, newline));
|
|
13653
14378
|
}
|
|
@@ -13820,7 +14545,7 @@ var JUnitFormatter = class {
|
|
|
13820
14545
|
function escapeXml(str) {
|
|
13821
14546
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
13822
14547
|
}
|
|
13823
|
-
function
|
|
14548
|
+
function groupBy5(items, keyFn) {
|
|
13824
14549
|
const map = /* @__PURE__ */ new Map();
|
|
13825
14550
|
for (const item of items) {
|
|
13826
14551
|
const key = keyFn(item);
|
|
@@ -13995,7 +14720,7 @@ var MarkdownFormatter = class {
|
|
|
13995
14720
|
* Render scenarios grouped by file.
|
|
13996
14721
|
*/
|
|
13997
14722
|
renderByFile(lines, testCases) {
|
|
13998
|
-
const byFile =
|
|
14723
|
+
const byFile = groupBy6(testCases, (tc) => tc.sourceFile);
|
|
13999
14724
|
for (const [file, fileTestCases] of byFile) {
|
|
14000
14725
|
lines.push(`## ${file}`);
|
|
14001
14726
|
lines.push("");
|
|
@@ -14012,7 +14737,7 @@ var MarkdownFormatter = class {
|
|
|
14012
14737
|
* Render suite groups.
|
|
14013
14738
|
*/
|
|
14014
14739
|
renderSuiteGroups(lines, testCases, baseLevel) {
|
|
14015
|
-
const bySuite =
|
|
14740
|
+
const bySuite = groupBy6(
|
|
14016
14741
|
testCases,
|
|
14017
14742
|
(tc) => tc.titlePath.join(this.options.suiteSeparator)
|
|
14018
14743
|
);
|
|
@@ -14306,7 +15031,7 @@ var MarkdownFormatter = class {
|
|
|
14306
15031
|
return entries;
|
|
14307
15032
|
}
|
|
14308
15033
|
};
|
|
14309
|
-
function
|
|
15034
|
+
function groupBy6(items, keyFn) {
|
|
14310
15035
|
const map = /* @__PURE__ */ new Map();
|
|
14311
15036
|
for (const item of items) {
|
|
14312
15037
|
const key = keyFn(item);
|
|
@@ -17259,7 +17984,9 @@ var ReportGenerator = class {
|
|
|
17259
17984
|
markdownEnabled: options.html?.markdownEnabled ?? true,
|
|
17260
17985
|
permalinkBaseUrl: options.html?.permalinkBaseUrl,
|
|
17261
17986
|
ticketUrlTemplate: options.html?.ticketUrlTemplate,
|
|
17262
|
-
theme: options.html?.theme ?? "default"
|
|
17987
|
+
theme: options.html?.theme ?? "default",
|
|
17988
|
+
tocEnabled: options.html?.tocEnabled ?? true,
|
|
17989
|
+
themePickerEnabled: options.html?.themePickerEnabled ?? false
|
|
17263
17990
|
},
|
|
17264
17991
|
junit: {
|
|
17265
17992
|
suiteName: options.junit?.suiteName ?? "Test Suite",
|
|
@@ -17382,7 +18109,9 @@ var ReportGenerator = class {
|
|
|
17382
18109
|
mermaidEnabled: this.options.html.mermaidEnabled,
|
|
17383
18110
|
markdownEnabled: this.options.html.markdownEnabled,
|
|
17384
18111
|
permalinkBaseUrl: this.options.html.permalinkBaseUrl,
|
|
17385
|
-
ticketUrlTemplate: this.options.html.ticketUrlTemplate
|
|
18112
|
+
ticketUrlTemplate: this.options.html.ticketUrlTemplate,
|
|
18113
|
+
tocEnabled: this.options.html.tocEnabled,
|
|
18114
|
+
themePickerEnabled: this.options.html.themePickerEnabled
|
|
17386
18115
|
});
|
|
17387
18116
|
return formatter.format(run);
|
|
17388
18117
|
}
|
|
@@ -17503,6 +18232,8 @@ OPTIONS
|
|
|
17503
18232
|
--html-no-mermaid Disable mermaid diagrams in HTML (enabled by default)
|
|
17504
18233
|
--html-no-markdown Disable markdown parsing in HTML (enabled by default)
|
|
17505
18234
|
--html-permalink-base-url <url> Base URL for source permalinks in HTML (e.g. "https://github.com/org/repo/blob/main")
|
|
18235
|
+
--html-no-toc Disable table of contents sidebar in HTML (enabled by default)
|
|
18236
|
+
--html-theme-picker Include theme picker in HTML report (embeds all CSS-only themes)
|
|
17506
18237
|
--html-ticket-url-template <url> URL template for ticket links in HTML (use {ticket} as placeholder)
|
|
17507
18238
|
--asset-mode <mode> Asset bundling: "none" (default) or "copy"
|
|
17508
18239
|
--allow-missing-assets Warn on missing assets instead of failing
|
|
@@ -17586,6 +18317,8 @@ function parseCliArgs(argv) {
|
|
|
17586
18317
|
"html-no-markdown": { type: "boolean", default: false },
|
|
17587
18318
|
"html-permalink-base-url": { type: "string" },
|
|
17588
18319
|
"html-ticket-url-template": { type: "string" },
|
|
18320
|
+
"html-no-toc": { type: "boolean", default: false },
|
|
18321
|
+
"html-theme-picker": { type: "boolean", default: false },
|
|
17589
18322
|
stdin: { type: "boolean", default: false },
|
|
17590
18323
|
"json-summary": { type: "boolean", default: false },
|
|
17591
18324
|
"emit-canonical": { type: "string" },
|
|
@@ -17745,6 +18478,8 @@ function parseCliArgs(argv) {
|
|
|
17745
18478
|
htmlNoMarkdown: values["html-no-markdown"],
|
|
17746
18479
|
htmlPermalinkBaseUrl: values["html-permalink-base-url"],
|
|
17747
18480
|
htmlTicketUrlTemplate: values["html-ticket-url-template"],
|
|
18481
|
+
htmlNoToc: values["html-no-toc"],
|
|
18482
|
+
htmlThemePicker: values["html-theme-picker"],
|
|
17748
18483
|
jsonSummary: values["json-summary"],
|
|
17749
18484
|
emitCanonical: values["emit-canonical"],
|
|
17750
18485
|
slackWebhook,
|
|
@@ -18247,7 +18982,9 @@ async function generateReports(run, args, _droppedMissingStory = 0) {
|
|
|
18247
18982
|
mermaidEnabled: !args.htmlNoMermaid,
|
|
18248
18983
|
markdownEnabled: !args.htmlNoMarkdown,
|
|
18249
18984
|
permalinkBaseUrl: args.htmlPermalinkBaseUrl,
|
|
18250
|
-
ticketUrlTemplate: args.htmlTicketUrlTemplate
|
|
18985
|
+
ticketUrlTemplate: args.htmlTicketUrlTemplate,
|
|
18986
|
+
tocEnabled: !args.htmlNoToc,
|
|
18987
|
+
themePickerEnabled: args.htmlThemePicker
|
|
18251
18988
|
},
|
|
18252
18989
|
assetMode: args.assetMode,
|
|
18253
18990
|
allowMissingAssets: args.allowMissingAssets
|