Fixed #32556 -- Fixed handling empty string as non-boolean attributes value by assertHTMLEqual().

This commit is contained in:
Baptiste Mispelon 2021-03-19 00:43:38 +01:00 committed by Mariusz Felisiak
parent 98abf80cde
commit 41e6b2a3c5
4 changed files with 49 additions and 11 deletions

View File

@ -9,6 +9,16 @@ from django.utils.regex_helper import _lazy_re_compile
# https://infra.spec.whatwg.org/#ascii-whitespace # https://infra.spec.whatwg.org/#ascii-whitespace
ASCII_WHITESPACE = _lazy_re_compile(r'[\t\n\f\r ]+') 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): def normalize_whitespace(string):
return ASCII_WHITESPACE.sub(' ', string) return ASCII_WHITESPACE.sub(' ', string)
@ -23,11 +33,14 @@ def normalize_attributes(attributes):
value = ' '.join(sorted( value = ' '.join(sorted(
value for value in ASCII_WHITESPACE.split(value) if value value for value in ASCII_WHITESPACE.split(value) if value
)) ))
# Attributes without a value is same as attribute with value that # Boolean attributes without a value is same as attribute with value
# equals the attributes name: # that equals the attributes name. For example:
# <input checked> == <input checked="checked"> # <input checked> == <input checked="checked">
if name in BOOLEAN_ATTRIBUTES:
if not value or value == name: if not value or value == name:
value = None value = None
elif value is None:
value = ''
normalized.append((name, value)) normalized.append((name, value))
return normalized return normalized
@ -131,7 +144,7 @@ class Element:
def __str__(self): def __str__(self):
output = '<%s' % self.name output = '<%s' % self.name
for key, value in self.attributes: for key, value in self.attributes:
if value: if value is not None:
output += ' %s="%s"' % (key, value) output += ' %s="%s"' % (key, value)
else: else:
output += ' %s' % key output += ' %s' % key

View File

@ -362,6 +362,10 @@ Miscellaneous
``EmailMessage.attach()`` with an invalid ``content`` or ``mimetype`` ``EmailMessage.attach()`` with an invalid ``content`` or ``mimetype``
arguments now raise ``ValueError`` instead of ``AssertionError``. 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: .. _deprecated-features-4.0:
Features deprecated in 4.0 Features deprecated in 4.0

View File

@ -1596,8 +1596,8 @@ your test suite.
closed or the HTML document ends. closed or the HTML document ends.
* Empty tags are equivalent to their self-closing version. * Empty tags are equivalent to their self-closing version.
* The ordering of attributes of an HTML element is not significant. * The ordering of attributes of an HTML element is not significant.
* Attributes without an argument are equal to attributes that equal in * Boolean attributes (like ``checked``) without an argument are equal to
name and value (see the examples). attributes that equal in name and value (see the examples).
* Text, character references, and entity references that refer to the same * Text, character references, and entity references that refer to the same
character are equivalent. character are equivalent.
@ -1620,6 +1620,12 @@ your test suite.
Output in case of error can be customized with the ``msg`` argument. 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) .. method:: SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None)
Asserts that the strings ``html1`` and ``html2`` are *not* equal. The Asserts that the strings ``html1`` and ``html2`` are *not* equal. The

View File

@ -715,10 +715,25 @@ class HTMLEqualTests(SimpleTestCase):
self.assertHTMLEqual(html1, html2) self.assertHTMLEqual(html1, html2)
def test_boolean_attribute(self): def test_boolean_attribute(self):
html1 = '<input attr>' html1 = '<input checked>'
html2 = '<input attr="">' html2 = '<input checked="">'
html3 = '<input checked="checked">'
self.assertHTMLEqual(html1, html2) self.assertHTMLEqual(html1, html2)
self.assertEqual(parse_html(html1), parse_html(html2)) self.assertHTMLEqual(html1, html3)
self.assertHTMLEqual(html2, html3)
self.assertHTMLNotEqual(html1, '<input checked="invalid">')
self.assertEqual(str(parse_html(html1)), '<input checked>')
self.assertEqual(str(parse_html(html2)), '<input checked>')
self.assertEqual(str(parse_html(html3)), '<input checked>')
def test_non_boolean_attibutes(self):
html1 = '<input value>'
html2 = '<input value="">'
html3 = '<input value="value">'
self.assertHTMLEqual(html1, html2)
self.assertHTMLNotEqual(html1, html3)
self.assertEqual(str(parse_html(html1)), '<input value="">')
self.assertEqual(str(parse_html(html2)), '<input value="">')
def test_normalize_refs(self): def test_normalize_refs(self):
pairs = [ pairs = [