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/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,337 @@ 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
|
+
text-decoration: none;
|
|
4032
|
+
cursor: pointer;
|
|
4033
|
+
}
|
|
4034
|
+
|
|
4035
|
+
a.toc-title:hover {
|
|
4036
|
+
color: var(--primary);
|
|
4037
|
+
}
|
|
4038
|
+
|
|
4039
|
+
.toc-feature {
|
|
4040
|
+
margin-bottom: 0.25rem;
|
|
4041
|
+
}
|
|
4042
|
+
|
|
4043
|
+
.toc-feature-toggle {
|
|
4044
|
+
display: flex;
|
|
4045
|
+
align-items: center;
|
|
4046
|
+
width: 100%;
|
|
4047
|
+
padding: 0.375rem 1rem;
|
|
4048
|
+
border: none;
|
|
4049
|
+
background: none;
|
|
4050
|
+
text-align: left;
|
|
4051
|
+
cursor: pointer;
|
|
4052
|
+
font-size: 0.8125rem;
|
|
4053
|
+
font-weight: 600;
|
|
4054
|
+
color: var(--foreground);
|
|
4055
|
+
font-family: var(--font-sans);
|
|
4056
|
+
}
|
|
4057
|
+
|
|
4058
|
+
.toc-feature-toggle:hover {
|
|
4059
|
+
background: var(--accent);
|
|
4060
|
+
}
|
|
4061
|
+
|
|
4062
|
+
.toc-feature-toggle[aria-expanded="false"] + .toc-scenarios {
|
|
4063
|
+
display: none;
|
|
4064
|
+
}
|
|
4065
|
+
|
|
4066
|
+
.toc-scenarios {
|
|
4067
|
+
display: flex;
|
|
4068
|
+
flex-direction: column;
|
|
4069
|
+
}
|
|
4070
|
+
|
|
4071
|
+
.toc-scenario {
|
|
4072
|
+
display: flex;
|
|
4073
|
+
align-items: baseline;
|
|
4074
|
+
gap: 0.375rem;
|
|
4075
|
+
padding: 0.25rem 1rem 0.25rem 1.5rem;
|
|
4076
|
+
color: var(--muted-foreground);
|
|
4077
|
+
text-decoration: none;
|
|
4078
|
+
font-size: 0.8125rem;
|
|
4079
|
+
line-height: 1.4;
|
|
4080
|
+
border-left: 2px solid transparent;
|
|
4081
|
+
transition: all 0.1s ease;
|
|
4082
|
+
}
|
|
4083
|
+
|
|
4084
|
+
.toc-scenario:hover {
|
|
4085
|
+
color: var(--foreground);
|
|
4086
|
+
background: var(--accent);
|
|
4087
|
+
}
|
|
4088
|
+
|
|
4089
|
+
.toc-scenario.toc-active {
|
|
4090
|
+
color: var(--foreground);
|
|
4091
|
+
border-left-color: var(--primary);
|
|
4092
|
+
font-weight: 500;
|
|
4093
|
+
}
|
|
4094
|
+
|
|
4095
|
+
.toc-scenario.toc-failed {
|
|
4096
|
+
border-left-color: var(--error, var(--destructive));
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4099
|
+
.toc-status {
|
|
4100
|
+
flex-shrink: 0;
|
|
4101
|
+
font-size: 0.75rem;
|
|
4102
|
+
}
|
|
4103
|
+
|
|
4104
|
+
.toc-toggle {
|
|
4105
|
+
display: inline-flex;
|
|
4106
|
+
align-items: center;
|
|
4107
|
+
justify-content: center;
|
|
4108
|
+
width: 2.25rem;
|
|
4109
|
+
height: 2.25rem;
|
|
4110
|
+
border: 1px solid var(--border);
|
|
4111
|
+
border-radius: var(--radius);
|
|
4112
|
+
background: var(--background);
|
|
4113
|
+
cursor: pointer;
|
|
4114
|
+
color: var(--foreground);
|
|
4115
|
+
font-size: 1rem;
|
|
4116
|
+
transition: all 0.15s ease;
|
|
4117
|
+
}
|
|
4118
|
+
|
|
4119
|
+
.toc-toggle:hover {
|
|
4120
|
+
background: var(--accent);
|
|
4121
|
+
}
|
|
4122
|
+
|
|
4123
|
+
/* Mobile: overlay sidebar */
|
|
4124
|
+
@media (max-width: 767px) {
|
|
4125
|
+
.toc-sidebar {
|
|
4126
|
+
position: fixed;
|
|
4127
|
+
left: 0;
|
|
4128
|
+
top: 0;
|
|
4129
|
+
z-index: 50;
|
|
4130
|
+
box-shadow: var(--shadow-sm, 0 1px 3px rgb(0 0 0 / 0.1));
|
|
4131
|
+
transform: translateX(-100%);
|
|
4132
|
+
transition: transform 0.2s ease;
|
|
4133
|
+
}
|
|
4134
|
+
|
|
4135
|
+
.toc-sidebar.toc-mobile-open {
|
|
4136
|
+
transform: translateX(0);
|
|
4137
|
+
}
|
|
4138
|
+
}
|
|
4139
|
+
|
|
4140
|
+
/* ============================================================================
|
|
4141
|
+
Theme Picker
|
|
4142
|
+
============================================================================ */
|
|
4143
|
+
.theme-picker {
|
|
4144
|
+
height: 2.25rem;
|
|
4145
|
+
padding: 0 0.5rem;
|
|
4146
|
+
border: 1px solid var(--border);
|
|
4147
|
+
border-radius: var(--radius);
|
|
4148
|
+
background: var(--background);
|
|
4149
|
+
color: var(--foreground);
|
|
4150
|
+
font-size: 0.8125rem;
|
|
4151
|
+
font-family: var(--font-sans);
|
|
4152
|
+
cursor: pointer;
|
|
4153
|
+
transition: all 0.15s ease;
|
|
4154
|
+
}
|
|
4155
|
+
|
|
4156
|
+
.theme-picker:hover {
|
|
4157
|
+
background: var(--accent);
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
.theme-picker:focus-visible {
|
|
4161
|
+
outline: 2px solid var(--ring);
|
|
4162
|
+
outline-offset: 2px;
|
|
4163
|
+
}
|
|
4164
|
+
|
|
3516
4165
|
`;
|
|
3517
4166
|
|
|
3518
4167
|
// src/formatters/html/themes/default.ts
|
|
@@ -3562,7 +4211,7 @@ function corporateBuildBody(args, deps) {
|
|
|
3562
4211
|
const sidebar = `
|
|
3563
4212
|
<nav class="toc">
|
|
3564
4213
|
<div class="toc-header">
|
|
3565
|
-
<
|
|
4214
|
+
<a href="#" class="toc-title" onclick="window.scrollTo({top:0,behavior:'smooth'});return false;">Test Report</a>
|
|
3566
4215
|
<div class="toc-stats">
|
|
3567
4216
|
<div class="toc-stat-row">
|
|
3568
4217
|
<span class="toc-stat-label">Total</span>
|
|
@@ -12769,6 +13418,11 @@ function resolveTheme(nameOrTheme) {
|
|
|
12769
13418
|
}
|
|
12770
13419
|
return theme;
|
|
12771
13420
|
}
|
|
13421
|
+
function getCssOnlyThemes() {
|
|
13422
|
+
return [...THEME_REGISTRY.values()].filter(
|
|
13423
|
+
(theme) => !theme.buildBody && !theme.generateTemplate
|
|
13424
|
+
);
|
|
13425
|
+
}
|
|
12772
13426
|
|
|
12773
13427
|
// src/formatters/html/renderers/status.ts
|
|
12774
13428
|
function getStatusIcon(status) {
|
|
@@ -13050,7 +13704,7 @@ function renderStep(step, stepResult, index, deps) {
|
|
|
13050
13704
|
const stepClass = isContinuation ? "step continuation" : "step";
|
|
13051
13705
|
const stepDocs = deps.renderDocs(step.docs, "step-docs");
|
|
13052
13706
|
const textHtml = deps.highlightStepParams ? deps.highlightStepParams(step.text) : deps.escapeHtml(step.text);
|
|
13053
|
-
return `<div class="${stepClass}">
|
|
13707
|
+
return `<div class="${stepClass}" data-keyword="${deps.escapeHtml(keywordTrimmed)}" data-text="${deps.escapeHtml(step.text)}">
|
|
13054
13708
|
<span class="step-status ${statusClass}">${statusIcon}</span>
|
|
13055
13709
|
<span class="step-keyword">${deps.escapeHtml(step.keyword)}</span>
|
|
13056
13710
|
<span class="step-text">${textHtml}</span>
|
|
@@ -13176,7 +13830,11 @@ function renderScenario(args, deps) {
|
|
|
13176
13830
|
</div>
|
|
13177
13831
|
<div class="scenario-meta">${tags}${tickets}${sourceLink}${traceBadge}${metricBadges}</div>
|
|
13178
13832
|
</div>
|
|
13179
|
-
<
|
|
13833
|
+
<div class="scenario-actions">
|
|
13834
|
+
<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>
|
|
13835
|
+
<button class="permalink-anchor" onclick="copyPermalink('scenario-${tc.id}')" aria-label="Copy link to scenario" title="Copy link">#</button>
|
|
13836
|
+
<span class="scenario-duration">${duration}</span>
|
|
13837
|
+
</div>
|
|
13180
13838
|
</div>
|
|
13181
13839
|
<div class="scenario-content">
|
|
13182
13840
|
${storyDocs}
|
|
@@ -13386,6 +14044,7 @@ function renderFeature(args, deps) {
|
|
|
13386
14044
|
const featureName = suitePaths.length > 0 && suitePaths[0].length > 0 ? suitePaths[0][0] : file.split("/").pop()?.replace(/\.[^.]+$/, "") ?? file;
|
|
13387
14045
|
const collapsedClass = deps.startCollapsed ? " collapsed" : "";
|
|
13388
14046
|
const ariaExpanded = !deps.startCollapsed;
|
|
14047
|
+
const featureSlug = `feature-${slugify(file)}`;
|
|
13389
14048
|
const scenarios = testCases.map(
|
|
13390
14049
|
(tc) => deps.renderScenario(
|
|
13391
14050
|
{ tc, metrics: args.metricsMap?.get(tc.id) },
|
|
@@ -13393,8 +14052,9 @@ function renderFeature(args, deps) {
|
|
|
13393
14052
|
)
|
|
13394
14053
|
).join("\n");
|
|
13395
14054
|
return `
|
|
13396
|
-
<div class="feature${collapsedClass}">
|
|
14055
|
+
<div class="feature${collapsedClass}" id="${featureSlug}">
|
|
13397
14056
|
<div class="feature-header" role="button" tabindex="0" aria-expanded="${ariaExpanded}">
|
|
14057
|
+
<button class="permalink-anchor" onclick="copyPermalink('${featureSlug}')" aria-label="Copy link to feature" title="Copy link">#</button>
|
|
13398
14058
|
<div class="feature-info">
|
|
13399
14059
|
<div class="feature-title">${deps.escapeHtml(featureName)}</div>
|
|
13400
14060
|
<div class="feature-path">${deps.escapeHtml(file)}</div>
|
|
@@ -13507,6 +14167,57 @@ function renderFailureSummary(args, deps) {
|
|
|
13507
14167
|
</div>`;
|
|
13508
14168
|
}
|
|
13509
14169
|
|
|
14170
|
+
// src/formatters/html/renderers/toc.ts
|
|
14171
|
+
function groupBy4(items, keyFn) {
|
|
14172
|
+
const map = /* @__PURE__ */ new Map();
|
|
14173
|
+
for (const item of items) {
|
|
14174
|
+
const key = keyFn(item);
|
|
14175
|
+
const existing = map.get(key);
|
|
14176
|
+
if (existing) {
|
|
14177
|
+
existing.push(item);
|
|
14178
|
+
} else {
|
|
14179
|
+
map.set(key, [item]);
|
|
14180
|
+
}
|
|
14181
|
+
}
|
|
14182
|
+
return map;
|
|
14183
|
+
}
|
|
14184
|
+
function renderToc(args, deps) {
|
|
14185
|
+
const { run } = args;
|
|
14186
|
+
if (run.testCases.length === 0) return "";
|
|
14187
|
+
const byFile = groupBy4(run.testCases, (tc) => tc.sourceFile);
|
|
14188
|
+
const features = [];
|
|
14189
|
+
for (const [file, testCases] of byFile) {
|
|
14190
|
+
const suitePaths = testCases.map((tc) => tc.titlePath).filter((p) => p.length > 0);
|
|
14191
|
+
const featureName = suitePaths.length > 0 && suitePaths[0].length > 0 ? suitePaths[0][0] : file.split("/").pop()?.replace(/\.[^.]+$/, "") ?? file;
|
|
14192
|
+
const featureSlug = `feature-${slugify(file)}`;
|
|
14193
|
+
const scenarios = testCases.map((tc) => {
|
|
14194
|
+
const statusIcon = deps.getStatusIcon(tc.status);
|
|
14195
|
+
const statusClass = `status-${tc.status}`;
|
|
14196
|
+
const failedClass = tc.status === "failed" ? " toc-failed" : "";
|
|
14197
|
+
return `<a class="toc-scenario${failedClass}" href="#scenario-${tc.id}">
|
|
14198
|
+
<span class="toc-status ${statusClass}">${statusIcon}</span>
|
|
14199
|
+
${deps.escapeHtml(tc.story.scenario)}
|
|
14200
|
+
</a>`;
|
|
14201
|
+
}).join("\n");
|
|
14202
|
+
features.push(`<div class="toc-feature">
|
|
14203
|
+
<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}">
|
|
14204
|
+
${deps.escapeHtml(featureName)}
|
|
14205
|
+
</button>
|
|
14206
|
+
<div class="toc-scenarios">
|
|
14207
|
+
${scenarios}
|
|
14208
|
+
</div>
|
|
14209
|
+
</div>`);
|
|
14210
|
+
}
|
|
14211
|
+
return `<nav class="toc-sidebar" aria-label="Table of contents">
|
|
14212
|
+
<div class="toc-header">
|
|
14213
|
+
<a href="#" class="toc-title" onclick="window.scrollTo({top:0,behavior:'smooth'});return false;">Contents</a>
|
|
14214
|
+
</div>
|
|
14215
|
+
<div class="toc-body">
|
|
14216
|
+
${features.join("\n")}
|
|
14217
|
+
</div>
|
|
14218
|
+
</nav>`;
|
|
14219
|
+
}
|
|
14220
|
+
|
|
13510
14221
|
// src/formatters/html/renderers/index.ts
|
|
13511
14222
|
function normalizeOptions(options = {}) {
|
|
13512
14223
|
return {
|
|
@@ -13520,7 +14231,9 @@ function normalizeOptions(options = {}) {
|
|
|
13520
14231
|
markdownEnabled: options.markdownEnabled ?? true,
|
|
13521
14232
|
permalinkBaseUrl: options.permalinkBaseUrl,
|
|
13522
14233
|
ticketUrlTemplate: options.ticketUrlTemplate,
|
|
13523
|
-
|
|
14234
|
+
tocEnabled: options.tocEnabled ?? true,
|
|
14235
|
+
theme: options.theme ?? "default",
|
|
14236
|
+
themePickerEnabled: options.themePickerEnabled ?? false
|
|
13524
14237
|
};
|
|
13525
14238
|
}
|
|
13526
14239
|
function createHtmlFormatter(options = {}) {
|
|
@@ -13562,6 +14275,10 @@ function createHtmlFormatter(options = {}) {
|
|
|
13562
14275
|
scenarioDeps
|
|
13563
14276
|
};
|
|
13564
14277
|
const tagBarDeps = { escapeHtml };
|
|
14278
|
+
const tocDeps = {
|
|
14279
|
+
escapeHtml,
|
|
14280
|
+
getStatusIcon
|
|
14281
|
+
};
|
|
13565
14282
|
const bodyDeps = {
|
|
13566
14283
|
renderMetaInfo,
|
|
13567
14284
|
renderSummary,
|
|
@@ -13580,6 +14297,16 @@ function createHtmlFormatter(options = {}) {
|
|
|
13580
14297
|
const bodyFn = theme.buildBody ?? buildBody;
|
|
13581
14298
|
const body = bodyFn({ run }, bodyDeps);
|
|
13582
14299
|
const templateFn = theme.generateTemplate ?? generateHtmlTemplate;
|
|
14300
|
+
const isStructuralTheme = !!(theme.buildBody || theme.generateTemplate);
|
|
14301
|
+
const tocHtml = opts.tocEnabled && !isStructuralTheme ? renderToc({ run }, tocDeps) : void 0;
|
|
14302
|
+
let themePickerHtml;
|
|
14303
|
+
let additionalThemeCss;
|
|
14304
|
+
if (opts.themePickerEnabled) {
|
|
14305
|
+
const cssOnlyThemes = getCssOnlyThemes();
|
|
14306
|
+
const pickerOptions = cssOnlyThemes.map((t) => `<option value="${t.name}"${t.name === theme.name ? " selected" : ""}>${t.label}</option>`).join("");
|
|
14307
|
+
themePickerHtml = `<select class="theme-picker" aria-label="Select theme">${pickerOptions}</select>`;
|
|
14308
|
+
additionalThemeCss = cssOnlyThemes.filter((t) => t.name !== theme.name).map((t) => ({ name: t.name, label: t.label, css: t.css }));
|
|
14309
|
+
}
|
|
13583
14310
|
return templateFn(
|
|
13584
14311
|
opts.title,
|
|
13585
14312
|
theme.css,
|
|
@@ -13591,7 +14318,11 @@ function createHtmlFormatter(options = {}) {
|
|
|
13591
14318
|
mermaidEnabled: opts.mermaidEnabled,
|
|
13592
14319
|
markdownEnabled: opts.markdownEnabled,
|
|
13593
14320
|
additionalJs: theme.additionalJs,
|
|
13594
|
-
additionalImports: theme.additionalImports
|
|
14321
|
+
additionalImports: theme.additionalImports,
|
|
14322
|
+
tocHtml,
|
|
14323
|
+
themePickerHtml,
|
|
14324
|
+
additionalThemeCss,
|
|
14325
|
+
activeThemeName: theme.name
|
|
13595
14326
|
}
|
|
13596
14327
|
);
|
|
13597
14328
|
}
|
|
@@ -13647,7 +14378,7 @@ var JUnitFormatter = class {
|
|
|
13647
14378
|
lines.push(
|
|
13648
14379
|
`<testsuites name="${escapeXml(this.options.suiteName)}" tests="${tests}" failures="${failures}" errors="${errors}" skipped="${skipped}" time="${time}">`
|
|
13649
14380
|
);
|
|
13650
|
-
const byFile =
|
|
14381
|
+
const byFile = groupBy5(run.testCases, (tc) => tc.sourceFile);
|
|
13651
14382
|
for (const [file, testCases] of byFile) {
|
|
13652
14383
|
lines.push(...this.buildTestSuite(file, testCases, indent, newline));
|
|
13653
14384
|
}
|
|
@@ -13820,7 +14551,7 @@ var JUnitFormatter = class {
|
|
|
13820
14551
|
function escapeXml(str) {
|
|
13821
14552
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
13822
14553
|
}
|
|
13823
|
-
function
|
|
14554
|
+
function groupBy5(items, keyFn) {
|
|
13824
14555
|
const map = /* @__PURE__ */ new Map();
|
|
13825
14556
|
for (const item of items) {
|
|
13826
14557
|
const key = keyFn(item);
|
|
@@ -13995,7 +14726,7 @@ var MarkdownFormatter = class {
|
|
|
13995
14726
|
* Render scenarios grouped by file.
|
|
13996
14727
|
*/
|
|
13997
14728
|
renderByFile(lines, testCases) {
|
|
13998
|
-
const byFile =
|
|
14729
|
+
const byFile = groupBy6(testCases, (tc) => tc.sourceFile);
|
|
13999
14730
|
for (const [file, fileTestCases] of byFile) {
|
|
14000
14731
|
lines.push(`## ${file}`);
|
|
14001
14732
|
lines.push("");
|
|
@@ -14012,7 +14743,7 @@ var MarkdownFormatter = class {
|
|
|
14012
14743
|
* Render suite groups.
|
|
14013
14744
|
*/
|
|
14014
14745
|
renderSuiteGroups(lines, testCases, baseLevel) {
|
|
14015
|
-
const bySuite =
|
|
14746
|
+
const bySuite = groupBy6(
|
|
14016
14747
|
testCases,
|
|
14017
14748
|
(tc) => tc.titlePath.join(this.options.suiteSeparator)
|
|
14018
14749
|
);
|
|
@@ -14306,7 +15037,7 @@ var MarkdownFormatter = class {
|
|
|
14306
15037
|
return entries;
|
|
14307
15038
|
}
|
|
14308
15039
|
};
|
|
14309
|
-
function
|
|
15040
|
+
function groupBy6(items, keyFn) {
|
|
14310
15041
|
const map = /* @__PURE__ */ new Map();
|
|
14311
15042
|
for (const item of items) {
|
|
14312
15043
|
const key = keyFn(item);
|
|
@@ -17230,7 +17961,7 @@ var ReportGenerator = class {
|
|
|
17230
17961
|
excludeTags: options.excludeTags ?? [],
|
|
17231
17962
|
formats: options.formats ?? ["cucumber-json"],
|
|
17232
17963
|
outputDir: options.outputDir ?? "reports",
|
|
17233
|
-
outputName: options.outputName ?? "
|
|
17964
|
+
outputName: options.outputName ?? "index",
|
|
17234
17965
|
outputNameTimestamp: options.outputNameTimestamp ?? false,
|
|
17235
17966
|
sortTestCases: options.sortTestCases ?? "none",
|
|
17236
17967
|
output: {
|
|
@@ -17259,7 +17990,9 @@ var ReportGenerator = class {
|
|
|
17259
17990
|
markdownEnabled: options.html?.markdownEnabled ?? true,
|
|
17260
17991
|
permalinkBaseUrl: options.html?.permalinkBaseUrl,
|
|
17261
17992
|
ticketUrlTemplate: options.html?.ticketUrlTemplate,
|
|
17262
|
-
theme: options.html?.theme ?? "default"
|
|
17993
|
+
theme: options.html?.theme ?? "default",
|
|
17994
|
+
tocEnabled: options.html?.tocEnabled ?? true,
|
|
17995
|
+
themePickerEnabled: options.html?.themePickerEnabled ?? false
|
|
17263
17996
|
},
|
|
17264
17997
|
junit: {
|
|
17265
17998
|
suiteName: options.junit?.suiteName ?? "Test Suite",
|
|
@@ -17382,7 +18115,9 @@ var ReportGenerator = class {
|
|
|
17382
18115
|
mermaidEnabled: this.options.html.mermaidEnabled,
|
|
17383
18116
|
markdownEnabled: this.options.html.markdownEnabled,
|
|
17384
18117
|
permalinkBaseUrl: this.options.html.permalinkBaseUrl,
|
|
17385
|
-
ticketUrlTemplate: this.options.html.ticketUrlTemplate
|
|
18118
|
+
ticketUrlTemplate: this.options.html.ticketUrlTemplate,
|
|
18119
|
+
tocEnabled: this.options.html.tocEnabled,
|
|
18120
|
+
themePickerEnabled: this.options.html.themePickerEnabled
|
|
17386
18121
|
});
|
|
17387
18122
|
return formatter.format(run);
|
|
17388
18123
|
}
|
|
@@ -17488,7 +18223,7 @@ OPTIONS
|
|
|
17488
18223
|
cucumber-messages Raw NDJSON (Cucumber Messages)
|
|
17489
18224
|
--input-type <type> Input type: raw, canonical, or ndjson (default: raw)
|
|
17490
18225
|
--output-dir <dir> Output directory (default: reports)
|
|
17491
|
-
--output-name <name> Base filename (default:
|
|
18226
|
+
--output-name <name> Base filename (default: index)
|
|
17492
18227
|
--output-name-timestamp Append run timestamp (UTC seconds) to output filename for before/after diffs
|
|
17493
18228
|
--sort-test-cases <mode> Sort scenarios deterministically: id, source, none (default: none)
|
|
17494
18229
|
--include <globs> Comma-separated globs to include test cases by sourceFile (e.g. "**/*.Story*.cs")
|
|
@@ -17503,6 +18238,8 @@ OPTIONS
|
|
|
17503
18238
|
--html-no-mermaid Disable mermaid diagrams in HTML (enabled by default)
|
|
17504
18239
|
--html-no-markdown Disable markdown parsing in HTML (enabled by default)
|
|
17505
18240
|
--html-permalink-base-url <url> Base URL for source permalinks in HTML (e.g. "https://github.com/org/repo/blob/main")
|
|
18241
|
+
--html-no-toc Disable table of contents sidebar in HTML (enabled by default)
|
|
18242
|
+
--html-theme-picker Include theme picker in HTML report (embeds all CSS-only themes)
|
|
17506
18243
|
--html-ticket-url-template <url> URL template for ticket links in HTML (use {ticket} as placeholder)
|
|
17507
18244
|
--asset-mode <mode> Asset bundling: "none" (default) or "copy"
|
|
17508
18245
|
--allow-missing-assets Warn on missing assets instead of failing
|
|
@@ -17570,7 +18307,7 @@ function parseCliArgs(argv) {
|
|
|
17570
18307
|
"baseline-dir": { type: "string" },
|
|
17571
18308
|
"input-type": { type: "string", default: "raw" },
|
|
17572
18309
|
"output-dir": { type: "string", default: "reports" },
|
|
17573
|
-
"output-name": { type: "string", default: "
|
|
18310
|
+
"output-name": { type: "string", default: "index" },
|
|
17574
18311
|
"output-name-timestamp": { type: "boolean", default: false },
|
|
17575
18312
|
"sort-test-cases": { type: "string", default: "none" },
|
|
17576
18313
|
include: { type: "string" },
|
|
@@ -17586,6 +18323,8 @@ function parseCliArgs(argv) {
|
|
|
17586
18323
|
"html-no-markdown": { type: "boolean", default: false },
|
|
17587
18324
|
"html-permalink-base-url": { type: "string" },
|
|
17588
18325
|
"html-ticket-url-template": { type: "string" },
|
|
18326
|
+
"html-no-toc": { type: "boolean", default: false },
|
|
18327
|
+
"html-theme-picker": { type: "boolean", default: false },
|
|
17589
18328
|
stdin: { type: "boolean", default: false },
|
|
17590
18329
|
"json-summary": { type: "boolean", default: false },
|
|
17591
18330
|
"emit-canonical": { type: "string" },
|
|
@@ -17745,6 +18484,8 @@ function parseCliArgs(argv) {
|
|
|
17745
18484
|
htmlNoMarkdown: values["html-no-markdown"],
|
|
17746
18485
|
htmlPermalinkBaseUrl: values["html-permalink-base-url"],
|
|
17747
18486
|
htmlTicketUrlTemplate: values["html-ticket-url-template"],
|
|
18487
|
+
htmlNoToc: values["html-no-toc"],
|
|
18488
|
+
htmlThemePicker: values["html-theme-picker"],
|
|
17748
18489
|
jsonSummary: values["json-summary"],
|
|
17749
18490
|
emitCanonical: values["emit-canonical"],
|
|
17750
18491
|
slackWebhook,
|
|
@@ -18247,7 +18988,9 @@ async function generateReports(run, args, _droppedMissingStory = 0) {
|
|
|
18247
18988
|
mermaidEnabled: !args.htmlNoMermaid,
|
|
18248
18989
|
markdownEnabled: !args.htmlNoMarkdown,
|
|
18249
18990
|
permalinkBaseUrl: args.htmlPermalinkBaseUrl,
|
|
18250
|
-
ticketUrlTemplate: args.htmlTicketUrlTemplate
|
|
18991
|
+
ticketUrlTemplate: args.htmlTicketUrlTemplate,
|
|
18992
|
+
tocEnabled: !args.htmlNoToc,
|
|
18993
|
+
themePickerEnabled: args.htmlThemePicker
|
|
18251
18994
|
},
|
|
18252
18995
|
assetMode: args.assetMode,
|
|
18253
18996
|
allowMissingAssets: args.allowMissingAssets
|