Skip to content

Commit 1478ee1

Browse files
committed
Render HTML coverage reports client-side from static JSON data
Replace server-side ERB template rendering with a static single-page JavaScript app. The HTML formatter now writes coverage_data.js (a thin JS wrapper around the JSON coverage data) and copies pre-compiled static assets. All rendering — file lists, source code views, coverage bars, group tabs — happens in the browser. This cleanly separates data (JSON) from presentation (JS/HTML/CSS): * Delete ERB templates, Ruby view helpers, and coverage helpers * Move all rendering logic into TypeScript (app.ts) * Add static index.html as a pre-compiled asset via rake assets:compile * Flatten formatter class hierarchy (5 classes → 3) * Introduce JSONFormatter.build_hash for shared, side-effect-free serialization used by both formatters * Eliminate double JSONFormatter execution when both formatters are configured
1 parent 94db9fa commit 1478ee1

29 files changed

+1324
-2745
lines changed

Rakefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ task test: %i[spec cucumber test_html]
4040
task default: %i[rubocop spec cucumber test_html]
4141

4242
namespace :assets do
43-
desc "Compile frontend assets (JS + CSS) using esbuild"
43+
desc "Compile frontend assets (HTML, JS, CSS) using esbuild"
4444
task :compile do
4545
frontend = File.expand_path("html_frontend", __dir__)
4646
outdir = File.expand_path("lib/simplecov/formatter/html_formatter/public", __dir__)
@@ -62,5 +62,8 @@ namespace :assets do
6262
io.close_write
6363
File.write("#{outdir}/application.css", io.read)
6464
end
65+
66+
# HTML: copy static index.html
67+
FileUtils.cp(File.join(frontend, "src/index.html"), File.join(outdir, "index.html"))
6568
end
6669
end

features/branch_coverage.feature

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ Feature:
1515
end
1616
"""
1717
When I open the coverage report generated with `bundle exec rspec spec`
18-
Then the output should contain "Line coverage: 56 / 61 (91.80%)"
19-
And the output should contain "Branch coverage: 2 / 4 (50.00%)"
18+
Then the output should contain "56 / 61 LOC (91.8%) covered"
2019
And I should see the groups:
2120
| name | coverage | files |
2221
| All Files | 91.80% | 7 |

features/step_definitions/html_steps.rb

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,29 @@
6060
end
6161

6262
Then /^there should be (\d+) skipped lines in the source files$/ do |expected_count|
63-
count = page.evaluate_script("document.querySelectorAll('.source_files template').length > 0 ? Array.from(document.querySelectorAll('.source_files template')).reduce(function(sum, t) { return sum + t.content.querySelectorAll('ol li.skipped').length; }, 0) : document.querySelectorAll('.source_table ol li.skipped').length")
63+
# Materialize all source files (renders them from coverage data), then count skipped lines
64+
count = page.evaluate_script(<<~JS)
65+
(function() {
66+
// Check for pre-rendered templates (old simplecov-html)
67+
var templates = document.querySelectorAll('.source_files template');
68+
if (templates.length > 0) {
69+
return Array.from(templates).reduce(function(sum, t) {
70+
return sum + t.content.querySelectorAll('ol li.skipped').length;
71+
}, 0);
72+
}
73+
// New architecture: count skipped lines directly from coverage data
74+
if (window.SIMPLECOV_DATA) {
75+
var count = 0;
76+
var coverage = window.SIMPLECOV_DATA.coverage;
77+
Object.keys(coverage).forEach(function(fn) {
78+
var lines = coverage[fn].lines;
79+
lines.forEach(function(l) { if (l === 'ignored') count++; });
80+
});
81+
return count;
82+
}
83+
return document.querySelectorAll('.source_table ol li.skipped').length;
84+
})()
85+
JS
6486
expect(count).to eq(expected_count.to_i)
6587
end
6688

features/support/env.rb

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,23 @@ def extended(base)
2323

2424
# Rack app for Capybara which returns the latest coverage report from Aruba temp project dir
2525
coverage_dir = File.expand_path("../../tmp/aruba/project/coverage/", __dir__)
26+
27+
# Prevent the browser from caching coverage_data.js between scenario visits
28+
class NoCacheMiddleware
29+
def initialize(app)
30+
@app = app
31+
end
32+
33+
def call(env)
34+
status, headers, body = @app.call(env)
35+
headers["cache-control"] = "no-store"
36+
[status, headers, body]
37+
end
38+
end
39+
2640
Capybara.app = Rack::Builder.new do
27-
use Rack::Static, urls: {"/" => "index.html"}, root: coverage_dir, header_rules: [[:all, {"cache-control" => "no-store"}]]
41+
use NoCacheMiddleware
42+
use Rack::Static, urls: {"/" => "index.html"}, root: coverage_dir
2843
run Rack::Directory.new(coverage_dir)
2944
end.to_app
3045

0 commit comments

Comments
 (0)