From b9bc3246feaa1bbd8adfa4c921547b24bc7dcd92 Mon Sep 17 00:00:00 2001
From: Marc Gibbons <1726961+marcgibbons@users.noreply.github.com>
Date: Fri, 3 Apr 2026 13:15:12 -0400
Subject: [PATCH 1/3] fix: endblock falsely reported as uncovered when on its
own line (issue #74)
In parent templates, a TEXT token inside a {% block %} can end with a
whitespace-only fragment before {% endblock %} with no newline terminator.
That fragment is not an executable line but was incorrectly added to
source_lines, causing {% endblock %} to appear as an uncovered line.
Skip the trailing fragment when inside a block (inblock=True).
---
django_coverage_plugin/plugin.py | 7 ++++
tests/test_extends.py | 68 ++++++++++++++++++++++++++++++++
2 files changed, 75 insertions(+)
diff --git a/django_coverage_plugin/plugin.py b/django_coverage_plugin/plugin.py
index 13e9c41..30adb8f 100644
--- a/django_coverage_plugin/plugin.py
+++ b/django_coverage_plugin/plugin.py
@@ -349,6 +349,13 @@ def lines(self):
if lines[0].isspace():
lineno += 1
num_lines -= 1
+ # When {% endblock %} is not at the start of a line, the
+ # preceding TEXT token ends with whitespace and no newline.
+ # That partial line is not executable content.
+ if inblock and num_lines > 0 and (
+ lines[-1].isspace() and not lines[-1].endswith(("\n", "\r"))
+ ):
+ num_lines -= 1
source_lines.update(range(lineno, lineno+num_lines))
if SHOW_PARSING:
diff --git a/tests/test_extends.py b/tests/test_extends.py
index 3f7a987..43dd0dc 100644
--- a/tests/test_extends.py
+++ b/tests/test_extends.py
@@ -95,6 +95,74 @@ def test_inheriting_with_unused_blocks(self):
self.assert_analysis([1, 2, 3], name="base.html")
self.assert_analysis([1, 4, 8], [8], name="specific.html")
+ def test_empty_parent_block_on_new_line_when_extended(self):
+ """
+ When a block is empty and extended, endblock should not appear
+ as an uncovered line.
+
+ https://github.com/coveragepy/django_coverage_plugin/issues/74
+ """
+ self.make_template(name="base.html", text="""\
+ Hello
+ {% block content %}
+ {% endblock content %}
+ Goodbye
+ """)
+ self.make_template(name="child.html", text="""\
+ {% extends "base.html" %}
+ {% block content %}
+ Override
+ {% endblock %}
+ """)
+ text = self.run_django_coverage(name="child.html")
+ self.assert_analysis([1, 2, 4], name="base.html")
+ self.assert_analysis([1, 3], name="child.html")
+ self.assertEqual(text.strip(), "Hello\n \n Override\n\nGoodbye")
+
+ def test_non_empty_parent_block_when_extended(self):
+ self.make_template(name="base.html", text="""\
+ Hello
+ {% block content %}
+ This line should be reported as uncovered.
+ {% endblock content %}
+ Goodbye
+ """)
+ self.make_template(name="child.html", text="""\
+ {% extends "base.html" %}
+ {% block content %}
+ Override
+ {% endblock %}
+ """)
+ text = self.run_django_coverage(name="child.html")
+ self.assert_analysis([1, 2, 3, 5], missing=[3], name="base.html")
+ self.assert_analysis([1, 3], name="child.html")
+
+ self.assertEqual(text.strip(), "Hello\n \n Override\n\nGoodbye")
+
+ def test_nested_blocks_outer_endblock_on_its_own_line(self):
+ """
+ When blocks are nested, on their own lines, and extended,
+ then endblock should not appear as uncovered.
+
+ Ref: https://github.com/coveragepy/django_coverage_plugin/issues/74
+ """
+ self.make_template(name="base.html", text="""\
+ {% block outer %}
+ {% block inner %}
+ {% endblock inner %}
+ {% endblock outer %}
+ """)
+ self.make_template(name="child.html", text="""\
+ {% extends "base.html" %}
+ {% block inner %}
+ Override
+ {% endblock %}
+ """)
+ text = self.run_django_coverage(name="child.html")
+ self.assert_analysis([1, 2], missing=[], name="base.html")
+ self.assert_analysis([1, 3], name="child.html")
+ self.assertEqual(text.strip(), "Override")
+
class LoadTest(DjangoPluginTestCase):
def test_load(self):
From b0a833ec82f61be4aee8ede307e20bf27b973d6f Mon Sep 17 00:00:00 2001
From: Marc Gibbons <1726961+marcgibbons@users.noreply.github.com>
Date: Fri, 3 Apr 2026 14:05:09 -0400
Subject: [PATCH 2/3] fix: skipped tags falsely reported as uncovered when not
at start of line
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
When a tag ({% endif %}, {% endfor %}, {% else %}, etc.) is not at the
start of a line, the preceding TEXT token ends with a whitespace-only
fragment with no newline terminator. That partial line is not executable
content but was incorrectly added to source_lines.
Extends the endblock fix to apply universally — built-in and user-defined
end tags are all covered by the same condition.
---
django_coverage_plugin/plugin.py | 6 ++---
tests/test_flow.py | 42 +++++++++++++++++++++++++++++---
tests/test_simple.py | 11 +++++++++
3 files changed, 53 insertions(+), 6 deletions(-)
diff --git a/django_coverage_plugin/plugin.py b/django_coverage_plugin/plugin.py
index 30adb8f..4037558 100644
--- a/django_coverage_plugin/plugin.py
+++ b/django_coverage_plugin/plugin.py
@@ -349,10 +349,10 @@ def lines(self):
if lines[0].isspace():
lineno += 1
num_lines -= 1
- # When {% endblock %} is not at the start of a line, the
- # preceding TEXT token ends with whitespace and no newline.
+ # When a tag is not at the start of a line, the preceding
+ # TEXT token ends with whitespace and no newline.
# That partial line is not executable content.
- if inblock and num_lines > 0 and (
+ if num_lines > 0 and (
lines[-1].isspace() and not lines[-1].endswith(("\n", "\r"))
):
num_lines -= 1
diff --git a/tests/test_flow.py b/tests/test_flow.py
index db83fa8..7a3a5a1 100644
--- a/tests/test_flow.py
+++ b/tests/test_flow.py
@@ -25,6 +25,31 @@ def test_if(self):
self.assertEqual(text.strip(), '')
self.assert_analysis([1, 2], [2])
+ def test_endif_not_at_start_of_line(self):
+ self.make_template("""\
+