Skip to content

Commit

Permalink
Merge pull request #179 from BTWS2/css_inheritance_checks
Browse files Browse the repository at this point in the history
Allow CSS inheritance
  • Loading branch information
stijndcl authored Oct 23, 2021
2 parents 3b00570 + 36bf498 commit ab9d327
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 19 deletions.
11 changes: 9 additions & 2 deletions docs/pages/element-class.md
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ value of the styling exactly.
#### Signature

```python
def has_styling(self, prop: str, value: Optional[str] = None, important: Optional[bool] = None) -> Check
def has_styling(self, prop: str, value: Optional[str] = None, important: Optional[bool] = None, allow_inheritance: bool = False) -> Check
```

#### Parameters
Expand All @@ -609,6 +609,7 @@ def has_styling(self, prop: str, value: Optional[str] = None, important: Optiona
| `prop` | The name of the CSS property to look for. || |
| `value` | A value to match the property against. | | `None`, which will make any value pass and only checks if the element has this style property. |
| `important` | A boolean indicating that this element should (or may not be) marked as important using **`!important`**. | | `None`, which won't check this. |
| `allow_inheritance` | A boolean indicating that a parent element can also have this styling and pass it down onto the child instead. | | `False`, which will require the element itself to have this property. |

#### Example usage

Expand All @@ -620,6 +621,9 @@ div_tag = body.get_child("div")
# Check that the div has any background colour at all
div_tag.has_styling("background-color")

# Check that the div has a background colour, optionally inheriting it from a parent element
div_tag.has_styling("background-color", allow_inheritance=True)

# Check that the div has a horizontal margin of exactly 3px marked as !important
div_tag.has_styling("margin", "3px", important=True)
```
Expand All @@ -632,7 +636,7 @@ of [`has_styling`](#has_styling) because it allows the value to be in multiple d
#### Signature

```python
def has_color(prop: str, color: str, important: Optional[bool] = None) -> Check
def has_color(prop: str, color: str, important: Optional[bool] = None, allow_inheritance: bool = False) -> Check
```

#### Parameters
Expand All @@ -642,6 +646,7 @@ def has_color(prop: str, color: str, important: Optional[bool] = None) -> Check
| `attr` | The name of the CSS attribute to look for. || |
| `value` | A value to match the property against. This value may be in any of the accepted formats: `name`, `rgb`, `rgba`, `hex`. || |
| `important` | A boolean indicating that this element should (or may not be) marked as important using **`!important`**. | | `None`, which won't check this. |
| `allow_inheritance` | A boolean indicating that a parent element can also have this styling and pass it down onto the child instead. | | `False`, which will require the element itself to have this property. |

#### Example usage

Expand Down Expand Up @@ -671,5 +676,7 @@ div.has_color("background-color", "blue") # By name
div.has_color("background-color", "rgb(0, 0, 255)") # By rgb value
div.has_color("background-color", "rgba(0, 0, 255, 1.0)") # By rgba value
div.has_color("background-color", "#0000FF") # By hex value

div.has_color("background-color", "blue", allow_inheritance=True) # Allow inheriting from the parent <body>
```

6 changes: 4 additions & 2 deletions tests/test_validators/test_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,9 @@ def test_has_color(self):
self.assertTrue(suite.check(div.has_color("color", "#FF0000")))

self.assertFalse(suite.check(phantom.has_color("color", "gold")))
self.assertTrue(suite.check(span.has_color("color", "gold"))) # TODO
self.assertFalse(suite.check(span.has_color("color", "blue"))) # TODO
self.assertFalse(suite.check(span.has_color("color", "gold")))
self.assertTrue(suite.check(span.has_color("color", "gold", allow_inheritance=True)))
self.assertFalse(suite.check(span.has_color("color", "blue")))

# TODO #106
# self.assertTrue(suite.check(p.has_color("color", "gold")))
Expand Down Expand Up @@ -287,6 +288,7 @@ def test_has_styling(self):
self.assertTrue(suite.check(p.has_styling("font-weight", "bold", important=True)))

self.assertFalse(suite.check(span.has_styling("background-color")))
self.assertTrue(suite.check(span.has_styling("background-color", allow_inheritance=True)))
self.assertFalse(suite.check(phantom.has_styling("border")))

def test_no_loose_text(self):
Expand Down
62 changes: 50 additions & 12 deletions validators/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from utils.flatten import flatten_queue
from utils.regexes import doctype_re
from utils.html_navigation import find_child, compare_content, match_emmet, find_emmet, contains_comment
from validators.css_validator import CssValidator, CssParsingError
from validators.css_validator import CssValidator, CssParsingError, Rule
from validators.html_validator import HtmlValidator


Expand Down Expand Up @@ -513,17 +513,50 @@ def _inner(_: BeautifulSoup) -> bool:
return Check(_inner)

# CSS checks
def _find_css_property(self, prop: str, inherit: bool) -> Optional[Rule]:
"""Find a css property recursively if necessary
Properties by parent elements are applied onto their children, so
an element can inherit a property from its parent
"""
prop = prop.lower()

# Inheritance is not allowed
if not inherit:
return self._css_validator.find(self._element, prop)

current_element = self._element
prop_value = None

# Keep going higher up the tree until a match is found
while prop_value is None and current_element is not None:
# Check if the current element has this rule & applies it onto the child
prop_value = self._css_validator.find(current_element, prop)

if prop_value is None:
parents = current_element.find_parents()

# find_parents() always returns the entire document as well,
# even when the current element is the root
# So at least 2 parents are required
if len(parents) <= 2:
current_element = None
else:
current_element = parents[0]

return prop_value

@css_check
def has_styling(self, prop: str, value: Optional[str] = None, important: Optional[bool] = None) -> Check:
def has_styling(self, prop: str, value: Optional[str] = None, important: Optional[bool] = None, allow_inheritance: bool = False) -> Check:
"""Check that this element has a CSS property
:param prop: the required CSS property to check
:param value: an optional value to add that must be checked against,
in case nothing is supplied any value will pass
:param important: indicate that this must (or may not be) marked as important
:param prop: the required CSS property to check
:param value: an optional value to add that must be checked against,
in case nothing is supplied any value will pass
:param important: indicate that this must (or may not be) marked as important
:param allow_inheritance: allow a parent element to have this property and apply it onto the child
"""

def _inner(_: BeautifulSoup) -> bool:
prop_value = self._css_validator.find(self._element, prop.lower())
prop_value = self._find_css_property(prop, allow_inheritance)

# Property not found
if prop_value is None:
Expand All @@ -542,18 +575,23 @@ def _inner(_: BeautifulSoup) -> bool:
return Check(_inner)

@css_check
def has_color(self, prop: str, color: str, important: Optional[bool] = None) -> Check:
def has_color(self, prop: str, color: str, important: Optional[bool] = None, allow_inheritance: bool = False) -> Check:
"""Check that this element has a given color
More flexible version of has_styling because it also allows RGB(r, g, b), hex format, ...
:param prop: the required CSS property to check (background-color, color, ...)
:param color: the color to check this property's value against, in any format
:param important: indicate that this must (or may not be) marked as important
:param prop: the required CSS property to check (background-color, color, ...)
:param color: the color to check this property's value against, in any format
:param important: indicate that this must (or may not be) marked as important
:param allow_inheritance: allow a parent element to have this property and apply it onto the child
"""

def _inner(_: BeautifulSoup) -> bool:
# Find the CSS Rule
prop_rule = self._css_validator.find(self._element, prop.lower())
prop_rule = self._find_css_property(prop, allow_inheritance)

# Property not found
if prop_rule is None:
return False

# !important modifier is incorrect
if important is not None and prop_rule.important != important:
Expand Down
12 changes: 9 additions & 3 deletions validators/checks.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ from typing import Callable, List, Optional, Union, Dict, TypeVar, Iterable, Ite

from dodona.dodona_config import DodonaConfig
from dodona.translator import Translator
from validators.css_validator import CssValidator
from validators.css_validator import CssValidator, Rule
from validators.html_validator import HtmlValidator


Expand Down Expand Up @@ -119,12 +119,18 @@ class Element:
"""Check if the element contains a comment, optionally matching a value"""
...

def _find_css_property(self, prop: str, inherit: bool) -> Optional[Rule]:
"""Find a css property recursively if necessary
Properties by parent elements are applied onto their children, so
an element can inherit a property from its parent
"""
...

def has_styling(self, attr: str, value: Optional[str] = ..., important: Optional[bool] = ...) -> Check:
def has_styling(self, attr: str, value: Optional[str] = ..., important: Optional[bool] = ..., allow_inheritance: bool = False) -> Check:
"""Check that this element is matched by a CSS selector to give it a particular styling. A value can be passed to match the value of the styling exactly."""
...

def has_color(self, prop: str, color: str, important: Optional[bool] = None) -> Check:
def has_color(self, prop: str, color: str, important: Optional[bool] = None, allow_inheritance: bool = False) -> Check:
"""Check that this element has a given color on a CSS property."""
...

Expand Down

0 comments on commit ab9d327

Please sign in to comment.