pydis-qualifier-25/tests/hypot.py
2025-07-27 12:51:35 +02:00

70 lines
2.2 KiB
Python

"""Reusable hypothesis strategies for CSS selectors."""
from typing import cast
from hypothesis import strategies as st
# CSS identifier (used for tag, class, ID names)
css_ident = st.from_regex(r"[_a-zA-Z][_a-zA-Z0-9-]*", fullmatch=True)
css_tag = css_ident
css_class = css_ident.map(lambda x: f".{x}")
css_class_multi = st.lists(css_class, min_size=1).map("".join)
css_id = css_ident.map(lambda x: f"#{x}")
# Any amount of whitespace, including none (spaces only)
_whitespace = st.from_regex(r" *", fullmatch=True)
def simple_selector() -> st.SearchStrategy[str]:
"""Returns a strategy for simple seletors: tag, .class, #id (all optional, at least one)."""
# Optional tag + (one or more classes) + optional id, at least one item present
parts = st.tuples(
st.none() | css_tag,
st.none() | css_class_multi,
st.none() | css_id,
).filter(lambda parts: any(parts))
return parts.map(lambda parts: "".join(p for p in parts if p))
# nth-child pseudo-classes
nth_child_suffix = st.from_regex(r":nth-child\([1-9][0-9]*\)", fullmatch=True)
_specific_child_pseudo_classes = st.sampled_from([":first-child", ":last-child"])
any_nth_child_suffix = st.one_of(nth_child_suffix, _specific_child_pseudo_classes)
def not_selector(inner: st.SearchStrategy[str]) -> st.SearchStrategy[str]:
return st.tuples(st.just(":not("), _whitespace, inner, _whitespace, st.just(")")).map("".join)
def pseudo_suffixes(inner: st.SearchStrategy[str]) -> st.SearchStrategy[str]:
"""Generate a single random pseudo-class suffix."""
return st.one_of(
not_selector(inner),
any_nth_child_suffix,
)
combinator = st.tuples(_whitespace, st.sampled_from([">", " ", "+", ",", "~"]), _whitespace).map("".join)
selector = st.recursive(
# Base: simple selector + pseudo-classes
base=simple_selector(),
extend=lambda s: st.one_of(
# Add combinator + selector sequence
cast(
"st.SearchStrategy[str]",
st.builds(
lambda left, comb, right: left + comb + right,
s,
combinator,
s,
),
),
# Apply pseudo-suffix
st.tuples(s, pseudo_suffixes(s)).map("".join),
),
max_leaves=10,
)