Skip to content

Commit d032660

Browse files
committed
Improve code quality
Extract coverage column rendering into reusable Ruby helpers (coverage_bar, coverage_cells, coverage_header_cells, file_data_attrs, coverage_type_summary), reducing file_list.erb from 103 to 53 lines and coverage_summary.erb from 32 to 5 lines. Remove unused strength_css_class method, replace SHA1 with MD5 for HTML IDs, eliminate @_summary instance-variable side-channel, and return placeholder HTML instead of nil on encoding errors. In TypeScript, add named constants for magic numbers, replace compare() if-chain with lookup table, extract parseFilters() and initDarkMode(), loop updateTotalsRow over dataAttrMap, move DOM elements into the dialog instead of deep-cloning, debounce equalizeBarWidths with requestAnimationFrame, cache visible file rows for keyboard navigation, and use .includes() and tbody.append().
1 parent bed718b commit d032660

File tree

8 files changed

+949
-211
lines changed

8 files changed

+949
-211
lines changed

lib/simplecov-html.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def formatted_source_file(source_file)
9393
template("source_file").result(binding)
9494
rescue Encoding::CompatibilityError => e
9595
puts "Encoding problems with file #{source_file.filename}. Simplecov/ERB can't handle non ASCII characters in filenames. Error: #{e.message}."
96+
%(<div class="source_table" id="#{id(source_file)}"><div class="header"><h2>Encoding Error</h2><p>#{ERB::Util.html_escape(e.message)}</p></div></div>)
9697
end
9798

9899
def formatted_file_list(title, source_files)

lib/simplecov-html/view_helpers.rb

Lines changed: 96 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# frozen_string_literal: true
22

3-
require "digest/sha1"
3+
require "digest/md5"
44
require "set"
55

66
module SimpleCov
77
module Formatter
88
class HTMLFormatter
99
# Helper methods used by ERB templates for rendering coverage data.
10-
module ViewHelpers
10+
module ViewHelpers # rubocop:disable Metrics/ModuleLength
1111
def line_status?(source_file, line)
1212
if branch_coverage? && source_file.line_with_missed_branch?(line.number)
1313
"missed-branch"
@@ -44,18 +44,8 @@ def coverage_css_class(covered_percent)
4444
end
4545
end
4646

47-
def strength_css_class(covered_strength)
48-
if covered_strength > 1
49-
"green"
50-
elsif covered_strength == 1
51-
"yellow"
52-
else
53-
"red"
54-
end
55-
end
56-
5747
def id(source_file)
58-
Digest::SHA1.hexdigest(source_file.filename)
48+
Digest::MD5.hexdigest(source_file.filename)
5949
end
6050

6151
def timeago(time)
@@ -83,8 +73,85 @@ def fmt(number)
8373
number.to_s.gsub(/(\d)(?=(\d{3})+(?!\d))/, '\\1,')
8474
end
8575

76+
def coverage_bar(pct)
77+
css = coverage_css_class(pct)
78+
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>)
80+
end
81+
82+
def coverage_cells(pct, covered, total, type:, totals: false) # rubocop:disable Metrics/MethodLength
83+
css = coverage_css_class(pct)
84+
if totals
85+
bar_cls = "cell--bar t-totals__#{type}-bar"
86+
pct_cls = "cell--pct strong t-totals__#{type}-pct #{css}"
87+
num_cls = "cell--numerator strong t-totals__#{type}-num"
88+
den_cls = "cell--denominator strong t-totals__#{type}-den"
89+
else
90+
bar_cls = "cell--bar"
91+
pct_cls = "cell--pct cell--#{type}-pct #{css}"
92+
num_cls = "cell--numerator"
93+
den_cls = "cell--denominator"
94+
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 +
103+
%(<td class="#{num_cls}">#{fmt(covered)}/</td>) +
104+
%(<td class="#{den_cls}">#{fmt(total)}</td>)
105+
end
106+
107+
def coverage_header_cells(label, type, covered_label, total_label)
108+
<<~HTML
109+
<th class="cell--coverage" colspan="2">
110+
<div class="th-with-filter">
111+
<span class="th-label">#{label}</span>
112+
<div class="col-filter__coverage">
113+
<select class="col-filter__op" data-type="#{type}"><option value="lt">&lt;</option><option value="lte" selected>&le;</option><option value="eq">=</option><option value="gte">&ge;</option><option value="gt">&gt;</option></select>
114+
<span class="col-filter__pct-wrap"><input type="number" class="col-filter__value" min="0" max="100" data-type="#{type}" value="100" step="any"></span>
115+
</div>
116+
</div>
117+
</th>
118+
<th class="cell--numerator">#{covered_label}</th>
119+
<th class="cell--denominator">#{total_label}</th>
120+
HTML
121+
end
122+
123+
def file_data_attrs(source_file) # rubocop:disable Metrics/AbcSize
124+
covered = source_file.covered_lines.count
125+
relevant = covered + source_file.missed_lines.count
126+
pairs = {"covered-lines" => covered, "relevant-lines" => relevant}
127+
pairs["covered-branches"] = source_file.covered_branches.count if branch_coverage?
128+
pairs["total-branches"] = source_file.total_branches.count if branch_coverage?
129+
pairs["covered-methods"] = source_file.covered_methods.count if method_coverage?
130+
pairs["total-methods"] = source_file.methods.count if method_coverage?
131+
pairs.map { |k, v| %(data-#{k}="#{v}") }.join(" ")
132+
end
133+
134+
def coverage_type_summary(type, label, summary, enabled:, **opts) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
135+
return disabled_summary(type, label) unless enabled
136+
137+
s = summary.fetch(type.to_sym)
138+
pct = s.fetch(:pct)
139+
covered = s.fetch(:covered)
140+
total = s.fetch(:total)
141+
missed = s.fetch(:missed)
142+
css = coverage_css_class(pct)
143+
parts = [
144+
%(<div class="t-#{type}-summary">\n #{label}: ),
145+
%(<span class="#{css}"><b>#{Kernel.format('%.2f', pct.floor(2))}%</b></span>),
146+
%(<span class="coverage-cell__fraction"> #{covered}/#{total} #{opts.fetch(:suffix, 'covered')}</span>),
147+
]
148+
parts << missed_summary_html(missed, opts.fetch(:missed_class, "red"), opts.fetch(:toggle, false)) if missed.positive?
149+
parts << "\n </div>"
150+
parts.join
151+
end
152+
86153
def coverage_summary(stats, show_method_toggle: false)
87-
@_summary = {
154+
_summary = {
88155
line: build_stats(stats.fetch(:covered_lines), stats.fetch(:total_lines)),
89156
branch: build_stats(stats.fetch(:covered_branches, 0), stats.fetch(:total_branches, 0)),
90157
method: build_stats(stats.fetch(:covered_methods, 0), stats.fetch(:total_methods, 0)),
@@ -97,6 +164,21 @@ def build_stats(covered, total)
97164
pct = total.positive? ? (covered * 100.0 / total) : 100.0
98165
{covered: covered, total: total, missed: total - covered, pct: pct}
99166
end
167+
168+
private
169+
170+
def disabled_summary(type, label)
171+
%(<div class="t-#{type}-summary">\n #{label}: <span class="coverage-disabled">disabled</span>\n </div>)
172+
end
173+
174+
def missed_summary_html(count, missed_class, toggle)
175+
missed = if toggle
176+
%(<a href="#" class="t-missed-method-toggle"><b>#{count}</b> missed</a>)
177+
else
178+
%(<span class="#{missed_class}"><b>#{count}</b> missed</span>)
179+
end
180+
%(<span class="coverage-cell__fraction">,</span>\n #{missed})
181+
end
100182
end
101183
end
102184
end

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.

0 commit comments

Comments
 (0)