Skip to content

Commit 99041ac

Browse files
committed
Shorten table column headings for narrower coverage columns
1 parent 28d43a4 commit 99041ac

8 files changed

Lines changed: 97 additions & 130 deletions

File tree

assets/stylesheets/screen.css

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ table.file_list th.cell--coverage .th-with-filter {
410410
/* --- File list table --------------------------------------- */
411411

412412
.file_list--responsive {
413+
overflow-x: auto;
413414
}
414415

415416
table.file_list {
@@ -502,30 +503,28 @@ a.src_link {
502503

503504
/* --- Coverage columns -------------------------------------- */
504505

505-
table.file_list td.cell--bar {
506-
padding-right: 4px;
507-
padding-left: 0;
506+
table.file_list td.cell--coverage {
507+
white-space: nowrap;
508508
}
509509

510-
table.file_list td.cell--bar .coverage-bar {
511-
margin-left: auto;
510+
.coverage-cell {
511+
display: flex;
512+
flex-wrap: nowrap;
513+
align-items: center;
514+
justify-content: flex-end;
515+
gap: 10px;
512516
}
513517

514-
table.file_list td.cell--pct {
515-
text-align: right;
518+
.coverage-cell .coverage-pct {
519+
flex: 0 0 4.5em;
516520
font-variant-numeric: tabular-nums;
517-
white-space: nowrap;
518-
width: 1%;
519-
padding-left: 4px;
520-
}
521-
522-
table.file_list .totals-row td.cell--bar {
523-
padding-right: 4px;
524-
padding-left: 0;
525521
}
526522

527-
table.file_list .totals-row td.cell--pct {
528-
padding-left: 4px;
523+
.coverage-cell .bar-sizer {
524+
flex: 0 0 auto;
525+
width: 240px;
526+
min-width: 160px;
527+
max-width: 240px;
529528
}
530529

531530
table.file_list td.cell--numerator {
@@ -567,7 +566,6 @@ table.file_list .totals-row td.cell--denominator {
567566
background: var(--bar-bg);
568567
border-radius: 6px;
569568
overflow: hidden;
570-
min-width: 40px;
571569
}
572570

573571
.coverage-bar__fill {

lib/simplecov-html/view_helpers.rb

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,37 +76,31 @@ def fmt(number)
7676
def coverage_bar(pct)
7777
css = coverage_css_class(pct)
7878
width = Kernel.format("%.1f", pct.floor(1))
79-
%(<div class="coverage-bar"><div class="coverage-bar__fill coverage-bar__fill--#{css}" style="width: #{width}%"></div></div>)
79+
%(<div class="bar-sizer"><div class="coverage-bar"><div class="coverage-bar__fill coverage-bar__fill--#{css}" style="width: #{width}%"></div></div></div>)
8080
end
8181

82-
def coverage_cells(pct, covered, total, type:, totals: false) # rubocop:disable Metrics/MethodLength
82+
def coverage_cells(pct, covered, total, type:, totals: false)
8383
css = coverage_css_class(pct)
84+
pct_str = Kernel.format("%.2f", pct.floor(2))
8485
if totals
85-
bar_cls = "cell--bar t-totals__#{type}-bar"
86-
pct_cls = "cell--pct strong t-totals__#{type}-pct #{css}"
86+
cov_cls = "cell--coverage strong t-totals__#{type}-pct #{css}"
8787
num_cls = "cell--numerator strong t-totals__#{type}-num"
8888
den_cls = "cell--denominator strong t-totals__#{type}-den"
89+
order = ""
8990
else
90-
bar_cls = "cell--bar"
91-
pct_cls = "cell--pct cell--#{type}-pct #{css}"
91+
cov_cls = "cell--coverage cell--#{type}-pct #{css}"
9292
num_cls = "cell--numerator"
9393
den_cls = "cell--denominator"
94+
order = %( data-order="#{Kernel.format('%.2f', pct)}")
9495
end
95-
pct_str = Kernel.format("%.2f", pct.floor(2))
96-
pct_td = if totals
97-
%(<td class="#{pct_cls}">#{pct_str}%</td>)
98-
else
99-
%(<td class="#{pct_cls}" data-order="#{Kernel.format('%.2f', pct)}">#{pct_str}%</td>)
100-
end
101-
%(<td class="#{bar_cls}">#{coverage_bar(pct)}</td>) +
102-
pct_td +
96+
%(<td class="#{cov_cls}"#{order}><div class="coverage-cell">#{coverage_bar(pct)}<span class="coverage-pct">#{pct_str}%</span></div></td>) +
10397
%(<td class="#{num_cls}">#{fmt(covered)}/</td>) +
10498
%(<td class="#{den_cls}">#{fmt(total)}</td>)
10599
end
106100

107101
def coverage_header_cells(label, type, covered_label, total_label)
108102
<<~HTML
109-
<th class="cell--coverage" colspan="2">
103+
<th class="cell--coverage">
110104
<div class="th-with-filter">
111105
<span class="th-label">#{label}</span>
112106
<div class="col-filter__coverage">

public/application.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/application.js

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app.ts

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ hljs.registerLanguage('ruby', ruby);
77
// --- Constants ------------------------------------------------
88

99
const MAX_BAR_WIDTH = 240;
10-
const MIN_BAR_WIDTH = 80;
10+
const MIN_BAR_WIDTH = 160;
1111
const GREEN_THRESHOLD = 90;
1212
const YELLOW_THRESHOLD = 75;
1313

@@ -74,21 +74,21 @@ function updateCoverageCells(
7474
covered: number,
7575
total: number
7676
): void {
77-
const barEl = $(prefix + '-bar', container);
78-
const pctEl = $(prefix + '-pct', container);
77+
const covCell = $(prefix + '-pct', container);
7978
const numEl = $(prefix + '-num', container);
8079
const denEl = $(prefix + '-den', container);
8180
if (total === 0) {
82-
if (barEl) barEl.innerHTML = '';
83-
if (pctEl) { pctEl.textContent = ''; pctEl.className = pctEl.className.replace(/green|yellow|red/g, '').trim(); }
81+
if (covCell) { covCell.innerHTML = ''; covCell.className = covCell.className.replace(/green|yellow|red/g, '').trim(); }
8482
if (numEl) numEl.textContent = '';
8583
if (denEl) denEl.textContent = '';
8684
return;
8785
}
8886
const p = (covered * 100.0) / total;
8987
const cls = pctClass(p);
90-
if (barEl) barEl.innerHTML = `<div class="coverage-bar"><div class="coverage-bar__fill coverage-bar__fill--${cls}" style="width: ${p.toFixed(1)}%"></div></div>`;
91-
if (pctEl) { pctEl.textContent = p.toFixed(2) + '%'; pctEl.className = `${pctEl.className.replace(/green|yellow|red/g, '').trim()} ${cls}`; }
88+
if (covCell) {
89+
covCell.innerHTML = `<div class="coverage-cell"><div class="bar-sizer"><div class="coverage-bar"><div class="coverage-bar__fill coverage-bar__fill--${cls}" style="width: ${p.toFixed(1)}%"></div></div></div><span class="coverage-pct">${p.toFixed(2)}%</span></div>`;
90+
covCell.className = `${covCell.className.replace(/green|yellow|red/g, '').trim()} ${cls}`;
91+
}
9292
if (numEl) numEl.textContent = fmtNum(covered) + '/';
9393
if (denEl) denEl.textContent = fmtNum(total);
9494
}
@@ -305,21 +305,18 @@ function materializeSourceFile(sourceFileId: string): HTMLElement | null {
305305

306306
// --- Bar width equalization ------------------------------------
307307

308-
function setBarWidth(bars: Element[], table: Element, px: number): void {
308+
function setBarSizerWidth(sizers: Element[], px: number): void {
309309
const w = px + 'px';
310-
$$('th.cell--coverage', table).forEach(h => h.setAttribute('colspan', '2'));
311-
bars.forEach(b => {
312-
const s = (b as HTMLElement).style;
313-
s.display = '';
314-
s.width = w; s.minWidth = w; s.maxWidth = w;
310+
sizers.forEach(s => {
311+
const st = (s as HTMLElement).style;
312+
st.width = w; st.minWidth = w; st.maxWidth = w;
315313
});
316314
}
317315

318-
function hideBars(bars: Element[], table: Element): void {
319-
$$('th.cell--coverage', table).forEach(h => h.setAttribute('colspan', '1'));
320-
bars.forEach(b => {
321-
const s = (b as HTMLElement).style;
322-
s.display = 'none'; s.width = ''; s.minWidth = ''; s.maxWidth = '';
316+
function clearBarSizerWidth(sizers: Element[]): void {
317+
sizers.forEach(s => {
318+
const st = (s as HTMLElement).style;
319+
st.width = ''; st.minWidth = ''; st.maxWidth = '';
323320
});
324321
}
325322

@@ -330,40 +327,34 @@ function equalizeBarWidths(): void {
330327

331328
const table = $('table.file_list', container) as HTMLTableElement | null;
332329
if (!table) return;
333-
const bars = $$('td.cell--bar', table);
334-
if (bars.length === 0) return;
330+
const sizers = $$('.bar-sizer', table);
331+
if (sizers.length === 0) return;
335332

336333
const wrapper = table.closest('.file_list--responsive') as HTMLElement | null;
337334
if (!wrapper) return;
338335

339336
wrapper.style.visibility = 'hidden';
340337

341-
const fitsAt = (px: number): boolean => {
342-
setBarWidth(bars, table, px);
343-
table.style.width = 'auto';
344-
void table.offsetWidth;
345-
const fits = table.scrollWidth <= wrapper.clientWidth;
346-
table.style.width = '';
347-
return fits;
348-
};
349-
350-
let barWidth = MAX_BAR_WIDTH;
351-
if (!fitsAt(MAX_BAR_WIDTH)) {
352-
if (!fitsAt(MIN_BAR_WIDTH)) {
353-
hideBars(bars, table);
354-
wrapper.style.visibility = '';
355-
return;
356-
}
357-
let lo = MIN_BAR_WIDTH, hi = MAX_BAR_WIDTH - 1;
338+
// Reset to CSS defaults and measure whether the table overflows
339+
clearBarSizerWidth(sizers);
340+
void table.offsetWidth;
341+
342+
if (table.scrollWidth <= wrapper.clientWidth) {
343+
// Table fits — binary search for the largest bar width that still fits
344+
let lo = MIN_BAR_WIDTH, hi = MAX_BAR_WIDTH;
358345
while (lo < hi) {
359346
const mid = Math.ceil((lo + hi) / 2);
360-
if (fitsAt(mid)) lo = mid;
347+
setBarSizerWidth(sizers, mid);
348+
void table.offsetWidth;
349+
if (table.scrollWidth <= wrapper.clientWidth) lo = mid;
361350
else hi = mid - 1;
362351
}
363-
barWidth = lo;
352+
setBarSizerWidth(sizers, lo);
353+
} else {
354+
// Table overflows — use minimum bar width
355+
setBarSizerWidth(sizers, MIN_BAR_WIDTH);
364356
}
365357

366-
setBarWidth(bars, table, barWidth);
367358
wrapper.style.visibility = '';
368359
});
369360
}

test/test_simple_cov-html.rb

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -64,27 +64,14 @@ def test_output_branch_coverages
6464
assert_equal EXPECTED_BRANCH_COVERAGES, table.sort_by(&:to_f)
6565
end
6666

67-
def test_coverage_headers_have_colspan_two
67+
def test_coverage_cells_contain_bar_and_percentage
6868
html_doc = format_results(CoverageFixtures::ALL_FIXTURES)
69-
headers = html_doc.css("div#AllFiles table.file_list th.cell--coverage")
69+
cov_cells = html_doc.css("div#AllFiles table.file_list tbody td.cell--coverage")
7070

71-
assert_operator headers.length, :>=, 1, "Expected at least one coverage header"
72-
headers.each do |th|
73-
assert_equal "2", th["colspan"], "Coverage header '#{th.text.strip}' must have colspan=2"
74-
end
75-
end
76-
77-
def test_bar_cells_precede_pct_cells
78-
html_doc = format_results(CoverageFixtures::ALL_FIXTURES)
79-
bar_cells = html_doc.css("div#AllFiles table.file_list tbody td.cell--bar")
80-
81-
assert_operator bar_cells.length, :>=, 1, "Expected at least one bar cell"
82-
bar_cells.each do |bar|
83-
pct = bar.next_element
84-
85-
assert pct, "Expected a sibling after each td.cell--bar"
86-
assert_includes pct["class"], "cell--pct",
87-
"td.cell--bar must be immediately followed by td.cell--pct"
71+
assert_operator cov_cells.length, :>=, 1, "Expected at least one coverage cell"
72+
cov_cells.each do |td|
73+
assert td.at_css(".bar-sizer"), "Coverage cell must contain a bar-sizer"
74+
assert td.at_css(".coverage-pct"), "Coverage cell must contain a coverage-pct"
8875
end
8976
end
9077

test/test_view_helpers.rb

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -665,24 +665,25 @@ def test_coverage_bar_hundred_percent
665665
def test_coverage_bar_exact_output_structure
666666
result = formatter.send(:coverage_bar, 90.0)
667667

668-
assert_equal '<div class="coverage-bar"><div class="coverage-bar__fill coverage-bar__fill--green" style="width: 90.0%"></div></div>', result
668+
assert_equal '<div class="bar-sizer"><div class="coverage-bar"><div class="coverage-bar__fill coverage-bar__fill--green" style="width: 90.0%"></div></div></div>', result
669669
end
670670

671671
# -- coverage_cells tests ---------------------------------------------------
672672

673673
cover "SimpleCov::Formatter::HTMLFormatter::ViewHelpers#coverage_cells" if respond_to?(:cover)
674674

675-
def test_coverage_cells_returns_four_td_elements
675+
def test_coverage_cells_returns_three_td_elements
676676
result = formatter.send(:coverage_cells, 85.0, 85, 100, type: :line)
677677

678-
assert_equal 4, result.scan("<td").count
678+
assert_equal 3, result.scan("<td").count
679679
end
680680

681-
def test_coverage_cells_bar_cell_has_coverage_bar
681+
def test_coverage_cells_combined_cell_has_bar_and_pct
682682
result = formatter.send(:coverage_cells, 85.0, 85, 100, type: :line)
683683

684-
assert_includes result, 'class="cell--bar"'
685684
assert_includes result, "coverage-bar"
685+
assert_includes result, "coverage-pct"
686+
assert_includes result, "coverage-cell"
686687
end
687688

688689
def test_coverage_cells_pct_cell_contains_formatted_percent
@@ -734,36 +735,36 @@ def test_coverage_cells_uses_red_css_class_for_low
734735
assert_includes result, "red"
735736
end
736737

737-
def test_coverage_cells_pct_td_class_contains_css_color_non_totals
738+
def test_coverage_cells_td_class_contains_css_color_non_totals
738739
result = formatter.send(:coverage_cells, 95.0, 95, 100, type: :line)
739-
pct_td = result.match(/<td class="cell--pct cell--line-pct ([^"]+)"/)
740+
cov_td = result.match(/<td class="cell--coverage cell--line-pct ([^"]+)"/)
740741

741-
assert pct_td, "Expected to find pct td with cell--line-pct class"
742-
assert_includes pct_td[1], "green"
742+
assert cov_td, "Expected to find coverage td with cell--line-pct class"
743+
assert_includes cov_td[1], "green"
743744
end
744745

745-
def test_coverage_cells_pct_td_class_contains_css_color_totals
746+
def test_coverage_cells_td_class_contains_css_color_totals
746747
result = formatter.send(:coverage_cells, 95.0, 95, 100, type: :line, totals: true)
747-
pct_td = result.match(/<td class="cell--pct strong t-totals__line-pct ([^"]+)"/)
748+
cov_td = result.match(/<td class="cell--coverage strong t-totals__line-pct ([^"]+)"/)
748749

749-
assert pct_td, "Expected to find pct td with t-totals__line-pct class"
750-
assert_includes pct_td[1], "green"
750+
assert cov_td, "Expected to find coverage td with t-totals__line-pct class"
751+
assert_includes cov_td[1], "green"
751752
end
752753

753-
def test_coverage_cells_pct_td_class_red_for_low_non_totals
754+
def test_coverage_cells_td_class_red_for_low_non_totals
754755
result = formatter.send(:coverage_cells, 50.0, 50, 100, type: :line)
755-
pct_td = result.match(/<td class="cell--pct cell--line-pct ([^"]+)"/)
756+
cov_td = result.match(/<td class="cell--coverage cell--line-pct ([^"]+)"/)
756757

757-
assert pct_td, "Expected to find pct td"
758-
assert_includes pct_td[1], "red"
758+
assert cov_td, "Expected to find coverage td"
759+
assert_includes cov_td[1], "red"
759760
end
760761

761-
def test_coverage_cells_pct_td_class_red_for_low_totals
762+
def test_coverage_cells_td_class_red_for_low_totals
762763
result = formatter.send(:coverage_cells, 50.0, 50, 100, type: :line, totals: true)
763-
pct_td = result.match(/<td class="cell--pct strong t-totals__line-pct ([^"]+)"/)
764+
cov_td = result.match(/<td class="cell--coverage strong t-totals__line-pct ([^"]+)"/)
764765

765-
assert pct_td, "Expected to find pct td"
766-
assert_includes pct_td[1], "red"
766+
assert cov_td, "Expected to find coverage td"
767+
assert_includes cov_td[1], "red"
767768
end
768769

769770
def test_coverage_cells_non_totals_uses_type_in_pct_class
@@ -778,12 +779,6 @@ def test_coverage_cells_non_totals_branch_type
778779
assert_includes result, "cell--branch-pct"
779780
end
780781

781-
def test_coverage_cells_totals_uses_type_in_bar_class
782-
result = formatter.send(:coverage_cells, 85.0, 85, 100, type: :line, totals: true)
783-
784-
assert_includes result, "t-totals__line-bar"
785-
end
786-
787782
def test_coverage_cells_totals_uses_type_in_pct_class
788783
result = formatter.send(:coverage_cells, 85.0, 85, 100, type: :line, totals: true)
789784

@@ -805,7 +800,7 @@ def test_coverage_cells_totals_uses_type_in_den_class
805800
def test_coverage_cells_totals_has_strong_classes
806801
result = formatter.send(:coverage_cells, 85.0, 85, 100, type: :line, totals: true)
807802

808-
assert_includes result, "strong t-totals__line-pct"
803+
assert_includes result, "cell--coverage strong t-totals__line-pct"
809804
assert_includes result, "strong t-totals__line-num"
810805
assert_includes result, "strong t-totals__line-den"
811806
end
@@ -844,7 +839,8 @@ def test_coverage_cells_exact_pct_format
844839
def test_coverage_cells_totals_pct_value_present
845840
result = formatter.send(:coverage_cells, 85.0, 85, 100, type: :line, totals: true)
846841

847-
assert_match(%r{<td class="cell--pct[^"]*">85\.00%</td>}, result)
842+
assert_includes result, "85.00%"
843+
assert_includes result, "coverage-pct"
848844
end
849845

850846
# -- coverage_header_cells tests --------------------------------------------
@@ -889,10 +885,11 @@ def test_coverage_header_cells_contains_input_filter
889885
assert_includes result, "col-filter__value"
890886
end
891887

892-
def test_coverage_header_cells_has_coverage_colspan
888+
def test_coverage_header_cells_has_no_colspan
893889
result = formatter.send(:coverage_header_cells, "Line", :line, "Covered", "Total")
894890

895-
assert_includes result, 'colspan="2"'
891+
refute_includes result, "colspan"
892+
assert_includes result, "cell--coverage"
896893
end
897894

898895
def test_coverage_header_cells_type_appears_in_both_filter_elements

0 commit comments

Comments
 (0)