import pytest
from src.node import Node
from src.node_helpers import ParentAwareNode
from src.parser import (
DescendantSelector,
MultiSelector,
NonMultiSelector,
NotPseudoClassSelector,
NthChildPseudoClassSelector,
SiblingSelector,
SimpleSelector,
)
# This test tree matches the one from python discord's original test suite
#
# Structure:
#
#
#
This is a heading!
#
# I have some content within this container also!
#
#
# This is another paragraph.
#
#
# This is a third paragraph.
#
#
# This is a button link.
#
#
#
#
This is a paragraph in a secondary container.
#
#
TEST_TREE = ParentAwareNode.from_node(
Node(
tag="div",
attributes={"id": "topDiv"},
children=[
Node(
tag="div",
attributes={"id": "innerDiv", "class": "container colour-primary"},
children=[
Node(tag="h1", text="This is a heading!"),
Node(
tag="p",
attributes={"class": "colour-secondary", "id": "innerContent"},
text="I have some content within this container also!",
),
Node(
tag="p",
attributes={"class": "colour-secondary", "id": "two"},
text="This is another paragraph.",
),
Node(
tag="p",
attributes={"class": "colour-secondary important"},
text="This is a third paragraph.",
),
Node(
tag="a",
attributes={"id": "home-link", "class": "colour-primary button"},
text="This is a button link.",
),
],
),
Node(
tag="div",
attributes={"class": "container colour-secondary"},
children=[
Node(
tag="p",
attributes={"class": "colour-primary"},
text="This is a paragraph in a secondary container.",
),
],
),
],
)
)
# Get references to important nodes
TOP_DIV = TEST_TREE
INNER_DIV = TOP_DIV.children[0]
H1 = INNER_DIV.children[0]
P1 = INNER_DIV.children[1]
P2 = INNER_DIV.children[2]
P3 = INNER_DIV.children[3]
A_LINK = INNER_DIV.children[4]
SECOND_DIV = TEST_TREE.children[1]
SECOND_P = SECOND_DIV.children[0]
@pytest.mark.parametrize(
("selector", "node", "expected"),
[
(SimpleSelector(tag="div"), TOP_DIV, True),
(SimpleSelector(tag="div"), H1, False),
(SimpleSelector(classes=["colour-primary"]), P1, False),
(SimpleSelector(classes=["colour-secondary"]), P1, True),
(SimpleSelector(ids=["home-link"]), A_LINK, True),
(SimpleSelector(tag="a", classes=["button"], ids=["home-link"]), A_LINK, True),
(SimpleSelector(tag="p", classes=["important"]), P3, True),
(SimpleSelector(tag="p", classes=["nonexistent"]), P3, False),
(SimpleSelector(tag="p", ids=["innerContent"]), P1, True),
(SimpleSelector(tag="h1", ids=["innerContent"]), P1, False),
],
)
def test_simple_selector(selector: SimpleSelector, node: ParentAwareNode, expected: bool) -> None:
assert selector.match_node(node) is expected
@pytest.mark.parametrize(
("selector", "node", "expected"),
[
pytest.param(
DescendantSelector(
parent=SimpleSelector(tag="div", classes=["container"]),
child=SimpleSelector(tag="h1"),
direct=True,
),
H1,
True,
id="div.container > h1 (on h1)",
),
pytest.param(
DescendantSelector(
parent=SimpleSelector(tag="div"),
child=DescendantSelector(
parent=SimpleSelector(tag="div"),
child=SimpleSelector(tag="p", classes=["important"]),
direct=True,
),
direct=True,
),
P3,
True,
id="div > div > p.important (on p3)",
),
pytest.param(
DescendantSelector(
parent=SimpleSelector(tag="div", classes=["container"]),
child=SimpleSelector(tag="p", classes=["colour-secondary"]),
direct=False,
),
P1,
True,
id="div.container p.colour-secondary (on p1)",
),
pytest.param(
DescendantSelector(
parent=SimpleSelector(tag="div", ids=["topDiv"]),
child=SimpleSelector(tag="p", classes=["colour-secondary"]),
direct=False,
),
P1,
True,
id="div#topDiv p.colour-secondary (on p1)",
),
pytest.param(
DescendantSelector(
parent=SimpleSelector(tag="div", classes=["container"]),
child=SimpleSelector(tag="a", classes=["colour-secondary"]),
direct=False,
),
P1,
False,
id="div.container a.colour-secondary (on p1)",
),
pytest.param(
DescendantSelector(
parent=SimpleSelector(tag="div", classes=["nonexistent"]),
child=SimpleSelector(tag="p", classes=["colour-secondary"]),
direct=False,
),
P1,
False,
id="div.nonexistent p.colour-secondary (on p1)",
),
pytest.param(
DescendantSelector(
parent=SimpleSelector(tag="div", ids=["topDiv"]),
child=SimpleSelector(tag="div", ids=["topDiv"]),
direct=False,
),
TOP_DIV,
False,
id="div#topDiv div#topDiv (on top_div)",
),
pytest.param(
DescendantSelector(
parent=SimpleSelector(tag="div"),
child=SimpleSelector(tag="div", classes=["container"]),
direct=False,
),
A_LINK,
True,
id="div#topDiv > div.container > a (on a_link)",
),
],
)
def test_descendant_selector_matching(selector: DescendantSelector, node: ParentAwareNode, expected: bool) -> None:
assert selector.match_node(node) is expected
@pytest.mark.parametrize(
("selector", "node", "expected"),
[
pytest.param(
SiblingSelector(
sibling_selector=SimpleSelector(tag="p", classes=["important"]),
selector=SimpleSelector(tag="a", classes=["button"]),
is_adjacent=True,
),
A_LINK,
True,
id="p.colour-secondary + a.button (on a_link)",
),
pytest.param(
SiblingSelector(
sibling_selector=SimpleSelector(tag="p", classes=["colour-secondary"]),
selector=SimpleSelector(tag="a", classes=["button"]),
is_adjacent=False,
),
A_LINK,
True,
id="p.colour-secondary ~ a.button (on a_link)",
),
pytest.param(
SiblingSelector(
sibling_selector=SimpleSelector(tag="p", ids=["innerContent"]),
selector=SimpleSelector(tag="p", ids=["two"]),
is_adjacent=True,
),
P2,
True,
id="p + p (on p2)",
),
pytest.param(
SiblingSelector(
sibling_selector=SimpleSelector(tag="h1"),
selector=SimpleSelector(tag="p", ids=["innerContent"]),
is_adjacent=True,
),
P1,
True,
id="h1 + p (on p1)",
),
pytest.param(
SiblingSelector(
sibling_selector=SimpleSelector(tag="h1"),
selector=SimpleSelector(tag="p", ids=["innerContent"]),
is_adjacent=True,
),
P2,
False,
id="h1 + p (on p2)",
),
pytest.param(
SiblingSelector(
sibling_selector=SimpleSelector(tag="div"),
selector=SimpleSelector(tag="h1"),
is_adjacent=True,
),
H1,
False,
id="div + h1 (on h1)",
),
],
)
def test_sibling_selector_matching(selector: SiblingSelector, node: ParentAwareNode, expected: bool) -> None:
assert selector.match_node(node) is expected
@pytest.mark.parametrize(
("n", "node", "expected"),
[
(1, H1, True), # first-child
(2, P1, True),
(3, P2, True),
(4, P3, True),
(5, A_LINK, True),
(6, A_LINK, False),
(-1, A_LINK, True), # last-child
(-1, P3, False),
(1, TOP_DIV, False),
(1, SECOND_P, True),
(-1, SECOND_P, True),
(2, SECOND_P, False),
],
)
def test_nth_child_selector(n: int, node: ParentAwareNode, expected: bool) -> None:
selector = NthChildPseudoClassSelector(selector=None, n=n)
assert selector.match_node(node) is expected
@pytest.mark.parametrize(
("selector", "node", "expected"),
[
pytest.param(
NotPseudoClassSelector(
selector=SimpleSelector("p"),
not_selector=SimpleSelector(classes=["important"]),
),
P1,
True,
id="p:not(.important) (on p1)",
),
pytest.param(
NotPseudoClassSelector(
selector=SimpleSelector("p"),
not_selector=SimpleSelector(classes=["important"]),
),
P3,
False,
id="p:not(.important) (on p3)",
),
pytest.param(
NotPseudoClassSelector(
selector=SimpleSelector("div"),
not_selector=SimpleSelector(classes=["important"]),
),
P1,
False,
id="div:not(.important) (on p1)",
),
pytest.param(
NotPseudoClassSelector(
selector=None,
not_selector=SimpleSelector(classes=["important"]),
),
P1,
True,
id=":not(.important) (on p1)",
),
pytest.param(
NotPseudoClassSelector(
selector=SimpleSelector("div"),
not_selector=SimpleSelector(ids=["innerDiv"]),
),
SECOND_DIV,
True,
id="div:not(#innerDiv) (on second_div)",
),
pytest.param(
NotPseudoClassSelector(
selector=SimpleSelector("div"),
not_selector=SimpleSelector(ids=["innerDiv"]),
),
INNER_DIV,
False,
id="div:not(#innerDiv) (on inner_div)",
),
pytest.param(
NotPseudoClassSelector(
selector=SimpleSelector("p"),
not_selector=DescendantSelector(
SimpleSelector(tag="div", classes=["colour-secondary"]), SimpleSelector(tag="p"), direct=False
),
),
P2,
True,
id="p:not(div.colour-secondary p) (on p2)",
),
pytest.param(
NotPseudoClassSelector(
selector=SimpleSelector("p"),
not_selector=DescendantSelector(
SimpleSelector(tag="div", classes=["container"]),
SimpleSelector(tag="p"),
direct=False,
),
),
SECOND_P,
False,
id="p:not(div.container p) (on second_p)",
),
],
)
def test_not_selector(selector: NotPseudoClassSelector, node: ParentAwareNode, expected: bool) -> None:
assert selector.match_node(node) is expected
@pytest.mark.parametrize(
("selectors", "node", "expected"),
[
pytest.param(
[
SimpleSelector(tag="a"),
SimpleSelector(tag="p", ids=["innerContent"]),
],
A_LINK,
True,
id="a, p#innerContent (on a_link)",
),
pytest.param(
[
SimpleSelector(tag="a"),
SimpleSelector(tag="p", ids=["innerContent"]),
],
P1,
True,
id="a, p#innerContent (on p1)",
),
pytest.param(
[
SimpleSelector(tag="a"),
SimpleSelector(tag="p", ids=["innerContent"]),
],
P2,
False,
id="a, p#innerContent (on p2)",
),
pytest.param(
[
SimpleSelector(tag="h1"),
SimpleSelector(classes=["colour-primary"]),
SimpleSelector(ids=["home-link"]),
],
TOP_DIV,
False,
id="h1, .colour-primary, #home-link (on top_div)",
),
pytest.param(
[
NotPseudoClassSelector(
selector=None,
not_selector=SimpleSelector(tag="h1"),
),
SimpleSelector(tag="p"),
],
TOP_DIV,
True,
id=":not(h1), p",
),
],
)
def test_multi_selector_matching(selectors: list[NonMultiSelector], node: ParentAwareNode, expected: bool) -> None:
selector = MultiSelector(selectors=selectors)
assert selector.match_node(node) is expected