From a3220ee416cb5a1989c0d3b1863a794965c04a23 Mon Sep 17 00:00:00 2001
From: "J. M. F. Tsang"
Date: Fri, 22 Apr 2022 00:32:11 +0100
Subject: [PATCH 1/7] Keyboard shortcuts for previous and next page
On each page the shortcuts '[' and ']' will take you to the previous and
next files respectively. On the index page they take you to the final
and first files respectively.
Test cases:
$ pytest --cov-report html --cov=. tests.py
in a directory with just tests.py, then with one, two or three .py
files.
Tested on Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:99.0) Gecko/20100101 Firefox/99.0
---
coverage/html.py | 60 ++++++++++++++++++++++++++---
coverage/htmlfiles/coverage_html.js | 27 ++++++++++---
coverage/htmlfiles/index.html | 13 +++++++
coverage/htmlfiles/pyfile.html | 14 ++++++-
4 files changed, 101 insertions(+), 13 deletions(-)
diff --git a/coverage/html.py b/coverage/html.py
index 342d2ad1c..7f60b8abc 100644
--- a/coverage/html.py
+++ b/coverage/html.py
@@ -165,6 +165,8 @@ def __init__(self, cov):
self.datagen = HtmlDataGeneration(self.coverage)
self.totals = Numbers(precision=self.config.precision)
self.directory_was_empty = False
+ self.first_fr = None
+ self.final_fr = None
self.template_globals = {
# Functions available in the templates.
@@ -204,9 +206,27 @@ def report(self, morfs):
self.incr.read()
self.incr.check_global_data(self.config, self.pyfile_html_source)
- # Process all the files.
- for fr, analysis in get_analysis_to_report(self.coverage, morfs):
- self.html_file(fr, analysis)
+ # Process all the files. For each page we need to supply a link
+ # to the next page. Therefore in each iteration of the loop we
+ # work on the fr and analysis from the previous iteration. We
+ # also need a link to the preceding page (i.e. 2 before the
+ # current iteration).
+ analysis_to_report = get_analysis_to_report(self.coverage, morfs)
+ pluprev_fr, prev_fr = None, None
+ prev_analysis = None
+
+ for fr, analysis in analysis_to_report:
+ if prev_fr is not None:
+ self.html_file(prev_fr, prev_analysis, pluprev_fr, fr)
+ else:
+ # This is the first file processed
+ self.first_fr = fr
+ pluprev_fr, prev_fr, prev_analysis = prev_fr, fr, analysis
+
+ # One more iteration for the final file.
+ self.html_file(prev_fr, prev_analysis, pluprev_fr, None)
+ # This is the last file processed
+ self.final_fr = prev_fr
if not self.all_files_nums:
raise NoDataError("No data to report.")
@@ -236,10 +256,22 @@ def make_local_static_report_files(self):
if self.extra_css:
shutil.copyfile(self.config.extra_css, os.path.join(self.directory, self.extra_css))
- def html_file(self, fr, analysis):
- """Generate an HTML file for one source file."""
+ def html_file(self, fr, analysis, prev_fr=None, next_fr=None):
+ """Generate an HTML file for one source file, with links to the
+ previous and the next file, or to the index."""
rootname = flat_rootname(fr.relative_filename())
html_filename = rootname + ".html"
+ if prev_fr is not None:
+ prev_html = flat_rootname(prev_fr.relative_filename()) + ".html"
+ else:
+ prev_html = "index.html"
+
+ if next_fr is not None:
+ next_html = flat_rootname(next_fr.relative_filename()) + ".html"
+ else:
+ next_html = "index.html"
+
+ print(prev_html, html_filename, next_html)
ensure_dir(self.directory)
if not os.listdir(self.directory):
self.directory_was_empty = True
@@ -316,7 +348,11 @@ def html_file(self, fr, analysis):
css_classes.append(self.template_globals['category'][ldata.category])
ldata.css_class = ' '.join(css_classes) or "pln"
- html = self.source_tmpl.render(file_data.__dict__)
+ html = self.source_tmpl.render({
+ **file_data.__dict__,
+ 'prev_html': prev_html,
+ 'next_html': next_html,
+ })
write_html(html_path, html)
# Save this file's information for the index file.
@@ -340,11 +376,23 @@ def index_file(self):
n = self.skipped_empty_count
skipped_empty_msg = f"{n} empty file{plural(n)} skipped."
+ if self.first_fr is not None:
+ first_html = flat_rootname(self.first_fr.relative_filename()) + ".html"
+ else:
+ first_html = "index.html"
+
+ if self.final_fr is not None:
+ final_html = flat_rootname(self.final_fr.relative_filename()) + ".html"
+ else:
+ final_html = "index.html"
+
html = index_tmpl.render({
'files': self.file_summaries,
'totals': self.totals,
'skipped_covered_msg': skipped_covered_msg,
'skipped_empty_msg': skipped_empty_msg,
+ 'first_html': first_html,
+ 'final_html': final_html,
})
index_file = os.path.join(self.directory, "index.html")
diff --git a/coverage/htmlfiles/coverage_html.js b/coverage/htmlfiles/coverage_html.js
index 00e18488d..1faf24f72 100644
--- a/coverage/htmlfiles/coverage_html.js
+++ b/coverage/htmlfiles/coverage_html.js
@@ -25,6 +25,13 @@ function checkVisible(element) {
return !(rect.bottom < viewTop || rect.top >= viewBottom);
}
+function on_click(sel, fn) {
+ const elt = document.querySelector(sel);
+ if (elt) {
+ elt.addEventListener("click", fn);
+ }
+}
+
// Helpers for table sorting
function getCellValue(row, column = 0) {
const cell = row.cells[column]
@@ -193,6 +200,9 @@ coverage.index_ready = function () {
direction: th.getAttribute("aria-sort"),
}));
});
+
+ on_click(".button_prev_file", coverage.to_prev_file);
+ on_click(".button_next_file", coverage.to_next_file);
};
// -- pyfile stuff --
@@ -209,12 +219,6 @@ coverage.pyfile_ready = function () {
coverage.set_sel(0);
}
- const on_click = function(sel, fn) {
- const elt = document.querySelector(sel);
- if (elt) {
- elt.addEventListener("click", fn);
- }
- }
on_click(".button_toggle_run", coverage.toggle_lines);
on_click(".button_toggle_mis", coverage.toggle_lines);
on_click(".button_toggle_exc", coverage.toggle_lines);
@@ -225,6 +229,9 @@ coverage.pyfile_ready = function () {
on_click(".button_top_of_page", coverage.to_top);
on_click(".button_first_chunk", coverage.to_first_chunk);
+ on_click(".button_prev_file", coverage.to_prev_file);
+ on_click(".button_next_file", coverage.to_next_file);
+
coverage.filters = undefined;
try {
coverage.filters = localStorage.getItem(coverage.LINE_FILTERS_STORAGE);
@@ -299,6 +306,14 @@ coverage.to_first_chunk = function () {
coverage.to_next_chunk();
};
+coverage.to_prev_file = function () {
+ window.location = document.getElementById("prevFileLink").href;
+}
+
+coverage.to_next_file = function () {
+ window.location = document.getElementById("nextFileLink").href;
+}
+
// Return a string indicating what kind of chunk this line belongs to,
// or null if not a chunk.
coverage.chunk_indicator = function (line_elt) {
diff --git a/coverage/htmlfiles/index.html b/coverage/htmlfiles/index.html
index e1d3e9b59..b740c0720 100644
--- a/coverage/htmlfiles/index.html
+++ b/coverage/htmlfiles/index.html
@@ -40,6 +40,11 @@ {{ title|escape }}:
{% endif %}
c change column sorting
+
+ [
+ ]
+ prev/next file
+
@@ -115,6 +120,14 @@ {{ title|escape }}:
created at {{ time_stamp }}
+