Initial commit
This commit is contained in:
commit
9fba4b3d34
17 changed files with 2939 additions and 0 deletions
335
tests/test_qualifier.py
Normal file
335
tests/test_qualifier.py
Normal file
|
@ -0,0 +1,335 @@
|
|||
"""Test suite which tests the entire logic together (tokenization, parsing, matching).
|
||||
|
||||
This suite doesn't focus on any individual components, only going from selector string -> matching.
|
||||
Some of the test cases in this suite will technically overlap those in the other suites, this
|
||||
is intentional, as this suite is also useful for testing other implementations of the matching
|
||||
(not just my specific one), unlike with the other suites, which work with my specific AST/parser.
|
||||
|
||||
This suite is written with pytest to allow for easier parametrizations and provide better output.
|
||||
Generally, this suite contains the same tests as the original python discord's test suite, with
|
||||
some additional ones to test out various edge cases (and it's rewritten from pure unit-test suite).
|
||||
|
||||
On top of the original simple tests for the basic implementation, this suite also includes tests
|
||||
with the 'bonus' marker, which cover the bonus task. (The basic implementation tests have the 'basic'
|
||||
marker).
|
||||
|
||||
Finally, this suite also contains some extra cases for sibling matching (+ and ~). This is a part of
|
||||
the CSS specification, however, it was not a part of even the bonus task. These tests are marked with
|
||||
the 'extra' marker.
|
||||
|
||||
If you'd like to port this test-suite to your code, you'll want to change the src.qualifier imports to
|
||||
just qualifier as that's what the original implementation expects.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from collections.abc import Iterable
|
||||
|
||||
import pytest
|
||||
|
||||
from src.node import Node
|
||||
from src.qualifier import query_selector_all as solution
|
||||
|
||||
# This test tree matches the one from python discord's original test suite
|
||||
#
|
||||
# Structure:
|
||||
# <div id="topDiv">
|
||||
# <div class="container colour-primary" id="innerDiv">
|
||||
# <h1>This is a heading!</h1>
|
||||
# <p class="colour-secondary" id="innerContent">
|
||||
# I have some content within this container also!
|
||||
# </p>
|
||||
# <p class="colour-secondary" id="two">
|
||||
# This is another paragraph.
|
||||
# </p>
|
||||
# <p class="colour-secondary important">
|
||||
# This is a third paragraph.
|
||||
# </p>
|
||||
# <a class="colour-primary button" id="home-link">
|
||||
# This is a button link.
|
||||
# </a>
|
||||
# </div>
|
||||
# <div class="container colour-secondary">
|
||||
# <p class="colour-primary">This is a paragraph in a secondary container.</p>
|
||||
# </div>
|
||||
# </div>
|
||||
TEST_TREE = 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]
|
||||
|
||||
|
||||
def assert_count_equal[T](first: Iterable[T], second: Iterable[T]) -> None:
|
||||
case = unittest.TestCase()
|
||||
case.assertCountEqual(first, second) # noqa: PT009
|
||||
|
||||
|
||||
@pytest.mark.basic
|
||||
@pytest.mark.parametrize(
|
||||
("selector", "expected"),
|
||||
[
|
||||
# matches pydis tests exactly
|
||||
("div", [TOP_DIV, INNER_DIV, SECOND_DIV]),
|
||||
("#innerDiv", [INNER_DIV]),
|
||||
(".colour-primary", [INNER_DIV, A_LINK, SECOND_P]),
|
||||
("p.colour-secondary", [P1, P2, P3]),
|
||||
("div#innerDiv", [INNER_DIV]),
|
||||
("#innerContent.colour-secondary", [P1]),
|
||||
("div#innerDiv.colour-primary", [INNER_DIV]),
|
||||
(".colour-primary.button", [A_LINK]),
|
||||
("#home-link.colour-primary.button", [A_LINK]),
|
||||
("a#home-link.colour-primary.button", [A_LINK]),
|
||||
("i", []),
|
||||
("#badId", []),
|
||||
(".missing-class", []),
|
||||
# some extra tests
|
||||
("#home-link", [A_LINK]),
|
||||
("a.button#home-link", [A_LINK]),
|
||||
("p.important", [P3]),
|
||||
("p.nonexistent", []),
|
||||
("p#innerContent", [P1]),
|
||||
("h1#innerContent", []),
|
||||
],
|
||||
)
|
||||
def test_simple_selector(selector: str, expected: list[Node]) -> None:
|
||||
assert_count_equal(solution(TEST_TREE, selector), expected)
|
||||
|
||||
|
||||
@pytest.mark.basic
|
||||
@pytest.mark.parametrize(
|
||||
("selector", "expected"),
|
||||
[
|
||||
# matches pydis tests exactly
|
||||
("#topDiv, h1, .colour-primary", [TOP_DIV, H1, INNER_DIV, A_LINK, SECOND_P]),
|
||||
("h1, a#home-link.colour-primary.button", [H1, A_LINK]),
|
||||
("p#two.colour-secondary, a#home-link.colour-primary.button", [P2, A_LINK]),
|
||||
("i, #badID, .missing-class", []),
|
||||
("h1, #badID, .missing-class", [H1]),
|
||||
("li#random.someclass, a#home-link, div, .colour-primary.badclass", [A_LINK, TOP_DIV, INNER_DIV, SECOND_DIV]),
|
||||
],
|
||||
)
|
||||
def test_multi_selector(selector: str, expected: list[Node]) -> None:
|
||||
assert_count_equal(solution(TEST_TREE, selector), expected)
|
||||
|
||||
|
||||
@pytest.mark.bonus
|
||||
@pytest.mark.parametrize(
|
||||
("selector", "expected"),
|
||||
[
|
||||
("div.container > h1", [H1]),
|
||||
("div > div > p.important", [P3]),
|
||||
("div.container p.colour-secondary", [P1, P2, P3]),
|
||||
("div.container > p.colour-secondary", [P1, P2, P3]),
|
||||
("div#topDiv p.colour-secondary", [P1, P2, P3]),
|
||||
("div#topDiv p", [P1, P2, P3, SECOND_P]),
|
||||
("div#topDiv > p", []),
|
||||
("div#innerDiv p.colour-primary", []),
|
||||
("div.nonexistent p.colour-secondary", []),
|
||||
("div#topDiv div#topDiv", []),
|
||||
# ("div#topDiv > div.container > a", [A_LINK]),
|
||||
],
|
||||
)
|
||||
def test_descendant_selector(selector: str, expected: list[Node]) -> None:
|
||||
assert_count_equal(solution(TEST_TREE, selector), expected)
|
||||
|
||||
|
||||
@pytest.mark.extra
|
||||
@pytest.mark.parametrize(
|
||||
("selector", "expected"),
|
||||
[
|
||||
("p.important + a.button", [A_LINK]),
|
||||
("p.colour-secondary ~ a.button", [A_LINK]),
|
||||
("p + p", [P2, P3]),
|
||||
("p ~ p", [P2, P3]),
|
||||
("h1 + p", [P1]),
|
||||
("h1 ~ p", [P1, P2, P3]),
|
||||
("div + h1", []),
|
||||
("p.nonexistent + p", []),
|
||||
("p + h1", []),
|
||||
("p ~ h1", []),
|
||||
("div + div", [SECOND_DIV]),
|
||||
("div ~ div", [SECOND_DIV]),
|
||||
("div#innerDiv ~ div", [SECOND_DIV]),
|
||||
("div#topDiv ~ div", []),
|
||||
],
|
||||
)
|
||||
def test_sibling_selector(selector: str, expected: list[Node]) -> None:
|
||||
assert_count_equal(solution(TEST_TREE, selector), expected)
|
||||
|
||||
|
||||
@pytest.mark.bonus
|
||||
@pytest.mark.parametrize(
|
||||
("selector", "expected"),
|
||||
[
|
||||
(":first-child", [INNER_DIV, H1, SECOND_P]),
|
||||
(":last-child", [SECOND_DIV, A_LINK, SECOND_P]),
|
||||
(":nth-child(1)", [INNER_DIV, H1, SECOND_P]),
|
||||
(":nth-child(2)", [SECOND_DIV, P1]),
|
||||
(":nth-child(3)", [P2]),
|
||||
(":nth-child(4)", [P3]),
|
||||
(":nth-child(5)", [A_LINK]),
|
||||
(":nth-child(6)", []),
|
||||
("p:first-child", [SECOND_P]),
|
||||
("p:last-child", [SECOND_P]),
|
||||
("p:nth-child(1)", [SECOND_P]),
|
||||
("a:first-child", []),
|
||||
("a:last-child", [A_LINK]),
|
||||
("a:nth-child(5)", [A_LINK]),
|
||||
("div:last-child", [SECOND_DIV]),
|
||||
("div.nonexistent:last-child", []),
|
||||
(".colour-primary:first-child", [INNER_DIV, SECOND_P]),
|
||||
],
|
||||
)
|
||||
def test_nth_child_selector(selector: str, expected: list[Node]) -> None:
|
||||
assert_count_equal(solution(TEST_TREE, selector), expected)
|
||||
|
||||
|
||||
@pytest.mark.bonus
|
||||
@pytest.mark.parametrize(
|
||||
("selector", "expected"),
|
||||
[
|
||||
(":not(p)", [TOP_DIV, INNER_DIV, H1, A_LINK, SECOND_DIV]),
|
||||
("p:not(.important)", [P1, P2, SECOND_P]),
|
||||
("div:not(.important)", [TOP_DIV, INNER_DIV, SECOND_DIV]),
|
||||
(":not(.important)", [TOP_DIV, INNER_DIV, H1, P1, P2, A_LINK, SECOND_DIV, SECOND_P]),
|
||||
("div:not(#innerDiv)", [TOP_DIV, SECOND_DIV]),
|
||||
("#innerDiv:not(p)", [INNER_DIV]),
|
||||
("#innerDiv:not(div)", []),
|
||||
("p:not(p)", []),
|
||||
(":not(div):not(p):not(a)", [H1]),
|
||||
(":not(div):not(p):not(a):not(h1)", []),
|
||||
("p:not(.colour-primary:not(#two))", [P1, P2, P3]),
|
||||
("p:not(.colour-secondary:not(#two))", [P2, SECOND_P]),
|
||||
(":not(:not(div))", [TOP_DIV, INNER_DIV, SECOND_DIV]),
|
||||
],
|
||||
)
|
||||
def test_not_selector(selector: str, expected: list[Node]) -> None:
|
||||
assert_count_equal(solution(TEST_TREE, selector), expected)
|
||||
|
||||
|
||||
@pytest.mark.basic
|
||||
@pytest.mark.parametrize(
|
||||
("selector", "expected"),
|
||||
[
|
||||
("q, b", []),
|
||||
],
|
||||
)
|
||||
def test_combined_basic(selector: str, expected: list[Node]) -> None:
|
||||
assert_count_equal(solution(TEST_TREE, selector), expected)
|
||||
|
||||
|
||||
@pytest.mark.bonus
|
||||
@pytest.mark.parametrize(
|
||||
("selector", "expected"),
|
||||
[
|
||||
("p:first-child:not(:last-child)", []),
|
||||
("h1:first-child:not(:last-child)", [H1]),
|
||||
(":first-child:not(:nth-child(2))", [INNER_DIV, SECOND_P, H1]),
|
||||
(":first-child:not(:nth-child(2)):not(h1)", [INNER_DIV, SECOND_P]),
|
||||
(":first-child:not(:nth-child(2), h1)", [INNER_DIV, SECOND_P]),
|
||||
("p:not(p:first-child)", [P1, P2, P3]),
|
||||
("p:not(p:nth-child(1))", [P1, P2, P3]),
|
||||
("p:not(.colour-secondary:not(#two)):not(:first-child)", [P2]),
|
||||
("p:not(div.colour-secondary p)", [P1, P2, P3]),
|
||||
(":not(div, p, a)", [H1]),
|
||||
(":not(div, p, a, h1)", []),
|
||||
(":not(div p)", [TOP_DIV, INNER_DIV, H1, A_LINK, SECOND_DIV]),
|
||||
(".colour-primary > p:not(#two)", [P1, P3]),
|
||||
(".colour-primary p:not(#two)", [P1, P3]),
|
||||
("#topDiv p:not(#two)", [P1, P3, SECOND_P]),
|
||||
("#topDiv > p:not(#two)", []),
|
||||
],
|
||||
)
|
||||
def test_combined_bonus(selector: str, expected: list[Node]) -> None:
|
||||
assert_count_equal(solution(TEST_TREE, selector), expected)
|
||||
|
||||
|
||||
@pytest.mark.basic
|
||||
@pytest.mark.parametrize(
|
||||
("selector", "expected"),
|
||||
[
|
||||
("a,div", [A_LINK, TOP_DIV, INNER_DIV, SECOND_DIV]),
|
||||
("a ,div", [A_LINK, TOP_DIV, INNER_DIV, SECOND_DIV]),
|
||||
("a, div", [A_LINK, TOP_DIV, INNER_DIV, SECOND_DIV]),
|
||||
("a , div", [A_LINK, TOP_DIV, INNER_DIV, SECOND_DIV]),
|
||||
("a , div", [A_LINK, TOP_DIV, INNER_DIV, SECOND_DIV]),
|
||||
("a#home-link , div", [A_LINK, TOP_DIV, INNER_DIV, SECOND_DIV]),
|
||||
("a.button#home-link , div.container", [A_LINK, INNER_DIV, SECOND_DIV]),
|
||||
],
|
||||
)
|
||||
def test_whitespace_basic(selector: str, expected: list[Node]) -> None:
|
||||
assert_count_equal(solution(TEST_TREE, selector), expected)
|
||||
|
||||
|
||||
@pytest.mark.bonus
|
||||
@pytest.mark.parametrize(
|
||||
("selector", "expected"),
|
||||
[
|
||||
("div.container>h1", [H1]),
|
||||
("div.container> h1", [H1]),
|
||||
("div.container >h1", [H1]),
|
||||
("div.container > h1", [H1]),
|
||||
("div.container > h1", [H1]),
|
||||
# ("div#topDiv >div> a", [A_LINK]),
|
||||
# ("div#topDiv >div a", [A_LINK]),
|
||||
# ("div#topDiv >div:not(.colour-secondary) a", [A_LINK]),
|
||||
("div:not(.colour-secondary)", [TOP_DIV, INNER_DIV]),
|
||||
("div:not( .colour-secondary)", [TOP_DIV, INNER_DIV]),
|
||||
("div:not(.colour-secondary )", [TOP_DIV, INNER_DIV]),
|
||||
("div:not( .colour-secondary )", [TOP_DIV, INNER_DIV]),
|
||||
("div:not( .colour-secondary )", [TOP_DIV, INNER_DIV]),
|
||||
# ("div#topDiv >div:not( .colour-secondary ) a", [A_LINK]),
|
||||
],
|
||||
)
|
||||
def test_whitespace_bonus(selector: str, expected: list[Node]) -> None:
|
||||
assert_count_equal(solution(TEST_TREE, selector), expected)
|
Loading…
Add table
Add a link
Reference in a new issue