Skip to content

Commit bfee701

Browse files
authored
fix: endblock falsely reported as uncovered when on its own indented line #74 (#108)
* 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). * fix: skipped tags falsely reported as uncovered when not at start of line 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. * Update missed assertion when runing Django < 4
1 parent 59954f3 commit bfee701

File tree

4 files changed

+127
-5
lines changed

4 files changed

+127
-5
lines changed

django_coverage_plugin/plugin.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,13 @@ def lines(self):
349349
if lines[0].isspace():
350350
lineno += 1
351351
num_lines -= 1
352+
# When a tag is not at the start of a line, the preceding
353+
# TEXT token ends with whitespace and no newline.
354+
# That partial line is not executable content.
355+
if num_lines > 0 and (
356+
lines[-1].isspace() and not lines[-1].endswith(("\n", "\r"))
357+
):
358+
num_lines -= 1
352359
source_lines.update(range(lineno, lineno+num_lines))
353360

354361
if SHOW_PARSING:

tests/test_extends.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,74 @@ def test_inheriting_with_unused_blocks(self):
9595
self.assert_analysis([1, 2, 3], name="base.html")
9696
self.assert_analysis([1, 4, 8], [8], name="specific.html")
9797

98+
def test_empty_parent_block_on_new_line_when_extended(self):
99+
"""
100+
When a block is empty and extended, endblock should not appear
101+
as an uncovered line.
102+
103+
https://github.com/coveragepy/django_coverage_plugin/issues/74
104+
"""
105+
self.make_template(name="base.html", text="""\
106+
Hello
107+
{% block content %}
108+
{% endblock content %}
109+
Goodbye
110+
""")
111+
self.make_template(name="child.html", text="""\
112+
{% extends "base.html" %}
113+
{% block content %}
114+
Override
115+
{% endblock %}
116+
""")
117+
text = self.run_django_coverage(name="child.html")
118+
self.assert_analysis([1, 2, 4], name="base.html")
119+
self.assert_analysis([1, 3], name="child.html")
120+
self.assertEqual(text.strip(), "Hello\n \n Override\n\nGoodbye")
121+
122+
def test_non_empty_parent_block_when_extended(self):
123+
self.make_template(name="base.html", text="""\
124+
Hello
125+
{% block content %}
126+
This line should be reported as uncovered.
127+
{% endblock content %}
128+
Goodbye
129+
""")
130+
self.make_template(name="child.html", text="""\
131+
{% extends "base.html" %}
132+
{% block content %}
133+
Override
134+
{% endblock %}
135+
""")
136+
text = self.run_django_coverage(name="child.html")
137+
self.assert_analysis([1, 2, 3, 5], missing=[3], name="base.html")
138+
self.assert_analysis([1, 3], name="child.html")
139+
140+
self.assertEqual(text.strip(), "Hello\n \n Override\n\nGoodbye")
141+
142+
def test_nested_blocks_outer_endblock_on_its_own_line(self):
143+
"""
144+
When blocks are nested, on their own lines, and extended,
145+
then endblock should not appear as uncovered.
146+
147+
Ref: https://github.com/coveragepy/django_coverage_plugin/issues/74
148+
"""
149+
self.make_template(name="base.html", text="""\
150+
{% block outer %}
151+
{% block inner %}
152+
{% endblock inner %}
153+
{% endblock outer %}
154+
""")
155+
self.make_template(name="child.html", text="""\
156+
{% extends "base.html" %}
157+
{% block inner %}
158+
Override
159+
{% endblock %}
160+
""")
161+
text = self.run_django_coverage(name="child.html")
162+
self.assert_analysis([1, 2], missing=[], name="base.html")
163+
self.assert_analysis([1, 3], name="child.html")
164+
self.assertEqual(text.strip(), "Override")
165+
98166

99167
class LoadTest(DjangoPluginTestCase):
100168
def test_load(self):

tests/test_flow.py

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,31 @@ def test_if(self):
2525
self.assertEqual(text.strip(), '')
2626
self.assert_analysis([1, 2], [2])
2727

28+
def test_endif_not_at_start_of_line(self):
29+
self.make_template("""\
30+
<article>
31+
{% if foo %}
32+
Hello
33+
{% endif %}
34+
After
35+
</article>
36+
""")
37+
self.run_django_coverage(context={'foo': False})
38+
self.assert_analysis([1, 2, 3, 5, 6], missing=[3])
39+
40+
def test_else_not_at_start_of_line(self):
41+
self.make_template("""\
42+
<article>
43+
{% if foo %}
44+
Hello
45+
{% else %}
46+
Goodbye
47+
{% endif %}
48+
</article>
49+
""")
50+
self.run_django_coverage(context={'foo': True})
51+
self.assert_analysis([1, 2, 3, 5, 7], missing=[5])
52+
2853
def test_if_else(self):
2954
self.make_template("""\
3055
{% if foo %}
@@ -84,6 +109,17 @@ def test_loop(self):
84109
self.assertEqual(text, "Before\n\nAfter\n")
85110
self.assert_analysis([1, 2, 3, 5], [3])
86111

112+
def test_endfor_not_at_start_of_line(self):
113+
self.make_template("""\
114+
<ul>
115+
{% for item in items %}
116+
<li>{{ item }}</li>
117+
{% endfor %}
118+
</ul>
119+
""")
120+
self.run_django_coverage(context={'items': []})
121+
self.assert_analysis([1, 2, 3, 5], missing=[3])
122+
87123
def test_loop_with_empty_clause(self):
88124
self.make_template("""\
89125
Before
@@ -135,7 +171,7 @@ def test_regroup(self):
135171
<ul><li>New York: 20</li><li>Chicago: 7</li></ul></li><li>Japan
136172
<ul><li>Tokyo: 33</li></ul></li></ul>
137173
"""))
138-
self.assert_analysis([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13])
174+
self.assert_analysis([1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 13])
139175

140176

141177
class IfChangedTest(DjangoPluginTestCase):
@@ -154,7 +190,7 @@ def test_ifchanged(self):
154190
'items': [("A", "X"), ("A", "Y"), ("B", "Z"), ("B", "W")],
155191
})
156192
self.assertEqual(squashed(text), 'AXYBZW')
157-
self.assert_analysis([1, 2, 3, 4, 5])
193+
self.assert_analysis([1, 2, 3, 5])
158194

159195
def test_ifchanged_variable(self):
160196
self.make_template("""\
@@ -170,7 +206,7 @@ def test_ifchanged_variable(self):
170206
'items': [("A", "X"), ("A", "Y"), ("B", "Z"), ("B", "W")],
171207
})
172208
self.assertEqual(squashed(text), 'AXYBZW')
173-
self.assert_analysis([1, 2, 3, 4, 5])
209+
self.assert_analysis([1, 2, 3, 5])
174210

175211

176212
@django_stop_before(4, 0)
@@ -190,7 +226,7 @@ def test_ifequal(self):
190226
'items': [(0, 'A'), (1, 'X'), (2, 'X'), (3, 'B')],
191227
})
192228
self.assertEqual(squashed(text), '0X1X23')
193-
self.assert_analysis([1, 2, 3, 4, 5])
229+
self.assert_analysis([1, 2, 3, 5])
194230

195231
def test_ifnotequal(self):
196232
self.make_template("""\
@@ -206,4 +242,4 @@ def test_ifnotequal(self):
206242
'items': [(0, 'A'), (1, 'X'), (2, 'X'), (3, 'B')],
207243
})
208244
self.assertEqual(squashed(text), 'X012X3')
209-
self.assert_analysis([1, 2, 3, 4, 5])
245+
self.assert_analysis([1, 2, 3, 5])

tests/test_simple.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,17 @@ def test_with(self):
238238
self.assertEqual(text, "\nalpha = 1, beta = 2.\n\n")
239239
self.assert_analysis([1, 2])
240240

241+
def test_endwith_not_at_start_of_line(self):
242+
self.make_template("""\
243+
<div>
244+
{% with alpha=1 %}
245+
{{ alpha }}
246+
{% endwith %}
247+
</div>
248+
""")
249+
self.run_django_coverage()
250+
self.assert_analysis([1, 2, 3, 5])
251+
241252

242253
class StringTemplateTest(DjangoPluginTestCase):
243254

0 commit comments

Comments
 (0)