diff --git a/django/test/html.py b/django/test/html.py
index 4620b133dd..f1cbadfc54 100644
--- a/django/test/html.py
+++ b/django/test/html.py
@@ -9,6 +9,16 @@ from django.utils.regex_helper import _lazy_re_compile
# https://infra.spec.whatwg.org/#ascii-whitespace
ASCII_WHITESPACE = _lazy_re_compile(r'[\t\n\f\r ]+')
+# https://html.spec.whatwg.org/#attributes-3
+BOOLEAN_ATTRIBUTES = {
+ 'allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'controls',
+ 'default', 'defer ', 'disabled', 'formnovalidate', 'hidden', 'ismap',
+ 'itemscope', 'loop', 'multiple', 'muted', 'nomodule', 'novalidate', 'open',
+ 'playsinline', 'readonly', 'required', 'reversed', 'selected',
+ # Attributes for deprecated tags.
+ 'truespeed',
+}
+
def normalize_whitespace(string):
return ASCII_WHITESPACE.sub(' ', string)
@@ -23,11 +33,14 @@ def normalize_attributes(attributes):
value = ' '.join(sorted(
value for value in ASCII_WHITESPACE.split(value) if value
))
- # Attributes without a value is same as attribute with value that
- # equals the attributes name:
- # ==
- if not value or value == name:
- value = None
+ # Boolean attributes without a value is same as attribute with value
+ # that equals the attributes name. For example:
+ # ==
+ if name in BOOLEAN_ATTRIBUTES:
+ if not value or value == name:
+ value = None
+ elif value is None:
+ value = ''
normalized.append((name, value))
return normalized
@@ -131,7 +144,7 @@ class Element:
def __str__(self):
output = '<%s' % self.name
for key, value in self.attributes:
- if value:
+ if value is not None:
output += ' %s="%s"' % (key, value)
else:
output += ' %s' % key
diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt
index 6f10124c0c..2b82c62d02 100644
--- a/docs/releases/4.0.txt
+++ b/docs/releases/4.0.txt
@@ -362,6 +362,10 @@ Miscellaneous
``EmailMessage.attach()`` with an invalid ``content`` or ``mimetype``
arguments now raise ``ValueError`` instead of ``AssertionError``.
+* :meth:`~django.test.SimpleTestCase.assertHTMLEqual` no longer considers a
+ non-boolean attribute without a value equal to an attribute with the same
+ name and value.
+
.. _deprecated-features-4.0:
Features deprecated in 4.0
diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt
index 70b7f2df5c..02da186aa2 100644
--- a/docs/topics/testing/tools.txt
+++ b/docs/topics/testing/tools.txt
@@ -1596,8 +1596,8 @@ your test suite.
closed or the HTML document ends.
* Empty tags are equivalent to their self-closing version.
* The ordering of attributes of an HTML element is not significant.
- * Attributes without an argument are equal to attributes that equal in
- name and value (see the examples).
+ * Boolean attributes (like ``checked``) without an argument are equal to
+ attributes that equal in name and value (see the examples).
* Text, character references, and entity references that refer to the same
character are equivalent.
@@ -1620,6 +1620,12 @@ your test suite.
Output in case of error can be customized with the ``msg`` argument.
+ .. versionchanged:: 4.0
+
+ In older versions, any attribute (not only boolean attributes) without
+ a value was considered equal to an attribute with the same name and
+ value.
+
.. method:: SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None)
Asserts that the strings ``html1`` and ``html2`` are *not* equal. The
diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py
index 137a6e2e1f..44301aaa1b 100644
--- a/tests/test_utils/tests.py
+++ b/tests/test_utils/tests.py
@@ -715,10 +715,25 @@ class HTMLEqualTests(SimpleTestCase):
self.assertHTMLEqual(html1, html2)
def test_boolean_attribute(self):
- html1 = ''
- html2 = ''
+ html1 = ''
+ html2 = ''
+ html3 = ''
self.assertHTMLEqual(html1, html2)
- self.assertEqual(parse_html(html1), parse_html(html2))
+ self.assertHTMLEqual(html1, html3)
+ self.assertHTMLEqual(html2, html3)
+ self.assertHTMLNotEqual(html1, '')
+ self.assertEqual(str(parse_html(html1)), '')
+ self.assertEqual(str(parse_html(html2)), '')
+ self.assertEqual(str(parse_html(html3)), '')
+
+ def test_non_boolean_attibutes(self):
+ html1 = ''
+ html2 = ''
+ html3 = ''
+ self.assertHTMLEqual(html1, html2)
+ self.assertHTMLNotEqual(html1, html3)
+ self.assertEqual(str(parse_html(html1)), '')
+ self.assertEqual(str(parse_html(html2)), '')
def test_normalize_refs(self):
pairs = [