Skip to content

Commit f1fea21

Browse files
Copilotcodingjoe
andcommitted
Compress parser code and add comprehensive unit tests
Co-authored-by: codingjoe <[email protected]>
1 parent a35eb3e commit f1fea21

File tree

2 files changed

+80
-42
lines changed

2 files changed

+80
-42
lines changed

s3file/forms.py

Lines changed: 17 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,21 @@
1919

2020

2121
class InputToS3FileRewriter(HTMLParser):
22-
"""
23-
HTML parser that rewrites <input type="file"> tags to <s3-file> custom elements.
24-
25-
This provides a robust way to transform Django's rendered file input widgets
26-
into custom elements, handling various attribute orderings and formats.
27-
"""
22+
"""HTML parser that rewrites <input type="file"> to <s3-file> custom elements."""
2823

2924
def __init__(self):
3025
super().__init__()
3126
self.output = []
3227

33-
def _is_file_input(self, attrs):
34-
"""Check if attributes indicate a file input element."""
35-
attrs_dict = dict(attrs)
36-
return attrs_dict.get("type") == "file"
37-
3828
def handle_starttag(self, tag, attrs):
39-
if tag == "input" and self._is_file_input(attrs):
40-
# Replace with s3-file custom element
41-
self._write_s3_file_tag(attrs)
42-
return
43-
44-
# For all other tags, preserve as-is
45-
self.output.append(self.get_starttag_text())
29+
if tag == "input" and dict(attrs).get("type") == "file":
30+
self.output.append("<s3-file")
31+
for name, value in attrs:
32+
if name != "type":
33+
self.output.append(f' {name}="{html.escape(value, quote=True)}"' if value else f" {name}")
34+
self.output.append(">")
35+
else:
36+
self.output.append(self.get_starttag_text())
4637

4738
def handle_endtag(self, tag):
4839
self.output.append(f"</{tag}>")
@@ -51,32 +42,16 @@ def handle_data(self, data):
5142
self.output.append(data)
5243

5344
def handle_startendtag(self, tag, attrs):
54-
# For self-closing tags
55-
if tag == "input" and self._is_file_input(attrs):
56-
# Replace with s3-file custom element
57-
self._write_s3_file_tag(attrs)
58-
return
59-
60-
self.output.append(self.get_starttag_text())
61-
62-
def _write_s3_file_tag(self, attrs):
63-
"""
64-
Write the s3-file opening tag with all attributes except type.
65-
66-
Note: This creates an opening tag that requires a corresponding closing tag.
67-
"""
68-
self.output.append("<s3-file")
69-
for name, value in attrs:
70-
if name != "type": # Skip type attribute
71-
if value is None:
72-
self.output.append(f" {name}")
73-
else:
74-
escaped_value = html.escape(value, quote=True)
75-
self.output.append(f' {name}="{escaped_value}"')
76-
self.output.append(">")
45+
if tag == "input" and dict(attrs).get("type") == "file":
46+
self.output.append("<s3-file")
47+
for name, value in attrs:
48+
if name != "type":
49+
self.output.append(f' {name}="{html.escape(value, quote=True)}"' if value else f" {name}")
50+
self.output.append(">")
51+
else:
52+
self.output.append(self.get_starttag_text())
7753

7854
def get_html(self):
79-
"""Return the transformed HTML."""
8055
return "".join(self.output)
8156

8257

tests/test_forms.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,54 @@ def test_str(self, settings):
7272
assert str(js) == '<script src="/static/path" type="module"></script>'
7373

7474

75+
class TestInputToS3FileRewriter:
76+
def test_transforms_file_input(self):
77+
parser = forms.InputToS3FileRewriter()
78+
parser.feed('<input type="file" name="test">')
79+
assert parser.get_html() == '<s3-file name="test">'
80+
81+
def test_preserves_non_file_input(self):
82+
parser = forms.InputToS3FileRewriter()
83+
parser.feed('<input type="text" name="test">')
84+
assert parser.get_html() == '<input type="text" name="test">'
85+
86+
def test_handles_attribute_ordering(self):
87+
parser = forms.InputToS3FileRewriter()
88+
parser.feed('<input name="test" type="file" class="foo">')
89+
result = parser.get_html()
90+
assert result.startswith('<s3-file')
91+
assert 'name="test"' in result
92+
assert 'class="foo"' in result
93+
assert 'type="file"' not in result
94+
95+
def test_handles_multiple_attributes(self):
96+
parser = forms.InputToS3FileRewriter()
97+
parser.feed('<input type="file" name="test" accept="image/*" required multiple>')
98+
result = parser.get_html()
99+
assert result.startswith('<s3-file')
100+
assert 'name="test"' in result
101+
assert 'accept="image/*"' in result
102+
assert 'required' in result
103+
assert 'multiple' in result
104+
105+
def test_escapes_html_entities(self):
106+
parser = forms.InputToS3FileRewriter()
107+
parser.feed('<input type="file" name="test" data-value="test&value">')
108+
result = parser.get_html()
109+
assert 'data-value="test&amp;value"' in result
110+
111+
def test_handles_self_closing_tag(self):
112+
parser = forms.InputToS3FileRewriter()
113+
parser.feed('<input type="file" name="test" />')
114+
assert parser.get_html() == '<s3-file name="test">'
115+
116+
def test_preserves_surrounding_elements(self):
117+
parser = forms.InputToS3FileRewriter()
118+
parser.feed('<p><input type="file" name="test"></p>')
119+
result = parser.get_html()
120+
assert result == '<p><s3-file name="test"></p>'
121+
122+
75123
@contextmanager
76124
def wait_for_page_load(driver, timeout=30):
77125
old_page = driver.find_element(By.TAG_NAME, "html")
@@ -186,6 +234,21 @@ def test_render_wraps_in_s3_file_element(self, freeze_upload_folder):
186234
# Check that the output is the s3-file custom element
187235
assert html.startswith("<s3-file")
188236

237+
def test_render_preserves_attributes(self, freeze_upload_folder):
238+
widget = ClearableFileInput(attrs={"class": "test-class", "accept": "image/*"})
239+
html = widget.render(name="file", value=None)
240+
assert html.startswith("<s3-file")
241+
assert 'name="file"' in html
242+
assert 'class="test-class"' in html
243+
assert 'accept="image/*"' in html
244+
assert 'type="file"' not in html
245+
246+
def test_render_excludes_type_attribute(self, freeze_upload_folder):
247+
widget = ClearableFileInput()
248+
html = widget.render(name="file", value=None)
249+
assert 'type="file"' not in html
250+
assert html.startswith("<s3-file")
251+
189252
@pytest.mark.selenium
190253
def test_no_js_error(self, driver, live_server):
191254
driver.get(live_server + self.create_url)

0 commit comments

Comments
 (0)