summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2019-10-12 19:37:58 -0400
committerNed Batchelder <ned@nedbatchelder.com>2019-10-13 12:30:25 -0400
commiteba18707241a052419d54cdc304aea346aa0605c (patch)
tree2e8a9c1391aa7d293bebacf5ea86169319eafda9
parentbcbdf41c6bb9d4ee30842bd14d45c7d48c2c6d01 (diff)
downloadpython-coveragepy-git-eba18707241a052419d54cdc304aea346aa0605c.tar.gz
Better presentation of contexts. #855
-rw-r--r--CHANGES.rst5
-rw-r--r--coverage/html.py15
-rw-r--r--coverage/htmlfiles/pyfile.html17
-rw-r--r--coverage/htmlfiles/style.scss95
-rw-r--r--tests/test_html.py14
5 files changed, 104 insertions, 42 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index eb130b3e..a5a86caa 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -20,6 +20,10 @@ development at the same time, such as 4.5.x and 5.0.
Unreleased
----------
+- The HTML report has been reimplemented (no more table around the source
+ code). This allowed for a better presentation of the context information,
+ hopefully resolving `issue 855`_.
+
- Added sqlite3 module version information to ``coverage debug sys`` output.
- Asking the HTML report to show contexts (``[html] show_contexts=True`` or
@@ -27,6 +31,7 @@ Unreleased
contexts measured (`issue 851`_).
.. _issue 851: https://github.com/nedbat/coveragepy/issues/851
+.. _issue 855: https://github.com/nedbat/coveragepy/issues/855
.. _changes_50a8:
diff --git a/coverage/html.py b/coverage/html.py
index 5b4c6ca9..4a0e1a33 100644
--- a/coverage/html.py
+++ b/coverage/html.py
@@ -77,6 +77,8 @@ def write_html(fname, html):
class HtmlDataGeneration(object):
"""Generate structured data to be turned into HTML reports."""
+ EMPTY = "(empty)"
+
def __init__(self, cov):
self.coverage = cov
self.config = self.coverage.config
@@ -121,10 +123,15 @@ class HtmlDataGeneration(object):
elif lineno in analysis.statements:
category = 'run'
+ contexts = contexts_label = None
+ context_list = None
if category and self.config.show_contexts:
- contexts = sorted(c or "(empty)" for c in contexts_by_lineno[lineno])
- else:
- contexts = None
+ contexts = sorted(c or self.EMPTY for c in contexts_by_lineno[lineno])
+ if contexts == [self.EMPTY]:
+ contexts_label = self.EMPTY
+ else:
+ contexts_label = "{} ctx".format(len(contexts))
+ context_list = contexts
lines.append(SimpleNamespace(
tokens=tokens,
@@ -132,6 +139,8 @@ class HtmlDataGeneration(object):
category=category,
statement=(lineno in analysis.statements),
contexts=contexts,
+ contexts_label=contexts_label,
+ context_list=context_list,
short_annotations=short_annotations,
long_annotations=long_annotations,
))
diff --git a/coverage/htmlfiles/pyfile.html b/coverage/htmlfiles/pyfile.html
index 2154c06c..85f79f18 100644
--- a/coverage/htmlfiles/pyfile.html
+++ b/coverage/htmlfiles/pyfile.html
@@ -79,11 +79,18 @@
<span class="annotate long">{{line.annotate_long}}</span>{#-#}
{% endif -%}
{% if line.contexts -%}
- <span class="context-button">{{ line.contexts|len }} ctx</span>{#-#}
- <span class="context-list">
- {% for context in line.contexts -%}
- <span class="context-line">{{context}}</span>{#-#}
- {% endfor -%}
+ <span class="ctx">
+ {% if line.context_list -%}
+ <input type="checkbox" id="ctxs{{line.number}}" />
+ {% endif -%}
+ <label for="ctxs{{line.number}}">{{ line.contexts_label }}</label>{#-#}
+ {% if line.context_list -%}
+ <span class="ctxs">
+ {% for context in line.context_list -%}
+ <span>{{context}}</span>{#-#}
+ {% endfor -%}
+ </span>{#-#}
+ {% endif -%}
</span>{#-#}
{% endif -%}
</p>
diff --git a/coverage/htmlfiles/style.scss b/coverage/htmlfiles/style.scss
index 33ec38e4..3e4283e8 100644
--- a/coverage/htmlfiles/style.scss
+++ b/coverage/htmlfiles/style.scss
@@ -95,7 +95,7 @@ a.nav {
margin: 1em 0 0 3em;
}
-/* Header styles */
+// Header styles
#header .content {
padding: 1em $left-gutter;
}
@@ -242,17 +242,25 @@ $mis-hover-color: mix($mis-color, #000, $hover-dark-amt);
$run-hover-color: mix($run-color, #000, $hover-dark-amt);
$exc-hover-color: mix($exc-color, #000, $hover-dark-amt);
$par-hover-color: mix($par-color, #000, $hover-dark-amt);
+
+// The slim bar at the left edge of the source lines, colored by coverage.
$border-indicator-width: .2em;
+$context-panel-color: #aaeeff;
+
#source {
padding: 1em 0 1em $left-gutter;
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
p {
- /* position relative makes position:absolute pop-ups appear in the right place. */
+ // position relative makes position:absolute pop-ups appear in the right place.
position: relative;
white-space: pre;
+ * {
+ box-sizing: border-box;
+ }
+
.n {
float: left;
text-align: right;
@@ -387,42 +395,69 @@ $border-indicator-width: .2em;
float: right;
padding-right: .5em;
}
- }
-}
+ .ctx {
+ font-family: verdana, sans-serif;
+ white-space: nowrap;
-// Line contexts
-td.contexts {
- p {
- margin: 0;
- padding: 0 .5em;
- color: #999999;
- font-family: verdana, sans-serif;
- white-space: nowrap;
- position: relative;
- &:hover {
- background: #eee;
- }
- span.context-list {
- @extend %in-text-popup;
- min-width: 30em;
+ input {
+ display: none;
- span.context-line {
- display: block;
+ & ~ label {
+ cursor: pointer;
+ border-radius: .25em;
+ &::before {
+ content: "▶ ";
+ }
+ }
+
+ &:checked ~ label {
+ background: $context-panel-color;
+ color: #666;
+ border-radius: .75em .75em 0 0;
+ &::before {
+ content: "▼ ";
+ }
+ }
+
+ & ~ .ctxs {
+ display: none;
+ }
+
+ &:checked ~ .ctxs {
+ display: block;
+ }
+ }
+
+ label {
+ color: #999;
+ position: absolute;
+ right: 2.5em;
+ display: inline-block;
+ text-align: right;
+ font-size: .8333em; // 10/12
+ padding: .25em .5em;
+ &:hover {
+ background: mix($context-panel-color, #fff, 50%);
+ color: #666;
+ }
+ }
+
+ .ctxs {
+ font-family: verdana, sans-serif;
+ background: $context-panel-color;
+ padding: .25em .5em;
+ border-radius: .25em;
+ margin-right: 1.75em;
+ span {
+ display: block;
+ }
}
- }
- &:hover span.context-list {
- display: block;
- }
- span.context-button {
- display: inline-block;
- cursor: pointer;
- font-size: .8333em; // 10/12
- line-height: 1em;
}
}
}
+
// index styles
#index {
td, th {
diff --git a/tests/test_html.py b/tests/test_html.py
index 29f81a05..c9f82062 100644
--- a/tests/test_html.py
+++ b/tests/test_html.py
@@ -1038,6 +1038,8 @@ assert len(math) == 18
class HtmlWithContextsTest(HtmlTestHelpers, CoverageTest):
"""Tests of the HTML reports with shown contexts."""
+ EMPTY = coverage.html.HtmlDataGeneration.EMPTY
+
def html_data_from_cov(self, cov, morf):
"""Get HTML report data from a `Coverage` object for a morf."""
with self.assert_warnings(cov, []):
@@ -1083,11 +1085,15 @@ class HtmlWithContextsTest(HtmlTestHelpers, CoverageTest):
cov.set_option("html:show_contexts", True)
mod = self.start_import_stop(cov, "two_tests")
d = self.html_data_from_cov(cov, mod)
-
- context_labels = ['(empty)', 'two_tests.test_one', 'two_tests.test_two']
+ from coverage.debug import pp
+ pp(d)
+ context_labels = [self.EMPTY, 'two_tests.test_one', 'two_tests.test_two']
expected_lines = [self.OUTER_LINES, self.TEST_ONE_LINES, self.TEST_TWO_LINES]
for label, expected in zip(context_labels, expected_lines):
- actual = [ld.number for ld in d.lines if label in (ld.contexts or ())]
+ actual = [
+ ld.number for ld in d.lines
+ if label == ld.contexts_label or label in (ld.contexts or ())
+ ]
assert sorted(expected) == sorted(actual)
def test_filtered_dynamic_contexts(self):
@@ -1099,7 +1105,7 @@ class HtmlWithContextsTest(HtmlTestHelpers, CoverageTest):
mod = self.start_import_stop(cov, "two_tests")
d = self.html_data_from_cov(cov, mod)
- context_labels = ['(empty)', 'two_tests.test_one', 'two_tests.test_two']
+ context_labels = [self.EMPTY, 'two_tests.test_one', 'two_tests.test_two']
expected_lines = [[], self.TEST_ONE_LINES, []]
for label, expected in zip(context_labels, expected_lines):
actual = [ld.number for ld in d.lines if label in (ld.contexts or ())]