Added Controls testing + documentation

This commit is contained in:
2025-12-05 19:17:21 +01:00
parent 1d20fb8650
commit 8e5fa7f752
37 changed files with 4868 additions and 363 deletions

View File

@@ -1,7 +1,22 @@
import pytest
from fasthtml.components import Div, Span
from fastcore.basics import NotStr
from fasthtml.components import Div, Span, Main
from myfasthtml.test.matcher import find
from myfasthtml.test.matcher import find, TestObject, Contains, StartsWith
class Dummy:
def __init__(self, attr1, attr2=None):
self.attr1 = attr1
self.attr2 = attr2
def __eq__(self, other):
return (isinstance(other, Dummy)
and self.attr1 == other.attr1
and self.attr2 == other.attr2)
def __hash__(self):
return hash((self.attr1, self.attr2))
@pytest.mark.parametrize('ft, expected', [
@@ -9,10 +24,13 @@ from myfasthtml.test.matcher import find
(Div(id="id1"), Div(id="id1")),
(Div(Span(id="span_id"), id="div_id1"), Div(Span(id="span_id"), id="div_id1")),
(Div(id="id1", id2="id2"), Div(id="id1")),
(Div(Div(id="id2"), id2="id1"), Div(id="id1")),
(Div(Div(id="id2"), id="id1"), Div(id="id1")),
(Dummy(attr1="value"), Dummy(attr1="value")),
(Dummy(attr1="value"), TestObject(Dummy, attr1="value")),
(Div(attr="value1 value2"), Div(attr=Contains("value1"))),
])
def test_i_can_find(ft, expected):
assert find(expected, expected) == [expected]
assert find(ft, expected) == [ft]
def test_find_element_by_id_in_a_list():
@@ -25,12 +43,41 @@ def test_find_element_by_id_in_a_list():
def test_i_can_find_sub_element():
a = Div(id="id1")
b = Div(a, id="id2")
c = Div(b, id="id3")
b = Span(a, id="id2")
c = Main(b, id="id3")
assert find(c, a) == [a]
def test_i_can_find_when_pattern_appears_also_in_children():
a1 = Div(id="id1")
b = Div(a1, id="id2")
a2 = Div(b, id="id1")
c = Main(a2, id="id3")
assert find(c, a1) == [a2, a1]
@pytest.mark.parametrize('ft, to_search, expected', [
(NotStr("hello"), NotStr("hello"), [NotStr("hello")]),
(NotStr("hello my friend"), NotStr("hello"), NotStr("hello my friend")),
(NotStr("hello"), TestObject(NotStr, s="hello"), [NotStr("hello")]),
(NotStr("hello my friend"), TestObject(NotStr, s=StartsWith("hello")), NotStr("hello my friend")),
])
def test_i_can_manage_notstr_success_path(ft, to_search, expected):
assert find(ft, to_search) == expected
@pytest.mark.parametrize('ft, to_search', [
(NotStr("my friend"), NotStr("hello")),
(NotStr("hello"), Dummy(attr1="hello")), # important, because of the internal __eq__ of NotStr
(NotStr("hello my friend"), TestObject(NotStr, s="hello")),
])
def test_test_i_can_manage_notstr_failure_path(ft, to_search):
with pytest.raises(AssertionError):
find(ft, to_search)
@pytest.mark.parametrize('ft, expected', [
(None, Div(id="id1")),
(Span(id="id1"), Div(id="id1")),

View File

@@ -2,8 +2,11 @@ import pytest
from fastcore.basics import NotStr
from fasthtml.components import *
from myfasthtml.test.matcher import matches, StartsWith, Contains, DoesNotContain, Empty, DoNotCheck, ErrorOutput, \
ErrorComparisonOutput, AttributeForbidden, AnyValue, NoChildren, TestObject
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.icons.fluent_p3 import add20_regular
from myfasthtml.test.matcher import matches, StartsWith, Contains, DoesNotContain, Empty, ErrorOutput, \
ErrorComparisonOutput, AttributeForbidden, AnyValue, NoChildren, TestObject, Skip, DoNotCheck, TestIcon, HasHtmx
from myfasthtml.test.testclient import MyFT
@@ -50,6 +53,12 @@ class TestMatches:
(Dummy(123, "value"), TestObject(Dummy, attr2="value")),
(Div(Dummy(123, "value")), Div(TestObject(Dummy, attr1=123))),
(Dummy(123, "value"), TestObject("Dummy", attr1=123, attr2="value")),
(mk.icon(add20_regular), TestIcon("Add20Regular")),
(mk.icon(add20_regular), TestIcon("add20_regular")),
(mk.icon(add20_regular), TestIcon()),
(Div(None, None, None, Div(id="to_find")), Div(Skip(None), Div(id="to_find"))),
(Div(Div(id="to_skip"), Div(id="to_skip"), Div(id="to_find")), Div(Skip(Div(id="to_skip")), Div(id="to_find"))),
(Div(hx_post="/url"), Div(HasHtmx(hx_post="/url"))),
])
def test_i_can_match(self, actual, expected):
assert matches(actual, expected)
@@ -63,8 +72,8 @@ class TestMatches:
([], [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(), Span(), "The types are different"),
([Div(), Span()], [Div(), Div()], "The types are different"),
(Div(), Div(attr1="value"), "'attr1' is not found in Actual"),
(Div(attr2="value"), Div(attr1="value"), "'attr1' is not found in Actual"),
(Div(attr1="value1"), Div(attr1="value2"), "The values are different for 'attr1'"),
@@ -80,21 +89,23 @@ class TestMatches:
(Div(Span()), Div(Empty()), "The condition 'Empty()' is not satisfied"),
(Div(), Div(Span()), "Actual is lesser than expected"),
(Div(), Div(123), "Actual is lesser than expected"),
(Div(Span()), Div(Div()), "The elements are different"),
(Div(Span()), Div(Div()), "The types are different"),
(Div(123), Div(Div()), "The types are different"),
(Div(123), Div(456), "The values are different"),
(Div(Span(), Span()), Div(Span(), Div()), "The elements are different"),
(Div(Span(Div())), Div(Span(Span())), "The elements are different"),
(Div(Span(), Span()), Div(Span(), Div()), "The types are different"),
(Div(Span(Div())), Div(Span(Span())), "The types are different"),
(Div(attr1="value1"), Div(AttributeForbidden("attr1")), "condition 'AttributeForbidden(attr1)' is not satisfied"),
(Div(123, "value"), TestObject(Dummy, attr1=123, attr2="value2"), "The types are different:"),
(Div(123, "value"), TestObject(Dummy, attr1=123, attr2="value2"), "The types are different"),
(Dummy(123, "value"), TestObject(Dummy, attr1=123, attr3="value3"), "'attr3' is not found in Actual"),
(Dummy(123, "value"), TestObject(Dummy, attr1=123, attr2="value2"), "The values are different for 'attr2'"),
(Div(Div(123, "value")), Div(TestObject(Dummy, attr1=123, attr2="value2")), "The types are different:"),
(Div(Div(123, "value")), Div(TestObject(Dummy, attr1=123, attr2="value2")), "The types are different"),
(Div(Dummy(123, "value")), Div(TestObject(Dummy, attr1=123, attr3="value3")), "'attr3' is not found in Actual"),
(Div(Dummy(123, "value")), Div(TestObject(Dummy, attr1=123, attr2="value2")), "are different for 'attr2'"),
(Div(123, "value"), TestObject("Dummy", attr1=123, attr2="value2"), "The types are different:"),
(Dummy(123, "value"), TestObject("Dummy", attr1=123, attr2=Contains("value2")), "The condition 'Contains(value2)' is not satisfied"),
(Div(123, "value"), TestObject("Dummy", attr1=123, attr2="value2"), "The types are different"),
(Dummy(123, "value"), TestObject("Dummy", attr1=123, attr2=Contains("value2")),
"The condition 'Contains(value2)' is not satisfied"),
(Div(Div(id="to_skip")), Div(Skip(Div(id="to_skip"))), "Nothing more to skip"),
(Div(hx_post="/url"), Div(HasHtmx(hx_post="/url2")), "The condition 'HasHtmx()' is not satisfied"),
])
def test_i_can_detect_errors(self, actual, expected, error_message):
with pytest.raises(AssertionError) as exc_info:
@@ -440,3 +451,20 @@ Error : The condition 'Contains(value2)' is not satisfied.
assert "\n" + res == '''
(div "attr1"="123" "attr2"="value2") | (Dummy "attr1"="123" "attr2"="value2")
^^^ |'''
class TestPredicates:
def test_i_can_validate_contains_with_words_only(self):
assert Contains("value", _word=True).validate("value value2 value3")
assert Contains("value", "value2", _word=True).validate("value value2 value3")
assert not Contains("value", _word=True).validate("valuevalue2value3")
assert not Contains("value value2", _word=True).validate("value value2 value3")
def test_i_can_validate_has_htmx(self):
div = Div(hx_post="/url")
assert HasHtmx(hx_post="/url").validate(div)
c = Command("c", "testing has_htmx", None)
c.bind_ft(div)
assert HasHtmx(command=c).validate(div)