Files
ai-agent/backend/test_forms.py
Gabriel Vidal 7cad02cd70 feat(forms): field groups + an automatic "Anything else?" catch-all
valid_fields keeps a trimmed `group` name on any field (the viewer's
page) and ends every spec with an optional `anything_else` textarea
flagged `catchAll` — reusing a trailing optional "anything else…"
textarea instead of doubling it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:48:16 +02:00

140 lines
6.1 KiB
Python

"""forms.valid_fields / check_answers — the display blocks (markdown, html)."""
import unittest
import forms
class HtmlBlockTest(unittest.TestCase):
def test_html_block_is_display_only(self):
out = forms.valid_fields([
{"type": "html", "label": "Chart", "html": "<svg><rect/></svg>"},
{"key": "ok", "label": "Fine?", "type": "checkbox"},
])
blk = out[0]
self.assertEqual(blk["type"], "html")
self.assertEqual(blk["key"], "chart")
self.assertEqual(blk["html"], "<svg><rect/></svg>")
self.assertFalse(blk["required"])
self.assertFalse(blk["sandbox"])
self.assertNotIn("text", blk)
# Never answered: ignored by answer validation and the recap.
self.assertEqual(forms.check_answers(out, {"chart": "x", "ok": True}), {"ok": True})
self.assertEqual(forms.answer_lines(out, {"ok": True}), ["- **Fine?** — yes"])
def test_script_implies_sandbox_and_height_is_kept(self):
out = forms.valid_fields([
{"type": "html", "html": "<div id=c></div><SCRIPT>draw()</SCRIPT>", "height": "320.0"},
])
self.assertTrue(out[0]["sandbox"])
self.assertEqual(out[0]["height"], 320)
self.assertEqual(out[0]["key"], "html-1")
self.assertEqual(out[0]["label"], "")
def test_explicit_sandbox_and_text_alias(self):
out = forms.valid_fields([{"type": "html", "text": "<b>hi</b>", "sandbox": True}])
self.assertEqual(out[0]["html"], "<b>hi</b>")
self.assertTrue(out[0]["sandbox"])
self.assertNotIn("height", out[0])
def test_style_sheet_implies_sandbox(self):
out = forms.valid_fields([{"type": "html", "html": "<style>b{color:red}</style><b>hi</b>"}])
self.assertTrue(out[0]["sandbox"])
out = forms.valid_fields([{"type": "html", "html": "<b style='color:red'>hi</b>"}])
self.assertFalse(out[0]["sandbox"])
def test_empty_or_oversized_html_rejected(self):
with self.assertRaises(ValueError):
forms.valid_fields([{"type": "html", "label": "x"}])
with self.assertRaises(ValueError):
forms.valid_fields([{"type": "html", "html": "<p>" + "a" * (600 * 1024)}])
def test_markdown_block_unchanged(self):
out = forms.valid_fields([{"type": "markdown", "text": "**hi**"}])
self.assertEqual(out[0]["text"], "**hi**")
self.assertNotIn("html", out[0])
if __name__ == "__main__":
unittest.main()
class OtherOptionTest(unittest.TestCase):
FIELDS = [
{"key": "stack", "label": "Stack", "type": "radio", "required": True,
"options": ["Vite", "Next"]},
{"key": "feat", "label": "Features", "type": "multiselect",
"options": ["auth", "db"]},
{"key": "pick", "label": "Pick", "type": "select", "options": ["a", "b"],
"other": True},
]
def test_other_defaults_to_required(self):
out = forms.valid_fields(self.FIELDS)
self.assertTrue(out[0]["other"]) # required ⇒ on
self.assertFalse(out[1]["other"]) # optional ⇒ off
self.assertTrue(out[2]["other"]) # explicit
off = forms.valid_fields([dict(self.FIELDS[0], other=False)])
self.assertFalse(off[0]["other"])
def test_free_text_accepted_only_with_other(self):
out = forms.valid_fields(self.FIELDS)
ans = forms.check_answers(out, {"stack": "Astro", "pick": "zzz", "feat": ["auth"]})
self.assertEqual(ans, {"stack": "Astro", "pick": "zzz", "feat": ["auth"]})
with self.assertRaises(ValueError):
forms.check_answers(out, {"stack": "Vite", "feat": ["nope"]})
def test_sentinel_counts_as_unanswered(self):
out = forms.valid_fields(self.FIELDS)
with self.assertRaises(ValueError):
forms.check_answers(out, {"stack": forms.OTHER})
ans = forms.check_answers(out, {"stack": "Vite", "pick": forms.OTHER,
"feat": [forms.OTHER, "db"]})
self.assertEqual(ans, {"stack": "Vite", "feat": ["db"]})
lines = forms.answer_lines(out, ans)
self.assertEqual(lines, ["- **Stack** — Vite", "- **Features** — db"])
class AssetsTest(unittest.TestCase):
def test_apply_assets_recurses_and_keeps_unknown(self):
spec = [{"type": "html", "html": '<img src="{{asset:shot.png}}"> {{ asset:nope }}',
"nested": {"x": ["{{asset:shot.png}}"]}}]
out = forms.apply_assets(spec, {"shot.png": "/api/upload-file?path=b/shot.png"})
self.assertEqual(out[0]["html"],
'<img src="/api/upload-file?path=b/shot.png"> {{ asset:nope }}')
self.assertEqual(out[0]["nested"]["x"], ["/api/upload-file?path=b/shot.png"])
class GroupAndCatchAllTest(unittest.TestCase):
def test_group_is_kept_trimmed_and_capped(self):
out = forms.valid_fields([
{"key": "a", "label": "A", "group": " Scope "},
{"type": "markdown", "text": "ctx", "group": "x" * 200},
{"key": "b", "label": "B"},
])
self.assertEqual(out[0]["group"], "Scope")
self.assertEqual(len(out[1]["group"]), 80)
self.assertNotIn("group", out[2])
def test_catch_all_is_appended_optional(self):
out = forms.valid_fields([{"key": "a", "label": "A", "required": True}])
last = out[-1]
self.assertEqual(last["key"], "anything_else")
self.assertEqual(last["type"], "textarea")
self.assertFalse(last["required"])
self.assertTrue(last["catchAll"])
# Left empty, it isn't in the answers at all.
self.assertEqual(forms.check_answers(out, {"a": "x"}), {"a": "x"})
def test_own_anything_else_field_is_reused(self):
out = forms.valid_fields([
{"key": "a", "label": "A"},
{"key": "notes", "label": "Anything else I should know?", "type": "textarea"},
])
self.assertEqual([f["key"] for f in out], ["a", "notes"])
self.assertTrue(out[-1]["catchAll"])
def test_catch_all_key_never_collides(self):
out = forms.valid_fields([{"key": "anything_else", "label": "Other stuff"}])
self.assertEqual([f["key"] for f in out], ["anything_else", "anything_else-2"])