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 = [