|
| 1 | +<!DOCTYPE html> |
| 2 | +<html lang="en"> |
| 3 | +<head> |
| 4 | + <meta charset="UTF-8"> |
| 5 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 6 | + <title>Test Coverage - PhpQuality</title> |
| 7 | + <link rel="stylesheet" href="report.css"> |
| 8 | + <script src="https://cdn.jsdelivr.net/npm/chart.js@4"></script> |
| 9 | + <style> |
| 10 | + <style> |
| 11 | + .coverage-progress { |
| 12 | + margin: 1rem 0; |
| 13 | + } |
| 14 | + .progress-bar { |
| 15 | + height: 24px; |
| 16 | + background: var(--bg-secondary); |
| 17 | + border-radius: 12px; |
| 18 | + overflow: hidden; |
| 19 | + } |
| 20 | + .progress-fill { |
| 21 | + height: 100%; |
| 22 | + transition: width 0.3s ease; |
| 23 | + } |
| 24 | + .progress-labels { |
| 25 | + display: flex; |
| 26 | + justify-content: space-between; |
| 27 | + margin-top: 0.5rem; |
| 28 | + font-size: 0.85rem; |
| 29 | + color: var(--text-muted); |
| 30 | + } |
| 31 | + .mini-progress { |
| 32 | + display: flex; |
| 33 | + align-items: center; |
| 34 | + gap: 0.5rem; |
| 35 | + } |
| 36 | + .mini-progress .mini-progress-fill { |
| 37 | + height: 8px; |
| 38 | + border-radius: 4px; |
| 39 | + min-width: 60px; |
| 40 | + background: var(--bg-secondary); |
| 41 | + position: relative; |
| 42 | + } |
| 43 | + .rating-bg-A { background: #22c55e; } |
| 44 | + .rating-bg-B { background: #84cc16; } |
| 45 | + .rating-bg-C { background: #eab308; } |
| 46 | + .rating-bg-D { background: #f97316; } |
| 47 | + .rating-bg-F { background: #ef4444; } |
| 48 | +</style> |
| 49 | + </style> |
| 50 | +</head> |
| 51 | +<body> |
| 52 | + <nav class="navbar"> |
| 53 | + <div class="navbar-content"> |
| 54 | + <a href="index.html" class="navbar-brand"> |
| 55 | + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| 56 | + <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/> |
| 57 | + </svg> |
| 58 | + <span class="navbar-title">PhpQuality</span> |
| 59 | + <span class="navbar-author">by <a href="https://moi.ruedesjasses.fr" target="_blank" rel="noopener">Pascal CESCON</a></span> |
| 60 | + </a> |
| 61 | + |
| 62 | + <ul class="nav-links"> |
| 63 | + <li><a href="index.html" class="nav-link ">Dashboard</a></li> |
| 64 | + <li><a href="metrics.html" class="nav-link ">Documentation</a></li> |
| 65 | + |
| 66 | + <!-- Metrics Dropdown --> |
| 67 | + <li class="nav-dropdown"> |
| 68 | + <button class="nav-link nav-dropdown-toggle "> |
| 69 | + Metrics |
| 70 | + <svg class="dropdown-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="12" height="12"> |
| 71 | + <polyline points="6 9 12 15 18 9"></polyline> |
| 72 | + </svg> |
| 73 | + </button> |
| 74 | + <ul class="nav-dropdown-menu"> |
| 75 | + <li><a href="ccn.html" class="nav-link ">CCN - Complexity</a></li> |
| 76 | + <li><a href="mi.html" class="nav-link ">MI - Maintainability</a></li> |
| 77 | + <li><a href="lcom.html" class="nav-link ">LCOM - Cohesion</a></li> |
| 78 | + <li><a href="loc.html" class="nav-link ">LOC - Lines of Code</a></li> |
| 79 | + </ul> |
| 80 | + </li> |
| 81 | + |
| 82 | + <!-- Analysis Dropdown --> |
| 83 | + <li class="nav-dropdown"> |
| 84 | + <button class="nav-link nav-dropdown-toggle "> |
| 85 | + Analysis |
| 86 | + <svg class="dropdown-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="12" height="12"> |
| 87 | + <polyline points="6 9 12 15 18 9"></polyline> |
| 88 | + </svg> |
| 89 | + </button> |
| 90 | + <ul class="nav-dropdown-menu"> |
| 91 | + <li><a href="halstead.html" class="nav-link ">Halstead Metrics</a></li> |
| 92 | + <li><a href="analysis.html" class="nav-link ">Multi-dimensional</a></li> |
| 93 | + <li><a href="recommendations.html" class="nav-link ">Recommendations</a></li> |
| 94 | + </ul> |
| 95 | + </li> |
| 96 | + |
| 97 | + <!-- Architecture Dropdown --> |
| 98 | + <li class="nav-dropdown"> |
| 99 | + <button class="nav-link nav-dropdown-toggle "> |
| 100 | + Architecture |
| 101 | + <svg class="dropdown-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="12" height="12"> |
| 102 | + <polyline points="6 9 12 15 18 9"></polyline> |
| 103 | + </svg> |
| 104 | + </button> |
| 105 | + <ul class="nav-dropdown-menu"> |
| 106 | + <li><a href="architecture.html" class="nav-link ">Layers & SOLID</a></li> |
| 107 | + <li><a href="coverage.html" class="nav-link active">Test Coverage</a></li> |
| 108 | + <li><a href="dependencies.html" class="nav-link ">Dependencies</a></li> |
| 109 | + </ul> |
| 110 | + </li> |
| 111 | + </ul> |
| 112 | + |
| 113 | + <button class="btn-icon" onclick="toggleTheme()" title="Toggle light/dark theme"> |
| 114 | + <svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| 115 | + <circle cx="12" cy="12" r="5"/> |
| 116 | + <line x1="12" y1="1" x2="12" y2="3"/> |
| 117 | + <line x1="12" y1="21" x2="12" y2="23"/> |
| 118 | + <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/> |
| 119 | + <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/> |
| 120 | + <line x1="1" y1="12" x2="3" y2="12"/> |
| 121 | + <line x1="21" y1="12" x2="23" y2="12"/> |
| 122 | + <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/> |
| 123 | + <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/> |
| 124 | + </svg> |
| 125 | + <svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| 126 | + <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/> |
| 127 | + </svg> |
| 128 | + </button> |
| 129 | + </div> |
| 130 | + </nav> |
| 131 | + |
| 132 | + <div class="container"> |
| 133 | + <div class="page-header"> |
| 134 | + <h1>Test Coverage</h1> |
| 135 | + <p>Code coverage analysis from PHPUnit test suite</p> |
| 136 | + </div> |
| 137 | + |
| 138 | + <!-- No Coverage Data --> |
| 139 | + <section class="section"> |
| 140 | + <div class="card text-center" style="padding: 3rem;"> |
| 141 | + <div style="font-size: 4rem; margin-bottom: 1rem; opacity: 0.5;">📊</div> |
| 142 | + <h2>No Coverage Data</h2> |
| 143 | + <p class="text-muted" style="margin-bottom: 2rem;">No coverage file was provided. Use --coverage option to include test coverage analysis.</p> |
| 144 | + |
| 145 | + <div class="card" style="text-align: left; max-width: 700px; margin: 0 auto;"> |
| 146 | + <h3>How to Generate Coverage</h3> |
| 147 | + <p>Use PHPUnit with the --coverage-clover option to generate a coverage report in Clover XML format:</p> |
| 148 | + <pre><code># PHPUnit (Clover format) |
| 149 | +./vendor/bin/phpunit --coverage-clover coverage.xml |
| 150 | + |
| 151 | +# Then run PhpQuality with coverage |
| 152 | +docker run --rm \ |
| 153 | + -v $(pwd):/project \ |
| 154 | + amoifr13/phpquality \ |
| 155 | + analyze --source=/project/src --coverage=/project/coverage.xml</code></pre> |
| 156 | + </div> |
| 157 | + </div> |
| 158 | + </section> |
| 159 | + |
| 160 | + <footer> |
| 161 | + <p>Generated by <a href="https://github.com/amoifr/PhpQuality">PhpQuality</a> on 2026-03-26 06:49:34</p> |
| 162 | + <p style="margin-top: 0.5rem;">Developed by <a href="https://moi.ruedesjasses.fr">Pascal CESCON</a></p> |
| 163 | + </footer> |
| 164 | + </div> |
| 165 | + |
| 166 | + <script> |
| 167 | + // Theme toggle |
| 168 | + function initTheme() { |
| 169 | + const savedTheme = localStorage.getItem('phpquality-theme'); |
| 170 | + if (savedTheme) { |
| 171 | + document.documentElement.setAttribute('data-theme', savedTheme); |
| 172 | + } |
| 173 | + } |
| 174 | + |
| 175 | + function toggleTheme() { |
| 176 | + const currentTheme = document.documentElement.getAttribute('data-theme'); |
| 177 | + const newTheme = currentTheme === 'light' ? 'dark' : 'light'; |
| 178 | + |
| 179 | + if (newTheme === 'dark') { |
| 180 | + document.documentElement.removeAttribute('data-theme'); |
| 181 | + } else { |
| 182 | + document.documentElement.setAttribute('data-theme', newTheme); |
| 183 | + } |
| 184 | + |
| 185 | + localStorage.setItem('phpquality-theme', newTheme); |
| 186 | + } |
| 187 | + |
| 188 | + // Initialize theme on page load |
| 189 | + initTheme(); |
| 190 | + </script> |
| 191 | + |
| 192 | + <script> |
| 193 | + const coverageData = null; |
| 194 | + |
| 195 | + // Coverage Distribution Chart |
| 196 | + new Chart(document.getElementById('coverageDistChart').getContext('2d'), { |
| 197 | + type: 'doughnut', |
| 198 | + data: { |
| 199 | + labels: coverageData.distribution.labels, |
| 200 | + datasets: [{ |
| 201 | + data: coverageData.distribution.values, |
| 202 | + backgroundColor: coverageData.distribution.colors, |
| 203 | + borderWidth: 0 |
| 204 | + }] |
| 205 | + }, |
| 206 | + options: { |
| 207 | + responsive: true, |
| 208 | + maintainAspectRatio: false, |
| 209 | + plugins: { |
| 210 | + legend: { |
| 211 | + position: 'right', |
| 212 | + labels: { color: '#94a3b8', font: { size: 11 } } |
| 213 | + } |
| 214 | + } |
| 215 | + } |
| 216 | + }); |
| 217 | + |
| 218 | + // Filter functionality |
| 219 | + const searchInput = document.getElementById('searchInput'); |
| 220 | + const ratingFilter = document.getElementById('ratingFilter'); |
| 221 | + const tbody = document.querySelector('#filesTable tbody'); |
| 222 | + |
| 223 | + function filterTable() { |
| 224 | + const search = searchInput.value.toLowerCase(); |
| 225 | + const rating = ratingFilter.value; |
| 226 | + const rows = tbody.querySelectorAll('tr'); |
| 227 | + |
| 228 | + rows.forEach(row => { |
| 229 | + const text = row.textContent.toLowerCase(); |
| 230 | + const rowRating = row.dataset.rating; |
| 231 | + const matchesSearch = text.includes(search); |
| 232 | + const matchesRating = !rating || rowRating === rating; |
| 233 | + row.style.display = matchesSearch && matchesRating ? '' : 'none'; |
| 234 | + }); |
| 235 | + } |
| 236 | + |
| 237 | + searchInput.addEventListener('input', filterTable); |
| 238 | + ratingFilter.addEventListener('change', filterTable); |
| 239 | + |
| 240 | + // Sort functionality |
| 241 | + let currentSort = { col: 1, asc: true }; |
| 242 | + |
| 243 | + function sortTable(col) { |
| 244 | + const rows = Array.from(tbody.querySelectorAll('tr')); |
| 245 | + const isAsc = currentSort.col === col ? !currentSort.asc : true; |
| 246 | + currentSort = { col, asc: isAsc }; |
| 247 | + |
| 248 | + rows.sort((a, b) => { |
| 249 | + const aVal = a.cells[col].dataset.sort || a.cells[col].textContent; |
| 250 | + const bVal = b.cells[col].dataset.sort || b.cells[col].textContent; |
| 251 | + const aNum = parseFloat(aVal); |
| 252 | + const bNum = parseFloat(bVal); |
| 253 | + |
| 254 | + if (!isNaN(aNum) && !isNaN(bNum)) { |
| 255 | + return isAsc ? aNum - bNum : bNum - aNum; |
| 256 | + } |
| 257 | + return isAsc ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal); |
| 258 | + }); |
| 259 | + |
| 260 | + rows.forEach(row => tbody.appendChild(row)); |
| 261 | + |
| 262 | + // Update sort indicators |
| 263 | + document.querySelectorAll('#filesTable th').forEach((th, i) => { |
| 264 | + th.classList.toggle('sorted', i === col); |
| 265 | + const icon = th.querySelector('.sort-icon'); |
| 266 | + if (icon) icon.innerHTML = i === col ? (isAsc ? '▲' : '▼') : '▲'; |
| 267 | + }); |
| 268 | + } |
| 269 | +</script> |
| 270 | +</body> |
| 271 | +</html> |
0 commit comments