Added TestableCheckbox

This commit is contained in:
2025-10-31 21:49:12 +01:00
parent 3721bb7ad7
commit 991a6f07ff
12 changed files with 306 additions and 18 deletions

View File

@@ -0,0 +1,15 @@
.mf-icon-20 {
width: 20px;
min-width: 20px;
height: 20px;
margin-top: auto;
margin-bottom: auto;
}
.mf-icon-16 {
width: 16px;
min-width: 16px;
height: 16px;
margin-top: auto;
margin-bottom: 4px;
}

View File

@@ -0,0 +1,33 @@
from fasthtml.components import *
from myfasthtml.core.commands import Command
from myfasthtml.core.utils import merge_classes
class mk:
@staticmethod
def button(element, command: Command = None, **kwargs):
return mk.manage_command(Button(element, **kwargs), command)
@staticmethod
def icon(icon, size=20,
can_select=True,
can_hover=False,
cls='',
command: Command = None,
**kwargs):
merged_cls = merge_classes(f"mf-icon-{size}",
'icon-btn' if can_select else '',
'mmt-btn' if can_hover else '',
cls,
kwargs)
return mk.manage_command(Div(icon, cls=merged_cls, **kwargs), command)
@staticmethod
def manage_command(ft, command: Command):
htmx = command.get_htmx_params() if command else {}
ft.attrs |= htmx
return ft

View File

View File

@@ -0,0 +1,26 @@
from fasthtml import serve
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.myfastapp import create_app
# Define a simple command action
def say_hello():
return "Hello, FastHtml!"
# Create the command
hello_command = Command("say_hello", "Responds with a greeting", say_hello)
# Create the app
app, rt = create_app(protect_routes=False)
@rt("/")
def get_homepage():
return mk.button("Click Me!", command=hello_command)
if __name__ == "__main__":
serve(port=5002)

View File

@@ -0,0 +1,25 @@
from fasthtml import serve
from fasthtml.components import *
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.icons.fa import icon_home
from myfasthtml.myfastapp import create_app
app, rt = create_app(protect_routes=False)
def change_text():
return "New text"
command = Command("change_text", "change the text", change_text).htmx(target="#text")
@rt("/")
def index():
return mk.button(Div(mk.icon(icon_home), Div("Hello World", id="text"), cls="flex"), command=command)
if __name__ == "__main__":
serve(port=5002)

View File

@@ -0,0 +1,15 @@
from fasthtml import serve
from fasthtml.components import *
from myfasthtml.myfastapp import create_app
app, rt = create_app(protect_routes=False)
@rt("/")
def get_homepage():
return Div("Hello, FastHtml!")
if __name__ == "__main__":
serve(port=5002)

View File

View File

@@ -345,10 +345,6 @@ class TestableElement:
elif options:
self.fields[name] = options[0]['value']
def _get_my_ft(self, element: Tag):
_inner = element.find(self.tag) if self.tag and self.tag != element.name else element
return MyFT(_inner.name, _inner.attrs)
@staticmethod
def _get_input_identifier(input_field, counter):
"""
@@ -432,14 +428,6 @@ class TestableElement:
# Default to string
return value
@staticmethod
def _get_element(html_fragment: str):
html_fragment = html_fragment.strip()
if (not html_fragment.startswith('<div') and
not html_fragment.startswith('<form')):
html_fragment = "<div>" + html_fragment + "</div>"
return BeautifulSoup(html_fragment, 'html.parser').find()
@staticmethod
def _parse(tag, html_fragment: str):
elt = BeautifulSoup(html_fragment, 'html.parser')
@@ -801,8 +789,8 @@ class TestableForm(TestableElement):
# return value
class TestableInput(TestableElement):
def __init__(self, client, source):
class TestableControl(TestableElement):
def __init__(self, client, source, tag):
super().__init__(client, source, "input")
assert len(self.fields) <= 1
self._input_name = next(iter(self.fields))
@@ -815,12 +803,40 @@ class TestableInput(TestableElement):
def value(self):
return self.fields[self._input_name]
def _send_value(self):
if self._input_name and self._support_htmx():
return self._send_htmx_request(data={self._input_name: self.value})
return None
class TestableInput(TestableControl):
def __init__(self, client, source):
super().__init__(client, source, "input")
def send(self, value):
self.fields[self.name] = value
if self.name and self._support_htmx():
return self._send_htmx_request(data={self.name: self.value})
return self._send_value()
return None
class TestableCheckbox(TestableControl):
def __init__(self, client, source):
super().__init__(client, source, "input")
@property
def is_checked(self):
return self.fields[self._input_name] == True
def check(self):
self.fields[self._input_name] = True
return self._send_value()
def uncheck(self):
self.fields[self._input_name] = False
return self._send_value()
def toggle(self):
self.fields[self._input_name] = not self.fields[self._input_name]
return self._send_value()
# def get_value(tag):

View File

View File

@@ -0,0 +1,48 @@
import pytest
from fasthtml.components import *
from fasthtml.fastapp import fast_app
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.test.matcher import matches
from myfasthtml.test.testclient import MyTestClient
@pytest.fixture()
def user():
test_app, rt = fast_app(default_hdrs=False)
user = MyTestClient(test_app)
return user
@pytest.fixture()
def rt(user):
return user.app.route
def test_i_can_mk_button():
button = mk.button('button')
expected = Button('button')
assert matches(button, expected)
def test_i_can_mk_button_with_attrs():
button = mk.button('button', id="button_id", class_="button_class")
expected = Button('button', id="button_id", class_="button_class")
assert matches(button, expected)
def test_i_can_mk_button_with_command(user, rt):
def new_value(value): return value
command = Command('test', 'TestingCommand', new_value, "this is my new value")
@rt('/')
def get(): return mk.button('button', command)
user.open("/")
user.should_see("button")
user.find_element("button").click()
user.should_see("this is my new value")

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):
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=False"
input_elt.toggle()
assert test_client.get_content() == "Checkbox received male=True"

View File

@@ -0,0 +1,51 @@
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'"