diff --git a/coverage/htmlfiles/pyfile.html b/coverage/htmlfiles/pyfile.html
index bc86624ad..692709a31 100644
--- a/coverage/htmlfiles/pyfile.html
+++ b/coverage/htmlfiles/pyfile.html
@@ -30,7 +30,33 @@ Coverage for {{relative_filename|escape}} :
{{nums.pc_covered_str}}%
-
+
+
+
+
+
Shortcuts on this page
+
+
+ r
+ m
+ x
+ p toggle line displays
+
+
+ j
+ k next/prev highlighted chunk
+
+
+ 0 (zero) top of page
+
+
+ 1 (one) first highlighted chunk
+
+
+
+
{{nums.n_statements}} statements
@@ -45,29 +71,6 @@
-
-
-
Hot-keys on this page
-
-
- r
- m
- x
- p toggle line displays
-
-
- j
- k next/prev highlighted chunk
-
-
- 0 (zero) top of page
-
-
- 1 (one) first highlighted chunk
-
-
-
-
{% for line in lines -%}
{% joined %}
diff --git a/coverage/htmlfiles/style.css b/coverage/htmlfiles/style.css
index 86ce5b3f1..70fd68893 100644
--- a/coverage/htmlfiles/style.css
+++ b/coverage/htmlfiles/style.css
@@ -102,21 +102,25 @@ h2.stats { margin-top: .5em; font-size: 1em; }
@media (prefers-color-scheme: dark) { .stats button.par.show_par { background: #650; } }
-.help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; color: #333; padding: .25em .5em; }
+#help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; color: #333; padding: .25em .5em; }
#source p .annotate.long { white-space: normal; float: right; top: 1.75em; right: 1em; height: auto; }
-#keyboard_icon { float: right; margin: 5px; cursor: pointer; }
+#help_panel_wrapper { float: right; position: relative; }
-.help_panel { padding: .75em; border: 1px solid #883; }
+#keyboard_icon { margin: 5px; }
-.help_panel .legend { font-style: italic; margin-bottom: 1em; }
+#help_panel_state { display: none; }
-.indexfile .help_panel { width: 25em; }
+#help_panel { top: 25px; right: 0; padding: .75em; border: 1px solid #883; }
-.pyfile .help_panel { width: 18em; }
+#help_panel .legend { font-style: italic; margin-bottom: 1em; }
-#panel_icon { float: right; cursor: pointer; }
+.indexfile #help_panel { width: 25em; }
+
+.pyfile #help_panel { width: 18em; }
+
+#help_panel_state:checked ~ #help_panel { display: block; }
.keyhelp { margin-top: .75em; }
diff --git a/coverage/htmlfiles/style.scss b/coverage/htmlfiles/style.scss
index 5133f019e..5ec1c8464 100644
--- a/coverage/htmlfiles/style.scss
+++ b/coverage/htmlfiles/style.scss
@@ -297,14 +297,23 @@ h2.stats {
}
// Help panel
-#keyboard_icon {
+#help_panel_wrapper {
float: right;
+ position: relative;
+}
+
+#keyboard_icon {
margin: 5px;
- cursor: pointer;
}
-.help_panel {
+#help_panel_state {
+ display: none;
+}
+
+#help_panel {
@extend %popup;
+ top: 25px;
+ right: 0;
padding: .75em;
border: 1px solid #883;
@@ -322,11 +331,10 @@ h2.stats {
width: 18em;
//min-height: 8em;
}
-}
-#panel_icon {
- float: right;
- cursor: pointer;
+ #help_panel_state:checked ~ & {
+ display: block;
+ }
}
.keyhelp {
diff --git a/tests/gold/html/styled/style.css b/tests/gold/html/styled/style.css
index 86ce5b3f1..70fd68893 100644
--- a/tests/gold/html/styled/style.css
+++ b/tests/gold/html/styled/style.css
@@ -102,21 +102,25 @@ h2.stats { margin-top: .5em; font-size: 1em; }
@media (prefers-color-scheme: dark) { .stats button.par.show_par { background: #650; } }
-.help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; color: #333; padding: .25em .5em; }
+#help_panel, #source p .annotate.long { display: none; position: absolute; z-index: 999; background: #ffffcc; border: 1px solid #888; border-radius: .2em; color: #333; padding: .25em .5em; }
#source p .annotate.long { white-space: normal; float: right; top: 1.75em; right: 1em; height: auto; }
-#keyboard_icon { float: right; margin: 5px; cursor: pointer; }
+#help_panel_wrapper { float: right; position: relative; }
-.help_panel { padding: .75em; border: 1px solid #883; }
+#keyboard_icon { margin: 5px; }
-.help_panel .legend { font-style: italic; margin-bottom: 1em; }
+#help_panel_state { display: none; }
-.indexfile .help_panel { width: 25em; }
+#help_panel { top: 25px; right: 0; padding: .75em; border: 1px solid #883; }
-.pyfile .help_panel { width: 18em; }
+#help_panel .legend { font-style: italic; margin-bottom: 1em; }
-#panel_icon { float: right; cursor: pointer; }
+.indexfile #help_panel { width: 25em; }
+
+.pyfile #help_panel { width: 18em; }
+
+#help_panel_state:checked ~ #help_panel { display: block; }
.keyhelp { margin-top: .75em; }
From 7b6443ec5bb44f2946c6f3c221884e33413fdb05 Mon Sep 17 00:00:00 2001
From: Septatrix <24257556+Septatrix@users.noreply.github.com>
Date: Sat, 27 Mar 2021 22:11:43 +0100
Subject: [PATCH 4/8] Update filter
---
coverage/htmlfiles/coverage_html.js | 209 +++++++++++++++-------------
coverage/htmlfiles/index.html | 27 ++--
2 files changed, 125 insertions(+), 111 deletions(-)
diff --git a/coverage/htmlfiles/coverage_html.js b/coverage/htmlfiles/coverage_html.js
index 09c0fe530..27af5db5b 100644
--- a/coverage/htmlfiles/coverage_html.js
+++ b/coverage/htmlfiles/coverage_html.js
@@ -7,10 +7,23 @@
coverage = {};
+function debounce(callback, wait) {
+ let timeoutId = null;
+ return function(...args) {
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(() => {
+ callback.apply(this, args);
+ }, wait);
+ };
+};
+
// Find all the elements with data-shortcut attribute, and use them to assign a shortcut key.
coverage.assign_shortkeys = function () {
document.querySelectorAll("[data-shortcut]").forEach(element => {
document.addEventListener("keypress", event => {
+ if (event.target.tagName.toLowerCase() === "input") {
+ return; // ignore keypress from search filter
+ }
if (event.key === element.dataset.shortcut) {
element.click();
}
@@ -21,130 +34,132 @@ coverage.assign_shortkeys = function () {
// Create the events for the filter box.
coverage.wire_up_filter = function () {
// Cache elements.
- var table = $("table.index");
- var table_rows = table.find("tbody tr");
- var table_row_names = table_rows.find("td.name a");
- var no_rows = $("#no_rows");
+ const table = document.querySelector("table.index");
+ const table_rows = table.querySelectorAll("tbody tr");
+ const table_row_names = table.querySelectorAll("tbody tr td.name a");
+ const no_rows = document.getElementById("no_rows");
// Create a duplicate table footer that we can modify with dynamic summed values.
- var table_footer = $("table.index tfoot tr");
- var table_dynamic_footer = table_footer.clone();
- table_dynamic_footer.attr('class', 'total_dynamic hidden');
- table_footer.after(table_dynamic_footer);
+ const table_footer = document.querySelector("table.index tfoot tr");
+ const table_dynamic_footer = table_footer.cloneNode(true);
+ table_dynamic_footer.className = "total_dynamic hidden";
+ table_footer.insertAdjacentElement('afterend', table_dynamic_footer);
// Observe filter keyevents.
- $("#filter").on("keyup change", $.debounce(150, function (event) {
- var filter_value = $(this).val();
+ document.getElementById("filter").addEventListener("input", debounce(event => {
+ const filter_value = event.target.value;
if (filter_value === "") {
// Filter box is empty, remove all filtering.
- table_rows.removeClass("hidden");
+ table_rows.forEach(element => element.classList.remove("hidden"));
// Show standard footer, hide dynamic footer.
- table_footer.removeClass("hidden");
- table_dynamic_footer.addClass("hidden");
+ table_footer.classList.remove("hidden");
+ table_dynamic_footer.classList.add("hidden");
// Hide placeholder, show table.
if (no_rows.length > 0) {
- no_rows.hide();
+ no_rows.style.display = "none";
}
- table.show();
-
+ table.style.display = null;
+ return;
}
- else {
- // Filter table items by value.
- var hidden = 0;
- var shown = 0;
-
- // Hide / show elements.
- $.each(table_row_names, function () {
- var element = $(this).parents("tr");
-
- if ($(this).text().indexOf(filter_value) === -1) {
- // hide
- element.addClass("hidden");
- hidden++;
- }
- else {
- // show
- element.removeClass("hidden");
- shown++;
- }
- });
- // Show placeholder if no rows will be displayed.
- if (no_rows.length > 0) {
- if (shown === 0) {
- // Show placeholder, hide table.
- no_rows.show();
- table.hide();
- }
- else {
- // Hide placeholder, show table.
- no_rows.hide();
- table.show();
- }
+ // Filter table items by value.
+ let hidden = 0;
+ let shown = 0;
+
+ // Hide / show elements.
+ table_row_names.forEach(element => {
+ const row = element.closest("tr");
+
+ if (!element.textContent.includes(filter_value)) {
+ // hide
+ row.classList.add("hidden");
+ hidden++;
+ } else {
+ // show
+ row.classList.remove("hidden");
+ shown++;
+ }
+ });
+
+ // Show placeholder if no rows will be displayed.
+ if (no_rows.length > 0) {
+ if (shown === 0) {
+ // Show placeholder, hide table.
+ no_rows.style.display = "block";
+ table.style.display = "none";
+ }
+ else {
+ // Hide placeholder, show table.
+ no_rows.style.display = "none";
+ table.style.display = null;
}
+ }
- // Manage dynamic header:
- if (hidden > 0) {
- // Calculate new dynamic sum values based on visible rows.
- for (var column = 2; column < 20; column++) {
- // Calculate summed value.
- var cells = table_rows.find('td:nth-child(' + column + ')');
- if (!cells.length) {
- // No more columns...!
- break;
+ // Manage dynamic header:
+ if (hidden > 0) {
+ // Calculate new dynamic sum values based on visible rows.
+ for (let column = 1; column < table.tHead.rows[0].cells.length; column++) {
+ // Calculate summed value.
+ const cells = table.querySelectorAll(`tbody td:nth-child(${column + 1})`);
+
+ let sum = 0, numer = 0, denom = 0;
+ cells.forEach(element => {
+ if (!(
+ element.offsetWidth
+ || element.offsetHeight
+ || element.getClientRects().length
+ )) {
+ // element is not visible i.e. filtered out
+ return;
}
-
- var sum = 0, numer = 0, denom = 0;
- $.each(cells.filter(':visible'), function () {
- var ratio = $(this).data("ratio");
- if (ratio) {
- var splitted = ratio.split(" ");
- numer += parseInt(splitted[0], 10);
- denom += parseInt(splitted[1], 10);
- }
- else {
- sum += parseInt(this.innerHTML, 10);
- }
- });
-
- // Get footer cell element.
- var footer_cell = table_dynamic_footer.find('td:nth-child(' + column + ')');
-
- // Set value into dynamic footer cell element.
- if (cells[0].innerHTML.indexOf('%') > -1) {
- // Percentage columns use the numerator and denominator,
- // and adapt to the number of decimal places.
- var match = /\.([0-9]+)/.exec(cells[0].innerHTML);
- var places = 0;
- if (match) {
- places = match[1].length;
- }
- var pct = numer * 100 / denom;
- footer_cell.text(pct.toFixed(places) + '%');
+ if ("ratio" in element.dataset) {
+ let splitted = element.dataset.ratio.split(" ");
+ numer += parseInt(splitted[0], 10);
+ denom += parseInt(splitted[1], 10);
+ } else {
+ sum += parseInt(element.textContent, 10);
}
- else {
- footer_cell.text(sum);
+ });
+
+
+ // Get footer cell element.
+ let footer_cell = table_dynamic_footer.querySelector(`td:nth-child(${column + 1})`);
+ console.log(sum, numer, denom, footer_cell)
+
+ // Set value into dynamic footer cell element.
+ if (cells[0].textContent.includes('%')) {
+ // Percentage columns use the numerator and denominator,
+ // and adapt to the number of decimal places.
+ var match = /\.([0-9]+)/.exec(cells[0].textContent);
+ var places = 0;
+ if (match) {
+ places = match[1].length;
}
+ var pct = numer * 100 / denom;
+ footer_cell.textContent = `${pct.toFixed(places)}%`;
+ }
+ else {
+ footer_cell.textContent = sum;
}
-
- // Hide standard footer, show dynamic footer.
- table_footer.addClass("hidden");
- table_dynamic_footer.removeClass("hidden");
- }
- else {
- // Show standard footer, hide dynamic footer.
- table_footer.removeClass("hidden");
- table_dynamic_footer.addClass("hidden");
}
+
+ // Hide standard footer, show dynamic footer.
+ table_footer.classList.add("hidden");
+ table_dynamic_footer.classList.remove("hidden");
+ }
+ else {
+ // Show standard footer, hide dynamic footer.
+ table_footer.classList.remove("hidden");
+ table_dynamic_footer.classList.add("hidden");
}
}));
// Trigger change event on setup, to force filter on page refresh
// (filter value may still be present).
- $("#filter").trigger("change");
+ document.getElementById("filter").dispatchEvent(new Event("change"));
};
// Loaded on index.html
diff --git a/coverage/htmlfiles/index.html b/coverage/htmlfiles/index.html
index fbe85bbc2..627d1d79d 100644
--- a/coverage/htmlfiles/index.html
+++ b/coverage/htmlfiles/index.html
@@ -73,20 +73,6 @@
{{ title|escape }}:
coverage |
- {# HTML syntax requires thead, tfoot, tbody #}
-
-
- Total |
- {{totals.n_statements}} |
- {{totals.n_missing}} |
- {{totals.n_excluded}} |
- {% if has_arcs %}
- {{totals.n_branches}} |
- {{totals.n_partial_branches}} |
- {% endif %}
- {{totals.pc_covered_str}}% |
-
-
{% for file in files %}
@@ -102,6 +88,19 @@ {{ title|escape }}:
{% endfor %}
+
+
+ Total |
+ {{totals.n_statements}} |
+ {{totals.n_missing}} |
+ {{totals.n_excluded}} |
+ {% if has_arcs %}
+ {{totals.n_branches}} |
+ {{totals.n_partial_branches}} |
+ {% endif %}
+ {{totals.pc_covered_str}}% |
+
+
From 950fd5ac409a4d73d7656ae3d8e6b64fe3f29996 Mon Sep 17 00:00:00 2001
From: Septatrix <24257556+Septatrix@users.noreply.github.com>
Date: Sat, 27 Mar 2021 22:13:40 +0100
Subject: [PATCH 5/8] Iterate totals over rows rather than columns
---
coverage/htmlfiles/coverage_html.js | 159 ++++++++++------------------
1 file changed, 55 insertions(+), 104 deletions(-)
diff --git a/coverage/htmlfiles/coverage_html.js b/coverage/htmlfiles/coverage_html.js
index 27af5db5b..66c7020ec 100644
--- a/coverage/htmlfiles/coverage_html.js
+++ b/coverage/htmlfiles/coverage_html.js
@@ -35,125 +35,76 @@ coverage.assign_shortkeys = function () {
coverage.wire_up_filter = function () {
// Cache elements.
const table = document.querySelector("table.index");
- const table_rows = table.querySelectorAll("tbody tr");
- const table_row_names = table.querySelectorAll("tbody tr td.name a");
+ const table_body_rows = table.querySelectorAll("tbody tr");
const no_rows = document.getElementById("no_rows");
- // Create a duplicate table footer that we can modify with dynamic summed values.
- const table_footer = document.querySelector("table.index tfoot tr");
- const table_dynamic_footer = table_footer.cloneNode(true);
- table_dynamic_footer.className = "total_dynamic hidden";
- table_footer.insertAdjacentElement('afterend', table_dynamic_footer);
-
// Observe filter keyevents.
document.getElementById("filter").addEventListener("input", debounce(event => {
- const filter_value = event.target.value;
-
- if (filter_value === "") {
- // Filter box is empty, remove all filtering.
- table_rows.forEach(element => element.classList.remove("hidden"));
-
- // Show standard footer, hide dynamic footer.
- table_footer.classList.remove("hidden");
- table_dynamic_footer.classList.add("hidden");
-
- // Hide placeholder, show table.
- if (no_rows.length > 0) {
- no_rows.style.display = "none";
- }
- table.style.display = null;
- return;
- }
-
- // Filter table items by value.
- let hidden = 0;
- let shown = 0;
+ // Keep running total of each metric, first index contains number of shown rows
+ const totals = new Array(table.rows[0].cells.length).fill(0);
+ // Accumulate the percentage as fraction
+ totals[totals.length - 1] = { "numer": 0, "denom": 0 };
// Hide / show elements.
- table_row_names.forEach(element => {
- const row = element.closest("tr");
-
- if (!element.textContent.includes(filter_value)) {
+ table_body_rows.forEach(row => {
+ if (!row.cells[0].textContent.includes(event.target.value)) {
// hide
row.classList.add("hidden");
- hidden++;
- } else {
- // show
- row.classList.remove("hidden");
- shown++;
+ return;
+ }
+
+ // show
+ row.classList.remove("hidden");
+ totals[0]++;
+
+ for (let column = 1; column < totals.length; column++) {
+ // Accumulate dynamic totals
+ cell = row.cells[column]
+ if (column === totals.length - 1) {
+ // Last column contains percentage
+ const [numer, denom] = cell.dataset.ratio.split(" ");
+ totals[column]["numer"] += parseInt(numer, 10);
+ totals[column]["denom"] += parseInt(denom, 10);
+ } else {
+ totals[column] += parseInt(cell.textContent, 10);
+ }
}
});
+ console.log(totals)
+
// Show placeholder if no rows will be displayed.
- if (no_rows.length > 0) {
- if (shown === 0) {
- // Show placeholder, hide table.
- no_rows.style.display = "block";
- table.style.display = "none";
- }
- else {
- // Hide placeholder, show table.
- no_rows.style.display = "none";
- table.style.display = null;
- }
+ if (!totals[0]) {
+ // Show placeholder, hide table.
+ no_rows.style.display = "block";
+ table.style.display = "none";
+ return;
}
- // Manage dynamic header:
- if (hidden > 0) {
- // Calculate new dynamic sum values based on visible rows.
- for (let column = 1; column < table.tHead.rows[0].cells.length; column++) {
- // Calculate summed value.
- const cells = table.querySelectorAll(`tbody td:nth-child(${column + 1})`);
-
- let sum = 0, numer = 0, denom = 0;
- cells.forEach(element => {
- if (!(
- element.offsetWidth
- || element.offsetHeight
- || element.getClientRects().length
- )) {
- // element is not visible i.e. filtered out
- return;
- }
- if ("ratio" in element.dataset) {
- let splitted = element.dataset.ratio.split(" ");
- numer += parseInt(splitted[0], 10);
- denom += parseInt(splitted[1], 10);
- } else {
- sum += parseInt(element.textContent, 10);
- }
- });
-
-
- // Get footer cell element.
- let footer_cell = table_dynamic_footer.querySelector(`td:nth-child(${column + 1})`);
- console.log(sum, numer, denom, footer_cell)
-
- // Set value into dynamic footer cell element.
- if (cells[0].textContent.includes('%')) {
- // Percentage columns use the numerator and denominator,
- // and adapt to the number of decimal places.
- var match = /\.([0-9]+)/.exec(cells[0].textContent);
- var places = 0;
- if (match) {
- places = match[1].length;
- }
- var pct = numer * 100 / denom;
- footer_cell.textContent = `${pct.toFixed(places)}%`;
- }
- else {
- footer_cell.textContent = sum;
- }
+ // Hide placeholder, show table.
+ no_rows.style.display = null;
+ table.style.display = null;
+
+ // Calculate new dynamic sum values based on visible rows.
+ for (let column = 1; column < totals.length; column++) {
+ // Get footer cell element.
+ const cell = table.tFoot.rows[0].cells[column];
+
+ // Set value into dynamic footer cell element.
+ if (column === totals.length - 1) {
+ // Percentage column uses the numerator and denominator,
+ // and adapts to the number of decimal places.
+ const match = /\.([0-9]+)/.exec(cell.textContent);
+ const places = match ? match[1].length : 0;
+ const { numer, denom } = totals[column];
+ cell.dataset.ratio = `${numer} ${denom}`;
+ // Check denom to prevent NaN if filtered files contain no statements
+ cell.textContent = denom
+ ? `${(numer * 100 / denom).toFixed(places)}%`
+ : `${(100).toFixed(places)}%`;
+ } else {
+ cell.textContent = totals[column];
}
-
- // Hide standard footer, show dynamic footer.
- table_footer.classList.add("hidden");
- table_dynamic_footer.classList.remove("hidden");
- }
- else {
- // Show standard footer, hide dynamic footer.
- table_footer.classList.remove("hidden");
- table_dynamic_footer.classList.add("hidden");
}
}));
From c7b2db378aad1f14284d67a1596da136cd834261 Mon Sep 17 00:00:00 2001
From: Septatrix <24257556+Septatrix@users.noreply.github.com>
Date: Sun, 28 Mar 2021 17:27:36 +0200
Subject: [PATCH 6/8] Update scroll marker building
---
coverage/htmlfiles/coverage_html.js | 81 +++++++++++------------------
1 file changed, 31 insertions(+), 50 deletions(-)
diff --git a/coverage/htmlfiles/coverage_html.js b/coverage/htmlfiles/coverage_html.js
index 66c7020ec..1ce060a0b 100644
--- a/coverage/htmlfiles/coverage_html.js
+++ b/coverage/htmlfiles/coverage_html.js
@@ -17,6 +17,10 @@ function debounce(callback, wait) {
};
};
+function clamp(n, min, max) {
+ return Math.min(Math.max(min, n), max);
+}
+
// Find all the elements with data-shortcut attribute, and use them to assign a shortcut key.
coverage.assign_shortkeys = function () {
document.querySelectorAll("[data-shortcut]").forEach(element => {
@@ -482,73 +486,50 @@ coverage.finish_scrolling = function () {
coverage.init_scroll_markers = function () {
var c = coverage;
// Init some variables
- c.lines_len = $('#source p').length;
- c.body_h = $('body').height();
- c.header_h = $('div#header').height();
+ c.lines_len = document.querySelectorAll('#source p').length;
// Build html
c.build_scroll_markers();
};
coverage.build_scroll_markers = function () {
- var c = coverage,
- min_line_height = 3,
- max_line_height = 10,
- visible_window_h = $(window).height();
-
- c.lines_to_mark = $('#source').find('p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par');
- $('#scroll_marker').remove();
+ const temp_scroll_marker = document.getElementById('scroll_marker')
+ if (temp_scroll_marker) temp_scroll_marker.remove();
// Don't build markers if the window has no scroll bar.
- if (c.body_h <= visible_window_h) {
+ if (document.body.scrollHeight <= window.innerHeight) {
return;
}
- $("body").append("
");
- var scroll_marker = $('#scroll_marker'),
- marker_scale = scroll_marker.height() / c.body_h,
- line_height = scroll_marker.height() / c.lines_len;
-
- // Line height must be between the extremes.
- if (line_height > min_line_height) {
- if (line_height > max_line_height) {
- line_height = max_line_height;
- }
- }
- else {
- line_height = min_line_height;
- }
-
- var previous_line = -99,
- last_mark,
- last_top,
- offsets = {};
-
- // Calculate line offsets outside loop to prevent relayouts
- c.lines_to_mark.each(function() {
- offsets[this.id] = $(this).offset().top;
- });
- c.lines_to_mark.each(function () {
- var id_name = $(this).attr('id'),
- line_top = Math.round(offsets[id_name] * marker_scale),
- line_number = parseInt(id_name.substring(1, id_name.length));
+ const marker_scale = window.innerHeight / document.body.scrollHeight;
+ const line_height = clamp(window.innerHeight / coverage.lines_len, 3, 10);
+
+ let previous_line = -99, last_mark, last_top;
+
+ const scroll_marker = document.createElement("div");
+ scroll_marker.id = "scroll_marker";
+ document.getElementById('source').querySelectorAll(
+ 'p.show_run, p.show_mis, p.show_exc, p.show_exc, p.show_par'
+ ).forEach(element => {
+ const line_top = Math.floor(element.offsetTop * marker_scale);
+ const line_number = parseInt(element.id.substr(1));
if (line_number === previous_line + 1) {
// If this solid missed block just make previous mark higher.
- last_mark.css({
- 'height': line_top + line_height - last_top
- });
- }
- else {
+ last_mark.style.height = `${line_top + line_height - last_top}px`;
+ } else {
// Add colored line in scroll_marker block.
- scroll_marker.append('
');
- last_mark = $('#m' + line_number);
- last_mark.css({
- 'height': line_height,
- 'top': line_top
- });
+ last_mark = document.createElement("div");
+ last_mark.id = `m${line_number}`;
+ last_mark.classList.add("marker");
+ last_mark.style.height = `${line_height}px`;
+ last_mark.style.top = `${line_top}px`;
+ scroll_marker.append(last_mark);
last_top = line_top;
}
previous_line = line_number;
});
+
+ // Append last to prevent layout calculation
+ document.body.append(scroll_marker);
};
From d9e392be36c0adb94a9a2dabd51954c00dd695a7 Mon Sep 17 00:00:00 2001
From: Septatrix <24257556+Septatrix@users.noreply.github.com>
Date: Sun, 17 Oct 2021 20:20:33 +0200
Subject: [PATCH 7/8] Remove last usages of jQuery
---
coverage/html.py | 5 -
coverage/htmlfiles/coverage_html.js | 296 +++++++++---------
coverage/htmlfiles/index.html | 25 +-
.../jquery.ba-throttle-debounce.min.js | 9 -
coverage/htmlfiles/jquery.hotkeys.js | 99 ------
coverage/htmlfiles/jquery.isonscreen.js | 53 ----
coverage/htmlfiles/jquery.min.js | 2 -
coverage/htmlfiles/jquery.tablesorter.min.js | 2 -
coverage/htmlfiles/pyfile.html | 25 +-
coverage/htmlfiles/style.css | 8 +-
coverage/htmlfiles/style.scss | 11 +-
11 files changed, 181 insertions(+), 354 deletions(-)
delete mode 100644 coverage/htmlfiles/jquery.ba-throttle-debounce.min.js
delete mode 100644 coverage/htmlfiles/jquery.hotkeys.js
delete mode 100644 coverage/htmlfiles/jquery.isonscreen.js
delete mode 100644 coverage/htmlfiles/jquery.min.js
delete mode 100644 coverage/htmlfiles/jquery.tablesorter.min.js
diff --git a/coverage/html.py b/coverage/html.py
index 1fbac4b36..e56e30797 100644
--- a/coverage/html.py
+++ b/coverage/html.py
@@ -159,11 +159,6 @@ class HtmlReporter:
# directory.
STATIC_FILES = [
("style.css", ""),
- ("jquery.min.js", "jquery"),
- ("jquery.ba-throttle-debounce.min.js", "jquery-throttle-debounce"),
- ("jquery.hotkeys.js", "jquery-hotkeys"),
- ("jquery.isonscreen.js", "jquery-isonscreen"),
- ("jquery.tablesorter.min.js", "jquery-tablesorter"),
("coverage_html.js", ""),
("keybd_closed.png", ""),
("keybd_open.png", ""),
diff --git a/coverage/htmlfiles/coverage_html.js b/coverage/htmlfiles/coverage_html.js
index 1ce060a0b..1e66dd861 100644
--- a/coverage/htmlfiles/coverage_html.js
+++ b/coverage/htmlfiles/coverage_html.js
@@ -7,6 +7,7 @@
coverage = {};
+// General helpers
function debounce(callback, wait) {
let timeoutId = null;
return function(...args) {
@@ -17,8 +18,52 @@ function debounce(callback, wait) {
};
};
-function clamp(n, min, max) {
- return Math.min(Math.max(min, n), max);
+function checkVisible(element) {
+ var rect = element.getBoundingClientRect();
+ var viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
+ return !(rect.bottom < 0 || rect.top - viewHeight >= 0);
+}
+
+// Helpers for table sorting
+function getCellValue(row, column = 0) {
+ const cell = row.cells[column]
+ if (cell.childElementCount == 1) {
+ const child = cell.firstElementChild
+ if (child instanceof HTMLTimeElement && child.dateTime) {
+ return child.dateTime
+ } else if (child instanceof HTMLDataElement && child.value) {
+ return child.value
+ }
+ }
+ return cell.innerText || cell.textContent;
+}
+
+function rowComparator(rowA, rowB, column = 0) {
+ let valueA = getCellValue(rowA, column);
+ let valueB = getCellValue(rowB, column);
+ if (!isNaN(valueA) && !isNaN(valueB)) {
+ return valueA - valueB
+ }
+ return valueA.localeCompare(valueB, undefined, {numeric: true});
+}
+
+function sortColumn(th) {
+ // Get the current sorting direction of the selected header,
+ // clear state on other headers and then set the new sorting direction
+ const currentSortOrder = th.ariaSort;
+ [...th.parentElement.cells].forEach(header => header.ariaSort = "none");
+ if (currentSortOrder === "none") {
+ th.ariaSort = th.dataset.defaultSortOrder || "ascending"
+ } else {
+ th.ariaSort = currentSortOrder === "ascending" ? "descending" : "ascending";
+ }
+
+ const column = [...th.parentElement.cells].indexOf(th)
+
+ // Sort all rows and afterwards append them in order to move them in the DOM
+ Array.from(th.closest("table").querySelectorAll("tbody tr"))
+ .sort((rowA, rowB) => rowComparator(rowA, rowB, column) * (th.ariaSort === "ascending" ? 1 : -1))
+ .forEach(tr => tr.parentElement.appendChild(tr) );
}
// Find all the elements with data-shortcut attribute, and use them to assign a shortcut key.
@@ -89,10 +134,11 @@ coverage.wire_up_filter = function () {
no_rows.style.display = null;
table.style.display = null;
+ const footer = table.tFoot.rows[0];
// Calculate new dynamic sum values based on visible rows.
for (let column = 1; column < totals.length; column++) {
// Get footer cell element.
- const cell = table.tFoot.rows[0].cells[column];
+ const cell = footer.cells[column];
// Set value into dynamic footer cell element.
if (column === totals.length - 1) {
@@ -117,65 +163,27 @@ coverage.wire_up_filter = function () {
document.getElementById("filter").dispatchEvent(new Event("change"));
};
+coverage.INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT";
+
// Loaded on index.html
-coverage.index_ready = function ($) {
+coverage.index_ready = function () {
+ coverage.assign_shortkeys();
+ coverage.wire_up_filter();
+ document.querySelectorAll('[data-sortable] th[aria-sort]').forEach(
+ th => th.addEventListener('click', e => sortColumn(e.target))
+ );
+
// Look for a localStorage item containing previous sort settings:
- var sort_list = [];
- var storage_name = "COVERAGE_INDEX_SORT";
- var stored_list = undefined;
- try {
- stored_list = localStorage.getItem(storage_name);
- } catch(err) {}
+ const stored_list = localStorage.getItem(coverage.INDEX_SORT_STORAGE);
if (stored_list) {
- sort_list = JSON.parse('[[' + stored_list + ']]');
- }
-
- // Create a new widget which exists only to save and restore
- // the sort order:
- $.tablesorter.addWidget({
- id: "persistentSort",
-
- // Format is called by the widget before displaying:
- format: function (table) {
- if (table.config.sortList.length === 0 && sort_list.length > 0) {
- // This table hasn't been sorted before - we'll use
- // our stored settings:
- $(table).trigger('sorton', [sort_list]);
- }
- else {
- // This is not the first load - something has
- // already defined sorting so we'll just update
- // our stored value to match:
- sort_list = table.config.sortList;
- }
- }
- });
-
- // Configure our tablesorter to handle the variable number of
- // columns produced depending on report options:
- var headers = [];
- var col_count = $("table.index > thead > tr > th").length;
-
- headers[0] = { sorter: 'text' };
- for (i = 1; i < col_count-1; i++) {
- headers[i] = { sorter: 'digit' };
+ sort_list = JSON.parse(stored_list);
}
- headers[col_count-1] = { sorter: 'percent' };
-
- // Enable the table sorter:
- $("table.index").tablesorter({
- widgets: ['persistentSort'],
- headers: headers
- });
-
- coverage.assign_shortkeys();
- coverage.wire_up_filter();
// Watch for page unload events so we can save the final sort settings:
- $(window).on("unload", function () {
+ window.addEventListener("unload", function () {
try {
- localStorage.setItem(storage_name, sort_list.toString())
+ localStorage.setItem(storage_name, JSON.stringify(sort_list))
} catch(err) {}
});
};
@@ -184,28 +192,25 @@ coverage.index_ready = function ($) {
coverage.LINE_FILTERS_STORAGE = "COVERAGE_LINE_FILTERS";
-coverage.pyfile_ready = function ($) {
+coverage.pyfile_ready = function () {
// If we're directed to a particular line number, highlight the line.
var frag = location.hash;
if (frag.length > 2 && frag[1] === 't') {
- $(frag).addClass('highlight');
+ document.getElementById(frag.substring(1)).classList.add("highlight");
coverage.set_sel(parseInt(frag.substr(2), 10));
- }
- else {
+ } else {
coverage.set_sel(0);
}
- $(document)
- .bind('keydown', 'j', coverage.to_next_chunk_nicely)
- .bind('keydown', 'k', coverage.to_prev_chunk_nicely)
- .bind('keydown', '0', coverage.to_top)
- .bind('keydown', '1', coverage.to_first_chunk)
- ;
+ document.querySelector(".button_toggle_run").addEventListener("click", coverage.toggle_lines);
+ document.querySelector(".button_toggle_mis").addEventListener("click", coverage.toggle_lines);
+ document.querySelector(".button_toggle_exc").addEventListener("click", coverage.toggle_lines);
+ document.querySelector(".button_toggle_par").addEventListener("click", coverage.toggle_lines);
- $(".button_toggle_run").click(function (evt) {coverage.toggle_lines(evt.target, "run");});
- $(".button_toggle_exc").click(function (evt) {coverage.toggle_lines(evt.target, "exc");});
- $(".button_toggle_mis").click(function (evt) {coverage.toggle_lines(evt.target, "mis");});
- $(".button_toggle_par").click(function (evt) {coverage.toggle_lines(evt.target, "par");});
+ document.querySelector(".button_next_chunk").addEventListener("click", coverage.to_next_chunk_nicely);
+ document.querySelector(".button_prev_chunk").addEventListener("click", coverage.to_prev_chunk_nicely);
+ document.querySelector(".button_top_of_page").addEventListener("click", coverage.to_top);
+ document.querySelector(".button_first_chunk").addEventListener("click", coverage.to_first_chunk);
coverage.filters = undefined;
try {
@@ -227,35 +232,37 @@ coverage.pyfile_ready = function ($) {
coverage.init_scroll_markers();
// Rebuild scroll markers when the window height changes.
- $(window).resize(coverage.build_scroll_markers);
+ window.addEventListener("resize", coverage.build_scroll_markers);
};
-coverage.toggle_lines = function (btn, cls) {
- var onoff = !$(btn).hasClass("show_" + cls);
- coverage.set_line_visibilty(cls, onoff);
+coverage.toggle_lines = function (event) {
+ const btn = event.target;
+ const category = btn.value
+ const show = !btn.classList.contains("show_" + category);
+ coverage.set_line_visibilty(category, show);
coverage.build_scroll_markers();
- coverage.filters[cls] = onoff;
+ coverage.filters[category] = show;
try {
localStorage.setItem(coverage.LINE_FILTERS_STORAGE, JSON.stringify(coverage.filters));
} catch(err) {}
};
-coverage.set_line_visibilty = function (cls, onoff) {
- var show = "show_" + cls;
- var btn = $(".button_toggle_" + cls);
- if (onoff) {
- $("#source ." + cls).addClass(show);
- btn.addClass(show);
+coverage.set_line_visibilty = function (category, should_show) {
+ const cls = "show_" + category;
+ const btn = document.querySelector(".button_toggle_" + category);
+ if (should_show) {
+ document.querySelectorAll("#source ." + category).forEach(e => e.classList.add(cls));
+ btn.classList.add(cls);
}
else {
- $("#source ." + cls).removeClass(show);
- btn.removeClass(show);
+ document.querySelectorAll("#source ." + category).forEach(e => e.classList.remove(cls));
+ btn.classList.remove(cls);
}
};
// Return the nth line div.
coverage.line_elt = function (n) {
- return $("#t" + n);
+ return document.getElementById("t" + n);
};
// Set the selection. b and e are line numbers.
@@ -279,25 +286,26 @@ coverage.to_first_chunk = function () {
// Return a string indicating what kind of chunk this line belongs to,
// or null if not a chunk.
coverage.chunk_indicator = function (line_elt) {
- var klass = line_elt.attr('class');
- if (klass) {
- var m = klass.match(/\bshow_\w+\b/);
- if (m) {
- return m[0];
- }
+ const classes = line_elt.className;
+ if (!classes) {
+ return null;
+ }
+ const match = classes.match(/\bshow_\w+\b/);
+ if (!match) {
+ return null;
}
- return null;
+ return match[0];
};
coverage.to_next_chunk = function () {
- var c = coverage;
+ const c = coverage;
// Find the start of the next colored chunk.
var probe = c.sel_end;
var chunk_indicator, probe_line;
while (true) {
probe_line = c.line_elt(probe);
- if (probe_line.length === 0) {
+ if (!probe_line) {
return;
}
chunk_indicator = c.chunk_indicator(probe_line);
@@ -322,7 +330,7 @@ coverage.to_next_chunk = function () {
};
coverage.to_prev_chunk = function () {
- var c = coverage;
+ const c = coverage;
// Find the end of the prev colored chunk.
var probe = c.sel_begin-1;
@@ -334,7 +342,7 @@ coverage.to_prev_chunk = function () {
while (probe > 0 && !chunk_indicator) {
probe--;
probe_line = c.line_elt(probe);
- if (probe_line.length === 0) {
+ if (!probe_line) {
return;
}
chunk_indicator = c.chunk_indicator(probe_line);
@@ -354,28 +362,6 @@ coverage.to_prev_chunk = function () {
c.show_selection();
};
-// Return the line number of the line nearest pixel position pos
-coverage.line_at_pos = function (pos) {
- var l1 = coverage.line_elt(1),
- l2 = coverage.line_elt(2),
- result;
- if (l1.length && l2.length) {
- var l1_top = l1.offset().top,
- line_height = l2.offset().top - l1_top,
- nlines = (pos - l1_top) / line_height;
- if (nlines < 1) {
- result = 1;
- }
- else {
- result = Math.ceil(nlines);
- }
- }
- else {
- result = 1;
- }
- return result;
-};
-
// Returns 0, 1, or 2: how many of the two ends of the selection are on
// the screen right now?
coverage.selection_ends_on_screen = function () {
@@ -383,31 +369,49 @@ coverage.selection_ends_on_screen = function () {
return 0;
}
- var top = coverage.line_elt(coverage.sel_begin);
- var next = coverage.line_elt(coverage.sel_end-1);
+ const begin = coverage.line_elt(coverage.sel_begin);
+ const end = coverage.line_elt(coverage.sel_end-1);
return (
- (top.isOnScreen() ? 1 : 0) +
- (next.isOnScreen() ? 1 : 0)
+ (checkVisible(begin) ? 1 : 0)
+ + (checkVisible(end) ? 1 : 0)
);
};
coverage.to_next_chunk_nicely = function () {
- coverage.finish_scrolling();
if (coverage.selection_ends_on_screen() === 0) {
- // The selection is entirely off the screen: select the top line on
- // the screen.
- var win = $(window);
- coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop()));
+ // The selection is entirely off the screen:
+ // Set the top line on the screen as selection.
+
+ // This will select the top-left of the viewport
+ // As this is most likely the span with the line number we take the parent
+ const line = document.elementFromPoint(0, 0).parentElement;
+ if (line.parentElement !== document.getElementById("source")) {
+ // The element is not a source line but the header or similar
+ coverage.select_line_or_chunk(1);
+ } else {
+ // We extract the line number from the id
+ coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10));
+ }
}
coverage.to_next_chunk();
};
coverage.to_prev_chunk_nicely = function () {
- coverage.finish_scrolling();
if (coverage.selection_ends_on_screen() === 0) {
- var win = $(window);
- coverage.select_line_or_chunk(coverage.line_at_pos(win.scrollTop() + win.height()));
+ // The selection is entirely off the screen:
+ // Set the lowest line on the screen as selection.
+
+ // This will select the bottom-left of the viewport
+ // As this is most likely the span with the line number we take the parent
+ const line = document.elementFromPoint(document.documentElement.clientHeight-1, 0).parentElement;
+ if (line.parentElement !== document.getElementById("source")) {
+ // The element is not a source line but the header or similar
+ coverage.select_line_or_chunk(coverage.lines_len);
+ } else {
+ // We extract the line number from the id
+ coverage.select_line_or_chunk(parseInt(line.id.substring(1), 10));
+ }
}
coverage.to_prev_chunk();
};
@@ -453,43 +457,33 @@ coverage.select_line_or_chunk = function (lineno) {
};
coverage.show_selection = function () {
- var c = coverage;
-
// Highlight the lines in the chunk
- $("#source .highlight").removeClass("highlight");
- for (var probe = c.sel_begin; probe > 0 && probe < c.sel_end; probe++) {
- c.line_elt(probe).addClass("highlight");
+ document.querySelectorAll("#source .highlight").forEach(e => e.classList.remove("highlight"));
+ for (let probe = coverage.sel_begin; probe < coverage.sel_end; probe++) {
+ coverage.line_elt(probe).classList.add("highlight");
}
- c.scroll_to_selection();
+ coverage.scroll_to_selection();
};
coverage.scroll_to_selection = function () {
// Scroll the page if the chunk isn't fully visible.
if (coverage.selection_ends_on_screen() < 2) {
- // Need to move the page. The html,body trick makes it scroll in all
- // browsers, got it from http://stackoverflow.com/questions/3042651
- var top = coverage.line_elt(coverage.sel_begin);
- var top_pos = parseInt(top.offset().top, 10);
- coverage.scroll_window(top_pos - 30);
+ const element = coverage.line_elt(coverage.sel_begin);
+ coverage.scroll_window(element.offsetTop - 30);
}
};
coverage.scroll_window = function (to_pos) {
- $("html,body").animate({scrollTop: to_pos}, 200);
-};
-
-coverage.finish_scrolling = function () {
- $("html,body").stop(true, true);
+ window.scroll({top: to_pos, behavior: "smooth"});
};
coverage.init_scroll_markers = function () {
- var c = coverage;
// Init some variables
- c.lines_len = document.querySelectorAll('#source p').length;
+ coverage.lines_len = document.querySelectorAll('#source > p').length;
// Build html
- c.build_scroll_markers();
+ coverage.build_scroll_markers();
};
coverage.build_scroll_markers = function () {
@@ -501,7 +495,7 @@ coverage.build_scroll_markers = function () {
}
const marker_scale = window.innerHeight / document.body.scrollHeight;
- const line_height = clamp(window.innerHeight / coverage.lines_len, 3, 10);
+ const line_height = Math.min(Math.max(3, window.innerHeight / coverage.lines_len), 10);
let previous_line = -99, last_mark, last_top;
@@ -533,3 +527,11 @@ coverage.build_scroll_markers = function () {
// Append last to prevent layout calculation
document.body.append(scroll_marker);
};
+
+document.addEventListener("DOMContentLoaded", () => {
+ if (document.body.classList.contains("indexfile")) {
+ coverage.index_ready();
+ } else {
+ coverage.pyfile_ready();
+ }
+});
diff --git a/coverage/htmlfiles/index.html b/coverage/htmlfiles/index.html
index 627d1d79d..37dd6a892 100644
--- a/coverage/htmlfiles/index.html
+++ b/coverage/htmlfiles/index.html
@@ -11,14 +11,7 @@
{% if extra_css %}
{% endif %}
-
-
-
-
-
-
+
@@ -58,19 +51,19 @@
{{ title|escape }}: