@@ -57,14 +57,33 @@ function pctClass(pct: number): string {
5757 return 'red' ;
5858}
5959
60- function buildCoverageCell ( covered : number , total : number ) : string {
61- if ( total === 0 ) return '' ;
60+ function fmtNum ( n : number ) : string {
61+ return n . toString ( ) . replace ( / \B (? = ( \d { 3 } ) + (? ! \d ) ) / g, ',' ) ;
62+ }
63+
64+ function updateCoverageCells (
65+ container : Element ,
66+ prefix : string ,
67+ covered : number ,
68+ total : number
69+ ) : void {
70+ const barEl = $ ( prefix + '-bar' , container ) ;
71+ const pctEl = $ ( prefix + '-pct' , container ) ;
72+ const numEl = $ ( prefix + '-num' , container ) ;
73+ const denEl = $ ( prefix + '-den' , container ) ;
74+ if ( total === 0 ) {
75+ if ( barEl ) barEl . innerHTML = '' ;
76+ if ( pctEl ) { pctEl . textContent = '' ; pctEl . className = pctEl . className . replace ( / g r e e n | y e l l o w | r e d / g, '' ) . trim ( ) ; }
77+ if ( numEl ) numEl . textContent = '' ;
78+ if ( denEl ) denEl . textContent = '' ;
79+ return ;
80+ }
6281 const p = ( covered * 100.0 ) / total ;
63- return '<div class="coverage-cell">' +
64- '<div class="coverage-bar"><div class="coverage-bar__fill coverage-bar__fill--' + pctClass ( p ) + '" style="width: ' + p . toFixed ( 1 ) + '%"></div></div>' +
65- '<span class="coverage-cell__pct ' + pctClass ( p ) + '">' + p . toFixed ( 2 ) + '%</span> ' +
66- '<span class="coverage-cell__fraction">' + covered + '/' + total + '</span>' +
67- '</div>' ;
82+ const cls = pctClass ( p ) ;
83+ if ( barEl ) barEl . innerHTML = '<div class="coverage-bar"><div class="coverage-bar__fill coverage-bar__fill--' + cls + '" style="width: ' + p . toFixed ( 1 ) + '%"></div></div>' ;
84+ if ( pctEl ) { pctEl . textContent = p . toFixed ( 2 ) + '%' ; pctEl . className = pctEl . className . replace ( / g r e e n | y e l l o w | r e d / g , '' ) . trim ( ) + ' ' + cls ; }
85+ if ( numEl ) numEl . textContent = fmtNum ( covered ) + '/' ;
86+ if ( denEl ) denEl . textContent = fmtNum ( total ) ;
6887}
6988
7089// --- Sort state -----------------------------------------------
@@ -110,9 +129,11 @@ function sortTable(table: Element, colIndex: number): void {
110129 rows . forEach ( row => tbody . appendChild ( row ) ) ;
111130
112131 // Update sort indicators
113- $$ ( 'th' , table ) . forEach ( ( th , i ) => {
132+ let idx = 0 ;
133+ $$ ( 'thead tr:first-child th' , table ) . forEach ( ( th ) => {
114134 th . classList . remove ( 'sorting_asc' , 'sorting_desc' , 'sorting' ) ;
115- th . classList . add ( i === colIndex ? ( dir === 'asc' ? 'sorting_asc' : 'sorting_desc' ) : 'sorting' ) ;
135+ th . classList . add ( idx === colIndex ? ( dir === 'asc' ? 'sorting_asc' : 'sorting_desc' ) : 'sorting' ) ;
136+ idx += parseInt ( th . getAttribute ( 'colspan' ) || '1' , 10 ) ;
116137 } ) ;
117138}
118139
@@ -219,31 +240,34 @@ function updateTotalsRow(container: Element): void {
219240 }
220241
221242 const fileCount = $ ( '.t-file-count' , container ) ;
222- if ( fileCount ) fileCount . textContent = rows . length + ' files' ;
243+ const totalFiles = parseInt ( container . getAttribute ( 'data-total-files' ) || '0' , 10 ) ;
244+ if ( fileCount ) {
245+ const label = rows . length === 1 ? ' file' : ' files' ;
246+ fileCount . textContent = rows . length === totalFiles
247+ ? fmtNum ( totalFiles ) + label
248+ : fmtNum ( rows . length ) + '/' + fmtNum ( totalFiles ) + label ;
249+ }
223250
224251 const coveredLines = sumData ( 'coveredLines' ) ;
225252 const relevantLines = sumData ( 'relevantLines' ) ;
226- const lineCov = $ ( '.t-totals__line-coverage' , container ) ;
227- if ( lineCov ) lineCov . innerHTML = buildCoverageCell ( coveredLines , relevantLines ) ;
253+ updateCoverageCells ( container , '.t-totals__line' , coveredLines , relevantLines ) ;
228254
229255 const numberCells = $$ ( '.totals-row .cell--number' , container ) ;
230- if ( numberCells [ 0 ] ) numberCells [ 0 ] . textContent = relevantLines ? String ( relevantLines ) : '' ;
256+ if ( numberCells [ 0 ] ) numberCells [ 0 ] . textContent = relevantLines ? fmtNum ( relevantLines ) : '' ;
231257
232- const branchCov = $ ( '.t-totals__branch-coverage' , container ) ;
233- if ( branchCov ) {
258+ if ( $ ( '.t-totals__branch-pct' , container ) ) {
234259 const coveredBranches = sumData ( 'coveredBranches' ) ;
235260 const totalBranches = sumData ( 'totalBranches' ) ;
236- branchCov . innerHTML = buildCoverageCell ( coveredBranches , totalBranches ) ;
237- if ( numberCells [ 1 ] ) numberCells [ 1 ] . textContent = totalBranches ? String ( totalBranches ) : '' ;
261+ updateCoverageCells ( container , '.t-totals__branch' , coveredBranches , totalBranches ) ;
262+ if ( numberCells [ 1 ] ) numberCells [ 1 ] . textContent = totalBranches ? fmtNum ( totalBranches ) : '' ;
238263 }
239264
240- const methodCov = $ ( '.t-totals__method-coverage' , container ) ;
241- if ( methodCov ) {
265+ if ( $ ( '.t-totals__method-pct' , container ) ) {
242266 const coveredMethods = sumData ( 'coveredMethods' ) ;
243267 const totalMethods = sumData ( 'totalMethods' ) ;
244- methodCov . innerHTML = buildCoverageCell ( coveredMethods , totalMethods ) ;
245- const idx = branchCov ? 2 : 1 ;
246- if ( numberCells [ idx ] ) numberCells [ idx ] . textContent = totalMethods ? String ( totalMethods ) : '' ;
268+ updateCoverageCells ( container , '.t-totals__method' , coveredMethods , totalMethods ) ;
269+ const numIdx = $ ( '.t-totals__branch-pct' , container ) ? 2 : 1 ;
270+ if ( numberCells [ numIdx ] ) numberCells [ numIdx ] . textContent = totalMethods ? fmtNum ( totalMethods ) : '' ;
247271 }
248272}
249273
@@ -314,6 +338,11 @@ function showFileList(tabId: string): void {
314338 if ( target ) target . style . display = '' ;
315339 }
316340 }
341+ // Only equalize bars if the wrapper is actually visible
342+ const wrapper = document . getElementById ( 'wrapper' ) ;
343+ if ( wrapper && ! wrapper . classList . contains ( 'hide' ) ) {
344+ equalizeBarWidths ( ) ;
345+ }
317346}
318347
319348function navigateToHash ( ) : void {
@@ -385,12 +414,15 @@ document.addEventListener('DOMContentLoaded', function () {
385414 } ) ;
386415 } ) ( ) ;
387416
388- // Table sorting
417+ // Table sorting — compute td index from th colspan
389418 $$ ( 'table.file_list' ) . forEach ( table => {
390- $$ ( 'thead tr:first-child th' , table ) . forEach ( ( th , colIndex ) => {
419+ let tdIndex = 0 ;
420+ $$ ( 'thead tr:first-child th' , table ) . forEach ( ( th ) => {
421+ const myTdIndex = tdIndex ;
391422 th . classList . add ( 'sorting' ) ;
392423 ( th as HTMLElement ) . style . cursor = 'pointer' ;
393- th . addEventListener ( 'click' , ( ) => sortTable ( table , colIndex ) ) ;
424+ th . addEventListener ( 'click' , ( ) => sortTable ( table , myTdIndex ) ) ;
425+ tdIndex += parseInt ( th . getAttribute ( 'colspan' ) || '1' , 10 ) ;
394426 } ) ;
395427 } ) ;
396428
@@ -483,19 +515,84 @@ document.addEventListener('DOMContentLoaded', function () {
483515 window . location . hash = this . getAttribute ( 'href' ) ! . replace ( '#' , '#_' ) ;
484516 } ) ;
485517
518+ // Equalize bar column widths within each table
519+ function setBarWidth ( bars : Element [ ] , headers : Element [ ] , px : number ) : void {
520+ const w = px + 'px' ;
521+ headers . forEach ( h => h . setAttribute ( 'colspan' , '4' ) ) ;
522+ bars . forEach ( b => {
523+ const s = ( b as HTMLElement ) . style ;
524+ s . display = '' ;
525+ s . width = w ; s . minWidth = w ; s . maxWidth = w ;
526+ } ) ;
527+ }
528+
529+ function hideBars ( bars : Element [ ] , headers : Element [ ] ) : void {
530+ headers . forEach ( h => h . setAttribute ( 'colspan' , '3' ) ) ;
531+ bars . forEach ( b => {
532+ const s = ( b as HTMLElement ) . style ;
533+ s . display = 'none' ; s . width = '' ; s . minWidth = '' ; s . maxWidth = '' ;
534+ } ) ;
535+ }
536+
537+ function equalizeBarWidths ( ) : void {
538+ // Only measure visible containers
539+ $$ ( '.file_list_container' ) . forEach ( container => {
540+ if ( ( container as HTMLElement ) . style . display === 'none' ) return ;
541+ if ( container . offsetWidth === 0 ) return ;
542+
543+ const table = $ ( 'table.file_list' , container ) as HTMLTableElement | null ;
544+ if ( ! table ) return ;
545+ const bars = $$ ( 'td.cell--bar' , table ) ;
546+ const headers = $$ ( 'th[colspan]' , table ) ;
547+ if ( bars . length === 0 ) return ;
548+
549+ const wrapper = table . closest ( '.file_list--responsive' ) as HTMLElement | null ;
550+ if ( ! wrapper ) return ;
551+
552+ const firstDataRow = $ ( 'tbody tr' , table ) || $ ( 'thead tr.totals-row' , table ) ;
553+ const barsPerRow = firstDataRow ? $$ ( 'td.cell--bar' , firstDataRow ) . length : 1 ;
554+
555+ // Step 1: Hide bars, measure content width with table at auto
556+ hideBars ( bars , headers ) ;
557+ table . style . width = 'auto' ;
558+ void table . offsetWidth ;
559+ const contentWidth = table . scrollWidth ;
560+
561+ // Step 2: Restore table width, measure available space
562+ table . style . width = '' ;
563+ void table . offsetWidth ;
564+ const availableWidth = wrapper . clientWidth ;
565+
566+ // Step 3: Calculate bar width
567+ const totalBarSpace = availableWidth - contentWidth ;
568+ const perBar = Math . floor ( totalBarSpace / barsPerRow ) ;
569+
570+ if ( perBar < 100 ) return ; // keep bars hidden
571+
572+ setBarWidth ( bars , headers , Math . min ( perBar , 200 ) ) ;
573+ } ) ;
574+ }
575+
576+ // Defer until after wrapper is visible
577+ window . addEventListener ( 'resize' , equalizeBarWidths ) ;
578+
486579 // Initial state
487580 navigateToHash ( ) ;
488581
489582 // Finalize loading
490583 clearInterval ( ( window as any ) . _simplecovLoadingTimer ) ;
584+ clearTimeout ( ( window as any ) . _simplecovShowTimeout ) ;
491585
492- const loading = document . getElementById ( 'loading' ) ;
493- if ( loading ) {
494- loading . style . transition = 'opacity 0.3s' ;
495- loading . style . opacity = '0' ;
496- setTimeout ( ( ) => { loading . style . display = 'none' ; } , 300 ) ;
586+ const loadingEl = document . getElementById ( 'loading' ) ;
587+ if ( loadingEl ) {
588+ loadingEl . style . transition = 'opacity 0.3s' ;
589+ loadingEl . style . opacity = '0' ;
590+ setTimeout ( ( ) => { loadingEl . style . display = 'none' ; } , 300 ) ;
497591 }
498592
499- const wrapper = document . getElementById ( 'wrapper' ) ;
500- if ( wrapper ) wrapper . classList . remove ( 'hide' ) ;
593+ const wrapperEl = document . getElementById ( 'wrapper' ) ;
594+ if ( wrapperEl ) wrapperEl . classList . remove ( 'hide' ) ;
595+
596+ // Equalize bar widths now that wrapper is visible
597+ equalizeBarWidths ( ) ;
501598} ) ;
0 commit comments