Skip to content

Commit

Permalink
Merge pull request gawel#189 from whybin/fix-disabled
Browse files Browse the repository at this point in the history
Follow WHATWG spec when selecting :disabled and :enabled
  • Loading branch information
gawel authored Jun 22, 2018
2 parents cc2e339 + 2495b60 commit 8b460ce
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 5 deletions.
24 changes: 22 additions & 2 deletions pyquery/cssselectpatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,26 @@ def xpath_selected_pseudo(self, xpath):
xpath.add_condition("@selected and name(.) = 'option'")
return xpath

def _format_disabled_xpath(self, disabled=True):
"""Format XPath condition for :disabled or :enabled pseudo-classes
according to the WHATWG spec. See: https://html.spec.whatwg.org
/multipage/semantics-other.html#concept-element-disabled
"""
bool_op = '' if disabled else 'not'
return '''(
((name(.) = 'button' or name(.) = 'input' or name(.) = 'select'
or name(.) = 'textarea' or name(.) = 'fieldset')
and %s(@disabled or (ancestor::fieldset[@disabled]
and not(ancestor::legend[not(preceding-sibling::legend)])))
)
or
((name(.) = 'option'
and %s(@disabled or ancestor::optgroup[@disabled]))
)
or
((name(.) = 'optgroup' and %s(@disabled)))
)''' % (bool_op, bool_op, bool_op)

def xpath_disabled_pseudo(self, xpath):
"""Matches all elements that are disabled::
Expand All @@ -137,7 +157,7 @@ def xpath_disabled_pseudo(self, xpath):
..
"""
xpath.add_condition("@disabled")
xpath.add_condition(self._format_disabled_xpath())
return xpath

def xpath_enabled_pseudo(self, xpath):
Expand All @@ -150,7 +170,7 @@ def xpath_enabled_pseudo(self, xpath):
..
"""
xpath.add_condition("not(@disabled) and name(.) = 'input'")
xpath.add_condition(self._format_disabled_xpath(disabled=False))
return xpath

def xpath_file_pseudo(self, xpath):
Expand Down
41 changes: 38 additions & 3 deletions tests/test_pyquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,32 @@ class TestSelector(TestCase):
<body>
<form action="/">
<input name="enabled" type="text" value="test"/>
<b disabled>Not :disabled</b>
<input name="disabled" type="text"
value="disabled" disabled="disabled"/>
<fieldset>
<input name="fieldset-enabled">
</fieldset>
<fieldset disabled>
<legend>
<input name="legend-enabled">
</legend>
<input name="fieldset-disabled">
<legend>
<input name="legend-disabled">
</legend>
<select id="disabled-select">
<optgroup>
<option></option>
</optgroup>
</select>
</fieldset>
<select>
<optgroup id="disabled-optgroup" disabled>
<option id="disabled-from-optgroup"></option>
<option id="disabled-option" disabled></option>
</optgroup>
</select>
<input name="file" type="file" />
<select name="select">
<option value="">Choose something</option>
Expand Down Expand Up @@ -178,12 +202,23 @@ def test_pseudo_classes(self):

# test on the form
e = self.klass(self.html4)
assert len(e(':disabled')) == 1
assert len(e('input:enabled')) == 9
disabled = e(':disabled')
self.assertIn(e('[name="disabled"]')[0], disabled)
self.assertIn(e('fieldset[disabled]')[0], disabled)
self.assertIn(e('[name="legend-disabled"]')[0], disabled)
self.assertIn(e('[name="fieldset-disabled"]')[0], disabled)
self.assertIn(e('#disabled-optgroup')[0], disabled)
self.assertIn(e('#disabled-from-optgroup')[0], disabled)
self.assertIn(e('#disabled-option')[0], disabled)
self.assertIn(e('#disabled-select')[0], disabled)

assert len(disabled) == 8
assert len(e('select:enabled')) == 2
assert len(e('input:enabled')) == 11
assert len(e(':selected')) == 1
assert len(e(':checked')) == 2
assert len(e(':file')) == 1
assert len(e(':input')) == 12
assert len(e(':input')) == 18
assert len(e(':button')) == 2
assert len(e(':radio')) == 3
assert len(e(':checkbox')) == 3
Expand Down

0 comments on commit 8b460ce

Please sign in to comment.