import pytest
from fasthtml.fastapp import fast_app

from myfasthtml.test.testclient import TestableForm, MyTestClient


@pytest.fixture
def mock_client():
  """Mock client for testing purposes."""
  return None


class TestableFormUpdateFieldMapping:
  def test_i_can_map_label_with_explicit_for_attribute(self, mock_client):
    """
    Test that labels with explicit 'for' attribute are correctly mapped.
  
    This is the most reliable association method (priority 1).
    """
    html = '<form><label for="uid">Username</label><input id="uid" name="username" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields_mapping == {"Username": "username"}
  
  def test_i_can_map_label_containing_input(self, mock_client):
    """
    Test that labels containing inputs are correctly mapped.
  
    This tests implicit association by nesting (priority 2).
    """
    html = '<form><label>Username<input name="username" /></label></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields_mapping == {"Username": "username"}
  
  def test_i_can_map_label_and_input_as_siblings_with_for_id(self, mock_client):
    """
    Test that sibling labels and inputs with for/id are correctly mapped.
  
    This tests parent-level association with explicit for/id (priority 3).
    """
    html = '<form><div><label for="uid">Username</label><input id="uid" name="username" /></div></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields_mapping == {"Username": "username"}
  
  def test_i_can_map_label_and_input_as_siblings_by_proximity(self, mock_client):
    """
    Test that sibling labels and inputs are mapped by proximity.
  
    This tests association by proximity without for/id (priority 4).
    """
    html = '<form><div><label>Username</label><input name="username" /></div></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields_mapping == {"Username": "username"}
  
  def test_i_can_map_input_without_label_using_name(self, mock_client):
    """
    Test that inputs without labels use their name attribute as key.
  
    This tests the fallback mechanism (priority 5).
    """
    html = '<form><input name="csrf_token" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields_mapping == {"csrf_token": "csrf_token"}
  
  def test_i_can_map_input_without_name_using_id(self, mock_client):
    """
    Test that inputs without name attribute fallback to id attribute.
  
    This ensures inputs without name can still be identified.
    """
    html = '<form><input id="submit_btn" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields_mapping == {"submit_btn": "submit_btn"}
  
  def test_i_can_map_input_without_name_and_id_using_unnamed(self, mock_client):
    """
    Test that inputs without name or id get a generated unnamed key.
  
    This ensures all inputs are tracked even without identifiers.
    """
    html = '<form><input type="submit" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields_mapping == {"unnamed_0": "unnamed_0"}
  
  def test_i_can_handle_multiple_unnamed_inputs(self, mock_client):
    """
    Test that multiple unnamed inputs get incrementing counters.
  
    This ensures each unnamed input has a unique identifier.
    """
    html = '<form><input type="submit" /><input type="button" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields_mapping == {"unnamed_0": "unnamed_0", "unnamed_1": "unnamed_1"}
  
  def test_i_can_strip_whitespace_from_label_text(self, mock_client):
    """
    Test that whitespace and newlines are stripped from label text.
  
    This ensures clean, consistent label keys in the mapping.
    """
    html = '<form><label for="uid">  Username  \n</label><input id="uid" name="username" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields_mapping == {"Username": "username"}
  
  def test_i_can_extract_text_from_complex_labels(self, mock_client):
    """
    Test that text from nested elements in labels is extracted.
  
    This ensures labels with spans, emphasis, etc. are handled correctly.
    """
    html = '<form><label for="uid">Username <span class="required">*</span></label><input id="uid" name="username" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields_mapping == {"Username*": "username"}
  
  def test_i_can_handle_mixed_scenarios_in_same_form(self, mock_client):
    """
    Test that all association priorities work together in one form.
  
    This is a comprehensive test ensuring the priority system works correctly.
    """
    html = '''
      <form>
          <label for="email">Email</label>
          <input id="email" name="email" />
  
          <label>Password<input name="password" /></label>
  
          <div>
              <label for="phone">Phone</label>
              <input id="phone" name="phone" />
          </div>
  
          <div>
              <label>Address</label>
              <input name="address" />
          </div>
  
          <input name="csrf_token" />
          <input id="submit_btn" />
          <input type="hidden" />
      </form>
      '''
    form = TestableForm(mock_client, html)
    
    expected = {
        "Email": "email",
        "Password": "password",
        "Phone": "phone",
        "Address": "address",
        "csrf_token": "csrf_token",
        "submit_btn": "submit_btn",
        "unnamed_0": "unnamed_0"
    }
    
    assert form.fields_mapping == expected
  
  def test_i_can_handle_empty_form(self, mock_client):
    """
    Test that an empty form doesn't cause errors.
  
    This ensures robustness when dealing with minimal forms.
    """
    html = '<form></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields_mapping == {}
  
  def test_i_can_handle_form_with_only_labels(self, mock_client):
    """
    Test that labels without associated inputs don't cause errors.
  
    This ensures the code handles malformed or incomplete forms gracefully.
    """
    html = '<form><label>Test</label></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields_mapping == {}
  
  def test_i_can_handle_label_with_invalid_for_attribute(self, mock_client):
    """
    Test that labels with invalid 'for' attributes fallback correctly.
  
    This ensures the priority system cascades properly when higher
    priorities fail to find a match.
    """
    html = '<form><div><label for="nonexistent">Test</label><input name="field" /></div></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields_mapping == {"Test": "field"}


class TestableFormUpdateFieldValues:
  def test_i_can_handle_checkbox_checked(self, mock_client):
    """
    Test that a checked checkbox is converted to True.

    This ensures proper boolean handling for checked checkboxes.
    """
    html = '<form><input type="checkbox" name="agree" checked /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"agree": True}, \
      f"Expected {{'agree': True}}, got {form.fields}"
  
  def test_i_can_handle_checkbox_unchecked(self, mock_client):
    """
    Test that an unchecked checkbox is converted to False.
  
    This ensures proper boolean handling for unchecked checkboxes.
    """
    html = '<form><input type="checkbox" name="agree" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"agree": False}, \
      f"Expected {{'agree': False}}, got {form.fields}"
  
  def test_i_can_handle_radio_button_checked(self, mock_client):
    """
    Test that a checked radio button returns its value as string.
  
    This ensures radio buttons store their value attribute.
    """
    html = '<form><input type="radio" name="size" value="large" checked /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"size": "large"}, \
      f"Expected {{'size': 'large'}}, got {form.fields}"
  
  def test_i_can_handle_multiple_radio_buttons_with_one_checked(self, mock_client):
    """
    Test that only the checked radio button value is returned.
  
    This ensures correct handling of radio button groups.
    """
    html = '''
        <form>
            <input type="radio" name="size" value="small" />
            <input type="radio" name="size" value="medium" checked />
            <input type="radio" name="size" value="large" />
        </form>
        '''
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"size": "medium"}, \
      f"Expected {{'size': 'medium'}}, got {form.fields}"
  
  def test_i_can_handle_radio_buttons_with_none_checked(self, mock_client):
    """
    Test that no value is set when no radio button is checked.
  
    This ensures proper handling of unchecked radio button groups.
    """
    html = '''
        <form>
            <input type="radio" name="size" value="small" />
            <input type="radio" name="size" value="medium" />
            <input type="radio" name="size" value="large" />
        </form>
        '''
    form = TestableForm(mock_client, html)
    
    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):
    """
    Test that a number input with integer value becomes int.
  
    This ensures proper type conversion for integer numbers.
    """
    html = '<form><input type="number" name="age" value="25" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"age": 25}, \
      f"Expected {{'age': 25}}, got {form.fields}"
    assert isinstance(form.fields["age"], int), \
      f"Expected int type, got {type(form.fields['age'])}"
  
  def test_i_can_handle_number_input_with_float(self, mock_client):
    """
    Test that a number input with decimal value becomes float.
  
    This ensures proper type conversion for floating point numbers.
    """
    html = '<form><input type="number" name="price" value="19.99" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"price": 19.99}, \
      f"Expected {{'price': 19.99}}, got {form.fields}"
    assert isinstance(form.fields["price"], float), \
      f"Expected float type, got {type(form.fields['price'])}"
  
  def test_i_can_handle_text_input_with_string_value(self, mock_client):
    """
    Test that a text input with string value remains str.
  
    This ensures text values are not converted unnecessarily.
    """
    html = '<form><input type="text" name="username" value="john_doe" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"username": "john_doe"}, \
      f"Expected {{'username': 'john_doe'}}, got {form.fields}"
    assert isinstance(form.fields["username"], str), \
      f"Expected str type, got {type(form.fields['username'])}"
  
  def test_i_can_handle_text_input_with_integer_value(self, mock_client):
    """
    Test that a text input with numeric value is converted to int.
  
    This ensures automatic type detection for text inputs.
    """
    html = '<form><input type="text" name="code" value="123" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"code": 123}, \
      f"Expected {{'code': 123}}, got {form.fields}"
    assert isinstance(form.fields["code"], int), \
      f"Expected int type, got {type(form.fields['code'])}"
  
  def test_i_can_handle_text_input_with_float_value(self, mock_client):
    """
    Test that a text input with decimal value is converted to float.
  
    This ensures automatic float detection for text inputs.
    """
    html = '<form><input type="text" name="rate" value="3.14" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"rate": 3.14}, \
      f"Expected {{'rate': 3.14}}, got {form.fields}"
    assert isinstance(form.fields["rate"], float), \
      f"Expected float type, got {type(form.fields['rate'])}"
  
  def test_i_can_handle_text_input_with_boolean_true(self, mock_client):
    """
    Test that a text input with 'true' is converted to bool.
  
    This ensures boolean keyword detection for text inputs.
    """
    html = '<form><input type="text" name="flag" value="true" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"flag": True}, \
      f"Expected {{'flag': True}}, got {form.fields}"
    assert isinstance(form.fields["flag"], bool), \
      f"Expected bool type, got {type(form.fields['flag'])}"
  
  def test_i_can_handle_text_input_with_boolean_false(self, mock_client):
    """
    Test that a text input with 'false' is converted to bool.
  
    This ensures boolean keyword detection for false values.
    """
    html = '<form><input type="text" name="flag" value="false" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"flag": False}, \
      f"Expected {{'flag': False}}, got {form.fields}"
    assert isinstance(form.fields["flag"], bool), \
      f"Expected bool type, got {type(form.fields['flag'])}"
  
  def test_i_can_handle_hidden_input_with_auto_conversion(self, mock_client):
    """
    Test that hidden inputs benefit from automatic type conversion.
  
    This ensures hidden fields are processed like text fields.
    """
    html = '<form><input type="hidden" name="id" value="42" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"id": 42}, \
      f"Expected {{'id': 42}}, got {form.fields}"
    assert isinstance(form.fields["id"], int), \
      f"Expected int type, got {type(form.fields['id'])}"
  
  def test_i_can_handle_empty_input_value(self, mock_client):
    """
    Test that an empty input value remains an empty string.
  
    This ensures empty values are not converted to None or other types.
    """
    html = '<form><input type="text" name="optional" value="" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"optional": ""}, \
      f"Expected {{'optional': ''}}, got {form.fields}"
    assert isinstance(form.fields["optional"], str), \
      f"Expected str type, got {type(form.fields['optional'])}"
  
  def test_i_can_extract_select_options(self, mock_client):
    """
    Test that select options are correctly extracted.
  
    This ensures proper population of select_fields dictionary.
    """
    html = '''
        <form>
            <select name="country">
                <option value="FR">France</option>
                <option value="US">USA</option>
            </select>
        </form>
        '''
    form = TestableForm(mock_client, html)
    
    expected_options = [
        {"value": "FR", "text": "France"},
        {"value": "US", "text": "USA"}
    ]
    
    assert form.select_fields == {"country": expected_options}, \
      f"Expected {{'country': {expected_options}}}, got {form.select_fields}"
  
  def test_i_can_handle_select_with_selected_option(self, mock_client):
    """
    Test that the selected option is stored in fields.
  
    This ensures proper detection of selected options.
    """
    html = '''
        <form>
            <select name="country">
                <option value="FR">France</option>
                <option value="US" selected>USA</option>
            </select>
        </form>
        '''
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"country": "US"}, \
      f"Expected {{'country': 'US'}}, got {form.fields}"
  
  def test_i_can_handle_select_without_selected_option(self, mock_client):
    """
    Test that the first option is used by default.
  
    This ensures proper default value handling for select fields.
    """
    html = '''
        <form>
            <select name="country">
                <option value="FR">France</option>
                <option value="US">USA</option>
            </select>
        </form>
        '''
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"country": "FR"}, \
      f"Expected {{'country': 'FR'}}, got {form.fields}"
  
  def test_i_can_handle_select_option_without_value_attribute(self, mock_client):
    """
    Test that option text is used when value attribute is missing.
  
    This ensures fallback to text content for options without value.
    """
    html = '''
        <form>
            <select name="country">
                <option>France</option>
                <option>USA</option>
            </select>
        </form>
        '''
    form = TestableForm(mock_client, html)
    
    expected_options = [
        {"value": "France", "text": "France"},
        {"value": "USA", "text": "USA"}
    ]
    
    assert form.select_fields == {"country": expected_options}, \
      f"Expected {{'country': {expected_options}}}, got {form.select_fields}"
  
  def test_i_can_handle_mixed_input_types_in_same_form(self, mock_client):
    """
    Test that all input types work together correctly.
  
    This is a comprehensive test ensuring type handling is consistent.
    """
    html = '''
        <form>
            <input type="text" name="username" value="john" />
            <input type="number" name="age" value="30" />
            <input type="checkbox" name="subscribe" checked />
            <input type="radio" name="gender" value="male" checked />
            <input type="hidden" name="token" value="123" />
            <select name="country">
                <option value="FR" selected>France</option>
                <option value="US">USA</option>
            </select>
        </form>
        '''
    form = TestableForm(mock_client, html)
    
    expected_fields = {
        "username": "john",
        "age": 30,
        "subscribe": True,
        "gender": "male",
        "token": 123,
        "country": "FR"
    }
    
    assert form.fields == expected_fields, \
      f"Expected {expected_fields}, got {form.fields}"
    
    assert "country" in form.select_fields, \
      f"Expected 'country' in select_fields, got {form.select_fields}"
  
  def test_i_can_handle_input_without_name_attribute(self, mock_client):
    """
    Test that inputs without name attribute are ignored.
  
    This ensures proper handling of unnamed inputs.
    """
    html = '<form><input type="text" value="test" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields == {}, \
      f"Expected empty dict, got {form.fields}"
  
  def test_i_can_handle_select_without_name_attribute(self, mock_client):
    """
    Test that select elements without name attribute are ignored.
  
    This ensures proper handling of unnamed select fields.
    """
    html = '<form><select><option>Test</option></select></form>'
    form = TestableForm(mock_client, html)
    
    assert form.select_fields == {}, \
      f"Expected empty dict, got {form.select_fields}"
    assert form.fields == {}, \
      f"Expected empty dict, got {form.fields}"
  
  def test_i_can_handle_number_input_with_empty_value(self, mock_client):
    """
    Test that a number input with empty value remains empty string.
  
    This ensures empty values are not converted to 0 or None.
    """
    html = '<form><input type="number" name="count" value="" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"count": ""}, \
      f"Expected {{'count': ''}}, got {form.fields}"
  
  def test_i_can_handle_select_with_empty_options(self, mock_client):
    """
    Test behavior with a select element without options.
  
    This ensures robustness when dealing with empty selects.
    """
    html = '<form><select name="empty"></select></form>'
    form = TestableForm(mock_client, html)
    
    assert form.select_fields == {"empty": []}, \
      f"Expected {{'empty': []}}, got {form.select_fields}"
    assert "empty" not in form.fields, \
      f"Expected 'empty' not in fields, got {form.fields}"
  
  def test_i_can_handle_case_insensitive_boolean_values(self, mock_client):
    """
    Test that boolean values are case-insensitive.
  
    This ensures 'TRUE', 'True', 'FALSE', 'False' are all converted properly.
    """
    html = '<form><input type="text" name="flag" value="TRUE" /></form>'
    form = TestableForm(mock_client, html)
    
    assert form.fields == {"flag": True}, \
      f"Expected {{'flag': True}}, got {form.fields}"
    assert isinstance(form.fields["flag"], bool), \
      f"Expected bool type, got {type(form.fields['flag'])}"


class TestMyTestClientFill:
  def test_i_can_fill_form_using_input_name(self, mock_client):
    """
    I can fill using the input name
    """
    html = '<form><label for="uid">Username</label><input id="uid" name="username" /></form>'
    form = TestableForm(mock_client, html)
    
    form.fill(username="john_doe")
    
    assert form.fields == {"username": "john_doe"}
  
  def test_i_can_fill_form_using_label(self, mock_client):
    """
    I can fill using the label associated with the input
    """
    html = '<form><label for="uid">Username</label><input id="uid" name="username" /></form>'
    form = TestableForm(mock_client, html)
    
    form.fill(Username="john_doe")
    
    assert form.fields == {"username": "john_doe"}
  
  def test_i_cannot_fill_form_with_invalid_field_name(self, mock_client):
    """
    I cannot fill form with invalid field name
    """
    html = '<form><label for="uid">Username</label><input id="uid" name="username" /></form>'
    form = TestableForm(mock_client, html)
    
    with pytest.raises(ValueError) as excinfo:
      form.fill(invalid_field="john_doe")
    
    assert str(excinfo.value) == "Invalid field name 'invalid_field'."


class TestableFormSubmit:
  """
  Test suite for the submit() method of TestableForm class.
  This module tests form submission for both HTMX and classic forms.
  """
  
  def test_i_can_submit_classic_form_with_post_method(self):
    """
    Test that a classic form with POST method is submitted correctly.

    This ensures the form uses the action and method attributes properly.
    """
    # HTML form with classic submission
    html = '''
        <form action="/submit" method="post">
            <input type="text" name="username" value="john_doe" />
            <input type="password" name="password" value="secret123" />
        </form>
        '''
    
    # Create the form
    test_app, rt = fast_app(default_hdrs=False)
    client = MyTestClient(test_app)
    form = TestableForm(client, html)
    
    @rt('/submit')
    def post(username: str, password: str):
      return f"Form received {username=}, {password=}"
    
    form.submit()
    assert client.get_content() == "Form received username='john_doe', password='secret123'"
  
  def test_i_can_submit_classic_form_with_get_method(self):
    """
    Test that a classic form with GET method is submitted correctly.

    This ensures GET requests are properly handled.
    """
    html = '''
        <form action="/search" method="get">
            <input type="text" name="q" value="python" />
            <input type="text" name="page" value="1" />
        </form>
        '''
    
    test_app, rt = fast_app(default_hdrs=False)
    client = MyTestClient(test_app)
    form = TestableForm(client, html)
    
    @rt('/search')
    def get(q: str, page: int):
      return f"Search results for {q=}, {page=}"
    
    form.submit()
    assert client.get_content() == "Search results for q='python', page=1"
  
  def test_i_can_submit_classic_form_without_method_defaults_to_post(self):
    """
    Test that POST is used by default when method attribute is absent.

    This ensures proper default behavior matching HTML standards.
    """
    html = '''
        <form action="/submit">
            <input type="text" name="data" value="test" />
        </form>
        '''
    
    test_app, rt = fast_app(default_hdrs=False)
    client = MyTestClient(test_app)
    form = TestableForm(client, html)
    
    @rt('/submit')
    def post(data: str):
      return f"Received {data=}"
    
    form.submit()
    assert client.get_content() == "Received data='test'"
  
  def test_i_can_submit_form_with_htmx_post(self):
    """
    Test that a form with hx_post uses HTMX submission.

    This ensures HTMX-enabled forms are handled correctly.
    """
    html = '''
        <form hx-post="/htmx-submit">
            <input type="text" name="username" value="alice" />
        </form>
        '''
    
    test_app, rt = fast_app(default_hdrs=False)
    client = MyTestClient(test_app)
    form = TestableForm(client, html)
    
    @rt('/htmx-submit')
    def post(username: str):
      return f"HTMX received {username=}"
    
    form.submit()
    assert client.get_content() == "HTMX received username='alice'"
  
  def test_i_can_submit_form_with_htmx_get(self):
    """
    Test that a form with hx_get uses HTMX submission.

    This ensures HTMX GET requests work properly.
    """
    html = '''
        <form hx-get="/htmx-search">
            <input type="text" name="query" value="fasthtml" />
        </form>
        '''
    
    test_app, rt = fast_app(default_hdrs=False)
    client = MyTestClient(test_app)
    form = TestableForm(client, html)
    
    @rt('/htmx-search')
    def get(query: str):
      return f"HTMX search for {query=}"
    
    form.submit()
    assert client.get_content() == "HTMX search for query='fasthtml'"
  
  def test_i_can_submit_form_with_filled_fields(self):
    """
    Test that fields filled via fill_form() are submitted correctly.

    This ensures dynamic form filling works as expected.
    """
    html = '''
        <form action="/login" method="post">
            <input type="text" name="username" value="" />
            <input type="password" name="password" value="" />
        </form>
        '''
    
    test_app, rt = fast_app(default_hdrs=False)
    client = MyTestClient(test_app)
    form = TestableForm(client, html)
    
    # Fill the form dynamically
    form.fill(username="bob", password="secure456")
    
    @rt('/login')
    def post(username: str, password: str):
      return f"Login {username=}, {password=}"
    
    form.submit()
    assert client.get_content() == "Login username='bob', password='secure456'"
  
  def test_i_can_submit_form_with_mixed_field_types(self):
    """
    Test that all field types are submitted with correct values.

    This ensures type conversion and submission work together.
    """
    html = '''
        <form action="/register" method="post">
            <input type="text" name="username" value="charlie" />
            <input type="number" name="age" value="30" />
            <input type="checkbox" name="newsletter" checked />
            <select name="country">
                <option value="US" selected>USA</option>
                <option value="FR">France</option>
            </select>
        </form>
        '''
    
    test_app, rt = fast_app(default_hdrs=False)
    client = MyTestClient(test_app)
    form = TestableForm(client, html)
    
    @rt('/register')
    def post(username: str, age: int, newsletter: bool, country: str):
      return f"Registration {username=}, {age=}, {newsletter=}, {country=}"
    
    result = form.submit()
    
    # Note: the types are converted in self.fields
    expected = "Registration username='charlie', age=30, newsletter=True, country='US'"
    assert client.get_content() == expected
  
  def test_i_cannot_submit_classic_form_without_action(self):
    """
    Test that an exception is raised when action attribute is missing.

    This ensures proper error handling for malformed forms.
    """
    html = '''
        <form method="post">
            <input type="text" name="data" value="test" />
        </form>
        '''
    
    test_app, rt = fast_app(default_hdrs=False)
    client = MyTestClient(test_app)
    form = TestableForm(client, html)
    
    # Should raise ValueError
    try:
      form.submit()
      assert False, "Expected ValueError to be raised"
    except ValueError as e:
      assert "no 'action' attribute" in str(e).lower()
  
  def test_i_cannot_submit_classic_form_with_empty_action(self):
    """
    Test that an exception is raised when action attribute is empty.

    This ensures validation of the action attribute.
    """
    html = '''
        <form action="" method="post">
            <input type="text" name="data" value="test" />
        </form>
        '''
    
    test_app, rt = fast_app(default_hdrs=False)
    client = MyTestClient(test_app)
    form = TestableForm(client, html)
    
    # Should raise ValueError
    try:
      form.submit()
      assert False, "Expected ValueError to be raised"
    except ValueError as e:
      assert "no 'action' attribute" in str(e).lower()
  
  def test_i_can_submit_form_with_case_insensitive_method(self):
    """
    Test that HTTP method is properly normalized to uppercase.

    This ensures method attribute is case-insensitive.
    """
    html = '''
        <form action="/submit" method="PoSt">
            <input type="text" name="data" value="test" />
        </form>
        '''
    
    test_app, rt = fast_app(default_hdrs=False)
    client = MyTestClient(test_app)
    form = TestableForm(client, html)
    
    @rt('/submit')
    def post(data: str):
      return f"Received {data=}"
    
    form.submit()
    assert client.get_content() == "Received data='test'"
  
  def test_i_can_prioritize_htmx_over_classic_submission(self):
    """
    Test that HTMX is prioritized even when action/method are present.

    This ensures correct priority between HTMX and classic submission.
    """
    html = '''
        <form action="/classic" method="post" hx-post="/htmx">
            <input type="text" name="data" value="test" />
        </form>
        '''
    
    test_app, rt = fast_app(default_hdrs=False)
    client = MyTestClient(test_app)
    form = TestableForm(client, html)
    
    @rt('/classic')
    def post_classic(data: str):
      return "Classic submission"
    
    @rt('/htmx')
    def post_htmx(data: str):
      return "HTMX submission"
    
    form.submit()
    assert client.get_content() == "HTMX submission"
  
  def test_i_can_submit_empty_form(self):
    """
    Test that a form without fields can be submitted.

    This ensures robustness for minimal forms.
    """
    html = '<form action="/empty" method="post"></form>'
    
    test_app, rt = fast_app(default_hdrs=False)
    client = MyTestClient(test_app)
    form = TestableForm(client, html)
    
    @rt('/empty')
    def post():
      return "Empty form received"
    
    form.submit()
    assert client.get_content() == "Empty form received"
