Browse Source

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

Baptiste Mispelon 4 years ago
parent
commit
41e6b2a3c5
4 changed files with 49 additions and 11 deletions
  1. 19 6
      django/test/html.py
  2. 4 0
      docs/releases/4.0.txt
  3. 8 2
      docs/topics/testing/tools.txt
  4. 18 3
      tests/test_utils/tests.py

+ 19 - 6
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:
-        # <input checked> == <input checked="checked">
-        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:
+        #   <input checked> == <input checked="checked">
+        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

+ 4 - 0
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

+ 8 - 2
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

+ 18 - 3
tests/test_utils/tests.py

@@ -715,10 +715,25 @@ class HTMLEqualTests(SimpleTestCase):
                 self.assertHTMLEqual(html1, html2)
 
     def test_boolean_attribute(self):
-        html1 = '<input attr>'
-        html2 = '<input attr="">'
+        html1 = '<input checked>'
+        html2 = '<input checked="">'
+        html3 = '<input checked="checked">'
         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):
         pairs = [