Working on the matches() function
This commit is contained in:
106
src/myfasthtml/core/matcher.py
Normal file
106
src/myfasthtml/core/matcher.py
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
class Predicate:
|
||||||
|
def validate(self, actual):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class StartsWith(Predicate):
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def validate(self, actual):
|
||||||
|
return actual.startswith(self.value)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"StartsWith({self.value})"
|
||||||
|
|
||||||
|
|
||||||
|
class Contains(Predicate):
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def validate(self, actual):
|
||||||
|
return self.value in actual
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Contains({self.value})"
|
||||||
|
|
||||||
|
|
||||||
|
class DoesNotContain(Predicate):
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def validate(self, actual):
|
||||||
|
return self.value not in actual
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"DoesNotContain({self.value})"
|
||||||
|
|
||||||
|
|
||||||
|
def matches(actual, expected, path=""):
|
||||||
|
def print_path(p):
|
||||||
|
return f"Path '{p}':\n\t" if p else ""
|
||||||
|
|
||||||
|
def _type(x):
|
||||||
|
return type(x)
|
||||||
|
|
||||||
|
def _debug(elt):
|
||||||
|
return str(elt) if elt else "None"
|
||||||
|
|
||||||
|
def _debug_compare(a, b):
|
||||||
|
return f"actual={_debug(a)}, expected={_debug(b)}"
|
||||||
|
|
||||||
|
def _error_msg(msg, _actual=None, _expected=None):
|
||||||
|
if _actual is None and _expected is None:
|
||||||
|
debug_info = ""
|
||||||
|
elif _actual is None:
|
||||||
|
debug_info = _debug(_expected)
|
||||||
|
elif _expected is None:
|
||||||
|
debug_info = _debug(_actual)
|
||||||
|
else:
|
||||||
|
debug_info = _debug_compare(_actual, _expected)
|
||||||
|
|
||||||
|
return f"{print_path(path)}\n{msg} : {debug_info}"
|
||||||
|
|
||||||
|
def _assert_error(msg, _actual=None, _expected=None):
|
||||||
|
assert False, _error_msg(msg, _actual=_actual, _expected=_expected)
|
||||||
|
|
||||||
|
if actual is not None and expected is None:
|
||||||
|
_assert_error("Actual is not None while expected is None", _actual=actual)
|
||||||
|
|
||||||
|
if actual is None and expected is not None:
|
||||||
|
_assert_error("Actual is None while expected is ", _expected=expected)
|
||||||
|
|
||||||
|
assert _type(actual) == _type(expected) or (hasattr(actual, "tag") and hasattr(expected, "tag")), \
|
||||||
|
_assert_error("The types are different: ", _actual=actual, _expected=expected)
|
||||||
|
|
||||||
|
if isinstance(expected, (list, tuple)):
|
||||||
|
if len(actual) < len(expected):
|
||||||
|
_assert_error("Actual is smaller than expected: ", _actual=actual, _expected=expected)
|
||||||
|
if len(actual) > len(expected):
|
||||||
|
_assert_error("Actual is bigger than expected: ", _actual=actual, _expected=expected)
|
||||||
|
|
||||||
|
for actual_child, expected_child in zip(actual, expected):
|
||||||
|
assert matches(actual_child, expected_child, path=path)
|
||||||
|
|
||||||
|
elif hasattr(expected, "tag"):
|
||||||
|
assert actual.tag == expected.tag, _error_msg("The elements are different: ",
|
||||||
|
_actual=actual.tag,
|
||||||
|
_expected=expected.tag)
|
||||||
|
|
||||||
|
for expected_attr, expected_value in expected.attrs.items():
|
||||||
|
assert expected_attr in actual.attrs, _error_msg(f"'{expected_attr}' is not found in Actual:",
|
||||||
|
_actual=actual.attrs)
|
||||||
|
|
||||||
|
if isinstance(expected_value, Predicate):
|
||||||
|
assert expected_value.validate(actual.attrs[expected_attr]), \
|
||||||
|
_error_msg(f"The condition '{expected_value}' is not satisfied: ",
|
||||||
|
_actual=actual.attrs[expected_attr],
|
||||||
|
_expected=expected_value)
|
||||||
|
|
||||||
|
else:
|
||||||
|
assert actual.attrs[expected_attr] == expected.attrs[expected_attr], \
|
||||||
|
_error_msg(f"The values are different for '{expected_attr}': ",
|
||||||
|
_actual=actual.attrs[expected_attr],
|
||||||
|
_expected=expected.attrs[expected_attr])
|
||||||
|
|
||||||
|
return True
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import dataclasses
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@@ -15,6 +16,8 @@ from myfasthtml.core.commands import mount_commands
|
|||||||
class MyFT:
|
class MyFT:
|
||||||
tag: str
|
tag: str
|
||||||
attrs: dict
|
attrs: dict
|
||||||
|
children: list['MyFT'] = dataclasses.field(default_factory=list)
|
||||||
|
text: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class TestableElement:
|
class TestableElement:
|
||||||
|
|||||||
41
tests/tests_matches.py
Normal file
41
tests/tests_matches.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import pytest
|
||||||
|
from fasthtml.components import *
|
||||||
|
|
||||||
|
from myfasthtml.core.matcher import matches, StartsWith, Contains, DoesNotContain
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('actual, expected', [
|
||||||
|
(Div(), Div()),
|
||||||
|
(None, None),
|
||||||
|
([Div(), Span()], [Div(), Span()]),
|
||||||
|
(Div(attr1="value"), Div(attr1="value")),
|
||||||
|
(Div(attr1="value", attr2="value"), Div(attr1="value")),
|
||||||
|
(Div(attr1="valueXXX", attr2="value"), Div(attr1=StartsWith("value"))),
|
||||||
|
(Div(attr1="before value after", attr2="value"), Div(attr1=Contains("value"))),
|
||||||
|
(Div(attr1="before after", attr2="value"), Div(attr1=DoesNotContain("value"))),
|
||||||
|
])
|
||||||
|
def test_i_can_match(actual, expected):
|
||||||
|
assert matches(actual, expected)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('actual, expected, error_message', [
|
||||||
|
(None, Div(), "Actual is None"),
|
||||||
|
(Div(), None, "Actual is not None"),
|
||||||
|
(123, Div(), "The types are different:"),
|
||||||
|
([Div(), Span()], [], "Actual is bigger than expected:"),
|
||||||
|
([], [Div(), Span()], "Actual is smaller than expected:"),
|
||||||
|
("not a list", [Div(), Span()], "The types are different:"),
|
||||||
|
([Div(), Span()], [Div(), 123], "The types are different:"),
|
||||||
|
(Div(), Span(), "The elements are different:"),
|
||||||
|
([Div(), Span()], [Div(), Div()], "The elements are different:"),
|
||||||
|
(Div(), Div(attr1="value"), "'attr1' is not found in Actual:"),
|
||||||
|
(Div(attr1="value1"), Div(attr1="value2"), "The values are different for 'attr1':"),
|
||||||
|
(Div(attr1="value1"), Div(attr1=StartsWith("value2")), "The condition 'StartsWith(value2)' is not satisfied:"),
|
||||||
|
(Div(attr1="value1"), Div(attr1=Contains("value2")), "The condition 'Contains(value2)' is not satisfied:"),
|
||||||
|
(Div(attr1="value1 value2"), Div(attr1=DoesNotContain("value2")), "The condition 'DoesNotContain(value2)' is not satisfied:"),
|
||||||
|
|
||||||
|
])
|
||||||
|
def test_i_can_detect_errors(actual, expected, error_message):
|
||||||
|
with pytest.raises(AssertionError) as exc_info:
|
||||||
|
matches(actual, expected)
|
||||||
|
assert error_message in str(exc_info.value)
|
||||||
Reference in New Issue
Block a user