First implementation of bindings

This commit is contained in:
2025-11-09 19:23:18 +01:00
parent b5c1c15198
commit 86dfff812b
51 changed files with 5971 additions and 1080 deletions

View File

@@ -0,0 +1,358 @@
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
from myfasthtml.test.testclient import MyFT
@pytest.mark.parametrize('actual, expected', [
(None, None),
(123, 123),
(Div(), Div()),
([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"))),
(Div(attr1="value"), Div(attr1=AnyValue())),
(None, DoNotCheck()),
(123, DoNotCheck()),
(Div(), DoNotCheck()),
([Div(), Span()], DoNotCheck()),
(NotStr("123456"), NotStr("123")), # for NotStr, only the beginning is checked
(Div(), Div(Empty())),
(Div(attr1="value1"), Div(AttributeForbidden("attr2"))),
(Div(123), Div(123)),
(Div(Span(123)), Div(Span(123))),
(Div(Span(123)), Div(DoNotCheck())),
])
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"),
(123, 124, "The values 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(attr2="value"), 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)'"),
(Div(attr1=None), Div(attr1=AnyValue()), "'attr1' is not found in Actual"),
(Div(), Div(attr1=AnyValue()), "'attr1' is not found in Actual"),
(NotStr("456"), NotStr("123"), "Notstr values are different"),
(Div(attr="value"), Div(Empty()), "The condition 'Empty()' is not satisfied"),
(Div(120), Div(Empty()), "The condition 'Empty()' is not satisfied"),
(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(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(attr1="value1"), Div(AttributeForbidden("attr1")), "condition 'AttributeForbidden(attr1)' 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)
@pytest.mark.parametrize('element, expected_path', [
(Div(), "Path : 'div"),
(Div(Span()), "Path : 'div.span"),
(Div(Span(Div())), "Path : 'div.span.div"),
(Div(id="div_id"), "Path : 'div#div_id"),
(Div(cls="div_class"), "Path : 'div[class=div_class]"),
(Div(name="div_class"), "Path : 'div[name=div_class]"),
(Div(attr="value"), "Path : 'div"),
(Div(Span(Div(), cls="span_class"), id="div_id"), "Path : 'div#div_id.span[class=span_class].div"),
])
def test_i_can_properly_show_path(element, expected_path):
def _construct_test_element(source, tail):
res = MyFT(source.tag, source.attrs)
if source.children:
res.children = [_construct_test_element(child, tail) for child in source.children]
else:
res.children = [tail]
return res
with pytest.raises(AssertionError) as exc_info:
actual = _construct_test_element(element, "Actual")
expected = _construct_test_element(element, "Expected")
matches(actual, expected)
assert expected_path in str(exc_info.value)
def test_i_can_output_error_path():
elt = Div()
expected = Div()
path = "div#div_id.div.span[class=span_class].p[name=p_name].div"
error_output = ErrorOutput(path, elt, expected)
error_output.compute()
assert error_output.output == ['(div "id"="div_id" ...',
' (div ...',
' (span "class"="span_class" ...',
' (p "name"="p_name" ...',
' (div )']
def test_i_can_output_error_attribute():
elt = Div(attr1="value1", attr2="value2")
expected = elt
path = ""
error_output = ErrorOutput(path, elt, expected)
error_output.compute()
assert error_output.output == ['(div "attr1"="value1" "attr2"="value2")']
def test_i_can_output_error_attribute_missing_1():
elt = Div(attr2="value2")
expected = Div(attr1="value1", attr2="value2")
path = ""
error_output = ErrorOutput(path, elt, expected)
error_output.compute()
assert error_output.output == ['(div "attr1"="** MISSING **" "attr2"="value2")',
' ^^^^^^^^^^^^^^^^^^^^^^^ ']
def test_i_can_output_error_attribute_missing_2():
elt = Div(attr1="value1")
expected = Div(attr1="value1", attr2="value2")
path = ""
error_output = ErrorOutput(path, elt, expected)
error_output.compute()
assert error_output.output == ['(div "attr1"="value1" "attr2"="** MISSING **")',
' ^^^^^^^^^^^^^^^^^^^^^^^']
def test_i_can_output_error_attribute_wrong_value():
elt = Div(attr1="value3", attr2="value2")
expected = Div(attr1="value1", attr2="value2")
path = ""
error_output = ErrorOutput(path, elt, expected)
error_output.compute()
assert error_output.output == ['(div "attr1"="value3" "attr2"="value2")',
' ^^^^^^^^^^^^^^^^ ']
def test_i_can_output_error_constant():
elt = 123
expected = elt
path = ""
error_output = ErrorOutput(path, elt, expected)
error_output.compute()
assert error_output.output == ['123']
def test_i_can_output_error_constant_wrong_value():
elt = 123
expected = 456
path = ""
error_output = ErrorOutput(path, elt, expected)
error_output.compute()
assert error_output.output == ['123',
'^^^']
def test_i_can_output_error_when_predicate():
elt = "before value after"
expected = Contains("value")
path = ""
error_output = ErrorOutput(path, elt, expected)
error_output.compute()
assert error_output.output == ["before value after"]
def test_i_can_output_error_when_predicate_wrong_value():
"""I can display error when the condition predicate is not satisfied."""
elt = "before after"
expected = Contains("value")
path = ""
error_output = ErrorOutput(path, elt, expected)
error_output.compute()
assert error_output.output == ["before after",
"^^^^^^^^^^^^"]
def test_i_can_output_error_child_element():
"""I can display error when the element has children"""
elt = Div(P(id="p_id"), Div(id="child_1"), Div(id="child_2"), attr1="value1")
expected = elt
path = ""
error_output = ErrorOutput(path, elt, expected)
error_output.compute()
assert error_output.output == ['(div "attr1"="value1"',
' (p "id"="p_id")',
' (div "id"="child_1")',
' (div "id"="child_2")',
')',
]
def test_i_can_output_error_child_element_text():
"""I can display error when the children is not a FT"""
elt = Div("Hello world", Div(id="child_1"), Div(id="child_2"), attr1="value1")
expected = elt
path = ""
error_output = ErrorOutput(path, elt, expected)
error_output.compute()
assert error_output.output == ['(div "attr1"="value1"',
' "Hello world"',
' (div "id"="child_1")',
' (div "id"="child_2")',
')',
]
def test_i_can_output_error_child_element_indicating_sub_children():
elt = Div(P(id="p_id"), Div(Div(id="child_2"), id="child_1"), attr1="value1")
expected = elt
path = ""
error_output = ErrorOutput(path, elt, expected)
error_output.compute()
assert error_output.output == ['(div "attr1"="value1"',
' (p "id"="p_id")',
' (div "id"="child_1" ...)',
')',
]
def test_i_can_output_error_child_element_wrong_value():
elt = Div(P(id="p_id"), Div(id="child_2"), attr1="value1")
expected = Div(P(id="p_id"), Div(id="child_1"), attr1="value1")
path = ""
error_output = ErrorOutput(path, elt, expected)
error_output.compute()
assert error_output.output == ['(div "attr1"="value1"',
' (p "id"="p_id")',
' (div "id"="child_2")',
' ^^^^^^^^^^^^^^',
')',
]
def test_i_can_output_error_fewer_elements():
elt = Div(P(id="p_id"), attr1="value1")
expected = Div(P(id="p_id"), Div(id="child_1"), attr1="value1")
path = ""
error_output = ErrorOutput(path, elt, expected)
error_output.compute()
assert error_output.output == ['(div "attr1"="value1"',
' (p "id"="p_id")',
' ! ** MISSING ** !',
')',
]
def test_i_can_output_comparison():
actual = Div(P(id="p_id"), attr1="value1")
expected = actual
actual_out = ErrorOutput("", actual, expected)
expected_out = ErrorOutput("", expected, expected)
comparison_out = ErrorComparisonOutput(actual_out, expected_out)
res = comparison_out.render()
assert "\n" + res == '''
(div "attr1"="value1" | (div "attr1"="value1"
(p "id"="p_id") | (p "id"="p_id")
) | )'''
def test_i_can_output_comparison_with_path():
actual = Div(P(id="p_id"), attr1="value1")
expected = actual
actual_out = ErrorOutput("div#div_id.span[class=cls].div", actual, expected)
expected_out = ErrorOutput("div#div_id.span[class=cls].div", expected, expected)
comparison_out = ErrorComparisonOutput(actual_out, expected_out)
res = comparison_out.render()
assert "\n" + res == '''
(div "id"="div_id" ... | (div "id"="div_id" ...
(span "class"="cls" ... | (span "class"="cls" ...
(div "attr1"="value1" | (div "attr1"="value1"
(p "id"="p_id") | (p "id"="p_id")
) | )'''
def test_i_can_output_comparison_when_missing_attributes():
actual = Div(P(id="p_id"), attr1="value1")
expected = Div(P(id="p_id"), attr2="value1")
actual_out = ErrorOutput("", actual, expected)
expected_out = ErrorOutput("", expected, expected)
comparison_out = ErrorComparisonOutput(actual_out, expected_out)
res = comparison_out.render()
assert "\n" + res == '''
(div "attr2"="** MISSING **" | (div "attr2"="value1"
^^^^^^^^^^^^^^^^^^^^^^^ |
(p "id"="p_id") | (p "id"="p_id")
) | )'''
def test_i_can_output_comparison_when_wrong_attributes():
actual = Div(P(id="p_id"), attr1="value2")
expected = Div(P(id="p_id"), attr1="value1")
actual_out = ErrorOutput("", actual, expected)
expected_out = ErrorOutput("", expected, expected)
comparison_out = ErrorComparisonOutput(actual_out, expected_out)
res = comparison_out.render()
assert "\n" + res == '''
(div "attr1"="value2" | (div "attr1"="value1"
^^^^^^^^^^^^^^^^ |
(p "id"="p_id") | (p "id"="p_id")
) | )'''
def test_i_can_output_comparison_when_fewer_elements():
actual = Div(P(id="p_id"), attr1="value1")
expected = Div(Span(id="s_id"), P(id="p_id"), attr1="value1")
actual_out = ErrorOutput("", actual, expected)
expected_out = ErrorOutput("", expected, expected)
comparison_out = ErrorComparisonOutput(actual_out, expected_out)
res = comparison_out.render()
assert "\n" + res == '''
(div "attr1"="value1" | (div "attr1"="value1"
(p "id"="p_id") | (span "id"="s_id")
^ ^^^^^^^^^^^ |
! ** MISSING ** ! | (p "id"="p_id")
) | )'''
def test_i_can_see_the_diff_when_matching():
actual = Div(attr1="value1")
expected = Div(attr1=Contains("value2"))
with pytest.raises(AssertionError) as exc_info:
matches(actual, expected)
debug_output = str(exc_info.value)
assert "\n" + debug_output == """
Path : 'div'
Error : The condition 'Contains(value2)' is not satisfied.
(div "attr1"="value1") | (div "attr1"="Contains(value2)")
^^^^^^^^^^^^^^^^ |"""

View File

@@ -2,7 +2,7 @@ import pytest
from fasthtml.components import Div
from fasthtml.fastapp import fast_app
from myfasthtml.core.testclient import MyTestClient, TestableElement, TestableForm
from myfasthtml.test.testclient import MyTestClient, TestableElement, TestableForm
class TestMyTestClientOpen:
@@ -481,4 +481,3 @@ class TestMyTestClientFindForm:
error_message = str(exc_info.value)
assert "Found 2 forms (with the specified fields). Expected exactly 1." in error_message

View File

@@ -0,0 +1,89 @@
from dataclasses import dataclass
import pytest
from fasthtml.fastapp import fast_app
from myfasthtml.test.testclient import MyTestClient, TestableRadio
@dataclass
class Data:
value: str = "hello world"
@pytest.fixture
def test_app():
test_app, rt = fast_app(default_hdrs=False)
return test_app
@pytest.fixture
def rt(test_app):
return test_app.route
@pytest.fixture
def test_client(test_app):
return MyTestClient(test_app)
def test_i_can_read_not_selected_radio(test_client):
html = '''<input type="radio" name="radio_name" value="option1" />'''
input_elt = TestableRadio(test_client, html)
assert input_elt.name == "radio_name"
assert input_elt.value is None
def test_i_can_read_selected_radio(test_client):
html = '''<input type="radio" name="radio_name" value="option1" checked="true"/>'''
input_elt = TestableRadio(test_client, html)
assert input_elt.name == "radio_name"
assert input_elt.value == "option1"
def test_i_cannot_read_radio_with_multiple_values(test_client):
html = '''
<input type="radio" name="radio_name" value="option1" checked="true" />
<input type="radio" name="radio_name" value="option2" />
'''
with pytest.raises(AssertionError) as exc_info:
TestableRadio(test_client, html)
assert "Only one radio button per name is supported" in str(exc_info.value)
def test_i_cannot_read_radio_when_no_radio_button(test_client):
html = '''
<input type="text" name="radio_name" value="option1" checked="true" /> '''
with pytest.raises(AssertionError) as exc_info:
TestableRadio(test_client, html)
assert "No radio buttons found" in str(exc_info.value)
def test_i_can_read_input_with_label(test_client):
html = '''<label for="uid">John Doe</label><input id="uid" type="radio" name="username" value="john_doe" />'''
input_elt = TestableRadio(test_client, html)
assert input_elt.fields_mapping == {"John Doe": "username"}
assert input_elt.name == "username"
assert input_elt.value is None
def test_i_can_send_values(test_client, rt):
html = '''<input type="text" name="username" type="radio" value="john_doe" hx_post="/submit"/>'''
@rt('/submit')
def post(username: str):
return f"Input received {username=}"
input_elt = TestableRadio(test_client, html)
input_elt.select()
assert test_client.get_content() == "Input received username='john_doe'"

View File

@@ -0,0 +1,165 @@
"""
Comprehensive binding tests for all bindable FastHTML components.
This test suite covers:
- Input (text) - already tested
- Checkbox - already tested
- Textarea
- Select (single)
- Select (multiple)
- Range (slider)
- Radio buttons
- Button
- Input with Datalist (combobox)
"""
from dataclasses import dataclass
import pytest
from fasthtml.components import (
Input, Label, Textarea
)
from fasthtml.fastapp import fast_app
from myfasthtml.controls.helpers import mk
from myfasthtml.core.bindings import Binding
from myfasthtml.test.testclient import MyTestClient
@dataclass
class Data:
value: str = "hello world"
@dataclass
class NumericData:
value: int = 50
@dataclass
class BoolData:
value: bool = True
@dataclass
class ListData:
value: list = None
def __post_init__(self):
if self.value is None:
self.value = []
@pytest.fixture
def test_app():
test_app, rt = fast_app(default_hdrs=False)
return test_app
@pytest.fixture
def rt(test_app):
return test_app.route
@pytest.fixture
def user(test_app):
return MyTestClient(test_app)
class TestBindingEdgeCases:
"""Tests for edge cases and special scenarios."""
def test_multiple_components_bind_to_same_data(self, user, rt):
"""
Multiple different components can bind to the same data object.
"""
@rt("/")
def index():
data = Data("synchronized")
input_elt = Input(name="input_name")
textarea_elt = Textarea(name="textarea_name")
label_elt = Label()
mk.manage_binding(input_elt, Binding(data))
mk.manage_binding(textarea_elt, Binding(data))
mk.manage_binding(label_elt, Binding(data))
return input_elt, textarea_elt, label_elt
user.open("/")
user.should_see("synchronized")
# Change via input
testable_input = user.find_element("input")
testable_input.send("changed via input")
user.should_see("changed via input")
# Change via textarea
testable_textarea = user.find_element("textarea")
testable_textarea.send("changed via textarea")
user.should_see("changed via textarea")
def test_component_without_name_attribute(self, user, rt):
"""
Component without name attribute should handle gracefully.
"""
@rt("/")
def index():
data = Data("test")
# Input without name - should not crash
input_elt = Input() # No name attribute
label_elt = Label()
mk.manage_binding(label_elt, Binding(data))
return input_elt, label_elt
user.open("/")
user.should_see("test")
def test_binding_with_initial_empty_string(self, user, rt):
"""
Binding should work correctly with empty string initial values.
"""
@rt("/")
def index():
data = Data("")
input_elt = Input(name="input_name")
label_elt = Label()
mk.manage_binding(input_elt, Binding(data))
mk.manage_binding(label_elt, Binding(data))
return input_elt, label_elt
user.open("/")
testable_input = user.find_element("input")
testable_input.send("now has value")
user.should_see("now has value")
def test_binding_with_special_characters(self, user, rt):
"""
Binding should handle special characters correctly.
"""
@rt("/")
def index():
data = Data("Hello")
input_elt = Input(name="input_name")
label_elt = Label()
mk.manage_binding(input_elt, Binding(data))
mk.manage_binding(label_elt, Binding(data))
return input_elt, label_elt
user.open("/")
testable_input = user.find_element("input")
testable_input.send("Special: <>&\"'")
user.should_see("Special: <>&\"'")

View File

@@ -0,0 +1,59 @@
import pytest
from fasthtml.fastapp import fast_app
from myfasthtml.test.testclient import MyTestClient, TestableCheckbox
@pytest.fixture
def test_app():
test_app, rt = fast_app(default_hdrs=False)
return test_app
@pytest.fixture
def rt(test_app):
return test_app.route
@pytest.fixture
def test_client(test_app):
return MyTestClient(test_app)
@pytest.mark.parametrize("html,expected_value", [
('<input type="checkbox" name="male" checked />', True),
('<input type="checkbox" name="male" />', False),
])
def test_i_can_read_input(test_client, html, expected_value):
input_elt = TestableCheckbox(test_client, html)
assert input_elt.name == "male"
assert input_elt.value == expected_value
def test_i_can_read_input_with_label(test_client):
html = '''<label for="uid">Male</label><input id="uid" type="checkbox" name="male" checked />'''
input_elt = TestableCheckbox(test_client, html)
assert input_elt.fields_mapping == {"Male": "male"}
assert input_elt.name == "male"
assert input_elt.value == True
def test_i_can_check_checkbox(test_client, rt):
html = '''<input type="checkbox" name="male" hx_post="/submit"/>'''
@rt('/submit')
def post(male: bool=None):
return f"Checkbox received {male=}"
input_elt = TestableCheckbox(test_client, html)
input_elt.check()
assert test_client.get_content() == "Checkbox received male=True"
input_elt.uncheck()
assert test_client.get_content() == "Checkbox received male=None"
input_elt.toggle()
assert test_client.get_content() == "Checkbox received male=True"

View File

@@ -2,14 +2,14 @@ import pytest
from fasthtml.components import Div
from fasthtml.fastapp import fast_app
from myfasthtml.core.testclient import MyTestClient, TestableElement, MyFT
from myfasthtml.test.testclient import MyTestClient, TestableElement, MyFT
def test_i_can_create_testable_element_from_ft():
ft = Div("hello world", id="test")
testable_element = TestableElement(None, ft)
assert testable_element.ft == ft
assert testable_element.my_ft == MyFT('div', {'id': 'test'})
assert testable_element.html_fragment == '<div id="test">hello world</div>'
@@ -17,7 +17,7 @@ def test_i_can_create_testable_element_from_str():
ft = '<div id="test">hello world</div>'
testable_element = TestableElement(None, ft)
assert testable_element.ft == MyFT('div', {'id': 'test'})
assert testable_element.my_ft == MyFT('div', {'id': 'test'})
assert testable_element.html_fragment == '<div id="test">hello world</div>'
@@ -27,7 +27,7 @@ def test_i_can_create_testable_element_from_beautifulsoup_element():
tag = BeautifulSoup(ft, 'html.parser').div
testable_element = TestableElement(None, tag)
assert testable_element.ft == MyFT('div', {'id': 'test'})
assert testable_element.my_ft == MyFT('div', {'id': 'test'})
assert testable_element.html_fragment == '<div id="test">hello world</div>'

View File

@@ -1,7 +1,7 @@
import pytest
from fasthtml.fastapp import fast_app
from myfasthtml.core.testclient import TestableForm, MyTestClient
from myfasthtml.test.testclient import TestableForm, MyTestClient
@pytest.fixture
@@ -268,8 +268,7 @@ class TestableFormUpdateFieldValues:
'''
form = TestableForm(mock_client, html)
assert "size" not in form.fields, \
f"Expected 'size' not in fields, got {form.fields}"
assert form.fields == {"size": None}, f"Expected 'size' not in fields, got {form.fields}"
def test_i_can_handle_number_input_with_integer(self, mock_client):
"""

View File

@@ -0,0 +1,58 @@
import pytest
from fasthtml.fastapp import fast_app
from myfasthtml.test.testclient import TestableInput, MyTestClient
@pytest.fixture
def test_app():
test_app, rt = fast_app(default_hdrs=False)
return test_app
@pytest.fixture
def rt(test_app):
return test_app.route
@pytest.fixture
def test_client(test_app):
return MyTestClient(test_app)
def test_i_can_read_input(test_client):
html = '''<input type="text" name="username" value="john_doe" />'''
input_elt = TestableInput(test_client, html)
assert input_elt.name == "username"
assert input_elt.value == "john_doe"
def test_i_can_read_input_with_label(test_client):
html = '''<label for="uid">Username</label><input id="uid" name="username" value="john_doe" />'''
input_elt = TestableInput(test_client, html)
assert input_elt.fields_mapping == {"Username": "username"}
assert input_elt.name == "username"
assert input_elt.value == "john_doe"
def test_i_can_send_values(test_client, rt):
html = '''<input type="text" name="username" value="john_doe" hx_post="/submit"/>'''
@rt('/submit')
def post(username: str):
return f"Input received {username=}"
input_elt = TestableInput(test_client, html)
input_elt.send("another name")
assert test_client.get_content() == "Input received username='another name'"
def i_can_find_input_by_name(test_client):
html = '''<label for="uid">Username</label><input id="uid" name="username" value="john_doe" />'''
element = test_client.find_input("Username")
assert False

View File

@@ -0,0 +1,72 @@
from dataclasses import dataclass
import pytest
from fasthtml.fastapp import fast_app
from myfasthtml.test.testclient import MyTestClient, TestableRange
@dataclass
class Data:
value: str = "hello world"
@pytest.fixture
def test_app():
test_app, rt = fast_app(default_hdrs=False)
return test_app
@pytest.fixture
def rt(test_app):
return test_app.route
@pytest.fixture
def test_client(test_app):
return MyTestClient(test_app)
def test_i_can_read_range(test_client):
html = '''<input type="range" name="range_name" min="0" max="100" step="10" value="50" />'''
input_elt = TestableRange(test_client, html)
assert input_elt.name == "range_name"
assert input_elt.value == 50
assert input_elt.min_value == 0
assert input_elt.max_value == 100
assert input_elt.step == 10
@pytest.mark.parametrize("value, expected", [
(30, 30),
(24, 20), # step 10
(-10, 0), # min 0
(110, 100), # max 100
])
def test_i_can_set_value(test_client, value, expected):
html = '''<input type="range" name="range_name" min="0" max="100" step="10" value="50" />'''
input_elt = TestableRange(test_client, html)
input_elt.set(value)
assert input_elt.value == expected
def test_i_can_increase_value(test_client):
html = '''<input type="range" name="range_name" min="0" max="100" step="10" value="50" />'''
input_elt = TestableRange(test_client, html)
input_elt.increase()
assert input_elt.value == 60
def test_i_can_decrease_value(test_client):
html = '''<input type="range" name="range_name" min="0" max="100" step="10" value="50" />'''
input_elt = TestableRange(test_client, html)
input_elt.decrease()
assert input_elt.value == 40

View File

@@ -0,0 +1,63 @@
import pytest
from fasthtml.fastapp import fast_app
from myfasthtml.test.testclient import TestableSelect, MyTestClient
@pytest.fixture
def test_app():
test_app, rt = fast_app(default_hdrs=False)
return test_app
@pytest.fixture
def rt(test_app):
return test_app.route
@pytest.fixture
def test_client(test_app):
return MyTestClient(test_app)
def test_i_can_read_select(test_client):
html = '''<select name="select_name">
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
'''
select_elt = TestableSelect(test_client, html)
assert select_elt.name == "select_name"
assert select_elt.value == "option1" # if no selected found, the first option is selected by default
assert select_elt.options == [{'text': 'Option 1', 'value': 'option1'},
{'text': 'Option 2', 'value': 'option2'},
{'text': 'Option 3', 'value': 'option3'}]
assert select_elt.select_fields == {'select_name': [{'text': 'Option 1', 'value': 'option1'},
{'text': 'Option 2', 'value': 'option2'},
{'text': 'Option 3', 'value': 'option3'}]}
assert select_elt.is_multiple is False
def test_i_can_select_option(test_client):
html = '''<select name="select_name">
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
'''
select_elt = TestableSelect(test_client, html)
select_elt.select("option2")
assert select_elt.value == "option2"
def test_i_can_select_by_text(test_client):
html = '''<select name="select_name">
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
'''
select_elt = TestableSelect(test_client, html)
select_elt.select_by_text("Option 3")
assert select_elt.value == "option3"

View File

@@ -0,0 +1,107 @@
import pytest
from fasthtml.fastapp import fast_app
from myfasthtml.test.testclient import TestableSelect, MyTestClient
@pytest.fixture
def test_app():
test_app, rt = fast_app(default_hdrs=False)
return test_app
@pytest.fixture
def rt(test_app):
return test_app.route
@pytest.fixture
def test_client(test_app):
return MyTestClient(test_app)
def test_i_can_read_select(test_client):
html = '''<select name="select_name" multiple>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
'''
select_elt = TestableSelect(test_client, html)
assert select_elt.name == "select_name"
assert select_elt.value == []
assert select_elt.options == [{'text': 'Option 1', 'value': 'option1'},
{'text': 'Option 2', 'value': 'option2'},
{'text': 'Option 3', 'value': 'option3'}]
assert select_elt.select_fields == {'select_name': [{'text': 'Option 1', 'value': 'option1'},
{'text': 'Option 2', 'value': 'option2'},
{'text': 'Option 3', 'value': 'option3'}]}
assert select_elt.is_multiple is True
def test_i_can_read_select_with_multiple_selected_values(test_client):
html = '''<select name="select_name" multiple>
<option value="option1" selected>Option 1</option>
<option value="option2">Option 2</option>
<option value="option3" selected>Option 3</option>
</select>
'''
select_elt = TestableSelect(test_client, html)
assert select_elt.name == "select_name"
assert select_elt.value == ["option1", "option3"]
assert select_elt.is_multiple is True
def test_i_can_select_option(test_client):
html = '''<select name="select_name" multiple>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
'''
select_elt = TestableSelect(test_client, html)
select_elt.select("option2")
assert select_elt.value == "option2"
def test_i_can_select_multiple_options(test_client):
html = '''<select name="select_name" multiple>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
'''
select_elt = TestableSelect(test_client, html)
select_elt.select("option2")
select_elt.select("option3")
assert select_elt.value == ["option2", "option3"]
def test_i_can_select_by_text(test_client):
html = '''<select name="select_name" multiple>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
'''
select_elt = TestableSelect(test_client, html)
select_elt.select_by_text("Option 3")
assert select_elt.value == "option3"
def test_i_can_deselect(test_client):
html = '''<select name="select_name" multiple>
<option value="option1" selected>Option 1</option>
<option value="option2" selected>Option 2</option>
<option value="option3" selected>Option 3</option>
</select>
'''
select_elt = TestableSelect(test_client, html)
select_elt.deselect("option3")
assert select_elt.value == ["option1", "option2"]
select_elt.deselect("option2")
assert select_elt.value == "option1"
select_elt.deselect("option1")
assert select_elt.value == []

View File

@@ -0,0 +1,36 @@
from dataclasses import dataclass
import pytest
from fasthtml.fastapp import fast_app
from myfasthtml.test.testclient import MyTestClient, TestableTextarea
@dataclass
class Data:
value: str = "hello world"
@pytest.fixture
def test_app():
test_app, rt = fast_app(default_hdrs=False)
return test_app
@pytest.fixture
def rt(test_app):
return test_app.route
@pytest.fixture
def test_client(test_app):
return MyTestClient(test_app)
def test_i_can_read_input(test_client):
html = '''<textarea name="textarea_name">Lorem ipsum</textarea>'''
input_elt = TestableTextarea(test_client, html)
assert input_elt.name == "textarea_name"
assert input_elt.value == "Lorem ipsum"