@@ -963,7 +963,7 @@ function renderKPICards(summary) {
963963 const colorClass = pct >= 80 ? 'green' : pct >= 50 ? 'amber' : 'red' ;
964964
965965 html += `
966- <div class="cov-kpi-card ${ colorClass } " onclick="document.getElementById('details- ${ s . key } -coverage')?.scrollIntoView({behavior:'smooth'} )">
966+ <div class="cov-kpi-card ${ colorClass } " onclick="scrollToCoverageSection(' ${ s . target } ' )">
967967 <div class="cov-kpi-label">${ s . label } </div>
968968 <div class="cov-kpi-value ${ colorClass } ">${ pct . toFixed ( 1 ) } %</div>
969969 <div class="cov-kpi-bar"><div class="cov-kpi-bar-fill ${ colorClass } " style="width:${ Math . min ( pct , 100 ) } %"></div></div>
@@ -1356,13 +1356,17 @@ function renderCoverageSection(label, sectionData, unit, renderDetailsFn) {
13561356 // Expandable detail list
13571357 const detailCount = sectionData . details . length ;
13581358 html += `<button class="coverage-toggle-btn" onclick="toggleCoverageDetails('${ sectionId } ')">Show details (${ detailCount } )</button>` ;
1359- // Add search filter for large lists
1359+ // Add search filter and uncovered toggle
1360+ const uncoveredCount = sectionData . details . filter ( d => d . covered === false || ( d . percentage !== undefined && d . percentage < 100 ) ) . length ;
1361+ html += `<div class="coverage-detail-filter collapsed" id="filter-${ sectionId } ">` ;
13601362 if ( detailCount > 20 ) {
1361- html += `<div class="coverage-detail-filter collapsed" id="filter- ${ sectionId } ">
1362- <input type="text" placeholder="Filter by ID or text..." oninput="filterCoverageDetails(' ${ sectionId } ', this.value)" />
1363- <span class="filter-count" id="filter-count- ${ sectionId } "> ${ detailCount } items</span>
1364- </div >` ;
1363+ html += `<input type="text" placeholder="Filter by ID or text..." oninput="filterCoverageDetails(' ${ sectionId } ', this.value)" />` ;
1364+ }
1365+ if ( uncoveredCount > 0 && uncoveredCount < detailCount ) {
1366+ html += `<button class="uncovered-filter-btn" id="uncovered-btn- ${ sectionId } " onclick="toggleUncoveredFilter(' ${ sectionId } ')" title="Show only uncovered items">Show uncovered only ( ${ uncoveredCount } )</button >`;
13651367 }
1368+ html += `<span class="filter-count" id="filter-count-${ sectionId } ">${ detailCount } items</span>
1369+ </div>` ;
13661370 html += `<ul class="coverage-detail-list collapsed" id="details-${ sectionId } ">` ;
13671371 html += renderDetailsFn ( sectionData ) ;
13681372 html += '</ul>' ;
@@ -1458,6 +1462,61 @@ function filterCoverageDetails(sectionId, query) {
14581462 }
14591463}
14601464
1465+ /**
1466+ * Scroll to a coverage section and auto-expand its details.
1467+ */
1468+ function scrollToCoverageSection ( sectionId ) {
1469+ // The detail list has id="details-{sectionId}"
1470+ const detailList = document . getElementById ( 'details-' + sectionId ) ;
1471+ // The section wrapper is the parent .coverage-section
1472+ const section = detailList ?. closest ( '.coverage-section' ) ;
1473+ if ( section ) {
1474+ // Auto-expand if collapsed
1475+ if ( detailList . classList . contains ( 'collapsed' ) ) {
1476+ toggleCoverageDetails ( sectionId ) ;
1477+ }
1478+ section . scrollIntoView ( { behavior : 'smooth' , block : 'start' } ) ;
1479+ }
1480+ }
1481+
1482+ /**
1483+ * Toggle showing only uncovered items in a coverage detail list.
1484+ */
1485+ function toggleUncoveredFilter ( sectionId ) {
1486+ const list = document . getElementById ( 'details-' + sectionId ) ;
1487+ const btn = document . getElementById ( 'uncovered-btn-' + sectionId ) ;
1488+ const countEl = document . getElementById ( 'filter-count-' + sectionId ) ;
1489+ if ( ! list || ! btn ) return ;
1490+
1491+ const isActive = btn . classList . toggle ( 'active' ) ;
1492+
1493+ let visible = 0 ;
1494+ for ( const li of list . children ) {
1495+ const isCovered = li . getAttribute ( 'data-covered' ) === 'true' ;
1496+ if ( isActive && isCovered ) {
1497+ li . style . display = 'none' ;
1498+ } else if ( li . style . display === 'none' && ! isCovered ) {
1499+ // Already hidden by text filter — leave it
1500+ } else if ( ! isActive ) {
1501+ li . style . display = '' ;
1502+ visible ++ ;
1503+ } else {
1504+ visible ++ ;
1505+ }
1506+ }
1507+
1508+ // Recount visible items
1509+ visible = 0 ;
1510+ for ( const li of list . children ) {
1511+ if ( li . style . display !== 'none' ) visible ++ ;
1512+ }
1513+
1514+ if ( countEl ) {
1515+ countEl . textContent = isActive ? `${ visible } uncovered` : `${ list . children . length } items` ;
1516+ }
1517+ btn . textContent = isActive ? 'Show all' : `Show uncovered only (${ list . querySelectorAll ( '[data-covered="false"]' ) . length } )` ;
1518+ }
1519+
14611520/**
14621521 * Render documentation detail list items.
14631522 */
@@ -1467,7 +1526,7 @@ function renderDocDetails(section) {
14671526 const icon = d . covered
14681527 ? '<span class="detail-icon coverage-green">✓</span>'
14691528 : '<span class="detail-icon coverage-red">✗</span>' ;
1470- html += `<li>
1529+ html += `<li data-covered=" ${ d . covered ? 'true' : 'false' } " >
14711530 <span class="detail-name" title="${ escapeHtml ( d . doc ) } ">${ escapeHtml ( d . doc ) } </span>
14721531 <span class="detail-meta">${ d . test_count } test${ d . test_count !== 1 ? 's' : '' } </span>
14731532 ${ icon }
@@ -1490,7 +1549,7 @@ function renderCriteriaDetails(section) {
14901549 const displayName = titleText
14911550 ? `<strong>${ escapeHtml ( d . id ) } </strong> ${ escapeHtml ( titleText ) } `
14921551 : `<strong>${ escapeHtml ( d . id ) } </strong>` ;
1493- html += `<li title="${ escapeHtml ( titleText ) } ">
1552+ html += `<li data-covered=" ${ d . covered ? 'true' : 'false' } " title="${ escapeHtml ( titleText ) } ">
14941553 <span class="detail-name">${ displayName } </span>
14951554 <span class="detail-meta">${ testList } </span>
14961555 ${ icon }
@@ -1506,7 +1565,8 @@ function renderAutoDetails(section) {
15061565 let html = '' ;
15071566 for ( const d of section . details ) {
15081567 const suiteColor = d . percentage >= 80 ? 'coverage-green' : d . percentage >= 50 ? 'coverage-yellow' : 'coverage-red' ;
1509- html += `<li>
1568+ const fullyCovered = d . percentage >= 100 ;
1569+ html += `<li data-covered="${ fullyCovered ? 'true' : 'false' } ">
15101570 <span class="detail-name">${ escapeHtml ( d . suite ) } </span>
15111571 <span class="detail-meta ${ suiteColor } ">${ d . automated } /${ d . total } (${ d . percentage . toFixed ( 1 ) } %)</span>
15121572 </li>` ;
0 commit comments